diff --git a/.eslintrc-common.yaml b/.eslintrc-common.yaml index e502508ff9..d0fe9bed36 100644 --- a/.eslintrc-common.yaml +++ b/.eslintrc-common.yaml @@ -28,6 +28,7 @@ # ``` # Note that it should be "workingDirectories" rather than "WorkingDirectories". +root: true rules: # Check the rules in: node_modules/@typescript-eslint/eslint-plugin/README.md no-console: @@ -80,7 +81,6 @@ rules: no-octal: 2 no-octal-escape: 2 no-proto: 2 - no-redeclare: 2 no-self-compare: 2 no-unneeded-ternary: 2 no-with: 2 @@ -215,4 +215,5 @@ rules: - 1 - vars: "local" - args: "none" \ No newline at end of file + args: "none" + "@typescript-eslint/no-redeclare": 2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index d05d719df6..5787067b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -178,6 +178,10 @@ todo *.sublime-workspace *.sublime-project +# Result of node.js perf +*.asm + +# Distribution files /esm /echarts.all.js /echarts.simple.js @@ -191,7 +195,12 @@ todo /index.blank.js /extension-esm /extension +/core.js +/core.d.ts +/charts.js +/charts.d.ts +/components.js +/components.d.ts +/renderers.js +/renderers.d.ts *.tgz - -# Result of node.js perf -*.asm \ No newline at end of file diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 8fa122aea3..0000000000 --- a/.jshintrc +++ /dev/null @@ -1,70 +0,0 @@ -{ - "bitwise": false, - "camelcase": true, - "curly": true, - "eqeqeq": false, - "forin": false, - "immed": true, - "latedef": false, - "newcap": true, - "noarg": false, - "noempty": true, - "nonew": true, - "plusplus": false, - "quotmark": "single", - "regexp": false, - "undef": true, - "unused": "vars", - "strict": false, - "trailing": false, - "maxparams": 20, - "maxdepth": 6, - "maxlen": 200, - - "asi": false, - "boss": false, - "debug": false, - "eqnull": true, - "esversion": 6, - "module": true, - "evil": true, - "expr": true, - "funcscope": false, - "globalstrict": false, - "iterator": false, - "lastsemic": false, - "laxbreak": true, - "laxcomma": false, - "loopfunc": false, - "multistr": false, - "onecase": false, - "proto": false, - "regexdash": false, - "scripturl": false, - "smarttabs": false, - "shadow": true, - "sub": true, - "supernew": false, - "validthis": true, - - "browser": true, - "couch": false, - "devel": true, - "dojo": false, - "jquery": true, - "mootools": false, - "node": false, - "nonstandard": false, - "prototypejs": false, - "rhino": false, - "wsh": false, - - "nomen": false, - "onevar": false, - "passfail": false, - "white": false, - - "predef": [ - "global" - ] -} \ No newline at end of file diff --git a/build/build-i18n.js b/build/build-i18n.js index 32a408d130..37fa534f87 100644 --- a/build/build-i18n.js +++ b/build/build-i18n.js @@ -60,7 +60,7 @@ ${preamble.js} typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/build/config.js b/build/config.js index c1c31b78e0..b19ecbc8e9 100644 --- a/build/config.js +++ b/build/config.js @@ -18,7 +18,7 @@ */ const assert = require('assert'); -const nodeResolvePlugin = require('rollup-plugin-node-resolve'); +const nodeResolvePlugin = require('@rollup/plugin-node-resolve').default; const nodePath = require('path'); const ecDir = nodePath.resolve(__dirname, '..'); const typescriptPlugin = require('rollup-plugin-typescript2'); @@ -117,7 +117,9 @@ exports.createECharts = function (opt = {}) { plugins: preparePlugins(opt, { include }), - + treeshake: { + moduleSideEffects: false + }, // external: ['zrender'], // external: id => ['zrender'].includes(id), diff --git a/build/dev-fast.js b/build/dev-fast.js index 84a61329ac..f934fa662b 100644 --- a/build/dev-fast.js +++ b/build/dev-fast.js @@ -68,7 +68,7 @@ async function wrapUMDCode() { function rebuild() { build({ - stdio: 'inherit', + // stdio: 'inherit', entryPoints: [path.resolve(__dirname, '../src/echarts.all.ts')], outfile: outFilePath, format: 'cjs', diff --git a/build/pre-publish.js b/build/pre-publish.js index ec60b31269..8f5845983b 100644 --- a/build/pre-publish.js +++ b/build/pre-publish.js @@ -37,8 +37,7 @@ const ts = require('typescript'); const globby = require('globby'); const transformDEVUtil = require('./transform-dev'); const preamble = require('./preamble'); -// NOTE: v1.4.2 is the latest version that supports ts 3.8.3 -const dts = require('rollup-plugin-dts').default; +const dts = require('@lang/rollup-plugin-dts').default; const rollup = require('rollup'); const ecDir = nodePath.resolve(__dirname, '..'); @@ -67,10 +66,10 @@ const extensionSrcGlobby = { cwd: ecDir }; const extensionSrcDir = nodePath.resolve(ecDir, 'extension-src'); -const extensionCJSDir = nodePath.resolve(ecDir, 'extension'); -const extensionESMDir = nodePath.resolve(ecDir, 'extension-esm'); +const extensionESMDir = nodePath.resolve(ecDir, 'extension'); const typesDir = nodePath.resolve(ecDir, 'types'); +const esmDir = 'lib'; const compileWorkList = [ @@ -92,49 +91,8 @@ const compileWorkList = [ }, before: async function () { fsExtra.removeSync(tmpDir); - fsExtra.removeSync(nodePath.resolve(ecDir, 'esm')); - fsExtra.removeSync(nodePath.resolve(ecDir, 'echarts.all.js')); - fsExtra.removeSync(nodePath.resolve(ecDir, 'echarts.blank.js')); - fsExtra.removeSync(nodePath.resolve(ecDir, 'echarts.common.js')); - fsExtra.removeSync(nodePath.resolve(ecDir, 'echarts.simple.js')); - }, - after: async function () { - fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.all.js'), nodePath.resolve(ecDir, 'echarts.all.js')); - fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.blank.js'), nodePath.resolve(ecDir, 'echarts.blank.js')); - fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.common.js'), nodePath.resolve(ecDir, 'echarts.common.js')); - fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.simple.js'), nodePath.resolve(ecDir, 'echarts.simple.js')); - fs.renameSync(nodePath.resolve(tmpDir, 'src'), nodePath.resolve(ecDir, 'esm')); - - transformRootFolderInEntry(nodePath.resolve(ecDir, 'echarts.all.js'), 'esm'); - transformRootFolderInEntry(nodePath.resolve(ecDir, 'echarts.blank.js'), 'esm'); - transformRootFolderInEntry(nodePath.resolve(ecDir, 'echarts.common.js'), 'esm'); - transformRootFolderInEntry(nodePath.resolve(ecDir, 'echarts.simple.js'), 'esm'); - - await transformDistributionFiles(nodePath.resolve(ecDir, 'esm'), 'esm'); - await transformDistributionFiles(nodePath.resolve(ecDir, 'types'), 'esm'); - fsExtra.removeSync(tmpDir); - - await bundleDTS(); - } - }, - { - logLabel: 'main ts -> js-cjs', - compilerOptionsOverride: { - module: 'CommonJS', - // `rootDir` Only use to control the output - // directory structure with --outDir. - rootDir: ecDir, - outDir: tmpDir - }, - srcGlobby: mainSrcGlobby, - transformOptions: { - filesGlobby: {patterns: ['**/*.js'], cwd: tmpDir}, - preamble: preamble.js, - transformDEV: true - }, - before: async function () { - fsExtra.removeSync(tmpDir); - fsExtra.removeSync(nodePath.resolve(ecDir, 'lib')); + fsExtra.removeSync(nodePath.resolve(ecDir, 'types')); + fsExtra.removeSync(nodePath.resolve(ecDir, esmDir)); fsExtra.removeSync(nodePath.resolve(ecDir, 'index.js')); fsExtra.removeSync(nodePath.resolve(ecDir, 'index.blank.js')); fsExtra.removeSync(nodePath.resolve(ecDir, 'index.common.js')); @@ -145,39 +103,18 @@ const compileWorkList = [ fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.blank.js'), nodePath.resolve(ecDir, 'index.blank.js')); fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.common.js'), nodePath.resolve(ecDir, 'index.common.js')); fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.simple.js'), nodePath.resolve(ecDir, 'index.simple.js')); - fs.renameSync(nodePath.resolve(tmpDir, 'src'), nodePath.resolve(ecDir, 'lib')); - - transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.js'), 'lib'); - transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.blank.js'), 'lib'); - transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.common.js'), 'lib'); - transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.simple.js'), 'lib'); + fs.renameSync(nodePath.resolve(tmpDir, 'src'), nodePath.resolve(ecDir, esmDir)); - await transformDistributionFiles(nodePath.resolve(ecDir, 'lib'), 'lib'); - removeESmoduleMark(); + transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.js'), esmDir); + transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.blank.js'), esmDir); + transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.common.js'), esmDir); + transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.simple.js'), esmDir); + await transformDistributionFiles(nodePath.resolve(ecDir, esmDir), esmDir); + await transformDistributionFiles(nodePath.resolve(ecDir, 'types'), esmDir); fsExtra.removeSync(tmpDir); } }, - { - logLabel: 'extension ts -> js-cjs', - compilerOptionsOverride: { - module: 'CommonJS', - rootDir: extensionSrcDir, - outDir: extensionCJSDir - }, - srcGlobby: extensionSrcGlobby, - transformOptions: { - filesGlobby: {patterns: ['**/*.js'], cwd: extensionCJSDir}, - preamble: preamble.js, - transformDEV: true - }, - before: async function () { - fsExtra.removeSync(extensionCJSDir); - }, - after: async function () { - await transformDistributionFiles(extensionCJSDir, 'lib'); - } - }, { logLabel: 'extension ts -> js-esm', compilerOptionsOverride: { @@ -195,7 +132,7 @@ const compileWorkList = [ fsExtra.removeSync(extensionESMDir); }, after: async function () { - await transformDistributionFiles(extensionESMDir, 'esm'); + await transformDistributionFiles(extensionESMDir, 'lib'); } } ]; @@ -231,6 +168,11 @@ module.exports = async function () { process.stdout.write(chalk.green.dim(` done \n`)); } + process.stdout.write(chalk.green.dim(`Generating entries ...`)); + generateEntries(); + process.stdout.write(chalk.green.dim(`Bundling DTS ...`)); + await bundleDTS(); + console.log(chalk.green.dim('All done.')); }; @@ -395,8 +337,9 @@ async function readFilePaths({patterns, cwd}) { } async function bundleDTS() { - const bundle = await rollup.rollup({ - input: nodePath.resolve(__dirname, '../index.d.ts'), + + const outDir = nodePath.resolve(__dirname, '../types/dist'); + const commonConfig = { onwarn(warning, rollupWarn) { // Not warn circular dependency if (warning.code !== 'CIRCULAR_DEPENDENCY') { @@ -407,18 +350,50 @@ async function bundleDTS() { dts({ respectExternal: true }) +// { +// generateBundle(options, bundle) { +// for (let chunk of Object.values(bundle)) { +// chunk.code = ` +// type Omit = Pick>; +// ${chunk.code}` +// } +// } +// } ] + }; + + // Bundle chunks. + const parts = [ + 'core', 'charts', 'components', 'renderers', 'option' + ]; + const inputs = {}; + parts.forEach(partName => { + inputs[partName] = nodePath.resolve(__dirname, `../types/src/export/${partName}.d.ts`) + }); + + const bundle = await rollup.rollup({ + input: inputs, + ...commonConfig }); - const bundleFile = nodePath.resolve(__dirname, '../types/dist/echarts.d.ts'); + let idx = 1; await bundle.write({ - file: bundleFile + dir: outDir, + minifyInternalExports: false, + manualChunks: (id) => { + // Only create one chunk. + return 'shared'; + }, + chunkFileNames: 'shared.d.ts' + }); + + // Bundle all in one + const bundleAllInOne = await rollup.rollup({ + input: nodePath.resolve(__dirname, `../types/src/export/all.d.ts`), + ...commonConfig + }); + await bundleAllInOne.write({ + file: nodePath.resolve(outDir, 'echarts.d.ts') }); - // To support ts 3.4 - const extra = ` -type Omit = Pick>; -` - const code = extra + fs.readFileSync(bundleFile, 'utf-8'); - fs.writeFileSync(bundleFile, code, 'utf-8'); } function readTSConfig() { @@ -428,4 +403,18 @@ function readTSConfig() { const tsConfigText = fs.readFileSync(filePath, {encoding: 'utf8'}); return (new Function(`return ( ${tsConfigText} )`))(); } + + +function generateEntries() { + ['charts', 'components', 'renderers', 'core'].forEach(entryName => { + if (entryName !== 'option') { + const jsCode = fs.readFileSync(nodePath.join(__dirname, `template/${entryName}.js`), 'utf-8'); + fs.writeFileSync(nodePath.join(__dirname, `../${entryName}.js`), jsCode, 'utf-8'); + } + + const dtsCode = fs.readFileSync(nodePath.join(__dirname, `/template/${entryName}.d.ts`), 'utf-8'); + fs.writeFileSync(nodePath.join(__dirname, `../${entryName}.d.ts`), dtsCode, 'utf-8'); + }); +} + module.exports.readTSConfig = readTSConfig; diff --git a/src/component/angleAxis.ts b/build/template/charts.d.ts similarity index 92% rename from src/component/angleAxis.ts rename to build/template/charts.d.ts index 2bda940114..5c9edfcd9b 100644 --- a/src/component/angleAxis.ts +++ b/build/template/charts.d.ts @@ -17,5 +17,4 @@ * under the License. */ -import '../coord/polar/polarCreator'; -import './axis/AngleAxisView'; \ No newline at end of file +export * from './types/dist/charts'; \ No newline at end of file diff --git a/src/chart/helper/focusNodeAdjacencyAction.ts b/build/template/charts.js similarity index 69% rename from src/chart/helper/focusNodeAdjacencyAction.ts rename to build/template/charts.js index d160943b2e..f3d519ba67 100644 --- a/src/chart/helper/focusNodeAdjacencyAction.ts +++ b/build/template/charts.js @@ -17,16 +17,11 @@ * under the License. */ -import * as echarts from '../../echarts'; -echarts.registerAction({ - type: 'focusNodeAdjacency', - event: 'focusNodeAdjacency', - update: 'series:focusNodeAdjacency' -}, function () {}); +// In somehow. If we export like +// export * as LineChart './chart/line/install' +// The exported code will be transformed to +// import * as LineChart_1 './chart/line/install'; export {LineChart_1 as LineChart}; +// Treeshaking in webpack will not work even if we configured sideEffects to false in package.json -echarts.registerAction({ - type: 'unfocusNodeAdjacency', - event: 'unfocusNodeAdjacency', - update: 'series:unfocusNodeAdjacency' -}, function () {}); +export * from './lib/export/charts'; \ No newline at end of file diff --git a/src/component/radiusAxis.ts b/build/template/components.d.ts similarity index 91% rename from src/component/radiusAxis.ts rename to build/template/components.d.ts index 067c335e57..5bb38bef14 100644 --- a/src/component/radiusAxis.ts +++ b/build/template/components.d.ts @@ -17,5 +17,4 @@ * under the License. */ -import '../coord/polar/polarCreator'; -import './axis/RadiusAxisView'; \ No newline at end of file +export * from './types/dist/components'; \ No newline at end of file diff --git a/src/component/axis.ts b/build/template/components.js similarity index 91% rename from src/component/axis.ts rename to build/template/components.js index 7b05c47926..74e95af781 100644 --- a/src/component/axis.ts +++ b/build/template/components.js @@ -17,5 +17,4 @@ * under the License. */ -import '../coord/cartesian/AxisModel'; -import './axis/CartesianAxisView'; \ No newline at end of file +export * from './lib/export/components'; \ No newline at end of file diff --git a/build/template/core.d.ts b/build/template/core.d.ts new file mode 100644 index 0000000000..c3f461a0eb --- /dev/null +++ b/build/template/core.d.ts @@ -0,0 +1,20 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export * from './types/dist/core'; \ No newline at end of file diff --git a/build/template/core.js b/build/template/core.js new file mode 100644 index 0000000000..203185ec00 --- /dev/null +++ b/build/template/core.js @@ -0,0 +1,20 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export * from './lib/export/core'; \ No newline at end of file diff --git a/build/template/option.d.ts b/build/template/option.d.ts new file mode 100644 index 0000000000..7ebeaaefb4 --- /dev/null +++ b/build/template/option.d.ts @@ -0,0 +1,20 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export * from './types/dist/option'; \ No newline at end of file diff --git a/build/template/renderers.d.ts b/build/template/renderers.d.ts new file mode 100644 index 0000000000..2fb46e91d3 --- /dev/null +++ b/build/template/renderers.d.ts @@ -0,0 +1,20 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export * from './types/dist/renderers'; \ No newline at end of file diff --git a/build/template/renderers.js b/build/template/renderers.js new file mode 100644 index 0000000000..39f7004c55 --- /dev/null +++ b/build/template/renderers.js @@ -0,0 +1,20 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export * from './lib/export/renderers'; \ No newline at end of file diff --git a/build/testDts.js b/build/testDts.js index fdf1d1fb3c..813fd0837a 100644 --- a/build/testDts.js +++ b/build/testDts.js @@ -24,11 +24,11 @@ const { installAllTypeScriptVersions, typeScriptPath } = require('@definitelytyped/utils'); -const { runTsCompile, readTSConfig } = require('./pre-publish'); +const { runTsCompile } = require('./pre-publish'); const globby = require('globby'); const semver = require('semver'); -const MIN_VERSION = '3.4.0'; +const MIN_VERSION = '3.5.0'; async function installTs() { // await cleanTypeScriptInstalls(); @@ -36,9 +36,7 @@ async function installTs() { } async function runTests() { - const tsConfig = readTSConfig(); const compilerOptions = { - ...tsConfig.compilerOptions, declaration: false, importHelpers: false, sourceMap: false, @@ -47,7 +45,10 @@ async function runTests() { allowJs: false, outDir: __dirname + '/../test/types/tmp', typeRoots: [__dirname + '/../types/dist'], - rootDir: __dirname + '/../test/types' + rootDir: __dirname + '/../test/types', + + // Must pass in most strict cases + strict: true }; const testsList = await globby(__dirname + '/../test/types/*.ts'); diff --git a/i18n/langDE.js b/i18n/langDE.js index 7e61b21495..0f87876aca 100644 --- a/i18n/langDE.js +++ b/i18n/langDE.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/i18n/langEN.js b/i18n/langEN.js index 37e347263a..5c75c49597 100644 --- a/i18n/langEN.js +++ b/i18n/langEN.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/i18n/langES.js b/i18n/langES.js index db20ab4456..99b0247701 100644 --- a/i18n/langES.js +++ b/i18n/langES.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/i18n/langFI.js b/i18n/langFI.js index 5a2ba9024c..fe59e0da05 100644 --- a/i18n/langFI.js +++ b/i18n/langFI.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/i18n/langFR.js b/i18n/langFR.js index ffeb03d324..0153c50e17 100644 --- a/i18n/langFR.js +++ b/i18n/langFR.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/i18n/langJA.js b/i18n/langJA.js index 612ce7a013..1b31961aa7 100644 --- a/i18n/langJA.js +++ b/i18n/langJA.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/i18n/langTH.js b/i18n/langTH.js index 5a2f3f2857..1ebda1d99a 100644 --- a/i18n/langTH.js +++ b/i18n/langTH.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/i18n/langZH.js b/i18n/langZH.js index 022d01fa35..52ae06c8bf 100644 --- a/i18n/langZH.js +++ b/i18n/langZH.js @@ -32,7 +32,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/index.d.ts b/index.d.ts index 4546cf1cf4..20195af345 100644 --- a/index.d.ts +++ b/index.d.ts @@ -17,16 +17,4 @@ * under the License. */ -/////////////////////////////////////////////////////////////////////// -/// NOTE: types folder is generated by `npm run prepublish` command /// -/// Make sure run it before edit this file. /// -/////////////////////////////////////////////////////////////////////// - - -// Restrict exports -export { - init, connect, disConnect, dispose, getInstanceByDom, getInstanceById, - registerMap, registerLocale, getMap, registerTheme -} from './types/src/echarts.all'; - -export {EChartsFullOption as EChartsOption} from './types/src/option'; \ No newline at end of file +export * from './types/dist/echarts'; \ No newline at end of file diff --git a/package.json b/package.json index b5f6130ac8..69a1fb5d5f 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,11 @@ "canvas", "svg" ], - "main": "index.js", - "module": "echarts.all.js", + "main": "dist/echarts.js", + "module": "index.js", "jsdelivr": "dist/echarts.min.js", - "types": "types/dist/echarts.d.ts", - "homepage": "https://echarts.apache.org", + "types": "index.d.ts", + "homepage": "http://echarts.apache.org", "bugs": { "url": "https://github.com/apache/incubator-echarts/issues", "email": "dev@echarts.apache.org" @@ -27,6 +27,17 @@ "type": "git", "url": "git+https://github.com/apache/incubator-echarts.git" }, + "sideEffects": [ + "index.js", + "index.blank.js", + "index.common.js", + "index.simple.js", + "lib/echarts.js", + "lib/chart/*.js", + "lib/component/*.js", + "theme/*.js", + "i18n/*.js" + ], "scripts": { "prepublish": "node build/build.js --prepublish", "build": "node build/build.js", @@ -38,8 +49,8 @@ "help": "node build/build.js --help", "test:visual": "node test/runTest/server.js", "test:visual:report": "node test/runTest/genReport.js", - "test": "jest --config test/ut/jest.config.js", - "test:single": "jest --config test/ut/jest.config.js --coverage=false -t", + "test": "npx jest --config test/ut/jest.config.js", + "test:single": "npx jest --config test/ut/jest.config.js --coverage=false -t", "test:dts": "node build/testDts.js", "mktest": "node test/build/mktest.js", "mktest:help": "node test/build/mktest.js -h", @@ -48,23 +59,29 @@ "lint:dist": "echo 'It might take a while. Please wait ...' && ./node_modules/.bin/jshint --config .jshintrc-dist dist/echarts.js" }, "dependencies": { - "tslib": "1.10.0", + "tslib": "2.0.3", "zrender": "5.0.1" }, "devDependencies": { + "@babel/code-frame": "7.10.4", "@babel/core": "7.3.4", - "@babel/types": "^7.10.5", + "@babel/types": "7.10.5", + "@definitelytyped/typescript-versions": "0.0.64", + "@definitelytyped/utils": "0.0.64", + "@lang/rollup-plugin-dts": "2.0.2", "@microsoft/api-extractor": "7.7.2", + "@rollup/plugin-commonjs": "^17.0.0", + "@rollup/plugin-node-resolve": "^11.0.0", "@types/jest": "^26.0.14", - "@typescript-eslint/eslint-plugin": "^2.15.0", - "@typescript-eslint/parser": "^2.18.0", + "@typescript-eslint/eslint-plugin": "^4.9.1", + "@typescript-eslint/parser": "^4.9.1", "canvas": "^2.6.0", "chalk": "^3.0.0", "chokidar": "^3.4.0", "commander": "2.11.0", "dtslint": "^4.0.5", - "esbuild": "^0.4.1", - "eslint": "6.3.0", + "esbuild": "^0.8.23", + "eslint": "^7.15.0", "fs-extra": "0.26.7", "glob": "7.0.0", "globby": "11.0.0", @@ -76,10 +93,7 @@ "open": "6.4.0", "pixelmatch": "5.0.2", "pngjs": "3.4.0", - "rollup": "1.28.0", - "rollup-plugin-commonjs": "8.4.1", - "rollup-plugin-dts": "1.4.2", - "rollup-plugin-node-resolve": "3.0.0", + "rollup": "2.34.2", "rollup-plugin-typescript2": "0.25.3", "seedrandom": "3.0.3", "semver": "6.3.0", @@ -88,7 +102,7 @@ "socket.io": "2.2.0", "terser": "^5.3.8", "ts-jest": "^26.4.3", - "typescript": "3.8.3", + "typescript": "^4.1.2", "uglify-js": "^3.10.0" } } diff --git a/src/action/geoRoam.ts b/src/action/geoRoam.ts deleted file mode 100644 index 2a452e8ab8..0000000000 --- a/src/action/geoRoam.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -// @ts-nocheck - -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; -import {updateCenterAndZoom} from './roamHelper'; - - -/** - * @payload - * @property {string} [componentType=series] - * @property {number} [dx] - * @property {number} [dy] - * @property {number} [zoom] - * @property {number} [originX] - * @property {number} [originY] - */ -echarts.registerAction({ - type: 'geoRoam', - event: 'geoRoam', - update: 'updateTransform' -}, function (payload, ecModel) { - const componentType = payload.componentType || 'series'; - - ecModel.eachComponent( - { mainType: componentType, query: payload }, - function (componentModel) { - const geo = componentModel.coordinateSystem; - if (geo.type !== 'geo') { - return; - } - - const res = updateCenterAndZoom( - geo, payload, componentModel.get('scaleLimit') - ); - - componentModel.setCenter - && componentModel.setCenter(res.center); - - componentModel.setZoom - && componentModel.setZoom(res.zoom); - - // All map series with same `map` use the same geo coordinate system - // So the center and zoom must be in sync. Include the series not selected by legend - if (componentType === 'series') { - zrUtil.each(componentModel.seriesGroup, function (seriesModel) { - seriesModel.setCenter(res.center); - seriesModel.setZoom(res.zoom); - }); - } - } - ); -}); \ No newline at end of file diff --git a/src/chart/bar.ts b/src/chart/bar.ts index 27ecad4a1a..a0e8e83b3d 100644 --- a/src/chart/bar.ts +++ b/src/chart/bar.ts @@ -17,34 +17,8 @@ * under the License. */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; -import {layout, largeLayout} from '../layout/barGrid'; +import { use } from '../extension'; +import { install } from './bar/install'; -import '../coord/cartesian/Grid'; -import './bar/BarSeries'; -import './bar/BarView'; -import '../action/changeAxisOrder'; -// In case developer forget to include grid component -import '../component/gridSimple'; -import dataSample from '../processor/dataSample'; +use(install); - -echarts.registerLayout(echarts.PRIORITY.VISUAL.LAYOUT, zrUtil.curry(layout, 'bar')); -// Use higher prority to avoid to be blocked by other overall layout, which do not -// only exist in this module, but probably also exist in other modules, like `barPolar`. -echarts.registerLayout(echarts.PRIORITY.VISUAL.PROGRESSIVE_LAYOUT, largeLayout); - -echarts.registerVisual({ - seriesType: 'bar', - reset: function (seriesModel) { - // Visual coding for legend - seriesModel.getData().setVisual('legendSymbol', 'roundRect'); - } -}); - -// Down sample after filter -echarts.registerProcessor( - echarts.PRIORITY.PROCESSOR.STATISTIC, - dataSample('bar') -); diff --git a/src/chart/bar/BarSeries.ts b/src/chart/bar/BarSeries.ts index e6717ca2e1..0cb539920f 100644 --- a/src/chart/bar/BarSeries.ts +++ b/src/chart/bar/BarSeries.ts @@ -18,7 +18,6 @@ */ import BaseBarSeriesModel, {BaseBarSeriesOption} from './BaseBarSeries'; -import SeriesModel from '../../model/Series'; import { ItemStyleOption, OptionDataValue, @@ -143,6 +142,4 @@ class BarSeriesModel extends BaseBarSeriesModel { } -SeriesModel.registerClass(BarSeriesModel); - export default BarSeriesModel; \ No newline at end of file diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index aca98c5644..4c7cb7354d 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -36,7 +36,7 @@ import Sausage from '../../util/shape/sausage'; import ChartView from '../../view/Chart'; import List, {DefaultDataVisual} from '../../data/List'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { StageHandlerProgressParams, ZRElementEvent, @@ -1103,6 +1103,4 @@ function createBackgroundEl( }); } -ChartView.registerClass(BarView); - export default BarView; diff --git a/src/chart/bar/PictorialBarSeries.ts b/src/chart/bar/PictorialBarSeries.ts index e651be31a7..17191b639c 100644 --- a/src/chart/bar/PictorialBarSeries.ts +++ b/src/chart/bar/PictorialBarSeries.ts @@ -18,7 +18,6 @@ */ import BaseBarSeriesModel, { BaseBarSeriesOption } from './BaseBarSeries'; -import SeriesModel from '../../model/Series'; import { OptionDataValue, ItemStyleOption, @@ -27,7 +26,7 @@ import { SeriesStackOptionMixin, StatesOptionMixin, OptionDataItemObject, - DefaultExtraEmpasisState + DefaultEmphasisFocus } from '../../util/types'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import { inheritDefaultOption } from '../../util/component'; @@ -93,7 +92,7 @@ interface PictorialBarSeriesSymbolOption { interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] + focus?: DefaultEmphasisFocus scale?: boolean } } @@ -176,6 +175,4 @@ class PictorialBarSeriesModel extends BaseBarSeriesModel { interface BoxplotSeriesModel extends WhiskerBoxCommonMixin { getBaseAxis(): Axis2D } -zrUtil.mixin(BoxplotSeriesModel, WhiskerBoxCommonMixin, true); - -SeriesModel.registerClass(BoxplotSeriesModel); +mixin(BoxplotSeriesModel, WhiskerBoxCommonMixin, true); export default BoxplotSeriesModel; diff --git a/src/chart/boxplot/BoxplotView.ts b/src/chart/boxplot/BoxplotView.ts index f08982c0b1..4321d6aecd 100644 --- a/src/chart/boxplot/BoxplotView.ts +++ b/src/chart/boxplot/BoxplotView.ts @@ -24,7 +24,7 @@ import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states import Path, { PathProps } from 'zrender/src/graphic/Path'; import BoxplotSeriesModel, { BoxplotDataItemOption } from './BoxplotSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import List from '../../data/List'; import { BoxplotItemLayout } from './boxplotLayout'; @@ -196,6 +196,4 @@ function transInit(points: number[][], dim: number, itemLayout: BoxplotItemLayou }); } -ChartView.registerClass(BoxplotView); - export default BoxplotView; diff --git a/src/chart/boxplot/boxplotVisual.ts b/src/chart/boxplot/boxplotVisual.ts index ae89251c03..d8ff69d279 100644 --- a/src/chart/boxplot/boxplotVisual.ts +++ b/src/chart/boxplot/boxplotVisual.ts @@ -18,7 +18,7 @@ */ import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import BoxplotSeriesModel from './BoxplotSeries'; export default function boxplotVisual(ecModel: GlobalModel, api: ExtensionAPI) { diff --git a/src/chart/boxplot/install.ts b/src/chart/boxplot/install.ts new file mode 100644 index 0000000000..e579f296f2 --- /dev/null +++ b/src/chart/boxplot/install.ts @@ -0,0 +1,33 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import BoxplotSeriesModel from './BoxplotSeries'; +import BoxplotView from './BoxplotView'; +import boxplotVisual from './boxplotVisual'; +import boxplotLayout from './boxplotLayout'; +import { boxplotTransform } from './boxplotTransform'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerSeriesModel(BoxplotSeriesModel); + registers.registerChartView(BoxplotView); + registers.registerVisual(boxplotVisual); + registers.registerLayout(boxplotLayout); + registers.registerTransform(boxplotTransform); +} \ No newline at end of file diff --git a/src/chart/candlestick.ts b/src/chart/candlestick.ts index ee19a96422..8820487fd8 100644 --- a/src/chart/candlestick.ts +++ b/src/chart/candlestick.ts @@ -17,15 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './candlestick/install'; -import './candlestick/CandlestickSeries'; -import './candlestick/CandlestickView'; -import preprocessor from './candlestick/preprocessor'; - -import candlestickVisual from './candlestick/candlestickVisual'; -import candlestickLayout from './candlestick/candlestickLayout'; - -echarts.registerPreprocessor(preprocessor); -echarts.registerVisual(candlestickVisual); -echarts.registerLayout(candlestickLayout); +use(install); \ No newline at end of file diff --git a/src/chart/candlestick/CandlestickSeries.ts b/src/chart/candlestick/CandlestickSeries.ts index 6a58ff2ac8..68e8b8a128 100644 --- a/src/chart/candlestick/CandlestickSeries.ts +++ b/src/chart/candlestick/CandlestickSeries.ts @@ -17,7 +17,6 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; import SeriesModel from '../../model/Series'; import {WhiskerBoxCommonMixin} from '../helper/whiskerBoxCommon'; import { @@ -31,12 +30,13 @@ import { SeriesLargeOptionMixin, OptionDataValueNumeric, StatesOptionMixin, - DefaultExtraEmpasisState, - SeriesEncodeOptionMixin + SeriesEncodeOptionMixin, + DefaultEmphasisFocus } from '../../util/types'; import List from '../../data/List'; import Cartesian2D from '../../coord/cartesian/Cartesian2D'; import { BrushCommonSelectorsForSeries } from '../../component/brush/selector'; +import { mixin } from 'zrender/src/core/util'; type CandlestickDataValue = OptionDataValueNumeric[]; @@ -55,7 +55,7 @@ export interface CandlestickDataItemOption interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] + focus?: DefaultEmphasisFocus scale?: boolean } } @@ -157,8 +157,6 @@ class CandlestickSeriesModel extends SeriesModel { } } -zrUtil.mixin(CandlestickSeriesModel, WhiskerBoxCommonMixin, true); - -SeriesModel.registerClass(CandlestickSeriesModel); +mixin(CandlestickSeriesModel, WhiskerBoxCommonMixin, true); export default CandlestickSeriesModel; diff --git a/src/chart/candlestick/CandlestickView.ts b/src/chart/candlestick/CandlestickView.ts index 595d1ee098..bc3cd981b5 100644 --- a/src/chart/candlestick/CandlestickView.ts +++ b/src/chart/candlestick/CandlestickView.ts @@ -25,7 +25,7 @@ import Path, { PathProps } from 'zrender/src/graphic/Path'; import {createClipPath} from '../helper/createClipPathFromCoordSys'; import CandlestickSeriesModel, { CandlestickDataItemOption } from './CandlestickSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { StageHandlerProgressParams } from '../../util/types'; import List from '../../data/List'; import {CandlestickItemLayout} from './candlestickLayout'; @@ -199,8 +199,6 @@ class CandlestickView extends ChartView { } } -ChartView.registerClass(CandlestickView); - class NormalBoxPathShape { points: number[][]; } diff --git a/src/chart/candlestick/install.ts b/src/chart/candlestick/install.ts new file mode 100644 index 0000000000..9296ebc9a4 --- /dev/null +++ b/src/chart/candlestick/install.ts @@ -0,0 +1,34 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import CandlestickView from './CandlestickView'; +import CandlestickSeriesModel from './CandlestickSeries'; +import preprocessor from './preprocessor'; + +import candlestickVisual from './candlestickVisual'; +import candlestickLayout from './candlestickLayout'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(CandlestickView); + registers.registerSeriesModel(CandlestickSeriesModel); + registers.registerPreprocessor(preprocessor); + registers.registerVisual(candlestickVisual); + registers.registerLayout(candlestickLayout); +} \ No newline at end of file diff --git a/src/chart/custom.ts b/src/chart/custom.ts index a3664523f7..2e77d6857f 100644 --- a/src/chart/custom.ts +++ b/src/chart/custom.ts @@ -17,2767 +17,8 @@ * under the License. */ -import { - hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, - keys, isArrayLike, bind, isFunction, eqNaN, indexOf, clone -} from 'zrender/src/core/util'; -import * as graphicUtil from '../util/graphic'; -import { setDefaultStateProxy, enableHoverEmphasis } from '../util/states'; -import * as labelStyleHelper from '../label/labelStyle'; -import {getDefaultLabel} from './helper/labelHelper'; -import createListFromArray from './helper/createListFromArray'; -import {getLayoutOnAxis, BarGridLayoutResult, BarGridLayoutOptionForCustomSeries} from '../layout/barGrid'; -import DataDiffer, { DataDiffMode } from '../data/DataDiffer'; -import SeriesModel from '../model/Series'; -import Model from '../model/Model'; -import ChartView from '../view/Chart'; -import {createClipPath} from './helper/createClipPathFromCoordSys'; -import { - EventQueryItem, ECEvent, SeriesOption, SeriesOnCartesianOptionMixin, - SeriesOnPolarOptionMixin, SeriesOnSingleOptionMixin, SeriesOnGeoOptionMixin, - SeriesOnCalendarOptionMixin, ItemStyleOption, SeriesEncodeOptionMixin, - DimensionLoose, - ParsedValue, - Dictionary, - CallbackDataParams, - Payload, - StageHandlerProgressParams, - LabelOption, - ViewRootGroup, - OptionDataValue, - ZRStyleProps, - DisplayState, - ECElement, - DisplayStateNonNormal, - BlurScope, - SeriesDataType, - OrdinalRawValue, - PayloadAnimationPart, - DecalObject, - InnerDecalObject, - TextCommonOption -} from '../util/types'; -import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element'; -import prepareCartesian2d from '../coord/cartesian/prepareCustom'; -import prepareGeo from '../coord/geo/prepareCustom'; -import prepareSingleAxis from '../coord/single/prepareCustom'; -import preparePolar from '../coord/polar/prepareCustom'; -import prepareCalendar from '../coord/calendar/prepareCustom'; -import ComponentModel from '../model/Component'; -import List, { DefaultDataVisual } from '../data/List'; -import GlobalModel from '../model/Global'; -import { makeInner, normalizeToArray } from '../util/model'; -import ExtensionAPI from '../ExtensionAPI'; -import Displayable from 'zrender/src/graphic/Displayable'; -import Axis2D from '../coord/cartesian/Axis2D'; -import { RectLike } from 'zrender/src/core/BoundingRect'; -import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path'; -import { ImageStyleProps } from 'zrender/src/graphic/Image'; -import { CoordinateSystem } from '../coord/CoordinateSystem'; -import { TextStyleProps } from 'zrender/src/graphic/Text'; -import { - convertToEC4StyleForCustomSerise, - isEC4CompatibleStyle, - convertFromEC4CompatibleStyle, - LegacyStyleProps, - warnDeprecated -} from '../util/styleCompat'; -import Transformable from 'zrender/src/core/Transformable'; -import { ItemStyleProps } from '../model/mixin/itemStyle'; -import { cloneValue } from 'zrender/src/animation/Animator'; -import { warn, throwError } from '../util/log'; -import { - combine, isInAnyMorphing, morphPath, isCombiningPath, CombineSeparateConfig, separate, CombineSeparateResult -} from 'zrender/src/tool/morphPath'; -import { AnimationEasing } from 'zrender/src/animation/easing'; -import * as matrix from 'zrender/src/core/matrix'; -import { PatternObject } from 'zrender/src/graphic/Pattern'; -import { createOrUpdatePatternFromDecal } from '../util/decal'; -import { ZRenderType } from 'zrender/src/zrender'; +import { use } from '../extension'; +import { install } from './custom/install'; -const inner = makeInner<{ - info: CustomExtraElementInfo; - customPathData: string; - customGraphicType: string; - customImagePath: CustomImageOption['style']['image']; - // customText: string; - txConZ2Set: number; - leaveToProps: ElementProps; - // Can morph: "morph" specified in option and el is Path. - canMorph: boolean; - userDuring: CustomBaseElementOption['during']; -}, Element>(); - -type CustomExtraElementInfo = Dictionary; -const TRANSFORM_PROPS = { - x: 1, - y: 1, - scaleX: 1, - scaleY: 1, - originX: 1, - originY: 1, - rotation: 1 -} as const; -type TransformProp = keyof typeof TRANSFORM_PROPS; -const transformPropNamesStr = keys(TRANSFORM_PROPS).join(', '); - -// Do not declare "Dictionary" in TransitionAnyOption to restrict the type check. -type TransitionAnyOption = { - transition?: TransitionAnyProps; - enterFrom?: Dictionary; - leaveTo?: Dictionary; -}; -type TransitionAnyProps = string | string[]; -type TransitionTransformOption = { - transition?: ElementRootTransitionProp | ElementRootTransitionProp[]; - enterFrom?: Dictionary; - leaveTo?: Dictionary; -}; -type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style'; -type ShapeMorphingOption = { - /** - * If do shape morphing animation when type is changed. - * Only available on path. - */ - morph?: boolean -}; - -interface CustomBaseElementOption extends Partial>, TransitionTransformOption { - // element type, mandatory. - type: string; - id?: string; - // For animation diff. - name?: string; - info?: CustomExtraElementInfo; - // `false` means remove the textContent. - textContent?: CustomTextOption | false; - // `false` means remove the clipPath - clipPath?: CustomZRPathOption | false; - // `extra` can be set in any el option for custom prop for annimation duration. - extra?: TransitionAnyOption; - // updateDuringAnimation - during?(params: typeof customDuringAPI): void; - - focus?: 'none' | 'self' | 'series' | ArrayLike - blurScope?: BlurScope -}; -interface CustomDisplayableOption extends CustomBaseElementOption, Partial> { - style?: ZRStyleProps & TransitionAnyOption; - // `false` means remove emphasis trigger. - styleEmphasis?: ZRStyleProps | false; - emphasis?: CustomDisplayableOptionOnState; - blur?: CustomDisplayableOptionOnState; - select?: CustomDisplayableOptionOnState; -} -interface CustomDisplayableOptionOnState extends Partial> { - // `false` means remove emphasis trigger. - style?: (ZRStyleProps & TransitionAnyOption) | false; -} -interface CustomGroupOption extends CustomBaseElementOption { - type: 'group'; - width?: number; - height?: number; - // @deprecated - diffChildrenByName?: boolean; - // Can only set focus, blur on the root element. - children: Omit[]; - $mergeChildren: false | 'byName' | 'byIndex'; -} -interface CustomZRPathOption extends CustomDisplayableOption, ShapeMorphingOption { - shape?: PathProps['shape'] & TransitionAnyOption; - style?: CustomDisplayableOption['style'] & { - decal?: DecalObject; - // Only internal usage. Any user specified value will be overwritten. - __decalPattern?: PatternObject; - }; -} -interface CustomSVGPathOption extends CustomDisplayableOption, ShapeMorphingOption { - type: 'path'; - shape?: { - // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z' - pathData?: string; - // "d" is the alias of `pathData` follows the SVG convention. - d?: string; - layout?: 'center' | 'cover'; - x?: number; - y?: number; - width?: number; - height?: number; - } & TransitionAnyOption; -} -interface CustomImageOption extends CustomDisplayableOption { - type: 'image'; - style?: ImageStyleProps & TransitionAnyOption; - emphasis?: CustomImageOptionOnState; - blur?: CustomImageOptionOnState; - select?: CustomImageOptionOnState; -} -interface CustomImageOptionOnState extends CustomDisplayableOptionOnState { - style?: ImageStyleProps & TransitionAnyOption; -} -interface CustomTextOption extends CustomDisplayableOption { - type: 'text'; -} -type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption; -type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState; - - -export interface CustomSeriesRenderItemAPI extends - CustomSeriesRenderItemCoordinateSystemAPI { - - // Methods from ExtensionAPI. - // NOTE: Not using Pick here because we don't want to bundle ExtensionAPI into the d.ts - getWidth(): number - getHeight(): number - getZr(): ZRenderType - getDevicePixelRatio(): number - - value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue; - ordinalRawValue(dim: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue; - style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps; - styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps; - visual( - visualType: VT, - dataIndexInside?: number - ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT] - : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]] - : void; - barLayout(opt: BarGridLayoutOptionForCustomSeries): BarGridLayoutResult; - currentSeriesIndices(): number[]; - font(opt: Pick): string; -} -interface CustomSeriesRenderItemParamsCoordSys { - type: string; - // And extra params for each coordinate systems. -} -interface CustomSeriesRenderItemCoordinateSystemAPI { - coord( - data: OptionDataValue | OptionDataValue[], - clamp?: boolean - ): number[]; - size?( - dataSize: OptionDataValue | OptionDataValue[], - dataItem: OptionDataValue | OptionDataValue[] - ): number | number[]; -} -export interface CustomSeriesRenderItemParams { - context: Dictionary; - seriesId: string; - seriesName: string; - seriesIndex: number; - coordSys: CustomSeriesRenderItemParamsCoordSys; - dataInsideLength: number; - encode: WrapEncodeDefRet; -} -type CustomSeriesRenderItem = ( - params: CustomSeriesRenderItemParams, - api: CustomSeriesRenderItemAPI -) => CustomElementOption; - -interface CustomSeriesStateOption { - itemStyle?: ItemStyleOption; - label?: LabelOption; -} - -export interface CustomSeriesOption extends - SeriesOption, // don't support StateOption in custom series. - SeriesEncodeOptionMixin, - SeriesOnCartesianOptionMixin, - SeriesOnPolarOptionMixin, - SeriesOnSingleOptionMixin, - SeriesOnGeoOptionMixin, - SeriesOnCalendarOptionMixin { - - type?: 'custom' - - // If set as 'none', do not depends on coord sys. - coordinateSystem?: string | 'none'; - - renderItem?: CustomSeriesRenderItem; - - // Only works on polar and cartesian2d coordinate system. - clip?: boolean; -} - -interface LegacyCustomSeriesOption extends SeriesOption, CustomSeriesStateOption {} - - -interface LooseElementProps extends ElementProps { - style?: ZRStyleProps; - shape?: Dictionary; -} - -// Also compat with ec4, where -// `visual('color') visual('borderColor')` is supported. -const STYLE_VISUAL_TYPE = { - color: 'fill', - borderColor: 'stroke' -} as const; -type StyleVisualProps = keyof typeof STYLE_VISUAL_TYPE; - -const NON_STYLE_VISUAL_PROPS = { - symbol: 1, - symbolSize: 1, - symbolKeepAspect: 1, - legendSymbol: 1, - visualMeta: 1, - liftZ: 1, - decal: 1 -} as const; -type NonStyleVisualProps = keyof typeof NON_STYLE_VISUAL_PROPS; - -const EMPHASIS = 'emphasis' as const; -const NORMAL = 'normal' as const; -const BLUR = 'blur' as const; -const SELECT = 'select' as const; -const STATES = [NORMAL, EMPHASIS, BLUR, SELECT] as const; -const PATH_ITEM_STYLE = { - normal: ['itemStyle'], - emphasis: [EMPHASIS, 'itemStyle'], - blur: [BLUR, 'itemStyle'], - select: [SELECT, 'itemStyle'] -} as const; -const PATH_LABEL = { - normal: ['label'], - emphasis: [EMPHASIS, 'label'], - blur: [BLUR, 'label'], - select: [SELECT, 'label'] -} as const; -// Use prefix to avoid index to be the same as el.name, -// which will cause weird update animation. -const GROUP_DIFF_PREFIX = 'e\0\0'; - -type AttachedTxInfo = { - isLegacy: boolean; - normal: { - cfg: ElementTextConfig; - conOpt: CustomElementOption | false; - }; - emphasis: { - cfg: ElementTextConfig; - conOpt: CustomElementOptionOnState; - }; - blur: { - cfg: ElementTextConfig; - conOpt: CustomElementOptionOnState; - }; - select: { - cfg: ElementTextConfig; - conOpt: CustomElementOptionOnState; - }; -}; -const attachedTxInfoTmp = { - normal: {}, - emphasis: {}, - blur: {}, - select: {} -} as AttachedTxInfo; - -const LEGACY_TRANSFORM_PROPS = { - position: ['x', 'y'], - scale: ['scaleX', 'scaleY'], - origin: ['originX', 'originY'] -} as const; -type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS; - -export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { - coordSys: CustomSeriesRenderItemParamsCoordSys; - api: CustomSeriesRenderItemCoordinateSystemAPI -}; - -const tmpTransformable = new Transformable(); - -/** - * To reduce total package size of each coordinate systems, the modules `prepareCustom` - * of each coordinate systems are not required by each coordinate systems directly, but - * required by the module `custom`. - * - * prepareInfoForCustomSeries {Function}: optional - * @return {Object} {coordSys: {...}, api: { - * coord: function (data, clamp) {}, // return point in global. - * size: function (dataSize, dataItem) {} // return size of each axis in coordSys. - * }} - */ -const prepareCustoms: Dictionary = { - cartesian2d: prepareCartesian2d, - geo: prepareGeo, - singleAxis: prepareSingleAxis, - polar: preparePolar, - calendar: prepareCalendar -}; - -class CustomSeriesModel extends SeriesModel { - - static type = 'series.custom'; - readonly type = CustomSeriesModel.type; - - static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; - - // preventAutoZ = true; - - currentZLevel: number; - currentZ: number; - - static defaultOption: CustomSeriesOption = { - coordinateSystem: 'cartesian2d', // Can be set as 'none' - zlevel: 0, - z: 2, - legendHoverLink: true, - - // Custom series will not clip by default. - // Some case will use custom series to draw label - // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight - clip: false - - // Cartesian coordinate system - // xAxisIndex: 0, - // yAxisIndex: 0, - - // Polar coordinate system - // polarIndex: 0, - - // Geo coordinate system - // geoIndex: 0, - }; - - optionUpdated() { - this.currentZLevel = this.get('zlevel', true); - this.currentZ = this.get('z', true); - } - - getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List { - return createListFromArray(this.getSource(), this); - } - - getDataParams(dataIndex: number, dataType?: SeriesDataType, el?: Element): CallbackDataParams & { - info: CustomExtraElementInfo - } { - const params = super.getDataParams(dataIndex, dataType) as ReturnType; - el && (params.info = inner(el).info); - return params; - } -} - -ComponentModel.registerClass(CustomSeriesModel); - - - -class CustomSeriesView extends ChartView { - - static type = 'custom'; - readonly type = CustomSeriesView.type; - - private _data: List; - - - render( - customSeries: CustomSeriesModel, - ecModel: GlobalModel, - api: ExtensionAPI, - payload: Payload - ): void { - const oldData = this._data; - const data = customSeries.getData(); - const group = this.group; - const renderItem = makeRenderItem(customSeries, data, ecModel, api); - - // By default, merge mode is applied. In most cases, custom series is - // used in the scenario that data amount is not large but graphic elements - // is complicated, where merge mode is probably necessary for optimization. - // For example, reuse graphic elements and only update the transform when - // roam or data zoom according to `actionType`. - - const transOpt = customSeries.__transientTransitionOpt; - - // Enable user to disable transition animation by both set - // `from` and `to` dimension as `null`/`undefined`. - if (transOpt && (transOpt.from == null || transOpt.to == null)) { - oldData && oldData.each(function (oldIdx) { - doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group); - }); - data.each(function (newIdx) { - createOrUpdateItem( - api, null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null - ); - }); - } - else { - const morphPreparation = new MorphPreparation(customSeries, transOpt); - const diffMode: DataDiffMode = transOpt ? 'multiple' : 'oneToOne'; - - (new DataDiffer( - oldData ? oldData.getIndices() : [], - data.getIndices(), - createGetKey(oldData, diffMode, transOpt && transOpt.from), - createGetKey(data, diffMode, transOpt && transOpt.to), - null, - diffMode - )) - .add(function (newIdx) { - createOrUpdateItem( - api, null, newIdx, renderItem(newIdx, payload), customSeries, group, - data, null - ); - }) - .remove(function (oldIdx) { - doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group); - }) - .update(function (newIdx, oldIdx) { - morphPreparation.reset('oneToOne'); - let oldEl = oldData.getItemGraphicEl(oldIdx); - morphPreparation.findAndAddFrom(oldEl); - - // PENDING: - // if may morph, currently we alway recreate the whole el. - // because if reuse some of the el in the group tree, the old el has to - // be removed from the group, and consequently we can not calculate - // the "global transition" of the old element. - // But is there performance issue? - if (morphPreparation.hasFrom()) { - removeElementDirectly(oldEl, group); - oldEl = null; - } - createOrUpdateItem( - api, oldEl, newIdx, renderItem(newIdx, payload), customSeries, group, - data, morphPreparation - ); - morphPreparation.applyMorphing(); - }) - .updateManyToOne(function (newIdx, oldIndices) { - morphPreparation.reset('manyToOne'); - for (let i = 0; i < oldIndices.length; i++) { - const oldEl = oldData.getItemGraphicEl(oldIndices[i]); - morphPreparation.findAndAddFrom(oldEl); - removeElementDirectly(oldEl, group); - } - createOrUpdateItem( - api, null, newIdx, renderItem(newIdx, payload), customSeries, group, - data, morphPreparation - ); - morphPreparation.applyMorphing(); - }) - .updateOneToMany(function (newIndices, oldIdx) { - morphPreparation.reset('oneToMany'); - const newLen = newIndices.length; - const oldEl = oldData.getItemGraphicEl(oldIdx); - morphPreparation.findAndAddFrom(oldEl); - removeElementDirectly(oldEl, group); - - for (let i = 0; i < newLen; i++) { - createOrUpdateItem( - api, null, newIndices[i], renderItem(newIndices[i], payload), customSeries, group, - data, morphPreparation - ); - } - morphPreparation.applyMorphing(); - }) - .execute(); - } - - // Do clipping - const clipPath = customSeries.get('clip', true) - ? createClipPath(customSeries.coordinateSystem, false, customSeries) - : null; - if (clipPath) { - group.setClipPath(clipPath); - } - else { - group.removeClipPath(); - } - - this._data = data; - } - - incrementalPrepareRender( - customSeries: CustomSeriesModel, - ecModel: GlobalModel, - api: ExtensionAPI - ): void { - this.group.removeAll(); - this._data = null; - } - - incrementalRender( - params: StageHandlerProgressParams, - customSeries: CustomSeriesModel, - ecModel: GlobalModel, - api: ExtensionAPI, - payload: Payload - ): void { - const data = customSeries.getData(); - const renderItem = makeRenderItem(customSeries, data, ecModel, api); - function setIncrementalAndHoverLayer(el: Displayable) { - if (!el.isGroup) { - el.incremental = true; - el.ensureState('emphasis').hoverLayer = true; - } - } - for (let idx = params.start; idx < params.end; idx++) { - const el = createOrUpdateItem( - null, null, idx, renderItem(idx, payload), customSeries, this.group, data, null - ); - el.traverse(setIncrementalAndHoverLayer); - } - } - - filterForExposedEvent( - eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECEvent - ): boolean { - const elementName = query.element; - if (elementName == null || targetEl.name === elementName) { - return true; - } - - // Enable to give a name on a group made by `renderItem`, and listen - // events that triggerd by its descendents. - while ((targetEl = (targetEl.__hostTarget || targetEl.parent)) && targetEl !== this.group) { - if (targetEl.name === elementName) { - return true; - } - } - - return false; - } -} - -ChartView.registerClass(CustomSeriesView); - - -function createGetKey( - data: List, - diffMode: DataDiffMode, - dimension: DimensionLoose -) { - if (!data) { - return; - } - - if (diffMode === 'oneToOne') { - return function (rawIdx: number, dataIndex: number) { - return data.getId(dataIndex); - }; - } - - const diffByDimName = data.getDimension(dimension); - const dimInfo = data.getDimensionInfo(diffByDimName); - - if (!dimInfo) { - let errMsg = ''; - if (__DEV__) { - errMsg = `${dimension} is not a valid dimension.`; - } - throwError(errMsg); - } - const ordinalMeta = dimInfo.ordinalMeta; - return function (rawIdx: number, dataIndex: number) { - let key = data.get(diffByDimName, dataIndex); - if (ordinalMeta) { - key = ordinalMeta.categories[key as number]; - } - return (key == null || eqNaN(key)) - ? rawIdx + '' - : '_ec_' + key; - }; -} - - -function createEl(elOption: CustomElementOption): Element { - const graphicType = elOption.type; - let el; - - // Those graphic elements are not shapes. They should not be - // overwritten by users, so do them first. - if (graphicType === 'path') { - const shape = (elOption as CustomSVGPathOption).shape; - // Using pathRect brings convenience to users sacle svg path. - const pathRect = (shape.width != null && shape.height != null) - ? { - x: shape.x || 0, - y: shape.y || 0, - width: shape.width, - height: shape.height - } as RectLike - : null; - const pathData = getPathData(shape); - // Path is also used for icon, so layout 'center' by default. - el = graphicUtil.makePath(pathData, null, pathRect, shape.layout || 'center'); - inner(el).customPathData = pathData; - } - else if (graphicType === 'image') { - el = new graphicUtil.Image({}); - inner(el).customImagePath = (elOption as CustomImageOption).style.image; - } - else if (graphicType === 'text') { - el = new graphicUtil.Text({}); - // inner(el).customText = (elOption.style as TextStyleProps).text; - } - else if (graphicType === 'group') { - el = new graphicUtil.Group(); - } - else if (graphicType === 'compoundPath') { - throw new Error('"compoundPath" is not supported yet.'); - } - else { - const Clz = graphicUtil.getShapeClass(graphicType); - if (!Clz) { - let errMsg = ''; - if (__DEV__) { - errMsg = 'graphic type "' + graphicType + '" can not be found.'; - } - throwError(errMsg); - } - el = new Clz(); - } - - inner(el).customGraphicType = graphicType; - el.name = elOption.name; - - // Compat ec4: the default z2 lift is 1. If changing the number, - // some cases probably be broken: hierarchy layout along z, like circle packing, - // where emphasis only intending to modify color/border rather than lift z2. - (el as ECElement).z2EmphasisLift = 1; - (el as ECElement).z2SelectLift = 1; - - return el; -} - -/** - * ---------------------------------------------------------- - * [STRATEGY_MERGE] Merge properties or erase all properties: - * - * Based on the fact that the existing zr element probably be reused, we now consider whether - * merge or erase all properties to the exsiting elements. - * That is, if a certain props is not specified in the lastest return of `renderItem`: - * + "Merge" means that do not modify the value on the existing element. - * + "Erase all" means that use a default value to the existing element. - * - * "Merge" might bring some unexpected state retaining for users and "erase all" seams to be - * more safe. "erase all" force users to specify all of the props each time, which is recommanded - * in most cases. - * But "erase all" theoretically disables the chance of performance optimization (e.g., just - * generete shape and style at the first time rather than always do that). - * So we still use "merge" rather than "erase all". If users need "erase all", they can - * simple always set all of the props each time. - * Some "object-like" config like `textConfig`, `textContent`, `style` which are not needed for - * every elment, so we replace them only when user specify them. And the that is a total replace. - * - * TODO: there is no hint of 'isFirst' to users. So the performance enhancement can not be - * performed yet. Consider the case: - * (1) setOption to "mergeChildren" with a smaller children count - * (2) Use dataZoom to make an item disappear. - * (3) User dataZoom to make the item display again. At that time, renderItem need to return the - * full option rather than partial option to recreate the element. - * - * ---------------------------------------------- - * [STRATEGY_NULL] `hasOwnProperty` or `== null`: - * - * Ditinguishing "own property" probably bring little trouble to user when make el options. - * So we trade a {xx: null} or {xx: undefined} as "not specified" if possible rather than - * "set them to null/undefined". In most cases, props can not be cleared. Some typicall - * clearable props like `style`/`textConfig`/`textContent` we enable `false` to means - * "clear". In some othere special cases that the prop is able to set as null/undefined, - * but not suitable to use `false`, `hasOwnProperty` is checked. - * - * --------------------------------------------- - * [STRATEGY_TRANSITION] The rule of transition: - * + For props on the root level of a element: - * If there is no `transition` specified, tansform props will be transitioned by default, - * which is the same as the previous setting in echarts4 and suitable for the scenario - * of dataZoom change. - * If `transition` specified, only the specified props will be transitioned. - * + For props in `shape` and `style`: - * Only props specified in `transition` will be transitioned. - * + Break: - * Since ec5, do not make transition to shape by default, because it might result in - * performance issue (especially `points` of polygon) and do not necessary in most cases. - * - * @return if `isMorphTo`, return `allPropsFinal`. - */ -function updateElNormal( - // Can be null/undefined - api: ExtensionAPI, - el: Element, - // Whether be a morph target. - isMorphTo: boolean, - dataIndex: number, - elOption: CustomElementOption, - styleOpt: CustomElementOption['style'], - attachedTxInfo: AttachedTxInfo, - seriesModel: CustomSeriesModel, - isInit: boolean, - isTextContent: boolean -): ElementProps { - const transFromProps = {} as ElementProps; - const allPropsFinal = {} as ElementProps; - const elDisplayable = el.isGroup ? null : el as Displayable; - - // If be "morph to", delay the `updateElNormal` when all of the els in - // this data item processed. Because at that time we can get all of the - // "morph from" and make correct separate/combine. - - !isMorphTo && prepareShapeOrExtraTransitionFrom('shape', el, null, elOption, transFromProps, isInit); - prepareShapeOrExtraAllPropsFinal('shape', elOption, allPropsFinal); - !isMorphTo && prepareShapeOrExtraTransitionFrom('extra', el, null, elOption, transFromProps, isInit); - prepareShapeOrExtraAllPropsFinal('extra', elOption, allPropsFinal); - !isMorphTo && prepareTransformTransitionFrom(el, null, elOption, transFromProps, isInit); - prepareTransformAllPropsFinal(elOption, allPropsFinal); - - const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg; - if (txCfgOpt) { - // PENDING: whether use user object directly rather than clone? - // TODO:5.0 textConfig transition animation? - el.setTextConfig(txCfgOpt); - } - - if (el.type === 'text' && styleOpt) { - const textOptionStyle = styleOpt as TextStyleProps; - // Compatible with ec4: if `textFill` or `textStroke` exists use them. - hasOwn(textOptionStyle, 'textFill') && ( - textOptionStyle.fill = (textOptionStyle as any).textFill - ); - hasOwn(textOptionStyle, 'textStroke') && ( - textOptionStyle.stroke = (textOptionStyle as any).textStroke - ); - } - - if (styleOpt) { - let decalPattern; - const decalObj = isPath(el) ? (styleOpt as CustomZRPathOption['style']).decal : null; - if (api && decalObj) { - (decalObj as InnerDecalObject).dirty = true; - decalPattern = createOrUpdatePatternFromDecal(decalObj, api); - } - // Always overwrite in case user specify this prop. - (styleOpt as CustomZRPathOption['style']).__decalPattern = decalPattern; - } - - !isMorphTo && prepareStyleTransitionFrom(el, null, elOption, styleOpt, transFromProps, isInit); - - if (elDisplayable) { - hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible); - } - - // If `isMorphTo`, we should not update these props to el directly, otherwise, - // when applying morph finally, the original prop are missing for making "animation from". - if (!isMorphTo) { - applyPropsFinal(el, allPropsFinal, styleOpt); - applyTransitionFrom(el, dataIndex, elOption, seriesModel, transFromProps, isInit); - } - - // Merge by default. - hasOwn(elOption, 'silent') && (el.silent = elOption.silent); - hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); - - if (!isTextContent) { - // `elOption.info` enables user to mount some info on - // elements and use them in event handlers. - // Update them only when user specified, otherwise, remain. - hasOwn(elOption, 'info') && (inner(el).info = elOption.info); - } - - styleOpt ? el.dirty() : el.markRedraw(); - - return isMorphTo ? allPropsFinal : null; -} - -function applyPropsFinal( - el: Element, - // Can be null/undefined - allPropsFinal: ElementProps, - styleOpt: CustomElementOption['style'] -) { - const elDisplayable = el.isGroup ? null : el as Displayable; - - if (elDisplayable && styleOpt) { - - const decalPattern = (styleOpt as CustomZRPathOption['style']).__decalPattern; - let originalDecalObj; - if (decalPattern) { - originalDecalObj = (styleOpt as CustomZRPathOption['style']).decal; - (styleOpt as any).decal = decalPattern; - } - - // PENDING: here the input style object is used directly. - // Good for performance but bad for compatibility control. - elDisplayable.useStyle(styleOpt); - - if (decalPattern) { - (styleOpt as CustomZRPathOption['style']).decal = originalDecalObj; - } - - // When style object changed, how to trade the existing animation? - // It is probably conplicated and not needed to cover all the cases. - // But still need consider the case: - // (1) When using init animation on `style.opacity`, and before the animation - // ended users triggers an update by mousewhell. At that time the init - // animation should better be continued rather than terminated. - // So after `useStyle` called, we should change the animation target manually - // to continue the effect of the init animation. - // (2) PENDING: If the previous animation targeted at a `val1`, and currently we need - // to update the value to `val2` and no animation declared, should be terminate - // the previous animation or just modify the target of the animation? - // Therotically That will happen not only on `style` but also on `shape` and - // `transfrom` props. But we haven't handle this case at present yet. - // (3) PENDING: Is it proper to visit `animators` and `targetName`? - const animators = elDisplayable.animators; - for (let i = 0; i < animators.length; i++) { - const animator = animators[i]; - // targetName is the "topKey". - if (animator.targetName === 'style') { - animator.changeTarget(elDisplayable.style); - } - } - } - - // Set el to the final state firstly. - allPropsFinal && el.attr(allPropsFinal); -} - -function applyTransitionFrom( - el: Element, - dataIndex: number, - elOption: CustomElementOption, - seriesModel: CustomSeriesModel, - // Can be null/undefined - transFromProps: ElementProps, - isInit: boolean -): void { - if (transFromProps) { - // Do not use `el.updateDuringAnimation` here becuase `el.updateDuringAnimation` will - // be called mutiple time in each animation frame. For example, if both "transform" props - // and shape props and style props changed, it will generate three animator and called - // one-by-one in each animation frame. - // We use the during in `animateTo/From` params. - const userDuring = elOption.during; - // For simplicity, if during not specified, the previous during will not work any more. - inner(el).userDuring = userDuring; - const cfgDuringCall = userDuring ? bind(duringCall, { el: el, userDuring: userDuring }) : null; - const cfg = { - dataIndex: dataIndex, - isFrom: true, - during: cfgDuringCall - }; - isInit - ? graphicUtil.initProps(el, transFromProps, seriesModel, cfg) - : graphicUtil.updateProps(el, transFromProps, seriesModel, cfg); - } -} - - -// See [STRATEGY_TRANSITION] -function prepareShapeOrExtraTransitionFrom( - mainAttr: 'shape' | 'extra', - el: Element, - morphFromEl: graphicUtil.Path, - elOption: CustomElementOption, - transFromProps: LooseElementProps, - isInit: boolean -): void { - - const attrOpt: Dictionary & TransitionAnyOption = (elOption as any)[mainAttr]; - if (!attrOpt) { - return; - } - - const elPropsInAttr = (el as LooseElementProps)[mainAttr]; - let transFromPropsInAttr: Dictionary; - - const enterFrom = attrOpt.enterFrom; - if (isInit && enterFrom) { - !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); - const enterFromKeys = keys(enterFrom); - for (let i = 0; i < enterFromKeys.length; i++) { - // `enterFrom` props are not necessarily also declared in `shape`/`style`/..., - // for example, `opacity` can only declared in `enterFrom` but not in `style`. - const key = enterFromKeys[i]; - // Do not clone, animator will perform that clone. - transFromPropsInAttr[key] = enterFrom[key]; - } - } - - if (!isInit - && elPropsInAttr - // Just ignore shape animation in morphing. - && !(morphFromEl != null && mainAttr === 'shape') - ) { - if (attrOpt.transition) { - !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); - const transitionKeys = normalizeToArray(attrOpt.transition); - for (let i = 0; i < transitionKeys.length; i++) { - const key = transitionKeys[i]; - const elVal = elPropsInAttr[key]; - if (__DEV__) { - checkNonStyleTansitionRefer(key, (attrOpt as any)[key], elVal); - } - // Do not clone, see `checkNonStyleTansitionRefer`. - transFromPropsInAttr[key] = elVal; - } - } - else if (indexOf(elOption.transition, mainAttr) >= 0) { - !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); - const elPropsInAttrKeys = keys(elPropsInAttr); - for (let i = 0; i < elPropsInAttrKeys.length; i++) { - const key = elPropsInAttrKeys[i]; - const elVal = elPropsInAttr[key]; - if (isNonStyleTransitionEnabled((attrOpt as any)[key], elVal)) { - transFromPropsInAttr[key] = elVal; - } - } - } - } - - const leaveTo = attrOpt.leaveTo; - if (leaveTo) { - const leaveToProps = getOrCreateLeaveToPropsFromEl(el); - const leaveToPropsInAttr: Dictionary = leaveToProps[mainAttr] || (leaveToProps[mainAttr] = {}); - const leaveToKeys = keys(leaveTo); - for (let i = 0; i < leaveToKeys.length; i++) { - const key = leaveToKeys[i]; - leaveToPropsInAttr[key] = leaveTo[key]; - } - } -} - -function prepareShapeOrExtraAllPropsFinal( - mainAttr: 'shape' | 'extra', - elOption: CustomElementOption, - allProps: LooseElementProps -): void { - const attrOpt: Dictionary & TransitionAnyOption = (elOption as any)[mainAttr]; - if (!attrOpt) { - return; - } - const allPropsInAttr = allProps[mainAttr] = {} as Dictionary; - const keysInAttr = keys(attrOpt); - for (let i = 0; i < keysInAttr.length; i++) { - const key = keysInAttr[i]; - // To avoid share one object with different element, and - // to avoid user modify the object inexpectedly, have to clone. - allPropsInAttr[key] = cloneValue((attrOpt as any)[key]); - } -} - -// See [STRATEGY_TRANSITION]. -function prepareTransformTransitionFrom( - el: Element, - morphFromEl: graphicUtil.Path, - elOption: CustomElementOption, - transFromProps: ElementProps, - isInit: boolean -): void { - const enterFrom = elOption.enterFrom; - if (isInit && enterFrom) { - const enterFromKeys = keys(enterFrom); - for (let i = 0; i < enterFromKeys.length; i++) { - const key = enterFromKeys[i] as TransformProp; - if (__DEV__) { - checkTransformPropRefer(key, 'el.enterFrom'); - } - // Do not clone, animator will perform that clone. - transFromProps[key] = enterFrom[key] as number; - } - } - - if (!isInit) { - // If morphing, force transition all transform props. - // otherwise might have incorrect morphing animation. - if (morphFromEl) { - const fromTransformable = calcOldElLocalTransformBasedOnNewElParent(morphFromEl, el); - setTransformPropToTransitionFrom(transFromProps, 'x', fromTransformable); - setTransformPropToTransitionFrom(transFromProps, 'y', fromTransformable); - setTransformPropToTransitionFrom(transFromProps, 'scaleX', fromTransformable); - setTransformPropToTransitionFrom(transFromProps, 'scaleY', fromTransformable); - setTransformPropToTransitionFrom(transFromProps, 'originX', fromTransformable); - setTransformPropToTransitionFrom(transFromProps, 'originY', fromTransformable); - setTransformPropToTransitionFrom(transFromProps, 'rotation', fromTransformable); - } - else if (elOption.transition) { - const transitionKeys = normalizeToArray(elOption.transition); - for (let i = 0; i < transitionKeys.length; i++) { - const key = transitionKeys[i]; - if (key === 'style' || key === 'shape' || key === 'extra') { - continue; - } - const elVal = el[key]; - if (__DEV__) { - checkTransformPropRefer(key, 'el.transition'); - checkNonStyleTansitionRefer(key, elOption[key], elVal); - } - // Do not clone, see `checkNonStyleTansitionRefer`. - transFromProps[key] = elVal; - } - } - // This default transition see [STRATEGY_TRANSITION] - else { - setTransformPropToTransitionFrom(transFromProps, 'x', el); - setTransformPropToTransitionFrom(transFromProps, 'y', el); - } - } - - const leaveTo = elOption.leaveTo; - if (leaveTo) { - const leaveToProps = getOrCreateLeaveToPropsFromEl(el); - const leaveToKeys = keys(leaveTo); - for (let i = 0; i < leaveToKeys.length; i++) { - const key = leaveToKeys[i] as TransformProp; - if (__DEV__) { - checkTransformPropRefer(key, 'el.leaveTo'); - } - leaveToProps[key] = leaveTo[key] as number; - } - } -} - -function prepareTransformAllPropsFinal( - elOption: CustomElementOption, - allProps: ElementProps -): void { - setLagecyTransformProp(elOption, allProps, 'position'); - setLagecyTransformProp(elOption, allProps, 'scale'); - setLagecyTransformProp(elOption, allProps, 'origin'); - setTransformProp(elOption, allProps, 'x'); - setTransformProp(elOption, allProps, 'y'); - setTransformProp(elOption, allProps, 'scaleX'); - setTransformProp(elOption, allProps, 'scaleY'); - setTransformProp(elOption, allProps, 'originX'); - setTransformProp(elOption, allProps, 'originY'); - setTransformProp(elOption, allProps, 'rotation'); -} - -// See [STRATEGY_TRANSITION]. -function prepareStyleTransitionFrom( - el: Element, - morphFromEl: graphicUtil.Path, - elOption: CustomElementOption, - styleOpt: CustomElementOption['style'], - transFromProps: LooseElementProps, - isInit: boolean -): void { - if (!styleOpt) { - return; - } - - // At present in "many-to-one"/"one-to-many" case, to not support "many" have - // different styles and make style transitions. That might be a rare case. - const fromEl = morphFromEl || el; - - const fromElStyle = (fromEl as LooseElementProps).style as LooseElementProps['style']; - let transFromStyleProps: LooseElementProps['style']; - - const enterFrom = styleOpt.enterFrom; - if (isInit && enterFrom) { - const enterFromKeys = keys(enterFrom); - !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); - for (let i = 0; i < enterFromKeys.length; i++) { - const key = enterFromKeys[i]; - // Do not clone, animator will perform that clone. - (transFromStyleProps as any)[key] = enterFrom[key]; - } - } - - if (!isInit && fromElStyle) { - if (styleOpt.transition) { - const transitionKeys = normalizeToArray(styleOpt.transition); - !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); - for (let i = 0; i < transitionKeys.length; i++) { - const key = transitionKeys[i]; - const elVal = (fromElStyle as any)[key]; - // Do not clone, see `checkNonStyleTansitionRefer`. - (transFromStyleProps as any)[key] = elVal; - } - } - else if ( - (el as Displayable).getAnimationStyleProps - && indexOf(elOption.transition, 'style') >= 0 - ) { - const animationProps = (el as Displayable).getAnimationStyleProps(); - const animationStyleProps = animationProps ? animationProps.style : null; - if (animationStyleProps) { - !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); - const styleKeys = keys(styleOpt); - for (let i = 0; i < styleKeys.length; i++) { - const key = styleKeys[i]; - if ((animationStyleProps as Dictionary)[key]) { - const elVal = (fromElStyle as any)[key]; - (transFromStyleProps as any)[key] = elVal; - } - } - } - } - } - - const leaveTo = styleOpt.leaveTo; - if (leaveTo) { - const leaveToKeys = keys(leaveTo); - const leaveToProps = getOrCreateLeaveToPropsFromEl(el); - const leaveToStyleProps = leaveToProps.style || (leaveToProps.style = {}); - for (let i = 0; i < leaveToKeys.length; i++) { - const key = leaveToKeys[i]; - (leaveToStyleProps as any)[key] = leaveTo[key]; - } - } -} - -/** - * If make "transform"(x/y/scaleX/scaleY/orient/originX/originY) transition between - * two path elements that have different hierarchy, before we retrieve the "from" props, - * we have to calculate the local transition of the "oldPath" based on the parent of - * the "newPath". - * At present, the case only happend in "morphing". Without morphing, the transform - * transition are all between elements in the same hierarchy, where this kind of process - * is not needed. - * - * [CAVEAT]: - * This method makes sense only if: (very tricky) - * (1) "newEl" has been added to its final parent. - * (2) Local transform props of "newPath.parent" are not at their final value but already - * have been at the "from value". - * This is currently ensured by: - * (2.1) "graphicUtil.animationFrom", which will set the element to the "from value" - * immediately. - * (2.2) "morph" option is not allowed to be set on Group, so all of the groups have - * been finished their "updateElNormal" when calling this method in morphing process. - */ -function calcOldElLocalTransformBasedOnNewElParent(oldEl: Element, newEl: Element): Transformable { - if (!oldEl || oldEl === newEl || oldEl.parent === newEl.parent) { - return oldEl; - } - - // Not sure oldEl is rendered (may have "lazyUpdate"), - // so always call `getComputedTransform`. - const tmpM = tmpTransformable.transform - || (tmpTransformable.transform = matrix.identity([])); - - const oldGlobalTransform = oldEl.getComputedTransform(); - oldGlobalTransform - ? matrix.copy(tmpM, oldGlobalTransform) - : matrix.identity(tmpM); - - const newParent = newEl.parent; - if (newParent) { - newParent.getComputedTransform(); - } - - tmpTransformable.originX = oldEl.originX; - tmpTransformable.originY = oldEl.originY; - tmpTransformable.parent = newParent; - tmpTransformable.decomposeTransform(); - - return tmpTransformable; -} - -let checkNonStyleTansitionRefer: (propName: string, optVal: unknown, elVal: unknown) => void; -if (__DEV__) { - checkNonStyleTansitionRefer = function (propName: string, optVal: unknown, elVal: unknown): void { - if (!isArrayLike(optVal)) { - assert( - optVal != null && isFinite(optVal as number), - 'Prop `' + propName + '` must refer to a finite number or ArrayLike for transition.' - ); - } - else { - // Try not to copy array for performance, but if user use the same object in different - // call of `renderItem`, it will casue animation transition fail. - assert( - optVal !== elVal, - 'Prop `' + propName + '` must use different Array object each time for transition.' - ); - } - }; -} - -function isNonStyleTransitionEnabled(optVal: unknown, elVal: unknown): boolean { - // The same as `checkNonStyleTansitionRefer`. - return !isArrayLike(optVal) - ? (optVal != null && isFinite(optVal as number)) - : optVal !== elVal; -} - -let checkTransformPropRefer: (key: string, usedIn: string) => void; -if (__DEV__) { - checkTransformPropRefer = function (key: string, usedIn: string): void { - assert( - hasOwn(TRANSFORM_PROPS, key), - 'Prop `' + key + '` is not a permitted in `' + usedIn + '`. ' - + 'Only `' + keys(TRANSFORM_PROPS).join('`, `') + '` are permitted.' - ); - }; -} - -function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps { - const innerEl = inner(el); - return innerEl.leaveToProps || (innerEl.leaveToProps = {}); -} - -// Use it to avoid it be exposed to user. -const tmpDuringScope = {} as { - el: Element; - isShapeDirty: boolean; - isStyleDirty: boolean; -}; -const customDuringAPI = { - // Usually other props do not need to be changed in animation during. - setTransform(key: TransformProp, val: unknown) { - if (__DEV__) { - assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.'); - } - tmpDuringScope.el[key] = val as number; - return this; - }, - getTransform(key: TransformProp): unknown { - if (__DEV__) { - assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.'); - } - return tmpDuringScope.el[key]; - }, - setShape(key: string, val: unknown) { - if (__DEV__) { - assertNotReserved(key); - } - const shape = (tmpDuringScope.el as graphicUtil.Path).shape - || ((tmpDuringScope.el as graphicUtil.Path).shape = {}); - shape[key] = val; - tmpDuringScope.isShapeDirty = true; - return this; - }, - getShape(key: string): unknown { - if (__DEV__) { - assertNotReserved(key); - } - const shape = (tmpDuringScope.el as graphicUtil.Path).shape; - if (shape) { - return shape[key]; - } - }, - setStyle(key: string, val: unknown) { - if (__DEV__) { - assertNotReserved(key); - } - const style = (tmpDuringScope.el as Displayable).style; - if (style) { - if (__DEV__) { - if (eqNaN(val)) { - warn('style.' + key + ' must not be assigned with NaN.'); - } - } - style[key] = val; - tmpDuringScope.isStyleDirty = true; - } - return this; - }, - getStyle(key: string): unknown { - if (__DEV__) { - assertNotReserved(key); - } - const style = (tmpDuringScope.el as Displayable).style; - if (style) { - return style[key]; - } - }, - setExtra(key: string, val: unknown) { - if (__DEV__) { - assertNotReserved(key); - } - const extra = (tmpDuringScope.el as LooseElementProps).extra - || ((tmpDuringScope.el as LooseElementProps).extra = {}); - extra[key] = val; - return this; - }, - getExtra(key: string): unknown { - if (__DEV__) { - assertNotReserved(key); - } - const extra = (tmpDuringScope.el as LooseElementProps).extra; - if (extra) { - return extra[key]; - } - } -}; - -function assertNotReserved(key: string) { - if (__DEV__) { - if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') { - throw new Error('key must not be "' + key + '"'); - } - } -} - -function duringCall( - this: { - el: Element; - userDuring: CustomBaseElementOption['during'] - } -): void { - // Do not provide "percent" until some requirements come. - // Because consider thies case: - // enterFrom: {x: 100, y: 30}, transition: 'x'. - // And enter duration is different from update duration. - // Thus it might be confused about the meaning of "percent" in during callback. - const scope = this; - const el = scope.el; - if (!el) { - return; - } - // If el is remove from zr by reason like legend, during still need to called, - // becuase el will be added back to zr and the prop value should not be incorrect. - - const newstUserDuring = inner(el).userDuring; - const scopeUserDuring = scope.userDuring; - // Ensured a during is only called once in each animation frame. - // If a during is called multiple times in one frame, maybe some users' calulation logic - // might be wrong (not sure whether this usage exists). - // The case of a during might be called twice can be: by default there is a animator for - // 'x', 'y' when init. Before the init animation finished, call `setOption` to start - // another animators for 'style'/'shape'/'extra'. - if (newstUserDuring !== scopeUserDuring) { - // release - scope.el = scope.userDuring = null; - return; - } - - tmpDuringScope.el = el; - tmpDuringScope.isShapeDirty = false; - tmpDuringScope.isStyleDirty = false; - - // Give no `this` to user in "during" calling. - scopeUserDuring(customDuringAPI); - - if (tmpDuringScope.isShapeDirty && (el as graphicUtil.Path).dirtyShape) { - (el as graphicUtil.Path).dirtyShape(); - } - if (tmpDuringScope.isStyleDirty && (el as Displayable).dirtyStyle) { - (el as Displayable).dirtyStyle(); - } - // markRedraw() will be called by default in during. - // FIXME `this.markRedraw();` directly ? - - // FIXME: if in future meet the case that some prop will be both modified in `during` and `state`, - // consider the issue that the prop might be incorrect when return to "normal" state. -} - -function updateElOnState( - state: DisplayStateNonNormal, - el: Element, - elStateOpt: CustomElementOptionOnState, - styleOpt: CustomElementOptionOnState['style'], - attachedTxInfo: AttachedTxInfo, - isRoot: boolean, - isTextContent: boolean -): void { - const elDisplayable = el.isGroup ? null : el as Displayable; - const txCfgOpt = attachedTxInfo && attachedTxInfo[state].cfg; - - // PENDING:5.0 support customize scale change and transition animation? - - if (elDisplayable) { - // By default support auto lift color when hover whether `emphasis` specified. - const stateObj = elDisplayable.ensureState(state); - if (styleOpt === false) { - const existingEmphasisState = elDisplayable.getState(state); - if (existingEmphasisState) { - existingEmphasisState.style = null; - } - } - else { - // style is needed to enable defaut emphasis. - stateObj.style = styleOpt || null; - } - // If `elOption.styleEmphasis` or `elOption.emphasis.style` is `false`, - // remove hover style. - // If `elOption.textConfig` or `elOption.emphasis.textConfig` is null/undefined, it does not - // make sense. So for simplicity, we do not ditinguish `hasOwnProperty` and null/undefined. - if (txCfgOpt) { - stateObj.textConfig = txCfgOpt; - } - - setDefaultStateProxy(elDisplayable); - } -} - -function updateZ( - el: Element, - elOption: CustomElementOption, - seriesModel: CustomSeriesModel, - attachedTxInfo: AttachedTxInfo -): void { - // Group not support textContent and not support z yet. - if (el.isGroup) { - return; - } - - const elDisplayable = el as Displayable; - const currentZ = seriesModel.currentZ; - const currentZLevel = seriesModel.currentZLevel; - // Always erase. - elDisplayable.z = currentZ; - elDisplayable.zlevel = currentZLevel; - // z2 must not be null/undefined, otherwise sort error may occur. - const optZ2 = elOption.z2; - optZ2 != null && (elDisplayable.z2 = optZ2 || 0); - - for (let i = 0; i < STATES.length; i++) { - updateZForEachState(elDisplayable, elOption, STATES[i]); - } -} - -function updateZForEachState( - elDisplayable: Displayable, - elOption: CustomDisplayableOption, - state: DisplayState -): void { - const isNormal = state === NORMAL; - const elStateOpt = isNormal ? elOption : retrieveStateOption(elOption, state as DisplayStateNonNormal); - const optZ2 = elStateOpt ? elStateOpt.z2 : null; - let stateObj; - if (optZ2 != null) { - // Do not `ensureState` until required. - stateObj = isNormal ? elDisplayable : elDisplayable.ensureState(state); - stateObj.z2 = optZ2 || 0; - } -} - -function setLagecyTransformProp( - elOption: CustomElementOption, - targetProps: Partial>, - legacyName: LegacyTransformProp, - fromTransformable?: Transformable // If provided, retrieve from the element. -): void { - const legacyArr = (elOption as any)[legacyName]; - const xyName = LEGACY_TRANSFORM_PROPS[legacyName]; - if (legacyArr) { - if (fromTransformable) { - targetProps[xyName[0]] = fromTransformable[xyName[0]]; - targetProps[xyName[1]] = fromTransformable[xyName[1]]; - } - else { - targetProps[xyName[0]] = legacyArr[0]; - targetProps[xyName[1]] = legacyArr[1]; - } - } -} - -function setTransformProp( - elOption: CustomElementOption, - allProps: Partial>, - name: TransformProp, - fromTransformable?: Transformable // If provided, retrieve from the element. -): void { - if (elOption[name] != null) { - allProps[name] = fromTransformable ? fromTransformable[name] : elOption[name]; - } -} - -function setTransformPropToTransitionFrom( - transitionFrom: Partial>, - name: TransformProp, - fromTransformable?: Transformable // If provided, retrieve from the element. -): void { - if (fromTransformable) { - transitionFrom[name] = fromTransformable[name]; - } -} - - -function makeRenderItem( - customSeries: CustomSeriesModel, - data: List, - ecModel: GlobalModel, - api: ExtensionAPI -) { - const renderItem = customSeries.get('renderItem'); - const coordSys = customSeries.coordinateSystem; - let prepareResult = {} as ReturnType; - - if (coordSys) { - if (__DEV__) { - assert(renderItem, 'series.render is required.'); - assert( - coordSys.prepareCustoms || prepareCustoms[coordSys.type], - 'This coordSys does not support custom series.' - ); - } - - // `coordSys.prepareCustoms` is used for external coord sys like bmap. - prepareResult = coordSys.prepareCustoms - ? coordSys.prepareCustoms(coordSys) - : prepareCustoms[coordSys.type](coordSys); - } - - const userAPI = defaults({ - getWidth: api.getWidth, - getHeight: api.getHeight, - getZr: api.getZr, - getDevicePixelRatio: api.getDevicePixelRatio, - value: value, - style: style, - ordinalRawValue: ordinalRawValue, - styleEmphasis: styleEmphasis, - visual: visual, - barLayout: barLayout, - currentSeriesIndices: currentSeriesIndices, - font: font - }, prepareResult.api || {}) as CustomSeriesRenderItemAPI; - - const userParams: CustomSeriesRenderItemParams = { - // The life cycle of context: current round of rendering. - // The global life cycle is probably not necessary, because - // user can store global status by themselves. - context: {}, - seriesId: customSeries.id, - seriesName: customSeries.name, - seriesIndex: customSeries.seriesIndex, - coordSys: prepareResult.coordSys, - dataInsideLength: data.count(), - encode: wrapEncodeDef(customSeries.getData()) - }; - - // If someday intending to refactor them to a class, should consider do not - // break change: currently these attribute member are encapsulated in a closure - // so that do not need to force user to call these method with a scope. - - // Do not support call `api` asynchronously without dataIndexInside input. - let currDataIndexInside: number; - let currItemModel: Model; - let currItemStyleModels: Partial>> = {}; - let currLabelModels: Partial>> = {}; - - const seriesItemStyleModels = {} as Record>; - - const seriesLabelModels = {} as Record>; - - for (let i = 0; i < STATES.length; i++) { - const stateName = STATES[i]; - seriesItemStyleModels[stateName] = (customSeries as Model) - .getModel(PATH_ITEM_STYLE[stateName]); - seriesLabelModels[stateName] = (customSeries as Model) - .getModel(PATH_LABEL[stateName]); - } - - function getItemModel(dataIndexInside: number): Model { - return dataIndexInside === currDataIndexInside - ? (currItemModel || (currItemModel = data.getItemModel(dataIndexInside))) - : data.getItemModel(dataIndexInside); - } - function getItemStyleModel(dataIndexInside: number, state: DisplayState) { - return !data.hasItemOption - ? seriesItemStyleModels[state] - : dataIndexInside === currDataIndexInside - ? (currItemStyleModels[state] || ( - currItemStyleModels[state] = getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]) - )) - : getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]); - } - function getLabelModel(dataIndexInside: number, state: DisplayState) { - return !data.hasItemOption - ? seriesLabelModels[state] - : dataIndexInside === currDataIndexInside - ? (currLabelModels[state] || ( - currLabelModels[state] = getItemModel(dataIndexInside).getModel(PATH_LABEL[state]) - )) - : getItemModel(dataIndexInside).getModel(PATH_LABEL[state]); - } - - return function (dataIndexInside: number, payload: Payload): CustomElementOption { - currDataIndexInside = dataIndexInside; - currItemModel = null; - currItemStyleModels = {}; - currLabelModels = {}; - - return renderItem && renderItem( - defaults({ - dataIndexInside: dataIndexInside, - dataIndex: data.getRawIndex(dataIndexInside), - // Can be used for optimization when zoom or roam. - actionType: payload ? payload.type : null - }, userParams), - userAPI - ); - }; - - /** - * @public - * @param dim by default 0. - * @param dataIndexInside by default `currDataIndexInside`. - */ - function value(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue { - dataIndexInside == null && (dataIndexInside = currDataIndexInside); - return data.get(data.getDimension(dim || 0), dataIndexInside); - } - - /** - * @public - * @param dim by default 0. - * @param dataIndexInside by default `currDataIndexInside`. - */ - function ordinalRawValue(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue { - dataIndexInside == null && (dataIndexInside = currDataIndexInside); - const dimInfo = data.getDimensionInfo(dim || 0); - if (!dimInfo) { - return; - } - const val = data.get(dimInfo.name, dataIndexInside); - const ordinalMeta = dimInfo && dimInfo.ordinalMeta; - return ordinalMeta - ? ordinalMeta.categories[val as number] - : val; - } - - /** - * @deprecated The orgininal intention of `api.style` is enable to set itemStyle - * like other series. But it not necessary and not easy to give a strict definition - * of what it return. And since echarts5 it needs to be make compat work. So - * deprecates it since echarts5. - * - * By default, `visual` is applied to style (to support visualMap). - * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`, - * it can be implemented as: - * `api.style({stroke: api.visual('color'), fill: null})`; - * - * [Compat]: since ec5, RectText has been separated from its hosts el. - * so `api.style()` will only return the style from `itemStyle` but not handle `label` - * any more. But `series.label` config is never published in doc. - * We still compat it in `api.style()`. But not encourage to use it and will still not - * to pulish it to doc. - * @public - * @param dataIndexInside by default `currDataIndexInside`. - */ - function style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps { - if (__DEV__) { - warnDeprecated('api.style', 'Please write literal style directly instead.'); - } - - dataIndexInside == null && (dataIndexInside = currDataIndexInside); - - const style = data.getItemVisual(dataIndexInside, 'style'); - const visualColor = style && style.fill; - const opacity = style && style.opacity; - - let itemStyle = getItemStyleModel(dataIndexInside, NORMAL).getItemStyle(); - visualColor != null && (itemStyle.fill = visualColor); - opacity != null && (itemStyle.opacity = opacity); - - const opt = {inheritColor: isString(visualColor) ? visualColor : '#000'}; - const labelModel = getLabelModel(dataIndexInside, NORMAL); - // Now that the feture of "auto adjust text fill/stroke" has been migrated to zrender - // since ec5, we should set `isAttached` as `false` here and make compat in - // `convertToEC4StyleForCustomSerise`. - const textStyle = labelStyleHelper.createTextStyle(labelModel, null, opt, false, true); - textStyle.text = labelModel.getShallow('show') - ? retrieve2( - customSeries.getFormattedLabel(dataIndexInside, NORMAL), - getDefaultLabel(data, dataIndexInside) - ) - : null; - const textConfig = labelStyleHelper.createTextConfig(labelModel, opt, false); - - preFetchFromExtra(userProps, itemStyle); - itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig); - - userProps && applyUserPropsAfter(itemStyle, userProps); - (itemStyle as LegacyStyleProps).legacy = true; - - return itemStyle; - } - - /** - * @deprecated The reason see `api.style()` - * @public - * @param dataIndexInside by default `currDataIndexInside`. - */ - function styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps { - if (__DEV__) { - warnDeprecated('api.styleEmphasis', 'Please write literal style directly instead.'); - } - - dataIndexInside == null && (dataIndexInside = currDataIndexInside); - - let itemStyle = getItemStyleModel(dataIndexInside, EMPHASIS).getItemStyle(); - const labelModel = getLabelModel(dataIndexInside, EMPHASIS); - const textStyle = labelStyleHelper.createTextStyle(labelModel, null, null, true, true); - textStyle.text = labelModel.getShallow('show') - ? retrieve3( - customSeries.getFormattedLabel(dataIndexInside, EMPHASIS), - customSeries.getFormattedLabel(dataIndexInside, NORMAL), - getDefaultLabel(data, dataIndexInside) - ) - : null; - const textConfig = labelStyleHelper.createTextConfig(labelModel, null, true); - - preFetchFromExtra(userProps, itemStyle); - itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig); - - userProps && applyUserPropsAfter(itemStyle, userProps); - (itemStyle as LegacyStyleProps).legacy = true; - - return itemStyle; - } - - function applyUserPropsAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void { - for (const key in extra) { - if (hasOwn(extra, key)) { - (itemStyle as any)[key] = (extra as any)[key]; - } - } - } - - function preFetchFromExtra(extra: ZRStyleProps, itemStyle: ItemStyleProps): void { - // A trick to retrieve those props firstly, which are used to - // apply auto inside fill/stroke in `convertToEC4StyleForCustomSerise`. - // (It's not reasonable but only for a degree of compat) - if (extra) { - (extra as any).textFill && ((itemStyle as any).textFill = (extra as any).textFill); - (extra as any).textPosition && ((itemStyle as any).textPosition = (extra as any).textPosition); - } - } - - /** - * @public - * @param dataIndexInside by default `currDataIndexInside`. - */ - function visual( - visualType: VT, - dataIndexInside?: number - ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT] - : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]] - : never { - - dataIndexInside == null && (dataIndexInside = currDataIndexInside); - - if (hasOwn(STYLE_VISUAL_TYPE, visualType)) { - const style = data.getItemVisual(dataIndexInside, 'style'); - return style - ? style[STYLE_VISUAL_TYPE[visualType as StyleVisualProps]] as any - : null; - } - // Only support these visuals. Other visual might be inner tricky - // for performance (like `style`), do not expose to users. - if (hasOwn(NON_STYLE_VISUAL_PROPS, visualType)) { - return data.getItemVisual(dataIndexInside, visualType as NonStyleVisualProps) as any; - } - } - - /** - * @public - * @return If not support, return undefined. - */ - function barLayout( - opt: Omit[0], 'axis'> - ): ReturnType { - if (coordSys.type === 'cartesian2d') { - const baseAxis = coordSys.getBaseAxis() as Axis2D; - return getLayoutOnAxis(defaults({axis: baseAxis}, opt)); - } - } - - /** - * @public - */ - function currentSeriesIndices(): ReturnType { - return ecModel.getCurrentSeriesIndices(); - } - - /** - * @public - * @return font string - */ - function font( - opt: Parameters[0] - ): ReturnType { - return labelStyleHelper.getFont(opt, ecModel); - } -} - -type WrapEncodeDefRet = Dictionary; - -function wrapEncodeDef(data: List): WrapEncodeDefRet { - const encodeDef = {} as WrapEncodeDefRet; - each(data.dimensions, function (dimName, dataDimIndex) { - const dimInfo = data.getDimensionInfo(dimName); - if (!dimInfo.isExtraCoord) { - const coordDim = dimInfo.coordDim; - const dataDims = encodeDef[coordDim] = encodeDef[coordDim] || []; - dataDims[dimInfo.coordDimIndex] = dataDimIndex; - } - }); - return encodeDef; -} - -function createOrUpdateItem( - api: ExtensionAPI, - el: Element, - dataIndex: number, - elOption: CustomElementOption, - seriesModel: CustomSeriesModel, - group: ViewRootGroup, - data: List, - morphPreparation: MorphPreparation -): Element { - // [Rule] - // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing. - // (It seems that violate the "merge" principle, but most of users probably intuitively - // regard "return;" as "show nothing element whatever", so make a exception to meet the - // most cases.) - // The rule or "merge" see [STRATEGY_MERGE]. - - // If `elOption` is `null`/`undefined`/`false` (when `renderItem` returns nothing). - if (!elOption) { - removeElementDirectly(el, group); - return; - } - el = doCreateOrUpdateEl(api, el, dataIndex, elOption, seriesModel, group, true, morphPreparation); - el && data.setItemGraphicEl(dataIndex, el); - - enableHoverEmphasis(el, elOption.focus, elOption.blurScope); - - return el; -} - -function doCreateOrUpdateEl( - api: ExtensionAPI, - el: Element, - dataIndex: number, - elOption: CustomElementOption, - seriesModel: CustomSeriesModel, - group: ViewRootGroup, - isRoot: boolean, - morphPreparation: MorphPreparation -): Element { - - if (__DEV__) { - assert(elOption, 'should not have an null/undefined element setting'); - } - - let toBeReplacedIdx = -1; - if ( - el && ( - doesElNeedRecreate(el, elOption) - // || ( - // // PENDING: even in one-to-one mapping case, if el is marked as morph, - // // do not sure whether the el will be mapped to another el with different - // // hierarchy in Group tree. So always recreate el rather than reuse the el. - // morphPreparation && morphPreparation.isOneToOneFrom(el) - // ) - ) - ) { - // Should keep at the original index, otherwise "merge by index" will be incorrect. - toBeReplacedIdx = group.childrenRef().indexOf(el); - el = null; - } - - const elIsNewCreated = !el; - - if (!el) { - el = createEl(elOption); - } - else { - // FIMXE:NEXT unified clearState? - // If in some case the performance issue arised, consider - // do not clearState but update cached normal state directly. - el.clearStates(); - } - - const canMorph = inner(el).canMorph = (elOption as CustomZRPathOption).morph && isPath(el); - const thisElIsMorphTo = canMorph && morphPreparation && morphPreparation.hasFrom(); - - // Use update animation when morph is enabled. - const isInit = elIsNewCreated && !thisElIsMorphTo; - - attachedTxInfoTmp.normal.cfg = attachedTxInfoTmp.normal.conOpt = - attachedTxInfoTmp.emphasis.cfg = attachedTxInfoTmp.emphasis.conOpt = - attachedTxInfoTmp.blur.cfg = attachedTxInfoTmp.blur.conOpt = - attachedTxInfoTmp.select.cfg = attachedTxInfoTmp.select.conOpt = null; - - attachedTxInfoTmp.isLegacy = false; - - doCreateOrUpdateAttachedTx( - el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp - ); - - doCreateOrUpdateClipPath( - el, dataIndex, elOption, seriesModel, isInit - ); - - const pendingAllPropsFinal = updateElNormal( - api, - el, - thisElIsMorphTo, - dataIndex, - elOption, - elOption.style, - attachedTxInfoTmp, - seriesModel, - isInit, - false - ); - - if (thisElIsMorphTo) { - morphPreparation.addTo(el as graphicUtil.Path, elOption, dataIndex, pendingAllPropsFinal); - } - - for (let i = 0; i < STATES.length; i++) { - const stateName = STATES[i]; - if (stateName !== NORMAL) { - const otherStateOpt = retrieveStateOption(elOption, stateName); - const otherStyleOpt = retrieveStyleOptionOnState(elOption, otherStateOpt, stateName); - updateElOnState(stateName, el, otherStateOpt, otherStyleOpt, attachedTxInfoTmp, isRoot, false); - } - } - - updateZ(el, elOption, seriesModel, attachedTxInfoTmp); - - if (elOption.type === 'group') { - mergeChildren( - api, el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, morphPreparation - ); - } - - if (toBeReplacedIdx >= 0) { - group.replaceAt(el, toBeReplacedIdx); - } - else { - group.add(el); - } - - return el; -} - -// `el` must not be null/undefined. -function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean { - const elInner = inner(el); - const elOptionType = elOption.type; - const elOptionShape = (elOption as CustomZRPathOption).shape; - const elOptionStyle = elOption.style; - return ( - // If `elOptionType` is `null`, follow the merge principle. - (elOptionType != null - && elOptionType !== elInner.customGraphicType - ) - || (elOptionType === 'path' - && hasOwnPathData(elOptionShape) - && getPathData(elOptionShape) !== elInner.customPathData - ) - || (elOptionType === 'image' - && hasOwn(elOptionStyle, 'image') - && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath - ) - // // FIXME test and remove this restriction? - // || (elOptionType === 'text' - // && hasOwn(elOptionStyle, 'text') - // && (elOptionStyle as TextStyleProps).text !== elInner.customText - // ) - ); -} - -function doCreateOrUpdateClipPath( - el: Element, - dataIndex: number, - elOption: CustomElementOption, - seriesModel: CustomSeriesModel, - isInit: boolean -): void { - // Based on the "merge" principle, if no clipPath provided, - // do nothing. The exists clip will be totally removed only if - // `el.clipPath` is `false`. Otherwise it will be merged/replaced. - const clipPathOpt = elOption.clipPath; - if (clipPathOpt === false) { - if (el && el.getClipPath()) { - el.removeClipPath(); - } - } - else if (clipPathOpt) { - let clipPath = el.getClipPath(); - if (clipPath && doesElNeedRecreate(clipPath, clipPathOpt)) { - clipPath = null; - } - if (!clipPath) { - clipPath = createEl(clipPathOpt) as graphicUtil.Path; - if (__DEV__) { - assert( - clipPath instanceof graphicUtil.Path, - 'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.' - ); - } - el.setClipPath(clipPath); - } - updateElNormal( - null, clipPath, null, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false - ); - } - // If not define `clipPath` in option, do nothing unnecessary. -} - -function doCreateOrUpdateAttachedTx( - el: Element, - dataIndex: number, - elOption: CustomElementOption, - seriesModel: CustomSeriesModel, - isInit: boolean, - attachedTxInfo: AttachedTxInfo -): void { - // group do not support textContent temporarily untill necessary. - if (el.isGroup) { - return; - } - - // Normal must be called before emphasis, for `isLegacy` detection. - processTxInfo(elOption, null, attachedTxInfo); - processTxInfo(elOption, EMPHASIS, attachedTxInfo); - - // If `elOption.textConfig` or `elOption.textContent` is null/undefined, it does not make sence. - // So for simplicity, if "elOption hasOwnProperty of them but be null/undefined", we do not - // trade them as set to null to el. - // Especially: - // `elOption.textContent: false` means remove textContent. - // `elOption.textContent.emphasis.style: false` means remove the style from emphasis state. - let txConOptNormal = attachedTxInfo.normal.conOpt as CustomElementOption | false; - const txConOptEmphasis = attachedTxInfo.emphasis.conOpt as CustomElementOptionOnState; - const txConOptBlur = attachedTxInfo.blur.conOpt as CustomElementOptionOnState; - const txConOptSelect = attachedTxInfo.select.conOpt as CustomElementOptionOnState; - - if (txConOptNormal != null || txConOptEmphasis != null || txConOptSelect != null || txConOptBlur != null) { - let textContent = el.getTextContent(); - if (txConOptNormal === false) { - textContent && el.removeTextContent(); - } - else { - txConOptNormal = attachedTxInfo.normal.conOpt = txConOptNormal || {type: 'text'}; - if (!textContent) { - textContent = createEl(txConOptNormal) as graphicUtil.Text; - el.setTextContent(textContent); - } - else { - // If in some case the performance issue arised, consider - // do not clearState but update cached normal state directly. - textContent.clearStates(); - } - const txConStlOptNormal = txConOptNormal && txConOptNormal.style; - - updateElNormal( - null, textContent, null, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true - ); - for (let i = 0; i < STATES.length; i++) { - const stateName = STATES[i]; - if (stateName !== NORMAL) { - const txConOptOtherState = attachedTxInfo[stateName].conOpt as CustomElementOptionOnState; - updateElOnState( - stateName, - textContent, - txConOptOtherState, - retrieveStyleOptionOnState(txConOptNormal, txConOptOtherState, stateName), - null, false, true - ); - } - } - - txConStlOptNormal ? textContent.dirty() : textContent.markRedraw(); - } - } -} - -function processTxInfo( - elOption: CustomElementOption, - state: DisplayStateNonNormal, - attachedTxInfo: AttachedTxInfo -): void { - const stateOpt = !state ? elOption : retrieveStateOption(elOption, state); - const styleOpt = !state ? elOption.style : retrieveStyleOptionOnState(elOption, stateOpt, EMPHASIS); - - const elType = elOption.type; - let txCfg = stateOpt ? stateOpt.textConfig : null; - const txConOptNormal = elOption.textContent; - let txConOpt: CustomElementOption | CustomElementOptionOnState = - !txConOptNormal ? null : !state ? txConOptNormal : retrieveStateOption(txConOptNormal, state); - - if (styleOpt && ( - // Because emphasis style has little info to detect legacy, - // if normal is legacy, emphasis is trade as legacy. - attachedTxInfo.isLegacy - || isEC4CompatibleStyle(styleOpt, elType, !!txCfg, !!txConOpt) - )) { - attachedTxInfo.isLegacy = true; - const convertResult = convertFromEC4CompatibleStyle(styleOpt, elType, !state); - // Explicitly specified `textConfig` and `textContent` has higher priority than - // the ones generated by legacy style. Otherwise if users use them and `api.style` - // at the same time, they not both work and hardly to known why. - if (!txCfg && convertResult.textConfig) { - txCfg = convertResult.textConfig; - } - if (!txConOpt && convertResult.textContent) { - txConOpt = convertResult.textContent; - } - } - - if (!state && txConOpt) { - const txConOptNormal = txConOpt as CustomElementOption; - // `textContent: {type: 'text'}`, the "type" is easy to be missing. So we tolerate it. - !txConOptNormal.type && (txConOptNormal.type = 'text'); - if (__DEV__) { - // Do not tolerate incorret type for forward compat. - txConOptNormal.type !== 'text' && assert( - txConOptNormal.type === 'text', - 'textContent.type must be "text"' - ); - } - } - - const info = !state ? attachedTxInfo.normal : attachedTxInfo[state]; - info.cfg = txCfg; - info.conOpt = txConOpt; -} - -function retrieveStateOption( - elOption: CustomElementOption, state: DisplayStateNonNormal -): CustomElementOptionOnState { - return !state ? elOption : elOption ? elOption[state] : null; -} - -function retrieveStyleOptionOnState( - stateOptionNormal: CustomElementOption, - stateOption: CustomElementOptionOnState, - state: DisplayStateNonNormal -): CustomElementOptionOnState['style'] { - let style = stateOption && stateOption.style; - if (style == null && state === EMPHASIS && stateOptionNormal) { - style = stateOptionNormal.styleEmphasis; - } - return style; -} - - -// Usage: -// (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that -// the existing children will not be removed, and enables the feature that -// update some of the props of some of the children simply by construct -// the returned children of `renderItem` like: -// `var children = group.children = []; children[3] = {opacity: 0.5};` -// (2) If `elOption.$mergeChildren` is `'byName'`, add/update/remove children -// by child.name. But that might be lower performance. -// (3) If `elOption.$mergeChildren` is `false`, the existing children will be -// replaced totally. -// (4) If `!elOption.children`, following the "merge" principle, nothing will happen. -// -// For implementation simpleness, do not provide a direct way to remove sinlge -// child (otherwise the total indicies of the children array have to be modified). -// User can remove a single child by set its `ignore` as `true`. -function mergeChildren( - api: ExtensionAPI, - el: graphicUtil.Group, - dataIndex: number, - elOption: CustomGroupOption, - seriesModel: CustomSeriesModel, - morphPreparation: MorphPreparation -): void { - - const newChildren = elOption.children; - const newLen = newChildren ? newChildren.length : 0; - const mergeChildren = elOption.$mergeChildren; - // `diffChildrenByName` has been deprecated. - const byName = mergeChildren === 'byName' || elOption.diffChildrenByName; - const notMerge = mergeChildren === false; - - // For better performance on roam update, only enter if necessary. - if (!newLen && !byName && !notMerge) { - return; - } - - if (byName) { - diffGroupChildren({ - api: api, - oldChildren: el.children() || [], - newChildren: newChildren || [], - dataIndex: dataIndex, - seriesModel: seriesModel, - group: el, - morphPreparation: morphPreparation - }); - return; - } - - notMerge && el.removeAll(); - - // Mapping children of a group simply by index, which - // might be better performance. - let index = 0; - for (; index < newLen; index++) { - newChildren[index] && doCreateOrUpdateEl( - api, - el.childAt(index), - dataIndex, - newChildren[index], - seriesModel, - el, - false, - morphPreparation - ); - } - for (let i = el.childCount() - 1; i >= index; i--) { - // Do not supprot leave elements that are not mentioned in the latest - // `renderItem` return. Otherwise users may not have a clear and simple - // concept that how to contorl all of the elements. - doRemoveEl(el.childAt(i), seriesModel, el); - } -} - -type DiffGroupContext = { - api: ExtensionAPI; - oldChildren: Element[]; - newChildren: CustomElementOption[]; - dataIndex: number; - seriesModel: CustomSeriesModel; - group: graphicUtil.Group; - morphPreparation: MorphPreparation; -}; -function diffGroupChildren(context: DiffGroupContext) { - (new DataDiffer( - context.oldChildren, - context.newChildren, - getKey, - getKey, - context - )) - .add(processAddUpdate) - .update(processAddUpdate) - .remove(processRemove) - .execute(); -} - -function getKey(item: Element, idx: number): string { - const name = item && item.name; - return name != null ? name : GROUP_DIFF_PREFIX + idx; -} - -function processAddUpdate( - this: DataDiffer, - newIndex: number, - oldIndex?: number -): void { - const context = this.context; - const childOption = newIndex != null ? context.newChildren[newIndex] : null; - const child = oldIndex != null ? context.oldChildren[oldIndex] : null; - - doCreateOrUpdateEl( - context.api, - child, - context.dataIndex, - childOption, - context.seriesModel, - context.group, - false, - context.morphPreparation - ); -} - -function processRemove(this: DataDiffer, oldIndex: number): void { - const context = this.context; - const child = context.oldChildren[oldIndex]; - doRemoveEl(child, context.seriesModel, context.group); -} - -function doRemoveEl( - el: Element, - seriesModel: CustomSeriesModel, - group: ViewRootGroup -): void { - if (el) { - const leaveToProps = inner(el).leaveToProps; - leaveToProps - ? graphicUtil.updateProps(el, leaveToProps, seriesModel, { - cb: function () { - group.remove(el); - } - }) - : group.remove(el); - } -} - -/** - * @return SVG Path data. - */ -function getPathData(shape: CustomSVGPathOption['shape']): string { - // "d" follows the SVG convention. - return shape && (shape.pathData || shape.d); -} - -function hasOwnPathData(shape: CustomSVGPathOption['shape']): boolean { - return shape && (hasOwn(shape, 'pathData') || hasOwn(shape, 'd')); -} - -function isPath(el: Element): el is graphicUtil.Path { - return el && el instanceof graphicUtil.Path; -} - -function removeElementDirectly(el: Element, group: ViewRootGroup): void { - el && group.remove(el); -} - - -type MorphPreparationType = 'oneToOne' | 'oneToMany' | 'manyToOne'; - -/** - * Any morph-potential el should added by `morphPreparation.addTo(el)`. - * And they may apply morph or not when `morphPreparation.applyMorphing()`. - * But at least, all of the "to" elements will apply all of the updates - * as `doCreateOrUpdateItem` did. - */ -class MorphPreparation { - private _type: MorphPreparationType; - private _fromList: graphicUtil.Path[] = []; - private _toList: graphicUtil.Path[] = []; - private _toElOptionList: CustomElementOption[] = []; - private _allPropsFinalList: ElementProps[] = []; - private _toDataIndices: number[] = []; - private _transOpt: SeriesModel['__transientTransitionOpt']; - private _seriesModel: CustomSeriesModel; - // Key: `toDataIndex`, not `toIdx` - private _morphConfigList: CombineSeparateConfig[] = []; - - constructor( - seriesModel: CustomSeriesModel, - transOpt: SeriesModel['__transientTransitionOpt'] - ) { - this._seriesModel = seriesModel; - this._transOpt = transOpt; - } - - hasFrom(): boolean { - return !!this._fromList.length; - } - - // isOneToOneFrom(el: Element): boolean { - // if (el && inner(el).canMorph) { - // const fromList = this._fromList; - // for (let i = 0; i < fromList.length; i++) { - // if (fromList[i] === el) { - // return true; - // } - // } - // } - // } - - findAndAddFrom(el: Element): void { - if (!el) { - return; - } - if (inner(el).canMorph) { - this._fromList.push(el as graphicUtil.Path); - } - if (el.isGroup) { - const children = (el as graphicUtil.Group).childrenRef(); - for (let i = 0; i < children.length; i++) { - this.findAndAddFrom(children[i]); - } - } - } - - addTo( - path: graphicUtil.Path, - elOption: CustomElementOption, - dataIndex: number, - allPropsFinal: ElementProps - ): void { - if (path) { - this._toList.push(path); - this._toElOptionList.push(elOption); - this._toDataIndices.push(dataIndex); - this._allPropsFinalList.push(allPropsFinal); - } - } - - applyMorphing(): void { - // [MORPHING_LOGIC_HINT] - // Pay attention to the order: - // (A) Apply `allPropsFinal` and `styleOption` to "to". - // (Then "to" becomes to the final state.) - // (B) Apply `morphPath`/`combine`/`separate`. - // (Based on the current state of "from" and the final state of "to".) - // (Then we may get "from.subList" or "to.subList".) - // (C) Copy the related props from "from" to "from.subList", from "to" to "to.subList". - // (D) Collect `transitionFromProps` for "to" and "to.subList" - // (Based on "from" or "from.subList".) - // (E) Apply `transitionFromProps` to "to" and "to.subList" - // (It might change the prop values to the first frame value.) - // Case_I: - // If (D) should be after (C), we use sequence: A - B - C - D - E - // Case_II: - // If (A) should be after (D), we use sequence: D - A - B - C - E - - // [MORPHING_LOGIC_HINT] - // zrender `morphPath`/`combine`/`separate` only manages the shape animation. - // Other props (like transfrom, style transition) will handled in echarts). - - // [MORPHING_LOGIC_HINT] - // Make sure `applyPropsFinal` always be called for "to". - - const type = this._type; - const fromList = this._fromList; - const toList = this._toList; - const toListLen = toList.length; - const fromListLen = fromList.length; - - if (!fromListLen || !toListLen) { - return; - } - - if (type === 'oneToOne') { - // In one-to-one case, we by default apply a simple rule: - // map "from" and "to" one by one. - // For this case: old_data_item_el and new_data_item_el - // has the same hierarchy of group tree but only some path type changed. - for (let toIdx = 0; toIdx < toListLen; toIdx++) { - this._oneToOneForSingleTo(toIdx, toIdx); - } - } - - else if (type === 'manyToOne') { - // A rough strategy: if there are more than one "to", we simply divide "fromList" equally. - const fromSingleSegLen = Math.max(1, Math.floor(fromListLen / toListLen)); - for ( - let toIdx = 0, fromIdxStart = 0; - toIdx < toListLen; - toIdx++, fromIdxStart += fromSingleSegLen - ) { - const fromCount = toIdx + 1 >= toListLen - ? fromListLen - fromIdxStart - : fromSingleSegLen; - this._manyToOneForSingleTo( - toIdx, fromIdxStart >= fromListLen ? null : fromIdxStart, fromCount - ); - } - } - - else if (type === 'oneToMany') { - // A rough strategy: if there are more than one "from", we simply divide "toList" equally. - const toSingleSegLen = Math.max(1, Math.floor(toListLen / fromListLen)); - for ( - let toIdxStart = 0, fromIdx = 0; - toIdxStart < toListLen; - toIdxStart += toSingleSegLen, fromIdx++ - ) { - const toCount = toIdxStart + toSingleSegLen >= toListLen - ? toListLen - toIdxStart - : toSingleSegLen; - this._oneToManyForSingleFrom( - toIdxStart, toCount, fromIdx >= fromListLen ? null : fromIdx - ); - } - } - } - - private _oneToOneForSingleTo( - // "to" must NOT be null/undefined. - toIdx: number, - // May `fromIdx >= this._fromList.length` - fromIdx: number - ): void { - const to = this._toList[toIdx]; - const toElOption = this._toElOptionList[toIdx]; - const toDataIndex = this._toDataIndices[toIdx]; - const allPropsFinal = this._allPropsFinalList[toIdx]; - const from = this._fromList[fromIdx]; - - const elAnimationConfig = this._getOrCreateMorphConfig(toDataIndex); - const morphDuration = elAnimationConfig.duration; - - if (from && isCombiningPath(from)) { - applyPropsFinal(to, allPropsFinal, toElOption.style); - - if (morphDuration) { - const combineResult = combine([from], to, elAnimationConfig, copyPropsWhenDivided); - this._processResultIndividuals(combineResult, toIdx, null); - } - // The target el will not be displayed and transition from multiple path. - // transition on the target el does not make sense. - } - else { - const morphFrom = ( - morphDuration - // from === to usually happen in scenarios where internal update like - // "dataZoom", "legendToggle" happen. If from is not in any morphing, - // we do not need to call `morphPath`. - && from - && (from !== to || isInAnyMorphing(from)) - ) ? from : null; - - // See [Case_II] above. - // In this case, there is probably `from === to`. And the `transitionFromProps` collecting - // does not depends on morphing. So we collect `transitionFromProps` first. - const transFromProps = {} as ElementProps; - prepareShapeOrExtraTransitionFrom('shape', to, morphFrom, toElOption, transFromProps, false); - prepareShapeOrExtraTransitionFrom('extra', to, morphFrom, toElOption, transFromProps, false); - prepareTransformTransitionFrom(to, morphFrom, toElOption, transFromProps, false); - prepareStyleTransitionFrom(to, morphFrom, toElOption, toElOption.style, transFromProps, false); - - applyPropsFinal(to, allPropsFinal, toElOption.style); - - if (morphFrom) { - morphPath(morphFrom, to, elAnimationConfig); - } - applyTransitionFrom(to, toDataIndex, toElOption, this._seriesModel, transFromProps, false); - } - } - - private _manyToOneForSingleTo( - // "to" must NOT be null/undefined. - toIdx: number, - // May be null. - fromIdxStart: number, - fromCount: number - ): void { - const to = this._toList[toIdx]; - const toElOption = this._toElOptionList[toIdx]; - const allPropsFinal = this._allPropsFinalList[toIdx]; - - applyPropsFinal(to, allPropsFinal, toElOption.style); - - const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdx]); - if (elAnimationConfig.duration && fromIdxStart != null) { - const combineFromList = []; - for (let fromIdx = fromIdxStart; fromIdx < fromCount; fromIdx++) { - combineFromList.push(this._fromList[fromIdx]); - } - const combineResult = combine(combineFromList, to, elAnimationConfig, copyPropsWhenDivided); - this._processResultIndividuals(combineResult, toIdx, null); - } - } - - private _oneToManyForSingleFrom( - // "to" must NOT be null/undefined. - toIdxStart: number, - toCount: number, - // May be null - fromIdx: number - ): void { - const from = fromIdx == null ? null : this._fromList[fromIdx]; - const toList = this._toList; - - const separateToList = []; - for (let toIdx = toIdxStart; toIdx < toCount; toIdx++) { - const to = toList[toIdx]; - applyPropsFinal(to, this._allPropsFinalList[toIdx], this._toElOptionList[toIdx].style); - separateToList.push(to); - } - - const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdxStart]); - if (elAnimationConfig.duration && from) { - const separateResult = separate(from, separateToList, elAnimationConfig, copyPropsWhenDivided); - this._processResultIndividuals(separateResult, toIdxStart, toCount); - } - } - - private _processResultIndividuals( - combineSeparateResult: CombineSeparateResult, - toIdxStart: number, - toCount: number - ): void { - const isSeparate = toCount != null; - - for (let i = 0; i < combineSeparateResult.count; i++) { - const fromIndividual = combineSeparateResult.fromIndividuals[i]; - const toIndividual = combineSeparateResult.toIndividuals[i]; - // Here it's a trick: - // For "combine" case, all of the `toIndividuals` map to the same `toIdx`. - // For "separate" case, the `toIndividuals` map to some certain segment of `_toList` accurately. - const toIdx = toIdxStart + (isSeparate ? i : 0); - - const toElOption = this._toElOptionList[toIdx]; - const dataIndex = this._toDataIndices[toIdx]; - - const transFromProps = {} as ElementProps; - prepareTransformTransitionFrom( - toIndividual, fromIndividual, toElOption, transFromProps, false - ); - prepareStyleTransitionFrom( - toIndividual, fromIndividual, toElOption, toElOption.style, transFromProps, false - ); - applyTransitionFrom( - toIndividual, dataIndex, toElOption, this._seriesModel, transFromProps, false - ); - } - } - - _getOrCreateMorphConfig(dataIndex: number): CombineSeparateConfig { - const morphConfigList = this._morphConfigList; - let config = morphConfigList[dataIndex]; - if (config) { - return config; - } - - let duration: number; - let easing: AnimationEasing; - let delay: number; - const seriesModel = this._seriesModel; - const transOpt = this._transOpt; - - if (seriesModel.isAnimationEnabled()) { - // PENDING: refactor? this is the same logic as `src/util/graphic.ts#animateOrSetProps`. - let animationPayload: PayloadAnimationPart; - if (seriesModel && seriesModel.ecModel) { - const updatePayload = seriesModel.ecModel.getUpdatePayload(); - animationPayload = (updatePayload && updatePayload.animation) as PayloadAnimationPart; - } - if (animationPayload) { - duration = animationPayload.duration || 0; - easing = animationPayload.easing || 'cubicOut'; - delay = animationPayload.delay || 0; - } - else { - easing = seriesModel.get('animationEasingUpdate'); - const delayOption = seriesModel.get('animationDelayUpdate'); - delay = isFunction(delayOption) ? delayOption(dataIndex) : delayOption; - const durationOption = seriesModel.get('animationDurationUpdate'); - duration = isFunction(durationOption) ? durationOption(dataIndex) : durationOption; - } - } - - config = { - duration: duration || 0, - delay: delay, - easing: easing, - dividingMethod: transOpt ? transOpt.dividingMethod : null - }; - morphConfigList[dataIndex] = config; - - return config; - } - - reset(type: MorphPreparationType): void { - // `this._morphConfigList` can be kept. It only related to `dataIndex`. - this._type = type; - this._fromList.length = - this._toList.length = - this._toElOptionList.length = - this._allPropsFinalList.length = - this._toDataIndices.length = 0; - } -} - -function copyPropsWhenDivided( - srcPath: graphicUtil.Path, - tarPath: graphicUtil.Path, - willClone: boolean -): void { - // Do not copy transform props. - // Sub paths are transfrom based on their host path. - // tarPath.x = srcPath.x; - // tarPath.y = srcPath.y; - // tarPath.scaleX = srcPath.scaleX; - // tarPath.scaleY = srcPath.scaleY; - // tarPath.originX = srcPath.originX; - // tarPath.originY = srcPath.originY; - - // If just carry the style, will not be modifed, so do not copy. - tarPath.style = willClone - ? clone(srcPath.style) - : srcPath.style; - - tarPath.zlevel = srcPath.zlevel; - tarPath.z = srcPath.z; - tarPath.z2 = srcPath.z2; -} +use(install); \ No newline at end of file diff --git a/src/chart/custom/install.ts b/src/chart/custom/install.ts new file mode 100644 index 0000000000..e95d1ae005 --- /dev/null +++ b/src/chart/custom/install.ts @@ -0,0 +1,2784 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { + hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, + keys, isArrayLike, bind, isFunction, eqNaN, indexOf, clone +} from 'zrender/src/core/util'; +import * as graphicUtil from '../../util/graphic'; +import { setDefaultStateProxy, enableHoverEmphasis } from '../../util/states'; +import * as labelStyleHelper from '../../label/labelStyle'; +import {getDefaultLabel} from '../helper/labelHelper'; +import createListFromArray from '../helper/createListFromArray'; +import {getLayoutOnAxis, BarGridLayoutResult, BarGridLayoutOptionForCustomSeries} from '../../layout/barGrid'; +import DataDiffer, { DataDiffMode } from '../../data/DataDiffer'; +import SeriesModel from '../../model/Series'; +import Model from '../../model/Model'; +import ChartView from '../../view/Chart'; +import {createClipPath} from '../helper/createClipPathFromCoordSys'; +import { + EventQueryItem, ECEvent, SeriesOption, SeriesOnCartesianOptionMixin, + SeriesOnPolarOptionMixin, SeriesOnSingleOptionMixin, SeriesOnGeoOptionMixin, + SeriesOnCalendarOptionMixin, ItemStyleOption, SeriesEncodeOptionMixin, + DimensionLoose, + ParsedValue, + Dictionary, + CallbackDataParams, + Payload, + StageHandlerProgressParams, + LabelOption, + ViewRootGroup, + OptionDataValue, + ZRStyleProps, + DisplayState, + ECElement, + DisplayStateNonNormal, + BlurScope, + SeriesDataType, + OrdinalRawValue, + PayloadAnimationPart, + DecalObject, + InnerDecalObject, + TextCommonOption +} from '../../util/types'; +import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element'; +import prepareCartesian2d from '../../coord/cartesian/prepareCustom'; +import prepareGeo from '../../coord/geo/prepareCustom'; +import prepareSingleAxis from '../../coord/single/prepareCustom'; +import preparePolar from '../../coord/polar/prepareCustom'; +import prepareCalendar from '../../coord/calendar/prepareCustom'; +import List, { DefaultDataVisual } from '../../data/List'; +import GlobalModel from '../../model/Global'; +import { makeInner, normalizeToArray } from '../../util/model'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import Displayable from 'zrender/src/graphic/Displayable'; +import Axis2D from '../../coord/cartesian/Axis2D'; +import { RectLike } from 'zrender/src/core/BoundingRect'; +import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path'; +import { ImageStyleProps } from 'zrender/src/graphic/Image'; +import { CoordinateSystem } from '../../coord/CoordinateSystem'; +import { TextStyleProps } from 'zrender/src/graphic/Text'; +import { + convertToEC4StyleForCustomSerise, + isEC4CompatibleStyle, + convertFromEC4CompatibleStyle, + LegacyStyleProps, + warnDeprecated +} from '../../util/styleCompat'; +import Transformable from 'zrender/src/core/Transformable'; +import { ItemStyleProps } from '../../model/mixin/itemStyle'; +import { cloneValue } from 'zrender/src/animation/Animator'; +import { warn, throwError } from '../../util/log'; +import { + combine, isInAnyMorphing, morphPath, isCombiningPath, CombineSeparateConfig, separate, CombineSeparateResult +} from 'zrender/src/tool/morphPath'; +import { AnimationEasing } from 'zrender/src/animation/easing'; +import * as matrix from 'zrender/src/core/matrix'; +import { PatternObject } from 'zrender/src/graphic/Pattern'; +import { createOrUpdatePatternFromDecal } from '../../util/decal'; +import { ZRenderType } from 'zrender/src/zrender'; +import { EChartsExtensionInstallRegisters } from '../../extension'; + + +const inner = makeInner<{ + info: CustomExtraElementInfo; + customPathData: string; + customGraphicType: string; + customImagePath: CustomImageOption['style']['image']; + // customText: string; + txConZ2Set: number; + leaveToProps: ElementProps; + // Can morph: "morph" specified in option and el is Path. + canMorph: boolean; + userDuring: CustomBaseElementOption['during']; +}, Element>(); + +type CustomExtraElementInfo = Dictionary; +const TRANSFORM_PROPS = { + x: 1, + y: 1, + scaleX: 1, + scaleY: 1, + originX: 1, + originY: 1, + rotation: 1 +} as const; +type TransformProp = keyof typeof TRANSFORM_PROPS; +const transformPropNamesStr = keys(TRANSFORM_PROPS).join(', '); + +// Do not declare "Dictionary" in TransitionAnyOption to restrict the type check. +type TransitionAnyOption = { + transition?: TransitionAnyProps; + enterFrom?: Dictionary; + leaveTo?: Dictionary; +}; +type TransitionAnyProps = string | string[]; +type TransitionTransformOption = { + transition?: ElementRootTransitionProp | ElementRootTransitionProp[]; + enterFrom?: Dictionary; + leaveTo?: Dictionary; +}; +type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style'; +type ShapeMorphingOption = { + /** + * If do shape morphing animation when type is changed. + * Only available on path. + */ + morph?: boolean +}; + +interface CustomBaseElementOption extends Partial>, TransitionTransformOption { + // element type, mandatory. + type: string; + id?: string; + // For animation diff. + name?: string; + info?: CustomExtraElementInfo; + // `false` means remove the textContent. + textContent?: CustomTextOption | false; + // `false` means remove the clipPath + clipPath?: CustomZRPathOption | false; + // `extra` can be set in any el option for custom prop for annimation duration. + extra?: TransitionAnyOption; + // updateDuringAnimation + during?(params: typeof customDuringAPI): void; + + focus?: 'none' | 'self' | 'series' | ArrayLike + blurScope?: BlurScope +}; +interface CustomDisplayableOption extends CustomBaseElementOption, Partial> { + style?: ZRStyleProps & TransitionAnyOption; + // `false` means remove emphasis trigger. + styleEmphasis?: ZRStyleProps | false; + emphasis?: CustomDisplayableOptionOnState; + blur?: CustomDisplayableOptionOnState; + select?: CustomDisplayableOptionOnState; +} +interface CustomDisplayableOptionOnState extends Partial> { + // `false` means remove emphasis trigger. + style?: (ZRStyleProps & TransitionAnyOption) | false; +} +interface CustomGroupOption extends CustomBaseElementOption { + type: 'group'; + width?: number; + height?: number; + // @deprecated + diffChildrenByName?: boolean; + // Can only set focus, blur on the root element. + children: Omit[]; + $mergeChildren: false | 'byName' | 'byIndex'; +} +interface CustomZRPathOption extends CustomDisplayableOption, ShapeMorphingOption { + shape?: PathProps['shape'] & TransitionAnyOption; + style?: CustomDisplayableOption['style'] & { + decal?: DecalObject; + // Only internal usage. Any user specified value will be overwritten. + __decalPattern?: PatternObject; + }; +} +interface CustomSVGPathOption extends CustomDisplayableOption, ShapeMorphingOption { + type: 'path'; + shape?: { + // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z' + pathData?: string; + // "d" is the alias of `pathData` follows the SVG convention. + d?: string; + layout?: 'center' | 'cover'; + x?: number; + y?: number; + width?: number; + height?: number; + } & TransitionAnyOption; +} +interface CustomImageOption extends CustomDisplayableOption { + type: 'image'; + style?: ImageStyleProps & TransitionAnyOption; + emphasis?: CustomImageOptionOnState; + blur?: CustomImageOptionOnState; + select?: CustomImageOptionOnState; +} +interface CustomImageOptionOnState extends CustomDisplayableOptionOnState { + style?: ImageStyleProps & TransitionAnyOption; +} +interface CustomTextOption extends CustomDisplayableOption { + type: 'text'; +} +type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption; +type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState; + + +export interface CustomSeriesRenderItemAPI extends + CustomSeriesRenderItemCoordinateSystemAPI { + + // Methods from ExtensionAPI. + // NOTE: Not using Pick here because we don't want to bundle ExtensionAPI into the d.ts + getWidth(): number + getHeight(): number + getZr(): ZRenderType + getDevicePixelRatio(): number + + value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue; + ordinalRawValue(dim: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue; + style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps; + styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps; + visual( + visualType: VT, + dataIndexInside?: number + ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT] + : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]] + : void; + barLayout(opt: BarGridLayoutOptionForCustomSeries): BarGridLayoutResult; + currentSeriesIndices(): number[]; + font(opt: Pick): string; +} +interface CustomSeriesRenderItemParamsCoordSys { + type: string; + // And extra params for each coordinate systems. +} +interface CustomSeriesRenderItemCoordinateSystemAPI { + coord( + data: OptionDataValue | OptionDataValue[], + clamp?: boolean + ): number[]; + size?( + dataSize: OptionDataValue | OptionDataValue[], + dataItem: OptionDataValue | OptionDataValue[] + ): number | number[]; +} +export interface CustomSeriesRenderItemParams { + context: Dictionary; + seriesId: string; + seriesName: string; + seriesIndex: number; + coordSys: CustomSeriesRenderItemParamsCoordSys; + dataInsideLength: number; + encode: WrapEncodeDefRet; +} +type CustomSeriesRenderItem = ( + params: CustomSeriesRenderItemParams, + api: CustomSeriesRenderItemAPI +) => CustomElementOption; + +interface CustomSeriesStateOption { + itemStyle?: ItemStyleOption; + label?: LabelOption; +} + +export interface CustomSeriesOption extends + SeriesOption, // don't support StateOption in custom series. + SeriesEncodeOptionMixin, + SeriesOnCartesianOptionMixin, + SeriesOnPolarOptionMixin, + SeriesOnSingleOptionMixin, + SeriesOnGeoOptionMixin, + SeriesOnCalendarOptionMixin { + + type?: 'custom' + + // If set as 'none', do not depends on coord sys. + coordinateSystem?: string | 'none'; + + renderItem?: CustomSeriesRenderItem; + + // Only works on polar and cartesian2d coordinate system. + clip?: boolean; +} + +interface LegacyCustomSeriesOption extends SeriesOption, CustomSeriesStateOption {} + + +interface LooseElementProps extends ElementProps { + style?: ZRStyleProps; + shape?: Dictionary; +} + +// Also compat with ec4, where +// `visual('color') visual('borderColor')` is supported. +const STYLE_VISUAL_TYPE = { + color: 'fill', + borderColor: 'stroke' +} as const; +type StyleVisualProps = keyof typeof STYLE_VISUAL_TYPE; + +const NON_STYLE_VISUAL_PROPS = { + symbol: 1, + symbolSize: 1, + symbolKeepAspect: 1, + legendSymbol: 1, + visualMeta: 1, + liftZ: 1, + decal: 1 +} as const; +type NonStyleVisualProps = keyof typeof NON_STYLE_VISUAL_PROPS; + +const EMPHASIS = 'emphasis' as const; +const NORMAL = 'normal' as const; +const BLUR = 'blur' as const; +const SELECT = 'select' as const; +const STATES = [NORMAL, EMPHASIS, BLUR, SELECT] as const; +const PATH_ITEM_STYLE = { + normal: ['itemStyle'], + emphasis: [EMPHASIS, 'itemStyle'], + blur: [BLUR, 'itemStyle'], + select: [SELECT, 'itemStyle'] +} as const; +const PATH_LABEL = { + normal: ['label'], + emphasis: [EMPHASIS, 'label'], + blur: [BLUR, 'label'], + select: [SELECT, 'label'] +} as const; +// Use prefix to avoid index to be the same as el.name, +// which will cause weird update animation. +const GROUP_DIFF_PREFIX = 'e\0\0'; + +type AttachedTxInfo = { + isLegacy: boolean; + normal: { + cfg: ElementTextConfig; + conOpt: CustomElementOption | false; + }; + emphasis: { + cfg: ElementTextConfig; + conOpt: CustomElementOptionOnState; + }; + blur: { + cfg: ElementTextConfig; + conOpt: CustomElementOptionOnState; + }; + select: { + cfg: ElementTextConfig; + conOpt: CustomElementOptionOnState; + }; +}; +const attachedTxInfoTmp = { + normal: {}, + emphasis: {}, + blur: {}, + select: {} +} as AttachedTxInfo; + +const LEGACY_TRANSFORM_PROPS = { + position: ['x', 'y'], + scale: ['scaleX', 'scaleY'], + origin: ['originX', 'originY'] +} as const; +type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS; + +export type PrepareCustomInfo = (coordSys: CoordinateSystem) => { + coordSys: CustomSeriesRenderItemParamsCoordSys; + api: CustomSeriesRenderItemCoordinateSystemAPI +}; + +const tmpTransformable = new Transformable(); + +/** + * To reduce total package size of each coordinate systems, the modules `prepareCustom` + * of each coordinate systems are not required by each coordinate systems directly, but + * required by the module `custom`. + * + * prepareInfoForCustomSeries {Function}: optional + * @return {Object} {coordSys: {...}, api: { + * coord: function (data, clamp) {}, // return point in global. + * size: function (dataSize, dataItem) {} // return size of each axis in coordSys. + * }} + */ +const prepareCustoms: Dictionary = { + cartesian2d: prepareCartesian2d, + geo: prepareGeo, + singleAxis: prepareSingleAxis, + polar: preparePolar, + calendar: prepareCalendar +}; + +class CustomSeriesModel extends SeriesModel { + + static type = 'series.custom'; + readonly type = CustomSeriesModel.type; + + static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; + + // preventAutoZ = true; + + currentZLevel: number; + currentZ: number; + + static defaultOption: CustomSeriesOption = { + coordinateSystem: 'cartesian2d', // Can be set as 'none' + zlevel: 0, + z: 2, + legendHoverLink: true, + + // Custom series will not clip by default. + // Some case will use custom series to draw label + // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight + clip: false + + // Cartesian coordinate system + // xAxisIndex: 0, + // yAxisIndex: 0, + + // Polar coordinate system + // polarIndex: 0, + + // Geo coordinate system + // geoIndex: 0, + }; + + optionUpdated() { + this.currentZLevel = this.get('zlevel', true); + this.currentZ = this.get('z', true); + } + + getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List { + return createListFromArray(this.getSource(), this); + } + + getDataParams(dataIndex: number, dataType?: SeriesDataType, el?: Element): CallbackDataParams & { + info: CustomExtraElementInfo + } { + const params = super.getDataParams(dataIndex, dataType) as ReturnType; + el && (params.info = inner(el).info); + return params; + } +} + + + +class CustomSeriesView extends ChartView { + + static type = 'custom'; + readonly type = CustomSeriesView.type; + + private _data: List; + + + render( + customSeries: CustomSeriesModel, + ecModel: GlobalModel, + api: ExtensionAPI, + payload: Payload + ): void { + const oldData = this._data; + const data = customSeries.getData(); + const group = this.group; + const renderItem = makeRenderItem(customSeries, data, ecModel, api); + + // By default, merge mode is applied. In most cases, custom series is + // used in the scenario that data amount is not large but graphic elements + // is complicated, where merge mode is probably necessary for optimization. + // For example, reuse graphic elements and only update the transform when + // roam or data zoom according to `actionType`. + + const transOpt = customSeries.__transientTransitionOpt; + + // Enable user to disable transition animation by both set + // `from` and `to` dimension as `null`/`undefined`. + if (transOpt && (transOpt.from == null || transOpt.to == null)) { + oldData && oldData.each(function (oldIdx) { + doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group); + }); + data.each(function (newIdx) { + createOrUpdateItem( + api, null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null + ); + }); + } + else { + const morphPreparation = new MorphPreparation(customSeries, transOpt); + const diffMode: DataDiffMode = transOpt ? 'multiple' : 'oneToOne'; + + (new DataDiffer( + oldData ? oldData.getIndices() : [], + data.getIndices(), + createGetKey(oldData, diffMode, transOpt && transOpt.from), + createGetKey(data, diffMode, transOpt && transOpt.to), + null, + diffMode + )) + .add(function (newIdx) { + createOrUpdateItem( + api, null, newIdx, renderItem(newIdx, payload), customSeries, group, + data, null + ); + }) + .remove(function (oldIdx) { + doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group); + }) + .update(function (newIdx, oldIdx) { + morphPreparation.reset('oneToOne'); + let oldEl = oldData.getItemGraphicEl(oldIdx); + morphPreparation.findAndAddFrom(oldEl); + + // PENDING: + // if may morph, currently we alway recreate the whole el. + // because if reuse some of the el in the group tree, the old el has to + // be removed from the group, and consequently we can not calculate + // the "global transition" of the old element. + // But is there performance issue? + if (morphPreparation.hasFrom()) { + removeElementDirectly(oldEl, group); + oldEl = null; + } + createOrUpdateItem( + api, oldEl, newIdx, renderItem(newIdx, payload), customSeries, group, + data, morphPreparation + ); + morphPreparation.applyMorphing(); + }) + .updateManyToOne(function (newIdx, oldIndices) { + morphPreparation.reset('manyToOne'); + for (let i = 0; i < oldIndices.length; i++) { + const oldEl = oldData.getItemGraphicEl(oldIndices[i]); + morphPreparation.findAndAddFrom(oldEl); + removeElementDirectly(oldEl, group); + } + createOrUpdateItem( + api, null, newIdx, renderItem(newIdx, payload), customSeries, group, + data, morphPreparation + ); + morphPreparation.applyMorphing(); + }) + .updateOneToMany(function (newIndices, oldIdx) { + morphPreparation.reset('oneToMany'); + const newLen = newIndices.length; + const oldEl = oldData.getItemGraphicEl(oldIdx); + morphPreparation.findAndAddFrom(oldEl); + removeElementDirectly(oldEl, group); + + for (let i = 0; i < newLen; i++) { + createOrUpdateItem( + api, null, newIndices[i], renderItem(newIndices[i], payload), customSeries, group, + data, morphPreparation + ); + } + morphPreparation.applyMorphing(); + }) + .execute(); + } + + // Do clipping + const clipPath = customSeries.get('clip', true) + ? createClipPath(customSeries.coordinateSystem, false, customSeries) + : null; + if (clipPath) { + group.setClipPath(clipPath); + } + else { + group.removeClipPath(); + } + + this._data = data; + } + + incrementalPrepareRender( + customSeries: CustomSeriesModel, + ecModel: GlobalModel, + api: ExtensionAPI + ): void { + this.group.removeAll(); + this._data = null; + } + + incrementalRender( + params: StageHandlerProgressParams, + customSeries: CustomSeriesModel, + ecModel: GlobalModel, + api: ExtensionAPI, + payload: Payload + ): void { + const data = customSeries.getData(); + const renderItem = makeRenderItem(customSeries, data, ecModel, api); + function setIncrementalAndHoverLayer(el: Displayable) { + if (!el.isGroup) { + el.incremental = true; + el.ensureState('emphasis').hoverLayer = true; + } + } + for (let idx = params.start; idx < params.end; idx++) { + const el = createOrUpdateItem( + null, null, idx, renderItem(idx, payload), customSeries, this.group, data, null + ); + el.traverse(setIncrementalAndHoverLayer); + } + } + + filterForExposedEvent( + eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECEvent + ): boolean { + const elementName = query.element; + if (elementName == null || targetEl.name === elementName) { + return true; + } + + // Enable to give a name on a group made by `renderItem`, and listen + // events that triggerd by its descendents. + while ((targetEl = (targetEl.__hostTarget || targetEl.parent)) && targetEl !== this.group) { + if (targetEl.name === elementName) { + return true; + } + } + + return false; + } +} + + +function createGetKey( + data: List, + diffMode: DataDiffMode, + dimension: DimensionLoose +) { + if (!data) { + return; + } + + if (diffMode === 'oneToOne') { + return function (rawIdx: number, dataIndex: number) { + return data.getId(dataIndex); + }; + } + + const diffByDimName = data.getDimension(dimension); + const dimInfo = data.getDimensionInfo(diffByDimName); + + if (!dimInfo) { + let errMsg = ''; + if (__DEV__) { + errMsg = `${dimension} is not a valid dimension.`; + } + throwError(errMsg); + } + const ordinalMeta = dimInfo.ordinalMeta; + return function (rawIdx: number, dataIndex: number) { + let key = data.get(diffByDimName, dataIndex); + if (ordinalMeta) { + key = ordinalMeta.categories[key as number]; + } + return (key == null || eqNaN(key)) + ? rawIdx + '' + : '_ec_' + key; + }; +} + + +function createEl(elOption: CustomElementOption): Element { + const graphicType = elOption.type; + let el; + + // Those graphic elements are not shapes. They should not be + // overwritten by users, so do them first. + if (graphicType === 'path') { + const shape = (elOption as CustomSVGPathOption).shape; + // Using pathRect brings convenience to users sacle svg path. + const pathRect = (shape.width != null && shape.height != null) + ? { + x: shape.x || 0, + y: shape.y || 0, + width: shape.width, + height: shape.height + } as RectLike + : null; + const pathData = getPathData(shape); + // Path is also used for icon, so layout 'center' by default. + el = graphicUtil.makePath(pathData, null, pathRect, shape.layout || 'center'); + inner(el).customPathData = pathData; + } + else if (graphicType === 'image') { + el = new graphicUtil.Image({}); + inner(el).customImagePath = (elOption as CustomImageOption).style.image; + } + else if (graphicType === 'text') { + el = new graphicUtil.Text({}); + // inner(el).customText = (elOption.style as TextStyleProps).text; + } + else if (graphicType === 'group') { + el = new graphicUtil.Group(); + } + else if (graphicType === 'compoundPath') { + throw new Error('"compoundPath" is not supported yet.'); + } + else { + const Clz = graphicUtil.getShapeClass(graphicType); + if (!Clz) { + let errMsg = ''; + if (__DEV__) { + errMsg = 'graphic type "' + graphicType + '" can not be found.'; + } + throwError(errMsg); + } + el = new Clz(); + } + + inner(el).customGraphicType = graphicType; + el.name = elOption.name; + + // Compat ec4: the default z2 lift is 1. If changing the number, + // some cases probably be broken: hierarchy layout along z, like circle packing, + // where emphasis only intending to modify color/border rather than lift z2. + (el as ECElement).z2EmphasisLift = 1; + (el as ECElement).z2SelectLift = 1; + + return el; +} + +/** + * ---------------------------------------------------------- + * [STRATEGY_MERGE] Merge properties or erase all properties: + * + * Based on the fact that the existing zr element probably be reused, we now consider whether + * merge or erase all properties to the exsiting elements. + * That is, if a certain props is not specified in the lastest return of `renderItem`: + * + "Merge" means that do not modify the value on the existing element. + * + "Erase all" means that use a default value to the existing element. + * + * "Merge" might bring some unexpected state retaining for users and "erase all" seams to be + * more safe. "erase all" force users to specify all of the props each time, which is recommanded + * in most cases. + * But "erase all" theoretically disables the chance of performance optimization (e.g., just + * generete shape and style at the first time rather than always do that). + * So we still use "merge" rather than "erase all". If users need "erase all", they can + * simple always set all of the props each time. + * Some "object-like" config like `textConfig`, `textContent`, `style` which are not needed for + * every elment, so we replace them only when user specify them. And the that is a total replace. + * + * TODO: there is no hint of 'isFirst' to users. So the performance enhancement can not be + * performed yet. Consider the case: + * (1) setOption to "mergeChildren" with a smaller children count + * (2) Use dataZoom to make an item disappear. + * (3) User dataZoom to make the item display again. At that time, renderItem need to return the + * full option rather than partial option to recreate the element. + * + * ---------------------------------------------- + * [STRATEGY_NULL] `hasOwnProperty` or `== null`: + * + * Ditinguishing "own property" probably bring little trouble to user when make el options. + * So we trade a {xx: null} or {xx: undefined} as "not specified" if possible rather than + * "set them to null/undefined". In most cases, props can not be cleared. Some typicall + * clearable props like `style`/`textConfig`/`textContent` we enable `false` to means + * "clear". In some othere special cases that the prop is able to set as null/undefined, + * but not suitable to use `false`, `hasOwnProperty` is checked. + * + * --------------------------------------------- + * [STRATEGY_TRANSITION] The rule of transition: + * + For props on the root level of a element: + * If there is no `transition` specified, tansform props will be transitioned by default, + * which is the same as the previous setting in echarts4 and suitable for the scenario + * of dataZoom change. + * If `transition` specified, only the specified props will be transitioned. + * + For props in `shape` and `style`: + * Only props specified in `transition` will be transitioned. + * + Break: + * Since ec5, do not make transition to shape by default, because it might result in + * performance issue (especially `points` of polygon) and do not necessary in most cases. + * + * @return if `isMorphTo`, return `allPropsFinal`. + */ +function updateElNormal( + // Can be null/undefined + api: ExtensionAPI, + el: Element, + // Whether be a morph target. + isMorphTo: boolean, + dataIndex: number, + elOption: CustomElementOption, + styleOpt: CustomElementOption['style'], + attachedTxInfo: AttachedTxInfo, + seriesModel: CustomSeriesModel, + isInit: boolean, + isTextContent: boolean +): ElementProps { + const transFromProps = {} as ElementProps; + const allPropsFinal = {} as ElementProps; + const elDisplayable = el.isGroup ? null : el as Displayable; + + // If be "morph to", delay the `updateElNormal` when all of the els in + // this data item processed. Because at that time we can get all of the + // "morph from" and make correct separate/combine. + + !isMorphTo && prepareShapeOrExtraTransitionFrom('shape', el, null, elOption, transFromProps, isInit); + prepareShapeOrExtraAllPropsFinal('shape', elOption, allPropsFinal); + !isMorphTo && prepareShapeOrExtraTransitionFrom('extra', el, null, elOption, transFromProps, isInit); + prepareShapeOrExtraAllPropsFinal('extra', elOption, allPropsFinal); + !isMorphTo && prepareTransformTransitionFrom(el, null, elOption, transFromProps, isInit); + prepareTransformAllPropsFinal(elOption, allPropsFinal); + + const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg; + if (txCfgOpt) { + // PENDING: whether use user object directly rather than clone? + // TODO:5.0 textConfig transition animation? + el.setTextConfig(txCfgOpt); + } + + if (el.type === 'text' && styleOpt) { + const textOptionStyle = styleOpt as TextStyleProps; + // Compatible with ec4: if `textFill` or `textStroke` exists use them. + hasOwn(textOptionStyle, 'textFill') && ( + textOptionStyle.fill = (textOptionStyle as any).textFill + ); + hasOwn(textOptionStyle, 'textStroke') && ( + textOptionStyle.stroke = (textOptionStyle as any).textStroke + ); + } + + if (styleOpt) { + let decalPattern; + const decalObj = isPath(el) ? (styleOpt as CustomZRPathOption['style']).decal : null; + if (api && decalObj) { + (decalObj as InnerDecalObject).dirty = true; + decalPattern = createOrUpdatePatternFromDecal(decalObj, api); + } + // Always overwrite in case user specify this prop. + (styleOpt as CustomZRPathOption['style']).__decalPattern = decalPattern; + } + + !isMorphTo && prepareStyleTransitionFrom(el, null, elOption, styleOpt, transFromProps, isInit); + + if (elDisplayable) { + hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible); + } + + // If `isMorphTo`, we should not update these props to el directly, otherwise, + // when applying morph finally, the original prop are missing for making "animation from". + if (!isMorphTo) { + applyPropsFinal(el, allPropsFinal, styleOpt); + applyTransitionFrom(el, dataIndex, elOption, seriesModel, transFromProps, isInit); + } + + // Merge by default. + hasOwn(elOption, 'silent') && (el.silent = elOption.silent); + hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); + + if (!isTextContent) { + // `elOption.info` enables user to mount some info on + // elements and use them in event handlers. + // Update them only when user specified, otherwise, remain. + hasOwn(elOption, 'info') && (inner(el).info = elOption.info); + } + + styleOpt ? el.dirty() : el.markRedraw(); + + return isMorphTo ? allPropsFinal : null; +} + +function applyPropsFinal( + el: Element, + // Can be null/undefined + allPropsFinal: ElementProps, + styleOpt: CustomElementOption['style'] +) { + const elDisplayable = el.isGroup ? null : el as Displayable; + + if (elDisplayable && styleOpt) { + + const decalPattern = (styleOpt as CustomZRPathOption['style']).__decalPattern; + let originalDecalObj; + if (decalPattern) { + originalDecalObj = (styleOpt as CustomZRPathOption['style']).decal; + (styleOpt as any).decal = decalPattern; + } + + // PENDING: here the input style object is used directly. + // Good for performance but bad for compatibility control. + elDisplayable.useStyle(styleOpt); + + if (decalPattern) { + (styleOpt as CustomZRPathOption['style']).decal = originalDecalObj; + } + + // When style object changed, how to trade the existing animation? + // It is probably conplicated and not needed to cover all the cases. + // But still need consider the case: + // (1) When using init animation on `style.opacity`, and before the animation + // ended users triggers an update by mousewhell. At that time the init + // animation should better be continued rather than terminated. + // So after `useStyle` called, we should change the animation target manually + // to continue the effect of the init animation. + // (2) PENDING: If the previous animation targeted at a `val1`, and currently we need + // to update the value to `val2` and no animation declared, should be terminate + // the previous animation or just modify the target of the animation? + // Therotically That will happen not only on `style` but also on `shape` and + // `transfrom` props. But we haven't handle this case at present yet. + // (3) PENDING: Is it proper to visit `animators` and `targetName`? + const animators = elDisplayable.animators; + for (let i = 0; i < animators.length; i++) { + const animator = animators[i]; + // targetName is the "topKey". + if (animator.targetName === 'style') { + animator.changeTarget(elDisplayable.style); + } + } + } + + // Set el to the final state firstly. + allPropsFinal && el.attr(allPropsFinal); +} + +function applyTransitionFrom( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + // Can be null/undefined + transFromProps: ElementProps, + isInit: boolean +): void { + if (transFromProps) { + // Do not use `el.updateDuringAnimation` here becuase `el.updateDuringAnimation` will + // be called mutiple time in each animation frame. For example, if both "transform" props + // and shape props and style props changed, it will generate three animator and called + // one-by-one in each animation frame. + // We use the during in `animateTo/From` params. + const userDuring = elOption.during; + // For simplicity, if during not specified, the previous during will not work any more. + inner(el).userDuring = userDuring; + const cfgDuringCall = userDuring ? bind(duringCall, { el: el, userDuring: userDuring }) : null; + const cfg = { + dataIndex: dataIndex, + isFrom: true, + during: cfgDuringCall + }; + isInit + ? graphicUtil.initProps(el, transFromProps, seriesModel, cfg) + : graphicUtil.updateProps(el, transFromProps, seriesModel, cfg); + } +} + + +// See [STRATEGY_TRANSITION] +function prepareShapeOrExtraTransitionFrom( + mainAttr: 'shape' | 'extra', + el: Element, + morphFromEl: graphicUtil.Path, + elOption: CustomElementOption, + transFromProps: LooseElementProps, + isInit: boolean +): void { + + const attrOpt: Dictionary & TransitionAnyOption = (elOption as any)[mainAttr]; + if (!attrOpt) { + return; + } + + const elPropsInAttr = (el as LooseElementProps)[mainAttr]; + let transFromPropsInAttr: Dictionary; + + const enterFrom = attrOpt.enterFrom; + if (isInit && enterFrom) { + !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); + const enterFromKeys = keys(enterFrom); + for (let i = 0; i < enterFromKeys.length; i++) { + // `enterFrom` props are not necessarily also declared in `shape`/`style`/..., + // for example, `opacity` can only declared in `enterFrom` but not in `style`. + const key = enterFromKeys[i]; + // Do not clone, animator will perform that clone. + transFromPropsInAttr[key] = enterFrom[key]; + } + } + + if (!isInit + && elPropsInAttr + // Just ignore shape animation in morphing. + && !(morphFromEl != null && mainAttr === 'shape') + ) { + if (attrOpt.transition) { + !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); + const transitionKeys = normalizeToArray(attrOpt.transition); + for (let i = 0; i < transitionKeys.length; i++) { + const key = transitionKeys[i]; + const elVal = elPropsInAttr[key]; + if (__DEV__) { + checkNonStyleTansitionRefer(key, (attrOpt as any)[key], elVal); + } + // Do not clone, see `checkNonStyleTansitionRefer`. + transFromPropsInAttr[key] = elVal; + } + } + else if (indexOf(elOption.transition, mainAttr) >= 0) { + !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); + const elPropsInAttrKeys = keys(elPropsInAttr); + for (let i = 0; i < elPropsInAttrKeys.length; i++) { + const key = elPropsInAttrKeys[i]; + const elVal = elPropsInAttr[key]; + if (isNonStyleTransitionEnabled((attrOpt as any)[key], elVal)) { + transFromPropsInAttr[key] = elVal; + } + } + } + } + + const leaveTo = attrOpt.leaveTo; + if (leaveTo) { + const leaveToProps = getOrCreateLeaveToPropsFromEl(el); + const leaveToPropsInAttr: Dictionary = leaveToProps[mainAttr] || (leaveToProps[mainAttr] = {}); + const leaveToKeys = keys(leaveTo); + for (let i = 0; i < leaveToKeys.length; i++) { + const key = leaveToKeys[i]; + leaveToPropsInAttr[key] = leaveTo[key]; + } + } +} + +function prepareShapeOrExtraAllPropsFinal( + mainAttr: 'shape' | 'extra', + elOption: CustomElementOption, + allProps: LooseElementProps +): void { + const attrOpt: Dictionary & TransitionAnyOption = (elOption as any)[mainAttr]; + if (!attrOpt) { + return; + } + const allPropsInAttr = allProps[mainAttr] = {} as Dictionary; + const keysInAttr = keys(attrOpt); + for (let i = 0; i < keysInAttr.length; i++) { + const key = keysInAttr[i]; + // To avoid share one object with different element, and + // to avoid user modify the object inexpectedly, have to clone. + allPropsInAttr[key] = cloneValue((attrOpt as any)[key]); + } +} + +// See [STRATEGY_TRANSITION]. +function prepareTransformTransitionFrom( + el: Element, + morphFromEl: graphicUtil.Path, + elOption: CustomElementOption, + transFromProps: ElementProps, + isInit: boolean +): void { + const enterFrom = elOption.enterFrom; + if (isInit && enterFrom) { + const enterFromKeys = keys(enterFrom); + for (let i = 0; i < enterFromKeys.length; i++) { + const key = enterFromKeys[i] as TransformProp; + if (__DEV__) { + checkTransformPropRefer(key, 'el.enterFrom'); + } + // Do not clone, animator will perform that clone. + transFromProps[key] = enterFrom[key] as number; + } + } + + if (!isInit) { + // If morphing, force transition all transform props. + // otherwise might have incorrect morphing animation. + if (morphFromEl) { + const fromTransformable = calcOldElLocalTransformBasedOnNewElParent(morphFromEl, el); + setTransformPropToTransitionFrom(transFromProps, 'x', fromTransformable); + setTransformPropToTransitionFrom(transFromProps, 'y', fromTransformable); + setTransformPropToTransitionFrom(transFromProps, 'scaleX', fromTransformable); + setTransformPropToTransitionFrom(transFromProps, 'scaleY', fromTransformable); + setTransformPropToTransitionFrom(transFromProps, 'originX', fromTransformable); + setTransformPropToTransitionFrom(transFromProps, 'originY', fromTransformable); + setTransformPropToTransitionFrom(transFromProps, 'rotation', fromTransformable); + } + else if (elOption.transition) { + const transitionKeys = normalizeToArray(elOption.transition); + for (let i = 0; i < transitionKeys.length; i++) { + const key = transitionKeys[i]; + if (key === 'style' || key === 'shape' || key === 'extra') { + continue; + } + const elVal = el[key]; + if (__DEV__) { + checkTransformPropRefer(key, 'el.transition'); + checkNonStyleTansitionRefer(key, elOption[key], elVal); + } + // Do not clone, see `checkNonStyleTansitionRefer`. + transFromProps[key] = elVal; + } + } + // This default transition see [STRATEGY_TRANSITION] + else { + setTransformPropToTransitionFrom(transFromProps, 'x', el); + setTransformPropToTransitionFrom(transFromProps, 'y', el); + } + } + + const leaveTo = elOption.leaveTo; + if (leaveTo) { + const leaveToProps = getOrCreateLeaveToPropsFromEl(el); + const leaveToKeys = keys(leaveTo); + for (let i = 0; i < leaveToKeys.length; i++) { + const key = leaveToKeys[i] as TransformProp; + if (__DEV__) { + checkTransformPropRefer(key, 'el.leaveTo'); + } + leaveToProps[key] = leaveTo[key] as number; + } + } +} + +function prepareTransformAllPropsFinal( + elOption: CustomElementOption, + allProps: ElementProps +): void { + setLagecyTransformProp(elOption, allProps, 'position'); + setLagecyTransformProp(elOption, allProps, 'scale'); + setLagecyTransformProp(elOption, allProps, 'origin'); + setTransformProp(elOption, allProps, 'x'); + setTransformProp(elOption, allProps, 'y'); + setTransformProp(elOption, allProps, 'scaleX'); + setTransformProp(elOption, allProps, 'scaleY'); + setTransformProp(elOption, allProps, 'originX'); + setTransformProp(elOption, allProps, 'originY'); + setTransformProp(elOption, allProps, 'rotation'); +} + +// See [STRATEGY_TRANSITION]. +function prepareStyleTransitionFrom( + el: Element, + morphFromEl: graphicUtil.Path, + elOption: CustomElementOption, + styleOpt: CustomElementOption['style'], + transFromProps: LooseElementProps, + isInit: boolean +): void { + if (!styleOpt) { + return; + } + + // At present in "many-to-one"/"one-to-many" case, to not support "many" have + // different styles and make style transitions. That might be a rare case. + const fromEl = morphFromEl || el; + + const fromElStyle = (fromEl as LooseElementProps).style as LooseElementProps['style']; + let transFromStyleProps: LooseElementProps['style']; + + const enterFrom = styleOpt.enterFrom; + if (isInit && enterFrom) { + const enterFromKeys = keys(enterFrom); + !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); + for (let i = 0; i < enterFromKeys.length; i++) { + const key = enterFromKeys[i]; + // Do not clone, animator will perform that clone. + (transFromStyleProps as any)[key] = enterFrom[key]; + } + } + + if (!isInit && fromElStyle) { + if (styleOpt.transition) { + const transitionKeys = normalizeToArray(styleOpt.transition); + !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); + for (let i = 0; i < transitionKeys.length; i++) { + const key = transitionKeys[i]; + const elVal = (fromElStyle as any)[key]; + // Do not clone, see `checkNonStyleTansitionRefer`. + (transFromStyleProps as any)[key] = elVal; + } + } + else if ( + (el as Displayable).getAnimationStyleProps + && indexOf(elOption.transition, 'style') >= 0 + ) { + const animationProps = (el as Displayable).getAnimationStyleProps(); + const animationStyleProps = animationProps ? animationProps.style : null; + if (animationStyleProps) { + !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); + const styleKeys = keys(styleOpt); + for (let i = 0; i < styleKeys.length; i++) { + const key = styleKeys[i]; + if ((animationStyleProps as Dictionary)[key]) { + const elVal = (fromElStyle as any)[key]; + (transFromStyleProps as any)[key] = elVal; + } + } + } + } + } + + const leaveTo = styleOpt.leaveTo; + if (leaveTo) { + const leaveToKeys = keys(leaveTo); + const leaveToProps = getOrCreateLeaveToPropsFromEl(el); + const leaveToStyleProps = leaveToProps.style || (leaveToProps.style = {}); + for (let i = 0; i < leaveToKeys.length; i++) { + const key = leaveToKeys[i]; + (leaveToStyleProps as any)[key] = leaveTo[key]; + } + } +} + +/** + * If make "transform"(x/y/scaleX/scaleY/orient/originX/originY) transition between + * two path elements that have different hierarchy, before we retrieve the "from" props, + * we have to calculate the local transition of the "oldPath" based on the parent of + * the "newPath". + * At present, the case only happend in "morphing". Without morphing, the transform + * transition are all between elements in the same hierarchy, where this kind of process + * is not needed. + * + * [CAVEAT]: + * This method makes sense only if: (very tricky) + * (1) "newEl" has been added to its final parent. + * (2) Local transform props of "newPath.parent" are not at their final value but already + * have been at the "from value". + * This is currently ensured by: + * (2.1) "graphicUtil.animationFrom", which will set the element to the "from value" + * immediately. + * (2.2) "morph" option is not allowed to be set on Group, so all of the groups have + * been finished their "updateElNormal" when calling this method in morphing process. + */ +function calcOldElLocalTransformBasedOnNewElParent(oldEl: Element, newEl: Element): Transformable { + if (!oldEl || oldEl === newEl || oldEl.parent === newEl.parent) { + return oldEl; + } + + // Not sure oldEl is rendered (may have "lazyUpdate"), + // so always call `getComputedTransform`. + const tmpM = tmpTransformable.transform + || (tmpTransformable.transform = matrix.identity([])); + + const oldGlobalTransform = oldEl.getComputedTransform(); + oldGlobalTransform + ? matrix.copy(tmpM, oldGlobalTransform) + : matrix.identity(tmpM); + + const newParent = newEl.parent; + if (newParent) { + newParent.getComputedTransform(); + } + + tmpTransformable.originX = oldEl.originX; + tmpTransformable.originY = oldEl.originY; + tmpTransformable.parent = newParent; + tmpTransformable.decomposeTransform(); + + return tmpTransformable; +} + +let checkNonStyleTansitionRefer: (propName: string, optVal: unknown, elVal: unknown) => void; +if (__DEV__) { + checkNonStyleTansitionRefer = function (propName: string, optVal: unknown, elVal: unknown): void { + if (!isArrayLike(optVal)) { + assert( + optVal != null && isFinite(optVal as number), + 'Prop `' + propName + '` must refer to a finite number or ArrayLike for transition.' + ); + } + else { + // Try not to copy array for performance, but if user use the same object in different + // call of `renderItem`, it will casue animation transition fail. + assert( + optVal !== elVal, + 'Prop `' + propName + '` must use different Array object each time for transition.' + ); + } + }; +} + +function isNonStyleTransitionEnabled(optVal: unknown, elVal: unknown): boolean { + // The same as `checkNonStyleTansitionRefer`. + return !isArrayLike(optVal) + ? (optVal != null && isFinite(optVal as number)) + : optVal !== elVal; +} + +let checkTransformPropRefer: (key: string, usedIn: string) => void; +if (__DEV__) { + checkTransformPropRefer = function (key: string, usedIn: string): void { + assert( + hasOwn(TRANSFORM_PROPS, key), + 'Prop `' + key + '` is not a permitted in `' + usedIn + '`. ' + + 'Only `' + keys(TRANSFORM_PROPS).join('`, `') + '` are permitted.' + ); + }; +} + +function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps { + const innerEl = inner(el); + return innerEl.leaveToProps || (innerEl.leaveToProps = {}); +} + +// Use it to avoid it be exposed to user. +const tmpDuringScope = {} as { + el: Element; + isShapeDirty: boolean; + isStyleDirty: boolean; +}; +const customDuringAPI = { + // Usually other props do not need to be changed in animation during. + setTransform(key: TransformProp, val: unknown) { + if (__DEV__) { + assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.'); + } + tmpDuringScope.el[key] = val as number; + return this; + }, + getTransform(key: TransformProp): unknown { + if (__DEV__) { + assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.'); + } + return tmpDuringScope.el[key]; + }, + setShape(key: string, val: unknown) { + if (__DEV__) { + assertNotReserved(key); + } + const shape = (tmpDuringScope.el as graphicUtil.Path).shape + || ((tmpDuringScope.el as graphicUtil.Path).shape = {}); + shape[key] = val; + tmpDuringScope.isShapeDirty = true; + return this; + }, + getShape(key: string): unknown { + if (__DEV__) { + assertNotReserved(key); + } + const shape = (tmpDuringScope.el as graphicUtil.Path).shape; + if (shape) { + return shape[key]; + } + }, + setStyle(key: string, val: unknown) { + if (__DEV__) { + assertNotReserved(key); + } + const style = (tmpDuringScope.el as Displayable).style; + if (style) { + if (__DEV__) { + if (eqNaN(val)) { + warn('style.' + key + ' must not be assigned with NaN.'); + } + } + style[key] = val; + tmpDuringScope.isStyleDirty = true; + } + return this; + }, + getStyle(key: string): unknown { + if (__DEV__) { + assertNotReserved(key); + } + const style = (tmpDuringScope.el as Displayable).style; + if (style) { + return style[key]; + } + }, + setExtra(key: string, val: unknown) { + if (__DEV__) { + assertNotReserved(key); + } + const extra = (tmpDuringScope.el as LooseElementProps).extra + || ((tmpDuringScope.el as LooseElementProps).extra = {}); + extra[key] = val; + return this; + }, + getExtra(key: string): unknown { + if (__DEV__) { + assertNotReserved(key); + } + const extra = (tmpDuringScope.el as LooseElementProps).extra; + if (extra) { + return extra[key]; + } + } +}; + +function assertNotReserved(key: string) { + if (__DEV__) { + if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') { + throw new Error('key must not be "' + key + '"'); + } + } +} + +function duringCall( + this: { + el: Element; + userDuring: CustomBaseElementOption['during'] + } +): void { + // Do not provide "percent" until some requirements come. + // Because consider thies case: + // enterFrom: {x: 100, y: 30}, transition: 'x'. + // And enter duration is different from update duration. + // Thus it might be confused about the meaning of "percent" in during callback. + const scope = this; + const el = scope.el; + if (!el) { + return; + } + // If el is remove from zr by reason like legend, during still need to called, + // becuase el will be added back to zr and the prop value should not be incorrect. + + const newstUserDuring = inner(el).userDuring; + const scopeUserDuring = scope.userDuring; + // Ensured a during is only called once in each animation frame. + // If a during is called multiple times in one frame, maybe some users' calulation logic + // might be wrong (not sure whether this usage exists). + // The case of a during might be called twice can be: by default there is a animator for + // 'x', 'y' when init. Before the init animation finished, call `setOption` to start + // another animators for 'style'/'shape'/'extra'. + if (newstUserDuring !== scopeUserDuring) { + // release + scope.el = scope.userDuring = null; + return; + } + + tmpDuringScope.el = el; + tmpDuringScope.isShapeDirty = false; + tmpDuringScope.isStyleDirty = false; + + // Give no `this` to user in "during" calling. + scopeUserDuring(customDuringAPI); + + if (tmpDuringScope.isShapeDirty && (el as graphicUtil.Path).dirtyShape) { + (el as graphicUtil.Path).dirtyShape(); + } + if (tmpDuringScope.isStyleDirty && (el as Displayable).dirtyStyle) { + (el as Displayable).dirtyStyle(); + } + // markRedraw() will be called by default in during. + // FIXME `this.markRedraw();` directly ? + + // FIXME: if in future meet the case that some prop will be both modified in `during` and `state`, + // consider the issue that the prop might be incorrect when return to "normal" state. +} + +function updateElOnState( + state: DisplayStateNonNormal, + el: Element, + elStateOpt: CustomElementOptionOnState, + styleOpt: CustomElementOptionOnState['style'], + attachedTxInfo: AttachedTxInfo, + isRoot: boolean, + isTextContent: boolean +): void { + const elDisplayable = el.isGroup ? null : el as Displayable; + const txCfgOpt = attachedTxInfo && attachedTxInfo[state].cfg; + + // PENDING:5.0 support customize scale change and transition animation? + + if (elDisplayable) { + // By default support auto lift color when hover whether `emphasis` specified. + const stateObj = elDisplayable.ensureState(state); + if (styleOpt === false) { + const existingEmphasisState = elDisplayable.getState(state); + if (existingEmphasisState) { + existingEmphasisState.style = null; + } + } + else { + // style is needed to enable defaut emphasis. + stateObj.style = styleOpt || null; + } + // If `elOption.styleEmphasis` or `elOption.emphasis.style` is `false`, + // remove hover style. + // If `elOption.textConfig` or `elOption.emphasis.textConfig` is null/undefined, it does not + // make sense. So for simplicity, we do not ditinguish `hasOwnProperty` and null/undefined. + if (txCfgOpt) { + stateObj.textConfig = txCfgOpt; + } + + setDefaultStateProxy(elDisplayable); + } +} + +function updateZ( + el: Element, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + attachedTxInfo: AttachedTxInfo +): void { + // Group not support textContent and not support z yet. + if (el.isGroup) { + return; + } + + const elDisplayable = el as Displayable; + const currentZ = seriesModel.currentZ; + const currentZLevel = seriesModel.currentZLevel; + // Always erase. + elDisplayable.z = currentZ; + elDisplayable.zlevel = currentZLevel; + // z2 must not be null/undefined, otherwise sort error may occur. + const optZ2 = elOption.z2; + optZ2 != null && (elDisplayable.z2 = optZ2 || 0); + + for (let i = 0; i < STATES.length; i++) { + updateZForEachState(elDisplayable, elOption, STATES[i]); + } +} + +function updateZForEachState( + elDisplayable: Displayable, + elOption: CustomDisplayableOption, + state: DisplayState +): void { + const isNormal = state === NORMAL; + const elStateOpt = isNormal ? elOption : retrieveStateOption(elOption, state as DisplayStateNonNormal); + const optZ2 = elStateOpt ? elStateOpt.z2 : null; + let stateObj; + if (optZ2 != null) { + // Do not `ensureState` until required. + stateObj = isNormal ? elDisplayable : elDisplayable.ensureState(state); + stateObj.z2 = optZ2 || 0; + } +} + +function setLagecyTransformProp( + elOption: CustomElementOption, + targetProps: Partial>, + legacyName: LegacyTransformProp, + fromTransformable?: Transformable // If provided, retrieve from the element. +): void { + const legacyArr = (elOption as any)[legacyName]; + const xyName = LEGACY_TRANSFORM_PROPS[legacyName]; + if (legacyArr) { + if (fromTransformable) { + targetProps[xyName[0]] = fromTransformable[xyName[0]]; + targetProps[xyName[1]] = fromTransformable[xyName[1]]; + } + else { + targetProps[xyName[0]] = legacyArr[0]; + targetProps[xyName[1]] = legacyArr[1]; + } + } +} + +function setTransformProp( + elOption: CustomElementOption, + allProps: Partial>, + name: TransformProp, + fromTransformable?: Transformable // If provided, retrieve from the element. +): void { + if (elOption[name] != null) { + allProps[name] = fromTransformable ? fromTransformable[name] : elOption[name]; + } +} + +function setTransformPropToTransitionFrom( + transitionFrom: Partial>, + name: TransformProp, + fromTransformable?: Transformable // If provided, retrieve from the element. +): void { + if (fromTransformable) { + transitionFrom[name] = fromTransformable[name]; + } +} + + +function makeRenderItem( + customSeries: CustomSeriesModel, + data: List, + ecModel: GlobalModel, + api: ExtensionAPI +) { + const renderItem = customSeries.get('renderItem'); + const coordSys = customSeries.coordinateSystem; + let prepareResult = {} as ReturnType; + + if (coordSys) { + if (__DEV__) { + assert(renderItem, 'series.render is required.'); + assert( + coordSys.prepareCustoms || prepareCustoms[coordSys.type], + 'This coordSys does not support custom series.' + ); + } + + // `coordSys.prepareCustoms` is used for external coord sys like bmap. + prepareResult = coordSys.prepareCustoms + ? coordSys.prepareCustoms(coordSys) + : prepareCustoms[coordSys.type](coordSys); + } + + const userAPI = defaults({ + getWidth: api.getWidth, + getHeight: api.getHeight, + getZr: api.getZr, + getDevicePixelRatio: api.getDevicePixelRatio, + value: value, + style: style, + ordinalRawValue: ordinalRawValue, + styleEmphasis: styleEmphasis, + visual: visual, + barLayout: barLayout, + currentSeriesIndices: currentSeriesIndices, + font: font + }, prepareResult.api || {}) as CustomSeriesRenderItemAPI; + + const userParams: CustomSeriesRenderItemParams = { + // The life cycle of context: current round of rendering. + // The global life cycle is probably not necessary, because + // user can store global status by themselves. + context: {}, + seriesId: customSeries.id, + seriesName: customSeries.name, + seriesIndex: customSeries.seriesIndex, + coordSys: prepareResult.coordSys, + dataInsideLength: data.count(), + encode: wrapEncodeDef(customSeries.getData()) + }; + + // If someday intending to refactor them to a class, should consider do not + // break change: currently these attribute member are encapsulated in a closure + // so that do not need to force user to call these method with a scope. + + // Do not support call `api` asynchronously without dataIndexInside input. + let currDataIndexInside: number; + let currItemModel: Model; + let currItemStyleModels: Partial>> = {}; + let currLabelModels: Partial>> = {}; + + const seriesItemStyleModels = {} as Record>; + + const seriesLabelModels = {} as Record>; + + for (let i = 0; i < STATES.length; i++) { + const stateName = STATES[i]; + seriesItemStyleModels[stateName] = (customSeries as Model) + .getModel(PATH_ITEM_STYLE[stateName]); + seriesLabelModels[stateName] = (customSeries as Model) + .getModel(PATH_LABEL[stateName]); + } + + function getItemModel(dataIndexInside: number): Model { + return dataIndexInside === currDataIndexInside + ? (currItemModel || (currItemModel = data.getItemModel(dataIndexInside))) + : data.getItemModel(dataIndexInside); + } + function getItemStyleModel(dataIndexInside: number, state: DisplayState) { + return !data.hasItemOption + ? seriesItemStyleModels[state] + : dataIndexInside === currDataIndexInside + ? (currItemStyleModels[state] || ( + currItemStyleModels[state] = getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]) + )) + : getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]); + } + function getLabelModel(dataIndexInside: number, state: DisplayState) { + return !data.hasItemOption + ? seriesLabelModels[state] + : dataIndexInside === currDataIndexInside + ? (currLabelModels[state] || ( + currLabelModels[state] = getItemModel(dataIndexInside).getModel(PATH_LABEL[state]) + )) + : getItemModel(dataIndexInside).getModel(PATH_LABEL[state]); + } + + return function (dataIndexInside: number, payload: Payload): CustomElementOption { + currDataIndexInside = dataIndexInside; + currItemModel = null; + currItemStyleModels = {}; + currLabelModels = {}; + + return renderItem && renderItem( + defaults({ + dataIndexInside: dataIndexInside, + dataIndex: data.getRawIndex(dataIndexInside), + // Can be used for optimization when zoom or roam. + actionType: payload ? payload.type : null + }, userParams), + userAPI + ); + }; + + /** + * @public + * @param dim by default 0. + * @param dataIndexInside by default `currDataIndexInside`. + */ + function value(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue { + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + return data.get(data.getDimension(dim || 0), dataIndexInside); + } + + /** + * @public + * @param dim by default 0. + * @param dataIndexInside by default `currDataIndexInside`. + */ + function ordinalRawValue(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue { + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + const dimInfo = data.getDimensionInfo(dim || 0); + if (!dimInfo) { + return; + } + const val = data.get(dimInfo.name, dataIndexInside); + const ordinalMeta = dimInfo && dimInfo.ordinalMeta; + return ordinalMeta + ? ordinalMeta.categories[val as number] + : val; + } + + /** + * @deprecated The orgininal intention of `api.style` is enable to set itemStyle + * like other series. But it not necessary and not easy to give a strict definition + * of what it return. And since echarts5 it needs to be make compat work. So + * deprecates it since echarts5. + * + * By default, `visual` is applied to style (to support visualMap). + * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`, + * it can be implemented as: + * `api.style({stroke: api.visual('color'), fill: null})`; + * + * [Compat]: since ec5, RectText has been separated from its hosts el. + * so `api.style()` will only return the style from `itemStyle` but not handle `label` + * any more. But `series.label` config is never published in doc. + * We still compat it in `api.style()`. But not encourage to use it and will still not + * to pulish it to doc. + * @public + * @param dataIndexInside by default `currDataIndexInside`. + */ + function style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps { + if (__DEV__) { + warnDeprecated('api.style', 'Please write literal style directly instead.'); + } + + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + + const style = data.getItemVisual(dataIndexInside, 'style'); + const visualColor = style && style.fill; + const opacity = style && style.opacity; + + let itemStyle = getItemStyleModel(dataIndexInside, NORMAL).getItemStyle(); + visualColor != null && (itemStyle.fill = visualColor); + opacity != null && (itemStyle.opacity = opacity); + + const opt = {inheritColor: isString(visualColor) ? visualColor : '#000'}; + const labelModel = getLabelModel(dataIndexInside, NORMAL); + // Now that the feture of "auto adjust text fill/stroke" has been migrated to zrender + // since ec5, we should set `isAttached` as `false` here and make compat in + // `convertToEC4StyleForCustomSerise`. + const textStyle = labelStyleHelper.createTextStyle(labelModel, null, opt, false, true); + textStyle.text = labelModel.getShallow('show') + ? retrieve2( + customSeries.getFormattedLabel(dataIndexInside, NORMAL), + getDefaultLabel(data, dataIndexInside) + ) + : null; + const textConfig = labelStyleHelper.createTextConfig(labelModel, opt, false); + + preFetchFromExtra(userProps, itemStyle); + itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig); + + userProps && applyUserPropsAfter(itemStyle, userProps); + (itemStyle as LegacyStyleProps).legacy = true; + + return itemStyle; + } + + /** + * @deprecated The reason see `api.style()` + * @public + * @param dataIndexInside by default `currDataIndexInside`. + */ + function styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps { + if (__DEV__) { + warnDeprecated('api.styleEmphasis', 'Please write literal style directly instead.'); + } + + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + + let itemStyle = getItemStyleModel(dataIndexInside, EMPHASIS).getItemStyle(); + const labelModel = getLabelModel(dataIndexInside, EMPHASIS); + const textStyle = labelStyleHelper.createTextStyle(labelModel, null, null, true, true); + textStyle.text = labelModel.getShallow('show') + ? retrieve3( + customSeries.getFormattedLabel(dataIndexInside, EMPHASIS), + customSeries.getFormattedLabel(dataIndexInside, NORMAL), + getDefaultLabel(data, dataIndexInside) + ) + : null; + const textConfig = labelStyleHelper.createTextConfig(labelModel, null, true); + + preFetchFromExtra(userProps, itemStyle); + itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig); + + userProps && applyUserPropsAfter(itemStyle, userProps); + (itemStyle as LegacyStyleProps).legacy = true; + + return itemStyle; + } + + function applyUserPropsAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void { + for (const key in extra) { + if (hasOwn(extra, key)) { + (itemStyle as any)[key] = (extra as any)[key]; + } + } + } + + function preFetchFromExtra(extra: ZRStyleProps, itemStyle: ItemStyleProps): void { + // A trick to retrieve those props firstly, which are used to + // apply auto inside fill/stroke in `convertToEC4StyleForCustomSerise`. + // (It's not reasonable but only for a degree of compat) + if (extra) { + (extra as any).textFill && ((itemStyle as any).textFill = (extra as any).textFill); + (extra as any).textPosition && ((itemStyle as any).textPosition = (extra as any).textPosition); + } + } + + /** + * @public + * @param dataIndexInside by default `currDataIndexInside`. + */ + function visual( + visualType: VT, + dataIndexInside?: number + ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT] + : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]] + : never { + + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + + if (hasOwn(STYLE_VISUAL_TYPE, visualType)) { + const style = data.getItemVisual(dataIndexInside, 'style'); + return style + ? style[STYLE_VISUAL_TYPE[visualType as StyleVisualProps]] as any + : null; + } + // Only support these visuals. Other visual might be inner tricky + // for performance (like `style`), do not expose to users. + if (hasOwn(NON_STYLE_VISUAL_PROPS, visualType)) { + return data.getItemVisual(dataIndexInside, visualType as NonStyleVisualProps) as any; + } + } + + /** + * @public + * @return If not support, return undefined. + */ + function barLayout( + opt: Omit[0], 'axis'> + ): ReturnType { + if (coordSys.type === 'cartesian2d') { + const baseAxis = coordSys.getBaseAxis() as Axis2D; + return getLayoutOnAxis(defaults({axis: baseAxis}, opt)); + } + } + + /** + * @public + */ + function currentSeriesIndices(): ReturnType { + return ecModel.getCurrentSeriesIndices(); + } + + /** + * @public + * @return font string + */ + function font( + opt: Parameters[0] + ): ReturnType { + return labelStyleHelper.getFont(opt, ecModel); + } +} + +type WrapEncodeDefRet = Dictionary; + +function wrapEncodeDef(data: List): WrapEncodeDefRet { + const encodeDef = {} as WrapEncodeDefRet; + each(data.dimensions, function (dimName, dataDimIndex) { + const dimInfo = data.getDimensionInfo(dimName); + if (!dimInfo.isExtraCoord) { + const coordDim = dimInfo.coordDim; + const dataDims = encodeDef[coordDim] = encodeDef[coordDim] || []; + dataDims[dimInfo.coordDimIndex] = dataDimIndex; + } + }); + return encodeDef; +} + +function createOrUpdateItem( + api: ExtensionAPI, + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + group: ViewRootGroup, + data: List, + morphPreparation: MorphPreparation +): Element { + // [Rule] + // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing. + // (It seems that violate the "merge" principle, but most of users probably intuitively + // regard "return;" as "show nothing element whatever", so make a exception to meet the + // most cases.) + // The rule or "merge" see [STRATEGY_MERGE]. + + // If `elOption` is `null`/`undefined`/`false` (when `renderItem` returns nothing). + if (!elOption) { + removeElementDirectly(el, group); + return; + } + el = doCreateOrUpdateEl(api, el, dataIndex, elOption, seriesModel, group, true, morphPreparation); + el && data.setItemGraphicEl(dataIndex, el); + + enableHoverEmphasis(el, elOption.focus, elOption.blurScope); + + return el; +} + +function doCreateOrUpdateEl( + api: ExtensionAPI, + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + group: ViewRootGroup, + isRoot: boolean, + morphPreparation: MorphPreparation +): Element { + + if (__DEV__) { + assert(elOption, 'should not have an null/undefined element setting'); + } + + let toBeReplacedIdx = -1; + if ( + el && ( + doesElNeedRecreate(el, elOption) + // || ( + // // PENDING: even in one-to-one mapping case, if el is marked as morph, + // // do not sure whether the el will be mapped to another el with different + // // hierarchy in Group tree. So always recreate el rather than reuse the el. + // morphPreparation && morphPreparation.isOneToOneFrom(el) + // ) + ) + ) { + // Should keep at the original index, otherwise "merge by index" will be incorrect. + toBeReplacedIdx = group.childrenRef().indexOf(el); + el = null; + } + + const elIsNewCreated = !el; + + if (!el) { + el = createEl(elOption); + } + else { + // FIMXE:NEXT unified clearState? + // If in some case the performance issue arised, consider + // do not clearState but update cached normal state directly. + el.clearStates(); + } + + const canMorph = inner(el).canMorph = (elOption as CustomZRPathOption).morph && isPath(el); + const thisElIsMorphTo = canMorph && morphPreparation && morphPreparation.hasFrom(); + + // Use update animation when morph is enabled. + const isInit = elIsNewCreated && !thisElIsMorphTo; + + attachedTxInfoTmp.normal.cfg = attachedTxInfoTmp.normal.conOpt = + attachedTxInfoTmp.emphasis.cfg = attachedTxInfoTmp.emphasis.conOpt = + attachedTxInfoTmp.blur.cfg = attachedTxInfoTmp.blur.conOpt = + attachedTxInfoTmp.select.cfg = attachedTxInfoTmp.select.conOpt = null; + + attachedTxInfoTmp.isLegacy = false; + + doCreateOrUpdateAttachedTx( + el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp + ); + + doCreateOrUpdateClipPath( + el, dataIndex, elOption, seriesModel, isInit + ); + + const pendingAllPropsFinal = updateElNormal( + api, + el, + thisElIsMorphTo, + dataIndex, + elOption, + elOption.style, + attachedTxInfoTmp, + seriesModel, + isInit, + false + ); + + if (thisElIsMorphTo) { + morphPreparation.addTo(el as graphicUtil.Path, elOption, dataIndex, pendingAllPropsFinal); + } + + for (let i = 0; i < STATES.length; i++) { + const stateName = STATES[i]; + if (stateName !== NORMAL) { + const otherStateOpt = retrieveStateOption(elOption, stateName); + const otherStyleOpt = retrieveStyleOptionOnState(elOption, otherStateOpt, stateName); + updateElOnState(stateName, el, otherStateOpt, otherStyleOpt, attachedTxInfoTmp, isRoot, false); + } + } + + updateZ(el, elOption, seriesModel, attachedTxInfoTmp); + + if (elOption.type === 'group') { + mergeChildren( + api, el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, morphPreparation + ); + } + + if (toBeReplacedIdx >= 0) { + group.replaceAt(el, toBeReplacedIdx); + } + else { + group.add(el); + } + + return el; +} + +// `el` must not be null/undefined. +function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean { + const elInner = inner(el); + const elOptionType = elOption.type; + const elOptionShape = (elOption as CustomZRPathOption).shape; + const elOptionStyle = elOption.style; + return ( + // If `elOptionType` is `null`, follow the merge principle. + (elOptionType != null + && elOptionType !== elInner.customGraphicType + ) + || (elOptionType === 'path' + && hasOwnPathData(elOptionShape) + && getPathData(elOptionShape) !== elInner.customPathData + ) + || (elOptionType === 'image' + && hasOwn(elOptionStyle, 'image') + && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath + ) + // // FIXME test and remove this restriction? + // || (elOptionType === 'text' + // && hasOwn(elOptionStyle, 'text') + // && (elOptionStyle as TextStyleProps).text !== elInner.customText + // ) + ); +} + +function doCreateOrUpdateClipPath( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + isInit: boolean +): void { + // Based on the "merge" principle, if no clipPath provided, + // do nothing. The exists clip will be totally removed only if + // `el.clipPath` is `false`. Otherwise it will be merged/replaced. + const clipPathOpt = elOption.clipPath; + if (clipPathOpt === false) { + if (el && el.getClipPath()) { + el.removeClipPath(); + } + } + else if (clipPathOpt) { + let clipPath = el.getClipPath(); + if (clipPath && doesElNeedRecreate(clipPath, clipPathOpt)) { + clipPath = null; + } + if (!clipPath) { + clipPath = createEl(clipPathOpt) as graphicUtil.Path; + if (__DEV__) { + assert( + clipPath instanceof graphicUtil.Path, + 'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.' + ); + } + el.setClipPath(clipPath); + } + updateElNormal( + null, clipPath, null, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false + ); + } + // If not define `clipPath` in option, do nothing unnecessary. +} + +function doCreateOrUpdateAttachedTx( + el: Element, + dataIndex: number, + elOption: CustomElementOption, + seriesModel: CustomSeriesModel, + isInit: boolean, + attachedTxInfo: AttachedTxInfo +): void { + // group do not support textContent temporarily untill necessary. + if (el.isGroup) { + return; + } + + // Normal must be called before emphasis, for `isLegacy` detection. + processTxInfo(elOption, null, attachedTxInfo); + processTxInfo(elOption, EMPHASIS, attachedTxInfo); + + // If `elOption.textConfig` or `elOption.textContent` is null/undefined, it does not make sence. + // So for simplicity, if "elOption hasOwnProperty of them but be null/undefined", we do not + // trade them as set to null to el. + // Especially: + // `elOption.textContent: false` means remove textContent. + // `elOption.textContent.emphasis.style: false` means remove the style from emphasis state. + let txConOptNormal = attachedTxInfo.normal.conOpt as CustomElementOption | false; + const txConOptEmphasis = attachedTxInfo.emphasis.conOpt as CustomElementOptionOnState; + const txConOptBlur = attachedTxInfo.blur.conOpt as CustomElementOptionOnState; + const txConOptSelect = attachedTxInfo.select.conOpt as CustomElementOptionOnState; + + if (txConOptNormal != null || txConOptEmphasis != null || txConOptSelect != null || txConOptBlur != null) { + let textContent = el.getTextContent(); + if (txConOptNormal === false) { + textContent && el.removeTextContent(); + } + else { + txConOptNormal = attachedTxInfo.normal.conOpt = txConOptNormal || {type: 'text'}; + if (!textContent) { + textContent = createEl(txConOptNormal) as graphicUtil.Text; + el.setTextContent(textContent); + } + else { + // If in some case the performance issue arised, consider + // do not clearState but update cached normal state directly. + textContent.clearStates(); + } + const txConStlOptNormal = txConOptNormal && txConOptNormal.style; + + updateElNormal( + null, textContent, null, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true + ); + for (let i = 0; i < STATES.length; i++) { + const stateName = STATES[i]; + if (stateName !== NORMAL) { + const txConOptOtherState = attachedTxInfo[stateName].conOpt as CustomElementOptionOnState; + updateElOnState( + stateName, + textContent, + txConOptOtherState, + retrieveStyleOptionOnState(txConOptNormal, txConOptOtherState, stateName), + null, false, true + ); + } + } + + txConStlOptNormal ? textContent.dirty() : textContent.markRedraw(); + } + } +} + +function processTxInfo( + elOption: CustomElementOption, + state: DisplayStateNonNormal, + attachedTxInfo: AttachedTxInfo +): void { + const stateOpt = !state ? elOption : retrieveStateOption(elOption, state); + const styleOpt = !state ? elOption.style : retrieveStyleOptionOnState(elOption, stateOpt, EMPHASIS); + + const elType = elOption.type; + let txCfg = stateOpt ? stateOpt.textConfig : null; + const txConOptNormal = elOption.textContent; + let txConOpt: CustomElementOption | CustomElementOptionOnState = + !txConOptNormal ? null : !state ? txConOptNormal : retrieveStateOption(txConOptNormal, state); + + if (styleOpt && ( + // Because emphasis style has little info to detect legacy, + // if normal is legacy, emphasis is trade as legacy. + attachedTxInfo.isLegacy + || isEC4CompatibleStyle(styleOpt, elType, !!txCfg, !!txConOpt) + )) { + attachedTxInfo.isLegacy = true; + const convertResult = convertFromEC4CompatibleStyle(styleOpt, elType, !state); + // Explicitly specified `textConfig` and `textContent` has higher priority than + // the ones generated by legacy style. Otherwise if users use them and `api.style` + // at the same time, they not both work and hardly to known why. + if (!txCfg && convertResult.textConfig) { + txCfg = convertResult.textConfig; + } + if (!txConOpt && convertResult.textContent) { + txConOpt = convertResult.textContent; + } + } + + if (!state && txConOpt) { + const txConOptNormal = txConOpt as CustomElementOption; + // `textContent: {type: 'text'}`, the "type" is easy to be missing. So we tolerate it. + !txConOptNormal.type && (txConOptNormal.type = 'text'); + if (__DEV__) { + // Do not tolerate incorret type for forward compat. + txConOptNormal.type !== 'text' && assert( + txConOptNormal.type === 'text', + 'textContent.type must be "text"' + ); + } + } + + const info = !state ? attachedTxInfo.normal : attachedTxInfo[state]; + info.cfg = txCfg; + info.conOpt = txConOpt; +} + +function retrieveStateOption( + elOption: CustomElementOption, state: DisplayStateNonNormal +): CustomElementOptionOnState { + return !state ? elOption : elOption ? elOption[state] : null; +} + +function retrieveStyleOptionOnState( + stateOptionNormal: CustomElementOption, + stateOption: CustomElementOptionOnState, + state: DisplayStateNonNormal +): CustomElementOptionOnState['style'] { + let style = stateOption && stateOption.style; + if (style == null && state === EMPHASIS && stateOptionNormal) { + style = stateOptionNormal.styleEmphasis; + } + return style; +} + + +// Usage: +// (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that +// the existing children will not be removed, and enables the feature that +// update some of the props of some of the children simply by construct +// the returned children of `renderItem` like: +// `var children = group.children = []; children[3] = {opacity: 0.5};` +// (2) If `elOption.$mergeChildren` is `'byName'`, add/update/remove children +// by child.name. But that might be lower performance. +// (3) If `elOption.$mergeChildren` is `false`, the existing children will be +// replaced totally. +// (4) If `!elOption.children`, following the "merge" principle, nothing will happen. +// +// For implementation simpleness, do not provide a direct way to remove sinlge +// child (otherwise the total indicies of the children array have to be modified). +// User can remove a single child by set its `ignore` as `true`. +function mergeChildren( + api: ExtensionAPI, + el: graphicUtil.Group, + dataIndex: number, + elOption: CustomGroupOption, + seriesModel: CustomSeriesModel, + morphPreparation: MorphPreparation +): void { + + const newChildren = elOption.children; + const newLen = newChildren ? newChildren.length : 0; + const mergeChildren = elOption.$mergeChildren; + // `diffChildrenByName` has been deprecated. + const byName = mergeChildren === 'byName' || elOption.diffChildrenByName; + const notMerge = mergeChildren === false; + + // For better performance on roam update, only enter if necessary. + if (!newLen && !byName && !notMerge) { + return; + } + + if (byName) { + diffGroupChildren({ + api: api, + oldChildren: el.children() || [], + newChildren: newChildren || [], + dataIndex: dataIndex, + seriesModel: seriesModel, + group: el, + morphPreparation: morphPreparation + }); + return; + } + + notMerge && el.removeAll(); + + // Mapping children of a group simply by index, which + // might be better performance. + let index = 0; + for (; index < newLen; index++) { + newChildren[index] && doCreateOrUpdateEl( + api, + el.childAt(index), + dataIndex, + newChildren[index], + seriesModel, + el, + false, + morphPreparation + ); + } + for (let i = el.childCount() - 1; i >= index; i--) { + // Do not supprot leave elements that are not mentioned in the latest + // `renderItem` return. Otherwise users may not have a clear and simple + // concept that how to contorl all of the elements. + doRemoveEl(el.childAt(i), seriesModel, el); + } +} + +type DiffGroupContext = { + api: ExtensionAPI; + oldChildren: Element[]; + newChildren: CustomElementOption[]; + dataIndex: number; + seriesModel: CustomSeriesModel; + group: graphicUtil.Group; + morphPreparation: MorphPreparation; +}; +function diffGroupChildren(context: DiffGroupContext) { + (new DataDiffer( + context.oldChildren, + context.newChildren, + getKey, + getKey, + context + )) + .add(processAddUpdate) + .update(processAddUpdate) + .remove(processRemove) + .execute(); +} + +function getKey(item: Element, idx: number): string { + const name = item && item.name; + return name != null ? name : GROUP_DIFF_PREFIX + idx; +} + +function processAddUpdate( + this: DataDiffer, + newIndex: number, + oldIndex?: number +): void { + const context = this.context; + const childOption = newIndex != null ? context.newChildren[newIndex] : null; + const child = oldIndex != null ? context.oldChildren[oldIndex] : null; + + doCreateOrUpdateEl( + context.api, + child, + context.dataIndex, + childOption, + context.seriesModel, + context.group, + false, + context.morphPreparation + ); +} + +function processRemove(this: DataDiffer, oldIndex: number): void { + const context = this.context; + const child = context.oldChildren[oldIndex]; + doRemoveEl(child, context.seriesModel, context.group); +} + +function doRemoveEl( + el: Element, + seriesModel: CustomSeriesModel, + group: ViewRootGroup +): void { + if (el) { + const leaveToProps = inner(el).leaveToProps; + leaveToProps + ? graphicUtil.updateProps(el, leaveToProps, seriesModel, { + cb: function () { + group.remove(el); + } + }) + : group.remove(el); + } +} + +/** + * @return SVG Path data. + */ +function getPathData(shape: CustomSVGPathOption['shape']): string { + // "d" follows the SVG convention. + return shape && (shape.pathData || shape.d); +} + +function hasOwnPathData(shape: CustomSVGPathOption['shape']): boolean { + return shape && (hasOwn(shape, 'pathData') || hasOwn(shape, 'd')); +} + +function isPath(el: Element): el is graphicUtil.Path { + return el && el instanceof graphicUtil.Path; +} + +function removeElementDirectly(el: Element, group: ViewRootGroup): void { + el && group.remove(el); +} + + +type MorphPreparationType = 'oneToOne' | 'oneToMany' | 'manyToOne'; + +/** + * Any morph-potential el should added by `morphPreparation.addTo(el)`. + * And they may apply morph or not when `morphPreparation.applyMorphing()`. + * But at least, all of the "to" elements will apply all of the updates + * as `doCreateOrUpdateItem` did. + */ +class MorphPreparation { + private _type: MorphPreparationType; + private _fromList: graphicUtil.Path[] = []; + private _toList: graphicUtil.Path[] = []; + private _toElOptionList: CustomElementOption[] = []; + private _allPropsFinalList: ElementProps[] = []; + private _toDataIndices: number[] = []; + private _transOpt: SeriesModel['__transientTransitionOpt']; + private _seriesModel: CustomSeriesModel; + // Key: `toDataIndex`, not `toIdx` + private _morphConfigList: CombineSeparateConfig[] = []; + + constructor( + seriesModel: CustomSeriesModel, + transOpt: SeriesModel['__transientTransitionOpt'] + ) { + this._seriesModel = seriesModel; + this._transOpt = transOpt; + } + + hasFrom(): boolean { + return !!this._fromList.length; + } + + // isOneToOneFrom(el: Element): boolean { + // if (el && inner(el).canMorph) { + // const fromList = this._fromList; + // for (let i = 0; i < fromList.length; i++) { + // if (fromList[i] === el) { + // return true; + // } + // } + // } + // } + + findAndAddFrom(el: Element): void { + if (!el) { + return; + } + if (inner(el).canMorph) { + this._fromList.push(el as graphicUtil.Path); + } + if (el.isGroup) { + const children = (el as graphicUtil.Group).childrenRef(); + for (let i = 0; i < children.length; i++) { + this.findAndAddFrom(children[i]); + } + } + } + + addTo( + path: graphicUtil.Path, + elOption: CustomElementOption, + dataIndex: number, + allPropsFinal: ElementProps + ): void { + if (path) { + this._toList.push(path); + this._toElOptionList.push(elOption); + this._toDataIndices.push(dataIndex); + this._allPropsFinalList.push(allPropsFinal); + } + } + + applyMorphing(): void { + // [MORPHING_LOGIC_HINT] + // Pay attention to the order: + // (A) Apply `allPropsFinal` and `styleOption` to "to". + // (Then "to" becomes to the final state.) + // (B) Apply `morphPath`/`combine`/`separate`. + // (Based on the current state of "from" and the final state of "to".) + // (Then we may get "from.subList" or "to.subList".) + // (C) Copy the related props from "from" to "from.subList", from "to" to "to.subList". + // (D) Collect `transitionFromProps` for "to" and "to.subList" + // (Based on "from" or "from.subList".) + // (E) Apply `transitionFromProps` to "to" and "to.subList" + // (It might change the prop values to the first frame value.) + // Case_I: + // If (D) should be after (C), we use sequence: A - B - C - D - E + // Case_II: + // If (A) should be after (D), we use sequence: D - A - B - C - E + + // [MORPHING_LOGIC_HINT] + // zrender `morphPath`/`combine`/`separate` only manages the shape animation. + // Other props (like transfrom, style transition) will handled in echarts). + + // [MORPHING_LOGIC_HINT] + // Make sure `applyPropsFinal` always be called for "to". + + const type = this._type; + const fromList = this._fromList; + const toList = this._toList; + const toListLen = toList.length; + const fromListLen = fromList.length; + + if (!fromListLen || !toListLen) { + return; + } + + if (type === 'oneToOne') { + // In one-to-one case, we by default apply a simple rule: + // map "from" and "to" one by one. + // For this case: old_data_item_el and new_data_item_el + // has the same hierarchy of group tree but only some path type changed. + for (let toIdx = 0; toIdx < toListLen; toIdx++) { + this._oneToOneForSingleTo(toIdx, toIdx); + } + } + + else if (type === 'manyToOne') { + // A rough strategy: if there are more than one "to", we simply divide "fromList" equally. + const fromSingleSegLen = Math.max(1, Math.floor(fromListLen / toListLen)); + for ( + let toIdx = 0, fromIdxStart = 0; + toIdx < toListLen; + toIdx++, fromIdxStart += fromSingleSegLen + ) { + const fromCount = toIdx + 1 >= toListLen + ? fromListLen - fromIdxStart + : fromSingleSegLen; + this._manyToOneForSingleTo( + toIdx, fromIdxStart >= fromListLen ? null : fromIdxStart, fromCount + ); + } + } + + else if (type === 'oneToMany') { + // A rough strategy: if there are more than one "from", we simply divide "toList" equally. + const toSingleSegLen = Math.max(1, Math.floor(toListLen / fromListLen)); + for ( + let toIdxStart = 0, fromIdx = 0; + toIdxStart < toListLen; + toIdxStart += toSingleSegLen, fromIdx++ + ) { + const toCount = toIdxStart + toSingleSegLen >= toListLen + ? toListLen - toIdxStart + : toSingleSegLen; + this._oneToManyForSingleFrom( + toIdxStart, toCount, fromIdx >= fromListLen ? null : fromIdx + ); + } + } + } + + private _oneToOneForSingleTo( + // "to" must NOT be null/undefined. + toIdx: number, + // May `fromIdx >= this._fromList.length` + fromIdx: number + ): void { + const to = this._toList[toIdx]; + const toElOption = this._toElOptionList[toIdx]; + const toDataIndex = this._toDataIndices[toIdx]; + const allPropsFinal = this._allPropsFinalList[toIdx]; + const from = this._fromList[fromIdx]; + + const elAnimationConfig = this._getOrCreateMorphConfig(toDataIndex); + const morphDuration = elAnimationConfig.duration; + + if (from && isCombiningPath(from)) { + applyPropsFinal(to, allPropsFinal, toElOption.style); + + if (morphDuration) { + const combineResult = combine([from], to, elAnimationConfig, copyPropsWhenDivided); + this._processResultIndividuals(combineResult, toIdx, null); + } + // The target el will not be displayed and transition from multiple path. + // transition on the target el does not make sense. + } + else { + const morphFrom = ( + morphDuration + // from === to usually happen in scenarios where internal update like + // "dataZoom", "legendToggle" happen. If from is not in any morphing, + // we do not need to call `morphPath`. + && from + && (from !== to || isInAnyMorphing(from)) + ) ? from : null; + + // See [Case_II] above. + // In this case, there is probably `from === to`. And the `transitionFromProps` collecting + // does not depends on morphing. So we collect `transitionFromProps` first. + const transFromProps = {} as ElementProps; + prepareShapeOrExtraTransitionFrom('shape', to, morphFrom, toElOption, transFromProps, false); + prepareShapeOrExtraTransitionFrom('extra', to, morphFrom, toElOption, transFromProps, false); + prepareTransformTransitionFrom(to, morphFrom, toElOption, transFromProps, false); + prepareStyleTransitionFrom(to, morphFrom, toElOption, toElOption.style, transFromProps, false); + + applyPropsFinal(to, allPropsFinal, toElOption.style); + + if (morphFrom) { + morphPath(morphFrom, to, elAnimationConfig); + } + applyTransitionFrom(to, toDataIndex, toElOption, this._seriesModel, transFromProps, false); + } + } + + private _manyToOneForSingleTo( + // "to" must NOT be null/undefined. + toIdx: number, + // May be null. + fromIdxStart: number, + fromCount: number + ): void { + const to = this._toList[toIdx]; + const toElOption = this._toElOptionList[toIdx]; + const allPropsFinal = this._allPropsFinalList[toIdx]; + + applyPropsFinal(to, allPropsFinal, toElOption.style); + + const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdx]); + if (elAnimationConfig.duration && fromIdxStart != null) { + const combineFromList = []; + for (let fromIdx = fromIdxStart; fromIdx < fromCount; fromIdx++) { + combineFromList.push(this._fromList[fromIdx]); + } + const combineResult = combine(combineFromList, to, elAnimationConfig, copyPropsWhenDivided); + this._processResultIndividuals(combineResult, toIdx, null); + } + } + + private _oneToManyForSingleFrom( + // "to" must NOT be null/undefined. + toIdxStart: number, + toCount: number, + // May be null + fromIdx: number + ): void { + const from = fromIdx == null ? null : this._fromList[fromIdx]; + const toList = this._toList; + + const separateToList = []; + for (let toIdx = toIdxStart; toIdx < toCount; toIdx++) { + const to = toList[toIdx]; + applyPropsFinal(to, this._allPropsFinalList[toIdx], this._toElOptionList[toIdx].style); + separateToList.push(to); + } + + const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdxStart]); + if (elAnimationConfig.duration && from) { + const separateResult = separate(from, separateToList, elAnimationConfig, copyPropsWhenDivided); + this._processResultIndividuals(separateResult, toIdxStart, toCount); + } + } + + private _processResultIndividuals( + combineSeparateResult: CombineSeparateResult, + toIdxStart: number, + toCount: number + ): void { + const isSeparate = toCount != null; + + for (let i = 0; i < combineSeparateResult.count; i++) { + const fromIndividual = combineSeparateResult.fromIndividuals[i]; + const toIndividual = combineSeparateResult.toIndividuals[i]; + // Here it's a trick: + // For "combine" case, all of the `toIndividuals` map to the same `toIdx`. + // For "separate" case, the `toIndividuals` map to some certain segment of `_toList` accurately. + const toIdx = toIdxStart + (isSeparate ? i : 0); + + const toElOption = this._toElOptionList[toIdx]; + const dataIndex = this._toDataIndices[toIdx]; + + const transFromProps = {} as ElementProps; + prepareTransformTransitionFrom( + toIndividual, fromIndividual, toElOption, transFromProps, false + ); + prepareStyleTransitionFrom( + toIndividual, fromIndividual, toElOption, toElOption.style, transFromProps, false + ); + applyTransitionFrom( + toIndividual, dataIndex, toElOption, this._seriesModel, transFromProps, false + ); + } + } + + _getOrCreateMorphConfig(dataIndex: number): CombineSeparateConfig { + const morphConfigList = this._morphConfigList; + let config = morphConfigList[dataIndex]; + if (config) { + return config; + } + + let duration: number; + let easing: AnimationEasing; + let delay: number; + const seriesModel = this._seriesModel; + const transOpt = this._transOpt; + + if (seriesModel.isAnimationEnabled()) { + // PENDING: refactor? this is the same logic as `src/util/graphic.ts#animateOrSetProps`. + let animationPayload: PayloadAnimationPart; + if (seriesModel && seriesModel.ecModel) { + const updatePayload = seriesModel.ecModel.getUpdatePayload(); + animationPayload = (updatePayload && updatePayload.animation) as PayloadAnimationPart; + } + if (animationPayload) { + duration = animationPayload.duration || 0; + easing = animationPayload.easing || 'cubicOut'; + delay = animationPayload.delay || 0; + } + else { + easing = seriesModel.get('animationEasingUpdate'); + const delayOption = seriesModel.get('animationDelayUpdate'); + delay = isFunction(delayOption) ? delayOption(dataIndex) : delayOption; + const durationOption = seriesModel.get('animationDurationUpdate'); + duration = isFunction(durationOption) ? durationOption(dataIndex) : durationOption; + } + } + + config = { + duration: duration || 0, + delay: delay, + easing: easing, + dividingMethod: transOpt ? transOpt.dividingMethod : null + }; + morphConfigList[dataIndex] = config; + + return config; + } + + reset(type: MorphPreparationType): void { + // `this._morphConfigList` can be kept. It only related to `dataIndex`. + this._type = type; + this._fromList.length = + this._toList.length = + this._toElOptionList.length = + this._allPropsFinalList.length = + this._toDataIndices.length = 0; + } +} + +function copyPropsWhenDivided( + srcPath: graphicUtil.Path, + tarPath: graphicUtil.Path, + willClone: boolean +): void { + // Do not copy transform props. + // Sub paths are transfrom based on their host path. + // tarPath.x = srcPath.x; + // tarPath.y = srcPath.y; + // tarPath.scaleX = srcPath.scaleX; + // tarPath.scaleY = srcPath.scaleY; + // tarPath.originX = srcPath.originX; + // tarPath.originY = srcPath.originY; + + // If just carry the style, will not be modifed, so do not copy. + tarPath.style = willClone + ? clone(srcPath.style) + : srcPath.style; + + tarPath.zlevel = srcPath.zlevel; + tarPath.z = srcPath.z; + tarPath.z2 = srcPath.z2; +} + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(CustomSeriesView); + registers.registerSeriesModel(CustomSeriesModel); +} \ No newline at end of file diff --git a/src/chart/effectScatter.ts b/src/chart/effectScatter.ts index 06e7dda67c..b4226f515d 100644 --- a/src/chart/effectScatter.ts +++ b/src/chart/effectScatter.ts @@ -17,11 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './effectScatter/install'; -import './effectScatter/EffectScatterSeries'; -import './effectScatter/EffectScatterView'; - -import layoutPoints from '../layout/points'; - -echarts.registerLayout(layoutPoints('effectScatter')); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/effectScatter/EffectScatterSeries.ts b/src/chart/effectScatter/EffectScatterSeries.ts index 1d4c7fa696..f0340fb9ac 100644 --- a/src/chart/effectScatter/EffectScatterSeries.ts +++ b/src/chart/effectScatter/EffectScatterSeries.ts @@ -137,6 +137,4 @@ class EffectScatterSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(EffectScatterSeriesModel); - export default EffectScatterSeriesModel; \ No newline at end of file diff --git a/src/chart/effectScatter/EffectScatterView.ts b/src/chart/effectScatter/EffectScatterView.ts index 3209a89672..722073140f 100644 --- a/src/chart/effectScatter/EffectScatterView.ts +++ b/src/chart/effectScatter/EffectScatterView.ts @@ -24,7 +24,7 @@ import * as matrix from 'zrender/src/core/matrix'; import pointsLayout from '../../layout/points'; import ChartView from '../../view/Chart'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import EffectScatterSeriesModel from './EffectScatterSeries'; import { StageHandlerProgressExecutor } from '../../util/types'; @@ -75,8 +75,4 @@ class EffectScatterView extends ChartView { } } - -ChartView.registerClass(EffectScatterView); - - export default EffectScatterView; \ No newline at end of file diff --git a/src/chart/effectScatter/install.ts b/src/chart/effectScatter/install.ts new file mode 100644 index 0000000000..c50addaea1 --- /dev/null +++ b/src/chart/effectScatter/install.ts @@ -0,0 +1,30 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import EffectScatterView from './EffectScatterView'; +import EffectScatterSeriesModel from './EffectScatterSeries'; + +import layoutPoints from '../../layout/points'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(EffectScatterView); + registers.registerSeriesModel(EffectScatterSeriesModel); + registers.registerLayout(layoutPoints('effectScatter')); +} \ No newline at end of file diff --git a/src/chart/funnel.ts b/src/chart/funnel.ts index 98c5594b5d..8755679eb4 100644 --- a/src/chart/funnel.ts +++ b/src/chart/funnel.ts @@ -17,13 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './funnel/install'; -import './funnel/FunnelSeries'; -import './funnel/FunnelView'; - -import funnelLayout from './funnel/funnelLayout'; -import dataFilter from '../processor/dataFilter'; - -echarts.registerLayout(funnelLayout); -echarts.registerProcessor(dataFilter('funnel')); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/funnel/FunnelSeries.ts b/src/chart/funnel/FunnelSeries.ts index 4fb89c7c55..eb99752100 100644 --- a/src/chart/funnel/FunnelSeries.ts +++ b/src/chart/funnel/FunnelSeries.ts @@ -191,6 +191,4 @@ class FunnelSeriesModel extends SeriesModel { } -ComponentModel.registerClass(FunnelSeriesModel); - export default FunnelSeriesModel; diff --git a/src/chart/funnel/FunnelView.ts b/src/chart/funnel/FunnelView.ts index 63895adcc8..ab9b202927 100644 --- a/src/chart/funnel/FunnelView.ts +++ b/src/chart/funnel/FunnelView.ts @@ -22,7 +22,7 @@ import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states import ChartView from '../../view/Chart'; import FunnelSeriesModel, {FunnelDataItemOption} from './FunnelSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import List from '../../data/List'; import { ColorString } from '../../util/types'; import { setLabelLineStyle, getLabelLineStatesModels } from '../../label/labelGuideHelper'; @@ -209,7 +209,5 @@ class FunnelView extends ChartView { dispose() {} } -ChartView.registerClass(FunnelView); - export default FunnelView; \ No newline at end of file diff --git a/src/chart/funnel/funnelLayout.ts b/src/chart/funnel/funnelLayout.ts index 22d4d28861..71111c3804 100644 --- a/src/chart/funnel/funnelLayout.ts +++ b/src/chart/funnel/funnelLayout.ts @@ -20,7 +20,7 @@ import * as layout from '../../util/layout'; import {parsePercent, linearMap} from '../../util/number'; import FunnelSeriesModel, { FunnelSeriesOption, FunnelDataItemOption } from './FunnelSeries'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import List from '../../data/List'; import GlobalModel from '../../model/Global'; diff --git a/src/chart/funnel/install.ts b/src/chart/funnel/install.ts new file mode 100644 index 0000000000..103b1279f5 --- /dev/null +++ b/src/chart/funnel/install.ts @@ -0,0 +1,31 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import FunnelView from './FunnelView'; +import FunnelSeriesModel from './FunnelSeries'; +import funnelLayout from './funnelLayout'; +import dataFilter from '../../processor/dataFilter'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(FunnelView); + registers.registerSeriesModel(FunnelSeriesModel); + registers.registerLayout(funnelLayout); + registers.registerProcessor(dataFilter('funnel')); +} \ No newline at end of file diff --git a/src/chart/gauge.ts b/src/chart/gauge.ts index 0aeac37629..2985d8b036 100644 --- a/src/chart/gauge.ts +++ b/src/chart/gauge.ts @@ -17,5 +17,7 @@ * under the License. */ -import './gauge/GaugeSeries'; -import './gauge/GaugeView'; \ No newline at end of file +import { use } from '../extension'; +import { install } from './gauge/install'; + +use(install); \ No newline at end of file diff --git a/src/chart/gauge/GaugeSeries.ts b/src/chart/gauge/GaugeSeries.ts index bdce305d37..64a694070f 100644 --- a/src/chart/gauge/GaugeSeries.ts +++ b/src/chart/gauge/GaugeSeries.ts @@ -308,7 +308,5 @@ class GaugeSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(GaugeSeriesModel); - export default GaugeSeriesModel; \ No newline at end of file diff --git a/src/chart/gauge/GaugeView.ts b/src/chart/gauge/GaugeView.ts index 0560361acb..82bcdfe61a 100644 --- a/src/chart/gauge/GaugeView.ts +++ b/src/chart/gauge/GaugeView.ts @@ -25,7 +25,7 @@ import ChartView from '../../view/Chart'; import {parsePercent, round, linearMap} from '../../util/number'; import GaugeSeriesModel, { GaugeDataItemOption } from './GaugeSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ColorString, ECElement, ParsedValue } from '../../util/types'; import List from '../../data/List'; import Sausage from '../../util/shape/sausage'; @@ -631,6 +631,4 @@ class GaugeView extends ChartView { } } -ChartView.registerClass(GaugeView); - export default GaugeView; diff --git a/src/chart/gauge/install.ts b/src/chart/gauge/install.ts new file mode 100644 index 0000000000..456fb87675 --- /dev/null +++ b/src/chart/gauge/install.ts @@ -0,0 +1,27 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import GaugeView from './GaugeView'; +import GaugeSeriesModel from './GaugeSeries'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(GaugeView); + registers.registerSeriesModel(GaugeSeriesModel); +} \ No newline at end of file diff --git a/src/chart/graph.ts b/src/chart/graph.ts index 9ed32c8a96..ba6533c0ba 100644 --- a/src/chart/graph.ts +++ b/src/chart/graph.ts @@ -17,32 +17,8 @@ * under the License. */ -import * as echarts from '../echarts'; -import './graph/GraphSeries'; -import './graph/GraphView'; -import './graph/graphAction'; +import { use } from '../extension'; +import { install } from './graph/install'; -import categoryFilter from './graph/categoryFilter'; -import categoryVisual from './graph/categoryVisual'; -import edgeVisual from './graph/edgeVisual'; -import simpleLayout from './graph/simpleLayout'; -import circularLayout from './graph/circularLayout'; -import forceLayout from './graph/forceLayout'; -import createView from './graph/createView'; -import View from '../coord/View'; - -echarts.registerProcessor(categoryFilter); - -echarts.registerVisual(categoryVisual); -echarts.registerVisual(edgeVisual); - -echarts.registerLayout(simpleLayout); -echarts.registerLayout(echarts.PRIORITY.VISUAL.POST_CHART_LAYOUT, circularLayout); -echarts.registerLayout(forceLayout); - -// Graph view coordinate system -echarts.registerCoordinateSystem('graphView', { - dimensions: View.dimensions, - create: createView -}); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts index 2471725cb0..076bec8c5c 100644 --- a/src/chart/graph/GraphSeries.ts +++ b/src/chart/graph/GraphSeries.ts @@ -42,8 +42,8 @@ import { StatesOptionMixin, GraphEdgeItemObject, OptionDataValueNumeric, - DefaultExtraEmpasisState, - CallbackDataParams + CallbackDataParams, + DefaultEmphasisFocus } from '../../util/types'; import SeriesModel from '../../model/Series'; import Graph from '../../data/Graph'; @@ -69,7 +69,7 @@ export interface GraphNodeStateOption { interface ExtraEmphasisState { - focus?: DefaultExtraEmpasisState['focus'] | 'adjacency' + focus?: DefaultEmphasisFocus | 'adjacency' } interface ExtraNodeStateOption { emphasis?: ExtraEmphasisState @@ -178,7 +178,7 @@ export interface GraphSeriesOption extends SeriesOption, lineStyle?: GraphEdgeLineStyleOption emphasis?: { - focus?: GraphNodeItemOption['emphasis']['focus'] + focus?: Exclude['focus'] scale?: boolean label?: SeriesLabelOption edgeLabel?: SeriesLabelOption @@ -225,6 +225,8 @@ class GraphSeriesModel extends SeriesModel { static readonly type = 'series.graph'; readonly type = GraphSeriesModel.type; + static readonly dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; + private _categoriesData: List; private _categoriesModels: Model[]; @@ -503,6 +505,4 @@ class GraphSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(GraphSeriesModel); - export default GraphSeriesModel; \ No newline at end of file diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts index c632ad62d9..ee068a8566 100644 --- a/src/chart/graph/GraphView.ts +++ b/src/chart/graph/GraphView.ts @@ -28,7 +28,7 @@ import adjustEdge from './adjustEdge'; import {getNodeGlobalScale} from './graphHelper'; import ChartView from '../../view/Chart'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GraphSeriesModel, { GraphNodeItemOption, GraphEdgeItemOption } from './GraphSeries'; import { CoordinateSystem } from '../../coord/CoordinateSystem'; import View from '../../coord/View'; @@ -305,6 +305,4 @@ class GraphView extends ChartView { } } -ChartView.registerClass(GraphView); - export default GraphView; \ No newline at end of file diff --git a/src/chart/graph/createView.ts b/src/chart/graph/createView.ts index 79a6388886..11324eb8fb 100644 --- a/src/chart/graph/createView.ts +++ b/src/chart/graph/createView.ts @@ -22,7 +22,7 @@ import View from '../../coord/View'; import {getLayoutRect} from '../../util/layout'; import * as bbox from 'zrender/src/core/bbox'; import GraphSeriesModel, { GraphNodeItemOption } from './GraphSeries'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GlobalModel from '../../model/Global'; import { extend } from 'zrender/src/core/util'; diff --git a/src/chart/graph/graphAction.ts b/src/chart/graph/graphAction.ts deleted file mode 100644 index 87350dc108..0000000000 --- a/src/chart/graph/graphAction.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -import * as echarts from '../../echarts'; -import {updateCenterAndZoom, RoamPaylod} from '../../action/roamHelper'; -import '../helper/focusNodeAdjacencyAction'; -import GlobalModel from '../../model/Global'; -import GraphSeriesModel from './GraphSeries'; -import View from '../../coord/View'; - -const actionInfo = { - type: 'graphRoam', - event: 'graphRoam', - update: 'none' -}; - -/** - * @payload - * @property {string} name Series name - * @property {number} [dx] - * @property {number} [dy] - * @property {number} [zoom] - * @property {number} [originX] - * @property {number} [originY] - */ -echarts.registerAction(actionInfo, function (payload: RoamPaylod, ecModel: GlobalModel) { - ecModel.eachComponent({ - mainType: 'series', query: payload - }, function (seriesModel: GraphSeriesModel) { - const coordSys = seriesModel.coordinateSystem as View; - - const res = updateCenterAndZoom(coordSys, payload); - - seriesModel.setCenter - && seriesModel.setCenter(res.center); - - seriesModel.setZoom - && seriesModel.setZoom(res.zoom); - }); -}); - diff --git a/src/chart/graph/install.ts b/src/chart/graph/install.ts new file mode 100644 index 0000000000..16ccf75fa5 --- /dev/null +++ b/src/chart/graph/install.ts @@ -0,0 +1,91 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; + +import categoryFilter from './categoryFilter'; +import categoryVisual from './categoryVisual'; +import edgeVisual from './edgeVisual'; +import simpleLayout from './simpleLayout'; +import circularLayout from './circularLayout'; +import forceLayout from './forceLayout'; +import createView from './createView'; +import View from '../../coord/View'; +import GraphView from './GraphView'; +import GraphSeriesModel from './GraphSeries'; +import { RoamPaylod, updateCenterAndZoom } from '../../action/roamHelper'; +import GlobalModel from '../../model/Global'; + +const actionInfo = { + type: 'graphRoam', + event: 'graphRoam', + update: 'none' +}; + +export function install(registers: EChartsExtensionInstallRegisters) { + + registers.registerChartView(GraphView); + registers.registerSeriesModel(GraphSeriesModel); + + registers.registerProcessor(categoryFilter); + + registers.registerVisual(categoryVisual); + registers.registerVisual(edgeVisual); + + registers.registerLayout(simpleLayout); + registers.registerLayout(registers.PRIORITY.VISUAL.POST_CHART_LAYOUT, circularLayout); + registers.registerLayout(forceLayout); + + registers.registerCoordinateSystem('graphView', { + dimensions: View.dimensions, + create: createView + }); + + // Register legacy focus actions + registers.registerAction({ + type: 'focusNodeAdjacency', + event: 'focusNodeAdjacency', + update: 'series:focusNodeAdjacency' + }, function () {}); + + registers.registerAction({ + type: 'unfocusNodeAdjacency', + event: 'unfocusNodeAdjacency', + update: 'series:unfocusNodeAdjacency' + }, function () {}); + + // Register roam action. + registers.registerAction(actionInfo, function (payload: RoamPaylod, ecModel: GlobalModel) { + ecModel.eachComponent({ + mainType: 'series', query: payload + }, function (seriesModel: GraphSeriesModel) { + const coordSys = seriesModel.coordinateSystem as View; + + const res = updateCenterAndZoom(coordSys, payload); + + seriesModel.setCenter + && seriesModel.setCenter(res.center); + + seriesModel.setZoom + && seriesModel.setZoom(res.zoom); + }); + }); + + +} \ No newline at end of file diff --git a/src/chart/graph/simpleLayout.ts b/src/chart/graph/simpleLayout.ts index a288ed5101..b28ddeb1d2 100644 --- a/src/chart/graph/simpleLayout.ts +++ b/src/chart/graph/simpleLayout.ts @@ -20,7 +20,7 @@ import {each} from 'zrender/src/core/util'; import {simpleLayout, simpleLayoutEdge} from './simpleLayoutHelper'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GraphSeriesModel from './GraphSeries'; export default function graphSimpleLayout(ecModel: GlobalModel, api: ExtensionAPI) { diff --git a/src/chart/heatmap.ts b/src/chart/heatmap.ts index 788e50bd8e..c719eae223 100644 --- a/src/chart/heatmap.ts +++ b/src/chart/heatmap.ts @@ -17,5 +17,8 @@ * under the License. */ -import './heatmap/HeatmapSeries'; -import './heatmap/HeatmapView'; \ No newline at end of file + +import { use } from '../extension'; +import { install } from './heatmap/install'; + +use(install); \ No newline at end of file diff --git a/src/chart/heatmap/HeatmapSeries.ts b/src/chart/heatmap/HeatmapSeries.ts index 5139e315e9..af831ef135 100644 --- a/src/chart/heatmap/HeatmapSeries.ts +++ b/src/chart/heatmap/HeatmapSeries.ts @@ -19,7 +19,7 @@ import SeriesModel from '../../model/Series'; import createListFromArray from '../helper/createListFromArray'; -import CoordinateSystem from '../../CoordinateSystem'; +import CoordinateSystem from '../../core/CoordinateSystem'; import { SeriesOption, SeriesOnCartesianOptionMixin, @@ -68,6 +68,7 @@ class HeatmapSeriesModel extends SeriesModel { static readonly type = 'series.heatmap'; readonly type = HeatmapSeriesModel.type; + static readonly dependencies = ['grid', 'geo', 'calendar']; // @ts-ignore coordinateSystem: Cartesian2D | Geo | Calendar; @@ -115,6 +116,4 @@ class HeatmapSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(HeatmapSeriesModel); - export default HeatmapSeriesModel; \ No newline at end of file diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts index 12bc997571..23e15ee54a 100644 --- a/src/chart/heatmap/HeatmapView.ts +++ b/src/chart/heatmap/HeatmapView.ts @@ -24,7 +24,7 @@ import * as zrUtil from 'zrender/src/core/util'; import ChartView from '../../view/Chart'; import HeatmapSeriesModel, { HeatmapDataItemOption } from './HeatmapSeries'; import type GlobalModel from '../../model/Global'; -import type ExtensionAPI from '../../ExtensionAPI'; +import type ExtensionAPI from '../../core/ExtensionAPI'; import type VisualMapModel from '../../component/visualMap/VisualMapModel'; import type PiecewiseModel from '../../component/visualMap/PiecewiseModel'; import type ContinuousModel from '../../component/visualMap/ContinuousModel'; @@ -379,6 +379,4 @@ class HeatmapView extends ChartView { } } -ChartView.registerClass(HeatmapView); - export default HeatmapView; \ No newline at end of file diff --git a/src/chart/heatmap/install.ts b/src/chart/heatmap/install.ts new file mode 100644 index 0000000000..e8401cf1ae --- /dev/null +++ b/src/chart/heatmap/install.ts @@ -0,0 +1,27 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import HeatmapView from './HeatmapView'; +import HeatmapSeriesModel from './HeatmapSeries'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(HeatmapView); + registers.registerSeriesModel(HeatmapSeriesModel); +} \ No newline at end of file diff --git a/src/chart/helper/createGraphFromNodeEdge.ts b/src/chart/helper/createGraphFromNodeEdge.ts index 5e30dcfac9..e2a0fca814 100644 --- a/src/chart/helper/createGraphFromNodeEdge.ts +++ b/src/chart/helper/createGraphFromNodeEdge.ts @@ -23,7 +23,7 @@ import List from '../../data/List'; import Graph from '../../data/Graph'; import linkList from '../../data/helper/linkList'; import createDimensions from '../../data/helper/createDimensions'; -import CoordinateSystem from '../../CoordinateSystem'; +import CoordinateSystem from '../../core/CoordinateSystem'; import createListFromArray from './createListFromArray'; import { OptionSourceDataOriginal, GraphEdgeItemObject, OptionDataValue, diff --git a/src/chart/helper/createListFromArray.ts b/src/chart/helper/createListFromArray.ts index 215e660836..1c3768c291 100644 --- a/src/chart/helper/createListFromArray.ts +++ b/src/chart/helper/createListFromArray.ts @@ -22,7 +22,7 @@ import List from '../../data/List'; import createDimensions from '../../data/helper/createDimensions'; import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper'; import {getDataItemValue} from '../../util/model'; -import CoordinateSystem from '../../CoordinateSystem'; +import CoordinateSystem from '../../core/CoordinateSystem'; import {getCoordSysInfoBySeries} from '../../model/referHelper'; import { createSourceFromSeriesDataOption, isSourceInstance, Source } from '../../data/Source'; import {enableDataStack} from '../../data/helper/dataStackHelper'; diff --git a/src/chart/line.ts b/src/chart/line.ts index 53be4a6600..886ea79e14 100644 --- a/src/chart/line.ts +++ b/src/chart/line.ts @@ -17,21 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './line/install'; -import './line/LineSeries'; -import './line/LineView'; - -import layoutPoints from '../layout/points'; -import dataSample from '../processor/dataSample'; - -// In case developer forget to include grid component -import '../component/gridSimple'; - -echarts.registerLayout(layoutPoints('line', true)); - -// Down sample after filter -echarts.registerProcessor( - echarts.PRIORITY.PROCESSOR.STATISTIC, - dataSample('line') -); +use(install); \ No newline at end of file diff --git a/src/chart/line/LineSeries.ts b/src/chart/line/LineSeries.ts index fb50f9ce4e..f940873046 100644 --- a/src/chart/line/LineSeries.ts +++ b/src/chart/line/LineSeries.ts @@ -32,9 +32,9 @@ import { SymbolOptionMixin, SeriesSamplingOptionMixin, StatesOptionMixin, - DefaultExtraEmpasisState, SeriesEncodeOptionMixin, - CallbackDataParams + CallbackDataParams, + DefaultEmphasisFocus } from '../../util/types'; import List from '../../data/List'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; @@ -44,7 +44,7 @@ type LineDataValue = OptionDataValue | OptionDataValue[]; interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] + focus?: DefaultEmphasisFocus scale?: boolean } } @@ -206,6 +206,4 @@ class LineSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(LineSeriesModel); - export default LineSeriesModel; diff --git a/src/chart/line/LineView.ts b/src/chart/line/LineView.ts index 9ed8becae2..1c20120a97 100644 --- a/src/chart/line/LineView.ts +++ b/src/chart/line/LineView.ts @@ -31,7 +31,7 @@ import {prepareDataCoordInfo, getStackedOnPoint} from './helper'; import {createGridClipPath, createPolarClipPath} from '../helper/createClipPathFromCoordSys'; import LineSeriesModel, { LineSeriesOption } from './LineSeries'; import type GlobalModel from '../../model/Global'; -import type ExtensionAPI from '../../ExtensionAPI'; +import type ExtensionAPI from '../../core/ExtensionAPI'; // TODO import Cartesian2D from '../../coord/cartesian/Cartesian2D'; import Polar from '../../coord/polar/Polar'; @@ -1300,6 +1300,4 @@ class LineView extends ChartView { } } -ChartView.registerClass(LineView); - -export default ChartView; +export default LineView; diff --git a/src/chart/line/install.ts b/src/chart/line/install.ts new file mode 100644 index 0000000000..af11575715 --- /dev/null +++ b/src/chart/line/install.ts @@ -0,0 +1,43 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import LineSeries from './LineSeries'; +import LineView from './LineView'; + +// In case developer forget to include grid component + +import layoutPoints from '../../layout/points'; +import dataSample from '../../processor/dataSample'; + +import { EChartsExtensionInstallRegisters } from '../../extension'; + +export function install(registers: EChartsExtensionInstallRegisters) { + + registers.registerChartView(LineView); + registers.registerSeriesModel(LineSeries); + + registers.registerLayout(layoutPoints('line', true)); + + // Down sample after filter + registers.registerProcessor( + registers.PRIORITY.PROCESSOR.STATISTIC, + dataSample('line') + ); + +} \ No newline at end of file diff --git a/src/chart/lines.ts b/src/chart/lines.ts index 95b41b7272..1a5ccca91d 100644 --- a/src/chart/lines.ts +++ b/src/chart/lines.ts @@ -17,13 +17,8 @@ * under the License. */ -import * as echarts from '../echarts'; -import './lines/LinesSeries'; -import './lines/LinesView'; +import { use } from '../extension'; +import { install } from './lines/install'; -import linesLayout from './lines/linesLayout'; -import linesVisual from './lines/linesVisual'; - -echarts.registerLayout(linesLayout); -echarts.registerVisual(linesVisual); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/lines/LinesSeries.ts b/src/chart/lines/LinesSeries.ts index cd9d8065f7..98cb2dd5e4 100644 --- a/src/chart/lines/LinesSeries.ts +++ b/src/chart/lines/LinesSeries.ts @@ -22,7 +22,7 @@ import SeriesModel from '../../model/Series'; import List from '../../data/List'; import { concatArray, mergeAll, map } from 'zrender/src/core/util'; -import CoordinateSystem from '../../CoordinateSystem'; +import CoordinateSystem from '../../core/CoordinateSystem'; import { SeriesOption, SeriesOnCartesianOptionMixin, @@ -405,6 +405,4 @@ class LinesSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(LinesSeriesModel); - export default LinesSeriesModel; diff --git a/src/chart/lines/LinesView.ts b/src/chart/lines/LinesView.ts index effb2fa594..72453c16fd 100644 --- a/src/chart/lines/LinesView.ts +++ b/src/chart/lines/LinesView.ts @@ -28,7 +28,7 @@ import {createClipPath} from '../helper/createClipPathFromCoordSys'; import ChartView from '../../view/Chart'; import LinesSeriesModel from './LinesSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import CanvasPainter from 'zrender/src/canvas/Painter'; import { StageHandlerProgressParams, StageHandlerProgressExecutor } from '../../util/types'; import List from '../../data/List'; @@ -216,6 +216,4 @@ class LinesView extends ChartView { } -ChartView.registerClass(LinesView); - export default LinesView; \ No newline at end of file diff --git a/src/chart/lines/install.ts b/src/chart/lines/install.ts new file mode 100644 index 0000000000..952bb5205b --- /dev/null +++ b/src/chart/lines/install.ts @@ -0,0 +1,31 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import LinesView from './LinesView'; +import LinesSeriesModel from './LinesSeries'; +import linesLayout from './linesLayout'; +import linesVisual from './linesVisual'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(LinesView); + registers.registerSeriesModel(LinesSeriesModel); + registers.registerLayout(linesLayout); + registers.registerVisual(linesVisual); +} \ No newline at end of file diff --git a/src/chart/map.ts b/src/chart/map.ts index 9689b2dcf4..74e6d3a843 100644 --- a/src/chart/map.ts +++ b/src/chart/map.ts @@ -17,18 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './map/install'; -import './map/MapSeries'; -import './map/MapView'; -import '../action/geoRoam'; -import '../coord/geo/geoCreator'; - -import mapSymbolLayout from './map/mapSymbolLayout'; -import mapDataStatistic from './map/mapDataStatistic'; -import {createLegacyDataSelectAction} from '../legacy/dataSelectAction'; - -echarts.registerLayout(mapSymbolLayout); -echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.STATISTIC, mapDataStatistic); - -createLegacyDataSelectAction('map', echarts.registerAction); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/map/MapSeries.ts b/src/chart/map/MapSeries.ts index 38c1736eff..2f0f3d8250 100644 --- a/src/chart/map/MapSeries.ts +++ b/src/chart/map/MapSeries.ts @@ -311,6 +311,4 @@ class MapSeries extends SeriesModel { } -SeriesModel.registerClass(MapSeries); - export default MapSeries; \ No newline at end of file diff --git a/src/chart/map/MapView.ts b/src/chart/map/MapView.ts index 1dda02f22a..48df6e8441 100644 --- a/src/chart/map/MapView.ts +++ b/src/chart/map/MapView.ts @@ -23,7 +23,7 @@ import MapDraw from '../../component/helper/MapDraw'; import ChartView from '../../view/Chart'; import MapSeries, { MapDataItemOption } from './MapSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Payload, DisplayState, ECElement } from '../../util/types'; import Model from '../../model/Model'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; @@ -196,45 +196,4 @@ class MapView extends ChartView { } } -// function onRegionHighDown(this: RegionGroupExtend, toHighOrDown: boolean): void { -// let highDownRecord = this[HIGH_DOWN_PROP]; -// if (highDownRecord && highDownRecord.recordVersion === this[RECORD_VERSION_PROP]) { -// enterRegionHighDown(highDownRecord, toHighOrDown); -// } -// } - -// function enterRegionHighDown(highDownRecord: HighDownRecord, toHighOrDown: boolean): void { -// let circle = highDownRecord.circle; -// let labelModel = highDownRecord.labelModel; -// let hoverLabelModel = highDownRecord.hoverLabelModel; -// let emphasisText = highDownRecord.emphasisText; -// let normalText = highDownRecord.normalText; - -// if (toHighOrDown) { -// circle.style.extendFrom( -// graphic.setTextStyle({}, hoverLabelModel, { -// text: hoverLabelModel.get('show') ? emphasisText : null -// }, {isRectText: true, useInsideStyle: false}, true) -// ); -// // Make label upper than others if overlaps. -// circle[ORIGINAL_Z2] = circle.z2; -// circle.z2 += graphic.Z2_EMPHASIS_LIFT; -// } -// else { -// graphic.setTextStyle(circle.style, labelModel, { -// text: labelModel.get('show') ? normalText : null, -// textPosition: labelModel.getShallow('position') || 'bottom' -// }, {isRectText: true, useInsideStyle: false}); -// // Trigger normalize style like padding. -// circle.markRedraw(); - -// if (circle[ORIGINAL_Z2] != null) { -// circle.z2 = circle[ORIGINAL_Z2]; -// circle[ORIGINAL_Z2] = null; -// } -// } -// } - -ChartView.registerClass(MapView); - export default MapView; diff --git a/src/chart/map/install.ts b/src/chart/map/install.ts new file mode 100644 index 0000000000..93301ce34e --- /dev/null +++ b/src/chart/map/install.ts @@ -0,0 +1,35 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import MapView from './MapView'; +import MapSeries from './MapSeries'; +import mapDataStatistic from './mapDataStatistic'; +import mapSymbolLayout from './mapSymbolLayout'; +import {createLegacyDataSelectAction} from '../../legacy/dataSelectAction'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(MapView); + registers.registerSeriesModel(MapSeries); + + registers.registerLayout(mapSymbolLayout); + registers.registerProcessor(registers.PRIORITY.PROCESSOR.STATISTIC, mapDataStatistic); + + createLegacyDataSelectAction('map', registers.registerAction); +} \ No newline at end of file diff --git a/src/chart/parallel.ts b/src/chart/parallel.ts index 83c1996e40..7dd6c291cd 100644 --- a/src/chart/parallel.ts +++ b/src/chart/parallel.ts @@ -17,10 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import '../component/parallel'; -import './parallel/ParallelSeries'; -import './parallel/ParallelView'; -import parallelVisual from './parallel/parallelVisual'; +import { use } from '../extension'; +import { install } from './parallel/install'; -echarts.registerVisual(echarts.PRIORITY.VISUAL.BRUSH, parallelVisual); +use(install); \ No newline at end of file diff --git a/src/chart/parallel/ParallelSeries.ts b/src/chart/parallel/ParallelSeries.ts index 845a828cfa..aaca025b05 100644 --- a/src/chart/parallel/ParallelSeries.ts +++ b/src/chart/parallel/ParallelSeries.ts @@ -149,8 +149,6 @@ class ParallelSeriesModel extends SeriesModel { } -SeriesModel.registerClass(ParallelSeriesModel); - function makeDefaultEncode(seriesModel: ParallelSeriesModel): OptionEncode { // The mapping of parallelAxis dimension to data dimension can // be specified in parallelAxis.option.dim. For example, if diff --git a/src/chart/parallel/ParallelView.ts b/src/chart/parallel/ParallelView.ts index 2ca004712e..8449497c79 100644 --- a/src/chart/parallel/ParallelView.ts +++ b/src/chart/parallel/ParallelView.ts @@ -23,7 +23,7 @@ import ChartView from '../../view/Chart'; import List from '../../data/List'; import ParallelSeriesModel, { ParallelSeriesDataItemOption } from './ParallelSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { StageHandlerProgressParams, ParsedValue, Payload } from '../../util/types'; import Parallel from '../../coord/parallel/Parallel'; import { OptionAxisType } from '../../coord/axisCommonTypes'; @@ -235,6 +235,4 @@ function isEmptyValue(val: ParsedValue, axisType: OptionAxisType) { : (val == null || isNaN(val as number)); // axisType === 'value' } -ChartView.registerClass(ParallelView); - export default ParallelView; diff --git a/src/chart/parallel/install.ts b/src/chart/parallel/install.ts new file mode 100644 index 0000000000..bbd9075846 --- /dev/null +++ b/src/chart/parallel/install.ts @@ -0,0 +1,33 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import ParallelView from './ParallelView'; +import ParallelSeriesModel from './ParallelSeries'; +import parallelVisual from './parallelVisual'; +import {install as installParallelComponent} from '../../component/parallel/install'; + +export function install(registers: EChartsExtensionInstallRegisters) { + + use(installParallelComponent); + + registers.registerChartView(ParallelView); + registers.registerSeriesModel(ParallelSeriesModel); + registers.registerVisual(registers.PRIORITY.VISUAL.BRUSH, parallelVisual); +} \ No newline at end of file diff --git a/src/chart/pictorialBar.ts b/src/chart/pictorialBar.ts index 65e494caf5..5f251c6c99 100644 --- a/src/chart/pictorialBar.ts +++ b/src/chart/pictorialBar.ts @@ -17,18 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; +import { use } from '../extension'; +import { install } from './bar/installPictorialBar'; -import '../coord/cartesian/Grid'; -import './bar/PictorialBarSeries'; -import './bar/PictorialBarView'; - -import { layout } from '../layout/barGrid'; - -// In case developer forget to include grid component -import '../component/gridSimple'; - -echarts.registerLayout(zrUtil.curry( - layout, 'pictorialBar' -)); +use(install); \ No newline at end of file diff --git a/src/chart/pie.ts b/src/chart/pie.ts index 664f8e0d16..3228169121 100644 --- a/src/chart/pie.ts +++ b/src/chart/pie.ts @@ -17,17 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; +import { use } from '../extension'; +import { install } from './pie/install'; -import './pie/PieSeries'; -import './pie/PieView'; - -import {createLegacyDataSelectAction} from '../legacy/dataSelectAction'; -import pieLayout from './pie/pieLayout'; -import dataFilter from '../processor/dataFilter'; - -createLegacyDataSelectAction('pie', echarts.registerAction); - -echarts.registerLayout(zrUtil.curry(pieLayout, 'pie')); -echarts.registerProcessor(dataFilter('pie')); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts index a74c31aa3f..d9ecd98c2b 100644 --- a/src/chart/pie/PieSeries.ts +++ b/src/chart/pie/PieSeries.ts @@ -35,8 +35,8 @@ import { SeriesEncodeOptionMixin, OptionDataItemObject, StatesOptionMixin, - DefaultExtraEmpasisState, - SeriesLabelOption + SeriesLabelOption, + DefaultEmphasisFocus } from '../../util/types'; import List from '../../data/List'; @@ -78,7 +78,7 @@ interface PieLabelLineOption extends LabelLineOption { interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] + focus?: DefaultEmphasisFocus scale?: boolean scaleSize?: number } @@ -301,6 +301,4 @@ class PieSeriesModel extends SeriesModel { } -SeriesModel.registerClass(PieSeriesModel); - export default PieSeriesModel; diff --git a/src/chart/pie/PieView.ts b/src/chart/pie/PieView.ts index 02c03e8438..ba1ade0de1 100644 --- a/src/chart/pie/PieView.ts +++ b/src/chart/pie/PieView.ts @@ -24,7 +24,7 @@ import * as graphic from '../../util/graphic'; import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states'; import ChartView from '../../view/Chart'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Payload, ColorString } from '../../util/types'; import List from '../../data/List'; import PieSeriesModel, {PieDataItemOption} from './PieSeries'; @@ -273,6 +273,4 @@ class PieView extends ChartView { } } -ChartView.registerClass(PieView); - export default PieView; diff --git a/src/action/changeAxisOrder.ts b/src/chart/pie/install.ts similarity index 54% rename from src/action/changeAxisOrder.ts rename to src/chart/pie/install.ts index bf63080402..79027b224f 100644 --- a/src/action/changeAxisOrder.ts +++ b/src/chart/pie/install.ts @@ -17,33 +17,21 @@ * under the License. */ -// @ts-nocheck +import { EChartsExtensionInstallRegisters } from '../../extension'; -import * as echarts from '../echarts'; +import {createLegacyDataSelectAction} from '../../legacy/dataSelectAction'; +import pieLayout from '../pie/pieLayout'; +import dataFilter from '../../processor/dataFilter'; +import { curry } from 'zrender/src/core/util'; +import PieView from './PieView'; +import PieSeriesModel from './PieSeries'; +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(PieView); + registers.registerSeriesModel(PieSeriesModel); -/** - * @payload - * @property {string} [componentType=series] - * @property {number} [dx] - * @property {number} [dy] - * @property {number} [zoom] - * @property {number} [originX] - * @property {number} [originY] - */ -echarts.registerAction({ - type: 'changeAxisOrder', - event: 'changeAxisOrder', - update: 'update' -}, function (payload, ecModel) { - const componentType = payload.componentType || 'series'; + createLegacyDataSelectAction('pie', registers.registerAction); - ecModel.eachComponent( - { mainType: componentType, query: payload }, - function (componentModel) { - if (payload.sortInfo) { - componentModel.axis.setCategorySortInfo(payload.sortInfo); - } - } - ); -}); + registers.registerLayout(curry(pieLayout, 'pie')); + registers.registerProcessor(dataFilter('pie')); +} \ No newline at end of file diff --git a/src/chart/pie/pieLayout.ts b/src/chart/pie/pieLayout.ts index d216b5b302..f515233d64 100644 --- a/src/chart/pie/pieLayout.ts +++ b/src/chart/pie/pieLayout.ts @@ -21,7 +21,7 @@ import {parsePercent, linearMap} from '../../util/number'; import * as layout from '../../util/layout'; import * as zrUtil from 'zrender/src/core/util'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import PieSeriesModel from './PieSeries'; const PI2 = Math.PI * 2; diff --git a/src/chart/radar.ts b/src/chart/radar.ts index 282537f81a..2483defa9d 100644 --- a/src/chart/radar.ts +++ b/src/chart/radar.ts @@ -17,17 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './radar/install'; -// Must use radar component -import '../component/radar'; -import './radar/RadarSeries'; -import './radar/RadarView'; - -import radarLayout from './radar/radarLayout'; -import dataFilter from '../processor/dataFilter'; -import backwardCompat from './radar/backwardCompat'; - -echarts.registerLayout(radarLayout); -echarts.registerProcessor(dataFilter('radar')); -echarts.registerPreprocessor(backwardCompat); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/radar/RadarSeries.ts b/src/chart/radar/RadarSeries.ts index a846027181..31c5e4cced 100644 --- a/src/chart/radar/RadarSeries.ts +++ b/src/chart/radar/RadarSeries.ts @@ -166,6 +166,4 @@ class RadarSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(RadarSeriesModel); - export default RadarSeriesModel; \ No newline at end of file diff --git a/src/chart/radar/RadarView.ts b/src/chart/radar/RadarView.ts index e87ae5c220..6060593b76 100644 --- a/src/chart/radar/RadarView.ts +++ b/src/chart/radar/RadarView.ts @@ -23,7 +23,7 @@ import * as zrUtil from 'zrender/src/core/util'; import * as symbolUtil from '../../util/symbol'; import ChartView from '../../view/Chart'; import RadarSeriesModel, { RadarSeriesDataItemOption } from './RadarSeries'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import List from '../../data/List'; import { ColorString } from '../../util/types'; import GlobalModel from '../../model/Global'; @@ -273,4 +273,4 @@ class RadarView extends ChartView { } } -ChartView.registerClass(RadarView); \ No newline at end of file +export default RadarView; \ No newline at end of file diff --git a/src/chart/radar/install.ts b/src/chart/radar/install.ts new file mode 100644 index 0000000000..8ebb3ab8e6 --- /dev/null +++ b/src/chart/radar/install.ts @@ -0,0 +1,37 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import radarLayout from '../radar/radarLayout'; +import dataFilter from '../../processor/dataFilter'; +import backwardCompat from '../radar/backwardCompat'; +import RadarView from './RadarView'; +import RadarSeriesModel from './RadarSeries'; +import { install as installRadarComponent } from '../../component/radar/install'; + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installRadarComponent); + + registers.registerChartView(RadarView); + registers.registerSeriesModel(RadarSeriesModel); + + registers.registerLayout(radarLayout); + registers.registerProcessor(dataFilter('radar')); + registers.registerPreprocessor(backwardCompat); +} \ No newline at end of file diff --git a/src/chart/sankey.ts b/src/chart/sankey.ts index d12c5affec..8692607e6d 100644 --- a/src/chart/sankey.ts +++ b/src/chart/sankey.ts @@ -17,14 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './sankey/install'; -import './sankey/SankeySeries'; -import './sankey/SankeyView'; -import './sankey/sankeyAction'; - -import sankeyLayout from './sankey/sankeyLayout'; -import sankeyVisual from './sankey/sankeyVisual'; - -echarts.registerLayout(sankeyLayout); -echarts.registerVisual(sankeyVisual); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts index b90716aa73..e12ee0c978 100644 --- a/src/chart/sankey/SankeySeries.ts +++ b/src/chart/sankey/SankeySeries.ts @@ -33,7 +33,7 @@ import { OptionDataItemObject, GraphEdgeItemObject, OptionDataValueNumeric, - DefaultExtraEmpasisState + DefaultEmphasisFocus } from '../../util/types'; import GlobalModel from '../../model/Global'; import List from '../../data/List'; @@ -61,7 +61,7 @@ interface SankeyEdgeStyleOption extends LineStyleOption { interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] | 'adjacency' + focus?: DefaultEmphasisFocus | 'adjacency' } } @@ -336,6 +336,4 @@ class SankeySeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(SankeySeriesModel); - export default SankeySeriesModel; \ No newline at end of file diff --git a/src/chart/sankey/SankeyView.ts b/src/chart/sankey/SankeyView.ts index cea0d4c777..c71d1398d2 100644 --- a/src/chart/sankey/SankeyView.ts +++ b/src/chart/sankey/SankeyView.ts @@ -24,7 +24,7 @@ import { PathProps } from 'zrender/src/graphic/Path'; import SankeySeriesModel, { SankeyEdgeItemOption, SankeyNodeItemOption } from './SankeySeries'; import ChartView from '../../view/Chart'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import List from '../../data/List'; import { RectLike } from 'zrender/src/core/BoundingRect'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; @@ -344,6 +344,4 @@ function createGridClipShape(rect: RectLike, seriesModel: SankeySeriesModel, cb: return rectEl; } -ChartView.registerClass(SankeyView); - export default SankeyView; \ No newline at end of file diff --git a/src/chart/sankey/sankeyAction.ts b/src/chart/sankey/install.ts similarity index 50% rename from src/chart/sankey/sankeyAction.ts rename to src/chart/sankey/install.ts index 216795a597..b4f2fd493d 100644 --- a/src/chart/sankey/sankeyAction.ts +++ b/src/chart/sankey/install.ts @@ -17,28 +17,40 @@ * under the License. */ -import * as echarts from '../../echarts'; -import '../helper/focusNodeAdjacencyAction'; +import { EChartsExtensionInstallRegisters } from '../../extension'; +import SankeyView from './SankeyView'; +import SankeySeriesModel from './SankeySeries'; + +import sankeyLayout from './sankeyLayout'; +import sankeyVisual from './sankeyVisual'; import { Payload } from '../../util/types'; import GlobalModel from '../../model/Global'; -import SankeySeriesModel from './SankeySeries'; interface SankeyDragNodePayload extends Payload { localX: number localY: number } -echarts.registerAction({ - type: 'dragNode', - event: 'dragnode', - // here can only use 'update' now, other value is not support in echarts. - update: 'update' -}, function (payload: SankeyDragNodePayload, ecModel: GlobalModel) { - ecModel.eachComponent({ - mainType: 'series', - subType: 'sankey', - query: payload - }, function (seriesModel: SankeySeriesModel) { - seriesModel.setNodePosition(payload.dataIndex, [payload.localX, payload.localY]); +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(SankeyView); + registers.registerSeriesModel(SankeySeriesModel); + + registers.registerLayout(sankeyLayout); + registers.registerVisual(sankeyVisual); + + registers.registerAction({ + type: 'dragNode', + event: 'dragnode', + // here can only use 'update' now, other value is not support in echarts. + update: 'update' + }, function (payload: SankeyDragNodePayload, ecModel: GlobalModel) { + ecModel.eachComponent({ + mainType: 'series', + subType: 'sankey', + query: payload + }, function (seriesModel: SankeySeriesModel) { + seriesModel.setNodePosition(payload.dataIndex, [payload.localX, payload.localY]); + }); }); -}); + +} \ No newline at end of file diff --git a/src/chart/sankey/sankeyLayout.ts b/src/chart/sankey/sankeyLayout.ts index ba872f7e81..c4343694b1 100644 --- a/src/chart/sankey/sankeyLayout.ts +++ b/src/chart/sankey/sankeyLayout.ts @@ -20,7 +20,7 @@ import * as layout from '../../util/layout'; import * as zrUtil from 'zrender/src/core/util'; import {groupData} from '../../util/model'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import SankeySeriesModel, { SankeySeriesOption, SankeyNodeItemOption } from './SankeySeries'; import { GraphNode, GraphEdge } from '../../data/Graph'; import { LayoutOrient } from '../../util/types'; diff --git a/src/chart/scatter.ts b/src/chart/scatter.ts index 91d572196b..ed13ae2680 100644 --- a/src/chart/scatter.ts +++ b/src/chart/scatter.ts @@ -17,14 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './scatter/install'; -import './scatter/ScatterSeries'; -import './scatter/ScatterView'; - -import layoutPoints from '../layout/points'; - -// In case developer forget to include grid component -import '../component/gridSimple'; - -echarts.registerLayout(layoutPoints('scatter')); +use(install); \ No newline at end of file diff --git a/src/chart/scatter/ScatterSeries.ts b/src/chart/scatter/ScatterSeries.ts index cdb64ed675..92efb41f47 100644 --- a/src/chart/scatter/ScatterSeries.ts +++ b/src/chart/scatter/ScatterSeries.ts @@ -34,9 +34,9 @@ import { SymbolOptionMixin, StatesOptionMixin, OptionDataItemObject, - DefaultExtraEmpasisState, SeriesEncodeOptionMixin, - CallbackDataParams + CallbackDataParams, + DefaultEmphasisFocus } from '../../util/types'; import GlobalModel from '../../model/Global'; import List from '../../data/List'; @@ -49,7 +49,7 @@ interface ScatterStateOption { interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] + focus?: DefaultEmphasisFocus scale?: boolean } } @@ -150,6 +150,4 @@ class ScatterSeriesModel extends SeriesModel { } -SeriesModel.registerClass(ScatterSeriesModel); - export default ScatterSeriesModel; \ No newline at end of file diff --git a/src/chart/scatter/ScatterView.ts b/src/chart/scatter/ScatterView.ts index f38b6ed425..4a7fbb0b4d 100644 --- a/src/chart/scatter/ScatterView.ts +++ b/src/chart/scatter/ScatterView.ts @@ -24,9 +24,9 @@ import pointsLayout from '../../layout/points'; import ChartView from '../../view/Chart'; import ScatterSeriesModel from './ScatterSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import List from '../../data/List'; -import { TaskProgressParams } from '../../stream/task'; +import { TaskProgressParams } from '../../core/task'; import type { StageHandlerProgressExecutor } from '../../util/types'; class ScatterView extends ChartView { @@ -126,6 +126,4 @@ class ScatterView extends ChartView { dispose() {} } -ChartView.registerClass(ScatterView); - export default ScatterView; \ No newline at end of file diff --git a/src/chart/scatter/install.ts b/src/chart/scatter/install.ts new file mode 100644 index 0000000000..df69507b4c --- /dev/null +++ b/src/chart/scatter/install.ts @@ -0,0 +1,34 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import ScatterSeriesModel from './ScatterSeries'; +import ScatterView from './ScatterView'; +import {install as installGridSimple} from '../../component/grid/installSimple'; +import layoutPoints from '../../layout/points'; + +export function install(registers: EChartsExtensionInstallRegisters) { + // In case developer forget to include grid component + use(installGridSimple); + + registers.registerSeriesModel(ScatterSeriesModel); + registers.registerChartView(ScatterView); + registers.registerLayout(layoutPoints('scatter')); + +} \ No newline at end of file diff --git a/src/chart/sunburst.ts b/src/chart/sunburst.ts index 3d47543f6d..7107bab4b5 100644 --- a/src/chart/sunburst.ts +++ b/src/chart/sunburst.ts @@ -17,17 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; +import { use } from '../extension'; +import { install } from './sunburst/install'; -import './sunburst/SunburstSeries'; -import './sunburst/SunburstView'; -import './sunburst/sunburstAction'; - -import sunburstLayout from './sunburst/sunburstLayout'; -import sunburstVisual from './sunburst/sunburstVisual'; -import dataFilter from '../processor/dataFilter'; - -echarts.registerLayout(zrUtil.curry(sunburstLayout, 'sunburst')); -echarts.registerProcessor(zrUtil.curry(dataFilter, 'sunburst')); -echarts.registerVisual(sunburstVisual); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/sunburst/SunburstPiece.ts b/src/chart/sunburst/SunburstPiece.ts index e7551eadd1..f5a53ca305 100644 --- a/src/chart/sunburst/SunburstPiece.ts +++ b/src/chart/sunburst/SunburstPiece.ts @@ -30,7 +30,7 @@ import Model from '../../model/Model'; import { getECData } from '../../util/innerStore'; import { getSectorCornerRadius } from '../helper/pieHelper'; import {createOrUpdatePatternFromDecal} from '../../util/decal'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; const DEFAULT_SECTOR_Z = 2; const DEFAULT_TEXT_Z = 4; diff --git a/src/chart/sunburst/SunburstSeries.ts b/src/chart/sunburst/SunburstSeries.ts index 4e30f5dbcb..a1e38bf748 100644 --- a/src/chart/sunburst/SunburstSeries.ts +++ b/src/chart/sunburst/SunburstSeries.ts @@ -30,7 +30,7 @@ import { CallbackDataParams, StatesOptionMixin, OptionDataItemObject, - DefaultExtraEmpasisState + DefaultEmphasisFocus } from '../../util/types'; import GlobalModel from '../../model/Global'; import List from '../../data/List'; @@ -66,7 +66,7 @@ interface SunburstDataParams extends CallbackDataParams { interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] | 'descendant' | 'ancestor' + focus?: DefaultEmphasisFocus | 'descendant' | 'ancestor' } } @@ -331,6 +331,4 @@ function completeTreeValue(dataNode: SunburstSeriesNodeItemOption) { } -SeriesModel.registerClass(SunburstSeriesModel); - export default SunburstSeriesModel; diff --git a/src/chart/sunburst/SunburstView.ts b/src/chart/sunburst/SunburstView.ts index 8f38fd083c..45632d868f 100644 --- a/src/chart/sunburst/SunburstView.ts +++ b/src/chart/sunburst/SunburstView.ts @@ -23,7 +23,7 @@ import SunburstPiece from './SunburstPiece'; import DataDiffer from '../../data/DataDiffer'; import SunburstSeriesModel, { SunburstSeriesNodeItemOption } from './SunburstSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { TreeNode } from '../../data/Tree'; import { ROOT_TO_NODE_ACTION } from './sunburstAction'; import { windowOpen } from '../../util/format'; @@ -249,7 +249,4 @@ class SunburstView extends ChartView { } - -ChartView.registerClass(SunburstView); - export default SunburstView; diff --git a/src/chart/sunburst/install.ts b/src/chart/sunburst/install.ts new file mode 100644 index 0000000000..0d8508a0a2 --- /dev/null +++ b/src/chart/sunburst/install.ts @@ -0,0 +1,36 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import SunburstView from './SunburstView'; +import SunburstSeriesModel from './SunburstSeries'; +import sunburstLayout from './sunburstLayout'; +import sunburstVisual from './sunburstVisual'; +import dataFilter from '../../processor/dataFilter'; +import { curry } from 'zrender/src/core/util'; +import { installSunburstAction } from './sunburstAction'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(SunburstView); + registers.registerSeriesModel(SunburstSeriesModel); + registers.registerLayout(curry(sunburstLayout, 'sunburst')); + registers.registerProcessor(curry(dataFilter, 'sunburst')); + registers.registerVisual(sunburstVisual); + installSunburstAction(registers); +} \ No newline at end of file diff --git a/src/chart/sunburst/sunburstAction.ts b/src/chart/sunburst/sunburstAction.ts index 61428fd647..d45fdde79b 100644 --- a/src/chart/sunburst/sunburstAction.ts +++ b/src/chart/sunburst/sunburstAction.ts @@ -21,93 +21,95 @@ * @file Sunburst action */ -import * as echarts from '../../echarts'; -import * as helper from '../helper/treeHelper'; import SunburstSeriesModel from './SunburstSeries'; import { Payload } from '../../util/types'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { extend } from 'zrender/src/core/util'; import { deprecateReplaceLog } from '../../util/log'; +import { EChartsExtensionInstallRegisters } from '../../extension'; +import { retrieveTargetInfo, aboveViewRoot } from '../helper/treeHelper'; export const ROOT_TO_NODE_ACTION = 'sunburstRootToNode'; interface SunburstRootToNodePayload extends Payload {} -echarts.registerAction( - {type: ROOT_TO_NODE_ACTION, update: 'updateView'}, - function (payload: SunburstRootToNodePayload, ecModel: GlobalModel) { - ecModel.eachComponent( - {mainType: 'series', subType: 'sunburst', query: payload}, - handleRootToNode - ); +const HIGHLIGHT_ACTION = 'sunburstHighlight'; - function handleRootToNode(model: SunburstSeriesModel, index: number) { - const targetInfo = helper - .retrieveTargetInfo(payload, [ROOT_TO_NODE_ACTION], model); +interface SunburstHighlightPayload extends Payload {} - if (targetInfo) { - const originViewRoot = model.getViewRoot(); - if (originViewRoot) { - payload.direction = helper.aboveViewRoot(originViewRoot, targetInfo.node) - ? 'rollUp' : 'drillDown'; - } - model.resetViewRoot(targetInfo.node); - } - } - } -); -const HIGHLIGHT_ACTION = 'sunburstHighlight'; +const UNHIGHLIGHT_ACTION = 'sunburstUnhighlight'; -interface SunburstHighlightPayload extends Payload {} +interface SunburstUnhighlightPayload extends Payload {} -echarts.registerAction( - {type: HIGHLIGHT_ACTION, update: 'none'}, - function (payload: SunburstHighlightPayload, ecModel: GlobalModel, api: ExtensionAPI) { - // Clone - payload = extend({}, payload); - ecModel.eachComponent( - {mainType: 'series', subType: 'sunburst', query: payload}, - handleHighlight - ); - - function handleHighlight(model: SunburstSeriesModel) { - const targetInfo = helper - .retrieveTargetInfo(payload, [HIGHLIGHT_ACTION], model); - if (targetInfo) { - payload.dataIndex = targetInfo.node.dataIndex; +export function installSunburstAction(registers: EChartsExtensionInstallRegisters) { + registers.registerAction( + {type: ROOT_TO_NODE_ACTION, update: 'updateView'}, + function (payload: SunburstRootToNodePayload, ecModel: GlobalModel) { + + ecModel.eachComponent( + {mainType: 'series', subType: 'sunburst', query: payload}, + handleRootToNode + ); + + function handleRootToNode(model: SunburstSeriesModel, index: number) { + const targetInfo = retrieveTargetInfo(payload, [ROOT_TO_NODE_ACTION], model); + + if (targetInfo) { + const originViewRoot = model.getViewRoot(); + if (originViewRoot) { + payload.direction = aboveViewRoot(originViewRoot, targetInfo.node) + ? 'rollUp' : 'drillDown'; + } + model.resetViewRoot(targetInfo.node); + } } } + ); + + registers.registerAction( + {type: HIGHLIGHT_ACTION, update: 'none'}, + function (payload: SunburstHighlightPayload, ecModel: GlobalModel, api: ExtensionAPI) { + // Clone + payload = extend({}, payload); + ecModel.eachComponent( + {mainType: 'series', subType: 'sunburst', query: payload}, + handleHighlight + ); + + function handleHighlight(model: SunburstSeriesModel) { + const targetInfo = retrieveTargetInfo(payload, [HIGHLIGHT_ACTION], model); + if (targetInfo) { + payload.dataIndex = targetInfo.node.dataIndex; + } + } - if (__DEV__) { - deprecateReplaceLog('highlight', 'sunburstHighlight'); - } - - // Fast forward action - api.dispatchAction(extend(payload, { - type: 'highlight' - })); - } -); - + if (__DEV__) { + deprecateReplaceLog('highlight', 'sunburstHighlight'); + } -const UNHIGHLIGHT_ACTION = 'sunburstUnhighlight'; + // Fast forward action + api.dispatchAction(extend(payload, { + type: 'highlight' + })); + } + ); -interface SunburstUnhighlightPayload extends Payload {} + registers.registerAction( + {type: UNHIGHLIGHT_ACTION, update: 'updateView'}, + function (payload: SunburstUnhighlightPayload, ecModel: GlobalModel, api: ExtensionAPI) { + payload = extend({}, payload); -echarts.registerAction( - {type: UNHIGHLIGHT_ACTION, update: 'updateView'}, - function (payload: SunburstUnhighlightPayload, ecModel: GlobalModel, api: ExtensionAPI) { - payload = extend({}, payload); + if (__DEV__) { + deprecateReplaceLog('downplay', 'sunburstUnhighlight'); + } - if (__DEV__) { - deprecateReplaceLog('downplay', 'sunburstUnhighlight'); + api.dispatchAction(extend(payload, { + type: 'downplay' + })); } + ); - api.dispatchAction(extend(payload, { - type: 'downplay' - })); - } -); +} \ No newline at end of file diff --git a/src/chart/sunburst/sunburstLayout.ts b/src/chart/sunburst/sunburstLayout.ts index df333a443e..b2a15e86c5 100644 --- a/src/chart/sunburst/sunburstLayout.ts +++ b/src/chart/sunburst/sunburstLayout.ts @@ -20,7 +20,7 @@ import { parsePercent } from '../../util/number'; import * as zrUtil from 'zrender/src/core/util'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import SunburstSeriesModel, { SunburstSeriesNodeItemOption, SunburstSeriesOption } from './SunburstSeries'; import { TreeNode } from '../../data/Tree'; diff --git a/src/chart/themeRiver.ts b/src/chart/themeRiver.ts index 582003b2ab..a2dbdb3b24 100644 --- a/src/chart/themeRiver.ts +++ b/src/chart/themeRiver.ts @@ -17,14 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './themeRiver/install'; -import '../component/singleAxis'; -import './themeRiver/ThemeRiverSeries'; -import './themeRiver/ThemeRiverView'; - -import themeRiverLayout from './themeRiver/themeRiverLayout'; -import dataFilter from '../processor/dataFilter'; - -echarts.registerLayout(themeRiverLayout); -echarts.registerProcessor(dataFilter('themeRiver')); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/themeRiver/ThemeRiverSeries.ts b/src/chart/themeRiver/ThemeRiverSeries.ts index 1929a17328..6eb4f721e5 100644 --- a/src/chart/themeRiver/ThemeRiverSeries.ts +++ b/src/chart/themeRiver/ThemeRiverSeries.ts @@ -320,6 +320,4 @@ class ThemeRiverSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(ThemeRiverSeriesModel); - export default ThemeRiverSeriesModel; \ No newline at end of file diff --git a/src/chart/themeRiver/ThemeRiverView.ts b/src/chart/themeRiver/ThemeRiverView.ts index ec43d7882a..c62e7897cd 100644 --- a/src/chart/themeRiver/ThemeRiverView.ts +++ b/src/chart/themeRiver/ThemeRiverView.ts @@ -26,7 +26,7 @@ import DataDiffer from '../../data/DataDiffer'; import ChartView from '../../view/Chart'; import ThemeRiverSeriesModel from './ThemeRiverSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { RectLike } from 'zrender/src/core/BoundingRect'; import { ColorString } from '../../util/types'; @@ -192,5 +192,4 @@ function createGridClipShape(rect: RectLike, seriesModel: ThemeRiverSeriesModel, return rectEl; } - -ChartView.registerClass(ThemeRiverView); \ No newline at end of file +export default ThemeRiverView; \ No newline at end of file diff --git a/src/chart/themeRiver/install.ts b/src/chart/themeRiver/install.ts new file mode 100644 index 0000000000..48eeb77637 --- /dev/null +++ b/src/chart/themeRiver/install.ts @@ -0,0 +1,32 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +import { EChartsExtensionInstallRegisters } from '../../extension'; +import ThemeRiverView from './ThemeRiverView'; +import ThemeRiverSeriesModel from './ThemeRiverSeries'; +import themeRiverLayout from './themeRiverLayout'; +import dataFilter from '../../processor/dataFilter'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(ThemeRiverView); + registers.registerSeriesModel(ThemeRiverSeriesModel); + + registers.registerLayout(themeRiverLayout); + registers.registerProcessor(dataFilter('themeRiver')); + +} \ No newline at end of file diff --git a/src/chart/themeRiver/themeRiverLayout.ts b/src/chart/themeRiver/themeRiverLayout.ts index fcd8b7d8f5..6de3e658d1 100644 --- a/src/chart/themeRiver/themeRiverLayout.ts +++ b/src/chart/themeRiver/themeRiverLayout.ts @@ -20,7 +20,7 @@ import * as zrUtil from 'zrender/src/core/util'; import * as numberUtil from '../../util/number'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import ThemeRiverSeriesModel, { ThemeRiverSeriesOption } from './ThemeRiverSeries'; import { RectLike } from 'zrender/src/core/BoundingRect'; import List from '../../data/List'; diff --git a/src/chart/tree.ts b/src/chart/tree.ts index 4dd7310c56..61e7b43c76 100644 --- a/src/chart/tree.ts +++ b/src/chart/tree.ts @@ -17,14 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './tree/install'; -import './tree/TreeSeries'; -import './tree/TreeView'; -import './tree/treeAction'; - -import treeLayout from './tree/treeLayout'; -import treeVisual from './tree/treeVisual'; - -echarts.registerLayout(treeLayout); -echarts.registerVisual(treeVisual); +use(install); \ No newline at end of file diff --git a/src/chart/tree/TreeSeries.ts b/src/chart/tree/TreeSeries.ts index e290403aba..7bca340a3d 100644 --- a/src/chart/tree/TreeSeries.ts +++ b/src/chart/tree/TreeSeries.ts @@ -30,8 +30,8 @@ import { OptionDataValue, StatesOptionMixin, OptionDataItemObject, - DefaultExtraEmpasisState, - CallbackDataParams + CallbackDataParams, + DefaultEmphasisFocus } from '../../util/types'; import List from '../../data/List'; import View from '../../coord/View'; @@ -54,7 +54,7 @@ export interface TreeSeriesStateOption { interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] | 'ancestor' | 'descendant' + focus?: DefaultEmphasisFocus | 'ancestor' | 'descendant' scale?: boolean } } @@ -288,6 +288,4 @@ class TreeSeriesModel extends SeriesModel { }; } -SeriesModel.registerClass(TreeSeriesModel); - export default TreeSeriesModel; \ No newline at end of file diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts index a38670e073..dbee5b1275 100644 --- a/src/chart/tree/TreeView.ts +++ b/src/chart/tree/TreeView.ts @@ -32,7 +32,7 @@ import ChartView from '../../view/Chart'; import TreeSeriesModel, { TreeSeriesOption, TreeSeriesNodeItemOption } from './TreeSeries'; import Path, { PathProps } from 'zrender/src/graphic/Path'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { TreeNode } from '../../data/Tree'; import List from '../../data/List'; import { setStatesStylesFromModel, setStatesFlag, setDefaultStateProxy, HOVER_STATE_BLUR } from '../../util/states'; @@ -758,6 +758,4 @@ function getEdgeShape( }; } -ChartView.registerClass(TreeView); - export default TreeView; \ No newline at end of file diff --git a/src/chart/tree/install.ts b/src/chart/tree/install.ts new file mode 100644 index 0000000000..fe4083c344 --- /dev/null +++ b/src/chart/tree/install.ts @@ -0,0 +1,34 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import TreeView from './TreeView'; +import TreeSeriesModel from './TreeSeries'; +import treeLayout from './treeLayout'; +import treeVisual from './treeVisual'; +import {installTreeAction} from './treeAction'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerChartView(TreeView); + registers.registerSeriesModel(TreeSeriesModel); + registers.registerLayout(treeLayout); + registers.registerVisual(treeVisual); + + installTreeAction(registers); +} \ No newline at end of file diff --git a/src/chart/tree/layoutHelper.ts b/src/chart/tree/layoutHelper.ts index 0d13860065..1c56275a31 100644 --- a/src/chart/tree/layoutHelper.ts +++ b/src/chart/tree/layoutHelper.ts @@ -36,7 +36,7 @@ import * as layout from '../../util/layout'; import { TreeNode } from '../../data/Tree'; import TreeSeriesModel from './TreeSeries'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; interface HierNode { defaultAncestor: TreeLayoutNode, diff --git a/src/chart/tree/treeAction.ts b/src/chart/tree/treeAction.ts index e6f41cd674..8543ef56e2 100644 --- a/src/chart/tree/treeAction.ts +++ b/src/chart/tree/treeAction.ts @@ -17,50 +17,53 @@ * under the License. */ -import * as echarts from '../../echarts'; import {updateCenterAndZoom, RoamPaylod} from '../../action/roamHelper'; import { Payload } from '../../util/types'; import TreeSeriesModel from './TreeSeries'; import GlobalModel from '../../model/Global'; +import { EChartsExtensionInstallRegisters } from '../../extension'; export interface TreeExpandAndCollapsePayload extends Payload { dataIndex: number } -echarts.registerAction({ - type: 'treeExpandAndCollapse', - event: 'treeExpandAndCollapse', - update: 'update' -}, function (payload: TreeExpandAndCollapsePayload, ecModel) { - ecModel.eachComponent({ - mainType: 'series', subType: 'tree', query: payload - }, function (seriesModel: TreeSeriesModel) { - const dataIndex = payload.dataIndex; - const tree = seriesModel.getData().tree; - const node = tree.getNodeByDataIndex(dataIndex); - node.isExpand = !node.isExpand; +export function installTreeAction(registers: EChartsExtensionInstallRegisters) { + registers.registerAction({ + type: 'treeExpandAndCollapse', + event: 'treeExpandAndCollapse', + update: 'update' + }, function (payload: TreeExpandAndCollapsePayload, ecModel) { + ecModel.eachComponent({ + mainType: 'series', subType: 'tree', query: payload + }, function (seriesModel: TreeSeriesModel) { + const dataIndex = payload.dataIndex; + const tree = seriesModel.getData().tree; + const node = tree.getNodeByDataIndex(dataIndex); + node.isExpand = !node.isExpand; + }); }); -}); -echarts.registerAction({ - type: 'treeRoam', - event: 'treeRoam', - // Here we set 'none' instead of 'update', because roam action - // just need to update the transform matrix without having to recalculate - // the layout. So don't need to go through the whole update process, such - // as 'dataPrcocess', 'coordSystemUpdate', 'layout' and so on. - update: 'none' -}, function (payload: RoamPaylod, ecModel: GlobalModel) { - ecModel.eachComponent({ - mainType: 'series', subType: 'tree', query: payload - }, function (seriesModel: TreeSeriesModel) { - const coordSys = seriesModel.coordinateSystem; - const res = updateCenterAndZoom(coordSys, payload); + registers.registerAction({ + type: 'treeRoam', + event: 'treeRoam', + // Here we set 'none' instead of 'update', because roam action + // just need to update the transform matrix without having to recalculate + // the layout. So don't need to go through the whole update process, such + // as 'dataPrcocess', 'coordSystemUpdate', 'layout' and so on. + update: 'none' + }, function (payload: RoamPaylod, ecModel: GlobalModel) { + ecModel.eachComponent({ + mainType: 'series', subType: 'tree', query: payload + }, function (seriesModel: TreeSeriesModel) { + const coordSys = seriesModel.coordinateSystem; + const res = updateCenterAndZoom(coordSys, payload); - seriesModel.setCenter - && seriesModel.setCenter(res.center); + seriesModel.setCenter + && seriesModel.setCenter(res.center); - seriesModel.setZoom - && seriesModel.setZoom(res.zoom); + seriesModel.setZoom + && seriesModel.setZoom(res.zoom); + }); }); -}); + +} \ No newline at end of file diff --git a/src/chart/tree/treeLayout.ts b/src/chart/tree/treeLayout.ts index 04425fda17..d614347e2f 100644 --- a/src/chart/tree/treeLayout.ts +++ b/src/chart/tree/treeLayout.ts @@ -31,7 +31,7 @@ import { TreeLayoutNode } from './layoutHelper'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import TreeSeriesModel from './TreeSeries'; export default function treeLayout(ecModel: GlobalModel, api: ExtensionAPI) { diff --git a/src/chart/treemap.ts b/src/chart/treemap.ts index 34140527de..0312db3524 100644 --- a/src/chart/treemap.ts +++ b/src/chart/treemap.ts @@ -17,14 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './treemap/install'; -import './treemap/TreemapSeries'; -import './treemap/TreemapView'; -import './treemap/treemapAction'; - -import treemapVisual from './treemap/treemapVisual'; -import treemapLayout from './treemap/treemapLayout'; - -echarts.registerVisual(treemapVisual); -echarts.registerLayout(treemapLayout); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/chart/treemap/Breadcrumb.ts b/src/chart/treemap/Breadcrumb.ts index beacf13d2b..51c6cc65d6 100644 --- a/src/chart/treemap/Breadcrumb.ts +++ b/src/chart/treemap/Breadcrumb.ts @@ -22,7 +22,7 @@ import {getECData} from '../../util/innerStore'; import * as layout from '../../util/layout'; import {wrapTreePathInfo} from '../helper/treeHelper'; import TreemapSeriesModel, { TreemapSeriesNodeItemOption, TreemapSeriesOption } from './TreemapSeries'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { TreeNode } from '../../data/Tree'; import { curry, defaults } from 'zrender/src/core/util'; import { ZRElementEvent, BoxLayoutOptionMixin, ECElement } from '../../util/types'; diff --git a/src/chart/treemap/TreemapSeries.ts b/src/chart/treemap/TreemapSeries.ts index c636137391..67b0081a21 100644 --- a/src/chart/treemap/TreemapSeries.ts +++ b/src/chart/treemap/TreemapSeries.ts @@ -34,8 +34,9 @@ import { OptionId, OptionName, DecalObject, - DefaultExtraEmpasisState, - SeriesLabelOption + SeriesLabelOption, + DefaultEmphasisFocus, + AriaOptionMixin } from '../../util/types'; import GlobalModel from '../../model/Global'; import { LayoutRect } from '../../util/layout'; @@ -80,7 +81,7 @@ interface TreemapSeriesCallbackDataParams extends CallbackDataParams { interface ExtraStateOption { emphasis?: { - focus?: DefaultExtraEmpasisState['focus'] | 'descendant' | 'ancestor' + focus?: DefaultEmphasisFocus | 'descendant' | 'ancestor' } } @@ -535,7 +536,9 @@ function completeTreeValue(dataNode: TreemapSeriesNodeItemOption) { */ function setDefault(levels: TreemapSeriesLevelOption[], ecModel: GlobalModel) { const globalColorList = normalizeToArray(ecModel.get('color')) as ColorString[]; - const globalDecalList = normalizeToArray(ecModel.get(['aria', 'decal', 'decals'])) as DecalObject[]; + const globalDecalList = normalizeToArray( + (ecModel as Model).get(['aria', 'decal', 'decals']) + ) as DecalObject[]; if (!globalColorList) { return; @@ -572,6 +575,4 @@ function setDefault(levels: TreemapSeriesLevelOption[], ecModel: GlobalModel) { return levels; } -SeriesModel.registerClass(TreemapSeriesModel); - export default TreemapSeriesModel; \ No newline at end of file diff --git a/src/chart/treemap/TreemapView.ts b/src/chart/treemap/TreemapView.ts index 66a3181cd8..81e78c7b29 100644 --- a/src/chart/treemap/TreemapView.ts +++ b/src/chart/treemap/TreemapView.ts @@ -38,7 +38,7 @@ import ChartView from '../../view/Chart'; import Tree, { TreeNode } from '../../data/Tree'; import TreemapSeriesModel, { TreemapSeriesNodeItemOption } from './TreemapSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import Model from '../../model/Model'; import { LayoutRect } from '../../util/layout'; import { TreemapLayoutNode } from './treemapLayout'; @@ -1110,4 +1110,4 @@ function calculateZ(depth: number, zInLevel: number) { return (zb - 1) / zb; } -ChartView.registerClass(TreemapView); \ No newline at end of file +export default TreemapView; \ No newline at end of file diff --git a/src/chart/treemap/install.ts b/src/chart/treemap/install.ts new file mode 100644 index 0000000000..00b3b1dabc --- /dev/null +++ b/src/chart/treemap/install.ts @@ -0,0 +1,35 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import { installTreemapAction } from './treemapAction'; +import TreemapSeriesModel from './TreemapSeries'; +import TreemapView from './TreemapView'; +import treemapVisual from './treemapVisual'; +import treemapLayout from './treemapLayout'; + + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerSeriesModel(TreemapSeriesModel); + registers.registerChartView(TreemapView); + registers.registerVisual(treemapVisual); + registers.registerLayout(treemapLayout); + + installTreemapAction(registers); +} \ No newline at end of file diff --git a/src/chart/treemap/treemapAction.ts b/src/chart/treemap/treemapAction.ts index 61b19f5c0c..8577e480ed 100644 --- a/src/chart/treemap/treemapAction.ts +++ b/src/chart/treemap/treemapAction.ts @@ -17,13 +17,12 @@ * under the License. */ - -import * as echarts from '../../echarts'; import * as helper from '../helper/treeHelper'; import { Payload } from '../../util/types'; import TreemapSeriesModel from './TreemapSeries'; import { TreeNode } from '../../data/Tree'; import { RectLike } from 'zrender/src/core/BoundingRect'; +import { EChartsExtensionInstallRegisters } from '../../extension'; const noop = function () {}; @@ -52,34 +51,37 @@ export interface TreemapRootToNodePayload extends Payload { direction?: 'rollUp' | 'drillDown' } -for (let i = 0; i < actionTypes.length; i++) { - echarts.registerAction({ - type: actionTypes[i], - update: 'updateView' - }, noop); -} +export function installTreemapAction(registers: EChartsExtensionInstallRegisters) { + for (let i = 0; i < actionTypes.length; i++) { + registers.registerAction({ + type: actionTypes[i], + update: 'updateView' + }, noop); + } -echarts.registerAction( - {type: 'treemapRootToNode', update: 'updateView'}, - function (payload, ecModel) { + registers.registerAction( + {type: 'treemapRootToNode', update: 'updateView'}, + function (payload, ecModel) { - ecModel.eachComponent( - {mainType: 'series', subType: 'treemap', query: payload}, - handleRootToNode - ); + ecModel.eachComponent( + {mainType: 'series', subType: 'treemap', query: payload}, + handleRootToNode + ); - function handleRootToNode(model: TreemapSeriesModel, index: number) { - const types = ['treemapZoomToNode', 'treemapRootToNode']; - const targetInfo = helper.retrieveTargetInfo(payload, types, model); + function handleRootToNode(model: TreemapSeriesModel, index: number) { + const types = ['treemapZoomToNode', 'treemapRootToNode']; + const targetInfo = helper.retrieveTargetInfo(payload, types, model); - if (targetInfo) { - const originViewRoot = model.getViewRoot(); - if (originViewRoot) { - payload.direction = helper.aboveViewRoot(originViewRoot, targetInfo.node) - ? 'rollUp' : 'drillDown'; + if (targetInfo) { + const originViewRoot = model.getViewRoot(); + if (originViewRoot) { + payload.direction = helper.aboveViewRoot(originViewRoot, targetInfo.node) + ? 'rollUp' : 'drillDown'; + } + model.resetViewRoot(targetInfo.node); } - model.resetViewRoot(targetInfo.node); } } - } -); + ); + +} \ No newline at end of file diff --git a/src/chart/treemap/treemapLayout.ts b/src/chart/treemap/treemapLayout.ts index 9046d328ec..26003794b0 100644 --- a/src/chart/treemap/treemapLayout.ts +++ b/src/chart/treemap/treemapLayout.ts @@ -34,7 +34,7 @@ import * as layout from '../../util/layout'; import * as helper from '../helper/treeHelper'; import TreemapSeriesModel, { TreemapSeriesNodeItemOption } from './TreemapSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { TreeNode } from '../../data/Tree'; import Model from '../../model/Model'; import { TreemapRenderPayload, TreemapMovePayload, TreemapZoomToNodePayload } from './treemapAction'; diff --git a/src/component/aria.ts b/src/component/aria.ts index baa1252e2c..2a8e6884f1 100644 --- a/src/component/aria.ts +++ b/src/component/aria.ts @@ -17,59 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import ariaVisual from '../visual/aria'; -import ariaPreprocessor from './aria/preprocessor'; -import { DecalObject } from '../util/types'; +import { use } from '../extension'; +import { install } from './aria/install'; -const PRIORITY_VISUAL_ARIA = echarts.PRIORITY.VISUAL.ARIA; - -export interface AriaLabelOption { - enabled?: boolean; - description?: string; - general?: { - withTitle?: string; - withoutTitle?: string; - }; - series?: { - maxCount?: number; - single?: { - prefix?: string; - withName?: string; - withoutName?: string; - }; - multiple?: { - prefix?: string; - withName?: string; - withoutName?: string; - separator?: { - middle?: string; - end?: string; - } - } - }; - data?: { - maxCount?: number; - allData?: string; - partialData?: string; - withName?: string; - withoutName?: string; - separator?: { - middle?: string; - end?: string; - } - } -} - -// Extending is for compating ECharts 4 -export interface AriaOption extends AriaLabelOption { - enabled?: boolean; - label?: AriaLabelOption; - decal?: { - show?: boolean; - decals?: DecalObject | DecalObject[]; - }; -} - -echarts.registerPreprocessor(ariaPreprocessor); -echarts.registerVisual(PRIORITY_VISUAL_ARIA, ariaVisual); +use(install); \ No newline at end of file diff --git a/src/component/aria/install.ts b/src/component/aria/install.ts new file mode 100644 index 0000000000..552e81586a --- /dev/null +++ b/src/component/aria/install.ts @@ -0,0 +1,27 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import ariaVisual from '../../visual/aria'; +import ariaPreprocessor from './preprocessor'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerPreprocessor(ariaPreprocessor); + registers.registerVisual(registers.PRIORITY.VISUAL.ARIA, ariaVisual); +} \ No newline at end of file diff --git a/src/component/aria/preprocessor.ts b/src/component/aria/preprocessor.ts index 771e1428f4..2a8d9985fa 100644 --- a/src/component/aria/preprocessor.ts +++ b/src/component/aria/preprocessor.ts @@ -18,9 +18,9 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import { ECUnitOption } from '../../util/types'; +import { ECUnitOption, AriaOptionMixin } from '../../util/types'; -export default function ariaPreprocessor(option: ECUnitOption) { +export default function ariaPreprocessor(option: ECUnitOption & AriaOptionMixin) { if (!option || !option.aria) { return; } diff --git a/src/component/axis/AngleAxisView.ts b/src/component/axis/AngleAxisView.ts index e197cec967..1910defc6b 100644 --- a/src/component/axis/AngleAxisView.ts +++ b/src/component/axis/AngleAxisView.ts @@ -26,7 +26,6 @@ import AxisBuilder from './AxisBuilder'; import { AngleAxisModel } from '../../coord/polar/AxisModel'; import GlobalModel from '../../model/Global'; import Polar from '../../coord/polar/Polar'; -import ComponentView from '../../view/Component'; import AngleAxis from '../../coord/polar/AngleAxis'; import { ZRTextAlign, ZRTextVerticalAlign, ColorString } from '../../util/types'; import { getECData } from '../../util/innerStore'; @@ -393,5 +392,4 @@ const angelAxisElementsBuilders: Record { + mainType?: 'axisPointer' + type?: 'line' | 'shadow' | 'cross' | 'none' link?: AxisPointerLink[] diff --git a/src/component/axisPointer/AxisPointerView.ts b/src/component/axisPointer/AxisPointerView.ts index 3a269e1119..fef625a518 100644 --- a/src/component/axisPointer/AxisPointerView.ts +++ b/src/component/axisPointer/AxisPointerView.ts @@ -21,7 +21,7 @@ import * as globalListener from './globalListener'; import ComponentView from '../../view/Component'; import AxisPointerModel from './AxisPointerModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import TooltipModel from '../tooltip/TooltipModel'; class AxisPointerView extends ComponentView { @@ -63,6 +63,4 @@ class AxisPointerView extends ComponentView { } } -ComponentView.registerClass(AxisPointerView); - export default AxisPointerView; \ No newline at end of file diff --git a/src/component/axisPointer/BaseAxisPointer.ts b/src/component/axisPointer/BaseAxisPointer.ts index 7dcd472089..8b5ada458a 100644 --- a/src/component/axisPointer/BaseAxisPointer.ts +++ b/src/component/axisPointer/BaseAxisPointer.ts @@ -26,7 +26,7 @@ import * as throttleUtil from '../../util/throttle'; import {makeInner} from '../../util/model'; import { AxisPointer } from './AxisPointer'; import { AxisBaseModel } from '../../coord/AxisBaseModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable'; import Element from 'zrender/src/Element'; import { VerticalAlign, HorizontalAlign, CommonAxisPointerOption } from '../../util/types'; diff --git a/src/component/axisPointer/CartesianAxisPointer.ts b/src/component/axisPointer/CartesianAxisPointer.ts index 73f95dc962..a6a10e6469 100644 --- a/src/component/axisPointer/CartesianAxisPointer.ts +++ b/src/component/axisPointer/CartesianAxisPointer.ts @@ -22,7 +22,7 @@ import * as viewHelper from './viewHelper'; import * as cartesianAxisHelper from '../../coord/cartesian/cartesianAxisHelper'; import AxisView from '../axis/AxisView'; import CartesianAxisModel from '../../coord/cartesian/AxisModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ScaleDataValue, VerticalAlign, HorizontalAlign, CommonAxisPointerOption } from '../../util/types'; import Grid from '../../coord/cartesian/Grid'; import Axis2D from '../../coord/cartesian/Axis2D'; @@ -176,7 +176,4 @@ function getAxisDimIndex(axis: Axis2D) { return axis.dim === 'x' ? 0 : 1; } -// @ts-ignore -AxisView.registerAxisPointerClass('CartesianAxisPointer', CartesianAxisPointer); - export default CartesianAxisPointer; \ No newline at end of file diff --git a/src/component/axisPointer/PolarAxisPointer.ts b/src/component/axisPointer/PolarAxisPointer.ts index b62d601bf6..37e5fbb632 100644 --- a/src/component/axisPointer/PolarAxisPointer.ts +++ b/src/component/axisPointer/PolarAxisPointer.ts @@ -30,7 +30,7 @@ import {OptionDataValue, ZRTextVerticalAlign } from '../../util/types'; import { PolarAxisModel } from '../../coord/polar/AxisModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import Polar from '../../coord/polar/Polar'; import AngleAxis from '../../coord/polar/AngleAxis'; import RadiusAxis from '../../coord/polar/RadiusAxis'; @@ -191,7 +191,4 @@ const pointerShapeBuilder = { } }; -// @ts-ignore -AxisView.registerAxisPointerClass('PolarAxisPointer', PolarAxisPointer); - export default PolarAxisPointer; \ No newline at end of file diff --git a/src/component/axisPointer/SingleAxisPointer.ts b/src/component/axisPointer/SingleAxisPointer.ts index 345a67c5c0..ae54b14cdb 100644 --- a/src/component/axisPointer/SingleAxisPointer.ts +++ b/src/component/axisPointer/SingleAxisPointer.ts @@ -25,7 +25,7 @@ import SingleAxis from '../../coord/single/SingleAxis'; import Single from '../../coord/single/Single'; import { PathProps } from 'zrender/src/graphic/Path'; import { ScaleDataValue, VerticalAlign, CommonAxisPointerOption } from '../../util/types'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import SingleAxisModel from '../../coord/single/AxisModel'; import Model from '../../model/Model'; @@ -170,7 +170,4 @@ function getGlobalExtent(coordSys: Single, dimIndex: number) { return [rect[XY[dimIndex]], rect[XY[dimIndex]] + rect[WH[dimIndex]]]; } -// @ts-ignore -AxisView.registerAxisPointerClass('SingleAxisPointer', SingleAxisPointer); - export default SingleAxisPointer; \ No newline at end of file diff --git a/src/component/axisPointer/axisTrigger.ts b/src/component/axisPointer/axisTrigger.ts index c8031cd139..bae5d0938b 100644 --- a/src/component/axisPointer/axisTrigger.ts +++ b/src/component/axisPointer/axisTrigger.ts @@ -21,7 +21,7 @@ import {makeInner, ModelFinderObject} from '../../util/model'; import * as modelHelper from './modelHelper'; import findPointFromSeries from './findPointFromSeries'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Dictionary, Payload, CommonAxisPointerOption, HighlightPayload, DownplayPayload } from '../../util/types'; import AxisPointerModel, { AxisPointerOption } from './AxisPointerModel'; import { each, curry, bind, extend, Curry1 } from 'zrender/src/core/util'; diff --git a/src/component/axisPointer/globalListener.ts b/src/component/axisPointer/globalListener.ts index d854a01801..af62ccbfe6 100644 --- a/src/component/axisPointer/globalListener.ts +++ b/src/component/axisPointer/globalListener.ts @@ -20,7 +20,7 @@ import * as zrUtil from 'zrender/src/core/util'; import env from 'zrender/src/core/env'; import {makeInner} from '../../util/model'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ZRenderType } from 'zrender/src/zrender'; import { ZRElementEvent } from '../../util/types'; import { Dictionary } from 'zrender/src/core/types'; diff --git a/src/component/axisPointer/install.ts b/src/component/axisPointer/install.ts new file mode 100644 index 0000000000..73ce7df447 --- /dev/null +++ b/src/component/axisPointer/install.ts @@ -0,0 +1,71 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import AxisView from '../axis/AxisView'; +import CartesianAxisPointer from './CartesianAxisPointer'; +import AxisPointerModel from './AxisPointerModel'; +import AxisPointerView from './AxisPointerView'; +import { isArray } from 'zrender/src/core/util'; +import { collect } from './modelHelper'; +import axisTrigger from './axisTrigger'; + +export function install(registers: EChartsExtensionInstallRegisters) { + // CartesianAxisPointer is not supposed to be required here. But consider + // echarts.simple.js and online build tooltip, which only require gridSimple, + // CartesianAxisPointer should be able to required somewhere. + AxisView.registerAxisPointerClass('CartesianAxisPointer', CartesianAxisPointer); + + registers.registerComponentModel(AxisPointerModel); + registers.registerComponentView(AxisPointerView); + + registers.registerPreprocessor(function (option) { + // Always has a global axisPointerModel for default setting. + if (option) { + (!option.axisPointer || (option.axisPointer as []).length === 0) + && (option.axisPointer = {}); + + const link = (option.axisPointer as any).link; + // Normalize to array to avoid object mergin. But if link + // is not set, remain null/undefined, otherwise it will + // override existent link setting. + if (link && !isArray(link)) { + (option.axisPointer as any).link = [link]; + } + } + }); + + // This process should proformed after coordinate systems created + // and series data processed. So put it on statistic processing stage. + registers.registerProcessor(registers.PRIORITY.PROCESSOR.STATISTIC, function (ecModel, api) { + // Build axisPointerModel, mergin tooltip.axisPointer model for each axis. + // allAxesInfo should be updated when setOption performed. + (ecModel.getComponent('axisPointer') as AxisPointerModel).coordSysAxesInfo = + collect(ecModel, api); + }); + + // Broadcast to all views. + registers.registerAction({ + type: 'updateAxisPointer', + event: 'updateAxisPointer', + update: ':updateAxisPointer' + }, axisTrigger); + + +} \ No newline at end of file diff --git a/src/component/axisPointer/modelHelper.ts b/src/component/axisPointer/modelHelper.ts index c7d09e760b..c991ae0c29 100644 --- a/src/component/axisPointer/modelHelper.ts +++ b/src/component/axisPointer/modelHelper.ts @@ -19,7 +19,7 @@ import Model from '../../model/Model'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { each, curry, clone, defaults, isArray, indexOf } from 'zrender/src/core/util'; import AxisPointerModel, { AxisPointerOption } from './AxisPointerModel'; import Axis from '../../coord/Axis'; diff --git a/src/component/axisPointer/viewHelper.ts b/src/component/axisPointer/viewHelper.ts index f00a78d329..aea2c9fe25 100644 --- a/src/component/axisPointer/viewHelper.ts +++ b/src/component/axisPointer/viewHelper.ts @@ -34,7 +34,7 @@ import IntervalScale from '../../scale/Interval'; import Axis2D from '../../coord/cartesian/Axis2D'; import { AxisPointerElementOptions } from './BaseAxisPointer'; import { AxisBaseModel } from '../../coord/AxisBaseModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import CartesianAxisModel from '../../coord/cartesian/AxisModel'; import Model from '../../model/Model'; import { PathStyleProps } from 'zrender/src/graphic/Path'; diff --git a/src/component/brush.ts b/src/component/brush.ts index a39dc85ee7..24f0af8565 100644 --- a/src/component/brush.ts +++ b/src/component/brush.ts @@ -17,17 +17,7 @@ * under the License. */ -/** - * Brush component entry - */ +import { use } from '../extension'; +import { install } from './brush/install'; -import * as echarts from '../echarts'; -import preprocessor from './brush/preprocessor'; - -import './brush/visualEncoding'; -import './brush/BrushModel'; -import './brush/BrushView'; -import './brush/brushAction'; -import './toolbox/feature/Brush'; - -echarts.registerPreprocessor(preprocessor); +use(install); \ No newline at end of file diff --git a/src/component/brush/BrushModel.ts b/src/component/brush/BrushModel.ts index 0172afabe0..8ee179a7d9 100644 --- a/src/component/brush/BrushModel.ts +++ b/src/component/brush/BrushModel.ts @@ -30,7 +30,6 @@ import { } from '../helper/BrushController'; import { ModelFinderObject } from '../../util/model'; - const DEFAULT_OUT_OF_BRUSH_COLOR = '#ddd'; /** @@ -87,6 +86,8 @@ export interface BrushAreaParamInternal extends BrushAreaParam { export type BrushToolboxIconType = BrushType | 'keep' | 'clear'; export interface BrushOption extends ComponentOption, ModelFinderObject { + mainType?: 'brush'; + // Default value see preprocessor. toolbox?: BrushToolboxIconType[]; @@ -213,8 +214,6 @@ class BrushModel extends ComponentModel { } -ComponentModel.registerClass(BrushModel); - function generateBrushOption( option: BrushOption, brushOption: BrushAreaParam @@ -239,4 +238,4 @@ function generateBrushOption( ); } -export default BrushModel; +export default BrushModel; \ No newline at end of file diff --git a/src/component/brush/BrushView.ts b/src/component/brush/BrushView.ts index 06563a15e8..a7c0ba915a 100644 --- a/src/component/brush/BrushView.ts +++ b/src/component/brush/BrushView.ts @@ -23,7 +23,7 @@ import BrushController, { BrushControllerEvents, BrushCoverConfig } from '../hel import {layoutCovers} from './visualEncoding'; import BrushModel from './BrushModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Payload } from '../../util/types'; import ComponentView from '../../view/Component'; @@ -110,4 +110,4 @@ class BrushView extends ComponentView { } -ComponentView.registerClass(BrushView); +export default BrushView; \ No newline at end of file diff --git a/src/component/brush/brushAction.ts b/src/component/brush/brushAction.ts deleted file mode 100644 index 86c09d09ce..0000000000 --- a/src/component/brush/brushAction.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -import * as echarts from '../../echarts'; -import GlobalModel from '../../model/Global'; -import { Payload } from '../../util/types'; -import BrushModel, { BrushAreaParam } from './BrushModel'; - -interface BrushPayload extends Payload { - // If "areas" is empty, all of the select-boxes will be deleted - areas?: BrushAreaParam[]; -} - -echarts.registerAction( - {type: 'brush', event: 'brush', update: 'updateVisual' }, - function (payload: BrushPayload, ecModel: GlobalModel) { - ecModel.eachComponent( - {mainType: 'brush', query: payload}, - function (brushModel: BrushModel) { - brushModel.setAreas(payload.areas); - } - ); - } -); - -/** - * payload: { - * brushComponents: [ - * { - * brushId, - * brushIndex, - * brushName, - * series: [ - * { - * seriesId, - * seriesIndex, - * seriesName, - * rawIndices: [21, 34, ...] - * }, - * ... - * ] - * }, - * ... - * ] - * } - */ -echarts.registerAction( - {type: 'brushSelect', event: 'brushSelected', update: 'none'}, - function () {} -); - -echarts.registerAction( - {type: 'brushEnd', event: 'brushEnd', update: 'none'}, - function () {} -); diff --git a/src/component/brush/install.ts b/src/component/brush/install.ts new file mode 100644 index 0000000000..69554c7a28 --- /dev/null +++ b/src/component/brush/install.ts @@ -0,0 +1,90 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import brushPreprocessor from './preprocessor'; +import BrushView from './BrushView'; +import BrushModel, { BrushAreaParam } from './BrushModel'; +import brushVisual from './visualEncoding'; +import { Payload } from '../../util/types'; +import GlobalModel from '../../model/Global'; + +// TODO +import BrushFeature from '../toolbox/feature/Brush'; +import { registerFeature } from '../toolbox/featureManager'; + +interface BrushPayload extends Payload { + // If "areas" is empty, all of the select-boxes will be deleted + areas?: BrushAreaParam[]; +} + +export function install(registers: EChartsExtensionInstallRegisters) { + + registers.registerComponentView(BrushView); + registers.registerComponentModel(BrushModel); + + registers.registerPreprocessor(brushPreprocessor); + + registers.registerVisual(registers.PRIORITY.VISUAL.BRUSH, brushVisual); + + registers.registerAction( + {type: 'brush', event: 'brush', update: 'updateVisual' }, + function (payload: BrushPayload, ecModel: GlobalModel) { + ecModel.eachComponent( + {mainType: 'brush', query: payload}, + function (brushModel: BrushModel) { + brushModel.setAreas(payload.areas); + } + ); + } + ); + + /** + * payload: { + * brushComponents: [ + * { + * brushId, + * brushIndex, + * brushName, + * series: [ + * { + * seriesId, + * seriesIndex, + * seriesName, + * rawIndices: [21, 34, ...] + * }, + * ... + * ] + * }, + * ... + * ] + * } + */ + registers.registerAction( + {type: 'brushSelect', event: 'brushSelected', update: 'none'}, + function () {} + ); + + registers.registerAction( + {type: 'brushEnd', event: 'brushEnd', update: 'none'}, + function () {} + ); + + registerFeature('brush', BrushFeature); +} \ No newline at end of file diff --git a/src/component/brush/visualEncoding.ts b/src/component/brush/visualEncoding.ts index 87a9ea16cb..6a221eb218 100644 --- a/src/component/brush/visualEncoding.ts +++ b/src/component/brush/visualEncoding.ts @@ -18,7 +18,6 @@ */ -import * as echarts from '../../echarts'; import * as zrUtil from 'zrender/src/core/util'; import BoundingRect from 'zrender/src/core/BoundingRect'; import * as visualSolution from '../../visual/visualSolution'; @@ -26,7 +25,7 @@ import { BrushSelectableArea, makeBrushCommonSelectorForSeries } from './selecto import * as throttleUtil from '../../util/throttle'; import BrushTargetManager from '../helper/BrushTargetManager'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Payload } from '../../util/types'; import BrushModel, { BrushAreaParamInternal } from './BrushModel'; import SeriesModel from '../../model/Series'; @@ -39,7 +38,6 @@ type BrushVisualState = 'inBrush' | 'outOfBrush'; const STATE_LIST = ['inBrush', 'outOfBrush'] as const; const DISPATCH_METHOD = '__ecBrushSelect' as const; const DISPATCH_FLAG = '__ecInBrushSelectEvent' as const; -const PRIORITY_BRUSH = echarts.PRIORITY.VISUAL.BRUSH; interface BrushGlobalDispatcher extends ZRenderType { [DISPATCH_FLAG]: boolean; @@ -69,7 +67,7 @@ export function layoutCovers(ecModel: GlobalModel): void { /** * Register the visual encoding if this modules required. */ -echarts.registerVisual(PRIORITY_BRUSH, function (ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) { +export default function brushVisual(ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) { const brushSelected: BrushSelectedItem[] = []; let throttleType; @@ -235,7 +233,7 @@ echarts.registerVisual(PRIORITY_BRUSH, function (ecModel: GlobalModel, api: Exte }); dispatchAction(api, throttleType, throttleDelay, brushSelected, payload); -}); +}; function dispatchAction( api: ExtensionAPI, diff --git a/src/component/calendar.ts b/src/component/calendar.ts index c65dbde19d..a216948b56 100644 --- a/src/component/calendar.ts +++ b/src/component/calendar.ts @@ -17,6 +17,7 @@ * under the License. */ -import '../coord/calendar/Calendar'; -import '../coord/calendar/CalendarModel'; -import './calendar/CalendarView'; +import { use } from '../extension'; +import { install } from './calendar/install'; + +use(install); \ No newline at end of file diff --git a/src/component/calendar/CalendarView.ts b/src/component/calendar/CalendarView.ts index a1cf115e7c..d2060ad810 100644 --- a/src/component/calendar/CalendarView.ts +++ b/src/component/calendar/CalendarView.ts @@ -25,7 +25,7 @@ import * as numberUtil from '../../util/number'; import CalendarModel from '../../coord/calendar/CalendarModel'; import {CalendarParsedDateRangeInfo, CalendarParsedDateInfo} from '../../coord/calendar/Calendar'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { LayoutOrient, OptionDataValueDate, ZRTextAlign, ZRTextVerticalAlign } from '../../util/types'; import ComponentView from '../../view/Component'; import { PathStyleProps } from 'zrender/src/graphic/Path'; @@ -546,4 +546,4 @@ class CalendarView extends ComponentView { } } -ComponentView.registerClass(CalendarView); \ No newline at end of file +export default CalendarView; \ No newline at end of file diff --git a/src/component/calendar/install.ts b/src/component/calendar/install.ts new file mode 100644 index 0000000000..e43f4da740 --- /dev/null +++ b/src/component/calendar/install.ts @@ -0,0 +1,29 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import CalendarModel from '../../coord/calendar/CalendarModel'; +import CalendarView from './CalendarView'; +import Calendar from '../../coord/calendar/Calendar'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(CalendarModel); + registers.registerComponentView(CalendarView); + registers.registerCoordinateSystem('calendar', Calendar); +} \ No newline at end of file diff --git a/src/component/dataZoom.ts b/src/component/dataZoom.ts index 8be3b0234c..0254e2876a 100644 --- a/src/component/dataZoom.ts +++ b/src/component/dataZoom.ts @@ -17,8 +17,7 @@ * under the License. */ -import './dataZoomSlider'; -import './dataZoomInside'; +import { use } from '../extension'; +import { install } from './dataZoom/install'; -// Do not include './dataZoomSelect', -// since it only work for toolbox dataZoom. +use(install); diff --git a/src/component/dataZoom/AxisProxy.ts b/src/component/dataZoom/AxisProxy.ts index f34a08b15f..e9c997d9af 100644 --- a/src/component/dataZoom/AxisProxy.ts +++ b/src/component/dataZoom/AxisProxy.ts @@ -22,7 +22,7 @@ import * as numberUtil from '../../util/number'; import sliderMove from '../helper/sliderMove'; import GlobalModel from '../../model/Global'; import SeriesModel from '../../model/Series'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Dictionary } from '../../util/types'; // TODO Polar? import DataZoomModel from './DataZoomModel'; diff --git a/src/component/dataZoom/DataZoomModel.ts b/src/component/dataZoom/DataZoomModel.ts index 01b6478c73..17b1005484 100644 --- a/src/component/dataZoom/DataZoomModel.ts +++ b/src/component/dataZoom/DataZoomModel.ts @@ -37,6 +37,8 @@ import { MULTIPLE_REFERRING, SINGLE_REFERRING } from '../../util/model'; export interface DataZoomOption extends ComponentOption { + mainType?: 'dataZoom' + /** * Default auto by axisIndex */ diff --git a/src/component/dataZoom/DataZoomView.ts b/src/component/dataZoom/DataZoomView.ts index 2a96a35c8d..2bee7dd8e7 100644 --- a/src/component/dataZoom/DataZoomView.ts +++ b/src/component/dataZoom/DataZoomView.ts @@ -20,7 +20,7 @@ import ComponentView from '../../view/Component'; import DataZoomModel from './DataZoomModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; class DataZoomView extends ComponentView { @@ -39,6 +39,4 @@ class DataZoomView extends ComponentView { } -ComponentView.registerClass(DataZoomView); - export default DataZoomView; \ No newline at end of file diff --git a/src/component/dataZoom/InsideZoomModel.ts b/src/component/dataZoom/InsideZoomModel.ts index 3b8d3c342d..ad417afc87 100644 --- a/src/component/dataZoom/InsideZoomModel.ts +++ b/src/component/dataZoom/InsideZoomModel.ts @@ -18,7 +18,6 @@ */ import DataZoomModel, {DataZoomOption} from './DataZoomModel'; -import ComponentModel from '../../model/Component'; import { inheritDefaultOption } from '../../util/component'; export interface InsideDataZoomOption extends DataZoomOption { @@ -62,6 +61,4 @@ class InsideZoomModel extends DataZoomModel { }); } -ComponentModel.registerClass(InsideZoomModel); - export default InsideZoomModel; \ No newline at end of file diff --git a/src/component/dataZoom/InsideZoomView.ts b/src/component/dataZoom/InsideZoomView.ts index 878fef2e47..99b9cfd729 100644 --- a/src/component/dataZoom/InsideZoomView.ts +++ b/src/component/dataZoom/InsideZoomView.ts @@ -22,8 +22,7 @@ import sliderMove from '../helper/sliderMove'; import * as roams from './roams'; import InsideZoomModel from './InsideZoomModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; -import ComponentView from '../../view/Component'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { bind } from 'zrender/src/core/util'; import RoamController, {RoamEventParams} from '../helper/RoamController'; import { AxisBaseModel } from '../../coord/AxisBaseModel'; @@ -286,6 +285,4 @@ const getDirectionInfo: Record<'grid' | 'polar' | 'singleAxis', GetDirectionInfo } }; -ComponentView.registerClass(InsideZoomView); - export default InsideZoomView; diff --git a/src/component/dataZoom/SelectZoomModel.ts b/src/component/dataZoom/SelectZoomModel.ts index 00b5f52a32..b4920fdca6 100644 --- a/src/component/dataZoom/SelectZoomModel.ts +++ b/src/component/dataZoom/SelectZoomModel.ts @@ -18,11 +18,10 @@ */ import DataZoomModel from './DataZoomModel'; -import ComponentModel from '../../model/Component'; class SelectDataZoomModel extends DataZoomModel { static type = 'dataZoom.select'; type = SelectDataZoomModel.type; } -ComponentModel.registerClass(SelectDataZoomModel); +export default SelectDataZoomModel; \ No newline at end of file diff --git a/src/component/dataZoom/SelectZoomView.ts b/src/component/dataZoom/SelectZoomView.ts index ae96b79b5f..1324ed8610 100644 --- a/src/component/dataZoom/SelectZoomView.ts +++ b/src/component/dataZoom/SelectZoomView.ts @@ -18,11 +18,10 @@ */ import DataZoomView from './DataZoomView'; -import ComponentView from '../../view/Component'; class SelectDataZoomView extends DataZoomView { static type = 'dataZoom.select'; type = SelectDataZoomView.type; } -ComponentView.registerClass(SelectDataZoomView); \ No newline at end of file +export default SelectDataZoomView; \ No newline at end of file diff --git a/src/component/dataZoom/SliderZoomModel.ts b/src/component/dataZoom/SliderZoomModel.ts index a487ec7166..a6d051e1b4 100644 --- a/src/component/dataZoom/SliderZoomModel.ts +++ b/src/component/dataZoom/SliderZoomModel.ts @@ -18,7 +18,6 @@ */ import DataZoomModel, {DataZoomOption} from './DataZoomModel'; -import ComponentModel from '../../model/Component'; import { BoxLayoutOptionMixin, ZRColor, @@ -212,6 +211,4 @@ class SliderZoomModel extends DataZoomModel { } as SliderDataZoomOption); } -ComponentModel.registerClass(SliderZoomModel); - export default SliderZoomModel; \ No newline at end of file diff --git a/src/component/dataZoom/SliderZoomView.ts b/src/component/dataZoom/SliderZoomView.ts index 7ef1b87ea2..28a94cc423 100644 --- a/src/component/dataZoom/SliderZoomView.ts +++ b/src/component/dataZoom/SliderZoomView.ts @@ -26,7 +26,7 @@ import {linearMap, asc, parsePercent} from '../../util/number'; import * as layout from '../../util/layout'; import sliderMove from '../helper/sliderMove'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { LayoutOrient, Payload, ZRTextVerticalAlign, ZRTextAlign, ZRElementEvent, ParsedValue } from '../../util/types'; @@ -1062,6 +1062,4 @@ function getCursor(orient: LayoutOrient) { return orient === 'vertical' ? 'ns-resize' : 'ew-resize'; } -ComponentView.registerClass(SliderZoomView); - export default SliderZoomView; diff --git a/src/component/dataZoom/dataZoomAction.ts b/src/component/dataZoom/dataZoomAction.ts index 11c50f2466..30ae964671 100644 --- a/src/component/dataZoom/dataZoomAction.ts +++ b/src/component/dataZoom/dataZoomAction.ts @@ -17,23 +17,25 @@ * under the License. */ -import * as echarts from '../../echarts'; -import * as zrUtil from 'zrender/src/core/util'; import GlobalModel from '../../model/Global'; import { findEffectedDataZooms } from './helper'; +import { EChartsExtensionInstallRegisters } from '../../extension'; +import { each } from 'zrender/src/core/util'; -echarts.registerAction('dataZoom', function (payload, ecModel: GlobalModel) { +export default function installDataZoomAction(registers: EChartsExtensionInstallRegisters) { + registers.registerAction('dataZoom', function (payload, ecModel: GlobalModel) { - const effectedModels = findEffectedDataZooms(ecModel, payload); + const effectedModels = findEffectedDataZooms(ecModel, payload); - zrUtil.each(effectedModels, function (dataZoomModel) { - dataZoomModel.setRawRange({ - start: payload.start, - end: payload.end, - startValue: payload.startValue, - endValue: payload.endValue + each(effectedModels, function (dataZoomModel) { + dataZoomModel.setRawRange({ + start: payload.start, + end: payload.end, + startValue: payload.startValue, + endValue: payload.endValue + }); }); - }); -}); + }); +} diff --git a/src/component/dataZoom/dataZoomProcessor.ts b/src/component/dataZoom/dataZoomProcessor.ts index 7404abfb16..f511aaed31 100644 --- a/src/component/dataZoom/dataZoomProcessor.ts +++ b/src/component/dataZoom/dataZoomProcessor.ts @@ -17,20 +17,19 @@ * under the License. */ -import * as echarts from '../../echarts'; import {createHashMap, each} from 'zrender/src/core/util'; import SeriesModel from '../../model/Series'; import DataZoomModel, { DataZoomExtendedAxisBaseModel } from './DataZoomModel'; import { getAxisMainType, DataZoomAxisDimension } from './helper'; import AxisProxy from './AxisProxy'; +import { StageHandler } from '../../util/types'; - -echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER, { +const dataZoomProcessor: StageHandler = { // `dataZoomProcessor` will only be performed in needed series. Consider if // there is a line series and a pie series, it is better not to update the // line series if only pie series is needed to be updated. - getTargetSeries: function (ecModel) { + getTargetSeries(ecModel) { function eachAxisModel( cb: ( @@ -77,7 +76,7 @@ echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER, { // Consider appendData, where filter should be performed. Because data process is // in block mode currently, it is not need to worry about that the overallProgress // execute every frame. - overallReset: function (ecModel, api) { + overallReset(ecModel, api) { ecModel.eachComponent('dataZoom', function (dataZoomModel: DataZoomModel) { // We calculate window and reset axis here but not in model @@ -123,4 +122,6 @@ echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER, { } }); } -}); +}; + +export default dataZoomProcessor; \ No newline at end of file diff --git a/src/component/dataZoom/install.ts b/src/component/dataZoom/install.ts new file mode 100644 index 0000000000..75d8c647d7 --- /dev/null +++ b/src/component/dataZoom/install.ts @@ -0,0 +1,31 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import {install as installDataZoomInside} from './installDataZoomInside'; +import {install as installDataZoomSlider} from './installDataZoomSlider'; + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installDataZoomInside); + use(installDataZoomSlider); + + // Do not install './dataZoomSelect', + // since it only work for toolbox dataZoom. + +} \ No newline at end of file diff --git a/src/component/dataZoom/installCommon.ts b/src/component/dataZoom/installCommon.ts new file mode 100644 index 0000000000..4c70aad1e7 --- /dev/null +++ b/src/component/dataZoom/installCommon.ts @@ -0,0 +1,39 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import dataZoomProcessor from './dataZoomProcessor'; +import installDataZoomAction from './dataZoomAction'; + +let installed = false; +export default function installCommon(registers: EChartsExtensionInstallRegisters) { + if (installed) { + return; + } + installed = true; + + registers.registerProcessor(registers.PRIORITY.PROCESSOR.FILTER, dataZoomProcessor); + + installDataZoomAction(registers); + + registers.registerSubTypeDefaulter('dataZoom', function () { + // Default 'slider' when no type specified. + return 'slider'; + }); +} \ No newline at end of file diff --git a/src/component/dataZoom/installDataZoomInside.ts b/src/component/dataZoom/installDataZoomInside.ts new file mode 100644 index 0000000000..b987d8e089 --- /dev/null +++ b/src/component/dataZoom/installDataZoomInside.ts @@ -0,0 +1,34 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import InsideZoomModel from './InsideZoomModel'; +import InsideZoomView from './InsideZoomView'; +import {installDataZoomRoamProcessor} from './roams'; +import installCommon from './installCommon'; + +export function install(registers: EChartsExtensionInstallRegisters) { + + installCommon(registers); + + registers.registerComponentModel(InsideZoomModel); + registers.registerComponentView(InsideZoomView); + + installDataZoomRoamProcessor(registers); +} \ No newline at end of file diff --git a/src/component/dataZoom/installDataZoomSelect.ts b/src/component/dataZoom/installDataZoomSelect.ts new file mode 100644 index 0000000000..0154bf2ab1 --- /dev/null +++ b/src/component/dataZoom/installDataZoomSelect.ts @@ -0,0 +1,31 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import SelectZoomModel from './SelectZoomModel'; +import SelectZoomView from './SelectZoomView'; +import installCommon from './installCommon'; + +export function install(registers: EChartsExtensionInstallRegisters) { + + registers.registerComponentModel(SelectZoomModel); + registers.registerComponentView(SelectZoomView); + + installCommon(registers); +} \ No newline at end of file diff --git a/src/component/dataZoom/installDataZoomSlider.ts b/src/component/dataZoom/installDataZoomSlider.ts new file mode 100644 index 0000000000..3fde871ef9 --- /dev/null +++ b/src/component/dataZoom/installDataZoomSlider.ts @@ -0,0 +1,31 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import SliderZoomModel from './SliderZoomModel'; +import SliderZoomView from './SliderZoomView'; +import installCommon from './installCommon'; + +export function install(registers: EChartsExtensionInstallRegisters) { + + registers.registerComponentModel(SliderZoomModel); + registers.registerComponentView(SliderZoomView); + + installCommon(registers); +} \ No newline at end of file diff --git a/src/component/dataZoom/roams.ts b/src/component/dataZoom/roams.ts index 47f2d3b48f..8624bb46db 100644 --- a/src/component/dataZoom/roams.ts +++ b/src/component/dataZoom/roams.ts @@ -23,12 +23,11 @@ // pan or zoom, only dispatch one action for those data zoom // components. -import * as echarts from '../../echarts'; import RoamController, { RoamType } from '../../component/helper/RoamController'; import * as throttleUtil from '../../util/throttle'; import { makeInner } from '../../util/model'; import { Dictionary, ZRElementEvent } from '../../util/types'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import InsideZoomModel from './InsideZoomModel'; import { each, curry, Curry1, HashMap, createHashMap } from 'zrender/src/core/util'; import { @@ -38,6 +37,7 @@ import { import GlobalModel from '../../model/Global'; import { CoordinateSystemHostModel } from '../../coord/CoordinateSystem'; import { DataZoomGetRangeHandlers } from './InsideZoomView'; +import { EChartsExtensionInstallRegisters } from '../../extension'; interface DataZoomInfo { @@ -63,76 +63,6 @@ const inner = makeInner<{ coordSysRecordMap: HashMap; }, ExtensionAPI>(); - -echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER, function (ecModel: GlobalModel, api: ExtensionAPI): void { - const apiInner = inner(api); - const coordSysRecordMap = apiInner.coordSysRecordMap - || (apiInner.coordSysRecordMap = createHashMap()); - - coordSysRecordMap.each(function (coordSysRecord) { - // `coordSysRecordMap` always exists (becuase it hold the `roam controller`, which should - // better not re-create each time), but clear `dataZoomInfoMap` each round of the workflow. - coordSysRecord.dataZoomInfoMap = null; - }); - - ecModel.eachComponent( - { mainType: 'dataZoom', subType: 'inside' }, - function (dataZoomModel: InsideZoomModel) { - const dzReferCoordSysWrap = collectReferCoordSysModelInfo(dataZoomModel); - - each(dzReferCoordSysWrap.infoList, function (dzCoordSysInfo) { - - const coordSysUid = dzCoordSysInfo.model.uid; - const coordSysRecord = coordSysRecordMap.get(coordSysUid) - || coordSysRecordMap.set(coordSysUid, createCoordSysRecord(api, dzCoordSysInfo.model)); - - const dataZoomInfoMap = coordSysRecord.dataZoomInfoMap - || (coordSysRecord.dataZoomInfoMap = createHashMap()); - // Notice these props might be changed each time for a single dataZoomModel. - dataZoomInfoMap.set(dataZoomModel.uid, { - dzReferCoordSysInfo: dzCoordSysInfo, - model: dataZoomModel, - getRange: null - }); - }); - } - ); - - // (1) Merge dataZoom settings for each coord sys and set to the roam controller. - // (2) Clear coord sys if not refered by any dataZoom. - coordSysRecordMap.each(function (coordSysRecord) { - const controller = coordSysRecord.controller; - let firstDzInfo: DataZoomInfo; - const dataZoomInfoMap = coordSysRecord.dataZoomInfoMap; - - if (dataZoomInfoMap) { - const firstDzKey = dataZoomInfoMap.keys()[0]; - if (firstDzKey != null) { - firstDzInfo = dataZoomInfoMap.get(firstDzKey); - } - } - - if (!firstDzInfo) { - disposeCoordSysRecord(coordSysRecordMap, coordSysRecord); - return; - } - - const controllerParams = mergeControllerParams(dataZoomInfoMap); - controller.enable(controllerParams.controlType, controllerParams.opt); - - controller.setPointerChecker(coordSysRecord.containsPoint); - - throttleUtil.createOrUpdate( - coordSysRecord, - 'dispatchAction', - firstDzInfo.model.get('throttle', true), - 'fixRate' - ); - }); - -}); - - export function setViewInfoToCoordSysRecord( api: ExtensionAPI, dataZoomModel: InsideZoomModel, @@ -290,3 +220,76 @@ function mergeControllerParams(dataZoomInfoMap: HashMap<{ model: InsideZoomModel } }; } + +export function installDataZoomRoamProcessor(registers: EChartsExtensionInstallRegisters) { + + registers.registerProcessor( + registers.PRIORITY.PROCESSOR.FILTER, + function (ecModel: GlobalModel, api: ExtensionAPI): void { + const apiInner = inner(api); + const coordSysRecordMap = apiInner.coordSysRecordMap + || (apiInner.coordSysRecordMap = createHashMap()); + + coordSysRecordMap.each(function (coordSysRecord) { + // `coordSysRecordMap` always exists (becuase it hold the `roam controller`, which should + // better not re-create each time), but clear `dataZoomInfoMap` each round of the workflow. + coordSysRecord.dataZoomInfoMap = null; + }); + + ecModel.eachComponent( + { mainType: 'dataZoom', subType: 'inside' }, + function (dataZoomModel: InsideZoomModel) { + const dzReferCoordSysWrap = collectReferCoordSysModelInfo(dataZoomModel); + + each(dzReferCoordSysWrap.infoList, function (dzCoordSysInfo) { + + const coordSysUid = dzCoordSysInfo.model.uid; + const coordSysRecord = coordSysRecordMap.get(coordSysUid) + || coordSysRecordMap.set(coordSysUid, createCoordSysRecord(api, dzCoordSysInfo.model)); + + const dataZoomInfoMap = coordSysRecord.dataZoomInfoMap + || (coordSysRecord.dataZoomInfoMap = createHashMap()); + // Notice these props might be changed each time for a single dataZoomModel. + dataZoomInfoMap.set(dataZoomModel.uid, { + dzReferCoordSysInfo: dzCoordSysInfo, + model: dataZoomModel, + getRange: null + }); + }); + } + ); + + // (1) Merge dataZoom settings for each coord sys and set to the roam controller. + // (2) Clear coord sys if not refered by any dataZoom. + coordSysRecordMap.each(function (coordSysRecord) { + const controller = coordSysRecord.controller; + let firstDzInfo: DataZoomInfo; + const dataZoomInfoMap = coordSysRecord.dataZoomInfoMap; + + if (dataZoomInfoMap) { + const firstDzKey = dataZoomInfoMap.keys()[0]; + if (firstDzKey != null) { + firstDzInfo = dataZoomInfoMap.get(firstDzKey); + } + } + + if (!firstDzInfo) { + disposeCoordSysRecord(coordSysRecordMap, coordSysRecord); + return; + } + + const controllerParams = mergeControllerParams(dataZoomInfoMap); + controller.enable(controllerParams.controlType, controllerParams.opt); + + controller.setPointerChecker(coordSysRecord.containsPoint); + + throttleUtil.createOrUpdate( + coordSysRecord, + 'dispatchAction', + firstDzInfo.model.get('throttle', true), + 'fixRate' + ); + }); + }); + +} \ No newline at end of file diff --git a/src/component/dataZoomInside.ts b/src/component/dataZoomInside.ts index b291f856e5..a8b6cbb171 100644 --- a/src/component/dataZoomInside.ts +++ b/src/component/dataZoomInside.ts @@ -17,13 +17,7 @@ * under the License. */ -import './dataZoom/typeDefaulter'; +import { use } from '../extension'; +import { install } from './dataZoom/installDataZoomInside'; -import './dataZoom/DataZoomModel'; -import './dataZoom/DataZoomView'; - -import './dataZoom/InsideZoomModel'; -import './dataZoom/InsideZoomView'; - -import './dataZoom/dataZoomProcessor'; -import './dataZoom/dataZoomAction'; +use(install); \ No newline at end of file diff --git a/src/component/dataZoomSelect.ts b/src/component/dataZoomSelect.ts index 6cab5f08c0..49288e55ef 100644 --- a/src/component/dataZoomSelect.ts +++ b/src/component/dataZoomSelect.ts @@ -22,13 +22,7 @@ * MUST NOT import this module directly. */ -import './dataZoom/typeDefaulter'; +import { use } from '../extension'; +import { install } from './dataZoom/installDataZoomSelect'; -import './dataZoom/DataZoomModel'; -import './dataZoom/DataZoomView'; - -import './dataZoom/SelectZoomModel'; -import './dataZoom/SelectZoomView'; - -import './dataZoom/dataZoomProcessor'; -import './dataZoom/dataZoomAction'; +use(install); \ No newline at end of file diff --git a/src/component/dataZoomSlider.ts b/src/component/dataZoomSlider.ts index b89b97f535..c4750e087a 100644 --- a/src/component/dataZoomSlider.ts +++ b/src/component/dataZoomSlider.ts @@ -17,13 +17,8 @@ * under the License. */ -import './dataZoom/typeDefaulter'; -import './dataZoom/DataZoomModel'; -import './dataZoom/DataZoomView'; +import { use } from '../extension'; +import { install } from './dataZoom/installDataZoomSlider'; -import './dataZoom/SliderZoomModel'; -import './dataZoom/SliderZoomView'; - -import './dataZoom/dataZoomProcessor'; -import './dataZoom/dataZoomAction'; +use(install); \ No newline at end of file diff --git a/src/component/dataset.ts b/src/component/dataset.ts index 26b77ec006..31297f65cc 100644 --- a/src/component/dataset.ts +++ b/src/component/dataset.ts @@ -17,79 +17,7 @@ * under the License. */ -/** - * This module is imported by echarts directly. - * - * Notice: - * Always keep this file exists for backward compatibility. - * Because before 4.1.0, dataset is an optional component, - * some users may import this module manually. - */ +import { use } from '../extension'; +import { install } from './dataset/install'; -import ComponentModel from '../model/Component'; -import ComponentView from '../view/Component'; -import { - SERIES_LAYOUT_BY_COLUMN, ComponentOption, SeriesEncodeOptionMixin, - OptionSourceData, SeriesLayoutBy, OptionSourceHeader -} from '../util/types'; -import { DataTransformOption, PipedDataTransformOption } from '../data/helper/transform'; -import GlobalModel from '../model/Global'; -import Model from '../model/Model'; -import { disableTransformOptionMerge, SourceManager } from '../data/helper/sourceManager'; - - -export interface DatasetOption extends - Pick, - Pick { - seriesLayoutBy?: SeriesLayoutBy; - sourceHeader?: OptionSourceHeader; - source?: OptionSourceData; - - fromDatasetIndex?: number; - fromDatasetId?: string; - transform?: DataTransformOption | PipedDataTransformOption; - // When a transform result more than on results, the results can be referenced only by: - // Using `fromDatasetIndex`/`fromDatasetId` and `transfromResultIndex` to retrieve - // the results from other dataset. - fromTransformResult?: number; -} - -export class DatasetModel extends ComponentModel { - - type = 'dataset'; - static type = 'dataset'; - - static defaultOption: DatasetOption = { - seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN - }; - - private _sourceManager: SourceManager; - - init(option: Opts, parentModel: Model, ecModel: GlobalModel): void { - super.init(option, parentModel, ecModel); - this._sourceManager = new SourceManager(this); - disableTransformOptionMerge(this); - } - - mergeOption(newOption: Opts, ecModel: GlobalModel): void { - super.mergeOption(newOption, ecModel); - disableTransformOptionMerge(this); - } - - optionUpdated() { - this._sourceManager.dirty(); - } - - getSourceManager() { - return this._sourceManager; - } -} - -ComponentModel.registerClass(DatasetModel); - -class DatasetView extends ComponentView { - static type = 'dataset'; - type = 'dataset'; -} - -ComponentView.registerClass(DatasetView); +use(install); \ No newline at end of file diff --git a/src/component/dataset/install.ts b/src/component/dataset/install.ts new file mode 100644 index 0000000000..2e5d7be1d2 --- /dev/null +++ b/src/component/dataset/install.ts @@ -0,0 +1,99 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/** + * This module is imported by echarts directly. + * + * Notice: + * Always keep this file exists for backward compatibility. + * Because before 4.1.0, dataset is an optional component, + * some users may import this module manually. + */ + +import ComponentModel from '../../model/Component'; +import ComponentView from '../../view/Component'; +import { + SERIES_LAYOUT_BY_COLUMN, ComponentOption, SeriesEncodeOptionMixin, + OptionSourceData, SeriesLayoutBy, OptionSourceHeader +} from '../../util/types'; +import { DataTransformOption, PipedDataTransformOption } from '../../data/helper/transform'; +import GlobalModel from '../../model/Global'; +import Model from '../../model/Model'; +import { disableTransformOptionMerge, SourceManager } from '../../data/helper/sourceManager'; +import { EChartsExtensionInstallRegisters } from '../../extension'; + + +export interface DatasetOption extends + Pick, + Pick { + mainType?: 'dataset'; + + seriesLayoutBy?: SeriesLayoutBy; + sourceHeader?: OptionSourceHeader; + source?: OptionSourceData; + + fromDatasetIndex?: number; + fromDatasetId?: string; + transform?: DataTransformOption | PipedDataTransformOption; + // When a transform result more than on results, the results can be referenced only by: + // Using `fromDatasetIndex`/`fromDatasetId` and `transfromResultIndex` to retrieve + // the results from other dataset. + fromTransformResult?: number; +} + +export class DatasetModel extends ComponentModel { + + type = 'dataset'; + static type = 'dataset'; + + static defaultOption: DatasetOption = { + seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN + }; + + private _sourceManager: SourceManager; + + init(option: Opts, parentModel: Model, ecModel: GlobalModel): void { + super.init(option, parentModel, ecModel); + this._sourceManager = new SourceManager(this); + disableTransformOptionMerge(this); + } + + mergeOption(newOption: Opts, ecModel: GlobalModel): void { + super.mergeOption(newOption, ecModel); + disableTransformOptionMerge(this); + } + + optionUpdated() { + this._sourceManager.dirty(); + } + + getSourceManager() { + return this._sourceManager; + } +} + +class DatasetView extends ComponentView { + static type = 'dataset'; + type = 'dataset'; +} + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(DatasetModel); + registers.registerComponentView(DatasetView); +} \ No newline at end of file diff --git a/src/component/geo.ts b/src/component/geo.ts index edb418aaa0..33ae3763fa 100644 --- a/src/component/geo.ts +++ b/src/component/geo.ts @@ -18,54 +18,8 @@ */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; -import '../coord/geo/geoCreator'; -import './geo/GeoView'; -import '../action/geoRoam'; -import { ActionInfo } from '../util/types'; +import { use } from '../extension'; +import { install } from './geo/install'; -// NOTE: DONT Remove this import, or GeoModel will be treeshaked. -import '../coord/geo/GeoModel'; -/* eslint-disable-next-line */ -import GeoModel from '../coord/geo/GeoModel'; - -function makeAction( - method: 'toggleSelected' | 'select' | 'unSelect', - actionInfo: ActionInfo -): void { - actionInfo.update = 'geo:updateSelectStatus'; - echarts.registerAction(actionInfo, function (payload, ecModel) { - const selected = {} as {[regionName: string]: boolean}; - - ecModel.eachComponent( - { mainType: 'geo', query: payload}, - function (geoModel: GeoModel) { - geoModel[method](payload.name); - const geo = geoModel.coordinateSystem; - zrUtil.each(geo.regions, function (region) { - selected[region.name] = geoModel.isSelected(region.name) || false; - }); - } - ); - - return { - selected: selected, - name: payload.name - }; - }); -} - -makeAction('toggleSelected', { - type: 'geoToggleSelect', - event: 'geoselectchanged' -}); -makeAction('select', { - type: 'geoSelect', - event: 'geoselected' -}); -makeAction('unSelect', { - type: 'geoUnSelect', - event: 'geounselected' -}); +use(install); \ No newline at end of file diff --git a/src/component/geo/GeoView.ts b/src/component/geo/GeoView.ts index c1ca7e6ef2..a3e1c71e26 100644 --- a/src/component/geo/GeoView.ts +++ b/src/component/geo/GeoView.ts @@ -21,7 +21,7 @@ import MapDraw from '../helper/MapDraw'; import ComponentView from '../../view/Component'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GeoModel from '../../coord/geo/GeoModel'; import { Payload, ZRElementEvent, ECEventData } from '../../util/types'; import { getECData } from '../../util/innerStore'; @@ -100,6 +100,4 @@ class GeoView extends ComponentView { } -ComponentView.registerClass(GeoView); - export default GeoView; diff --git a/src/component/geo/install.ts b/src/component/geo/install.ts new file mode 100644 index 0000000000..cb0de2915d --- /dev/null +++ b/src/component/geo/install.ts @@ -0,0 +1,122 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import GeoModel from '../../coord/geo/GeoModel'; +import geoCreator from '../../coord/geo/geoCreator'; +import { ActionInfo } from '../../util/types'; +import { each } from 'zrender/src/core/util'; +import GlobalModel from '../../model/Global'; +import { updateCenterAndZoom, RoamPaylod } from '../../action/roamHelper'; +import MapSeries from '../../chart/map/MapSeries'; +import GeoView from './GeoView'; + +export function install(registers: EChartsExtensionInstallRegisters) { + + registers.registerCoordinateSystem('geo', geoCreator); + + registers.registerComponentModel(GeoModel); + registers.registerComponentView(GeoView); + + + function makeAction( + method: 'toggleSelected' | 'select' | 'unSelect', + actionInfo: ActionInfo + ): void { + actionInfo.update = 'geo:updateSelectStatus'; + registers.registerAction(actionInfo, function (payload, ecModel) { + const selected = {} as {[regionName: string]: boolean}; + + ecModel.eachComponent( + { mainType: 'geo', query: payload}, + function (geoModel: GeoModel) { + geoModel[method](payload.name); + const geo = geoModel.coordinateSystem; + each(geo.regions, function (region) { + selected[region.name] = geoModel.isSelected(region.name) || false; + }); + } + ); + + return { + selected: selected, + name: payload.name + }; + }); + } + + makeAction('toggleSelected', { + type: 'geoToggleSelect', + event: 'geoselectchanged' + }); + makeAction('select', { + type: 'geoSelect', + event: 'geoselected' + }); + makeAction('unSelect', { + type: 'geoUnSelect', + event: 'geounselected' + }); + + /** + * @payload + * @property {string} [componentType=series] + * @property {number} [dx] + * @property {number} [dy] + * @property {number} [zoom] + * @property {number} [originX] + * @property {number} [originY] + */ + registers.registerAction({ + type: 'geoRoam', + event: 'geoRoam', + update: 'updateTransform' + }, function (payload: RoamPaylod, ecModel: GlobalModel) { + const componentType = payload.componentType || 'series'; + + ecModel.eachComponent( + { mainType: componentType, query: payload }, + function (componentModel: GeoModel | MapSeries) { + const geo = componentModel.coordinateSystem; + if (geo.type !== 'geo') { + return; + } + + const res = updateCenterAndZoom( + geo, payload, (componentModel as GeoModel).get('scaleLimit') + ); + + componentModel.setCenter + && componentModel.setCenter(res.center); + + componentModel.setZoom + && componentModel.setZoom(res.zoom); + + // All map series with same `map` use the same geo coordinate system + // So the center and zoom must be in sync. Include the series not selected by legend + if (componentType === 'series') { + each((componentModel as MapSeries).seriesGroup, function (seriesModel) { + seriesModel.setCenter(res.center); + seriesModel.setZoom(res.zoom); + }); + } + } + ); + }) +} \ No newline at end of file diff --git a/src/component/graphic.ts b/src/component/graphic.ts index 909976e914..3453d36853 100644 --- a/src/component/graphic.ts +++ b/src/component/graphic.ts @@ -17,760 +17,7 @@ * under the License. */ +import { use } from '../extension'; +import { install } from './graphic/install'; -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; - -import * as modelUtil from '../util/model'; -import * as graphicUtil from '../util/graphic'; -import * as layoutUtil from '../util/layout'; -import {parsePercent} from '../util/number'; -import { ComponentOption, BoxLayoutOptionMixin, Dictionary, ZRStyleProps, OptionId } from '../util/types'; -import ComponentModel from '../model/Component'; -import Element, { ElementTextConfig } from 'zrender/src/Element'; -import Displayable from 'zrender/src/graphic/Displayable'; -import { PathProps } from 'zrender/src/graphic/Path'; -import { ImageStyleProps } from 'zrender/src/graphic/Image'; -import GlobalModel from '../model/Global'; -import ComponentView from '../view/Component'; -import ExtensionAPI from '../ExtensionAPI'; -import { getECData } from '../util/innerStore'; -import { TextStyleProps } from 'zrender/src/graphic/Text'; -import { isEC4CompatibleStyle, convertFromEC4CompatibleStyle } from '../util/styleCompat'; - - -const TRANSFORM_PROPS = { - x: 1, - y: 1, - scaleX: 1, - scaleY: 1, - originX: 1, - originY: 1, - rotation: 1 -} as const; -type TransformProp = keyof typeof TRANSFORM_PROPS; - -interface GraphicComponentBaseElementOption extends - Partial>, - /** - * left/right/top/bottom: (like 12, '22%', 'center', default undefined) - * If left/rigth is set, shape.x/shape.cx/position will not be used. - * If top/bottom is set, shape.y/shape.cy/position will not be used. - * This mechanism is useful when you want to position a group/element - * against the right side or the center of this container. - */ - Partial> { - - /** - * element type, mandatory. - * Only can be omit if call setOption not at the first time and perform merge. - */ - type?: string; - - id?: OptionId; - name?: string; - - // Only internal usage. Use specified value does NOT make sense. - parentId?: OptionId; - parentOption?: GraphicComponentElementOption; - children?: GraphicComponentElementOption[]; - hv?: [boolean, boolean]; - - /** - * bounding: (enum: 'all' (default) | 'raw') - * Specify how to calculate boundingRect when locating. - * 'all': Get uioned and transformed boundingRect - * from both itself and its descendants. - * This mode simplies confining a group of elements in the bounding - * of their ancester container (e.g., using 'right: 0'). - * 'raw': Only use the boundingRect of itself and before transformed. - * This mode is similar to css behavior, which is useful when you - * want an element to be able to overflow its container. (Consider - * a rotated circle needs to be located in a corner.) - */ - bounding?: 'raw' | 'all'; - - /** - * info: custom info. enables user to mount some info on elements and use them - * in event handlers. Update them only when user specified, otherwise, remain. - */ - info?: GraphicExtraElementInfo; - - textContent?: GraphicComponentTextOption; - textConfig?: ElementTextConfig; - - $action?: 'merge' | 'replace' | 'remove'; -}; -interface GraphicComponentDisplayableOption extends - GraphicComponentBaseElementOption, - Partial> { - - style?: ZRStyleProps; - - // TODO: states? - // emphasis?: GraphicComponentDisplayableOptionOnState; - // blur?: GraphicComponentDisplayableOptionOnState; - // select?: GraphicComponentDisplayableOptionOnState; -} -// TODO: states? -// interface GraphicComponentDisplayableOptionOnState extends Partial> { -// style?: ZRStyleProps; -// } -interface GraphicComponentGroupOption extends GraphicComponentBaseElementOption { - type?: 'group'; - - /** - * width/height: (can only be pixel value, default 0) - * Only be used to specify contianer(group) size, if needed. And - * can not be percentage value (like '33%'). See the reason in the - * layout algorithm below. - */ - width?: number; - height?: number; - - // TODO: Can only set focus, blur on the root element. - // children: Omit[]; - children: GraphicComponentElementOption[]; -} -export interface GraphicComponentZRPathOption extends GraphicComponentDisplayableOption { - shape?: PathProps['shape']; -} -export interface GraphicComponentImageOption extends GraphicComponentDisplayableOption { - type?: 'image'; - style?: ImageStyleProps; - // TODO: states? - // emphasis?: GraphicComponentImageOptionOnState; - // blur?: GraphicComponentImageOptionOnState; - // select?: GraphicComponentImageOptionOnState; -} -// TODO: states? -// interface GraphicComponentImageOptionOnState extends GraphicComponentDisplayableOptionOnState { -// style?: ImageStyleProps; -// } -interface GraphicComponentTextOption - extends Omit { - type?: 'text'; - style?: TextStyleProps; -} -type GraphicComponentElementOption = - GraphicComponentGroupOption - | GraphicComponentZRPathOption - | GraphicComponentImageOption - | GraphicComponentTextOption; -// type GraphicComponentElementOptionOnState = -// GraphicComponentDisplayableOptionOnState -// | GraphicComponentImageOptionOnState; - -type GraphicExtraElementInfo = Dictionary; - -type ElementMap = zrUtil.HashMap; - -const inner = modelUtil.makeInner<{ - __ecGraphicWidthOption: number; - __ecGraphicHeightOption: number; - __ecGraphicWidth: number; - __ecGraphicHeight: number; - __ecGraphicId: string; -}, Element>(); - - -const _nonShapeGraphicElements = { - - // Reserved but not supported in graphic component. - path: null as unknown, - compoundPath: null as unknown, - - // Supported in graphic component. - group: graphicUtil.Group, - image: graphicUtil.Image, - text: graphicUtil.Text -} as const; -type NonShapeGraphicElementType = keyof typeof _nonShapeGraphicElements; - -// ------------------------ -// Preprocessor -// ------------------------ - -echarts.registerPreprocessor(function (option) { - const graphicOption = option.graphic as GraphicComponentOption | GraphicComponentOption[]; - - // Convert - // {graphic: [{left: 10, type: 'circle'}, ...]} - // or - // {graphic: {left: 10, type: 'circle'}} - // to - // {graphic: [{elements: [{left: 10, type: 'circle'}, ...]}]} - if (zrUtil.isArray(graphicOption)) { - if (!graphicOption[0] || !graphicOption[0].elements) { - option.graphic = [{elements: graphicOption}]; - } - else { - // Only one graphic instance can be instantiated. (We dont - // want that too many views are created in echarts._viewMap) - option.graphic = [(option.graphic as any)[0]]; - } - } - else if (graphicOption && !graphicOption.elements) { - option.graphic = [{elements: [graphicOption]}]; - } -}); - -// ------------------------ -// Model -// ------------------------ - -export type GraphicComponentLooseOption = GraphicComponentOption | GraphicComponentElementOption; - -export interface GraphicComponentOption extends ComponentOption { - // Note: elements is always behind its ancestors in this elements array. - elements?: GraphicComponentElementOption[]; - // parentId: string; -}; - - -class GraphicComponentModel extends ComponentModel { - - static type = 'graphic'; - type = GraphicComponentModel.type; - - static defaultOption: GraphicComponentOption = { - elements: [] - // parentId: null - }; - - /** - * Save el options for the sake of the performance (only update modified graphics). - * The order is the same as those in option. (ancesters -> descendants) - */ - private _elOptionsToUpdate: GraphicComponentElementOption[]; - - mergeOption(option: GraphicComponentOption, ecModel: GlobalModel): void { - // Prevent default merge to elements - const elements = this.option.elements; - this.option.elements = null; - - super.mergeOption(option, ecModel); - - this.option.elements = elements; - } - - optionUpdated(newOption: GraphicComponentOption, isInit: boolean): void { - const thisOption = this.option; - const newList = (isInit ? thisOption : newOption).elements; - const existList = thisOption.elements = isInit ? [] : thisOption.elements; - - const flattenedList = [] as GraphicComponentElementOption[]; - this._flatten(newList, flattenedList, null); - - const mappingResult = modelUtil.mappingToExists(existList, flattenedList, 'normalMerge'); - - // Clear elOptionsToUpdate - const elOptionsToUpdate = this._elOptionsToUpdate = [] as GraphicComponentElementOption[]; - - zrUtil.each(mappingResult, function (resultItem, index) { - const newElOption = resultItem.newOption as GraphicComponentElementOption; - - if (__DEV__) { - zrUtil.assert( - zrUtil.isObject(newElOption) || resultItem.existing, - 'Empty graphic option definition' - ); - } - - if (!newElOption) { - return; - } - - elOptionsToUpdate.push(newElOption); - - setKeyInfoToNewElOption(resultItem, newElOption); - - mergeNewElOptionToExist(existList, index, newElOption); - - setLayoutInfoToExist(existList[index], newElOption); - - }, this); - - // Clean - for (let i = existList.length - 1; i >= 0; i--) { - if (existList[i] == null) { - existList.splice(i, 1); - } - else { - // $action should be volatile, otherwise option gotten from - // `getOption` will contain unexpected $action. - delete existList[i].$action; - } - } - } - - /** - * Convert - * [{ - * type: 'group', - * id: 'xx', - * children: [{type: 'circle'}, {type: 'polygon'}] - * }] - * to - * [ - * {type: 'group', id: 'xx'}, - * {type: 'circle', parentId: 'xx'}, - * {type: 'polygon', parentId: 'xx'} - * ] - */ - private _flatten( - optionList: GraphicComponentElementOption[], - result: GraphicComponentElementOption[], - parentOption: GraphicComponentElementOption - ): void { - zrUtil.each(optionList, function (option) { - if (!option) { - return; - } - - if (parentOption) { - option.parentOption = parentOption; - } - - result.push(option); - - const children = option.children; - if (option.type === 'group' && children) { - this._flatten(children, result, option); - } - // Deleting for JSON output, and for not affecting group creation. - delete option.children; - }, this); - } - - // FIXME - // Pass to view using payload? setOption has a payload? - useElOptionsToUpdate(): GraphicComponentElementOption[] { - const els = this._elOptionsToUpdate; - // Clear to avoid render duplicately when zooming. - this._elOptionsToUpdate = null; - return els; - } -} - -ComponentModel.registerClass(GraphicComponentModel); - -// ------------------------ -// View -// ------------------------ - -class GraphicComponentView extends ComponentView { - - static type = 'graphic'; - type = GraphicComponentView.type; - - private _elMap: ElementMap; - private _lastGraphicModel: GraphicComponentModel; - - init() { - this._elMap = zrUtil.createHashMap(); - } - - render(graphicModel: GraphicComponentModel, ecModel: GlobalModel, api: ExtensionAPI): void { - - // Having leveraged between use cases and algorithm complexity, a very - // simple layout mechanism is used: - // The size(width/height) can be determined by itself or its parent (not - // implemented yet), but can not by its children. (Top-down travel) - // The location(x/y) can be determined by the bounding rect of itself - // (can including its descendants or not) and the size of its parent. - // (Bottom-up travel) - - // When `chart.clear()` or `chart.setOption({...}, true)` with the same id, - // view will be reused. - if (graphicModel !== this._lastGraphicModel) { - this._clear(); - } - this._lastGraphicModel = graphicModel; - - this._updateElements(graphicModel); - this._relocate(graphicModel, api); - } - - /** - * Update graphic elements. - */ - private _updateElements(graphicModel: GraphicComponentModel): void { - const elOptionsToUpdate = graphicModel.useElOptionsToUpdate(); - - if (!elOptionsToUpdate) { - return; - } - - const elMap = this._elMap; - const rootGroup = this.group; - - // Top-down tranverse to assign graphic settings to each elements. - zrUtil.each(elOptionsToUpdate, function (elOption) { - const id = modelUtil.convertOptionIdName(elOption.id, null); - const elExisting = id != null ? elMap.get(id) : null; - const parentId = modelUtil.convertOptionIdName(elOption.parentId, null); - const targetElParent = (parentId != null ? elMap.get(parentId) : rootGroup) as graphicUtil.Group; - - const elType = elOption.type; - const elOptionStyle = (elOption as GraphicComponentDisplayableOption).style; - if (elType === 'text' && elOptionStyle) { - // In top/bottom mode, textVerticalAlign should not be used, which cause - // inaccurately locating. - if (elOption.hv && elOption.hv[1]) { - (elOptionStyle as any).textVerticalAlign = - (elOptionStyle as any).textBaseline = - (elOptionStyle as TextStyleProps).verticalAlign = - (elOptionStyle as TextStyleProps).align = null; - } - } - - let textContentOption = (elOption as GraphicComponentZRPathOption).textContent; - let textConfig = (elOption as GraphicComponentZRPathOption).textConfig; - if (elOptionStyle - && isEC4CompatibleStyle(elOptionStyle, elType, !!textConfig, !!textContentOption) - ) { - const convertResult = convertFromEC4CompatibleStyle(elOptionStyle, elType, true); - if (!textConfig && convertResult.textConfig) { - textConfig = (elOption as GraphicComponentZRPathOption).textConfig = convertResult.textConfig; - } - if (!textContentOption && convertResult.textContent) { - textContentOption = convertResult.textContent; - } - } - - // Remove unnecessary props to avoid potential problems. - const elOptionCleaned = getCleanedElOption(elOption); - - // For simple, do not support parent change, otherwise reorder is needed. - if (__DEV__) { - elExisting && zrUtil.assert( - targetElParent === elExisting.parent, - 'Changing parent is not supported.' - ); - } - - const $action = elOption.$action || 'merge'; - if ($action === 'merge') { - elExisting - ? elExisting.attr(elOptionCleaned) - : createEl(id, targetElParent, elOptionCleaned, elMap); - } - else if ($action === 'replace') { - removeEl(elExisting, elMap); - createEl(id, targetElParent, elOptionCleaned, elMap); - } - else if ($action === 'remove') { - removeEl(elExisting, elMap); - } - - const el = elMap.get(id); - - if (el && textContentOption) { - if ($action === 'merge') { - const textContentExisting = el.getTextContent(); - textContentExisting - ? textContentExisting.attr(textContentOption) - : el.setTextContent(new graphicUtil.Text(textContentOption)); - } - else if ($action === 'replace') { - el.setTextContent(new graphicUtil.Text(textContentOption)); - } - } - - if (el) { - const elInner = inner(el); - elInner.__ecGraphicWidthOption = (elOption as GraphicComponentGroupOption).width; - elInner.__ecGraphicHeightOption = (elOption as GraphicComponentGroupOption).height; - setEventData(el, graphicModel, elOption); - } - }); - } - - /** - * Locate graphic elements. - */ - private _relocate(graphicModel: GraphicComponentModel, api: ExtensionAPI): void { - const elOptions = graphicModel.option.elements; - const rootGroup = this.group; - const elMap = this._elMap; - const apiWidth = api.getWidth(); - const apiHeight = api.getHeight(); - - // Top-down to calculate percentage width/height of group - for (let i = 0; i < elOptions.length; i++) { - const elOption = elOptions[i]; - const id = modelUtil.convertOptionIdName(elOption.id, null); - const el = id != null ? elMap.get(id) : null; - - if (!el || !el.isGroup) { - continue; - } - const parentEl = el.parent; - const isParentRoot = parentEl === rootGroup; - // Like 'position:absolut' in css, default 0. - const elInner = inner(el); - const parentElInner = inner(parentEl); - elInner.__ecGraphicWidth = parsePercent( - elInner.__ecGraphicWidthOption, - isParentRoot ? apiWidth : parentElInner.__ecGraphicWidth - ) || 0; - elInner.__ecGraphicHeight = parsePercent( - elInner.__ecGraphicHeightOption, - isParentRoot ? apiHeight : parentElInner.__ecGraphicHeight - ) || 0; - } - - // Bottom-up tranvese all elements (consider ec resize) to locate elements. - for (let i = elOptions.length - 1; i >= 0; i--) { - const elOption = elOptions[i]; - const id = modelUtil.convertOptionIdName(elOption.id, null); - const el = id != null ? elMap.get(id) : null; - - if (!el) { - continue; - } - - const parentEl = el.parent; - const parentElInner = inner(parentEl); - const containerInfo = parentEl === rootGroup - ? { - width: apiWidth, - height: apiHeight - } - : { - width: parentElInner.__ecGraphicWidth, - height: parentElInner.__ecGraphicHeight - }; - - // PENDING - // Currently, when `bounding: 'all'`, the union bounding rect of the group - // does not include the rect of [0, 0, group.width, group.height], which - // is probably weird for users. Should we make a break change for it? - layoutUtil.positionElement( - el, elOption, containerInfo, null, - {hv: elOption.hv, boundingMode: elOption.bounding} - ); - } - } - - /** - * Clear all elements. - */ - private _clear(): void { - const elMap = this._elMap; - elMap.each(function (el) { - removeEl(el, elMap); - }); - this._elMap = zrUtil.createHashMap(); - } - - dispose(): void { - this._clear(); - } -} - -ComponentView.registerClass(GraphicComponentView); - - -function createEl( - id: string, - targetElParent: graphicUtil.Group, - elOption: GraphicComponentElementOption, - elMap: ElementMap -): void { - const graphicType = elOption.type; - - if (__DEV__) { - zrUtil.assert(graphicType, 'graphic type MUST be set'); - } - - const Clz = ( - zrUtil.hasOwn(_nonShapeGraphicElements, graphicType) - // Those graphic elements are not shapes. They should not be - // overwritten by users, so do them first. - ? _nonShapeGraphicElements[graphicType as NonShapeGraphicElementType] - : graphicUtil.getShapeClass(graphicType) - ) as { new(opt: GraphicComponentElementOption): Element }; - - if (__DEV__) { - zrUtil.assert(Clz, 'graphic type can not be found'); - } - - const el = new Clz(elOption); - targetElParent.add(el); - elMap.set(id, el); - inner(el).__ecGraphicId = id; -} - -function removeEl(elExisting: Element, elMap: ElementMap): void { - const existElParent = elExisting && elExisting.parent; - if (existElParent) { - elExisting.type === 'group' && elExisting.traverse(function (el) { - removeEl(el, elMap); - }); - elMap.removeKey(inner(elExisting).__ecGraphicId); - existElParent.remove(elExisting); - } -} - -// Remove unnecessary props to avoid potential problems. -function getCleanedElOption( - elOption: GraphicComponentElementOption -): Omit { - elOption = zrUtil.extend({}, elOption); - zrUtil.each( - ['id', 'parentId', '$action', 'hv', 'bounding', 'textContent'].concat(layoutUtil.LOCATION_PARAMS), - function (name) { - delete (elOption as any)[name]; - } - ); - return elOption; -} - -function isSetLoc( - obj: GraphicComponentElementOption, - props: ('left' | 'right' | 'top' | 'bottom')[] -): boolean { - let isSet; - zrUtil.each(props, function (prop) { - obj[prop] != null && obj[prop] !== 'auto' && (isSet = true); - }); - return isSet; -} - -function setKeyInfoToNewElOption( - resultItem: ReturnType[number], - newElOption: GraphicComponentElementOption -): void { - const existElOption = resultItem.existing as GraphicComponentElementOption; - - // Set id and type after id assigned. - newElOption.id = resultItem.keyInfo.id; - !newElOption.type && existElOption && (newElOption.type = existElOption.type); - - // Set parent id if not specified - if (newElOption.parentId == null) { - const newElParentOption = newElOption.parentOption; - if (newElParentOption) { - newElOption.parentId = newElParentOption.id; - } - else if (existElOption) { - newElOption.parentId = existElOption.parentId; - } - } - - // Clear - newElOption.parentOption = null; -} - -function mergeNewElOptionToExist( - existList: GraphicComponentElementOption[], - index: number, - newElOption: GraphicComponentElementOption -): void { - // Update existing options, for `getOption` feature. - const newElOptCopy = zrUtil.extend({}, newElOption); - const existElOption = existList[index]; - - const $action = newElOption.$action || 'merge'; - if ($action === 'merge') { - if (existElOption) { - - if (__DEV__) { - const newType = newElOption.type; - zrUtil.assert( - !newType || existElOption.type === newType, - 'Please set $action: "replace" to change `type`' - ); - } - - // We can ensure that newElOptCopy and existElOption are not - // the same object, so `merge` will not change newElOptCopy. - zrUtil.merge(existElOption, newElOptCopy, true); - // Rigid body, use ignoreSize. - layoutUtil.mergeLayoutParam(existElOption, newElOptCopy, {ignoreSize: true}); - // Will be used in render. - layoutUtil.copyLayoutParams(newElOption, existElOption); - } - else { - existList[index] = newElOptCopy; - } - } - else if ($action === 'replace') { - existList[index] = newElOptCopy; - } - else if ($action === 'remove') { - // null will be cleaned later. - existElOption && (existList[index] = null); - } -} - -function setLayoutInfoToExist( - existItem: GraphicComponentElementOption, - newElOption: GraphicComponentElementOption -) { - if (!existItem) { - return; - } - existItem.hv = newElOption.hv = [ - // Rigid body, dont care `width`. - isSetLoc(newElOption, ['left', 'right']), - // Rigid body, dont care `height`. - isSetLoc(newElOption, ['top', 'bottom']) - ]; - // Give default group size. Otherwise layout error may occur. - if (existItem.type === 'group') { - const existingGroupOpt = existItem as GraphicComponentGroupOption; - const newGroupOpt = newElOption as GraphicComponentGroupOption; - existingGroupOpt.width == null && (existingGroupOpt.width = newGroupOpt.width = 0); - existingGroupOpt.height == null && (existingGroupOpt.height = newGroupOpt.height = 0); - } -} - -function setEventData( - el: Element, - graphicModel: GraphicComponentModel, - elOption: GraphicComponentElementOption -): void { - let eventData = getECData(el).eventData; - // Simple optimize for large amount of elements that no need event. - if (!el.silent && !el.ignore && !eventData) { - eventData = getECData(el).eventData = { - componentType: 'graphic', - componentIndex: graphicModel.componentIndex, - name: el.name - }; - } - - // `elOption.info` enables user to mount some info on - // elements and use them in event handlers. - if (eventData) { - eventData.info = elOption.info; - } -} +use(install); \ No newline at end of file diff --git a/src/component/graphic/install.ts b/src/component/graphic/install.ts new file mode 100644 index 0000000000..b51bb67994 --- /dev/null +++ b/src/component/graphic/install.ts @@ -0,0 +1,785 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +import * as zrUtil from 'zrender/src/core/util'; + +import * as modelUtil from '../../util/model'; +import * as graphicUtil from '../../util/graphic'; +import * as layoutUtil from '../../util/layout'; +import {parsePercent} from '../../util/number'; +import { + ComponentOption, + BoxLayoutOptionMixin, + Dictionary, + ZRStyleProps, + OptionId, + OptionPreprocessor +} from '../../util/types'; +import ComponentModel from '../../model/Component'; +import Element, { ElementTextConfig } from 'zrender/src/Element'; +import Displayable from 'zrender/src/graphic/Displayable'; +import { PathProps } from 'zrender/src/graphic/Path'; +import { ImageStyleProps } from 'zrender/src/graphic/Image'; +import GlobalModel from '../../model/Global'; +import ComponentView from '../../view/Component'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import { getECData } from '../../util/innerStore'; +import { TextStyleProps } from 'zrender/src/graphic/Text'; +import { isEC4CompatibleStyle, convertFromEC4CompatibleStyle } from '../../util/styleCompat'; +import { EChartsExtensionInstallRegisters } from '../../extension'; + +const TRANSFORM_PROPS = { + x: 1, + y: 1, + scaleX: 1, + scaleY: 1, + originX: 1, + originY: 1, + rotation: 1 +} as const; +type TransformProp = keyof typeof TRANSFORM_PROPS; + +interface GraphicComponentBaseElementOption extends + Partial>, + /** + * left/right/top/bottom: (like 12, '22%', 'center', default undefined) + * If left/rigth is set, shape.x/shape.cx/position will not be used. + * If top/bottom is set, shape.y/shape.cy/position will not be used. + * This mechanism is useful when you want to position a group/element + * against the right side or the center of this container. + */ + Partial> { + + /** + * element type, mandatory. + * Only can be omit if call setOption not at the first time and perform merge. + */ + type?: string; + + id?: OptionId; + name?: string; + + // Only internal usage. Use specified value does NOT make sense. + parentId?: OptionId; + parentOption?: GraphicComponentElementOption; + children?: GraphicComponentElementOption[]; + hv?: [boolean, boolean]; + + /** + * bounding: (enum: 'all' (default) | 'raw') + * Specify how to calculate boundingRect when locating. + * 'all': Get uioned and transformed boundingRect + * from both itself and its descendants. + * This mode simplies confining a group of elements in the bounding + * of their ancester container (e.g., using 'right: 0'). + * 'raw': Only use the boundingRect of itself and before transformed. + * This mode is similar to css behavior, which is useful when you + * want an element to be able to overflow its container. (Consider + * a rotated circle needs to be located in a corner.) + */ + bounding?: 'raw' | 'all'; + + /** + * info: custom info. enables user to mount some info on elements and use them + * in event handlers. Update them only when user specified, otherwise, remain. + */ + info?: GraphicExtraElementInfo; + + textContent?: GraphicComponentTextOption; + textConfig?: ElementTextConfig; + + $action?: 'merge' | 'replace' | 'remove'; +}; +interface GraphicComponentDisplayableOption extends + GraphicComponentBaseElementOption, + Partial> { + + style?: ZRStyleProps; + + // TODO: states? + // emphasis?: GraphicComponentDisplayableOptionOnState; + // blur?: GraphicComponentDisplayableOptionOnState; + // select?: GraphicComponentDisplayableOptionOnState; +} +// TODO: states? +// interface GraphicComponentDisplayableOptionOnState extends Partial> { +// style?: ZRStyleProps; +// } +interface GraphicComponentGroupOption extends GraphicComponentBaseElementOption { + type?: 'group'; + + /** + * width/height: (can only be pixel value, default 0) + * Only be used to specify contianer(group) size, if needed. And + * can not be percentage value (like '33%'). See the reason in the + * layout algorithm below. + */ + width?: number; + height?: number; + + // TODO: Can only set focus, blur on the root element. + // children: Omit[]; + children: GraphicComponentElementOption[]; +} +export interface GraphicComponentZRPathOption extends GraphicComponentDisplayableOption { + shape?: PathProps['shape']; +} +export interface GraphicComponentImageOption extends GraphicComponentDisplayableOption { + type?: 'image'; + style?: ImageStyleProps; + // TODO: states? + // emphasis?: GraphicComponentImageOptionOnState; + // blur?: GraphicComponentImageOptionOnState; + // select?: GraphicComponentImageOptionOnState; +} +// TODO: states? +// interface GraphicComponentImageOptionOnState extends GraphicComponentDisplayableOptionOnState { +// style?: ImageStyleProps; +// } +interface GraphicComponentTextOption + extends Omit { + type?: 'text'; + style?: TextStyleProps; +} +type GraphicComponentElementOption = + GraphicComponentGroupOption + | GraphicComponentZRPathOption + | GraphicComponentImageOption + | GraphicComponentTextOption; +// type GraphicComponentElementOptionOnState = +// GraphicComponentDisplayableOptionOnState +// | GraphicComponentImageOptionOnState; + +type GraphicExtraElementInfo = Dictionary; + +type ElementMap = zrUtil.HashMap; + +const inner = modelUtil.makeInner<{ + __ecGraphicWidthOption: number; + __ecGraphicHeightOption: number; + __ecGraphicWidth: number; + __ecGraphicHeight: number; + __ecGraphicId: string; +}, Element>(); + + +const _nonShapeGraphicElements = { + + // Reserved but not supported in graphic component. + path: null as unknown, + compoundPath: null as unknown, + + // Supported in graphic component. + group: graphicUtil.Group, + image: graphicUtil.Image, + text: graphicUtil.Text +} as const; +type NonShapeGraphicElementType = keyof typeof _nonShapeGraphicElements; + +// ------------------------ +// Preprocessor +// ------------------------ + +const preprocessor: OptionPreprocessor = function (option) { + const graphicOption = option.graphic as GraphicComponentOption | GraphicComponentOption[]; + + // Convert + // {graphic: [{left: 10, type: 'circle'}, ...]} + // or + // {graphic: {left: 10, type: 'circle'}} + // to + // {graphic: [{elements: [{left: 10, type: 'circle'}, ...]}]} + if (zrUtil.isArray(graphicOption)) { + if (!graphicOption[0] || !graphicOption[0].elements) { + option.graphic = [{elements: graphicOption}]; + } + else { + // Only one graphic instance can be instantiated. (We dont + // want that too many views are created in echarts._viewMap) + option.graphic = [(option.graphic as any)[0]]; + } + } + else if (graphicOption && !graphicOption.elements) { + option.graphic = [{elements: [graphicOption]}]; + } +}; + +// ------------------------ +// Model +// ------------------------ + +export type GraphicComponentLooseOption = (GraphicComponentOption | GraphicComponentElementOption) & { + mainType?: 'graphic'; +}; + +export interface GraphicComponentOption extends ComponentOption { + // Note: elements is always behind its ancestors in this elements array. + elements?: GraphicComponentElementOption[]; + // parentId: string; +}; + + +class GraphicComponentModel extends ComponentModel { + + static type = 'graphic'; + type = GraphicComponentModel.type; + + static defaultOption: GraphicComponentOption = { + elements: [] + // parentId: null + }; + + /** + * Save el options for the sake of the performance (only update modified graphics). + * The order is the same as those in option. (ancesters -> descendants) + */ + private _elOptionsToUpdate: GraphicComponentElementOption[]; + + mergeOption(option: GraphicComponentOption, ecModel: GlobalModel): void { + // Prevent default merge to elements + const elements = this.option.elements; + this.option.elements = null; + + super.mergeOption(option, ecModel); + + this.option.elements = elements; + } + + optionUpdated(newOption: GraphicComponentOption, isInit: boolean): void { + const thisOption = this.option; + const newList = (isInit ? thisOption : newOption).elements; + const existList = thisOption.elements = isInit ? [] : thisOption.elements; + + const flattenedList = [] as GraphicComponentElementOption[]; + this._flatten(newList, flattenedList, null); + + const mappingResult = modelUtil.mappingToExists(existList, flattenedList, 'normalMerge'); + + // Clear elOptionsToUpdate + const elOptionsToUpdate = this._elOptionsToUpdate = [] as GraphicComponentElementOption[]; + + zrUtil.each(mappingResult, function (resultItem, index) { + const newElOption = resultItem.newOption as GraphicComponentElementOption; + + if (__DEV__) { + zrUtil.assert( + zrUtil.isObject(newElOption) || resultItem.existing, + 'Empty graphic option definition' + ); + } + + if (!newElOption) { + return; + } + + elOptionsToUpdate.push(newElOption); + + setKeyInfoToNewElOption(resultItem, newElOption); + + mergeNewElOptionToExist(existList, index, newElOption); + + setLayoutInfoToExist(existList[index], newElOption); + + }, this); + + // Clean + for (let i = existList.length - 1; i >= 0; i--) { + if (existList[i] == null) { + existList.splice(i, 1); + } + else { + // $action should be volatile, otherwise option gotten from + // `getOption` will contain unexpected $action. + delete existList[i].$action; + } + } + } + + /** + * Convert + * [{ + * type: 'group', + * id: 'xx', + * children: [{type: 'circle'}, {type: 'polygon'}] + * }] + * to + * [ + * {type: 'group', id: 'xx'}, + * {type: 'circle', parentId: 'xx'}, + * {type: 'polygon', parentId: 'xx'} + * ] + */ + private _flatten( + optionList: GraphicComponentElementOption[], + result: GraphicComponentElementOption[], + parentOption: GraphicComponentElementOption + ): void { + zrUtil.each(optionList, function (option) { + if (!option) { + return; + } + + if (parentOption) { + option.parentOption = parentOption; + } + + result.push(option); + + const children = option.children; + if (option.type === 'group' && children) { + this._flatten(children, result, option); + } + // Deleting for JSON output, and for not affecting group creation. + delete option.children; + }, this); + } + + // FIXME + // Pass to view using payload? setOption has a payload? + useElOptionsToUpdate(): GraphicComponentElementOption[] { + const els = this._elOptionsToUpdate; + // Clear to avoid render duplicately when zooming. + this._elOptionsToUpdate = null; + return els; + } +} + +// ------------------------ +// View +// ------------------------ + +class GraphicComponentView extends ComponentView { + + static type = 'graphic'; + type = GraphicComponentView.type; + + private _elMap: ElementMap; + private _lastGraphicModel: GraphicComponentModel; + + init() { + this._elMap = zrUtil.createHashMap(); + } + + render(graphicModel: GraphicComponentModel, ecModel: GlobalModel, api: ExtensionAPI): void { + + // Having leveraged between use cases and algorithm complexity, a very + // simple layout mechanism is used: + // The size(width/height) can be determined by itself or its parent (not + // implemented yet), but can not by its children. (Top-down travel) + // The location(x/y) can be determined by the bounding rect of itself + // (can including its descendants or not) and the size of its parent. + // (Bottom-up travel) + + // When `chart.clear()` or `chart.setOption({...}, true)` with the same id, + // view will be reused. + if (graphicModel !== this._lastGraphicModel) { + this._clear(); + } + this._lastGraphicModel = graphicModel; + + this._updateElements(graphicModel); + this._relocate(graphicModel, api); + } + + /** + * Update graphic elements. + */ + private _updateElements(graphicModel: GraphicComponentModel): void { + const elOptionsToUpdate = graphicModel.useElOptionsToUpdate(); + + if (!elOptionsToUpdate) { + return; + } + + const elMap = this._elMap; + const rootGroup = this.group; + + // Top-down tranverse to assign graphic settings to each elements. + zrUtil.each(elOptionsToUpdate, function (elOption) { + const id = modelUtil.convertOptionIdName(elOption.id, null); + const elExisting = id != null ? elMap.get(id) : null; + const parentId = modelUtil.convertOptionIdName(elOption.parentId, null); + const targetElParent = (parentId != null ? elMap.get(parentId) : rootGroup) as graphicUtil.Group; + + const elType = elOption.type; + const elOptionStyle = (elOption as GraphicComponentDisplayableOption).style; + if (elType === 'text' && elOptionStyle) { + // In top/bottom mode, textVerticalAlign should not be used, which cause + // inaccurately locating. + if (elOption.hv && elOption.hv[1]) { + (elOptionStyle as any).textVerticalAlign = + (elOptionStyle as any).textBaseline = + (elOptionStyle as TextStyleProps).verticalAlign = + (elOptionStyle as TextStyleProps).align = null; + } + } + + let textContentOption = (elOption as GraphicComponentZRPathOption).textContent; + let textConfig = (elOption as GraphicComponentZRPathOption).textConfig; + if (elOptionStyle + && isEC4CompatibleStyle(elOptionStyle, elType, !!textConfig, !!textContentOption) + ) { + const convertResult = convertFromEC4CompatibleStyle(elOptionStyle, elType, true) as GraphicComponentZRPathOption; + if (!textConfig && convertResult.textConfig) { + textConfig = (elOption as GraphicComponentZRPathOption).textConfig = convertResult.textConfig; + } + if (!textContentOption && convertResult.textContent) { + textContentOption = convertResult.textContent; + } + } + + // Remove unnecessary props to avoid potential problems. + const elOptionCleaned = getCleanedElOption(elOption); + + // For simple, do not support parent change, otherwise reorder is needed. + if (__DEV__) { + elExisting && zrUtil.assert( + targetElParent === elExisting.parent, + 'Changing parent is not supported.' + ); + } + + const $action = elOption.$action || 'merge'; + if ($action === 'merge') { + elExisting + ? elExisting.attr(elOptionCleaned) + : createEl(id, targetElParent, elOptionCleaned, elMap); + } + else if ($action === 'replace') { + removeEl(elExisting, elMap); + createEl(id, targetElParent, elOptionCleaned, elMap); + } + else if ($action === 'remove') { + removeEl(elExisting, elMap); + } + + const el = elMap.get(id); + + if (el && textContentOption) { + if ($action === 'merge') { + const textContentExisting = el.getTextContent(); + textContentExisting + ? textContentExisting.attr(textContentOption) + : el.setTextContent(new graphicUtil.Text(textContentOption)); + } + else if ($action === 'replace') { + el.setTextContent(new graphicUtil.Text(textContentOption)); + } + } + + if (el) { + const elInner = inner(el); + elInner.__ecGraphicWidthOption = (elOption as GraphicComponentGroupOption).width; + elInner.__ecGraphicHeightOption = (elOption as GraphicComponentGroupOption).height; + setEventData(el, graphicModel, elOption); + } + }); + } + + /** + * Locate graphic elements. + */ + private _relocate(graphicModel: GraphicComponentModel, api: ExtensionAPI): void { + const elOptions = graphicModel.option.elements; + const rootGroup = this.group; + const elMap = this._elMap; + const apiWidth = api.getWidth(); + const apiHeight = api.getHeight(); + + // Top-down to calculate percentage width/height of group + for (let i = 0; i < elOptions.length; i++) { + const elOption = elOptions[i]; + const id = modelUtil.convertOptionIdName(elOption.id, null); + const el = id != null ? elMap.get(id) : null; + + if (!el || !el.isGroup) { + continue; + } + const parentEl = el.parent; + const isParentRoot = parentEl === rootGroup; + // Like 'position:absolut' in css, default 0. + const elInner = inner(el); + const parentElInner = inner(parentEl); + elInner.__ecGraphicWidth = parsePercent( + elInner.__ecGraphicWidthOption, + isParentRoot ? apiWidth : parentElInner.__ecGraphicWidth + ) || 0; + elInner.__ecGraphicHeight = parsePercent( + elInner.__ecGraphicHeightOption, + isParentRoot ? apiHeight : parentElInner.__ecGraphicHeight + ) || 0; + } + + // Bottom-up tranvese all elements (consider ec resize) to locate elements. + for (let i = elOptions.length - 1; i >= 0; i--) { + const elOption = elOptions[i]; + const id = modelUtil.convertOptionIdName(elOption.id, null); + const el = id != null ? elMap.get(id) : null; + + if (!el) { + continue; + } + + const parentEl = el.parent; + const parentElInner = inner(parentEl); + const containerInfo = parentEl === rootGroup + ? { + width: apiWidth, + height: apiHeight + } + : { + width: parentElInner.__ecGraphicWidth, + height: parentElInner.__ecGraphicHeight + }; + + // PENDING + // Currently, when `bounding: 'all'`, the union bounding rect of the group + // does not include the rect of [0, 0, group.width, group.height], which + // is probably weird for users. Should we make a break change for it? + layoutUtil.positionElement( + el, elOption, containerInfo, null, + {hv: elOption.hv, boundingMode: elOption.bounding} + ); + } + } + + /** + * Clear all elements. + */ + private _clear(): void { + const elMap = this._elMap; + elMap.each(function (el) { + removeEl(el, elMap); + }); + this._elMap = zrUtil.createHashMap(); + } + + dispose(): void { + this._clear(); + } +} + +function createEl( + id: string, + targetElParent: graphicUtil.Group, + elOption: GraphicComponentElementOption, + elMap: ElementMap +): void { + const graphicType = elOption.type; + + if (__DEV__) { + zrUtil.assert(graphicType, 'graphic type MUST be set'); + } + + const Clz = ( + zrUtil.hasOwn(_nonShapeGraphicElements, graphicType) + // Those graphic elements are not shapes. They should not be + // overwritten by users, so do them first. + ? _nonShapeGraphicElements[graphicType as NonShapeGraphicElementType] + : graphicUtil.getShapeClass(graphicType) + ) as { new(opt: GraphicComponentElementOption): Element }; + + if (__DEV__) { + zrUtil.assert(Clz, 'graphic type can not be found'); + } + + const el = new Clz(elOption); + targetElParent.add(el); + elMap.set(id, el); + inner(el).__ecGraphicId = id; +} + +function removeEl(elExisting: Element, elMap: ElementMap): void { + const existElParent = elExisting && elExisting.parent; + if (existElParent) { + elExisting.type === 'group' && elExisting.traverse(function (el) { + removeEl(el, elMap); + }); + elMap.removeKey(inner(elExisting).__ecGraphicId); + existElParent.remove(elExisting); + } +} + +// Remove unnecessary props to avoid potential problems. +function getCleanedElOption( + elOption: GraphicComponentElementOption +): Omit { + elOption = zrUtil.extend({}, elOption); + zrUtil.each( + ['id', 'parentId', '$action', 'hv', 'bounding', 'textContent'].concat(layoutUtil.LOCATION_PARAMS), + function (name) { + delete (elOption as any)[name]; + } + ); + return elOption; +} + +function isSetLoc( + obj: GraphicComponentElementOption, + props: ('left' | 'right' | 'top' | 'bottom')[] +): boolean { + let isSet; + zrUtil.each(props, function (prop) { + obj[prop] != null && obj[prop] !== 'auto' && (isSet = true); + }); + return isSet; +} + +function setKeyInfoToNewElOption( + resultItem: ReturnType[number], + newElOption: GraphicComponentElementOption +): void { + const existElOption = resultItem.existing as GraphicComponentElementOption; + + // Set id and type after id assigned. + newElOption.id = resultItem.keyInfo.id; + !newElOption.type && existElOption && (newElOption.type = existElOption.type); + + // Set parent id if not specified + if (newElOption.parentId == null) { + const newElParentOption = newElOption.parentOption; + if (newElParentOption) { + newElOption.parentId = newElParentOption.id; + } + else if (existElOption) { + newElOption.parentId = existElOption.parentId; + } + } + + // Clear + newElOption.parentOption = null; +} + +function mergeNewElOptionToExist( + existList: GraphicComponentElementOption[], + index: number, + newElOption: GraphicComponentElementOption +): void { + // Update existing options, for `getOption` feature. + const newElOptCopy = zrUtil.extend({}, newElOption); + const existElOption = existList[index]; + + const $action = newElOption.$action || 'merge'; + if ($action === 'merge') { + if (existElOption) { + + if (__DEV__) { + const newType = newElOption.type; + zrUtil.assert( + !newType || existElOption.type === newType, + 'Please set $action: "replace" to change `type`' + ); + } + + // We can ensure that newElOptCopy and existElOption are not + // the same object, so `merge` will not change newElOptCopy. + zrUtil.merge(existElOption, newElOptCopy, true); + // Rigid body, use ignoreSize. + layoutUtil.mergeLayoutParam(existElOption, newElOptCopy, {ignoreSize: true}); + // Will be used in render. + layoutUtil.copyLayoutParams(newElOption, existElOption); + } + else { + existList[index] = newElOptCopy; + } + } + else if ($action === 'replace') { + existList[index] = newElOptCopy; + } + else if ($action === 'remove') { + // null will be cleaned later. + existElOption && (existList[index] = null); + } +} + +function setLayoutInfoToExist( + existItem: GraphicComponentElementOption, + newElOption: GraphicComponentElementOption +) { + if (!existItem) { + return; + } + existItem.hv = newElOption.hv = [ + // Rigid body, dont care `width`. + isSetLoc(newElOption, ['left', 'right']), + // Rigid body, dont care `height`. + isSetLoc(newElOption, ['top', 'bottom']) + ]; + // Give default group size. Otherwise layout error may occur. + if (existItem.type === 'group') { + const existingGroupOpt = existItem as GraphicComponentGroupOption; + const newGroupOpt = newElOption as GraphicComponentGroupOption; + existingGroupOpt.width == null && (existingGroupOpt.width = newGroupOpt.width = 0); + existingGroupOpt.height == null && (existingGroupOpt.height = newGroupOpt.height = 0); + } +} + +function setEventData( + el: Element, + graphicModel: GraphicComponentModel, + elOption: GraphicComponentElementOption +): void { + let eventData = getECData(el).eventData; + // Simple optimize for large amount of elements that no need event. + if (!el.silent && !el.ignore && !eventData) { + eventData = getECData(el).eventData = { + componentType: 'graphic', + componentIndex: graphicModel.componentIndex, + name: el.name + }; + } + + // `elOption.info` enables user to mount some info on + // elements and use them in event handlers. + if (eventData) { + eventData.info = elOption.info; + } +} + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(GraphicComponentModel); + registers.registerComponentView(GraphicComponentView); + registers.registerPreprocessor(preprocessor); +} \ No newline at end of file diff --git a/src/component/grid.ts b/src/component/grid.ts index 1c3a744562..0a98ed22fe 100644 --- a/src/component/grid.ts +++ b/src/component/grid.ts @@ -17,6 +17,8 @@ * under the License. */ -import './gridSimple'; -import './axisPointer/CartesianAxisPointer'; -import './axisPointer'; + +import { use } from '../extension'; +import { install } from './grid/install'; + +use(install); \ No newline at end of file diff --git a/src/component/grid/install.ts b/src/component/grid/install.ts new file mode 100644 index 0000000000..da3e8864ee --- /dev/null +++ b/src/component/grid/install.ts @@ -0,0 +1,27 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import {install as installSimple} from './installSimple'; +import {install as installAxisPointer} from '../axisPointer/install'; +import { EChartsExtensionInstallRegisters, use } from '../../extension'; + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installSimple); + use(installAxisPointer); +} \ No newline at end of file diff --git a/src/component/grid/installSimple.ts b/src/component/grid/installSimple.ts new file mode 100644 index 0000000000..e704c6dc31 --- /dev/null +++ b/src/component/grid/installSimple.ts @@ -0,0 +1,78 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import ComponentView from '../../view/Component'; +import GridModel from '../../coord/cartesian/GridModel'; +import GlobalModel from '../../model/Global'; +import { Rect } from '../../util/graphic'; +import { defaults } from 'zrender/src/core/util'; +import {CartesianAxisOption, CartesianAxisModel} from '../../coord/cartesian/AxisModel'; +import axisModelCreator from '../../coord/axisModelCreator'; +import Grid from '../../coord/cartesian/Grid'; +import {CartesianXAxisView, CartesianYAxisView} from '../axis/CartesianAxisView'; + +// Grid view +class GridView extends ComponentView { + static readonly type = 'grid'; + readonly type = 'grid'; + + render(gridModel: GridModel, ecModel: GlobalModel) { + this.group.removeAll(); + if (gridModel.get('show')) { + this.group.add(new Rect({ + shape: gridModel.coordinateSystem.getRect(), + style: defaults({ + fill: gridModel.get('backgroundColor') + }, gridModel.getItemStyle()), + silent: true, + z2: -1 + })); + } + } + +} +const extraOption: CartesianAxisOption = { + // gridIndex: 0, + // gridId: '', + offset: 0 +}; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentView(GridView); + registers.registerComponentModel(GridModel); + registers.registerCoordinateSystem('cartesian2d', Grid); + + axisModelCreator( + registers, 'x', CartesianAxisModel, extraOption + ); + axisModelCreator( + registers, 'y', CartesianAxisModel, extraOption + ); + + registers.registerComponentView(CartesianXAxisView); + registers.registerComponentView(CartesianYAxisView); + + registers.registerPreprocessor(function (option) { + // Only create grid when need + if (option.xAxis && option.yAxis && !option.grid) { + option.grid = {}; + } + }); +} \ No newline at end of file diff --git a/src/component/gridSimple.ts b/src/component/gridSimple.ts index f1b6c9c63a..718d831249 100644 --- a/src/component/gridSimple.ts +++ b/src/component/gridSimple.ts @@ -17,44 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; -import * as graphic from '../util/graphic'; +import { use } from '../extension'; +import { install } from './grid/installSimple'; -import './axis'; -import '../coord/cartesian/defaultAxisExtentFromData'; -import ComponentView from '../view/Component'; -import GlobalModel from '../model/Global'; -import GridModel from '../coord/cartesian/GridModel'; -import ComponentModel from '../model/Component'; - -// Grid view -class GridView extends ComponentView { - static readonly type = 'grid'; - readonly type = 'grid'; - - render(gridModel: GridModel, ecModel: GlobalModel) { - this.group.removeAll(); - if (gridModel.get('show')) { - this.group.add(new graphic.Rect({ - shape: gridModel.coordinateSystem.getRect(), - style: zrUtil.defaults({ - fill: gridModel.get('backgroundColor') - }, gridModel.getItemStyle()), - silent: true, - z2: -1 - })); - } - } - -} - -ComponentView.registerClass(GridView); -ComponentModel.registerClass(GridModel); - -echarts.registerPreprocessor(function (option) { - // Only create grid when need - if (option.xAxis && option.yAxis && !option.grid) { - option.grid = {}; - } -}); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/component/helper/BrushController.ts b/src/component/helper/BrushController.ts index 1b24befbe0..9320ece4ae 100644 --- a/src/component/helper/BrushController.ts +++ b/src/component/helper/BrushController.ts @@ -117,8 +117,6 @@ export interface BrushPanelConfig { // `true` represents global panel; type BrushPanelConfigOrGlobal = BrushPanelConfig | typeof BRUSH_PANEL_GLOBAL; -interface BrushPanelElement extends graphic.Group { -} interface BrushCover extends graphic.Group { __brushOption: BrushCoverConfig; diff --git a/src/component/helper/BrushTargetManager.ts b/src/component/helper/BrushTargetManager.ts index f141a793d4..f8c8c8e693 100644 --- a/src/component/helper/BrushTargetManager.ts +++ b/src/component/helper/BrushTargetManager.ts @@ -25,7 +25,7 @@ import { BrushPanelConfig, BrushControllerEvents, BrushType, BrushAreaRange, BrushDimensionMinMax } from './BrushController'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GridModel from '../../coord/cartesian/GridModel'; import GeoModel from '../../coord/geo/GeoModel'; import { CoordinateSystemMaster } from '../../coord/CoordinateSystem'; @@ -37,7 +37,8 @@ import SeriesModel from '../../model/Series'; import { Dictionary } from '../../util/types'; import { ModelFinderObject, ParsedModelFinder, ModelFinder, - parseFinder as modelUtilParseFinder + parseFinder as modelUtilParseFinder, + ParsedModelFinderKnown } from '../../util/model'; @@ -286,14 +287,14 @@ function formatMinMax(minMax: BrushDimensionMinMax): BrushDimensionMinMax { function parseFinder( ecModel: GlobalModel, finder: ModelFinder -): ParsedModelFinder { +): ParsedModelFinderKnown { return modelUtilParseFinder( ecModel, finder, {includeMainTypes: INCLUDE_FINDER_MAIN_TYPES} ); } type TargetInfoBuilder = ( - foundCpts: ParsedModelFinder, targetInfoList: BrushTargetInfo[] + foundCpts: ParsedModelFinderKnown, targetInfoList: BrushTargetInfo[] ) => void; const targetInfoBuilders: Record = { @@ -367,7 +368,7 @@ const targetInfoBuilders: Record = { }; type TargetInfoMatcher = ( - foundCpts: ParsedModelFinder, targetInfo: BrushTargetInfo + foundCpts: ParsedModelFinderKnown, targetInfo: BrushTargetInfo ) => boolean; const targetInfoMatchers: TargetInfoMatcher[] = [ diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts index ceb1e32dd5..617712ab23 100644 --- a/src/component/helper/MapDraw.ts +++ b/src/component/helper/MapDraw.ts @@ -25,7 +25,7 @@ import * as graphic from '../../util/graphic'; import { enableHoverEmphasis, DISPLAY_STATES } from '../../util/states'; import geoSourceManager from '../../coord/geo/geoSourceManager'; import {getUID} from '../../util/component'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption } from '../../coord/geo/GeoModel'; import MapSeries from '../../chart/map/MapSeries'; import GlobalModel from '../../model/Global'; diff --git a/src/component/helper/brushHelper.ts b/src/component/helper/brushHelper.ts index 5a75c7b843..2497301ac6 100644 --- a/src/component/helper/brushHelper.ts +++ b/src/component/helper/brushHelper.ts @@ -21,7 +21,7 @@ import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import {onIrrelevantElement} from './cursorHelper'; import * as graphicUtil from '../../util/graphic'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ElementEvent } from 'zrender/src/Element'; import ComponentModel from '../../model/Component'; diff --git a/src/component/helper/cursorHelper.ts b/src/component/helper/cursorHelper.ts index d43e3e3989..dd23501dbc 100644 --- a/src/component/helper/cursorHelper.ts +++ b/src/component/helper/cursorHelper.ts @@ -19,7 +19,7 @@ import { ElementEvent } from 'zrender/src/Element'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import SeriesModel from '../../model/Series'; import { CoordinateSystem } from '../../coord/CoordinateSystem'; diff --git a/src/component/helper/interactionMutex.ts b/src/component/helper/interactionMutex.ts index 686475fe86..996650da2a 100644 --- a/src/component/helper/interactionMutex.ts +++ b/src/component/helper/interactionMutex.ts @@ -18,8 +18,7 @@ */ // @ts-nocheck - -import * as echarts from '../../echarts'; +import * as echarts from '../../core/echarts'; const ATTR = '\0_ec_interaction_mutex'; @@ -52,6 +51,7 @@ function getStore(zr) { * If no userKey, release global cursor. * } */ +// TODO: SELF REGISTERED. echarts.registerAction( {type: 'takeGlobalCursor', event: 'globalCursorTaken', update: 'update'}, function () {} diff --git a/src/component/legend.ts b/src/component/legend.ts index b5d10c971e..f726d2e167 100644 --- a/src/component/legend.ts +++ b/src/component/legend.ts @@ -19,19 +19,7 @@ // Do not contain scrollable legend, for sake of file size. -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './legend/install'; -import './legend/LegendModel'; -import './legend/legendAction'; -import './legend/LegendView'; - -import legendFilter from './legend/legendFilter'; -import Component from '../model/Component'; - -// Series Filter -echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.SERIES_FILTER, legendFilter); - -Component.registerSubTypeDefaulter('legend', function () { - // Default 'plain' when no type specified. - return 'plain'; -}); +use(install); \ No newline at end of file diff --git a/src/component/legend/LegendModel.ts b/src/component/legend/LegendModel.ts index 4ee162bf45..14079d7f91 100644 --- a/src/component/legend/LegendModel.ts +++ b/src/component/legend/LegendModel.ts @@ -75,6 +75,9 @@ export interface LegendTooltipFormatterParams { $vars: ['name'] } export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin { + + mainType?: 'legend' + show?: boolean orient?: LayoutOrient @@ -438,6 +441,4 @@ class LegendModel extends ComponentMode }; } -ComponentModel.registerClass(LegendModel); - export default LegendModel; diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index 46ab81e5c7..cabb036734 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -27,7 +27,7 @@ import * as layoutUtil from '../../util/layout'; import ComponentView from '../../view/Component'; import LegendModel, { LegendOption, LegendSelectorButtonOption, LegendTooltipFormatterParams } from './LegendModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ZRTextAlign, ZRColor, @@ -635,7 +635,4 @@ function dispatchDownplayAction( } } - -ComponentView.registerClass(LegendView); - export default LegendView; \ No newline at end of file diff --git a/src/component/legend/ScrollableLegendModel.ts b/src/component/legend/ScrollableLegendModel.ts index 143b70bd53..7db50321cb 100644 --- a/src/component/legend/ScrollableLegendModel.ts +++ b/src/component/legend/ScrollableLegendModel.ts @@ -24,7 +24,6 @@ import { } from '../../util/layout'; import { ZRColor, LabelOption } from '../../util/types'; import Model from '../../model/Model'; -import ComponentModel from '../../model/Component'; import GlobalModel from '../../model/Global'; import { inheritDefaultOption } from '../../util/component'; @@ -121,6 +120,4 @@ function mergeAndNormalizeLayoutParams( }); } -ComponentModel.registerClass(ScrollableLegendModel); - export default ScrollableLegendModel; \ No newline at end of file diff --git a/src/component/legend/ScrollableLegendView.ts b/src/component/legend/ScrollableLegendView.ts index da616e05ff..da19a699d1 100644 --- a/src/component/legend/ScrollableLegendView.ts +++ b/src/component/legend/ScrollableLegendView.ts @@ -26,11 +26,10 @@ import * as graphic from '../../util/graphic'; import * as layoutUtil from '../../util/layout'; import LegendView from './LegendView'; import { LegendSelectorButtonOption } from './LegendModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GlobalModel from '../../model/Global'; import ScrollableLegendModel, {ScrollableLegendOption} from './ScrollableLegendModel'; import Displayable from 'zrender/src/graphic/Displayable'; -import ComponentView from '../../view/Component'; import Element from 'zrender/src/Element'; import { ZRRectLike } from '../../util/types'; @@ -550,6 +549,4 @@ class ScrollableLegendView extends LegendView { } } -ComponentView.registerClass(ScrollableLegendView); - export default ScrollableLegendView; diff --git a/src/component/legend/install.ts b/src/component/legend/install.ts new file mode 100644 index 0000000000..075cbf04f8 --- /dev/null +++ b/src/component/legend/install.ts @@ -0,0 +1,27 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import {install as installLegendPlain} from './installLegendPlain'; +import {install as installLegendScroll} from './installLegendScroll'; + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installLegendPlain); + use(installLegendScroll); +} \ No newline at end of file diff --git a/src/component/legend/installLegendPlain.ts b/src/component/legend/installLegendPlain.ts new file mode 100644 index 0000000000..bfaff168a6 --- /dev/null +++ b/src/component/legend/installLegendPlain.ts @@ -0,0 +1,36 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import LegendModel from './LegendModel'; +import LegendView from './LegendView'; +import legendFilter from './legendFilter'; +import { installLegendAction } from './legendAction'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(LegendModel); + registers.registerComponentView(LegendView); + + registers.registerProcessor(registers.PRIORITY.PROCESSOR.SERIES_FILTER, legendFilter); + registers.registerSubTypeDefaulter('legend', function () { + return 'plain'; + }); + + installLegendAction(registers); +} \ No newline at end of file diff --git a/src/component/legend/installLegendScroll.ts b/src/component/legend/installLegendScroll.ts new file mode 100644 index 0000000000..76b6096dec --- /dev/null +++ b/src/component/legend/installLegendScroll.ts @@ -0,0 +1,33 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import {install as installLegendPlain} from './installLegendPlain'; +import ScrollableLegendModel from './ScrollableLegendModel'; +import ScrollableLegendView from './ScrollableLegendView'; +import installScrollableLegendAction from './scrollableLegendAction'; + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installLegendPlain); + + registers.registerComponentModel(ScrollableLegendModel); + registers.registerComponentView(ScrollableLegendView); + + installScrollableLegendAction(registers); +} \ No newline at end of file diff --git a/src/component/legend/legendAction.ts b/src/component/legend/legendAction.ts index e703885db5..04ed3723a8 100644 --- a/src/component/legend/legendAction.ts +++ b/src/component/legend/legendAction.ts @@ -19,8 +19,7 @@ // @ts-nocheck -import * as echarts from '../../echarts'; -import * as zrUtil from 'zrender/src/core/util'; +import {curry, each} from 'zrender/src/core/util'; function legendSelectActionHandler(methodName, payload, ecModel) { const selectedMap = {}; @@ -43,7 +42,7 @@ function legendSelectActionHandler(methodName, payload, ecModel) { isSelected = legendModel.isSelected(payload.name); } const legendData = legendModel.getData(); - zrUtil.each(legendData, function (model) { + each(legendData, function (model) { const name = model.get('name'); // Wrap element if (name === '\n' || name === '') { @@ -69,46 +68,49 @@ function legendSelectActionHandler(methodName, payload, ecModel) { selected: selectedMap }; } -/** - * @event legendToggleSelect - * @type {Object} - * @property {string} type 'legendToggleSelect' - * @property {string} [from] - * @property {string} name Series name or data item name - */ -echarts.registerAction( - 'legendToggleSelect', 'legendselectchanged', - zrUtil.curry(legendSelectActionHandler, 'toggleSelected') -); -echarts.registerAction( - 'legendAllSelect', 'legendselectall', - zrUtil.curry(legendSelectActionHandler, 'allSelect') -); +export function installLegendAction(registers) { + /** + * @event legendToggleSelect + * @type {Object} + * @property {string} type 'legendToggleSelect' + * @property {string} [from] + * @property {string} name Series name or data item name + */ + registers.registerAction( + 'legendToggleSelect', 'legendselectchanged', + curry(legendSelectActionHandler, 'toggleSelected') + ); -echarts.registerAction( - 'legendInverseSelect', 'legendinverseselect', - zrUtil.curry(legendSelectActionHandler, 'inverseSelect') -); + registers.registerAction( + 'legendAllSelect', 'legendselectall', + curry(legendSelectActionHandler, 'allSelect') + ); -/** - * @event legendSelect - * @type {Object} - * @property {string} type 'legendSelect' - * @property {string} name Series name or data item name - */ -echarts.registerAction( - 'legendSelect', 'legendselected', - zrUtil.curry(legendSelectActionHandler, 'select') -); + registers.registerAction( + 'legendInverseSelect', 'legendinverseselect', + curry(legendSelectActionHandler, 'inverseSelect') + ); -/** - * @event legendUnSelect - * @type {Object} - * @property {string} type 'legendUnSelect' - * @property {string} name Series name or data item name - */ -echarts.registerAction( - 'legendUnSelect', 'legendunselected', - zrUtil.curry(legendSelectActionHandler, 'unSelect') -); + /** + * @event legendSelect + * @type {Object} + * @property {string} type 'legendSelect' + * @property {string} name Series name or data item name + */ + registers.registerAction( + 'legendSelect', 'legendselected', + curry(legendSelectActionHandler, 'select') + ); + + /** + * @event legendUnSelect + * @type {Object} + * @property {string} type 'legendUnSelect' + * @property {string} name Series name or data item name + */ + registers.registerAction( + 'legendUnSelect', 'legendunselected', + curry(legendSelectActionHandler, 'unSelect') + ); +} \ No newline at end of file diff --git a/src/component/legend/scrollableLegendAction.ts b/src/component/legend/scrollableLegendAction.ts index cad4a7d9a9..49c5468df8 100644 --- a/src/component/legend/scrollableLegendAction.ts +++ b/src/component/legend/scrollableLegendAction.ts @@ -17,25 +17,27 @@ * under the License. */ -import * as echarts from '../../echarts'; import ScrollableLegendModel from './ScrollableLegendModel'; +import { EChartsExtensionInstallRegisters } from '../../extension'; -/** - * @event legendScroll - * @type {Object} - * @property {string} type 'legendScroll' - * @property {string} scrollDataIndex - */ -echarts.registerAction( - 'legendScroll', 'legendscroll', - function (payload, ecModel) { - const scrollDataIndex = payload.scrollDataIndex; +export default function installScrollableLegendAction(registers: EChartsExtensionInstallRegisters) { + /** + * @event legendScroll + * @type {Object} + * @property {string} type 'legendScroll' + * @property {string} scrollDataIndex + */ + registers.registerAction( + 'legendScroll', 'legendscroll', + function (payload, ecModel) { + const scrollDataIndex = payload.scrollDataIndex; - scrollDataIndex != null && ecModel.eachComponent( - {mainType: 'legend', subType: 'scroll', query: payload}, - function (legendModel: ScrollableLegendModel) { - legendModel.setScrollDataIndex(scrollDataIndex); - } - ); - } -); \ No newline at end of file + scrollDataIndex != null && ecModel.eachComponent( + {mainType: 'legend', subType: 'scroll', query: payload}, + function (legendModel: ScrollableLegendModel) { + legendModel.setScrollDataIndex(scrollDataIndex); + } + ); + } + ); +} \ No newline at end of file diff --git a/src/component/legendPlain.ts b/src/component/legendPlain.ts new file mode 100644 index 0000000000..0a926a00d9 --- /dev/null +++ b/src/component/legendPlain.ts @@ -0,0 +1,25 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// Do not contain scrollable legend, for sake of file size. + +import { use } from '../extension'; +import { install } from './legend/installLegendPlain'; + +use(install); \ No newline at end of file diff --git a/src/component/legendScroll.ts b/src/component/legendScroll.ts index 8cdb181d61..8a2b010334 100644 --- a/src/component/legendScroll.ts +++ b/src/component/legendScroll.ts @@ -21,7 +21,7 @@ * Legend component entry file8 */ -import './legend'; -import './legend/ScrollableLegendModel'; -import './legend/ScrollableLegendView'; -import './legend/scrollableLegendAction'; +import { use } from '../extension'; +import { install } from './legend/installLegendScroll'; + +use(install); \ No newline at end of file diff --git a/src/component/markArea.ts b/src/component/markArea.ts index 3c3a0f98fa..2229d4303f 100644 --- a/src/component/markArea.ts +++ b/src/component/markArea.ts @@ -17,12 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './marker/installMarkArea'; -import './marker/MarkAreaModel'; -import './marker/MarkAreaView'; - -echarts.registerPreprocessor(function (opt) { - // Make sure markArea component is enabled - opt.markArea = opt.markArea || {}; -}); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/component/markLine.ts b/src/component/markLine.ts index 07baf11986..4ba56b648e 100644 --- a/src/component/markLine.ts +++ b/src/component/markLine.ts @@ -17,12 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './marker/installMarkLine'; -import './marker/MarkLineModel'; -import './marker/MarkLineView'; - -echarts.registerPreprocessor(function (opt) { - // Make sure markLine component is enabled - opt.markLine = opt.markLine || {}; -}); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/component/markPoint.ts b/src/component/markPoint.ts index 6454ba5e70..c9ba6aa1df 100644 --- a/src/component/markPoint.ts +++ b/src/component/markPoint.ts @@ -18,12 +18,8 @@ */ // HINT Markpoint can't be used too much -import * as echarts from '../echarts'; -import './marker/MarkPointModel'; -import './marker/MarkPointView'; +import { use } from '../extension'; +import { install } from './marker/installMarkPoint'; -echarts.registerPreprocessor(function (opt) { - // Make sure markPoint component is enabled - opt.markPoint = opt.markPoint || {}; -}); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/component/marker/MarkAreaModel.ts b/src/component/marker/MarkAreaModel.ts index 08f3721eb6..9136c66bda 100644 --- a/src/component/marker/MarkAreaModel.ts +++ b/src/component/marker/MarkAreaModel.ts @@ -19,7 +19,6 @@ import MarkerModel, { MarkerOption, MarkerStatisticType, MarkerPositionOption } from './MarkerModel'; import { SeriesLabelOption, ItemStyleOption, StatesOptionMixin } from '../../util/types'; -import ComponentModel from '../../model/Component'; import GlobalModel from '../../model/Global'; @@ -57,6 +56,7 @@ export type MarkArea2DDataItemOption = [ ]; export interface MarkAreaOption extends MarkerOption, MarkAreaStateOption, StatesOptionMixin { + mainType?: 'markArea' precision?: number @@ -105,6 +105,4 @@ class MarkAreaModel extends MarkerModel { }; } -ComponentModel.registerClass(MarkAreaModel); - export default MarkAreaModel; \ No newline at end of file diff --git a/src/component/marker/MarkAreaView.ts b/src/component/marker/MarkAreaView.ts index 2b12284586..631390aea3 100644 --- a/src/component/marker/MarkAreaView.ts +++ b/src/component/marker/MarkAreaView.ts @@ -33,14 +33,14 @@ import MarkAreaModel, { MarkArea2DDataItemOption } from './MarkAreaModel'; import SeriesModel from '../../model/Series'; import Cartesian2D from '../../coord/cartesian/Cartesian2D'; import DataDimensionInfo from '../../data/DataDimensionInfo'; -import ComponentView from '../../view/Component'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import MarkerModel from './MarkerModel'; import { makeInner } from '../../util/model'; import { getVisualFromData } from '../../visual/helper'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; import { getECData } from '../../util/innerStore'; +import Axis2D from '../../coord/cartesian/Axis2D'; interface MarkAreaDrawGroup { group: graphic.Group @@ -168,8 +168,9 @@ function getSingleMarkerEndPoint( point = coordSys.dataToPoint(pt, true); } if (isCoordinateSystemType(coordSys, 'cartesian2d')) { - const xAxis = coordSys.getAxis('x'); - const yAxis = coordSys.getAxis('y'); + // TODO: TYPE ts@4.1 may still infer it as Axis instead of Axis2D. Not sure if it's a bug + const xAxis = coordSys.getAxis('x') as Axis2D; + const yAxis = coordSys.getAxis('y') as Axis2D; const x = data.get(dims[0], idx) as number; const y = data.get(dims[1], idx) as number; if (isInifinity(x)) { @@ -203,7 +204,7 @@ class MarkAreaView extends MarkerView { updateTransform(markAreaModel: MarkAreaModel, ecModel: GlobalModel, api: ExtensionAPI) { ecModel.eachSeries(function (seriesModel) { - const maModel = MarkerModel.getMarkerModelFromSeries(seriesModel, 'markArea'); + const maModel = MarkerModel.getMarkerModelFromSeries(seriesModel, 'markArea') as MarkAreaModel; if (maModel) { const areaData = maModel.getData(); areaData.each(function (idx) { @@ -418,4 +419,4 @@ function createList( return areaData; } -ComponentView.registerClass(MarkAreaView); \ No newline at end of file +export default MarkAreaView; \ No newline at end of file diff --git a/src/component/marker/MarkLineModel.ts b/src/component/marker/MarkLineModel.ts index bd81c5d505..760664b174 100644 --- a/src/component/marker/MarkLineModel.ts +++ b/src/component/marker/MarkLineModel.ts @@ -18,15 +18,13 @@ */ import MarkerModel, { MarkerOption, MarkerStatisticType, MarkerPositionOption } from './MarkerModel'; -import ComponentModel from '../../model/Component'; import GlobalModel from '../../model/Global'; import { LineStyleOption, SeriesLineLabelOption, SymbolOptionMixin, ItemStyleOption, - StatesOptionMixin, - CallbackDataParams + StatesOptionMixin } from '../../util/types'; interface MarkLineStateOption { @@ -80,6 +78,7 @@ export type MarkLine2DDataItemOption = [ export interface MarkLineOption extends MarkerOption, MarkLineStateOption, StatesOptionMixin { + mainType?: 'markLine' symbol?: string[] | string symbolSize?: number[] | number @@ -138,6 +137,4 @@ class MarkLineModel extends MarkerModel { }; } -ComponentModel.registerClass(MarkLineModel); - export default MarkLineModel; \ No newline at end of file diff --git a/src/component/marker/MarkLineView.ts b/src/component/marker/MarkLineView.ts index 56e6b6547a..f0f713efa3 100644 --- a/src/component/marker/MarkLineView.ts +++ b/src/component/marker/MarkLineView.ts @@ -28,7 +28,7 @@ import MarkLineModel, { MarkLine2DDataItemOption, MarkLineOption } from './MarkL import { ScaleDataValue, ColorString } from '../../util/types'; import SeriesModel from '../../model/Series'; import { getECData } from '../../util/innerStore'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import Cartesian2D from '../../coord/cartesian/Cartesian2D'; import GlobalModel from '../../model/Global'; import MarkerModel from './MarkerModel'; @@ -45,10 +45,10 @@ import { filter, HashMap } from 'zrender/src/core/util'; -import ComponentView from '../../view/Component'; import { makeInner } from '../../util/model'; import { LineDataVisual } from '../../visual/commonVisualTypes'; import { getVisualFromData } from '../../visual/helper'; +import Axis2D from '../../coord/cartesian/Axis2D'; // Item option for configuring line and each end of symbol. // Line option. be merged from configuration of two ends. @@ -234,8 +234,9 @@ function updateSingleMarkerEndLayout( // }] // } if (isCoordinateSystemType(coordSys, 'cartesian2d')) { - const xAxis = coordSys.getAxis('x'); - const yAxis = coordSys.getAxis('y'); + // TODO: TYPE ts@4.1 may still infer it as Axis instead of Axis2D. Not sure if it's a bug + const xAxis = coordSys.getAxis('x') as Axis2D; + const yAxis = coordSys.getAxis('y') as Axis2D; const dims = coordSys.dimensions; if (isInifinity(data.get(dims[0], idx))) { point[0] = xAxis.toGlobalCoord(xAxis.getExtent()[isFrom ? 0 : 1]); @@ -461,4 +462,4 @@ function createList(coordSys: CoordinateSystem, seriesModel: SeriesModel, mlMode }; } -ComponentView.registerClass(MarkLineView); +export default MarkLineView; \ No newline at end of file diff --git a/src/component/marker/MarkPointModel.ts b/src/component/marker/MarkPointModel.ts index f67254666e..db5f1663eb 100644 --- a/src/component/marker/MarkPointModel.ts +++ b/src/component/marker/MarkPointModel.ts @@ -18,7 +18,6 @@ */ import MarkerModel, { MarkerOption, MarkerPositionOption } from './MarkerModel'; -import ComponentModel from '../../model/Component'; import GlobalModel from '../../model/Global'; import { SymbolOptionMixin, @@ -48,6 +47,7 @@ export interface MarkPointDataItemOption extends export interface MarkPointOption extends MarkerOption, SymbolOptionMixin, StatesOptionMixin, MarkPointStateOption { + mainType?: 'markPoint' precision?: number @@ -92,6 +92,4 @@ class MarkPointModel extends MarkerModel { }; } -ComponentModel.registerClass(MarkPointModel); - export default MarkPointModel; \ No newline at end of file diff --git a/src/component/marker/MarkPointView.ts b/src/component/marker/MarkPointView.ts index db5bc146ab..82f1d69864 100644 --- a/src/component/marker/MarkPointView.ts +++ b/src/component/marker/MarkPointView.ts @@ -23,13 +23,12 @@ import * as numberUtil from '../../util/number'; import List from '../../data/List'; import * as markerHelper from './markerHelper'; import MarkerView from './MarkerView'; -import ComponentView from '../../view/Component'; import { CoordinateSystem } from '../../coord/CoordinateSystem'; import SeriesModel from '../../model/Series'; import MarkPointModel, {MarkPointDataItemOption} from './MarkPointModel'; import GlobalModel from '../../model/Global'; import MarkerModel from './MarkerModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { HashMap, isFunction, map, defaults, filter, curry } from 'zrender/src/core/util'; import { getECData } from '../../util/innerStore'; import { getVisualFromData } from '../../visual/helper'; @@ -210,4 +209,4 @@ function createList( return mpData; } -ComponentView.registerClass(MarkPointView); \ No newline at end of file +export default MarkPointView; \ No newline at end of file diff --git a/src/component/marker/MarkerView.ts b/src/component/marker/MarkerView.ts index 56b8a04539..cfe62cbb68 100644 --- a/src/component/marker/MarkerView.ts +++ b/src/component/marker/MarkerView.ts @@ -21,7 +21,7 @@ import ComponentView from '../../view/Component'; import { HashMap, createHashMap, each } from 'zrender/src/core/util'; import MarkerModel from './MarkerModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { makeInner } from '../../util/model'; import SeriesModel from '../../model/Series'; import Group from 'zrender/src/graphic/Group'; diff --git a/src/component/marker/installMarkArea.ts b/src/component/marker/installMarkArea.ts new file mode 100644 index 0000000000..a34b07d064 --- /dev/null +++ b/src/component/marker/installMarkArea.ts @@ -0,0 +1,32 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import MarkAreaModel from './MarkAreaModel'; +import MarkAreaView from './MarkAreaView'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(MarkAreaModel); + registers.registerComponentView(MarkAreaView); + + registers.registerPreprocessor(function (opt) { + // Make sure markArea component is enabled + opt.markArea = opt.markArea || {}; + }); +} \ No newline at end of file diff --git a/src/component/marker/installMarkLine.ts b/src/component/marker/installMarkLine.ts new file mode 100644 index 0000000000..2946b7117f --- /dev/null +++ b/src/component/marker/installMarkLine.ts @@ -0,0 +1,31 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +import { EChartsExtensionInstallRegisters } from '../../extension'; +import MarkLineModel from './MarkLineModel'; +import MarkLineView from './MarkLineView'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(MarkLineModel); + registers.registerComponentView(MarkLineView); + + registers.registerPreprocessor(function (opt) { + // Make sure markLine component is enabled + opt.markLine = opt.markLine || {}; + }); +} \ No newline at end of file diff --git a/src/component/marker/installMarkPoint.ts b/src/component/marker/installMarkPoint.ts new file mode 100644 index 0000000000..7526ddd643 --- /dev/null +++ b/src/component/marker/installMarkPoint.ts @@ -0,0 +1,31 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +import { EChartsExtensionInstallRegisters } from '../../extension'; +import MarkPointModel from './MarkPointModel'; +import MarkPointView from './MarkPointView'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(MarkPointModel); + registers.registerComponentView(MarkPointView); + + registers.registerPreprocessor(function (opt) { + // Make sure markPoint component is enabled + opt.markPoint = opt.markPoint || {}; + }); +} \ No newline at end of file diff --git a/src/component/parallel.ts b/src/component/parallel.ts index aaa010ffcb..5aa86a521a 100644 --- a/src/component/parallel.ts +++ b/src/component/parallel.ts @@ -18,160 +18,7 @@ */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; -import * as throttleUtil from '../util/throttle'; -import parallelPreprocessor from '../coord/parallel/parallelPreprocessor'; -import '../coord/parallel/parallelCreator'; -import './parallelAxis'; -import GlobalModel from '../model/Global'; +import { use } from '../extension'; +import { install } from './parallel/install'; -// NOTE: DONT Remove this import, or GeoModel will be treeshaked. -import '../coord/parallel/ParallelModel'; -/* eslint-disable-next-line */ -import ParallelModel, { ParallelCoordinateSystemOption } from '../coord/parallel/ParallelModel'; -import ExtensionAPI from '../ExtensionAPI'; -import ComponentView from '../view/Component'; -import { ElementEventName } from 'zrender/src/core/types'; -import { ElementEvent } from 'zrender/src/Element'; -import { ParallelAxisExpandPayload } from './axis/parallelAxisAction'; - - -const CLICK_THRESHOLD = 5; // > 4 - - -class ParallelView extends ComponentView { - - static type = 'parallel'; - readonly type = ParallelView.type; - - // @internal - _model: ParallelModel; - private _api: ExtensionAPI; - - // @internal - _mouseDownPoint: number[]; - - private _handlers: Partial>; - - - render(parallelModel: ParallelModel, ecModel: GlobalModel, api: ExtensionAPI): void { - this._model = parallelModel; - this._api = api; - - if (!this._handlers) { - this._handlers = {}; - zrUtil.each(handlers, function (handler: ElementEventHandler, eventName) { - api.getZr().on( - eventName, - this._handlers[eventName] = zrUtil.bind(handler, this) as ElementEventHandler - ); - }, this); - } - - throttleUtil.createOrUpdate( - this, - '_throttledDispatchExpand', - parallelModel.get('axisExpandRate'), - 'fixRate' - ); - } - - dispose(ecModel: GlobalModel, api: ExtensionAPI): void { - zrUtil.each(this._handlers, function (handler: ElementEventHandler, eventName) { - api.getZr().off(eventName, handler); - }); - this._handlers = null; - } - - /** - * @internal - * @param {Object} [opt] If null, cancle the last action triggering for debounce. - */ - _throttledDispatchExpand(this: ParallelView, opt: Omit): void { - this._dispatchExpand(opt); - } - - /** - * @internal - */ - _dispatchExpand(opt: Omit) { - opt && this._api.dispatchAction( - zrUtil.extend({type: 'parallelAxisExpand'}, opt) - ); - } - -} - -ComponentView.registerClass(ParallelView); - -type ElementEventHandler = (this: ParallelView, e: ElementEvent) => void; -const handlers: Partial> = { - - mousedown: function (e) { - if (checkTrigger(this, 'click')) { - this._mouseDownPoint = [e.offsetX, e.offsetY]; - } - }, - - mouseup: function (e) { - const mouseDownPoint = this._mouseDownPoint; - - if (checkTrigger(this, 'click') && mouseDownPoint) { - const point = [e.offsetX, e.offsetY]; - const dist = Math.pow(mouseDownPoint[0] - point[0], 2) - + Math.pow(mouseDownPoint[1] - point[1], 2); - - if (dist > CLICK_THRESHOLD) { - return; - } - - const result = this._model.coordinateSystem.getSlidedAxisExpandWindow( - [e.offsetX, e.offsetY] - ); - - result.behavior !== 'none' && this._dispatchExpand({ - axisExpandWindow: result.axisExpandWindow - }); - } - - this._mouseDownPoint = null; - }, - - mousemove: function (e) { - // Should do nothing when brushing. - if (this._mouseDownPoint || !checkTrigger(this, 'mousemove')) { - return; - } - const model = this._model; - const result = model.coordinateSystem.getSlidedAxisExpandWindow( - [e.offsetX, e.offsetY] - ); - - const behavior = result.behavior; - behavior === 'jump' && ( - this._throttledDispatchExpand as ParallelView['_throttledDispatchExpand'] & throttleUtil.ThrottleController - ).debounceNextCall(model.get('axisExpandDebounce')); - this._throttledDispatchExpand( - behavior === 'none' - ? null // Cancle the last trigger, in case that mouse slide out of the area quickly. - : { - axisExpandWindow: result.axisExpandWindow, - // Jumping uses animation, and sliding suppresses animation. - animation: behavior === 'jump' ? null : { - duration: 0 // Disable animation. - } - } - ); - } -}; - -function checkTrigger( - view: ParallelView, - triggerOn: ParallelCoordinateSystemOption['axisExpandTriggerOn'] -): boolean { - const model = view._model; - return model.get('axisExpandable') && model.get('axisExpandTriggerOn') === triggerOn; -} - -echarts.registerPreprocessor(parallelPreprocessor); +use(install); \ No newline at end of file diff --git a/src/component/parallel/ParallelView.ts b/src/component/parallel/ParallelView.ts new file mode 100644 index 0000000000..88f669343f --- /dev/null +++ b/src/component/parallel/ParallelView.ts @@ -0,0 +1,122 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import GlobalModel from '../../model/Global'; +import ParallelModel, { ParallelCoordinateSystemOption } from '../../coord/parallel/ParallelModel'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import ComponentView from '../../view/Component'; +import { ElementEventName } from 'zrender/src/core/types'; +import { ElementEvent } from 'zrender/src/Element'; +import { ParallelAxisExpandPayload } from '../axis/parallelAxisAction'; +import { each, bind, extend } from 'zrender/src/core/util'; +import { ThrottleController, createOrUpdate } from '../../util/throttle'; + +const CLICK_THRESHOLD = 5; // > 4 + +class ParallelView extends ComponentView { + static type = 'parallel'; + readonly type = ParallelView.type; + // @internal + _model: ParallelModel; + private _api: ExtensionAPI; + // @internal + _mouseDownPoint: number[]; + private _handlers: Partial>; + render(parallelModel: ParallelModel, ecModel: GlobalModel, api: ExtensionAPI): void { + this._model = parallelModel; + this._api = api; + if (!this._handlers) { + this._handlers = {}; + each(handlers, function (handler: ElementEventHandler, eventName) { + api.getZr().on(eventName, this._handlers[eventName] = bind(handler, this) as ElementEventHandler); + }, this); + } + createOrUpdate(this, '_throttledDispatchExpand', parallelModel.get('axisExpandRate'), 'fixRate'); + } + dispose(ecModel: GlobalModel, api: ExtensionAPI): void { + each(this._handlers, function (handler: ElementEventHandler, eventName) { + api.getZr().off(eventName, handler); + }); + this._handlers = null; + } + /** + * @internal + * @param {Object} [opt] If null, cancle the last action triggering for debounce. + */ + _throttledDispatchExpand(this: ParallelView, opt: Omit): void { + this._dispatchExpand(opt); + } + /** + * @internal + */ + _dispatchExpand(opt: Omit) { + opt && this._api.dispatchAction(extend({ type: 'parallelAxisExpand' }, opt)); + } +} +type ElementEventHandler = (this: ParallelView, e: ElementEvent) => void; +const handlers: Partial> = { + mousedown: function (e) { + if (checkTrigger(this, 'click')) { + this._mouseDownPoint = [e.offsetX, e.offsetY]; + } + }, + mouseup: function (e) { + const mouseDownPoint = this._mouseDownPoint; + if (checkTrigger(this, 'click') && mouseDownPoint) { + const point = [e.offsetX, e.offsetY]; + const dist = Math.pow(mouseDownPoint[0] - point[0], 2) + + Math.pow(mouseDownPoint[1] - point[1], 2); + if (dist > CLICK_THRESHOLD) { + return; + } + const result = this._model.coordinateSystem.getSlidedAxisExpandWindow([e.offsetX, e.offsetY]); + result.behavior !== 'none' && this._dispatchExpand({ + axisExpandWindow: result.axisExpandWindow + }); + } + this._mouseDownPoint = null; + }, + mousemove: function (e) { + // Should do nothing when brushing. + if (this._mouseDownPoint || !checkTrigger(this, 'mousemove')) { + return; + } + const model = this._model; + const result = model.coordinateSystem.getSlidedAxisExpandWindow([e.offsetX, e.offsetY]); + const behavior = result.behavior; + behavior === 'jump' + && (this._throttledDispatchExpand as ParallelView['_throttledDispatchExpand'] & ThrottleController) + .debounceNextCall(model.get('axisExpandDebounce')); + this._throttledDispatchExpand(behavior === 'none' + ? null // Cancle the last trigger, in case that mouse slide out of the area quickly. + : { + axisExpandWindow: result.axisExpandWindow, + // Jumping uses animation, and sliding suppresses animation. + animation: behavior === 'jump' ? null : { + duration: 0 // Disable animation. + } + }); + } +}; +function checkTrigger(view: ParallelView, triggerOn: ParallelCoordinateSystemOption['axisExpandTriggerOn']): boolean { + const model = view._model; + return model.get('axisExpandable') && model.get('axisExpandTriggerOn') === triggerOn; +} + +export default ParallelView; \ No newline at end of file diff --git a/src/component/parallel/install.ts b/src/component/parallel/install.ts new file mode 100644 index 0000000000..3f3577bffe --- /dev/null +++ b/src/component/parallel/install.ts @@ -0,0 +1,59 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import parallelPreprocessor from '../../coord/parallel/parallelPreprocessor'; + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import ParallelView from './ParallelView'; +import ParallelModel from '../../coord/parallel/ParallelModel'; +import parallelCoordSysCreator from '../../coord/parallel/parallelCreator'; +import axisModelCreator from '../../coord/axisModelCreator'; +import ParallelAxisModel, {ParallelAxisOption} from '../../coord/parallel/AxisModel'; +import ParallelAxisView from '../axis/ParallelAxisView'; +import { installParallelActions } from '../axis/parallelAxisAction'; + +const defaultAxisOption: ParallelAxisOption = { + type: 'value', + areaSelectStyle: { + width: 20, + borderWidth: 1, + borderColor: 'rgba(160,197,232)', + color: 'rgba(160,197,232)', + opacity: 0.3 + }, + realtime: true, + z: 10 +}; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentView(ParallelView); + registers.registerComponentModel(ParallelModel); + + registers.registerCoordinateSystem('parallel', parallelCoordSysCreator); + registers.registerPreprocessor(parallelPreprocessor); + + registers.registerComponentModel(ParallelAxisModel); + registers.registerComponentView(ParallelAxisView); + + axisModelCreator( + registers, 'parallel', ParallelAxisModel, defaultAxisOption + ); + + installParallelActions(registers); +} \ No newline at end of file diff --git a/src/component/polar.ts b/src/component/polar.ts index 16743ed9b4..fb3bc0a8f2 100644 --- a/src/component/polar.ts +++ b/src/component/polar.ts @@ -17,23 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import * as zrUtil from 'zrender/src/core/util'; -import barPolar from '../layout/barPolar'; +import { use } from '../extension'; +import { install } from './polar/install'; -// Import self registered models. -import '../coord/polar/PolarModel'; -import '../coord/polar/AxisModel'; -import '../coord/polar/polarCreator'; -import './angleAxis'; -import './radiusAxis'; -import './axisPointer'; -import './axisPointer/PolarAxisPointer'; - -// For reducing size of echarts.min, barLayoutPolar is required by polar. -echarts.registerLayout(zrUtil.curry(barPolar, 'bar')); - -// Polar view -echarts.extendComponentView({ - type: 'polar' -}); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/component/polar/install.ts b/src/component/polar/install.ts new file mode 100644 index 0000000000..fbd61fa45f --- /dev/null +++ b/src/component/polar/install.ts @@ -0,0 +1,80 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import AxisView from '../axis/AxisView'; +import PolarAxisPointer from '../axisPointer/PolarAxisPointer'; +import {install as installAxisPointer} from '../axisPointer/install'; +import PolarModel from '../../coord/polar/PolarModel'; +import axisModelCreator from '../../coord/axisModelCreator'; +import { + AngleAxisOption, + RadiusAxisOption, + AngleAxisModel, + RadiusAxisModel +} from '../../coord/polar/AxisModel'; +import polarCreator from '../../coord/polar/polarCreator'; +import AngleAxisView from '../axis/AngleAxisView'; +import RadiusAxisView from '../axis/RadiusAxisView'; +import ComponentView from '../../view/Component'; +import { curry } from 'zrender/src/core/util'; +import barLayoutPolar from '../../layout/barPolar'; + + +const angleAxisExtraOption: AngleAxisOption = { + startAngle: 90, + + clockwise: true, + + splitNumber: 12, + + axisLabel: { + rotate: 0 + } +}; + +const radiusAxisExtraOption: RadiusAxisOption = { + splitNumber: 5 +}; + +class PolarView extends ComponentView { + static type = 'polar'; + type = PolarView.type; +} + +export function install(registers: EChartsExtensionInstallRegisters) { + + use(installAxisPointer); + + AxisView.registerAxisPointerClass('PolarAxisPointer', PolarAxisPointer); + + registers.registerCoordinateSystem('polar', polarCreator); + + registers.registerComponentModel(PolarModel); + registers.registerComponentView(PolarView); + + // Model and view for angleAxis and radiusAxis + axisModelCreator(registers, 'angle', AngleAxisModel, angleAxisExtraOption); + axisModelCreator(registers, 'radius', RadiusAxisModel, radiusAxisExtraOption); + + registers.registerComponentView(AngleAxisView); + registers.registerComponentView(RadiusAxisView); + + registers.registerLayout(curry(barLayoutPolar, 'bar')); +} \ No newline at end of file diff --git a/src/component/radar.ts b/src/component/radar.ts index 4e17b12c9c..3abdb6d0d1 100644 --- a/src/component/radar.ts +++ b/src/component/radar.ts @@ -17,6 +17,8 @@ * under the License. */ -import '../coord/radar/Radar'; -import '../coord/radar/RadarModel'; -import './radar/RadarView'; \ No newline at end of file + +import { use } from '../extension'; +import { install } from './radar/install'; + +use(install); \ No newline at end of file diff --git a/src/component/radar/RadarView.ts b/src/component/radar/RadarView.ts index 9544750e8f..430ec277a6 100644 --- a/src/component/radar/RadarView.ts +++ b/src/component/radar/RadarView.ts @@ -23,7 +23,7 @@ import * as graphic from '../../util/graphic'; import ComponentView from '../../view/Component'; import RadarModel from '../../coord/radar/RadarModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ZRColor } from '../../util/types'; const axisBuilderAttrs = [ @@ -203,4 +203,4 @@ class RadarView extends ComponentView { } } -ComponentView.registerClass(RadarView); \ No newline at end of file +export default RadarView; \ No newline at end of file diff --git a/src/component/radar/install.ts b/src/component/radar/install.ts new file mode 100644 index 0000000000..eb6babf8a6 --- /dev/null +++ b/src/component/radar/install.ts @@ -0,0 +1,29 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import RadarModel from '../../coord/radar/RadarModel'; +import RadarView from './RadarView'; +import Radar from '../../coord/radar/Radar'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerCoordinateSystem('radar', Radar); + registers.registerComponentModel(RadarModel); + registers.registerComponentView(RadarView); +} \ No newline at end of file diff --git a/src/component/singleAxis.ts b/src/component/singleAxis.ts index 0723b33c6d..c5e2b4aeb9 100644 --- a/src/component/singleAxis.ts +++ b/src/component/singleAxis.ts @@ -17,14 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; +import { use } from '../extension'; +import { install } from './singleAxis/install'; -import '../coord/single/singleCreator'; -import './axis/SingleAxisView'; -import '../coord/single/AxisModel'; -import './axisPointer'; -import './axisPointer/SingleAxisPointer'; - -echarts.extendComponentView({ - type: 'single' -}); +use(install); \ No newline at end of file diff --git a/src/component/singleAxis/install.ts b/src/component/singleAxis/install.ts new file mode 100644 index 0000000000..b2e6b8dc88 --- /dev/null +++ b/src/component/singleAxis/install.ts @@ -0,0 +1,49 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import ComponentView from '../../view/Component'; +import SingleAxisView from '../axis/SingleAxisView'; +import axisModelCreator from '../../coord/axisModelCreator'; +import SingleAxisModel from '../../coord/single/AxisModel'; +import singleCreator from '../../coord/single/singleCreator'; +import {install as installAxisPointer} from '../axisPointer/install'; +import AxisView from '../axis/AxisView'; +import SingleAxisPointer from '../axisPointer/SingleAxisPointer'; + +class SingleView extends ComponentView { + static type = 'single'; + type = SingleView.type; +} + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installAxisPointer); + + AxisView.registerAxisPointerClass('SingleAxisPointer', SingleAxisPointer); + + registers.registerComponentView(SingleView); + + // Axis + registers.registerComponentView(SingleAxisView); + registers.registerComponentModel(SingleAxisModel); + + axisModelCreator(registers, 'single', SingleAxisModel, SingleAxisModel.defaultOption); + + registers.registerCoordinateSystem('single', singleCreator); +} \ No newline at end of file diff --git a/src/component/timeline.ts b/src/component/timeline.ts index 995f2abfcc..d42d780593 100644 --- a/src/component/timeline.ts +++ b/src/component/timeline.ts @@ -21,12 +21,7 @@ * DataZoom component entry */ -import * as echarts from '../echarts'; -import preprocessor from './timeline/preprocessor'; +import { use } from '../extension'; +import { install } from './timeline/install'; -import './timeline/typeDefaulter'; -import './timeline/timelineAction'; -import './timeline/SliderTimelineModel'; -import './timeline/SliderTimelineView'; - -echarts.registerPreprocessor(preprocessor); +use(install); \ No newline at end of file diff --git a/src/component/timeline/SliderTimelineModel.ts b/src/component/timeline/SliderTimelineModel.ts index a5cc4e43ef..55871bd3f3 100644 --- a/src/component/timeline/SliderTimelineModel.ts +++ b/src/component/timeline/SliderTimelineModel.ts @@ -19,7 +19,6 @@ import TimelineModel, { TimelineOption } from './TimelineModel'; import { DataFormatMixin } from '../../model/mixin/dataFormat'; -import ComponentModel from '../../model/Component'; import { mixin } from 'zrender/src/core/util'; import List from '../../data/List'; import { inheritDefaultOption } from '../../util/component'; @@ -153,6 +152,4 @@ interface SliderTimelineModel extends DataFormatMixin { mixin(SliderTimelineModel, DataFormatMixin.prototype); -ComponentModel.registerClass(SliderTimelineModel); - export default SliderTimelineModel; \ No newline at end of file diff --git a/src/component/timeline/SliderTimelineView.ts b/src/component/timeline/SliderTimelineView.ts index 70f786ee0b..4d9e8c867d 100644 --- a/src/component/timeline/SliderTimelineView.ts +++ b/src/component/timeline/SliderTimelineView.ts @@ -27,7 +27,7 @@ import TimelineAxis from './TimelineAxis'; import {createSymbol} from '../../util/symbol'; import * as numberUtil from '../../util/number'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { merge, each, extend, isString, bind, defaults, retrieve2 } from 'zrender/src/core/util'; import SliderTimelineModel from './SliderTimelineModel'; import ComponentView from '../../view/Component'; @@ -895,7 +895,4 @@ function pointerMoveTo( } } - -ComponentView.registerClass(SliderTimelineView); - export default SliderTimelineView; \ No newline at end of file diff --git a/src/component/timeline/TimelineModel.ts b/src/component/timeline/TimelineModel.ts index 3a33389679..619f468f3a 100644 --- a/src/component/timeline/TimelineModel.ts +++ b/src/component/timeline/TimelineModel.ts @@ -102,6 +102,7 @@ export interface TimelineDataItemOption extends SymbolOptionMixin { } export interface TimelineOption extends ComponentOption, BoxLayoutOptionMixin, SymbolOptionMixin { + mainType?: 'timeline' backgroundColor?: ZRColor borderColor?: ColorString @@ -334,6 +335,4 @@ class TimelineModel extends ComponentModel { } -ComponentModel.registerClass(TimelineModel); - export default TimelineModel; \ No newline at end of file diff --git a/src/component/timeline/install.ts b/src/component/timeline/install.ts new file mode 100644 index 0000000000..1cf9c92acd --- /dev/null +++ b/src/component/timeline/install.ts @@ -0,0 +1,37 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +import { EChartsExtensionInstallRegisters } from '../../extension'; +import SliderTimelineModel from './SliderTimelineModel'; +import SliderTimelineView from './SliderTimelineView'; +import { installTimelineAction } from './timelineAction'; +import preprocessor from './preprocessor'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(SliderTimelineModel); + registers.registerComponentView(SliderTimelineView); + + registers.registerSubTypeDefaulter('timeline', function () { + // Only slider now. + return 'slider'; + }); + + installTimelineAction(registers); + + registers.registerPreprocessor(preprocessor); +} \ No newline at end of file diff --git a/src/component/timeline/timelineAction.ts b/src/component/timeline/timelineAction.ts index 6972844cd2..c4ef6869d7 100644 --- a/src/component/timeline/timelineAction.ts +++ b/src/component/timeline/timelineAction.ts @@ -17,11 +17,11 @@ * under the License. */ -import * as echarts from '../../echarts'; import GlobalModel from '../../model/Global'; import TimelineModel from './TimelineModel'; import { defaults } from 'zrender/src/core/util'; import { Payload } from '../../util/types'; +import { EChartsExtensionInstallRegisters } from '../../extension'; export interface TimelineChangePayload extends Payload { type: 'timelineChange' @@ -32,38 +32,41 @@ export interface TimelinePlayChangePayload extends Payload { playState: boolean } -echarts.registerAction( - {type: 'timelineChange', event: 'timelineChanged', update: 'prepareAndUpdate'}, +export function installTimelineAction(registers: EChartsExtensionInstallRegisters) { + registers.registerAction( - function (payload: TimelineChangePayload, ecModel: GlobalModel) { + {type: 'timelineChange', event: 'timelineChanged', update: 'prepareAndUpdate'}, - const timelineModel = ecModel.getComponent('timeline') as TimelineModel; - if (timelineModel && payload.currentIndex != null) { - timelineModel.setCurrentIndex(payload.currentIndex); + function (payload: TimelineChangePayload, ecModel: GlobalModel) { - if (!timelineModel.get('loop', true) && timelineModel.isIndexMax()) { - timelineModel.setPlayState(false); + const timelineModel = ecModel.getComponent('timeline') as TimelineModel; + if (timelineModel && payload.currentIndex != null) { + timelineModel.setCurrentIndex(payload.currentIndex); + + if (!timelineModel.get('loop', true) && timelineModel.isIndexMax()) { + timelineModel.setPlayState(false); + } } - } - // Set normalized currentIndex to payload. - ecModel.resetOption('timeline', { replaceMerge: timelineModel.get('replaceMerge', true) }); + // Set normalized currentIndex to payload. + ecModel.resetOption('timeline', { replaceMerge: timelineModel.get('replaceMerge', true) }); - return defaults({ - currentIndex: timelineModel.option.currentIndex - }, payload); - } -); + return defaults({ + currentIndex: timelineModel.option.currentIndex + }, payload); + } + ); -echarts.registerAction( + registers.registerAction( - {type: 'timelinePlayChange', event: 'timelinePlayChanged', update: 'update'}, + {type: 'timelinePlayChange', event: 'timelinePlayChanged', update: 'update'}, - function (payload: TimelinePlayChangePayload, ecModel: GlobalModel) { - const timelineModel = ecModel.getComponent('timeline') as TimelineModel; - if (timelineModel && payload.playState != null) { - timelineModel.setPlayState(payload.playState); + function (payload: TimelinePlayChangePayload, ecModel: GlobalModel) { + const timelineModel = ecModel.getComponent('timeline') as TimelineModel; + if (timelineModel && payload.playState != null) { + timelineModel.setPlayState(payload.playState); + } } - } -); + ); +} \ No newline at end of file diff --git a/src/component/title.ts b/src/component/title.ts index 376a823b2f..b0e36188dc 100644 --- a/src/component/title.ts +++ b/src/component/title.ts @@ -17,266 +17,7 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; -import * as graphic from '../util/graphic'; -import {getECData} from '../util/innerStore'; -import {createTextStyle} from '../label/labelStyle'; -import {getLayoutRect} from '../util/layout'; -import ComponentModel from '../model/Component'; -import { - ComponentOption, - BoxLayoutOptionMixin, - ZRTextAlign, - ZRTextVerticalAlign, - ZRColor, - BorderOptionMixin, - LabelOption -} from '../util/types'; -import ComponentView from '../view/Component'; -import GlobalModel from '../model/Global'; -import ExtensionAPI from '../ExtensionAPI'; -import {windowOpen} from '../util/format'; +import { use } from '../extension'; +import { install } from './title/install'; - -export interface TitleOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin { - show?: boolean - - type?: 'title' - - text?: string - /** - * Link to url - */ - link?: string - target?: 'self' | 'blank' - - subtext?: string - sublink?: string - subtarget?: 'self' | 'blank' - - textAlign?: ZRTextAlign - textVerticalAlign?: ZRTextVerticalAlign - - /** - * @deprecated Use textVerticalAlign instead - */ - textBaseline?: ZRTextVerticalAlign - - backgroundColor?: ZRColor - /** - * Padding between text and border. - * Support to be a single number or an array. - */ - padding?: number | number[] - /** - * Gap between text and subtext - */ - itemGap?: number - - textStyle?: LabelOption - - subtextStyle?: LabelOption - - /** - * If trigger mouse or touch event - */ - triggerEvent?: boolean - - /** - * Radius of background border. - */ - borderRadius?: number | number[] -} -class TitleModel extends ComponentModel { - static type = 'title' as const; - type = TitleModel.type; - - readonly layoutMode = {type: 'box', ignoreSize: true} as const; - - static defaultOption: TitleOption = { - zlevel: 0, - z: 6, - show: true, - - text: '', - target: 'blank', - subtext: '', - - subtarget: 'blank', - - left: 0, - top: 0, - - backgroundColor: 'rgba(0,0,0,0)', - - borderColor: '#ccc', - - borderWidth: 0, - - padding: 5, - - itemGap: 10, - textStyle: { - fontSize: 18, - fontWeight: 'bold', - color: '#464646' - }, - subtextStyle: { - fontSize: 12, - color: '#6E7079' - } - }; -} -ComponentModel.registerClass(TitleModel); - - -// View -class TitleView extends ComponentView { - - static type = 'title' as const; - type = TitleView.type; - - - render(titleModel: TitleModel, ecModel: GlobalModel, api: ExtensionAPI) { - this.group.removeAll(); - - if (!titleModel.get('show')) { - return; - } - - const group = this.group; - - const textStyleModel = titleModel.getModel('textStyle'); - const subtextStyleModel = titleModel.getModel('subtextStyle'); - - let textAlign = titleModel.get('textAlign'); - let textVerticalAlign = zrUtil.retrieve2( - titleModel.get('textBaseline'), titleModel.get('textVerticalAlign') - ); - - const textEl = new graphic.Text({ - style: createTextStyle(textStyleModel, { - text: titleModel.get('text'), - fill: textStyleModel.getTextColor() - }, {disableBox: true}), - z2: 10 - }); - - const textRect = textEl.getBoundingRect(); - - const subText = titleModel.get('subtext'); - const subTextEl = new graphic.Text({ - style: createTextStyle(subtextStyleModel, { - text: subText, - fill: subtextStyleModel.getTextColor(), - y: textRect.height + titleModel.get('itemGap'), - verticalAlign: 'top' - }, {disableBox: true}), - z2: 10 - }); - - const link = titleModel.get('link'); - const sublink = titleModel.get('sublink'); - const triggerEvent = titleModel.get('triggerEvent', true); - - textEl.silent = !link && !triggerEvent; - subTextEl.silent = !sublink && !triggerEvent; - - if (link) { - textEl.on('click', function () { - windowOpen(link, '_' + titleModel.get('target')); - }); - } - if (sublink) { - subTextEl.on('click', function () { - windowOpen(sublink, '_' + titleModel.get('subtarget')); - }); - } - - getECData(textEl).eventData = getECData(subTextEl).eventData = triggerEvent - ? { - componentType: 'title', - componentIndex: titleModel.componentIndex - } - : null; - - group.add(textEl); - subText && group.add(subTextEl); - // If no subText, but add subTextEl, there will be an empty line. - - let groupRect = group.getBoundingRect(); - const layoutOption = titleModel.getBoxLayoutParams(); - layoutOption.width = groupRect.width; - layoutOption.height = groupRect.height; - const layoutRect = getLayoutRect( - layoutOption, { - width: api.getWidth(), - height: api.getHeight() - }, titleModel.get('padding') - ); - // Adjust text align based on position - if (!textAlign) { - // Align left if title is on the left. center and right is same - textAlign = (titleModel.get('left') || titleModel.get('right')) as ZRTextAlign; - // @ts-ignore - if (textAlign === 'middle') { - textAlign = 'center'; - } - // Adjust layout by text align - if (textAlign === 'right') { - layoutRect.x += layoutRect.width; - } - else if (textAlign === 'center') { - layoutRect.x += layoutRect.width / 2; - } - } - if (!textVerticalAlign) { - textVerticalAlign = (titleModel.get('top') || titleModel.get('bottom')) as ZRTextVerticalAlign; - // @ts-ignore - if (textVerticalAlign === 'center') { - textVerticalAlign = 'middle'; - } - if (textVerticalAlign === 'bottom') { - layoutRect.y += layoutRect.height; - } - else if (textVerticalAlign === 'middle') { - layoutRect.y += layoutRect.height / 2; - } - - textVerticalAlign = textVerticalAlign || 'top'; - } - - group.x = layoutRect.x; - group.y = layoutRect.y; - group.markRedraw(); - const alignStyle = { - align: textAlign, - verticalAlign: textVerticalAlign - }; - textEl.setStyle(alignStyle); - subTextEl.setStyle(alignStyle); - - // Render background - // Get groupRect again because textAlign has been changed - groupRect = group.getBoundingRect(); - const padding = layoutRect.margin; - const style = titleModel.getItemStyle(['color', 'opacity']); - style.fill = titleModel.get('backgroundColor'); - const rect = new graphic.Rect({ - shape: { - x: groupRect.x - padding[3], - y: groupRect.y - padding[0], - width: groupRect.width + padding[1] + padding[3], - height: groupRect.height + padding[0] + padding[2], - r: titleModel.get('borderRadius') - }, - style: style, - subPixelOptimize: true, - silent: true - }); - - group.add(rect); - } -} - -ComponentView.registerClass(TitleView); +use(install); \ No newline at end of file diff --git a/src/component/title/install.ts b/src/component/title/install.ts new file mode 100644 index 0000000000..c7e0678a02 --- /dev/null +++ b/src/component/title/install.ts @@ -0,0 +1,287 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import * as zrUtil from 'zrender/src/core/util'; +import * as graphic from '../../util/graphic'; +import {getECData} from '../../util/innerStore'; +import {createTextStyle} from '../../label/labelStyle'; +import {getLayoutRect} from '../../util/layout'; +import ComponentModel from '../../model/Component'; +import { + ComponentOption, + BoxLayoutOptionMixin, + ZRTextAlign, + ZRTextVerticalAlign, + ZRColor, + BorderOptionMixin, + LabelOption +} from '../../util/types'; +import ComponentView from '../../view/Component'; +import GlobalModel from '../../model/Global'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import {windowOpen} from '../../util/format'; +import { EChartsExtensionInstallRegisters } from '../../extension'; + + +export interface TitleOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin { + + mainType?: 'title' + + show?: boolean + + text?: string + /** + * Link to url + */ + link?: string + target?: 'self' | 'blank' + + subtext?: string + sublink?: string + subtarget?: 'self' | 'blank' + + textAlign?: ZRTextAlign + textVerticalAlign?: ZRTextVerticalAlign + + /** + * @deprecated Use textVerticalAlign instead + */ + textBaseline?: ZRTextVerticalAlign + + backgroundColor?: ZRColor + /** + * Padding between text and border. + * Support to be a single number or an array. + */ + padding?: number | number[] + /** + * Gap between text and subtext + */ + itemGap?: number + + textStyle?: LabelOption + + subtextStyle?: LabelOption + + /** + * If trigger mouse or touch event + */ + triggerEvent?: boolean + + /** + * Radius of background border. + */ + borderRadius?: number | number[] +} +class TitleModel extends ComponentModel { + static type = 'title' as const; + type = TitleModel.type; + + readonly layoutMode = {type: 'box', ignoreSize: true} as const; + + static defaultOption: TitleOption = { + zlevel: 0, + z: 6, + show: true, + + text: '', + target: 'blank', + subtext: '', + + subtarget: 'blank', + + left: 0, + top: 0, + + backgroundColor: 'rgba(0,0,0,0)', + + borderColor: '#ccc', + + borderWidth: 0, + + padding: 5, + + itemGap: 10, + textStyle: { + fontSize: 18, + fontWeight: 'bold', + color: '#464646' + }, + subtextStyle: { + fontSize: 12, + color: '#6E7079' + } + }; +} + + +// View +class TitleView extends ComponentView { + + static type = 'title' as const; + type = TitleView.type; + + + render(titleModel: TitleModel, ecModel: GlobalModel, api: ExtensionAPI) { + this.group.removeAll(); + + if (!titleModel.get('show')) { + return; + } + + const group = this.group; + + const textStyleModel = titleModel.getModel('textStyle'); + const subtextStyleModel = titleModel.getModel('subtextStyle'); + + let textAlign = titleModel.get('textAlign'); + let textVerticalAlign = zrUtil.retrieve2( + titleModel.get('textBaseline'), titleModel.get('textVerticalAlign') + ); + + const textEl = new graphic.Text({ + style: createTextStyle(textStyleModel, { + text: titleModel.get('text'), + fill: textStyleModel.getTextColor() + }, {disableBox: true}), + z2: 10 + }); + + const textRect = textEl.getBoundingRect(); + + const subText = titleModel.get('subtext'); + const subTextEl = new graphic.Text({ + style: createTextStyle(subtextStyleModel, { + text: subText, + fill: subtextStyleModel.getTextColor(), + y: textRect.height + titleModel.get('itemGap'), + verticalAlign: 'top' + }, {disableBox: true}), + z2: 10 + }); + + const link = titleModel.get('link'); + const sublink = titleModel.get('sublink'); + const triggerEvent = titleModel.get('triggerEvent', true); + + textEl.silent = !link && !triggerEvent; + subTextEl.silent = !sublink && !triggerEvent; + + if (link) { + textEl.on('click', function () { + windowOpen(link, '_' + titleModel.get('target')); + }); + } + if (sublink) { + subTextEl.on('click', function () { + windowOpen(sublink, '_' + titleModel.get('subtarget')); + }); + } + + getECData(textEl).eventData = getECData(subTextEl).eventData = triggerEvent + ? { + componentType: 'title', + componentIndex: titleModel.componentIndex + } + : null; + + group.add(textEl); + subText && group.add(subTextEl); + // If no subText, but add subTextEl, there will be an empty line. + + let groupRect = group.getBoundingRect(); + const layoutOption = titleModel.getBoxLayoutParams(); + layoutOption.width = groupRect.width; + layoutOption.height = groupRect.height; + const layoutRect = getLayoutRect( + layoutOption, { + width: api.getWidth(), + height: api.getHeight() + }, titleModel.get('padding') + ); + // Adjust text align based on position + if (!textAlign) { + // Align left if title is on the left. center and right is same + textAlign = (titleModel.get('left') || titleModel.get('right')) as ZRTextAlign; + // @ts-ignore + if (textAlign === 'middle') { + textAlign = 'center'; + } + // Adjust layout by text align + if (textAlign === 'right') { + layoutRect.x += layoutRect.width; + } + else if (textAlign === 'center') { + layoutRect.x += layoutRect.width / 2; + } + } + if (!textVerticalAlign) { + textVerticalAlign = (titleModel.get('top') || titleModel.get('bottom')) as ZRTextVerticalAlign; + // @ts-ignore + if (textVerticalAlign === 'center') { + textVerticalAlign = 'middle'; + } + if (textVerticalAlign === 'bottom') { + layoutRect.y += layoutRect.height; + } + else if (textVerticalAlign === 'middle') { + layoutRect.y += layoutRect.height / 2; + } + + textVerticalAlign = textVerticalAlign || 'top'; + } + + group.x = layoutRect.x; + group.y = layoutRect.y; + group.markRedraw(); + const alignStyle = { + align: textAlign, + verticalAlign: textVerticalAlign + }; + textEl.setStyle(alignStyle); + subTextEl.setStyle(alignStyle); + + // Render background + // Get groupRect again because textAlign has been changed + groupRect = group.getBoundingRect(); + const padding = layoutRect.margin; + const style = titleModel.getItemStyle(['color', 'opacity']); + style.fill = titleModel.get('backgroundColor'); + const rect = new graphic.Rect({ + shape: { + x: groupRect.x - padding[3], + y: groupRect.y - padding[0], + width: groupRect.width + padding[1] + padding[3], + height: groupRect.height + padding[0] + padding[2], + r: titleModel.get('borderRadius') + }, + style: style, + subPixelOptimize: true, + silent: true + }); + + group.add(rect); + } +} + + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(TitleModel); + registers.registerComponentView(TitleView); +} \ No newline at end of file diff --git a/src/component/toolbox.ts b/src/component/toolbox.ts index b583db5890..fddd547ddb 100644 --- a/src/component/toolbox.ts +++ b/src/component/toolbox.ts @@ -17,10 +17,7 @@ * under the License. */ -import './toolbox/ToolboxModel'; -import './toolbox/ToolboxView'; -import './toolbox/feature/SaveAsImage'; -import './toolbox/feature/MagicType'; -import './toolbox/feature/DataView'; -import './toolbox/feature/DataZoom'; -import './toolbox/feature/Restore'; \ No newline at end of file +import { use } from '../extension'; +import { install } from './toolbox/install'; + +use(install); \ No newline at end of file diff --git a/src/component/toolbox/ToolboxModel.ts b/src/component/toolbox/ToolboxModel.ts index 1d6a1daa9d..c99cd18b48 100644 --- a/src/component/toolbox/ToolboxModel.ts +++ b/src/component/toolbox/ToolboxModel.ts @@ -42,6 +42,8 @@ export interface ToolboxTooltipFormatterParams { export interface ToolboxOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin { + mainType?: 'toolbox' + show?: boolean orient?: LayoutOrient @@ -71,7 +73,7 @@ export interface ToolboxOption extends ComponentOption, /** * Write all supported features in the final export option. */ - feature?: Dictionary + feature?: Partial> } class ToolboxModel extends ComponentModel { @@ -151,6 +153,4 @@ class ToolboxModel extends ComponentModel { }; } -ComponentModel.registerClass(ToolboxModel); - export default ToolboxModel; diff --git a/src/component/toolbox/ToolboxView.ts b/src/component/toolbox/ToolboxView.ts index 40f20606ab..8dd7881d73 100644 --- a/src/component/toolbox/ToolboxView.ts +++ b/src/component/toolbox/ToolboxView.ts @@ -27,7 +27,7 @@ import * as listComponentHelper from '../helper/listComponent'; import ComponentView from '../../view/Component'; import ToolboxModel from './ToolboxModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { DisplayState, Dictionary, ECElement, Payload } from '../../util/types'; import { ToolboxFeature, @@ -358,9 +358,8 @@ class ToolboxView extends ComponentView { } } -ComponentView.registerClass(ToolboxView); - function isUserFeatureName(featureName: string): boolean { return featureName.indexOf('my') === 0; } +export default ToolboxView; \ No newline at end of file diff --git a/src/component/toolbox/feature/Brush.ts b/src/component/toolbox/feature/Brush.ts index c935d69f4f..c8c3197050 100644 --- a/src/component/toolbox/feature/Brush.ts +++ b/src/component/toolbox/feature/Brush.ts @@ -21,11 +21,10 @@ import * as zrUtil from 'zrender/src/core/util'; import { ToolboxFeatureModel, ToolboxFeatureOption, - registerFeature, ToolboxFeature } from '../featureManager'; import GlobalModel from '../../../model/Global'; -import ExtensionAPI from '../../../ExtensionAPI'; +import ExtensionAPI from '../../../core/ExtensionAPI'; import BrushModel from '../../brush/BrushModel'; import { BrushTypeUncertain } from '../../helper/BrushController'; @@ -151,6 +150,4 @@ class BrushFeature extends ToolboxFeature { } } -registerFeature('brush', BrushFeature); - export default BrushFeature; diff --git a/src/component/toolbox/feature/DataView.ts b/src/component/toolbox/feature/DataView.ts index 591e9504fe..15c17420e3 100644 --- a/src/component/toolbox/feature/DataView.ts +++ b/src/component/toolbox/feature/DataView.ts @@ -17,13 +17,13 @@ * under the License. */ -import * as echarts from '../../../echarts'; +import * as echarts from '../../../core/echarts'; import * as zrUtil from 'zrender/src/core/util'; import GlobalModel from '../../../model/Global'; import SeriesModel from '../../../model/Series'; -import { ToolboxFeature, registerFeature, ToolboxFeatureOption } from '../featureManager'; +import { ToolboxFeature, ToolboxFeatureOption } from '../featureManager'; import { ColorString, ECUnitOption, SeriesOption, Payload, Dictionary } from '../../../util/types'; -import ExtensionAPI from '../../../ExtensionAPI'; +import ExtensionAPI from '../../../core/ExtensionAPI'; import { addEventListener } from 'zrender/src/core/event'; import Axis from '../../../coord/Axis'; import Cartesian2D from '../../../coord/cartesian/Cartesian2D'; @@ -487,8 +487,8 @@ function tryMergeDataOption(newData: DataList, originalData: DataList) { }); } -registerFeature('dataView', DataView); +// TODO: SELF REGISTERED. echarts.registerAction({ type: 'changeDataView', event: 'dataViewChanged', diff --git a/src/component/toolbox/feature/DataZoom.ts b/src/component/toolbox/feature/DataZoom.ts index f98927b213..0d8603c55c 100644 --- a/src/component/toolbox/feature/DataZoom.ts +++ b/src/component/toolbox/feature/DataZoom.ts @@ -24,16 +24,13 @@ import BrushController, { BrushControllerEvents, BrushDimensionMinMax } from '.. import BrushTargetManager, { BrushTargetInfoCartesian2D } from '../../helper/BrushTargetManager'; import * as history from '../../dataZoom/history'; import sliderMove from '../../helper/sliderMove'; -// Use dataZoomSelect -import '../../dataZoomSelect'; import { ToolboxFeature, ToolboxFeatureModel, - ToolboxFeatureOption, - registerFeature + ToolboxFeatureOption } from '../featureManager'; import GlobalModel from '../../../model/Global'; -import ExtensionAPI from '../../../ExtensionAPI'; +import ExtensionAPI from '../../../core/ExtensionAPI'; import { Payload, Dictionary, ComponentOption, ItemStyleOption } from '../../../util/types'; import Cartesian2D from '../../../coord/cartesian/Cartesian2D'; import CartesianAxisModel from '../../../coord/cartesian/AxisModel'; @@ -43,7 +40,7 @@ import { } from '../../dataZoom/helper'; import { ModelFinderObject, ModelFinderIndexQuery, makeInternalComponentId, - ModelFinderIdQuery, parseFinder + ModelFinderIdQuery, parseFinder, ParsedModelFinderKnown } from '../../../util/model'; import ToolboxModel from '../ToolboxModel'; import { registerInternalOptionCreator } from '../../../model/internalComponentCreator'; @@ -320,8 +317,6 @@ function updateZoomBtnStatus( ); } -registerFeature('dataZoom', DataZoomFeature); - registerInternalOptionCreator('dataZoom', function (ecModel: GlobalModel): ComponentOption[] { const toolboxModel = ecModel.getComponent('toolbox', 0) as ToolboxModel; if (!toolboxModel) { @@ -331,7 +326,7 @@ registerInternalOptionCreator('dataZoom', function (ecModel: GlobalModel): Compo const dzOptions = [] as ComponentOption[]; const finder = makeAxisFinder(dzFeatureModel); - const finderResult = parseFinder(ecModel, finder); + const finderResult = parseFinder(ecModel, finder) as ParsedModelFinderKnown; each(finderResult.xAxisModels, axisModel => buildInternalOptions(axisModel, 'xAxis', 'xAxisIndex')); each(finderResult.yAxisModels, axisModel => buildInternalOptions(axisModel, 'yAxis', 'yAxisIndex')); diff --git a/src/component/toolbox/feature/MagicType.ts b/src/component/toolbox/feature/MagicType.ts index cc2bd600f2..f51cc5cab4 100644 --- a/src/component/toolbox/feature/MagicType.ts +++ b/src/component/toolbox/feature/MagicType.ts @@ -17,12 +17,12 @@ * under the License. */ -import * as echarts from '../../../echarts'; +import * as echarts from '../../../core/echarts'; import * as zrUtil from 'zrender/src/core/util'; -import {ToolboxFeature, ToolboxFeatureOption, ToolboxFeatureModel, registerFeature} from '../featureManager'; +import {ToolboxFeature, ToolboxFeatureOption, ToolboxFeatureModel} from '../featureManager'; import { SeriesOption, ECUnitOption } from '../../../util/types'; import GlobalModel from '../../../model/Global'; -import ExtensionAPI from '../../../ExtensionAPI'; +import ExtensionAPI from '../../../core/ExtensionAPI'; import SeriesModel from '../../../model/Series'; import { SINGLE_REFERRING } from '../../../util/model'; @@ -231,6 +231,7 @@ const seriesOptGenreator: Record = { }; +// TODO: SELF REGISTERED. echarts.registerAction({ type: 'changeMagicType', event: 'magicTypeChanged', @@ -239,6 +240,4 @@ echarts.registerAction({ ecModel.mergeOption(payload.newOption); }); -registerFeature('magicType', MagicType); - export default MagicType; diff --git a/src/component/toolbox/feature/Restore.ts b/src/component/toolbox/feature/Restore.ts index debea2e2cc..e7ce593bdb 100644 --- a/src/component/toolbox/feature/Restore.ts +++ b/src/component/toolbox/feature/Restore.ts @@ -17,10 +17,10 @@ * under the License. */ -import * as echarts from '../../../echarts'; +import * as echarts from '../../../core/echarts'; import * as history from '../../dataZoom/history'; -import { ToolboxFeatureOption, ToolboxFeature, registerFeature } from '../featureManager'; -import ExtensionAPI from '../../../ExtensionAPI'; +import { ToolboxFeatureOption, ToolboxFeature } from '../featureManager'; +import ExtensionAPI from '../../../core/ExtensionAPI'; import GlobalModel from '../../../model/Global'; export interface ToolboxRestoreFeatureOption extends ToolboxFeatureOption { @@ -51,11 +51,13 @@ class RestoreOption extends ToolboxFeature { } } -registerFeature('restore', RestoreOption); - +// TODO: SELF REGISTERED. echarts.registerAction( {type: 'restore', event: 'restore', update: 'prepareAndUpdate'}, function (payload, ecModel) { ecModel.resetOption('recreate'); } ); + + +export default RestoreOption; \ No newline at end of file diff --git a/src/component/toolbox/feature/SaveAsImage.ts b/src/component/toolbox/feature/SaveAsImage.ts index c1eb653fe5..65a57aafea 100644 --- a/src/component/toolbox/feature/SaveAsImage.ts +++ b/src/component/toolbox/feature/SaveAsImage.ts @@ -20,10 +20,10 @@ /* global Uint8Array */ import env from 'zrender/src/core/env'; -import { ToolboxFeature, ToolboxFeatureOption, registerFeature } from '../featureManager'; +import { ToolboxFeature, ToolboxFeatureOption } from '../featureManager'; import { ZRColor } from '../../../util/types'; import GlobalModel from '../../../model/Global'; -import ExtensionAPI from '../../../ExtensionAPI'; +import ExtensionAPI from '../../../core/ExtensionAPI'; export interface ToolboxSaveAsImageFeatureOption extends ToolboxFeatureOption { icon?: string @@ -143,6 +143,4 @@ class SaveAsImage extends ToolboxFeature { SaveAsImage.prototype.unusable = !env.canvasSupported; -registerFeature('saveAsImage', SaveAsImage); - export default SaveAsImage; diff --git a/src/component/toolbox/featureManager.ts b/src/component/toolbox/featureManager.ts index 8bf7575868..49553ebe59 100644 --- a/src/component/toolbox/featureManager.ts +++ b/src/component/toolbox/featureManager.ts @@ -19,7 +19,7 @@ import { Dictionary, DisplayState, ZRElementEvent, ItemStyleOption, LabelOption } from '../../util/types'; import Model from '../../model/Model'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; // import * as graphic from '../../util/graphic'; import Displayable from 'zrender/src/graphic/Displayable'; @@ -38,16 +38,16 @@ export interface ToolboxFeatureOption { show?: boolean - title?: string | Dictionary + title?: string | Partial> - icon?: string | Dictionary + icon?: string | Partial> iconStyle?: IconStyle emphasis?: { iconStyle?: IconStyle } - iconStatus?: Dictionary + iconStatus?: Partial> onclick?: () => void } @@ -58,7 +58,7 @@ export interface ToolboxFeatureModel + iconPaths: Partial> setIconStatus(iconName: string, status: DisplayState): void } diff --git a/src/component/toolbox/install.ts b/src/component/toolbox/install.ts new file mode 100644 index 0000000000..61f6050dc3 --- /dev/null +++ b/src/component/toolbox/install.ts @@ -0,0 +1,44 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import { install as installDataZoomSelect } from '../../component/dataZoom/installDataZoomSelect'; +import ToolboxModel from './ToolboxModel'; +import ToolboxView from './ToolboxView'; + +// TODOD: REGISTER IN INSTALL +import { registerFeature } from './featureManager'; +import SaveAsImage from './feature/SaveAsImage'; +import MagicType from './feature/MagicType'; +import DataView from './feature/DataView'; +import Restore from './feature/Restore'; +import DataZoom from './feature/DataZoom'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(ToolboxModel); + registers.registerComponentView(ToolboxView); + + registerFeature('saveAsImage', SaveAsImage); + registerFeature('magicType', MagicType); + registerFeature('dataView', DataView); + registerFeature('dataZoom', DataZoom); + registerFeature('restore', Restore); + + use(installDataZoomSelect); +} \ No newline at end of file diff --git a/src/component/tooltip.ts b/src/component/tooltip.ts index 31176e8bc0..8f40253f37 100644 --- a/src/component/tooltip.ts +++ b/src/component/tooltip.ts @@ -17,39 +17,7 @@ * under the License. */ -// FIXME Better way to pack data in graphic element +import { use } from '../extension'; +import { install } from './tooltip/install'; -import * as echarts from '../echarts'; - -import './axisPointer'; -import './tooltip/TooltipModel'; -import './tooltip/TooltipView'; - - -/** - * @action - * @property {string} type - * @property {number} seriesIndex - * @property {number} dataIndex - * @property {number} [x] - * @property {number} [y] - */ -echarts.registerAction( - { - type: 'showTip', - event: 'showTip', - update: 'tooltip:manuallyShowTip' - }, - // noop - function () {} -); - -echarts.registerAction( - { - type: 'hideTip', - event: 'hideTip', - update: 'tooltip:manuallyHideTip' - }, - // noop - function () {} -); \ No newline at end of file +use(install); \ No newline at end of file diff --git a/src/component/tooltip/TooltipHTMLContent.ts b/src/component/tooltip/TooltipHTMLContent.ts index aba453edc1..8f600934dc 100644 --- a/src/component/tooltip/TooltipHTMLContent.ts +++ b/src/component/tooltip/TooltipHTMLContent.ts @@ -23,7 +23,7 @@ import { normalizeEvent } from 'zrender/src/core/event'; import { transformLocalCoord } from 'zrender/src/core/dom'; import env from 'zrender/src/core/env'; import { convertToColorString, toCamelCase, normalizeCssArray } from '../../util/format'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ZRenderType } from 'zrender/src/zrender'; import { TooltipOption } from './TooltipModel'; import Model from '../../model/Model'; diff --git a/src/component/tooltip/TooltipModel.ts b/src/component/tooltip/TooltipModel.ts index 82302262f3..94c2776c42 100644 --- a/src/component/tooltip/TooltipModel.ts +++ b/src/component/tooltip/TooltipModel.ts @@ -33,6 +33,7 @@ import {AxisPointerOption} from '../axisPointer/AxisPointerModel'; type TopLevelFormatterParams = CallbackDataParams | CallbackDataParams[]; export interface TooltipOption extends CommonTooltipOption, ComponentOption { + mainType?: 'tooltip' axisPointer?: AxisPointerOption & { axis?: 'auto' | 'x' | 'y' | 'angle' | 'radius' @@ -174,6 +175,4 @@ class TooltipModel extends ComponentModel { }; } -ComponentModel.registerClass(TooltipModel); - export default TooltipModel; diff --git a/src/component/tooltip/TooltipRichContent.ts b/src/component/tooltip/TooltipRichContent.ts index ac51792558..94c2e6defb 100644 --- a/src/component/tooltip/TooltipRichContent.ts +++ b/src/component/tooltip/TooltipRichContent.ts @@ -18,7 +18,7 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ZRenderType } from 'zrender/src/zrender'; import { TooltipOption } from './TooltipModel'; import { ZRColor } from '../../util/types'; diff --git a/src/component/tooltip/TooltipView.ts b/src/component/tooltip/TooltipView.ts index 92e0af1c8c..f34e27d683 100644 --- a/src/component/tooltip/TooltipView.ts +++ b/src/component/tooltip/TooltipView.ts @@ -44,7 +44,7 @@ import { ZRColor } from '../../util/types'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import TooltipModel, {TooltipOption} from './TooltipModel'; import Element from 'zrender/src/Element'; import { AxisBaseModel } from '../../coord/AxisBaseModel'; @@ -906,12 +906,14 @@ class TooltipView extends ComponentView { } type TooltipableOption = { - tooltip?: TooltipOption | string + tooltip?: Omit | string }; /** * From top to bottom. (the last one should be globalTooltipModel); */ -function buildTooltipModel(modelCascade: (TooltipModel | Model | TooltipOption | string)[]) { +function buildTooltipModel(modelCascade: ( + TooltipModel | Model | Omit | string +)[]) { // Last is always tooltip model. let resultModel = modelCascade.pop() as Model; while (modelCascade.length) { @@ -1032,4 +1034,4 @@ function isCenterAlign(align: HorizontalAlign | VerticalAlign) { return align === 'center' || align === 'middle'; } -ComponentView.registerClass(TooltipView); +export default TooltipView; diff --git a/src/component/tooltip/install.ts b/src/component/tooltip/install.ts new file mode 100644 index 0000000000..ae80ef036e --- /dev/null +++ b/src/component/tooltip/install.ts @@ -0,0 +1,57 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import {install as installAxisPointer} from '../axisPointer/install'; +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import TooltipModel from './TooltipModel'; +import TooltipView from './TooltipView'; + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installAxisPointer); + + registers.registerComponentModel(TooltipModel); + registers.registerComponentView(TooltipView); + /** + * @action + * @property {string} type + * @property {number} seriesIndex + * @property {number} dataIndex + * @property {number} [x] + * @property {number} [y] + */ + registers.registerAction( + { + type: 'showTip', + event: 'showTip', + update: 'tooltip:manuallyShowTip' + }, + // noop + function () {} + ); + + registers.registerAction( + { + type: 'hideTip', + event: 'hideTip', + update: 'tooltip:manuallyHideTip' + }, + // noop + function () {} + ); +} \ No newline at end of file diff --git a/src/component/transform.ts b/src/component/transform.ts index d679f8c31c..96937aa371 100644 --- a/src/component/transform.ts +++ b/src/component/transform.ts @@ -17,9 +17,7 @@ * under the License. */ -import * as echarts from '../echarts'; -import {filterTransform} from './transform/filterTransform'; -import {sortTransform} from './transform/sortTransform'; +import { use } from '../extension'; +import { install } from './transform/install'; -echarts.registerTransform(filterTransform); -echarts.registerTransform(sortTransform); +use(install); \ No newline at end of file diff --git a/src/component/transform/install.ts b/src/component/transform/install.ts new file mode 100644 index 0000000000..208f6e0add --- /dev/null +++ b/src/component/transform/install.ts @@ -0,0 +1,27 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import {filterTransform} from './filterTransform'; +import {sortTransform} from './sortTransform'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerTransform(filterTransform); + registers.registerTransform(sortTransform); +} \ No newline at end of file diff --git a/src/component/visualMap.ts b/src/component/visualMap.ts index 044425dcc3..425f850914 100644 --- a/src/component/visualMap.ts +++ b/src/component/visualMap.ts @@ -17,9 +17,7 @@ * under the License. */ -/** - * visualMap component entry - */ +import { use } from '../extension'; +import { install } from './visualMap/install'; -import './visualMapContinuous'; -import './visualMapPiecewise'; +use(install); \ No newline at end of file diff --git a/src/component/visualMap/ContinuousModel.ts b/src/component/visualMap/ContinuousModel.ts index 5759d5fc4b..c43e2dd905 100644 --- a/src/component/visualMap/ContinuousModel.ts +++ b/src/component/visualMap/ContinuousModel.ts @@ -20,7 +20,6 @@ import * as zrUtil from 'zrender/src/core/util'; import VisualMapModel, { VisualMapOption, VisualMeta } from './VisualMapModel'; import * as numberUtil from '../../util/number'; -import ComponentModel from '../../model/Component'; import { VisualMappingOption } from '../../visual/VisualMapping'; import { inheritDefaultOption } from '../../util/component'; import { ItemStyleOption } from '../../util/types'; @@ -338,6 +337,4 @@ function getColorStopValues( return stopValues; } -ComponentModel.registerClass(ContinuousModel); - export default ContinuousModel; diff --git a/src/component/visualMap/ContinuousView.ts b/src/component/visualMap/ContinuousView.ts index cd83e3d4a2..e15a74cd6b 100644 --- a/src/component/visualMap/ContinuousView.ts +++ b/src/component/visualMap/ContinuousView.ts @@ -26,11 +26,10 @@ import * as numberUtil from '../../util/number'; import sliderMove from '../helper/sliderMove'; import * as helper from './helper'; import * as modelUtil from '../../util/model'; -import ComponentView from '../../view/Component'; import VisualMapModel from './VisualMapModel'; import ContinuousModel from './ContinuousModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import Element, { ElementEvent } from 'zrender/src/Element'; import { TextVerticalAlign, TextAlign } from 'zrender/src/core/types'; import { ColorString, Payload } from '../../util/types'; @@ -949,6 +948,4 @@ function getCursor(orient: Orient) { return orient === 'vertical' ? 'ns-resize' : 'ew-resize'; } -ComponentView.registerClass(ContinuousView); - export default ContinuousView; \ No newline at end of file diff --git a/src/component/visualMap/PiecewiseModel.ts b/src/component/visualMap/PiecewiseModel.ts index d8d0785da6..44880408ef 100644 --- a/src/component/visualMap/PiecewiseModel.ts +++ b/src/component/visualMap/PiecewiseModel.ts @@ -24,7 +24,6 @@ import visualDefault from '../../visual/visualDefault'; import {reformIntervals} from '../../util/number'; import { VisualOptionPiecewise, BuiltinVisualProperty } from '../../util/types'; import { Dictionary } from 'zrender/src/core/types'; -import ComponentModel from '../../model/Component'; import { inheritDefaultOption } from '../../util/component'; @@ -591,6 +590,4 @@ function normalizeReverse(thisOption: PiecewiseVisualMapOption, pieceList: Inner } } -ComponentModel.registerClass(PiecewiseModel); - export default PiecewiseModel; \ No newline at end of file diff --git a/src/component/visualMap/PiecewiseView.ts b/src/component/visualMap/PiecewiseView.ts index 70cb29c290..c62b27e2c0 100644 --- a/src/component/visualMap/PiecewiseView.ts +++ b/src/component/visualMap/PiecewiseView.ts @@ -24,7 +24,6 @@ import {createSymbol} from '../../util/symbol'; import * as layout from '../../util/layout'; import * as helper from './helper'; import PiecewiseModel from './PiecewiseModel'; -import ComponentView from '../../view/Component'; import { TextAlign } from 'zrender/src/core/types'; import { VisualMappingOption } from '../../visual/VisualMapping'; @@ -241,6 +240,4 @@ class PiecewiseVisualMapView extends VisualMapView { } } -ComponentView.registerClass(PiecewiseVisualMapView); - export default PiecewiseVisualMapView; \ No newline at end of file diff --git a/src/component/visualMap/VisualMapModel.ts b/src/component/visualMap/VisualMapModel.ts index 6fff616beb..6da2a7eeb4 100644 --- a/src/component/visualMap/VisualMapModel.ts +++ b/src/component/visualMap/VisualMapModel.ts @@ -56,6 +56,9 @@ export interface VisualMapOption ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin { + + mainType?: 'visualMap' + show?: boolean align?: string diff --git a/src/component/visualMap/VisualMapView.ts b/src/component/visualMap/VisualMapView.ts index b7757f1686..43d49a17fb 100644 --- a/src/component/visualMap/VisualMapView.ts +++ b/src/component/visualMap/VisualMapView.ts @@ -24,7 +24,7 @@ import * as layout from '../../util/layout'; import VisualMapping from '../../visual/VisualMapping'; import ComponentView from '../../view/Component'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import VisualMapModel from './VisualMapModel'; import { VisualOptionUnit, ColorString } from '../../util/types'; import PiecewiseModel from './PiecewiseModel'; @@ -173,6 +173,4 @@ class VisualMapView extends ComponentView { ) {} } -ComponentView.registerClass(VisualMapView); - export default VisualMapView; \ No newline at end of file diff --git a/src/component/visualMap/helper.ts b/src/component/visualMap/helper.ts index 843b752b9d..f264f8b51b 100644 --- a/src/component/visualMap/helper.ts +++ b/src/component/visualMap/helper.ts @@ -20,7 +20,7 @@ import * as zrUtil from 'zrender/src/core/util'; import {getLayoutRect} from '../../util/layout'; import VisualMapModel from './VisualMapModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Payload } from '../../util/types'; const paramsSet = [ diff --git a/src/component/visualMap/install.ts b/src/component/visualMap/install.ts new file mode 100644 index 0000000000..cc35b7d6f6 --- /dev/null +++ b/src/component/visualMap/install.ts @@ -0,0 +1,30 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters, use } from '../../extension'; +import {install as installVisualMapContinuous} from './installVisualMapContinuous'; +import {install as installVisualMapPiecewise} from './installVisualMapPiecewise'; + +export function install(registers: EChartsExtensionInstallRegisters) { + use(installVisualMapContinuous); + use(installVisualMapPiecewise); + + // Do not install './dataZoomSelect', + // since it only work for toolbox dataZoom. +} \ No newline at end of file diff --git a/src/component/visualMap/installCommon.ts b/src/component/visualMap/installCommon.ts new file mode 100644 index 0000000000..c18ad4bf0f --- /dev/null +++ b/src/component/visualMap/installCommon.ts @@ -0,0 +1,59 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import { VisualMapOption } from './VisualMapModel'; +import { PiecewiseVisualMapOption } from './PiecewiseModel'; +import { ContinousVisualMapOption } from './ContinuousModel'; +import { visualMapActionInfo, visualMapActionHander } from './visualMapAction'; +import { visualMapEncodingHandlers } from './visualEncoding'; +import { each } from 'zrender/src/core/util'; +import preprocessor from './preprocessor'; + +let installed = false; +export default function installCommon(registers: EChartsExtensionInstallRegisters) { + if (installed) { + return; + } + installed = true; + + registers.registerSubTypeDefaulter( + 'visualMap', function (option: VisualMapOption) { + // Compatible with ec2, when splitNumber === 0, continuous visualMap will be used. + return ( + !option.categories + && ( + !( + (option as PiecewiseVisualMapOption).pieces + ? ((option as PiecewiseVisualMapOption)).pieces.length > 0 + : ((option as PiecewiseVisualMapOption)).splitNumber > 0 + ) + || (option as ContinousVisualMapOption).calculable + ) + ) + ? 'continuous' : 'piecewise'; + }); + + registers.registerAction(visualMapActionInfo, visualMapActionHander); + + each(visualMapEncodingHandlers, (handler) => { + registers.registerVisual(registers.PRIORITY.VISUAL.COMPONENT, handler); + }); + registers.registerPreprocessor(preprocessor); +} \ No newline at end of file diff --git a/src/component/visualMap/installVisualMapContinuous.ts b/src/component/visualMap/installVisualMapContinuous.ts new file mode 100644 index 0000000000..68006f42bd --- /dev/null +++ b/src/component/visualMap/installVisualMapContinuous.ts @@ -0,0 +1,30 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import ContinuousModel from './ContinuousModel'; +import ContinuousView from './ContinuousView'; +import installCommon from './installCommon'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(ContinuousModel); + registers.registerComponentView(ContinuousView); + + installCommon(registers); +} \ No newline at end of file diff --git a/src/component/visualMap/installVisualMapPiecewise.ts b/src/component/visualMap/installVisualMapPiecewise.ts new file mode 100644 index 0000000000..8dfca33cdb --- /dev/null +++ b/src/component/visualMap/installVisualMapPiecewise.ts @@ -0,0 +1,30 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../../extension'; +import PiecewiseModel from './PiecewiseModel'; +import PiecewiseView from './PiecewiseView'; +import installCommon from './installCommon'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerComponentModel(PiecewiseModel); + registers.registerComponentView(PiecewiseView); + + installCommon(registers); +} \ No newline at end of file diff --git a/src/component/visualMap/visualEncoding.ts b/src/component/visualMap/visualEncoding.ts index e21ff389a5..f2a008bbbb 100644 --- a/src/component/visualMap/visualEncoding.ts +++ b/src/component/visualMap/visualEncoding.ts @@ -17,71 +17,69 @@ * under the License. */ -import * as echarts from '../../echarts'; import * as zrUtil from 'zrender/src/core/util'; import * as visualSolution from '../../visual/visualSolution'; import VisualMapping from '../../visual/VisualMapping'; import VisualMapModel, { VisualMeta } from './VisualMapModel'; -import { StageHandlerProgressExecutor, BuiltinVisualProperty, ParsedValue } from '../../util/types'; +import { StageHandlerProgressExecutor, BuiltinVisualProperty, ParsedValue, StageHandler } from '../../util/types'; import SeriesModel from '../../model/Series'; import { getVisualFromData } from '../../visual/helper'; -const VISUAL_PRIORITY = echarts.PRIORITY.VISUAL.COMPONENT; - -echarts.registerVisual(VISUAL_PRIORITY, { - createOnAllSeries: true, - reset: function (seriesModel, ecModel) { - const resetDefines: StageHandlerProgressExecutor[] = []; - ecModel.eachComponent('visualMap', function (visualMapModel: VisualMapModel) { - const pipelineContext = seriesModel.pipelineContext; - if (!visualMapModel.isTargetSeries(seriesModel) - || (pipelineContext && pipelineContext.large) - ) { - return; - } - - resetDefines.push(visualSolution.incrementalApplyVisual( - visualMapModel.stateList, - visualMapModel.targetVisuals, - zrUtil.bind(visualMapModel.getValueState, visualMapModel), - visualMapModel.getDataDimension(seriesModel.getData()) - )); - }); +export const visualMapEncodingHandlers: StageHandler[] = [ + { + createOnAllSeries: true, + reset: function (seriesModel, ecModel) { + const resetDefines: StageHandlerProgressExecutor[] = []; + ecModel.eachComponent('visualMap', function (visualMapModel: VisualMapModel) { + const pipelineContext = seriesModel.pipelineContext; + if (!visualMapModel.isTargetSeries(seriesModel) + || (pipelineContext && pipelineContext.large) + ) { + return; + } - return resetDefines; - } -}); + resetDefines.push(visualSolution.incrementalApplyVisual( + visualMapModel.stateList, + visualMapModel.targetVisuals, + zrUtil.bind(visualMapModel.getValueState, visualMapModel), + visualMapModel.getDataDimension(seriesModel.getData()) + )); + }); -// Only support color. -echarts.registerVisual(VISUAL_PRIORITY, { - createOnAllSeries: true, - reset: function (seriesModel, ecModel) { - const data = seriesModel.getData(); - const visualMetaList: VisualMeta[] = []; + return resetDefines; + } + }, + // Only support color. + { + createOnAllSeries: true, + reset: function (seriesModel, ecModel) { + const data = seriesModel.getData(); + const visualMetaList: VisualMeta[] = []; - ecModel.eachComponent('visualMap', function (visualMapModel: VisualMapModel) { - if (visualMapModel.isTargetSeries(seriesModel)) { - const visualMeta = visualMapModel.getVisualMeta( - zrUtil.bind(getColorVisual, null, seriesModel, visualMapModel) - ) || { - stops: [], - outerColors: [] - } as VisualMeta; + ecModel.eachComponent('visualMap', function (visualMapModel: VisualMapModel) { + if (visualMapModel.isTargetSeries(seriesModel)) { + const visualMeta = visualMapModel.getVisualMeta( + zrUtil.bind(getColorVisual, null, seriesModel, visualMapModel) + ) || { + stops: [], + outerColors: [] + } as VisualMeta; - const concreteDim = visualMapModel.getDataDimension(data); - const dimInfo = data.getDimensionInfo(concreteDim); - if (dimInfo != null) { - // visualMeta.dimension should be dimension index, but not concrete dimension. - visualMeta.dimension = dimInfo.index; - visualMetaList.push(visualMeta); + const concreteDim = visualMapModel.getDataDimension(data); + const dimInfo = data.getDimensionInfo(concreteDim); + if (dimInfo != null) { + // visualMeta.dimension should be dimension index, but not concrete dimension. + visualMeta.dimension = dimInfo.index; + visualMetaList.push(visualMeta); + } } - } - }); + }); - // console.log(JSON.stringify(visualMetaList.map(a => a.stops))); - seriesModel.getData().setVisual('visualMeta', visualMetaList); + // console.log(JSON.stringify(visualMetaList.map(a => a.stops))); + seriesModel.getData().setVisual('visualMeta', visualMetaList); + } } -}); +]; // FIXME // performance and export for heatmap? diff --git a/src/component/visualMap/visualMapAction.ts b/src/component/visualMap/visualMapAction.ts index 2dc0c6d139..88cda0e255 100644 --- a/src/component/visualMap/visualMapAction.ts +++ b/src/component/visualMap/visualMapAction.ts @@ -17,19 +17,19 @@ * under the License. */ -import * as echarts from '../../echarts'; import VisualMapModel from './VisualMapModel'; +import { Payload } from '../../util/types'; +import GlobalModel from '../../model/Global'; -const actionInfo = { +export const visualMapActionInfo = { type: 'selectDataRange', event: 'dataRangeSelected', // FIXME use updateView appears wrong update: 'update' }; -echarts.registerAction(actionInfo, function (payload, ecModel) { - +export const visualMapActionHander = function (payload: Payload, ecModel: GlobalModel) { ecModel.eachComponent({mainType: 'visualMap', query: payload}, function (model) { (model as VisualMapModel).setSelected(payload.selected); }); -}); +}; diff --git a/src/component/visualMapContinuous.ts b/src/component/visualMapContinuous.ts index 722f9ea15e..d34a1e8383 100644 --- a/src/component/visualMapContinuous.ts +++ b/src/component/visualMapContinuous.ts @@ -17,17 +17,7 @@ * under the License. */ -/** - * DataZoom component entry - */ +import { use } from '../extension'; +import { install } from './visualMap/installVisualMapContinuous'; -import * as echarts from '../echarts'; -import preprocessor from './visualMap/preprocessor'; - -import './visualMap/typeDefaulter'; -import './visualMap/visualEncoding'; -import './visualMap/ContinuousModel'; -import './visualMap/ContinuousView'; -import './visualMap/visualMapAction'; - -echarts.registerPreprocessor(preprocessor); +use(install); \ No newline at end of file diff --git a/src/component/visualMapPiecewise.ts b/src/component/visualMapPiecewise.ts index e8f4ad0476..39bb73480b 100644 --- a/src/component/visualMapPiecewise.ts +++ b/src/component/visualMapPiecewise.ts @@ -17,17 +17,7 @@ * under the License. */ -/** - * DataZoom component entry - */ +import { use } from '../extension'; +import { install } from './visualMap/installVisualMapPiecewise'; -import * as echarts from '../echarts'; -import preprocessor from './visualMap/preprocessor'; - -import './visualMap/typeDefaulter'; -import './visualMap/visualEncoding'; -import './visualMap/PiecewiseModel'; -import './visualMap/PiecewiseView'; -import './visualMap/visualMapAction'; - -echarts.registerPreprocessor(preprocessor); +use(install); \ No newline at end of file diff --git a/src/coord/CoordinateSystem.ts b/src/coord/CoordinateSystem.ts index 91f7a82a6a..83708d871a 100644 --- a/src/coord/CoordinateSystem.ts +++ b/src/coord/CoordinateSystem.ts @@ -19,14 +19,14 @@ import GlobalModel from '../model/Global'; import {ParsedModelFinder} from '../util/model'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import { DimensionDefinitionLoose, ScaleDataValue, DimensionName } from '../util/types'; import Axis from './Axis'; import { BoundingRect } from '../util/graphic'; import { MatrixArray } from 'zrender/src/core/matrix'; import ComponentModel from '../model/Component'; import { RectLike } from 'zrender/src/core/BoundingRect'; -import { PrepareCustomInfo } from '../chart/custom'; +import type { PrepareCustomInfo } from '../chart/custom/install'; export interface CoordinateSystemCreator { diff --git a/src/coord/View.ts b/src/coord/View.ts index 259938a4ea..f724650ea2 100644 --- a/src/coord/View.ts +++ b/src/coord/View.ts @@ -29,7 +29,7 @@ import BoundingRect from 'zrender/src/core/BoundingRect'; import Transformable from 'zrender/src/core/Transformable'; import { CoordinateSystemMaster, CoordinateSystem } from './CoordinateSystem'; import GlobalModel from '../model/Global'; -import { ParsedModelFinder } from '../util/model'; +import { ParsedModelFinder, ParsedModelFinderKnown } from '../util/model'; const v2ApplyTransform = vector.applyTransform; @@ -268,7 +268,7 @@ class View extends Transformable implements CoordinateSystemMaster, CoordinateSy // } } -function getCoordSys(finder: ParsedModelFinder): View { +function getCoordSys(finder: ParsedModelFinderKnown): View { const seriesModel = finder.seriesModel; return seriesModel ? seriesModel.coordinateSystem as View : null; // e.g., graph. } diff --git a/src/coord/axisModelCreator.ts b/src/coord/axisModelCreator.ts index 8eff111e72..e525f76f26 100644 --- a/src/coord/axisModelCreator.ts +++ b/src/coord/axisModelCreator.ts @@ -17,7 +17,6 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; import axisDefault from './axisDefault'; import ComponentModel from '../model/Component'; import { @@ -29,6 +28,8 @@ import OrdinalMeta from '../data/OrdinalMeta'; import { DimensionName, BoxLayoutOptionMixin, OrdinalRawValue } from '../util/types'; import { AxisBaseOption, AXIS_TYPES } from './axisCommonTypes'; import GlobalModel from '../model/Global'; +import { each, merge } from 'zrender/src/core/util'; +import { EChartsExtensionInstallRegisters } from '../extension'; type Constructor = new (...args: any[]) => T; @@ -46,15 +47,16 @@ export default function axisModelCreator< AxisOptionT extends AxisBaseOption, AxisModelCtor extends Constructor> >( + registers: EChartsExtensionInstallRegisters, axisName: DimensionName, BaseAxisModelClass: AxisModelCtor, extraDefaultOption?: AxisOptionT ) { - zrUtil.each(AXIS_TYPES, function (v, axisType) { + each(AXIS_TYPES, function (v, axisType) { - const defaultOption = zrUtil.merge( - zrUtil.merge({}, axisDefault[axisType], true), + const defaultOption = merge( + merge({}, axisDefault[axisType], true), extraDefaultOption, true ); @@ -77,8 +79,8 @@ export default function axisModelCreator< ? getLayoutParams(option as BoxLayoutOptionMixin) : {}; const themeModel = ecModel.getTheme(); - zrUtil.merge(option, themeModel.get(axisType + 'Axis')); - zrUtil.merge(option, this.getDefaultOption()); + merge(option, themeModel.get(axisType + 'Axis')); + merge(option, this.getDefaultOption()); option.type = getAxisType(option); @@ -115,10 +117,10 @@ export default function axisModelCreator< } } - ComponentModel.registerClass(AxisModel); + registers.registerComponentModel(AxisModel); }); - ComponentModel.registerSubTypeDefaulter( + registers.registerSubTypeDefaulter( axisName + 'Axis', getAxisType ); diff --git a/src/coord/calendar/Calendar.ts b/src/coord/calendar/Calendar.ts index b0cd9af990..038dd40687 100644 --- a/src/coord/calendar/Calendar.ts +++ b/src/coord/calendar/Calendar.ts @@ -23,7 +23,7 @@ import * as numberUtil from '../../util/number'; import BoundingRect, {RectLike} from 'zrender/src/core/BoundingRect'; import CalendarModel from './CalendarModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { LayoutOrient, ScaleDataValue, @@ -31,10 +31,10 @@ import { SeriesOption, SeriesOnCalendarOptionMixin } from '../../util/types'; -import { ParsedModelFinder } from '../../util/model'; +import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; import SeriesModel from '../../model/Series'; -import CoordinateSystemManager from '../../CoordinateSystem'; +import CoordinateSystemManager from '../../core/CoordinateSystem'; // (24*60*60*1000) const PROXIMATE_ONE_DAY = 86400000; @@ -536,7 +536,7 @@ class Calendar implements CoordinateSystem, CoordinateSystemMaster { } } -function getCoordSys(finder: ParsedModelFinder): Calendar { +function getCoordSys(finder: ParsedModelFinderKnown): Calendar { const calendarModel = finder.calendarModel as CalendarModel; const seriesModel = finder.seriesModel; @@ -549,7 +549,4 @@ function getCoordSys(finder: ParsedModelFinder): Calendar { return coordSys as Calendar; } - -CoordinateSystemManager.register('calendar', Calendar); - export default Calendar; diff --git a/src/coord/calendar/CalendarModel.ts b/src/coord/calendar/CalendarModel.ts index 5bd34b41b0..1e8e1179de 100644 --- a/src/coord/calendar/CalendarModel.ts +++ b/src/coord/calendar/CalendarModel.ts @@ -64,6 +64,8 @@ export interface CalendarYearLabelFormatterCallbackParams { } export interface CalendarOption extends ComponentOption, BoxLayoutOptionMixin { + mainType?: 'calendar' + cellSize?: number | 'auto' | (number | 'auto')[] orient?: LayoutOrient @@ -281,6 +283,4 @@ function mergeAndNormalizeLayoutParams(target: CalendarOption, raw: BoxLayoutOpt }); } -ComponentModel.registerClass(CalendarModel); - export default CalendarModel; diff --git a/src/coord/cartesian/AxisModel.ts b/src/coord/cartesian/AxisModel.ts index 39bc942aa3..6722660576 100644 --- a/src/coord/cartesian/AxisModel.ts +++ b/src/coord/cartesian/AxisModel.ts @@ -19,7 +19,7 @@ import * as zrUtil from 'zrender/src/core/util'; import ComponentModel from '../../model/Component'; -import axisModelCreator, { AxisModelExtendedInCreator } from '../axisModelCreator'; +import { AxisModelExtendedInCreator } from '../axisModelCreator'; import {AxisModelCommonMixin} from '../axisModelCommonMixin'; import Axis2D from './Axis2D'; import { AxisBaseOption } from '../axisCommonTypes'; @@ -40,7 +40,14 @@ export interface CartesianAxisOption extends AxisBaseOption { categorySortInfo?: OrdinalSortInfo[]; } -class CartesianAxisModel extends ComponentModel +export interface XAXisOption extends CartesianAxisOption { + mainType?: 'xAxis' +} +export interface YAXisOption extends CartesianAxisOption { + mainType?: 'yAxis' +} + +export class CartesianAxisModel extends ComponentModel implements AxisBaseModel { static type = 'cartesian2dAxis'; @@ -52,18 +59,9 @@ class CartesianAxisModel extends ComponentModel } } -interface CartesianAxisModel extends AxisModelCommonMixin, +export interface CartesianAxisModel extends AxisModelCommonMixin, AxisModelExtendedInCreator {} zrUtil.mixin(CartesianAxisModel, AxisModelCommonMixin); -const extraOption: CartesianAxisOption = { - // gridIndex: 0, - // gridId: '', - offset: 0 -}; - -axisModelCreator('x', CartesianAxisModel, extraOption); -axisModelCreator('y', CartesianAxisModel, extraOption); - export default CartesianAxisModel; diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts index 88c7d33ed9..e13b633438 100644 --- a/src/coord/cartesian/Grid.ts +++ b/src/coord/cartesian/Grid.ts @@ -34,14 +34,13 @@ import { } from '../../coord/axisHelper'; import Cartesian2D, {cartesian2DDimensions} from './Cartesian2D'; import Axis2D from './Axis2D'; -import CoordinateSystemManager from '../../CoordinateSystem'; -import {ParsedModelFinder, SINGLE_REFERRING} from '../../util/model'; +import {ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING} from '../../util/model'; // Depends on GridModel, AxisModel, which performs preprocess. import GridModel from './GridModel'; import CartesianAxisModel from './AxisModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Dictionary } from 'zrender/src/core/types'; import {CoordinateSystemMaster} from '../CoordinateSystem'; import { ScaleDataValue } from '../../util/types'; @@ -256,7 +255,7 @@ class Grid implements CoordinateSystemMaster { : null; } - private _findConvertTarget(finder: ParsedModelFinder): { + private _findConvertTarget(finder: ParsedModelFinderKnown): { cartesian: Cartesian2D, axis: Axis2D } { @@ -621,6 +620,4 @@ function updateAxisTransform(axis: Axis2D, coordBase: number) { }; } -CoordinateSystemManager.register('cartesian2d', Grid); - export default Grid; diff --git a/src/coord/cartesian/GridModel.ts b/src/coord/cartesian/GridModel.ts index a3ed5121a3..bfc071cb13 100644 --- a/src/coord/cartesian/GridModel.ts +++ b/src/coord/cartesian/GridModel.ts @@ -18,13 +18,14 @@ */ -import './AxisModel'; import ComponentModel from '../../model/Component'; import { ComponentOption, BoxLayoutOptionMixin, ZRColor, ShadowOptionMixin } from '../../util/types'; import Grid from './Grid'; import { CoordinateSystemHostModel } from '../CoordinateSystem'; export interface GridOption extends ComponentOption, BoxLayoutOptionMixin, ShadowOptionMixin { + mainType?: 'grid'; + show?: boolean; // Whether grid size contain label. diff --git a/src/coord/cartesian/defaultAxisExtentFromData.ts b/src/coord/cartesian/defaultAxisExtentFromData.ts index b82391b987..1ca8442446 100644 --- a/src/coord/cartesian/defaultAxisExtentFromData.ts +++ b/src/coord/cartesian/defaultAxisExtentFromData.ts @@ -17,7 +17,7 @@ * under the License. */ -import * as echarts from '../../echarts'; +import * as echarts from '../../core/echarts'; import { createHashMap, each, HashMap, hasOwn, keys, map } from 'zrender/src/core/util'; import SeriesModel from '../../model/Series'; import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper'; @@ -44,6 +44,7 @@ type SeriesRecord = { // A tricky: the priority is just after dataZoom processor. // If dataZoom has fixed the min/max, this processor do not need to work. +// TODO: SELF REGISTERED. echarts.registerProcessor(echarts.PRIORITY.PROCESSOR.FILTER + 10, { getTargetSeries: function (ecModel) { diff --git a/src/coord/geo/Geo.ts b/src/coord/geo/Geo.ts index c5e06308a6..75b92cbca2 100644 --- a/src/coord/geo/Geo.ts +++ b/src/coord/geo/Geo.ts @@ -24,7 +24,7 @@ import geoSourceManager from './geoSourceManager'; import Region from './Region'; import { NameMap } from './geoTypes'; import GlobalModel from '../../model/Global'; -import { ParsedModelFinder, SINGLE_REFERRING } from '../../util/model'; +import { ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING } from '../../util/model'; import GeoModel from './GeoModel'; import { resizeGeoType } from './geoCreator'; @@ -165,7 +165,7 @@ class Geo extends View { zrUtil.mixin(Geo, View); -function getCoordSys(finder: ParsedModelFinder): Geo { +function getCoordSys(finder: ParsedModelFinderKnown): Geo { const geoModel = finder.geoModel as GeoModel; const seriesModel = finder.seriesModel; return geoModel diff --git a/src/coord/geo/GeoModel.ts b/src/coord/geo/GeoModel.ts index d874a366de..16c14bbc25 100644 --- a/src/coord/geo/GeoModel.ts +++ b/src/coord/geo/GeoModel.ts @@ -92,6 +92,7 @@ export interface GeoOption extends AnimationOptionMixin, GeoCommonOptionMixin, StatesOptionMixin, GeoStateOption { + mainType?: 'geo'; show?: boolean; silent?: boolean; @@ -290,6 +291,4 @@ class GeoModel extends ComponentModel { } } -ComponentModel.registerClass(GeoModel); - export default GeoModel; diff --git a/src/coord/geo/geoCreator.ts b/src/coord/geo/geoCreator.ts index b692094c6c..47ad2cfa13 100644 --- a/src/coord/geo/geoCreator.ts +++ b/src/coord/geo/geoCreator.ts @@ -17,8 +17,6 @@ * under the License. */ - -import * as echarts from '../../echarts'; import * as zrUtil from 'zrender/src/core/util'; import Geo from './Geo'; import * as layout from '../../util/layout'; @@ -27,7 +25,7 @@ import geoSourceManager from './geoSourceManager'; import mapDataStorage from './mapDataStorage'; import GeoModel, { GeoOption, RegoinOption } from './GeoModel'; import MapSeries, { MapSeriesOption } from '../../chart/map/MapSeries'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { CoordinateSystemCreator } from '../CoordinateSystem'; import { NameMap } from './geoTypes'; import SeriesModel from '../../model/Series'; @@ -244,6 +242,4 @@ class GeoCreator implements CoordinateSystemCreator { const geoCreator = new GeoCreator(); -echarts.registerCoordinateSystem('geo', geoCreator); - export default geoCreator; diff --git a/src/coord/geo/mapDataStorage.ts b/src/coord/geo/mapDataStorage.ts index 69cd4b41c3..b9562f96bc 100644 --- a/src/coord/geo/mapDataStorage.ts +++ b/src/coord/geo/mapDataStorage.ts @@ -18,7 +18,7 @@ */ import {createHashMap, isString, isArray, each, assert} from 'zrender/src/core/util'; -import {parseXML} from 'zrender/src/tool/parseSVG'; +import {parseXML} from 'zrender/src/tool/parseXML'; import { GeoSpecialAreas, GeoJSON, GeoJSONCompressed } from './geoTypes'; import { Dictionary } from 'zrender/src/core/types'; diff --git a/src/coord/parallel/AxisModel.ts b/src/coord/parallel/AxisModel.ts index c9c163d8c9..31beb29f8a 100644 --- a/src/coord/parallel/AxisModel.ts +++ b/src/coord/parallel/AxisModel.ts @@ -21,7 +21,7 @@ import * as zrUtil from 'zrender/src/core/util'; import ComponentModel from '../../model/Component'; import makeStyleMapper from '../../model/mixin/makeStyleMapper'; -import axisModelCreator, { AxisModelExtendedInCreator } from '../axisModelCreator'; +import { AxisModelExtendedInCreator } from '../axisModelCreator'; import * as numberUtil from '../../util/number'; import {AxisModelCommonMixin} from '../axisModelCommonMixin'; import ParallelAxis from './ParallelAxis'; @@ -139,29 +139,9 @@ class ParallelAxisModel extends ComponentModel { } } - -const defaultOption: ParallelAxisOption = { - type: 'value', - areaSelectStyle: { - width: 20, - borderWidth: 1, - borderColor: 'rgba(160,197,232)', - color: 'rgba(160,197,232)', - opacity: 0.3 - }, - realtime: true, - z: 10 -}; - -ComponentModel.registerClass(ParallelAxisModel); - interface ParallelAxisModel extends AxisModelCommonMixin, AxisModelExtendedInCreator {} zrUtil.mixin(ParallelAxisModel, AxisModelCommonMixin); -axisModelCreator( - 'parallel', ParallelAxisModel, defaultOption -); - export default ParallelAxisModel; diff --git a/src/coord/parallel/Parallel.ts b/src/coord/parallel/Parallel.ts index 657cd3f907..6080b85f2b 100644 --- a/src/coord/parallel/Parallel.ts +++ b/src/coord/parallel/Parallel.ts @@ -33,7 +33,7 @@ import * as numberUtil from '../../util/number'; import sliderMove from '../../component/helper/sliderMove'; import ParallelModel, { ParallelLayoutDirection } from './ParallelModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { Dictionary, DimensionName, ScaleDataValue } from '../../util/types'; import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; import ParallelAxisModel, { ParallelActiveState } from './AxisModel'; diff --git a/src/coord/parallel/ParallelModel.ts b/src/coord/parallel/ParallelModel.ts index 158b20f3ad..2998da9348 100644 --- a/src/coord/parallel/ParallelModel.ts +++ b/src/coord/parallel/ParallelModel.ts @@ -31,6 +31,8 @@ import SeriesModel from '../../model/Series'; export type ParallelLayoutDirection = 'horizontal' | 'vertical'; export interface ParallelCoordinateSystemOption extends ComponentOption, BoxLayoutOptionMixin { + mainType?: 'parallel'; + layout?: ParallelLayoutDirection; axisExpandable?: boolean; @@ -171,6 +173,4 @@ class ParallelModel extends ComponentModel { } -ComponentModel.registerClass(ParallelModel); - export default ParallelModel; diff --git a/src/coord/parallel/parallelCreator.ts b/src/coord/parallel/parallelCreator.ts index ba747ad11e..35b3ae11d2 100644 --- a/src/coord/parallel/parallelCreator.ts +++ b/src/coord/parallel/parallelCreator.ts @@ -24,14 +24,13 @@ import Parallel from './Parallel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import ParallelModel from './ParallelModel'; import { CoordinateSystemMaster } from '../CoordinateSystem'; import ParallelSeriesModel from '../../chart/parallel/ParallelSeries'; -import CoordinateSystemManager from '../../CoordinateSystem'; import { SINGLE_REFERRING } from '../../util/model'; -function create(ecModel: GlobalModel, api: ExtensionAPI): CoordinateSystemMaster[] { +function createParallelCoordSys(ecModel: GlobalModel, api: ExtensionAPI): CoordinateSystemMaster[] { const coordSysList: CoordinateSystemMaster[] = []; ecModel.eachComponent('parallel', function (parallelModel: ParallelModel, idx: number) { @@ -58,5 +57,8 @@ function create(ecModel: GlobalModel, api: ExtensionAPI): CoordinateSystemMaster return coordSysList; } +const parallelCoordSysCreator = { + create: createParallelCoordSys +}; -CoordinateSystemManager.register('parallel', {create: create}); +export default parallelCoordSysCreator; diff --git a/src/coord/polar/AxisModel.ts b/src/coord/polar/AxisModel.ts index f630519074..2afbc3c11e 100644 --- a/src/coord/polar/AxisModel.ts +++ b/src/coord/polar/AxisModel.ts @@ -19,7 +19,7 @@ import * as zrUtil from 'zrender/src/core/util'; import ComponentModel from '../../model/Component'; -import axisModelCreator, { AxisModelExtendedInCreator } from '../axisModelCreator'; +import { AxisModelExtendedInCreator } from '../axisModelCreator'; import {AxisModelCommonMixin} from '../axisModelCommonMixin'; import { AxisBaseOption } from '../axisCommonTypes'; import AngleAxis from './AngleAxis'; @@ -28,34 +28,34 @@ import { AxisBaseModel } from '../AxisBaseModel'; import { SINGLE_REFERRING } from '../../util/model'; export interface AngleAxisOption extends AxisBaseOption { + mainType?: 'angleAxis'; /** * Index of host polar component */ - polarIndex?: number + polarIndex?: number; /** * Id of host polar component */ - polarId?: string + polarId?: string; - startAngle?: number - clockwise?: boolean + startAngle?: number; + clockwise?: boolean; - splitNumber?: number + splitNumber?: number; - axisLabel?: Omit & { - rotate?: AxisBaseOption['axisLabel']['rotate'] - } + axisLabel?: AxisBaseOption['axisLabel'] } export interface RadiusAxisOption extends AxisBaseOption { + mainType?: 'radiusAxis'; /** * Index of host polar component */ - polarIndex?: number + polarIndex?: number; /** * Id of host polar component */ - polarId?: string + polarId?: string; } type PolarAxisOption = AngleAxisOption | RadiusAxisOption; @@ -87,24 +87,4 @@ export class RadiusAxisModel extends PolarAxisModel { static type = 'radiusAxis'; type = RadiusAxisModel.type; axis: RadiusAxis; -} - -const angleAxisExtraOption: AngleAxisOption = { - startAngle: 90, - - clockwise: true, - - splitNumber: 12, - - axisLabel: { - rotate: 0 - } -}; - -const radiusAxisExtraOption: RadiusAxisOption = { - splitNumber: 5 -}; - - -axisModelCreator('angle', AngleAxisModel, angleAxisExtraOption); -axisModelCreator('radius', RadiusAxisModel, radiusAxisExtraOption); +} \ No newline at end of file diff --git a/src/coord/polar/Polar.ts b/src/coord/polar/Polar.ts index e2dd29badd..4e53b13e72 100644 --- a/src/coord/polar/Polar.ts +++ b/src/coord/polar/Polar.ts @@ -22,9 +22,9 @@ import AngleAxis from './AngleAxis'; import PolarModel from './PolarModel'; import { CoordinateSystem, CoordinateSystemMaster, CoordinateSystemClipArea } from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; -import { ParsedModelFinder } from '../../util/model'; +import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; import { ScaleDataValue } from '../../util/types'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; interface Polar { update(ecModel: GlobalModel, api: ExtensionAPI): void @@ -246,7 +246,7 @@ class Polar implements CoordinateSystem, CoordinateSystemMaster { } } -function getCoordSys(finder: ParsedModelFinder) { +function getCoordSys(finder: ParsedModelFinderKnown) { const seriesModel = finder.seriesModel; const polarModel = finder.polarModel as PolarModel; return polarModel && polarModel.coordinateSystem diff --git a/src/coord/polar/PolarModel.ts b/src/coord/polar/PolarModel.ts index d64888b275..da334cbb0c 100644 --- a/src/coord/polar/PolarModel.ts +++ b/src/coord/polar/PolarModel.ts @@ -23,6 +23,7 @@ import Polar from './Polar'; import { AngleAxisModel, RadiusAxisModel } from './AxisModel'; export interface PolarOption extends ComponentOption, CircleLayoutOptionMixin { + mainType?: 'polar'; } class PolarModel extends ComponentModel { @@ -59,6 +60,4 @@ class PolarModel extends ComponentModel { }; } -ComponentModel.registerClass(PolarModel); - export default PolarModel; \ No newline at end of file diff --git a/src/coord/polar/polarCreator.ts b/src/coord/polar/polarCreator.ts index 2b9248ed11..2b00a2df09 100644 --- a/src/coord/polar/polarCreator.ts +++ b/src/coord/polar/polarCreator.ts @@ -27,10 +27,9 @@ import { niceScaleExtent, getDataDimensionsOnAxis } from '../../coord/axisHelper'; -import CoordinateSystem from '../../CoordinateSystem'; import PolarModel from './PolarModel'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import GlobalModel from '../../model/Global'; import OrdinalScale from '../../scale/Ordinal'; import RadiusAxis from './RadiusAxis'; @@ -187,4 +186,4 @@ const polarCreator = { } }; -CoordinateSystem.register('polar', polarCreator); \ No newline at end of file +export default polarCreator; \ No newline at end of file diff --git a/src/coord/radar/Radar.ts b/src/coord/radar/Radar.ts index 5347cd93ec..b6dc92e003 100644 --- a/src/coord/radar/Radar.ts +++ b/src/coord/radar/Radar.ts @@ -19,7 +19,6 @@ // TODO clockwise -import * as zrUtil from 'zrender/src/core/util'; import IndicatorAxis from './IndicatorAxis'; import IntervalScale from '../../scale/Interval'; import * as numberUtil from '../../util/number'; @@ -27,14 +26,15 @@ import { getScaleExtent, niceScaleExtent } from '../axisHelper'; -import CoordinateSystemManager from '../../CoordinateSystem'; +import CoordinateSystemManager from '../../core/CoordinateSystem'; import { CoordinateSystemMaster, CoordinateSystem } from '../CoordinateSystem'; import RadarModel from './RadarModel'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import { ScaleDataValue } from '../../util/types'; import { ParsedModelFinder } from '../../util/model'; import { parseAxisModelMinMax } from '../scaleRawExtentInfo'; +import { map, each } from 'zrender/src/core/util'; class Radar implements CoordinateSystem, CoordinateSystemMaster { @@ -63,7 +63,7 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster { constructor(radarModel: RadarModel, ecModel: GlobalModel, api: ExtensionAPI) { this._model = radarModel; - this._indicatorAxes = zrUtil.map(radarModel.getIndicatorModels(), function (indicatorModel, idx) { + this._indicatorAxes = map(radarModel.getIndicatorModels(), function (indicatorModel, idx) { const dim = 'indicator_' + idx; const indicatorAxis = new IndicatorAxis(dim, new IntervalScale() @@ -144,7 +144,7 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster { this.r0 = numberUtil.parsePercent(radius[0], viewSize); this.r = numberUtil.parsePercent(radius[1], viewSize); - zrUtil.each(this._indicatorAxes, function (indicatorAxis, idx) { + each(this._indicatorAxes, function (indicatorAxis, idx) { indicatorAxis.setExtent(this.r0, this.r); let angle = (this.startAngle + idx * Math.PI * 2 / this._indicatorAxes.length); // Normalize to [-PI, PI] @@ -156,7 +156,7 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster { update(ecModel: GlobalModel, api: ExtensionAPI) { const indicatorAxes = this._indicatorAxes; const radarModel = this._model; - zrUtil.each(indicatorAxes, function (indicatorAxis) { + each(indicatorAxes, function (indicatorAxis) { indicatorAxis.scale.setExtent(Infinity, -Infinity); }); ecModel.eachSeriesByType('radar', function (radarSeries, idx) { @@ -167,7 +167,7 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster { return; } const data = radarSeries.getData(); - zrUtil.each(indicatorAxes, function (indicatorAxis) { + each(indicatorAxes, function (indicatorAxis) { indicatorAxis.scale.unionExtentFromData(data, data.mapDimension(indicatorAxis.dim)); }); }, this); @@ -187,7 +187,7 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster { return f * exp10; } // Force all the axis fixing the maxSplitNumber. - zrUtil.each(indicatorAxes, function (indicatorAxis, idx) { + each(indicatorAxes, function (indicatorAxis, idx) { const rawExtent = getScaleExtent(indicatorAxis.scale, indicatorAxis.model).extent; niceScaleExtent(indicatorAxis.scale, indicatorAxis.model); @@ -277,6 +277,4 @@ class Radar implements CoordinateSystem, CoordinateSystemMaster { } -CoordinateSystemManager.register('radar', Radar); - export default Radar; \ No newline at end of file diff --git a/src/coord/radar/RadarModel.ts b/src/coord/radar/RadarModel.ts index e0cd22063f..070892f064 100644 --- a/src/coord/radar/RadarModel.ts +++ b/src/coord/radar/RadarModel.ts @@ -51,6 +51,8 @@ export interface RadarIndicatorOption { } export interface RadarOption extends ComponentOption, CircleLayoutOptionMixin { + mainType?: 'radar' + startAngle?: number shape?: 'polygon' | 'circle' @@ -138,7 +140,7 @@ class RadarModel extends ComponentModel implements CoordinateSystem // min: 0, nameTextStyle: iNameTextStyle, triggerEvent: triggerEvent - }, false); + } as InnerIndicatorAxisOption, false); if (!showName) { innerIndicatorOpt.name = ''; } @@ -216,6 +218,4 @@ class RadarModel extends ComponentModel implements CoordinateSystem }; } -ComponentModel.registerClass(RadarModel); - export default RadarModel; diff --git a/src/coord/single/AxisModel.ts b/src/coord/single/AxisModel.ts index 31fb2e1257..7f761387b9 100644 --- a/src/coord/single/AxisModel.ts +++ b/src/coord/single/AxisModel.ts @@ -17,19 +17,20 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; import ComponentModel from '../../model/Component'; -import axisModelCreator, { AxisModelExtendedInCreator } from '../axisModelCreator'; +import { AxisModelExtendedInCreator } from '../axisModelCreator'; import {AxisModelCommonMixin} from '../axisModelCommonMixin'; import Single from './Single'; import SingleAxis from './SingleAxis'; import { AxisBaseOption } from '../axisCommonTypes'; import { BoxLayoutOptionMixin, LayoutOrient } from '../../util/types'; import { AxisBaseModel } from '../AxisBaseModel'; +import { mixin } from 'zrender/src/core/util'; export type SingleAxisPosition = 'top' | 'bottom' | 'left' | 'right'; export interface SingleAxisOption extends AxisBaseOption, BoxLayoutOptionMixin { + mainType?: 'singleAxis' position?: SingleAxisPosition orient?: LayoutOrient } @@ -100,13 +101,9 @@ class SingleAxisModel extends ComponentModel }; } -ComponentModel.registerClass(SingleAxisModel); - interface SingleAxisModel extends AxisModelCommonMixin, AxisModelExtendedInCreator {} -zrUtil.mixin(SingleAxisModel, AxisModelCommonMixin.prototype); - -axisModelCreator('single', SingleAxisModel, SingleAxisModel.defaultOption); +mixin(SingleAxisModel, AxisModelCommonMixin.prototype); export default SingleAxisModel; \ No newline at end of file diff --git a/src/coord/single/Single.ts b/src/coord/single/Single.ts index 4d5324e58d..cee845b8c9 100644 --- a/src/coord/single/Single.ts +++ b/src/coord/single/Single.ts @@ -27,10 +27,10 @@ import {getLayoutRect} from '../../util/layout'; import {each} from 'zrender/src/core/util'; import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import BoundingRect from 'zrender/src/core/BoundingRect'; import SingleAxisModel from './AxisModel'; -import { ParsedModelFinder } from '../../util/model'; +import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model'; import { ScaleDataValue } from '../../util/types'; /** @@ -249,7 +249,7 @@ class Single implements CoordinateSystem, CoordinateSystemMaster { } } -function getCoordSys(finder: ParsedModelFinder): Single { +function getCoordSys(finder: ParsedModelFinderKnown): Single { const seriesModel = finder.seriesModel; const singleModel = finder.singleAxisModel as SingleAxisModel; return singleModel && singleModel.coordinateSystem diff --git a/src/coord/single/prepareCustom.ts b/src/coord/single/prepareCustom.ts index 45b005377a..0ca8e46942 100644 --- a/src/coord/single/prepareCustom.ts +++ b/src/coord/single/prepareCustom.ts @@ -17,8 +17,8 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; import Single from './Single'; +import { bind } from 'zrender/src/core/util'; function dataToCoordSize(this: Single, dataSize: number | number[], dataItem: number | number[]) { // dataItem is necessary in log axis. @@ -46,7 +46,7 @@ export default function singlePrepareCustom(coordSys: Single) { // do not provide "out" param return coordSys.dataToPoint(val); }, - size: zrUtil.bind(dataToCoordSize, coordSys) + size: bind(dataToCoordSize, coordSys) } }; } diff --git a/src/coord/single/singleCreator.ts b/src/coord/single/singleCreator.ts index ba432cf9db..537395038d 100644 --- a/src/coord/single/singleCreator.ts +++ b/src/coord/single/singleCreator.ts @@ -22,9 +22,8 @@ */ import Single from './Single'; -import CoordinateSystem from '../../CoordinateSystem'; import GlobalModel from '../../model/Global'; -import ExtensionAPI from '../../ExtensionAPI'; +import ExtensionAPI from '../../core/ExtensionAPI'; import SingleAxisModel from './AxisModel'; import SeriesModel from '../../model/Series'; import { SeriesOption } from '../../util/types'; @@ -61,7 +60,9 @@ function create(ecModel: GlobalModel, api: ExtensionAPI) { return singles; } -CoordinateSystem.register('single', { +const singleCreator = { create: create, dimensions: Single.prototype.dimensions -}); \ No newline at end of file +}; + +export default singleCreator; \ No newline at end of file diff --git a/src/CoordinateSystem.ts b/src/core/CoordinateSystem.ts similarity index 91% rename from src/CoordinateSystem.ts rename to src/core/CoordinateSystem.ts index a1c2e5bcdc..1e58d66478 100644 --- a/src/CoordinateSystem.ts +++ b/src/core/CoordinateSystem.ts @@ -18,9 +18,10 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import GlobalModel from './model/Global'; -import ExtensionAPI from './ExtensionAPI'; -import { CoordinateSystemCreator, CoordinateSystemMaster } from './coord/CoordinateSystem'; + +import type GlobalModel from '../model/Global'; +import type ExtensionAPI from './ExtensionAPI'; +import type { CoordinateSystemCreator, CoordinateSystemMaster } from '../coord/CoordinateSystem'; const coordinateSystemCreators: {[type: string]: CoordinateSystemCreator} = {}; diff --git a/src/ExtensionAPI.ts b/src/core/ExtensionAPI.ts similarity index 86% rename from src/ExtensionAPI.ts rename to src/core/ExtensionAPI.ts index 58550b63f5..c52fe7d8df 100644 --- a/src/ExtensionAPI.ts +++ b/src/core/ExtensionAPI.ts @@ -19,13 +19,14 @@ import * as zrUtil from 'zrender/src/core/util'; import {EChartsType} from './echarts'; -import {CoordinateSystemMaster} from './coord/CoordinateSystem'; -import Element from 'zrender/src/Element'; -import ComponentModel from './model/Component'; -import ComponentView from './view/Component'; -import ChartView from './view/Chart'; -import SeriesModel from './model/Series'; -import GlobalModel from './model/Global'; + +import type {CoordinateSystemMaster} from '../coord/CoordinateSystem'; +import type Element from 'zrender/src/Element'; +import type ComponentModel from '../model/Component'; +import type ComponentView from '../view/Component'; +import type ChartView from '../view/Chart'; +import type SeriesModel from '../model/Series'; +import type GlobalModel from '../model/Global'; const availableMethods: (keyof EChartsType)[] = [ 'getDom', diff --git a/src/stream/Scheduler.ts b/src/core/Scheduler.ts similarity index 99% rename from src/stream/Scheduler.ts rename to src/core/Scheduler.ts index ded74c40c8..2081e4a094 100644 --- a/src/stream/Scheduler.ts +++ b/src/core/Scheduler.ts @@ -24,13 +24,13 @@ import { } from './task'; import {getUID} from '../util/component'; import GlobalModel from '../model/Global'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from './ExtensionAPI'; import {normalizeToArray} from '../util/model'; import { StageHandlerInternal, StageHandlerOverallReset, StageHandler, Payload, StageHandlerReset, StageHandlerPlan, StageHandlerProgressExecutor, SeriesLargeOptionMixin, SeriesOption } from '../util/types'; -import { EChartsType } from '../echarts'; +import { EChartsType } from './echarts'; import SeriesModel from '../model/Series'; import ChartView from '../view/Chart'; import List from '../data/List'; diff --git a/src/core/echarts.ts b/src/core/echarts.ts new file mode 100644 index 0000000000..069ef7f986 --- /dev/null +++ b/src/core/echarts.ts @@ -0,0 +1,2960 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +import * as zrender from 'zrender/src/zrender'; +import * as zrUtil from 'zrender/src/core/util'; +import * as colorTool from 'zrender/src/tool/color'; +import env from 'zrender/src/core/env'; +import timsort from 'zrender/src/core/timsort'; +import Eventful from 'zrender/src/core/Eventful'; +import Element, { ElementEvent } from 'zrender/src/Element'; +import GlobalModel, {QueryConditionKindA, GlobalModelSetOptionOpts} from '../model/Global'; +import ExtensionAPI from './ExtensionAPI'; +import CoordinateSystemManager from './CoordinateSystem'; +import OptionManager from '../model/OptionManager'; +import backwardCompat from '../preprocessor/backwardCompat'; +import dataStack from '../processor/dataStack'; +import ComponentModel, { ComponentModelConstructor } from '../model/Component'; +import SeriesModel, { SeriesModelConstructor } from '../model/Series'; +import ComponentView, {ComponentViewConstructor} from '../view/Component'; +import ChartView, {ChartViewConstructor} from '../view/Chart'; +import * as graphic from '../util/graphic'; +import {getECData} from '../util/innerStore'; +import { + enterEmphasisWhenMouseOver, + leaveEmphasisWhenMouseOut, + isHighDownDispatcher, + HOVER_STATE_EMPHASIS, + HOVER_STATE_BLUR, + toggleSeriesBlurState, + toggleSeriesBlurStateFromPayload, + toggleSelectionFromPayload, + updateSeriesElementSelection, + getAllSelectedIndices, + isSelectChangePayload, + isHighDownPayload, + HIGHLIGHT_ACTION_TYPE, + DOWNPLAY_ACTION_TYPE, + SELECT_ACTION_TYPE, + UNSELECT_ACTION_TYPE, + TOGGLE_SELECT_ACTION_TYPE, + savePathStates, + enterEmphasis, + leaveEmphasis, + leaveBlur, + enterSelect, + leaveSelect, + enterBlur +} from '../util/states'; +import * as modelUtil from '../util/model'; +import {throttle} from '../util/throttle'; +import {seriesStyleTask, dataStyleTask, dataColorPaletteTask} from '../visual/style'; +import loadingDefault from '../loading/default'; +import Scheduler from './Scheduler'; +import lightTheme from '../theme/light'; +import darkTheme from '../theme/dark'; +import mapDataStorage from '../coord/geo/mapDataStorage'; +import {CoordinateSystemMaster, CoordinateSystemCreator, CoordinateSystemHostModel} from '../coord/CoordinateSystem'; +import { parseClassType } from '../util/clazz'; +import {ECEventProcessor} from '../util/ECEventProcessor'; +import { + Payload, ECElement, RendererType, ECEvent, + ActionHandler, ActionInfo, OptionPreprocessor, PostUpdater, + LoadingEffect, LoadingEffectCreator, StageHandlerInternal, + StageHandlerOverallReset, StageHandler, + ViewRootGroup, DimensionDefinitionLoose, ECEventData, ThemeOption, + ECBasicOption, + ECUnitOption, + ZRColor, + ComponentMainType, + ComponentSubType, + ColorString, + SelectChangedPayload, + DimensionLoose, + ScaleDataValue +} from '../util/types'; +import Displayable from 'zrender/src/graphic/Displayable'; +import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable'; +import { seriesSymbolTask, dataSymbolTask } from '../visual/symbol'; +import { getVisualFromData, getItemVisualFromData } from '../visual/helper'; +import LabelManager from '../label/LabelManager'; +import { deprecateLog, throwError } from '../util/log'; +import { handleLegacySelectEvents } from '../legacy/dataSelectAction'; + +import { registerExternalTransform } from '../data/helper/transform'; +import { createLocaleObject, SYSTEM_LANG, LocaleOption } from './locale'; + +import type {EChartsOption} from '../export/option'; +import { findEventDispatcher } from '../util/event'; +import decal from '../visual/decal'; +import type {MorphDividingMethod} from 'zrender/src/tool/morphPath'; +import CanvasPainter from 'zrender/src/canvas/Painter'; +import SVGPainter from 'zrender/src/svg/Painter'; + +declare let global: any; +type ModelFinder = modelUtil.ModelFinder; + +const assert = zrUtil.assert; +const each = zrUtil.each; +const isFunction = zrUtil.isFunction; +const isObject = zrUtil.isObject; +const indexOf = zrUtil.indexOf; + +export const version = '5.0.0'; + +export const dependencies = { + zrender: '5.0.1' +}; + +const TEST_FRAME_REMAIN_TIME = 1; + +const PRIORITY_PROCESSOR_SERIES_FILTER = 800; +// Some data processors depends on the stack result dimension (to calculate data extent). +// So data stack stage should be in front of data processing stage. +const PRIORITY_PROCESSOR_DATASTACK = 900; +// "Data filter" will block the stream, so it should be +// put at the begining of data processing. +const PRIORITY_PROCESSOR_FILTER = 1000; +const PRIORITY_PROCESSOR_DEFAULT = 2000; +const PRIORITY_PROCESSOR_STATISTIC = 5000; + +const PRIORITY_VISUAL_LAYOUT = 1000; +const PRIORITY_VISUAL_PROGRESSIVE_LAYOUT = 1100; +const PRIORITY_VISUAL_GLOBAL = 2000; +const PRIORITY_VISUAL_CHART = 3000; +const PRIORITY_VISUAL_COMPONENT = 4000; +// Visual property in data. Greater than `PRIORITY_VISUAL_COMPONENT` to enable to +// overwrite the viusal result of component (like `visualMap`) +// using data item specific setting (like itemStyle.xxx on data item) +const PRIORITY_VISUAL_CHART_DATA_CUSTOM = 4500; +// Greater than `PRIORITY_VISUAL_CHART_DATA_CUSTOM` to enable to layout based on +// visual result like `symbolSize`. +const PRIORITY_VISUAL_POST_CHART_LAYOUT = 4600; +const PRIORITY_VISUAL_BRUSH = 5000; +const PRIORITY_VISUAL_ARIA = 6000; +const PRIORITY_VISUAL_DECAL = 7000; + +export const PRIORITY = { + PROCESSOR: { + FILTER: PRIORITY_PROCESSOR_FILTER, + SERIES_FILTER: PRIORITY_PROCESSOR_SERIES_FILTER, + STATISTIC: PRIORITY_PROCESSOR_STATISTIC + }, + VISUAL: { + LAYOUT: PRIORITY_VISUAL_LAYOUT, + PROGRESSIVE_LAYOUT: PRIORITY_VISUAL_PROGRESSIVE_LAYOUT, + GLOBAL: PRIORITY_VISUAL_GLOBAL, + CHART: PRIORITY_VISUAL_CHART, + POST_CHART_LAYOUT: PRIORITY_VISUAL_POST_CHART_LAYOUT, + COMPONENT: PRIORITY_VISUAL_COMPONENT, + BRUSH: PRIORITY_VISUAL_BRUSH, + CHART_ITEM: PRIORITY_VISUAL_CHART_DATA_CUSTOM, + ARIA: PRIORITY_VISUAL_ARIA, + DECAL: PRIORITY_VISUAL_DECAL + } +}; + +// Main process have three entries: `setOption`, `dispatchAction` and `resize`, +// where they must not be invoked nestedly, except the only case: invoke +// dispatchAction with updateMethod "none" in main process. +// This flag is used to carry out this rule. +// All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]). +const IN_MAIN_PROCESS_KEY = '__flagInMainProcess' as const; +const OPTION_UPDATED_KEY = '__optionUpdated' as const; +const STATUS_NEEDS_UPDATE_KEY = '__needsUpdateStatus' as const; +const ACTION_REG = /^[a-zA-Z0-9_]+$/; + +const CONNECT_STATUS_KEY = '__connectUpdateStatus' as const; +const CONNECT_STATUS_PENDING = 0 as const; +const CONNECT_STATUS_UPDATING = 1 as const; +const CONNECT_STATUS_UPDATED = 2 as const; +type ConnectStatus = + typeof CONNECT_STATUS_PENDING + | typeof CONNECT_STATUS_UPDATING + | typeof CONNECT_STATUS_UPDATED; + +interface SetOptionOpts { + notMerge?: boolean; + lazyUpdate?: boolean; + silent?: boolean; + // Rule: only `id` mapped will be merged, + // other components of the certain `mainType` will be removed. + replaceMerge?: GlobalModelSetOptionOpts['replaceMerge']; + transition?: SetOptionTransitionOpt +}; + +export interface SetOptionTransitionOptItem { + // If `from` not given, it means that do not make series transition mandatorily. + // There might be transition mapping dy default. Sometimes we do not need them, + // which might bring about misleading. + from?: SetOptionTransitionOptFinder; + to: SetOptionTransitionOptFinder; + dividingMethod: MorphDividingMethod; +} +interface SetOptionTransitionOptFinder extends modelUtil.ModelFinderObject { + dimension: DimensionLoose; +} +type SetOptionTransitionOpt = SetOptionTransitionOptItem | SetOptionTransitionOptItem[]; + +interface PostIniter { + (chart: EChartsType): void +} + +type EventMethodName = 'on' | 'off'; +function createRegisterEventWithLowercaseECharts(method: EventMethodName) { + return function (this: ECharts, ...args: any): ECharts { + if (this.isDisposed()) { + disposedWarning(this.id); + return; + } + return toLowercaseNameAndCallEventful(this, method, args); + }; +} +function createRegisterEventWithLowercaseMessageCenter(method: EventMethodName) { + return function (this: MessageCenter, ...args: any): MessageCenter { + return toLowercaseNameAndCallEventful(this, method, args); + }; +} +function toLowercaseNameAndCallEventful(host: T, method: EventMethodName, args: any): T { + // `args[0]` is event name. Event name is all lowercase. + args[0] = args[0] && args[0].toLowerCase(); + return Eventful.prototype[method].apply(host, args) as any; +} + + +class MessageCenter extends Eventful {} +const messageCenterProto = MessageCenter.prototype; +messageCenterProto.on = createRegisterEventWithLowercaseMessageCenter('on'); +messageCenterProto.off = createRegisterEventWithLowercaseMessageCenter('off'); + +// --------------------------------------- +// Internal method names for class ECharts +// --------------------------------------- +let prepare: (ecIns: ECharts) => void; +let prepareView: (ecIns: ECharts, isComponent: boolean) => void; +let updateDirectly: ( + ecIns: ECharts, method: string, payload: Payload, mainType: ComponentMainType, subType?: ComponentSubType +) => void; +type UpdateMethod = (this: ECharts, payload?: Payload) => void; +let updateMethods: { + prepareAndUpdate: UpdateMethod, + update: UpdateMethod, + updateTransform: UpdateMethod, + updateView: UpdateMethod, + updateVisual: UpdateMethod, + updateLayout: UpdateMethod +}; +let doConvertPixel: ( + ecIns: ECharts, + methodName: string, + finder: ModelFinder, + value: (number | number[]) | (ScaleDataValue | ScaleDataValue[]) +) => (number | number[]); +let updateStreamModes: (ecIns: ECharts, ecModel: GlobalModel) => void; +let doDispatchAction: (this: ECharts, payload: Payload, silent: boolean) => void; +let flushPendingActions: (this: ECharts, silent: boolean) => void; +let triggerUpdatedEvent: (this: ECharts, silent: boolean) => void; +let bindRenderedEvent: (zr: zrender.ZRenderType, ecIns: ECharts) => void; +let bindMouseEvent: (zr: zrender.ZRenderType, ecIns: ECharts) => void; +let clearColorPalette: (ecModel: GlobalModel) => void; +let render: (ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) => void; +let renderComponents: ( + ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, dirtyList?: ComponentView[] +) => void; +let renderSeries: ( + ecIns: ECharts, + ecModel: GlobalModel, + api: ExtensionAPI, + payload: Payload | 'remain', + dirtyMap?: {[uid: string]: any} +) => void; +let performPostUpdateFuncs: (ecModel: GlobalModel, api: ExtensionAPI) => void; +let createExtensionAPI: (ecIns: ECharts) => ExtensionAPI; +let enableConnect: (ecIns: ECharts) => void; +let setTransitionOpt: ( + chart: ECharts, + transitionOpt: SetOptionTransitionOpt +) => void; + +let markStatusToUpdate: (ecIns: ECharts) => void; +let applyChangedStates: (ecIns: ECharts) => void; +class ECharts extends Eventful { + + /** + * @readonly + */ + id: string; + + /** + * Group id + * @readonly + */ + group: string; + + private _zr: zrender.ZRenderType; + + private _dom: HTMLElement; + + private _model: GlobalModel; + + private _throttledZrFlush: zrender.ZRenderType extends {flush: infer R} ? R : never; + + private _theme: ThemeOption; + + private _locale: LocaleOption; + + private _chartsViews: ChartView[] = []; + + private _chartsMap: {[viewId: string]: ChartView} = {}; + + private _componentsViews: ComponentView[] = []; + + private _componentsMap: {[viewId: string]: ComponentView} = {}; + + private _coordSysMgr: CoordinateSystemManager; + + private _api: ExtensionAPI; + + private _scheduler: Scheduler; + + private _messageCenter: MessageCenter; + + // Can't dispatch action during rendering procedure + private _pendingActions: Payload[] = []; + + // We use never here so ECEventProcessor will not been exposed. + // which may include many unexpected types won't be exposed in the types to developers. + protected _$eventProcessor: never; + + private _disposed: boolean; + + private _loadingFX: LoadingEffect; + + private _labelManager: LabelManager; + + + private [OPTION_UPDATED_KEY]: boolean | {silent: boolean}; + private [IN_MAIN_PROCESS_KEY]: boolean; + private [CONNECT_STATUS_KEY]: ConnectStatus; + private [STATUS_NEEDS_UPDATE_KEY]: boolean; + + constructor( + dom: HTMLElement, + // Theme name or themeOption. + theme?: string | ThemeOption, + opts?: { + locale?: string | LocaleOption, + renderer?: RendererType, + devicePixelRatio?: number, + useDirtyRect?: boolean, + width?: number, + height?: number + } + ) { + super(new ECEventProcessor()); + + opts = opts || {}; + + // Get theme by name + if (typeof theme === 'string') { + theme = themeStorage[theme] as object; + } + + this._dom = dom; + + const root = ( + typeof window === 'undefined' ? global : window + ) as any; + + let defaultRenderer = 'canvas'; + let defaultUseDirtyRect = false; + if (__DEV__) { + defaultRenderer = root.__ECHARTS__DEFAULT__RENDERER__ || defaultRenderer; + + const devUseDirtyRect = root.__ECHARTS__DEFAULT__USE_DIRTY_RECT__; + defaultUseDirtyRect = devUseDirtyRect == null + ? defaultUseDirtyRect + : devUseDirtyRect; + } + + const zr = this._zr = zrender.init(dom, { + renderer: opts.renderer || defaultRenderer, + devicePixelRatio: opts.devicePixelRatio, + width: opts.width, + height: opts.height, + useDirtyRect: opts.useDirtyRect == null ? defaultUseDirtyRect : opts.useDirtyRect + }); + + // Expect 60 fps. + this._throttledZrFlush = throttle(zrUtil.bind(zr.flush, zr), 17); + + theme = zrUtil.clone(theme); + theme && backwardCompat(theme as ECUnitOption, true); + + this._theme = theme; + + this._locale = createLocaleObject(opts.locale || SYSTEM_LANG); + + this._coordSysMgr = new CoordinateSystemManager(); + + const api = this._api = createExtensionAPI(this); + + // Sort on demand + function prioritySortFunc(a: StageHandlerInternal, b: StageHandlerInternal): number { + return a.__prio - b.__prio; + } + timsort(visualFuncs, prioritySortFunc); + timsort(dataProcessorFuncs, prioritySortFunc); + + this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs); + + this._messageCenter = new MessageCenter(); + + this._labelManager = new LabelManager(); + + // Init mouse events + this._initEvents(); + + // In case some people write `window.onresize = chart.resize` + this.resize = zrUtil.bind(this.resize, this); + + zr.animation.on('frame', this._onframe, this); + + bindRenderedEvent(zr, this); + + bindMouseEvent(zr, this); + + // ECharts instance can be used as value. + zrUtil.setAsPrimitive(this); + } + + private _onframe(): void { + if (this._disposed) { + return; + } + + applyChangedStates(this); + + const scheduler = this._scheduler; + + // Lazy update + if (this[OPTION_UPDATED_KEY]) { + const silent = (this[OPTION_UPDATED_KEY] as any).silent; + + this[IN_MAIN_PROCESS_KEY] = true; + + prepare(this); + updateMethods.update.call(this); + + // At present, in each frame, zrender performs: + // (1) animation step forward. + // (2) trigger('frame') (where this `_onframe` is called) + // (3) zrender flush (render). + // If we do nothing here, since we use `setToFinal: true`, the step (3) above + // will render the final state of the elements before the real animation started. + this._zr.flush(); + + this[IN_MAIN_PROCESS_KEY] = false; + + this[OPTION_UPDATED_KEY] = false; + + flushPendingActions.call(this, silent); + + triggerUpdatedEvent.call(this, silent); + } + // Avoid do both lazy update and progress in one frame. + else if (scheduler.unfinished) { + // Stream progress. + let remainTime = TEST_FRAME_REMAIN_TIME; + const ecModel = this._model; + const api = this._api; + scheduler.unfinished = false; + do { + const startTime = +new Date(); + + scheduler.performSeriesTasks(ecModel); + + // Currently dataProcessorFuncs do not check threshold. + scheduler.performDataProcessorTasks(ecModel); + + updateStreamModes(this, ecModel); + + // Do not update coordinate system here. Because that coord system update in + // each frame is not a good user experience. So we follow the rule that + // the extent of the coordinate system is determin in the first frame (the + // frame is executed immedietely after task reset. + // this._coordSysMgr.update(ecModel, api); + + // console.log('--- ec frame visual ---', remainTime); + scheduler.performVisualTasks(ecModel); + + renderSeries(this, this._model, api, 'remain'); + + remainTime -= (+new Date() - startTime); + } + while (remainTime > 0 && scheduler.unfinished); + + // Call flush explicitly for trigger finished event. + if (!scheduler.unfinished) { + this._zr.flush(); + } + // Else, zr flushing be ensue within the same frame, + // because zr flushing is after onframe event. + } + } + + getDom(): HTMLElement { + return this._dom; + } + + getId(): string { + return this.id; + } + + getZr(): zrender.ZRenderType { + return this._zr; + } + + /** + * Usage: + * chart.setOption(option, notMerge, lazyUpdate); + * chart.setOption(option, { + * notMerge: ..., + * lazyUpdate: ..., + * silent: ... + * }); + * + * @param opts opts or notMerge. + * @param opts.notMerge Default `false`. + * @param opts.lazyUpdate Default `false`. Useful when setOption frequently. + * @param opts.silent Default `false`. + * @param opts.replaceMerge Default undefined. + */ + // Expose to user full option. + setOption(option: Opt, notMerge?: boolean, lazyUpdate?: boolean): void; + setOption(option: Opt, opts?: SetOptionOpts): void; + /* eslint-disable-next-line */ + setOption(option: Opt, notMerge?: boolean | SetOptionOpts, lazyUpdate?: boolean): void { + if (__DEV__) { + assert(!this[IN_MAIN_PROCESS_KEY], '`setOption` should not be called during main process.'); + } + if (this._disposed) { + disposedWarning(this.id); + return; + } + + let silent; + let replaceMerge; + let transitionOpt: SetOptionTransitionOpt; + if (isObject(notMerge)) { + lazyUpdate = notMerge.lazyUpdate; + silent = notMerge.silent; + replaceMerge = notMerge.replaceMerge; + transitionOpt = notMerge.transition; + notMerge = notMerge.notMerge; + } + + this[IN_MAIN_PROCESS_KEY] = true; + + if (!this._model || notMerge) { + const optionManager = new OptionManager(this._api); + const theme = this._theme; + const ecModel = this._model = new GlobalModel(); + ecModel.scheduler = this._scheduler; + ecModel.init(null, null, null, theme, this._locale, optionManager); + } + + this._model.setOption(option as ECBasicOption, { replaceMerge }, optionPreprocessorFuncs); + + setTransitionOpt(this, transitionOpt); + + if (lazyUpdate) { + this[OPTION_UPDATED_KEY] = {silent: silent}; + this[IN_MAIN_PROCESS_KEY] = false; + + // `setOption(option, {lazyMode: true})` may be called when zrender has been slept. + // It should wake it up to make sure zrender start to render at the next frame. + this.getZr().wakeUp(); + } + else { + prepare(this); + + updateMethods.update.call(this); + + // Ensure zr refresh sychronously, and then pixel in canvas can be + // fetched after `setOption`. + this._zr.flush(); + + this[OPTION_UPDATED_KEY] = false; + this[IN_MAIN_PROCESS_KEY] = false; + + flushPendingActions.call(this, silent); + triggerUpdatedEvent.call(this, silent); + } + } + + /** + * @DEPRECATED + */ + private setTheme(): void { + console.error('ECharts#setTheme() is DEPRECATED in ECharts 3.0'); + } + + // We don't want developers to use getModel directly. + private getModel(): GlobalModel { + return this._model; + } + + getOption(): ECBasicOption { + return this._model && this._model.getOption() as ECBasicOption; + } + + getWidth(): number { + return this._zr.getWidth(); + } + + getHeight(): number { + return this._zr.getHeight(); + } + + getDevicePixelRatio(): number { + return (this._zr.painter as CanvasPainter).dpr || window.devicePixelRatio || 1; + } + + /** + * Get canvas which has all thing rendered + */ + getRenderedCanvas(opts?: { + backgroundColor?: ZRColor + pixelRatio?: number + }): HTMLCanvasElement { + if (!env.canvasSupported) { + return; + } + opts = zrUtil.extend({}, opts || {}); + opts.pixelRatio = opts.pixelRatio || 1; + opts.backgroundColor = opts.backgroundColor + || this._model.get('backgroundColor'); + const zr = this._zr; + // let list = zr.storage.getDisplayList(); + // Stop animations + // Never works before in init animation, so remove it. + // zrUtil.each(list, function (el) { + // el.stopAnimation(true); + // }); + return (zr.painter as CanvasPainter).getRenderedCanvas(opts); + } + + /** + * Get svg data url + */ + getSvgDataURL(): string { + if (!env.svgSupported) { + return; + } + + const zr = this._zr; + const list = zr.storage.getDisplayList(); + // Stop animations + zrUtil.each(list, function (el: Element) { + el.stopAnimation(null, true); + }); + + return (zr.painter as SVGPainter).toDataURL(); + } + + getDataURL(opts?: { + // file type 'png' by default + type?: 'png' | 'jpg' | 'svg', + pixelRatio?: number, + backgroundColor?: ZRColor, + // component type array + excludeComponents?: ComponentMainType[] + }): string { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + opts = opts || {}; + const excludeComponents = opts.excludeComponents; + const ecModel = this._model; + const excludesComponentViews: ComponentView[] = []; + const self = this; + + each(excludeComponents, function (componentType) { + ecModel.eachComponent({ + mainType: componentType + }, function (component) { + const view = self._componentsMap[component.__viewId]; + if (!view.group.ignore) { + excludesComponentViews.push(view); + view.group.ignore = true; + } + }); + }); + + const url = this._zr.painter.getType() === 'svg' + ? this.getSvgDataURL() + : this.getRenderedCanvas(opts).toDataURL( + 'image/' + (opts && opts.type || 'png') + ); + + each(excludesComponentViews, function (view) { + view.group.ignore = false; + }); + + return url; + } + + getConnectedDataURL(opts?: { + // file type 'png' by default + type?: 'png' | 'jpg' | 'svg', + pixelRatio?: number, + backgroundColor?: ZRColor, + connectedBackgroundColor?: ZRColor + excludeComponents?: string[] + }): string { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + if (!env.canvasSupported) { + return; + } + const isSvg = opts.type === 'svg'; + const groupId = this.group; + const mathMin = Math.min; + const mathMax = Math.max; + const MAX_NUMBER = Infinity; + if (connectedGroups[groupId]) { + let left = MAX_NUMBER; + let top = MAX_NUMBER; + let right = -MAX_NUMBER; + let bottom = -MAX_NUMBER; + const canvasList: {dom: HTMLCanvasElement | string, left: number, top: number}[] = []; + const dpr = (opts && opts.pixelRatio) || 1; + + zrUtil.each(instances, function (chart, id) { + if (chart.group === groupId) { + const canvas = isSvg + ? (chart.getZr().painter as SVGPainter).getSvgDom().innerHTML + : chart.getRenderedCanvas(zrUtil.clone(opts)); + const boundingRect = chart.getDom().getBoundingClientRect(); + left = mathMin(boundingRect.left, left); + top = mathMin(boundingRect.top, top); + right = mathMax(boundingRect.right, right); + bottom = mathMax(boundingRect.bottom, bottom); + canvasList.push({ + dom: canvas, + left: boundingRect.left, + top: boundingRect.top + }); + } + }); + + left *= dpr; + top *= dpr; + right *= dpr; + bottom *= dpr; + const width = right - left; + const height = bottom - top; + const targetCanvas = zrUtil.createCanvas(); + const zr = zrender.init(targetCanvas, { + renderer: isSvg ? 'svg' : 'canvas' + }); + zr.resize({ + width: width, + height: height + }); + + if (isSvg) { + let content = ''; + each(canvasList, function (item) { + const x = item.left - left; + const y = item.top - top; + content += '' + item.dom + ''; + }); + (zr.painter as SVGPainter).getSvgRoot().innerHTML = content; + + if (opts.connectedBackgroundColor) { + (zr.painter as SVGPainter).setBackgroundColor(opts.connectedBackgroundColor as string); + } + + zr.refreshImmediately(); + return (zr.painter as SVGPainter).toDataURL(); + } + else { + // Background between the charts + if (opts.connectedBackgroundColor) { + zr.add(new graphic.Rect({ + shape: { + x: 0, + y: 0, + width: width, + height: height + }, + style: { + fill: opts.connectedBackgroundColor + } + })); + } + + each(canvasList, function (item) { + const img = new graphic.Image({ + style: { + x: item.left * dpr - left, + y: item.top * dpr - top, + image: item.dom + } + }); + zr.add(img); + }); + zr.refreshImmediately(); + + return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png')); + } + } + else { + return this.getDataURL(opts); + } + } + + /** + * Convert from logical coordinate system to pixel coordinate system. + * See CoordinateSystem#convertToPixel. + */ + convertToPixel(finder: ModelFinder, value: ScaleDataValue): number; + convertToPixel(finder: ModelFinder, value: ScaleDataValue[]): number[]; + convertToPixel(finder: ModelFinder, value: ScaleDataValue | ScaleDataValue[]): number | number[] { + return doConvertPixel(this, 'convertToPixel', finder, value); + } + + /** + * Convert from pixel coordinate system to logical coordinate system. + * See CoordinateSystem#convertFromPixel. + */ + convertFromPixel(finder: ModelFinder, value: number): number; + convertFromPixel(finder: ModelFinder, value: number[]): number[]; + convertFromPixel(finder: ModelFinder, value: number | number[]): number | number[] { + return doConvertPixel(this, 'convertFromPixel', finder, value); + } + + /** + * Is the specified coordinate systems or components contain the given pixel point. + * @param {Array|number} value + * @return {boolean} result + */ + containPixel(finder: ModelFinder, value: number[]): boolean { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + const ecModel = this._model; + let result: boolean; + + const findResult = modelUtil.parseFinder(ecModel, finder); + + zrUtil.each(findResult, function (models, key) { + key.indexOf('Models') >= 0 && zrUtil.each(models as ComponentModel[], function (model) { + const coordSys = (model as CoordinateSystemHostModel).coordinateSystem; + if (coordSys && coordSys.containPoint) { + result = result || !!coordSys.containPoint(value); + } + else if (key === 'seriesModels') { + const view = this._chartsMap[model.__viewId]; + if (view && view.containPoint) { + result = result || view.containPoint(value, model as SeriesModel); + } + else { + if (__DEV__) { + console.warn(key + ': ' + (view + ? 'The found component do not support containPoint.' + : 'No view mapping to the found component.' + )); + } + } + } + else { + if (__DEV__) { + console.warn(key + ': containPoint is not supported'); + } + } + }, this); + }, this); + + return !!result; + } + + /** + * Get visual from series or data. + * @param finder + * If string, e.g., 'series', means {seriesIndex: 0}. + * If Object, could contain some of these properties below: + * { + * seriesIndex / seriesId / seriesName, + * dataIndex / dataIndexInside + * } + * If dataIndex is not specified, series visual will be fetched, + * but not data item visual. + * If all of seriesIndex, seriesId, seriesName are not specified, + * visual will be fetched from first series. + * @param visualType 'color', 'symbol', 'symbolSize' + */ + getVisual(finder: ModelFinder, visualType: string) { + const ecModel = this._model; + + const parsedFinder = modelUtil.parseFinder(ecModel, finder, { + defaultMainType: 'series' + }) as modelUtil.ParsedModelFinderKnown; + + const seriesModel = parsedFinder.seriesModel; + + if (__DEV__) { + if (!seriesModel) { + console.warn('There is no specified seires model'); + } + } + + const data = seriesModel.getData(); + + const dataIndexInside = parsedFinder.hasOwnProperty('dataIndexInside') + ? parsedFinder.dataIndexInside + : parsedFinder.hasOwnProperty('dataIndex') + ? data.indexOfRawIndex(parsedFinder.dataIndex) + : null; + + return dataIndexInside != null + ? getItemVisualFromData(data, dataIndexInside, visualType) + : getVisualFromData(data, visualType); + } + + /** + * Get view of corresponding component model + */ + private getViewOfComponentModel(componentModel: ComponentModel): ComponentView { + return this._componentsMap[componentModel.__viewId]; + } + + /** + * Get view of corresponding series model + */ + private getViewOfSeriesModel(seriesModel: SeriesModel): ChartView { + return this._chartsMap[seriesModel.__viewId]; + } + + + private _initEvents(): void { + each(MOUSE_EVENT_NAMES, (eveName) => { + const handler = (e: ElementEvent) => { + const ecModel = this.getModel(); + const el = e.target; + let params: ECEvent; + const isGlobalOut = eveName === 'globalout'; + // no e.target when 'globalout'. + if (isGlobalOut) { + params = {} as ECEvent; + } + else { + el && findEventDispatcher(el, (parent) => { + const ecData = getECData(parent); + if (ecData && ecData.dataIndex != null) { + const dataModel = ecData.dataModel || ecModel.getSeriesByIndex(ecData.seriesIndex); + params = ( + dataModel && dataModel.getDataParams(ecData.dataIndex, ecData.dataType) || {} + ) as ECEvent; + return true; + } + // If element has custom eventData of components + else if (ecData.eventData) { + params = zrUtil.extend({}, ecData.eventData) as ECEvent; + return true; + } + }, true); + } + + // Contract: if params prepared in mouse event, + // these properties must be specified: + // { + // componentType: string (component main type) + // componentIndex: number + // } + // Otherwise event query can not work. + + if (params) { + let componentType = params.componentType; + let componentIndex = params.componentIndex; + // Special handling for historic reason: when trigger by + // markLine/markPoint/markArea, the componentType is + // 'markLine'/'markPoint'/'markArea', but we should better + // enable them to be queried by seriesIndex, since their + // option is set in each series. + if (componentType === 'markLine' + || componentType === 'markPoint' + || componentType === 'markArea' + ) { + componentType = 'series'; + componentIndex = params.seriesIndex; + } + const model = componentType && componentIndex != null + && ecModel.getComponent(componentType, componentIndex); + const view = model && this[ + model.mainType === 'series' ? '_chartsMap' : '_componentsMap' + ][model.__viewId]; + + if (__DEV__) { + // `event.componentType` and `event[componentTpype + 'Index']` must not + // be missed, otherwise there is no way to distinguish source component. + // See `dataFormat.getDataParams`. + if (!isGlobalOut && !(model && view)) { + console.warn('model or view can not be found by params'); + } + } + + params.event = e; + params.type = eveName; + + (this._$eventProcessor as ECEventProcessor).eventInfo = { + targetEl: el, + packedEvent: params, + model: model, + view: view + }; + + this.trigger(eveName, params); + } + }; + // Consider that some component (like tooltip, brush, ...) + // register zr event handler, but user event handler might + // do anything, such as call `setOption` or `dispatchAction`, + // which probably update any of the content and probably + // cause problem if it is called previous other inner handlers. + (handler as any).zrEventfulCallAtLast = true; + this._zr.on(eveName, handler, this); + }); + + each(eventActionMap, (actionType, eventType) => { + this._messageCenter.on(eventType, function (event) { + this.trigger(eventType, event); + }, this); + }); + + // Extra events + // TODO register? + each( + ['selectchanged'], + (eventType) => { + this._messageCenter.on(eventType, function (event) { + this.trigger(eventType, event); + }, this); + } + ); + + handleLegacySelectEvents(this._messageCenter, this, this._api); + } + + isDisposed(): boolean { + return this._disposed; + } + + clear(): void { + if (this._disposed) { + disposedWarning(this.id); + return; + } + this.setOption({ series: [] } as EChartsOption, true); + } + + dispose(): void { + if (this._disposed) { + disposedWarning(this.id); + return; + } + this._disposed = true; + + modelUtil.setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, ''); + + const api = this._api; + const ecModel = this._model; + + each(this._componentsViews, function (component) { + component.dispose(ecModel, api); + }); + each(this._chartsViews, function (chart) { + chart.dispose(ecModel, api); + }); + + // Dispose after all views disposed + this._zr.dispose(); + + delete instances[this.id]; + } + + /** + * Resize the chart + */ + resize(opts?: { + width?: number | 'auto', // Can be 'auto' (the same as null/undefined) + height?: number | 'auto', // Can be 'auto' (the same as null/undefined) + silent?: boolean // by default false. + }): void { + if (__DEV__) { + assert(!this[IN_MAIN_PROCESS_KEY], '`resize` should not be called during main process.'); + } + if (this._disposed) { + disposedWarning(this.id); + return; + } + + this._zr.resize(opts); + + const ecModel = this._model; + + // Resize loading effect + this._loadingFX && this._loadingFX.resize(); + + if (!ecModel) { + return; + } + + const optionChanged = ecModel.resetOption('media'); + + const silent = opts && opts.silent; + + this[IN_MAIN_PROCESS_KEY] = true; + + optionChanged && prepare(this); + updateMethods.update.call(this, { + type: 'resize', + animation: { + // Disable animation + duration: 0 + } + }); + + this[IN_MAIN_PROCESS_KEY] = false; + + flushPendingActions.call(this, silent); + + triggerUpdatedEvent.call(this, silent); + } + + /** + * Show loading effect + * @param name 'default' by default + * @param cfg cfg of registered loading effect + */ + showLoading(cfg?: object): void; + showLoading(name?: string, cfg?: object): void; + showLoading(name?: string | object, cfg?: object): void { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + if (isObject(name)) { + cfg = name as object; + name = ''; + } + name = name || 'default'; + + this.hideLoading(); + if (!loadingEffects[name]) { + if (__DEV__) { + console.warn('Loading effects ' + name + ' not exists.'); + } + return; + } + const el = loadingEffects[name](this._api, cfg); + const zr = this._zr; + this._loadingFX = el; + + zr.add(el); + } + + /** + * Hide loading effect + */ + hideLoading(): void { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + this._loadingFX && this._zr.remove(this._loadingFX); + this._loadingFX = null; + } + + makeActionFromEvent(eventObj: ECEvent): Payload { + const payload = zrUtil.extend({}, eventObj) as Payload; + payload.type = eventActionMap[eventObj.type]; + return payload; + } + + /** + * @param opt If pass boolean, means opt.silent + * @param opt.silent Default `false`. Whether trigger events. + * @param opt.flush Default `undefined`. + * true: Flush immediately, and then pixel in canvas can be fetched + * immediately. Caution: it might affect performance. + * false: Not flush. + * undefined: Auto decide whether perform flush. + */ + dispatchAction( + payload: Payload, + opt?: boolean | { + silent?: boolean, + flush?: boolean | undefined + } + ): void { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + if (!isObject(opt)) { + opt = {silent: !!opt}; + } + + if (!actions[payload.type]) { + return; + } + + // Avoid dispatch action before setOption. Especially in `connect`. + if (!this._model) { + return; + } + + // May dispatchAction in rendering procedure + if (this[IN_MAIN_PROCESS_KEY]) { + this._pendingActions.push(payload); + return; + } + + const silent = opt.silent; + doDispatchAction.call(this, payload, silent); + + const flush = opt.flush; + if (flush) { + this._zr.flush(); + } + else if (flush !== false && env.browser.weChat) { + // In WeChat embeded browser, `requestAnimationFrame` and `setInterval` + // hang when sliding page (on touch event), which cause that zr does not + // refresh util user interaction finished, which is not expected. + // But `dispatchAction` may be called too frequently when pan on touch + // screen, which impacts performance if do not throttle them. + this._throttledZrFlush(); + } + + flushPendingActions.call(this, silent); + + triggerUpdatedEvent.call(this, silent); + } + + updateLabelLayout() { + const labelManager = this._labelManager; + labelManager.updateLayoutConfig(this._api); + labelManager.layout(this._api); + labelManager.processLabelsOverall(); + } + + appendData(params: { + seriesIndex: number, + data: any + }): void { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + const seriesIndex = params.seriesIndex; + const ecModel = this.getModel(); + const seriesModel = ecModel.getSeriesByIndex(seriesIndex) as SeriesModel; + + if (__DEV__) { + assert(params.data && seriesModel); + } + + seriesModel.appendData(params); + + // Note: `appendData` does not support that update extent of coordinate + // system, util some scenario require that. In the expected usage of + // `appendData`, the initial extent of coordinate system should better + // be fixed by axis `min`/`max` setting or initial data, otherwise if + // the extent changed while `appendData`, the location of the painted + // graphic elements have to be changed, which make the usage of + // `appendData` meaningless. + + this._scheduler.unfinished = true; + + this.getZr().wakeUp(); + } + + + // A work around for no `internal` modifier in ts yet but + // need to strictly hide private methods to JS users. + private static internalField = (function () { + + prepare = function (ecIns: ECharts): void { + const scheduler = ecIns._scheduler; + + scheduler.restorePipelines(ecIns._model); + scheduler.prepareStageTasks(); + + prepareView(ecIns, true); + prepareView(ecIns, false); + + scheduler.plan(); + }; + + /** + * Prepare view instances of charts and components + */ + prepareView = function (ecIns: ECharts, isComponent: boolean): void { + const ecModel = ecIns._model; + const scheduler = ecIns._scheduler; + const viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews; + const viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap; + const zr = ecIns._zr; + const api = ecIns._api; + + for (let i = 0; i < viewList.length; i++) { + viewList[i].__alive = false; + } + + isComponent + ? ecModel.eachComponent(function (componentType, model) { + componentType !== 'series' && doPrepare(model); + }) + : ecModel.eachSeries(doPrepare); + + function doPrepare(model: ComponentModel): void { + // By defaut view will be reused if possible for the case that `setOption` with "notMerge" + // mode and need to enable transition animation. (Usually, when they have the same id, or + // especially no id but have the same type & name & index. See the `model.id` generation + // rule in `makeIdAndName` and `viewId` generation rule here). + // But in `replaceMerge` mode, this feature should be able to disabled when it is clear that + // the new model has nothing to do with the old model. + const requireNewView = model.__requireNewView; + // This command should not work twice. + model.__requireNewView = false; + // Consider: id same and type changed. + const viewId = '_ec_' + model.id + '_' + model.type; + let view = !requireNewView && viewMap[viewId]; + if (!view) { + const classType = parseClassType(model.type); + const Clazz = isComponent + ? (ComponentView as ComponentViewConstructor).getClass(classType.main, classType.sub) + : ( + // FIXME:TS + // (ChartView as ChartViewConstructor).getClass('series', classType.sub) + // For backward compat, still support a chart type declared as only subType + // like "liquidfill", but recommend "series.liquidfill" + // But need a base class to make a type series. + (ChartView as ChartViewConstructor).getClass(classType.sub) + ); + + if (__DEV__) { + assert(Clazz, classType.sub + ' does not exist.'); + } + + view = new Clazz(); + view.init(ecModel, api); + viewMap[viewId] = view; + viewList.push(view as any); + zr.add(view.group); + } + + model.__viewId = view.__id = viewId; + view.__alive = true; + view.__model = model; + view.group.__ecComponentInfo = { + mainType: model.mainType, + index: model.componentIndex + }; + !isComponent && scheduler.prepareView( + view as ChartView, model as SeriesModel, ecModel, api + ); + } + + for (let i = 0; i < viewList.length;) { + const view = viewList[i]; + if (!view.__alive) { + !isComponent && (view as ChartView).renderTask.dispose(); + zr.remove(view.group); + view.dispose(ecModel, api); + viewList.splice(i, 1); + if (viewMap[view.__id] === view) { + delete viewMap[view.__id]; + } + view.__id = view.group.__ecComponentInfo = null; + } + else { + i++; + } + } + }; + + updateDirectly = function ( + ecIns: ECharts, + method: string, + payload: Payload, + mainType: ComponentMainType, + subType?: ComponentSubType + ): void { + const ecModel = ecIns._model; + + ecModel.setUpdatePayload(payload); + + // broadcast + if (!mainType) { + // FIXME + // Chart will not be update directly here, except set dirty. + // But there is no such scenario now. + each([].concat(ecIns._componentsViews).concat(ecIns._chartsViews), callView); + return; + } + + const query: QueryConditionKindA['query'] = {}; + query[mainType + 'Id'] = payload[mainType + 'Id']; + query[mainType + 'Index'] = payload[mainType + 'Index']; + query[mainType + 'Name'] = payload[mainType + 'Name']; + + const condition = {mainType: mainType, query: query} as QueryConditionKindA; + subType && (condition.subType = subType); // subType may be '' by parseClassType; + + const excludeSeriesId = payload.excludeSeriesId; + let excludeSeriesIdMap: zrUtil.HashMap; + if (excludeSeriesId != null) { + excludeSeriesIdMap = zrUtil.createHashMap(); + each(modelUtil.normalizeToArray(excludeSeriesId), id => { + const modelId = modelUtil.convertOptionIdName(id, null); + if (modelId != null) { + excludeSeriesIdMap.set(modelId, true); + } + }); + } + + // If dispatchAction before setOption, do nothing. + ecModel && ecModel.eachComponent(condition, function (model) { + if (!excludeSeriesIdMap || excludeSeriesIdMap.get(model.id) == null) { + if (isHighDownPayload(payload) && !payload.notBlur) { + if (model instanceof SeriesModel) { + toggleSeriesBlurStateFromPayload(model, payload, ecIns._api); + } + } + else if (isSelectChangePayload(payload)) { + // TODO geo + if (model instanceof SeriesModel) { + toggleSelectionFromPayload(model, payload, ecIns._api); + updateSeriesElementSelection(model); + markStatusToUpdate(ecIns); + } + } + + callView(ecIns[ + mainType === 'series' ? '_chartsMap' : '_componentsMap' + ][model.__viewId]); + } + }, ecIns); + + function callView(view: ComponentView | ChartView) { + view && view.__alive && (view as any)[method] && (view as any)[method]( + view.__model, ecModel, ecIns._api, payload + ); + } + }; + + updateMethods = { + + prepareAndUpdate: function (this: ECharts, payload: Payload): void { + prepare(this); + updateMethods.update.call(this, payload); + }, + + update: function (this: ECharts, payload: Payload): void { + // console.profile && console.profile('update'); + + const ecModel = this._model; + const api = this._api; + const zr = this._zr; + const coordSysMgr = this._coordSysMgr; + const scheduler = this._scheduler; + + // update before setOption + if (!ecModel) { + return; + } + + ecModel.setUpdatePayload(payload); + + scheduler.restoreData(ecModel, payload); + + scheduler.performSeriesTasks(ecModel); + + // TODO + // Save total ecModel here for undo/redo (after restoring data and before processing data). + // Undo (restoration of total ecModel) can be carried out in 'action' or outside API call. + + // Create new coordinate system each update + // In LineView may save the old coordinate system and use it to get the orignal point + coordSysMgr.create(ecModel, api); + + scheduler.performDataProcessorTasks(ecModel, payload); + + // Current stream render is not supported in data process. So we can update + // stream modes after data processing, where the filtered data is used to + // deteming whether use progressive rendering. + updateStreamModes(this, ecModel); + + // We update stream modes before coordinate system updated, then the modes info + // can be fetched when coord sys updating (consider the barGrid extent fix). But + // the drawback is the full coord info can not be fetched. Fortunately this full + // coord is not requied in stream mode updater currently. + coordSysMgr.update(ecModel, api); + + clearColorPalette(ecModel); + scheduler.performVisualTasks(ecModel, payload); + + render(this, ecModel, api, payload); + + // Set background + let backgroundColor = ecModel.get('backgroundColor') || 'transparent'; + const darkMode = ecModel.get('darkMode'); + + // In IE8 + if (!env.canvasSupported) { + const colorArr = colorTool.parse(backgroundColor as ColorString); + backgroundColor = colorTool.stringify(colorArr, 'rgb'); + if (colorArr[3] === 0) { + backgroundColor = 'transparent'; + } + } + else { + zr.setBackgroundColor(backgroundColor); + + // Force set dark mode. + if (darkMode != null && darkMode !== 'auto') { + zr.setDarkMode(darkMode); + } + } + + performPostUpdateFuncs(ecModel, api); + + // console.profile && console.profileEnd('update'); + }, + + updateTransform: function (this: ECharts, payload: Payload): void { + const ecModel = this._model; + const api = this._api; + + // update before setOption + if (!ecModel) { + return; + } + + ecModel.setUpdatePayload(payload); + + // ChartView.markUpdateMethod(payload, 'updateTransform'); + + const componentDirtyList = []; + ecModel.eachComponent((componentType, componentModel) => { + if (componentType === 'series') { + return; + } + + const componentView = this.getViewOfComponentModel(componentModel); + if (componentView && componentView.__alive) { + if (componentView.updateTransform) { + const result = componentView.updateTransform(componentModel, ecModel, api, payload); + result && result.update && componentDirtyList.push(componentView); + } + else { + componentDirtyList.push(componentView); + } + } + }); + + const seriesDirtyMap = zrUtil.createHashMap(); + ecModel.eachSeries((seriesModel) => { + const chartView = this._chartsMap[seriesModel.__viewId]; + if (chartView.updateTransform) { + const result = chartView.updateTransform(seriesModel, ecModel, api, payload); + result && result.update && seriesDirtyMap.set(seriesModel.uid, 1); + } + else { + seriesDirtyMap.set(seriesModel.uid, 1); + } + }); + + clearColorPalette(ecModel); + // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. + // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true); + this._scheduler.performVisualTasks( + ecModel, payload, {setDirty: true, dirtyMap: seriesDirtyMap} + ); + + // Currently, not call render of components. Geo render cost a lot. + // renderComponents(ecIns, ecModel, api, payload, componentDirtyList); + renderSeries(this, ecModel, api, payload, seriesDirtyMap); + + performPostUpdateFuncs(ecModel, this._api); + }, + + updateView: function (this: ECharts, payload: Payload): void { + const ecModel = this._model; + + // update before setOption + if (!ecModel) { + return; + } + + ecModel.setUpdatePayload(payload); + + ChartView.markUpdateMethod(payload, 'updateView'); + + clearColorPalette(ecModel); + + // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. + this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true}); + + render(this, this._model, this._api, payload); + + performPostUpdateFuncs(ecModel, this._api); + }, + + updateVisual: function (this: ECharts, payload: Payload): void { + // updateMethods.update.call(this, payload); + + const ecModel = this._model; + + // update before setOption + if (!ecModel) { + return; + } + + ecModel.setUpdatePayload(payload); + + // clear all visual + ecModel.eachSeries(function (seriesModel) { + seriesModel.getData().clearAllVisual(); + }); + + // Perform visual + ChartView.markUpdateMethod(payload, 'updateVisual'); + + clearColorPalette(ecModel); + + // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. + this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true}); + + ecModel.eachComponent((componentType, componentModel) => { // TODO componentType may be series. + if (componentType !== 'series') { + const componentView = this.getViewOfComponentModel(componentModel); + componentView && componentView.__alive + && componentView.updateVisual(componentModel, ecModel, this._api, payload); + } + }); + + ecModel.eachSeries((seriesModel) => { + const chartView = this._chartsMap[seriesModel.__viewId]; + chartView.updateVisual(seriesModel, ecModel, this._api, payload); + }); + + performPostUpdateFuncs(ecModel, this._api); + }, + + updateLayout: function (this: ECharts, payload: Payload): void { + updateMethods.update.call(this, payload); + } + }; + + doConvertPixel = function ( + ecIns: ECharts, + methodName: 'convertFromPixel' | 'convertToPixel', + finder: ModelFinder, + value: (number | number[]) | (ScaleDataValue | ScaleDataValue[]) + ): (number | number[]) { + if (ecIns._disposed) { + disposedWarning(ecIns.id); + return; + } + const ecModel = ecIns._model; + const coordSysList = ecIns._coordSysMgr.getCoordinateSystems(); + let result; + + const parsedFinder = modelUtil.parseFinder(ecModel, finder); + + for (let i = 0; i < coordSysList.length; i++) { + const coordSys = coordSysList[i]; + if (coordSys[methodName] + && (result = coordSys[methodName](ecModel, parsedFinder, value as any)) != null + ) { + return result; + } + } + + if (__DEV__) { + console.warn( + 'No coordinate system that supports ' + methodName + ' found by the given finder.' + ); + } + }; + + updateStreamModes = function (ecIns: ECharts, ecModel: GlobalModel): void { + const chartsMap = ecIns._chartsMap; + const scheduler = ecIns._scheduler; + ecModel.eachSeries(function (seriesModel) { + scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]); + }); + }; + + doDispatchAction = function (this: ECharts, payload: Payload, silent: boolean): void { + const ecModel = this.getModel(); + const payloadType = payload.type; + const escapeConnect = payload.escapeConnect; + const actionWrap = actions[payloadType]; + const actionInfo = actionWrap.actionInfo; + + const cptTypeTmp = (actionInfo.update || 'update').split(':'); + const updateMethod = cptTypeTmp.pop(); + const cptType = cptTypeTmp[0] != null && parseClassType(cptTypeTmp[0]); + + this[IN_MAIN_PROCESS_KEY] = true; + + let payloads: Payload[] = [payload]; + let batched = false; + // Batch action + if (payload.batch) { + batched = true; + payloads = zrUtil.map(payload.batch, function (item) { + item = zrUtil.defaults(zrUtil.extend({}, item), payload); + item.batch = null; + return item as Payload; + }); + } + + const eventObjBatch: ECEventData[] = []; + let eventObj: ECEvent; + + const isSelectChange = isSelectChangePayload(payload); + const isStatusChange = isHighDownPayload(payload) || isSelectChange; + + each(payloads, (batchItem) => { + // Action can specify the event by return it. + eventObj = actionWrap.action(batchItem, this._model, this._api) as ECEvent; + // Emit event outside + eventObj = eventObj || zrUtil.extend({} as ECEvent, batchItem); + // Convert type to eventType + eventObj.type = actionInfo.event || eventObj.type; + eventObjBatch.push(eventObj); + + // light update does not perform data process, layout and visual. + if (isStatusChange) { + // method, payload, mainType, subType + updateDirectly(this, updateMethod, batchItem as Payload, 'series'); + + // Mark status to update + markStatusToUpdate(this); + } + else if (cptType) { + updateDirectly(this, updateMethod, batchItem as Payload, cptType.main, cptType.sub); + } + }); + + if (updateMethod !== 'none' && !isStatusChange && !cptType) { + // Still dirty + if (this[OPTION_UPDATED_KEY]) { + prepare(this); + updateMethods.update.call(this, payload); + this[OPTION_UPDATED_KEY] = false; + } + else { + updateMethods[updateMethod as keyof typeof updateMethods].call(this, payload); + } + } + + // Follow the rule of action batch + if (batched) { + eventObj = { + type: actionInfo.event || payloadType, + escapeConnect: escapeConnect, + batch: eventObjBatch + }; + } + else { + eventObj = eventObjBatch[0] as ECEvent; + } + + this[IN_MAIN_PROCESS_KEY] = false; + + if (!silent) { + const messageCenter = this._messageCenter; + messageCenter.trigger(eventObj.type, eventObj); + // Extra triggered 'selectchanged' event + if (isSelectChange) { + const newObj: SelectChangedPayload = { + type: 'selectchanged', + escapeConnect: escapeConnect, + selected: getAllSelectedIndices(ecModel), + isFromClick: payload.isFromClick || false, + fromAction: payload.type as 'select' | 'unselect' | 'toggleSelected', + fromActionPayload: payload + }; + messageCenter.trigger(newObj.type, newObj); + } + } + }; + + flushPendingActions = function (this: ECharts, silent: boolean): void { + const pendingActions = this._pendingActions; + while (pendingActions.length) { + const payload = pendingActions.shift(); + doDispatchAction.call(this, payload, silent); + } + }; + + triggerUpdatedEvent = function (this: ECharts, silent): void { + !silent && this.trigger('updated'); + }; + + /** + * Event `rendered` is triggered when zr + * rendered. It is useful for realtime + * snapshot (reflect animation). + * + * Event `finished` is triggered when: + * (1) zrender rendering finished. + * (2) initial animation finished. + * (3) progressive rendering finished. + * (4) no pending action. + * (5) no delayed setOption needs to be processed. + */ + bindRenderedEvent = function (zr: zrender.ZRenderType, ecIns: ECharts): void { + zr.on('rendered', function (params) { + + ecIns.trigger('rendered', params); + + // The `finished` event should not be triggered repeatly, + // so it should only be triggered when rendering indeed happend + // in zrender. (Consider the case that dipatchAction is keep + // triggering when mouse move). + if ( + // Although zr is dirty if initial animation is not finished + // and this checking is called on frame, we also check + // animation finished for robustness. + zr.animation.isFinished() + && !ecIns[OPTION_UPDATED_KEY] + && !ecIns._scheduler.unfinished + && !ecIns._pendingActions.length + ) { + ecIns.trigger('finished'); + } + }); + }; + + bindMouseEvent = function (zr: zrender.ZRenderType, ecIns: ECharts): void { + zr.on('mouseover', function (e) { + const el = e.target; + const dispatcher = findEventDispatcher(el, isHighDownDispatcher); + if (dispatcher) { + const ecData = getECData(dispatcher); + // Try blur all in the related series. Then emphasis the hoverred. + // TODO. progressive mode. + toggleSeriesBlurState( + ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns._api, true + ); + enterEmphasisWhenMouseOver(dispatcher, e); + + markStatusToUpdate(ecIns); + } + }).on('mouseout', function (e) { + const el = e.target; + const dispatcher = findEventDispatcher(el, isHighDownDispatcher); + if (dispatcher) { + const ecData = getECData(dispatcher); + toggleSeriesBlurState( + ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns._api, false + ); + + leaveEmphasisWhenMouseOut(dispatcher, e); + + markStatusToUpdate(ecIns); + } + }).on('click', function (e) { + const el = e.target; + const dispatcher = findEventDispatcher( + el, (target) => getECData(target).dataIndex != null, true + ); + if (dispatcher) { + const actionType = (dispatcher as ECElement).selected ? 'unselect' : 'select'; + const ecData = getECData(dispatcher); + ecIns._api.dispatchAction({ + type: actionType, + dataType: ecData.dataType, + dataIndexInside: ecData.dataIndex, + seriesIndex: ecData.seriesIndex, + isFromClick: true + }); + } + }); + }; + + clearColorPalette = function (ecModel: GlobalModel): void { + ecModel.clearColorPalette(); + ecModel.eachSeries(function (seriesModel) { + seriesModel.clearColorPalette(); + }); + }; + + render = function (ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { + + renderComponents(ecIns, ecModel, api, payload); + + each(ecIns._chartsViews, function (chart: ChartView) { + chart.__alive = false; + }); + + renderSeries(ecIns, ecModel, api, payload); + + // Remove groups of unrendered charts + each(ecIns._chartsViews, function (chart: ChartView) { + if (!chart.__alive) { + chart.remove(ecModel, api); + } + }); + }; + + renderComponents = function ( + ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, dirtyList?: ComponentView[] + ): void { + each(dirtyList || ecIns._componentsViews, function (componentView: ComponentView) { + const componentModel = componentView.__model; + clearStates(componentModel, componentView); + + componentView.render(componentModel, ecModel, api, payload); + + updateZ(componentModel, componentView); + + updateStates(componentModel, componentView); + }); + + }; + + /** + * Render each chart and component + */ + renderSeries = function ( + ecIns: ECharts, + ecModel: GlobalModel, + api: ExtensionAPI, + payload: Payload | 'remain', + dirtyMap?: {[uid: string]: any} + ): void { + // Render all charts + const scheduler = ecIns._scheduler; + const labelManager = ecIns._labelManager; + + labelManager.clearLabels(); + + let unfinished: boolean = false; + ecModel.eachSeries(function (seriesModel) { + const chartView = ecIns._chartsMap[seriesModel.__viewId]; + chartView.__alive = true; + + const renderTask = chartView.renderTask; + scheduler.updatePayload(renderTask, payload); + + // TODO states on marker. + clearStates(seriesModel, chartView); + + if (dirtyMap && dirtyMap.get(seriesModel.uid)) { + renderTask.dirty(); + } + if (renderTask.perform(scheduler.getPerformArgs(renderTask))) { + unfinished = true; + } + + seriesModel.__transientTransitionOpt = null; + + chartView.group.silent = !!seriesModel.get('silent'); + // Should not call markRedraw on group, because it will disable zrender + // increamental render (alway render from the __startIndex each frame) + // chartView.group.markRedraw(); + + updateBlend(seriesModel, chartView); + + updateSeriesElementSelection(seriesModel); + + // Add labels. + labelManager.addLabelsOfSeries(chartView); + }); + + scheduler.unfinished = unfinished || scheduler.unfinished; + + labelManager.updateLayoutConfig(api); + labelManager.layout(api); + labelManager.processLabelsOverall(); + + ecModel.eachSeries(function (seriesModel) { + const chartView = ecIns._chartsMap[seriesModel.__viewId]; + // Update Z after labels updated. Before applying states. + updateZ(seriesModel, chartView); + + // NOTE: Update states after label is updated. + // label should be in normal status when layouting. + updateStates(seriesModel, chartView); + }); + + + // If use hover layer + updateHoverLayerStatus(ecIns, ecModel); + }; + + performPostUpdateFuncs = function (ecModel: GlobalModel, api: ExtensionAPI): void { + each(postUpdateFuncs, function (func) { + func(ecModel, api); + }); + }; + + markStatusToUpdate = function (ecIns: ECharts): void { + ecIns[STATUS_NEEDS_UPDATE_KEY] = true; + // Wake up zrender if it's sleep. Let it update states in the next frame. + ecIns.getZr().wakeUp(); + }; + + applyChangedStates = function (ecIns: ECharts): void { + if (!ecIns[STATUS_NEEDS_UPDATE_KEY]) { + return; + } + + ecIns.getZr().storage.traverse(function (el: ECElement) { + // Not applied on removed elements, it may still in fading. + if (graphic.isElementRemoved(el)) { + return; + } + applyElementStates(el); + }); + + ecIns[STATUS_NEEDS_UPDATE_KEY] = false; + }; + + function applyElementStates(el: ECElement) { + const newStates = []; + + const oldStates = el.currentStates; + // Keep other states. + for (let i = 0; i < oldStates.length; i++) { + const stateName = oldStates[i]; + if (!(stateName === 'emphasis' || stateName === 'blur' || stateName === 'select')) { + newStates.push(stateName); + } + } + + // Only use states when it's exists. + if (el.selected && el.states.select) { + newStates.push('select'); + } + if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) { + newStates.push('emphasis'); + } + else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) { + newStates.push('blur'); + } + el.useStates(newStates); + } + + function updateHoverLayerStatus(ecIns: ECharts, ecModel: GlobalModel): void { + const zr = ecIns._zr; + const storage = zr.storage; + let elCount = 0; + + storage.traverse(function (el) { + if (!el.isGroup) { + elCount++; + } + }); + + if (elCount > ecModel.get('hoverLayerThreshold') && !env.node && !env.worker) { + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.preventUsingHoverLayer) { + return; + } + const chartView = ecIns._chartsMap[seriesModel.__viewId]; + if (chartView.__alive) { + chartView.group.traverse(function (el: ECElement) { + if (el.states.emphasis) { + el.states.emphasis.hoverLayer = true; + } + }); + } + }); + } + }; + + /** + * Update chart and blend. + */ + function updateBlend(seriesModel: SeriesModel, chartView: ChartView): void { + const blendMode = seriesModel.get('blendMode') || null; + if (__DEV__) { + if (!env.canvasSupported && blendMode && blendMode !== 'source-over') { + console.warn('Only canvas support blendMode'); + } + } + chartView.group.traverse(function (el: Displayable) { + // FIXME marker and other components + if (!el.isGroup) { + // DONT mark the element dirty. In case element is incremental and don't wan't to rerender. + el.style.blend = blendMode; + } + if ((el as IncrementalDisplayable).eachPendingDisplayable) { + (el as IncrementalDisplayable).eachPendingDisplayable(function (displayable) { + displayable.style.blend = blendMode; + }); + } + }); + }; + + function updateZ(model: ComponentModel, view: ComponentView | ChartView): void { + if (model.preventAutoZ) { + return; + } + const z = model.get('z'); + const zlevel = model.get('zlevel'); + // Set z and zlevel + view.group.traverse(function (el: Displayable) { + if (!el.isGroup) { + z != null && (el.z = z); + zlevel != null && (el.zlevel = zlevel); + + // TODO if textContent is on group. + const label = el.getTextContent(); + const labelLine = el.getTextGuideLine(); + if (label) { + label.z = el.z; + label.zlevel = el.zlevel; + // lift z2 of text content + // TODO if el.emphasis.z2 is spcefied, what about textContent. + label.z2 = el.z2 + 2; + } + if (labelLine) { + const showAbove = el.textGuideLineConfig && el.textGuideLineConfig.showAbove; + labelLine.z = el.z; + labelLine.zlevel = el.zlevel; + labelLine.z2 = el.z2 + (showAbove ? 1 : -1); + } + } + }); + }; + + // Clear states without animation. + // TODO States on component. + function clearStates(model: ComponentModel, view: ComponentView | ChartView): void { + view.group.traverse(function (el: Displayable) { + // Not applied on removed elements, it may still in fading. + if (graphic.isElementRemoved(el)) { + return; + } + + const textContent = el.getTextContent(); + const textGuide = el.getTextGuideLine(); + if (el.stateTransition) { + el.stateTransition = null; + } + if (textContent && textContent.stateTransition) { + textContent.stateTransition = null; + } + if (textGuide && textGuide.stateTransition) { + textGuide.stateTransition = null; + } + + // TODO If el is incremental. + if (el.hasState()) { + el.prevStates = el.currentStates; + el.clearStates(); + } + else if (el.prevStates) { + el.prevStates = null; + } + }); + } + + function updateStates(model: ComponentModel, view: ComponentView | ChartView): void { + const stateAnimationModel = (model as SeriesModel).getModel('stateAnimation'); + const enableAnimation = model.isAnimationEnabled(); + const duration = stateAnimationModel.get('duration'); + const stateTransition = duration > 0 ? { + duration, + delay: stateAnimationModel.get('delay'), + easing: stateAnimationModel.get('easing') + // additive: stateAnimationModel.get('additive') + } : null; + view.group.traverse(function (el: Displayable) { + if (el.states && el.states.emphasis) { + // Not applied on removed elements, it may still in fading. + if (graphic.isElementRemoved(el)) { + return; + } + + if (el instanceof graphic.Path) { + savePathStates(el); + } + + // Only updated on changed element. In case element is incremental and don't wan't to rerender. + // TODO, a more proper way? + if (el.__dirty) { + const prevStates = el.prevStates; + // Restore states without animation + if (prevStates) { + el.useStates(prevStates); + } + } + + // Update state transition and enable animation again. + if (enableAnimation) { + el.stateTransition = stateTransition; + const textContent = el.getTextContent(); + const textGuide = el.getTextGuideLine(); + // TODO Is it necessary to animate label? + if (textContent) { + textContent.stateTransition = stateTransition; + } + if (textGuide) { + textGuide.stateTransition = stateTransition; + } + } + + // The use higlighted and selected flag to toggle states. + if (el.__dirty) { + applyElementStates(el); + } + } + }); + }; + + createExtensionAPI = function (ecIns: ECharts): ExtensionAPI { + return new (class extends ExtensionAPI { + getCoordinateSystems(): CoordinateSystemMaster[] { + return ecIns._coordSysMgr.getCoordinateSystems(); + } + getComponentByElement(el: Element) { + while (el) { + const modelInfo = (el as ViewRootGroup).__ecComponentInfo; + if (modelInfo != null) { + return ecIns._model.getComponent(modelInfo.mainType, modelInfo.index); + } + el = el.parent; + } + } + enterEmphasis(el: Element, highlightDigit?: number) { + enterEmphasis(el, highlightDigit); + markStatusToUpdate(ecIns); + } + leaveEmphasis(el: Element, highlightDigit?: number) { + leaveEmphasis(el, highlightDigit); + markStatusToUpdate(ecIns); + } + enterBlur(el: Element) { + enterBlur(el); + markStatusToUpdate(ecIns); + } + leaveBlur(el: Element) { + leaveBlur(el); + markStatusToUpdate(ecIns); + } + enterSelect(el: Element) { + enterSelect(el); + markStatusToUpdate(ecIns); + } + leaveSelect(el: Element) { + leaveSelect(el); + markStatusToUpdate(ecIns); + } + getModel(): GlobalModel { + return ecIns.getModel(); + } + getViewOfComponentModel(componentModel: ComponentModel): ComponentView { + return ecIns.getViewOfComponentModel(componentModel); + } + getViewOfSeriesModel(seriesModel: SeriesModel): ChartView { + return ecIns.getViewOfSeriesModel(seriesModel); + } + })(ecIns); + }; + + enableConnect = function (chart: ECharts): void { + + function updateConnectedChartsStatus(charts: ECharts[], status: ConnectStatus) { + for (let i = 0; i < charts.length; i++) { + const otherChart = charts[i]; + otherChart[CONNECT_STATUS_KEY] = status; + } + } + + each(eventActionMap, function (actionType, eventType) { + chart._messageCenter.on(eventType, function (event: ECEvent) { + if (connectedGroups[chart.group] && chart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_PENDING) { + if (event && event.escapeConnect) { + return; + } + + const action = chart.makeActionFromEvent(event); + const otherCharts: ECharts[] = []; + + each(instances, function (otherChart) { + if (otherChart !== chart && otherChart.group === chart.group) { + otherCharts.push(otherChart); + } + }); + + updateConnectedChartsStatus(otherCharts, CONNECT_STATUS_PENDING); + each(otherCharts, function (otherChart) { + if (otherChart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_UPDATING) { + otherChart.dispatchAction(action); + } + }); + updateConnectedChartsStatus(otherCharts, CONNECT_STATUS_UPDATED); + } + }); + }); + }; + + setTransitionOpt = function ( + chart: ECharts, + transitionOpt: SetOptionTransitionOpt + ): void { + const ecModel = chart._model; + + zrUtil.each(modelUtil.normalizeToArray(transitionOpt), transOpt => { + let errMsg; + const fromOpt = transOpt.from; + const toOpt = transOpt.to; + + if (toOpt == null) { + if (__DEV__) { + errMsg = '`transition.to` must be specified.'; + } + throwError(errMsg); + } + + const finderOpt = { + includeMainTypes: ['series'], + enableAll: false, + enableNone: false + }; + const fromResult = fromOpt ? modelUtil.parseFinder(ecModel, fromOpt, finderOpt) : null; + const toResult = modelUtil.parseFinder(ecModel, toOpt, finderOpt) as modelUtil.ParsedModelFinderKnown; + const toSeries = toResult.seriesModel; + + if (toSeries == null) { + errMsg = ''; + if (__DEV__) { + errMsg = '`transition` is only supported on series.'; + } + } + if (fromResult && fromResult.seriesModel !== toSeries) { + errMsg = ''; + if (__DEV__) { + errMsg = '`transition.from` and `transition.to` must be specified to the same series.'; + } + } + if (errMsg != null) { + throwError(errMsg); + } + + // Just a temp solution: mount them on series. + toSeries.__transientTransitionOpt = { + from: fromOpt ? fromOpt.dimension : null, + to: toOpt.dimension, + dividingMethod: transOpt.dividingMethod + }; + }); + }; + + })(); +} + + +const echartsProto = ECharts.prototype; +echartsProto.on = createRegisterEventWithLowercaseECharts('on'); +echartsProto.off = createRegisterEventWithLowercaseECharts('off'); +// @ts-ignore +echartsProto.one = function (eventName: string, cb: Function, ctx?: any) { + const self = this; + deprecateLog('ECharts#one is deprecated.'); + function wrapped(this: unknown, ...args2: any) { + cb && cb.apply && cb.apply(this, args2); + self.off(eventName, wrapped); + }; + this.on.call(this, eventName, wrapped, ctx); +}; + +// /** +// * Encode visual infomation from data after data processing +// * +// * @param {module:echarts/model/Global} ecModel +// * @param {object} layout +// * @param {boolean} [layoutFilter] `true`: only layout, +// * `false`: only not layout, +// * `null`/`undefined`: all. +// * @param {string} taskBaseTag +// * @private +// */ +// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) { +// each(visualFuncs, function (visual, index) { +// let isLayout = visual.isLayout; +// if (layoutFilter == null +// || (layoutFilter === false && !isLayout) +// || (layoutFilter === true && isLayout) +// ) { +// visual.func(ecModel, api, payload); +// } +// }); +// } + + +const MOUSE_EVENT_NAMES = [ + 'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove', + 'mousedown', 'mouseup', 'globalout', 'contextmenu' +]; + +function disposedWarning(id: string): void { + if (__DEV__) { + console.warn('Instance ' + id + ' has been disposed'); + } +} + + +const actions: { + [actionType: string]: { + action: ActionHandler, + actionInfo: ActionInfo + } +} = {}; + +/** + * Map eventType to actionType + */ +const eventActionMap: {[eventType: string]: string} = {}; + +const dataProcessorFuncs: StageHandlerInternal[] = []; + +const optionPreprocessorFuncs: OptionPreprocessor[] = []; + +const postInitFuncs: PostIniter[] = []; + +const postUpdateFuncs: PostUpdater[] = []; + +const visualFuncs: StageHandlerInternal[] = []; + +const themeStorage: {[themeName: string]: ThemeOption} = {}; + +const loadingEffects: {[effectName: string]: LoadingEffectCreator} = {}; + +const instances: {[id: string]: ECharts} = {}; +const connectedGroups: {[groupId: string]: boolean} = {}; + +let idBase: number = +(new Date()) - 0; +let groupIdBase: number = +(new Date()) - 0; +const DOM_ATTRIBUTE_KEY = '_echarts_instance_'; + + +/** + * @param opts.devicePixelRatio Use window.devicePixelRatio by default + * @param opts.renderer Can choose 'canvas' or 'svg' to render the chart. + * @param opts.width Use clientWidth of the input `dom` by default. + * Can be 'auto' (the same as null/undefined) + * @param opts.height Use clientHeight of the input `dom` by default. + * Can be 'auto' (the same as null/undefined) + */ +export function init( + dom: HTMLElement, + theme?: string | object, + opts?: { + renderer?: RendererType, + devicePixelRatio?: number, + width?: number, + height?: number, + locale?: string | LocaleOption + } +): EChartsType { + if (__DEV__) { + if (!dom) { + throw new Error('Initialize failed: invalid dom.'); + } + } + + const existInstance = getInstanceByDom(dom); + if (existInstance) { + if (__DEV__) { + console.warn('There is a chart instance already initialized on the dom.'); + } + return existInstance; + } + + if (__DEV__) { + if (zrUtil.isDom(dom) + && dom.nodeName.toUpperCase() !== 'CANVAS' + && ( + (!dom.clientWidth && (!opts || opts.width == null)) + || (!dom.clientHeight && (!opts || opts.height == null)) + ) + ) { + console.warn('Can\'t get DOM width or height. Please check ' + + 'dom.clientWidth and dom.clientHeight. They should not be 0.' + + 'For example, you may need to call this in the callback ' + + 'of window.onload.'); + } + } + + const chart = new ECharts(dom, theme, opts); + chart.id = 'ec_' + idBase++; + instances[chart.id] = chart; + + modelUtil.setAttribute(dom, DOM_ATTRIBUTE_KEY, chart.id); + + enableConnect(chart); + + each(postInitFuncs, (postInitFunc) => { + postInitFunc(chart); + }); + + return chart; +} + +/** + * @usage + * (A) + * ```js + * let chart1 = echarts.init(dom1); + * let chart2 = echarts.init(dom2); + * chart1.group = 'xxx'; + * chart2.group = 'xxx'; + * echarts.connect('xxx'); + * ``` + * (B) + * ```js + * let chart1 = echarts.init(dom1); + * let chart2 = echarts.init(dom2); + * echarts.connect('xxx', [chart1, chart2]); + * ``` + */ +export function connect(groupId: string | EChartsType[]): string { + // Is array of charts + if (zrUtil.isArray(groupId)) { + const charts = groupId; + groupId = null; + // If any chart has group + each(charts, function (chart) { + if (chart.group != null) { + groupId = chart.group; + } + }); + groupId = groupId || ('g_' + groupIdBase++); + each(charts, function (chart) { + chart.group = groupId as string; + }); + } + connectedGroups[groupId as string] = true; + return groupId as string; +} + +/** + * @deprecated + */ +export function disConnect(groupId: string): void { + connectedGroups[groupId] = false; +} + +/** + * Alias and backword compat + */ +export const disconnect = disConnect; + +/** + * Dispose a chart instance + */ +export function dispose(chart: EChartsType | HTMLElement | string): void { + if (typeof chart === 'string') { + chart = instances[chart]; + } + else if (!(chart instanceof ECharts)) { + // Try to treat as dom + chart = getInstanceByDom(chart); + } + if ((chart instanceof ECharts) && !chart.isDisposed()) { + chart.dispose(); + } +} + +export function getInstanceByDom(dom: HTMLElement): EChartsType { + return instances[modelUtil.getAttribute(dom, DOM_ATTRIBUTE_KEY)]; +} + +export function getInstanceById(key: string): EChartsType { + return instances[key]; +} + +/** + * Register theme + */ +export function registerTheme(name: string, theme: ThemeOption): void { + themeStorage[name] = theme; +} + +/** + * Register option preprocessor + */ +export function registerPreprocessor(preprocessorFunc: OptionPreprocessor): void { + if (indexOf(optionPreprocessorFuncs, preprocessorFunc) < 0) { + optionPreprocessorFuncs.push(preprocessorFunc); + } +} + +export function registerProcessor( + priority: number | StageHandler | StageHandlerOverallReset, + processor?: StageHandler | StageHandlerOverallReset +): void { + normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_DEFAULT); +} + + +/** + * Register postIniter + * @param {Function} postInitFunc + */ +export function registerPostInit(postInitFunc: PostIniter): void { + if (indexOf(postInitFuncs, postInitFunc) < 0) { + postInitFunc && postInitFuncs.push(postInitFunc); + } +} + +/** + * Register postUpdater + * @param {Function} postUpdateFunc + */ +export function registerPostUpdate(postUpdateFunc: PostUpdater): void { + if (indexOf(postUpdateFuncs, postUpdateFunc) < 0) { + postUpdateFunc && postUpdateFuncs.push(postUpdateFunc); + } +} + +/** + * @usage + * registerAction('someAction', 'someEvent', function () { ... }); + * registerAction('someAction', function () { ... }); + * registerAction( + * {type: 'someAction', event: 'someEvent', update: 'updateView'}, + * function () { ... } + * ); + * + * @param {(string|Object)} actionInfo + * @param {string} actionInfo.type + * @param {string} [actionInfo.event] + * @param {string} [actionInfo.update] + * @param {string} [eventName] + * @param {Function} action + */ +export function registerAction(type: string, eventName: string, action: ActionHandler): void; +export function registerAction(type: string, action: ActionHandler): void; +export function registerAction(actionInfo: ActionInfo, action: ActionHandler): void; +export function registerAction( + actionInfo: string | ActionInfo, + eventName: string | ActionHandler, + action?: ActionHandler +): void { + if (typeof eventName === 'function') { + action = eventName; + eventName = ''; + } + const actionType = isObject(actionInfo) + ? (actionInfo as ActionInfo).type + : ([actionInfo, actionInfo = { + event: eventName + } as ActionInfo][0]); + + // Event name is all lowercase + (actionInfo as ActionInfo).event = ( + (actionInfo as ActionInfo).event || actionType as string + ).toLowerCase(); + eventName = (actionInfo as ActionInfo).event; + + if (eventActionMap[eventName as string]) { + // Already registered. + return; + } + + // Validate action type and event name. + assert(ACTION_REG.test(actionType as string) && ACTION_REG.test(eventName)); + + if (!actions[actionType as string]) { + actions[actionType as string] = {action: action, actionInfo: actionInfo as ActionInfo}; + } + eventActionMap[eventName as string] = actionType as string; +} + +export function registerCoordinateSystem( + type: string, + coordSysCreator: CoordinateSystemCreator +): void { + CoordinateSystemManager.register(type, coordSysCreator); +} + +/** + * Get dimensions of specified coordinate system. + * @param {string} type + * @return {Array.} + */ +export function getCoordinateSystemDimensions(type: string): DimensionDefinitionLoose[] { + const coordSysCreator = CoordinateSystemManager.get(type); + if (coordSysCreator) { + return coordSysCreator.getDimensionsInfo + ? coordSysCreator.getDimensionsInfo() + : coordSysCreator.dimensions.slice(); + } +} + +export {registerLocale} from './locale'; + +/** + * Layout is a special stage of visual encoding + * Most visual encoding like color are common for different chart + * But each chart has it's own layout algorithm + */ +function registerLayout(priority: number, layoutTask: StageHandler | StageHandlerOverallReset): void; +function registerLayout(layoutTask: StageHandler | StageHandlerOverallReset): void; +function registerLayout( + priority: number | StageHandler | StageHandlerOverallReset, + layoutTask?: StageHandler | StageHandlerOverallReset +): void { + normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, 'layout'); +} + +function registerVisual(priority: number, layoutTask: StageHandler | StageHandlerOverallReset): void; +function registerVisual(layoutTask: StageHandler | StageHandlerOverallReset): void; +function registerVisual( + priority: number | StageHandler | StageHandlerOverallReset, + visualTask?: StageHandler | StageHandlerOverallReset +): void { + normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, 'visual'); +} + +export {registerLayout, registerVisual}; + +const registeredTasks: (StageHandler | StageHandlerOverallReset)[] = []; + +function normalizeRegister( + targetList: StageHandler[], + priority: number | StageHandler | StageHandlerOverallReset, + fn: StageHandler | StageHandlerOverallReset, + defaultPriority: number, + visualType?: StageHandlerInternal['visualType'] +): void { + if (isFunction(priority) || isObject(priority)) { + fn = priority as (StageHandler | StageHandlerOverallReset); + priority = defaultPriority; + } + + if (__DEV__) { + if (isNaN(priority) || priority == null) { + throw new Error('Illegal priority'); + } + // Check duplicate + each(targetList, function (wrap) { + assert((wrap as StageHandlerInternal).__raw !== fn); + }); + } + + // Already registered + if (indexOf(registeredTasks, fn) >= 0) { + return; + } + registeredTasks.push(fn); + + const stageHandler = Scheduler.wrapStageHandler(fn, visualType); + + stageHandler.__prio = priority; + stageHandler.__raw = fn; + targetList.push(stageHandler); +} + +export function registerLoading( + name: string, + loadingFx: LoadingEffectCreator +): void { + loadingEffects[name] = loadingFx; +} + +export function extendComponentModel(proto: object): ComponentModel { + // let Clazz = ComponentModel; + // if (superClass) { + // let classType = parseClassType(superClass); + // Clazz = ComponentModel.getClass(classType.main, classType.sub, true); + // } + return (ComponentModel as ComponentModelConstructor).extend(proto) as any; +} + +export function extendComponentView(proto: object): ChartView { + // let Clazz = ComponentView; + // if (superClass) { + // let classType = parseClassType(superClass); + // Clazz = ComponentView.getClass(classType.main, classType.sub, true); + // } + return (ComponentView as ComponentViewConstructor).extend(proto) as any; +} + +export function extendSeriesModel(proto: object): SeriesModel { + // let Clazz = SeriesModel; + // if (superClass) { + // superClass = 'series.' + superClass.replace('series.', ''); + // let classType = parseClassType(superClass); + // Clazz = ComponentModel.getClass(classType.main, classType.sub, true); + // } + return (SeriesModel as SeriesModelConstructor).extend(proto) as any; +} + +export function extendChartView(proto: object): ChartView { + // let Clazz = ChartView; + // if (superClass) { + // superClass = superClass.replace('series.', ''); + // let classType = parseClassType(superClass); + // Clazz = ChartView.getClass(classType.main, true); + // } + return (ChartView as ChartViewConstructor).extend(proto) as any; +} + +/** + * ZRender need a canvas context to do measureText. + * But in node environment canvas may be created by node-canvas. + * So we need to specify how to create a canvas instead of using document.createElement('canvas') + * + * Be careful of using it in the browser. + * + * @example + * let Canvas = require('canvas'); + * let echarts = require('echarts'); + * echarts.setCanvasCreator(function () { + * // Small size is enough. + * return new Canvas(32, 32); + * }); + */ +export function setCanvasCreator(creator: () => HTMLCanvasElement): void { + zrUtil.$override('createCanvas', creator); +} + +/** + * The parameters and usage: see `mapDataStorage.registerMap`. + * Compatible with previous `echarts.registerMap`. + */ +export function registerMap( + mapName: Parameters[0], + geoJson: Parameters[1], + specialAreas?: Parameters[2] +): void { + mapDataStorage.registerMap(mapName, geoJson, specialAreas); +} + +export function getMap(mapName: string) { + // For backward compatibility, only return the first one. + const records = mapDataStorage.retrieveMap(mapName); + // FIXME support SVG, where return not only records[0]. + return records && records[0] && { + // @ts-ignore + geoJson: records[0].geoJSON, + specialAreas: records[0].specialAreas + }; +} + +export const registerTransform = registerExternalTransform; + +/** + * Globa dispatchAction to a specified chart instance. + */ +// export function dispatchAction(payload: { chartId: string } & Payload, opt?: Parameters[1]) { +// if (!payload || !payload.chartId) { +// // Must have chartId to find chart +// return; +// } +// const chart = instances[payload.chartId]; +// if (chart) { +// chart.dispatchAction(payload, opt); +// } +// } + + + +// Buitlin global visual +registerVisual(PRIORITY_VISUAL_GLOBAL, seriesStyleTask); +registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataStyleTask); +registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataColorPaletteTask); + +registerVisual(PRIORITY_VISUAL_GLOBAL, seriesSymbolTask); +registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataSymbolTask); + +registerVisual(PRIORITY_VISUAL_DECAL, decal); + +registerPreprocessor(backwardCompat); +registerProcessor(PRIORITY_PROCESSOR_DATASTACK, dataStack); +registerLoading('default', loadingDefault); + +// Default actions + +registerAction({ + type: HIGHLIGHT_ACTION_TYPE, + event: HIGHLIGHT_ACTION_TYPE, + update: HIGHLIGHT_ACTION_TYPE +}, zrUtil.noop); + +registerAction({ + type: DOWNPLAY_ACTION_TYPE, + event: DOWNPLAY_ACTION_TYPE, + update: DOWNPLAY_ACTION_TYPE +}, zrUtil.noop); + +registerAction({ + type: SELECT_ACTION_TYPE, + event: SELECT_ACTION_TYPE, + update: SELECT_ACTION_TYPE +}, zrUtil.noop); + +registerAction({ + type: UNSELECT_ACTION_TYPE, + event: UNSELECT_ACTION_TYPE, + update: UNSELECT_ACTION_TYPE +}, zrUtil.noop); + +registerAction({ + type: TOGGLE_SELECT_ACTION_TYPE, + event: TOGGLE_SELECT_ACTION_TYPE, + update: TOGGLE_SELECT_ACTION_TYPE +}, zrUtil.noop); + +// Default theme +registerTheme('light', lightTheme); +registerTheme('dark', darkTheme); + +// For backward compatibility, where the namespace `dataTool` will +// be mounted on `echarts` is the extension `dataTool` is imported. +export const dataTool = {}; + +export interface EChartsType extends ECharts {} diff --git a/src/locale.ts b/src/core/locale.ts similarity index 94% rename from src/locale.ts rename to src/core/locale.ts index becfc45a49..0d4b7be409 100644 --- a/src/locale.ts +++ b/src/core/locale.ts @@ -17,12 +17,12 @@ * under the License. */ -import { Dictionary } from './util/types'; -import Model from './model/Model'; +import { Dictionary } from '../util/types'; +import Model from '../model/Model'; import env from 'zrender/src/core/env'; // default import ZH and EN lang -import langEN from './i18n/langEN'; -import langZH from './i18n/langZH'; +import langEN from '../i18n/langEN'; +import langZH from '../i18n/langZH'; import { isString, clone, merge } from 'zrender/src/core/util'; export type LocaleOption = typeof langEN; diff --git a/src/stream/task.ts b/src/core/task.ts similarity index 100% rename from src/stream/task.ts rename to src/core/task.ts diff --git a/src/data/Graph.ts b/src/data/Graph.ts index e71dc750e7..75ec38f654 100644 --- a/src/data/Graph.ts +++ b/src/data/Graph.ts @@ -432,10 +432,26 @@ class GraphEdge { type GetDataName = Host extends GraphEdge ? 'edgeData' : 'data'; +interface GraphDataProxyMixin { + getValue(dimension?: DimensionLoose): ParsedValue; + // TODO: TYPE stricter type. + setVisual(key: string | Dictionary, value?: any): void; + + getVisual(key: string): any, + + setLayout(layout: any, merge?: boolean): void; + + getLayout(): any + + getGraphicEl(): Element + + getRawIndex(): number +} + function createGraphDataProxyMixin( hostName: 'hostGraph', dataName: GetDataName -) { +): GraphDataProxyMixin { return { /** * @param Default 'value'. can be 'a', 'b', 'c', 'd', 'e'. @@ -474,8 +490,8 @@ function createGraphDataProxyMixin( }; -interface GraphEdge extends ReturnType {}; -interface GraphNode extends ReturnType {}; +interface GraphEdge extends GraphDataProxyMixin {}; +interface GraphNode extends GraphDataProxyMixin {}; zrUtil.mixin(GraphNode, createGraphDataProxyMixin('hostGraph', 'data')); zrUtil.mixin(GraphEdge, createGraphDataProxyMixin('hostGraph', 'edgeData')); diff --git a/src/data/Source.ts b/src/data/Source.ts index f5516e6669..5fb0c1e87f 100644 --- a/src/data/Source.ts +++ b/src/data/Source.ts @@ -43,7 +43,7 @@ import { OptionSourceDataOriginal, OptionSourceDataKeyedColumns } from '../util/types'; -import { DatasetOption } from '../component/dataset'; +import { DatasetOption } from '../component/dataset/install'; import { getDataItemValue } from '../util/model'; /** diff --git a/src/data/helper/sourceHelper.ts b/src/data/helper/sourceHelper.ts index 0d17f5c589..99c118d988 100644 --- a/src/data/helper/sourceHelper.ts +++ b/src/data/helper/sourceHelper.ts @@ -47,7 +47,7 @@ import { DimensionIndex, SeriesEncodableModel } from '../../util/types'; -import { DatasetModel } from '../../component/dataset'; +import { DatasetModel } from '../../component/dataset/install'; import SeriesModel from '../../model/Series'; import GlobalModel from '../../model/Global'; import { CoordDimensionDefinition } from './createDimensions'; diff --git a/src/data/helper/sourceManager.ts b/src/data/helper/sourceManager.ts index 9b3dba42e6..bf3f1ece88 100644 --- a/src/data/helper/sourceManager.ts +++ b/src/data/helper/sourceManager.ts @@ -17,7 +17,7 @@ * under the License. */ -import { DatasetModel } from '../../component/dataset'; +import { DatasetModel } from '../../component/dataset/install'; import SeriesModel from '../../model/Series'; import { setAsPrimitive, map, isTypedArray, assert, each, retrieve2 } from 'zrender/src/core/util'; import { SourceMetaRawOption, Source, createSource, cloneSourceShallow } from '../Source'; diff --git a/src/echarts.all.ts b/src/echarts.all.ts index 00d27f804d..4603073388 100644 --- a/src/echarts.all.ts +++ b/src/echarts.all.ts @@ -17,12 +17,9 @@ * under the License. */ -export * from './echarts'; -export * from './export'; - -import './component/dataset'; -import './component/transform'; +import {use} from './extension'; +export * from './export/core'; // ---------------------------------------------- // All of the modules that are allowed to be // imported are listed below. @@ -31,14 +28,82 @@ import './component/transform'; // not included in this list. // ---------------------------------------------- +import { + SVGRenderer, + CanvasRenderer +} from './export/renderers'; + +import { + LineChart, + BarChart, + PieChart, + ScatterChart, + RadarChart, + MapChart, + TreeChart, + TreemapChart, + GraphChart, + GaugeChart, + FunnelChart, + ParallelChart, + SankeyChart, + BoxplotChart, + CandlestickChart, + EffectScatterChart, + LinesChart, + HeatmapChart, + PictorialBarChart, + ThemeRiverChart, + SunburstChart, + CustomChart +} from './export/charts'; + +import { + GridComponent, + PolarComponent, + GeoComponent, + SingleAxisComponent, + ParallelComponent, + CalendarComponent, + GraphicComponent, + ToolboxComponent, + TooltipComponent, + AxisPointerComponent, + BrushComponent, + TitleComponent, + TimelineComponent, + MarkPointComponent, + MarkLineComponent, + MarkAreaComponent, + LegendComponent, + DataZoomComponent, + DataZoomInsideComponent, + DataZoomSliderComponent, + VisualMapComponent, + VisualMapContinuousComponent, + VisualMapPiecewiseComponent, + AriaComponent, + DatasetComponent, + TransformComponent +} from './export/components'; + + +// ----------------- +// Render engines +// ----------------- +// Render via Canvas. +// echarts.init(dom, null, { renderer: 'canvas' }) +use([CanvasRenderer]); +// Render via SVG. +// echarts.init(dom, null, { renderer: 'svg' }) +use([SVGRenderer]); + // ---------------- // Charts (series) // ---------------- - - // All of the series types, for example: // chart.setOption({ // series: [{ @@ -46,30 +111,31 @@ import './component/transform'; // }] // }); -import './chart/line'; -import './chart/bar'; -import './chart/pie'; -import './chart/scatter'; -import './chart/radar'; -import './chart/map'; -import './chart/tree'; -import './chart/treemap'; -import './chart/graph'; -import './chart/gauge'; -import './chart/funnel'; -import './chart/parallel'; -import './chart/sankey'; -import './chart/boxplot'; -import './chart/candlestick'; -import './chart/effectScatter'; -import './chart/lines'; -import './chart/heatmap'; -import './chart/pictorialBar'; -import './chart/themeRiver'; -import './chart/sunburst'; -import './chart/custom'; - +use([ + LineChart, + BarChart, + PieChart, + ScatterChart, + RadarChart, + MapChart, + TreeChart, + TreemapChart, + GraphChart, + GaugeChart, + FunnelChart, + ParallelChart, + SankeyChart, + BoxplotChart, + CandlestickChart, + EffectScatterChart, + LinesChart, + HeatmapChart, + PictorialBarChart, + ThemeRiverChart, + SunburstChart, + CustomChart +]); // ------------------- // Coordinate systems @@ -89,7 +155,7 @@ import './chart/custom'; // yAxis: {...}, // series: [{...}] // }); -import './component/grid'; +use(GridComponent); // `polar` coordinate system, for example: // chart.setOption({ @@ -100,7 +166,7 @@ import './component/grid'; // coordinateSystem: 'polar' // }] // }); -import './component/polar'; +use(PolarComponent); // `geo` coordinate system, for example: // chart.setOption({ @@ -109,7 +175,7 @@ import './component/polar'; // coordinateSystem: 'geo' // }] // }); -import './component/geo'; +use(GeoComponent); // `singleAxis` coordinate system (notice, it is a coordinate system // with only one axis, work for chart like theme river), for example: @@ -117,7 +183,7 @@ import './component/geo'; // singleAxis: {...} // series: [{type: 'themeRiver', ...}] // }); -import './component/singleAxis'; +use(SingleAxisComponent); // `parallel` coordinate system, only work for parallel series, for example: // chart.setOption({ @@ -127,7 +193,7 @@ import './component/singleAxis'; // type: 'parallel' // }] // }); -import './component/parallel'; +use(ParallelComponent); // `calendar` coordinate system. for example, // chart.setOptionp({ @@ -136,7 +202,7 @@ import './component/parallel'; // coordinateSystem: 'calendar' // }] // ); -import './component/calendar'; +use(CalendarComponent); @@ -150,19 +216,19 @@ import './component/calendar'; // chart.setOption({ // graphic: {...} // }); -import './component/graphic'; +use(GraphicComponent); // `toolbox` component, for example: // chart.setOption({ // toolbox: {...} // }); -import './component/toolbox'; +use(ToolboxComponent); // `tooltip` component, for example: // chart.setOption({ // tooltip: {...} // }); -import './component/tooltip'; +use(TooltipComponent); // `axisPointer` component, for example: // chart.setOption({ @@ -172,7 +238,7 @@ import './component/tooltip'; // chart.setOption({ // axisPointer: {...} // }); -import './component/axisPointer'; +use(AxisPointerComponent); // `brush` component, for example: // chart.setOption({ @@ -182,97 +248,88 @@ import './component/axisPointer'; // chart.setOption({ // tooltip: {feature: {brush: {...}} // }) -import './component/brush'; +use(BrushComponent); // `title` component, for example: // chart.setOption({ // title: {...} // }); -import './component/title'; +use(TitleComponent); // `timeline` component, for example: // chart.setOption({ // timeline: {...} // }); -import './component/timeline'; +use(TimelineComponent); // `markPoint` component, for example: // chart.setOption({ // series: [{markPoint: {...}}] // }); -import './component/markPoint'; +use(MarkPointComponent); // `markLine` component, for example: // chart.setOption({ // series: [{markLine: {...}}] // }); -import './component/markLine'; +use(MarkLineComponent); // `markArea` component, for example: // chart.setOption({ // series: [{markArea: {...}}] // }); -import './component/markArea'; - -// `legend` component scrollable, for example: -// chart.setOption({ -// legend: {type: 'scroll'} -// }); -import './component/legendScroll'; +use(MarkAreaComponent); // `legend` component not scrollable. for example: // chart.setOption({ // legend: {...} // }); -import './component/legend'; +use(LegendComponent); // `dataZoom` component including both `dataZoomInside` and `dataZoomSlider`. -import './component/dataZoom'; +use(DataZoomComponent); // `dataZoom` component providing drag, pinch, wheel behaviors // inside coodinate system, for example: // chart.setOption({ // dataZoom: {type: 'inside'} // }); -import './component/dataZoomInside'; +use(DataZoomInsideComponent); // `dataZoom` component providing a slider bar, for example: // chart.setOption({ // dataZoom: {type: 'slider'} // }); -import './component/dataZoomSlider'; +use(DataZoomSliderComponent); // `dataZoom` component including both `visualMapContinuous` and `visualMapPiecewise`. -import './component/visualMap'; +use(VisualMapComponent); // `visualMap` component providing continuous bar, for example: // chart.setOption({ // visualMap: {type: 'continuous'} // }); -import './component/visualMapContinuous'; +use(VisualMapContinuousComponent); // `visualMap` component providing pieces bar, for example: // chart.setOption({ // visualMap: {type: 'piecewise'} // }); -import './component/visualMapPiecewise'; +use(VisualMapPiecewiseComponent); // `aria` component providing aria, for example: // chart.setOption({ // aria: {...} // }); -import './component/aria'; - +use(AriaComponent); -// ----------------- -// Render engines -// ----------------- - - - -// Provide IE 6,7,8 compatibility. -// import 'zrender/vml/vml'; +// dataset transform +// chart.setOption({ +// dataset: { +// transform: [] +// } +// }); +use(TransformComponent); -// Render via SVG rather than canvas. -import 'zrender/src/svg/svg'; +use(DatasetComponent); \ No newline at end of file diff --git a/src/echarts.blank.ts b/src/echarts.blank.ts index b900e3a7f9..d8ae94b91e 100644 --- a/src/echarts.blank.ts +++ b/src/echarts.blank.ts @@ -17,7 +17,4 @@ * under the License. */ -export * from './echarts'; -export * from './export'; - -import './component/dataset'; +export * from './echarts'; \ No newline at end of file diff --git a/src/echarts.common.ts b/src/echarts.common.ts index 9741cebfed..d8be6f6d7c 100644 --- a/src/echarts.common.ts +++ b/src/echarts.common.ts @@ -17,29 +17,56 @@ * under the License. */ -export * from './echarts'; -export * from './export'; - -import './component/dataset'; - -import './chart/line'; -import './chart/bar'; -import './chart/pie'; -import './chart/scatter'; -import './component/graphic'; -import './component/tooltip'; -import './component/axisPointer'; -import './component/legendScroll'; - -import './component/grid'; -import './component/title'; - -import './component/markPoint'; -import './component/markLine'; -import './component/markArea'; -import './component/dataZoom'; -import './component/toolbox'; -import './component/aria'; - -// import 'zrender/vml/vml'; -import 'zrender/src/svg/svg'; +import {use} from './extension'; + +export * from './export/core'; + +import {install as SVGRenderer} from './renderer/installSVGRenderer'; +import {install as CanvasRenderer} from './renderer/installCanvasRenderer'; + +import {install as LineChart} from './chart/line/install'; +import {install as BarChart} from './chart/bar/install'; +import {install as PieChart} from './chart/pie/install'; +import {install as ScatterChart} from './chart/scatter/install'; + + +import {install as GridComponent} from './component/grid/install'; +import {install as GraphicComponent} from './component/graphic/install'; +import {install as ToolboxComponent} from './component/toolbox/install'; +import {install as TooltipComponent} from './component/tooltip/install'; +import {install as AxisPointerComponent} from './component/axisPointer/install'; +import {install as TitleComponent} from './component/title/install'; +import {install as MarkPointComponent} from './component/marker/installMarkPoint'; +import {install as MarkLineComponent} from './component/marker/installMarkLine'; +import {install as MarkAreaComponent} from './component/marker/installMarkArea'; +import {install as LegendComponent} from './component/legend/install'; +import {install as DataZoomComponent} from './component/dataZoom/install'; +import {install as AriaComponent} from './component/aria/install'; +import {install as DatasetComponent} from './component/dataset/install'; + + +use([CanvasRenderer]); +use([SVGRenderer]); + +use([ + LineChart, + BarChart, + PieChart, + ScatterChart +]); + +use([ + GraphicComponent, + TooltipComponent, + AxisPointerComponent, + LegendComponent, + GridComponent, + TitleComponent, + MarkPointComponent, + MarkLineComponent, + MarkAreaComponent, + DataZoomComponent, + ToolboxComponent, + AriaComponent, + DatasetComponent +]); diff --git a/src/echarts.simple.ts b/src/echarts.simple.ts index 8fede486c1..5dde0d2924 100644 --- a/src/echarts.simple.ts +++ b/src/echarts.simple.ts @@ -17,12 +17,30 @@ * under the License. */ -export * from './echarts'; +import {use} from './extension'; -import './component/dataset'; +export * from './export/core'; -import './chart/line'; -import './chart/bar'; -import './chart/pie'; -import './component/gridSimple'; -import './component/aria'; +import {install as CanvasRenderer} from './renderer/installCanvasRenderer'; + +import {install as LineChart} from './chart/line/install'; +import {install as BarChart} from './chart/bar/install'; +import {install as PieChart} from './chart/pie/install'; + +import {install as GridSimpleComponent} from './component/grid/installSimple'; +import {install as AriaComponent} from './component/aria/install'; +import {install as DatasetComponent} from './component/dataset/install'; + +use([CanvasRenderer]); + +use([ + LineChart, + BarChart, + PieChart +]); + +use([ + GridSimpleComponent, + AriaComponent, + DatasetComponent +]); diff --git a/src/echarts.ts b/src/echarts.ts index 6635337964..c457055bba 100644 --- a/src/echarts.ts +++ b/src/echarts.ts @@ -16,2927 +16,26 @@ * specific language governing permissions and limitations * under the License. */ -import * as zrender from 'zrender/src/zrender'; -import * as zrUtil from 'zrender/src/core/util'; -import * as colorTool from 'zrender/src/tool/color'; -import env from 'zrender/src/core/env'; -import timsort from 'zrender/src/core/timsort'; -import Eventful from 'zrender/src/core/Eventful'; -import Element, { ElementEvent } from 'zrender/src/Element'; -import CanvasPainter from 'zrender/src/canvas/Painter'; -import SVGPainter from 'zrender/src/svg/Painter'; -import GlobalModel, {QueryConditionKindA, GlobalModelSetOptionOpts} from './model/Global'; -import ExtensionAPI from './ExtensionAPI'; -import CoordinateSystemManager from './CoordinateSystem'; -import OptionManager from './model/OptionManager'; -import backwardCompat from './preprocessor/backwardCompat'; -import dataStack from './processor/dataStack'; -import ComponentModel, { ComponentModelConstructor } from './model/Component'; -import SeriesModel, { SeriesModelConstructor } from './model/Series'; -import ComponentView, {ComponentViewConstructor} from './view/Component'; -import ChartView, {ChartViewConstructor} from './view/Chart'; -import * as graphic from './util/graphic'; -import {getECData} from './util/innerStore'; -import { - enterEmphasisWhenMouseOver, - leaveEmphasisWhenMouseOut, - isHighDownDispatcher, - HOVER_STATE_EMPHASIS, - HOVER_STATE_BLUR, - toggleSeriesBlurState, - toggleSeriesBlurStateFromPayload, - toggleSelectionFromPayload, - updateSeriesElementSelection, - getAllSelectedIndices, - isSelectChangePayload, - isHighDownPayload, - HIGHLIGHT_ACTION_TYPE, - DOWNPLAY_ACTION_TYPE, - SELECT_ACTION_TYPE, - UNSELECT_ACTION_TYPE, - TOGGLE_SELECT_ACTION_TYPE, - savePathStates, - enterEmphasis, - leaveEmphasis, - leaveBlur, - enterSelect, - leaveSelect, - enterBlur -} from './util/states'; -import * as modelUtil from './util/model'; -import {throttle} from './util/throttle'; -import {seriesStyleTask, dataStyleTask, dataColorPaletteTask} from './visual/style'; -import loadingDefault from './loading/default'; -import Scheduler from './stream/Scheduler'; -import lightTheme from './theme/light'; -import darkTheme from './theme/dark'; -import './component/dataset'; -import mapDataStorage from './coord/geo/mapDataStorage'; -import {CoordinateSystemMaster, CoordinateSystemCreator, CoordinateSystemHostModel} from './coord/CoordinateSystem'; -import { parseClassType } from './util/clazz'; -import {ECEventProcessor} from './util/ECEventProcessor'; -import { - Payload, ECElement, RendererType, ECEvent, - ActionHandler, ActionInfo, OptionPreprocessor, PostUpdater, - LoadingEffect, LoadingEffectCreator, StageHandlerInternal, - StageHandlerOverallReset, StageHandler, - ViewRootGroup, DimensionDefinitionLoose, ECEventData, ThemeOption, - ECOption, - ECUnitOption, - ZRColor, - ComponentMainType, - ComponentSubType, - ColorString, - SelectChangedPayload, - DimensionLoose, - ScaleDataValue -} from './util/types'; -import Displayable from 'zrender/src/graphic/Displayable'; -import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable'; -import { seriesSymbolTask, dataSymbolTask } from './visual/symbol'; -import { getVisualFromData, getItemVisualFromData } from './visual/helper'; -import LabelManager from './label/LabelManager'; -import { deprecateLog, throwError } from './util/log'; -import { handleLegacySelectEvents } from './legacy/dataSelectAction'; -// At least canvas renderer. -import 'zrender/src/canvas/canvas'; -import { registerExternalTransform } from './data/helper/transform'; -import { createLocaleObject, SYSTEM_LANG, LocaleOption } from './locale'; +export * from './export/core'; +import { use } from './extension'; +import { init } from './core/echarts'; -import type {EChartsFullOption} from './option'; -import { findEventDispatcher } from './util/event'; -import decal from './visual/decal'; -import {MorphDividingMethod} from 'zrender/src/tool/morphPath'; +import {install as CanvasRenderer} from './renderer/installCanvasRenderer'; +import {install as DatasetComponent} from './component/dataset/install'; -declare let global: any; -type ModelFinder = modelUtil.ModelFinder; +// Default to have canvas renderer and dataset for compitatble reason. +use([CanvasRenderer, DatasetComponent]); -const assert = zrUtil.assert; -const each = zrUtil.each; -const isFunction = zrUtil.isFunction; -const isObject = zrUtil.isObject; - -export const version = '5.0.0'; - -export const dependencies = { - zrender: '5.0.1' -}; - -const TEST_FRAME_REMAIN_TIME = 1; - -const PRIORITY_PROCESSOR_SERIES_FILTER = 800; -// Some data processors depends on the stack result dimension (to calculate data extent). -// So data stack stage should be in front of data processing stage. -const PRIORITY_PROCESSOR_DATASTACK = 900; -// "Data filter" will block the stream, so it should be -// put at the begining of data processing. -const PRIORITY_PROCESSOR_FILTER = 1000; -const PRIORITY_PROCESSOR_DEFAULT = 2000; -const PRIORITY_PROCESSOR_STATISTIC = 5000; - -const PRIORITY_VISUAL_LAYOUT = 1000; -const PRIORITY_VISUAL_PROGRESSIVE_LAYOUT = 1100; -const PRIORITY_VISUAL_GLOBAL = 2000; -const PRIORITY_VISUAL_CHART = 3000; -const PRIORITY_VISUAL_COMPONENT = 4000; -// Visual property in data. Greater than `PRIORITY_VISUAL_COMPONENT` to enable to -// overwrite the viusal result of component (like `visualMap`) -// using data item specific setting (like itemStyle.xxx on data item) -const PRIORITY_VISUAL_CHART_DATA_CUSTOM = 4500; -// Greater than `PRIORITY_VISUAL_CHART_DATA_CUSTOM` to enable to layout based on -// visual result like `symbolSize`. -const PRIORITY_VISUAL_POST_CHART_LAYOUT = 4600; -const PRIORITY_VISUAL_BRUSH = 5000; -const PRIORITY_VISUAL_ARIA = 6000; -const PRIORITY_VISUAL_DECAL = 7000; - -export const PRIORITY = { - PROCESSOR: { - FILTER: PRIORITY_PROCESSOR_FILTER, - SERIES_FILTER: PRIORITY_PROCESSOR_SERIES_FILTER, - STATISTIC: PRIORITY_PROCESSOR_STATISTIC - }, - VISUAL: { - LAYOUT: PRIORITY_VISUAL_LAYOUT, - PROGRESSIVE_LAYOUT: PRIORITY_VISUAL_PROGRESSIVE_LAYOUT, - GLOBAL: PRIORITY_VISUAL_GLOBAL, - CHART: PRIORITY_VISUAL_CHART, - POST_CHART_LAYOUT: PRIORITY_VISUAL_POST_CHART_LAYOUT, - COMPONENT: PRIORITY_VISUAL_COMPONENT, - BRUSH: PRIORITY_VISUAL_BRUSH, - CHART_ITEM: PRIORITY_VISUAL_CHART_DATA_CUSTOM, - ARIA: PRIORITY_VISUAL_ARIA, - DECAL: PRIORITY_VISUAL_DECAL - } -}; - -// Main process have three entries: `setOption`, `dispatchAction` and `resize`, -// where they must not be invoked nestedly, except the only case: invoke -// dispatchAction with updateMethod "none" in main process. -// This flag is used to carry out this rule. -// All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]). -const IN_MAIN_PROCESS_KEY = '__flagInMainProcess' as const; -const OPTION_UPDATED_KEY = '__optionUpdated' as const; -const STATUS_NEEDS_UPDATE_KEY = '__needsUpdateStatus' as const; -const ACTION_REG = /^[a-zA-Z0-9_]+$/; - -const CONNECT_STATUS_KEY = '__connectUpdateStatus' as const; -const CONNECT_STATUS_PENDING = 0 as const; -const CONNECT_STATUS_UPDATING = 1 as const; -const CONNECT_STATUS_UPDATED = 2 as const; -type ConnectStatus = - typeof CONNECT_STATUS_PENDING - | typeof CONNECT_STATUS_UPDATING - | typeof CONNECT_STATUS_UPDATED; - -interface SetOptionOpts { - notMerge?: boolean; - lazyUpdate?: boolean; - silent?: boolean; - // Rule: only `id` mapped will be merged, - // other components of the certain `mainType` will be removed. - replaceMerge?: GlobalModelSetOptionOpts['replaceMerge']; - transition?: SetOptionTransitionOpt -}; - -export interface SetOptionTransitionOptItem { - // If `from` not given, it means that do not make series transition mandatorily. - // There might be transition mapping dy default. Sometimes we do not need them, - // which might bring about misleading. - from?: SetOptionTransitionOptFinder; - to: SetOptionTransitionOptFinder; - dividingMethod: MorphDividingMethod; -} -interface SetOptionTransitionOptFinder extends modelUtil.ModelFinderObject { - dimension: DimensionLoose; -} -type SetOptionTransitionOpt = SetOptionTransitionOptItem | SetOptionTransitionOptItem[]; - -interface PostIniter { - (chart: EChartsType): void -} - -type EventMethodName = 'on' | 'off'; -function createRegisterEventWithLowercaseECharts(method: EventMethodName) { - return function (this: ECharts, ...args: any): ECharts { - if (this.isDisposed()) { - disposedWarning(this.id); - return; - } - return toLowercaseNameAndCallEventful(this, method, args); - }; -} -function createRegisterEventWithLowercaseMessageCenter(method: EventMethodName) { - return function (this: MessageCenter, ...args: any): MessageCenter { - return toLowercaseNameAndCallEventful(this, method, args); - }; -} -function toLowercaseNameAndCallEventful(host: T, method: EventMethodName, args: any): T { - // `args[0]` is event name. Event name is all lowercase. - args[0] = args[0] && args[0].toLowerCase(); - return Eventful.prototype[method].apply(host, args) as any; -} - - -class MessageCenter extends Eventful {} -const messageCenterProto = MessageCenter.prototype; -messageCenterProto.on = createRegisterEventWithLowercaseMessageCenter('on'); -messageCenterProto.off = createRegisterEventWithLowercaseMessageCenter('off'); - -// --------------------------------------- -// Internal method names for class ECharts -// --------------------------------------- -let prepare: (ecIns: ECharts) => void; -let prepareView: (ecIns: ECharts, isComponent: boolean) => void; -let updateDirectly: ( - ecIns: ECharts, method: string, payload: Payload, mainType: ComponentMainType, subType?: ComponentSubType -) => void; -type UpdateMethod = (this: ECharts, payload?: Payload) => void; -let updateMethods: { - prepareAndUpdate: UpdateMethod, - update: UpdateMethod, - updateTransform: UpdateMethod, - updateView: UpdateMethod, - updateVisual: UpdateMethod, - updateLayout: UpdateMethod -}; -let doConvertPixel: ( - ecIns: ECharts, - methodName: string, - finder: ModelFinder, - value: (number | number[]) | (ScaleDataValue | ScaleDataValue[]) -) => (number | number[]); -let updateStreamModes: (ecIns: ECharts, ecModel: GlobalModel) => void; -let doDispatchAction: (this: ECharts, payload: Payload, silent: boolean) => void; -let flushPendingActions: (this: ECharts, silent: boolean) => void; -let triggerUpdatedEvent: (this: ECharts, silent: boolean) => void; -let bindRenderedEvent: (zr: zrender.ZRenderType, ecIns: ECharts) => void; -let bindMouseEvent: (zr: zrender.ZRenderType, ecIns: ECharts) => void; -let clearColorPalette: (ecModel: GlobalModel) => void; -let render: (ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) => void; -let renderComponents: ( - ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, dirtyList?: ComponentView[] -) => void; -let renderSeries: ( - ecIns: ECharts, - ecModel: GlobalModel, - api: ExtensionAPI, - payload: Payload | 'remain', - dirtyMap?: {[uid: string]: any} -) => void; -let performPostUpdateFuncs: (ecModel: GlobalModel, api: ExtensionAPI) => void; -let createExtensionAPI: (ecIns: ECharts) => ExtensionAPI; -let enableConnect: (ecIns: ECharts) => void; -let setTransitionOpt: ( - chart: ECharts, - transitionOpt: SetOptionTransitionOpt -) => void; - -let markStatusToUpdate: (ecIns: ECharts) => void; -let applyChangedStates: (ecIns: ECharts) => void; -class ECharts extends Eventful { - - /** - * @readonly - */ - id: string; - - /** - * Group id - * @readonly - */ - group: string; - - private _zr: zrender.ZRenderType; - - private _dom: HTMLElement; - - private _model: GlobalModel; - - private _throttledZrFlush: zrender.ZRenderType extends {flush: infer R} ? R : never; - - private _theme: ThemeOption; - - private _locale: LocaleOption; - - private _chartsViews: ChartView[] = []; - - private _chartsMap: {[viewId: string]: ChartView} = {}; - - private _componentsViews: ComponentView[] = []; - - private _componentsMap: {[viewId: string]: ComponentView} = {}; - - private _coordSysMgr: CoordinateSystemManager; - - private _api: ExtensionAPI; - - private _scheduler: Scheduler; - - private _messageCenter: MessageCenter; - - // Can't dispatch action during rendering procedure - private _pendingActions: Payload[] = []; - - // We use never here so ECEventProcessor will not been exposed. - // which may include many unexpected types won't be exposed in the types to developers. - protected _$eventProcessor: never; - - private _disposed: boolean; - - private _loadingFX: LoadingEffect; - - private _labelManager: LabelManager; - - - private [OPTION_UPDATED_KEY]: boolean | {silent: boolean}; - private [IN_MAIN_PROCESS_KEY]: boolean; - private [CONNECT_STATUS_KEY]: ConnectStatus; - private [STATUS_NEEDS_UPDATE_KEY]: boolean; - - constructor( - dom: HTMLElement, - // Theme name or themeOption. - theme?: string | ThemeOption, - opts?: { - locale?: string | LocaleOption, - renderer?: RendererType, - devicePixelRatio?: number, - useDirtyRect?: boolean, - width?: number, - height?: number - } - ) { - super(new ECEventProcessor()); - - opts = opts || {}; - - // Get theme by name - if (typeof theme === 'string') { - theme = themeStorage[theme] as object; - } - - this._dom = dom; - - const root = ( - typeof window === 'undefined' ? global : window - ) as any; - - let defaultRenderer = 'canvas'; - let defaultUseDirtyRect = false; - if (__DEV__) { - defaultRenderer = root.__ECHARTS__DEFAULT__RENDERER__ || defaultRenderer; - - const devUseDirtyRect = root.__ECHARTS__DEFAULT__USE_DIRTY_RECT__; - defaultUseDirtyRect = devUseDirtyRect == null - ? defaultUseDirtyRect - : devUseDirtyRect; - } - - const zr = this._zr = zrender.init(dom, { - renderer: opts.renderer || defaultRenderer, - devicePixelRatio: opts.devicePixelRatio, - width: opts.width, - height: opts.height, - useDirtyRect: opts.useDirtyRect == null ? defaultUseDirtyRect : opts.useDirtyRect - }); - - // Expect 60 fps. - this._throttledZrFlush = throttle(zrUtil.bind(zr.flush, zr), 17); - - theme = zrUtil.clone(theme); - theme && backwardCompat(theme as ECUnitOption, true); - - this._theme = theme; - - this._locale = createLocaleObject(opts.locale || SYSTEM_LANG); - - this._coordSysMgr = new CoordinateSystemManager(); - - const api = this._api = createExtensionAPI(this); - - // Sort on demand - function prioritySortFunc(a: StageHandlerInternal, b: StageHandlerInternal): number { - return a.__prio - b.__prio; - } - timsort(visualFuncs, prioritySortFunc); - timsort(dataProcessorFuncs, prioritySortFunc); - - this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs); - - this._messageCenter = new MessageCenter(); - - this._labelManager = new LabelManager(); - - // Init mouse events - this._initEvents(); - - // In case some people write `window.onresize = chart.resize` - this.resize = zrUtil.bind(this.resize, this); - - zr.animation.on('frame', this._onframe, this); - - bindRenderedEvent(zr, this); - - bindMouseEvent(zr, this); - - // ECharts instance can be used as value. - zrUtil.setAsPrimitive(this); - } - - private _onframe(): void { - if (this._disposed) { - return; - } - - applyChangedStates(this); - - const scheduler = this._scheduler; - - // Lazy update - if (this[OPTION_UPDATED_KEY]) { - const silent = (this[OPTION_UPDATED_KEY] as any).silent; - - this[IN_MAIN_PROCESS_KEY] = true; - - prepare(this); - updateMethods.update.call(this); - - // At present, in each frame, zrender performs: - // (1) animation step forward. - // (2) trigger('frame') (where this `_onframe` is called) - // (3) zrender flush (render). - // If we do nothing here, since we use `setToFinal: true`, the step (3) above - // will render the final state of the elements before the real animation started. - this._zr.flush(); - - this[IN_MAIN_PROCESS_KEY] = false; - - this[OPTION_UPDATED_KEY] = false; - - flushPendingActions.call(this, silent); - - triggerUpdatedEvent.call(this, silent); - } - // Avoid do both lazy update and progress in one frame. - else if (scheduler.unfinished) { - // Stream progress. - let remainTime = TEST_FRAME_REMAIN_TIME; - const ecModel = this._model; - const api = this._api; - scheduler.unfinished = false; - do { - const startTime = +new Date(); - - scheduler.performSeriesTasks(ecModel); - - // Currently dataProcessorFuncs do not check threshold. - scheduler.performDataProcessorTasks(ecModel); - - updateStreamModes(this, ecModel); - - // Do not update coordinate system here. Because that coord system update in - // each frame is not a good user experience. So we follow the rule that - // the extent of the coordinate system is determin in the first frame (the - // frame is executed immedietely after task reset. - // this._coordSysMgr.update(ecModel, api); - - // console.log('--- ec frame visual ---', remainTime); - scheduler.performVisualTasks(ecModel); - - renderSeries(this, this._model, api, 'remain'); - - remainTime -= (+new Date() - startTime); - } - while (remainTime > 0 && scheduler.unfinished); - - // Call flush explicitly for trigger finished event. - if (!scheduler.unfinished) { - this._zr.flush(); - } - // Else, zr flushing be ensue within the same frame, - // because zr flushing is after onframe event. - } - } - - getDom(): HTMLElement { - return this._dom; - } - - getId(): string { - return this.id; - } - - getZr(): zrender.ZRenderType { - return this._zr; - } - - /** - * Usage: - * chart.setOption(option, notMerge, lazyUpdate); - * chart.setOption(option, { - * notMerge: ..., - * lazyUpdate: ..., - * silent: ... - * }); - * - * @param opts opts or notMerge. - * @param opts.notMerge Default `false`. - * @param opts.lazyUpdate Default `false`. Useful when setOption frequently. - * @param opts.silent Default `false`. - * @param opts.replaceMerge Default undefined. - */ - // Expose to user full option. - setOption(option: EChartsFullOption, notMerge?: boolean, lazyUpdate?: boolean): void; - setOption(option: EChartsFullOption, opts?: SetOptionOpts): void; - setOption(option: EChartsFullOption, notMerge?: boolean | SetOptionOpts, lazyUpdate?: boolean): void { - if (__DEV__) { - assert(!this[IN_MAIN_PROCESS_KEY], '`setOption` should not be called during main process.'); - } - if (this._disposed) { - disposedWarning(this.id); - return; - } - - let silent; - let replaceMerge; - let transitionOpt: SetOptionTransitionOpt; - if (isObject(notMerge)) { - lazyUpdate = notMerge.lazyUpdate; - silent = notMerge.silent; - replaceMerge = notMerge.replaceMerge; - transitionOpt = notMerge.transition; - notMerge = notMerge.notMerge; - } - - this[IN_MAIN_PROCESS_KEY] = true; - - if (!this._model || notMerge) { - const optionManager = new OptionManager(this._api); - const theme = this._theme; - const ecModel = this._model = new GlobalModel(); - ecModel.scheduler = this._scheduler; - ecModel.init(null, null, null, theme, this._locale, optionManager); - } - - this._model.setOption(option as ECOption, { replaceMerge }, optionPreprocessorFuncs); - - setTransitionOpt(this, transitionOpt); - - if (lazyUpdate) { - this[OPTION_UPDATED_KEY] = {silent: silent}; - this[IN_MAIN_PROCESS_KEY] = false; - - // `setOption(option, {lazyMode: true})` may be called when zrender has been slept. - // It should wake it up to make sure zrender start to render at the next frame. - this.getZr().wakeUp(); - } - else { - prepare(this); - - updateMethods.update.call(this); - - // Ensure zr refresh sychronously, and then pixel in canvas can be - // fetched after `setOption`. - this._zr.flush(); - - this[OPTION_UPDATED_KEY] = false; - this[IN_MAIN_PROCESS_KEY] = false; - - flushPendingActions.call(this, silent); - triggerUpdatedEvent.call(this, silent); - } - } - - /** - * @DEPRECATED - */ - private setTheme(): void { - console.error('ECharts#setTheme() is DEPRECATED in ECharts 3.0'); - } - - // We don't want developers to use getModel directly. - private getModel(): GlobalModel { - return this._model; - } - - getOption(): EChartsFullOption { - return this._model && this._model.getOption() as EChartsFullOption; - } - - getWidth(): number { - return this._zr.getWidth(); - } - - getHeight(): number { - return this._zr.getHeight(); - } - - getDevicePixelRatio(): number { - return (this._zr.painter as CanvasPainter).dpr || window.devicePixelRatio || 1; - } - - /** - * Get canvas which has all thing rendered - */ - getRenderedCanvas(opts?: { - backgroundColor?: ZRColor - pixelRatio?: number - }): HTMLCanvasElement { - if (!env.canvasSupported) { - return; - } - opts = zrUtil.extend({}, opts || {}); - opts.pixelRatio = opts.pixelRatio || 1; - opts.backgroundColor = opts.backgroundColor - || this._model.get('backgroundColor'); - const zr = this._zr; - // let list = zr.storage.getDisplayList(); - // Stop animations - // Never works before in init animation, so remove it. - // zrUtil.each(list, function (el) { - // el.stopAnimation(true); - // }); - return (zr.painter as CanvasPainter).getRenderedCanvas(opts); - } - - /** - * Get svg data url - */ - getSvgDataURL(): string { - if (!env.svgSupported) { - return; - } - - const zr = this._zr; - const list = zr.storage.getDisplayList(); - // Stop animations - zrUtil.each(list, function (el: Element) { - el.stopAnimation(null, true); - }); - - return (zr.painter as SVGPainter).toDataURL(); - } - - getDataURL(opts?: { - // file type 'png' by default - type?: 'png' | 'jpg' | 'svg', - pixelRatio?: number, - backgroundColor?: ZRColor, - // component type array - excludeComponents?: ComponentMainType[] - }): string { - if (this._disposed) { - disposedWarning(this.id); - return; - } - - opts = opts || {}; - const excludeComponents = opts.excludeComponents; - const ecModel = this._model; - const excludesComponentViews: ComponentView[] = []; - const self = this; - - each(excludeComponents, function (componentType) { - ecModel.eachComponent({ - mainType: componentType - }, function (component) { - const view = self._componentsMap[component.__viewId]; - if (!view.group.ignore) { - excludesComponentViews.push(view); - view.group.ignore = true; - } - }); - }); - - const url = this._zr.painter.getType() === 'svg' - ? this.getSvgDataURL() - : this.getRenderedCanvas(opts).toDataURL( - 'image/' + (opts && opts.type || 'png') - ); - - each(excludesComponentViews, function (view) { - view.group.ignore = false; - }); - - return url; - } - - getConnectedDataURL(opts?: { - // file type 'png' by default - type?: 'png' | 'jpg' | 'svg', - pixelRatio?: number, - backgroundColor?: ZRColor, - connectedBackgroundColor?: ZRColor - excludeComponents?: string[] - }): string { - if (this._disposed) { - disposedWarning(this.id); - return; - } - - if (!env.canvasSupported) { - return; - } - const isSvg = opts.type === 'svg'; - const groupId = this.group; - const mathMin = Math.min; - const mathMax = Math.max; - const MAX_NUMBER = Infinity; - if (connectedGroups[groupId]) { - let left = MAX_NUMBER; - let top = MAX_NUMBER; - let right = -MAX_NUMBER; - let bottom = -MAX_NUMBER; - const canvasList: {dom: HTMLCanvasElement | string, left: number, top: number}[] = []; - const dpr = (opts && opts.pixelRatio) || 1; - - zrUtil.each(instances, function (chart, id) { - if (chart.group === groupId) { - const canvas = isSvg - ? (chart.getZr().painter as SVGPainter).getSvgDom().innerHTML - : chart.getRenderedCanvas(zrUtil.clone(opts)); - const boundingRect = chart.getDom().getBoundingClientRect(); - left = mathMin(boundingRect.left, left); - top = mathMin(boundingRect.top, top); - right = mathMax(boundingRect.right, right); - bottom = mathMax(boundingRect.bottom, bottom); - canvasList.push({ - dom: canvas, - left: boundingRect.left, - top: boundingRect.top - }); - } - }); - - left *= dpr; - top *= dpr; - right *= dpr; - bottom *= dpr; - const width = right - left; - const height = bottom - top; - const targetCanvas = zrUtil.createCanvas(); - const zr = zrender.init(targetCanvas, { - renderer: isSvg ? 'svg' : 'canvas' - }); - zr.resize({ - width: width, - height: height - }); - - if (isSvg) { - let content = ''; - each(canvasList, function (item) { - const x = item.left - left; - const y = item.top - top; - content += '' + item.dom + ''; - }); - (zr.painter as SVGPainter).getSvgRoot().innerHTML = content; - - if (opts.connectedBackgroundColor) { - (zr.painter as SVGPainter).setBackgroundColor(opts.connectedBackgroundColor as string); - } - - zr.refreshImmediately(); - return (zr.painter as SVGPainter).toDataURL(); - } - else { - // Background between the charts - if (opts.connectedBackgroundColor) { - zr.add(new graphic.Rect({ - shape: { - x: 0, - y: 0, - width: width, - height: height - }, - style: { - fill: opts.connectedBackgroundColor - } - })); - } - - each(canvasList, function (item) { - const img = new graphic.Image({ - style: { - x: item.left * dpr - left, - y: item.top * dpr - top, - image: item.dom - } - }); - zr.add(img); - }); - zr.refreshImmediately(); - - return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png')); - } - } - else { - return this.getDataURL(opts); - } - } - - /** - * Convert from logical coordinate system to pixel coordinate system. - * See CoordinateSystem#convertToPixel. - */ - convertToPixel(finder: ModelFinder, value: ScaleDataValue): number; - convertToPixel(finder: ModelFinder, value: ScaleDataValue[]): number[]; - convertToPixel(finder: ModelFinder, value: ScaleDataValue | ScaleDataValue[]): number | number[] { - return doConvertPixel(this, 'convertToPixel', finder, value); - } - - /** - * Convert from pixel coordinate system to logical coordinate system. - * See CoordinateSystem#convertFromPixel. - */ - convertFromPixel(finder: ModelFinder, value: number): number; - convertFromPixel(finder: ModelFinder, value: number[]): number[]; - convertFromPixel(finder: ModelFinder, value: number | number[]): number | number[] { - return doConvertPixel(this, 'convertFromPixel', finder, value); - } - - /** - * Is the specified coordinate systems or components contain the given pixel point. - * @param {Array|number} value - * @return {boolean} result - */ - containPixel(finder: ModelFinder, value: number[]): boolean { - if (this._disposed) { - disposedWarning(this.id); - return; - } - - const ecModel = this._model; - let result: boolean; - - const findResult = modelUtil.parseFinder(ecModel, finder); - - zrUtil.each(findResult, function (models, key) { - key.indexOf('Models') >= 0 && zrUtil.each(models as ComponentModel[], function (model) { - const coordSys = (model as CoordinateSystemHostModel).coordinateSystem; - if (coordSys && coordSys.containPoint) { - result = result || !!coordSys.containPoint(value); - } - else if (key === 'seriesModels') { - const view = this._chartsMap[model.__viewId]; - if (view && view.containPoint) { - result = result || view.containPoint(value, model as SeriesModel); - } - else { - if (__DEV__) { - console.warn(key + ': ' + (view - ? 'The found component do not support containPoint.' - : 'No view mapping to the found component.' - )); - } - } - } - else { - if (__DEV__) { - console.warn(key + ': containPoint is not supported'); - } - } - }, this); - }, this); - - return !!result; - } - - /** - * Get visual from series or data. - * @param finder - * If string, e.g., 'series', means {seriesIndex: 0}. - * If Object, could contain some of these properties below: - * { - * seriesIndex / seriesId / seriesName, - * dataIndex / dataIndexInside - * } - * If dataIndex is not specified, series visual will be fetched, - * but not data item visual. - * If all of seriesIndex, seriesId, seriesName are not specified, - * visual will be fetched from first series. - * @param visualType 'color', 'symbol', 'symbolSize' - */ - getVisual(finder: ModelFinder, visualType: string) { - const ecModel = this._model; - - const parsedFinder = modelUtil.parseFinder(ecModel, finder, { - defaultMainType: 'series' - }); - - const seriesModel = parsedFinder.seriesModel; - - if (__DEV__) { - if (!seriesModel) { - console.warn('There is no specified seires model'); - } - } - - const data = seriesModel.getData(); - - const dataIndexInside = parsedFinder.hasOwnProperty('dataIndexInside') - ? parsedFinder.dataIndexInside - : parsedFinder.hasOwnProperty('dataIndex') - ? data.indexOfRawIndex(parsedFinder.dataIndex) - : null; - - return dataIndexInside != null - ? getItemVisualFromData(data, dataIndexInside, visualType) - : getVisualFromData(data, visualType); - } - - /** - * Get view of corresponding component model - */ - private getViewOfComponentModel(componentModel: ComponentModel): ComponentView { - return this._componentsMap[componentModel.__viewId]; - } - - /** - * Get view of corresponding series model - */ - private getViewOfSeriesModel(seriesModel: SeriesModel): ChartView { - return this._chartsMap[seriesModel.__viewId]; - } - - - private _initEvents(): void { - each(MOUSE_EVENT_NAMES, (eveName) => { - const handler = (e: ElementEvent) => { - const ecModel = this.getModel(); - const el = e.target; - let params: ECEvent; - const isGlobalOut = eveName === 'globalout'; - // no e.target when 'globalout'. - if (isGlobalOut) { - params = {} as ECEvent; - } - else { - el && findEventDispatcher(el, (parent) => { - const ecData = getECData(parent); - if (ecData && ecData.dataIndex != null) { - const dataModel = ecData.dataModel || ecModel.getSeriesByIndex(ecData.seriesIndex); - params = ( - dataModel && dataModel.getDataParams(ecData.dataIndex, ecData.dataType) || {} - ) as ECEvent; - return true; - } - // If element has custom eventData of components - else if (ecData.eventData) { - params = zrUtil.extend({}, ecData.eventData) as ECEvent; - return true; - } - }, true); - } - - // Contract: if params prepared in mouse event, - // these properties must be specified: - // { - // componentType: string (component main type) - // componentIndex: number - // } - // Otherwise event query can not work. - - if (params) { - let componentType = params.componentType; - let componentIndex = params.componentIndex; - // Special handling for historic reason: when trigger by - // markLine/markPoint/markArea, the componentType is - // 'markLine'/'markPoint'/'markArea', but we should better - // enable them to be queried by seriesIndex, since their - // option is set in each series. - if (componentType === 'markLine' - || componentType === 'markPoint' - || componentType === 'markArea' - ) { - componentType = 'series'; - componentIndex = params.seriesIndex; - } - const model = componentType && componentIndex != null - && ecModel.getComponent(componentType, componentIndex); - const view = model && this[ - model.mainType === 'series' ? '_chartsMap' : '_componentsMap' - ][model.__viewId]; - - if (__DEV__) { - // `event.componentType` and `event[componentTpype + 'Index']` must not - // be missed, otherwise there is no way to distinguish source component. - // See `dataFormat.getDataParams`. - if (!isGlobalOut && !(model && view)) { - console.warn('model or view can not be found by params'); - } - } - - params.event = e; - params.type = eveName; - - (this._$eventProcessor as ECEventProcessor).eventInfo = { - targetEl: el, - packedEvent: params, - model: model, - view: view - }; - - this.trigger(eveName, params); - } - }; - // Consider that some component (like tooltip, brush, ...) - // register zr event handler, but user event handler might - // do anything, such as call `setOption` or `dispatchAction`, - // which probably update any of the content and probably - // cause problem if it is called previous other inner handlers. - (handler as any).zrEventfulCallAtLast = true; - this._zr.on(eveName, handler, this); - }); - - each(eventActionMap, (actionType, eventType) => { - this._messageCenter.on(eventType, function (event) { - this.trigger(eventType, event); - }, this); - }); - - // Extra events - // TODO register? - each( - ['selectchanged'], - (eventType) => { - this._messageCenter.on(eventType, function (event) { - this.trigger(eventType, event); - }, this); - } - ); - - handleLegacySelectEvents(this._messageCenter, this, this._model); - } - - isDisposed(): boolean { - return this._disposed; - } - - clear(): void { - if (this._disposed) { - disposedWarning(this.id); - return; - } - this.setOption({ series: [] } as EChartsFullOption, true); - } - - dispose(): void { - if (this._disposed) { - disposedWarning(this.id); - return; - } - this._disposed = true; - - modelUtil.setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, ''); - - const api = this._api; - const ecModel = this._model; - - each(this._componentsViews, function (component) { - component.dispose(ecModel, api); - }); - each(this._chartsViews, function (chart) { - chart.dispose(ecModel, api); - }); - - // Dispose after all views disposed - this._zr.dispose(); - - delete instances[this.id]; - } - - /** - * Resize the chart - */ - resize(opts?: { - width?: number | 'auto', // Can be 'auto' (the same as null/undefined) - height?: number | 'auto', // Can be 'auto' (the same as null/undefined) - silent?: boolean // by default false. - }): void { - if (__DEV__) { - assert(!this[IN_MAIN_PROCESS_KEY], '`resize` should not be called during main process.'); - } - if (this._disposed) { - disposedWarning(this.id); - return; - } - - this._zr.resize(opts); - - const ecModel = this._model; - - // Resize loading effect - this._loadingFX && this._loadingFX.resize(); - - if (!ecModel) { - return; - } - - const optionChanged = ecModel.resetOption('media'); - - const silent = opts && opts.silent; - - this[IN_MAIN_PROCESS_KEY] = true; - - optionChanged && prepare(this); - updateMethods.update.call(this, { - type: 'resize', - animation: { - // Disable animation - duration: 0 - } - }); - - this[IN_MAIN_PROCESS_KEY] = false; - - flushPendingActions.call(this, silent); - - triggerUpdatedEvent.call(this, silent); - } - - /** - * Show loading effect - * @param name 'default' by default - * @param cfg cfg of registered loading effect - */ - showLoading(cfg?: object): void; - showLoading(name?: string, cfg?: object): void; - showLoading(name?: string | object, cfg?: object): void { - if (this._disposed) { - disposedWarning(this.id); - return; - } - - if (isObject(name)) { - cfg = name as object; - name = ''; - } - name = name || 'default'; - - this.hideLoading(); - if (!loadingEffects[name]) { - if (__DEV__) { - console.warn('Loading effects ' + name + ' not exists.'); - } - return; - } - const el = loadingEffects[name](this._api, cfg); - const zr = this._zr; - this._loadingFX = el; - - zr.add(el); - } - - /** - * Hide loading effect - */ - hideLoading(): void { - if (this._disposed) { - disposedWarning(this.id); - return; - } - - this._loadingFX && this._zr.remove(this._loadingFX); - this._loadingFX = null; - } - - makeActionFromEvent(eventObj: ECEvent): Payload { - const payload = zrUtil.extend({}, eventObj) as Payload; - payload.type = eventActionMap[eventObj.type]; - return payload; - } - - /** - * @param opt If pass boolean, means opt.silent - * @param opt.silent Default `false`. Whether trigger events. - * @param opt.flush Default `undefined`. - * true: Flush immediately, and then pixel in canvas can be fetched - * immediately. Caution: it might affect performance. - * false: Not flush. - * undefined: Auto decide whether perform flush. - */ - dispatchAction( - payload: Payload, - opt?: boolean | { - silent?: boolean, - flush?: boolean | undefined - } - ): void { - if (this._disposed) { - disposedWarning(this.id); - return; - } - - if (!isObject(opt)) { - opt = {silent: !!opt}; - } - - if (!actions[payload.type]) { - return; - } - - // Avoid dispatch action before setOption. Especially in `connect`. - if (!this._model) { - return; - } - - // May dispatchAction in rendering procedure - if (this[IN_MAIN_PROCESS_KEY]) { - this._pendingActions.push(payload); - return; - } - - const silent = opt.silent; - doDispatchAction.call(this, payload, silent); - - const flush = opt.flush; - if (flush) { - this._zr.flush(); - } - else if (flush !== false && env.browser.weChat) { - // In WeChat embeded browser, `requestAnimationFrame` and `setInterval` - // hang when sliding page (on touch event), which cause that zr does not - // refresh util user interaction finished, which is not expected. - // But `dispatchAction` may be called too frequently when pan on touch - // screen, which impacts performance if do not throttle them. - this._throttledZrFlush(); - } - - flushPendingActions.call(this, silent); - - triggerUpdatedEvent.call(this, silent); - } - - updateLabelLayout() { - const labelManager = this._labelManager; - labelManager.updateLayoutConfig(this._api); - labelManager.layout(this._api); - labelManager.processLabelsOverall(); - } - - appendData(params: { - seriesIndex: number, - data: any - }): void { - if (this._disposed) { - disposedWarning(this.id); - return; - } - - const seriesIndex = params.seriesIndex; - const ecModel = this.getModel(); - const seriesModel = ecModel.getSeriesByIndex(seriesIndex) as SeriesModel; - - if (__DEV__) { - assert(params.data && seriesModel); - } - - seriesModel.appendData(params); - - // Note: `appendData` does not support that update extent of coordinate - // system, util some scenario require that. In the expected usage of - // `appendData`, the initial extent of coordinate system should better - // be fixed by axis `min`/`max` setting or initial data, otherwise if - // the extent changed while `appendData`, the location of the painted - // graphic elements have to be changed, which make the usage of - // `appendData` meaningless. - - this._scheduler.unfinished = true; - - this.getZr().wakeUp(); - } - - - // A work around for no `internal` modifier in ts yet but - // need to strictly hide private methods to JS users. - private static internalField = (function () { - - prepare = function (ecIns: ECharts): void { - const scheduler = ecIns._scheduler; - - scheduler.restorePipelines(ecIns._model); - scheduler.prepareStageTasks(); - - prepareView(ecIns, true); - prepareView(ecIns, false); - - scheduler.plan(); - }; - - /** - * Prepare view instances of charts and components - */ - prepareView = function (ecIns: ECharts, isComponent: boolean): void { - const ecModel = ecIns._model; - const scheduler = ecIns._scheduler; - const viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews; - const viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap; - const zr = ecIns._zr; - const api = ecIns._api; - - for (let i = 0; i < viewList.length; i++) { - viewList[i].__alive = false; - } - - isComponent - ? ecModel.eachComponent(function (componentType, model) { - componentType !== 'series' && doPrepare(model); - }) - : ecModel.eachSeries(doPrepare); - - function doPrepare(model: ComponentModel): void { - // By defaut view will be reused if possible for the case that `setOption` with "notMerge" - // mode and need to enable transition animation. (Usually, when they have the same id, or - // especially no id but have the same type & name & index. See the `model.id` generation - // rule in `makeIdAndName` and `viewId` generation rule here). - // But in `replaceMerge` mode, this feature should be able to disabled when it is clear that - // the new model has nothing to do with the old model. - const requireNewView = model.__requireNewView; - // This command should not work twice. - model.__requireNewView = false; - // Consider: id same and type changed. - const viewId = '_ec_' + model.id + '_' + model.type; - let view = !requireNewView && viewMap[viewId]; - if (!view) { - const classType = parseClassType(model.type); - const Clazz = isComponent - ? (ComponentView as ComponentViewConstructor).getClass(classType.main, classType.sub) - : ( - // FIXME:TS - // (ChartView as ChartViewConstructor).getClass('series', classType.sub) - // For backward compat, still support a chart type declared as only subType - // like "liquidfill", but recommend "series.liquidfill" - // But need a base class to make a type series. - (ChartView as ChartViewConstructor).getClass(classType.sub) - ); - - if (__DEV__) { - assert(Clazz, classType.sub + ' does not exist.'); - } - - view = new Clazz(); - view.init(ecModel, api); - viewMap[viewId] = view; - viewList.push(view as any); - zr.add(view.group); - } - - model.__viewId = view.__id = viewId; - view.__alive = true; - view.__model = model; - view.group.__ecComponentInfo = { - mainType: model.mainType, - index: model.componentIndex - }; - !isComponent && scheduler.prepareView( - view as ChartView, model as SeriesModel, ecModel, api - ); - } - - for (let i = 0; i < viewList.length;) { - const view = viewList[i]; - if (!view.__alive) { - !isComponent && (view as ChartView).renderTask.dispose(); - zr.remove(view.group); - view.dispose(ecModel, api); - viewList.splice(i, 1); - if (viewMap[view.__id] === view) { - delete viewMap[view.__id]; - } - view.__id = view.group.__ecComponentInfo = null; - } - else { - i++; - } - } - }; - - updateDirectly = function ( - ecIns: ECharts, - method: string, - payload: Payload, - mainType: ComponentMainType, - subType?: ComponentSubType - ): void { - const ecModel = ecIns._model; - - ecModel.setUpdatePayload(payload); - - // broadcast - if (!mainType) { - // FIXME - // Chart will not be update directly here, except set dirty. - // But there is no such scenario now. - each([].concat(ecIns._componentsViews).concat(ecIns._chartsViews), callView); - return; - } - - const query: QueryConditionKindA['query'] = {}; - query[mainType + 'Id'] = payload[mainType + 'Id']; - query[mainType + 'Index'] = payload[mainType + 'Index']; - query[mainType + 'Name'] = payload[mainType + 'Name']; - - const condition = {mainType: mainType, query: query} as QueryConditionKindA; - subType && (condition.subType = subType); // subType may be '' by parseClassType; - - const excludeSeriesId = payload.excludeSeriesId; - let excludeSeriesIdMap: zrUtil.HashMap; - if (excludeSeriesId != null) { - excludeSeriesIdMap = zrUtil.createHashMap(); - each(modelUtil.normalizeToArray(excludeSeriesId), id => { - const modelId = modelUtil.convertOptionIdName(id, null); - if (modelId != null) { - excludeSeriesIdMap.set(modelId, true); - } - }); - } - - // If dispatchAction before setOption, do nothing. - ecModel && ecModel.eachComponent(condition, function (model) { - if (!excludeSeriesIdMap || excludeSeriesIdMap.get(model.id) == null) { - if (isHighDownPayload(payload) && !payload.notBlur) { - if (model instanceof SeriesModel) { - toggleSeriesBlurStateFromPayload(model, payload, ecIns._api); - } - } - else if (isSelectChangePayload(payload)) { - // TODO geo - if (model instanceof SeriesModel) { - toggleSelectionFromPayload(model, payload, ecIns._api); - updateSeriesElementSelection(model); - markStatusToUpdate(ecIns); - } - } - - callView(ecIns[ - mainType === 'series' ? '_chartsMap' : '_componentsMap' - ][model.__viewId]); - } - }, ecIns); - - function callView(view: ComponentView | ChartView) { - view && view.__alive && (view as any)[method] && (view as any)[method]( - view.__model, ecModel, ecIns._api, payload - ); - } - }; - - updateMethods = { - - prepareAndUpdate: function (this: ECharts, payload: Payload): void { - prepare(this); - updateMethods.update.call(this, payload); - }, - - update: function (this: ECharts, payload: Payload): void { - // console.profile && console.profile('update'); - - const ecModel = this._model; - const api = this._api; - const zr = this._zr; - const coordSysMgr = this._coordSysMgr; - const scheduler = this._scheduler; - - // update before setOption - if (!ecModel) { - return; - } - - ecModel.setUpdatePayload(payload); - - scheduler.restoreData(ecModel, payload); - - scheduler.performSeriesTasks(ecModel); - - // TODO - // Save total ecModel here for undo/redo (after restoring data and before processing data). - // Undo (restoration of total ecModel) can be carried out in 'action' or outside API call. - - // Create new coordinate system each update - // In LineView may save the old coordinate system and use it to get the orignal point - coordSysMgr.create(ecModel, api); - - scheduler.performDataProcessorTasks(ecModel, payload); - - // Current stream render is not supported in data process. So we can update - // stream modes after data processing, where the filtered data is used to - // deteming whether use progressive rendering. - updateStreamModes(this, ecModel); - - // We update stream modes before coordinate system updated, then the modes info - // can be fetched when coord sys updating (consider the barGrid extent fix). But - // the drawback is the full coord info can not be fetched. Fortunately this full - // coord is not requied in stream mode updater currently. - coordSysMgr.update(ecModel, api); - - clearColorPalette(ecModel); - scheduler.performVisualTasks(ecModel, payload); - - render(this, ecModel, api, payload); - - // Set background - let backgroundColor = ecModel.get('backgroundColor') || 'transparent'; - const darkMode = ecModel.get('darkMode'); - - // In IE8 - if (!env.canvasSupported) { - const colorArr = colorTool.parse(backgroundColor as ColorString); - backgroundColor = colorTool.stringify(colorArr, 'rgb'); - if (colorArr[3] === 0) { - backgroundColor = 'transparent'; - } - } - else { - zr.setBackgroundColor(backgroundColor); - - // Force set dark mode. - if (darkMode != null && darkMode !== 'auto') { - zr.setDarkMode(darkMode); - } - } - - performPostUpdateFuncs(ecModel, api); - - // console.profile && console.profileEnd('update'); - }, - - updateTransform: function (this: ECharts, payload: Payload): void { - const ecModel = this._model; - const api = this._api; - - // update before setOption - if (!ecModel) { - return; - } - - ecModel.setUpdatePayload(payload); - - // ChartView.markUpdateMethod(payload, 'updateTransform'); - - const componentDirtyList = []; - ecModel.eachComponent((componentType, componentModel) => { - if (componentType === 'series') { - return; - } - - const componentView = this.getViewOfComponentModel(componentModel); - if (componentView && componentView.__alive) { - if (componentView.updateTransform) { - const result = componentView.updateTransform(componentModel, ecModel, api, payload); - result && result.update && componentDirtyList.push(componentView); - } - else { - componentDirtyList.push(componentView); - } - } - }); - - const seriesDirtyMap = zrUtil.createHashMap(); - ecModel.eachSeries((seriesModel) => { - const chartView = this._chartsMap[seriesModel.__viewId]; - if (chartView.updateTransform) { - const result = chartView.updateTransform(seriesModel, ecModel, api, payload); - result && result.update && seriesDirtyMap.set(seriesModel.uid, 1); - } - else { - seriesDirtyMap.set(seriesModel.uid, 1); - } - }); - - clearColorPalette(ecModel); - // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. - // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true); - this._scheduler.performVisualTasks( - ecModel, payload, {setDirty: true, dirtyMap: seriesDirtyMap} - ); - - // Currently, not call render of components. Geo render cost a lot. - // renderComponents(ecIns, ecModel, api, payload, componentDirtyList); - renderSeries(this, ecModel, api, payload, seriesDirtyMap); - - performPostUpdateFuncs(ecModel, this._api); - }, - - updateView: function (this: ECharts, payload: Payload): void { - const ecModel = this._model; - - // update before setOption - if (!ecModel) { - return; - } - - ecModel.setUpdatePayload(payload); - - ChartView.markUpdateMethod(payload, 'updateView'); - - clearColorPalette(ecModel); - - // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. - this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true}); - - render(this, this._model, this._api, payload); - - performPostUpdateFuncs(ecModel, this._api); - }, - - updateVisual: function (this: ECharts, payload: Payload): void { - // updateMethods.update.call(this, payload); - - const ecModel = this._model; - - // update before setOption - if (!ecModel) { - return; - } - - ecModel.setUpdatePayload(payload); - - // clear all visual - ecModel.eachSeries(function (seriesModel) { - seriesModel.getData().clearAllVisual(); - }); - - // Perform visual - ChartView.markUpdateMethod(payload, 'updateVisual'); - - clearColorPalette(ecModel); - - // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. - this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true}); - - ecModel.eachComponent((componentType, componentModel) => { // TODO componentType may be series. - if (componentType !== 'series') { - const componentView = this.getViewOfComponentModel(componentModel); - componentView && componentView.__alive - && componentView.updateVisual(componentModel, ecModel, this._api, payload); - } - }); - - ecModel.eachSeries((seriesModel) => { - const chartView = this._chartsMap[seriesModel.__viewId]; - chartView.updateVisual(seriesModel, ecModel, this._api, payload); - }); - - performPostUpdateFuncs(ecModel, this._api); - }, - - updateLayout: function (this: ECharts, payload: Payload): void { - updateMethods.update.call(this, payload); - } - }; - - doConvertPixel = function ( - ecIns: ECharts, - methodName: 'convertFromPixel' | 'convertToPixel', - finder: ModelFinder, - value: (number | number[]) | (ScaleDataValue | ScaleDataValue[]) - ): (number | number[]) { - if (ecIns._disposed) { - disposedWarning(ecIns.id); - return; - } - const ecModel = ecIns._model; - const coordSysList = ecIns._coordSysMgr.getCoordinateSystems(); - let result; - - const parsedFinder = modelUtil.parseFinder(ecModel, finder); - - for (let i = 0; i < coordSysList.length; i++) { - const coordSys = coordSysList[i]; - if (coordSys[methodName] - && (result = coordSys[methodName](ecModel, parsedFinder, value as any)) != null - ) { - return result; - } - } - - if (__DEV__) { - console.warn( - 'No coordinate system that supports ' + methodName + ' found by the given finder.' - ); - } - }; - - updateStreamModes = function (ecIns: ECharts, ecModel: GlobalModel): void { - const chartsMap = ecIns._chartsMap; - const scheduler = ecIns._scheduler; - ecModel.eachSeries(function (seriesModel) { - scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]); - }); - }; - - doDispatchAction = function (this: ECharts, payload: Payload, silent: boolean): void { - const ecModel = this.getModel(); - const payloadType = payload.type; - const escapeConnect = payload.escapeConnect; - const actionWrap = actions[payloadType]; - const actionInfo = actionWrap.actionInfo; - - const cptTypeTmp = (actionInfo.update || 'update').split(':'); - const updateMethod = cptTypeTmp.pop(); - const cptType = cptTypeTmp[0] != null && parseClassType(cptTypeTmp[0]); - - this[IN_MAIN_PROCESS_KEY] = true; - - let payloads: Payload[] = [payload]; - let batched = false; - // Batch action - if (payload.batch) { - batched = true; - payloads = zrUtil.map(payload.batch, function (item) { - item = zrUtil.defaults(zrUtil.extend({}, item), payload); - item.batch = null; - return item as Payload; - }); - } - - const eventObjBatch: ECEventData[] = []; - let eventObj: ECEvent; - - const isSelectChange = isSelectChangePayload(payload); - const isStatusChange = isHighDownPayload(payload) || isSelectChange; - - each(payloads, (batchItem) => { - // Action can specify the event by return it. - eventObj = actionWrap.action(batchItem, this._model, this._api) as ECEvent; - // Emit event outside - eventObj = eventObj || zrUtil.extend({} as ECEvent, batchItem); - // Convert type to eventType - eventObj.type = actionInfo.event || eventObj.type; - eventObjBatch.push(eventObj); - - // light update does not perform data process, layout and visual. - if (isStatusChange) { - // method, payload, mainType, subType - updateDirectly(this, updateMethod, batchItem as Payload, 'series'); - - // Mark status to update - markStatusToUpdate(this); - } - else if (cptType) { - updateDirectly(this, updateMethod, batchItem as Payload, cptType.main, cptType.sub); - } - }); - - if (updateMethod !== 'none' && !isStatusChange && !cptType) { - // Still dirty - if (this[OPTION_UPDATED_KEY]) { - prepare(this); - updateMethods.update.call(this, payload); - this[OPTION_UPDATED_KEY] = false; - } - else { - updateMethods[updateMethod as keyof typeof updateMethods].call(this, payload); - } - } - - // Follow the rule of action batch - if (batched) { - eventObj = { - type: actionInfo.event || payloadType, - escapeConnect: escapeConnect, - batch: eventObjBatch - }; - } - else { - eventObj = eventObjBatch[0] as ECEvent; - } - - this[IN_MAIN_PROCESS_KEY] = false; - - if (!silent) { - const messageCenter = this._messageCenter; - messageCenter.trigger(eventObj.type, eventObj); - // Extra triggered 'selectchanged' event - if (isSelectChange) { - const newObj: SelectChangedPayload = { - type: 'selectchanged', - escapeConnect: escapeConnect, - selected: getAllSelectedIndices(ecModel), - isFromClick: payload.isFromClick || false, - fromAction: payload.type as 'select' | 'unselect' | 'toggleSelected', - fromActionPayload: payload - }; - messageCenter.trigger(newObj.type, newObj); - } - } - }; - - flushPendingActions = function (this: ECharts, silent: boolean): void { - const pendingActions = this._pendingActions; - while (pendingActions.length) { - const payload = pendingActions.shift(); - doDispatchAction.call(this, payload, silent); - } - }; - - triggerUpdatedEvent = function (this: ECharts, silent): void { - !silent && this.trigger('updated'); - }; - - /** - * Event `rendered` is triggered when zr - * rendered. It is useful for realtime - * snapshot (reflect animation). - * - * Event `finished` is triggered when: - * (1) zrender rendering finished. - * (2) initial animation finished. - * (3) progressive rendering finished. - * (4) no pending action. - * (5) no delayed setOption needs to be processed. - */ - bindRenderedEvent = function (zr: zrender.ZRenderType, ecIns: ECharts): void { - zr.on('rendered', function (params) { - - ecIns.trigger('rendered', params); - - // The `finished` event should not be triggered repeatly, - // so it should only be triggered when rendering indeed happend - // in zrender. (Consider the case that dipatchAction is keep - // triggering when mouse move). - if ( - // Although zr is dirty if initial animation is not finished - // and this checking is called on frame, we also check - // animation finished for robustness. - zr.animation.isFinished() - && !ecIns[OPTION_UPDATED_KEY] - && !ecIns._scheduler.unfinished - && !ecIns._pendingActions.length - ) { - ecIns.trigger('finished'); - } - }); - }; - - bindMouseEvent = function (zr: zrender.ZRenderType, ecIns: ECharts): void { - zr.on('mouseover', function (e) { - const el = e.target; - const dispatcher = findEventDispatcher(el, isHighDownDispatcher); - if (dispatcher) { - const ecData = getECData(dispatcher); - // Try blur all in the related series. Then emphasis the hoverred. - // TODO. progressive mode. - toggleSeriesBlurState( - ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns._api, true - ); - enterEmphasisWhenMouseOver(dispatcher, e); - - markStatusToUpdate(ecIns); - } - }).on('mouseout', function (e) { - const el = e.target; - const dispatcher = findEventDispatcher(el, isHighDownDispatcher); - if (dispatcher) { - const ecData = getECData(dispatcher); - toggleSeriesBlurState( - ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns._api, false - ); - - leaveEmphasisWhenMouseOut(dispatcher, e); - - markStatusToUpdate(ecIns); - } - }).on('click', function (e) { - const el = e.target; - const dispatcher = findEventDispatcher( - el, (target) => getECData(target).dataIndex != null, true - ); - if (dispatcher) { - const actionType = (dispatcher as ECElement).selected ? 'unselect' : 'select'; - const ecData = getECData(dispatcher); - ecIns._api.dispatchAction({ - type: actionType, - dataType: ecData.dataType, - dataIndexInside: ecData.dataIndex, - seriesIndex: ecData.seriesIndex, - isFromClick: true - }); - } - }); - }; - - clearColorPalette = function (ecModel: GlobalModel): void { - ecModel.clearColorPalette(); - ecModel.eachSeries(function (seriesModel) { - seriesModel.clearColorPalette(); - }); - }; - - render = function (ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload): void { - - renderComponents(ecIns, ecModel, api, payload); - - each(ecIns._chartsViews, function (chart: ChartView) { - chart.__alive = false; - }); - - renderSeries(ecIns, ecModel, api, payload); - - // Remove groups of unrendered charts - each(ecIns._chartsViews, function (chart: ChartView) { - if (!chart.__alive) { - chart.remove(ecModel, api); - } - }); - }; - - renderComponents = function ( - ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload, dirtyList?: ComponentView[] - ): void { - each(dirtyList || ecIns._componentsViews, function (componentView: ComponentView) { - const componentModel = componentView.__model; - clearStates(componentModel, componentView); - - componentView.render(componentModel, ecModel, api, payload); - - updateZ(componentModel, componentView); - - updateStates(componentModel, componentView); - }); - - }; - - /** - * Render each chart and component - */ - renderSeries = function ( - ecIns: ECharts, - ecModel: GlobalModel, - api: ExtensionAPI, - payload: Payload | 'remain', - dirtyMap?: {[uid: string]: any} - ): void { - // Render all charts - const scheduler = ecIns._scheduler; - const labelManager = ecIns._labelManager; - - labelManager.clearLabels(); - - let unfinished: boolean = false; - ecModel.eachSeries(function (seriesModel) { - const chartView = ecIns._chartsMap[seriesModel.__viewId]; - chartView.__alive = true; - - const renderTask = chartView.renderTask; - scheduler.updatePayload(renderTask, payload); - - // TODO states on marker. - clearStates(seriesModel, chartView); - - if (dirtyMap && dirtyMap.get(seriesModel.uid)) { - renderTask.dirty(); - } - if (renderTask.perform(scheduler.getPerformArgs(renderTask))) { - unfinished = true; - } - - seriesModel.__transientTransitionOpt = null; - - chartView.group.silent = !!seriesModel.get('silent'); - // Should not call markRedraw on group, because it will disable zrender - // increamental render (alway render from the __startIndex each frame) - // chartView.group.markRedraw(); - - updateBlend(seriesModel, chartView); - - updateSeriesElementSelection(seriesModel); - - // Add labels. - labelManager.addLabelsOfSeries(chartView); - }); - - scheduler.unfinished = unfinished || scheduler.unfinished; - - labelManager.updateLayoutConfig(api); - labelManager.layout(api); - labelManager.processLabelsOverall(); - - ecModel.eachSeries(function (seriesModel) { - const chartView = ecIns._chartsMap[seriesModel.__viewId]; - // Update Z after labels updated. Before applying states. - updateZ(seriesModel, chartView); - - // NOTE: Update states after label is updated. - // label should be in normal status when layouting. - updateStates(seriesModel, chartView); - }); - - - // If use hover layer - updateHoverLayerStatus(ecIns, ecModel); - }; - - performPostUpdateFuncs = function (ecModel: GlobalModel, api: ExtensionAPI): void { - each(postUpdateFuncs, function (func) { - func(ecModel, api); - }); - }; - - markStatusToUpdate = function (ecIns: ECharts): void { - ecIns[STATUS_NEEDS_UPDATE_KEY] = true; - // Wake up zrender if it's sleep. Let it update states in the next frame. - ecIns.getZr().wakeUp(); - }; - - applyChangedStates = function (ecIns: ECharts): void { - if (!ecIns[STATUS_NEEDS_UPDATE_KEY]) { - return; - } - - ecIns.getZr().storage.traverse(function (el: ECElement) { - // Not applied on removed elements, it may still in fading. - if (graphic.isElementRemoved(el)) { - return; - } - applyElementStates(el); - }); - - ecIns[STATUS_NEEDS_UPDATE_KEY] = false; - }; - - function applyElementStates(el: ECElement) { - const newStates = []; - - const oldStates = el.currentStates; - // Keep other states. - for (let i = 0; i < oldStates.length; i++) { - const stateName = oldStates[i]; - if (!(stateName === 'emphasis' || stateName === 'blur' || stateName === 'select')) { - newStates.push(stateName); - } - } - - // Only use states when it's exists. - if (el.selected && el.states.select) { - newStates.push('select'); - } - if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) { - newStates.push('emphasis'); - } - else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) { - newStates.push('blur'); - } - el.useStates(newStates); - } - - function updateHoverLayerStatus(ecIns: ECharts, ecModel: GlobalModel): void { - const zr = ecIns._zr; - const storage = zr.storage; - let elCount = 0; - - storage.traverse(function (el) { - if (!el.isGroup) { - elCount++; - } - }); - - if (elCount > ecModel.get('hoverLayerThreshold') && !env.node && !env.worker) { - ecModel.eachSeries(function (seriesModel) { - if (seriesModel.preventUsingHoverLayer) { - return; - } - const chartView = ecIns._chartsMap[seriesModel.__viewId]; - if (chartView.__alive) { - chartView.group.traverse(function (el: ECElement) { - if (el.states.emphasis) { - el.states.emphasis.hoverLayer = true; - } - }); - } - }); - } - }; - - /** - * Update chart and blend. - */ - function updateBlend(seriesModel: SeriesModel, chartView: ChartView): void { - const blendMode = seriesModel.get('blendMode') || null; - if (__DEV__) { - if (!env.canvasSupported && blendMode && blendMode !== 'source-over') { - console.warn('Only canvas support blendMode'); - } - } - chartView.group.traverse(function (el: Displayable) { - // FIXME marker and other components - if (!el.isGroup) { - // DONT mark the element dirty. In case element is incremental and don't wan't to rerender. - el.style.blend = blendMode; - } - if ((el as IncrementalDisplayable).eachPendingDisplayable) { - (el as IncrementalDisplayable).eachPendingDisplayable(function (displayable) { - displayable.style.blend = blendMode; - }); - } - }); - }; - - function updateZ(model: ComponentModel, view: ComponentView | ChartView): void { - if (model.preventAutoZ) { - return; - } - const z = model.get('z'); - const zlevel = model.get('zlevel'); - // Set z and zlevel - view.group.traverse(function (el: Displayable) { - if (!el.isGroup) { - z != null && (el.z = z); - zlevel != null && (el.zlevel = zlevel); - - // TODO if textContent is on group. - const label = el.getTextContent(); - const labelLine = el.getTextGuideLine(); - if (label) { - label.z = el.z; - label.zlevel = el.zlevel; - // lift z2 of text content - // TODO if el.emphasis.z2 is spcefied, what about textContent. - label.z2 = el.z2 + 2; - } - if (labelLine) { - const showAbove = el.textGuideLineConfig && el.textGuideLineConfig.showAbove; - labelLine.z = el.z; - labelLine.zlevel = el.zlevel; - labelLine.z2 = el.z2 + (showAbove ? 1 : -1); - } - } - }); - }; - - // Clear states without animation. - // TODO States on component. - function clearStates(model: ComponentModel, view: ComponentView | ChartView): void { - view.group.traverse(function (el: Displayable) { - // Not applied on removed elements, it may still in fading. - if (graphic.isElementRemoved(el)) { - return; - } - - const textContent = el.getTextContent(); - const textGuide = el.getTextGuideLine(); - if (el.stateTransition) { - el.stateTransition = null; - } - if (textContent && textContent.stateTransition) { - textContent.stateTransition = null; - } - if (textGuide && textGuide.stateTransition) { - textGuide.stateTransition = null; - } - - // TODO If el is incremental. - if (el.hasState()) { - el.prevStates = el.currentStates; - el.clearStates(); - } - else if (el.prevStates) { - el.prevStates = null; - } - }); - } - - function updateStates(model: ComponentModel, view: ComponentView | ChartView): void { - const stateAnimationModel = (model as SeriesModel).getModel('stateAnimation'); - const enableAnimation = model.isAnimationEnabled(); - const duration = stateAnimationModel.get('duration'); - const stateTransition = duration > 0 ? { - duration, - delay: stateAnimationModel.get('delay'), - easing: stateAnimationModel.get('easing') - // additive: stateAnimationModel.get('additive') - } : null; - view.group.traverse(function (el: Displayable) { - if (el.states && el.states.emphasis) { - // Not applied on removed elements, it may still in fading. - if (graphic.isElementRemoved(el)) { - return; - } - - if (el instanceof graphic.Path) { - savePathStates(el); - } - - // Only updated on changed element. In case element is incremental and don't wan't to rerender. - // TODO, a more proper way? - if (el.__dirty) { - const prevStates = el.prevStates; - // Restore states without animation - if (prevStates) { - el.useStates(prevStates); - } - } - - // Update state transition and enable animation again. - if (enableAnimation) { - el.stateTransition = stateTransition; - const textContent = el.getTextContent(); - const textGuide = el.getTextGuideLine(); - // TODO Is it necessary to animate label? - if (textContent) { - textContent.stateTransition = stateTransition; - } - if (textGuide) { - textGuide.stateTransition = stateTransition; - } - } - - // The use higlighted and selected flag to toggle states. - if (el.__dirty) { - applyElementStates(el); - } - } - }); - }; - - createExtensionAPI = function (ecIns: ECharts): ExtensionAPI { - return new (class extends ExtensionAPI { - getCoordinateSystems(): CoordinateSystemMaster[] { - return ecIns._coordSysMgr.getCoordinateSystems(); - } - getComponentByElement(el: Element) { - while (el) { - const modelInfo = (el as ViewRootGroup).__ecComponentInfo; - if (modelInfo != null) { - return ecIns._model.getComponent(modelInfo.mainType, modelInfo.index); - } - el = el.parent; - } - } - enterEmphasis(el: Element, highlightDigit?: number) { - enterEmphasis(el, highlightDigit); - markStatusToUpdate(ecIns); - } - leaveEmphasis(el: Element, highlightDigit?: number) { - leaveEmphasis(el, highlightDigit); - markStatusToUpdate(ecIns); - } - enterBlur(el: Element) { - enterBlur(el); - markStatusToUpdate(ecIns); - } - leaveBlur(el: Element) { - leaveBlur(el); - markStatusToUpdate(ecIns); - } - enterSelect(el: Element) { - enterSelect(el); - markStatusToUpdate(ecIns); - } - leaveSelect(el: Element) { - leaveSelect(el); - markStatusToUpdate(ecIns); - } - getModel(): GlobalModel { - return ecIns.getModel(); - } - getViewOfComponentModel(componentModel: ComponentModel): ComponentView { - return ecIns.getViewOfComponentModel(componentModel); - } - getViewOfSeriesModel(seriesModel: SeriesModel): ChartView { - return ecIns.getViewOfSeriesModel(seriesModel); - } - })(ecIns); - }; - - enableConnect = function (chart: ECharts): void { - - function updateConnectedChartsStatus(charts: ECharts[], status: ConnectStatus) { - for (let i = 0; i < charts.length; i++) { - const otherChart = charts[i]; - otherChart[CONNECT_STATUS_KEY] = status; - } - } - - each(eventActionMap, function (actionType, eventType) { - chart._messageCenter.on(eventType, function (event: ECEvent) { - if (connectedGroups[chart.group] && chart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_PENDING) { - if (event && event.escapeConnect) { - return; - } - - const action = chart.makeActionFromEvent(event); - const otherCharts: ECharts[] = []; - - each(instances, function (otherChart) { - if (otherChart !== chart && otherChart.group === chart.group) { - otherCharts.push(otherChart); - } - }); - - updateConnectedChartsStatus(otherCharts, CONNECT_STATUS_PENDING); - each(otherCharts, function (otherChart) { - if (otherChart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_UPDATING) { - otherChart.dispatchAction(action); - } - }); - updateConnectedChartsStatus(otherCharts, CONNECT_STATUS_UPDATED); - } - }); - }); - }; - - setTransitionOpt = function ( - chart: ECharts, - transitionOpt: SetOptionTransitionOpt - ): void { - const ecModel = chart._model; - - zrUtil.each(modelUtil.normalizeToArray(transitionOpt), transOpt => { - let errMsg; - const fromOpt = transOpt.from; - const toOpt = transOpt.to; - - if (toOpt == null) { - if (__DEV__) { - errMsg = '`transition.to` must be specified.'; - } - throwError(errMsg); - } - - const finderOpt = { - includeMainTypes: ['series'], - enableAll: false, - enableNone: false - }; - const fromResult = fromOpt ? modelUtil.parseFinder(ecModel, fromOpt, finderOpt) : null; - const toResult = modelUtil.parseFinder(ecModel, toOpt, finderOpt); - const toSeries = toResult.seriesModel; - - if (toSeries == null) { - errMsg = ''; - if (__DEV__) { - errMsg = '`transition` is only supported on series.'; - } - } - if (fromResult && fromResult.seriesModel !== toSeries) { - errMsg = ''; - if (__DEV__) { - errMsg = '`transition.from` and `transition.to` must be specified to the same series.'; - } - } - if (errMsg != null) { - throwError(errMsg); - } - - // Just a temp solution: mount them on series. - toSeries.__transientTransitionOpt = { - from: fromOpt ? fromOpt.dimension : null, - to: toOpt.dimension, - dividingMethod: transOpt.dividingMethod - }; - }); - }; - - })(); -} - - -const echartsProto = ECharts.prototype; -echartsProto.on = createRegisterEventWithLowercaseECharts('on'); -echartsProto.off = createRegisterEventWithLowercaseECharts('off'); -// @ts-ignore -echartsProto.one = function (eventName: string, cb: Function, ctx?: any) { - const self = this; - deprecateLog('ECharts#one is deprecated.'); - function wrapped(this: unknown, ...args2: any) { - cb && cb.apply && cb.apply(this, args2); - self.off(eventName, wrapped); - }; - this.on.call(this, eventName, wrapped, ctx); -}; - -// /** -// * Encode visual infomation from data after data processing -// * -// * @param {module:echarts/model/Global} ecModel -// * @param {object} layout -// * @param {boolean} [layoutFilter] `true`: only layout, -// * `false`: only not layout, -// * `null`/`undefined`: all. -// * @param {string} taskBaseTag -// * @private -// */ -// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) { -// each(visualFuncs, function (visual, index) { -// let isLayout = visual.isLayout; -// if (layoutFilter == null -// || (layoutFilter === false && !isLayout) -// || (layoutFilter === true && isLayout) -// ) { -// visual.func(ecModel, api, payload); -// } -// }); -// } - - -const MOUSE_EVENT_NAMES = [ - 'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove', - 'mousedown', 'mouseup', 'globalout', 'contextmenu' -]; - -function disposedWarning(id: string): void { - if (__DEV__) { - console.warn('Instance ' + id + ' has been disposed'); - } -} - - -const actions: { - [actionType: string]: { - action: ActionHandler, - actionInfo: ActionInfo - } -} = {}; - -/** - * Map eventType to actionType - */ -const eventActionMap: {[eventType: string]: string} = {}; - -const dataProcessorFuncs: StageHandlerInternal[] = []; - -const optionPreprocessorFuncs: OptionPreprocessor[] = []; - -const postInitFuncs: PostIniter[] = []; - -const postUpdateFuncs: PostUpdater[] = []; - -const visualFuncs: StageHandlerInternal[] = []; - -const themeStorage: {[themeName: string]: ThemeOption} = {}; - -const loadingEffects: {[effectName: string]: LoadingEffectCreator} = {}; - -const instances: {[id: string]: ECharts} = {}; -const connectedGroups: {[groupId: string]: boolean} = {}; - -let idBase: number = +(new Date()) - 0; -let groupIdBase: number = +(new Date()) - 0; -const DOM_ATTRIBUTE_KEY = '_echarts_instance_'; - - -/** - * @param opts.devicePixelRatio Use window.devicePixelRatio by default - * @param opts.renderer Can choose 'canvas' or 'svg' to render the chart. - * @param opts.width Use clientWidth of the input `dom` by default. - * Can be 'auto' (the same as null/undefined) - * @param opts.height Use clientHeight of the input `dom` by default. - * Can be 'auto' (the same as null/undefined) - */ -export function init( - dom: HTMLElement, - theme?: string | object, - opts?: { - renderer?: RendererType, - devicePixelRatio?: number, - width?: number, - height?: number, - locale?: string | LocaleOption - } -): ECharts { - if (__DEV__) { - if (!dom) { - throw new Error('Initialize failed: invalid dom.'); - } - } - - const existInstance = getInstanceByDom(dom); - if (existInstance) { +// Compatitable with the following code +// import echarts from 'echarts/lib/echarts' +export default { + init() { if (__DEV__) { - console.warn('There is a chart instance already initialized on the dom.'); - } - return existInstance; - } - - if (__DEV__) { - if (zrUtil.isDom(dom) - && dom.nodeName.toUpperCase() !== 'CANVAS' - && ( - (!dom.clientWidth && (!opts || opts.width == null)) - || (!dom.clientHeight && (!opts || opts.height == null)) - ) - ) { - console.warn('Can\'t get DOM width or height. Please check ' - + 'dom.clientWidth and dom.clientHeight. They should not be 0.' - + 'For example, you may need to call this in the callback ' - + 'of window.onload.'); - } - } - - const chart = new ECharts(dom, theme, opts); - chart.id = 'ec_' + idBase++; - instances[chart.id] = chart; - - modelUtil.setAttribute(dom, DOM_ATTRIBUTE_KEY, chart.id); - - enableConnect(chart); - - each(postInitFuncs, (postInitFunc) => { - postInitFunc(chart); - }); - - return chart; -} - -/** - * @usage - * (A) - * ```js - * let chart1 = echarts.init(dom1); - * let chart2 = echarts.init(dom2); - * chart1.group = 'xxx'; - * chart2.group = 'xxx'; - * echarts.connect('xxx'); - * ``` - * (B) - * ```js - * let chart1 = echarts.init(dom1); - * let chart2 = echarts.init(dom2); - * echarts.connect('xxx', [chart1, chart2]); - * ``` - */ -export function connect(groupId: string | ECharts[]): string { - // Is array of charts - if (zrUtil.isArray(groupId)) { - const charts = groupId; - groupId = null; - // If any chart has group - each(charts, function (chart) { - if (chart.group != null) { - groupId = chart.group; - } - }); - groupId = groupId || ('g_' + groupIdBase++); - each(charts, function (chart) { - chart.group = groupId as string; - }); - } - connectedGroups[groupId as string] = true; - return groupId as string; -} - -/** - * @deprecated - */ -export function disConnect(groupId: string): void { - connectedGroups[groupId] = false; -} - -/** - * Alias and backword compat - */ -export const disconnect = disConnect; - -/** - * Dispose a chart instance - */ -export function dispose(chart: ECharts | HTMLElement | string): void { - if (typeof chart === 'string') { - chart = instances[chart]; - } - else if (!(chart instanceof ECharts)) { - // Try to treat as dom - chart = getInstanceByDom(chart); - } - if ((chart instanceof ECharts) && !chart.isDisposed()) { - chart.dispose(); - } -} - -export function getInstanceByDom(dom: HTMLElement): ECharts { - return instances[modelUtil.getAttribute(dom, DOM_ATTRIBUTE_KEY)]; -} - -export function getInstanceById(key: string): ECharts { - return instances[key]; -} - -/** - * Register theme - */ -export function registerTheme(name: string, theme: ThemeOption): void { - themeStorage[name] = theme; -} - -/** - * Register option preprocessor - */ -export function registerPreprocessor(preprocessorFunc: OptionPreprocessor): void { - optionPreprocessorFuncs.push(preprocessorFunc); -} - -export function registerProcessor( - priority: number | StageHandler | StageHandlerOverallReset, - processor?: StageHandler | StageHandlerOverallReset -): void { - normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_DEFAULT); -} - - -/** - * Register postIniter - * @param {Function} postInitFunc - */ -export function registerPostInit(postInitFunc: PostIniter): void { - postInitFunc && postInitFuncs.push(postInitFunc); -} - -/** - * Register postUpdater - * @param {Function} postUpdateFunc - */ -export function registerPostUpdate(postUpdateFunc: PostUpdater): void { - postUpdateFunc && postUpdateFuncs.push(postUpdateFunc); -} - -/** - * @usage - * registerAction('someAction', 'someEvent', function () { ... }); - * registerAction('someAction', function () { ... }); - * registerAction( - * {type: 'someAction', event: 'someEvent', update: 'updateView'}, - * function () { ... } - * ); - * - * @param {(string|Object)} actionInfo - * @param {string} actionInfo.type - * @param {string} [actionInfo.event] - * @param {string} [actionInfo.update] - * @param {string} [eventName] - * @param {Function} action - */ -export function registerAction(type: string, eventName: string, action: ActionHandler): void; -export function registerAction(type: string, action: ActionHandler): void; -export function registerAction(actionInfo: ActionInfo, action: ActionHandler): void; -export function registerAction( - actionInfo: string | ActionInfo, - eventName: string | ActionHandler, - action?: ActionHandler -): void { - if (typeof eventName === 'function') { - action = eventName; - eventName = ''; - } - const actionType = isObject(actionInfo) - ? (actionInfo as ActionInfo).type - : ([actionInfo, actionInfo = { - event: eventName - } as ActionInfo][0]); - - // Event name is all lowercase - (actionInfo as ActionInfo).event = ( - (actionInfo as ActionInfo).event || actionType as string - ).toLowerCase(); - eventName = (actionInfo as ActionInfo).event; - - // Validate action type and event name. - assert(ACTION_REG.test(actionType as string) && ACTION_REG.test(eventName)); - - if (!actions[actionType as string]) { - actions[actionType as string] = {action: action, actionInfo: actionInfo as ActionInfo}; - } - eventActionMap[eventName as string] = actionType as string; -} - -export function registerCoordinateSystem( - type: string, - coordSysCreator: CoordinateSystemCreator -): void { - CoordinateSystemManager.register(type, coordSysCreator); -} - -/** - * Get dimensions of specified coordinate system. - * @param {string} type - * @return {Array.} - */ -export function getCoordinateSystemDimensions(type: string): DimensionDefinitionLoose[] { - const coordSysCreator = CoordinateSystemManager.get(type); - if (coordSysCreator) { - return coordSysCreator.getDimensionsInfo - ? coordSysCreator.getDimensionsInfo() - : coordSysCreator.dimensions.slice(); - } -} - -export {registerLocale} from './locale'; - -/** - * Layout is a special stage of visual encoding - * Most visual encoding like color are common for different chart - * But each chart has it's own layout algorithm - */ -function registerLayout(priority: number, layoutTask: StageHandler | StageHandlerOverallReset): void; -function registerLayout(layoutTask: StageHandler | StageHandlerOverallReset): void; -function registerLayout( - priority: number | StageHandler | StageHandlerOverallReset, - layoutTask?: StageHandler | StageHandlerOverallReset -): void { - normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, 'layout'); -} - -function registerVisual(priority: number, layoutTask: StageHandler | StageHandlerOverallReset): void; -function registerVisual(layoutTask: StageHandler | StageHandlerOverallReset): void; -function registerVisual( - priority: number | StageHandler | StageHandlerOverallReset, - visualTask?: StageHandler | StageHandlerOverallReset -): void { - normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, 'visual'); -} - -export {registerLayout, registerVisual}; - -function normalizeRegister( - targetList: StageHandler[], - priority: number | StageHandler | StageHandlerOverallReset, - fn: StageHandler | StageHandlerOverallReset, - defaultPriority: number, - visualType?: StageHandlerInternal['visualType'] -): void { - if (isFunction(priority) || isObject(priority)) { - fn = priority as (StageHandler | StageHandlerOverallReset); - priority = defaultPriority; - } - - if (__DEV__) { - if (isNaN(priority) || priority == null) { - throw new Error('Illegal priority'); + /* eslint-disable-next-line */ + console.error(`"import echarts from 'echarts/lib/echarts'" is not supported anymore. Use "import * as echarts from 'echarts/lib/echarts'" instead;`); } - // Check duplicate - each(targetList, function (wrap) { - assert((wrap as StageHandlerInternal).__raw !== fn); - }); - } - - const stageHandler = Scheduler.wrapStageHandler(fn, visualType); - - stageHandler.__prio = priority; - stageHandler.__raw = fn; - targetList.push(stageHandler); -} - -export function registerLoading( - name: string, - loadingFx: LoadingEffectCreator -): void { - loadingEffects[name] = loadingFx; -} - -export function extendComponentModel(proto: object): ComponentModel { - // let Clazz = ComponentModel; - // if (superClass) { - // let classType = parseClassType(superClass); - // Clazz = ComponentModel.getClass(classType.main, classType.sub, true); - // } - return (ComponentModel as ComponentModelConstructor).extend(proto) as any; -} - -export function extendComponentView(proto: object): ChartView { - // let Clazz = ComponentView; - // if (superClass) { - // let classType = parseClassType(superClass); - // Clazz = ComponentView.getClass(classType.main, classType.sub, true); - // } - return (ComponentView as ComponentViewConstructor).extend(proto) as any; -} - -export function extendSeriesModel(proto: object): SeriesModel { - // let Clazz = SeriesModel; - // if (superClass) { - // superClass = 'series.' + superClass.replace('series.', ''); - // let classType = parseClassType(superClass); - // Clazz = ComponentModel.getClass(classType.main, classType.sub, true); - // } - return (SeriesModel as SeriesModelConstructor).extend(proto) as any; -} - -export function extendChartView(proto: object): ChartView { - // let Clazz = ChartView; - // if (superClass) { - // superClass = superClass.replace('series.', ''); - // let classType = parseClassType(superClass); - // Clazz = ChartView.getClass(classType.main, true); - // } - return (ChartView as ChartViewConstructor).extend(proto) as any; -} - -/** - * ZRender need a canvas context to do measureText. - * But in node environment canvas may be created by node-canvas. - * So we need to specify how to create a canvas instead of using document.createElement('canvas') - * - * Be careful of using it in the browser. - * - * @example - * let Canvas = require('canvas'); - * let echarts = require('echarts'); - * echarts.setCanvasCreator(function () { - * // Small size is enough. - * return new Canvas(32, 32); - * }); - */ -export function setCanvasCreator(creator: () => HTMLCanvasElement): void { - zrUtil.$override('createCanvas', creator); -} - -/** - * The parameters and usage: see `mapDataStorage.registerMap`. - * Compatible with previous `echarts.registerMap`. - */ -export function registerMap( - mapName: Parameters[0], - geoJson: Parameters[1], - specialAreas?: Parameters[2] -): void { - mapDataStorage.registerMap(mapName, geoJson, specialAreas); -} - -export function getMap(mapName: string) { - // For backward compatibility, only return the first one. - const records = mapDataStorage.retrieveMap(mapName); - // FIXME support SVG, where return not only records[0]. - return records && records[0] && { // @ts-ignore - geoJson: records[0].geoJSON, - specialAreas: records[0].specialAreas - }; -} - -export const registerTransform = registerExternalTransform; - -/** - * Globa dispatchAction to a specified chart instance. - */ -// export function dispatchAction(payload: { chartId: string } & Payload, opt?: Parameters[1]) { -// if (!payload || !payload.chartId) { -// // Must have chartId to find chart -// return; -// } -// const chart = instances[payload.chartId]; -// if (chart) { -// chart.dispatchAction(payload, opt); -// } -// } - - - -// Buitlin global visual -registerVisual(PRIORITY_VISUAL_GLOBAL, seriesStyleTask); -registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataStyleTask); -registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataColorPaletteTask); - -registerVisual(PRIORITY_VISUAL_GLOBAL, seriesSymbolTask); -registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataSymbolTask); - -registerVisual(PRIORITY_VISUAL_DECAL, decal); - -registerPreprocessor(backwardCompat); -registerProcessor(PRIORITY_PROCESSOR_DATASTACK, dataStack); -registerLoading('default', loadingDefault); - -// Default actions - -registerAction({ - type: HIGHLIGHT_ACTION_TYPE, - event: HIGHLIGHT_ACTION_TYPE, - update: HIGHLIGHT_ACTION_TYPE -}, zrUtil.noop); - -registerAction({ - type: DOWNPLAY_ACTION_TYPE, - event: DOWNPLAY_ACTION_TYPE, - update: DOWNPLAY_ACTION_TYPE -}, zrUtil.noop); - -registerAction({ - type: SELECT_ACTION_TYPE, - event: SELECT_ACTION_TYPE, - update: SELECT_ACTION_TYPE -}, zrUtil.noop); - -registerAction({ - type: UNSELECT_ACTION_TYPE, - event: UNSELECT_ACTION_TYPE, - update: UNSELECT_ACTION_TYPE -}, zrUtil.noop); - -registerAction({ - type: TOGGLE_SELECT_ACTION_TYPE, - event: TOGGLE_SELECT_ACTION_TYPE, - update: TOGGLE_SELECT_ACTION_TYPE -}, zrUtil.noop); - -// Default theme -registerTheme('light', lightTheme); -registerTheme('dark', darkTheme); - -// For backward compatibility, where the namespace `dataTool` will -// be mounted on `echarts` is the extension `dataTool` is imported. -export const dataTool = {}; - -export interface EChartsType extends ECharts {} \ No newline at end of file + return init.apply(null, arguments); + } +} \ No newline at end of file diff --git a/src/export.ts b/src/export.ts deleted file mode 100644 index 0ce997174e..0000000000 --- a/src/export.ts +++ /dev/null @@ -1,155 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -/** - * Do not mount those modules on 'src/echarts' for better tree shaking. - */ - -import * as zrender from 'zrender/src/zrender'; -import * as matrix from 'zrender/src/core/matrix'; -import * as vector from 'zrender/src/core/vector'; -import * as zrUtil from 'zrender/src/core/util'; -import * as colorTool from 'zrender/src/tool/color'; -import * as graphicUtil from './util/graphic'; -import * as numberUtil from './util/number'; -import * as formatUtil from './util/format'; -import * as timeUtil from './util/time'; -import {throttle} from './util/throttle'; -import * as ecHelper from './helper'; -import parseGeoJSON from './coord/geo/parseGeoJson'; - -// Only for GL -export {brushSingle as innerDrawElementOnCanvas} from 'zrender/src/canvas/graphic'; - -export {zrender}; -export {default as List} from './data/List'; -export {default as Model} from './model/Model'; -export {default as Axis} from './coord/Axis'; -export {throttle}; -export {ecHelper as helper}; -export {matrix}; -export {vector}; -export {colorTool as color}; -export {default as env} from 'zrender/src/core/env'; - -export {parseGeoJSON}; -export const parseGeoJson = parseGeoJSON; - -export const number = {}; -zrUtil.each( - [ - 'linearMap', - 'round', - 'asc', - 'getPrecision', - 'getPrecisionSafe', - 'getPixelPrecision', - 'getPercentWithPrecision', - 'MAX_SAFE_INTEGER', - 'remRadian', - 'isRadianAroundZero', - 'parseDate', - 'quantity', - 'quantityExponent', - 'nice', - 'quantile', - 'reformIntervals', - 'isNumeric', - 'numericToNumber' - ], - function (name) { - (number as any)[name] = (numberUtil as any)[name]; - } -); - - -export const format = {}; -zrUtil.each( - [ - 'addCommas', - 'toCamelCase', - 'normalizeCssArray', - 'encodeHTML', - 'formatTpl', - 'getTooltipMarker', - 'formatTime', - 'capitalFirst', - 'truncateText', - 'getTextRect' - ], - function (name) { - (format as any)[name] = (formatUtil as any)[name]; - } -); - - -export const time = { - parse: numberUtil.parseDate, - format: timeUtil.format -}; - -const ecUtil = {}; -zrUtil.each( - [ - 'map', 'each', 'filter', 'indexOf', 'inherits', 'reduce', 'filter', - 'bind', 'curry', 'isArray', 'isString', 'isObject', 'isFunction', - 'extend', 'defaults', 'clone', 'merge' - ], - function (name) { - (ecUtil as any)[name] = (zrUtil as any)[name]; - } -); -export {ecUtil as util}; - -const GRAPHIC_KEYS = [ - 'extendShape', 'extendPath', 'makePath', 'makeImage', - 'mergePath', 'resizePath', 'createIcon', - // 'setHoverStyle', - // 'setLabelStyle', 'createTextStyle', - // 'getFont', - 'updateProps', 'initProps', 'getTransform', - 'clipPointsByRect', 'clipRectByRect', - 'registerShape', 'getShapeClass', - 'Group', - 'Image', - 'Text', - 'Circle', - 'Ellipse', - 'Sector', - 'Ring', - 'Polygon', - 'Polyline', - 'Rect', - 'Line', - 'BezierCurve', - 'Arc', - 'IncrementalDisplayable', - 'CompoundPath', - 'LinearGradient', - 'RadialGradient', - 'BoundingRect' -] as const; - -export const graphic = {} as Record; -zrUtil.each( - GRAPHIC_KEYS, - function (name) { - (graphic as any)[name] = (graphicUtil as any)[name]; - } -); diff --git a/src/component/timeline/typeDefaulter.ts b/src/export/all.ts similarity index 83% rename from src/component/timeline/typeDefaulter.ts rename to src/export/all.ts index f353f4cbda..41b74206a7 100644 --- a/src/component/timeline/typeDefaulter.ts +++ b/src/export/all.ts @@ -17,9 +17,7 @@ * under the License. */ -import Component from '../../model/Component'; +// This file is for providing types when import whole module. -Component.registerSubTypeDefaulter('timeline', function () { - // Only slider now. - return 'slider'; -}); +export * from './core'; +export * from './option'; \ No newline at end of file diff --git a/src/export/api.ts b/src/export/api.ts new file mode 100644 index 0000000000..2906d21dc4 --- /dev/null +++ b/src/export/api.ts @@ -0,0 +1,50 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// Provide utilities API in echarts. It will be in echarts namespace. +// Like echarts.util, echarts.graphic +export * as zrender from 'zrender/src/zrender'; + +export * as matrix from 'zrender/src/core/matrix'; +export * as vector from 'zrender/src/core/vector'; +export * as zrUtil from 'zrender/src/core/util'; +export * as color from 'zrender/src/tool/color'; +export {throttle} from '../util/throttle'; +export * as helper from './api/helper'; + +export {use} from '../extension'; + +// Only for GL +export {brushSingle as innerDrawElementOnCanvas} from 'zrender/src/canvas/graphic'; + +export {default as List} from '../data/List'; +export {default as Model} from '../model/Model'; +export {default as Axis} from '../coord/Axis'; +export {default as env} from 'zrender/src/core/env'; + +export {default as parseGeoJSON} from '../coord/geo/parseGeoJson'; +export {default as parseGeoJson} from '../coord/geo/parseGeoJson'; + +export * as number from './api/number'; +export * as time from './api/time'; +export * as graphic from './api/graphic'; + +export * as format from './api/format'; + +export * as util from './api/util'; \ No newline at end of file diff --git a/src/component/parallelAxis.ts b/src/export/api/format.ts similarity index 78% rename from src/component/parallelAxis.ts rename to src/export/api/format.ts index ea9f663b6e..ae2a3c89ce 100644 --- a/src/component/parallelAxis.ts +++ b/src/export/api/format.ts @@ -17,10 +17,15 @@ * under the License. */ -import '../coord/parallel/parallelCreator'; - -import '../coord/parallel/AxisModel'; -import '../coord/parallel/ParallelModel'; - -import './axis/parallelAxisAction'; -import './axis/ParallelAxisView'; +export { + addCommas, + toCamelCase, + normalizeCssArray, + encodeHTML, + formatTpl, + getTooltipMarker, + formatTime, + capitalFirst, + truncateText, + getTextRect +} from '../../util/format'; \ No newline at end of file diff --git a/src/export/api/graphic.ts b/src/export/api/graphic.ts new file mode 100644 index 0000000000..c6b47126f9 --- /dev/null +++ b/src/export/api/graphic.ts @@ -0,0 +1,44 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export { + extendShape, extendPath, makePath, makeImage, + mergePath, resizePath, createIcon, + updateProps, initProps, getTransform, + clipPointsByRect, clipRectByRect, + registerShape, getShapeClass, + Group, + Image, + Text, + Circle, + Ellipse, + Sector, + Ring, + Polygon, + Polyline, + Rect, + Line, + BezierCurve, + Arc, + IncrementalDisplayable, + CompoundPath, + LinearGradient, + RadialGradient, + BoundingRect +} from '../../util/graphic'; \ No newline at end of file diff --git a/src/helper.ts b/src/export/api/helper.ts similarity index 84% rename from src/helper.ts rename to src/export/api/helper.ts index d76de34542..8b956a4cb8 100644 --- a/src/helper.ts +++ b/src/export/api/helper.ts @@ -22,20 +22,20 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import createListFromArray from './chart/helper/createListFromArray'; +import createListFromArray from '../../chart/helper/createListFromArray'; // import createGraphFromNodeEdge from './chart/helper/createGraphFromNodeEdge'; -import * as axisHelper from './coord/axisHelper'; -import {AxisModelCommonMixin} from './coord/axisModelCommonMixin'; -import Model from './model/Model'; -import {getLayoutRect} from './util/layout'; +import * as axisHelper from '../../coord/axisHelper'; +import {AxisModelCommonMixin} from '../../coord/axisModelCommonMixin'; +import Model from '../../model/Model'; +import {getLayoutRect} from '../../util/layout'; import { enableDataStack, isDimensionStacked, getStackedDimension -} from './data/helper/dataStackHelper'; -import SeriesModel from './model/Series'; -import { AxisBaseModel } from './coord/AxisBaseModel'; -import { getECData } from './util/innerStore'; +} from '../../data/helper/dataStackHelper'; +import SeriesModel from '../../model/Series'; +import { AxisBaseModel } from '../../coord/AxisBaseModel'; +import { getECData } from '../../util/innerStore'; /** * Create a muti dimension List structure from seriesModel. @@ -52,7 +52,7 @@ export function createList(seriesModel: SeriesModel) { export {getLayoutRect}; -export {default as createDimensions} from './data/helper/createDimensions'; +export {default as createDimensions} from '../../data/helper/createDimensions'; export const dataStack = { isDimensionStacked: isDimensionStacked, @@ -69,7 +69,7 @@ export const dataStack = { * @param {number} h * @param {string} color */ -export {createSymbol} from './util/symbol'; +export {createSymbol} from '../../util/symbol'; /** * Create scale diff --git a/src/export/api/number.ts b/src/export/api/number.ts new file mode 100644 index 0000000000..ba8cf7a309 --- /dev/null +++ b/src/export/api/number.ts @@ -0,0 +1,39 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export { + linearMap, + round, + asc, + getPrecision, + getPrecisionSafe, + getPixelPrecision, + getPercentWithPrecision, + MAX_SAFE_INTEGER, + remRadian, + isRadianAroundZero, + parseDate, + quantity, + quantityExponent, + nice, + quantile, + reformIntervals, + isNumeric, + numericToNumber +} from '../../util/number'; \ No newline at end of file diff --git a/src/export/api/time.ts b/src/export/api/time.ts new file mode 100644 index 0000000000..41ab2b7417 --- /dev/null +++ b/src/export/api/time.ts @@ -0,0 +1,22 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export {parseDate as parse} from '../../util/number'; + +export {format} from '../../util/time'; \ No newline at end of file diff --git a/src/component/dataZoom/typeDefaulter.ts b/src/export/api/util.ts similarity index 81% rename from src/component/dataZoom/typeDefaulter.ts rename to src/export/api/util.ts index e0f14f17a2..5ba911fa75 100644 --- a/src/component/dataZoom/typeDefaulter.ts +++ b/src/export/api/util.ts @@ -17,9 +17,8 @@ * under the License. */ -import Component from '../../model/Component'; - -Component.registerSubTypeDefaulter('dataZoom', function () { - // Default 'slider' when no type specified. - return 'slider'; -}); +export { + map, each, indexOf, inherits, reduce, filter, + bind, curry, isArray, isString, isObject, isFunction, + extend, defaults, clone, merge +} from 'zrender/src/core/util'; \ No newline at end of file diff --git a/src/export/charts.ts b/src/export/charts.ts new file mode 100644 index 0000000000..1faf7c1b39 --- /dev/null +++ b/src/export/charts.ts @@ -0,0 +1,99 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +// In somehow. If we export like +// export * as LineChart './chart/line/install' +// The exported code will be transformed to +// import * as LineChart_1 './chart/line/install'; export {LineChart_1 as LineChart}; +// Treeshaking in webpack will not work even if we configured sideEffects to false in package.json + +export {install as LineChart} from '../chart/line/install'; +export {install as BarChart} from '../chart/bar/install'; + +export {install as PieChart} from '../chart/pie/install'; +export {install as ScatterChart} from '../chart/scatter/install'; +export {install as RadarChart} from '../chart/radar/install'; +export {install as MapChart} from '../chart/map/install'; +export {install as TreeChart} from '../chart/tree/install'; +export {install as TreemapChart} from '../chart/treemap/install'; +export {install as GraphChart} from '../chart/graph/install'; +export {install as GaugeChart} from '../chart/gauge/install'; +export {install as FunnelChart} from '../chart/funnel/install'; +export {install as ParallelChart} from '../chart/parallel/install'; +export {install as SankeyChart} from '../chart/sankey/install'; +export {install as BoxplotChart} from '../chart/boxplot/install'; +export {install as CandlestickChart} from '../chart/candlestick/install'; +export {install as EffectScatterChart} from '../chart/effectScatter/install'; +export {install as LinesChart} from '../chart/lines/install'; +export {install as HeatmapChart} from '../chart/heatmap/install'; +export {install as PictorialBarChart} from '../chart/bar/installPictorialBar'; +export {install as ThemeRiverChart} from '../chart/themeRiver/install'; +export {install as SunburstChart} from '../chart/sunburst/install'; +export {install as CustomChart} from '../chart/custom/install'; + + +// // NOTE: Don't use XXXSeriesOption from 'option.ts' becuase they have been injected markPoint, markLine etc. +// export {LineSeriesOption} from '../chart/line/LineSeries'; +// export {BarSeriesOption} from '../chart/bar/BarSeries'; +// export {ScatterSeriesOption} from '../chart/scatter/ScatterSeries'; +// export {PieSeriesOption} from '../chart/pie/PieSeries'; +// export {RadarSeriesOption} from '../chart/radar/RadarSeries'; +// export {MapSeriesOption} from '../chart/map/MapSeries'; +// export {TreeSeriesOption} from '../chart/tree/TreeSeries'; +// export {TreemapSeriesOption} from '../chart/treemap/TreemapSeries'; +// export {GraphSeriesOption} from '../chart/graph/GraphSeries'; +// export {GaugeSeriesOption} from '../chart/gauge/GaugeSeries'; +// export {FunnelSeriesOption} from '../chart/funnel/FunnelSeries'; +// export {ParallelSeriesOption} from '../chart/parallel/ParallelSeries'; +// export {SankeySeriesOption} from '../chart/sankey/SankeySeries'; +// export {BoxplotSeriesOption} from '../chart/boxplot/BoxplotSeries'; +// export {CandlestickSeriesOption} from '../chart/candlestick/CandlestickSeries'; +// export {EffectScatterSeriesOption} from '../chart/effectScatter/EffectScatterSeries'; +// export {LinesSeriesOption} from '../chart/lines/LinesSeries'; +// export {HeatmapSeriesOption} from '../chart/heatmap/HeatmapSeries'; +// export {PictorialBarSeriesOption} from '../chart/bar/PictorialBarSeries'; +// export {ThemeRiverSeriesOption} from '../chart/themeRiver/ThemeRiverSeries'; +// export {SunburstSeriesOption} from '../chart/sunburst/SunburstSeries'; +// export {CustomSeriesOption} from '../chart/custom/install'; + +export { + LineSeriesOption, + BarSeriesOption, + ScatterSeriesOption, + PieSeriesOption, + RadarSeriesOption, + MapSeriesOption, + TreeSeriesOption, + TreemapSeriesOption, + GraphSeriesOption, + GaugeSeriesOption, + FunnelSeriesOption, + ParallelSeriesOption, + SankeySeriesOption, + BoxplotSeriesOption, + CandlestickSeriesOption, + EffectScatterSeriesOption, + LinesSeriesOption, + HeatmapSeriesOption, + PictorialBarSeriesOption, + ThemeRiverSeriesOption, + SunburstSeriesOption, + CustomSeriesOption +} from './option'; \ No newline at end of file diff --git a/src/export/components.ts b/src/export/components.ts new file mode 100644 index 0000000000..8e4e14e26b --- /dev/null +++ b/src/export/components.ts @@ -0,0 +1,105 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export {install as GridSimpleComponent} from '../component/grid/installSimple'; +export {install as GridComponent} from '../component/grid/install'; + +export {install as PolarComponent} from '../component/polar/install'; +export {install as RadarComponent} from '../component/radar/install'; + +export {install as GeoComponent} from '../component/geo/install'; + +export {install as SingleAxisComponent} from '../component/singleAxis/install'; +export {install as ParallelComponent} from '../component/parallel/install'; +export {install as CalendarComponent} from '../component/calendar/install'; + +export {install as GraphicComponent} from '../component/graphic/install'; + +export {install as ToolboxComponent} from '../component/toolbox/install'; + +export {install as TooltipComponent} from '../component/tooltip/install'; + +export {install as AxisPointerComponent} from '../component/axisPointer/install'; +export {install as BrushComponent} from '../component/brush/install'; +export {install as TitleComponent} from '../component/title/install'; +export {install as TimelineComponent} from '../component/timeline/install'; +export {install as MarkPointComponent} from '../component/marker/installMarkPoint'; +export {install as MarkLineComponent} from '../component/marker/installMarkLine'; +export {install as MarkAreaComponent} from '../component/marker/installMarkArea'; + +export {install as LegendComponent} from '../component/legend/install'; +export {install as LegendScrollComponent} from '../component/legend/installLegendScroll'; +export {install as LegendPlainComponent} from '../component/legend/installLegendPlain'; + +export {install as DataZoomComponent} from '../component/dataZoom/install'; +export {install as DataZoomInsideComponent} from '../component/dataZoom/installDataZoomInside'; +export {install as DataZoomSliderComponent} from '../component/dataZoom/installDataZoomSlider'; + +export {install as VisualMapComponent} from '../component/visualMap/install'; + +export {install as VisualMapContinuousComponent} from '../component/visualMap/installVisualMapContinuous'; + +export {install as VisualMapPiecewiseComponent} from '../component/visualMap/installVisualMapPiecewise'; + +export {install as AriaComponent} from '../component/aria/install'; + +export {install as TransformComponent} from '../component/transform/install'; + +export {install as DatasetComponent} from '../component/dataset/install'; + + + +export { + GridComponentOption, + + PolarComponentOption, + + RadarComponentOption, + + GeoComponentOption, + + SingleAxisComponentOption, + ParallelComponentOption, + CalendarComponentOption, + + GraphicComponentOption, + + ToolboxComponentOption, + + TooltipComponentOption, + + AxisPointerComponentOption, + BrushComponentOption, + TitleComponentOption, + TimelineComponentOption, + MarkPointComponentOption, + MarkLineComponentOption, + MarkAreaComponentOption, + + LegendComponentOption, + + DataZoomComponentOption, + + VisualMapComponentOption, + + AriaComponentOption, + + DatasetComponentOption +} from './option'; + diff --git a/src/export/core.ts b/src/export/core.ts new file mode 100644 index 0000000000..b5da29c77e --- /dev/null +++ b/src/export/core.ts @@ -0,0 +1,102 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// Core API from echarts/src/echarts + +import type { ComponentOption, ECBasicOption as EChartsCoreOption } from '../util/types'; + +import type { AxisPointerOption } from '../component/axisPointer/AxisPointerModel'; +import type { XAXisOption, YAXisOption } from '../coord/cartesian/AxisModel'; +import type { AngleAxisOption, RadiusAxisOption } from '../coord/polar/AxisModel'; +import type { ParallelAxisOption } from '../coord/parallel/AxisModel'; + +export * from '../core/echarts'; +export * from './api'; + +export {EChartsType as ECharts} from '../core/echarts'; + +export {EChartsCoreOption}; + +// type SeriesSubComponentsTypes = 'markPoint' | 'markLine' | 'markArea' | 'tooltip'; +// type InjectSeriesSubComponents = +// 'series' extends GetMainType +// ? (OptionUnion & Injected) : OptionUnion; +// // NOTE: Can't use GetMainType extends xxx ? GetMainType : xxx +// // Or the infer can't work. +// type GetSeriesInjectedSubOption = { +// [key in Extract]?: ExtractComponentOption +// }; + + +// TODO: Handwritten dependencies +type Dependencies = { + grid: XAXisOption | YAXisOption | AxisPointerOption; + polar: AngleAxisOption | RadiusAxisOption + parallel: ParallelAxisOption +}; + +type DependenciesKeys = keyof Dependencies & string; + +type Arrayable = T | T[]; + +type GetMainType = Exclude; + +// NOTE: Needs to extract the specify ComponentOption for each component type. +type ExtractComponentOption = OptionUnion extends { + mainType?: ExtractMainType +} ? OptionUnion : never; + +type GetDependency = { + [key in GetMainType]?: Arrayable< + ExtractComponentOption + > +}; + +type GetDependencies = GetDependency]>; + +type ComposeUnitOption = + // Will be never if some component forget to specify mainType. + CheckMainType> & + Omit & { + [key in GetMainType]?: Arrayable< + ExtractComponentOption + // TODO: It will make error log too complex. + // So this more strict type checking will not be used currently to make sure the error msg is friendly. + // + // Inject markPoint, markLine, markArea, tooltip in series. + // ExtractComponentOption< + // InjectSeriesSubComponents< + // OptionUnion, GetSeriesInjectedSubOption, OptionUnion> + // >, + // key + // > + > + } & GetDependencies>; + +type CheckMainType = + // If some component forget to specify mainType. we should do a fast check. + string extends OptionUnionMainType ? never : {}; + + +// TODO Provide a strict option. +export type ComposeOption = + ComposeUnitOption & { + baseOption?: ComposeUnitOption + options?: ComposeUnitOption[] + }; \ No newline at end of file diff --git a/src/export/option.ts b/src/export/option.ts new file mode 100644 index 0000000000..9bd56abfd3 --- /dev/null +++ b/src/export/option.ts @@ -0,0 +1,228 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import type {GridOption as GridComponentOption} from '../coord/cartesian/GridModel'; +import type {PolarOption as PolarComponentOption} from '../coord/polar/PolarModel'; +import type {RadarOption as RadarComponentOption} from '../coord/radar/RadarModel'; +import type {GeoOption as GeoComponentOption} from '../coord/geo/GeoModel'; +import type { + RadiusAxisOption as RadiusAxisComponentOption, + AngleAxisOption as AngleAxisComponentOption +} from '../coord/polar/AxisModel'; +import type { + XAXisOption as XAXisComponentOption, + YAXisOption as YAXisComponentOption +} from '../coord/cartesian/AxisModel'; +import type {SingleAxisOption as SingleAxisComponentOption} from '../coord/single/AxisModel'; +import type {ParallelAxisOption as ParallelAxisComponentOption} from '../coord/parallel/AxisModel'; +import type {ParallelCoordinateSystemOption as ParallelComponentOption} from '../coord/parallel/ParallelModel'; +import type {CalendarOption as CalendarComponentOption} from '../coord/calendar/CalendarModel'; +import type {ToolboxOption} from '../component/toolbox/ToolboxModel'; +import type {TooltipOption as TooltipComponentOption} from '../component/tooltip/TooltipModel'; +import type {AxisPointerOption as AxisPointerComponentOption} from '../component/axisPointer/AxisPointerModel'; +import type {BrushOption as BrushComponentOption} from '../component/brush/BrushModel'; +import type {TitleOption as TitleComponentOption} from '../component/title/install'; +import type {TimelineOption as TimelineComponentOption} from '../component/timeline/TimelineModel'; +import type {SliderTimelineOption as TimelineSliderComponentOption} from '../component/timeline/SliderTimelineModel'; + +import type {LegendOption} from '../component/legend/LegendModel'; +import type {ScrollableLegendOption} from '../component/legend/ScrollableLegendModel'; + +import type {SliderDataZoomOption} from '../component/dataZoom/SliderZoomModel'; +import type {InsideDataZoomOption} from '../component/dataZoom/InsideZoomModel'; + +import type {ContinousVisualMapOption} from '../component/visualMap/ContinuousModel'; +import type {PiecewiseVisualMapOption} from '../component/visualMap/PiecewiseModel'; + +import type {MarkLineOption as MarkLineComponentOption} from '../component/marker/MarkLineModel'; +import type {MarkPointOption as MarkPointComponentOption} from '../component/marker/MarkPointModel'; +import type {MarkAreaOption as MarkAreaComponentOption} from '../component/marker/MarkAreaModel'; + +import type {LineSeriesOption as LineSeriesOptionInner} from '../chart/line/LineSeries'; +import type {BarSeriesOption as BarSeriesOptionInner} from '../chart/bar/BarSeries'; +import type {ScatterSeriesOption as ScatterSeriesOptionInner} from '../chart/scatter/ScatterSeries'; +import type {PieSeriesOption as PieSeriesOptionInner} from '../chart/pie/PieSeries'; +import type {RadarSeriesOption as RadarSeriesOptionInner} from '../chart/radar/RadarSeries'; +import type {MapSeriesOption as MapSeriesOptionInner} from '../chart/map/MapSeries'; +import type {TreeSeriesOption as TreeSeriesOptionInner} from '../chart/tree/TreeSeries'; +import type {TreemapSeriesOption as TreemapSeriesOptionInner} from '../chart/treemap/TreemapSeries'; +import type {GraphSeriesOption as GraphSeriesOptionInner} from '../chart/graph/GraphSeries'; +import type {GaugeSeriesOption as GaugeSeriesOptionInner} from '../chart/gauge/GaugeSeries'; +import type {FunnelSeriesOption as FunnelSeriesOptionInner} from '../chart/funnel/FunnelSeries'; +import type {ParallelSeriesOption as ParallelSeriesOptionInner} from '../chart/parallel/ParallelSeries'; +import type {SankeySeriesOption as SankeySeriesOptionInner} from '../chart/sankey/SankeySeries'; +import type {BoxplotSeriesOption as BoxplotSeriesOptionInner} from '../chart/boxplot/BoxplotSeries'; +import type {CandlestickSeriesOption as CandlestickSeriesOptionInner} from '../chart/candlestick/CandlestickSeries'; +import type { + EffectScatterSeriesOption as EffectScatterSeriesOptionInner +} from '../chart/effectScatter/EffectScatterSeries'; +import type {LinesSeriesOption as LinesSeriesOptionInner} from '../chart/lines/LinesSeries'; +import type {HeatmapSeriesOption as HeatmapSeriesOptionInner} from '../chart/heatmap/HeatmapSeries'; +import type {PictorialBarSeriesOption as PictorialBarSeriesOptionInner} from '../chart/bar/PictorialBarSeries'; +import type {ThemeRiverSeriesOption as ThemeRiverSeriesOptionInner} from '../chart/themeRiver/ThemeRiverSeries'; +import type {SunburstSeriesOption as SunburstSeriesOptionInner} from '../chart/sunburst/SunburstSeries'; +import type {CustomSeriesOption as CustomSeriesOptionInner} from '../chart/custom/install'; + +import type { GraphicComponentLooseOption as GraphicComponentOption } from '../component/graphic/install'; +import type { DatasetOption as DatasetComponentOption } from '../component/dataset/install'; + +import type {ToolboxBrushFeatureOption} from '../component/toolbox/feature/Brush'; +import type {ToolboxDataViewFeatureOption} from '../component/toolbox/feature/DataView'; +import type {ToolboxDataZoomFeatureOption} from '../component/toolbox/feature/DataZoom'; +import type {ToolboxMagicTypeFeatureOption} from '../component/toolbox/feature/MagicType'; +import type {ToolboxRestoreFeatureOption} from '../component/toolbox/feature/Restore'; +import type {ToolboxSaveAsImageFeatureOption} from '../component/toolbox/feature/SaveAsImage'; +import type {ToolboxFeatureOption} from '../component/toolbox/featureManager'; + + +import type { ECBasicOption, SeriesTooltipOption, AriaOption as AriaComponentOption } from '../util/types'; + +interface ToolboxComponentOption extends ToolboxOption { + feature?: { + brush?: ToolboxBrushFeatureOption + dataView?: ToolboxDataViewFeatureOption + dataZoom?: ToolboxDataZoomFeatureOption + magicType?: ToolboxMagicTypeFeatureOption + restore?: ToolboxRestoreFeatureOption + saveAsImage?: ToolboxSaveAsImageFeatureOption + // custom feature + [key: string]: ToolboxFeatureOption | { + [key: string]: any + } | undefined + } +} + +export type DataZoomComponentOption = SliderDataZoomOption | InsideDataZoomOption; +export type VisualMapComponentOption = ContinousVisualMapOption | PiecewiseVisualMapOption; +export type LegendComponentOption = LegendOption | ScrollableLegendOption; +export { + GridComponentOption, + PolarComponentOption, + RadarComponentOption, + GeoComponentOption, + XAXisComponentOption, + YAXisComponentOption, + SingleAxisComponentOption, + RadiusAxisComponentOption, + AngleAxisComponentOption, + ParallelComponentOption, + CalendarComponentOption, + TooltipComponentOption, + AxisPointerComponentOption, + BrushComponentOption, + TitleComponentOption, + TimelineComponentOption, + MarkLineComponentOption, + MarkPointComponentOption, + MarkAreaComponentOption, + ToolboxComponentOption, + GraphicComponentOption, + AriaComponentOption, + DatasetComponentOption +}; + + +type SeriesInjectedOption = { + // Inject markArea markLine + markArea?: MarkAreaComponentOption + markLine?: MarkLineComponentOption + markPoint?: MarkPointComponentOption + tooltip?: SeriesTooltipOption +}; + +export type LineSeriesOption = LineSeriesOptionInner & SeriesInjectedOption; +export type BarSeriesOption = BarSeriesOptionInner & SeriesInjectedOption; +export type ScatterSeriesOption = ScatterSeriesOptionInner & SeriesInjectedOption; +export type PieSeriesOption = PieSeriesOptionInner & SeriesInjectedOption; +export type RadarSeriesOption = RadarSeriesOptionInner & SeriesInjectedOption; +export type MapSeriesOption = MapSeriesOptionInner & SeriesInjectedOption; +export type TreeSeriesOption = TreeSeriesOptionInner & SeriesInjectedOption; +export type TreemapSeriesOption = TreemapSeriesOptionInner & SeriesInjectedOption; +export type GraphSeriesOption = GraphSeriesOptionInner & SeriesInjectedOption; +export type GaugeSeriesOption = GaugeSeriesOptionInner & SeriesInjectedOption; +export type FunnelSeriesOption = FunnelSeriesOptionInner & SeriesInjectedOption; +export type ParallelSeriesOption = ParallelSeriesOptionInner & SeriesInjectedOption; +export type SankeySeriesOption = SankeySeriesOptionInner & SeriesInjectedOption; +export type BoxplotSeriesOption = BoxplotSeriesOptionInner & SeriesInjectedOption; +export type CandlestickSeriesOption = CandlestickSeriesOptionInner & SeriesInjectedOption; +export type EffectScatterSeriesOption = EffectScatterSeriesOptionInner & SeriesInjectedOption; +export type LinesSeriesOption = LinesSeriesOptionInner & SeriesInjectedOption; +export type HeatmapSeriesOption = HeatmapSeriesOptionInner & SeriesInjectedOption; +export type PictorialBarSeriesOption = PictorialBarSeriesOptionInner & SeriesInjectedOption; +export type ThemeRiverSeriesOption = ThemeRiverSeriesOptionInner & SeriesInjectedOption; +export type SunburstSeriesOption = SunburstSeriesOptionInner & SeriesInjectedOption; +export type CustomSeriesOption = CustomSeriesOptionInner & SeriesInjectedOption; + +export type SeriesOption = LineSeriesOption + | BarSeriesOption + | ScatterSeriesOption + | PieSeriesOption + | RadarSeriesOption + | MapSeriesOption + | TreeSeriesOption + | TreemapSeriesOption + | GraphSeriesOption + | GaugeSeriesOption + | FunnelSeriesOption + | ParallelSeriesOption + | SankeySeriesOption + | BoxplotSeriesOption + | CandlestickSeriesOption + | EffectScatterSeriesOption + | LinesSeriesOption + | HeatmapSeriesOption + | PictorialBarSeriesOption + | ThemeRiverSeriesOption + | SunburstSeriesOption + | CustomSeriesOption; + +export interface EChartsOption extends ECBasicOption { + dataset?: DatasetComponentOption | DatasetComponentOption[]; + aria?: AriaComponentOption; + title?: TitleComponentOption | TitleComponentOption[]; + grid?: GridComponentOption | GridComponentOption[]; + radar?: RadarComponentOption | RadarComponentOption[]; + polar?: PolarComponentOption | PolarComponentOption[]; + geo?: GeoComponentOption | GeoComponentOption[]; + angleAxis?: AngleAxisComponentOption | AngleAxisComponentOption[]; + radiusAxis?: RadiusAxisComponentOption | RadiusAxisComponentOption[]; + xAxis?: XAXisComponentOption | XAXisComponentOption[]; + yAxis?: YAXisComponentOption | YAXisComponentOption[]; + singleAxis?: SingleAxisComponentOption | SingleAxisComponentOption[]; + parallel?: ParallelComponentOption | ParallelComponentOption[]; + parallelAxis?: ParallelAxisComponentOption | ParallelAxisComponentOption[]; + calendar?: CalendarComponentOption | CalendarComponentOption[]; + toolbox?: ToolboxComponentOption | ToolboxComponentOption[]; + tooltip?: TooltipComponentOption | TooltipComponentOption[]; + axisPointer?: AxisPointerComponentOption | AxisPointerComponentOption[]; + brush?: BrushComponentOption | BrushComponentOption[]; + timeline?: TimelineComponentOption | TimelineSliderComponentOption; + legend?: LegendComponentOption | (LegendComponentOption)[]; + dataZoom?: DataZoomComponentOption | (DataZoomComponentOption)[]; + visualMap?: VisualMapComponentOption | (VisualMapComponentOption)[]; + graphic?: GraphicComponentOption | GraphicComponentOption[]; + + // TODO Generally we support specify a single object on series. + // But in practice we found the error hint in monaco editor is not clear if we also support + // single object in type. + series?: SeriesOption | SeriesOption[]; + + options?: EChartsOption[]; + baseOption?: EChartsOption; +} \ No newline at end of file diff --git a/src/export/renderers.ts b/src/export/renderers.ts new file mode 100644 index 0000000000..5acbd39e01 --- /dev/null +++ b/src/export/renderers.ts @@ -0,0 +1,21 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +export {install as SVGRenderer} from '../renderer/installSVGRenderer'; +export {install as CanvasRenderer} from '../renderer/installCanvasRenderer'; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts new file mode 100644 index 0000000000..e4a64937fa --- /dev/null +++ b/src/extension.ts @@ -0,0 +1,113 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { + registerPreprocessor, + registerProcessor, + registerPostInit, + registerPostUpdate, + registerAction, + registerCoordinateSystem, + registerLayout, + registerVisual, + registerTransform, + registerLoading, + registerMap, + PRIORITY +} from './core/echarts'; +import ComponentView from './view/Component'; +import ChartView from './view/Chart'; +import ComponentModel from './model/Component'; +import SeriesModel from './model/Series'; +import { isFunction, indexOf, isArray, each } from 'zrender/src/core/util'; +import { Constructor } from './util/clazz'; +import { SubTypeDefaulter } from './util/component'; +import { registerPainter } from 'zrender/src/zrender'; + +const extensions: (EChartsExtensionInstaller | EChartsExtension)[] = []; + +const extensionRegisters = { + registerPreprocessor, + registerProcessor, + registerPostInit, + registerPostUpdate, + registerAction, + registerCoordinateSystem, + registerLayout, + registerVisual, + registerTransform, + registerLoading, + registerMap, + PRIORITY, + // TODO Use ComponentModel and SeriesModel instead of Constructor + registerComponentModel(ComponentModelClass: Constructor) { + ComponentModel.registerClass(ComponentModelClass); + }, + registerComponentView(ComponentViewClass: typeof ComponentView) { + ComponentView.registerClass(ComponentViewClass); + }, + registerSeriesModel(SeriesModelClass: Constructor) { + SeriesModel.registerClass(SeriesModelClass); + }, + registerChartView(ChartViewClass: typeof ChartView) { + ChartView.registerClass(ChartViewClass); + }, + registerSubTypeDefaulter(componentType: string, defaulter: SubTypeDefaulter) { + ComponentModel.registerSubTypeDefaulter(componentType, defaulter); + }, + registerPainter(painterType: string, PainterCtor: Parameters[1]) { + registerPainter(painterType, PainterCtor); + } +}; + +export type EChartsExtensionInstallRegisters = typeof extensionRegisters; + +export type EChartsExtensionInstaller = (ec: EChartsExtensionInstallRegisters) => void; +export interface EChartsExtension { + install: EChartsExtensionInstaller +} + +export function use( + ext: EChartsExtensionInstaller | EChartsExtension | (EChartsExtensionInstaller | EChartsExtension)[] +) { + if (isArray(ext)) { + // use([ChartLine, ChartBar]); + each(ext, (singleExt) => { + use(singleExt); + }); + return; + } + + if (indexOf(extensions, ext) >= 0) { + return; + } + extensions.push(ext); + + if (isFunction(ext)) { + ext = { + install: ext + }; + } + ext.install(extensionRegisters); +} + +// A simpler use type for exporting to reduce exported inner modules. +export type EChartsExtensionInstallerSimple = (registers: any) => void; +type SimpleEChartsExtensionType = EChartsExtensionInstallerSimple | { install: EChartsExtensionInstallerSimple }; +export declare function useSimple(ext: SimpleEChartsExtensionType | (SimpleEChartsExtensionType)[]): void; diff --git a/src/label/LabelManager.ts b/src/label/LabelManager.ts index 12c3b0ef5e..23ff7ef4df 100644 --- a/src/label/LabelManager.ts +++ b/src/label/LabelManager.ts @@ -28,7 +28,7 @@ import { isElementRemoved } from '../util/graphic'; import { getECData } from '../util/innerStore'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import { ZRTextAlign, ZRTextVerticalAlign, diff --git a/src/layout/barPolar.ts b/src/layout/barPolar.ts index 51d2f3f164..081d884634 100644 --- a/src/layout/barPolar.ts +++ b/src/layout/barPolar.ts @@ -25,7 +25,7 @@ import type Polar from '../coord/polar/Polar'; import AngleAxis from '../coord/polar/AngleAxis'; import RadiusAxis from '../coord/polar/RadiusAxis'; import GlobalModel from '../model/Global'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import { Dictionary } from '../util/types'; type PolarAxis = AngleAxis | RadiusAxis; diff --git a/src/legacy/dataSelectAction.ts b/src/legacy/dataSelectAction.ts index 109496a9b4..e1a68c6afd 100644 --- a/src/legacy/dataSelectAction.ts +++ b/src/legacy/dataSelectAction.ts @@ -23,8 +23,9 @@ import { extend, each, isArray } from 'zrender/src/core/util'; import GlobalModel from '../model/Global'; import { deprecateReplaceLog, deprecateLog } from '../util/log'; import Eventful from 'zrender/src/core/Eventful'; -import type { EChartsType, registerAction } from '../echarts'; +import type { EChartsType, registerAction } from '../core/echarts'; import { queryDataIndex } from '../util/model'; +import ExtensionAPI from '../core/ExtensionAPI'; // Legacy data selection action. // Inlucdes: pieSelect, pieUnSelect, pieToggleSelect, mapSelect, mapUnSelect, mapToggleSelect @@ -93,8 +94,9 @@ function handleSeriesLegacySelectEvents( } } -export function handleLegacySelectEvents(messageCenter: Eventful, ecIns: EChartsType, ecModel: GlobalModel) { +export function handleLegacySelectEvents(messageCenter: Eventful, ecIns: EChartsType, api: ExtensionAPI) { messageCenter.on('selectchanged', function (params: SelectChangedPayload) { + const ecModel = api.getModel(); if (params.isFromClick) { handleSeriesLegacySelectEvents('map', 'selectchanged', ecIns, ecModel, params); handleSeriesLegacySelectEvents('pie', 'selectchanged', ecIns, ecModel, params); diff --git a/src/loading/default.ts b/src/loading/default.ts index 0a122afd55..c267e171f8 100644 --- a/src/loading/default.ts +++ b/src/loading/default.ts @@ -20,7 +20,7 @@ import * as zrUtil from 'zrender/src/core/util'; import * as graphic from '../util/graphic'; import { LoadingEffect } from '../util/types'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import * as textContain from 'zrender/src/contain/text'; const PI = Math.PI; diff --git a/src/model/Global.ts b/src/model/Global.ts index a5ba20790a..59dccdb4ac 100644 --- a/src/model/Global.ts +++ b/src/model/Global.ts @@ -46,19 +46,20 @@ import SeriesModel from './Series'; import { Payload, OptionPreprocessor, - ECOption, + ECBasicOption, ECUnitOption, ThemeOption, ComponentOption, ComponentMainType, ComponentSubType, OptionId, - OptionName + OptionName, + AriaOptionMixin } from '../util/types'; import OptionManager from './OptionManager'; -import Scheduler from '../stream/Scheduler'; +import Scheduler from '../core/Scheduler'; import { concatInternalOptions } from './internalComponentCreator'; -import { LocaleOption } from '../locale'; +import { LocaleOption } from '../core/locale'; import {PaletteMixin} from './mixin/palette'; export interface GlobalModelSetOptionOpts { @@ -76,6 +77,8 @@ let assertSeriesInitialized: (ecModel: GlobalModel) => void; let initBase: (ecModel: GlobalModel, baseOption: ECUnitOption) => void; const OPTION_INNER_KEY = '\0_ec_inner'; +const OPTION_INNER_VALUE = 1; + class GlobalModel extends Model { // @readonly option: ECUnitOption; @@ -119,7 +122,7 @@ class GlobalModel extends Model { init( - option: ECOption, + option: ECBasicOption, parentModel: Model, ecModel: GlobalModel, theme: object, @@ -134,14 +137,18 @@ class GlobalModel extends Model { } setOption( - option: ECOption, + option: ECBasicOption, opts: GlobalModelSetOptionOpts, optionPreprocessorFuncs: OptionPreprocessor[] ): void { - assert( - !(OPTION_INNER_KEY in option), - 'please use chart.getOption()' - ); + + if (__DEV__) { + assert(option != null, 'option is null/undefined'); + assert( + option[OPTION_INNER_KEY] !== OPTION_INNER_VALUE, + 'please use chart.getOption()' + ); + } const innerOpt = normalizeSetOptionInput(opts); @@ -802,11 +809,11 @@ class GlobalModel extends Model { } }; - initBase = function (ecModel: GlobalModel, baseOption: ECUnitOption): void { + initBase = function (ecModel: GlobalModel, baseOption: ECUnitOption & AriaOptionMixin): void { // Using OPTION_INNER_KEY to mark that this option can not be used outside, // i.e. `chart.setOption(chart.getModel().option);` is forbiden. ecModel.option = {} as ECUnitOption; - ecModel.option[OPTION_INNER_KEY] = 1; + ecModel.option[OPTION_INNER_KEY] = OPTION_INNER_VALUE; // Init with series: [], in case of calling findSeries method // before series initialized. diff --git a/src/model/Model.ts b/src/model/Model.ts index bfeac1c508..559c3e96d7 100644 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -30,7 +30,7 @@ import TextStyleMixin from './mixin/textStyle'; import {LineStyleMixin} from './mixin/lineStyle'; import {ItemStyleMixin} from './mixin/itemStyle'; import GlobalModel from './Global'; -import { ModelOption } from '../util/types'; +import { AnimationOptionMixin, ModelOption } from '../util/types'; import { Dictionary } from 'zrender/src/core/types'; import { mixin, clone, merge } from 'zrender/src/core/util'; @@ -42,7 +42,9 @@ type Value = Opt extends Dictionary ? (R extends keyof Opt ? Opt[R] : ModelOption) : ModelOption; -class Model { // TODO: TYPE use unkown insteadof any? +interface Model + extends LineStyleMixin, ItemStyleMixin, TextStyleMixin, AreaStyleMixin {} +class Model { // TODO: TYPE use unkown insteadof any? // [Caution]: Becuase this class or desecendants can be used as `XXX.extend(subProto)`, // the class members must not be initialized in constructor or declaration place. @@ -127,7 +129,7 @@ class Model { // TODO: TYPE use unkown val = parentModel.getShallow(key); } } - return val; + return val as Opt[R]; } // TODO At most 3 depth? @@ -242,8 +244,8 @@ class Model { // TODO: TYPE use unkown // FIXME:TS check whether put this method here isAnimationEnabled(): boolean { if (!env.node && this.option) { - if (this.option.animation != null) { - return !!this.option.animation; + if ((this.option as AnimationOptionMixin).animation != null) { + return !!(this.option as AnimationOptionMixin).animation; } else if (this.parentModel) { return this.parentModel.isAnimationEnabled(); @@ -263,7 +265,8 @@ class Model { // TODO: TYPE use unkown continue; } // obj could be number/string/... (like 0) - obj = (obj && typeof obj === 'object') ? obj[pathArr[i] as keyof ModelOption] : null; + obj = (obj && typeof obj === 'object') + ? (obj as ModelOption)[pathArr[i] as keyof ModelOption] : null; if (obj == null) { break; } @@ -287,7 +290,7 @@ type ModelConstructor = typeof Model enableClassExtend(Model as ModelConstructor); enableClassCheck(Model as ModelConstructor); -interface Model extends LineStyleMixin, ItemStyleMixin, TextStyleMixin, AreaStyleMixin {} + mixin(Model, LineStyleMixin); mixin(Model, ItemStyleMixin); mixin(Model, AreaStyleMixin); diff --git a/src/model/OptionManager.ts b/src/model/OptionManager.ts index e1cd9e2765..21af55f54b 100644 --- a/src/model/OptionManager.ts +++ b/src/model/OptionManager.ts @@ -23,9 +23,9 @@ // import ComponentModel, { ComponentModelConstructor } from './Component'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import { - OptionPreprocessor, MediaQuery, ECUnitOption, MediaUnit, ECOption, SeriesOption + OptionPreprocessor, MediaQuery, ECUnitOption, MediaUnit, ECBasicOption, SeriesOption } from '../util/types'; import GlobalModel, { InnerSetOptionOpts } from './Global'; import { @@ -36,7 +36,7 @@ import { each, clone, map, isTypedArray, setAsPrimitive, isArray, isObject // , HashMap , createHashMap, extend, merge, } from 'zrender/src/core/util'; -import { DatasetOption } from '../component/dataset'; +import { DatasetOption } from '../component/dataset/install'; import { error } from '../util/log'; const QUERY_REG = /^(min|max)?(.+)$/; @@ -91,7 +91,7 @@ class OptionManager { } setOption( - rawOption: ECOption, + rawOption: ECBasicOption, optionPreprocessorFuncs: OptionPreprocessor[], opt: InnerSetOptionOpts ): void { @@ -299,7 +299,7 @@ class OptionManager { */ function parseRawOption( // `rawOption` May be modified - rawOption: ECOption, + rawOption: ECBasicOption, optionPreprocessorFuncs: OptionPreprocessor[], isNew: boolean ): ParsedRawOption { diff --git a/src/model/Series.ts b/src/model/Series.ts index 47b9782c52..7128ec3d61 100644 --- a/src/model/Series.ts +++ b/src/model/Series.ts @@ -19,12 +19,12 @@ import * as zrUtil from 'zrender/src/core/util'; import env from 'zrender/src/core/env'; -import {MorphDividingMethod} from 'zrender/src/tool/morphPath'; +import type {MorphDividingMethod} from 'zrender/src/tool/morphPath'; import * as modelUtil from '../util/model'; import { DataHost, DimensionName, StageHandlerProgressParams, SeriesOption, ZRColor, BoxLayoutOptionMixin, - ScaleDataValue, Dictionary, OptionDataItemObject, SeriesDataType, DimensionLoose, DecalObject + ScaleDataValue, Dictionary, OptionDataItemObject, SeriesDataType, DimensionLoose } from '../util/types'; import ComponentModel, { ComponentModelConstructor } from './Component'; import {PaletteMixin} from './mixin/palette'; @@ -35,11 +35,11 @@ import { mergeLayoutParam, fetchLayoutMode } from '../util/layout'; -import {createTask} from '../stream/task'; +import {createTask} from '../core/task'; import GlobalModel from './Global'; import { CoordinateSystem } from '../coord/CoordinateSystem'; import { ExtendableConstructor, mountExtend, Constructor } from '../util/clazz'; -import { PipelineContext, SeriesTaskContext, GeneralTask, OverallTask, SeriesTask } from '../stream/Scheduler'; +import { PipelineContext, SeriesTaskContext, GeneralTask, OverallTask, SeriesTask } from '../core/Scheduler'; import LegendVisualProvider from '../visual/LegendVisualProvider'; import List from '../data/List'; import Axis from '../coord/Axis'; diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts index 863fc5ccee..53ec13e040 100644 --- a/src/model/mixin/dataFormat.ts +++ b/src/model/mixin/dataFormat.ts @@ -28,6 +28,8 @@ import { ZRColor, OptionDataValue, SeriesDataType, + ComponentMainType, + ComponentSubType, DimensionLoose } from '../../util/types'; import GlobalModel from '../Global'; @@ -39,8 +41,8 @@ const DIMENSION_LABEL_REG = /\{@(.+?)\}/g; export interface DataFormatMixin extends DataHost { ecModel: GlobalModel; - mainType: string; - subType: string; + mainType: ComponentMainType; + subType: ComponentSubType; componentIndex: number; id: string; name: string; diff --git a/src/model/mixin/palette.ts b/src/model/mixin/palette.ts index 0f74d7ddeb..73b6e7ac2a 100644 --- a/src/model/mixin/palette.ts +++ b/src/model/mixin/palette.ts @@ -21,7 +21,7 @@ import {Dictionary} from 'zrender/src/core/types'; import {isArray} from 'zrender/src/core/util'; import {makeInner, normalizeToArray} from '../../util/model'; import Model from '../Model'; -import {ZRColor, PaletteOptionMixin, DecalObject} from '../../util/types'; +import {ZRColor, PaletteOptionMixin, DecalObject, AriaOptionMixin} from '../../util/types'; import GlobalModel from '../Global'; type Inner = (hostObj: PaletteMixin) => { @@ -67,7 +67,7 @@ export function getDecalFromPalette( scope?: any, requestNum?: number ): DecalObject { - const defaultDecals = normalizeToArray(ecModel.get(['aria', 'decal', 'decals'])); + const defaultDecals = normalizeToArray((ecModel as Model).get(['aria', 'decal', 'decals'])); return getFromPalette(ecModel, innerDecal, defaultDecals, null, name, scope, requestNum); } diff --git a/src/option.ts b/src/option.ts deleted file mode 100644 index c9316e9ae4..0000000000 --- a/src/option.ts +++ /dev/null @@ -1,160 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -import type {GridOption} from './coord/cartesian/GridModel'; -import type {PolarOption} from './coord/polar/PolarModel'; -import type {GeoOption} from './coord/geo/GeoModel'; -import type {RadiusAxisOption, AngleAxisOption} from './coord/polar/AxisModel'; -import type {CartesianAxisOption} from './coord/cartesian/AxisModel'; -import type {SingleAxisOption} from './coord/single/AxisModel'; -import type {ParallelAxisOption} from './coord/parallel/AxisModel'; -import type {ParallelCoordinateSystemOption} from './coord/parallel/ParallelModel'; -import type {CalendarOption} from './coord/calendar/CalendarModel'; -import type {ToolboxOption} from './component/toolbox/ToolboxModel'; -import type {TooltipOption} from './component/tooltip/TooltipModel'; -import type {AxisPointerOption} from './component/axisPointer/AxisPointerModel'; -import type {BrushOption} from './component/brush/BrushModel'; -import type {TitleOption} from './component/title'; - -import type {TimelineOption} from './component/timeline/TimelineModel'; -import type {SliderTimelineOption} from './component/timeline/SliderTimelineModel'; - -import type {LegendOption} from './component/legend/LegendModel'; -import type {ScrollableLegendOption} from './component/legend/ScrollableLegendModel'; - -import type {SliderDataZoomOption} from './component/dataZoom/SliderZoomModel'; -import type {InsideDataZoomOption} from './component/dataZoom/InsideZoomModel'; - -import type {ContinousVisualMapOption} from './component/visualMap/ContinuousModel'; -import type {PiecewiseVisualMapOption} from './component/visualMap/PiecewiseModel'; - -import type {LineSeriesOption} from './chart/line/LineSeries'; -import type {BarSeriesOption} from './chart/bar/BarSeries'; -import type {ScatterSeriesOption} from './chart/scatter/ScatterSeries'; -import type {PieSeriesOption} from './chart/pie/PieSeries'; -import type {RadarSeriesOption} from './chart/radar/RadarSeries'; -import type {MapSeriesOption} from './chart/map/MapSeries'; -import type {TreeSeriesOption} from './chart/tree/TreeSeries'; -import type {TreemapSeriesOption} from './chart/treemap/TreemapSeries'; -import type {GraphSeriesOption} from './chart/graph/GraphSeries'; -import type {GaugeSeriesOption} from './chart/gauge/GaugeSeries'; -import type {FunnelSeriesOption} from './chart/funnel/FunnelSeries'; -import type {ParallelSeriesOption} from './chart/parallel/ParallelSeries'; -import type {SankeySeriesOption} from './chart/sankey/SankeySeries'; -import type {BoxplotSeriesOption} from './chart/boxplot/BoxplotSeries'; -import type {CandlestickSeriesOption} from './chart/candlestick/CandlestickSeries'; -import type {EffectScatterSeriesOption} from './chart/effectScatter/EffectScatterSeries'; -import type {LinesSeriesOption} from './chart/lines/LinesSeries'; -import type {HeatmapSeriesOption} from './chart/heatmap/HeatmapSeries'; -import type {PictorialBarSeriesOption} from './chart/bar/PictorialBarSeries'; -import type {ThemeRiverSeriesOption} from './chart/themeRiver/ThemeRiverSeries'; -import type {SunburstSeriesOption} from './chart/sunburst/SunburstSeries'; -import type {CustomSeriesOption} from './chart/custom'; - -import {ToolboxBrushFeatureOption} from './component/toolbox/feature/Brush'; -import {ToolboxDataViewFeatureOption} from './component/toolbox/feature/DataView'; -import {ToolboxDataZoomFeatureOption} from './component/toolbox/feature/DataZoom'; -import {ToolboxMagicTypeFeatureOption} from './component/toolbox/feature/MagicType'; -import {ToolboxRestoreFeatureOption} from './component/toolbox/feature/Restore'; -import {ToolboxSaveAsImageFeatureOption} from './component/toolbox/feature/SaveAsImage'; -import {ToolboxFeatureOption} from './component/toolbox/featureManager'; -import { MarkAreaOption } from './component/marker/MarkAreaModel'; - - -import { ECOption, SeriesTooltipOption } from './util/types'; -import { GraphicComponentLooseOption } from './component/graphic'; -import { MarkLineOption } from './component/marker/MarkLineModel'; -import { MarkPointOption } from './component/marker/MarkPointModel'; - -interface ToolboxFullOptionWithFeatures extends ToolboxOption { - feature?: { - brush?: ToolboxBrushFeatureOption - dataView?: ToolboxDataViewFeatureOption - dataZoom?: ToolboxDataZoomFeatureOption - magicType?: ToolboxMagicTypeFeatureOption - restore?: ToolboxRestoreFeatureOption - saveAsImage?: ToolboxSaveAsImageFeatureOption - // custom feature - [key: string]: ToolboxFeatureOption | { - [key: string]: any - } - } -} - -type SeriesOption = (LineSeriesOption - | BarSeriesOption - | ScatterSeriesOption - | PieSeriesOption - | RadarSeriesOption - | MapSeriesOption - | TreeSeriesOption - | TreemapSeriesOption - | GraphSeriesOption - | GaugeSeriesOption - | FunnelSeriesOption - | ParallelSeriesOption - | SankeySeriesOption - | BoxplotSeriesOption - | CandlestickSeriesOption - | EffectScatterSeriesOption - | LinesSeriesOption - | HeatmapSeriesOption - | PictorialBarSeriesOption - | ThemeRiverSeriesOption - | SunburstSeriesOption - | CustomSeriesOption) & { - // Inject markArea markLine - markArea?: MarkAreaOption - markLine?: MarkLineOption - markPoint?: MarkPointOption - tooltip?: SeriesTooltipOption - }; - -export interface EChartsFullOption extends ECOption { - title?: TitleOption | TitleOption[] - grid?: GridOption | GridOption[] - polar?: PolarOption | PolarOption[] - geo?: GeoOption | GeoOption[] - angleAxis?: AngleAxisOption | AngleAxisOption[] - radiusAxis?: RadiusAxisOption | RadiusAxisOption[] - xAxis?: CartesianAxisOption | CartesianAxisOption[] - yAxis?: CartesianAxisOption | CartesianAxisOption[] - singleAxis?: SingleAxisOption | SingleAxisOption[] - parallel?: ParallelCoordinateSystemOption | ParallelCoordinateSystemOption[] - parallelAxis?: ParallelAxisOption | ParallelAxisOption[] - calendar?: CalendarOption | CalendarOption[] - toolbox?: ToolboxFullOptionWithFeatures | ToolboxFullOptionWithFeatures[] - tooltip?: TooltipOption | TooltipOption[] - axisPointer?: AxisPointerOption | AxisPointerOption[] - brush?: BrushOption | BrushOption[] - timeline?: TimelineOption | SliderTimelineOption - legend?: LegendOption | ScrollableLegendOption | (LegendOption | ScrollableLegendOption)[] - dataZoom?: SliderDataZoomOption | InsideDataZoomOption | (SliderDataZoomOption | InsideDataZoomOption)[] - visualMap?: ContinousVisualMapOption | PiecewiseVisualMapOption - | (ContinousVisualMapOption | PiecewiseVisualMapOption)[] - graphic?: GraphicComponentLooseOption | GraphicComponentLooseOption[] - - // TODO Generally we support specify a single object on series. - // But in practice we found the error hint in monaco editor is not clear if we also support - // single object in type. - series?: SeriesOption | SeriesOption[] - - options?: EChartsFullOption[] - baseOption?: EChartsFullOption -} \ No newline at end of file diff --git a/src/renderer/installCanvasRenderer.ts b/src/renderer/installCanvasRenderer.ts new file mode 100644 index 0000000000..d2ac56da03 --- /dev/null +++ b/src/renderer/installCanvasRenderer.ts @@ -0,0 +1,25 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../extension'; +import CanvasPainter from 'zrender/src/canvas/Painter'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerPainter('canvas', CanvasPainter); +} \ No newline at end of file diff --git a/src/renderer/installSVGRenderer.ts b/src/renderer/installSVGRenderer.ts new file mode 100644 index 0000000000..24ac306537 --- /dev/null +++ b/src/renderer/installSVGRenderer.ts @@ -0,0 +1,25 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { EChartsExtensionInstallRegisters } from '../extension'; +import SVGPainter from 'zrender/src/svg/Painter'; + +export function install(registers: EChartsExtensionInstallRegisters) { + registers.registerPainter('svg', SVGPainter); +} \ No newline at end of file diff --git a/src/scale/Time.ts b/src/scale/Time.ts index c195727877..d9e1b945eb 100644 --- a/src/scale/Time.ts +++ b/src/scale/Time.ts @@ -76,7 +76,7 @@ import Scale from './Scale'; import {TimeScaleTick, ScaleTick} from '../util/types'; import {TimeAxisLabelFormatterOption} from '../coord/axisCommonTypes'; import { warn } from '../util/log'; -import { LocaleOption } from '../locale'; +import { LocaleOption } from '../core/locale'; import Model from '../model/Model'; import { filter, map } from 'zrender/src/core/util'; diff --git a/src/util/decal.ts b/src/util/decal.ts index da796e063f..e671f749ca 100644 --- a/src/util/decal.ts +++ b/src/util/decal.ts @@ -24,7 +24,7 @@ import LRU from 'zrender/src/core/LRU'; import {defaults, createCanvas, map, isArray} from 'zrender/src/core/util'; import {getLeastCommonMultiple} from './number'; import {createSymbol} from './symbol'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import type SVGPainter from 'zrender/src/svg/Painter'; import { brushSingle } from 'zrender/src/canvas/graphic'; import {DecalDashArrayX, DecalDashArrayY, InnerDecalObject, DecalObject} from './types'; diff --git a/src/util/model.ts b/src/util/model.ts index b270dc01ec..31eccd3f15 100644 --- a/src/util/model.ts +++ b/src/util/model.ts @@ -786,7 +786,12 @@ export type ModelFinderObject = { * ... * } */ -type ParsedModelFinderKnown = { +export type ParsedModelFinder = { + // other components + [key: string]: ComponentModel | ComponentModel[] | undefined; +}; + +export type ParsedModelFinderKnown = ParsedModelFinder & { seriesModels?: SeriesModel[]; seriesModel?: SeriesModel; xAxisModels?: CartesianAxisModel[]; @@ -798,10 +803,6 @@ type ParsedModelFinderKnown = { dataIndex?: number; dataIndexInside?: number; }; -export type ParsedModelFinder = ParsedModelFinderKnown & { - // other components - [key: string]: ComponentModel | ComponentModel[]; -}; /** * The same behavior as `component.getReferringComponents`. @@ -829,7 +830,7 @@ export function parseFinder( } const queryOptionMap = createHashMap(); - const result = {} as ParsedModelFinder; + const result = {} as ParsedModelFinderKnown; let mainTypeSpecified = false; each(finder, function (value, key) { diff --git a/src/util/states.ts b/src/util/states.ts index 779297bd33..2136fd73c9 100644 --- a/src/util/states.ts +++ b/src/util/states.ts @@ -46,7 +46,7 @@ import { CoordinateSystemMaster, CoordinateSystem } from '../coord/CoordinateSys import { queryDataIndex, makeInner } from './model'; import Path, { PathStyleProps } from 'zrender/src/graphic/Path'; import GlobalModel from '../model/Global'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; // Reserve 0 as default. let _highlightNextDigit = 1; diff --git a/src/util/time.ts b/src/util/time.ts index dd9c818b0a..d71a8daff9 100644 --- a/src/util/time.ts +++ b/src/util/time.ts @@ -21,7 +21,7 @@ import * as zrUtil from 'zrender/src/core/util'; import {TimeAxisLabelFormatterOption} from './../coord/axisCommonTypes'; import * as numberUtil from './number'; import {TimeScaleTick} from './types'; -import { getDefaultLocaleModel, getLocaleModel, SYSTEM_LANG, LocaleOption } from '../locale'; +import { getDefaultLocaleModel, getLocaleModel, SYSTEM_LANG, LocaleOption } from '../core/locale'; import Model from '../model/Model'; export const ONE_SECOND = 1000; diff --git a/src/util/types.ts b/src/util/types.ts index ba3785b72f..de1e432b6e 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -1,4 +1,3 @@ -import { AriaOption } from './../component/aria'; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -30,10 +29,10 @@ import Group from 'zrender/src/graphic/Group'; import Element, {ElementEvent, ElementTextConfig} from 'zrender/src/Element'; import { DataFormatMixin } from '../model/mixin/dataFormat'; import GlobalModel from '../model/Global'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import SeriesModel from '../model/Series'; import { createHashMap, HashMap } from 'zrender/src/core/util'; -import { TaskPlanCallbackReturn, TaskProgressParams } from '../stream/task'; +import { TaskPlanCallbackReturn, TaskProgressParams } from '../core/task'; import List, {ListDimensionType} from '../data/List'; import { Dictionary, ImageLike, TextAlign, TextVerticalAlign } from 'zrender/src/core/types'; import { PatternObject } from 'zrender/src/graphic/Pattern'; @@ -87,7 +86,7 @@ export type ZRStyleProps = PathStyleProps | ImageStyleProps | TSpanStyleProps | // See `checkClassType` check the restict definition. export type ComponentFullType = string; export type ComponentMainType = keyof ECUnitOption & string; -export type ComponentSubType = ComponentOption['type']; +export type ComponentSubType = Exclude; /** * Use `parseClassType` to parse componentType declaration to componentTypeInfo. * For example: @@ -481,7 +480,7 @@ export type ECUnitOption = { [key: string]: ComponentOption | ComponentOption[] | Dictionary | unknown stateAnimation?: AnimationOption -} & AnimationOptionMixin & ColorPaletteOptionMixin & AriaOptionMixin; +} & AnimationOptionMixin & ColorPaletteOptionMixin; /** * [ECOption]: @@ -522,7 +521,7 @@ export type ECUnitOption = { * }; * ``` */ -export interface ECOption extends ECUnitOption { +export interface ECBasicOption extends ECUnitOption { baseOption?: ECUnitOption; timeline?: ComponentOption | ComponentOption[]; options?: ECUnitOption[]; @@ -611,7 +610,7 @@ export interface OptionEncodeVisualDimensions { // Notice: `value` is coordDim, not nonCoordDim. } export interface OptionEncode extends OptionEncodeVisualDimensions { - [coordDim: string]: OptionEncodeValue + [coordDim: string]: OptionEncodeValue | undefined } export type OptionEncodeValue = DimensionLoose | DimensionLoose[]; export type EncodeDefaulter = (source: Source, dimCount: number) => OptionEncode; @@ -719,6 +718,55 @@ export interface ColorPaletteOptionMixin { colorLayer?: ZRColor[][] } +export interface AriaLabelOption { + enabled?: boolean; + description?: string; + general?: { + withTitle?: string; + withoutTitle?: string; + }; + series?: { + maxCount?: number; + single?: { + prefix?: string; + withName?: string; + withoutName?: string; + }; + multiple?: { + prefix?: string; + withName?: string; + withoutName?: string; + separator?: { + middle?: string; + end?: string; + } + } + }; + data?: { + maxCount?: number; + allData?: string; + partialData?: string; + withName?: string; + withoutName?: string; + separator?: { + middle?: string; + end?: string; + } + } +} + +// Extending is for compating ECharts 4 +export interface AriaOption extends AriaLabelOption { + mainType?: 'aria'; + + enabled?: boolean; + label?: AriaLabelOption; + decal?: { + show?: boolean; + decals?: DecalObject | DecalObject[]; + }; +} + export interface AriaOptionMixin { aria?: AriaOption } @@ -1363,6 +1411,8 @@ export interface CommonAxisPointerOption { } export interface ComponentOption { + mainType?: string; + type?: string; id?: OptionId; @@ -1387,12 +1437,14 @@ export interface DefaultExtraStateOpts { blur: any } +export type DefaultEmphasisFocus = 'none' | 'self' | 'series'; + export interface DefaultExtraEmpasisState { /** * self: Focus self and blur all others. * series: Focus series and blur all other series. */ - focus?: 'none' | 'self' | 'series' + focus?: DefaultEmphasisFocus } interface ExtraStateOptsBase { @@ -1436,6 +1488,8 @@ export interface SeriesOption< ColorPaletteOptionMixin, StatesOptionMixin { + mainType?: 'series' + silent?: boolean blendMode?: string diff --git a/src/view/Chart.ts b/src/view/Chart.ts index c5b7292592..cabb7b9327 100644 --- a/src/view/Chart.ts +++ b/src/view/Chart.ts @@ -23,17 +23,17 @@ import * as componentUtil from '../util/component'; import * as clazzUtil from '../util/clazz'; import * as modelUtil from '../util/model'; import { enterEmphasis, leaveEmphasis, getHighlightDigit } from '../util/states'; -import {createTask, TaskResetCallbackReturn} from '../stream/task'; +import {createTask, TaskResetCallbackReturn} from '../core/task'; import createRenderPlanner from '../chart/helper/createRenderPlanner'; import SeriesModel from '../model/Series'; import GlobalModel from '../model/Global'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import Element from 'zrender/src/Element'; import { Payload, ViewRootGroup, ECEvent, EventQueryItem, StageHandlerPlanReturn, DisplayState, StageHandlerProgressParams } from '../util/types'; -import { SeriesTaskContext, SeriesTask } from '../stream/Scheduler'; +import { SeriesTaskContext, SeriesTask } from '../core/Scheduler'; import List from '../data/List'; const inner = modelUtil.makeInner<{ diff --git a/src/view/Component.ts b/src/view/Component.ts index 1852c4b2af..e3e3dae28c 100644 --- a/src/view/Component.ts +++ b/src/view/Component.ts @@ -22,7 +22,7 @@ import * as componentUtil from '../util/component'; import * as clazzUtil from '../util/clazz'; import ComponentModel from '../model/Component'; import GlobalModel from '../model/Global'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import {Payload, ViewRootGroup, ECEvent, EventQueryItem} from '../util/types'; import Element from 'zrender/src/Element'; import SeriesModel from '../model/Series'; diff --git a/src/visual/aria.ts b/src/visual/aria.ts index 392b59ac2f..62b2eca2d4 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -18,17 +18,16 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import {retrieveRawValue} from '../data/helper/dataProvider'; import GlobalModel from '../model/Global'; import Model from '../model/Model'; import SeriesModel from '../model/Series'; -import {AriaOption} from '../component/aria'; -import {TitleOption} from '../component/title'; import {makeInner} from '../util/model'; -import {Dictionary, DecalObject, InnerDecalObject} from '../util/types'; -import {LocaleOption} from '../locale'; +import {Dictionary, DecalObject, InnerDecalObject, AriaOption} from '../util/types'; +import {LocaleOption} from '../core/locale'; import { getDecalFromPalette } from '../model/mixin/palette'; +import type {TitleOption} from '../component/title/install'; const DEFAULT_OPTION: AriaOption = { label: { diff --git a/src/visual/decal.ts b/src/visual/decal.ts index b5b283510a..e48f5e20c7 100644 --- a/src/visual/decal.ts +++ b/src/visual/decal.ts @@ -17,7 +17,7 @@ * under the License. */ -import ExtensionAPI from '../ExtensionAPI'; +import ExtensionAPI from '../core/ExtensionAPI'; import GlobalModel from '../model/Global'; import {createOrUpdatePatternFromDecal} from '../util/decal'; diff --git a/test/runTest/actions/__meta__.json b/test/runTest/actions/__meta__.json index f6eb460be9..d949986a35 100644 --- a/test/runTest/actions/__meta__.json +++ b/test/runTest/actions/__meta__.json @@ -5,7 +5,7 @@ "area-stack": 2, "area2": 1, "aria-line-bar": 1, - "aria-pie": 1, + "aria-pie": 2, "axes": 0, "axis": 1, "axis-boundaryGap": 1, diff --git a/test/runTest/actions/aria-pie.json b/test/runTest/actions/aria-pie.json index 58ae931ef1..9ff2d9895e 100644 --- a/test/runTest/actions/aria-pie.json +++ b/test/runTest/actions/aria-pie.json @@ -1 +1 @@ -[{"name":"Action 1","ops":[{"type":"mousemove","time":621,"x":116,"y":26},{"type":"mousemove","time":821,"x":79,"y":19},{"type":"mousemove","time":1033,"x":79,"y":19},{"type":"mousedown","time":2203,"x":79,"y":19},{"type":"mouseup","time":2308,"x":79,"y":19},{"time":2309,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":3418,"x":79,"y":19},{"type":"mouseup","time":3538,"x":79,"y":19},{"time":3539,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4724,"x":79,"y":21},{"type":"mousemove","time":4925,"x":72,"y":39},{"type":"screenshot","time":5894},{"type":"mousedown","time":6302,"x":72,"y":39},{"type":"mouseup","time":6411,"x":72,"y":39},{"time":6412,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":7604,"x":72,"y":39},{"type":"mousemove","time":7723,"x":72,"y":39},{"type":"mouseup","time":7730,"x":72,"y":39},{"time":7731,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8047,"x":72,"y":40},{"type":"mousemove","time":8247,"x":73,"y":43},{"type":"mousemove","time":8646,"x":73,"y":44},{"type":"mousemove","time":8847,"x":74,"y":70},{"type":"mousemove","time":9058,"x":73,"y":98},{"type":"mousemove","time":9259,"x":72,"y":100},{"type":"mousemove","time":9344,"x":72,"y":100},{"type":"mousemove","time":9544,"x":64,"y":125},{"type":"mousemove","time":9692,"x":64,"y":124},{"type":"mousemove","time":9892,"x":65,"y":118},{"type":"mousemove","time":10174,"x":65,"y":117},{"type":"mousedown","time":10691,"x":65,"y":117},{"type":"mouseup","time":10778,"x":65,"y":117},{"time":10779,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":11810,"x":65,"y":117},{"type":"mouseup","time":11901,"x":65,"y":117},{"time":11902,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1568012040315}] \ No newline at end of file +[{"name":"Action 1","ops":[{"type":"mousemove","time":621,"x":116,"y":26},{"type":"mousemove","time":821,"x":79,"y":19},{"type":"mousemove","time":1033,"x":79,"y":19},{"type":"mousedown","time":2203,"x":79,"y":19},{"type":"mouseup","time":2308,"x":79,"y":19},{"time":2309,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":3418,"x":79,"y":19},{"type":"mouseup","time":3538,"x":79,"y":19},{"time":3539,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4724,"x":79,"y":21},{"type":"mousemove","time":4925,"x":72,"y":39},{"type":"screenshot","time":5894},{"type":"mousedown","time":6302,"x":72,"y":39},{"type":"mouseup","time":6411,"x":72,"y":39},{"time":6412,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":7604,"x":72,"y":39},{"type":"mousemove","time":7723,"x":72,"y":39},{"type":"mouseup","time":7730,"x":72,"y":39},{"time":7731,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8047,"x":72,"y":40},{"type":"mousemove","time":8247,"x":73,"y":43},{"type":"mousemove","time":8646,"x":73,"y":44},{"type":"mousemove","time":8847,"x":74,"y":70},{"type":"mousemove","time":9058,"x":73,"y":98},{"type":"mousemove","time":9259,"x":72,"y":100},{"type":"mousemove","time":9344,"x":72,"y":100},{"type":"mousemove","time":9544,"x":64,"y":125},{"type":"mousemove","time":9692,"x":64,"y":124},{"type":"mousemove","time":9892,"x":65,"y":118},{"type":"mousemove","time":10174,"x":65,"y":117},{"type":"mousedown","time":10691,"x":65,"y":117},{"type":"mouseup","time":10778,"x":65,"y":117},{"time":10779,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":11810,"x":65,"y":117},{"type":"mouseup","time":11901,"x":65,"y":117},{"time":11902,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1568012040315},{"name":"Action 2","ops":[{"type":"mousemove","time":117,"x":459,"y":143},{"type":"mousemove","time":317,"x":439,"y":225},{"type":"mousedown","time":489,"x":439,"y":238},{"type":"mousemove","time":535,"x":439,"y":238},{"type":"mouseup","time":560,"x":439,"y":238},{"time":561,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":793,"x":440,"y":241},{"type":"mousemove","time":993,"x":467,"y":273},{"type":"mousemove","time":1199,"x":467,"y":272},{"type":"mousedown","time":1270,"x":467,"y":272},{"type":"mouseup","time":1383,"x":467,"y":272},{"time":1384,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1393,"x":467,"y":272},{"type":"mousemove","time":1605,"x":480,"y":295},{"type":"mousemove","time":1810,"x":486,"y":327},{"type":"mousedown","time":1851,"x":486,"y":327},{"type":"mouseup","time":1935,"x":486,"y":327},{"time":1936,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":2417,"x":486,"y":327},{"type":"mouseup","time":2506,"x":486,"y":327},{"time":2507,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2727,"x":486,"y":330},{"type":"mousemove","time":2928,"x":492,"y":405},{"type":"mousedown","time":2985,"x":492,"y":405},{"type":"mouseup","time":3094,"x":492,"y":405},{"time":3095,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3344,"x":490,"y":408},{"type":"mousedown","time":3553,"x":390,"y":471},{"type":"mousemove","time":3572,"x":390,"y":471},{"type":"mouseup","time":3657,"x":390,"y":472},{"time":3658,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3780,"x":391,"y":464},{"type":"mousemove","time":3991,"x":422,"y":359},{"type":"mousemove","time":4196,"x":427,"y":317},{"type":"mousedown","time":4272,"x":427,"y":317},{"type":"mouseup","time":4360,"x":427,"y":317},{"time":4361,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4404,"x":427,"y":317}],"scrollY":0,"scrollX":0,"timestamp":1606877880149}] \ No newline at end of file diff --git a/test/runTest/runtime/timer.js b/test/runTest/runtime/MockDate.js similarity index 98% rename from test/runTest/runtime/timer.js rename to test/runTest/runtime/MockDate.js index fd81940af3..6ebccb2b42 100644 --- a/test/runTest/runtime/timer.js +++ b/test/runTest/runtime/MockDate.js @@ -37,5 +37,6 @@ function MockDate(...args) { MockDate.prototype = Object.create(NativeDate.prototype); Object.setPrototypeOf(MockDate, NativeDate); MockDate.now = mockNow; -window.Date = MockDate; + +export default MockDate; diff --git a/test/runTest/runtime/main.js b/test/runTest/runtime/main.js index 50af6248bf..9173e0fe31 100644 --- a/test/runTest/runtime/main.js +++ b/test/runTest/runtime/main.js @@ -18,7 +18,9 @@ */ import seedrandom from 'seedrandom'; -import './timer'; +import MockDate from './MockDate'; + +window.Date = MockDate; if (typeof __TEST_PLAYBACK_SPEED__ === 'undefined') { window.__TEST_PLAYBACK_SPEED__ = 1; diff --git a/test/runTest/util.js b/test/runTest/util.js index 84c2a7dae6..d7541d8346 100644 --- a/test/runTest/util.js +++ b/test/runTest/util.js @@ -22,8 +22,8 @@ const fse = require('fs-extra'); const https = require('https'); const fs = require('fs'); const rollup = require('rollup'); -const resolve = require('rollup-plugin-node-resolve'); -const commonjs = require('rollup-plugin-commonjs'); +const {nodeResolve} = require('@rollup/plugin-node-resolve'); +const commonjs = require('@rollup/plugin-commonjs'); const config = require('./config'); function modifyEChartsCode(code) { @@ -112,17 +112,18 @@ module.exports.buildRuntimeCode = async function () { const bundle = await rollup.rollup({ input: path.join(__dirname, 'runtime/main.js'), plugins: [ - resolve(), - commonjs(), { - resolveId(importee) { - return importee === 'crypto' ? importee : null; + // https://rollupjs.org/guide/en/#a-simple-example + resolveId(source, importer) { + return source === 'crypto' ? source : null; }, load(id) { // seedrandom use crypto as external module return id === 'crypto' ? 'export default null;' : null; } - } + }, + nodeResolve(), + commonjs() ] }); const { output } = await bundle.generate({ diff --git a/test/types/basic.ts b/test/types/basic.ts index 02ad307d61..92a70d887f 100644 --- a/test/types/basic.ts +++ b/test/types/basic.ts @@ -1,10 +1,13 @@ import * as echarts from '../../'; + const dom = document.createElement('div'); dom.className = 'chart'; const chart = echarts.init(dom); -chart.setOption({ + +const option: echarts.EChartsOption = { series: [{ type: 'bar' }] -}); \ No newline at end of file +}; +chart.setOption(option); \ No newline at end of file diff --git a/test/types/importPartial.ts b/test/types/importPartial.ts new file mode 100644 index 0000000000..2f43d0b427 --- /dev/null +++ b/test/types/importPartial.ts @@ -0,0 +1,45 @@ +import {init, use, ComposeOption} from '../../core'; +import { + BarChart, + BarSeriesOption, + LineChart, + LineSeriesOption +} from '../../charts'; +import { + GridComponent, + GridComponentOption, + + DataZoomComponent, + DataZoomComponentOption, +} from '../../components'; +import { + CanvasRenderer +} from '../../renderers'; + +use([BarChart, LineChart, GridComponent, DataZoomComponent, CanvasRenderer]); + +type Option = ComposeOption< + GridComponentOption | DataZoomComponentOption + | BarSeriesOption | LineSeriesOption +>; + +const option: Option= { + // xAxis and yAxis should been add as dependencies + xAxis: { + min: 0, + max: 10 + }, + yAxis: { + min: 0, + max: 10 + }, + series: [{ + type: 'bar' + }] +} + +const dom = document.createElement('div'); +dom.className = 'chart'; + +const chart = init(dom); +chart.setOption(option); \ No newline at end of file diff --git a/theme/azul.js b/theme/azul.js index 58a76048e3..ee7bba7ac0 100644 --- a/theme/azul.js +++ b/theme/azul.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/bee-inspired.js b/theme/bee-inspired.js index e318e6b47a..8d552f314b 100644 --- a/theme/bee-inspired.js +++ b/theme/bee-inspired.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/blue.js b/theme/blue.js index 7a605e048b..3accf04cb5 100644 --- a/theme/blue.js +++ b/theme/blue.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/caravan.js b/theme/caravan.js index f1a6996dbd..22bec89340 100644 --- a/theme/caravan.js +++ b/theme/caravan.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/carp.js b/theme/carp.js index 8f19828f50..bc5e715187 100644 --- a/theme/carp.js +++ b/theme/carp.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/cool.js b/theme/cool.js index cebc2b72c0..d83e4475c4 100644 --- a/theme/cool.js +++ b/theme/cool.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/dark-blue.js b/theme/dark-blue.js index 3116bf41dd..73715effe9 100644 --- a/theme/dark-blue.js +++ b/theme/dark-blue.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/dark-bold.js b/theme/dark-bold.js index ad74d6a73c..fc2292739a 100644 --- a/theme/dark-bold.js +++ b/theme/dark-bold.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/dark-digerati.js b/theme/dark-digerati.js index 1cbae57a93..e20c8c98b7 100644 --- a/theme/dark-digerati.js +++ b/theme/dark-digerati.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/dark-fresh-cut.js b/theme/dark-fresh-cut.js index 4c96d2ec46..ba9f74438b 100644 --- a/theme/dark-fresh-cut.js +++ b/theme/dark-fresh-cut.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/dark-mushroom.js b/theme/dark-mushroom.js index fdbd8daf4f..1f4954ace0 100644 --- a/theme/dark-mushroom.js +++ b/theme/dark-mushroom.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/dark.js b/theme/dark.js index dceda1fe9c..70598b5ab8 100644 --- a/theme/dark.js +++ b/theme/dark.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/eduardo.js b/theme/eduardo.js index c22c1ad66f..7f567d66ef 100644 --- a/theme/eduardo.js +++ b/theme/eduardo.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/forest.js b/theme/forest.js index 6d4076135a..995858e464 100644 --- a/theme/forest.js +++ b/theme/forest.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/fresh-cut.js b/theme/fresh-cut.js index f63e9910ef..21baf4f43d 100644 --- a/theme/fresh-cut.js +++ b/theme/fresh-cut.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/fruit.js b/theme/fruit.js index a61d89af83..40739342ce 100644 --- a/theme/fruit.js +++ b/theme/fruit.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/gray.js b/theme/gray.js index faae5524a3..f9ab3d64b8 100644 --- a/theme/gray.js +++ b/theme/gray.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/green.js b/theme/green.js index a27cf649d9..a9607f4944 100644 --- a/theme/green.js +++ b/theme/green.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/helianthus.js b/theme/helianthus.js index 1891c29adb..ac9f1b232e 100644 --- a/theme/helianthus.js +++ b/theme/helianthus.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/infographic.js b/theme/infographic.js index 1ee115c573..13a86c3dae 100644 --- a/theme/infographic.js +++ b/theme/infographic.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/inspired.js b/theme/inspired.js index 264a3d8057..15f620eaac 100644 --- a/theme/inspired.js +++ b/theme/inspired.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/jazz.js b/theme/jazz.js index c8cc9b7b63..c93d661e2f 100644 --- a/theme/jazz.js +++ b/theme/jazz.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/london.js b/theme/london.js index 3abe436e39..438828598a 100644 --- a/theme/london.js +++ b/theme/london.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/macarons.js b/theme/macarons.js index d6094aed6a..d78f7e99d4 100644 --- a/theme/macarons.js +++ b/theme/macarons.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/macarons2.js b/theme/macarons2.js index 7a69402b35..3fdb4b9fbc 100644 --- a/theme/macarons2.js +++ b/theme/macarons2.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/mint.js b/theme/mint.js index 17d2d1652e..c3975a96cd 100644 --- a/theme/mint.js +++ b/theme/mint.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/red-velvet.js b/theme/red-velvet.js index 3577dbe800..be75b73efc 100644 --- a/theme/red-velvet.js +++ b/theme/red-velvet.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/red.js b/theme/red.js index 5535dbe995..a70347bcf9 100644 --- a/theme/red.js +++ b/theme/red.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/roma.js b/theme/roma.js index 538edce0b6..8eda0bf3c3 100644 --- a/theme/roma.js +++ b/theme/roma.js @@ -27,7 +27,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/royal.js b/theme/royal.js index dd850fb6b3..4578a109c2 100644 --- a/theme/royal.js +++ b/theme/royal.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/sakura.js b/theme/sakura.js index 4e463aaf22..6038f8cf5e 100644 --- a/theme/sakura.js +++ b/theme/sakura.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/shine.js b/theme/shine.js index 1217a44a68..e554af7683 100644 --- a/theme/shine.js +++ b/theme/shine.js @@ -25,7 +25,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/tech-blue.js b/theme/tech-blue.js index d4ff74a7e1..e5435f0e55 100644 --- a/theme/tech-blue.js +++ b/theme/tech-blue.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts); diff --git a/theme/vintage.js b/theme/vintage.js index 78d0f89826..a1bd4b6e2d 100644 --- a/theme/vintage.js +++ b/theme/vintage.js @@ -26,7 +26,7 @@ typeof exports.nodeName !== 'string' ) { // CommonJS - factory(exports, require('echarts')); + factory(exports, require('echarts/lib/echarts')); } else { // Browser globals factory({}, root.echarts);