diff --git a/.rat-excludes b/.headerignore similarity index 72% rename from .rat-excludes rename to .headerignore index 903c4bd61dc890ee8125b985c9769dc1d67e5950..41d55d65359c8484678f45490aecf5ca03ba692f 100644 --- a/.rat-excludes +++ b/.headerignore @@ -1,19 +1,17 @@ # Only support regexp, testing against each relative file path # based on the echart base directory. And the pattern should # match the relative path completely. -# Do not use wildcard. Although Apache Rat v0.12 implemented -# it, it probably does not do it right, where patterns are -# parsed as Regex firstly but do not catch the exception. node_modules .*\.git .*\.github .*\.editorconfig .*\.gitignore -.*\.jshintrc$ +.*\.jshintrc .*\.jshintrc-dist .*\.npmignore -.*\.rat-excludes +.*\.ratignore +.*\.headerignore .*\.DS_Store .*\.idea .*rat\.iml @@ -24,6 +22,7 @@ DISCLAIMER NOTICE KEYS LICENSE +LICENSE-.+ licenses map/js map/json @@ -36,8 +35,6 @@ test/lib/countup\.js .*jquery\.min\.js .*rollup\.browser\.js .*configure -.*IAxisPointer -.*ICoordinateSystem .+\.json .+\.map .+\.gexf diff --git a/LICENSE b/LICENSE index 94b9b0dd426e39ca1032e1d0dcecfdefa800106f..717eab381bf5caccb7211b32dea31c7a831dda35 100644 --- a/LICENSE +++ b/LICENSE @@ -213,46 +213,12 @@ notices and license terms. Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses. -BSD 3-Clause (zrender): -The following files embed [zrender](https://github.com/ecomfe/zrender) BSD 3-Clause: - `dist/echarts-en.common.js`, - `dist/echarts-en.common.min.js`, - `dist/echarts-en.js`, - `dist/echarts-en.min.js`, - `dist/echarts-en.simple.js`, - `dist/echarts-en.simple.min.js`, - `dist/echarts.common.js`, - `dist/echarts.common.min.js`, - `dist/echarts.js`, - `dist/echarts.min.js`, - `dist/echarts.simple.js`, - `dist/echarts.simple.min.js`, -See `licenses/LICENSE-zrender` for details of the license. - BSD 3-Clause (d3.js): The following files embed [d3.js](https://github.com/d3/d3) BSD 3-Clause: `src/chart/treemap/treemapLayout.js`, `src/chart/tree/layoutHelper.js`, `src/chart/graph/forceHelper.js`, `src/util/array/nest.js`, + `src/util/number.js`, `src/scale/Time.js`, - `lib/chart/treemap/treemapLayout.js`, - `lib/chart/tree/layoutHelper.js`, - `lib/chart/graph/forceHelper.js`, - `lib/util/array/nest.js`, - `lib/scale/Time.js`, - `extension-src/dataTool/quantile.js`, - `extension/dataTool/quantile.js`, - `dist/echarts-en.common.js`, - `dist/echarts-en.common.min.js`, - `dist/echarts-en.js`, - `dist/echarts-en.min.js`, - `dist/echarts-en.simple.js`, - `dist/echarts-en.simple.min.js`, - `dist/echarts.common.js`, - `dist/echarts.common.min.js`, - `dist/echarts.js`, - `dist/echarts.min.js`, - `dist/echarts.simple.js`, - `dist/echarts.simple.min.js`, See `licenses/LICENSE-d3` for details of the license. diff --git a/README.md b/README.md index 8ee21e3f56214170e83ff77a0fb4a7b7077dc34e..e1188bf298f92132205f20108d985e810006ea8d 100644 --- a/README.md +++ b/README.md @@ -150,3 +150,10 @@ Check this tutorial [Create Custom Build of ECharts](https://ecomfe.github.io/ec ## License ECharts is available under the Apache License V2. + +## Reference Paper + +Deqing Li, Honghui Mei, Yi Shen, Shuang Su, Wenli Zhang, Junting Wang, Ming Zu, Wei Chen. +[ECharts: A Declarative Framework for Rapid Construction of Wed-based Visualization](http://www.cad.zju.edu.cn/home/vagblog/VAG_Work/echarts.pdf). +Visual Informatics, 2018. + diff --git a/build/addHeader.js b/build/addHeader.js index 747f3ec9b2af81f841b6acbe98ea6b000622751f..1599ffc2d7348bd3fe10c0ae117b7ccda762fba9 100644 --- a/build/addHeader.js +++ b/build/addHeader.js @@ -17,18 +17,16 @@ * under the License. */ -/** - * For consistency, we use `.rat-excludes` for both Apache Rat and this tool. - * In the `.rat-excludes`, each line is a pattern in RegExp. - * all relative path (based on the echarts base directory) is tected. - * The pattern should match the relative path completely. - */ const fs = require('fs'); const preamble = require('./preamble'); const pathTool = require('path'); const {color} = require('zrender/build/helper'); -const excludesPath = pathTool.join(__dirname, '../.rat-excludes'); + +// In the `.headerignore`, each line is a pattern in RegExp. +// all relative path (based on the echarts base directory) is tested. +// The pattern should match the relative path completely. +const excludesPath = pathTool.join(__dirname, '../.headerignore'); const ecBasePath = pathTool.join(__dirname, '../'); const isVerbose = process.argv[2] === '--verbose'; diff --git a/build/preamble.js b/build/preamble.js index 5d815a1546a44c83db41b8294b74c553b10084b6..c58af71439858243408fbf35e12252f507f54b5c 100644 --- a/build/preamble.js +++ b/build/preamble.js @@ -160,7 +160,8 @@ const licenseReg = [ {name: 'LGPL', reg: /LGPL/}, {name: 'GPL', reg: /GPL/}, {name: 'Mozilla', reg: /mozilla public/i}, - {name: 'MIT', reg: /mit license/i} + {name: 'MIT', reg: /mit license/i}, + {name: 'BSD-d3', reg: /Copyright\s+\(c\)\s+2010-2015,\s+Michael\s+Bostock/i} ]; function extractLicense(fileStr, fileExt) { diff --git a/build/rat/apache-rat-0.12.jar b/build/rat/apache-rat-0.12.jar deleted file mode 100644 index 8cbb23b0cbbd4d07242be139a435d7e93e3b29e6..0000000000000000000000000000000000000000 Binary files a/build/rat/apache-rat-0.12.jar and /dev/null differ diff --git a/build/rat/build-run-rat.sh b/build/rat/build-run-rat.sh deleted file mode 100644 index d02ffefec7bc25a9501968f6cf972e88ae3c1cf9..0000000000000000000000000000000000000000 --- a/build/rat/build-run-rat.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -# 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. - -basePath=$(cd `dirname $0`; pwd) -ecPath=${basePath}/../.. -runRatSrcDir=${ecPath}/build/rat/src - -javac -classpath ${ecPath}/build/rat/javassist.jar ${runRatSrcDir}/RunRat.java -jar cfmv ${ecPath}/build/rat/runrat.jar ${runRatSrcDir}/MANIFEST.txt -C ${runRatSrcDir} RunRat.class diff --git a/build/rat/javassist.jar b/build/rat/javassist.jar deleted file mode 100755 index 232bb340746efd217029ecaea41a59dbf0f82f19..0000000000000000000000000000000000000000 Binary files a/build/rat/javassist.jar and /dev/null differ diff --git a/build/rat/rat.sh b/build/rat/rat.sh deleted file mode 100644 index 6ce68199cc9308570417705ed9fac2aac74d8b7f..0000000000000000000000000000000000000000 --- a/build/rat/rat.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh - -# 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. - - -# RunRat is used to call Apache Rat. -# -# (1) Usage: -# -# Check all: -# ```shell -# java -jar ${ecPath}/build/rat/runrat.jar -# ``` -# -# Get help: -# ```shell -# java -jar ${ecPath}/build/rat/runrat.jar --help -# ``` -# -# Notice that most of the arguments is the same as Apache Rat, -# only `--dir` and `--exclude` should not be specified. -# -# Ohter feature of Apache Rat: -# ```shell -# java -jar ${ecPath}/build/rat/runrat.jar [option] -# ``` -# -# -# (2) Why call Apache Rat via `RunRat`? -# -# Because Apache Rat only support specifying file name (in regexp or wildcard) in its -# "exclude" file, but not support specifying file path (in regexp or wildcard), which -# is commonly necessary in the "ignore/exclude" file of this kind of features. -# -# For example: -# the file path "aaa/lib" (with slash) is not supported. But if only specifying "lib", -# all of the directories "lib" are excluded, which is not expected. -# -# (See `org.apache.rat.walker.Walker#isIgnore` for details, where the file `dir` is not -# actually used by `org.apache.commons.io.filefilter.RegexFileFilter#accept` and -# `org.apache.commons.io.filefilter.WildcardFileFilter#accept`.) -# So use this tool as a workaround. - - -basePath=$(cd `dirname $0`; pwd) -ecPath=${basePath}/../.. - -java -jar ${ecPath}/build/rat/runrat.jar diff --git a/build/rat/runrat.jar b/build/rat/runrat.jar deleted file mode 100644 index 2407c55a0f975873133bba9a5f7a49a0a7454a36..0000000000000000000000000000000000000000 Binary files a/build/rat/runrat.jar and /dev/null differ diff --git a/build/rat/src/MANIFEST.txt b/build/rat/src/MANIFEST.txt deleted file mode 100755 index a785f68e8c310d00a8303fc2a1445680b8b97288..0000000000000000000000000000000000000000 --- a/build/rat/src/MANIFEST.txt +++ /dev/null @@ -1,2 +0,0 @@ -Main-Class: RunRat -Class-Path: ./ ./javassist.jar diff --git a/build/rat/src/RunRat.class b/build/rat/src/RunRat.class deleted file mode 100644 index 9c008b4ff9971240abcd8930ea0bbc08981cf138..0000000000000000000000000000000000000000 Binary files a/build/rat/src/RunRat.class and /dev/null differ diff --git a/build/rat/src/RunRat.java b/build/rat/src/RunRat.java deleted file mode 100644 index 763f9365957838013e79141e610d8d476515a61b..0000000000000000000000000000000000000000 --- a/build/rat/src/RunRat.java +++ /dev/null @@ -1,184 +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. - */ - -/** - * RunRat is used to call Apache Rat. - * - * - * (1) Usage: - * - * Prepare, in command line: - * ```shell - * cd ${echartsBaseDir}/build/rat - * ``` - * - * Check all: - * ```shell - * java RunRat - * ``` - * - * Get help: - * ```shell - * java RunRat --help - * ``` - * - * Notice that most of the arguments is the same as Apache Rat, - * only `--dir` and `--exclude` should not be specified. - * - * Ohter feature of Apache Rat: - * ```shell - * java RunRat [option] - * ``` - * - * Rebuild: - * ```shell - * javac RunRat.java - * ``` - * - * - * (2) Why call Apache Rat via `RunRat`? - * - * Because Apache Rat only support specifying file name (in regexp or wildcard) in its - * "exclude" file, but not support specifying file path (in regexp or wildcard), which - * is commonly necessary in the "ignore/exclude" file of this kind of features. - * - * For example: - * the file path "aaa/lib" (with slash) is not supported. But if only specifying "lib", - * all of the directories "lib" are excluded, which is not expected. - * - * (See `org.apache.rat.walker.Walker#isIgnore` for details, where the file `dir` is not - * actually used by `org.apache.commons.io.filefilter.RegexFileFilter#accept` and - * `org.apache.commons.io.filefilter.WildcardFileFilter#accept`.) - * - * So use this tool as a workaround. - */ - -import java.io.File; -import java.lang.reflect.Method; -import java.util.ArrayList; - -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtMethod; -import javassist.Loader; - -public class RunRat { - - private String defaultRatMainClassName = "org.apache.rat.Report"; - - private Method ratReportMainMethod; - - private File ecBaseDir; - - private ArrayList ratArgList = new ArrayList(); - - public static void main(String[] args) throws Exception { - - RunRat runRat = new RunRat(); - - runRat.prepareArgs(args); - - runRat.prepareLibs(args); - - runRat.callRat(); - } - - private void prepareArgs(String[] args) throws IllegalArgumentException { - boolean reportTplSpecified = false; - for (int i = 0; i < args.length; ) { - String argStr = args[i]; - - if (argStr.equals("--dir") - || argStr.equals("-d") - || argStr.equals("-e") - || argStr.equals("--exclude") - || argStr.equals("-E") - || argStr.equals("-exclude-file") - ) { - throw new IllegalArgumentException(argStr + " should not be specified!"); - } - - if (argStr.equals("-s") || argStr.equals("--stylesheet")) { - reportTplSpecified = true; - } - - if (argStr.equals("--ec-base")) { - this.ecBaseDir = new File(args[i + 1]); - i += 2; - } - else { - this.ratArgList.add(argStr); - i++; - } - } - - if (this.ecBaseDir == null) { - this.ecBaseDir = new File(new File( - this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath() - ).getParent() + "/../.."); - } - - this.ratArgList.add("--dir"); - this.ratArgList.add(this.ecBaseDir.getPath()); - this.ratArgList.add("--exclude-file"); - this.ratArgList.add(this.ecBaseDir.getPath() + "/.rat-excludes"); - if (!reportTplSpecified) { - this.ratArgList.add("-s"); - this.ratArgList.add(this.ecBaseDir.getPath() + "/build/rat/src/report.xsl"); - } - } - - private void prepareLibs(String[] args) throws Exception { - - ClassPool cPool = new ClassPool(true); - Loader loader = new Loader(cPool); - cPool.appendClassPath(this.ecBaseDir + "/build/rat/apache-rat-0.12.jar"); - - CtClass ctclzWalker = cPool.get("org.apache.rat.walker.Walker"); - CtMethod ctmethodIgnored = ctclzWalker.getDeclaredMethod("ignored"); - - String ecBaseDirPath = this.ecBaseDir.getPath(); - - ctmethodIgnored.setBody("" - + "{" - + " boolean result = false;" - + " if (this.filter != null) {" - + " String name = $1.getName();" - + " java.io.File dir = $1.getParentFile();" - + " String relativePath = new java.io.File(\"" + ecBaseDirPath + "\").toURI().relativize($1.toURI()).getPath();" - + " if (relativePath.endsWith(\"/\")) {" - + " relativePath = relativePath.substring(0, relativePath.length() - 1);" - + " }" - + " result = !this.filter.accept(dir, relativePath);" - + " }" - + " return result;" - + "}" - ); - - Class clzRatReport = loader.loadClass(this.defaultRatMainClassName); - this.ratReportMainMethod = clzRatReport.getDeclaredMethod("main", String[].class); - } - - private void callRat() throws Exception { - String[] ratArgs = this.ratArgList.toArray(new String[this.ratArgList.size()]); - - this.ratReportMainMethod.invoke(null, (Object)ratArgs); - } - -} diff --git a/build/rat/src/report.xsl b/build/rat/src/report.xsl deleted file mode 100644 index 51383475e45c7d6d30f6bec100b997c841731d87..0000000000000000000000000000000000000000 --- a/build/rat/src/report.xsl +++ /dev/null @@ -1,88 +0,0 @@ - - - - - -***************************************************** -Summary -------- -Generated at: - -Notes: -Binaries: -Archives: -Standards: - -Apache Licensed: -Generated Documents: - -JavaDocs are generated, thus a license header is optional. -Generated files do not require license headers. - - Unknown Licenses - -***************************************************** - -Files with unapproved licenses: - - - - - - - -***************************************************** - - -Archives: - - + - - - - - - -***************************************************** - Files with Apache License headers will be marked AL - Binary files (which do not require any license headers) will be marked B - Compressed archives will be marked A - Notices, licenses etc. will be marked N - - - ! - - - - N - A - B - - !!!!! - - - - - - - - - \ No newline at end of file diff --git a/extension-src/dataTool/prepareBoxplotData.js b/extension-src/dataTool/prepareBoxplotData.js index 1503526ee5d681c0475c9a1705319d967fce4267..690a8de23ec8ccb3cfe7154aa665090c2289f06b 100644 --- a/extension-src/dataTool/prepareBoxplotData.js +++ b/extension-src/dataTool/prepareBoxplotData.js @@ -17,7 +17,6 @@ * under the License. */ -import quantile from './quantile'; import * as numberUtil from '../../src/util/number'; /** @@ -58,9 +57,9 @@ export default function (rawData, opt) { axisData.push(i + ''); var ascList = numberUtil.asc(rawData[i].slice()); - var Q1 = quantile(ascList, 0.25); - var Q2 = quantile(ascList, 0.5); - var Q3 = quantile(ascList, 0.75); + var Q1 = numberUtil.quantile(ascList, 0.25); + var Q2 = numberUtil.quantile(ascList, 0.5); + var Q3 = numberUtil.quantile(ascList, 0.75); var min = ascList[0]; var max = ascList[ascList.length - 1]; diff --git a/extension-src/dataTool/quantile.js b/extension-src/dataTool/quantile.js deleted file mode 100644 index 33c877170f476bf9a24b498f538453b8f8fae503..0000000000000000000000000000000000000000 --- a/extension-src/dataTool/quantile.js +++ /dev/null @@ -1,60 +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. -*/ - -/** - * Copyright (c) 2010-2015, Michael Bostock - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * The name Michael Bostock may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @see - * @see - * @param {Array.} ascArr - */ -export default function(ascArr, p) { - var H = (ascArr.length - 1) * p + 1, - h = Math.floor(H), - v = +ascArr[h - 1], - e = H - h; - return e ? v + e * (ascArr[h] - v) : v; -} diff --git a/src/chart/custom.js b/src/chart/custom.js index b4535e704168d6dea84fdcd5e933b6c3b0edb6a2..191cc9b60817fce2d9f6541f5a39f1e5355ffcd3 100644 --- a/src/chart/custom.js +++ b/src/chart/custom.js @@ -74,7 +74,9 @@ echarts.extendSeriesModel({ coordinateSystem: 'cartesian2d', // Can be set as 'none' zlevel: 0, z: 2, - legendHoverLink: true + legendHoverLink: true, + + useTransform: true // Cartesian coordinate system // xAxisIndex: 0, @@ -112,24 +114,27 @@ echarts.extendChartView({ /** * @override */ - render: function (customSeries, ecModel, api) { + render: function (customSeries, ecModel, api, payload) { var oldData = this._data; var data = customSeries.getData(); var group = this.group; var renderItem = makeRenderItem(customSeries, data, ecModel, api); - this.group.removeAll(); - + // 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`. data.diff(oldData) .add(function (newIdx) { createOrUpdate( - null, newIdx, renderItem(newIdx), customSeries, group, data + null, newIdx, renderItem(newIdx, payload), customSeries, group, data ); }) .update(function (newIdx, oldIdx) { var el = oldData.getItemGraphicEl(oldIdx); createOrUpdate( - el, newIdx, renderItem(newIdx), customSeries, group, data + el, newIdx, renderItem(newIdx, payload), customSeries, group, data ); }) .remove(function (oldIdx) { @@ -146,7 +151,7 @@ echarts.extendChartView({ this._data = null; }, - incrementalRender: function (params, customSeries, ecModel, api) { + incrementalRender: function (params, customSeries, ecModel, api, payload) { var data = customSeries.getData(); var renderItem = makeRenderItem(customSeries, data, ecModel, api); function setIncrementalAndHoverLayer(el) { @@ -156,7 +161,7 @@ echarts.extendChartView({ } } for (var idx = params.start; idx < params.end; idx++) { - var el = createOrUpdate(null, idx, renderItem(idx), customSeries, this.group, data); + var el = createOrUpdate(null, idx, renderItem(idx, payload), customSeries, this.group, data); el.traverse(setIncrementalAndHoverLayer); } }, @@ -328,13 +333,16 @@ function makeRenderItem(customSeries, data, ecModel, api) { var currLabelEmphasisModel; var currVisualColor; - return function (dataIndexInside) { + return function (dataIndexInside, payload) { currDataIndexInside = dataIndexInside; currDirty = true; + return renderItem && renderItem( zrUtil.defaults({ dataIndexInside: dataIndexInside, - dataIndex: data.getRawIndex(dataIndexInside) + dataIndex: data.getRawIndex(dataIndexInside), + // Can be used for optimization when zoom or roam. + actionType: payload ? payload.type : null }, userParams), userAPI ) || {}; @@ -495,13 +503,20 @@ function createOrUpdate(el, dataIndex, elOption, animatableModel, group, data) { } function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data) { + elOption = elOption || {}; + var elOptionType = elOption.type; - if (el - && elOptionType !== el.__customGraphicType - && (elOptionType !== 'path' || elOption.pathData !== el.__customPathData) - && (elOptionType !== 'image' || elOption.style.image !== el.__customImagePath) - && (elOptionType !== 'text' || elOption.style.text !== el.__customText) - ) { + if (el && ( + // Also consider that if `renderItem` returns nothing, the original element + // (if exists) will be removed (elOption is an empty object in that case). + elOptionType == null + || elOption.$merge === false + || (elOptionType !== el.__customGraphicType + && (elOptionType !== 'path' || elOption.pathData !== el.__customPathData) + && (elOptionType !== 'image' || elOption.style.image !== el.__customImagePath) + && (elOptionType !== 'text' || elOption.style.text !== el.__customText) + ) + )) { group.remove(el); el = null; } @@ -515,12 +530,18 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data) !el && (el = createEl(elOption)); updateEl(el, dataIndex, elOption, animatableModel, data, isInit); - if (elOptionType === 'group') { + // If `renderItem` returns no children, follow the principle of + // "merge", remain the children of the original elements + // (if exists). The feature can help optimization when roam and + // data zoom. If intending to clear children, `renderItem` could + // returns an empty array as children. + var newChildren = elOption.children; + if (elOptionType === 'group' && newChildren) { var oldChildren = el.children() || []; - var newChildren = elOption.children || []; + // By default, do not diff elements by name inside a + // group, because that might be lower performance. if (elOption.diffChildrenByName) { - // lower performance. diffGroupChildren({ oldChildren: oldChildren, newChildren: newChildren, @@ -530,8 +551,9 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data) data: data }); } + // Mapping children of a group simply by index, which + // might be better performance. else { - // better performance. var index = 0; for (; index < newChildren.length; index++) { doCreateOrUpdate( @@ -549,6 +571,7 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data) } } + // Always add whatever already added to ensure sequence. group.add(el); return el; diff --git a/src/chart/graph/GraphView.js b/src/chart/graph/GraphView.js index c0a7a5931653a98811b4980f9a1b8290d169b590..4605420c041226ddcae1ad136aaaebf0f2007aa9 100644 --- a/src/chart/graph/GraphView.js +++ b/src/chart/graph/GraphView.js @@ -132,7 +132,7 @@ export default echarts.extendChartView({ var itemModel = data.getItemModel(idx); // Update draggable el.off('drag').off('dragend'); - var draggable = data.getItemModel(idx).get('draggable'); + var draggable = itemModel.get('draggable'); if (draggable) { el.on('drag', function () { if (forceLayout) { @@ -321,23 +321,23 @@ export default echarts.extendChartView({ controller .off('pan') .off('zoom') - .on('pan', function (dx, dy) { - roamHelper.updateViewOnPan(controllerHost, dx, dy); + .on('pan', function (e) { + roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy); api.dispatchAction({ seriesId: seriesModel.id, type: 'graphRoam', - dx: dx, - dy: dy + dx: e.dx, + dy: e.dy }); }) - .on('zoom', function (zoom, mouseX, mouseY) { - roamHelper.updateViewOnZoom(controllerHost, zoom, mouseX, mouseY); + .on('zoom', function (e) { + roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); api.dispatchAction({ seriesId: seriesModel.id, type: 'graphRoam', - zoom: zoom, - originX: mouseX, - originY: mouseY + zoom: e.scale, + originX: e.originX, + originY: e.originY }); this._updateNodeAndLinkScale(); adjustEdge(seriesModel.getGraph(), this._getNodeGlobalScale(seriesModel)); diff --git a/src/chart/graph/graphAction.js b/src/chart/graph/graphAction.js index d65d809384fcc667c96b5dd5973c4cd65c99cb69..6f7bc1368c9e2746abd86dae654eb93dabaed651 100644 --- a/src/chart/graph/graphAction.js +++ b/src/chart/graph/graphAction.js @@ -19,6 +19,7 @@ import * as echarts from '../../echarts'; import {updateCenterAndZoom} from '../../action/roamHelper'; +import '../helper/focusNodeAdjacencyAction'; var actionInfo = { type: 'graphRoam', @@ -49,28 +50,3 @@ echarts.registerAction(actionInfo, function (payload, ecModel) { }); }); - -/** - * @payload - * @property {number} [seriesIndex] - * @property {string} [seriesId] - * @property {string} [seriesName] - * @property {number} [dataIndex] - */ -echarts.registerAction({ - type: 'focusNodeAdjacency', - event: 'focusNodeAdjacency', - update: 'series.graph:focusNodeAdjacency' -}, function () {}); - -/** - * @payload - * @property {number} [seriesIndex] - * @property {string} [seriesId] - * @property {string} [seriesName] - */ -echarts.registerAction({ - type: 'unfocusNodeAdjacency', - event: 'unfocusNodeAdjacency', - update: 'series.graph:unfocusNodeAdjacency' -}, function () {}); diff --git a/src/chart/helper/focusNodeAdjacencyAction.js b/src/chart/helper/focusNodeAdjacencyAction.js new file mode 100644 index 0000000000000000000000000000000000000000..598950ce3022e4393a494194227f75864a4ad724 --- /dev/null +++ b/src/chart/helper/focusNodeAdjacencyAction.js @@ -0,0 +1,45 @@ +/* +* 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'; + +/** + * @payload + * @property {number} [seriesIndex] + * @property {string} [seriesId] + * @property {string} [seriesName] + * @property {number} [dataIndex] + */ +echarts.registerAction({ + type: 'focusNodeAdjacency', + event: 'focusNodeAdjacency', + update: 'series:focusNodeAdjacency' +}, function () {}); + +/** + * @payload + * @property {number} [seriesIndex] + * @property {string} [seriesId] + * @property {string} [seriesName] + */ +echarts.registerAction({ + type: 'unfocusNodeAdjacency', + event: 'unfocusNodeAdjacency', + update: 'series:unfocusNodeAdjacency' +}, function () {}); diff --git a/src/chart/sankey/SankeySeries.js b/src/chart/sankey/SankeySeries.js index 1835c0916d1503c0d1976a7eea55d1bb3df7dd08..8f25b613cfc985fa6cbd5b479583be63c7ea6962 100644 --- a/src/chart/sankey/SankeySeries.js +++ b/src/chart/sankey/SankeySeries.js @@ -111,6 +111,8 @@ var SankeySeries = SeriesModel.extend({ // control if the node can move or not draggable: true, + + focusNodeAdjacency: false, // the number of iterations to change the position of the node layoutIterations: 32, diff --git a/src/chart/sankey/SankeyView.js b/src/chart/sankey/SankeyView.js index 180f49144725eafeca2d5cf2b9adf2f96220ff2f..3bfcf753e72c82d38aabaf5c3007936202bc177e 100644 --- a/src/chart/sankey/SankeyView.js +++ b/src/chart/sankey/SankeyView.js @@ -24,6 +24,43 @@ import * as graphic from '../../util/graphic'; import * as echarts from '../../echarts'; +import * as zrUtil from 'zrender/src/core/util'; + +var nodeOpacityPath = ['itemStyle', 'opacity']; +var lineOpacityPath = ['lineStyle', 'opacity']; + +function getItemOpacity(item, opacityPath) { + return item.getVisual('opacity') || item.getModel().get(opacityPath); +} + +function fadeOutItem(item, opacityPath, opacityRatio) { + var el = item.getGraphicEl(); + + var opacity = getItemOpacity(item, opacityPath); + if (opacityRatio != null) { + opacity == null && (opacity = 1); + opacity *= opacityRatio; + } + + el.downplay && el.downplay(); + el.traverse(function (child) { + if (child.type !== 'group') { + child.setStyle('opacity', opacity); + } + }); +} + +function fadeInItem(item, opacityPath) { + var opacity = getItemOpacity(item, opacityPath); + var el = item.getGraphicEl(); + + el.highlight && el.highlight(); + el.traverse(function (child) { + if (child.type !== 'group') { + child.setStyle('opacity', opacity); + } + }); +} var SankeyShape = graphic.extendShape({ shape: { @@ -180,10 +217,11 @@ export default echarts.extendChartView({ rect.dataType = 'node'; }); - - var draggable = seriesModel.get('draggable'); - if (draggable) { - nodeData.eachItemGraphicEl(function (el, dataIndex) { + + nodeData.eachItemGraphicEl(function (el, dataIndex) { + var itemModel = nodeData.getItemModel(dataIndex); + // var draggable = seriesModel.get('draggable'); + if (itemModel.get('draggable')) { el.drift = function (dx, dy) { this.shape.x += dx; this.shape.y += dy; @@ -199,9 +237,45 @@ export default echarts.extendChartView({ el.draggable = true; el.cursor = 'move'; - }); - } - + + } + + if (itemModel.get('focusNodeAdjacency')) { + el.off('mouseover').on('mouseover', function () { + api.dispatchAction({ + type: 'focusNodeAdjacency', + seriesId: seriesModel.id, + dataIndex: el.dataIndex + }); + }); + el.off('mouseout').on('mouseout', function () { + api.dispatchAction({ + type: 'unfocusNodeAdjacency', + seriesId: seriesModel.id + }) + }); + } + }); + + edgeData.eachItemGraphicEl(function (el, dataIndex) { + var edgeModel = edgeData.getItemModel(dataIndex); + if (edgeModel.get('focusNodeAdjacency')) { + el.off('mouseover').on('mouseover', function () { + api.dispatchAction({ + type: 'focusNodeAdjacency', + seriesId: seriesModel.id, + edgeDataIndex: el.dataIndex + }); + }); + el.off('mouseout').on('mouseout', function () { + api.dispatchAction({ + type: 'unfocusNodeAdjacency', + seriesId: seriesModel.id, + }); + }) + } + }); + if (!this._data && seriesModel.get('animation')) { group.setClipPath(createGridClipShape(group.getBoundingRect(), seriesModel, function () { group.removeClipPath(); @@ -211,7 +285,77 @@ export default echarts.extendChartView({ this._data = seriesModel.getData(); }, - dispose: function () {} + dispose: function () {}, + + focusNodeAdjacency: function (seriesModel, ecModel, api, payload) { + var data = this._model.getData(); + var graph = data.graph; + var dataIndex = payload.dataIndex; + var itemModel = data.getItemModel(dataIndex); + var edgeDataIndex = payload.edgeDataIndex; + + if (dataIndex == null && edgeDataIndex == null) { + return; + } + var node = graph.getNodeByIndex(dataIndex); + var edge = graph.getEdgeByIndex(edgeDataIndex); + + graph.eachNode(function (node) { + fadeOutItem(node, nodeOpacityPath, 0.1); + }); + graph.eachEdge(function (edge) { + fadeOutItem(edge, lineOpacityPath, 0.1); + }); + + if (node) { + fadeInItem(node, nodeOpacityPath); + var focusNodeAdj = itemModel.get('focusNodeAdjacency'); + if (focusNodeAdj === 'outEdges') { + zrUtil.each(node.outEdges, function (edge) { + if (edge.dataIndex < 0) { + return; + } + fadeInItem(edge, lineOpacityPath); + fadeInItem(edge.node2, nodeOpacityPath); + }); + } + else if (focusNodeAdj === 'inEdges') { + zrUtil.each(node.inEdges, function (edge) { + if (edge.dataIndex < 0) { + return; + } + fadeInItem(edge, lineOpacityPath); + fadeInItem(edge.node1, nodeOpacityPath); + }); + } + else if (focusNodeAdj === 'allEdges') { + zrUtil.each(node.edges, function (edge) { + if (edge.dataIndex < 0) { + return; + } + fadeInItem(edge, lineOpacityPath); + fadeInItem(edge.node1, nodeOpacityPath); + fadeInItem(edge.node2, nodeOpacityPath); + }); + } + } + if (edge) { + fadeInItem(edge, lineOpacityPath); + fadeInItem(edge.node1, nodeOpacityPath); + fadeInItem(edge.node2, nodeOpacityPath); + } + }, + + unfocusNodeAdjacency: function (seriesModel, ecModel, api, payload) { + var graph = this._model.getGraph(); + + graph.eachNode(function (node) { + fadeOutItem(node, nodeOpacityPath); + }); + graph.eachEdge(function (edge) { + fadeOutItem(edge, lineOpacityPath); + }); + } }); // add animation to the view @@ -232,4 +376,4 @@ function createGridClipShape(rect, seriesModel, cb) { }, seriesModel, cb); return rectEl; -} \ No newline at end of file +} diff --git a/src/chart/sankey/sankeyAction.js b/src/chart/sankey/sankeyAction.js index 8683eecdb4c425e054c7dc48d2209442108b36f1..5a0ad64a3b1febf28aad1c9b5f0a188788e17cbf 100644 --- a/src/chart/sankey/sankeyAction.js +++ b/src/chart/sankey/sankeyAction.js @@ -17,7 +17,13 @@ * under the License. */ +/** + * @file The interactive action of sankey view + * @author Deqing Li(annong035@gmail.com) + */ + import * as echarts from '../../echarts'; +import '../helper/focusNodeAdjacencyAction'; echarts.registerAction({ type: 'dragNode', diff --git a/src/chart/themeRiver/themeRiverVisual.js b/src/chart/themeRiver/themeRiverVisual.js index 13da281a9cf260c4944d7f8e3f1d22c8dbd281db..78d8394463cb955e2550df9caa0e8b321b2bea90 100644 --- a/src/chart/themeRiver/themeRiverVisual.js +++ b/src/chart/themeRiver/themeRiverVisual.js @@ -19,7 +19,7 @@ /** * @file Visual encoding for themeRiver view - * @author Deqing Li(annong035@gmail.com) + * @author Deqing Li(annong035@gmail.com) */ import {createHashMap} from 'zrender/src/core/util'; diff --git a/src/chart/tree.js b/src/chart/tree.js index e498d259bf112afc8fedf2fc88ceac740a636310..01a7e26d65d39c4eb15458a4d915d2fc18dbb5e3 100644 --- a/src/chart/tree.js +++ b/src/chart/tree.js @@ -27,4 +27,4 @@ import visualSymbol from '../visual/symbol'; import treeLayout from './tree/treeLayout'; echarts.registerVisual(visualSymbol('tree', 'circle')); -echarts.registerLayout(treeLayout); +echarts.registerLayout(treeLayout); \ No newline at end of file diff --git a/src/chart/tree/TreeSeries.js b/src/chart/tree/TreeSeries.js index 635a7b0c1b65c11072ca24bd75cf32e86aa5ad97..e10b484dc5186db19d62707e0361afc93a9016ca 100644 --- a/src/chart/tree/TreeSeries.js +++ b/src/chart/tree/TreeSeries.js @@ -19,6 +19,7 @@ /** * @file Create data struct and define tree view's series model + * @author Deqing Li(annong035@gmail.com) */ import SeriesModel from '../../model/Series'; @@ -75,7 +76,7 @@ export default SeriesModel.extend({ return tree.data; }, - + /** * Make the configuration 'orient' backward compatibly, with 'horizontal = LR', 'vertical = TB'. * @returns {string} orient @@ -91,6 +92,14 @@ export default SeriesModel.extend({ return orient; }, + setZoom: function (zoom) { + this.option.zoom = zoom; + }, + + setCenter: function (center) { + this.option.center = center; + }, + /** * @override * @param {number} dataIndex @@ -123,6 +132,15 @@ export default SeriesModel.extend({ // the layout of the tree, two value can be selected, 'orthogonal' or 'radial' layout: 'orthogonal', + roam: false, + // Symbol size scale ratio in roam + nodeScaleRatio: 0.4, + + // Default on center of graph + center: null, + + zoom: 1, + // The orient of orthoginal layout, can be setted to 'LR', 'TB', 'RL', 'BT'. // and the backward compatibility configuration 'horizontal = LR', 'vertical = TB'. orient: 'LR', diff --git a/src/chart/tree/TreeView.js b/src/chart/tree/TreeView.js index 483e41bf580cb878cd61f6d86f677c2f0b86719f..62677234a79dd9a03edab4d93ae0217463c53f68 100644 --- a/src/chart/tree/TreeView.js +++ b/src/chart/tree/TreeView.js @@ -19,6 +19,7 @@ /** * @file This file used to draw tree view + * @author Deqing Li(annong035@gmail.com) */ import * as zrUtil from 'zrender/src/core/util'; @@ -26,6 +27,11 @@ import * as graphic from '../../util/graphic'; import SymbolClz from '../helper/Symbol'; import {radialCoordinate} from './layoutHelper'; import * as echarts from '../../echarts'; +import * as bbox from 'zrender/src/core/bbox'; +import View from '../../coord/View'; +import * as roamHelper from '../../component/helper/roamHelper'; +import RoamController from '../../component/helper/RoamController'; +import {onIrrelevantElement} from '../../component/helper/cursorHelper'; export default echarts.extendChartView({ @@ -52,6 +58,9 @@ export default echarts.extendChartView({ this._mainGroup = new graphic.Group(); this.group.add(this._mainGroup); + + this._controller = new RoamController(api.getZr()); + this._controllerHost = {target: this.group}; }, render: function (seriesModel, ecModel, api, payload) { @@ -71,6 +80,9 @@ export default echarts.extendChartView({ group.attr('position', [layoutInfo.x, layoutInfo.y]); } + this._updateViewCoordSys(seriesModel); + this._updateController(seriesModel, ecModel, api); + var oldData = this._data; var seriesScope = { @@ -114,6 +126,10 @@ export default echarts.extendChartView({ }) .execute(); + this._nodeScaleRatio = seriesModel.get('nodeScaleRatio'); + + this._updateNodeAndLinkScale(seriesModel); + if (seriesScope.expandAndCollapse === true) { data.eachItemGraphicEl(function (el, dataIndex) { el.off('click').on('click', function () { @@ -129,7 +145,114 @@ export default echarts.extendChartView({ this._data = data; }, - dispose: function () {}, + _updateViewCoordSys: function (seriesModel) { + var data = seriesModel.getData(); + var points = []; + data.each(function (idx) { + var layout = data.getItemLayout(idx); + if (layout && !isNaN(layout.x) && !isNaN(layout.y)) { + points.push([+layout.x, +layout.y]); + } + }); + var min = []; + var max = []; + bbox.fromPoints(points, min, max); + // If width or height is 0 + if (max[0] - min[0] === 0) { + max[0] += 1; + min[0] -= 1; + } + if (max[1] - min[1] === 0) { + max[1] += 1; + min[1] -= 1; + } + + var viewCoordSys = seriesModel.coordinateSystem = new View(); + viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); + + viewCoordSys.setBoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]); + + viewCoordSys.setCenter(seriesModel.get('center')); + viewCoordSys.setZoom(seriesModel.get('zoom')); + + this.group.attr({ + position: viewCoordSys.position, + scale: viewCoordSys.scale + }); + + this._viewCoordSys = viewCoordSys; + }, + + _updateController: function (seriesModel, ecModel, api) { + var controller = this._controller; + var controllerHost = this._controllerHost; + var group = this.group; + controller.setPointerChecker(function (e, x, y) { + var rect = group.getBoundingRect(); + rect.applyTransform(group.transform); + return rect.contain(x, y) + && !onIrrelevantElement(e, api, seriesModel); + }); + + controller.enable(seriesModel.get('roam')); + controllerHost.zoomLimit = seriesModel.get('scaleLimit'); + controllerHost.zoom = seriesModel.coordinateSystem.getZoom(); + + controller.off('pan').off('zoom') + .on('pan', function (e) { + roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'treeRoam', + dx: e.dx, + dy: e.dy + }); + }, this) + .on('zoom', function (e) { + roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'treeRoam', + zoom: e.scale, + originX: e.originX, + originY: e.originY + }); + this._updateNodeAndLinkScale(seriesModel); + }, this); + }, + + _updateNodeAndLinkScale: function (seriesModel) { + var data = seriesModel.getData(); + + var nodeScale = this._getNodeGlobalScale(seriesModel); + var invScale = [nodeScale, nodeScale]; + + data.eachItemGraphicEl(function (el, idx) { + el.attr('scale', invScale); + }); + }, + + _getNodeGlobalScale: function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys.type !== 'view') { + return 1; + } + + var nodeScaleRatio = this._nodeScaleRatio; + + var groupScale = coordSys.scale; + var groupZoom = (groupScale && groupScale[0]) || 1; + // Scale node when zoom changes + var roamZoom = coordSys.getZoom(); + var nodeScale = (roamZoom - 1) * nodeScaleRatio + 1; + + return nodeScale / groupZoom; + }, + + dispose: function () { + this._controller && this._controller.dispose(); + this._controllerHost = {}; + }, remove: function () { this._mainGroup.removeAll(); @@ -258,7 +381,7 @@ function updateNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope) if (!edge) { edge = symbolEl.__edge = new graphic.BezierCurve({ shape: getEdgeShape(seriesScope, sourceOldLayout, sourceOldLayout), - style: zrUtil.defaults({opacity: 0}, seriesScope.lineStyle) + style: zrUtil.defaults({opacity: 0, strokeNoScale: true}, seriesScope.lineStyle) }); } diff --git a/src/chart/tree/layoutHelper.js b/src/chart/tree/layoutHelper.js index a223953b795e29d78113f9bfe3859b1a3154df0a..1e06c185522379f2b6e82474db71dbf2b2ea150e 100644 --- a/src/chart/tree/layoutHelper.js +++ b/src/chart/tree/layoutHelper.js @@ -267,7 +267,7 @@ function nextAncestor(nodeInLeft, node, ancestor) { * @param {module:echarts/data/Tree~TreeNode} wr * @param {number} shift [description] */ -function moveSubtree(wl, wr,shift) { +function moveSubtree(wl, wr, shift) { var change = shift / (wr.hierNode.i - wl.hierNode.i); wr.hierNode.change -= change; wr.hierNode.shift += shift; diff --git a/src/chart/tree/treeAction.js b/src/chart/tree/treeAction.js index 27ee8c95978821271d43622adb4d1c1d868d52cd..186d6c4410671343574420ede9ca3bc0efa2a799 100644 --- a/src/chart/tree/treeAction.js +++ b/src/chart/tree/treeAction.js @@ -18,6 +18,7 @@ */ import * as echarts from '../../echarts'; +import {updateCenterAndZoom} from '../../action/roamHelper'; echarts.registerAction({ type: 'treeExpandAndCollapse', @@ -32,3 +33,21 @@ echarts.registerAction({ }); }); + +echarts.registerAction({ + type: 'treeRoam', + event: 'treeRoam', + update: 'none' +}, function (payload, ecModel) { + ecModel.eachComponent({mainType: 'series', query: payload}, function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + + var 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/treemap/TreemapView.js b/src/chart/treemap/TreemapView.js index 73d522964cb834504f2d3aa83fe732163976e8d6..bf4b2ce468c1b0e13f0ac49899a29d184827722f 100644 --- a/src/chart/treemap/TreemapView.js +++ b/src/chart/treemap/TreemapView.js @@ -431,9 +431,9 @@ export default echarts.extendChartView({ /** * @private */ - _onPan: function (dx, dy) { + _onPan: function (e) { if (this._state !== 'animating' - && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) + && (Math.abs(e.dx) > DRAG_THRESHOLD || Math.abs(e.dy) > DRAG_THRESHOLD) ) { // These param must not be cached. var root = this.seriesModel.getData().tree.root; @@ -453,7 +453,7 @@ export default echarts.extendChartView({ from: this.uid, seriesId: this.seriesModel.id, rootRect: { - x: rootLayout.x + dx, y: rootLayout.y + dy, + x: rootLayout.x + e.dx, y: rootLayout.y + e.dy, width: rootLayout.width, height: rootLayout.height } }); @@ -463,7 +463,10 @@ export default echarts.extendChartView({ /** * @private */ - _onZoom: function (scale, mouseX, mouseY) { + _onZoom: function (e) { + var mouseX = e.originX; + var mouseY = e.originY; + if (this._state !== 'animating') { // These param must not be cached. var root = this.seriesModel.getData().tree.root; @@ -490,7 +493,7 @@ export default echarts.extendChartView({ // Scale root bounding rect. var m = matrix.create(); matrix.translate(m, m, [-mouseX, -mouseY]); - matrix.scale(m, m, [scale, scale]); + matrix.scale(m, m, [e.scale, e.scale]); matrix.translate(m, m, [mouseX, mouseY]); rect.applyTransform(m); diff --git a/src/component/axisPointer/IAxisPointer b/src/component/axisPointer/IAxisPointer index 05f1c91ff24e76f85cc576e37beef27e19cd5b88..8fbd1f3fe53aa864d88db6d0244d620f3a680a75 100644 --- a/src/component/axisPointer/IAxisPointer +++ b/src/component/axisPointer/IAxisPointer @@ -1,3 +1,23 @@ +/* +* 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. +*/ + + /** * AxisPointer Interface: * diff --git a/src/component/dataZoom/InsideZoomModel.js b/src/component/dataZoom/InsideZoomModel.js index b13782b5a8fe8c4714cca241a65fed95601236a3..2022fd929988c032898cd90f97948d439cd2834f 100644 --- a/src/component/dataZoom/InsideZoomModel.js +++ b/src/component/dataZoom/InsideZoomModel.js @@ -31,6 +31,7 @@ export default DataZoomModel.extend({ zoomLock: false, // Whether disable zoom but only pan. zoomOnMouseWheel: true, // Can be: true / false / 'shift' / 'ctrl' / 'alt'. moveOnMouseMove: true, // Can be: true / false / 'shift' / 'ctrl' / 'alt'. + moveOnMouseWheel: false, // Can be: true / false / 'shift' / 'ctrl' / 'alt'. preventDefaultMouseMove: true } }); \ No newline at end of file diff --git a/src/component/dataZoom/InsideZoomView.js b/src/component/dataZoom/InsideZoomView.js index edd4e392524eada2bf8b72fd3975553197d445ec..48b98e30eb5de67203ee12ac6504f14955fc6934 100644 --- a/src/component/dataZoom/InsideZoomView.js +++ b/src/component/dataZoom/InsideZoomView.js @@ -61,7 +61,11 @@ var InsideZoomView = DataZoomView.extend({ zrUtil.each(coordInfoList, function (coordInfo) { var coordModel = coordInfo.model; - var dataZoomOption = dataZoomModel.option; + + var getRange = {}; + zrUtil.each(['pan', 'zoom', 'scrollMove'], function (eventName) { + getRange[eventName] = bind(roamHandlers[eventName], this, coordInfo, coordSysName); + }, this); roams.register( api, @@ -72,16 +76,8 @@ var InsideZoomView = DataZoomView.extend({ return coordModel.coordinateSystem.containPoint([x, y]); }, dataZoomId: dataZoomModel.id, - throttleRate: dataZoomModel.get('throttle', true), - panGetRange: bind(this._onPan, this, coordInfo, coordSysName), - zoomGetRange: bind(this._onZoom, this, coordInfo, coordSysName), - zoomLock: dataZoomOption.zoomLock, - disabled: dataZoomOption.disabled, - roamControllerOpt: { - zoomOnMouseWheel: dataZoomOption.zoomOnMouseWheel, - moveOnMouseMove: dataZoomOption.moveOnMouseMove, - preventDefaultMouseMove: dataZoomOption.preventDefaultMouseMove - } + dataZoomModel: dataZoomModel, + getRange: getRange } ); }, this); @@ -96,12 +92,16 @@ var InsideZoomView = DataZoomView.extend({ roams.unregister(this.api, this.dataZoomModel.id); InsideZoomView.superApply(this, 'dispose', arguments); this._range = null; - }, + } + +}); + +var roamHandlers = { /** - * @private + * @this {module:echarts/component/dataZoom/InsideZoomView} */ - _onPan: function (coordInfo, coordSysName, controller, dx, dy, oldX, oldY, newX, newY) { + zoom: function (coordInfo, coordSysName, controller, e) { var lastRange = this._range; var range = lastRange.slice(); @@ -112,14 +112,22 @@ var InsideZoomView = DataZoomView.extend({ } var directionInfo = getDirectionInfo[coordSysName]( - [oldX, oldY], [newX, newY], axisModel, controller, coordInfo + null, [e.originX, e.originY], axisModel, controller, coordInfo ); + var percentPoint = ( + directionInfo.signal > 0 + ? (directionInfo.pixelStart + directionInfo.pixelLength - directionInfo.pixel) + : (directionInfo.pixel - directionInfo.pixelStart) + ) / directionInfo.pixelLength * (range[1] - range[0]) + range[0]; - var percentDelta = directionInfo.signal - * (range[1] - range[0]) - * directionInfo.pixel / directionInfo.pixelLength; + var scale = Math.max(1 / e.scale, 0); + range[0] = (range[0] - percentPoint) * scale + percentPoint; + range[1] = (range[1] - percentPoint) * scale + percentPoint; - sliderMove(percentDelta, range, [0, 100], 'all'); + // Restrict range. + var minMaxSpan = this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan(); + + sliderMove(0, range, [0, 100], 0, minMaxSpan.minSpan, minMaxSpan.maxSpan); this._range = range; @@ -129,9 +137,28 @@ var InsideZoomView = DataZoomView.extend({ }, /** - * @private + * @this {module:echarts/component/dataZoom/InsideZoomView} */ - _onZoom: function (coordInfo, coordSysName, controller, scale, mouseX, mouseY) { + pan: makeMover(function (range, axisModel, coordInfo, coordSysName, controller, e) { + var directionInfo = getDirectionInfo[coordSysName]( + [e.oldX, e.oldY], [e.newX, e.newY], axisModel, controller, coordInfo + ); + + return directionInfo.signal + * (range[1] - range[0]) + * directionInfo.pixel / directionInfo.pixelLength; + }), + + /** + * @this {module:echarts/component/dataZoom/InsideZoomView} + */ + scrollMove: makeMover(function (range, axisModel, coordInfo, coordSysName, controller, e) { + return (range[1] - range[0]) * e.scrollDelta; + }) +}; + +function makeMover(getPercentDelta) { + return function (coordInfo, coordSysName, controller, e) { var lastRange = this._range; var range = lastRange.slice(); @@ -141,32 +168,19 @@ var InsideZoomView = DataZoomView.extend({ return; } - var directionInfo = getDirectionInfo[coordSysName]( - null, [mouseX, mouseY], axisModel, controller, coordInfo + var percentDelta = getPercentDelta( + range, axisModel, coordInfo, coordSysName, controller, e ); - var percentPoint = ( - directionInfo.signal > 0 - ? (directionInfo.pixelStart + directionInfo.pixelLength - directionInfo.pixel) - : (directionInfo.pixel - directionInfo.pixelStart) - ) / directionInfo.pixelLength * (range[1] - range[0]) + range[0]; - scale = Math.max(1 / scale, 0); - range[0] = (range[0] - percentPoint) * scale + percentPoint; - range[1] = (range[1] - percentPoint) * scale + percentPoint; - - // Restrict range. - var minMaxSpan = this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan(); - - sliderMove(0, range, [0, 100], 0, minMaxSpan.minSpan, minMaxSpan.maxSpan); + sliderMove(percentDelta, range, [0, 100], 'all'); this._range = range; if (lastRange[0] !== range[0] || lastRange[1] !== range[1]) { return range; } - } - -}); + }; +} var getDirectionInfo = { diff --git a/src/component/dataZoom/roams.js b/src/component/dataZoom/roams.js index b2c962c1ee3db9c46c4664c827cabe256fe526ec..0d7d80a06404f0b5ebe5588ddaa61fe1370aaad4 100644 --- a/src/component/dataZoom/roams.js +++ b/src/component/dataZoom/roams.js @@ -27,7 +27,6 @@ import * as zrUtil from 'zrender/src/core/util'; import RoamController from '../../component/helper/RoamController'; import * as throttleUtil from '../../util/throttle'; -var curry = zrUtil.curry; var ATTR = '\0_ec_dataZoom_roams'; @@ -40,11 +39,11 @@ var ATTR = '\0_ec_dataZoom_roams'; * @param {Function} dataZoomInfo.containsPoint * @param {Array.} dataZoomInfo.allCoordIds * @param {string} dataZoomInfo.dataZoomId - * @param {number} dataZoomInfo.throttleRate - * @param {Function} dataZoomInfo.panGetRange - * @param {Function} dataZoomInfo.zoomGetRange - * @param {boolean} [dataZoomInfo.zoomLock] - * @param {boolean} [dataZoomInfo.disabled] + * @param {Object} dataZoomInfo.getRange + * @param {Function} dataZoomInfo.getRange.pan + * @param {Function} dataZoomInfo.getRange.zoom + * @param {Function} dataZoomInfo.getRange.scrollMove + * @param {boolean} dataZoomInfo.dataZoomModel */ export function register(api, dataZoomInfo) { var store = giveStore(api); @@ -91,7 +90,7 @@ export function register(api, dataZoomInfo) { throttleUtil.createOrUpdate( record, 'dispatchAction', - dataZoomInfo.throttleRate, + dataZoomInfo.dataZoomModel.get('throttle', true), 'fixRate' ); } @@ -136,8 +135,29 @@ function giveStore(api) { function createController(api, newRecord) { var controller = new RoamController(api.getZr()); - controller.on('pan', curry(onPan, newRecord)); - controller.on('zoom', curry(onZoom, newRecord)); + + zrUtil.each(['pan', 'zoom', 'scrollMove'], function (eventName) { + controller.on(eventName, function (event) { + var batch = []; + + zrUtil.each(newRecord.dataZoomInfos, function (info) { + if (!event.isAvailableBehavior(info.dataZoomModel.option)) { + return; + } + + var method = (info.getRange || {})[eventName]; + var range = method && method(newRecord.controller, event); + + !info.dataZoomModel.get('disabled', true) && range && batch.push({ + dataZoomId: info.dataZoomId, + start: range[0], + end: range[1] + }); + }); + + batch.length && newRecord.dispatchAction(batch); + }); + }); return controller; } @@ -151,33 +171,6 @@ function cleanStore(store) { }); } -function onPan(record, dx, dy, oldX, oldY, newX, newY) { - wrapAndDispatch(record, function (info) { - return info.panGetRange(record.controller, dx, dy, oldX, oldY, newX, newY); - }); -} - -function onZoom(record, scale, mouseX, mouseY) { - wrapAndDispatch(record, function (info) { - return info.zoomGetRange(record.controller, scale, mouseX, mouseY); - }); -} - -function wrapAndDispatch(record, getRange) { - var batch = []; - - zrUtil.each(record.dataZoomInfos, function (info) { - var range = getRange(info); - !info.disabled && range && batch.push({ - dataZoomId: info.dataZoomId, - start: range[0], - end: range[1] - }); - }); - - batch.length && record.dispatchAction(batch); -} - /** * This action will be throttled. */ @@ -193,7 +186,6 @@ function dispatchAction(api, batch) { */ function mergeControllerParams(dataZoomInfos) { var controlType; - var opt = {}; // DO NOT use reserved word (true, false, undefined) as key literally. Even if encapsulated // as string, it is probably revert to reserved word by compress tool. See #7411. var prefix = 'type_'; @@ -203,17 +195,30 @@ function mergeControllerParams(dataZoomInfos) { 'type_false': 0, 'type_undefined': -1 }; + var preventDefaultMouseMove = true; + zrUtil.each(dataZoomInfos, function (dataZoomInfo) { - var oneType = dataZoomInfo.disabled ? false : dataZoomInfo.zoomLock ? 'move' : true; + var dataZoomModel = dataZoomInfo.dataZoomModel; + var oneType = dataZoomModel.get('disabled', true) + ? false + : dataZoomModel.get('zoomLock', true) + ? 'move' + : true; if (typePriority[prefix + oneType] > typePriority[prefix + controlType]) { controlType = oneType; } - // Do not support that different 'shift'/'ctrl'/'alt' setting used in one coord sys. - zrUtil.extend(opt, dataZoomInfo.roamControllerOpt); + // Prevent default move event by default. If one false, do not prevent. Otherwise + // users may be confused why it does not work when multiple insideZooms exist. + preventDefaultMouseMove &= dataZoomModel.get('preventDefaultMouseMove', true); }); return { controlType: controlType, - opt: opt + opt: { + zoomOnMouseWheel: true, + moveOnMouseMove: true, + moveOnMouseWheel: true, + preventDefaultMouseMove: !!preventDefaultMouseMove + } }; } diff --git a/src/component/helper/MapDraw.js b/src/component/helper/MapDraw.js index 0d00e2d19bce1c37a9f0a5bb332504ed6ddb34be..7f74047cfb1f7fd31f1c45e629c1fa8eb3d3d0d7 100644 --- a/src/component/helper/MapDraw.js +++ b/src/component/helper/MapDraw.js @@ -22,6 +22,8 @@ import RoamController from './RoamController'; import * as roamHelper from '../../component/helper/roamHelper'; import {onIrrelevantElement} from '../../component/helper/cursorHelper'; import * as graphic from '../../util/graphic'; +import geoSourceManager from '../../coord/geo/geoSourceManager'; +import {getUID} from '../../util/component'; function getFixedItemStyle(model, scale) { var itemStyle = model.getItemStyle(); @@ -36,17 +38,17 @@ function getFixedItemStyle(model, scale) { return itemStyle; } -function updateMapSelectHandler(mapDraw, mapOrGeoModel, group, api, fromView) { - group.off('click'); - group.off('mousedown'); +function updateMapSelectHandler(mapDraw, mapOrGeoModel, regionsGroup, api, fromView) { + regionsGroup.off('click'); + regionsGroup.off('mousedown'); if (mapOrGeoModel.get('selectedMode')) { - group.on('mousedown', function () { + regionsGroup.on('mousedown', function () { mapDraw._mouseDownFlag = true; }); - group.on('click', function (e) { + regionsGroup.on('click', function (e) { if (!mapDraw._mouseDownFlag) { return; } @@ -73,14 +75,14 @@ function updateMapSelectHandler(mapDraw, mapOrGeoModel, group, api, fromView) { api.dispatchAction(action); - updateMapSelected(mapOrGeoModel, group); + updateMapSelected(mapOrGeoModel, regionsGroup); }); } } -function updateMapSelected(mapOrGeoModel, group) { +function updateMapSelected(mapOrGeoModel, regionsGroup) { // FIXME - group.eachChild(function (otherRegionEl) { + regionsGroup.eachChild(function (otherRegionEl) { zrUtil.each(otherRegionEl.__regions, function (region) { otherRegionEl.trigger(mapOrGeoModel.isSelected(region.name) ? 'emphasis' : 'normal'); }); @@ -96,6 +98,12 @@ function MapDraw(api, updateGroup) { var group = new graphic.Group(); + /** + * @type {string} + * @private + */ + this.uid = getUID('ec_map_draw'); + /** * @type {module:echarts/component/helper/RoamController} * @private @@ -127,6 +135,26 @@ function MapDraw(api, updateGroup) { * @type {booelan} */ this._mouseDownFlag; + + /** + * @type {string} + */ + this._mapName; + + /** + * @type {boolean} + */ + this._initialized; + + /** + * @type {module:zrender/container/Group} + */ + group.add(this._regionsGroup = new graphic.Group()); + + /** + * @type {module:zrender/container/Group} + */ + group.add(this._backgroundGroup = new graphic.Group()); } MapDraw.prototype = { @@ -148,23 +176,26 @@ MapDraw.prototype = { var geo = mapOrGeoModel.coordinateSystem; + this._updateBackground(geo); + + var regionsGroup = this._regionsGroup; var group = this.group; var scale = geo.scale; - var groupNewProp = { + var transform = { position: geo.position, scale: scale }; // No animation when first draw or in action - if (!group.childAt(0) || payload) { - group.attr(groupNewProp); + if (!regionsGroup.childAt(0) || payload) { + group.attr(transform); } else { - graphic.updateProps(group, groupNewProp, mapOrGeoModel); + graphic.updateProps(group, transform, mapOrGeoModel); } - group.removeAll(); + regionsGroup.removeAll(); var itemStyleAccessPath = ['itemStyle']; var hoverItemStyleAccessPath = ['emphasis', 'itemStyle']; @@ -306,22 +337,37 @@ MapDraw.prototype = { {hoverSilentOnTouch: !!mapOrGeoModel.get('selectedMode')} ); - group.add(regionGroup); + regionsGroup.add(regionGroup); }); this._updateController(mapOrGeoModel, ecModel, api); - updateMapSelectHandler(this, mapOrGeoModel, group, api, fromView); + updateMapSelectHandler(this, mapOrGeoModel, regionsGroup, api, fromView); - updateMapSelected(mapOrGeoModel, group); + updateMapSelected(mapOrGeoModel, regionsGroup); }, remove: function () { - this.group.removeAll(); + this._regionsGroup.removeAll(); + this._backgroundGroup.removeAll(); this._controller.dispose(); + this._mapName && geoSourceManager.removeGraphic(this._mapName, this.uid); + this._mapName = null; this._controllerHost = {}; }, + _updateBackground: function (geo) { + var mapName = geo.map; + + if (this._mapName !== mapName) { + zrUtil.each(geoSourceManager.makeGraphic(mapName, this.uid), function (root) { + this._backgroundGroup.add(root); + }, this); + } + + this._mapName = mapName; + }, + _updateController: function (mapOrGeoModel, ecModel, api) { var geo = mapOrGeoModel.coordinateSystem; var controller = this._controller; @@ -343,32 +389,31 @@ MapDraw.prototype = { return action; } - controller.off('pan').on('pan', function (dx, dy) { + controller.off('pan').on('pan', function (e) { this._mouseDownFlag = false; - roamHelper.updateViewOnPan(controllerHost, dx, dy); + roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy); api.dispatchAction(zrUtil.extend(makeActionBase(), { - dx: dx, - dy: dy + dx: e.dx, + dy: e.dy })); }, this); - controller.off('zoom').on('zoom', function (zoom, mouseX, mouseY) { + controller.off('zoom').on('zoom', function (e) { this._mouseDownFlag = false; - roamHelper.updateViewOnZoom(controllerHost, zoom, mouseX, mouseY); + roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); api.dispatchAction(zrUtil.extend(makeActionBase(), { - zoom: zoom, - originX: mouseX, - originY: mouseY + zoom: e.scale, + originX: e.originX, + originY: e.originY })); if (this._updateGroup) { - var group = this.group; - var scale = group.scale; - group.traverse(function (el) { + var scale = this.group.scale; + this._regionsGroup.traverse(function (el) { if (el.type === 'text') { el.attr('scale', [1 / scale[0], 1 / scale[1]]); } diff --git a/src/component/helper/RoamController.js b/src/component/helper/RoamController.js index 09d9d8979d15ba438247068ba4e6e78ef973a839..f2bcef71ec2b9fd820c2e9590f6ff03de6af41e7 100644 --- a/src/component/helper/RoamController.js +++ b/src/component/helper/RoamController.js @@ -74,8 +74,9 @@ function RoamController(zr) { * which can be null/undefined or true/false * or 'pan/move' or 'zoom'/'scale' * @param {Object} [opt] - * @param {Object} [opt.zoomOnMouseWheel=true] - * @param {Object} [opt.moveOnMouseMove=true] + * @param {Object} [opt.zoomOnMouseWheel=true] The value can be: true / false / 'shift' / 'ctrl' / 'alt'. + * @param {Object} [opt.moveOnMouseMove=true] The value can be: true / false / 'shift' / 'ctrl' / 'alt'. + * @param {Object} [opt.moveOnMouseWheel=false] The value can be: true / false / 'shift' / 'ctrl' / 'alt'. * @param {Object} [opt.preventDefaultMouseMove=true] When pan. */ this.enable = function (controlType, opt) { @@ -86,6 +87,8 @@ function RoamController(zr) { this._opt = zrUtil.defaults(zrUtil.clone(opt) || {}, { zoomOnMouseWheel: true, moveOnMouseMove: true, + // By default, wheel do not trigger move. + moveOnMouseWheel: false, preventDefaultMouseMove: true }); @@ -147,7 +150,7 @@ function mousedown(e) { function mousemove(e) { if (eventTool.notLeftMouse(e) - || !checkKeyBinding(this, 'moveOnMouseMove', e) + || !isAvailableBehavior('moveOnMouseMove', e, this._opt) || !this._dragging || e.gestureEvent === 'pinch' || interactionMutex.isTaken(this._zr, 'globalPan') @@ -169,7 +172,9 @@ function mousemove(e) { this._opt.preventDefaultMouseMove && eventTool.stop(e.event); - this.trigger('pan', dx, dy, oldX, oldY, x, y); + trigger(this, 'pan', 'moveOnMouseMove', e, { + dx: dx, dy: dy, oldX: oldX, oldY: oldY, newX: x, newY: y + }); } function mouseup(e) { @@ -179,41 +184,87 @@ function mouseup(e) { } function mousewheel(e) { + var shouldZoom = isAvailableBehavior('zoomOnMouseWheel', e, this._opt); + var shouldMove = isAvailableBehavior('moveOnMouseWheel', e, this._opt); + var wheelDelta = e.wheelDelta; + var absWheelDeltaDelta = Math.abs(wheelDelta); + var originX = e.offsetX; + var originY = e.offsetY; + // wheelDelta maybe -0 in chrome mac. - if (!checkKeyBinding(this, 'zoomOnMouseWheel', e) || e.wheelDelta === 0) { + if (wheelDelta === 0 || (!shouldZoom && !shouldMove)) { return; } - - // Convenience: - // Mac and VM Windows on Mac: scroll up: zoom out. - // Windows: scroll up: zoom in. - var zoomDelta = e.wheelDelta > 0 ? 1.1 : 1 / 1.1; - zoom.call(this, e, zoomDelta, e.offsetX, e.offsetY); + // console.log(wheelDelta); + if (shouldZoom) { + // Convenience: + // Mac and VM Windows on Mac: scroll up: zoom out. + // Windows: scroll up: zoom in. + + // FIXME: Should do more test in different environment. + // wheelDelta is too complicated in difference nvironment + // (https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel), + // although it has been normallized by zrender. + // wheelDelta of mouse wheel is bigger than touch pad. + var factor = absWheelDeltaDelta > 3 ? 1.4 : absWheelDeltaDelta > 1 ? 1.2 : 1.1; + var scale = wheelDelta > 0 ? factor : 1 / factor; + checkPointerAndTrigger(this, 'zoom', 'zoomOnMouseWheel', e, { + scale: scale, originX: originX, originY: originY + }); + } + // console.log(shouldMove); + if (shouldMove) { + // FIXME: Should do more test in different environment. + var absDelta = Math.abs(wheelDelta); + // wheelDelta of mouse wheel is bigger than touch pad. + var scrollDelta = (wheelDelta > 0 ? 1 : -1) * (absDelta > 3 ? 0.4 : absDelta > 1 ? 0.15 : 0.05); + checkPointerAndTrigger(this, 'scrollMove', 'moveOnMouseWheel', e, { + scrollDelta: scrollDelta, originX: originX, originY: originY + }); + } } function pinch(e) { if (interactionMutex.isTaken(this._zr, 'globalPan')) { return; } - var zoomDelta = e.pinchScale > 1 ? 1.1 : 1 / 1.1; - zoom.call(this, e, zoomDelta, e.pinchX, e.pinchY); + var scale = e.pinchScale > 1 ? 1.1 : 1 / 1.1; + checkPointerAndTrigger(this, 'zoom', null, e, { + scale: scale, originX: e.pinchX, originY: e.pinchY + }); } -function zoom(e, zoomDelta, zoomX, zoomY) { - if (this.pointerChecker && this.pointerChecker(e, zoomX, zoomY)) { +function checkPointerAndTrigger(controller, eventName, behaviorToCheck, e, contollerEvent) { + if (controller.pointerChecker + && controller.pointerChecker(e, contollerEvent.originX, contollerEvent.originY) + ) { // When mouse is out of roamController rect, // default befavoius should not be be disabled, otherwise // page sliding is disabled, contrary to expectation. eventTool.stop(e.event); - this.trigger('zoom', zoomDelta, zoomX, zoomY); + trigger(controller, eventName, behaviorToCheck, e, contollerEvent); } } -function checkKeyBinding(roamController, prop, e) { - var setting = roamController._opt[prop]; - return setting - && (!zrUtil.isString(setting) || e.event[setting + 'Key']); +function trigger(controller, eventName, behaviorToCheck, e, contollerEvent) { + // Also provide behavior checker for event listener, for some case that + // multiple components share one listener. + contollerEvent.isAvailableBehavior = zrUtil.bind(isAvailableBehavior, null, behaviorToCheck, e); + controller.trigger(eventName, contollerEvent); +} + +// settings: { +// zoomOnMouseWheel +// moveOnMouseMove +// moveOnMouseWheel +// } +// The value can be: true / false / 'shift' / 'ctrl' / 'alt'. +function isAvailableBehavior(behaviorToCheck, e, settings) { + var setting = settings[behaviorToCheck]; + return !behaviorToCheck || ( + setting && (!zrUtil.isString(setting) || e.event[setting + 'Key']) + ); } export default RoamController; \ No newline at end of file diff --git a/src/component/tooltip/TooltipContent.js b/src/component/tooltip/TooltipContent.js index ac0959b5659e5cdb4c26f0057fcc79b9615ddcd8..e9594df5594b33dfff7c2443fed0357194fa76fa 100644 --- a/src/component/tooltip/TooltipContent.js +++ b/src/component/tooltip/TooltipContent.js @@ -217,6 +217,13 @@ TooltipContent.prototype = { el.style.display = el.innerHTML ? 'block' : 'none'; + // If mouse occsionally move over the tooltip, a mouseout event will be + // triggered by canvas, and cuase some unexpectable result like dragging + // stop, "unfocusAdjacency". Here `pointer-events: none` is used to solve + // it. Although it is not suppored by IE8~IE10, fortunately it is a rare + // scenario. + el.style.pointerEvents = this._enterable ? 'auto' : 'none'; + this._show = true; }, diff --git a/src/coord/ICoordinateSystem b/src/coord/ICoordinateSystem index 79124aebcfc4fe68509bd837e860ad02d1c3929c..592c85d1f4dbb5296ba075a31954799ca4567040 100644 --- a/src/coord/ICoordinateSystem +++ b/src/coord/ICoordinateSystem @@ -1,3 +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. +*/ + /** * Coordinate System Interface: * diff --git a/src/coord/axisHelper.js b/src/coord/axisHelper.js index 2d07bdfab83210282da4bc7bf06797b940030d88..34762bf0059ca598c2211a42b34afe7074548617 100644 --- a/src/coord/axisHelper.js +++ b/src/coord/axisHelper.js @@ -290,6 +290,9 @@ export function makeLabelFormatter(axis) { if (typeof labelFormatter === 'string') { labelFormatter = (function (tpl) { return function (val) { + // For category axis, get raw value; for numeric axis, + // get foramtted label like '1,333,444'. + val = axis.scale.getLabel(val); return tpl.replace('{value}', val != null ? val : ''); }; })(labelFormatter); diff --git a/src/coord/geo/Geo.js b/src/coord/geo/Geo.js index 83428afb5e18e34dbbb1aae47a21479e473e9d0a..8c6491cafa7aafe4ae8da034336dd6cc59b33a2b 100644 --- a/src/coord/geo/Geo.js +++ b/src/coord/geo/Geo.js @@ -19,33 +19,22 @@ import * as zrUtil from 'zrender/src/core/util'; import BoundingRect from 'zrender/src/core/BoundingRect'; -import parseGeoJson from './parseGeoJson'; import View from '../View'; +import geoSourceManager from './geoSourceManager'; -import fixNanhai from './fix/nanhai'; -import fixTextCoord from './fix/textCoord'; -import fixGeoCoord from './fix/geoCoord'; -import fixDiaoyuIsland from './fix/diaoyuIsland'; - -// Geo fix functions -var geoFixFuncs = [ - fixNanhai, - fixTextCoord, - fixGeoCoord, - fixDiaoyuIsland -]; /** * [Geo description] - * @param {string} name Geo name + * For backward compatibility, the orginal interface: + * `name, map, geoJson, specialAreas, nameMap` is kept. + * + * @param {string|Object} name * @param {string} map Map type - * @param {Object} geoJson - * @param {Object} [specialAreas] * Specify the positioned areas by left, top, width, height * @param {Object.} [nameMap] * Specify name alias */ -function Geo(name, map, geoJson, specialAreas, nameMap) { +function Geo(name, map, nameMap) { View.call(this, name); @@ -55,9 +44,20 @@ function Geo(name, map, geoJson, specialAreas, nameMap) { */ this.map = map; - this._nameCoordMap = zrUtil.createHashMap(); + var source = geoSourceManager.load(map, nameMap); + + this._nameCoordMap = source.nameCoordMap; + this._regionsMap = source.nameCoordMap; + + /** + * @readOnly + */ + this.regions = source.regions; - this.loadGeoJson(geoJson, specialAreas, nameMap); + /** + * @type {module:zrender/src/core/BoundingRect} + */ + this._rect = source.boundingRect; } Geo.prototype = { @@ -86,61 +86,23 @@ Geo.prototype = { } return false; }, + /** - * @param {Object} geoJson - * @param {Object} [specialAreas] - * Specify the positioned areas by left, top, width, height - * @param {Object.} [nameMap] - * Specify name alias + * @override */ - loadGeoJson: function (geoJson, specialAreas, nameMap) { - // https://jsperf.com/try-catch-performance-overhead - try { - this.regions = geoJson ? parseGeoJson(geoJson) : []; - } - catch (e) { - throw 'Invalid geoJson format\n' + e.message; - } - specialAreas = specialAreas || {}; - nameMap = nameMap || {}; - var regions = this.regions; - var regionsMap = zrUtil.createHashMap(); - for (var i = 0; i < regions.length; i++) { - var regionName = regions[i].name; - // Try use the alias in nameMap - regionName = nameMap.hasOwnProperty(regionName) ? nameMap[regionName] : regionName; - regions[i].name = regionName; - - regionsMap.set(regionName, regions[i]); - // Add geoJson - this.addGeoCoord(regionName, regions[i].center); - - // Some area like Alaska in USA map needs to be tansformed - // to look better - var specialArea = specialAreas[regionName]; - if (specialArea) { - regions[i].transformTo( - specialArea.left, specialArea.top, specialArea.width, specialArea.height - ); - } - } - - this._regionsMap = regionsMap; - - this._rect = null; - - zrUtil.each(geoFixFuncs, function (fixFunc) { - fixFunc(this); - }, this); - }, - - // Overwrite transformTo: function (x, y, width, height) { var rect = this.getBoundingRect(); + // FIXME + // Should not name it as invertLng. + var invertLng = this.invertLng; + rect = rect.clone(); - // Longitute is inverted - rect.y = -rect.y - rect.height; + + if (invertLng) { + // Longitute is inverted + rect.y = -rect.y - rect.height; + } var rawTransformable = this._rawTransformable; @@ -150,8 +112,10 @@ Geo.prototype = { rawTransformable.decomposeTransform(); - var scale = rawTransformable.scale; - scale[1] = -scale[1]; + if (invertLng) { + var scale = rawTransformable.scale; + scale[1] = -scale[1]; + } rawTransformable.updateTransform(); @@ -193,21 +157,11 @@ Geo.prototype = { return this._nameCoordMap.get(name); }, - // Overwrite + /** + * @override + */ getBoundingRect: function () { - if (this._rect) { - return this._rect; - } - var rect; - - var regions = this.regions; - for (var i = 0; i < regions.length; i++) { - var regionRect = regions[i].getBoundingRect(); - rect = rect || regionRect.clone(); - rect.union(regionRect); - } - // FIXME Always return new ? - return (this._rect = rect || new BoundingRect(0, 0, 0, 0)); + return this._rect; }, /** @@ -227,12 +181,12 @@ Geo.prototype = { }, /** - * @inheritDoc + * @override */ convertToPixel: zrUtil.curry(doConvert, 'dataToPoint'), /** - * @inheritDoc + * @override */ convertFromPixel: zrUtil.curry(doConvert, 'pointToData') diff --git a/src/coord/geo/GeoModel.js b/src/coord/geo/GeoModel.js index 63f92579c17314cd8a37fe97044972ec9d8d895a..fad997706cc2c1d3159364372d536b4072012578 100644 --- a/src/coord/geo/GeoModel.js +++ b/src/coord/geo/GeoModel.js @@ -78,7 +78,9 @@ var GeoModel = ComponentModel.extend({ // Aspect is width / height. Inited to be geoJson bbox aspect // This parameter is used for scale this aspect - aspectScale: 0.75, + // If svg used, aspectScale is 1 by default. + // aspectScale: 0.75, + aspectScale: null, ///// Layout with center and size // If you wan't to put map in a fixed size box with right aspect ratio @@ -86,7 +88,6 @@ var GeoModel = ComponentModel.extend({ // layoutCenter: [50%, 50%] // layoutSize: 100 - silent: false, // Map type diff --git a/src/coord/geo/Region.js b/src/coord/geo/Region.js index 43d9cd69750fc7d89eb752d03e8758891aba8908..4de33b145fce47c48315b1319cff5b5312c28aa1 100644 --- a/src/coord/geo/Region.js +++ b/src/coord/geo/Region.js @@ -27,7 +27,7 @@ import * as vec2 from 'zrender/src/core/vector'; import * as polygonContain from 'zrender/src/contain/polygon'; /** - * @param {string} name + * @param {string|Region} name * @param {Array} geometries * @param {Array.} cp */ @@ -168,6 +168,14 @@ Region.prototype = { rect.x + rect.width / 2, rect.y + rect.height / 2 ]; + }, + + cloneShallow: function (name) { + name == null && (name = this.name); + var newRegion = new Region(name, this.geometries, this.center); + newRegion._rect = this._rect; + newRegion.transformTo = null; // Simply avoid to be called. + return newRegion; } }; diff --git a/src/coord/geo/fix/diaoyuIsland.js b/src/coord/geo/fix/diaoyuIsland.js index 22fcd7778a25434da765cd9b4572ab7c86f109b8..87b3f572e3196b8e13e73318774ae85340532919 100644 --- a/src/coord/geo/fix/diaoyuIsland.js +++ b/src/coord/geo/fix/diaoyuIsland.js @@ -34,15 +34,11 @@ var points = [ ] ]; -export default function (geo) { - if (geo.map === 'china') { - for (var i = 0, len = geo.regions.length; i < len; ++i) { - if (geo.regions[i].name === '台湾') { - geo.regions[i].geometries.push({ - type: 'polygon', - exterior: points[0] - }); - } - } +export default function (mapType, region) { + if (mapType === 'china' && region.name === '台湾') { + region.geometries.push({ + type: 'polygon', + exterior: points[0] + }); } } \ No newline at end of file diff --git a/src/coord/geo/fix/geoCoord.js b/src/coord/geo/fix/geoCoord.js index 69cc11a79d82c6bfa3a09bd18446b201799bcbbb..b6e7080a314e7c2adffff2e36c4842e96a97d1ca 100644 --- a/src/coord/geo/fix/geoCoord.js +++ b/src/coord/geo/fix/geoCoord.js @@ -17,21 +17,19 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; - var geoCoordMap = { 'Russia': [100, 60], 'United States': [-99, 38], 'United States of America': [-99, 38] }; -export default function (geo) { - zrUtil.each(geo.regions, function (region) { +export default function (mapType, region) { + if (mapType === 'world') { var geoCoord = geoCoordMap[region.name]; if (geoCoord) { var cp = region.center; cp[0] = geoCoord[0]; cp[1] = geoCoord[1]; } - }); + } } \ No newline at end of file diff --git a/src/coord/geo/fix/nanhai.js b/src/coord/geo/fix/nanhai.js index f59c529011304d22b47908e5f9f0912eb2148f6f..4eabeaf8d6a606040aff96a8e739122682d49b8f 100644 --- a/src/coord/geo/fix/nanhai.js +++ b/src/coord/geo/fix/nanhai.js @@ -51,9 +51,9 @@ for (var i = 0; i < points.length; i++) { } } -export default function (geo) { - if (geo.map === 'china') { - geo.regions.push(new Region( +export default function (mapType, regions) { + if (mapType === 'china') { + regions.push(new Region( '南海诸岛', zrUtil.map(points, function (exterior) { return { diff --git a/src/coord/geo/fix/textCoord.js b/src/coord/geo/fix/textCoord.js index c2b858a02082a76dec8ba918ac2620a1e6aa416c..3ec837c3b296ee55492718d3af503ad9c506bed2 100644 --- a/src/coord/geo/fix/textCoord.js +++ b/src/coord/geo/fix/textCoord.js @@ -17,8 +17,6 @@ * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; - var coordsOffsetMap = { '南海诸岛' : [32, 80], // 全国 @@ -29,13 +27,13 @@ var coordsOffsetMap = { '天津': [5, 5] }; -export default function (geo) { - zrUtil.each(geo.regions, function (region) { +export default function (mapType, region) { + if (mapType === 'china') { var coordFix = coordsOffsetMap[region.name]; if (coordFix) { var cp = region.center; cp[0] += coordFix[0] / 10.5; cp[1] += -coordFix[1] / (10.5 / 0.75); } - }); + } } \ No newline at end of file diff --git a/src/coord/geo/geoCreator.js b/src/coord/geo/geoCreator.js index da94330fb4cc156ca087a196eb9012bfd9dac33d..e740b65e67fdcdb75bf4efbdcbb520b2eb359665 100644 --- a/src/coord/geo/geoCreator.js +++ b/src/coord/geo/geoCreator.js @@ -23,6 +23,8 @@ import * as zrUtil from 'zrender/src/core/util'; import Geo from './Geo'; import * as layout from '../../util/layout'; import * as numberUtil from '../../util/number'; +import geoSourceManager from './geoSourceManager'; +import mapDataStorage from './mapDataStorage'; /** * Resize method bound to the geo @@ -55,8 +57,7 @@ function resizeGeo(geoModel, api) { var viewWidth = api.getWidth(); var viewHeight = api.getHeight(); - var aspectScale = geoModel.get('aspectScale') || 0.75; - var aspect = rect.width / rect.height * aspectScale; + var aspect = rect.width / rect.height * this.aspectScale; var useCenterAndSize = false; @@ -122,12 +123,6 @@ function setGeoCoords(geo, model) { }); } -if (__DEV__) { - var mapNotExistsError = function (name) { - console.error('Map ' + name + ' not exists. You can download map file on http://echarts.baidu.com/download-map.html'); - }; -} - var geoCreator = { // For deciding which dimensions to use when creating list data @@ -139,17 +134,8 @@ var geoCreator = { // FIXME Create each time may be slow ecModel.eachComponent('geo', function (geoModel, idx) { var name = geoModel.get('map'); - var mapData = echarts.getMap(name); - if (__DEV__) { - if (!mapData) { - mapNotExistsError(name); - } - } - var geo = new Geo( - name + idx, name, - mapData && mapData.geoJson, mapData && mapData.specialAreas, - geoModel.get('nameMap') - ); + var geo = new Geo(name + idx, name, geoModel.get('nameMap')); + geo.zoomLimit = geoModel.get('scaleLimit'); geoList.push(geo); @@ -158,6 +144,20 @@ var geoCreator = { geoModel.coordinateSystem = geo; geo.model = geoModel; + // FIXME ??? + var aspectScale = geoModel.get('aspectScale'); + var invertLng = true; + var mapRecords = mapDataStorage.retrieveMap(name); + if (mapRecords && mapRecords[0] && mapRecords[0].type === 'svg') { + aspectScale == null && (aspectScale = 1); + invertLng = false; + } + else { + aspectScale == null && (aspectScale = 0.75); + } + geo.aspectScale = aspectScale; + geo.invertLng = invertLng; + // Inject resize method geo.resize = resizeGeo; @@ -184,21 +184,11 @@ var geoCreator = { }); zrUtil.each(mapModelGroupBySeries, function (mapSeries, mapType) { - var mapData = echarts.getMap(mapType); - if (__DEV__) { - if (!mapData) { - mapNotExistsError(mapSeries[0].get('map')); - } - } - var nameMapList = zrUtil.map(mapSeries, function (singleMapSeries) { return singleMapSeries.get('nameMap'); }); - var geo = new Geo( - mapType, mapType, - mapData && mapData.geoJson, mapData && mapData.specialAreas, - zrUtil.mergeAll(nameMapList) - ); + var geo = new Geo(mapType, mapType, zrUtil.mergeAll(nameMapList)); + geo.zoomLimit = zrUtil.retrieve.apply(null, zrUtil.map(mapSeries, function (singleMapSeries) { return singleMapSeries.get('scaleLimit'); })); @@ -229,34 +219,18 @@ var geoCreator = { getFilledRegions: function (originRegionArr, mapName, nameMap) { // Not use the original var regionsArr = (originRegionArr || []).slice(); - nameMap = nameMap || {}; - - var map = echarts.getMap(mapName); - var geoJson = map && map.geoJson; - if (!geoJson) { - if (__DEV__) { - mapNotExistsError(mapName); - } - return originRegionArr; - } var dataNameMap = zrUtil.createHashMap(); - var features = geoJson.features; for (var i = 0; i < regionsArr.length; i++) { dataNameMap.set(regionsArr[i].name, regionsArr[i]); } - for (var i = 0; i < features.length; i++) { - var name = features[i].properties.name; - if (!dataNameMap.get(name)) { - if (nameMap.hasOwnProperty(name)) { - name = nameMap[name]; - } - regionsArr.push({ - name: name - }); - } - } + var source = geoSourceManager.load(mapName, nameMap); + zrUtil.each(source.regions, function (region) { + var name = region.name; + !dataNameMap.get(name) && regionsArr.push({name: name}); + }); + return regionsArr; } }; diff --git a/src/coord/geo/geoJSONLoader.js b/src/coord/geo/geoJSONLoader.js new file mode 100644 index 0000000000000000000000000000000000000000..5f0c05aed2850d282a8a756e1fd108bfc9c6d3ab --- /dev/null +++ b/src/coord/geo/geoJSONLoader.js @@ -0,0 +1,94 @@ +/* +* 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 {each} from 'zrender/src/core/util'; +import parseGeoJson from './parseGeoJson'; +import {makeInner} from '../../util/model'; + +// Built-in GEO fixer. +import fixNanhai from './fix/nanhai'; +import fixTextCoord from './fix/textCoord'; +import fixGeoCoord from './fix/geoCoord'; +import fixDiaoyuIsland from './fix/diaoyuIsland'; + +var inner = makeInner(); + +export default { + + /** + * @param {string} mapName + * @param {Object} mapRecord {specialAreas, geoJSON} + * @return {Object} {regions, boundingRect} + */ + load: function (mapName, mapRecord) { + + var parsed = inner(mapRecord).parsed; + + if (parsed) { + return parsed; + } + + var specialAreas = mapRecord.specialAreas || {}; + var geoJSON = mapRecord.geoJSON; + var regions; + + // https://jsperf.com/try-catch-performance-overhead + try { + regions = geoJSON ? parseGeoJson(geoJSON) : []; + } + catch (e) { + throw new Error('Invalid geoJson format\n' + e.message); + } + + each(regions, function (region) { + var regionName = region.name; + + fixTextCoord(mapName, region); + fixGeoCoord(mapName, region); + fixDiaoyuIsland(mapName, region); + + // Some area like Alaska in USA map needs to be tansformed + // to look better + var specialArea = specialAreas[regionName]; + if (specialArea) { + region.transformTo( + specialArea.left, specialArea.top, specialArea.width, specialArea.height + ); + } + }); + + fixNanhai(mapName, regions); + + return (inner(mapRecord).parsed = { + regions: regions, + boundingRect: getBoundingRect(regions) + }); + } +}; + +function getBoundingRect(regions) { + var rect; + for (var i = 0; i < regions.length; i++) { + var regionRect = regions[i].getBoundingRect(); + rect = rect || regionRect.clone(); + rect.union(regionRect); + } + return rect; +} + diff --git a/src/coord/geo/geoSVGLoader.js b/src/coord/geo/geoSVGLoader.js new file mode 100644 index 0000000000000000000000000000000000000000..98eab0332d3c611147f9bef7fbcaa5a977ae67a8 --- /dev/null +++ b/src/coord/geo/geoSVGLoader.js @@ -0,0 +1,143 @@ +/* +* 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 {parseSVG, makeViewBoxTransform} from 'zrender/src/tool/parseSVG'; +import Group from 'zrender/src/container/Group'; +import Rect from 'zrender/src/graphic/shape/Rect'; +import {assert, createHashMap} from 'zrender/src/core/util'; +import BoundingRect from 'zrender/src/core/BoundingRect'; +import {makeInner} from '../../util/model'; + +var inner = makeInner(); + +export default { + + /** + * @param {string} mapName + * @param {Object} mapRecord {specialAreas, geoJSON} + * @return {Object} {root, boundingRect} + */ + load: function (mapName, mapRecord) { + var originRoot = inner(mapRecord).originRoot; + if (originRoot) { + return { + root: originRoot, + boundingRect: inner(mapRecord).boundingRect + }; + } + + var graphic = buildGraphic(mapRecord); + + inner(mapRecord).originRoot = graphic.root; + inner(mapRecord).boundingRect = graphic.boundingRect; + + return graphic; + }, + + makeGraphic: function (mapName, mapRecord, hostKey) { + // For performance consideration (in large SVG), graphic only maked + // when necessary and reuse them according to hostKey. + var field = inner(mapRecord); + var rootMap = field.rootMap || (field.rootMap = createHashMap()); + + var root = rootMap.get(hostKey); + if (root) { + return root; + } + + var originRoot = field.originRoot; + var boundingRect = field.boundingRect; + + // For performance, if originRoot is not used by a view, + // assign it to a view, but not reproduce graphic elements. + if (!field.originRootHostKey) { + field.originRootHostKey = hostKey; + root = originRoot; + } + else { + root = buildGraphic(mapRecord, boundingRect).root; + } + + return rootMap.set(hostKey, root); + }, + + removeGraphic: function (mapName, mapRecord, hostKey) { + var field = inner(mapRecord); + var rootMap = field.rootMap; + rootMap && rootMap.removeKey(hostKey); + if (hostKey === field.originRootHostKey) { + field.originRootHostKey = null; + } + } +}; + +function buildGraphic(mapRecord, boundingRect) { + var svgXML = mapRecord.svgXML; + var result; + var root; + + try { + result = svgXML && parseSVG(svgXML, { + ignoreViewBox: true, + ignoreRootClip: true + }) || {}; + root = result.root; + assert(root != null); + } + catch (e) { + throw new Error('Invalid svg format\n' + e.message); + } + + var svgWidth = result.width; + var svgHeight = result.height; + var viewBoxRect = result.viewBoxRect; + + if (!boundingRect) { + boundingRect = (svgWidth == null || svgHeight == null) + // If svg width / height not specified, calculate + // bounding rect as the width / height + ? root.getBoundingRect() + : new BoundingRect(0, 0, 0, 0); + + if (svgWidth != null) { + boundingRect.width = svgWidth; + } + if (svgHeight != null) { + boundingRect.height = svgHeight; + } + } + + if (viewBoxRect) { + var viewBoxTransform = makeViewBoxTransform(viewBoxRect, boundingRect.width, boundingRect.height); + var elRoot = root; + root = new Group(); + root.add(elRoot); + elRoot.scale = viewBoxTransform.scale; + elRoot.position = viewBoxTransform.position; + } + + root.setClipPath(new Rect({ + shape: boundingRect.plain() + })); + + return { + root: root, + boundingRect: boundingRect + }; +} diff --git a/src/coord/geo/geoSourceManager.js b/src/coord/geo/geoSourceManager.js new file mode 100644 index 0000000000000000000000000000000000000000..53ce1f8372acae1ea6ad1f0271f8d847512db211 --- /dev/null +++ b/src/coord/geo/geoSourceManager.js @@ -0,0 +1,126 @@ +/* +* 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 {__DEV__} from '../../config'; +import {each, createHashMap} from 'zrender/src/core/util'; +import mapDataStorage from './mapDataStorage'; +import geoJSONLoader from './geoJSONLoader'; +import geoSVGLoader from './geoSVGLoader'; +import BoundingRect from 'zrender/src/core/BoundingRect'; + +var loaders = { + geoJSON: geoJSONLoader, + svg: geoSVGLoader +}; + +export default { + + /** + * @param {string} mapName + * @param {Object} nameMap + * @return {Object} source {regions, regionsMap, nameCoordMap, boundingRect} + */ + load: function (mapName, nameMap) { + var regions = []; + var regionsMap = createHashMap(); + var nameCoordMap = createHashMap(); + var boundingRect; + var mapRecords = retrieveMap(mapName); + + each(mapRecords, function (record) { + var singleSource = loaders[record.type].load(mapName, record); + + each(singleSource.regions, function (region) { + var regionName = region.name; + + // Try use the alias in geoNameMap + if (nameMap && nameMap.hasOwnProperty(regionName)) { + region = region.cloneShallow(regionName = nameMap[regionName]); + } + + regions.push(region); + regionsMap.set(regionName, region); + nameCoordMap.set(regionName, region.center); + }); + + var rect = singleSource.boundingRect; + if (rect) { + boundingRect + ? boundingRect.union(rect) + : (boundingRect = rect.clone()); + } + }); + + return { + regions: regions, + regionsMap: regionsMap, + nameCoordMap: nameCoordMap, + // FIXME Always return new ? + boundingRect: boundingRect || new BoundingRect(0, 0, 0, 0) + }; + }, + + /** + * @param {string} mapName + * @param {string} hostKey For cache. + * @return {Array.} Roots. + */ + makeGraphic: makeInvoker('makeGraphic'), + + /** + * @param {string} mapName + * @param {string} hostKey For cache. + */ + removeGraphic: makeInvoker('removeGraphic') +}; + +function makeInvoker(methodName) { + return function (mapName, hostKey) { + var mapRecords = retrieveMap(mapName); + var results = []; + + each(mapRecords, function (record) { + var method = loaders[record.type][methodName]; + method && results.push(method(mapName, record, hostKey)); + }); + + return results; + }; +} + +function mapNotExistsError(mapName) { + if (__DEV__) { + console.error( + 'Map ' + mapName + ' not exists. You can download map file on http://echarts.baidu.com/download-map.html' + ); + } +} + +function retrieveMap(mapName) { + var mapRecords = mapDataStorage.retrieveMap(mapName) || []; + + if (__DEV__) { + if (!mapRecords.length) { + mapNotExistsError(mapName); + } + } + + return mapRecords; +} + diff --git a/src/coord/geo/mapDataStorage.js b/src/coord/geo/mapDataStorage.js new file mode 100644 index 0000000000000000000000000000000000000000..8f6de5767d824072ecb06072cf4b386e4aa02d6e --- /dev/null +++ b/src/coord/geo/mapDataStorage.js @@ -0,0 +1,104 @@ +/* +* 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 {__DEV__} from '../../config'; +import {createHashMap, isString, isArray, each, assert} from 'zrender/src/core/util'; +import {parseXML} from 'zrender/src/tool/parseSVG'; + + +var storage = createHashMap(); + +// For minimize the code size of common echarts package, +// do not put too much logic in this module. + +export default { + + // The format of record: see `echarts.registerMap`. + // Compatible with previous `echarts.registerMap`. + registerMap: function (mapName, rawGeoJson, rawSpecialAreas) { + + var records; + + if (isArray(rawGeoJson)) { + records = rawGeoJson; + } + else if (rawGeoJson.svg) { + records = [{ + type: 'svg', + source: rawGeoJson.svg, + specialAreas: rawGeoJson.specialAreas + }]; + } + else { + // Backward compatibility. + if (rawGeoJson.geoJson && !rawGeoJson.features) { + rawSpecialAreas = rawGeoJson.specialAreas; + rawGeoJson = rawGeoJson.geoJson; + } + records = [{ + type: 'geoJSON', + source: rawGeoJson, + specialAreas: rawSpecialAreas + }]; + } + + each(records, function (record) { + var type = record.type; + type === 'geoJson' && (type = record.type = 'geoJSON'); + + var parse = parsers[type]; + + if (__DEV__) { + assert(parse, 'Illegal map type: ' + type); + } + + parse(record); + }); + + return storage.set(mapName, records); + }, + + retrieveMap: function (mapName) { + return storage.get(mapName); + } + +}; + +var parsers = { + + geoJSON: function (record) { + var source = record.source; + record.geoJSON = !isString(source) + ? source + : (typeof JSON !== 'undefined' && JSON.parse) + ? JSON.parse(source) + : (new Function('return (' + source + ');'))(); + }, + + // Only perform parse to XML object here, which might be time + // consiming for large SVG. + // Although convert XML to zrender element is also time consiming, + // if we do it here, the clone of zrender elements has to be + // required. So we do it once for each geo instance, util real + // performance issues call for optimizing it. + svg: function (record) { + record.svgXML = parseXML(record.source); + } + +}; diff --git a/src/coord/geo/prepareCustom.js b/src/coord/geo/prepareCustom.js index 8ec5fd647faae873fd3bef7616e8c4a9e8181718..1cccc2becb4f6c655b54c6796d6b313342c370db 100644 --- a/src/coord/geo/prepareCustom.js +++ b/src/coord/geo/prepareCustom.js @@ -41,7 +41,8 @@ export default function (coordSys) { x: rect.x, y: rect.y, width: rect.width, - height: rect.height + height: rect.height, + zoom: coordSys.getZoom() }, api: { coord: function (data) { diff --git a/src/echarts.js b/src/echarts.js index 019ef6bbd49b68610c8752493f9dbc47898b6f79..dec71488824627d91b4e29c8a7e6052307d98a30 100644 --- a/src/echarts.js +++ b/src/echarts.js @@ -43,6 +43,7 @@ import Scheduler from './stream/Scheduler'; import lightTheme from './theme/light'; import darkTheme from './theme/dark'; import './component/dataset'; +import mapDataStorage from './coord/geo/mapDataStorage'; var assert = zrUtil.assert; var each = zrUtil.each; @@ -1720,8 +1721,6 @@ var idBase = new Date() - 0; var groupIdBase = new Date() - 0; var DOM_ATTRIBUTE_KEY = '_echarts_instance_'; -var mapDataStores = {}; - function enableConnect(chart) { var STATUS_PENDING = 0; var STATUS_UPDATING = 1; @@ -2115,10 +2114,10 @@ export function setCanvasCreator(creator) { /** * @param {string} mapName - * @param {Object|string} geoJson + * @param {Array.|Object|string} geoJson * @param {Object} [specialAreas] * - * @example + * @example GeoJSON * $.get('USA.json', function (geoJson) { * echarts.registerMap('USA', geoJson); * // Or @@ -2127,20 +2126,20 @@ export function setCanvasCreator(creator) { * specialAreas: {} * }) * }); + * + * $.get('airport.svg', function (svg) { + * echarts.registerMap('airport', { + * svg: svg + * } + * }); + * + * echarts.registerMap('eu', [ + * {svg: eu-topographic.svg}, + * {geoJSON: eu.json} + * ]) */ export function registerMap(mapName, geoJson, specialAreas) { - if (geoJson.geoJson && !geoJson.features) { - specialAreas = geoJson.specialAreas; - geoJson = geoJson.geoJson; - } - if (typeof geoJson === 'string') { - geoJson = (typeof JSON !== 'undefined' && JSON.parse) - ? JSON.parse(geoJson) : (new Function('return (' + geoJson + ');'))(); - } - mapDataStores[mapName] = { - geoJson: geoJson, - specialAreas: specialAreas - }; + mapDataStorage.registerMap(mapName, geoJson, specialAreas); } /** @@ -2148,7 +2147,12 @@ export function registerMap(mapName, geoJson, specialAreas) { * @return {Object} */ export function getMap(mapName) { - return mapDataStores[mapName]; + // For backward compatibility, only return the first one. + var records = mapDataStorage.retrieveMap(mapName); + return records && records[0] && { + geoJson: records[0].geoJSON, + specialAreas: records[0].specialAreas + }; } registerVisual(PRIORITY_VISUAL_GLOBAL, seriesColor); diff --git a/src/scale/Time.js b/src/scale/Time.js index 689581aef42d82845cfbd9603a2e814892c6e745..8c3226a5a8f8f9a9756918e6806ba1c09c532c12 100644 --- a/src/scale/Time.js +++ b/src/scale/Time.js @@ -201,8 +201,8 @@ var scaleLevels = [ ['month', ONE_DAY * 31], // 1M ['week', ONE_DAY * 42], // 6w ['month', ONE_DAY * 62], // 2M - ['week', ONE_DAY * 42], // 10w - ['quarter', ONE_DAY * 380 / 4], // 3M + ['week', ONE_DAY * 70], // 10w + ['quarter', ONE_DAY * 95], // 3M ['month', ONE_DAY * 31 * 4], // 4M ['month', ONE_DAY * 31 * 5], // 5M ['half-year', ONE_DAY * 380 / 2], // 6M diff --git a/src/util/number.js b/src/util/number.js index a66f3f01d2f5aa5087d3407c7d193ec50f5c5aba..f3dad2d26807fab7d015536c3f627a90f8855f6e 100644 --- a/src/util/number.js +++ b/src/util/number.js @@ -409,6 +409,50 @@ export function nice(val, round) { return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val; } +/** + * BSD 3-Clause + * + * Copyright (c) 2010-2015, Michael Bostock + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * The name Michael Bostock may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @see + * @see + * @param {Array.} ascArr + */ +export function quantile(ascArr, p) { + var H = (ascArr.length - 1) * p + 1; + var h = Math.floor(H); + var v = +ascArr[h - 1]; + var e = H - h; + return e ? v + e * (ascArr[h] - v) : v; +} + /** * Order intervals asc, and split them when overlap. * expect(numberUtil.reformIntervals([ diff --git a/src/view/Chart.js b/src/view/Chart.js index 04fdcadd58d1c80a7c9b61a3f1bf5b780b7161ab..67a1c376f9df0f677c9dffc36d13eb4756b1bbbd 100644 --- a/src/view/Chart.js +++ b/src/view/Chart.js @@ -118,6 +118,7 @@ Chart.prototype = { /** * Render in progressive mode. + * @param {Object} params See taskParams in `stream/task.js` * @param {module:echarts/model/Series} seriesModel * @param {module:echarts/model/Global} ecModel * @param {module:echarts/ExtensionAPI} api diff --git a/test/axis-interval.html b/test/axis-interval.html index d67d3ad9bead6540d790554df511482d78467e31..70ae3ab6f63e72907615215d1d165ce24ff14923 100644 --- a/test/axis-interval.html +++ b/test/axis-interval.html @@ -46,6 +46,8 @@ under the License. } +
+

[ Test minInterval ]    yAxis: {minInterval: 1, min: 0}, series.data: [1]

[ Test category axis interval ]    interval of xAxis should be approperiate after rotated.

@@ -54,6 +56,75 @@ under the License.
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/dataZoom-scroll.html b/test/dataZoom-scroll.html new file mode 100644 index 0000000000000000000000000000000000000000..bb2dd3d85f5d96f4821b803fdc062a03527e42e3 --- /dev/null +++ b/test/dataZoom-scroll.html @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/test/geoScatter.html b/test/geoScatter.html index 7964d7ea471f0a3434cce3b2b6797afe5407ad50..f42b3637fba0006e33bd7d28576f58eefc21520d 100644 --- a/test/geoScatter.html +++ b/test/geoScatter.html @@ -33,6 +33,7 @@ under the License. margin: 0; } + 新疆 should be yellow
\ No newline at end of file diff --git a/test/sankey.html b/test/sankey.html index 35daab694d30efa2c7f65bf0006d0141f0ea0143..118e5a69bdc20b28785bfd8444480ba7fa8591bb 100644 --- a/test/sankey.html +++ b/test/sankey.html @@ -73,6 +73,7 @@ under the License. { type: 'sankey', layout:'none', + focusNodeAdjacency: 'allEdges', data: data.nodes, links: data.links, lineStyle: { diff --git a/test/tooltip-event.html b/test/tooltip-event.html new file mode 100644 index 0000000000000000000000000000000000000000..e2268f36199a3ef1b0b58af2ade0e26b5b4a7816 --- /dev/null +++ b/test/tooltip-event.html @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/test/touch-slide.html b/test/touch-slide.html index 965824ffd5697f8d49cf107c739ccc9219555c2d..faecb27457055cac31e7436d055eb9497a71dea4 100644 --- a/test/touch-slide.html +++ b/test/touch-slide.html @@ -79,6 +79,24 @@ under the License.

This page should be able to scroll when sliding out of grid!

+ some text
+ some text
+ some text
+ some text
+ some text
+ some text
+ some text
+ some text
+ some text
+ some text + +

Touch move on map, page should be moved!

+
+ + some text
+ some text
+ some text
+ some text
some text
some text
some text
@@ -323,5 +341,496 @@ under the License. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/tree-image.html b/test/tree-image.html index 554280dc00c91c1d223a45e73f7756c866925b3f..21af66626763257e05d078fae9f98c4500b91f39 100644 --- a/test/tree-image.html +++ b/test/tree-image.html @@ -65,7 +65,7 @@ under the License. type: 'tree', data: [data], - + roam: true, top: '1%', left: '7%', bottom: '1%', diff --git a/test/tree-roam.html b/test/tree-roam.html new file mode 100644 index 0000000000000000000000000000000000000000..fac8df44bf5d401777c09c7ade9a95df546c706f --- /dev/null +++ b/test/tree-roam.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + + +
+ + + \ No newline at end of file