From c85390bf6121c4d164e48969dd766552821b5a43 Mon Sep 17 00:00:00 2001 From: wanghongjun <1445693971@qq,com> Date: Wed, 29 Nov 2023 14:11:29 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 66 + .idea/.gitignore | 8 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/php.xml | 19 + .idea/thinkphp.iml | 23 + .travis.yml | 42 + CHANGELOG.md | 1236 + LICENSE | 674 + LICENSE.txt | 32 + application/.htaccess | 0 application/api/controller/Base.php | 32 + application/api/controller/Information.php | 50 + .../api/validate/InformationValidate.php | 18 + application/command.php | 12 + application/common.php | 12 + application/config.php | 243 + application/database.php | 55 + application/extra/queue.php | 14 + .../index/controller/Administrator.php | 896 + application/index/controller/Base.php | 33 + application/index/controller/Index.php | 115 + application/index/controller/Tools.php | 699 + application/index/model/User.php | 30 + .../index/view/administrator/admin_list.html | 453 + .../index/view/administrator/device.html | 520 + .../index/view/administrator/device_list.html | 453 + .../index/view/administrator/index.html | 469 + application/index/view/administrator/log.html | 307 + .../index/view/administrator/test.html | 43 + .../index/view/administrator/user_list.html | 558 + .../index/view/administrator/welcome.html | 578 + application/index/view/index/index.html | 131 + application/index/view/tools/admin_add.html | 183 + application/index/view/tools/admin_edit.html | 192 + .../index/view/tools/device_add_page.html | 188 + .../index/view/tools/device_id_add_page.html | 139 + .../index/view/tools/device_operation.html | 284 + application/index/view/tools/getData.html | 1070 + application/index/view/tools/icon.html | 1211 + application/index/view/tools/show_detail.html | 443 + application/index/view/tools/user_add.html | 183 + application/index/view/tools/user_edit.html | 191 + application/route.php | 21 + application/tags.php | 28 + build.php | 25 + composer.json | 33 + composer.lock | 528 + data.sql | 5695 + extend/.gitignore | 2 + public/.htaccess | 12 + public/css/font.css | 16 + public/css/login.css | 123 + public/css/theme1.css | 21 + public/css/theme2.css | 21 + public/css/theme3.css | 22 + public/css/theme4.css | 21 + public/css/theme5.css | 27 + public/css/xadmin.css | 633 + public/dist/layarea.js | 4020 + public/dist/notice.css | 228 + public/dist/notice.js | 486 + public/dist/sliderVerify/sliderVerify.js | 1 + public/dist/xm-select.js | 8 + public/favicon.ico | Bin 0 -> 67646 bytes public/favicon1.ico | Bin 0 -> 1150 bytes public/fonts/iconfont.eot | Bin 0 -> 49600 bytes public/fonts/iconfont.svg | 477 + public/fonts/iconfont.ttf | Bin 0 -> 49432 bytes public/fonts/iconfont.woff | Bin 0 -> 30200 bytes public/icon.json | 1 + public/images/aiwrap.png | Bin 0 -> 3032 bytes public/images/handsome.ico | Bin 0 -> 67646 bytes public/images/start.jpg | Bin 0 -> 346462 bytes public/images/wallhaven.jpg | Bin 0 -> 831758 bytes public/images/work.jpg | Bin 0 -> 250432 bytes public/index.php | 17 + public/js/china.js | 492 + public/js/countUp.min.js | 1 + public/js/echarts.js | 97412 ++++++++++++++++ public/js/echarts.min.js | 22 + public/js/jquery.min.js | 4 + public/js/xadmin.js | 602 + public/js/xcity.js | 506 + public/lib/layui/css/layui.css | 2 + public/lib/layui/css/layui.mobile.css | 2 + public/lib/layui/css/modules/code.css | 2 + .../css/modules/laydate/default/laydate.css | 2 + .../css/modules/layer/default/icon-ext.png | Bin 0 -> 5911 bytes .../layui/css/modules/layer/default/icon.png | Bin 0 -> 11493 bytes .../layui/css/modules/layer/default/layer.css | 2 + .../css/modules/layer/default/loading-0.gif | Bin 0 -> 5793 bytes .../css/modules/layer/default/loading-1.gif | Bin 0 -> 701 bytes .../css/modules/layer/default/loading-2.gif | Bin 0 -> 1787 bytes public/lib/layui/font/iconfont.eot | Bin 0 -> 46684 bytes public/lib/layui/font/iconfont.svg | 554 + public/lib/layui/font/iconfont.ttf | Bin 0 -> 46508 bytes public/lib/layui/font/iconfont.woff | Bin 0 -> 30628 bytes public/lib/layui/font/iconfont.woff2 | Bin 0 -> 25964 bytes public/lib/layui/images/face/0.gif | Bin 0 -> 2689 bytes public/lib/layui/images/face/1.gif | Bin 0 -> 5514 bytes public/lib/layui/images/face/10.gif | Bin 0 -> 2797 bytes public/lib/layui/images/face/11.gif | Bin 0 -> 4121 bytes public/lib/layui/images/face/12.gif | Bin 0 -> 3361 bytes public/lib/layui/images/face/13.gif | Bin 0 -> 7425 bytes public/lib/layui/images/face/14.gif | Bin 0 -> 2375 bytes public/lib/layui/images/face/15.gif | Bin 0 -> 1793 bytes public/lib/layui/images/face/16.gif | Bin 0 -> 6721 bytes public/lib/layui/images/face/17.gif | Bin 0 -> 4439 bytes public/lib/layui/images/face/18.gif | Bin 0 -> 3017 bytes public/lib/layui/images/face/19.gif | Bin 0 -> 3040 bytes public/lib/layui/images/face/2.gif | Bin 0 -> 3222 bytes public/lib/layui/images/face/20.gif | Bin 0 -> 5144 bytes public/lib/layui/images/face/21.gif | Bin 0 -> 5191 bytes public/lib/layui/images/face/22.gif | Bin 0 -> 9823 bytes public/lib/layui/images/face/23.gif | Bin 0 -> 3792 bytes public/lib/layui/images/face/24.gif | Bin 0 -> 8096 bytes public/lib/layui/images/face/25.gif | Bin 0 -> 3127 bytes public/lib/layui/images/face/26.gif | Bin 0 -> 3291 bytes public/lib/layui/images/face/27.gif | Bin 0 -> 4377 bytes public/lib/layui/images/face/28.gif | Bin 0 -> 2793 bytes public/lib/layui/images/face/29.gif | Bin 0 -> 4854 bytes public/lib/layui/images/face/3.gif | Bin 0 -> 4017 bytes public/lib/layui/images/face/30.gif | Bin 0 -> 2555 bytes public/lib/layui/images/face/31.gif | Bin 0 -> 2002 bytes public/lib/layui/images/face/32.gif | Bin 0 -> 3481 bytes public/lib/layui/images/face/33.gif | Bin 0 -> 2454 bytes public/lib/layui/images/face/34.gif | Bin 0 -> 3700 bytes public/lib/layui/images/face/35.gif | Bin 0 -> 1800 bytes public/lib/layui/images/face/36.gif | Bin 0 -> 2331 bytes public/lib/layui/images/face/37.gif | Bin 0 -> 1513 bytes public/lib/layui/images/face/38.gif | Bin 0 -> 3615 bytes public/lib/layui/images/face/39.gif | Bin 0 -> 6495 bytes public/lib/layui/images/face/4.gif | Bin 0 -> 5689 bytes public/lib/layui/images/face/40.gif | Bin 0 -> 3154 bytes public/lib/layui/images/face/41.gif | Bin 0 -> 3644 bytes public/lib/layui/images/face/42.gif | Bin 0 -> 5305 bytes public/lib/layui/images/face/43.gif | Bin 0 -> 2674 bytes public/lib/layui/images/face/44.gif | Bin 0 -> 4126 bytes public/lib/layui/images/face/45.gif | Bin 0 -> 3417 bytes public/lib/layui/images/face/46.gif | Bin 0 -> 3007 bytes public/lib/layui/images/face/47.gif | Bin 0 -> 2333 bytes public/lib/layui/images/face/48.gif | Bin 0 -> 2689 bytes public/lib/layui/images/face/49.gif | Bin 0 -> 2315 bytes public/lib/layui/images/face/5.gif | Bin 0 -> 4567 bytes public/lib/layui/images/face/50.gif | Bin 0 -> 5866 bytes public/lib/layui/images/face/51.gif | Bin 0 -> 2785 bytes public/lib/layui/images/face/52.gif | Bin 0 -> 777 bytes public/lib/layui/images/face/53.gif | Bin 0 -> 2127 bytes public/lib/layui/images/face/54.gif | Bin 0 -> 2196 bytes public/lib/layui/images/face/55.gif | Bin 0 -> 1971 bytes public/lib/layui/images/face/56.gif | Bin 0 -> 2034 bytes public/lib/layui/images/face/57.gif | Bin 0 -> 2705 bytes public/lib/layui/images/face/58.gif | Bin 0 -> 2258 bytes public/lib/layui/images/face/59.gif | Bin 0 -> 10311 bytes public/lib/layui/images/face/6.gif | Bin 0 -> 2213 bytes public/lib/layui/images/face/60.gif | Bin 0 -> 3245 bytes public/lib/layui/images/face/61.gif | Bin 0 -> 2495 bytes public/lib/layui/images/face/62.gif | Bin 0 -> 2017 bytes public/lib/layui/images/face/63.gif | Bin 0 -> 5871 bytes public/lib/layui/images/face/64.gif | Bin 0 -> 6448 bytes public/lib/layui/images/face/65.gif | Bin 0 -> 3576 bytes public/lib/layui/images/face/66.gif | Bin 0 -> 3029 bytes public/lib/layui/images/face/67.gif | Bin 0 -> 2701 bytes public/lib/layui/images/face/68.gif | Bin 0 -> 1424 bytes public/lib/layui/images/face/69.gif | Bin 0 -> 2431 bytes public/lib/layui/images/face/7.gif | Bin 0 -> 3398 bytes public/lib/layui/images/face/70.gif | Bin 0 -> 4590 bytes public/lib/layui/images/face/71.gif | Bin 0 -> 5304 bytes public/lib/layui/images/face/8.gif | Bin 0 -> 4050 bytes public/lib/layui/images/face/9.gif | Bin 0 -> 4221 bytes public/lib/layui/lay/modules/carousel.js | 2 + public/lib/layui/lay/modules/code.js | 2 + public/lib/layui/lay/modules/colorpicker.js | 2 + public/lib/layui/lay/modules/element.js | 2 + public/lib/layui/lay/modules/flow.js | 2 + public/lib/layui/lay/modules/form.js | 2 + public/lib/layui/lay/modules/jquery.js | 5 + public/lib/layui/lay/modules/laydate.js | 2 + public/lib/layui/lay/modules/layedit.js | 2 + public/lib/layui/lay/modules/layer.js | 2 + public/lib/layui/lay/modules/laypage.js | 2 + public/lib/layui/lay/modules/laytpl.js | 2 + public/lib/layui/lay/modules/mobile.js | 2 + public/lib/layui/lay/modules/rate.js | 2 + public/lib/layui/lay/modules/slider.js | 2 + public/lib/layui/lay/modules/table.js | 2 + public/lib/layui/lay/modules/transfer.js | 2 + public/lib/layui/lay/modules/tree.js | 2 + public/lib/layui/lay/modules/upload.js | 2 + public/lib/layui/lay/modules/util.js | 2 + public/lib/layui/layui.all.js | 5 + public/lib/layui/layui.js | 2 + public/nginx.htaccess | 0 public/robots.txt | 2 + public/router.php | 20 + public/static/.gitignore | 2 + runtime/.gitignore | 2 + think | 17 + thinkphp/.gitignore | 4 + thinkphp/.htaccess | 1 + thinkphp/.travis.yml | 47 + thinkphp/CONTRIBUTING.md | 119 + thinkphp/LICENSE.txt | 32 + thinkphp/README.md | 114 + thinkphp/base.php | 65 + thinkphp/codecov.yml | 12 + thinkphp/composer.json | 35 + thinkphp/console.php | 20 + thinkphp/convention.php | 298 + thinkphp/helper.php | 589 + thinkphp/lang/zh-cn.php | 136 + thinkphp/library/think/App.php | 677 + thinkphp/library/think/Build.php | 235 + thinkphp/library/think/Cache.php | 247 + thinkphp/library/think/Collection.php | 467 + thinkphp/library/think/Config.php | 214 + thinkphp/library/think/Console.php | 863 + thinkphp/library/think/Controller.php | 229 + thinkphp/library/think/Cookie.php | 268 + thinkphp/library/think/Db.php | 180 + thinkphp/library/think/Debug.php | 252 + thinkphp/library/think/Env.php | 39 + thinkphp/library/think/Error.php | 136 + thinkphp/library/think/Exception.php | 55 + thinkphp/library/think/File.php | 478 + thinkphp/library/think/Hook.php | 148 + thinkphp/library/think/Lang.php | 265 + thinkphp/library/think/Loader.php | 677 + thinkphp/library/think/Log.php | 237 + thinkphp/library/think/Model.php | 2350 + thinkphp/library/think/Paginator.php | 409 + thinkphp/library/think/Process.php | 1205 + thinkphp/library/think/Request.php | 1690 + thinkphp/library/think/Response.php | 332 + thinkphp/library/think/Route.php | 1645 + thinkphp/library/think/Session.php | 366 + thinkphp/library/think/Template.php | 1139 + thinkphp/library/think/Url.php | 333 + thinkphp/library/think/Validate.php | 1371 + thinkphp/library/think/View.php | 239 + thinkphp/library/think/cache/Driver.php | 231 + thinkphp/library/think/cache/driver/File.php | 268 + thinkphp/library/think/cache/driver/Lite.php | 187 + .../library/think/cache/driver/Memcache.php | 177 + .../library/think/cache/driver/Memcached.php | 187 + thinkphp/library/think/cache/driver/Redis.php | 188 + .../library/think/cache/driver/Sqlite.php | 199 + .../library/think/cache/driver/Wincache.php | 152 + .../library/think/cache/driver/Xcache.php | 155 + thinkphp/library/think/config/driver/Ini.php | 24 + thinkphp/library/think/config/driver/Json.php | 24 + thinkphp/library/think/config/driver/Xml.php | 31 + thinkphp/library/think/console/Command.php | 470 + thinkphp/library/think/console/Input.php | 464 + thinkphp/library/think/console/LICENSE | 19 + thinkphp/library/think/console/Output.php | 222 + thinkphp/library/think/console/bin/README.md | 1 + .../library/think/console/bin/hiddeninput.exe | Bin 0 -> 9216 bytes .../library/think/console/command/Build.php | 56 + .../library/think/console/command/Clear.php | 63 + .../library/think/console/command/Help.php | 69 + .../library/think/console/command/Lists.php | 74 + .../library/think/console/command/Make.php | 110 + .../think/console/command/make/Controller.php | 50 + .../think/console/command/make/Model.php | 36 + .../command/make/stubs/controller.plain.stub | 10 + .../command/make/stubs/controller.stub | 85 + .../console/command/make/stubs/model.stub | 10 + .../console/command/optimize/Autoload.php | 294 + .../think/console/command/optimize/Config.php | 93 + .../think/console/command/optimize/Route.php | 75 + .../think/console/command/optimize/Schema.php | 118 + .../library/think/console/input/Argument.php | 115 + .../think/console/input/Definition.php | 375 + .../library/think/console/input/Option.php | 190 + thinkphp/library/think/console/output/Ask.php | 340 + .../think/console/output/Descriptor.php | 319 + .../think/console/output/Formatter.php | 198 + .../library/think/console/output/Question.php | 211 + .../console/output/descriptor/Console.php | 149 + .../think/console/output/driver/Buffer.php | 52 + .../think/console/output/driver/Console.php | 373 + .../think/console/output/driver/Nothing.php | 33 + .../think/console/output/formatter/Stack.php | 116 + .../think/console/output/formatter/Style.php | 189 + .../think/console/output/question/Choice.php | 163 + .../console/output/question/Confirmation.php | 57 + thinkphp/library/think/controller/Rest.php | 99 + thinkphp/library/think/controller/Yar.php | 51 + thinkphp/library/think/db/Builder.php | 899 + thinkphp/library/think/db/Connection.php | 1059 + thinkphp/library/think/db/Expression.php | 48 + thinkphp/library/think/db/Query.php | 3045 + thinkphp/library/think/db/builder/Mysql.php | 137 + thinkphp/library/think/db/builder/Pgsql.php | 89 + thinkphp/library/think/db/builder/Sqlite.php | 82 + thinkphp/library/think/db/builder/Sqlsrv.php | 137 + thinkphp/library/think/db/connector/Mysql.php | 126 + thinkphp/library/think/db/connector/Pgsql.php | 103 + .../library/think/db/connector/Sqlite.php | 104 + .../library/think/db/connector/Sqlsrv.php | 125 + thinkphp/library/think/db/connector/pgsql.sql | 117 + .../think/db/exception/BindParamException.php | 35 + .../db/exception/DataNotFoundException.php | 43 + .../db/exception/ModelNotFoundException.php | 43 + thinkphp/library/think/debug/Console.php | 160 + thinkphp/library/think/debug/Html.php | 111 + .../exception/ClassNotFoundException.php | 32 + .../library/think/exception/DbException.php | 43 + .../think/exception/ErrorException.php | 57 + thinkphp/library/think/exception/Handle.php | 282 + .../library/think/exception/HttpException.php | 36 + .../think/exception/HttpResponseException.php | 33 + .../library/think/exception/PDOException.php | 39 + .../exception/RouteNotFoundException.php | 22 + .../exception/TemplateNotFoundException.php | 33 + .../think/exception/ThrowableError.php | 47 + .../think/exception/ValidateException.php | 33 + thinkphp/library/think/log/driver/File.php | 270 + thinkphp/library/think/log/driver/Socket.php | 250 + thinkphp/library/think/log/driver/Test.php | 30 + thinkphp/library/think/model/Collection.php | 79 + thinkphp/library/think/model/Merge.php | 322 + thinkphp/library/think/model/Pivot.php | 42 + thinkphp/library/think/model/Relation.php | 155 + .../think/model/relation/BelongsTo.php | 243 + .../think/model/relation/BelongsToMany.php | 644 + .../library/think/model/relation/HasMany.php | 318 + .../think/model/relation/HasManyThrough.php | 157 + .../library/think/model/relation/HasOne.php | 215 + .../think/model/relation/MorphMany.php | 314 + .../library/think/model/relation/MorphOne.php | 263 + .../library/think/model/relation/MorphTo.php | 299 + .../library/think/model/relation/OneToOne.php | 337 + .../think/paginator/driver/Bootstrap.php | 205 + thinkphp/library/think/process/Builder.php | 233 + thinkphp/library/think/process/Utils.php | 75 + .../think/process/exception/Failed.php | 42 + .../think/process/exception/Timeout.php | 61 + .../library/think/process/pipes/Pipes.php | 93 + thinkphp/library/think/process/pipes/Unix.php | 196 + .../library/think/process/pipes/Windows.php | 228 + thinkphp/library/think/response/Json.php | 51 + thinkphp/library/think/response/Jsonp.php | 58 + thinkphp/library/think/response/Redirect.php | 105 + thinkphp/library/think/response/View.php | 89 + thinkphp/library/think/response/Xml.php | 102 + .../library/think/session/driver/Memcache.php | 118 + .../think/session/driver/Memcached.php | 126 + .../library/think/session/driver/Redis.php | 128 + thinkphp/library/think/template/TagLib.php | 334 + .../library/think/template/driver/File.php | 74 + thinkphp/library/think/template/taglib/Cx.php | 673 + thinkphp/library/think/view/driver/Php.php | 160 + thinkphp/library/think/view/driver/Think.php | 167 + thinkphp/library/traits/controller/Jump.php | 167 + thinkphp/library/traits/model/SoftDelete.php | 200 + thinkphp/library/traits/think/Instance.php | 54 + thinkphp/logo.png | Bin 0 -> 6995 bytes thinkphp/phpunit.xml | 35 + thinkphp/start.php | 19 + thinkphp/tpl/default_index.tpl | 10 + thinkphp/tpl/dispatch_jump.tpl | 49 + thinkphp/tpl/page_trace.tpl | 71 + thinkphp/tpl/think_exception.tpl | 537 + vendor/.gitignore | 2 + 367 files changed, 164991 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/php.xml create mode 100644 .idea/thinkphp.iml create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 LICENSE.txt create mode 100644 application/.htaccess create mode 100644 application/api/controller/Base.php create mode 100644 application/api/controller/Information.php create mode 100644 application/api/validate/InformationValidate.php create mode 100644 application/command.php create mode 100644 application/common.php create mode 100644 application/config.php create mode 100644 application/database.php create mode 100644 application/extra/queue.php create mode 100644 application/index/controller/Administrator.php create mode 100644 application/index/controller/Base.php create mode 100644 application/index/controller/Index.php create mode 100644 application/index/controller/Tools.php create mode 100644 application/index/model/User.php create mode 100644 application/index/view/administrator/admin_list.html create mode 100644 application/index/view/administrator/device.html create mode 100644 application/index/view/administrator/device_list.html create mode 100644 application/index/view/administrator/index.html create mode 100644 application/index/view/administrator/log.html create mode 100644 application/index/view/administrator/test.html create mode 100644 application/index/view/administrator/user_list.html create mode 100644 application/index/view/administrator/welcome.html create mode 100644 application/index/view/index/index.html create mode 100644 application/index/view/tools/admin_add.html create mode 100644 application/index/view/tools/admin_edit.html create mode 100644 application/index/view/tools/device_add_page.html create mode 100644 application/index/view/tools/device_id_add_page.html create mode 100644 application/index/view/tools/device_operation.html create mode 100644 application/index/view/tools/getData.html create mode 100644 application/index/view/tools/icon.html create mode 100644 application/index/view/tools/show_detail.html create mode 100644 application/index/view/tools/user_add.html create mode 100644 application/index/view/tools/user_edit.html create mode 100644 application/route.php create mode 100644 application/tags.php create mode 100644 build.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 data.sql create mode 100644 extend/.gitignore create mode 100644 public/.htaccess create mode 100644 public/css/font.css create mode 100644 public/css/login.css create mode 100644 public/css/theme1.css create mode 100644 public/css/theme2.css create mode 100644 public/css/theme3.css create mode 100644 public/css/theme4.css create mode 100644 public/css/theme5.css create mode 100644 public/css/xadmin.css create mode 100644 public/dist/layarea.js create mode 100644 public/dist/notice.css create mode 100644 public/dist/notice.js create mode 100644 public/dist/sliderVerify/sliderVerify.js create mode 100644 public/dist/xm-select.js create mode 100644 public/favicon.ico create mode 100644 public/favicon1.ico create mode 100644 public/fonts/iconfont.eot create mode 100644 public/fonts/iconfont.svg create mode 100644 public/fonts/iconfont.ttf create mode 100644 public/fonts/iconfont.woff create mode 100644 public/icon.json create mode 100644 public/images/aiwrap.png create mode 100644 public/images/handsome.ico create mode 100644 public/images/start.jpg create mode 100644 public/images/wallhaven.jpg create mode 100644 public/images/work.jpg create mode 100644 public/index.php create mode 100644 public/js/china.js create mode 100644 public/js/countUp.min.js create mode 100644 public/js/echarts.js create mode 100644 public/js/echarts.min.js create mode 100644 public/js/jquery.min.js create mode 100644 public/js/xadmin.js create mode 100644 public/js/xcity.js create mode 100644 public/lib/layui/css/layui.css create mode 100644 public/lib/layui/css/layui.mobile.css create mode 100644 public/lib/layui/css/modules/code.css create mode 100644 public/lib/layui/css/modules/laydate/default/laydate.css create mode 100644 public/lib/layui/css/modules/layer/default/icon-ext.png create mode 100644 public/lib/layui/css/modules/layer/default/icon.png create mode 100644 public/lib/layui/css/modules/layer/default/layer.css create mode 100644 public/lib/layui/css/modules/layer/default/loading-0.gif create mode 100644 public/lib/layui/css/modules/layer/default/loading-1.gif create mode 100644 public/lib/layui/css/modules/layer/default/loading-2.gif create mode 100644 public/lib/layui/font/iconfont.eot create mode 100644 public/lib/layui/font/iconfont.svg create mode 100644 public/lib/layui/font/iconfont.ttf create mode 100644 public/lib/layui/font/iconfont.woff create mode 100644 public/lib/layui/font/iconfont.woff2 create mode 100644 public/lib/layui/images/face/0.gif create mode 100644 public/lib/layui/images/face/1.gif create mode 100644 public/lib/layui/images/face/10.gif create mode 100644 public/lib/layui/images/face/11.gif create mode 100644 public/lib/layui/images/face/12.gif create mode 100644 public/lib/layui/images/face/13.gif create mode 100644 public/lib/layui/images/face/14.gif create mode 100644 public/lib/layui/images/face/15.gif create mode 100644 public/lib/layui/images/face/16.gif create mode 100644 public/lib/layui/images/face/17.gif create mode 100644 public/lib/layui/images/face/18.gif create mode 100644 public/lib/layui/images/face/19.gif create mode 100644 public/lib/layui/images/face/2.gif create mode 100644 public/lib/layui/images/face/20.gif create mode 100644 public/lib/layui/images/face/21.gif create mode 100644 public/lib/layui/images/face/22.gif create mode 100644 public/lib/layui/images/face/23.gif create mode 100644 public/lib/layui/images/face/24.gif create mode 100644 public/lib/layui/images/face/25.gif create mode 100644 public/lib/layui/images/face/26.gif create mode 100644 public/lib/layui/images/face/27.gif create mode 100644 public/lib/layui/images/face/28.gif create mode 100644 public/lib/layui/images/face/29.gif create mode 100644 public/lib/layui/images/face/3.gif create mode 100644 public/lib/layui/images/face/30.gif create mode 100644 public/lib/layui/images/face/31.gif create mode 100644 public/lib/layui/images/face/32.gif create mode 100644 public/lib/layui/images/face/33.gif create mode 100644 public/lib/layui/images/face/34.gif create mode 100644 public/lib/layui/images/face/35.gif create mode 100644 public/lib/layui/images/face/36.gif create mode 100644 public/lib/layui/images/face/37.gif create mode 100644 public/lib/layui/images/face/38.gif create mode 100644 public/lib/layui/images/face/39.gif create mode 100644 public/lib/layui/images/face/4.gif create mode 100644 public/lib/layui/images/face/40.gif create mode 100644 public/lib/layui/images/face/41.gif create mode 100644 public/lib/layui/images/face/42.gif create mode 100644 public/lib/layui/images/face/43.gif create mode 100644 public/lib/layui/images/face/44.gif create mode 100644 public/lib/layui/images/face/45.gif create mode 100644 public/lib/layui/images/face/46.gif create mode 100644 public/lib/layui/images/face/47.gif create mode 100644 public/lib/layui/images/face/48.gif create mode 100644 public/lib/layui/images/face/49.gif create mode 100644 public/lib/layui/images/face/5.gif create mode 100644 public/lib/layui/images/face/50.gif create mode 100644 public/lib/layui/images/face/51.gif create mode 100644 public/lib/layui/images/face/52.gif create mode 100644 public/lib/layui/images/face/53.gif create mode 100644 public/lib/layui/images/face/54.gif create mode 100644 public/lib/layui/images/face/55.gif create mode 100644 public/lib/layui/images/face/56.gif create mode 100644 public/lib/layui/images/face/57.gif create mode 100644 public/lib/layui/images/face/58.gif create mode 100644 public/lib/layui/images/face/59.gif create mode 100644 public/lib/layui/images/face/6.gif create mode 100644 public/lib/layui/images/face/60.gif create mode 100644 public/lib/layui/images/face/61.gif create mode 100644 public/lib/layui/images/face/62.gif create mode 100644 public/lib/layui/images/face/63.gif create mode 100644 public/lib/layui/images/face/64.gif create mode 100644 public/lib/layui/images/face/65.gif create mode 100644 public/lib/layui/images/face/66.gif create mode 100644 public/lib/layui/images/face/67.gif create mode 100644 public/lib/layui/images/face/68.gif create mode 100644 public/lib/layui/images/face/69.gif create mode 100644 public/lib/layui/images/face/7.gif create mode 100644 public/lib/layui/images/face/70.gif create mode 100644 public/lib/layui/images/face/71.gif create mode 100644 public/lib/layui/images/face/8.gif create mode 100644 public/lib/layui/images/face/9.gif create mode 100644 public/lib/layui/lay/modules/carousel.js create mode 100644 public/lib/layui/lay/modules/code.js create mode 100644 public/lib/layui/lay/modules/colorpicker.js create mode 100644 public/lib/layui/lay/modules/element.js create mode 100644 public/lib/layui/lay/modules/flow.js create mode 100644 public/lib/layui/lay/modules/form.js create mode 100644 public/lib/layui/lay/modules/jquery.js create mode 100644 public/lib/layui/lay/modules/laydate.js create mode 100644 public/lib/layui/lay/modules/layedit.js create mode 100644 public/lib/layui/lay/modules/layer.js create mode 100644 public/lib/layui/lay/modules/laypage.js create mode 100644 public/lib/layui/lay/modules/laytpl.js create mode 100644 public/lib/layui/lay/modules/mobile.js create mode 100644 public/lib/layui/lay/modules/rate.js create mode 100644 public/lib/layui/lay/modules/slider.js create mode 100644 public/lib/layui/lay/modules/table.js create mode 100644 public/lib/layui/lay/modules/transfer.js create mode 100644 public/lib/layui/lay/modules/tree.js create mode 100644 public/lib/layui/lay/modules/upload.js create mode 100644 public/lib/layui/lay/modules/util.js create mode 100644 public/lib/layui/layui.all.js create mode 100644 public/lib/layui/layui.js create mode 100644 public/nginx.htaccess create mode 100644 public/robots.txt create mode 100644 public/router.php create mode 100644 public/static/.gitignore create mode 100644 runtime/.gitignore create mode 100644 think create mode 100644 thinkphp/.gitignore create mode 100644 thinkphp/.htaccess create mode 100644 thinkphp/.travis.yml create mode 100644 thinkphp/CONTRIBUTING.md create mode 100644 thinkphp/LICENSE.txt create mode 100644 thinkphp/README.md create mode 100644 thinkphp/base.php create mode 100644 thinkphp/codecov.yml create mode 100644 thinkphp/composer.json create mode 100644 thinkphp/console.php create mode 100644 thinkphp/convention.php create mode 100644 thinkphp/helper.php create mode 100644 thinkphp/lang/zh-cn.php create mode 100644 thinkphp/library/think/App.php create mode 100644 thinkphp/library/think/Build.php create mode 100644 thinkphp/library/think/Cache.php create mode 100644 thinkphp/library/think/Collection.php create mode 100644 thinkphp/library/think/Config.php create mode 100644 thinkphp/library/think/Console.php create mode 100644 thinkphp/library/think/Controller.php create mode 100644 thinkphp/library/think/Cookie.php create mode 100644 thinkphp/library/think/Db.php create mode 100644 thinkphp/library/think/Debug.php create mode 100644 thinkphp/library/think/Env.php create mode 100644 thinkphp/library/think/Error.php create mode 100644 thinkphp/library/think/Exception.php create mode 100644 thinkphp/library/think/File.php create mode 100644 thinkphp/library/think/Hook.php create mode 100644 thinkphp/library/think/Lang.php create mode 100644 thinkphp/library/think/Loader.php create mode 100644 thinkphp/library/think/Log.php create mode 100644 thinkphp/library/think/Model.php create mode 100644 thinkphp/library/think/Paginator.php create mode 100644 thinkphp/library/think/Process.php create mode 100644 thinkphp/library/think/Request.php create mode 100644 thinkphp/library/think/Response.php create mode 100644 thinkphp/library/think/Route.php create mode 100644 thinkphp/library/think/Session.php create mode 100644 thinkphp/library/think/Template.php create mode 100644 thinkphp/library/think/Url.php create mode 100644 thinkphp/library/think/Validate.php create mode 100644 thinkphp/library/think/View.php create mode 100644 thinkphp/library/think/cache/Driver.php create mode 100644 thinkphp/library/think/cache/driver/File.php create mode 100644 thinkphp/library/think/cache/driver/Lite.php create mode 100644 thinkphp/library/think/cache/driver/Memcache.php create mode 100644 thinkphp/library/think/cache/driver/Memcached.php create mode 100644 thinkphp/library/think/cache/driver/Redis.php create mode 100644 thinkphp/library/think/cache/driver/Sqlite.php create mode 100644 thinkphp/library/think/cache/driver/Wincache.php create mode 100644 thinkphp/library/think/cache/driver/Xcache.php create mode 100644 thinkphp/library/think/config/driver/Ini.php create mode 100644 thinkphp/library/think/config/driver/Json.php create mode 100644 thinkphp/library/think/config/driver/Xml.php create mode 100644 thinkphp/library/think/console/Command.php create mode 100644 thinkphp/library/think/console/Input.php create mode 100644 thinkphp/library/think/console/LICENSE create mode 100644 thinkphp/library/think/console/Output.php create mode 100644 thinkphp/library/think/console/bin/README.md create mode 100644 thinkphp/library/think/console/bin/hiddeninput.exe create mode 100644 thinkphp/library/think/console/command/Build.php create mode 100644 thinkphp/library/think/console/command/Clear.php create mode 100644 thinkphp/library/think/console/command/Help.php create mode 100644 thinkphp/library/think/console/command/Lists.php create mode 100644 thinkphp/library/think/console/command/Make.php create mode 100644 thinkphp/library/think/console/command/make/Controller.php create mode 100644 thinkphp/library/think/console/command/make/Model.php create mode 100644 thinkphp/library/think/console/command/make/stubs/controller.plain.stub create mode 100644 thinkphp/library/think/console/command/make/stubs/controller.stub create mode 100644 thinkphp/library/think/console/command/make/stubs/model.stub create mode 100644 thinkphp/library/think/console/command/optimize/Autoload.php create mode 100644 thinkphp/library/think/console/command/optimize/Config.php create mode 100644 thinkphp/library/think/console/command/optimize/Route.php create mode 100644 thinkphp/library/think/console/command/optimize/Schema.php create mode 100644 thinkphp/library/think/console/input/Argument.php create mode 100644 thinkphp/library/think/console/input/Definition.php create mode 100644 thinkphp/library/think/console/input/Option.php create mode 100644 thinkphp/library/think/console/output/Ask.php create mode 100644 thinkphp/library/think/console/output/Descriptor.php create mode 100644 thinkphp/library/think/console/output/Formatter.php create mode 100644 thinkphp/library/think/console/output/Question.php create mode 100644 thinkphp/library/think/console/output/descriptor/Console.php create mode 100644 thinkphp/library/think/console/output/driver/Buffer.php create mode 100644 thinkphp/library/think/console/output/driver/Console.php create mode 100644 thinkphp/library/think/console/output/driver/Nothing.php create mode 100644 thinkphp/library/think/console/output/formatter/Stack.php create mode 100644 thinkphp/library/think/console/output/formatter/Style.php create mode 100644 thinkphp/library/think/console/output/question/Choice.php create mode 100644 thinkphp/library/think/console/output/question/Confirmation.php create mode 100644 thinkphp/library/think/controller/Rest.php create mode 100644 thinkphp/library/think/controller/Yar.php create mode 100644 thinkphp/library/think/db/Builder.php create mode 100644 thinkphp/library/think/db/Connection.php create mode 100644 thinkphp/library/think/db/Expression.php create mode 100644 thinkphp/library/think/db/Query.php create mode 100644 thinkphp/library/think/db/builder/Mysql.php create mode 100644 thinkphp/library/think/db/builder/Pgsql.php create mode 100644 thinkphp/library/think/db/builder/Sqlite.php create mode 100644 thinkphp/library/think/db/builder/Sqlsrv.php create mode 100644 thinkphp/library/think/db/connector/Mysql.php create mode 100644 thinkphp/library/think/db/connector/Pgsql.php create mode 100644 thinkphp/library/think/db/connector/Sqlite.php create mode 100644 thinkphp/library/think/db/connector/Sqlsrv.php create mode 100644 thinkphp/library/think/db/connector/pgsql.sql create mode 100644 thinkphp/library/think/db/exception/BindParamException.php create mode 100644 thinkphp/library/think/db/exception/DataNotFoundException.php create mode 100644 thinkphp/library/think/db/exception/ModelNotFoundException.php create mode 100644 thinkphp/library/think/debug/Console.php create mode 100644 thinkphp/library/think/debug/Html.php create mode 100644 thinkphp/library/think/exception/ClassNotFoundException.php create mode 100644 thinkphp/library/think/exception/DbException.php create mode 100644 thinkphp/library/think/exception/ErrorException.php create mode 100644 thinkphp/library/think/exception/Handle.php create mode 100644 thinkphp/library/think/exception/HttpException.php create mode 100644 thinkphp/library/think/exception/HttpResponseException.php create mode 100644 thinkphp/library/think/exception/PDOException.php create mode 100644 thinkphp/library/think/exception/RouteNotFoundException.php create mode 100644 thinkphp/library/think/exception/TemplateNotFoundException.php create mode 100644 thinkphp/library/think/exception/ThrowableError.php create mode 100644 thinkphp/library/think/exception/ValidateException.php create mode 100644 thinkphp/library/think/log/driver/File.php create mode 100644 thinkphp/library/think/log/driver/Socket.php create mode 100644 thinkphp/library/think/log/driver/Test.php create mode 100644 thinkphp/library/think/model/Collection.php create mode 100644 thinkphp/library/think/model/Merge.php create mode 100644 thinkphp/library/think/model/Pivot.php create mode 100644 thinkphp/library/think/model/Relation.php create mode 100644 thinkphp/library/think/model/relation/BelongsTo.php create mode 100644 thinkphp/library/think/model/relation/BelongsToMany.php create mode 100644 thinkphp/library/think/model/relation/HasMany.php create mode 100644 thinkphp/library/think/model/relation/HasManyThrough.php create mode 100644 thinkphp/library/think/model/relation/HasOne.php create mode 100644 thinkphp/library/think/model/relation/MorphMany.php create mode 100644 thinkphp/library/think/model/relation/MorphOne.php create mode 100644 thinkphp/library/think/model/relation/MorphTo.php create mode 100644 thinkphp/library/think/model/relation/OneToOne.php create mode 100644 thinkphp/library/think/paginator/driver/Bootstrap.php create mode 100644 thinkphp/library/think/process/Builder.php create mode 100644 thinkphp/library/think/process/Utils.php create mode 100644 thinkphp/library/think/process/exception/Failed.php create mode 100644 thinkphp/library/think/process/exception/Timeout.php create mode 100644 thinkphp/library/think/process/pipes/Pipes.php create mode 100644 thinkphp/library/think/process/pipes/Unix.php create mode 100644 thinkphp/library/think/process/pipes/Windows.php create mode 100644 thinkphp/library/think/response/Json.php create mode 100644 thinkphp/library/think/response/Jsonp.php create mode 100644 thinkphp/library/think/response/Redirect.php create mode 100644 thinkphp/library/think/response/View.php create mode 100644 thinkphp/library/think/response/Xml.php create mode 100644 thinkphp/library/think/session/driver/Memcache.php create mode 100644 thinkphp/library/think/session/driver/Memcached.php create mode 100644 thinkphp/library/think/session/driver/Redis.php create mode 100644 thinkphp/library/think/template/TagLib.php create mode 100644 thinkphp/library/think/template/driver/File.php create mode 100644 thinkphp/library/think/template/taglib/Cx.php create mode 100644 thinkphp/library/think/view/driver/Php.php create mode 100644 thinkphp/library/think/view/driver/Think.php create mode 100644 thinkphp/library/traits/controller/Jump.php create mode 100644 thinkphp/library/traits/model/SoftDelete.php create mode 100644 thinkphp/library/traits/think/Instance.php create mode 100644 thinkphp/logo.png create mode 100644 thinkphp/phpunit.xml create mode 100644 thinkphp/start.php create mode 100644 thinkphp/tpl/default_index.tpl create mode 100644 thinkphp/tpl/dispatch_jump.tpl create mode 100644 thinkphp/tpl/page_trace.tpl create mode 100644 thinkphp/tpl/think_exception.tpl create mode 100644 vendor/.gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72f4d98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d79f6c7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..aaa4bf0 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/thinkphp.iml b/.idea/thinkphp.iml new file mode 100644 index 0000000..a98fb34 --- /dev/null +++ b/.idea/thinkphp.iml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36f7b6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..56c9a4c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1236 @@ +## 2019-1-11 V5.0.24 + +本次更新包含了一个安全更新,建议更新 + +- 改进关联的save方法 +- 改进模型数据验证 +- Collection增加values方法 +- 改进unique验证方法 +- 改进Request类的method方法 + +## 2018-12-9 V5.0.23 + +本次版本更新主要涉及一个安全更新,推荐尽快更新到最新版本。 + +* Query支持调用模型的查询范围 +* 聚合查询字段支持`DISTINCT` +* 改进闭包验证的参数 +* 多对多关联支持指定中间表数据名称 +* after/before验证支持指定字段验证 +* 改进多对多关联 +* 改进验证类 +* 增加`afterWith`和`beforeWith`验证规则 用于比较日期字段 +* 完善规则提示 +* 改进断线重连 +* 修正软删除的`destroy`方法 +* 修复模型的`save`方法当`data`变量为空 数据不验证 +* 模型增加`replace`方法 +* MorphOne 增加 make 方法创建关联对象实例 +* 改进`count`方法返回值类型 +* 改进聚合查询方法的正则判断 +* 改进`sqlsrv`驱动 +* 完善关联的`save`方法 +* 修正控制器名获取 + + +## 2018-10-22 V5.0.22 + +该版本主要增加了JSON日志格式的支持,并且包含了一个安全更新。 + +* 调试模式下关闭路由解析缓存 +* 改进Log类支持`json`日志格式 +* 改进聚合查询的安全性 +* 改进`count`查询的返回值类型 + +## 2018-9-7 V5.0.21 + +该版本主要做了一些已知问题的修正,改进了对Swoole的支持,以及增加路由解析缓存功能。 + +* 增加路由解析缓存功能 +* 改进url生成的端口问题 +* 改进缓存驱动 +* 改进value方法的缓存处理 +* 修正Builder类的insertAll方法 +* 改进对Swoole的支持(使用参考:[xavier-swoole](https://github.com/xavieryang007/xavier-swoole)) + +## 2018-5-11 V5.0.20 + +该版本为修正版本,修正了一些已知的问题。 + +* `join`方法的条件支持传入`Expression`对象 +* 改进驱动的`parseKey`方法 +* 改进Request类的`host`方法 +* 使用`exp`表达式更新数据的异常提示 +* 修正查询 +* 改进多对多关联的中间表模型更新 + +## 2018-4-25 V5.0.19 + +该版本属于改进版本,主要改进了composer自动加载及内置模板引擎的一处可能的安全隐患。 + +* 改进composer自动加载 +* 改进模板引擎一处安全隐患 +* 改进`comment`方法解析 +* 改进分布式写入数据后及时读取的问题 +* 改进url操作方法的自动转换 +* 改进分页类魔术方法的返回值 +* SQL日志增加主从标记 + +## 2018-4-14 V5.0.18 + +该版本主要修正上一个发布的一些BUG,并且改进了`exp`表达式查询/写入的严谨性。 + +* 修正`field`方法`*`兼容问题; +* 修正`inc/dec`方法; +* 修正`setInc/setDec`方法; +* 改进`insertAll`方法; +* 改进`parseTime`方法; +* 改进`exp`表达式查询/写入的严谨性; + +## 2018-4-12 V5.0.17 + +该版本主要是一些修正和改进,并且包含了一个安全更新。 + +* 改进Response类`create`方法 +* 改进`inc/dec`查询 +* 默认模板渲染规则支持直接使用操作方法名 +* 改进视图驱动 +* 改进Request类ip方法 支持代理设置 +* 修正request类的`create`方法 +* 闭包查询使用`cache(true)`抛出异常 +* 改进composer自动加载文件 +* 增加`Expression`类及相关方法 + +## 2018-3-26 V5.0.16 + +该版本主要做了一些修正和改进,由于包含了一个安全更新,是一个推荐更新的版本。 + +* 改进Url生成 +* 改进composer自动加载性能 +* 改进一对一查询 +* 改进查询缓存 +* 改进field方法 +* 优化Template类 +* 修正分页参数 +* 改进默认模板的自动识别 +* 改进Query类查询 +* Collection类改进 +* 改进模型类`readTransform`方法对序列化类型的处理 +* 改进trace显示 +* 文件日志支持自动清理 +* 改进断线重连的判断 +* 改进验证方法 +* 修正Query类view方法的数组表名定义 +* 改进参数绑定 +* 改进文件缓存的并发删除 +* 改进`inc/dec/exp`更新的安全性 +* 增加控制台配置 + +## 2018-1-31 V5.0.15 + +该版本主要进行了一些修正和完善 + +* 改进View类 +* 改进chunk方法 +* 改进模板引擎的表达式语法 +* 改进自关联查询多级调用问题 +* 关联定义增加`selfRelation`方法用于设置是否自关联 +* 改进file类型的缓存`inc`和`dec`方法不改变缓存有效期 +* 改进软删除 支持设置`deleteTime`属性关闭 +* 改进`union`查询 +* 改进查询缓存 +* 优化File缓存自动生成空目录的问题 +* 改进日志写入并发问题 +* 修正`MorphTo`关联 +* 改进`join`自关联查询 +* 改进`case`标签解析 +* 改进Url类对`url_convert`配置的支持 + + +## 2018-1-1 V5.0.14 + +V5.0.14版本主对复合主键进行了更多支持,改进了PHP7的兼容性,并且对数据库的一些问题做了改进。 + +主要更新如下: + +* 改进Validate类的unique验证 +* Validate类增加checkRule方法用于静态验证多个规则 +* 改进多对多关联的save方法 +* 改进多对多的pivot对象 +* 修正setDec方法的延迟写入 +* max和min方法增加第二个参数用于设置是否强制转换数字 +* 改进View类 +* 改进join关联自身的问题 +* 改进union查询 +* 改进Url类 +* 改进同名路由不同请求的注册 +* 改进Builder类parseData对空数组的判断 +* 改进模板替换 +* 调整BelongsTo的hasWhere方法 +* 改进模板的编译缓存命名规则 增加布局模板的标识 +* 改进insertall方法 +* 改进chunk方法支持复合主键 +* 改进Error类的一个兼容问题 +* 改进model类的save方法的复合主键包含自增的情况 +* save方法改进复合主键的支持 +* 改进mysql的insertAll方法 +* 改进redis长连接多编号库的情况 + +## 2017-12-12 V5.0.13 + +`V5.0.13`主要是对模型和日志方面做了一些改进 + +### [数据库和模型] + +* 改进Model类`save`方法对`oracle`的支持 +* 改进中间表模型的实例化 +* 改进`Pivot`类 +* 模型`saveall`方法支持配合`isUpdate`方法 +* 模型类增加`force`方法设置是否强制更新所有数据 +* 关联自动删除增加一对多关联删除支持 +* 改进`hasWhere`查询的数据重复问题 +* 改进一对多`with`关联查询的`field`支持 +* 模型`saveall`方法支持返回数据集 读取`resultSetType`属性 +* 改进废弃字段判断 +* 模型的`hasWhere`方法增加`fields`参数 +* 改进断线重连异常捕获机制 +* 修正Query类的`inc`和`dec`方法的Mysql关键词问题 +* 修正数据集对象的BUG + +### [其它] + +* 增加`app_dispatch`钩子位置 +* cookie类`httponly`参数默认改为false +* File日志驱动增加`single`参数配置是否记录单个文件日志 +* 单个日志文件支持大小设置 +* 改进日志记录的ip地址 +* Redis缓存驱动改用`serialize`序列化替代json序列化 +* 改进异常捕获 +* 改进上传文件验证 +* 修正redis驱动 +* 改进File缓存的`clear`方法 +* 代码格式化规范 +* 改进一处PHP7.2的兼容问题 +* 调试模式下不读取字段缓存文件 +* `default_filter`支持在模块中配置生效 + +## 2017-11-06 V5.0.12 + +5.0.12是一个修正版本,包含了上个版本发布以来的一些修正和完善,主要包括: + +* 上传类和验证类的多语言支持; +* 模型增加排除和废弃字段支持; +* 改进insertAll方法的分批处理; +* 改进对枚举类型的参数绑定支持; +* 修正社区反馈的问题; + + +### [数据库和模型] + +* 改进Connection类的getRealSql方法 +* 改进append方法支持一对一关联的bind设置 +* 改进whereTime查询 +* 改进model类的`destroy`方法 +* 修正softdelete +* 修正`chunk`方法对时间字段的支持 +* Collection类增加`push`方法 +* 改进alias方法 +* 修正模型类的`append`处理 +* 改进`appendRelationAttr`方法 +* 改进HasManyThrough关联 +* 改进MorphTo关联 +* 模型增加废除字段`disuse`定义 +* 增加排除字段方法`except` +* 修正`has`方法 +* 改进参数绑定类型对枚举类型的支持 +* 改进`insertAll`方法的分批处理 + + +### [其它] + +* 改进Loader类`controller`和`validate`方法支持多层 +* 验证提示信息支持多语言 +* File类错误信息支持多语言 +* 模板渲染异常处理 +* 修正rest控制器 +* 改进trace驱动 +* 改进Cache类的`remember`方法 +* 改进`url_common_param`的情况下urlencode的问题 +* 改进Url类 +* 改进`exception_handle`配置参数对闭包的支持 +* 执行路由缓存命令前检测RUNTIME_PATH是否存在 +* 调整部分`CacheDriver::dec`在为空的时候递减的行为 +* 优化移动端的显示 +* 改进对JSON-Handle插件的支持 +* 改进redis的`get`方法 +* 改进Request类的`host`方法 + + +## 2017-09-08 V5.0.11 + +5.0.11是一个安全及修正版本,包含了上个版本发布以来的一些修正和完善,更新了几处可能的安全问题,主要包括: + +* 完善缓存驱动; +* 改进数据库查询; +* 改进URL生成类; +* 缓存有效期支持指定过期时间; + +### [数据库和模型] + +* 改进数据库驱动类 +* 改进`group`方法的字段关键字冲突 +* 修正聚合查询返回null的问题 +* 改进Db类的强制重连 +* 改进关联的属性绑定 +* 修正事务的断线重连 +* 修正对象的条件查询 +* Db类增加`clear`方法 +* 改进数组查询条件中的`null`查询 +* 改进Query类的`chunk`方法支持排序设置 +* 改进HasOne和HasMany关联的`has`方法 +* 改进软删除的关联删除 +* 改进一个字段多次查询条件 + +### [其它] + +* 缓存有效期支持指定过期时间(`DateTime`); +* 改进Url生成对端口号的支持 +* 改进`RouteNotFound`异常提示 +* 改进路由分组的全局完整路由匹配 +* 修正部分验证规则的错误提示问题 +* 支持数据集和模型的XML响应输出 +* 改进模板的三元运算标签 +* 改进控制器不存在的错误提示 +* input助手函数支持`route`变量获取 +* 支持在配置文件中读取额外配置参数 +* 完善分页类 +* 修复Trait命名空间重复问题 +* 修正Request类的env方法 +* 优先使用Cookie中的多语言设置 +* 获取缓存标签的时候过滤无效的缓存标识 +* 修正路由批量注册的一个BUG +* `exception_handle`配置参数支持使用闭包定义`render`处理 +* 请求缓存支持缓存标签设置 +* 缓存类`remember`方法增加并发锁定机制 +* 改进上传类对`swf`的支持 +* 改进Session类的`prefix`方法 + +## 2017-07-04 V5.0.10 + +5.0.10是一个修正版本,并包含了一个安全更新,推荐更新,主要包含: + +* 数据库和模型的多处改进 +* 添加新的行为监听 +* 路由支持Response设置 +* 改进调试模式下数据库敏感信息暴露 + +### [数据库和模型] + +* 修正join其他表时生成的delete语句错误 +* 修正远程一对多 +* insertall支持replace +* 修正多对多默认的中间表获取 +* 改进更新后的模型`update_time`数据更新 +* model类增加`removeRelation`方法 +* 模型类增加`setInc`和`setDec`方法 +* 模型类增加`autoWriteTimestamp`方法动态设置时间字段写入 +* 改进驱动类方法的断线重连判断 +* 改进多对多的数据更新 +* 改进BelongsToMany关联查询 +* 修正Query类的value和column方法 +* 改进in查询的去重问题 +* 修正模型类的scope方法传值问题 +* 调整模型的save方法`before_update`检查位置 +* 修改器和获取器方法支持第三个关联数据参数 + +### [其它] + +* 默认关闭调试模式 +* 修复配置extra目录临时文件的错误加载 +* 添加log存储完成行为监听 `log_write_done` +* 改进Build类生成公共文件的目录判断 +* 增加`response_send`行为监听 +* 路由增加response参数用于绑定response处理行为 +* 改进redirect的参数传入 +* 改进环境变量的布尔值读取 +* 改进Url类的域名传入 +* 修正命令行文件生成 +* 改进命令行下面的URL生成 +* 添加`app_host`参数设置默认的URL根地址 +* 改进`Request`类`isSsl`方法判断支持CDN +* 增加`record_trace`配置参数用于日志记录trace信息 + +## 2017-05-20 V5.0.9 + +5.0.9是一个修正版本,推荐更新,主要更新包含: + +### [数据库和模型] + +* 修正关联自动写入 +* 修正模型数据变化判断对为空数据的支持 +* 修正Query类的useSoftDelete方法返回值 +* 修正一对一嵌套关联数组定义的问题 +* 修正使用了手动参数绑定的时候的缓存BUG +* 改进数据库类的一处不能嵌套查询的缺陷 +* 改进数据库断线重连判断 +* 改进模型的appendRelationAttr方法 +* 改进模型类destroy方法传入空数组的时候不进行任何删除操作 +* 改进一对多关联数据的输出 +* 改进模型的save方法对allowField方法的支持 +* 改进分页类的toarray方法 增加总页数 +* 比较运算增加闭包子查询支持 +* db助手函数默认不再强制重新连接 +* 改进belongsToMany的查询字段指定 +* 分页类增加each方法 + +### [其它] + +* 修正路由分组的路由规则大小写识别问题 +* 修正命令行的日志切割生成 +* 修复URL生成时路由规则中的参数没有进行 urlencode +* 改进Request类的filter过滤机制 支持正则 +* 改进Response类支持手动设置contentType +* 修正异常模板中助手函数未定义错误 + +## 2017-04-28 V5.0.8 + +### 主要调整 + +* 改进关联模型 +* 改进日志记录 +* 增加多态一对一关联 +* 修正社区反馈的一些BUG + +### [ 请求和路由 ] + +* 修正Request类`cookie`方法对前缀的支持 +* 改进全局请求缓存的缓存标识 +* 改进Request类`param`方法 +* 修正别名路由 + +### [ 模型和数据库 ] + +* 改进模型数据的更新检查 +* 改进Query类的`column`方法 +* 改进软删除条件在使用闭包查询情况下多次生成的问题 +* belongsToMany增加数据同步方法 +* 查询范围支持静态调用 +* 增加多态一对一(MorphOne)关联 +* 改进BelongsTo关联 +* 改进多态关联支持关联数据添加和注销 +* 改进多对多关联,支持中间表模型自定义 并且定义的时候不需要使用完整表名 +* 改进浮点数类型转换避免出现逗号 +* 调整关联模型的save方法返回值 +* 模型类的get方法第一个参数必须 如果传入null则返回null +* model的save方法改进如果数据没有更新不执行 +* Query增加`useSoftDelete`方法可以单独设置软删除条件 +* 重载BelongsToMany的`selectOrFail`和`findOrFail`方法 +* 重载BelongsToMany的`select` 、`find`和 `paginate`方法 +* 增加模型和`Pivot`对象的`parent`属性 +* 多对多关联支持设置中间表模型 +* 改进Query类的`view`方法中字段的关键字问题 +* 主从数据库的时候开启事务始终操作主库 + +### [ 其它 ] + +* 改进Cookie类的`get`方法支持获取全部 +* `schema`指令增加`config`参数,支持传入数据库连接配置 +* 改进cache类的`store`方法为当次有效 +* 修正cache助手函数对`option`传参的支持 +* 修复`optimize:autoload`命令在`EXTEND_PATH`目录不存在的情况下,类库映射生成错误问题 +* 支持自定义的根命名空间也可以生成类库映射缓存 +* 验证字段比较支持对比其他字段 +* 修复`Session::prefix('xxx');`设置当前作用域BUG +* 改进`optimize::schema`指令 +* 修复`clear`指令无法删除多级目录下文件的问题 +* 改进默认语言读取和自动侦测 +* 改进日志记录格式 并且命令行下面日志改为实时写入 +* 修正模板标签默认值某些情况无效bug +* 改进Url生成对完整域名的支持 +* 改进`Clear`指令不删除`.gitignore` 文件 +* 修复Memcache缓存驱动的`inc`方法 + +### 调整 + +* 如果自定义了应用的命名空间的话,原来的`app_namespace`配置参数改为`APP_NAMESPACE`常量在入口文件中定义 +* 多对多关联的中间表名称不需要添加表前缀 +* 模型的scope方法之后只能使用数据库查询方法而不能使用模型的方法 + +## 2017-02-24 V5.0.7 + +### 主要调整 + +本次更新主要为BUG修正和改进,主要改进如下: + +* 改进全局请求缓存对子域名的支持; +* 改进数据缓存自动更新机制; +* 关联统计支持指定统计属性名; +* 模型嵌套关联支持数组方式; +* HasOne关联支持`has`和`hasWhere`方法; +* 路由的`ext`和`deny_ext`参数允许设置为空(表示不允许任何后缀或者必须使用后缀访问); + +### 修正如下 + +* 修正 IN / NOT IN 型查询条件为空导致的 sql 语法错误 +* 修正分页类的`toArray`方法对简洁模式的支持 +* 修正Model类`delete`方法对多主键的处理 +* 修正软删除对`Mongodb`的支持 +* 修正`Connection`类一处可能的错误 +* 改进Query类的find方法的缓存机制 +* 修正BelongsTo关联 +* 修正JOIN方式一对一关联预载入闭包查询 +* 修正Query类的`insert`方法一处可能存在的警告错误 +* 修正Model类一处Collection的`use`冲突 +* 修正Model类`hasWhere`方法 +* 修正URl生成对`ext`参数的支持 +* 文件缓存`clear`方法会删除空目录 +* 修正Route类的`parseUrlPath`方法一处问题 + +### 调整如下 + +* 默认关闭session的安全参数`secure`,此选项仅能在HTTPS下设置开启 + +## 2017-02-07 V5.0.6 + +### 主要调整: + +本次更新主要为BUG修正及优化(可无缝升级): + +* 数据库支持断线重连机制; +* 改进查询事件的回调参数; +* 改进数据自动缓存机制; +* 增加时间字段自动格式转换设置; +* `MongoDb`和`Oracle`扩展更新至最新核心框架; + +### [数据库和模型] + +* 修正hasMany关联的`has`方法 +* 去除一些数据库惯例配置 避免使用数据库扩展的时候影响 +* 改进多对多的`attach`方法的返回值 +* 增加Mysql的断线重连机制和开关 +* 改进Query类的`find`方法数据缓存机制 +* 改进Query类查询事件的回调参数 +* 改进Query类的自动缓存更新 +* Model类增加`readonly`方法 +* 改进Model类的`has`和`hasWhere`方法 +* 改进模型类的`get`和`all`方法 第二个参数为true或者数字表示缓存参数 +* 修复闭包查询条件为空导致的 sql 语法错误 +* 改进Query类的`setBuilder`方法 避免因自定义连接器类后找不到生成器类 +* 删除Connection类废弃属性`resultSetType` +* 优化Connection类`close`方法 +* 修正Connection类的`bindParam`方法对存储过程的支持 +* 数据库配置参数`datetime_format` 设置为`false`表示关闭时间字段自动转换输出 +* 改进软删除的数据库兼容性问题 支持`Mongodb` + +### [其它] + +* 改进Url类生成 `root`为`/`的情况 +* redirect助手函数和controller类的redirect方法增加with参数 +* 全局请求缓存添加排除规则 添加request_cache_except配置参数 +* Cache类store方法参数允许为空 表示获取当前缓存驱动句柄 +* 改进Validate类的ip验证规则 + +## 2017-01-23 V5.0.5 +### 主要调整: + +本次更新主要改进了数据访问层和模型关联: + +* 增加快捷查询及设置方法; +* 增加关联统计功能; +* 增加关联查询延迟预载入功能; +* 增加关联一对一自动写入和删除; +* 改进存储过程查询; +* 改进关联数据输出; +* 优化查询性能; +* 模型时间字段自动格式化输出; + +### [请求和路由] + +* 改进路由定义的后缀检测 +* Route类的`rest`方法支持覆盖定义 +* 改进Request类的`put`和`post`方法对`json`格式参数的接收 +* Request类增加`contentType`方法 +* 改进Route类`setRule`方法 +* 改进Request类的`create`方法 +* 改进路由到控制器类的方法对默认渲染模板的影响 +* 修正Url类`build`方法定义路由别名后的BUG + +### [数据库和模型] + +* 增加关联统计功能 +* 增加一对一关联自动写入功能 +* 修正聚合模型的`delete`方法 +* 改进Model类的`useGlobalScope`方法 +* Model类的日期类型支持设置为类名 +* Query类增加`data`/`inc`/`dec`/`exp`方法用于快捷设置数据 `insert`和`update`方法参数可以为空 读取`data`设置数据 +* 优化Connection的查询性能 +* 修正Builder类的`parseOrder`方法 +* 修正BelongsToMany类的`attach`方法 +* BelongsToMany类的`attach`方法改进 支持批量写入 +* 改进BelongsToMany类的`saveall`方法 增加第三个参数 用于指定额外参数是否一致 +* Query类的`order`方法支持多次调用合并 +* 改进`count`方法对`group`查询的支持 +* 增加时间戳自动写入的判断 +* 改进Model类`writeTransform`方法 +* 改进Model的时间戳字段写入和读取 +* 写入数据为对象的时候检测是否有`__toString`方法 +* 改进Mysql驱动的`getFields`方法 +* 改进自动时间字段的输出 +* `like`查询条件支持数组 +* 自动时间字段的获取自动使用时间格式化 +* 改进单个字段多次Or查询情况的查询 +* 修正`null`查询的条件合并 +* 改进Query类`paginate`方法第一个参数可以使用数组参数 +* 改进数据集对象的返回,由Query类的select方法进行数据集转换,原生查询不再支持返回数据集对象 +* 增加`whereNull`、`whereIn`等一系列快捷查询方法 +* `fetchPdo`方法调整 +* 改进对存储过程调用的支持 改进`getRealSql`的调用机制 改进数据表字段使用中划线的参数绑定支持 +* 数据库配置参数增加`result_type` 用于设置数据返回类型 方法参数名称调整 +* 改进Query类的`whereTime`方法支持更多的时间日期表达式(默认查询条件为大于指定时间表达式) +* 取消`min`/`max`/`sum`/`avg`方法的参数默认值 +* Query类增加`getPdo`方法用于返回`PDOStatement`对象 +* 改进`today`的日期表达式查询 +* 改进关联属性的获取 +* 改进关联定义中包含查询条件后重复执行的问题 +* 改进参数绑定支持中文字段自动绑定 +* 改进Builder类的`insertall`方法 增加对null和对象数据的处理 +* 改进参数绑定类型 支持`bit`类型自动绑定 +* Connection类`model`方法更改为`getQuery` +* 优化Connection类`__call`方法 +* 修正聚合模型 +* 一对一关联预载入默认改为IN查询方式 +* 增加`collection`助手函数用于数据集转换 +* 增加`load_relation`助手函数用于数组的延迟预载入 +* 改进Model类的`has`方法第二个参数支持使用数组和闭包,无需再使用`hasWhere` +* `relation`方法支持嵌套关联查询 +* 增加`think\model\Collection`作为模型的数据集查询集合对象 +* 取消关联定义的`alias`参数(仅`morphTo`保留) +* Model类的`delete`方法,支持没有主键的情况 +* Model类的`allowField`方法支持逗号分割的字符串 +* 改进写入数据的自动参数绑定的参数名混淆问题 +* 关联预载入查询的属性名默认使用小写+下划线命名 +* Query类的`with`和`relation`方法支持多次调用 +* Collection类增加`hidden`、`visible`和`append`方法 +* 修正软删除的强制删除方法 + +### [其它] + +* `unique`验证规则支持指定完整模型类 并且默认会优先检测模型类是否存在 不存在则检测数据表 +* 改进`Loader`类的`model`、`controller` 和 `validate`方法 支持直接传入类名实例化 +* `Session`类增加安全选项`httponly`和`secure` +* 可以允许自定义`Output`的driver,以适应命令行模式下调用其它命令行指令 +* 改进`loader`类`action`的参数污染问题 +* Validate类的`confirm`验证改为恒等判断 +* 改进`Validate`类的错误信息处理 +* 修正`Validate`类的布尔值规则验证 +* 改进`cookie`助手函数对前缀的支持 +* 文件缓存默认开启子目录缓存避免文件过多导致性能问题 + +### [调整] +* Connection类`model`方法更改为`getQuery` +* 原生查询不再支持返回数据集对象 +* 分页查询返回类型变成`think\Paginator`(用法不变) +* 模型的时间日期字段会自动进行格式化输出,不需要进行额外处理。 +* Session类添加了`secure`和`httponly`参数,并且默认是true + +## 2016-12-20 V5.0.4 +### 主要调整: + +* 关联模型重构并增加多态一对多关联; +* 数据库支持一个字段多次调用不同查询条件; +* 增加数据库CURD事件支持; +* 路由到类和控制器的方法支持传入额外参数; +* 支持全局模板变量赋值; +* 模型支持独立设置查询数据集对象; +* 日志针对命令行及调试做出改进; +* 改进Hook类的行为方法调用 + +### [请求和路由] +* 请求缓存支持模块单独开启 +* Request类`post`方法支持获取`json`方式的请求数据 +* 路由到类的方法和控制器方法 支持传入额外参数,用于方法的参数 +* 改进控制器自动搜索的目录规范 +* 改进请求缓存 +* 改进自动参数绑定 +* 修正路由的请求缓存设置 +* 改进Route类name方法 + +### [数据库和模型] +* 增加数据库查询(CURD)事件 +* 改进多表更新的字段不存在问题 +* 改进Model类的`useGlobalScope`方法 +* 修正子查询作为表名查询的问题 +* Model类增加`resultSetType`属性 用于指定模型查询的数据集对象(默认为空返回数组) +* Model类增加`toCollection`方法(自动调用) +* 关联模型架构调整 +* 改进预载入`with`方法的参数支持小写和下划线定义 +* 修正关联多对多一处错误 +* 改进关联多对多的查询 +* 关联模型支持多态一对多关联 +* 预载入关联查询支持关联对象属性绑定到当前模型 +* 支持追加关联对象的属性到当前模型数据 +* 一对一关联预载入支持JOIN和IN两种方式(默认为JOIN) +* 改进多对多查询 +* 改进模型更新的数据变化比较规则 +* 查询支持一个字段多次查询条件 +* 改进sql日志的sql语句 +* 修正`join`自身表的别名覆盖问题 +* 模型类的`connection`属性和数据库默认配置合并 +* 改进`in`和`between`查询条件的自动参数绑定 +* 改进Query类对数据集对象以及关联字段排序的支持 +* 增加模型的快捷事件方法 +* 改进Query类的`getTableInfo`方法缓存读取 +* model类的`saveAll`方法支持调用`allowField`方法进行字段过滤 +* 修正关联查询的时候 `whereTime`方法的bug +* 改进Query类的聚合查询 +* table方法支持字符串方式的子查询 +* 修正`count` `avg`方法使用`fetchsql`无法正确返回sql的问题 + +### [其它] +* 改进命令行下的日志记录 +* 部署模式下简化日志记录 +* 增加debug日志类型 仅限调试模式记录 +* 改进Template类`parseTemplateFile`方法 +* 改进Validate类的`getRuleMsg`方法 +* 控制器的`error`方法在AJAX请求默认返回url为空 +* Validate类架构方法增加`field`参数 用于设置验证字段的描述 +* 改进App类`invokeMethod`方法对架构函数依赖注入的支持 +* 增加RedirectResponse的`restore`方法返回值 +* View类增加`share`静态方法 用于静态赋值模板变量 +* 验证类增加`hasScene`方法判断是否存在某个场景的验证配置 +* 修正redis和session驱动的`destroy`方法返回值 +* 空操作方法的参数传入去掉操作方法后缀 +* 在控制器中调用request和view增加类型提示 +* 改进`input`助手函数支持多维数据获取 +* Cache类增加`pull`和`remember`方法 +* 改进验证类的`confirm`验证规则 支持自动规则识别 +* 改进验证类的错误信息定义 +* 增加Validate类自定义验证错误信息的替换规则 +* Cookie类增加`forever`方法用于永久保存 +* 模板渲染支持从视图根目录读取模板 +* 改进Hook类的exec方法 + +### [调整] +* Db类查询不再支持设置自定义数据集对象 +* 废除Query类的`fetchClass`方法 +* 控制器的`error`方法在AJAX请求默认返回的url为空 +* 关联方法定义不支持使用小写下划线,必须使用驼峰法 +* 行为类的方法必须使用驼峰法命名 + +## 2016-11-11 V5.0.3 +### 主要调整: +* 请求缓存增强; +* 路由增强; +* 数据库和模型完善; +* 支持反射的异常捕获; +* File类改进; +* 修正社区反馈的一些BUG; + +### [ 请求和路由 ] + +* 资源路由自动注册的路由规则的时候会记录当前使用的资源标识; +* 增强请求缓存功能和规则定义,支持全局自动缓存 +* 修正控制器自动搜索的大小写问题 +* 修正路由绑定到命名空间后 类的自动定位 +* 改进Route类的parseRule方法 路由地址中的变量替换不自动去除路由变量 +* 改进控制器自动搜索 +* Route类增加setOption和getOption方法 用于记录当前路由执行过程中的参数信息 +* 优化路由分组方法 +* 改进分组路由的url生成 + +### [ 数据库和模型 ] + +* 一对一关联查询方法支持定义`field`方法 +* 聚合模型支持设置`field`属性 +* 改进Query类的`alias`方法 +* 改进Query类`join`和`view`方法的table参数 +* 改进Query类`where`方法 +* 改进Query类的`paginate`方法,支持`order`方法 +* 改进Query类的`min`和`max`方法支持日期类型 +* 修正软删除`withTrashed`方法 +* 优化Connection类的`getRealSql`方法生成的sql + +### [ 其它 ] +* 增加request_cache和request_cache_expire配置参数用于配置全局请求缓存; +* 修正input助手函数的数组过滤 +* cache助手函数支持清空操作 +* 改进Config类load方法 一级配置名称强制转为小写 +* 修正Url多次生成的问题 +* File类修正某些环境下面无法识别上传文件的问题 +* 改进App类的空操作方法调用 +* 域名部署URL生成不依赖 url_domain_deploy 配置参数 +* 修正Url类域名部署的问题 +* 视图文件目录支持集中式存放 不放入模块目录 +* cache助手函数支持 remember方法 +* Request类的input方法或者input助手函数的`filter`参数支持传入null 表示不过滤 + +## 2016-10-24 V5.0.2 +### 主要调整: + +* 数据库和模型完善; +* 路由功能完善; +* 增加`yaml`配置格式支持; +* 依赖注入完善; +* Session类完善; +* Cookie类完善; +* Validate类完善; +* 支持反射类的异常捕获; +* 修正社区反馈BUG; + +### [ 请求和路由 ] +* 依赖注入的类如果定义了`invoke`方法则自动调用 +* Request类的`header`方法增加自定义header支持 +* Request类禁止直接实例化调用 +* 改进Request类ip方法 +* 路由变量规则支持闭包定义 +* 路由参数增加`ajax`和`pjax`判断 +* 别名路由增加允许和排除操作 +* 改进路由域名绑定后的url生成 +* 路由生成改进对路由到类的支持 +* 路由生成支持`url_param_type`配置参数 +* 路由生成支持别名路由 +* Route重定向规则支持更多` schema` +* 别名路由支持定义单独方法的请求类型 +* 改进路由分组的url生成 +* 路由规则的组合变量支持可选分隔符定义 +* 改进路由合并参数的获取 +* 路由规则支持单独设置url分隔符,路由参数为 `param_depr` +* 自动搜索控制器支持自定义访问控制器层的情况 +* 改进路由标识不区分大小写 +* 改进路由地址是否定义过路由规则的检测 + +### [ 数据库和模型 ] +* 改进Query类的join方法 +* 改进Query类分页方法的参数绑定 +* 修正软删除方法 +* 修正Query类parseOrder方法一处错误 +* 修正sqlsrv驱动parseOrder方法 +* 修正Query类setInc和setDec方法 +* 改进Model类的save方法支持非自增主键的处理 +* 整型字段的参数绑定如果为空写入默认值0 +* 改进Model类has和hasWhere方法 +* 改进Query类的value方法缓存判断 +* 改进Query类join方法对子查询支持 +* 改进Query类的table方法和alias方法用法 +* 关联预载入支持`hasOne`自关联 +* 改进Builder类的parseKey方法 +* 改进Builder类的join/alias/table方法的解析 +* 改进全局查询范围 +* 改进Query类的聚合查询方法的返回值 +* 改进关联属性的读取 +* 改进聚合模型主键和关联键相同的情况 +* 改进模型在开启`class_suffix`参数情况下的name属性的识别 + +### [ 其它 ] +* Cache类增加`remember`方法 用于当获取的缓存不存在的时候自动写入 +* Session类增加`flash`方法用于设置下一次请求有效的值 +* Session类增加`flush`方法用于清空当前请求有效的值 +* Session类增加`push`方法用于更新数组数据 +* 增加yaml配置格式支持 +* 改进App类的反射异常无法捕获问题 +* 修正session助手函数的清空操作 +* 改进验证类的`image`方法 +* 改进验证类的`activeUrl`方法 +* 改进自定义验证规则的使用 +* 改进控制器自动搜索后的控制器名获取 +* 修正import方法加载extend目录类库 +* 修正json_encode时 "Failed calling XXX::jsonSerialize()" 的异常 +* 改进Loader类model和validate方法的单例问题 +* 改进方法执行的日志记录 +* 改进模板引擎的Think变量解析 +* 改进Lang类`load`方法 +* 验证错误信息支持多语言读取 +* 改进ROOT_PATH常量 +* 改进语言包加载 +* 改进模板session和cookie变量获取,自动判断前缀 +* 缓存驱动统一增加handler方法用于获取操作对象的句柄(某些缓存类型可能为null) +* File类增加`__call`方法用于兼容5.0版本的`md5`和 `sha1`方法 +* 改进文件缓存驱动的`clear`方法 +* Lang类增加`setLangCookieExpire`方法设置多语言cookie过期时间 +* 增加`route_complete_match`配置参数 + +### [ 调整 ] +下列模型属性和方法由原来的静态(static)定义改为动态定义: +* 聚合模型的`relationModel`属性 +* Model类的`useGlobalScope `属性 +* 全局查询范围方法`base`改为动态方法 +* 软删除属性 `deleteTime`属性 + + +## 2016-9-28 V5.0.1 +### 主要调整: +* [依赖注入](215849)完善; +* [扩展配置](118027)文件位置调整; +* 新增数据表[字段缓存命令](211524); +* 支持设置当前的查询对象; +* 支持[请求和路由缓存](215850); + +### [ 请求和路由 ] +* 改进Controller类的`success`和`error`方法的跳转地址识别 支持更多Scheme +* 操作方法和架构方法支持任何对象自动注入 +* Requesst类增加`getInput`方法 用于获取` php://input`值 +* 路由到方法的时候 支持架构方法注入请求对象 +* 改进Route类路由到类的判断 +* Request增加`cache`方法,支持请求缓存 +* 绑定到模块后 路由依然优先检查 +* 路由增加请求缓存参数 +* 修正路由组合变量的可选变量的BUG + +### [ 数据库 ] +* 修正`pgsql`数据库驱动的数据表字段信息读取 +* 改进Query类的`view`方法 第二个参数默认值更改为true 获取全部的字段 +* 数据库配置信息增加`query`参数用于配置查询对象名称 +* 型类增加`query`属性用于配置模型需要的查询对象名称 +* 改进数据表字段缓存读取 +* 改进数据表字段缓存生成 模型为抽象类或者 没有继承Model类 不生成字段缓存 +* 改进模型的字段缓存 虚拟模型不生成字段缓存 +* 改进数据表字段缓存生成 支持读取模块的模型生成 +* 改进聚合模型的`save`方法 主键写入 +* 模型类的field属性定义简化 取消`Query`类的`allowField`和`setFieldType`方法及相关属性 +* 改进数据表字段缓存生成 支持生成多个数据库的 +* 更新数据库驱动类 改进`getTables`方法 +* 增加` optimize:schema` 命令 用于生成数据表字段信息缓存 +* 修正一个查询条件多个条件的时候的参数绑定BUG +* 分页查询方法`paginate`第二个参数传入数字表示总记录数 +* 修正mysql的`JSON`字段查询 +* 改进Query类的getOptions方法 当name参数不存在的时候返回null + +### [ 模型和关联 ] +* 模型类的field属性不需要添加字段类型定义 +* 改进Model类 添加`getDb`静态方法获取db查询对象 +* 改进聚合模型`save`方法返回值 +* 改进Relation类`save`方法 +* 修正关联模型 多对多`save`方法一处问题 +* 改进Model类的save方法 修正不按主键查询的更新问题 +* 时间字段获取器获取的时候为NULL则不做转换 + +### [ 其它 ] + +* 改进配置缓存生成 支持扩展配置 +* 取消`extra_config_list`配置参数 扩展配置文件直接放到 `extra`目录下面即可自动加载(数据库配置文件位置不变) +* cache助手函数支持判断缓存是否有效 +* 修正 模板引擎驱动类的`config`方法 +* 修复在配置Model属性field=true情况下,通过`__call`调用db()引发的BUG +* 改进模板引擎驱动的config方法 支持获取配置参数值 +* 改进redirct的url地址解析 +* 删除`File`类的`md5`和`sha1`方法 改为`hash`方法 支持更多的散列值类型生成 +* 增加`response_end`行为标签 +* 改进默认语言的加载 + +## 2016-9-15 V5.0 + +### [ 请求和路由 ] + +* Request对象支持动态绑定属性 +* 定义了路由规则的URL原地址禁止访问 +* 改进路由规则存储结构 +* 路由分组功能增强,支持嵌套和虚拟分组 +* 路由URL高效反解 +* 改进Request对象param方法获取优先级 +* 路由增加name方法设置和获取路由标识 +* 增加MISS和AUTO路由规则 +* Route类增加auto方法 支持注册一个自动解析URL的路由 +* 路由规则支持模型绑定 +* 路由变量统一使用param方法获取 +* 路由规则标识功能和自动标识 +* 增加生成路由缓存指令 optimize:route +* Request对象增加route方法单独获取路由变量 +* Request对象的param get post put request delete server cookie env方法的第一个参数传入false 则表示获取原始数据 不进行过滤 +* 改进自动路由标识生成 支持不同的路由规则 指向同一个路由标识,改进Url自动生成对路由标识的支持 +* 改进Request类 filter属性的初始化 +* 改进Request类的isAjax和isPjax方法 +* Request类增加token方法 +* 路由配置文件支持多个 使用 route_config_file 配置参数配置 +* 域名绑定支持https检测 +* 改进域名绑定 支持同时绑定模块和其他 支持绑定到数组定义的路由规则,取消域名绑定到分组 +* 路由规则增加PATCH请求类型支持 +* 增加route_complete_match配置参数设置全局路由规则定义是否采用完整匹配 可以由路由规则的参数complete_match 进行覆盖 +* 改进路由的 后缀参数识别 优先于系统的伪静态后缀参数 +* Url类增加root方法用于指定当前root地址(不含域名) +* 改进Url生成对可选参数的支持 + +### [ 数据库 ] + +* 查询条件自动参数绑定 +* 改进分页方法支持参数绑定 +* Query类的cache方法增加缓存标签参数 +* Query类的update和delete方法支持调用cache方法 会自动清除指定key的缓存 配合查询方法的cache方法一起使用 +* 改进Query类的延迟写入方法 +* Query类的column和value方法支持fetchsql +* 改进日期查询方法 +* 改进存储过程方法exec的支持 +* 改进Connection类的getLastInsID方法获取 +* 记录数据库的连接日志(连接时间和DSN) +* 改进Query类的select方法的返回结果集判断 +* Connection类增加getNumRows方法 +* 数据库事务方法取消返回值 +* 改进Query类的chunk方法对主键的获取 +* 改进当数据库驱动类型使用完整命名空间的时候 Query类的builder方法的问题 + +### [ 模型 ] + +* 增加软删除功能 +* 关联模型和预载入改进 +* 关联预载入查询闭包支持更多的连贯操作 +* 完善savell方法支持更新和验证 +* 关联定义统一返回Relation类 +* Model类的has和hasWhere方法对join类型的支持 +* Model类的data方法 批量赋值数据的时候 清空原始数据 +* Model类的get方法第三个参数传入true的时候会自动更新缓存 +* Model类增加只读字段支持 +* Model类增加useGlobalScope方法设置是否启用全局查询范围 +* Model类的base方法改为静态定义 全局多次调用有效 +* Model类支持设定主键、字段信息和字段类型,不依赖自动获取,提高性能 +* Model类的data方法 支持修改器 +* 改进Relation类对非数字类型主键的支持 +* 改进Relation类的一对多删除 +* 修正Relation类的一对多关联预载入查询 + +### [ 日志和缓存 ] + +* 支持日志类型分离存储 +* 日志允许设置记录级别 +* 增加缓存标签功能 +* 缓存类增加pull方法用于获取并删除 +* cache助手函数增加tag参数 +* 简化日志信息,隐藏数据库密码 +* 增加cache/session redis驱动的库选择逻辑; +* memcached驱动的配置参数支持option参数 +* 调试模式下面 日志记录增加页面的header和param参数记录 +* memcached缓存驱动增加连接账号密码参数 +* 缓存支持设置complex类型 支持配置多种缓存并用store切换 +* 缓存类增加tag方法 用于缓存标签设置 clear方法支持清除某个缓存标签的数据 +* File类型日志驱动支持设置单独文件记录不同的日志级别 +* 改进文件缓存和日志的存储文件名命名规范 +* 缓存类增加inc和dec方法 针对数值型数据提供自增和自减操作 +* Cache类增加has方法 get方法支持默认值 + +### [ 其它 ] + +* 视图类支持设置模板引擎参数 +* 增加表单令牌生成和验证 +* 增加中文验证规则 +* 增加image和文件相关验证规则 +* 重定向Response对象支持with方法隐含传参 +* 改进Session类自动初始化 +* session类增加pull方法用于获取并删除 +* 增加Env类用于获取环境变量 +* Request类get/post/put等更改赋值后param方法依然有效 +* 改进Jump跳转地址支持Url::build 解析 +* 优化Hook类 +* 应用调试模式和页面trace支持环境变量设置 +* config助手函数支持 config('?name') 用法 +* 支持使用BIND_MODULE常量的方式绑定模块 +* 入口文件自动绑定模块功能 +* 改进验证异常类的错误信息和模板输出,支持批量验证的错误信息抛出 +* 完善console 增加output一些常用的方法 +* 增加token助手函数 用于在页面快速显示令牌 +* 增加halt方法用于变量调试并中断输出 +* 改进Validate类的number验证规则 和 integer区分开 +* optimize:autoload增加对extend扩展目录的扫描 +* 改进Validate类的boolean验证规则 支持表单数据 +* 改进cookie助手函数支持 判断是否存在某个cookie值 +* 改进abort助手函数 支持抛出HttpResponseException异常 +* 改进File类增加对上传错误的处理 +* 改进File类move方法的返回对象增加上传表单信息,增加获取文件散列值的方法 +* 改进File类的move方法的返回对象改为返回File对象实例 +* 增加clear和optimize:config 指令 +* 改进File类和Validate类的图像文件类型验证 +* 控制器的操作方法支持注入Request之外的对象实例 +* Request类 param(true) 支持获取带文件的数据 +* input助手函数第一个参数增加默认值 +* Validate类增加image验证规则 并改进max min length支持多种数据类型 +* json输出时数据编码失败后抛出异常 + +### [ 调整 ] +* 废除路由映射(静态路由)定义 +* 取消url_deny_suffix配置 改由路由的deny_ext参数设置 +* 模型save方法返回值改为影响的记录数,取消getId参数 +* Request对象controller方法返回驼峰控制器名 +* 控制器前置操作方法不存在则抛出异常 +* Loader类db方法增加name标识参数 +* db助手函数增加第三个参数用于指定连接标识 +* Sqlsrv驱动默认不对数据表字段进行小写转换 +* 移除sae驱动 改为扩展包 +* Oracle驱动移出核心包 +* Firebird驱动移出核心包 +* 取消别名定义文件alias.php +* 配置参数读取的时候取消环境变量判断 需要读取环境变量的时候使用Env类 +* 环境变量定义文件更改为 .env 由原来的PHP数组改为ini格式定义(支持数组方式) +* 状态配置和扩展配置的加载顺序调整 便于状态配置文件中可以更改扩展配置的参数 +* 取消域名绑定到路由分组功能 +* 控制器类的success和error方法url参数支持传入空字符串,则不做任何处理 +* 控制器的error success result redirect方法均不需要使用return +* 创建目录的权限修改为0644 + + +## 2016-7-1 RC4版本 +### [ 底层架构 ] +* 增加Request类 并支持自动注入 +* 统一Composer的自动加载机制 +* 增加Response类的子类扩展 +* 增加File类用于上传和文件操作 +* 取消模式扩展 SAE支持降权 +* 优化框架入口文件 +* 改进异常机制 +* App类输入/输出调整 +* 单元测试的完美支持 +* 增加新的控制台指令 +* 取消系统路径之外的大部分常量定义 +* 类库映射文件由命令行动态生成 包含应用类库 + +### [ 数据库 ] + +* 增加分表规则方法 +* 增加日期和时间表达式查询方法 +* 增加分页查询方法 +* 增加视图查询方法 +* 默认保持数据表字段大小写 +* 数据缓存自动更新机制 +* 完善事务嵌套支持 +* 改进存储过程数据读取 +* 支持设置数据库查询数据集返回类型 + +### [ 模型 ] +* 增加Merge扩展模型 +* 模型支持动态查询 +* 增加更多的类型自动转换支持 +* 增加全局查询范围 +* toJson/toArray支持隐藏和增加属性输出 +* 增加远程一对多关联 + +### [ 其它 ] +* 日志存储结构调整 +* Trace调试功能从日志类独立并增强 +* 原Input类功能并入Request类 +* 类库映射文件采用命令行生成 包含应用类库 +* 验证类的check方法data数据取消引用传参 +* 路由增加MISS路由规则 +* 路由增加路由别名功能 + +## 2016-4-23 RC3版本 +### [ 底层架构 ] +* 框架核心仓库和应用仓库分离 便于composer独立更新 +* 数据库类重构,拆分为Connection(连接器)/Query(查询器)/Builder(SQL生成器) +* 模型类重构,更加对象化 + +### [ 数据库 ] + +* 新的查询语法 +* 闭包查询和闭包事务 +* Query对象查询 +* 数据分批处理 +* 数据库SQL执行监听 + +### [ 模型 ] +* 对象化操作 +* 支持静态调用(查询) +* 支持读取器/修改器 +* 时间戳字段 +* 对象/数组访问 +* JSON序列化 +* 事件触发 +* 命名范围 +* 类型自动转换 +* 数据验证和完成 +* 关联查询/写入 +* 关联预载入 + +### [ 其它更新 ] +* 路由类增加快速路由支持 +* 验证Validate类重构 +* Build类增加快速创建模块的方法 +* Url生成类改进 +* Validate类改进 +* View类及模板引擎驱动设计改进 +* 取消模板引擎的模板主题设计 +* 修正社区反馈的一些问题 +* 助手函数重新命名 +* `router.php`文件位置移动 + +## 2016-3-11 RC2版本 + +* 重新设计的自动验证和自动完成机制(原有自动验证和完成支持采用traits\model\Auto兼容); +* 验证类Validate独立设计; +* 自动生成功能交给Console完成; +* 对数据表字段大小写的处理; +* 改进Controller类(取消traits\contorller\View); +* 改进Input类; +* 改进Url类; +* 改进Cookie类; +* 优化Loader类; +* 优化Route类; +* 优化Template类; +* Session类自动初始化; +* 增加traits\model\Bulk模型扩展用于大批量数据写入和更新; +* 缓存类和日志类增加Test驱动; +* 对异常机制和错误处理的改进; +* 增加URL控制器和操作是否自动转换开关; +* 支持类名后缀设置; +* 取消操作绑定到类的功能; +* 取消use_db_switch参数设计; + +## 2016-1-30 RC1版本 +### [ 底层架构 ] + +* 真正的惰性加载 +* 核心类库组件化 +* 框架引导文件 +* 完善的类库自动加载(支持Composer) +* 采用Traits扩展 +* API友好(输出、异常和调试) +* 文件命名规范调整 + +### [ 调试和异常 ] + +* 专为API开发而设计的输出、调试和异常处理 +* 日志类支持本地文件/SAE/页面Trace/SocketLog输出,可以实现远程浏览器插件调试 +* 内置trace方法直接远程调试 +* 异常预警通知驱动设计 +* 数据库SQL性能分析支持 + +### [ 路由 ] + +* 动态注册路由 +* 自定义路由检测方法 +* 路由分组功能 +* 规则路由中的变量支持采用正则规则定义(包括全局和局部) +* 闭包路由 +* 支持路由到多层控制器 + +### [ 控制器 ] + +* 控制器类无需继承controller类 +* 灵活的多层控制器支持 +* 可以Traits引入高级控制器功能 +* rest/yar/rpc/hprose/jsonrpc控制器扩展 +* 前置操作方法支持排除和指定操作 + + +### [ 模型 ] + +* 简化的核心模型 +* Traits引入高级模型/视图模型/关联模型 +* 主从分布时候主数据库读操作支持 +* 改进的join方法和order方法 + +### [ 视图 ] + +* 视图解析驱动设计(模板引擎) +* 所有方法不再直接输出而是返回交由系统统一输出处理 +* 动态切换模板主题设计 +* 动态切换模板引擎设计 + +### [ 数据库 ] + +* 完全基于PDO实现 +* 简化的数据库驱动设计 +* SQL性能监控(需要开启数据库调试模式) +* PDO参数绑定改进 + +### [ 其他方面 ] + +* 目录和MVC文件自动生成支持 +* I函数默认添加变量修饰符为/s +* 一个行为类里面支持为多个标签位定义不同的方法 +* 更多的社交扩展类库 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2cb9a8a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +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 THE +COPYRIGHT OWNER OR CONTRIBUTORS 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. diff --git a/application/.htaccess b/application/.htaccess new file mode 100644 index 0000000..e69de29 diff --git a/application/api/controller/Base.php b/application/api/controller/Base.php new file mode 100644 index 0000000..86244bb --- /dev/null +++ b/application/api/controller/Base.php @@ -0,0 +1,32 @@ + $this->ERROR_CODE, + 'msg' => $msg, + 'data' => $data + ]; + return json_encode($arr); + } + + public function jsonSuccess($msg = '成功',$data = []) + { + $arr = [ + 'code' => $this->SUCCESS_CODE, + 'msg' => $msg, + 'data' => $data + ]; + return json_encode($arr); + } + +} \ No newline at end of file diff --git a/application/api/controller/Information.php b/application/api/controller/Information.php new file mode 100644 index 0000000..4e6f09d --- /dev/null +++ b/application/api/controller/Information.php @@ -0,0 +1,50 @@ +param(); + + try { + + $InformationValidate = Loader::validate('InformationValidate'); + if(!$InformationValidate->check($param)){ + throw new ValidateException($InformationValidate->getError()); + } + + $insertArr = [ + 'tenant_id' => $param['tenantId'], + 'task_id' => $param['taskId'], + 'protocol' => $param['protocol'], + 'product_id' => $param['productId'], + 'device_id' => $param['deviceId'], + 'timestamp' => $param['timestamp'], + 'create_time' => date("Y-m-d H:i:s"), + ]; + + Db::table('bs_device_receiving_information')->insert($insertArr); + + return $this->jsonSuccess(); + } catch (ValidateException $exception) { + return $this->jsonError($exception->getMessage()); + } + } + +} \ No newline at end of file diff --git a/application/api/validate/InformationValidate.php b/application/api/validate/InformationValidate.php new file mode 100644 index 0000000..19ea5d2 --- /dev/null +++ b/application/api/validate/InformationValidate.php @@ -0,0 +1,18 @@ + 'require', + 'taskId' => 'require', + 'protocol' => 'require', + 'productId' => 'require', + 'deviceId' => 'require', + 'timestamp' => 'require' + ]; +} \ No newline at end of file diff --git a/application/command.php b/application/command.php new file mode 100644 index 0000000..826bb2b --- /dev/null +++ b/application/command.php @@ -0,0 +1,12 @@ + +// +---------------------------------------------------------------------- + +return []; diff --git a/application/common.php b/application/common.php new file mode 100644 index 0000000..55d22f2 --- /dev/null +++ b/application/common.php @@ -0,0 +1,12 @@ + +// +---------------------------------------------------------------------- + +// 应用公共文件 diff --git a/application/config.php b/application/config.php new file mode 100644 index 0000000..3f64a59 --- /dev/null +++ b/application/config.php @@ -0,0 +1,243 @@ + +// +---------------------------------------------------------------------- + +return [ + // +---------------------------------------------------------------------- + // | 应用设置 + // +---------------------------------------------------------------------- + + // 应用调试模式 + 'app_debug' => true, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 扩展函数文件 + 'extra_file_list' => [THINK_PATH . 'helper' . EXT], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // URL伪静态后缀 + 'url_html_suffix' => '', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由 + 'url_route_on' => true, + // 路由使用完整匹配 + 'route_complete_match' => false, + // 路由配置文件(支持配置多个) + 'route_config_file' => ['route'], + // 是否开启路由解析缓存 + 'route_check_cache' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 域名部署 + 'url_domain_deploy' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // 视图输出字符串内容替换 + 'view_replace_str' => [], + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 异常页面的模板文件 + 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + 'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => 'think', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], +]; diff --git a/application/database.php b/application/database.php new file mode 100644 index 0000000..9e1af98 --- /dev/null +++ b/application/database.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +return [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'iotdata', + // 用户名 + 'username' => 'root', + // 密码 + 'password' => 'root', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => 'bs_', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 自动读取主库数据 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, +]; diff --git a/application/extra/queue.php b/application/extra/queue.php new file mode 100644 index 0000000..9223ef6 --- /dev/null +++ b/application/extra/queue.php @@ -0,0 +1,14 @@ + +// +---------------------------------------------------------------------- + +return [ + 'connector' => 'Sync' +]; diff --git a/application/index/controller/Administrator.php b/application/index/controller/Administrator.php new file mode 100644 index 0000000..b6e6b96 --- /dev/null +++ b/application/index/controller/Administrator.php @@ -0,0 +1,896 @@ +error('亲,您还没有登录呢!', '/'); + } + } + + public function quit() + { + session(null); + $this->redirect('/'); + } + +//////////////////////////////////////// +//////////////////////////////////////// +//////////////////////////////////////// +/////////以下为页面跳转函数///////////// +//////////////////////////////////////// + + + //主页 + public function index() + { + $name = $value = session('name'); + $device = Db::table('bs_device_map')->select(); + $this->assign('name', $name); + $this->assign('dev', $device); +// dump($device); + return $this->fetch(); + } + +//统计页面 + public function statistics() + { + return $this->fetch(); + } + +//我的桌面 + public function welcome() + { + $data = $value = session('name'); + $this->assign('data', $data); + return $this->fetch(); + } + +//管理员列表 + public function admin_list() + { + return $this->fetch(); + } + + //用户列表 + public function user_list() + { + return $this->fetch(); + } + +//设备总览 + public function device() + { + $device = Db::table('bs_device_map')->select(); + + for ($i = 0; $i < count($device); $i++) { + $device[$i]['all'] = Db::table('bs_device_serial' )->where('type',$device[$i]['function']) ->count(); + $device[$i]['error'] = Db::table('bs_device_' . $device[$i]['function'])->where('status', 0)->count(); + } + $this->assign('dev', $device); + return $this->fetch(); + } + +// 列表中所有设备共用模板函数 + public function device_list() + { + $request = Request::instance(); + $device = $request->get(); +// dump($device); + $this->assign('dev', $device); + return $this->fetch(); + } + +// 日志记录 + public function log() + { + return $this->fetch(); + } + + + + + +//////////////////////////////////////// +//////////////////////////////////////// +//////////////////////////////////////// +//////////以下为功能函数//////////////// +//////////////////////////////////////// + +//将所有固定表中的ID重新排序 + public function resort() + { +// 重新排序bs_people表 + Db::execute("ALTER TABLE bs_people DROP id;"); + Db::execute("alter table bs_people add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_user表 + Db::execute("ALTER TABLE bs_user DROP id;"); + Db::execute("alter table bs_user add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_administrator表 + Db::execute("ALTER TABLE bs_administrator DROP id;"); + Db::execute("alter table bs_administrator add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_device_map表 + Db::execute("ALTER TABLE bs_device_map DROP id;"); + Db::execute("alter table bs_device_map add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_device_serial表 + Db::execute("ALTER TABLE bs_device_serial DROP id;"); + Db::execute("alter table bs_device_serial add id int(11) not null auto_increment primary key first;"); + + } + +//停用、恢复 管理员 + public function banAdmin(Request $request) + { + $receive = $request->post(); + $flag1 = false; + $flag2 = false; + if ($receive['type'] == 'true') { + $flag1 = Db::table('bs_administrator')->where('account', $receive['account'])->update(['status' => 1]); + } else { + $flag2 = Db::table('bs_administrator')->where('account', $receive['account'])->update(['status' => 0]); + } + if ($flag1 || $flag2) { + if ($flag1) { + $this->logInsert('停用管理员:' . $receive['account']); + }; + if ($flag2) { + $this->logInsert('恢复管理员:' . $receive['account']); + } + return 'ok'; + } else { + return '后台数据发生错误,请联系后台管理员'; + } + } + +// 禁用用户 + public function banUser(Request $request) + { + $receive = $request->post(); + $flag1 = false; + $flag2 = false; + if ($receive['type'] == 'true') { + $flag1 = Db::table('bs_user')->where('uid', $receive['uid'])->update(['status' => 1]); + } else { + $flag2 = Db::table('bs_user')->where('uid', $receive['uid'])->update(['status' => 0]); + } + if ($flag1 || $flag2) { + if ($flag1) { + $this->logInsert('冻结用户:' . $receive['uid']); + }; + if ($flag2) { + $this->logInsert('恢复用户:' . $receive['uid']); + } + return 'ok'; + } else { + return '后台数据传输错误,请联系后台管理员'; + } + } + +// 禁用设备ID对应的设备 + public function banDev(Request $request) + { + $data = $request->post(); + $flag1 = false; + $flag2 = false; + //根据设备名获得对应的标识符 + $deviceName = (Db::table('bs_device_map')->where('chinese', $data['deviceName'])->find())['function']; + //在对应的数据库表中将该设备ID的状态更新 + if ($data['type'] == 'true') { + $flag1 = Db::table('bs_device_' . $deviceName)->where('serial_num', $data['serial_num'])->update(['status' => 1]); + } else { + $flag2 = Db::table('bs_device_' . $deviceName)->where('serial_num', $data['serial_num'])->update(['status' => 0]); + } + if ($flag1 || $flag2) { + return 'ok'; + } else { + return '后台数据传输错误,请联系后台管理员'; + } + } + +//单个删除管理员 + public function delAdmin(Request $request) + { + $receive = $request->post(); +// 从总账户表中删除该管理员 + $flag1 = Db::table('bs_administrator')->where('account', $receive['account'])->delete(); +// 从管理员表中删除该管理员 + $flag2 = Db::table('bs_people')->where('name', $receive['account'])->delete(); + $this->resort(); + if ($flag1 && $flag2) { + $this->logInsert('删除管理员:' . $receive['account']); + return 'ok'; + } else { + if (!$flag1) { + return 'ERROR1'; + }; + if (!$flag2) { + return 'ERROR2'; + } + } + } + +//单个删除用户 + public function delUser(Request $request) + { + $receive = $request->post(); +// 在用户表中删除该用户 + $flag1 = Db::table('bs_user')->where('uid', $receive['uid'])->delete(); +// 在用户设备统计表中删除该用户 + $flag2 = Db::table('bs_device')->where('uid', $receive['uid'])->delete(); +// 在总账户表中删除该用户 + $flag3 = Db::table('bs_people')->where('name', $receive['uid'])->delete(); +// 判断接收到的指令是否要删除该用户与设备的绑定信息,如果是则遍历每个设备绑定信息表删除 + if ($receive['more']) { + $deviceName = Db::table('bs_device_map')->field('function')->select(); + for ($i = 0; $i < count($deviceName); $i++) { + $devArr[] = $deviceName[$i]['function']; + }; + for ($i = 0; $i < count($devArr); $i++) { + Db::table('bs_device_' . $devArr[$i])->where('uid', $receive['uid'])->delete(); + } + + } + $this->resort(); + if ($flag1 && $flag2 && $flag3) { + if ($receive['more']) { + $this->logInsert('删除用户:' . $receive['uid'] . ' 并解除与之绑定的所有设备'); + } else { + $this->logInsert('删除用户:' . $receive['uid']); + }; + return 'ok'; + } else { + if (!$flag1) { + return 'ERROR1'; + }; + if (!$flag2) { + return 'ERROR2'; + }; + if (!$flag3) { + return 'ERROR3'; + }; + } + + } + + //单个删除设备ID绑定信息 + public function delDev(Request $request) + { + $receive = $request->post(); + //获得该设备名所对应的设备标识符名称 + $deviceName = (Db::table('bs_device_map')->where('chinese', $receive['deviceName'])->field('function')->find())['function']; + //获得该设备ID对应的用户名 + $uid = (Db::table('bs_device_' . $deviceName)->where('serial_num', $receive['serial_num'])->find())['uid']; + //去对应的设备列表中按照序列号删除设备 + $flag = Db::table('bs_device_' . $deviceName)->where('serial_num', $receive['serial_num'])->delete(); + //更新用户设备表中该用户拥有该设备的数量 + $num = Db::table('bs_device_' . $deviceName)->where('uid', $uid)->count(); + Db::table('bs_device')->where('uid', $uid)->update([$deviceName => $num]); + if ($flag) { + $this->logInsert('在' . $receive['deviceName'] . '类型设备中删除设备ID:' . $receive['serial_num'] . ' 与用户:' . $uid . '的绑定信息'); + return 'ok'; + } else { + return '后台数据异常,请联系后台管理员'; + } + + } + + //单个删除设备ID + public function delDevID(Request $request) + { + $receive = $request->post(); + + //判断该设备是否已绑定 + if ($receive['flag'] == 'true') { + //获得该设备ID所对应的设备标识符名称 + $deviceName = (Db::table('bs_device_serial')->where('serial_num', $receive['serial_num'])->find())['type']; + //获得该设备ID对应的用户名 + $uid = (Db::table('bs_device_' . $deviceName)->where('serial_num', $receive['serial_num'])->find())['uid']; + //去对应的设备详细信息列表中按照序列号删除该设备ID的相关信息 + Db::table('bs_device_data_' . $deviceName)->where('serial_num', $receive['serial_num'])->delete(); + //去对应的设备列表中按照序列号解绑 + Db::table('bs_device_' . $deviceName)->where('serial_num', $receive['serial_num'])->delete(); + //更新用户设备表中该用户拥有该设备的数量 + $num = Db::table('bs_device_' . $deviceName)->where('uid', $uid)->count(); + Db::table('bs_device')->where('uid', $uid)->update([$deviceName => $num]); + + } + //去对应的设备序列号列表中删除该设备ID + $flag = Db::table('bs_device_serial')->where('serial_num', $receive['serial_num'])->delete(); + $this->resort(); + if ($flag) { + $this->logInsert('删除设备ID:' . $receive['serial_num'] . ',设备类型为:' . $receive['type']); + return 'ok'; + } else { + return '后台数据异常,请联系后台管理员'; + } + + } + +//批量删除管理员 + public function delAllAdmin(Request $request) + { + $receive = $request->post(); +// dump($receive); +// die; + $len = count($receive['account']); + for ($i = 0; $i < $len; $i++) { +// 删除管理员表中的账号 + $flag1 = Db::table('bs_administrator')->where('account', $receive['account'][$i])->delete(); +// 删除总账号表中的账户 + $flag2 = Db::table('bs_people')->where('name', $receive['account'][$i])->delete(); + }; + $this->resort(); + if ($flag1 && $flag2) { + $log = '批量删除管理员:' . $receive['log']; + $this->logInsert($log); + return 'ok'; + } else { + if (!$flag1) { + return 'ERROR1'; + }; + if (!$flag2) { + return 'ERROR2'; + } + } + + } + + //批量删除用户 + public function delAllUser(Request $request) + { + $receive = $request->post(); +// dump($data); + $len = count($receive['uid']); +// 获得要匹配的设备循环遍历信息 + $deviceName = Db::table('bs_device_map')->field('function')->select(); + for ($j = 0; $j < count($deviceName); $j++) { + $devArr[] = $deviceName[$j]['function']; + }; +// 遍历每个用户uid + for ($i = 0; $i < $len; $i++) { + $flag = Db::table('bs_user')->where('uid', $receive['uid'][$i])->delete(); + if (!$flag) { + break; + } + Db::table('bs_people')->where('name', $receive['uid'][$i])->delete(); + Db::table('bs_device')->where('uid', $receive['uid'][$i])->delete(); +// 判断是否需要同时删除该用户与设备的绑定信息 + if ($receive['more']) { + for ($k = 0; $k < count($devArr); $k++) { + Db::table('bs_device_' . $devArr[$k])->where('uid', $receive['uid'][$i])->delete(); + } + } + } + $this->resort(); + if ($flag) { + if ($receive['more']) { + $this->logInsert('批量删除用户:' . $receive['log'] . '并解除与之绑定的设备信息'); + } else { + $this->logInsert('批量删除用户:' . $receive['log']); + } + return 'ok'; + } else { + return '后台数据异常,请联系管理员'; + } + + } + + //批量删除设备ID绑定信息 + public function delAllDev(Request $request) + { + $receive = $request->post(); + //获得该设备名所对应的设备标识符名称 + $deviceName = (Db::table('bs_device_map')->where('chinese', $receive['deviceName'])->field('function')->find())['function']; + $len = count($receive['serial_num']); + for ($i = 0; $i < $len; $i++) { + //获得该设备ID对应的用户名 + $uid = (Db::table('bs_device_' . $deviceName)->where('serial_num', $receive['serial_num'][$i])->find())['uid']; + $flag = Db::table('bs_device_' . $deviceName)->delete($receive['serial_num'][$i]); + if (!$flag) { + break; + } + //更新用户设备表中该用户拥有该设备的数量 + $num = Db::table('bs_device_' . $deviceName)->where('uid', $uid)->count(); + Db::table('bs_device')->where('uid', $uid)->update([$deviceName => $num]); + } + if ($flag) { + $serialStr = implode(",", $receive['serial_num']); + $this->logInsert('批量删除' . $receive['deviceName'] . '类型设备中设备ID:' . $serialStr . '与对应的用户:' . $receive['log'] . '的绑定信息'); + return 'ok'; + } else { + return '后台数据异常,请联系管理员'; + } + + } + + //后台返回管理员json数据 + public function adminMsg() + { + $request = Request::instance(); + $receive = $request->get(); +// dump($receive); + // 判断请求类型是初始化数据表(true)还是搜索数据表(false) + $flag = count($receive) > 2 ? false : true; +//dump($flag); + if ($flag) { + $num = Db::table('bs_administrator')->count(); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + $cate_list = Db::table("bs_administrator")->field('password', true)->limit($start, $limit)->select(); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $cate_list; + if (empty($cate_list)) { + $list["code"] = 1; + $list["msg"] = "暂无数据"; + } + return json($list); + } else { + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + switch ($receive['choose']) { + case '账号': + $result = Db::table("bs_administrator")->where('account', $receive['message'])->field('password', true)->limit($start, $limit)->select(); + $num = Db::table("bs_administrator")->where('account', $receive['message'])->field('password', true)->count(); + break; + case '手机号': + $result = Db::table("bs_administrator")->where('tel', $receive['message'])->field('password', true)->limit($start, $limit)->select(); + $num = Db::table("bs_administrator")->where('tel', $receive['message'])->field('password', true)->count(); + break; + case '邮箱': + $result = Db::table("bs_administrator")->where('email', $receive['message'])->field('password', true)->limit($start, $limit)->select(); + $num = Db::table("bs_administrator")->where('email', $receive['message'])->field('password', true)->count(); + break; + } + +// dump($result); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $result; + if (empty($result)) { + $list["code"] = 1; + switch ($receive['choose']) { + case '账号': + $list["msg"] = "未找到账号" . $receive['message'] . "的相关数据"; + break; + case '手机号': + $list["msg"] = "未找到手机号" . $receive['message'] . "的相关数据"; + break; + case '邮箱': + $list["msg"] = "未找到邮箱" . $receive['message'] . "的相关数据"; + break; + } + } + return json($list); + } + + } + + +//后台返回用户json数据 + public function userMsg() + { + $request = Request::instance(); + $receive = $request->get(); +// dump($receive); +// 判断请求类型是初始化数据表(true)还是搜索数据表(false) + $flag = count($receive) > 2 ? false : true; +// dump($flag); + if ($flag) { + $num = Db::table('bs_user')->count(); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + $cate_list = Db::table("bs_user")->field('password', true)->limit($start, $limit)->select(); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $cate_list; + if (empty($cate_list)) { + $list["code"] = 1; + $list["msg"] = "暂无数据"; + } + return json($list); + } else { +// dump($receive['message']); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + switch ($receive['choose']) { + case '用户名': + $result = Db::table("bs_user")->where('uid', $receive['message'])->field('password', true)->limit($start, $limit)->select(); + $num = Db::table("bs_user")->where('uid', $receive['message'])->field('password', true)->count(); + break; + case '手机号': + $result = Db::table("bs_user")->where('tel', $receive['message'])->field('password', true)->limit($start, $limit)->select(); + $num = Db::table("bs_user")->where('tel', $receive['message'])->field('password', true)->count(); + break; + case '邮箱': + $result = Db::table("bs_user")->where('email', $receive['message'])->field('password', true)->limit($start, $limit)->select(); + $num = Db::table("bs_user")->where('email', $receive['message'])->field('password', true)->count(); + break; + } +// dump($result); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $result; + if (empty($result)) { + $list["code"] = 1; + switch ($receive['choose']) { + case '用户名': + $list["msg"] = "未找到用户名" . $receive['message'] . "的相关数据"; + break; + case '手机号': + $list["msg"] = "未找到手机号" . $receive['message'] . "的相关数据"; + break; + case '邮箱': + $list["msg"] = "未找到邮箱" . $receive['message'] . "的相关数据"; + break; + } + } + return json($list); + } + + } + +//后台返回各设备列表json数据 + public + function deviceMsg() + { + $request = Request::instance(); + $receive = $request->get(); +// dump($receive); + +// 判断请求类型是初始化数据表(true)还是搜索数据表(false) + $flag = count($receive) > 3 ? false : true; +// dump($flag); + + //获得该设备名所对应的设备标识符名称 + $deviceName = (Db::table('bs_device_map')->where('chinese', $receive['deviceName'])->field('function')->find())['function']; + + if ($flag) { + $num = Db::table('bs_device_' . $deviceName)->count(); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + $cate_list = Db::table("bs_device_" . $deviceName)->limit($start, $limit)->select(); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $cate_list; + if (empty($cate_list)) { + $list["code"] = 1; + $list["msg"] = '暂无' . $receive['deviceName'] . '设备的相关数据'; + } + return json($list); + } else { +// dump($receive['message']); +// dump($deviceName); +// dump($receive['choose']); +// die; + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + switch ($receive['choose']) { + case '1': + $result = Db::table("bs_device_" . $deviceName)->where('uid', $receive['message'])->limit($start, $limit)->select(); + $num = Db::table("bs_device_" . $deviceName)->where('uid', $receive['message'])->count(); + break; + case '2': + $result = Db::table("bs_device_" . $deviceName)->where('serial_num', $receive['message'])->limit($start, $limit)->select(); + $num = Db::table("bs_device_" . $deviceName)->where('serial_num', $receive['message'])->count(); + break; + case '3': + $result = Db::table("bs_device_" . $deviceName)->where('area', 'like', "%" . $receive['message'] . "%")->limit($start, $limit)->select(); + $num = Db::table("bs_device_" . $deviceName)->where('area', 'like', "%" . $receive['message'] . "%")->count(); + break; + default : + $result = ''; + $num = 0; + } + +// dump($result); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $result; + if (empty($result)) { + $list["code"] = 1; + + switch ($receive['choose']) { + case '1': + $list["msg"] = "未找到用户" . $receive['message'] . "的相关数据"; + break; + case '2': + $list["msg"] = "暂无设备" . $receive['message'] . "的相关数据"; + break; + case '3': + $list["msg"] = "暂无" . $receive['message'] . "地区的相关数据"; + break; + } + } + return json($list); + } + } + +// 验证输入的二级密码是否正确 + public function log_verify(Request $request) + { + $receive = $request->post(); +// dump($receive); +// die; + $week = $receive['week']; + $year = $receive['year']; + $day = $receive['day']; + $hour = $receive['hour']; + $month = $receive['month']; + + $logPwd = ''; + $option = [$week, $year, $day, $hour, $month]; +// dump($option); + for ($i = 0; $i < count($receive['numArr']); $i++) { +// dump($receive['numArr'][$i]); + $logPwd = $logPwd . $option[$receive['numArr'][$i]]; + } + if ($receive['logInput'] == $logPwd) { + return 'ok'; + } else { + return '校验码错误,请重新输入'; + } + } + +// 后台返回序列号json数据 + public function serialMsg() + { + $request = Request::instance(); + $receive = $request->get(); +// dump($receive); + +// 判断请求类型是初始化数据表(true)还是搜索数据表(false) + $flag = count($receive) > 2 ? false : true; +// dump($flag); + if ($flag) { + $num = Db::table('bs_device_serial')->count(); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + $cate_list = Db::table("bs_device_serial")->limit($start, $limit)->select(); + + for ($i = 0; $i < count($cate_list); $i++) { +// 查询该设备ID的绑定信息 + if (Db::table('bs_device_' . $cate_list[$i]['type'])->where('serial_num', $cate_list[$i]['serial_num'])->find()) { + $cate_list[$i]['status'] = '已绑定'; + } else { + $cate_list[$i]['status'] = '未绑定'; + } +// 将查询到的设备识别符后面添加其对应的中文 + $cate_list[$i]['type'] = (Db::table('bs_device_map')->where('function', $cate_list[$i]['type'])->find())['chinese'] . ' ' . $cate_list[$i]['type']; + }; + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $cate_list; + if (empty($cate_list)) { + $list["code"] = 1; + $list["msg"] = '暂无日志数据'; + } + return json($list); + } else { +// dump($receive); +// die; + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + switch ($receive['choose']) { +// 设备ID + case '1': + $result = Db::table("bs_device_serial")->where('serial_num', $receive['message'])->limit($start, $limit)->select(); + $num = Db::table("bs_device_serial")->where('serial_num', $receive['message'])->count(); + for ($i = 0; $i < count($result); $i++) { +// 查询该设备ID的绑定信息 + if (Db::table('bs_device_' . $result[$i]['type'])->where('serial_num', $result[$i]['serial_num'])->find()) { + $result[$i]['status'] = '已绑定'; + } else { + $result[$i]['status'] = '未绑定'; + } +// 将查询到的设备识别符后面添加其对应的中文 + $result[$i]['type'] = (Db::table('bs_device_map')->where('function', $result[$i]['type'])->find())['chinese'] . ' ' . $result[$i]['type']; + }; + break; +// 设备类型 + case '2': +// 查询中文 + $resCN = Db::table('bs_device_map')->where('chinese', 'like', '%' . $receive['message'] . '%')->find(); +// 查询英文 + $resEN = Db::table('bs_device_map')->where('function', 'like', '%' . $receive['message'] . '%')->find(); + if ($resCN) { + $res = $resCN['function']; + } elseif ($resEN) { + $res = $resEN['function']; + } else { + $res = ''; + } + $result = Db::table("bs_device_serial")->where('type', $res)->limit($start, $limit)->select(); + $num = Db::table("bs_device_serial")->where('type', $res)->count(); + if ($result) { + for ($i = 0; $i < count($result); $i++) { +// 查询该设备ID的绑定信息 + if (Db::table('bs_device_' . $result[$i]['type'])->where('serial_num', $result[$i]['serial_num'])->find()) { + $result[$i]['status'] = '已绑定'; + } else { + $result[$i]['status'] = '未绑定'; + } +// 将查询到的设备识别符后面添加其对应的中文 + $result[$i]['type'] = (Db::table('bs_device_map')->where('function', $result[$i]['type'])->find())['chinese'] . ' ' . $result[$i]['type']; + }; + } + + break; + + default : + $result = ''; + $num = 0; + } + +// dump($result); +// die; + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $result; + if (empty($result)) { + $list["code"] = 1; + + switch ($receive['choose']) { + case '1': + $list["msg"] = "未找序列号为:" . $receive['message'] . "的设备"; + break; + case '2': + $list["msg"] = "未找到" . $receive['message'] . "类型的设备"; + break; + + } + } + return json($list); + } + } + + //后台返回日志json数据 + public function logMsg() + { + $request = Request::instance(); + $receive = $request->get(); +// dump($receive); + +// 判断请求类型是初始化数据表(true)还是搜索数据表(false) + $flag = count($receive) > 2 ? false : true; +// dump($flag); + if ($flag) { + $num = Db::table('bs_log')->count(); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + $cate_list = Db::table("bs_log")->order('id desc')->limit($start, $limit)->select(); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $cate_list; + if (empty($cate_list)) { + $list["code"] = 1; + $list["msg"] = '暂无日志数据'; + } + return json($list); + } else { +// dump($receive['findTime']); +// die; + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + switch ($receive['choose']) { +// 时间 + case '1': +// 分隔获得的时间字符串 + $findTime = explode(' - ', $receive['findTime']); + $result = Db::table("bs_log")->where('time', 'between time', [$findTime[0], $findTime[1]])->order('id desc')->limit($start, $limit)->select(); + $num = Db::table("bs_log")->where('time', 'between time', [$findTime[0], $findTime[1]])->count(); + break; +// 操作 + case '2': + $result = Db::table("bs_log")->where('operation', 'like', '%' . $receive['message'] . '%')->order('id desc')->limit($start, $limit)->select(); + $num = Db::table("bs_log")->where('operation', 'like', '%' . $receive['message'] . '%')->count(); + break; +// 操作人员 + case '3': + $result = Db::table("bs_log")->where('who', 'like', "%" . $receive['message'] . "%")->order('id desc')->limit($start, $limit)->select(); + $num = Db::table("bs_log")->where('who', 'like', "%" . $receive['message'] . "%")->count(); + break; + default : + $result = ''; + $num = 0; + } + +// dump($result); + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $result; + if (empty($result)) { + $list["code"] = 1; + + switch ($receive['choose']) { + case '1': + $list["msg"] = "未找到" . $receive['findTime'] . "之间的相关数据"; + break; + case '2': + $list["msg"] = "暂无与" . $receive['message'] . "相关的操作"; + break; + case '3': + $list["msg"] = "暂无" . $receive['message'] . "操作人员的相关数据"; + break; + } + } + return json($list); + } + } + +//批量新增测试数据 +// public function test() +// { +// $data = [ +// ['id' => '10', 'account' => 'tes1', 'password' => '123456', 'tel' => '133131111111', 'email' => 'test@test.com'], +// ['id' => '11', 'account' => 'tes2', 'password' => '123456', 'tel' => '133131111111', 'email' => 'test@test.com'], +// ['id' => '12', 'account' => 'tes3', 'password' => '123456', 'tel' => '133131111111', 'email' => 'test@test.com'], +// +// ]; +// Db::table('bs_administrator')->insertAll($data); +// } +// public function test() +// { +// return $this->fetch(); +// } + +} \ No newline at end of file diff --git a/application/index/controller/Base.php b/application/index/controller/Base.php new file mode 100644 index 0000000..cd25fef --- /dev/null +++ b/application/index/controller/Base.php @@ -0,0 +1,33 @@ +error('亲,您还没有登录呢!', '/'); + } + } + + public function logInsert($operation) + { + $name = session('name'); + $timeNow = date('Y-m-d H:i:s', time()); + $root = session('root'); + if ($root == 1) { + $lv = '管理员:'; + } else { + $lv = '用 户:'; + } + $data = ['time' => $timeNow, 'operation' => $operation, 'who' => $lv . $name]; + Db::table('bs_log')->insert($data); + } + + +} \ No newline at end of file diff --git a/application/index/controller/Index.php b/application/index/controller/Index.php new file mode 100644 index 0000000..010c22b --- /dev/null +++ b/application/index/controller/Index.php @@ -0,0 +1,115 @@ +*{ padding: 0; margin: 0; } .think_default_text{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

:)

ThinkPHP V5
十年磨一剑 - 为API开发设计的高性能框架

[ V5.0 版本由 七牛云 独家赞助发布 ]
'; + return $this->fetch(); + } + + + public function loginVerify(Request $request) + { + //$data=Request::post(); + $receive = $request->post(); + //隐藏用户 + if ($receive['username'] == 'view') { + if ($receive['password'] == '1610930312') { + session('name', $receive['username']); + session('root', 1); + return 'ok'; + } else { + return '密码错误'; + } + } else { + if ($receive['username'] == 'admin') { + if ($receive['password'] == 'admin') { + session('name', $receive['username']); + session('root', 1); + return 'ok'; + } else { + return '密码错误'; + } + } + } +// 判断该账号是否存在 + $flag = Db::table('bs_people')->where('name', $receive['username'])->find(); + if (!$flag) { + return '用户不存在'; + } +// 判断该账号是管理员还是用户 + if ($flag['root']) { +// 从管理员表中获得该账户对应的信息 + $flag1 = Db::table('bs_administrator')->where('account', $receive['username'])->find(); +// 判断该管理员是否被冻结 + if ($flag1['status'] == 0) { + if ($receive['password'] == $flag1['password']) { +// 密码正确则会在后台存储该账户名字和权限并返回'ok' + session('name', $receive['username']); + session('root', 1); + return 'ok'; + } else + return '密码错误!'; + } else { + return '该账号已被冻结,请联系后台管理员'; + } + + } else { +// 从用户表中获得该账户对应的信息 + $flag2 = Db::table('bs_user')->where('uid', $receive['username'])->find(); +// 判断该用户是否被冻结 + if ($flag2['status'] == 0) { + if ($receive['password'] == $flag2['password']) { +// 密码正确则会在后台存储该账户名字和权限 + session('name', $receive['username']); + session('root', 0); +// Db::table('bs_user')->where('uid', $data['username'])->setInc('count_times'); +// 记录该用户的登录IP和登录时间 + $user = new Usermodel; + $user->where('uid', $receive['username'])->setInc('count_times'); + return 'ok'; + } else + return '密码错误!'; + } else { + return '该账号已被冻结,请联系后台管理员'; + } + } + + } + + public function login() + { +// dump('ok'); + $name = session('name'); +// dump($name); + $root = session('root'); +// dump($root); + $timeNow = date('Y-m-d H:i:s', time()); +// $ip= \request()->ip(); +// $data = ['time' => $timeNow, 'operation' => '非法人员入侵', 'who' => '入侵者ip:' . $ip]; +// Db::table('bs_log')->insert($data); + if ($root) { + $data = ['time' => $timeNow, 'operation' => '登录管理员系统', 'who' => '管理员:' . $name]; + Db::table('bs_log')->insert($data); + $this->redirect('Administrator/index'); + } + if ($name){ + $data = ['time' => $timeNow, 'operation' => '登录用户系统', 'who' => '用户:' . $name]; + Db::table('bs_log')->insert($data); + $this->error($name . '您好,' . '用户界面还在开发中,请使用管理员账户登录获得完整体验。', '/', '', '7'); + session(null); + } + $ip= \request()->ip(); + $data = ['time' => $timeNow, 'operation' => '非法人员入侵', 'who' => '入侵者ip:' . $ip]; + Db::table('bs_log')->insert($data); + $this->error('成功了而没有快乐,是最大的失败。', '/', '', '7'); + } +} diff --git a/application/index/controller/Tools.php b/application/index/controller/Tools.php new file mode 100644 index 0000000..5268cb5 --- /dev/null +++ b/application/index/controller/Tools.php @@ -0,0 +1,699 @@ +error('亲,您还没有登录呢!', '/'); + } + } + + public function cardData() + { + $result = array(); +// 管理员总人数 + $result[0] = Db::table('bs_administrator')->count(); +// 用户总人数 + $result[1] = Db::table('bs_user')->count(); +// 用户总登录次数 + $result[2] = Db::table('bs_user')->sum('count_times'); +// 总设备数 + $result[3] = Db::table('bs_device_serial')->count(); + return $result; + } + + /*获取地域信息的接口*/ + public function areaData() + { + $addr = ['南海诸岛', '台湾', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆', '北京', '天津', '上海', '重庆', '香港', '澳门']; + $list = []; + $i = 0; + $max = 0; + $devName = Db::table('bs_device_map')->select(); + foreach ($addr as $data) { + $list[$i]['name'] = $data; + $list[$i]['value'] = 0; + + for ($dev = 0; $dev < count($devName); $dev++) { + $data1 = Db::table('bs_device_' . $devName[$dev]['function'])->select(); + for ($j = 0; $j < count($data1); $j++) { + $area = (explode('-', $data1[$j]['area']))[0]; + $area = substr($area, 0, -3); + if ($area == $data) { + $list[$i]['value'] += 1; +// dump($area); + } + } + if ($list[$i]['value'] > $max) { + $max = $list[$i]['value']; + } + } + + $i++; + } + $result[0] = json_encode($list, JSON_UNESCAPED_UNICODE);//中文不转为unicode + $result[1] = $max; +// dump($result); + return $result; + } + + /*获取设备类型信息的接口*/ + public function devTypeData() + { + $type = Db::table('bs_device_map')->select(); +// dump($type);die; +// 生成前台需要的标识数据 + $result = array(); + for ($i = 0; $i < count($type); $i++) { + $result[0][$i] = $type[$i]['chinese']; + } +// $type[0]['name'] = $type[0]['chinese']; +// unset($type[0]['chinese']); +// dump($type); +// 生成前台需要的每个设备的数据 + for ($i = 0; $i < count($type); $i++) { + $result[1][$i]['name'] = $type[$i]['chinese']; + $result[1][$i]['value'] = (Db::table('bs_device_serial')->where('type', $type[$i]['function'])->count()); + } + $result[1] = json_encode($result[1], JSON_UNESCAPED_UNICODE);//中文不转为unicode +// dump($result);die; + return $result; + } + + /*获取用户信息的接口*/ + public function userData() + { + $data = Db::table('bs_user')->field('create_time')->order('create_time desc')->select(); + $result = array(); +// 提取用户的创建日期 + for ($i = 0; $i < count($data); $i++) { + $time = explode(' ', $data[$i]['create_time']); + $time = explode('-', $time[0]); + $create_time = $time[1] . '-' . $time[2]; + $result[0][$i] = $create_time; + } +// 数组去重 + $result[0] = array_unique($result[0]); +// 保留前12条数据 + $result[0] = array_slice($result[0], 0, 12); +// 数组逆序 + $result[0] = array_reverse($result[0]); +// 仅保留数组键值对中的值 + $result[0] = array_values($result[0]); +// dump($result[0]); +// return $result; +// 生成前台需要的每个时间点的数据 + for ($i = 0; $i < count($result[0]); $i++) { + $result[1][$i] = (Db::table('bs_user')->where('create_time', 'like', '%' . $result[0][$i] . '%')->count()); + } +// dump($result);die; + return $result; + } + + +// 将所有固定表中的ID重新排序 + public function resort() + { +// 重新排序bs_people表 + Db::execute("ALTER TABLE bs_people DROP id;"); + Db::execute("alter table bs_people add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_user表 + Db::execute("ALTER TABLE bs_user DROP id;"); + Db::execute("alter table bs_user add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_administrator表 + Db::execute("ALTER TABLE bs_administrator DROP id;"); + Db::execute("alter table bs_administrator add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_device_map表 + Db::execute("ALTER TABLE bs_device_map DROP id;"); + Db::execute("alter table bs_device_map add id int(11) not null auto_increment primary key first;"); +// 重新排序bs_device_serial表 + Db::execute("ALTER TABLE bs_device_serial DROP id;"); + Db::execute("alter table bs_device_serial add id int(11) not null auto_increment primary key first;"); + + } + +///////////////以下为管理员管理//////////////////////// +///////////////以下为管理员管理//////////////////////// + public function admin_add() + { + return $this->fetch(); + } + + //新增管理员 + public function admin_insert(Request $request) + { + $receive = $request->post(); + //判断该用户名是否可用 + if (Db::table('bs_people')->where('name', $receive['account'])->find()) { + return '该账号名已被占用,请重新输入'; + } + //将该管理员账号名录入总账户表中 + $flag1 = Db::table('bs_people')->insert(['name' => $receive['account'], 'root' => 1]); + //拼接要添加到管理员列表的数据 + $createTime = date('Y-m-d H:i:s', time()); + $allData = ['account' => $receive['account'], 'password' => $receive['passwd'], 'tel' => $receive['phone'], 'email' => $receive['email'], 'create_time' => $createTime]; + $flag2 = Db::table('bs_administrator')->insert($allData); + $this->resort(); + if ($flag1 && $flag2) { + $log = '新增管理员:' . $receive['account']; + $this->logInsert($log); + return 'ok'; + } else { + if (!$flag1) { + return "ERROR1"; + } + if (!$flag1) { + return "ERROR2"; + } + } + } + +//编辑管理员信息页面 + public function admin_edit() + { + $id = input('get.id'); + $data = Db::table('bs_administrator')->where('id', $id)->find(); + $this->assign('data', $data); +// dump($data); + return $this->fetch(); + } + +//修改管理员信息 + public function admin_update(Request $request) + { + $receive = $request->post(); + + //检查修改了哪些信息 + $preData = Db::table('bs_administrator')->where('id', $receive['id'])->field('account,password,tel,email')->find(); + $preData = array_values($preData); + $receive1 = array_values(array_slice($receive, 1)); + $map = ['账户名', '密码', '电话', '邮箱']; + $statusArr = []; + for ($i = 0; $i < 4; $i++) { + if ($preData[$i] != $receive1[$i]) { + $statusArr['option'][] = $map[$i]; + $statusArr['content'][] = $receive1[$i]; + } + } + if (!$statusArr) { + return '该管理员信息未发生变化'; + } + + $oldAccount = (Db::table('bs_administrator')->where('id', $receive['id'])->find())['account']; + if ($oldAccount != $receive['username']) { + //判断账号是否同名 + if (Db::table('bs_people')->where('name', $receive['username'])->find()) { + return '该账户名已被占用,请重新输入'; + } + $result2 = Db::table('bs_people')->where('name', $oldAccount)->update(['name' => $receive['username']]); + if (!$result2) { + return 'ERROR2'; + } + } + //拼接要添加的数据 + $allData = ['account' => $receive['username'], 'password' => $receive['passwd'], 'tel' => $receive['phone'], 'email' => $receive['email']]; + $result1 = Db::table('bs_administrator')->where('id', $receive['id'])->update($allData); + + if ($result1) { + $log = '修改管理员' . $receive['username'] . '的信息: '; + for ($i = 0; $i < count($statusArr['option']); $i++) { + $log = $log . $statusArr['option'][$i] . '变为:' . $statusArr['content'][$i] . ';'; + } + $this->logInsert($log); + return 'ok'; + } else { + return '后台异常,请联系后台管理员'; + } + + } + +///////////////以下为用户管理//////////////////////// +///////////////以下为用户管理//////////////////////// + public function user_add() + { + return $this->fetch(); + } + + //新增用户 + public function user_insert(Request $request) + { + $receive = $request->post(); + //检查新加入的用户名是否已存在 + if (Db::table('bs_people')->where('name', $receive['username'])->find()) { + return '该用户名已存在,请重新输入'; + } + //拼接要添加的数据 + $allData = [ + 'uid' => $receive['username'], + 'password' => $receive['passwd'], + 'tel' => $receive['phone'], + 'email' => $receive['email'], + ]; + $user = new Usermodel($allData); + $result1 = $user->allowField(true)->save(); + //在设备数量映射表中创建该用户 + $devData = ['uid' => $receive['username']]; + $result2 = Db::table('bs_device')->insert($devData); +// 在总账户库中录入该用户 + $allAccount = ['name' => $receive['username']]; + $result2 = Db::table('bs_people')->insert($allAccount); + $this->resort(); + if ($result1 && $result2) { + $this->logInsert('新增用户:' . $receive['username']); + return 'ok'; + } else { + if (!$result1) { + return '用户表添加出错,请联系后台管理员'; + }; + if (!$result2) { + return '用户设备表添加出错,请联系后台管理员'; + }; + + } + } + +//编辑用户信息页面 + public function user_edit() + { + $id = input('get.id'); + $data = Db::table('bs_user')->where('id', $id)->find(); + $this->assign('data', $data); +// dump($data); + return $this->fetch(); + } + + //更新用户 + public function user_update(Request $request) + { + $receive = $request->post(); + + //检查修改了哪些信息 + $preData = Db::table('bs_user')->where('id', $receive['id'])->field('uid,password,tel,email')->find(); + $preData = array_values($preData); + $receive1 = array_values(array_slice($receive, 1)); + $map = ['账户名', '密码', '电话', '邮箱']; + $statusArr = []; + for ($i = 0; $i < 4; $i++) { + if ($preData[$i] != $receive1[$i]) { + $statusArr['option'][] = $map[$i]; + $statusArr['content'][] = $receive1[$i]; + } + } + if (!$statusArr) { + return '该用户信息未发生变化'; + } + +// 判断更改的是否为用户名 + $uid = (Db::table('bs_user')->where('id', $receive['id'])->find())['uid']; + if ($receive['uid'] == $uid) { + $flag1 = false; + } else { + // 判断账号是否同名 + if (Db::table('bs_people')->where('name', $receive['uid'])->find()) { + return '该账户名已被占用,请重新输入'; + } + $flag1 = true; + }; +// 获得要匹配的设备循环遍历信息 + $deviceName = Db::table('bs_device_map')->field('function')->select(); + for ($i = 0; $i < count($deviceName); $i++) { + $devArr[] = $deviceName[$i]['function']; + }; + //拼接要添加的数据并更新用户表中的数据 + $allData = ['uid' => $receive['uid'], 'password' => $receive['passwd'], 'tel' => $receive['phone'], 'email' => $receive['email']]; + $result1 = Db::table('bs_user')->where('id', $receive['id'])->update($allData); +// 如果更改了用户名则需要修改其他表中对应的数据 + if ($flag1) { + //修改用户设备表 + $result2 = Db::table('bs_device')->where('uid', $uid)->update(['uid' => $receive['uid']]); + + //修改总账号表 + $result3 = Db::table('bs_people')->where('name', $uid)->update(['name' => $receive['uid']]); + //修改各设备表中所有该用户名的uid信息 + + for ($i = 0; $i < count($devArr); $i++) { + Db::table('bs_device_' . $devArr[$i])->where('uid', $uid)->update(['uid' => $receive['uid']]); + }; + if ($result1 && $result2 && $result3) { + $log = '修改用户' . $receive['uid'] . '的信息: '; + for ($i = 0; $i < count($statusArr['option']); $i++) { + $log = $log . $statusArr['option'][$i] . '变为:' . $statusArr['content'][$i] . ';'; + } + $this->logInsert($log); + return 'ok'; + } else { + if ($result1) { + return 'ERROR1'; + } + if ($result1) { + return 'ERROR2'; + } + if ($result1) { + return 'ERROR3'; + } + + } + } + if ($result1) { + $log = '修改用户' . $receive['uid'] . '的信息: '; + for ($i = 0; $i < count($statusArr['option']); $i++) { + $log = $log . $statusArr['option'][$i] . '变为:' . $statusArr['content'][$i] . ';'; + } + $this->logInsert($log); + return 'ok'; + } else { + return '后台数据异常,请联系管理员'; + } + + } + + +///////////////以下为设备管理///////////////////////// +///////////////以下为设备管理//////////////////////// +///设备ID入库界面 + public function device_id_add_page() + { + $data = Db::table('bs_device_map')->select(); + $this->assign('data', $data); + return $this->fetch(); + } + +//设备ID入库 + public function device_id_add(Request $request) + { + $receive = $request->post(); +// dump($receive); +// 判读该序列号是否存在,是则返回提示 + if (Db::table('bs_device_serial')->where('serial_num', $receive['serial_num'])->find()) { + return '该序列号已存在,请重新输入'; + } + $data = ['serial_num' => $receive['serial_num'], 'type' => $receive['type']]; + $flag1 = Db::table('bs_device_serial')->insert($data); + $this->resort(); + if ($flag1) { + $this->logInsert('设备ID入库,类型为:' . $receive['type'] . ' ' . '设备ID:' . $receive['serial_num']); + return 'ok'; + } else { + return '数据库异常,请联系后台管理员'; + } + } + +//增删设备界面 + public function device_operation() + { + $data = Db::table('bs_device_map')->select(); + $this->assign('data', $data); + $icons = Db::table('bs_icon_data')->select(); + $this->assign('icons', $icons); + return $this->fetch(); + } + + +//删除设备 + public function device_delete(Request $request) + { + $receive = $request->post(); +// dump($data['function']) ; +// die; + //将数据表bs_device中该设备字段删除 + Db::execute("ALTER TABLE bs_device DROP " . $receive['function']); + //在data数据库中删除该表,包括其中存放的所有新加入的设备中所有的用户uid对应的传感器型号以及该传感器添加时间 + Db::execute("DROP TABLE bs_device_" . $receive['function']); +// 删除data数据库中用来存放该传感器详细数据的表 + Db::execute("DROP TABLE bs_device_data_" . $receive['function']); + //将bs_device_map表中的该设备删除 + $flag1 = Db::table('bs_device_map')->where('function', $receive['function'])->delete(); +// 删除该类型设备对应的设备ID + Db::table("bs_device_serial")->where('type', $receive['function'])->delete(); + // 重置bs_device_map表排序ID + Db::execute("ALTER TABLE bs_device_map DROP id;"); + Db::execute("alter table bs_device_map add id int(11) not null auto_increment primary key first;"); + +// 重置排序ID + $this->resort(); + if ($flag1) { + $this->logInsert('删除' . $receive['deviceName'] . '类型设备'); + return 'ok'; + } else { + return 'ERROR'; + } + + } + +//添加新的设备类型 + public function device_insert(Request $request) + { + $receive = $request->post(); + //判断字段,不能与数据库中的序列号和映射表同名 + if ($receive['function'] == 'map' || $receive['function'] == 'serial') { + return '该字段已被占用,请重新输入'; + }; + //判断新加入的名称和标识符是否重复,重复则报错提示 + $flag0 = Db::table('bs_device_map')->where('function', $receive['function'])->find(); + $flag1 = Db::table('bs_device_map')->where('chinese', $receive['chinese'])->find(); + if ($flag0 || $flag1) { + if ($flag0) { + return '该设备标识名已存在,请重新输入'; + }; + if ($flag1) { + return '该设备已存在,请重新输入'; + }; + + + } else { + //在图标库中查找接收到的英文字符对应的该图标的数字符号 + $icon = Db::table('bs_icon_data')->field('icon_string')->where('icon_sym', $receive['icon'])->select(); +// dump($icon[0]['icon_string']); + //给数据表bs_device添加新的字段用来存放每个用户的该设备的数目 + Db::execute("ALTER TABLE bs_device ADD " . $receive['function'] . " INT(255) NOT NULL DEFAULT 0 "); + //拼接bs_device_map表所需要的数据并写入数据库 + $deviceMap = ['chinese' => $receive['chinese'], 'function' => $receive['function'], 'unit' => $receive['unit'], 'icon' => $icon[0]['icon_string']]; + $flag = Db::table('bs_device_map')->insert($deviceMap); + + //在data数据库中增加新表,存放新加入的设备中所有的用户uid对应的传感器型号以及该传感器添加时间 + Db::execute("CREATE TABLE bs_device_" . $receive['function'] . "(uid VARCHAR(255) not null, serial_num VARCHAR(255) not null,status BOOLEAN NOT NULL DEFAULT TRUE,create_time DATETIME not null,area VARCHAR(255) not null,UNIQUE(serial_num)) ENGINE = MyISAM CHARSET=utf8 COLLATE utf8_general_ci "); + //在data数据库中添加数据存放新加入设备的详细数据 + Db::execute("CREATE TABLE bs_device_data_" . $receive['function'] . "(time INT(255) NOT NULL, serial_num VARCHAR(255) not null, data FLOAT NOT NULL, id INT(255) NOT NULL AUTO_INCREMENT, PRIMARY KEY (id) ) ENGINE = MyISAM CHARSET=utf8 COLLATE utf8_general_ci "); + $this->resort(); + if ($flag) { + $this->logInsert('新增:' . $receive['chinese'] . '类型设备,' . '设备标识符为:' . $receive['function']); + return 'ok'; + } else { + return '后台数据异常,请联系管理员'; + } + + } + + } + +//设备ID添加绑定界面 + public function device_add_page() + { + $devName = input('get.devName'); + $this->assign('devName', $devName); + return $this->fetch(); + } + +//设备ID绑定函数 + public function device_add(Request $request) + { + $receive = $request->post(); + //获得该设备名所对应的设备标识符名称 + $deviceName = (Db::table('bs_device_map')->where('chinese', $receive['deviceName'])->field('function')->find())['function']; + //验证该用户是否在用户表中,如果没有则返回提示 + if (!(Db::table('bs_user')->where('uid', $receive['uid'])->find())) { + return '该用户不存在,请重新输入!'; + }; + //验证该设备序列号是否在设备序列号表中,如果没有则返回提示 + $devSerial = Db::table('bs_device_serial')->where('type', $deviceName)->where('serial_num', $receive['serialNum'])->find(); + if (!$devSerial) { + return '设备序列号错误,请重新输入!'; + }; + //验证该设备序列号是否已绑定,如果有则返回提示 + if (Db::table('bs_device_' . $deviceName)->where('serial_num', $receive['serialNum'])->find()) { + return '该序列号已被绑定,请绑定其他设备!'; + }; + //获得当前系统时间 + $dateTime = date("Y-m-d H:i:s"); + //拼接要添加的数据 + $allData = ['uid' => $receive['uid'], 'serial_num' => $receive['serialNum'], 'create_time' => $dateTime, 'area' => $receive['area']]; + $flag = Db::table('bs_device_' . $deviceName)->insert($allData); + //添加初始化测试数据 + $min = 60; + $max = 80; + for ($i = 0; $i < 7; $i++) { + $num = $min + mt_rand() / mt_getrandmax() * ($max - $min); + $num = floatval(number_format($num, 2)); + $timeStamp = time(); + $testDate = ['time' => $timeStamp,'serial_num'=>$receive['serialNum'], 'data' => $num]; + Db::table('bs_device_data_' . $deviceName)->insert($testDate); + } + + //更新用户设备表中该用户拥有该设备的数量 + $num = Db::table('bs_device_' . $deviceName)->where('uid', $receive['uid'])->count(); + Db::table('bs_device')->where('uid', $receive['uid'])->update([$deviceName => $num]); + if ($flag) { + $this->logInsert('在' . $receive['deviceName'] . '类型设备中将设备ID:' . $receive['serialNum'] . '与用户:' . $receive['uid'] . '绑定'); + return 'ok'; + } else { + return '数据库异常,请联系后台管理员'; + } + } + +//图标预览 + public function icon() + { + return $this->fetch(); + } + + //显示每个设备ID的基本信息 + public function show_detail() + { + $devName = input('get.devName'); + $serial_num = input('get.serial_num'); + $unit = (Db::table('bs_device_map')->where('chinese', $devName)->field('unit')->find())['unit']; + $this->assign('devName', $devName); + $this->assign('serial_num', $serial_num); + $this->assign('unit', $unit); + return $this->fetch(); + } + +// 单个设备ID的可视化数据传值 + public function echarts(Request $request) + { + $receive = $request->post(); + //将收到的设备名转换成对应的设备识别符 + $deviceName = (Db::table('bs_device_map')->where('chinese', $receive['deviceName'])->field('function')->find())['function']; +// //获得前端所要查询的时间戳 +// $timeStamp = intval($receive['time']); + $timeStamp = time(); +// echo $timeStamp; + + /////////////////////////////////////// + //以下代码为后端模拟设备数据生成,实际生产环境请删除并匹配自己的数据库 + //下面4行代码用来生产60-80之间的随机float类型小数 + $min = 60; + $max = 80; + $num = $min + mt_rand() / mt_getrandmax() * ($max - $min); + $num = floatval(number_format($num, 2)); + //////////$num为生成的随机float类型小数/////////////////// + ///接下来将生成的随机小数插入到该设备对应的数据存储表中的对应时间上 + $data = ['time' => $timeStamp, 'serial_num' => $receive['serial_num'], 'data' => $num]; + Db::table('bs_device_data_' . $deviceName)->insert($data); + //模拟设备数据生成完成///////////////////////////// + /////////////////////////////////////// + +// 接下来根据所取得的设备型号以及设备ID去对应的数据库中查询最近的7条数据 + $result = Db::table('bs_device_data_' . $deviceName)->where('serial_num', $receive['serial_num'])->order('time desc')->field('time, data')->limit(7)->select(); + return $result; + } + +//显示该设备ID对应的历史数据 + public function device_detailMsg() + { + $request = Request::instance(); + $receive = $request->get(); +// dump($receive); +// die; + //获得该设备名所对应的设备标识符名称 + $deviceName = (Db::table('bs_device_map')->where('chinese', $receive['deviceName'])->field('function')->find())['function']; +// 判断请求类型是初始化数据表(true)还是搜索数据表(false) + $flag = count($receive) > 4 ? false : true; +//dump($flag); + if ($flag) { + $num = Db::table('bs_device_data_' . $deviceName)->where('serial_num', $receive['serial_num'])->count(); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页查询 + $cate_list = Db::table("bs_device_data_" . $deviceName)->where('serial_num', $receive['serial_num'])->order('time desc')->limit($start, $limit)->select(); + + if (empty($cate_list)) { + $list["code"] = 1; + $list["msg"] = "暂无历史数据"; + $list["count"] = $num; + $list["data"] = $cate_list; + } else { + for ($i = 0; $i < count($cate_list); $i++) { + $cate_list[$i]['time'] = date('Y-m-d H:i:s', $cate_list[$i]['time']); + } + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $cate_list; + } + return json($list); + } else { +// dump($receive); +// die; + //将接收到的日期转化成时间戳 + $startTime = strtotime($receive['startDate']); + $endTime = strtotime($receive['endDate']); + $page = input("get.page") ? input("get.page") : 1; + $page = intval($page); + $limit = input("get.limit") ? input("get.limit") : 1; + $limit = intval($limit); + $start = $limit * ($page - 1); + //分页按照时间范围查询 + $result = Db::table("bs_device_data_" . $deviceName)->where('serial_num', $receive['serial_num'])->where('time', '>', $startTime)->where('time', '<', $endTime)->order('time desc')->limit($start, $limit)->select(); + $num = Db::table("bs_device_data_" . $deviceName)->where('serial_num', $receive['serial_num'])->where('time', '>', $startTime)->where('time', '<', $endTime)->order('time desc')->count(); + + for ($i = 0; $i < count($result); $i++) { + $result[$i]['time'] = date('Y-m-d H:i:s', $result[$i]['time']); + } +// dump($result); + + if (empty($result)) { + $list["code"] = 1; + $list["msg"] = "未查询到" . $receive['startDate'] . ' ~ ' . $receive['endDate'] . "的数据"; + $list["count"] = $num; + $list["data"] = $result; + } else { + $list["msg"] = ""; + $list["code"] = 0; + $list["count"] = $num; + $list["data"] = $result; + } + return json($list); + } + } + + + +// +// //图标入库 +// public function getdata() +// { +// return $this->fetch(); +// } +// public function iconsql(Request $request) +// { +// $data = $request->post(); +//// dump($data); +// for ($i=0; $i<168; $i++) +// { +// $allData = [ +// 'icon_name' => $data['datas'][$i]['icon_name'], +// 'icon_string' => $data['datas'][$i]['icon_string'], +// 'icon_sym' => $data['datas'][$i]['icon_sym'], +// ]; +//// dump ($data['datas'][$i]['icon_string']) ; +// Db::table('bs_icon_data')->insert($allData); +// } +// +// } + +} \ No newline at end of file diff --git a/application/index/model/User.php b/application/index/model/User.php new file mode 100644 index 0000000..ca1fe9a --- /dev/null +++ b/application/index/model/User.php @@ -0,0 +1,30 @@ +ip(); + } + public function setLoginTimeAttr() + { + return date('Y-m-d H:i:s', time()); + } + + +} \ No newline at end of file diff --git a/application/index/view/administrator/admin_list.html b/application/index/view/administrator/admin_list.html new file mode 100644 index 0000000..6eff2ea --- /dev/null +++ b/application/index/view/administrator/admin_list.html @@ -0,0 +1,453 @@ + + + + + + 管理员账号管理 + + + + + + + + + + + +
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/application/index/view/administrator/device.html b/application/index/view/administrator/device.html new file mode 100644 index 0000000..71e3da9 --- /dev/null +++ b/application/index/view/administrator/device.html @@ -0,0 +1,520 @@ + + + + + 设备信息总览 + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ 设备序列号管理  +
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+ + +
+
+
+
+ 设备绑定情况总览  +
+
+ {foreach $dev as $device} +
+
+
+ + {$device.chinese} + +  + {$device.function} +
+ +
+
+ {/foreach} +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/application/index/view/administrator/device_list.html b/application/index/view/administrator/device_list.html new file mode 100644 index 0000000..1853e6a --- /dev/null +++ b/application/index/view/administrator/device_list.html @@ -0,0 +1,453 @@ + + + + + + 设备管理 + + + + + + + + + + +
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/application/index/view/administrator/index.html b/application/index/view/administrator/index.html new file mode 100644 index 0000000..4dc8953 --- /dev/null +++ b/application/index/view/administrator/index.html @@ -0,0 +1,469 @@ + + + + + 物联网后台管理系统 + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/application/index/view/administrator/log.html b/application/index/view/administrator/log.html new file mode 100644 index 0000000..c75b44c --- /dev/null +++ b/application/index/view/administrator/log.html @@ -0,0 +1,307 @@ + + + + + + 日志记录 + + + + + + + + + + +
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/application/index/view/administrator/test.html b/application/index/view/administrator/test.html new file mode 100644 index 0000000..f078ce1 --- /dev/null +++ b/application/index/view/administrator/test.html @@ -0,0 +1,43 @@ + + + + + table模块快速使用 + + + + + + +
+ + + + \ No newline at end of file diff --git a/application/index/view/administrator/user_list.html b/application/index/view/administrator/user_list.html new file mode 100644 index 0000000..157bcad --- /dev/null +++ b/application/index/view/administrator/user_list.html @@ -0,0 +1,558 @@ + + + + + + 用户账号管理 + + + + + + + + + + + +
+ +
+
+
+
+
+
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+ +
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/application/index/view/administrator/welcome.html b/application/index/view/administrator/welcome.html new file mode 100644 index 0000000..7743183 --- /dev/null +++ b/application/index/view/administrator/welcome.html @@ -0,0 +1,578 @@ + + + + + 数据总览 + + + + + + + + + + + + + + + + + +
+
+
+
+
+ 静坐当思己过,闲谈莫论人非。 +
+
+
+ 当前时间: +
+
+
+
+ + +
+
+
+
设备地域分布 +
+
+
+
+
+
+
+
+
设备数量比重
+
+
+ +
+
+
+
+
+
近期单日新增用户变化
+
+
+ +
+
+
+ +
+ +
+
+
系统信息
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
系统版本1.2.0内测版
服务器地址graduation.zengjianqi.com
服务器环境LAMP
运行环境Linux/CentOS-7.3
Apache版本2.4.41
MySQL版本5.6.44
PHP版本7.2
ThinkPHP版本5.0
Layui版本2.5.6
X-admin版本2.2/Deepin modified version by ZJQ
ECharts版本4.6.0
+
+
+
+
+
+
开发团队
+
+ + + + + + + + + + + + + + + + +
前端曾健起
后端曾健起
版权所有 + © Copyright + Reserved.zengjianqi.com   zengjianqi163@163.com +
+
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/application/index/view/index/index.html b/application/index/view/index/index.html new file mode 100644 index 0000000..7cdff5d --- /dev/null +++ b/application/index/view/index/index.html @@ -0,0 +1,131 @@ + + + + + 物联网云平台 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/index/view/tools/admin_add.html b/application/index/view/tools/admin_add.html new file mode 100644 index 0000000..5623f1e --- /dev/null +++ b/application/index/view/tools/admin_add.html @@ -0,0 +1,183 @@ + + + + + 欢迎页面-X-admin2.2 + + + + + + + + + + + + + +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/application/index/view/tools/admin_edit.html b/application/index/view/tools/admin_edit.html new file mode 100644 index 0000000..88ac407 --- /dev/null +++ b/application/index/view/tools/admin_edit.html @@ -0,0 +1,192 @@ + + + + + 欢迎页面-X-admin2.2 + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/application/index/view/tools/device_add_page.html b/application/index/view/tools/device_add_page.html new file mode 100644 index 0000000..626cea8 --- /dev/null +++ b/application/index/view/tools/device_add_page.html @@ -0,0 +1,188 @@ + + + + + 设备ID绑定 + + + + + + + + + + + + + + +
+
+
+ 绑定新的{$devName}设备  +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/application/index/view/tools/device_id_add_page.html b/application/index/view/tools/device_id_add_page.html new file mode 100644 index 0000000..952cf3b --- /dev/null +++ b/application/index/view/tools/device_id_add_page.html @@ -0,0 +1,139 @@ + + + + + 设备ID入库 + + + + + + + + + + + + + +
+
+
+ 增加新的设备ID  +
+
+
+ +
+ +
+
+
+ + +
+ +
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/application/index/view/tools/device_operation.html b/application/index/view/tools/device_operation.html new file mode 100644 index 0000000..2cb55c8 --- /dev/null +++ b/application/index/view/tools/device_operation.html @@ -0,0 +1,284 @@ + + + + + + 增删设备 + + + + + + + + + + + +
+ +
+
+
+
+
+
+ 删除设备  +
+
+ {foreach $data as $value} + + {/foreach} +
+
+
+
+
+
+ 添加设备  +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ + 图标预览 + +
+ +
+ +
+ + +
+
+ + +
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/application/index/view/tools/getData.html b/application/index/view/tools/getData.html new file mode 100644 index 0000000..663285f --- /dev/null +++ b/application/index/view/tools/getData.html @@ -0,0 +1,1070 @@ + + + + + Title + + +
    +
  • + +
    实心
    +
    &#xe68f;
    +
    layui-icon-heart-fill
    +
  • +
  • + +
    空心
    +
    &#xe68c;
    +
    layui-icon-heart
    +
  • +
  • + +
    亮度/晴
    +
    &#xe748;
    +
    layui-icon-light
    +
  • +
  • + +
    时间/历史
    +
    &#xe68d;
    +
    layui-icon-time
    +
  • + +
  • + +
    蓝牙
    +
    &#xe689;
    +
    layui-icon-bluetooth
    +
  • +
  • + +
    @艾特
    +
    &#xe687;
    +
    layui-icon-at
    +
  • +
  • + +
    静音
    +
    &#xe685;
    +
    layui-icon-mute
    +
  • +
  • + +
    录音/麦克风
    +
    &#xe6dc;
    +
    layui-icon-mike
    +
  • +
  • + +
    密钥/钥匙
    +
    &#xe683;
    +
    layui-icon-key
    +
  • +
  • + +
    礼物/活动
    +
    &#xe627;
    +
    layui-icon-gift
    +
  • +
  • + +
    邮箱
    +
    &#xe618;
    +
    layui-icon-email
    +
  • +
  • + +
    RSS
    +
    &#xe808;
    +
    layui-icon-rss
    +
  • +
  • + +
    WiFi
    +
    &#xe7e0;
    +
    layui-icon-wifi
    +
  • +
  • + +
    退出/注销
    +
    &#xe682;
    +
    layui-icon-logout
    +
  • +
  • + +
    Android 安卓
    +
    &#xe684;
    +
    layui-icon-android
    +
  • +
  • + +
    Apple IOS 苹果
    +
    &#xe680;
    +
    layui-icon-ios
    +
  • +
  • + +
    Windows
    +
    &#xe67f;
    +
    layui-icon-windows
    +
  • +
  • + +
    穿梭框
    +
    &#xe691;
    +
    layui-icon-transfer
    +
  • +
  • + +
    客服
    +
    &#xe626;
    +
    layui-icon-service
    +
  • +
  • + +
    +
    &#xe67e;
    +
    layui-icon-subtraction
    +
  • +
  • + +
    +
    &#xe624;
    +
    layui-icon-addition
    +
  • +
  • + +
    滑块
    +
    &#xe714;
    +
    layui-icon-slider
    +
  • +
  • + +
    打印
    +
    &#xe66d;
    +
    layui-icon-print
    +
  • +
  • + +
    导出
    +
    &#xe67d;
    +
    layui-icon-export
    +
  • +
  • + +
    +
    &#xe610;
    +
    layui-icon-cols
    +
  • +
  • + +
    退出全屏
    +
    &#xe758;
    +
    layui-icon-screen-restore
    +
  • +
  • + +
    全屏
    +
    &#xe622;
    +
    layui-icon-screen-full
    +
  • + +
  • + +
    半星
    +
    &#xe6c9;
    +
    layui-icon-rate-half
    +
  • +
  • + +
    星星-空心
    +
    &#xe67b;
    +
    layui-icon-rate
    +
  • +
  • + +
    星星-实心
    +
    &#xe67a;
    +
    layui-icon-rate-solid
    +
  • +
  • + +
    手机
    +
    &#xe678;
    +
    layui-icon-cellphone
    +
  • +
  • + +
    验证码
    +
    &#xe679;
    +
    layui-icon-vercode
    +
  • +
  • + +
    微信
    +
    &#xe677;
    +
    layui-icon-login-wechat
    +
  • +
  • + +
    QQ
    +
    &#xe676;
    +
    layui-icon-login-qq
    +
  • + +
  • + +
    微博
    +
    &#xe675;
    +
    layui-icon-login-weibo
    +
  • +
  • + +
    密码
    +
    &#xe673;
    +
    layui-icon-password
    +
  • +
  • + +
    用户名
    +
    &#xe66f;
    +
    layui-icon-username
    +
  • +
  • + +
    刷新-粗
    +
    &#xe9aa;
    +
    layui-icon-refresh-3
    +
  • +
  • + +
    授权
    +
    &#xe672;
    +
    layui-icon-auz
    +
  • +
  • + +
    左向右伸缩菜单
    +
    &#xe66b;
    +
    layui-icon-spread-left
    +
  • +
  • + +
    右向左伸缩菜单
    +
    &#xe668;
    +
    layui-icon-shrink-right
    +
  • + +
  • + +
    雪花
    +
    &#xe6b1;
    +
    layui-icon-snowflake
    +
  • +
  • + +
    提示说明
    +
    &#xe702;
    +
    layui-icon-tips
    +
  • +
  • + +
    便签
    +
    &#xe66e;
    +
    layui-icon-note
    +
  • +
  • + +
    主页
    +
    &#xe68e;
    +
    layui-icon-home
    +
  • +
  • + +
    高级
    +
    &#xe674;
    +
    layui-icon-senior
    +
  • +
  • + +
    刷新
    +
    &#xe669;
    +
    layui-icon-refresh
    +
  • +
  • + +
    刷新
    +
    &#xe666;
    +
    layui-icon-refresh-1
    +
  • + +
  • + +
    旗帜
    +
    &#xe66c;
    +
    layui-icon-flag
    +
  • +
  • + +
    主题
    +
    &#xe66a;
    +
    layui-icon-theme
    +
  • +
  • + +
    消息-通知
    +
    &#xe667;
    +
    layui-icon-notice
    +
  • +
  • + +
    网站
    +
    &#xe7ae;
    +
    layui-icon-website
    +
  • +
  • + +
    控制台
    +
    &#xe665;
    +
    layui-icon-console
    +
  • +
  • + +
    表情-惊讶
    +
    &#xe664;
    +
    layui-icon-face-surprised
    +
  • +
  • + +
    设置-空心
    +
    &#xe716;
    +
    layui-icon-set
    +
  • + +
  • + +
    模板
    +
    &#xe656;
    +
    layui-icon-template-1
    +
  • +
  • + +
    应用
    +
    &#xe653;
    +
    layui-icon-app
    +
  • +
  • + +
    模板
    +
    &#xe663;
    +
    layui-icon-template
    +
  • +
  • + +
    +
    &#xe6c6;
    +
    layui-icon-praise
    +
  • +
  • + +
    +
    &#xe6c5;
    +
    layui-icon-tread
    +
  • +
  • + +
    +
    &#xe662;
    +
    layui-icon-male
    +
  • + +
  • + +
    +
    &#xe661;
    +
    layui-icon-female
    +
  • +
  • + +
    相机-空心
    +
    &#xe660;
    +
    layui-icon-camera
    +
  • +
  • + +
    相机-实心
    +
    &#xe65d;
    +
    layui-icon-camera-fill
    +
  • +
  • + +
    菜单-水平
    +
    &#xe65f;
    +
    layui-icon-more
    +
  • +
  • + +
    菜单-垂直
    +
    &#xe671;
    +
    layui-icon-more-vertical
    +
  • +
  • + +
    金额-人民币
    +
    &#xe65e;
    +
    layui-icon-rmb
    +
  • +
  • + +
    金额-美元
    +
    &#xe659;
    +
    layui-icon-dollar
    +
  • +
  • + +
    钻石-等级
    +
    &#xe735;
    +
    layui-icon-diamond
    +
  • + +
  • + +
    +
    &#xe756;
    +
    layui-icon-fire
    +
  • +
  • + +
    返回
    +
    &#xe65c;
    +
    layui-icon-return
    +
  • +
  • + +
    位置-地图
    +
    &#xe715;
    +
    layui-icon-location
    +
  • +
  • + +
    办公-阅读
    +
    &#xe705;
    +
    layui-icon-read
    +
  • +
  • + +
    调查
    +
    &#xe6b2;
    +
    layui-icon-survey
    +
  • +
  • + +
    表情-微笑
    +
    &#xe6af;
    +
    layui-icon-face-smile
    +
  • +
  • + +
    表情-哭泣
    +
    &#xe69c;
    +
    layui-icon-face-cry
    +
  • + +
  • + +
    购物车
    +
    &#xe698;
    +
    layui-icon-cart-simple
    +
  • +
  • + +
    购物车
    +
    &#xe657;
    +
    layui-icon-cart
    +
  • +
  • + +
    下一页
    +
    &#xe65b;
    +
    layui-icon-next
    +
  • +
  • + +
    上一页
    +
    &#xe65a;
    +
    layui-icon-prev
    +
  • +
  • + +
    上传-空心-拖拽
    +
    &#xe681;
    +
    layui-icon-upload-drag
    +
  • +
  • + +
    上传-实心
    +
    &#xe67c;
    +
    layui-icon-upload
    +
  • +
  • + +
    下载-圆圈
    +
    &#xe601;
    +
    layui-icon-download-circle
    +
  • + +
  • + +
    组件
    +
    &#xe857;
    +
    layui-icon-component
    +
  • +
  • + +
    文件-粗
    +
    &#xe655;
    +
    layui-icon-file-b
    +
  • +
  • + +
    用户
    +
    &#xe770;
    +
    layui-icon-user
    +
  • +
  • + +
    发现-实心
    +
    &#xe670;
    +
    layui-icon-find-fill
    +
  • +
  • + +
    loading
    +
    &#xe63d;
    +
    layui-icon-loading
    +
  • +
  • + +
    loading
    +
    &#xe63e;
    +
    layui-icon-loading-1
    +
  • +
  • + +
    添加
    +
    &#xe654;
    +
    layui-icon-add-1
    +
  • + +
  • + +
    播放
    +
    &#xe652;
    +
    layui-icon-play
    +
  • +
  • + +
    暂停
    +
    &#xe651;
    +
    layui-icon-pause
    +
  • +
  • + +
    音频-耳机
    +
    &#xe6fc;
    +
    layui-icon-headset
    +
  • +
  • + +
    视频
    +
    &#xe6ed;
    +
    layui-icon-video
    +
  • +
  • + +
    语音-声音
    +
    &#xe688;
    +
    layui-icon-voice
    +
  • +
  • + +
    消息-通知-喇叭
    +
    &#xe645;
    +
    layui-icon-speaker
    +
  • +
  • + +
    删除线
    +
    &#xe64f;
    +
    layui-icon-fonts-del
    +
  • + +
  • + +
    代码
    +
    &#xe64e;
    +
    layui-icon-fonts-code
    +
  • +
  • + +
    HTML
    +
    &#xe64b;
    +
    layui-icon-fonts-html
    +
  • +
  • + +
    字体加粗
    +
    &#xe62b;
    +
    layui-icon-fonts-strong
    +
  • +
  • + +
    删除链接
    +
    &#xe64d;
    +
    layui-icon-unlink
    +
  • +
  • + +
    图片
    +
    &#xe64a;
    +
    layui-icon-picture
    +
  • +
  • + +
    链接
    +
    &#xe64c;
    +
    layui-icon-link
    +
  • +
  • + +
    表情-笑-粗
    +
    &#xe650;
    +
    layui-icon-face-smile-b
    +
  • + +
  • + +
    左对齐
    +
    &#xe649;
    +
    layui-icon-align-left
    +
  • +
  • + +
    右对齐
    +
    &#xe648;
    +
    layui-icon-align-right
    +
  • +
  • + +
    居中对齐
    +
    &#xe647;
    +
    layui-icon-align-center
    +
  • +
  • + +
    字体-下划线
    +
    &#xe646;
    +
    layui-icon-fonts-u
    +
  • +
  • + +
    字体-斜体
    +
    &#xe644;
    +
    layui-icon-fonts-i
    +
  • +
  • + +
    Tabs 选项卡
    +
    &#xe62a;
    +
    layui-icon-tabs
    +
  • +
  • + +
    单选框-选中
    +
    &#xe643;
    +
    layui-icon-radio
    +
  • + +
  • + +
    单选框-候选
    +
    &#xe63f;
    +
    layui-icon-circle
    +
  • +
  • + +
    编辑
    +
    &#xe642;
    +
    layui-icon-edit
    +
  • +
  • + +
    分享
    +
    &#xe641;
    +
    layui-icon-share
    +
  • +
  • + +
    删除
    +
    &#xe640;
    +
    layui-icon-delete
    +
  • +
  • + +
    表单
    +
    &#xe63c;
    +
    layui-icon-form
    +
  • +
  • + +
    手机-细体
    +
    &#xe63b;
    +
    layui-icon-cellphone-fine
    +
  • +
  • + +
    聊天 对话 沟通
    +
    &#xe63a;
    +
    layui-icon-dialogue
    +
  • + +
  • + +
    文字格式化
    +
    &#xe639;
    +
    layui-icon-fonts-clear
    +
  • +
  • + +
    窗口
    +
    &#xe638;
    +
    layui-icon-layer
    +
  • +
  • + +
    日期
    +
    &#xe637;
    +
    layui-icon-date
    +
  • +
  • + +
    水 下雨
    +
    &#xe636;
    +
    layui-icon-water
    +
  • +
  • + +
    代码-圆圈
    +
    &#xe635;
    +
    layui-icon-code-circle
    +
  • +
  • + +
    轮播组图
    +
    &#xe634;
    +
    layui-icon-carousel
    +
  • +
  • + +
    翻页
    +
    &#xe633;
    +
    layui-icon-prev-circle
    +
  • + +
  • + +
    布局
    +
    &#xe632;
    +
    layui-icon-layouts
    +
  • +
  • + +
    工具
    +
    &#xe631;
    +
    layui-icon-util
    +
  • +
  • + +
    选择模板
    +
    &#xe630;
    +
    layui-icon-templeate-1
    +
  • +
  • + +
    上传-圆圈
    +
    &#xe62f;
    +
    layui-icon-upload-circle
    +
  • +
  • + +
    +
    &#xe62e;
    +
    layui-icon-tree
    +
  • +
  • + +
    表格
    +
    &#xe62d;
    +
    layui-icon-table
    +
  • +
  • + +
    图表
    +
    &#xe62c;
    +
    layui-icon-chart
    +
  • + +
  • + +
    图标 报表 屏幕
    +
    &#xe629;
    +
    layui-icon-chart-screen
    +
  • +
  • + +
    引擎
    +
    &#xe628;
    +
    layui-icon-engine
    +
  • +
  • + +
    下三角
    +
    &#xe625;
    +
    layui-icon-triangle-d
    +
  • +
  • + +
    右三角
    +
    &#xe623;
    +
    layui-icon-triangle-r
    +
  • +
  • + +
    文件
    +
    &#xe621;
    +
    layui-icon-file
    +
  • +
  • + +
    设置-小型
    +
    &#xe620;
    +
    layui-icon-set-sm
    +
  • +
  • + +
    减少-圆圈
    +
    &#xe616
    +
    layui-icon-reduce-circle
    +
  • +
  • + +
    添加-圆圈
    +
    &#xe61f;
    +
    layui-icon-add-circle
    +
  • + + +
  • + +
    404
    +
    &#xe61c;
    +
    layui-icon-404
    +
  • +
  • + +
    关于
    +
    &#xe60b;
    +
    layui-icon-about
    +
  • +
  • + +
    箭头 向上
    +
    &#xe619;
    +
    layui-icon-up
    +
  • +
  • + +
    箭头 向下
    +
    &#xe61a;
    +
    layui-icon-down
    +
  • +
  • + +
    箭头 向左
    +
    &#xe603;
    +
    layui-icon-left
    +
  • +
  • + +
    箭头 向右
    +
    &#xe602;
    +
    layui-icon-right
    +
  • +
  • + +
    圆点
    +
    &#xe617;
    +
    layui-icon-circle-dot
    +
  • + +
  • + +
    搜索
    +
    &#xe615;
    +
    layui-icon-search
    +
  • +
  • + +
    设置-实心
    +
    &#xe614;
    +
    layui-icon-set-fill
    +
  • +
  • + +
    群组
    +
    &#xe613;
    +
    layui-icon-group
    +
  • +
  • + +
    好友
    +
    &#xe612;
    +
    layui-icon-friends
    +
  • +
  • + +
    回复 评论 实心
    +
    &#xe611;
    +
    layui-icon-reply-fill
    +
  • +
  • + +
    菜单 隐身 实心
    +
    &#xe60f;
    +
    layui-icon-menu-fill
    +
  • +
  • + +
    记录
    +
    &#xe60e;
    +
    layui-icon-log
    +
  • + +
  • + +
    图片-细体
    +
    &#xe60d;
    +
    layui-icon-picture-fine
    +
  • +
  • + +
    表情-笑-细体
    +
    &#xe60c;
    +
    layui-icon-face-smile-fine
    +
  • +
  • + +
    列表
    +
    &#xe60a;
    +
    layui-icon-list
    +
  • +
  • + +
    发布 纸飞机
    +
    &#xe609;
    +
    layui-icon-release
    +
  • +
  • + +
    对 OK
    +
    &#xe605;
    +
    layui-icon-ok
    +
  • +
  • + +
    帮助
    +
    &#xe607;
    +
    layui-icon-help
    +
  • +
  • + +
    客服
    +
    &#xe606;
    +
    layui-icon-chat
    +
  • + +
  • + +
    top 置顶
    +
    &#xe604;
    +
    layui-icon-top
    +
  • +
  • + +
    收藏-空心
    +
    &#xe600;
    +
    layui-icon-star
    +
  • +
  • + +
    收藏-实心
    +
    &#xe658;
    +
    layui-icon-star-fill
    +
  • +
  • + +
    关闭-实心
    +
    &#x1007;
    +
    layui-icon-close-fill
    +
  • +
  • + +
    关闭-空心
    +
    &#x1006;
    +
    layui-icon-close
    +
  • +
  • + +
    正确
    +
    &#x1005;
    +
    layui-icon-ok-circle
    +
  • +
  • + +
    添加-圆圈-细体
    +
    &#xe608;
    +
    layui-icon-add-circle-fine
    +
  • +
+ + + + \ No newline at end of file diff --git a/application/index/view/tools/icon.html b/application/index/view/tools/icon.html new file mode 100644 index 0000000..3df48b3 --- /dev/null +++ b/application/index/view/tools/icon.html @@ -0,0 +1,1211 @@ + + + + + Title + + + + + + +
+
+
+
+
+ +
+ 内置图标一览表(168个) +
+ +
+
+
+
    +
  • + +
    实心
    +
    &#xe68f;
    +
    layui-icon-heart-fill
    +
  • +
  • + +
    空心
    +
    &#xe68c;
    +
    layui-icon-heart
    +
  • +
  • + +
    亮度/晴
    +
    &#xe748;
    +
    layui-icon-light
    +
  • +
  • + +
    时间/历史
    +
    &#xe68d;
    +
    layui-icon-time
    +
  • + +
  • + +
    蓝牙
    +
    &#xe689;
    +
    layui-icon-bluetooth
    +
  • +
  • + +
    @艾特
    +
    &#xe687;
    +
    layui-icon-at
    +
  • +
  • + +
    静音
    +
    &#xe685;
    +
    layui-icon-mute
    +
  • +
  • + +
    录音/麦克风
    +
    &#xe6dc;
    +
    layui-icon-mike
    +
  • +
  • + +
    密钥/钥匙
    +
    &#xe683;
    +
    layui-icon-key
    +
  • +
  • + +
    礼物/活动
    +
    &#xe627;
    +
    layui-icon-gift
    +
  • +
  • + +
    邮箱
    +
    &#xe618;
    +
    layui-icon-email
    +
  • +
  • + +
    RSS
    +
    &#xe808;
    +
    layui-icon-rss
    +
  • +
  • + +
    WiFi
    +
    &#xe7e0;
    +
    layui-icon-wifi
    +
  • +
  • + +
    退出/注销
    +
    &#xe682;
    +
    layui-icon-logout
    +
  • +
  • + +
    Android 安卓
    +
    &#xe684;
    +
    layui-icon-android
    +
  • +
  • + +
    Apple IOS 苹果
    +
    &#xe680;
    +
    layui-icon-ios
    +
  • +
  • + +
    Windows
    +
    &#xe67f;
    +
    layui-icon-windows
    +
  • +
  • + +
    穿梭框
    +
    &#xe691;
    +
    layui-icon-transfer
    +
  • +
  • + +
    客服
    +
    &#xe626;
    +
    layui-icon-service
    +
  • +
  • + +
    +
    &#xe67e;
    +
    layui-icon-subtraction
    +
  • +
  • + +
    +
    &#xe624;
    +
    layui-icon-addition
    +
  • +
  • + +
    滑块
    +
    &#xe714;
    +
    layui-icon-slider
    +
  • +
  • + +
    打印
    +
    &#xe66d;
    +
    layui-icon-print
    +
  • +
  • + +
    导出
    +
    &#xe67d;
    +
    layui-icon-export
    +
  • +
  • + +
    +
    &#xe610;
    +
    layui-icon-cols
    +
  • +
  • + +
    退出全屏
    +
    &#xe758;
    +
    layui-icon-screen-restore
    +
  • +
  • + +
    全屏
    +
    &#xe622;
    +
    layui-icon-screen-full
    +
  • + +
  • + +
    半星
    +
    &#xe6c9;
    +
    layui-icon-rate-half
    +
  • +
  • + +
    星星-空心
    +
    &#xe67b;
    +
    layui-icon-rate
    +
  • +
  • + +
    星星-实心
    +
    &#xe67a;
    +
    layui-icon-rate-solid
    +
  • +
  • + +
    手机
    +
    &#xe678;
    +
    layui-icon-cellphone
    +
  • +
  • + +
    验证码
    +
    &#xe679;
    +
    layui-icon-vercode
    +
  • +
  • + +
    微信
    +
    &#xe677;
    +
    layui-icon-login-wechat
    +
  • +
  • + +
    QQ
    +
    &#xe676;
    +
    layui-icon-login-qq
    +
  • + +
  • + +
    微博
    +
    &#xe675;
    +
    layui-icon-login-weibo
    +
  • +
  • + +
    密码
    +
    &#xe673;
    +
    layui-icon-password
    +
  • +
  • + +
    用户名
    +
    &#xe66f;
    +
    layui-icon-username
    +
  • +
  • + +
    刷新-粗
    +
    &#xe9aa;
    +
    layui-icon-refresh-3
    +
  • +
  • + +
    授权
    +
    &#xe672;
    +
    layui-icon-auz
    +
  • +
  • + +
    左向右伸缩菜单
    +
    &#xe66b;
    +
    layui-icon-spread-left
    +
  • +
  • + +
    右向左伸缩菜单
    +
    &#xe668;
    +
    layui-icon-shrink-right
    +
  • + +
  • + +
    雪花
    +
    &#xe6b1;
    +
    layui-icon-snowflake
    +
  • +
  • + +
    提示说明
    +
    &#xe702;
    +
    layui-icon-tips
    +
  • +
  • + +
    便签
    +
    &#xe66e;
    +
    layui-icon-note
    +
  • +
  • + +
    主页
    +
    &#xe68e;
    +
    layui-icon-home
    +
  • +
  • + +
    高级
    +
    &#xe674;
    +
    layui-icon-senior
    +
  • +
  • + +
    刷新
    +
    &#xe669;
    +
    layui-icon-refresh
    +
  • +
  • + +
    刷新
    +
    &#xe666;
    +
    layui-icon-refresh-1
    +
  • + +
  • + +
    旗帜
    +
    &#xe66c;
    +
    layui-icon-flag
    +
  • +
  • + +
    主题
    +
    &#xe66a;
    +
    layui-icon-theme
    +
  • +
  • + +
    消息-通知
    +
    &#xe667;
    +
    layui-icon-notice
    +
  • +
  • + +
    网站
    +
    &#xe7ae;
    +
    layui-icon-website
    +
  • +
  • + +
    控制台
    +
    &#xe665;
    +
    layui-icon-console
    +
  • +
  • + +
    表情-惊讶
    +
    &#xe664;
    +
    layui-icon-face-surprised
    +
  • +
  • + +
    设置-空心
    +
    &#xe716;
    +
    layui-icon-set
    +
  • + +
  • + +
    模板
    +
    &#xe656;
    +
    layui-icon-template-1
    +
  • +
  • + +
    应用
    +
    &#xe653;
    +
    layui-icon-app
    +
  • +
  • + +
    模板
    +
    &#xe663;
    +
    layui-icon-template
    +
  • +
  • + +
    +
    &#xe6c6;
    +
    layui-icon-praise
    +
  • +
  • + +
    +
    &#xe6c5;
    +
    layui-icon-tread
    +
  • +
  • + +
    +
    &#xe662;
    +
    layui-icon-male
    +
  • + +
  • + +
    +
    &#xe661;
    +
    layui-icon-female
    +
  • +
  • + +
    相机-空心
    +
    &#xe660;
    +
    layui-icon-camera
    +
  • +
  • + +
    相机-实心
    +
    &#xe65d;
    +
    layui-icon-camera-fill
    +
  • +
  • + +
    菜单-水平
    +
    &#xe65f;
    +
    layui-icon-more
    +
  • +
  • + +
    菜单-垂直
    +
    &#xe671;
    +
    layui-icon-more-vertical
    +
  • +
  • + +
    金额-人民币
    +
    &#xe65e;
    +
    layui-icon-rmb
    +
  • +
  • + +
    金额-美元
    +
    &#xe659;
    +
    layui-icon-dollar
    +
  • +
  • + +
    钻石-等级
    +
    &#xe735;
    +
    layui-icon-diamond
    +
  • + +
  • + +
    +
    &#xe756;
    +
    layui-icon-fire
    +
  • +
  • + +
    返回
    +
    &#xe65c;
    +
    layui-icon-return
    +
  • +
  • + +
    位置-地图
    +
    &#xe715;
    +
    layui-icon-location
    +
  • +
  • + +
    办公-阅读
    +
    &#xe705;
    +
    layui-icon-read
    +
  • +
  • + +
    调查
    +
    &#xe6b2;
    +
    layui-icon-survey
    +
  • +
  • + +
    表情-微笑
    +
    &#xe6af;
    +
    layui-icon-face-smile
    +
  • +
  • + +
    表情-哭泣
    +
    &#xe69c;
    +
    layui-icon-face-cry
    +
  • + +
  • + +
    购物车
    +
    &#xe698;
    +
    layui-icon-cart-simple
    +
  • +
  • + +
    购物车
    +
    &#xe657;
    +
    layui-icon-cart
    +
  • +
  • + +
    下一页
    +
    &#xe65b;
    +
    layui-icon-next
    +
  • +
  • + +
    上一页
    +
    &#xe65a;
    +
    layui-icon-prev
    +
  • +
  • + +
    上传-空心-拖拽
    +
    &#xe681;
    +
    layui-icon-upload-drag
    +
  • +
  • + +
    上传-实心
    +
    &#xe67c;
    +
    layui-icon-upload
    +
  • +
  • + +
    下载-圆圈
    +
    &#xe601;
    +
    layui-icon-download-circle
    +
  • + +
  • + +
    组件
    +
    &#xe857;
    +
    layui-icon-component
    +
  • +
  • + +
    文件-粗
    +
    &#xe655;
    +
    layui-icon-file-b
    +
  • +
  • + +
    用户
    +
    &#xe770;
    +
    layui-icon-user
    +
  • +
  • + +
    发现-实心
    +
    &#xe670;
    +
    layui-icon-find-fill
    +
  • +
  • + +
    loading
    +
    &#xe63d;
    +
    layui-icon-loading
    +
  • +
  • + +
    loading
    +
    &#xe63e;
    +
    layui-icon-loading-1
    +
  • +
  • + +
    添加
    +
    &#xe654;
    +
    layui-icon-add-1
    +
  • + +
  • + +
    播放
    +
    &#xe652;
    +
    layui-icon-play
    +
  • +
  • + +
    暂停
    +
    &#xe651;
    +
    layui-icon-pause
    +
  • +
  • + +
    音频-耳机
    +
    &#xe6fc;
    +
    layui-icon-headset
    +
  • +
  • + +
    视频
    +
    &#xe6ed;
    +
    layui-icon-video
    +
  • +
  • + +
    语音-声音
    +
    &#xe688;
    +
    layui-icon-voice
    +
  • +
  • + +
    消息-通知-喇叭
    +
    &#xe645;
    +
    layui-icon-speaker
    +
  • +
  • + +
    删除线
    +
    &#xe64f;
    +
    layui-icon-fonts-del
    +
  • + +
  • + +
    代码
    +
    &#xe64e;
    +
    layui-icon-fonts-code
    +
  • +
  • + +
    HTML
    +
    &#xe64b;
    +
    layui-icon-fonts-html
    +
  • +
  • + +
    字体加粗
    +
    &#xe62b;
    +
    layui-icon-fonts-strong
    +
  • +
  • + +
    删除链接
    +
    &#xe64d;
    +
    layui-icon-unlink
    +
  • +
  • + +
    图片
    +
    &#xe64a;
    +
    layui-icon-picture
    +
  • +
  • + +
    链接
    +
    &#xe64c;
    +
    layui-icon-link
    +
  • +
  • + +
    表情-笑-粗
    +
    &#xe650;
    +
    layui-icon-face-smile-b
    +
  • + +
  • + +
    左对齐
    +
    &#xe649;
    +
    layui-icon-align-left
    +
  • +
  • + +
    右对齐
    +
    &#xe648;
    +
    layui-icon-align-right
    +
  • +
  • + +
    居中对齐
    +
    &#xe647;
    +
    layui-icon-align-center
    +
  • +
  • + +
    字体-下划线
    +
    &#xe646;
    +
    layui-icon-fonts-u
    +
  • +
  • + +
    字体-斜体
    +
    &#xe644;
    +
    layui-icon-fonts-i
    +
  • +
  • + +
    Tabs 选项卡
    +
    &#xe62a;
    +
    layui-icon-tabs
    +
  • +
  • + +
    单选框-选中
    +
    &#xe643;
    +
    layui-icon-radio
    +
  • + +
  • + +
    单选框-候选
    +
    &#xe63f;
    +
    layui-icon-circle
    +
  • +
  • + +
    编辑
    +
    &#xe642;
    +
    layui-icon-edit
    +
  • +
  • + +
    分享
    +
    &#xe641;
    +
    layui-icon-share
    +
  • +
  • + +
    删除
    +
    &#xe640;
    +
    layui-icon-delete
    +
  • +
  • + +
    表单
    +
    &#xe63c;
    +
    layui-icon-form
    +
  • +
  • + +
    手机-细体
    +
    &#xe63b;
    +
    layui-icon-cellphone-fine
    +
  • +
  • + +
    聊天 对话 沟通
    +
    &#xe63a;
    +
    layui-icon-dialogue
    +
  • + +
  • + +
    文字格式化
    +
    &#xe639;
    +
    layui-icon-fonts-clear
    +
  • +
  • + +
    窗口
    +
    &#xe638;
    +
    layui-icon-layer
    +
  • +
  • + +
    日期
    +
    &#xe637;
    +
    layui-icon-date
    +
  • +
  • + +
    水 下雨
    +
    &#xe636;
    +
    layui-icon-water
    +
  • +
  • + +
    代码-圆圈
    +
    &#xe635;
    +
    layui-icon-code-circle
    +
  • +
  • + +
    轮播组图
    +
    &#xe634;
    +
    layui-icon-carousel
    +
  • +
  • + +
    翻页
    +
    &#xe633;
    +
    layui-icon-prev-circle
    +
  • + +
  • + +
    布局
    +
    &#xe632;
    +
    layui-icon-layouts
    +
  • +
  • + +
    工具
    +
    &#xe631;
    +
    layui-icon-util
    +
  • +
  • + +
    选择模板
    +
    &#xe630;
    +
    layui-icon-templeate-1
    +
  • +
  • + +
    上传-圆圈
    +
    &#xe62f;
    +
    layui-icon-upload-circle
    +
  • +
  • + +
    +
    &#xe62e;
    +
    layui-icon-tree
    +
  • +
  • + +
    表格
    +
    &#xe62d;
    +
    layui-icon-table
    +
  • +
  • + +
    图表
    +
    &#xe62c;
    +
    layui-icon-chart
    +
  • + +
  • + +
    图标 报表 屏幕
    +
    &#xe629;
    +
    layui-icon-chart-screen
    +
  • +
  • + +
    引擎
    +
    &#xe628;
    +
    layui-icon-engine
    +
  • +
  • + +
    下三角
    +
    &#xe625;
    +
    layui-icon-triangle-d
    +
  • +
  • + +
    右三角
    +
    &#xe623;
    +
    layui-icon-triangle-r
    +
  • +
  • + +
    文件
    +
    &#xe621;
    +
    layui-icon-file
    +
  • +
  • + +
    设置-小型
    +
    &#xe620;
    +
    layui-icon-set-sm
    +
  • +
  • + +
    减少-圆圈
    +
    &#xe616;
    +
    layui-icon-reduce-circle
    +
  • +
  • + +
    添加-圆圈
    +
    &#xe61f;
    +
    layui-icon-add-circle
    +
  • + + +
  • + +
    404
    +
    &#xe61c;
    +
    layui-icon-404
    +
  • +
  • + +
    关于
    +
    &#xe60b;
    +
    layui-icon-about
    +
  • +
  • + +
    箭头 向上
    +
    &#xe619;
    +
    layui-icon-up
    +
  • +
  • + +
    箭头 向下
    +
    &#xe61a;
    +
    layui-icon-down
    +
  • +
  • + +
    箭头 向左
    +
    &#xe603;
    +
    layui-icon-left
    +
  • +
  • + +
    箭头 向右
    +
    &#xe602;
    +
    layui-icon-right
    +
  • +
  • + +
    圆点
    +
    &#xe617;
    +
    layui-icon-circle-dot
    +
  • + +
  • + +
    搜索
    +
    &#xe615;
    +
    layui-icon-search
    +
  • +
  • + +
    设置-实心
    +
    &#xe614;
    +
    layui-icon-set-fill
    +
  • +
  • + +
    群组
    +
    &#xe613;
    +
    layui-icon-group
    +
  • +
  • + +
    好友
    +
    &#xe612;
    +
    layui-icon-friends
    +
  • +
  • + +
    回复 评论 实心
    +
    &#xe611;
    +
    layui-icon-reply-fill
    +
  • +
  • + +
    菜单 隐身 实心
    +
    &#xe60f;
    +
    layui-icon-menu-fill
    +
  • +
  • + +
    记录
    +
    &#xe60e;
    +
    layui-icon-log
    +
  • + +
  • + +
    图片-细体
    +
    &#xe60d;
    +
    layui-icon-picture-fine
    +
  • +
  • + +
    表情-笑-细体
    +
    &#xe60c;
    +
    layui-icon-face-smile-fine
    +
  • +
  • + +
    列表
    +
    &#xe60a;
    +
    layui-icon-list
    +
  • +
  • + +
    发布 纸飞机
    +
    &#xe609;
    +
    layui-icon-release
    +
  • +
  • + +
    对 OK
    +
    &#xe605;
    +
    layui-icon-ok
    +
  • +
  • + +
    帮助
    +
    &#xe607;
    +
    layui-icon-help
    +
  • +
  • + +
    客服
    +
    &#xe606;
    +
    layui-icon-chat
    +
  • + +
  • + +
    top 置顶
    +
    &#xe604;
    +
    layui-icon-top
    +
  • +
  • + +
    收藏-空心
    +
    &#xe600;
    +
    layui-icon-star
    +
  • +
  • + +
    收藏-实心
    +
    &#xe658;
    +
    layui-icon-star-fill
    +
  • +
  • + +
    关闭-实心
    +
    &#x1007;
    +
    layui-icon-close-fill
    +
  • +
  • + +
    关闭-空心
    +
    &#x1006;
    +
    layui-icon-close
    +
  • +
  • + +
    正确
    +
    &#x1005;
    +
    layui-icon-ok-circle
    +
  • +
  • + +
    添加-圆圈-细体
    +
    &#xe608;
    +
    layui-icon-add-circle-fine
    +
  • +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/application/index/view/tools/show_detail.html b/application/index/view/tools/show_detail.html new file mode 100644 index 0000000..da579de --- /dev/null +++ b/application/index/view/tools/show_detail.html @@ -0,0 +1,443 @@ + + + + + {$devName}设备ID:{$serial_num}  + + + + + + + + + + + +
+
+
+
+
+ {$devName}设备ID:{$serial_num}  +
+
+ +
+
+
设置数据刷新频率(默认为3s)
+
+ + + + +
+
+
+
+
+
实时数据
+
+
+ +
+
+
+
+
+
最近数据
+
+
+ +
+
+
+
+ +
+
历史数据 + +
+ +
+
+
+ +
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/application/index/view/tools/user_add.html b/application/index/view/tools/user_add.html new file mode 100644 index 0000000..ec87e60 --- /dev/null +++ b/application/index/view/tools/user_add.html @@ -0,0 +1,183 @@ + + + + + 新增用户 + + + + + + + + + + + + + +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/application/index/view/tools/user_edit.html b/application/index/view/tools/user_edit.html new file mode 100644 index 0000000..a9b38f7 --- /dev/null +++ b/application/index/view/tools/user_edit.html @@ -0,0 +1,191 @@ + + + + + 编辑用户信息 + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/application/route.php b/application/route.php new file mode 100644 index 0000000..591e2c3 --- /dev/null +++ b/application/route.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- + +return [ + '__pattern__' => [ + 'name' => '\w+', + ], + '[hello]' => [ + ':id' => ['index/hello', ['method' => 'get'], ['id' => '\d+']], + ':name' => ['index/hello', ['method' => 'post']], + ], + +]; diff --git a/application/tags.php b/application/tags.php new file mode 100644 index 0000000..4b18d10 --- /dev/null +++ b/application/tags.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- + +// 应用行为扩展定义文件 +return [ + // 应用初始化 + 'app_init' => [], + // 应用开始 + 'app_begin' => [], + // 模块初始化 + 'module_init' => [], + // 操作开始执行 + 'action_begin' => [], + // 视图内容过滤 + 'view_filter' => [], + // 日志写入 + 'log_write' => [], + // 应用结束 + 'app_end' => [], +]; diff --git a/build.php b/build.php new file mode 100644 index 0000000..a12d26c --- /dev/null +++ b/build.php @@ -0,0 +1,25 @@ + +// +---------------------------------------------------------------------- + +return [ + // 生成应用公共文件 + '__file__' => ['common.php', 'config.php', 'database.php'], + + // 定义demo模块的自动生成 (按照实际定义的文件名生成) + 'demo' => [ + '__file__' => ['common.php'], + '__dir__' => ['behavior', 'controller', 'model', 'view'], + 'controller' => ['Index', 'Test', 'UserType'], + 'model' => ['User', 'UserType'], + 'view' => ['index/index'], + ], + // 其他更多的模块定义 +]; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..61d3fa7 --- /dev/null +++ b/composer.json @@ -0,0 +1,33 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=5.4.0", + "topthink/framework": "5.0.*" + }, + "autoload": { + "psr-4": { + "app\\": "application" + } + }, + "extra": { + "think-path": "thinkphp" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..ec72a41 --- /dev/null +++ b/composer.lock @@ -0,0 +1,528 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "57513f5dbb6f43960ca2e4899015c52d", + "packages": [ + { + "name": "topthink/framework", + "version": "v5.0.21", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "ab826da071a7a47116a7f1d01f72228d6bcf212a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/ab826da071a7a47116a7f1d01f72228d6bcf212a", + "reference": "ab826da071a7a47116a7f1d01f72228d6bcf212a", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phpdocumentor/reflection-docblock": "^2.0", + "phploc/phploc": "2.*", + "phpunit/phpunit": "4.8.*", + "sebastian/phpcpd": "2.*" + }, + "type": "think-framework", + "autoload": { + "psr-4": { + "think\\": "library/think" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the new thinkphp framework", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "time": "2018-09-04T09:18:48+00:00" + }, + { + "name": "topthink/think-captcha", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "0c55455df26a1626a60d0dc35d2d89002b741d44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/0c55455df26a1626a60d0dc35d2d89002b741d44", + "reference": "0c55455df26a1626a60d0dc35d2d89002b741d44", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\captcha\\": "src/" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "captcha package for thinkphp5", + "time": "2016-07-06T01:47:11+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f", + "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\helper\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Helper Package", + "time": "2017-04-05T07:15:37+00:00" + }, + { + "name": "topthink/think-image", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-image.git", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-image/zipball/8586cf47f117481c6d415b20f7dedf62e79d5512", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "topthink/framework": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Image Package", + "time": "2016-09-29T06:05:43+00:00" + }, + { + "name": "topthink/think-installer", + "version": "v1.0.12", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-installer.git", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-installer/zipball/1be326e68f63de4e95977ed50f46ae75f017556d", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "1.0.*@dev" + }, + "type": "composer-plugin", + "extra": { + "class": "think\\composer\\Plugin" + }, + "autoload": { + "psr-4": { + "think\\composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "time": "2017-05-27T06:58:09+00:00" + }, + { + "name": "topthink/think-migration", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-migration.git", + "reference": "8e489f8d38a39876690c0e00fcf9a54ae92c4d3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-migration/zipball/8e489f8d38a39876690c0e00fcf9a54ae92c4d3d", + "reference": "8e489f8d38a39876690c0e00fcf9a54ae92c4d3d", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require-dev": { + "topthink/framework": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phinx\\": "phinx/src/Phinx", + "think\\migration\\": "src" + }, + "files": [ + "src/config.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "time": "2017-03-31T06:33:23+00:00" + }, + { + "name": "topthink/think-mongo", + "version": "v1.8.5", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-mongo.git", + "reference": "657cc79bd5f090a58b0cc83776073dd69c83a3d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-mongo/zipball/657cc79bd5f090a58b0cc83776073dd69c83a3d1", + "reference": "657cc79bd5f090a58b0cc83776073dd69c83a3d1", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\mongo\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "mongodb driver for thinkphp5", + "time": "2018-06-03T01:51:27+00:00" + }, + { + "name": "topthink/think-oracle", + "version": "v1.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-oracle.git", + "reference": "89a049e876167030b489322f691aed00799fd68f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-oracle/zipball/89a049e876167030b489322f691aed00799fd68f", + "reference": "89a049e876167030b489322f691aed00799fd68f", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/framework": "~5.0.17" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\oracle\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "Oracle driver for thinkphp5", + "time": "2018-04-14T00:52:34+00:00" + }, + { + "name": "topthink/think-queue", + "version": "v1.1.5", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-queue.git", + "reference": "465320c9cb7811df22d4ff8f29f58ead7d104348" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-queue/zipball/465320c9cb7811df22d4ff8f29f58ead7d104348", + "reference": "465320c9cb7811df22d4ff8f29f58ead7d104348", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/think-helper": ">=1.0.4", + "topthink/think-installer": ">=1.0.10" + }, + "type": "think-extend", + "extra": { + "think-config": { + "queue": "src/config.php" + } + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/common.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Queue Package", + "time": "2018-05-04T05:29:53+00:00" + }, + { + "name": "topthink/think-worker", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-worker.git", + "reference": "b609ff5e38dbb7194aab027d2b2c6b31a7ed1bd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-worker/zipball/b609ff5e38dbb7194aab027d2b2c6b31a7ed1bd1", + "reference": "b609ff5e38dbb7194aab027d2b2c6b31a7ed1bd1", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "workerman/workerman": "^3.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\worker\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "workerman extend for thinkphp5", + "time": "2016-10-08T06:07:03+00:00" + }, + { + "name": "workerman/workerman", + "version": "v3.5.14", + "source": { + "type": "git", + "url": "https://github.com/walkor/Workerman.git", + "reference": "6d70996b2b4ec94000e807246aca0e861c9601d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/Workerman/zipball/6d70996b2b4ec94000e807246aca0e861c9601d8", + "reference": "6d70996b2b4ec94000e807246aca0e861c9601d8", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "time": "2018-08-04T12:41:54+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [] +} diff --git a/data.sql b/data.sql new file mode 100644 index 0000000..7fd2a1c --- /dev/null +++ b/data.sql @@ -0,0 +1,5695 @@ +-- phpMyAdmin SQL Dump +-- version 4.8.5 +-- +-- 作者:曾健起 +-- 环境: LAMP/WAMP +-- 生成日期: 2020-04-15 17:39:42 +-- 服务器版本: 5.7.26 +-- PHP 版本: 7.2.9 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET AUTOCOMMIT = 0; +START TRANSACTION; +SET time_zone = "+08:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- 数据库: `data` +-- + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_administrator` +-- + +CREATE TABLE `bs_administrator` ( + `id` int(11) NOT NULL, + `account` varchar(255) NOT NULL, + `password` char(32) NOT NULL, + `tel` varchar(32) NOT NULL, + `email` varchar(255) NOT NULL, + `status` tinyint(1) DEFAULT '0', + `create_time` datetime DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='管理员表'; + +-- +-- 转存表中的数据 `bs_administrator` +-- + +INSERT INTO `bs_administrator` (`id`, `account`, `password`, `tel`, `email`, `status`, `create_time`) VALUES +(1, 'zjq', '123456', '130xxxx0167', 'zjq@qq.com', 0, '2020-03-02 00:00:00'), +(2, '曾健起', '123456', '130xxxx0167', 'zengjianqi163@163.com', 0, '2020-03-19 15:16:28'), +(3, '王博君', '123456', '1234567', 'wangbojun@163.com', 0, '2020-03-19 15:18:16'), +(4, '范贤成', '123456', '233333', 'fanxiancheng@163.com', 0, '2020-03-19 18:39:22'), +(5, '曾玉宝', '123456', '123132', 'zyb@qq.com', 0, '2020-03-20 13:35:06'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device` +-- + +CREATE TABLE `bs_device` ( + `uid` varchar(255) NOT NULL, + `traffic_light` int(255) NOT NULL DEFAULT '0', + `temperature_sensor` int(255) NOT NULL DEFAULT '0', + `infrared_sensor` int(255) NOT NULL DEFAULT '0', + `light_sensor` int(255) NOT NULL DEFAULT '0', + `buzzer` int(255) NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_device` +-- + +INSERT INTO `bs_device` (`uid`, `traffic_light`, `temperature_sensor`, `infrared_sensor`, `light_sensor`, `buzzer`) VALUES +('曾健起小号', 1, 1, 0, 0, 0), +('zjquser', 1, 1, 0, 0, 0), +('叶文杰', 0, 0, 0, 0, 0), +('罗辑', 0, 0, 0, 0, 0), +('程心', 0, 0, 0, 0, 0), +('云天明', 0, 0, 0, 0, 0), +('史强', 0, 0, 0, 0, 0), +('章北海', 0, 0, 0, 0, 0), +('自然选择号', 0, 0, 0, 0, 0), +('蓝色空间号', 0, 0, 0, 0, 0), +('东方延绪', 0, 0, 0, 0, 0), +('万有引力号', 0, 0, 0, 0, 0), +('庄颜', 0, 0, 0, 0, 0), +('泰勒', 0, 0, 0, 0, 0), +('希恩斯', 0, 0, 0, 0, 0), +('丁仪', 0, 0, 0, 0, 0), +('杨冬', 0, 0, 0, 0, 0), +('维德', 0, 0, 0, 0, 0), +('上杉惠子', 0, 0, 0, 0, 0), +('歌者', 0, 0, 0, 0, 0), +('三体', 0, 0, 0, 0, 0), +('智子', 0, 0, 0, 0, 0), +('水滴', 0, 0, 0, 0, 0); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_buzzer` +-- + +CREATE TABLE `bs_device_buzzer` ( + `uid` varchar(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT '1', + `create_time` datetime NOT NULL, + `area` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_data_buzzer` +-- + +CREATE TABLE `bs_device_data_buzzer` ( + `time` int(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `data` float NOT NULL, + `id` int(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_data_infrared_sensor` +-- + +CREATE TABLE `bs_device_data_infrared_sensor` ( + `time` int(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `data` float NOT NULL, + `id` int(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_data_light_sensor` +-- + +CREATE TABLE `bs_device_data_light_sensor` ( + `time` int(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `data` float NOT NULL, + `id` int(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_data_temperature_sensor` +-- + +CREATE TABLE `bs_device_data_temperature_sensor` ( + `time` int(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `data` float NOT NULL, + `id` int(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_device_data_temperature_sensor` +-- + +INSERT INTO `bs_device_data_temperature_sensor` (`time`, `serial_num`, `data`, `id`) VALUES +(1584092467, '0005', 78.7, 1), +(1584092470, '0005', 77.25, 2), +(1584092473, '0005', 58.23, 3), +(1584092476, '0005', 16.75, 4), +(1584092479, '0005', 39.27, 5), +(1584092482, '0005', 44.84, 6), +(1584092485, '0005', 17.47, 7), +(1584092488, '0005', 72.9, 8), +(1584092491, '0005', 4.14, 9), +(1584092494, '0005', 56.93, 10), +(1584092497, '0005', 47.8, 11), +(1584092500, '0005', 56.3, 12), +(1584092503, '0005', 47.7, 13), +(1584092506, '0005', 68.44, 14), +(1584092509, '0005', 49.93, 15), +(1584092512, '0005', 61.25, 16), +(1584092515, '0005', 8.18, 17), +(1584092518, '0005', 72.26, 18), +(1584092521, '0005', 42.46, 19), +(1584092547, '0005', 19.31, 20), +(1584092550, '0005', 54.03, 21), +(1584241950, '0005', 7.2, 49), +(1584241947, '0005', 22.64, 48), +(1584241943, '0005', 73.86, 47), +(1584241940, '0005', 27.55, 46), +(1584241936, '0005', 20.05, 45), +(1584241933, '0005', 52.21, 44), +(1584241929, '0005', 58.2, 43), +(1584241926, '0005', 54.07, 42), +(1584241923, '0005', 36.19, 41), +(1584241921, '0005', 43.85, 40), +(1584241918, '0005', 47.03, 39), +(1584241914, '0005', 0.27, 38), +(1584241911, '0005', 52.84, 37), +(1584241908, '0005', 47.49, 36), +(1584241953, '0005', 28.99, 50), +(1584241956, '0005', 12.88, 51), +(1584241959, '0005', 23.23, 52), +(1584241963, '0005', 27.61, 53), +(1584241966, '0005', 65.43, 54), +(1584241969, '0005', 18, 55), +(1584416764, '0005', 6.22, 56), +(1584416950, '0005', 65.32, 57), +(1584416953, '0005', 3.52, 58), +(1584416956, '0005', 9.4, 59), +(1584416959, '0005', 61.44, 60), +(1584416962, '0005', 63.76, 61), +(1584416997, '0005', 30.89, 62), +(1584417000, '0005', 47.68, 63), +(1584417003, '0005', 25.78, 64), +(1584417006, '0005', 39.66, 65), +(1584417010, '0005', 39.77, 66), +(1584417013, '0005', 8, 67), +(1584417052, '0005', 16.64, 68), +(1584417055, '0005', 73.91, 69), +(1584417119, '0005', 4.77, 70), +(1584417122, '0005', 37.53, 71), +(1584417124, '0005', 66.81, 72), +(1584417125, '0005', 78.62, 73), +(1584417126, '0005', 45.66, 74), +(1584417176, '0005', 37.44, 75), +(1584417179, '0005', 27.47, 76), +(1584417245, '0005', 31.77, 77), +(1584417248, '0005', 24.37, 78), +(1584417251, '0005', 13.9, 79), +(1584417254, '0005', 28.74, 80), +(1584417258, '0005', 0.56, 81), +(1584417261, '0005', 32.48, 82), +(1584417264, '0005', 65.55, 83), +(1584417267, '0005', 32.31, 84), +(1584419182, '0005', 56.55, 85), +(1584419277, '0005', 37.12, 86), +(1584419298, '0005', 11.03, 87), +(1584419301, '0005', 16.3, 88), +(1584419301, '0005', 14.24, 89), +(1584419361, '0005', 44.91, 90), +(1584419376, '0005', 37.25, 91), +(1584419379, '0005', 5.83, 92), +(1584419382, '0005', 20.99, 93), +(1584419385, '0005', 40.72, 94), +(1584419388, '0005', 62.61, 95), +(1584419391, '0005', 44.46, 96), +(1584419394, '0005', 49.19, 97), +(1584419397, '0005', 0.99, 98), +(1584419400, '0005', 30.03, 99), +(1584419403, '0005', 57.86, 100), +(1584419406, '0005', 39.69, 101), +(1584419409, '0005', 11.59, 102), +(1584419412, '0005', 60.4, 103), +(1584419415, '0005', 17.71, 104), +(1584419418, '0005', 40.29, 105), +(1584419421, '0005', 41.51, 106), +(1584419424, '0005', 44.95, 107), +(1584419427, '0005', 67.96, 108), +(1584419430, '0005', 18.99, 109), +(1584419433, '0005', 14.95, 110), +(1584419436, '0005', 3.37, 111), +(1584419439, '0005', 30.75, 112), +(1584419442, '0005', 3.02, 113), +(1584419445, '0005', 13.86, 114), +(1584419448, '0005', 11.32, 115), +(1584419451, '0005', 20.92, 116), +(1584419455, '0005', 79.91, 117), +(1584419458, '0005', 8.31, 118), +(1584419460, '0005', 75.26, 119), +(1584419469, '0005', 32.62, 120), +(1584419472, '0005', 71.25, 121), +(1584419475, '0005', 15.27, 122), +(1584419478, '0005', 10.98, 123), +(1584419481, '0005', 28.38, 124), +(1584419484, '0005', 55.29, 125), +(1584419489, '0005', 52.01, 126), +(1584419492, '0005', 50.94, 127), +(1584419495, '0005', 53.56, 128), +(1584419499, '0005', 43.38, 129), +(1584419502, '0005', 42.24, 130), +(1584419505, '0005', 63.23, 131), +(1584419508, '0005', 42.83, 132), +(1584419511, '0005', 18.46, 133), +(1584419514, '0005', 72.26, 134), +(1584419517, '0005', 11.79, 135), +(1584419520, '0005', 21.12, 136), +(1584419523, '0005', 73.05, 137), +(1584419526, '0005', 16.28, 138), +(1584419530, '0005', 47.43, 139), +(1584419532, '0005', 30.22, 140), +(1584419538, '0005', 57.4, 141), +(1584419541, '0005', 7.7, 142), +(1584419544, '0005', 6.74, 143), +(1584419547, '0005', 59.06, 144), +(1584419550, '0005', 79.94, 145), +(1584419553, '0005', 66.7, 146), +(1584419556, '0005', 29.67, 147), +(1584419559, '0005', 65.38, 148), +(1584419562, '0005', 70.93, 149), +(1584419565, '0005', 47.85, 150), +(1584419568, '0005', 11.55, 151), +(1584419571, '0005', 11.63, 152), +(1584419574, '0005', 59.36, 153), +(1584419577, '0005', 65.99, 154), +(1584419580, '0005', 14.03, 155), +(1584419583, '0005', 59.05, 156), +(1584419586, '0005', 53.95, 157), +(1584419589, '0005', 24.11, 158), +(1584419592, '0005', 4.17, 159), +(1584419595, '0005', 61.15, 160), +(1584419598, '0005', 37.93, 161), +(1584419601, '0005', 8.06, 162), +(1584419604, '0005', 49.14, 163), +(1584419607, '0005', 31.97, 164), +(1584419610, '0005', 42.89, 165), +(1584419613, '0005', 6.27, 166), +(1584419616, '0005', 49.25, 167), +(1584419619, '0005', 75.06, 168), +(1584419622, '0005', 27.84, 169), +(1584419625, '0005', 56.46, 170), +(1584419628, '0005', 9.79, 171), +(1584419631, '0005', 72.81, 172), +(1584419634, '0005', 22.76, 173), +(1584419637, '0005', 62.35, 174), +(1584419640, '0005', 75.49, 175), +(1584419643, '0005', 33.3, 176), +(1584419646, '0005', 11.28, 177), +(1584419649, '0005', 74.34, 178), +(1584419652, '0005', 5.77, 179), +(1584419655, '0005', 32.85, 180), +(1584419658, '0005', 33.51, 181), +(1584419661, '0005', 79.65, 182), +(1584419664, '0005', 9.16, 183), +(1584419667, '0005', 45.45, 184), +(1584419670, '0005', 42.4, 185), +(1584419673, '0005', 57.58, 186), +(1584459160, '0005', 55.17, 187), +(1584459163, '0005', 33.13, 188), +(1584459166, '0005', 5.15, 189), +(1584459169, '0005', 48.8, 190), +(1584459172, '0005', 39.81, 191), +(1584459232, '0005', 14.15, 192), +(1584459261, '0005', 78.3, 193), +(1584459264, '0005', 29.48, 194), +(1584459267, '0005', 11.18, 195), +(1584459269, '0005', 46.52, 196), +(1584459274, '0005', 23.53, 197), +(1584459277, '0005', 54.62, 198), +(1584459280, '0005', 7.46, 199), +(1584459282, '0005', 5.76, 200), +(1584502503, '0005', 28.17, 201), +(1584502506, '0005', 64.77, 202), +(1584502509, '0005', 28.72, 203), +(1584502512, '0005', 65.05, 204), +(1584502516, '0005', 54.67, 205), +(1584502519, '0005', 1.25, 206), +(1584511416, '0005', 7.64, 207), +(1584511417, '0005', 26.44, 208), +(1584511453, '0005', 0.45, 209), +(1584511455, '0005', 16.13, 210), +(1584511515, '0005', 57.69, 211), +(1584511575, '0005', 8.47, 212), +(1584511581, '0005', 45.56, 213), +(1584511584, '0005', 22.03, 214), +(1584511587, '0005', 41.4, 215), +(1584511590, '0005', 46.09, 216), +(1584511593, '0005', 59.24, 217), +(1584511596, '0005', 16.71, 218), +(1584511599, '0005', 28.39, 219), +(1584511602, '0005', 22.23, 220), +(1584511605, '0005', 67.13, 221), +(1584511608, '0005', 19.68, 222), +(1584511611, '0005', 26.18, 223), +(1584511614, '0005', 35.72, 224), +(1584511617, '0005', 41.68, 225), +(1584511620, '0005', 66.03, 226), +(1584511623, '0005', 14.29, 227), +(1584511626, '0005', 37.72, 228), +(1584511629, '0005', 9.81, 229), +(1584511632, '0005', 19.71, 230), +(1584511635, '0005', 69.54, 231), +(1584511638, '0005', 53.28, 232), +(1584511641, '0005', 0.28, 233), +(1584511644, '0005', 37.8, 234), +(1584511647, '0005', 61.62, 235), +(1584511650, '0005', 76.02, 236), +(1584511653, '0005', 26.49, 237), +(1584511656, '0005', 68.91, 238), +(1584511659, '0005', 69.9, 239), +(1584511662, '0005', 10.58, 240), +(1584511665, '0005', 29.3, 241), +(1584511668, '0005', 38.61, 242), +(1584511671, '0005', 70.11, 243), +(1584511674, '0005', 26.66, 244), +(1584511677, '0005', 12.24, 245), +(1584511680, '0005', 76.54, 246), +(1584511683, '0005', 47.09, 247), +(1584511686, '0005', 42.89, 248), +(1584511689, '0005', 30.05, 249), +(1584511692, '0005', 14.41, 250), +(1584511695, '0005', 60.09, 251), +(1584511698, '0005', 40.46, 252), +(1584511701, '0005', 77.62, 253), +(1584511704, '0005', 64.54, 254), +(1584511707, '0005', 30.81, 255), +(1584511710, '0005', 72.9, 256), +(1584511713, '0005', 43.23, 257), +(1584511716, '0005', 73.45, 258), +(1584511719, '0005', 52.73, 259), +(1584511722, '0005', 54.52, 260), +(1584511725, '0005', 5.71, 261), +(1584511729, '0005', 70.63, 262), +(1584511732, '0005', 11.84, 263), +(1584511735, '0005', 48.97, 264), +(1584511738, '0005', 63.39, 265), +(1584511741, '0005', 49.4, 266), +(1584511744, '0005', 60.07, 267), +(1584511749, '0005', 15.54, 268), +(1584511751, '0005', 59.58, 269), +(1584511755, '0005', 51.59, 270), +(1584511758, '0005', 8.15, 271), +(1584511759, '0005', 31.02, 272), +(1584511762, '0005', 39.25, 273), +(1584511763, '0005', 30.94, 274), +(1584511766, '0005', 4.04, 275), +(1584511766, '0005', 32.08, 276), +(1584511769, '0005', 11.23, 277), +(1584511773, '0005', 37.81, 278), +(1584511773, '0005', 51.13, 279), +(1584511833, '0005', 25.02, 280), +(1584511893, '0005', 48.15, 281), +(1584511908, '0005', 41.2, 282), +(1584511911, '0005', 75.92, 283), +(1584511914, '0005', 60.41, 284), +(1584511917, '0005', 0.69, 285), +(1584511920, '0005', 20.99, 286), +(1584511923, '0005', 69.69, 287), +(1584511926, '0005', 17.13, 288), +(1584511929, '0005', 36.18, 289), +(1584511932, '0005', 6.34, 290), +(1584511935, '0005', 36.81, 291), +(1584511938, '0005', 71.58, 292), +(1584511941, '0005', 26.71, 293), +(1584511944, '0005', 5.11, 294), +(1584511947, '0005', 65.72, 295), +(1584511950, '0005', 12.95, 296), +(1584511953, '0005', 34.3, 297), +(1584511956, '0005', 22.39, 298), +(1584511959, '0005', 12.69, 299), +(1584511962, '0005', 71.91, 300), +(1584511965, '0005', 75.94, 301), +(1584511968, '0005', 34.99, 302), +(1584511971, '0005', 63.3, 303), +(1584511974, '0005', 54, 304), +(1584511975, '0005', 42.86, 305), +(1584511977, '0005', 41.14, 306), +(1584511980, '0005', 40.52, 307), +(1584511983, '0005', 8.91, 308), +(1584511985, '0005', 11.65, 309), +(1584511988, '0005', 5.78, 310), +(1584511991, '0005', 58.09, 311), +(1584511996, '0005', 4.49, 312), +(1584511999, '0005', 26.93, 313), +(1584512002, '0005', 18.74, 314), +(1584512005, '0005', 16.95, 315), +(1584512010, '0005', 59.47, 316), +(1584512013, '0005', 77.96, 317), +(1584512015, '0005', 69.49, 318), +(1584512017, '0005', 25.19, 319), +(1584512018, '0005', 28.09, 320), +(1584512021, '0005', 13.99, 321), +(1584512024, '0005', 66.85, 322), +(1584512027, '0005', 27.06, 323), +(1584512030, '0005', 46.16, 324), +(1584512033, '0005', 21.54, 325), +(1584512038, '0005', 7.93, 326), +(1584512041, '0005', 35.89, 327), +(1584512044, '0005', 14.7, 328), +(1584512047, '0005', 42.88, 329), +(1584512050, '0005', 1.03, 330), +(1584512053, '0005', 20.76, 331), +(1584512056, '0005', 21.82, 332), +(1584512059, '0005', 9.99, 333), +(1584512060, '0005', 21.43, 334), +(1584512120, '0005', 44.05, 335), +(1584512123, '0005', 24.61, 336), +(1584512126, '0005', 40.26, 337), +(1584512129, '0005', 19.91, 338), +(1584512132, '0005', 27.05, 339), +(1584512136, '0005', 66.29, 340), +(1584512139, '0005', 30.01, 341), +(1584512142, '0005', 74.25, 342), +(1584512145, '0005', 78.24, 343), +(1584512148, '0005', 61.87, 344), +(1584512150, '0005', 78.63, 345), +(1584512155, '0005', 65.99, 346), +(1584512159, '0005', 53.52, 347), +(1584512162, '0005', 48.57, 348), +(1584512165, '0005', 76.48, 349), +(1584512168, '0005', 17.79, 350), +(1584512171, '0005', 27.67, 351), +(1584512174, '0005', 59.18, 352), +(1584512177, '0005', 75.93, 353), +(1584512180, '0005', 72.54, 354), +(1584512183, '0005', 72.34, 355), +(1584512186, '0005', 11.11, 356), +(1584512189, '0005', 55.61, 357), +(1584512192, '0005', 64.19, 358), +(1584512195, '0005', 0.65, 359), +(1584512198, '0005', 36.2, 360), +(1584512201, '0005', 78.32, 361), +(1584512204, '0005', 5.27, 362), +(1584512207, '0005', 70.97, 363), +(1584512210, '0005', 35.75, 364), +(1584512213, '0005', 27.11, 365), +(1584512216, '0005', 25.56, 366), +(1584512219, '0005', 33.34, 367), +(1584512222, '0005', 16.84, 368), +(1584512225, '0005', 10.85, 369), +(1584512228, '0005', 51.92, 370), +(1584512231, '0005', 76.49, 371), +(1584512234, '0005', 73.07, 372), +(1584512237, '0005', 76.38, 373), +(1584512240, '0005', 1.65, 374), +(1584512243, '0005', 45.82, 375), +(1584512246, '0005', 74.99, 376), +(1584512249, '0005', 46.79, 377), +(1584512252, '0005', 54.04, 378), +(1584512255, '0005', 13.16, 379), +(1584512258, '0005', 9, 380), +(1584512261, '0005', 11.58, 381), +(1584512264, '0005', 75.51, 382), +(1584512267, '0005', 12.23, 383), +(1584512270, '0005', 62.58, 384), +(1584512273, '0005', 2.34, 385), +(1584512276, '0005', 16.55, 386), +(1584512279, '0005', 61.93, 387), +(1584512282, '0005', 54.69, 388), +(1584512285, '0005', 57.41, 389), +(1584512288, '0005', 2.04, 390), +(1584512291, '0005', 54.12, 391), +(1584512295, '0005', 54.28, 392), +(1584512298, '0005', 71.8, 393), +(1584512301, '0005', 60.83, 394), +(1584512304, '0005', 13.32, 395), +(1584512307, '0005', 68.93, 396), +(1584512310, '0005', 29.61, 397), +(1584512313, '0005', 0.05, 398), +(1584512316, '0005', 43.13, 399), +(1584512319, '0005', 51.21, 400), +(1584512322, '0005', 25.33, 401), +(1584512325, '0005', 19.18, 402), +(1584512328, '0005', 69.21, 403), +(1584512331, '0005', 46.15, 404), +(1584512334, '0005', 41.49, 405), +(1584512337, '0005', 49.64, 406), +(1584512340, '0005', 28.13, 407), +(1584512343, '0005', 69.07, 408), +(1584512346, '0005', 63.04, 409), +(1584512349, '0005', 76.11, 410), +(1584512352, '0005', 74.25, 411), +(1584512355, '0005', 75.19, 412), +(1584512358, '0005', 22.34, 413), +(1584512361, '0005', 17.03, 414), +(1584512364, '0005', 26.63, 415), +(1584512367, '0005', 34.19, 416), +(1584512370, '0005', 70.03, 417), +(1584512374, '0005', 60.03, 418), +(1584512377, '0005', 44.58, 419), +(1584512380, '0005', 42.46, 420), +(1584512383, '0005', 49.63, 421), +(1584512386, '0005', 77.78, 422), +(1584512389, '0005', 37.74, 423), +(1584512392, '0005', 48.62, 424), +(1584512395, '0005', 13.25, 425), +(1584512398, '0005', 17.6, 426), +(1584512401, '0005', 79.21, 427), +(1584512404, '0005', 44.36, 428), +(1584512407, '0005', 0.53, 429), +(1584512410, '0005', 1.34, 430), +(1584512413, '0005', 75.05, 431), +(1584512417, '0005', 33.92, 432), +(1584512420, '0005', 78.13, 433), +(1584512423, '0005', 51.41, 434), +(1584512426, '0005', 46.32, 435), +(1584512429, '0005', 77.76, 436), +(1584512434, '0005', 10.87, 437), +(1584512437, '0005', 21.27, 438), +(1584512440, '0005', 35.39, 439), +(1584512443, '0005', 58.99, 440), +(1584512446, '0005', 67.7, 441), +(1584512449, '0005', 42.1, 442), +(1584512452, '0005', 47.53, 443), +(1584512457, '0005', 40.76, 444), +(1584512460, '0005', 53.68, 445), +(1584512463, '0005', 22.38, 446), +(1584512466, '0005', 68.82, 447), +(1584512469, '0005', 50.43, 448), +(1584512472, '0005', 14.07, 449), +(1584512475, '0005', 53.63, 450), +(1584512478, '0005', 51.13, 451), +(1584512481, '0005', 15.89, 452), +(1584512484, '0005', 3.69, 453), +(1584512487, '0005', 64.3, 454), +(1584512490, '0005', 49.16, 455), +(1584512493, '0005', 75.94, 456), +(1584512496, '0005', 66.56, 457), +(1584512499, '0005', 56.16, 458), +(1584512502, '0005', 74.69, 459), +(1584512505, '0005', 34.91, 460), +(1584512508, '0005', 33.02, 461), +(1584512511, '0005', 38.12, 462), +(1584512514, '0005', 39.25, 463), +(1584512517, '0005', 25.34, 464), +(1584512520, '0005', 24.29, 465), +(1584512523, '0005', 10.97, 466), +(1584512526, '0005', 20.49, 467), +(1584512529, '0005', 60.47, 468), +(1584512532, '0005', 34.54, 469), +(1584512535, '0005', 24.48, 470), +(1584512538, '0005', 15.94, 471), +(1584512541, '0005', 78.07, 472), +(1584512544, '0005', 78.69, 473), +(1584512547, '0005', 69.6, 474), +(1584512550, '0005', 50.67, 475), +(1584512553, '0005', 68.46, 476), +(1584512556, '0005', 5.02, 477), +(1584512559, '0005', 56.7, 478), +(1584512562, '0005', 26.99, 479), +(1584512596, '0005', 46.37, 480), +(1584512599, '0005', 59.49, 481), +(1584512602, '0005', 76.83, 482), +(1584512605, '0005', 77.21, 483), +(1584512609, '0005', 68.16, 484), +(1584512612, '0005', 27.23, 485), +(1584512615, '0005', 36.28, 486), +(1584512618, '0005', 46.26, 487), +(1584512621, '0005', 14.76, 488), +(1584512624, '0005', 7.76, 489), +(1584512627, '0005', 35.75, 490), +(1584512630, '0005', 20.98, 491), +(1584512633, '0005', 26.79, 492), +(1584512636, '0005', 79.6, 493), +(1584512639, '0005', 26.44, 494), +(1584512642, '0005', 3.07, 495), +(1584512645, '0005', 55.06, 496), +(1584512648, '0005', 72.38, 497), +(1584512651, '0005', 57.39, 498), +(1584512654, '0005', 39.93, 499), +(1584512657, '0005', 76.05, 500), +(1584512660, '0005', 5.88, 501), +(1584512663, '0005', 67.48, 502), +(1584512666, '0005', 48.63, 503), +(1584512669, '0005', 77.48, 504), +(1584512672, '0005', 28.69, 505), +(1584512675, '0005', 42.14, 506), +(1584512678, '0005', 14.75, 507), +(1584512681, '0005', 77.94, 508), +(1584512684, '0005', 15.98, 509), +(1584512687, '0005', 75.21, 510), +(1584512690, '0005', 12.36, 511), +(1584512693, '0005', 43.07, 512), +(1584512696, '0005', 17.72, 513), +(1584512699, '0005', 26.94, 514), +(1584512702, '0005', 51.84, 515), +(1584512705, '0005', 7.39, 516), +(1584512708, '0005', 31.23, 517), +(1584512711, '0005', 41.88, 518), +(1584512714, '0005', 74.03, 519), +(1584512717, '0005', 5.59, 520), +(1584512720, '0005', 8.86, 521), +(1584512723, '0005', 73.28, 522), +(1584512726, '0005', 57.52, 523), +(1584512729, '0005', 44.81, 524), +(1584512732, '0005', 48.88, 525), +(1584512735, '0005', 54.08, 526), +(1584512738, '0005', 32.77, 527), +(1584512742, '0005', 0.25, 528), +(1584512744, '0005', 9.01, 529), +(1584512747, '0005', 71.27, 530), +(1584512749, '0005', 35.59, 531), +(1584512809, '0005', 54.97, 532), +(1584512869, '0005', 37.77, 533), +(1584512891, '0005', 66.91, 534), +(1584512894, '0005', 58.22, 535), +(1584512897, '0005', 63.76, 536), +(1584512900, '0005', 11.18, 537), +(1584512903, '0005', 58.31, 538), +(1584512906, '0005', 74.35, 539), +(1584512909, '0005', 44.41, 540), +(1584512912, '0005', 36.81, 541), +(1584512915, '0005', 26.85, 542), +(1584512918, '0005', 44, 543), +(1584512921, '0005', 56.05, 544), +(1584512924, '0005', 69.14, 545), +(1584512927, '0005', 39.68, 546), +(1584512930, '0005', 58.61, 547), +(1584512934, '0005', 76.27, 548), +(1584512937, '0005', 56.5, 549), +(1584512940, '0005', 59.44, 550), +(1584512943, '0005', 20.51, 551), +(1584512946, '0005', 35.84, 552), +(1584512949, '0005', 59.98, 553), +(1584512952, '0005', 59.2, 554), +(1584512955, '0005', 66.28, 555), +(1584512958, '0005', 44.18, 556), +(1584512961, '0005', 3.72, 557), +(1584512964, '0005', 50.66, 558), +(1584512967, '0005', 7.76, 559), +(1584512970, '0005', 23.41, 560), +(1584512973, '0005', 49.47, 561), +(1584512976, '0005', 32.9, 562), +(1584512979, '0005', 17.12, 563), +(1584512982, '0005', 74.52, 564), +(1584512985, '0005', 13.43, 565), +(1584512988, '0005', 25.62, 566), +(1584512991, '0005', 17.06, 567), +(1584512994, '0005', 73.4, 568), +(1584512997, '0005', 64.65, 569), +(1584513001, '0005', 18.33, 570), +(1584513004, '0005', 73.37, 571), +(1584513007, '0005', 27.5, 572), +(1584513010, '0005', 73.83, 573), +(1584513013, '0005', 42.84, 574), +(1584513016, '0005', 52.3, 575), +(1584513019, '0005', 1.13, 576), +(1584513022, '0005', 47.1, 577), +(1584513025, '0005', 29.26, 578), +(1584513028, '0005', 27.27, 579), +(1584513031, '0005', 24.21, 580), +(1584513034, '0005', 8, 581), +(1584513038, '0005', 9.37, 582), +(1584513041, '0005', 42.97, 583), +(1584513044, '0005', 3.54, 584), +(1584513047, '0005', 55.73, 585), +(1584513051, '0005', 34.45, 586), +(1584513054, '0005', 22.38, 587), +(1584513056, '0005', 12.32, 588), +(1584513059, '0005', 63.2, 589), +(1584513062, '0005', 43.43, 590), +(1584513065, '0005', 48.72, 591), +(1584513068, '0005', 51.44, 592), +(1584513071, '0005', 27.01, 593), +(1584513074, '0005', 79.17, 594), +(1584513077, '0005', 54.27, 595), +(1584513080, '0005', 57.82, 596), +(1584513083, '0005', 69.54, 597), +(1584513086, '0005', 62.93, 598), +(1584513089, '0005', 9.78, 599), +(1584513092, '0005', 69.21, 600), +(1584513095, '0005', 35.99, 601), +(1584513098, '0005', 7.96, 602), +(1584513102, '0005', 72.42, 603), +(1584513105, '0005', 0.16, 604), +(1584513108, '0005', 73.15, 605), +(1584513111, '0005', 69.57, 606), +(1584513114, '0005', 43.29, 607), +(1584513117, '0005', 42.56, 608), +(1584513120, '0005', 56.06, 609), +(1584513123, '0005', 78.32, 610), +(1584518980, '0005', 41.23, 611), +(1584518983, '0005', 79.86, 612), +(1584518986, '0005', 35.12, 613), +(1584518989, '0005', 54.49, 614), +(1584518992, '0005', 1.25, 615), +(1584518995, '0005', 71.99, 616), +(1584518998, '0005', 15.67, 617), +(1584519001, '0005', 17.49, 618), +(1584519004, '0005', 44.16, 619), +(1584519007, '0005', 2.06, 620), +(1584519010, '0005', 70.3, 621), +(1584519013, '0005', 48.03, 622), +(1584519016, '0005', 22.29, 623), +(1584519019, '0005', 68.71, 624), +(1584519022, '0005', 75.19, 625), +(1584519025, '0005', 73.5, 626), +(1584519029, '0005', 79.02, 627), +(1584519032, '0005', 24.31, 628), +(1584519045, '0005', 61.3, 629), +(1584519047, '0005', 2.41, 630), +(1584519050, '0005', 56.34, 631), +(1584519054, '0005', 25.24, 632), +(1584519056, '0005', 59.27, 633), +(1584519059, '0005', 63.9, 634), +(1584519062, '0005', 32.06, 635), +(1584519065, '0005', 20.81, 636), +(1584519068, '0005', 68.07, 637), +(1584519071, '0005', 53.94, 638), +(1584519074, '0005', 67.89, 639), +(1584519077, '0005', 76.31, 640), +(1584519080, '0005', 54.73, 641), +(1584519083, '0005', 36.11, 642), +(1584519086, '0005', 65.65, 643), +(1584519089, '0005', 51.43, 644), +(1584519092, '0005', 62.14, 645), +(1584519095, '0005', 54.14, 646), +(1584519098, '0005', 28.3, 647), +(1584519101, '0005', 0.7, 648), +(1584519104, '0005', 13.82, 649), +(1584519107, '0005', 32.92, 650), +(1584519110, '0005', 17.3, 651), +(1584631113, '0005', 66.39, 652), +(1584631116, '0005', 73.79, 653), +(1584631119, '0005', 15.17, 654), +(1584631121, '0005', 51.8, 655); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_data_traffic_light` +-- + +CREATE TABLE `bs_device_data_traffic_light` ( + `time` int(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `data` float NOT NULL, + `id` int(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_device_data_traffic_light` +-- + +INSERT INTO `bs_device_data_traffic_light` (`time`, `serial_num`, `data`, `id`) VALUES +(1583848893, '0002', 35.78, 1), +(1583848894, '0002', 59.51, 2), +(1583848895, '0002', 62.32, 3), +(1583848896, '0002', 2.46, 4), +(1583848897, '0002', 48.61, 5), +(1583848898, '0002', 19.74, 6), +(1583848899, '0002', 48.3, 7), +(1583848900, '0002', 38.44, 8), +(1583848901, '0002', 76.71, 9), +(1583848902, '0002', 36.93, 10), +(1583848903, '0002', 77.06, 11), +(1583848904, '0002', 46.49, 12), +(1583848907, '0002', 24.93, 13), +(1583848910, '0002', 33.7, 14), +(1583848913, '0002', 63.56, 15), +(1583848916, '0002', 9.53, 16), +(1583848919, '0002', 67.14, 17), +(1583848922, '0002', 47.67, 18), +(1583848925, '0002', 18.85, 19), +(1583848928, '0002', 63.92, 20), +(1583848931, '0002', 8.3, 21), +(1583848934, '0002', 0.62, 22), +(1583848937, '0002', 9.68, 23), +(1583848940, '0002', 52.4, 24), +(1583848943, '0002', 30.23, 25), +(1583848946, '0002', 62.5, 26), +(1583848949, '0002', 43.21, 27), +(1583848952, '0002', 37.95, 28), +(1583848955, '0002', 42.33, 29), +(1583848958, '0002', 11.07, 30), +(1583848961, '0002', 79.39, 31), +(1583848964, '0002', 14.54, 32), +(1583848967, '0002', 62.81, 33), +(1583848970, '0002', 24.64, 34), +(1583848973, '0002', 18.13, 35), +(1583848976, '0002', 4.12, 36), +(1583848979, '0002', 79.77, 37), +(1583848982, '0002', 64.71, 38), +(1583848985, '0002', 28.65, 39), +(1583848988, '0002', 49.67, 40), +(1583848991, '0002', 11.21, 41), +(1583848994, '0002', 21.34, 42), +(1583848997, '0002', 47.75, 43), +(1583849000, '0002', 1.27, 44), +(1583849003, '0002', 2.43, 45), +(1583849006, '0002', 25.47, 46), +(1583849009, '0002', 66.4, 47), +(1583849012, '0002', 7.89, 48), +(1583849015, '0002', 20.16, 49), +(1583849018, '0002', 16.7, 50), +(1583849021, '0002', 61.62, 51), +(1583849024, '0002', 19.8, 52), +(1583849027, '0002', 27.22, 53), +(1583849030, '0002', 10.13, 54), +(1583849033, '0002', 47.34, 55), +(1583849036, '0002', 38.5, 56), +(1583849039, '0002', 71.56, 57), +(1583849042, '0002', 35.36, 58), +(1583849045, '0002', 42.53, 59), +(1583849048, '0002', 38.97, 60), +(1583849051, '0002', 66.3, 61), +(1583849054, '0002', 48.54, 62), +(1583849057, '0002', 63.25, 63), +(1583849060, '0002', 76.15, 64), +(1583849063, '0002', 66.77, 65), +(1583849066, '0002', 62.06, 66), +(1583849069, '0002', 51.33, 67), +(1583849072, '0002', 77.65, 68), +(1583849075, '0002', 75.24, 69), +(1583849078, '0002', 8.18, 70), +(1583849081, '0002', 44.19, 71), +(1583849084, '0002', 26.16, 72), +(1583849087, '0002', 43.06, 73), +(1583849090, '0002', 70.93, 74), +(1583849093, '0002', 15.35, 75), +(1583849096, '0002', 28.11, 76), +(1583849099, '0002', 11.7, 77), +(1583849102, '0002', 77.5, 78), +(1583849105, '0002', 23.49, 79), +(1583849108, '0002', 61.93, 80), +(1583849111, '0002', 24.18, 81), +(1583849114, '0002', 31.47, 82), +(1583849117, '0002', 60.06, 83), +(1583849120, '0002', 44.49, 84), +(1583849123, '0002', 61.21, 85), +(1583849126, '0002', 4.33, 86), +(1583849129, '0002', 77.28, 87), +(1583849132, '0002', 55.35, 88), +(1583849135, '0002', 76.54, 89), +(1583849138, '0002', 77.3, 90), +(1583849141, '0002', 74.66, 91), +(1583849144, '0002', 17.29, 92), +(1583849147, '0002', 45.56, 93), +(1583849150, '0002', 21.71, 94), +(1583849153, '0002', 49.46, 95), +(1583849156, '0002', 66.95, 96), +(1583849159, '0002', 51, 97), +(1583849162, '0002', 52.6, 98), +(1583849165, '0002', 59.41, 99), +(1583849168, '0002', 57.67, 100), +(1583849171, '0002', 28.74, 101), +(1583849174, '0002', 69.34, 102), +(1583849177, '0002', 65.83, 103), +(1583849180, '0002', 57.97, 104), +(1583849183, '0002', 42.94, 105), +(1583849186, '0002', 31.17, 106), +(1583849189, '0002', 41.25, 107), +(1583849192, '0002', 22.3, 108), +(1583849195, '0002', 11.56, 109), +(1583849198, '0002', 44.97, 110), +(1583849201, '0002', 15.31, 111), +(1583849204, '0002', 38.88, 112), +(1583849207, '0002', 79.56, 113), +(1583849210, '0002', 13.92, 114), +(1583849213, '0002', 41.44, 115), +(1583849216, '0002', 55.19, 116), +(1583849219, '0002', 16.41, 117), +(1583849222, '0002', 75.22, 118), +(1583849225, '0002', 32.29, 119), +(1583849228, '0002', 65.12, 120), +(1583849235, '0002', 39.25, 121), +(1583849238, '0002', 1.55, 122), +(1583849241, '0002', 70.02, 123), +(1583849244, '0002', 51.17, 124), +(1583849247, '0002', 35.2, 125), +(1583849250, '0002', 42.54, 126), +(1583849253, '0002', 7.87, 127), +(1583849256, '0002', 71.27, 128), +(1583849259, '0002', 75.69, 129), +(1583849262, '0002', 60.41, 130), +(1583849265, '0002', 78.31, 131), +(1583849268, '0002', 44.33, 132), +(1583849271, '0002', 57.84, 133), +(1583849274, '0002', 25.69, 134), +(1583849277, '0002', 59.76, 135), +(1583849280, '0002', 37.06, 136), +(1583849283, '0002', 62.44, 137), +(1583849286, '0002', 21.4, 138), +(1583849289, '0002', 77.92, 139), +(1583849292, '0002', 64.7, 140), +(1583849295, '0002', 62.89, 141), +(1583849298, '0002', 57.71, 142), +(1583849301, '0002', 78.42, 143), +(1583849304, '0002', 18.83, 144), +(1583849307, '0002', 64.46, 145), +(1583849310, '0002', 71.9, 146), +(1583849313, '0002', 67.25, 147), +(1583849316, '0002', 66.43, 148), +(1583849319, '0002', 22.73, 149), +(1583849322, '0002', 17.1, 150), +(1583849325, '0002', 11.22, 151), +(1583849328, '0002', 30.04, 152), +(1583849331, '0002', 10.55, 153), +(1583849334, '0002', 11.7, 154), +(1583849337, '0002', 45.58, 155), +(1583849340, '0002', 50.32, 156), +(1583849343, '0002', 61.19, 157), +(1583849346, '0002', 70.95, 158), +(1583849349, '0002', 25.61, 159), +(1583849352, '0002', 3.94, 160), +(1583849355, '0002', 48.44, 161), +(1583849358, '0002', 30.95, 162), +(1583849361, '0002', 64.64, 163), +(1583849364, '0002', 12.92, 164), +(1583849367, '0002', 56.95, 165), +(1583849370, '0002', 48.4, 166), +(1583849373, '0002', 23.3, 167), +(1583849376, '0002', 40.3, 168), +(1583849379, '0002', 14.47, 169), +(1583849382, '0002', 27.06, 170), +(1583849385, '0002', 57.55, 171), +(1583849388, '0002', 56.77, 172), +(1583849391, '0002', 60.66, 173), +(1583849394, '0002', 75.98, 174), +(1583849397, '0002', 17.58, 175), +(1583849400, '0002', 67.53, 176), +(1583849403, '0002', 58.85, 177), +(1583849406, '0002', 35.96, 178), +(1583849409, '0002', 77.07, 179), +(1583849412, '0002', 1.35, 180), +(1583849415, '0002', 1.76, 181), +(1583849418, '0002', 38.63, 182), +(1583849421, '0002', 44.87, 183), +(1583849424, '0002', 22.82, 184), +(1583849427, '0002', 59.52, 185), +(1583849430, '0002', 48.64, 186), +(1583849433, '0002', 29.41, 187), +(1583849436, '0002', 71.34, 188), +(1583849439, '0002', 39.68, 189), +(1583849442, '0002', 38.08, 190), +(1583849445, '0002', 60.83, 191), +(1583849448, '0002', 77.74, 192), +(1583849451, '0002', 11.62, 193), +(1583849454, '0002', 58.63, 194), +(1583849457, '0002', 34.52, 195), +(1583849460, '0002', 11.26, 196), +(1583849463, '0002', 24.21, 197), +(1583849466, '0002', 38.25, 198), +(1583849469, '0002', 47.99, 199), +(1583849472, '0002', 73.34, 200), +(1583849475, '0002', 55.71, 201), +(1583849478, '0002', 77.16, 202), +(1583849481, '0002', 78.94, 203), +(1583849484, '0002', 31.66, 204), +(1583849487, '0002', 67.5, 205), +(1583849490, '0002', 12.38, 206), +(1583849493, '0002', 28.73, 207), +(1583849496, '0002', 17.21, 208), +(1583849503, '0002', 48, 209), +(1583849506, '0002', 1.82, 210), +(1583849509, '0002', 30.57, 211), +(1583849512, '0002', 39.73, 212), +(1583849515, '0002', 47.32, 213), +(1583849518, '0002', 44.27, 214), +(1583849521, '0002', 22.83, 215), +(1583849524, '0002', 62.45, 216), +(1583849527, '0002', 44.73, 217), +(1583849530, '0002', 29.45, 218), +(1583849533, '0002', 71.21, 219), +(1583849536, '0002', 54.6, 220), +(1583849539, '0002', 29.31, 221), +(1583849542, '0002', 66.14, 222), +(1583849545, '0002', 66.99, 223), +(1583849548, '0002', 20.24, 224), +(1583849551, '0002', 15.56, 225), +(1583849554, '0002', 16.15, 226), +(1583849557, '0002', 61.6, 227), +(1583849560, '0002', 30.88, 228), +(1583849563, '0002', 77.85, 229), +(1583849566, '0002', 70.62, 230), +(1583849569, '0002', 33.85, 231), +(1583849572, '0002', 10.18, 232), +(1583849575, '0002', 75.91, 233), +(1583849578, '0002', 72.44, 234), +(1583849581, '0002', 44.7, 235), +(1583849584, '0002', 29.13, 236), +(1583849587, '0002', 48.72, 237), +(1583849590, '0002', 37.55, 238), +(1583849593, '0002', 30.71, 239), +(1583849596, '0002', 20.35, 240), +(1583849599, '0002', 2.43, 241), +(1583849602, '0002', 7.67, 242), +(1583849605, '0002', 63.26, 243), +(1583849608, '0002', 23.75, 244), +(1583849611, '0002', 34.45, 245), +(1583849614, '0002', 48.61, 246), +(1583849617, '0002', 78.77, 247), +(1583849620, '0002', 32.42, 248), +(1583849623, '0002', 36.11, 249), +(1583849626, '0002', 64.41, 250), +(1583849629, '0002', 27.35, 251), +(1583849632, '0002', 25.67, 252), +(1583849635, '0002', 74.25, 253), +(1583849638, '0002', 57.38, 254), +(1583849641, '0002', 31.61, 255), +(1583849644, '0002', 42.02, 256), +(1583849647, '0002', 61.71, 257), +(1583849650, '0002', 58.11, 258), +(1583849653, '0002', 24.31, 259), +(1583849656, '0002', 27.41, 260), +(1583849659, '0002', 8.17, 261), +(1583849662, '0002', 56.67, 262), +(1583849665, '0002', 13.69, 263), +(1583849668, '0002', 54.46, 264), +(1583849671, '0002', 79.51, 265), +(1583849674, '0002', 74.18, 266), +(1583849677, '0002', 69.06, 267), +(1583849680, '0002', 28.3, 268), +(1583849683, '0002', 18.71, 269), +(1583849686, '0002', 9.8, 270), +(1583849689, '0002', 41.44, 271), +(1583849692, '0002', 11.72, 272), +(1583849695, '0002', 50.45, 273), +(1583849698, '0002', 64.59, 274), +(1583849701, '0002', 53.74, 275), +(1583849704, '0002', 25.22, 276), +(1583849707, '0002', 2.21, 277), +(1583849710, '0002', 60.67, 278), +(1583849713, '0002', 3.2, 279), +(1583849716, '0002', 68.29, 280), +(1583849719, '0002', 57.12, 281), +(1583849722, '0002', 43.3, 282), +(1583849725, '0002', 42.44, 283), +(1583849728, '0002', 3.68, 284), +(1583849731, '0002', 16.39, 285), +(1583849734, '0002', 78.09, 286), +(1583849737, '0002', 3.05, 287), +(1583849740, '0002', 77.76, 288), +(1583849743, '0002', 8.12, 289), +(1583849746, '0002', 39.67, 290), +(1583849749, '0002', 62.16, 291), +(1583849752, '0002', 22.86, 292), +(1583849755, '0002', 78.34, 293), +(1583849758, '0002', 38.08, 294), +(1583849761, '0002', 63.86, 295), +(1583849764, '0002', 25.42, 296), +(1583849767, '0002', 37.96, 297), +(1583849770, '0002', 44.28, 298), +(1583849773, '0002', 3.9, 299), +(1583849776, '0002', 16.83, 300), +(1583849779, '0002', 59.07, 301), +(1583849782, '0002', 35.33, 302), +(1583849785, '0002', 67.48, 303), +(1583849788, '0002', 57.07, 304), +(1583849791, '0002', 56.74, 305), +(1583849794, '0002', 17.73, 306), +(1583849797, '0002', 33.52, 307), +(1583849800, '0002', 66.26, 308), +(1583849803, '0002', 53.65, 309), +(1583849806, '0002', 66.96, 310), +(1583849809, '0002', 57.16, 311), +(1583849812, '0002', 34.65, 312), +(1583849815, '0002', 6.07, 313), +(1583849818, '0002', 12.7, 314), +(1583849821, '0002', 54.26, 315), +(1583849824, '0002', 33.67, 316), +(1583849827, '0002', 21.58, 317), +(1583849830, '0002', 57.08, 318), +(1583849833, '0002', 55.75, 319), +(1583849836, '0002', 14.16, 320), +(1583849839, '0002', 66.91, 321), +(1583849842, '0002', 47.73, 322), +(1583849845, '0002', 48.04, 323), +(1583849848, '0002', 3.94, 324), +(1583849851, '0002', 61.25, 325), +(1583849854, '0002', 48.03, 326), +(1583849857, '0002', 5.12, 327), +(1583849860, '0002', 58.56, 328), +(1583849863, '0002', 73.19, 329), +(1583849866, '0002', 22.97, 330), +(1583849869, '0002', 68.97, 331), +(1583849872, '0002', 11.2, 332), +(1583849875, '0002', 12.41, 333), +(1583849878, '0002', 49.37, 334), +(1583849881, '0002', 62.11, 335), +(1583849884, '0002', 15.43, 336), +(1583849887, '0002', 4.49, 337), +(1583849890, '0002', 36.24, 338), +(1583849893, '0002', 75.42, 339), +(1583849896, '0002', 67.21, 340), +(1583849899, '0002', 74.77, 341), +(1583849902, '0002', 5.29, 342), +(1583849905, '0002', 49.7, 343), +(1583849908, '0002', 75.99, 344), +(1583849911, '0002', 65.02, 345), +(1583849914, '0002', 74.66, 346), +(1583849917, '0002', 9.65, 347), +(1583849920, '0002', 37.33, 348), +(1583849923, '0002', 5.29, 349), +(1583849926, '0002', 52.84, 350), +(1583849929, '0002', 64.91, 351), +(1583849932, '0002', 74, 352), +(1583849935, '0002', 11.24, 353), +(1583849938, '0002', 60.45, 354), +(1583849941, '0002', 21.86, 355), +(1583849944, '0002', 79.4, 356), +(1583849947, '0002', 9.87, 357), +(1583849950, '0002', 35.9, 358), +(1583849953, '0002', 0.64, 359), +(1583849956, '0002', 15.84, 360), +(1583849959, '0002', 28.17, 361), +(1583849962, '0002', 43.16, 362), +(1583849965, '0002', 40.36, 363), +(1583849968, '0002', 3.48, 364), +(1583849971, '0002', 22.94, 365), +(1583849974, '0002', 38.95, 366), +(1583849977, '0002', 4.24, 367), +(1583849980, '0002', 22.46, 368), +(1583849983, '0002', 61.55, 369), +(1583849986, '0002', 9.8, 370), +(1583849989, '0002', 36.67, 371), +(1583849992, '0002', 12.38, 372), +(1583849995, '0002', 29.52, 373), +(1583849998, '0002', 9.53, 374), +(1583850001, '0002', 5.97, 375), +(1583850004, '0002', 12.46, 376), +(1583850007, '0002', 25.46, 377), +(1583850010, '0002', 56.35, 378), +(1583850013, '0002', 72.09, 379), +(1583850016, '0002', 45.51, 380), +(1583850019, '0002', 26.63, 381), +(1583850022, '0002', 48.94, 382), +(1583850025, '0002', 27.47, 383), +(1583850028, '0002', 5.8, 384), +(1583850031, '0002', 58.87, 385), +(1583850034, '0002', 60.18, 386), +(1583850037, '0002', 34.87, 387), +(1583850040, '0002', 61.51, 388), +(1583850043, '0002', 60.39, 389), +(1583850046, '0002', 27.93, 390), +(1583850049, '0002', 30.25, 391), +(1583850052, '0002', 4.07, 392), +(1583850055, '0002', 34.96, 393), +(1583850058, '0002', 6, 394), +(1583850061, '0002', 6.37, 395), +(1583850064, '0002', 26.82, 396), +(1583850067, '0002', 44.83, 397), +(1583850070, '0002', 65.7, 398), +(1583850073, '0002', 5.4, 399), +(1583850076, '0002', 18.91, 400), +(1583850079, '0002', 28.09, 401), +(1583850082, '0002', 31.06, 402), +(1583850085, '0002', 30.93, 403), +(1583850088, '0002', 65.92, 404), +(1583850091, '0002', 47.5, 405), +(1583850094, '0002', 54.98, 406), +(1583850097, '0002', 26.07, 407), +(1583850100, '0002', 19.08, 408), +(1583850103, '0002', 58.99, 409), +(1583850106, '0002', 45.47, 410), +(1583850109, '0002', 24.85, 411), +(1583850112, '0002', 71.86, 412), +(1583850115, '0002', 3.14, 413), +(1583850118, '0002', 36.43, 414), +(1583850121, '0002', 67.38, 415), +(1583850124, '0002', 22.01, 416), +(1583850127, '0002', 33.6, 417), +(1583850130, '0002', 9.87, 418), +(1583850133, '0002', 77.73, 419), +(1583850136, '0002', 45.3, 420), +(1583850139, '0002', 41, 421), +(1583850142, '0002', 65.81, 422), +(1583850145, '0002', 24.24, 423), +(1583850148, '0002', 19.38, 424), +(1583850151, '0002', 62.95, 425), +(1583850154, '0002', 56.24, 426), +(1583850157, '0002', 27.25, 427), +(1583850160, '0002', 46.38, 428), +(1583850163, '0002', 60.59, 429), +(1583850166, '0002', 44.63, 430), +(1583850169, '0002', 34.53, 431), +(1583850172, '0002', 22.37, 432), +(1583850175, '0002', 56.6, 433), +(1583850178, '0002', 51.21, 434), +(1583850181, '0002', 4.64, 435), +(1583850184, '0002', 15.19, 436), +(1583850187, '0002', 9.07, 437), +(1583850190, '0002', 18.2, 438), +(1583850193, '0002', 50.27, 439), +(1583850196, '0002', 40.77, 440), +(1583850199, '0002', 26.65, 441), +(1583850202, '0002', 54.49, 442), +(1583850205, '0002', 71.1, 443), +(1583850208, '0002', 2.94, 444), +(1583850211, '0002', 63.25, 445), +(1583850214, '0002', 19.51, 446), +(1583850217, '0002', 2.37, 447), +(1583850220, '0002', 53.99, 448), +(1583850558, '0002', 44.98, 449), +(1583850561, '0002', 26.36, 450), +(1583850564, '0002', 16.26, 451), +(1583850570, '0002', 9.8, 452), +(1583850573, '0002', 53.39, 453), +(1583983211, '0002', 33.97, 454), +(1583983214, '0002', 36.28, 455), +(1583983217, '0002', 4.53, 456), +(1583983220, '0002', 59.9, 457), +(1583983223, '0002', 34.99, 458), +(1583983226, '0002', 78.45, 459), +(1583986527, '0002', 23.21, 460), +(1583986587, '0002', 45.18, 461), +(1583986648, '0002', 27.12, 462), +(1583986650, '0002', 58.74, 463), +(1583986653, '0002', 69.66, 464), +(1583986656, '0002', 36.13, 465), +(1583986659, '0002', 0.18, 466), +(1583986665, '0002', 50.64, 467), +(1583986665, '0002', 58.49, 468), +(1583986725, '0002', 75.71, 469), +(1583986729, '0002', 61.03, 470), +(1583986761, '0002', 28.29, 471), +(1583986786, '0002', 66.74, 472), +(1583999970, '0002', 11.72, 473), +(1584058749, '0002', 76.93, 474), +(1584058752, '0002', 13.36, 475), +(1584058755, '0002', 4.76, 476), +(1584058758, '0002', 64.4, 477), +(1584058758, '0002', 57.72, 478), +(1584058759, '0002', 1.62, 479), +(1584058760, '0002', 5.44, 480), +(1584058761, '0002', 5.1, 481), +(1584058764, '0002', 7.32, 482), +(1584058767, '0002', 66.91, 483), +(1584058768, '0002', 36.32, 484), +(1584058775, '0002', 63.78, 485), +(1584058780, '0002', 25.12, 486), +(1584058781, '0002', 19.63, 487), +(1584058782, '0002', 5.77, 488), +(1584058783, '0002', 17.96, 489), +(1584058784, '0002', 25.44, 490), +(1584061766, '0002', 74.77, 491), +(1584061769, '0002', 65.08, 492), +(1584061772, '0002', 23.89, 493), +(1584061775, '0002', 6.99, 494), +(1584061778, '0002', 45.81, 495), +(1584061800, '0002', 3.13, 496), +(1584061876, '0002', 27.77, 497), +(1584061906, '0002', 10.42, 498), +(1584061916, '0002', 61.78, 499), +(1584061970, '0002', 39.86, 500), +(1584061978, '0002', 10.08, 501), +(1584061998, '0002', 29.07, 502), +(1584061998, '0002', 69.09, 503), +(1584062035, '0002', 73.48, 504), +(1584062050, '0002', 56.09, 505), +(1584062070, '0002', 32.42, 506), +(1584062097, '0002', 65.76, 507), +(1584062127, '0002', 77.71, 508), +(1584062158, '0002', 60.63, 509), +(1584062188, '0002', 23.17, 510), +(1584062218, '0002', 36.37, 511), +(1584062251, '0002', 11.23, 512), +(1584062255, '0002', 75.69, 513), +(1584062256, '0002', 74.95, 514), +(1584062257, '0002', 64.41, 515), +(1584062258, '0002', 47.31, 516), +(1584062259, '0002', 53.4, 517), +(1584062260, '0002', 0.76, 518), +(1584062261, '0002', 45.14, 519), +(1584062262, '0002', 53.17, 520), +(1584062271, '0002', 9.73, 521), +(1584062294, '0002', 68.85, 522), +(1584062325, '0002', 23.72, 523), +(1584062353, '0002', 62.39, 524), +(1584062383, '0002', 27.96, 525), +(1584062413, '0002', 55.21, 526), +(1584062440, '0002', 51.34, 527), +(1584062448, '0002', 5.63, 528), +(1584062449, '0002', 22.21, 529), +(1584062450, '0002', 79.66, 530), +(1584062451, '0002', 55.24, 531), +(1584062455, '0002', 39.78, 532), +(1584062483, '0002', 40.19, 533), +(1584062553, '0002', 35.47, 534), +(1584062575, '0002', 50.76, 535), +(1584062648, '0002', 12.88, 536), +(1584062803, '0002', 24.57, 537), +(1584062817, '0002', 40.75, 538), +(1584062850, '0002', 63.96, 539), +(1584062937, '0002', 0.98, 540), +(1584062954, '0002', 54.12, 541), +(1584062984, '0002', 25.87, 542), +(1584063014, '0002', 44.94, 543), +(1584063044, '0002', 53.68, 544), +(1584063074, '0002', 62.17, 545), +(1584063104, '0002', 56.49, 546), +(1584063134, '0002', 50.82, 547), +(1584063164, '0002', 59.91, 548), +(1584063194, '0002', 62.13, 549), +(1584063224, '0002', 12.82, 550), +(1584063226, '0002', 35.62, 551), +(1584063229, '0002', 4.04, 552), +(1584063232, '0002', 13.67, 553), +(1584063236, '0002', 3.4, 554), +(1584063239, '0002', 13.24, 555), +(1584063242, '0002', 60.22, 556), +(1584063245, '0002', 62.08, 557), +(1584063248, '0002', 3.29, 558), +(1584063251, '0002', 21.2, 559), +(1584063254, '0002', 20.75, 560), +(1584063257, '0002', 64.63, 561), +(1584063259, '0002', 27.29, 562), +(1584063261, '0002', 78.55, 563), +(1584063264, '0002', 57.27, 564), +(1584063267, '0002', 66.43, 565), +(1584063270, '0002', 79.27, 566), +(1584063274, '0002', 1.31, 567), +(1584063277, '0002', 58.51, 568), +(1584063279, '0002', 24.06, 569), +(1584063301, '0002', 78.71, 570), +(1584063304, '0002', 59.51, 571), +(1584063336, '0002', 61.61, 572), +(1584063339, '0002', 55.24, 573), +(1584063342, '0002', 32.16, 574), +(1584063345, '0002', 18.77, 575), +(1584063348, '0002', 58.41, 576), +(1584063349, '0002', 21.9, 577), +(1584063352, '0002', 75.64, 578), +(1584063355, '0002', 28.25, 579), +(1584063358, '0002', 5.44, 580), +(1584063361, '0002', 35.27, 581), +(1584063364, '0002', 18.11, 582), +(1584063367, '0002', 64.48, 583), +(1584063370, '0002', 73.35, 584), +(1584063373, '0002', 32.52, 585), +(1584063376, '0002', 22.92, 586), +(1584063379, '0002', 39.65, 587), +(1584063380, '0002', 46.19, 588), +(1584063383, '0002', 60.7, 589), +(1584063388, '0002', 7.35, 590), +(1584063391, '0002', 36.7, 591), +(1584063394, '0002', 37.85, 592), +(1584063397, '0002', 65.8, 593), +(1584063400, '0002', 24.38, 594), +(1584063404, '0002', 66.62, 595), +(1584063407, '0002', 54.01, 596), +(1584063410, '0002', 56.47, 597), +(1584063413, '0002', 16.87, 598), +(1584063416, '0002', 72.61, 599), +(1584063419, '0002', 69.22, 600), +(1584063422, '0002', 27.91, 601), +(1584063425, '0002', 62.54, 602), +(1584063428, '0002', 13.75, 603), +(1584063431, '0002', 52.38, 604), +(1584063434, '0002', 22.49, 605), +(1584063437, '0002', 74.34, 606), +(1584063440, '0002', 14.56, 607), +(1584063443, '0002', 0.51, 608), +(1584063446, '0002', 65.46, 609), +(1584063449, '0002', 23.21, 610), +(1584063452, '0002', 73.1, 611), +(1584063455, '0002', 23.79, 612), +(1584063459, '0002', 20.42, 613), +(1584063460, '0002', 20.28, 614), +(1584069990, '0002', 79.03, 615), +(1584069993, '0002', 27.78, 616), +(1584069996, '0002', 4.09, 617), +(1584069997, '0002', 11.39, 618), +(1584070181, '0002', 0.94, 619), +(1584070193, '0002', 67.94, 620), +(1584070193, '0002', 79.68, 621), +(1584070196, '0002', 32.19, 622), +(1584070197, '0002', 79.26, 623), +(1584070243, '0002', 5.79, 624), +(1584070246, '0002', 71.95, 625), +(1584070249, '0002', 49.5, 626), +(1584070249, '0002', 58.66, 627), +(1584070257, '0002', 50.16, 628), +(1584070260, '0002', 66.6, 629), +(1584070263, '0002', 63.61, 630), +(1584070266, '0002', 68.67, 631), +(1584070269, '0002', 44.33, 632), +(1584070272, '0002', 77.62, 633), +(1584070275, '0002', 75.9, 634), +(1584070278, '0002', 22.92, 635), +(1584070281, '0002', 38.51, 636), +(1584156920, '0002', 1.64, 637), +(1584156923, '0002', 53.71, 638), +(1584156926, '0002', 3.36, 639), +(1584156929, '0002', 23.41, 640), +(1584157013, '0002', 78.8, 641), +(1584157013, '0002', 70.47, 642), +(1584157065, '0002', 24.98, 643), +(1584157068, '0002', 79.44, 644), +(1584175285, '0002', 3.72, 645), +(1584175288, '0002', 6.5, 646), +(1584175801, '0002', 47.22, 647), +(1584175804, '0002', 56.27, 648), +(1584177537, '0002', 5.61, 649), +(1584177540, '0002', 65.86, 650), +(1584523448, '0001', 1.13, 651), +(1584523451, '0001', 75.93, 652), +(1584523454, '0001', 23.95, 653), +(1584523457, '0001', 46, 654), +(1584523460, '0001', 38.59, 655), +(1584523463, '0001', 22.06, 656), +(1584523463, '0001', 43.13, 657), +(1584523480, '0001', 55.11, 658), +(1584523482, '0001', 52.68, 659), +(1584523483, '0001', 20.71, 660), +(1584523484, '0001', 62.22, 661), +(1584523485, '0001', 14.01, 662), +(1584523545, '0001', 23.25, 663), +(1584523586, '0001', 7.86, 664), +(1584523590, '0001', 51.98, 665), +(1584523593, '0001', 26.25, 666), +(1584523594, '0001', 1.46, 667), +(1584523597, '0001', 56.69, 668), +(1584523600, '0001', 62.95, 669), +(1584523603, '0001', 25.73, 670), +(1584523606, '0001', 28.57, 671), +(1584523609, '0001', 68.37, 672), +(1584523612, '0001', 40.65, 673), +(1584523615, '0001', 40.05, 674), +(1584523618, '0001', 78.03, 675), +(1584523621, '0001', 9.93, 676), +(1584523624, '0001', 12.4, 677), +(1584523627, '0001', 74.3, 678), +(1584523630, '0001', 66.45, 679), +(1584523633, '0001', 2.77, 680), +(1584523636, '0001', 72.54, 681), +(1584523639, '0001', 55.61, 682), +(1584523642, '0001', 51.83, 683), +(1584523645, '0001', 6.85, 684), +(1584523648, '0001', 38.74, 685), +(1584523651, '0001', 15.61, 686), +(1584523654, '0001', 30.39, 687), +(1584523657, '0001', 54.46, 688), +(1584523660, '0001', 77.45, 689), +(1584523663, '0001', 31.3, 690), +(1584523666, '0001', 59.24, 691), +(1584523669, '0001', 17.84, 692), +(1584523672, '0001', 54.15, 693), +(1584523675, '0001', 55.81, 694), +(1584523678, '0001', 55.06, 695), +(1584523681, '0001', 27.81, 696), +(1584523684, '0001', 42.5, 697), +(1584523687, '0001', 4.86, 698), +(1584523690, '0001', 31.68, 699), +(1584523693, '0001', 75.48, 700), +(1584523696, '0001', 23.36, 701), +(1584523699, '0001', 33.03, 702), +(1584523702, '0001', 62.54, 703), +(1584523705, '0001', 70.46, 704), +(1584523708, '0001', 11, 705), +(1584523711, '0001', 77.31, 706), +(1584523714, '0001', 14.04, 707), +(1584523717, '0001', 17.76, 708), +(1584523720, '0001', 12.52, 709), +(1584523723, '0001', 33.05, 710), +(1584523726, '0001', 63.11, 711), +(1584523729, '0001', 15.31, 712), +(1584523732, '0001', 43.39, 713), +(1584523735, '0001', 67.17, 714), +(1584523735, '0001', 21.98, 715), +(1584523768, '0001', 7.54, 716), +(1584523771, '0001', 57.97, 717), +(1584523774, '0001', 71.03, 718), +(1584523777, '0001', 26.96, 719), +(1584523780, '0001', 1.19, 720), +(1584523783, '0001', 62.16, 721), +(1584523786, '0001', 67.46, 722), +(1584523789, '0001', 61.17, 723), +(1584523790, '0001', 67.73, 724), +(1584523794, '0001', 12.8, 725), +(1584523797, '0001', 21.6, 726), +(1584523800, '0001', 59.24, 727), +(1584523803, '0001', 63.13, 728), +(1584523806, '0001', 16.08, 729), +(1584523809, '0001', 6.67, 730), +(1584523812, '0001', 32.85, 731), +(1584523815, '0001', 3.58, 732), +(1584523818, '0001', 30.67, 733), +(1584523821, '0001', 49.26, 734), +(1584523824, '0001', 10.65, 735), +(1584523827, '0001', 46.23, 736), +(1584523830, '0001', 51.26, 737), +(1584523833, '0001', 28.73, 738), +(1584523836, '0001', 72.02, 739), +(1584523839, '0001', 43.87, 740), +(1584523842, '0001', 46.78, 741), +(1584523845, '0001', 49.17, 742), +(1584523848, '0001', 2.7, 743), +(1584523851, '0001', 7.21, 744), +(1584523854, '0001', 38.67, 745), +(1584523855, '0001', 1.55, 746), +(1584523915, '0001', 25.75, 747), +(1584523969, '0001', 74.17, 748), +(1584523972, '0001', 5.94, 749), +(1584523975, '0001', 35, 750), +(1584523978, '0001', 63.43, 751), +(1584523981, '0001', 12.31, 752), +(1584523984, '0001', 23.41, 753), +(1584523987, '0001', 68.43, 754), +(1584523990, '0001', 15.79, 755), +(1584523994, '0001', 69.6, 756), +(1584523997, '0001', 12.98, 757), +(1584524000, '0001', 22.29, 758), +(1584524003, '0001', 19.31, 759), +(1584524006, '0001', 17.69, 760), +(1584524009, '0001', 15.45, 761), +(1584524012, '0001', 7.53, 762), +(1584524015, '0001', 32.25, 763), +(1584524018, '0001', 40.26, 764), +(1584524021, '0001', 56.11, 765), +(1584524024, '0001', 10.86, 766), +(1584524027, '0001', 31.95, 767), +(1584524030, '0001', 70.64, 768), +(1584524033, '0001', 52.49, 769), +(1584524037, '0001', 38.92, 770), +(1584524040, '0001', 65.04, 771), +(1584524043, '0001', 63.37, 772), +(1584524046, '0001', 32.06, 773), +(1584524049, '0001', 54.97, 774), +(1584524052, '0001', 63.24, 775), +(1584524055, '0001', 67.24, 776), +(1584524059, '0001', 11.87, 777), +(1584524062, '0001', 35.07, 778), +(1584524065, '0001', 75.68, 779), +(1584524068, '0001', 47.9, 780), +(1584524071, '0001', 43.7, 781), +(1584524074, '0001', 56.27, 782), +(1584524077, '0001', 1.67, 783), +(1584524080, '0001', 61.07, 784), +(1584524083, '0001', 63.31, 785), +(1584524086, '0001', 46.58, 786), +(1584524089, '0001', 63.93, 787), +(1584524092, '0001', 60.32, 788), +(1584524097, '0001', 31.17, 789), +(1584524100, '0001', 66.94, 790), +(1584524103, '0001', 29.12, 791), +(1584524106, '0001', 23.59, 792), +(1584524109, '0001', 67.36, 793), +(1584524112, '0001', 4.04, 794), +(1584524115, '0001', 66.69, 795), +(1584524118, '0001', 26.07, 796), +(1584524121, '0001', 10.8, 797), +(1584524124, '0001', 42.4, 798), +(1584524127, '0001', 33.32, 799), +(1584524130, '0001', 33.53, 800), +(1584524133, '0001', 33.01, 801), +(1584524136, '0001', 3.81, 802), +(1584524139, '0001', 19.7, 803), +(1584524142, '0001', 25.93, 804), +(1584524145, '0001', 67.17, 805), +(1584524148, '0001', 48.93, 806), +(1584524151, '0001', 70.58, 807), +(1584524154, '0001', 42.85, 808), +(1584524157, '0001', 30.5, 809), +(1584524160, '0001', 17.28, 810), +(1584524163, '0001', 2.16, 811), +(1584524166, '0001', 55.3, 812), +(1584524169, '0001', 78.1, 813), +(1584524172, '0001', 47.42, 814), +(1584524175, '0001', 16.14, 815), +(1584524178, '0001', 31.89, 816), +(1584524182, '0001', 14.4, 817), +(1584524185, '0001', 18.29, 818), +(1584524188, '0001', 24.42, 819), +(1584524191, '0001', 12.75, 820), +(1584524194, '0001', 45.32, 821), +(1584524197, '0001', 8.03, 822), +(1584524200, '0001', 31.69, 823), +(1584524203, '0001', 16.9, 824), +(1584524206, '0001', 70.39, 825), +(1584524209, '0001', 7.95, 826), +(1584524212, '0001', 27.3, 827), +(1584524215, '0001', 48.2, 828), +(1584524218, '0001', 11.27, 829), +(1584524221, '0001', 68.96, 830), +(1584524223, '0001', 68.53, 831), +(1584524226, '0001', 49.79, 832), +(1584524229, '0001', 79.23, 833), +(1584524232, '0001', 25, 834), +(1584524235, '0001', 58.73, 835), +(1584524238, '0001', 17.31, 836), +(1584524241, '0001', 9.21, 837), +(1584524244, '0001', 22.84, 838), +(1584524247, '0001', 38.93, 839), +(1584524250, '0001', 7.73, 840), +(1584524254, '0001', 48.79, 841), +(1584524256, '0001', 44.62, 842), +(1584524259, '0001', 8.22, 843), +(1584524262, '0001', 28.4, 844), +(1584524265, '0001', 15.33, 845), +(1584524268, '0001', 25.35, 846), +(1584524270, '0001', 70.3, 847), +(1584524330, '0001', 56.2, 848), +(1584524339, '0001', 48.86, 849), +(1584524342, '0001', 22.43, 850), +(1584524345, '0001', 79.13, 851), +(1584524348, '0001', 39.85, 852), +(1584524351, '0001', 12.91, 853), +(1584524354, '0001', 56.63, 854), +(1584524357, '0001', 67.59, 855), +(1584524360, '0001', 6.55, 856), +(1584524363, '0001', 18, 857), +(1584524366, '0001', 29.52, 858), +(1584524369, '0001', 56.28, 859), +(1584524372, '0001', 59.48, 860), +(1584524375, '0001', 53.74, 861), +(1584524378, '0001', 49.93, 862), +(1584524381, '0001', 65.86, 863), +(1584524384, '0001', 24.06, 864), +(1584524387, '0001', 23.75, 865), +(1584524390, '0001', 15.21, 866), +(1584524393, '0001', 23.83, 867), +(1584524397, '0001', 73.36, 868), +(1584524399, '0001', 44.07, 869), +(1584524402, '0001', 28.09, 870), +(1584524405, '0001', 70.53, 871), +(1584524408, '0001', 24.3, 872), +(1584524411, '0001', 17.58, 873), +(1584524414, '0001', 78.43, 874), +(1584524417, '0001', 19.8, 875), +(1584524420, '0001', 63.35, 876), +(1584524423, '0001', 20.42, 877), +(1584524426, '0001', 65.08, 878), +(1584524429, '0001', 35.3, 879), +(1584524432, '0001', 44.98, 880), +(1584524434, '0001', 77.54, 881), +(1584524437, '0001', 71.88, 882), +(1584524440, '0001', 69.82, 883), +(1584524443, '0001', 32.58, 884), +(1584524446, '0001', 18.36, 885), +(1584524449, '0001', 31.36, 886), +(1584524452, '0001', 34.29, 887), +(1584524454, '0001', 55.83, 888), +(1584524457, '0001', 60.55, 889), +(1584524460, '0001', 78.83, 890), +(1584524463, '0001', 54.28, 891), +(1584524466, '0001', 69.42, 892), +(1584524469, '0001', 57.78, 893), +(1584524472, '0001', 39.64, 894), +(1584524476, '0001', 70, 895), +(1584524479, '0001', 77.3, 896), +(1584524482, '0001', 42.32, 897), +(1584524485, '0001', 26.94, 898), +(1584524488, '0001', 35.51, 899), +(1584524491, '0001', 22.99, 900), +(1584524494, '0001', 14.58, 901), +(1584524497, '0001', 9.25, 902), +(1584524501, '0001', 61.64, 903), +(1584524504, '0001', 56.96, 904), +(1584524507, '0001', 25.84, 905), +(1584524510, '0001', 15.43, 906), +(1584524513, '0001', 37.26, 907), +(1584524516, '0001', 66.41, 908), +(1584524519, '0001', 34.52, 909), +(1584524522, '0001', 71.36, 910), +(1584524525, '0001', 78.42, 911), +(1584524528, '0001', 73.33, 912), +(1584524531, '0001', 33.3, 913), +(1584524535, '0001', 56.14, 914), +(1584524538, '0001', 15.38, 915), +(1584524541, '0001', 15.2, 916), +(1584524544, '0001', 67.73, 917), +(1584524547, '0001', 50.86, 918), +(1584524550, '0001', 50.28, 919), +(1584524553, '0001', 17.53, 920), +(1584524557, '0001', 3.29, 921), +(1584524560, '0001', 61.99, 922), +(1584524563, '0001', 19.15, 923), +(1584524566, '0001', 68.08, 924), +(1584524569, '0001', 9.96, 925), +(1584524572, '0001', 74.17, 926), +(1584524575, '0001', 78.22, 927), +(1584524578, '0001', 48.91, 928), +(1584524581, '0001', 72.09, 929), +(1584524584, '0001', 76.35, 930), +(1584524587, '0001', 68.36, 931), +(1584524590, '0001', 52.65, 932), +(1584524593, '0001', 31.92, 933), +(1584524596, '0001', 39.56, 934), +(1584524599, '0001', 47, 935), +(1584524602, '0001', 10.74, 936), +(1584524605, '0001', 21.17, 937), +(1584524608, '0001', 42.82, 938), +(1584524611, '0001', 32.32, 939), +(1584524614, '0001', 44.91, 940), +(1584524617, '0001', 5.48, 941), +(1584524620, '0001', 52.48, 942), +(1584524623, '0001', 41.33, 943), +(1584524626, '0001', 59.01, 944), +(1584524629, '0001', 32.99, 945), +(1584524632, '0001', 38.43, 946), +(1584524635, '0001', 69.12, 947), +(1584524638, '0001', 61.17, 948), +(1584524641, '0001', 38.09, 949), +(1584524644, '0001', 35.77, 950), +(1584524647, '0001', 0.58, 951), +(1584524650, '0001', 20.69, 952), +(1584524653, '0001', 18.57, 953), +(1584524656, '0001', 77.62, 954), +(1584524659, '0001', 46.42, 955), +(1584524662, '0001', 35.58, 956), +(1584524665, '0001', 61.05, 957), +(1584524668, '0001', 58.08, 958), +(1584524671, '0001', 25.23, 959), +(1584524674, '0001', 6.53, 960), +(1584524677, '0001', 0.94, 961), +(1584524680, '0001', 30.47, 962), +(1584524683, '0001', 10.03, 963), +(1584524686, '0001', 54.49, 964), +(1584524689, '0001', 25.93, 965), +(1584524692, '0001', 69.01, 966), +(1584524695, '0001', 32.43, 967), +(1584524698, '0001', 57.7, 968), +(1584524701, '0001', 34.27, 969), +(1584524704, '0001', 65.75, 970), +(1584524707, '0001', 15.75, 971), +(1584524710, '0001', 49.87, 972), +(1584524713, '0001', 71.22, 973), +(1584524716, '0001', 17.4, 974), +(1584524719, '0001', 79.41, 975), +(1584524722, '0001', 19.79, 976), +(1584524725, '0001', 1.37, 977), +(1584524728, '0001', 5.59, 978), +(1584524731, '0001', 72.39, 979), +(1584524734, '0001', 32.77, 980), +(1584524737, '0001', 77.15, 981), +(1584524740, '0001', 75.11, 982), +(1584524743, '0001', 26.55, 983), +(1584524746, '0001', 33.26, 984), +(1584524749, '0001', 50.95, 985), +(1584524752, '0001', 4.36, 986), +(1584524755, '0001', 46.75, 987), +(1584524758, '0001', 0.56, 988), +(1584524761, '0001', 27.75, 989), +(1584524765, '0001', 14.37, 990), +(1584524768, '0001', 19.8, 991), +(1584524771, '0001', 32.63, 992), +(1584524774, '0001', 23.18, 993), +(1584524777, '0001', 63.82, 994), +(1584524780, '0001', 53.87, 995), +(1584524783, '0001', 2.2, 996), +(1584524786, '0001', 20.35, 997), +(1584524789, '0001', 34.78, 998), +(1584524791, '0001', 59.3, 999), +(1584524794, '0001', 74.52, 1000), +(1584524797, '0001', 55.9, 1001), +(1584524800, '0001', 63.48, 1002), +(1584524803, '0001', 47.23, 1003), +(1584524806, '0001', 33.62, 1004), +(1584529320, '0001', 32.08, 1005), +(1584529323, '0001', 69.08, 1006), +(1584529324, '0001', 24.67, 1007), +(1584529384, '0001', 64.15, 1008), +(1584529424, '0001', 75.18, 1009), +(1584529428, '0001', 14.95, 1010), +(1584529429, '0001', 65.36, 1011), +(1584529453, '0001', 4.63, 1012), +(1584529456, '0001', 8.36, 1013), +(1584529458, '0001', 43.31, 1014), +(1584529478, '0001', 0.14, 1015), +(1584529481, '0001', 33.51, 1016), +(1584529484, '0001', 61.02, 1017), +(1584529487, '0001', 65.46, 1018), +(1584529490, '0001', 41.19, 1019), +(1584529493, '0001', 51.44, 1020), +(1584529496, '0001', 39.56, 1021), +(1584529499, '0001', 77.18, 1022), +(1584529502, '0001', 26.78, 1023), +(1584529505, '0001', 56.54, 1024), +(1584529508, '0001', 55.82, 1025), +(1584529511, '0001', 68.43, 1026), +(1584529514, '0001', 70.51, 1027), +(1584529517, '0001', 38.76, 1028), +(1584529520, '0001', 29.8, 1029), +(1584529523, '0001', 60.17, 1030), +(1584529526, '0001', 40.49, 1031), +(1584529529, '0001', 17.01, 1032), +(1584529532, '0001', 55.11, 1033), +(1584529535, '0001', 18.4, 1034), +(1584529538, '0001', 37.19, 1035), +(1584529541, '0001', 44.83, 1036), +(1584529544, '0001', 50.94, 1037), +(1584529547, '0001', 4.1, 1038), +(1584529550, '0001', 18.22, 1039), +(1584529553, '0001', 79.99, 1040), +(1584529556, '0001', 55.99, 1041), +(1584529559, '0001', 76.43, 1042), +(1584529562, '0001', 43.42, 1043), +(1584529565, '0001', 57.51, 1044), +(1584529568, '0001', 67.19, 1045), +(1584529571, '0001', 44.27, 1046), +(1584529574, '0001', 41.49, 1047), +(1584529577, '0001', 76.48, 1048), +(1584529580, '0001', 74.52, 1049), +(1584529583, '0001', 5.18, 1050), +(1584529586, '0001', 14.85, 1051), +(1584529589, '0001', 67.45, 1052), +(1584529594, '0001', 65.3, 1053), +(1584529597, '0001', 28.63, 1054), +(1584529600, '0001', 69.74, 1055), +(1584529603, '0001', 68.33, 1056), +(1584529606, '0001', 5.66, 1057), +(1584529609, '0001', 22.79, 1058), +(1584529612, '0001', 61.19, 1059), +(1584529614, '0001', 75.87, 1060), +(1584529617, '0001', 27.11, 1061), +(1584529620, '0001', 73.86, 1062), +(1584529623, '0001', 44.17, 1063), +(1584529626, '0001', 29.52, 1064), +(1584529629, '0001', 44.64, 1065), +(1584529632, '0001', 6.6, 1066), +(1584529635, '0001', 51.32, 1067), +(1584529638, '0001', 3.88, 1068), +(1584529641, '0001', 20.22, 1069), +(1584529644, '0001', 28.28, 1070), +(1584529647, '0001', 8.83, 1071), +(1584529650, '0001', 44.2, 1072), +(1584535256, '0001', 73.3, 1073), +(1584535259, '0001', 5.75, 1074), +(1584535261, '0001', 34.12, 1075), +(1584535264, '0001', 5.48, 1076), +(1584536088, '0001', 75.7, 1077), +(1584536091, '0001', 32.03, 1078), +(1584536094, '0001', 58.42, 1079), +(1584536097, '0001', 36.05, 1080), +(1584536098, '0001', 21.81, 1081), +(1584536101, '0001', 57.73, 1082), +(1584536104, '0001', 73.88, 1083), +(1584536107, '0001', 36.19, 1084), +(1584536110, '0001', 23.56, 1085), +(1584536110, '0001', 17.25, 1086), +(1584536128, '0001', 5.36, 1087), +(1584536131, '0001', 42.64, 1088), +(1584536134, '0001', 5.79, 1089), +(1584536137, '0001', 0.67, 1090), +(1584536140, '0001', 2.07, 1091), +(1584536141, '0001', 8.2, 1092), +(1584536201, '0001', 56.87, 1093), +(1584536261, '0001', 34.53, 1094), +(1584536264, '0001', 58.77, 1095), +(1584536267, '0001', 54.92, 1096), +(1584536270, '0001', 6.57, 1097), +(1584536273, '0001', 0.41, 1098), +(1584536276, '0001', 75.99, 1099), +(1584536279, '0001', 77.08, 1100), +(1584536282, '0001', 36.27, 1101), +(1584536285, '0001', 34.37, 1102), +(1584536285, '0001', 26.83, 1103), +(1584536288, '0001', 5.69, 1104), +(1584536291, '0001', 12.77, 1105), +(1584536294, '0001', 39.5, 1106), +(1584536297, '0001', 23.59, 1107), +(1584536300, '0001', 3.03, 1108), +(1584536303, '0001', 57.47, 1109), +(1584536306, '0001', 15.97, 1110), +(1584536309, '0001', 34.14, 1111), +(1584536312, '0001', 14.28, 1112), +(1584536315, '0001', 44.98, 1113), +(1584536318, '0001', 10.53, 1114), +(1584536321, '0001', 57.97, 1115), +(1584536324, '0001', 10.82, 1116), +(1584536327, '0001', 63.87, 1117), +(1584536330, '0001', 68.07, 1118), +(1584536333, '0001', 57.66, 1119), +(1584536337, '0001', 74.42, 1120), +(1584536340, '0001', 21.67, 1121), +(1584536343, '0001', 53.5, 1122), +(1584536346, '0001', 76.31, 1123), +(1584536349, '0001', 74.61, 1124), +(1584536352, '0001', 33.5, 1125), +(1584536355, '0001', 47.06, 1126), +(1584536358, '0001', 72.92, 1127), +(1584536361, '0001', 73.92, 1128), +(1584536364, '0001', 12.07, 1129), +(1584536367, '0001', 30.55, 1130), +(1584536370, '0001', 74.59, 1131), +(1584536373, '0001', 74.67, 1132), +(1584536376, '0001', 23.67, 1133), +(1584536379, '0001', 52.16, 1134), +(1584536382, '0001', 70.97, 1135), +(1584536385, '0001', 64.64, 1136), +(1584536388, '0001', 14.66, 1137), +(1584536391, '0001', 79.43, 1138), +(1584536394, '0001', 78.11, 1139), +(1584536397, '0001', 79.71, 1140), +(1584536400, '0001', 24.24, 1141), +(1584536403, '0001', 72.72, 1142), +(1584536406, '0001', 72.14, 1143), +(1584536409, '0001', 39.28, 1144), +(1584536411, '0001', 72.33, 1145), +(1584536414, '0001', 52.89, 1146), +(1584536416, '0001', 24.89, 1147), +(1584536419, '0001', 20.33, 1148), +(1584536422, '0001', 18.34, 1149), +(1584536425, '0001', 23.79, 1150), +(1584536428, '0001', 16.54, 1151), +(1584536431, '0001', 30.98, 1152), +(1584536434, '0001', 25.61, 1153), +(1584536437, '0001', 79.69, 1154), +(1584536440, '0001', 0.73, 1155), +(1584536443, '0001', 44.76, 1156), +(1584536446, '0001', 58.01, 1157), +(1584536449, '0001', 68.66, 1158), +(1584536452, '0001', 32.09, 1159), +(1584536455, '0001', 53.81, 1160), +(1584536458, '0001', 33.09, 1161), +(1584536461, '0001', 70.25, 1162), +(1584536464, '0001', 46.72, 1163), +(1584536467, '0001', 53.62, 1164), +(1584536470, '0001', 41.68, 1165), +(1584536473, '0001', 32.04, 1166), +(1584536476, '0001', 46.07, 1167), +(1584536479, '0001', 52.11, 1168), +(1584536482, '0001', 79.74, 1169), +(1584536485, '0001', 10.61, 1170), +(1584536488, '0001', 66.69, 1171), +(1584536491, '0001', 41.93, 1172), +(1584536494, '0001', 27.8, 1173), +(1584536497, '0001', 73.45, 1174), +(1584536500, '0001', 13.08, 1175), +(1584536503, '0001', 78.89, 1176), +(1584536506, '0001', 42.75, 1177), +(1584536509, '0001', 72.84, 1178), +(1584536512, '0001', 43.6, 1179), +(1584536515, '0001', 47.6, 1180), +(1584536518, '0001', 26.28, 1181), +(1584536521, '0001', 43.73, 1182), +(1584536524, '0001', 61.7, 1183), +(1584536527, '0001', 68.11, 1184), +(1584536530, '0001', 65.78, 1185), +(1584536533, '0001', 76.23, 1186), +(1584536536, '0001', 79.48, 1187), +(1584536539, '0001', 75.34, 1188), +(1584536542, '0001', 13.28, 1189), +(1584536545, '0001', 24.23, 1190), +(1584536548, '0001', 42.92, 1191), +(1584536551, '0001', 6.03, 1192), +(1584536554, '0001', 76.96, 1193), +(1584536557, '0001', 58.69, 1194), +(1584536560, '0001', 8.72, 1195), +(1584536563, '0001', 69.55, 1196), +(1584536566, '0001', 41.65, 1197), +(1584536569, '0001', 32.99, 1198), +(1584536572, '0001', 11.9, 1199), +(1584536575, '0001', 50.16, 1200), +(1584536578, '0001', 79.01, 1201), +(1584536581, '0001', 66.43, 1202), +(1584536584, '0001', 24.47, 1203), +(1584536587, '0001', 33.21, 1204), +(1584536590, '0001', 49.31, 1205), +(1584536593, '0001', 12.77, 1206), +(1584536596, '0001', 24.59, 1207), +(1584536599, '0001', 52.83, 1208), +(1584536602, '0001', 30.03, 1209), +(1584536605, '0001', 41.47, 1210), +(1584536608, '0001', 48.07, 1211), +(1584536611, '0001', 7.12, 1212), +(1584536614, '0001', 58.19, 1213), +(1584536617, '0001', 38.42, 1214), +(1584536620, '0001', 30.44, 1215), +(1584536623, '0001', 24.38, 1216), +(1584536626, '0001', 63.69, 1217), +(1584536629, '0001', 29.93, 1218), +(1584536632, '0001', 13.95, 1219), +(1584536635, '0001', 18.96, 1220), +(1584536638, '0001', 37.87, 1221), +(1584536641, '0001', 56.45, 1222), +(1584536644, '0001', 51.3, 1223), +(1584536647, '0001', 32.24, 1224), +(1584536650, '0001', 67.54, 1225), +(1584536653, '0001', 40.43, 1226), +(1584536656, '0001', 28.69, 1227), +(1584536659, '0001', 62.42, 1228), +(1584536662, '0001', 64.12, 1229), +(1584536665, '0001', 45.69, 1230), +(1584536669, '0001', 2.97, 1231), +(1584536672, '0001', 42.02, 1232), +(1584536674, '0001', 44.76, 1233), +(1584536677, '0001', 41.14, 1234), +(1584536680, '0001', 30.18, 1235), +(1584536683, '0001', 11.87, 1236), +(1584536686, '0001', 27.39, 1237), +(1584536689, '0001', 64.21, 1238), +(1584536692, '0001', 69.4, 1239), +(1584536695, '0001', 14.8, 1240), +(1584536698, '0001', 34.94, 1241), +(1584536701, '0001', 21.36, 1242), +(1584536704, '0001', 14.07, 1243), +(1584536707, '0001', 15.78, 1244), +(1584536710, '0001', 76.93, 1245), +(1584536713, '0001', 79.47, 1246), +(1584536716, '0001', 51.82, 1247), +(1584536719, '0001', 4.89, 1248), +(1584536722, '0001', 39.94, 1249), +(1584536725, '0001', 26.33, 1250), +(1584536728, '0001', 7.13, 1251), +(1584536731, '0001', 14.96, 1252), +(1584536734, '0001', 78.56, 1253), +(1584536737, '0001', 29.83, 1254), +(1584536742, '0001', 21.35, 1255), +(1584536745, '0001', 78.31, 1256), +(1584536748, '0001', 59.01, 1257), +(1584536751, '0001', 8.13, 1258), +(1584536754, '0001', 43.04, 1259), +(1584536757, '0001', 67.04, 1260), +(1584536760, '0001', 73.64, 1261), +(1584536763, '0001', 9.75, 1262), +(1584536766, '0001', 0.94, 1263), +(1584536769, '0001', 32.7, 1264), +(1584536772, '0001', 56.46, 1265), +(1584536775, '0001', 46.29, 1266), +(1584536778, '0001', 69.03, 1267), +(1584536781, '0001', 55.56, 1268), +(1584536784, '0001', 10.3, 1269), +(1584536787, '0001', 69.08, 1270), +(1584536790, '0001', 51.05, 1271), +(1584536793, '0001', 8.74, 1272), +(1584536796, '0001', 72.83, 1273), +(1584536799, '0001', 55.14, 1274), +(1584536802, '0001', 11.33, 1275), +(1584536805, '0001', 27.19, 1276), +(1584537978, '0001', 40.61, 1277), +(1584537981, '0001', 24.64, 1278), +(1584537984, '0001', 78.58, 1279), +(1584537987, '0001', 2.95, 1280), +(1584537990, '0001', 57.46, 1281), +(1584537991, '0001', 47.06, 1282), +(1584538039, '0001', 13.88, 1283), +(1584538042, '0001', 66.76, 1284), +(1584538045, '0001', 69.59, 1285), +(1584538049, '0001', 48.76, 1286), +(1584538052, '0001', 34.51, 1287), +(1584538150, '0001', 5.6, 1288), +(1584538153, '0001', 42.11, 1289), +(1584538156, '0001', 25.59, 1290), +(1584538159, '0001', 29.23, 1291), +(1584538162, '0001', 60.22, 1292), +(1584538165, '0001', 44.44, 1293), +(1584538168, '0001', 54.66, 1294), +(1584538171, '0001', 46.01, 1295), +(1584538174, '0001', 41.67, 1296), +(1584538177, '0001', 62.27, 1297), +(1584538182, '0001', 3.4, 1298), +(1584538185, '0001', 54.14, 1299), +(1584538187, '0001', 46.88, 1300), +(1584538190, '0001', 16.79, 1301), +(1584538193, '0001', 54.64, 1302), +(1584538196, '0001', 4.38, 1303), +(1584538199, '0001', 72.04, 1304), +(1584538202, '0001', 1.01, 1305), +(1584538205, '0001', 56.58, 1306), +(1584538208, '0001', 21.71, 1307), +(1584538211, '0001', 12.94, 1308), +(1584538214, '0001', 14.4, 1309), +(1584538217, '0001', 43.85, 1310), +(1584538220, '0001', 46.56, 1311), +(1584538223, '0001', 45.3, 1312), +(1584538226, '0001', 6.49, 1313), +(1584538229, '0001', 6.74, 1314), +(1584538232, '0001', 67.42, 1315), +(1584538235, '0001', 25.56, 1316), +(1584538238, '0001', 24.45, 1317), +(1584538241, '0001', 34.87, 1318), +(1584538248, '0001', 43.56, 1319), +(1584538251, '0001', 8.74, 1320), +(1584538254, '0001', 31.09, 1321), +(1584538257, '0001', 47.81, 1322), +(1584538262, '0001', 59, 1323), +(1584538265, '0001', 27.51, 1324), +(1584538267, '0001', 76.56, 1325), +(1584538270, '0001', 65.46, 1326), +(1584538273, '0001', 59.27, 1327), +(1584538316, '0001', 12.51, 1328), +(1584538318, '0001', 76.72, 1329), +(1584538321, '0001', 46.83, 1330), +(1584538324, '0001', 37.07, 1331), +(1584538439, '0001', 58.41, 1332), +(1584538442, '0001', 76.79, 1333), +(1584538445, '0001', 64.65, 1334), +(1584538890, '0001', 69.61, 1335), +(1584538893, '0001', 50.25, 1336), +(1584538896, '0001', 21.04, 1337), +(1584538899, '0001', 45.61, 1338), +(1584538902, '0001', 13.66, 1339), +(1584538905, '0001', 19.78, 1340), +(1584538908, '0001', 45.22, 1341), +(1584538911, '0001', 73.73, 1342), +(1584538914, '0001', 54.67, 1343), +(1584538917, '0001', 61.41, 1344), +(1584538920, '0001', 20.63, 1345), +(1584538923, '0001', 77.67, 1346), +(1584539295, '0001', 34.86, 1347), +(1584539298, '0001', 40.63, 1348), +(1584539301, '0001', 38.97, 1349), +(1584539304, '0001', 11.28, 1350), +(1584539307, '0001', 36.44, 1351), +(1584539310, '0001', 33.66, 1352), +(1584539313, '0001', 29.37, 1353), +(1584539316, '0001', 17.98, 1354), +(1584539319, '0001', 75.43, 1355), +(1584539322, '0001', 24.2, 1356), +(1584539323, '0001', 61.54, 1357), +(1584541172, '0001', 15.1, 1358), +(1584541173, '0001', 20.26, 1359), +(1584589692, '0001', 58.73, 1360), +(1584589693, '0001', 61.1, 1361), +(1584612564, '0001', 79.21, 1362), +(1584612566, '0001', 48.96, 1363), +(1584612626, '0001', 26.65, 1364), +(1584612686, '0001', 54.88, 1365), +(1584613866, '0001', 55.97, 1366), +(1584613867, '0001', 40.29, 1367), +(1584620006, '0002', 4.84, 1368), +(1584620009, '0002', 23.42, 1369), +(1584627352, '0002', 5.46, 1370), +(1584627355, '0002', 22.14, 1371), +(1584632100, '0002', 78.57, 1372), +(1584632103, '0002', 61.02, 1373), +(1584632106, '0002', 45.69, 1374), +(1584632109, '0002', 15.41, 1375), +(1584632112, '0002', 79.25, 1376), +(1584632115, '0002', 46.34, 1377), +(1584632118, '0002', 21.26, 1378), +(1584632121, '0002', 5.12, 1379), +(1584632124, '0002', 37.06, 1380), +(1584632127, '0002', 17.63, 1381), +(1584632130, '0002', 30.06, 1382), +(1584632133, '0002', 25.68, 1383), +(1584632136, '0002', 39.62, 1384), +(1584632139, '0002', 29.72, 1385), +(1584632142, '0002', 13.26, 1386), +(1584632145, '0002', 70.19, 1387), +(1584632148, '0002', 11.97, 1388), +(1584632151, '0002', 29.42, 1389), +(1584632154, '0002', 22.84, 1390), +(1584632157, '0002', 0.73, 1391), +(1584632160, '0002', 34.68, 1392), +(1584632163, '0002', 59.25, 1393), +(1584632166, '0002', 44.91, 1394), +(1584632169, '0002', 18.3, 1395), +(1584632172, '0002', 32.43, 1396), +(1584632175, '0002', 58.24, 1397), +(1584632178, '0002', 21.35, 1398), +(1584632181, '0002', 43.67, 1399), +(1584632184, '0002', 20.59, 1400), +(1584632187, '0002', 21.45, 1401), +(1584632190, '0002', 79.3, 1402), +(1584632193, '0002', 65.96, 1403), +(1584632196, '0002', 23.6, 1404), +(1584632199, '0002', 6.32, 1405), +(1584632202, '0002', 4.22, 1406), +(1584632205, '0002', 12.4, 1407), +(1584632208, '0002', 19.51, 1408), +(1584632211, '0002', 47.32, 1409), +(1584632214, '0002', 2.63, 1410), +(1584632217, '0002', 42.84, 1411), +(1584632220, '0002', 78.24, 1412), +(1584632223, '0002', 43.14, 1413), +(1584632226, '0002', 48.33, 1414), +(1584632229, '0002', 18.9, 1415), +(1584632234, '0002', 27.49, 1416), +(1584632237, '0002', 74.01, 1417), +(1584632240, '0002', 70.14, 1418), +(1584632243, '0002', 63.27, 1419), +(1584632246, '0002', 59.2, 1420), +(1584632249, '0002', 22.47, 1421), +(1584632252, '0002', 47.82, 1422), +(1584632255, '0002', 6.44, 1423), +(1584632258, '0002', 77.01, 1424), +(1584632261, '0002', 55.52, 1425), +(1584632264, '0002', 8.71, 1426), +(1584632267, '0002', 65.54, 1427), +(1584632270, '0002', 50.61, 1428), +(1584632273, '0002', 8.67, 1429), +(1584632276, '0002', 42.88, 1430), +(1584632279, '0002', 62.81, 1431), +(1584632282, '0002', 24.87, 1432), +(1584632285, '0002', 65.26, 1433), +(1584632288, '0002', 68.86, 1434), +(1584632291, '0002', 75.71, 1435), +(1584632295, '0002', 36.88, 1436), +(1584632298, '0002', 1.62, 1437), +(1584632301, '0002', 77.01, 1438), +(1584632304, '0002', 58.73, 1439), +(1584632307, '0002', 76.89, 1440), +(1584632310, '0002', 28.4, 1441), +(1584632313, '0002', 51.37, 1442), +(1584632316, '0002', 15.35, 1443), +(1584632319, '0002', 48.94, 1444), +(1584632322, '0002', 39.9, 1445), +(1584632325, '0002', 26.49, 1446), +(1584632328, '0002', 27.38, 1447), +(1584632331, '0002', 50.72, 1448), +(1584632334, '0002', 7.95, 1449), +(1584632337, '0002', 21.66, 1450), +(1584632340, '0002', 37.31, 1451), +(1584632343, '0002', 14.41, 1452), +(1584632346, '0002', 45.2, 1453), +(1584632349, '0002', 14.73, 1454), +(1584632352, '0002', 40.02, 1455), +(1584632355, '0002', 55.5, 1456), +(1584632358, '0002', 7.24, 1457), +(1584632361, '0002', 17.75, 1458), +(1584632364, '0002', 44.52, 1459), +(1584632367, '0002', 44.68, 1460), +(1584632370, '0002', 21.13, 1461), +(1584632373, '0002', 19.3, 1462), +(1584632376, '0002', 0.81, 1463), +(1584632379, '0002', 10.35, 1464), +(1584632382, '0002', 3.44, 1465), +(1584632385, '0002', 72.97, 1466), +(1584632388, '0002', 75.95, 1467), +(1584632391, '0002', 63.39, 1468), +(1584632394, '0002', 3.36, 1469), +(1584632397, '0002', 57.47, 1470), +(1584632400, '0002', 17.51, 1471), +(1584632403, '0002', 27.89, 1472), +(1584632406, '0002', 74.23, 1473), +(1584632409, '0002', 3.78, 1474), +(1584632412, '0002', 28.19, 1475), +(1584632415, '0002', 69.72, 1476), +(1584632418, '0002', 9.2, 1477), +(1584632421, '0002', 9.88, 1478), +(1584632424, '0002', 35.41, 1479), +(1584632427, '0002', 47.72, 1480), +(1584632430, '0002', 58.92, 1481), +(1584632433, '0002', 4.3, 1482), +(1584632436, '0002', 30.4, 1483), +(1584632439, '0002', 76.04, 1484), +(1584632442, '0002', 19.54, 1485), +(1584632445, '0002', 42.25, 1486), +(1584632448, '0002', 38.93, 1487), +(1584632451, '0002', 35.56, 1488), +(1584632454, '0002', 56.15, 1489), +(1584632457, '0002', 16.36, 1490), +(1584632460, '0002', 66.34, 1491), +(1584632463, '0002', 50.17, 1492), +(1584632466, '0002', 43.29, 1493), +(1584632469, '0002', 37.18, 1494), +(1584632472, '0002', 40.42, 1495), +(1584632475, '0002', 19.92, 1496), +(1584632478, '0002', 64.86, 1497), +(1584632481, '0002', 43.17, 1498), +(1584632484, '0002', 5.99, 1499), +(1584632487, '0002', 37.82, 1500), +(1584632490, '0002', 48.37, 1501), +(1584632493, '0002', 42.31, 1502), +(1584632496, '0002', 30.36, 1503), +(1584632499, '0002', 60.56, 1504), +(1584632502, '0002', 63.94, 1505), +(1584632505, '0002', 7.29, 1506), +(1584632508, '0002', 14.61, 1507), +(1584632511, '0002', 70.75, 1508), +(1584632514, '0002', 28.53, 1509), +(1584632517, '0002', 52.43, 1510), +(1584632520, '0002', 57.45, 1511), +(1584632523, '0002', 46.44, 1512), +(1584632526, '0002', 16.67, 1513), +(1584632529, '0002', 28.97, 1514), +(1584632532, '0002', 74.97, 1515), +(1584632535, '0002', 36.29, 1516), +(1584632538, '0002', 67.43, 1517), +(1584632541, '0002', 19.36, 1518), +(1584632544, '0002', 26.22, 1519), +(1584632547, '0002', 63.76, 1520), +(1584632550, '0002', 4.65, 1521), +(1584632553, '0002', 48.67, 1522), +(1584632556, '0002', 58.53, 1523), +(1584632559, '0002', 33.18, 1524), +(1584632562, '0002', 72.75, 1525), +(1584632565, '0002', 43.7, 1526), +(1584632568, '0002', 10.44, 1527), +(1584632571, '0002', 14.51, 1528), +(1584632574, '0002', 67.2, 1529), +(1584632577, '0002', 44.82, 1530), +(1584632580, '0002', 24.5, 1531), +(1584632583, '0002', 44.84, 1532), +(1584632586, '0002', 53.91, 1533), +(1584632589, '0002', 51.21, 1534), +(1584632592, '0002', 37.95, 1535), +(1584632595, '0002', 22.27, 1536), +(1584632598, '0002', 24.04, 1537), +(1584632601, '0002', 1.1, 1538), +(1584632604, '0002', 0.31, 1539), +(1584632607, '0002', 69.05, 1540), +(1584632610, '0002', 76.21, 1541), +(1584632613, '0002', 42.15, 1542), +(1584632616, '0002', 24.94, 1543), +(1584632619, '0002', 41, 1544), +(1584632622, '0002', 12.05, 1545), +(1584632625, '0002', 30.44, 1546), +(1584632628, '0002', 3.72, 1547), +(1584632631, '0002', 66.7, 1548), +(1584632634, '0002', 7.95, 1549), +(1584632637, '0002', 47.7, 1550), +(1584632640, '0002', 21.18, 1551), +(1584632643, '0002', 6.79, 1552), +(1584632646, '0002', 10.68, 1553), +(1584632649, '0002', 6.76, 1554), +(1584632652, '0002', 69.03, 1555), +(1584632655, '0002', 52.1, 1556), +(1584632658, '0002', 47.41, 1557); +INSERT INTO `bs_device_data_traffic_light` (`time`, `serial_num`, `data`, `id`) VALUES +(1584632661, '0002', 48.79, 1558), +(1584632664, '0002', 30.87, 1559), +(1584632667, '0002', 4.02, 1560), +(1584632670, '0002', 9.89, 1561), +(1584632673, '0002', 19.03, 1562), +(1584632676, '0002', 3.48, 1563), +(1584632679, '0002', 6.04, 1564), +(1584632682, '0002', 35.5, 1565), +(1584632685, '0002', 29.78, 1566), +(1584632688, '0002', 55.52, 1567), +(1584632691, '0002', 55.74, 1568), +(1584632694, '0002', 19.09, 1569), +(1584632697, '0002', 22.96, 1570), +(1584632700, '0002', 40.13, 1571), +(1584632703, '0002', 0.24, 1572), +(1584632706, '0002', 52.7, 1573), +(1584632709, '0002', 63.51, 1574), +(1584632712, '0002', 2.86, 1575), +(1584632715, '0002', 79.16, 1576), +(1584632718, '0002', 52.97, 1577), +(1584632721, '0002', 39.63, 1578), +(1584632724, '0002', 67.03, 1579), +(1584632727, '0002', 2.85, 1580), +(1584632730, '0002', 45.3, 1581), +(1584632733, '0002', 12.84, 1582), +(1584632736, '0002', 60.55, 1583), +(1584632739, '0002', 23.27, 1584), +(1584632742, '0002', 8.66, 1585), +(1584632745, '0002', 23.83, 1586), +(1584632748, '0002', 51.7, 1587), +(1584632751, '0002', 34.81, 1588), +(1584632754, '0002', 27.79, 1589), +(1584632757, '0002', 11.65, 1590), +(1584632760, '0002', 63.62, 1591), +(1584632763, '0002', 33.81, 1592), +(1584632766, '0002', 77.54, 1593), +(1584632769, '0002', 73.49, 1594), +(1584632772, '0002', 23.81, 1595), +(1584632775, '0002', 56.79, 1596), +(1584632778, '0002', 76.56, 1597), +(1584632781, '0002', 9.31, 1598), +(1584632784, '0002', 45.54, 1599), +(1584632787, '0002', 27.13, 1600), +(1584632790, '0002', 24.63, 1601), +(1584632793, '0002', 27.67, 1602), +(1584632796, '0002', 24, 1603), +(1584632799, '0002', 4.43, 1604), +(1584632802, '0002', 72.72, 1605), +(1584632805, '0002', 74.45, 1606), +(1584632808, '0002', 24.67, 1607), +(1584632811, '0002', 28.28, 1608), +(1584632814, '0002', 37.33, 1609), +(1584632817, '0002', 7.19, 1610), +(1584632820, '0002', 55.25, 1611), +(1584632823, '0002', 24.09, 1612), +(1584632826, '0002', 21.01, 1613), +(1584632829, '0002', 77.92, 1614), +(1584632832, '0002', 71.35, 1615), +(1584632835, '0002', 61.01, 1616), +(1584632838, '0002', 57.48, 1617), +(1584632841, '0002', 71.74, 1618), +(1584632844, '0002', 35.4, 1619), +(1584632847, '0002', 24.45, 1620), +(1584632850, '0002', 29.16, 1621), +(1584632853, '0002', 22.7, 1622), +(1584632856, '0002', 11.79, 1623), +(1584632859, '0002', 54.17, 1624), +(1584632862, '0002', 38.38, 1625), +(1584632865, '0002', 72.27, 1626), +(1584632868, '0002', 57.23, 1627), +(1584632871, '0002', 45.46, 1628), +(1584632874, '0002', 72.12, 1629), +(1584632877, '0002', 26.31, 1630), +(1584632880, '0002', 39.28, 1631), +(1584632883, '0002', 38.67, 1632), +(1584632886, '0002', 61.86, 1633), +(1584632889, '0002', 24.15, 1634), +(1584632892, '0002', 30.97, 1635), +(1584632895, '0002', 19.96, 1636), +(1584632898, '0002', 65.6, 1637), +(1584632901, '0002', 24.67, 1638), +(1584632904, '0002', 66.94, 1639), +(1584632907, '0002', 14.37, 1640), +(1584632910, '0002', 38.28, 1641), +(1584632913, '0002', 19.37, 1642), +(1584632916, '0002', 10.05, 1643), +(1584632919, '0002', 71.96, 1644), +(1584632922, '0002', 70.82, 1645), +(1584632925, '0002', 53.38, 1646), +(1584632928, '0002', 4.78, 1647), +(1584632931, '0002', 0.66, 1648), +(1584632934, '0002', 49.79, 1649), +(1584632937, '0002', 66.16, 1650), +(1584632940, '0002', 51.33, 1651), +(1584632943, '0002', 62.42, 1652), +(1584632946, '0002', 79.68, 1653), +(1584632949, '0002', 5.55, 1654), +(1584632952, '0002', 62.99, 1655), +(1584632955, '0002', 65.47, 1656), +(1584632958, '0002', 58.97, 1657), +(1584632961, '0002', 58.67, 1658), +(1584632964, '0002', 62.19, 1659), +(1584632967, '0002', 56.26, 1660), +(1584632970, '0002', 12.12, 1661), +(1584632973, '0002', 45.28, 1662), +(1584632976, '0002', 65.23, 1663), +(1584632979, '0002', 5.29, 1664), +(1584632982, '0002', 18.58, 1665), +(1584632985, '0002', 66.36, 1666), +(1584632988, '0002', 22.07, 1667), +(1584632991, '0002', 57.75, 1668), +(1584632994, '0002', 16.4, 1669), +(1584632997, '0002', 13.05, 1670), +(1584633000, '0002', 12.7, 1671), +(1584633003, '0002', 13.04, 1672), +(1584633006, '0002', 37.22, 1673), +(1584633009, '0002', 55.58, 1674), +(1584633012, '0002', 3.39, 1675), +(1584633015, '0002', 37.94, 1676), +(1584633018, '0002', 27.11, 1677), +(1584633021, '0002', 43.53, 1678), +(1584633024, '0002', 48.98, 1679), +(1584633027, '0002', 46.18, 1680), +(1584633030, '0002', 12.69, 1681), +(1584633033, '0002', 17.26, 1682), +(1584633036, '0002', 60.12, 1683), +(1584633039, '0002', 33.58, 1684), +(1584633042, '0002', 68.37, 1685), +(1584633045, '0002', 2.01, 1686), +(1584633048, '0002', 37.36, 1687), +(1584633051, '0002', 49.88, 1688), +(1584633054, '0002', 55.86, 1689), +(1584633057, '0002', 24.66, 1690), +(1584633060, '0002', 58.99, 1691), +(1584633063, '0002', 42.24, 1692), +(1584633066, '0002', 75.17, 1693), +(1584633069, '0002', 43.37, 1694), +(1584633072, '0002', 26.07, 1695), +(1584633075, '0002', 43.72, 1696), +(1584633078, '0002', 39.99, 1697), +(1584633081, '0002', 22.1, 1698), +(1584633084, '0002', 47.55, 1699), +(1584633087, '0002', 33.59, 1700), +(1584633090, '0002', 31.09, 1701), +(1584633093, '0002', 56.14, 1702), +(1584633096, '0002', 0.43, 1703), +(1584633099, '0002', 13.52, 1704), +(1584633102, '0002', 22.51, 1705), +(1584633105, '0002', 60.7, 1706), +(1584633108, '0002', 2.92, 1707), +(1584633111, '0002', 42.64, 1708), +(1584633114, '0002', 70.35, 1709), +(1584633117, '0002', 52.3, 1710), +(1584633120, '0002', 51.21, 1711), +(1584633123, '0002', 58.32, 1712), +(1584633126, '0002', 28.57, 1713), +(1584633129, '0002', 56.27, 1714), +(1584633132, '0002', 17.57, 1715), +(1584633135, '0002', 59.24, 1716), +(1584633138, '0002', 44.43, 1717), +(1584633141, '0002', 22.95, 1718), +(1584633144, '0002', 72.25, 1719), +(1584633147, '0002', 59.67, 1720), +(1584633150, '0002', 27.63, 1721), +(1584633153, '0002', 36.16, 1722), +(1584633156, '0002', 18.16, 1723), +(1584633159, '0002', 18.47, 1724), +(1584633162, '0002', 65.92, 1725), +(1584633165, '0002', 35.02, 1726), +(1584633168, '0002', 68.81, 1727), +(1584633171, '0002', 13.56, 1728), +(1584633174, '0002', 52.86, 1729), +(1584633177, '0002', 53.71, 1730), +(1584633180, '0002', 17.61, 1731), +(1584633183, '0002', 33.45, 1732), +(1584633186, '0002', 37.9, 1733), +(1584633189, '0002', 38.73, 1734), +(1584633192, '0002', 62.68, 1735), +(1584633195, '0002', 53.97, 1736), +(1584633198, '0002', 16.35, 1737), +(1584633201, '0002', 62.9, 1738), +(1584633204, '0002', 56.47, 1739), +(1584633207, '0002', 38.94, 1740), +(1584633210, '0002', 27.86, 1741), +(1584633213, '0002', 63.15, 1742), +(1584633216, '0002', 40.33, 1743), +(1584633219, '0002', 39.76, 1744), +(1584633222, '0002', 18.3, 1745), +(1584633225, '0002', 17.21, 1746), +(1584633228, '0002', 60.23, 1747), +(1584633231, '0002', 56.12, 1748), +(1584633234, '0002', 50.55, 1749), +(1584633237, '0002', 62.1, 1750), +(1584633240, '0002', 17.31, 1751), +(1584633243, '0002', 14.58, 1752), +(1584633246, '0002', 47.87, 1753), +(1584633249, '0002', 42.91, 1754), +(1584633252, '0002', 3.71, 1755), +(1584633255, '0002', 58.34, 1756), +(1584633258, '0002', 30.58, 1757), +(1584633261, '0002', 31.97, 1758), +(1584633264, '0002', 77.48, 1759), +(1584633267, '0002', 53.92, 1760), +(1584633270, '0002', 27.35, 1761), +(1584633273, '0002', 78.81, 1762), +(1584633276, '0002', 29.97, 1763), +(1584633279, '0002', 53.17, 1764), +(1584633282, '0002', 55.59, 1765), +(1584633285, '0002', 38.65, 1766), +(1584633288, '0002', 4.96, 1767), +(1584633291, '0002', 79.42, 1768), +(1584633294, '0002', 64.16, 1769), +(1584633297, '0002', 45.16, 1770), +(1584633300, '0002', 64.32, 1771), +(1584633303, '0002', 18.46, 1772), +(1584633306, '0002', 41, 1773), +(1584633309, '0002', 64.07, 1774), +(1584633312, '0002', 71.46, 1775), +(1584633315, '0002', 53.83, 1776), +(1584633318, '0002', 64.43, 1777), +(1584633321, '0002', 1.71, 1778), +(1584633324, '0002', 62.73, 1779), +(1584633327, '0002', 42.8, 1780), +(1584633330, '0002', 37.52, 1781), +(1584633333, '0002', 30.18, 1782), +(1584633336, '0002', 31.29, 1783), +(1584633339, '0002', 48.19, 1784), +(1584633342, '0002', 47.4, 1785), +(1584633345, '0002', 45.06, 1786), +(1584633348, '0002', 23.44, 1787), +(1584633351, '0002', 63.59, 1788), +(1584633354, '0002', 48.83, 1789), +(1584633357, '0002', 63.06, 1790), +(1584633360, '0002', 1.55, 1791), +(1584633363, '0002', 2.43, 1792), +(1584633366, '0002', 45.31, 1793), +(1584633369, '0002', 28.88, 1794), +(1584633372, '0002', 53.56, 1795), +(1584633375, '0002', 18.03, 1796), +(1584633378, '0002', 41.01, 1797), +(1584633381, '0002', 30.92, 1798), +(1584633384, '0002', 26.9, 1799), +(1584633387, '0002', 37.36, 1800), +(1584633390, '0002', 0.85, 1801), +(1584633394, '0002', 62.39, 1802), +(1584633396, '0002', 70.82, 1803), +(1584633399, '0002', 36.54, 1804), +(1584633402, '0002', 72.32, 1805), +(1584633405, '0002', 18.18, 1806), +(1584633408, '0002', 51.05, 1807), +(1584633411, '0002', 7.57, 1808), +(1584633414, '0002', 14.52, 1809), +(1584633417, '0002', 44.03, 1810), +(1584633420, '0002', 30.9, 1811), +(1584633423, '0002', 37.32, 1812), +(1584633426, '0002', 26.99, 1813), +(1584633429, '0002', 50.78, 1814), +(1584633432, '0002', 15.24, 1815), +(1584633435, '0002', 24.82, 1816), +(1584633438, '0002', 48.84, 1817), +(1584633441, '0002', 53.04, 1818), +(1584633444, '0002', 46.67, 1819), +(1584633447, '0002', 20.96, 1820), +(1584633450, '0002', 64.58, 1821), +(1584633453, '0002', 3.71, 1822), +(1584633456, '0002', 47.32, 1823), +(1584633459, '0002', 10.36, 1824), +(1584633462, '0002', 77.17, 1825), +(1584633465, '0002', 41.68, 1826), +(1584633468, '0002', 55.52, 1827), +(1584633471, '0002', 55.67, 1828), +(1584633474, '0002', 77.8, 1829), +(1584633477, '0002', 38.52, 1830), +(1584633480, '0002', 28.26, 1831), +(1584633483, '0002', 24.76, 1832), +(1584633486, '0002', 14.19, 1833), +(1584633489, '0002', 35.2, 1834), +(1584633492, '0002', 59.29, 1835), +(1584633495, '0002', 5.87, 1836), +(1584633498, '0002', 77.58, 1837), +(1584633501, '0002', 33.72, 1838), +(1584633504, '0002', 54.48, 1839), +(1584633507, '0002', 20.16, 1840), +(1584633510, '0002', 77.68, 1841), +(1584633513, '0002', 67.54, 1842), +(1584633516, '0002', 68.36, 1843), +(1584633519, '0002', 64.24, 1844), +(1584633522, '0002', 67.97, 1845), +(1584633525, '0002', 53.1, 1846), +(1584633528, '0002', 41.8, 1847), +(1584633531, '0002', 79.39, 1848), +(1584633534, '0002', 11.7, 1849), +(1584633537, '0002', 40.28, 1850), +(1584633540, '0002', 75.06, 1851), +(1584633543, '0002', 1.62, 1852), +(1584633546, '0002', 21.77, 1853), +(1584633549, '0002', 46.54, 1854), +(1584633552, '0002', 19.18, 1855), +(1584633555, '0002', 0.24, 1856), +(1584633558, '0002', 19.44, 1857), +(1584633561, '0002', 7.14, 1858), +(1584633564, '0002', 10.19, 1859), +(1584633567, '0002', 35.88, 1860), +(1584633570, '0002', 14.17, 1861), +(1584633573, '0002', 32.7, 1862), +(1584633576, '0002', 64.24, 1863), +(1584633579, '0002', 78.51, 1864), +(1584633582, '0002', 70.71, 1865), +(1584633585, '0002', 16.34, 1866), +(1584633588, '0002', 58.13, 1867), +(1584633591, '0002', 14.45, 1868), +(1584633594, '0002', 38.91, 1869), +(1584633597, '0002', 32.45, 1870), +(1584633600, '0002', 74.12, 1871), +(1584633603, '0002', 77.63, 1872), +(1584633606, '0002', 59.81, 1873), +(1584633609, '0002', 62.21, 1874), +(1584633612, '0002', 63.4, 1875), +(1584633615, '0002', 49.46, 1876), +(1584633618, '0002', 51.07, 1877), +(1584633621, '0002', 31.46, 1878), +(1584633624, '0002', 23.11, 1879), +(1584633627, '0002', 68.12, 1880), +(1584633630, '0002', 24.44, 1881), +(1584633633, '0002', 0.97, 1882), +(1584633636, '0002', 4.13, 1883), +(1584633639, '0002', 21.84, 1884), +(1584633642, '0002', 48.07, 1885), +(1584633645, '0002', 28.45, 1886), +(1584633648, '0002', 33.79, 1887), +(1584633651, '0002', 61.71, 1888), +(1584633654, '0002', 44.15, 1889), +(1584633657, '0002', 52.5, 1890), +(1584633660, '0002', 29.75, 1891), +(1584633663, '0002', 1.77, 1892), +(1584633666, '0002', 14.7, 1893), +(1584633669, '0002', 8.95, 1894), +(1584633672, '0002', 21.14, 1895), +(1584633675, '0002', 28.61, 1896), +(1584633678, '0002', 60.56, 1897), +(1584633681, '0002', 13.16, 1898), +(1584633684, '0002', 4.53, 1899), +(1584633687, '0002', 42.78, 1900), +(1584633690, '0002', 70.1, 1901), +(1584633693, '0002', 64.25, 1902), +(1584633696, '0002', 28.37, 1903), +(1584633699, '0002', 33.11, 1904), +(1584633702, '0002', 57.57, 1905), +(1584633705, '0002', 47.56, 1906), +(1584633708, '0002', 20.8, 1907), +(1584633711, '0002', 49.82, 1908), +(1584633714, '0002', 4.81, 1909), +(1584633717, '0002', 77.08, 1910), +(1584633720, '0002', 50.67, 1911), +(1584633723, '0002', 20.6, 1912), +(1584633726, '0002', 3.6, 1913), +(1584633729, '0002', 66.87, 1914), +(1584633732, '0002', 30.21, 1915), +(1584633735, '0002', 68.65, 1916), +(1584633738, '0002', 42.91, 1917), +(1584633741, '0002', 60.24, 1918), +(1584633744, '0002', 35.98, 1919), +(1584633747, '0002', 43.85, 1920), +(1584633750, '0002', 2.85, 1921), +(1584633753, '0002', 0.75, 1922), +(1584633756, '0002', 59, 1923), +(1584633759, '0002', 54.33, 1924), +(1584633762, '0002', 55.98, 1925), +(1584633765, '0002', 58.78, 1926), +(1584633768, '0002', 49.75, 1927), +(1584633771, '0002', 47.66, 1928), +(1584633774, '0002', 18.35, 1929), +(1584633777, '0002', 57.3, 1930), +(1584633780, '0002', 11.95, 1931), +(1584633783, '0002', 37.6, 1932), +(1584633786, '0002', 20.32, 1933), +(1584633789, '0002', 49.71, 1934), +(1584633792, '0002', 37.46, 1935), +(1584633795, '0002', 52.79, 1936), +(1584633798, '0002', 31.07, 1937), +(1584633801, '0002', 22.17, 1938), +(1584633804, '0002', 22.39, 1939), +(1584633807, '0002', 8.34, 1940), +(1584633810, '0002', 58.81, 1941), +(1584633813, '0002', 73.36, 1942), +(1584633816, '0002', 43.95, 1943), +(1584633819, '0002', 73.82, 1944), +(1584633822, '0002', 20.01, 1945), +(1584633825, '0002', 24.9, 1946), +(1584633828, '0002', 76.59, 1947), +(1584633831, '0002', 1.15, 1948), +(1584633834, '0002', 71.52, 1949), +(1584633837, '0002', 25.65, 1950), +(1584633840, '0002', 43.6, 1951), +(1584633843, '0002', 2.61, 1952), +(1584633846, '0002', 35.01, 1953), +(1584633849, '0002', 30.88, 1954), +(1584633852, '0002', 44.76, 1955), +(1584633855, '0002', 49.79, 1956), +(1584633858, '0002', 23.28, 1957), +(1584633861, '0002', 64.66, 1958), +(1584633864, '0002', 19.24, 1959), +(1584633867, '0002', 11, 1960), +(1584633870, '0002', 7.41, 1961), +(1584633873, '0002', 48.94, 1962), +(1584633876, '0002', 46.85, 1963), +(1584633879, '0002', 10.14, 1964), +(1584633882, '0002', 61.02, 1965), +(1584633885, '0002', 41.49, 1966), +(1584633888, '0002', 77.42, 1967), +(1584633891, '0002', 24.18, 1968), +(1584633894, '0002', 45.42, 1969), +(1584633897, '0002', 46.28, 1970), +(1584633900, '0002', 46.45, 1971), +(1584633903, '0002', 29.46, 1972), +(1584633906, '0002', 69.28, 1973), +(1584633909, '0002', 19.18, 1974), +(1584633912, '0002', 8.37, 1975), +(1584633915, '0002', 21.9, 1976), +(1584633918, '0002', 70.38, 1977), +(1584633921, '0002', 17.52, 1978), +(1584633924, '0002', 77.02, 1979), +(1584633927, '0002', 62.24, 1980), +(1584633930, '0002', 47.6, 1981), +(1584633933, '0002', 63.46, 1982), +(1584633936, '0002', 36.01, 1983), +(1584633939, '0002', 40.6, 1984), +(1584633942, '0002', 47.44, 1985), +(1584633945, '0002', 3.28, 1986), +(1584633948, '0002', 40.91, 1987), +(1584633951, '0002', 6.88, 1988), +(1584633954, '0002', 68.14, 1989), +(1584633957, '0002', 9.06, 1990), +(1584633960, '0002', 55.32, 1991), +(1584633963, '0002', 38.01, 1992), +(1584633966, '0002', 68.34, 1993), +(1584633969, '0002', 70.8, 1994), +(1584633972, '0002', 42.23, 1995), +(1584633975, '0002', 52.86, 1996), +(1584633978, '0002', 10.12, 1997), +(1584633981, '0002', 35.2, 1998), +(1584633984, '0002', 74.99, 1999), +(1584633987, '0002', 4.69, 2000), +(1584633990, '0002', 73.16, 2001), +(1584633993, '0002', 14.81, 2002), +(1584633996, '0002', 56.66, 2003), +(1584633999, '0002', 53.82, 2004), +(1584634002, '0002', 51.87, 2005), +(1584634005, '0002', 12.67, 2006), +(1584634008, '0002', 37.95, 2007), +(1584634011, '0002', 43.9, 2008), +(1584634014, '0002', 2.69, 2009), +(1584634017, '0002', 45.86, 2010), +(1584634020, '0002', 45.19, 2011), +(1584634023, '0002', 67.51, 2012), +(1584634026, '0002', 7.78, 2013), +(1584634029, '0002', 48.86, 2014), +(1584634032, '0002', 77.57, 2015), +(1584634035, '0002', 54.67, 2016), +(1584634038, '0002', 42.67, 2017), +(1584634041, '0002', 73.06, 2018), +(1584634044, '0002', 79.71, 2019), +(1584634047, '0002', 27.02, 2020), +(1584634050, '0002', 51.26, 2021), +(1584634053, '0002', 37.62, 2022), +(1584634056, '0002', 59.43, 2023), +(1584634059, '0002', 63.5, 2024), +(1584634062, '0002', 54.27, 2025), +(1584634065, '0002', 11.59, 2026), +(1584634068, '0002', 34.76, 2027), +(1584634071, '0002', 34.69, 2028), +(1584634074, '0002', 62.5, 2029), +(1584634077, '0002', 10.57, 2030), +(1584634080, '0002', 13.52, 2031), +(1584634083, '0002', 70.25, 2032), +(1584634086, '0002', 25.08, 2033), +(1584634089, '0002', 39.59, 2034), +(1584634092, '0002', 1.16, 2035), +(1584634095, '0002', 41.55, 2036), +(1584634098, '0002', 20.43, 2037), +(1584634101, '0002', 35.28, 2038), +(1584634104, '0002', 51.76, 2039), +(1584634107, '0002', 49.79, 2040), +(1584634110, '0002', 59.92, 2041), +(1584634113, '0002', 51.24, 2042), +(1584634116, '0002', 65.14, 2043), +(1584634119, '0002', 68.14, 2044), +(1584634122, '0002', 75.46, 2045), +(1584634125, '0002', 65.39, 2046), +(1584634128, '0002', 7.59, 2047), +(1584634131, '0002', 17.53, 2048), +(1584634134, '0002', 63.17, 2049), +(1584634137, '0002', 28.62, 2050), +(1584634140, '0002', 35.96, 2051), +(1584634143, '0002', 71.09, 2052), +(1584634146, '0002', 32.25, 2053), +(1584634149, '0002', 40.04, 2054), +(1584634152, '0002', 74.53, 2055), +(1584634155, '0002', 22.03, 2056), +(1584634158, '0002', 65.57, 2057), +(1584634161, '0002', 32.89, 2058), +(1584634164, '0002', 39.28, 2059), +(1584634167, '0002', 31.92, 2060), +(1584634170, '0002', 1.95, 2061), +(1584634173, '0002', 0.81, 2062), +(1584634176, '0002', 74.5, 2063), +(1584634179, '0002', 24.48, 2064), +(1584634182, '0002', 12.74, 2065), +(1584634185, '0002', 26.74, 2066), +(1584634188, '0002', 75.79, 2067), +(1584634191, '0002', 67.36, 2068), +(1584634194, '0002', 29.54, 2069), +(1584634197, '0002', 36.16, 2070), +(1584634200, '0002', 75.29, 2071), +(1584634203, '0002', 66.84, 2072), +(1584634206, '0002', 71.3, 2073), +(1584634209, '0002', 24.77, 2074), +(1584634212, '0002', 59.77, 2075), +(1584634215, '0002', 34.7, 2076), +(1584634218, '0002', 41.36, 2077), +(1584634221, '0002', 50.32, 2078), +(1584634224, '0002', 38.01, 2079), +(1584634227, '0002', 37.28, 2080), +(1584634230, '0002', 47.55, 2081), +(1584634233, '0002', 53.05, 2082), +(1584634236, '0002', 33.99, 2083), +(1584634239, '0002', 23.15, 2084), +(1584634242, '0002', 8.2, 2085), +(1584634245, '0002', 13.83, 2086), +(1584634248, '0002', 9.78, 2087), +(1584634251, '0002', 78.02, 2088), +(1584634254, '0002', 57.09, 2089), +(1584634257, '0002', 52.1, 2090), +(1584634260, '0002', 16.85, 2091), +(1584634263, '0002', 40.24, 2092), +(1584634266, '0002', 57.85, 2093), +(1584634269, '0002', 15.93, 2094), +(1584634272, '0002', 47.76, 2095), +(1584634275, '0002', 42.95, 2096), +(1584634278, '0002', 20.13, 2097), +(1584634281, '0002', 1.3, 2098), +(1584634284, '0002', 37.14, 2099), +(1584634287, '0002', 12.86, 2100), +(1584634290, '0002', 66.09, 2101), +(1584634293, '0002', 12.27, 2102), +(1584634296, '0002', 18.55, 2103), +(1584634299, '0002', 50.1, 2104), +(1584634302, '0002', 28.79, 2105), +(1584634305, '0002', 58.09, 2106), +(1584634308, '0002', 1.27, 2107), +(1584634311, '0002', 8.89, 2108), +(1584634314, '0002', 12.58, 2109), +(1584634317, '0002', 31.95, 2110), +(1584634320, '0002', 28.26, 2111), +(1584634323, '0002', 43.39, 2112), +(1584634326, '0002', 39.3, 2113), +(1584634329, '0002', 7.85, 2114), +(1584634332, '0002', 72.94, 2115), +(1584634335, '0002', 7.04, 2116), +(1584634338, '0002', 46.99, 2117), +(1584634341, '0002', 27.78, 2118), +(1584634344, '0002', 77.33, 2119), +(1584634347, '0002', 8.19, 2120), +(1584634350, '0002', 47.21, 2121), +(1584634353, '0002', 22.18, 2122), +(1584634356, '0002', 25.28, 2123), +(1584634359, '0002', 32.3, 2124), +(1584634362, '0002', 28.78, 2125), +(1584634365, '0002', 66.11, 2126), +(1584634368, '0002', 46.21, 2127), +(1584634371, '0002', 59.09, 2128), +(1584634374, '0002', 21.19, 2129), +(1584634377, '0002', 31.17, 2130), +(1584634380, '0002', 39.92, 2131), +(1584634383, '0002', 71.17, 2132), +(1584634386, '0002', 13.94, 2133), +(1584634389, '0002', 5.24, 2134), +(1584634392, '0002', 67.42, 2135), +(1584634395, '0002', 36.2, 2136), +(1584634398, '0002', 41.66, 2137), +(1584634401, '0002', 17.65, 2138), +(1584634404, '0002', 50.83, 2139), +(1584634407, '0002', 2.63, 2140), +(1584634410, '0002', 12.72, 2141), +(1584634413, '0002', 18.69, 2142), +(1584634416, '0002', 44.91, 2143), +(1584634419, '0002', 56.94, 2144), +(1584634422, '0002', 27.32, 2145), +(1584634425, '0002', 21.33, 2146), +(1584634428, '0002', 9.18, 2147), +(1584634431, '0002', 60.09, 2148), +(1584634434, '0002', 52.35, 2149), +(1584634437, '0002', 67.98, 2150), +(1584634440, '0002', 69.96, 2151), +(1584634443, '0002', 21.9, 2152), +(1584634446, '0002', 33.76, 2153), +(1584634449, '0002', 40.72, 2154), +(1584634452, '0002', 23.53, 2155), +(1584634455, '0002', 37.11, 2156), +(1584634458, '0002', 67.55, 2157), +(1584634461, '0002', 63.81, 2158), +(1584634464, '0002', 58.28, 2159), +(1584634467, '0002', 38.19, 2160), +(1584634470, '0002', 51.37, 2161), +(1584634473, '0002', 19.06, 2162), +(1584634476, '0002', 23.78, 2163), +(1584634479, '0002', 16.98, 2164), +(1584634482, '0002', 35.55, 2165), +(1584634485, '0002', 35.96, 2166), +(1584634488, '0002', 70.37, 2167), +(1584634491, '0002', 45.01, 2168), +(1584634494, '0002', 30.82, 2169), +(1584634497, '0002', 0.72, 2170), +(1584634500, '0002', 15.6, 2171), +(1584634503, '0002', 45.33, 2172), +(1584634506, '0002', 43.65, 2173), +(1584634509, '0002', 34.07, 2174), +(1584634512, '0002', 49.96, 2175), +(1584634515, '0002', 14.06, 2176), +(1584634518, '0002', 0.81, 2177), +(1584634521, '0002', 26.12, 2178), +(1584634524, '0002', 8.01, 2179), +(1584634527, '0002', 37.86, 2180), +(1584634530, '0002', 34.13, 2181), +(1584634533, '0002', 10.76, 2182), +(1584634536, '0002', 64.08, 2183), +(1584634539, '0002', 67.16, 2184), +(1584634542, '0002', 40.65, 2185), +(1584634545, '0002', 49.81, 2186), +(1584634548, '0002', 6.76, 2187), +(1584634551, '0002', 31.99, 2188), +(1584634554, '0002', 35.16, 2189), +(1584634557, '0002', 54.34, 2190), +(1584634560, '0002', 79.86, 2191), +(1584634563, '0002', 15.19, 2192), +(1584634566, '0002', 69.35, 2193), +(1584634569, '0002', 18.88, 2194), +(1584634572, '0002', 55.64, 2195), +(1584634575, '0002', 59.54, 2196), +(1584634578, '0002', 0.3, 2197), +(1584634581, '0002', 21.34, 2198), +(1584634584, '0002', 11.69, 2199), +(1584634587, '0002', 36.21, 2200), +(1584634590, '0002', 60.55, 2201), +(1584634593, '0002', 27.84, 2202), +(1584634596, '0002', 5.75, 2203), +(1584634599, '0002', 7.73, 2204), +(1584634602, '0002', 75.1, 2205), +(1584634605, '0002', 33.28, 2206), +(1584634608, '0002', 38.55, 2207), +(1584634611, '0002', 16.19, 2208), +(1584634614, '0002', 46.11, 2209), +(1584634617, '0002', 18.68, 2210), +(1584634620, '0002', 8.83, 2211), +(1584634623, '0002', 22.43, 2212), +(1584634626, '0002', 18.19, 2213), +(1584634629, '0002', 36.71, 2214), +(1584634632, '0002', 39.58, 2215), +(1584634635, '0002', 33.5, 2216), +(1584634638, '0002', 13.56, 2217), +(1584634641, '0002', 71.49, 2218), +(1584634644, '0002', 41.48, 2219), +(1584634647, '0002', 19.67, 2220), +(1584634650, '0002', 17.05, 2221), +(1584634653, '0002', 3.87, 2222), +(1584634656, '0002', 71.27, 2223), +(1584634659, '0002', 26.81, 2224), +(1584634662, '0002', 57.01, 2225), +(1584634665, '0002', 18.62, 2226), +(1584634668, '0002', 62.64, 2227), +(1584634671, '0002', 55.17, 2228), +(1584634674, '0002', 42.34, 2229), +(1584634677, '0002', 57.86, 2230), +(1584634680, '0002', 14.13, 2231), +(1584634683, '0002', 8.59, 2232), +(1584634686, '0002', 15.97, 2233), +(1584634689, '0002', 41.87, 2234), +(1584634692, '0002', 20.62, 2235), +(1584634695, '0002', 39.49, 2236), +(1584634698, '0002', 78.64, 2237), +(1584634701, '0002', 5.72, 2238), +(1584634704, '0002', 74.19, 2239), +(1584634707, '0002', 57.43, 2240), +(1584634710, '0002', 73.91, 2241), +(1584634713, '0002', 53.19, 2242), +(1584634716, '0002', 58.1, 2243), +(1584634719, '0002', 13.91, 2244), +(1584634722, '0002', 76.87, 2245), +(1584634725, '0002', 78.23, 2246), +(1584634728, '0002', 9.84, 2247), +(1584634731, '0002', 11.38, 2248), +(1584634734, '0002', 16.4, 2249), +(1584634737, '0002', 59.64, 2250), +(1584634740, '0002', 14.7, 2251), +(1584634743, '0002', 23.78, 2252), +(1584634746, '0002', 40.35, 2253), +(1584634749, '0002', 62.63, 2254), +(1584634752, '0002', 76.26, 2255), +(1584634755, '0002', 24.42, 2256), +(1584634758, '0002', 21.67, 2257), +(1584634761, '0002', 2.47, 2258), +(1584634764, '0002', 45.69, 2259), +(1584634767, '0002', 12.03, 2260), +(1584634770, '0002', 9.65, 2261), +(1584634773, '0002', 21.98, 2262), +(1584634776, '0002', 5.11, 2263), +(1584634779, '0002', 57.48, 2264), +(1584634782, '0002', 21.68, 2265), +(1584634785, '0002', 37.94, 2266), +(1584634788, '0002', 17.82, 2267), +(1584634791, '0002', 70.78, 2268), +(1584634794, '0002', 46.15, 2269), +(1584634797, '0002', 5.59, 2270), +(1584634800, '0002', 14.47, 2271), +(1584634803, '0002', 13.76, 2272), +(1584634806, '0002', 5.65, 2273), +(1584634809, '0002', 74.73, 2274), +(1584634812, '0002', 35.56, 2275), +(1584634815, '0002', 6.95, 2276), +(1584634818, '0002', 14.08, 2277), +(1584634821, '0002', 9.06, 2278), +(1584634824, '0002', 50.63, 2279), +(1584634827, '0002', 24.87, 2280), +(1584634830, '0002', 10.77, 2281), +(1584634833, '0002', 33.6, 2282), +(1584634836, '0002', 63.59, 2283), +(1584634839, '0002', 1.14, 2284), +(1584634842, '0002', 25.88, 2285), +(1584634845, '0002', 42.76, 2286), +(1584634848, '0002', 59, 2287), +(1584634851, '0002', 35.02, 2288), +(1584634854, '0002', 56.53, 2289), +(1584634857, '0002', 7.61, 2290), +(1584634860, '0002', 24.6, 2291), +(1584634863, '0002', 28.42, 2292), +(1584634866, '0002', 38.54, 2293), +(1584634869, '0002', 61.95, 2294), +(1584634872, '0002', 4.33, 2295), +(1584634875, '0002', 36.87, 2296), +(1584634878, '0002', 30.88, 2297), +(1584634881, '0002', 27.48, 2298), +(1584634884, '0002', 39.71, 2299), +(1584634887, '0002', 15.18, 2300), +(1584634890, '0002', 56.96, 2301), +(1584634894, '0002', 63.25, 2302), +(1584634896, '0002', 36.96, 2303), +(1584634899, '0002', 74.17, 2304), +(1584634902, '0002', 11.89, 2305), +(1584634905, '0002', 0.05, 2306), +(1584634908, '0002', 7.13, 2307), +(1584634911, '0002', 1.63, 2308), +(1584634914, '0002', 23.94, 2309), +(1584634917, '0002', 22.42, 2310), +(1584634920, '0002', 23, 2311), +(1584634923, '0002', 37.16, 2312), +(1584634926, '0002', 21.72, 2313), +(1584634929, '0002', 45.11, 2314), +(1584634932, '0002', 21.26, 2315), +(1584634935, '0002', 18.49, 2316), +(1584634938, '0002', 33.61, 2317), +(1584634941, '0002', 65.69, 2318), +(1584634944, '0002', 30.63, 2319), +(1584634947, '0002', 40.71, 2320), +(1584634950, '0002', 42.29, 2321), +(1584634953, '0002', 36, 2322), +(1584634956, '0002', 75.08, 2323), +(1584634959, '0002', 27.79, 2324), +(1584634962, '0002', 62.58, 2325), +(1584634965, '0002', 64.07, 2326), +(1584634968, '0002', 41.66, 2327), +(1584634971, '0002', 53.67, 2328), +(1584634974, '0002', 6.09, 2329), +(1584634977, '0002', 12.07, 2330), +(1584634980, '0002', 62.48, 2331), +(1584634983, '0002', 67, 2332), +(1584634986, '0002', 22.29, 2333), +(1584634989, '0002', 32.41, 2334), +(1584634992, '0002', 70.96, 2335), +(1584634995, '0002', 12.24, 2336), +(1584634998, '0002', 54.95, 2337), +(1584635001, '0002', 70.83, 2338), +(1584635004, '0002', 15.35, 2339), +(1584635007, '0002', 61.93, 2340), +(1584635010, '0002', 64.24, 2341), +(1584635013, '0002', 43.89, 2342), +(1584635016, '0002', 35.34, 2343), +(1584635019, '0002', 73.12, 2344), +(1584635022, '0002', 6, 2345), +(1584635025, '0002', 74.11, 2346), +(1584635028, '0002', 60.55, 2347), +(1584635031, '0002', 13.07, 2348), +(1584635034, '0002', 15.97, 2349), +(1584635037, '0002', 13.01, 2350), +(1584635040, '0002', 46.05, 2351), +(1584635043, '0002', 68.83, 2352), +(1584635046, '0002', 32.57, 2353), +(1584635049, '0002', 63.02, 2354), +(1584635052, '0002', 66.01, 2355), +(1584635055, '0002', 9.08, 2356), +(1584635058, '0002', 32.22, 2357), +(1584635061, '0002', 36.13, 2358), +(1584635064, '0002', 76.97, 2359), +(1584635067, '0002', 9.67, 2360), +(1584635070, '0002', 41.35, 2361), +(1584635073, '0002', 16.3, 2362), +(1584635076, '0002', 59.08, 2363), +(1584635079, '0002', 66.12, 2364), +(1584635082, '0002', 20.83, 2365), +(1584635085, '0002', 57.22, 2366), +(1584635088, '0002', 65.13, 2367), +(1584635091, '0002', 62.23, 2368), +(1584635094, '0002', 28.04, 2369), +(1584635097, '0002', 75.98, 2370), +(1584635100, '0002', 34.14, 2371), +(1584635103, '0002', 62.99, 2372), +(1584635106, '0002', 50.8, 2373), +(1584635109, '0002', 59.51, 2374), +(1584635112, '0002', 1.99, 2375), +(1584635115, '0002', 8.16, 2376), +(1584635118, '0002', 22.45, 2377), +(1584635121, '0002', 25.24, 2378), +(1584635124, '0002', 45.43, 2379), +(1584635127, '0002', 37.45, 2380), +(1584635130, '0002', 69.12, 2381), +(1584635133, '0002', 26.9, 2382), +(1584635136, '0002', 53.95, 2383), +(1584635139, '0002', 68.55, 2384), +(1584635142, '0002', 63.82, 2385), +(1584635145, '0002', 1.23, 2386), +(1584635148, '0002', 46.63, 2387), +(1584635151, '0002', 76.47, 2388), +(1584635154, '0002', 64.48, 2389), +(1584635157, '0002', 79.96, 2390), +(1584635160, '0002', 15.65, 2391), +(1584635163, '0002', 50.42, 2392), +(1584635166, '0002', 43.56, 2393), +(1584635169, '0002', 34.43, 2394), +(1584635172, '0002', 57.02, 2395), +(1584635175, '0002', 58.96, 2396), +(1584635178, '0002', 34.84, 2397), +(1584635181, '0002', 59.8, 2398), +(1584635184, '0002', 76.49, 2399), +(1584635187, '0002', 39.55, 2400), +(1584635190, '0002', 25.87, 2401), +(1584635193, '0002', 43.63, 2402), +(1584635196, '0002', 75.1, 2403), +(1584635199, '0002', 45.05, 2404), +(1584635202, '0002', 49.7, 2405), +(1584635205, '0002', 13.24, 2406), +(1584635208, '0002', 3.62, 2407), +(1584635211, '0002', 48.14, 2408), +(1584635214, '0002', 55.3, 2409), +(1584635217, '0002', 63.4, 2410), +(1584635220, '0002', 38.94, 2411), +(1584635223, '0002', 43.01, 2412), +(1584635226, '0002', 7.82, 2413), +(1584635229, '0002', 36.45, 2414), +(1584635232, '0002', 72.25, 2415), +(1584635235, '0002', 15.18, 2416), +(1584635238, '0002', 12.26, 2417), +(1584635241, '0002', 46.48, 2418), +(1584635244, '0002', 50.94, 2419), +(1584635247, '0002', 10.72, 2420), +(1584635250, '0002', 34.71, 2421), +(1584635253, '0002', 26.68, 2422), +(1584635256, '0002', 28.38, 2423), +(1584635259, '0002', 28.8, 2424), +(1584635262, '0002', 8.4, 2425), +(1584635265, '0002', 68.66, 2426), +(1584635268, '0002', 64.24, 2427), +(1584635271, '0002', 70.23, 2428), +(1584635274, '0002', 15.01, 2429), +(1584635277, '0002', 9.4, 2430), +(1584635280, '0002', 44.6, 2431), +(1584635283, '0002', 34.81, 2432), +(1584635286, '0002', 9.18, 2433), +(1584635289, '0002', 77.4, 2434), +(1584635292, '0002', 67.95, 2435), +(1584635295, '0002', 14.13, 2436), +(1584635298, '0002', 8.56, 2437), +(1584635301, '0002', 26.97, 2438), +(1584635304, '0002', 34.93, 2439), +(1584635307, '0002', 19.53, 2440), +(1584635310, '0002', 19.75, 2441), +(1584635313, '0002', 63.89, 2442), +(1584635316, '0002', 4.67, 2443), +(1584635319, '0002', 56.59, 2444), +(1584635322, '0002', 20.47, 2445), +(1584635325, '0002', 5.37, 2446), +(1584635328, '0002', 9.43, 2447), +(1584635331, '0002', 60.29, 2448), +(1584635334, '0002', 52.49, 2449), +(1584635337, '0002', 19.67, 2450), +(1584635340, '0002', 76.26, 2451), +(1584635343, '0002', 73.73, 2452), +(1584635346, '0002', 6.33, 2453), +(1584635349, '0002', 20.8, 2454), +(1584635352, '0002', 67.33, 2455), +(1584635355, '0002', 20.5, 2456), +(1584635358, '0002', 60.87, 2457), +(1584635361, '0002', 66.58, 2458), +(1584635364, '0002', 54.24, 2459), +(1584635367, '0002', 25.88, 2460), +(1584635370, '0002', 51.03, 2461), +(1584635373, '0002', 20.76, 2462), +(1584635376, '0002', 47.87, 2463), +(1584635379, '0002', 0.44, 2464), +(1584635382, '0002', 52.65, 2465), +(1584635385, '0002', 66.53, 2466), +(1584635388, '0002', 77.42, 2467), +(1584635391, '0002', 44.35, 2468), +(1584635394, '0002', 26.93, 2469), +(1584635397, '0002', 63.11, 2470), +(1584635400, '0002', 52.75, 2471), +(1584635403, '0002', 57.54, 2472), +(1584635406, '0002', 75.22, 2473), +(1584635409, '0002', 52.52, 2474), +(1584635412, '0002', 62.87, 2475), +(1584635415, '0002', 62.87, 2476), +(1584635418, '0002', 26.19, 2477), +(1584635421, '0002', 77.04, 2478), +(1584635424, '0002', 16.19, 2479), +(1584635427, '0002', 10.14, 2480), +(1584635430, '0002', 42.92, 2481), +(1584635433, '0002', 23.3, 2482), +(1584635436, '0002', 3.57, 2483), +(1584635439, '0002', 49.68, 2484), +(1584635442, '0002', 77.97, 2485), +(1584635445, '0002', 53.45, 2486), +(1584635448, '0002', 36.96, 2487), +(1584635451, '0002', 72.47, 2488), +(1584635454, '0002', 6.57, 2489), +(1584635457, '0002', 64.25, 2490), +(1584635460, '0002', 31.02, 2491), +(1584635463, '0002', 75.29, 2492), +(1584635466, '0002', 64.52, 2493), +(1584635469, '0002', 50.12, 2494), +(1584635472, '0002', 13.23, 2495), +(1584635475, '0002', 46.51, 2496), +(1584635478, '0002', 73.75, 2497), +(1584635481, '0002', 62.22, 2498), +(1584635484, '0002', 53.58, 2499), +(1584635487, '0002', 11.2, 2500), +(1584635490, '0002', 20.37, 2501), +(1584635493, '0002', 40.66, 2502), +(1584635496, '0002', 64.76, 2503), +(1584635499, '0002', 48.86, 2504), +(1584635502, '0002', 31.77, 2505), +(1584635505, '0002', 31.89, 2506), +(1584635508, '0002', 52.96, 2507), +(1584635511, '0002', 1.45, 2508), +(1584635514, '0002', 58.83, 2509), +(1584635517, '0002', 20.4, 2510), +(1584635520, '0002', 29, 2511), +(1584635523, '0002', 20.84, 2512), +(1584635526, '0002', 56.2, 2513), +(1584635529, '0002', 63.92, 2514), +(1584635532, '0002', 12.45, 2515), +(1584635535, '0002', 11.4, 2516), +(1584635538, '0002', 10.88, 2517), +(1584635541, '0002', 36.56, 2518), +(1584635544, '0002', 60.83, 2519), +(1584635547, '0002', 16.19, 2520), +(1584635550, '0002', 75.58, 2521), +(1584635553, '0002', 69.8, 2522), +(1584635556, '0002', 20.54, 2523), +(1584635559, '0002', 55.34, 2524), +(1584635562, '0002', 34.61, 2525), +(1584635565, '0002', 33.18, 2526), +(1584635568, '0002', 57.51, 2527), +(1584635571, '0002', 68.31, 2528), +(1584635574, '0002', 58.3, 2529), +(1584635577, '0002', 39.99, 2530), +(1584635580, '0002', 61.9, 2531), +(1584635583, '0002', 55.97, 2532), +(1584635586, '0002', 29.85, 2533), +(1584635589, '0002', 31.74, 2534), +(1584635592, '0002', 35.45, 2535), +(1584635595, '0002', 76.27, 2536), +(1584635598, '0002', 27.51, 2537), +(1584635601, '0002', 13.14, 2538), +(1584635604, '0002', 68.97, 2539), +(1584635607, '0002', 69.75, 2540), +(1584635610, '0002', 14.68, 2541), +(1584635613, '0002', 32.66, 2542), +(1584635616, '0002', 65.45, 2543), +(1584635619, '0002', 13.72, 2544), +(1584635622, '0002', 75.31, 2545), +(1584635625, '0002', 22.09, 2546), +(1584635628, '0002', 6.85, 2547), +(1584635631, '0002', 42.97, 2548), +(1584635634, '0002', 29.7, 2549), +(1584635637, '0002', 54.85, 2550), +(1584635640, '0002', 24.11, 2551), +(1584635643, '0002', 40.99, 2552), +(1584635646, '0002', 28.69, 2553), +(1584635649, '0002', 41.39, 2554), +(1584635652, '0002', 40.32, 2555), +(1584635655, '0002', 42.25, 2556), +(1584635658, '0002', 43.11, 2557), +(1584635661, '0002', 26.25, 2558), +(1584635664, '0002', 76.66, 2559), +(1584635667, '0002', 33.1, 2560), +(1584635670, '0002', 42.8, 2561), +(1584635673, '0002', 12.98, 2562), +(1584635676, '0002', 46.34, 2563), +(1584635679, '0002', 13.14, 2564), +(1584635682, '0002', 28.25, 2565), +(1584635685, '0002', 77.52, 2566), +(1584635688, '0002', 45.54, 2567), +(1584635691, '0002', 77.92, 2568), +(1584635694, '0002', 20.26, 2569), +(1584635697, '0002', 30.5, 2570), +(1584635700, '0002', 36.97, 2571), +(1584635705, '0002', 3.07, 2572), +(1584635706, '0002', 78.51, 2573), +(1584635709, '0002', 25.47, 2574), +(1584635712, '0002', 75.76, 2575), +(1584635715, '0002', 70.45, 2576), +(1584635718, '0002', 41.77, 2577), +(1584663218, '0002', 35.84, 2578), +(1584663218, '0002', 60.8, 2579), +(1584663223, '0002', 64.92, 2580), +(1584663225, '0002', 39.44, 2581), +(1584663228, '0002', 14.1, 2582), +(1584663231, '0002', 39.8, 2583), +(1584663234, '0002', 7.78, 2584), +(1584663237, '0002', 44.97, 2585), +(1584663240, '0002', 16.28, 2586), +(1584663243, '0002', 21.28, 2587), +(1584663246, '0002', 56.37, 2588), +(1584663249, '0002', 65.58, 2589), +(1584663252, '0002', 4.82, 2590), +(1584663255, '0002', 24.13, 2591), +(1584663258, '0002', 2.16, 2592), +(1584663261, '0002', 78.57, 2593), +(1584663264, '0002', 73.21, 2594), +(1584663267, '0002', 60.39, 2595), +(1584663270, '0002', 29.07, 2596), +(1584663273, '0002', 11.66, 2597), +(1584663276, '0002', 13.94, 2598), +(1584663279, '0002', 55.33, 2599), +(1584663282, '0002', 39.75, 2600), +(1584663285, '0002', 13.58, 2601), +(1584663288, '0002', 49.77, 2602), +(1584663291, '0002', 30.55, 2603), +(1584663294, '0002', 40.08, 2604), +(1584663297, '0002', 22.14, 2605), +(1584663300, '0002', 24.56, 2606), +(1584663303, '0002', 11.32, 2607), +(1584663306, '0002', 26.92, 2608), +(1584663309, '0002', 61.87, 2609), +(1584663312, '0002', 55.8, 2610), +(1584663315, '0002', 68.04, 2611), +(1584663318, '0002', 2.36, 2612), +(1584663321, '0002', 24.08, 2613), +(1584663324, '0002', 65.02, 2614), +(1584663327, '0002', 31.6, 2615), +(1584663330, '0002', 71.96, 2616), +(1584663333, '0002', 73.59, 2617), +(1584663336, '0002', 29.59, 2618), +(1584663339, '0002', 7.52, 2619), +(1584663342, '0002', 1.75, 2620), +(1584663345, '0002', 32.71, 2621), +(1584663348, '0002', 67.59, 2622), +(1584663351, '0002', 14.38, 2623), +(1584663354, '0002', 70.45, 2624), +(1584663357, '0002', 61.78, 2625), +(1584663360, '0002', 63.45, 2626), +(1584663363, '0002', 53.7, 2627), +(1584663366, '0002', 26.16, 2628), +(1584663369, '0002', 10.91, 2629), +(1584663372, '0002', 17.45, 2630), +(1584663375, '0002', 6.31, 2631), +(1584663378, '0002', 60.71, 2632), +(1584663381, '0002', 9.97, 2633), +(1584663384, '0002', 34.03, 2634), +(1584663387, '0002', 16.39, 2635), +(1584663390, '0002', 18.14, 2636), +(1584663393, '0002', 70.6, 2637), +(1584663396, '0002', 7.39, 2638), +(1584663399, '0002', 6.17, 2639), +(1584663402, '0002', 28.14, 2640), +(1584663405, '0002', 25.05, 2641), +(1584663408, '0002', 65.67, 2642), +(1584663411, '0002', 27.93, 2643), +(1584663414, '0002', 53.53, 2644), +(1584663417, '0002', 7.34, 2645), +(1584663420, '0002', 56.18, 2646), +(1584663423, '0002', 26.3, 2647), +(1584663426, '0002', 25.07, 2648), +(1584663429, '0002', 2.25, 2649), +(1584663432, '0002', 79.88, 2650), +(1584663435, '0002', 22.03, 2651), +(1584663438, '0002', 79.06, 2652), +(1584663441, '0002', 38.96, 2653), +(1584663444, '0002', 27.82, 2654), +(1584663447, '0002', 65.26, 2655), +(1584663450, '0002', 56.75, 2656), +(1584663453, '0002', 10.29, 2657), +(1584663456, '0002', 38.77, 2658), +(1584663459, '0002', 11.42, 2659), +(1584663462, '0002', 3.33, 2660), +(1584663465, '0002', 67.88, 2661), +(1584663468, '0002', 59.92, 2662), +(1584663471, '0002', 42.35, 2663), +(1584663474, '0002', 5.35, 2664), +(1584663477, '0002', 13.12, 2665), +(1584663480, '0002', 8.71, 2666), +(1584663483, '0002', 57.16, 2667), +(1584663486, '0002', 44.77, 2668), +(1584663489, '0002', 57.17, 2669), +(1584663492, '0002', 11.06, 2670), +(1584663495, '0002', 40.95, 2671), +(1584663498, '0002', 76.7, 2672), +(1584663501, '0002', 0.08, 2673), +(1584663504, '0002', 4.87, 2674), +(1584663507, '0002', 45.75, 2675), +(1584663510, '0002', 34.09, 2676), +(1584663513, '0002', 64.96, 2677), +(1584663516, '0002', 11.15, 2678), +(1584663519, '0002', 79.06, 2679), +(1584663522, '0002', 67.86, 2680), +(1584663525, '0002', 62.93, 2681), +(1584663528, '0002', 45.21, 2682), +(1584663531, '0002', 2.43, 2683), +(1584663534, '0002', 47.39, 2684), +(1584663537, '0002', 52.16, 2685), +(1584663540, '0002', 70.2, 2686), +(1584663543, '0002', 68.93, 2687), +(1584663546, '0002', 27.55, 2688), +(1584663549, '0002', 65.59, 2689), +(1584663552, '0002', 13.62, 2690), +(1584663555, '0002', 67.95, 2691), +(1584663558, '0002', 39.17, 2692), +(1584663561, '0002', 43.42, 2693), +(1584663564, '0002', 42.88, 2694), +(1584663567, '0002', 3.26, 2695), +(1584663570, '0002', 14.32, 2696), +(1584663573, '0002', 19.72, 2697), +(1584663576, '0002', 32.36, 2698), +(1584663579, '0002', 22.88, 2699), +(1584663582, '0002', 31.53, 2700), +(1584663585, '0002', 41.45, 2701), +(1584663588, '0002', 68.83, 2702), +(1584663591, '0002', 51.12, 2703), +(1584663594, '0002', 37.49, 2704), +(1584663597, '0002', 51.15, 2705), +(1584663600, '0002', 27.81, 2706), +(1584663603, '0002', 66.99, 2707), +(1584663606, '0002', 44.25, 2708), +(1584663609, '0002', 25.48, 2709), +(1584663612, '0002', 54.92, 2710), +(1584663615, '0002', 59.46, 2711), +(1584663618, '0002', 62.38, 2712), +(1584663621, '0002', 28.68, 2713), +(1584663624, '0002', 78.7, 2714), +(1584663627, '0002', 43.33, 2715), +(1584663630, '0002', 69.14, 2716), +(1584663633, '0002', 57.3, 2717), +(1584663636, '0002', 63.16, 2718), +(1584663639, '0002', 44.45, 2719), +(1584663642, '0002', 23.08, 2720), +(1584663645, '0002', 72.55, 2721), +(1584663648, '0002', 0.06, 2722), +(1584663651, '0002', 42.58, 2723), +(1584663654, '0002', 55.95, 2724), +(1584663657, '0002', 2.11, 2725), +(1584663660, '0002', 4.07, 2726), +(1584663663, '0002', 33.54, 2727), +(1584663666, '0002', 12.1, 2728), +(1584663669, '0002', 15.92, 2729), +(1584663672, '0002', 67.94, 2730), +(1584663675, '0002', 55.1, 2731), +(1584663678, '0002', 44.76, 2732), +(1584663681, '0002', 60.84, 2733), +(1584663684, '0002', 71.01, 2734), +(1584663687, '0002', 1.5, 2735), +(1584663690, '0002', 39.03, 2736), +(1584663693, '0002', 51.39, 2737), +(1584663696, '0002', 39.41, 2738), +(1584663699, '0002', 6.71, 2739), +(1584663702, '0002', 78.6, 2740), +(1584663705, '0002', 17.16, 2741), +(1584663708, '0002', 53.26, 2742), +(1584663711, '0002', 69.92, 2743), +(1584663714, '0002', 30.87, 2744), +(1584663717, '0002', 42.63, 2745), +(1584663720, '0002', 48.72, 2746), +(1584663723, '0002', 24.15, 2747), +(1584663726, '0002', 13, 2748), +(1584663729, '0002', 18.18, 2749), +(1584663732, '0002', 40.71, 2750), +(1584663735, '0002', 30.46, 2751), +(1584663738, '0002', 59.26, 2752), +(1584663741, '0002', 40.07, 2753), +(1584663744, '0002', 22.03, 2754), +(1584663747, '0002', 37.09, 2755), +(1584663750, '0002', 46.24, 2756), +(1584663753, '0002', 64.66, 2757), +(1584663756, '0002', 30.18, 2758), +(1584663759, '0002', 7.26, 2759), +(1584663762, '0002', 45.24, 2760), +(1584663765, '0002', 62.79, 2761), +(1584663768, '0002', 51.89, 2762), +(1584663771, '0002', 79.45, 2763), +(1584663774, '0002', 13.91, 2764), +(1584663777, '0002', 78.57, 2765), +(1584663780, '0002', 3.96, 2766), +(1584663783, '0002', 42.47, 2767), +(1584663786, '0002', 49.5, 2768), +(1584663789, '0002', 16.33, 2769), +(1584663792, '0002', 74.3, 2770), +(1584663795, '0002', 60.52, 2771), +(1584663798, '0002', 37.05, 2772), +(1584663801, '0002', 62.95, 2773), +(1584663804, '0002', 49.86, 2774), +(1584663807, '0002', 49.9, 2775), +(1584663810, '0002', 63.39, 2776), +(1584663813, '0002', 58.8, 2777), +(1584663816, '0002', 8.82, 2778), +(1584663819, '0002', 31.05, 2779), +(1584663822, '0002', 58.81, 2780), +(1584663825, '0002', 9.68, 2781), +(1584663828, '0002', 63.06, 2782), +(1584663831, '0002', 29.26, 2783), +(1584663834, '0002', 36.52, 2784), +(1584663837, '0002', 52.25, 2785), +(1584663840, '0002', 70.83, 2786), +(1584663843, '0002', 31.99, 2787), +(1584663846, '0002', 37.37, 2788), +(1584663849, '0002', 17.58, 2789), +(1584663852, '0002', 29.76, 2790), +(1584663855, '0002', 60.77, 2791), +(1584663858, '0002', 3.01, 2792), +(1584663861, '0002', 75.9, 2793), +(1584663864, '0002', 69.12, 2794), +(1584663867, '0002', 61.61, 2795), +(1584663870, '0002', 9.37, 2796), +(1584663873, '0002', 65.97, 2797), +(1584663876, '0002', 18.26, 2798), +(1584663879, '0002', 22.22, 2799), +(1584663882, '0002', 69.86, 2800), +(1584663885, '0002', 61.47, 2801), +(1584663889, '0002', 43.23, 2802), +(1584663891, '0002', 8.82, 2803), +(1584663894, '0002', 0.85, 2804), +(1584663897, '0002', 33.9, 2805), +(1584663900, '0002', 57.18, 2806), +(1584663903, '0002', 52.83, 2807), +(1584663906, '0002', 27.24, 2808), +(1584663909, '0002', 31.84, 2809), +(1584663912, '0002', 5.15, 2810), +(1584663915, '0002', 64.09, 2811), +(1584663918, '0002', 23.39, 2812), +(1584663921, '0002', 49.13, 2813), +(1584663924, '0002', 62.78, 2814), +(1584663927, '0002', 38.85, 2815), +(1584663930, '0002', 17.5, 2816), +(1584663933, '0002', 9.33, 2817), +(1584663936, '0002', 46.14, 2818), +(1584663939, '0002', 32.74, 2819), +(1584663942, '0002', 41.66, 2820), +(1584663945, '0002', 23.11, 2821), +(1584663948, '0002', 16.93, 2822), +(1584663951, '0002', 72.69, 2823), +(1584663954, '0002', 14.64, 2824), +(1584663957, '0002', 38.3, 2825), +(1584663960, '0002', 47.34, 2826), +(1584663963, '0002', 20.18, 2827), +(1584663966, '0002', 67.65, 2828), +(1584663969, '0002', 29.92, 2829), +(1584663972, '0002', 74.24, 2830), +(1584663975, '0002', 50.49, 2831), +(1584663978, '0002', 67.47, 2832), +(1584663981, '0002', 35.89, 2833), +(1584663984, '0002', 1.07, 2834), +(1584663987, '0002', 0.65, 2835), +(1584663990, '0002', 7.31, 2836), +(1584663993, '0002', 78.81, 2837), +(1584663996, '0002', 26.41, 2838), +(1584663999, '0002', 29.67, 2839), +(1584664002, '0002', 13.48, 2840), +(1584664005, '0002', 24.48, 2841), +(1584664008, '0002', 23.16, 2842), +(1584664011, '0002', 43.44, 2843), +(1584664014, '0002', 26.62, 2844), +(1584664017, '0002', 43.41, 2845), +(1584664020, '0002', 75.39, 2846), +(1584664023, '0002', 72.06, 2847), +(1584664026, '0002', 14.3, 2848), +(1584664029, '0002', 70.49, 2849), +(1584664032, '0002', 6.74, 2850), +(1584664035, '0002', 41.75, 2851), +(1584664038, '0002', 59.32, 2852), +(1584664041, '0002', 35.12, 2853), +(1584664044, '0002', 71.16, 2854), +(1584664047, '0002', 64.93, 2855), +(1584664050, '0002', 72.08, 2856), +(1584664053, '0002', 79.17, 2857), +(1584664056, '0002', 52.69, 2858), +(1584664059, '0002', 31.78, 2859), +(1584664062, '0002', 22.3, 2860), +(1584664065, '0002', 66.96, 2861), +(1584664068, '0002', 5.36, 2862), +(1584664071, '0002', 68.09, 2863), +(1584664074, '0002', 10.27, 2864), +(1584664077, '0002', 16.56, 2865), +(1584664080, '0002', 5.49, 2866), +(1584664083, '0002', 17.19, 2867), +(1584664086, '0002', 43.93, 2868), +(1584664089, '0002', 79.63, 2869), +(1584664092, '0002', 78.24, 2870), +(1584664095, '0002', 36.08, 2871), +(1584664098, '0002', 20.45, 2872), +(1584664101, '0002', 1.29, 2873), +(1584664104, '0002', 56.73, 2874), +(1584664107, '0002', 35.39, 2875), +(1584664110, '0002', 52.73, 2876), +(1584664113, '0002', 66.72, 2877), +(1584664116, '0002', 34.01, 2878), +(1584664119, '0002', 11.68, 2879), +(1584664122, '0002', 0.39, 2880), +(1584664125, '0002', 1.76, 2881), +(1584664128, '0002', 52.75, 2882), +(1584664131, '0002', 36, 2883), +(1584664134, '0002', 68.59, 2884), +(1584664137, '0002', 18.98, 2885), +(1584664140, '0002', 49.13, 2886), +(1584664143, '0002', 1.48, 2887), +(1584664146, '0002', 0.97, 2888), +(1584664149, '0002', 72.67, 2889), +(1584664152, '0002', 24.8, 2890), +(1584664155, '0002', 8.49, 2891), +(1584664158, '0002', 14.34, 2892), +(1584664161, '0002', 14.11, 2893), +(1584664164, '0002', 23.41, 2894), +(1584664167, '0002', 56.19, 2895), +(1584664170, '0002', 55.1, 2896), +(1584664173, '0002', 9.48, 2897), +(1584664176, '0002', 53.84, 2898), +(1584664179, '0002', 60.25, 2899), +(1584664182, '0002', 31.28, 2900), +(1584664185, '0002', 1.66, 2901), +(1584664188, '0002', 74.76, 2902), +(1584664191, '0002', 61.95, 2903), +(1584664194, '0002', 50.69, 2904), +(1584664197, '0002', 2.06, 2905), +(1584664200, '0002', 65.15, 2906), +(1584664203, '0002', 0.69, 2907), +(1584664206, '0002', 11.53, 2908), +(1584664209, '0002', 72.04, 2909), +(1584664212, '0002', 67.76, 2910), +(1584664215, '0002', 59.78, 2911), +(1584664218, '0002', 61.83, 2912), +(1584664221, '0002', 40.31, 2913), +(1584664224, '0002', 44.98, 2914), +(1584664227, '0002', 62.18, 2915), +(1584664230, '0002', 49.43, 2916), +(1584664233, '0002', 26.45, 2917), +(1584664236, '0002', 10.95, 2918), +(1584664239, '0002', 7.5, 2919), +(1584664242, '0002', 73.49, 2920), +(1584664245, '0002', 49.33, 2921), +(1584664248, '0002', 54.36, 2922), +(1584664251, '0002', 64.28, 2923), +(1584664254, '0002', 72.4, 2924), +(1584664257, '0002', 24.03, 2925), +(1584664260, '0002', 47.23, 2926), +(1584664263, '0002', 59.04, 2927), +(1584664266, '0002', 70.32, 2928), +(1584664269, '0002', 26.3, 2929), +(1584664272, '0002', 53.39, 2930), +(1584664275, '0002', 20.6, 2931), +(1584664278, '0002', 27.82, 2932), +(1584664281, '0002', 44.83, 2933), +(1584664284, '0002', 74.87, 2934), +(1584664287, '0002', 45.24, 2935), +(1584664290, '0002', 73.16, 2936), +(1584664293, '0002', 10.39, 2937), +(1584664296, '0002', 56.2, 2938), +(1584664299, '0002', 33.19, 2939), +(1584664302, '0002', 76.31, 2940), +(1584664305, '0002', 54.04, 2941), +(1584664308, '0002', 5.24, 2942), +(1584664311, '0002', 61.91, 2943), +(1584664314, '0002', 62.56, 2944), +(1584664317, '0002', 71.66, 2945), +(1584664320, '0002', 63.77, 2946), +(1584664323, '0002', 76.14, 2947), +(1584664326, '0002', 18.12, 2948), +(1584664329, '0002', 47.88, 2949), +(1584664332, '0002', 50.01, 2950), +(1584664335, '0002', 36.01, 2951), +(1584664338, '0002', 32.14, 2952), +(1584664341, '0002', 48.67, 2953), +(1584664344, '0002', 17.14, 2954), +(1584664347, '0002', 64.95, 2955), +(1584664350, '0002', 41.15, 2956), +(1584664353, '0002', 1.95, 2957), +(1584664356, '0002', 44.93, 2958), +(1584664359, '0002', 21.31, 2959), +(1584664362, '0002', 17.66, 2960), +(1584664365, '0002', 75.64, 2961), +(1584664368, '0002', 36.9, 2962), +(1584664371, '0002', 45.5, 2963), +(1584664374, '0002', 62.25, 2964), +(1584664377, '0002', 70.05, 2965), +(1584664380, '0002', 49.64, 2966), +(1584664383, '0002', 56.76, 2967), +(1584664386, '0002', 39.02, 2968), +(1584664389, '0002', 51.38, 2969), +(1584664392, '0002', 9.75, 2970), +(1584664395, '0002', 76.08, 2971), +(1584664398, '0002', 14.28, 2972), +(1584664401, '0002', 3.07, 2973), +(1584664404, '0002', 72.31, 2974), +(1584664407, '0002', 54.1, 2975), +(1584664410, '0002', 13.57, 2976), +(1584664413, '0002', 10.11, 2977), +(1584664416, '0002', 24.5, 2978), +(1584664419, '0002', 15.42, 2979), +(1584664422, '0002', 35.94, 2980), +(1584664425, '0002', 76.61, 2981), +(1584664428, '0002', 37.99, 2982), +(1584664431, '0002', 70.59, 2983), +(1584664434, '0002', 1.4, 2984), +(1584664437, '0002', 38.28, 2985), +(1584664440, '0002', 66.25, 2986), +(1584664443, '0002', 59.33, 2987), +(1584664446, '0002', 50.35, 2988), +(1584664449, '0002', 3.93, 2989), +(1584664452, '0002', 5.7, 2990), +(1584664455, '0002', 4.17, 2991), +(1584664458, '0002', 73.18, 2992), +(1584664461, '0002', 65.63, 2993), +(1584664464, '0002', 3.5, 2994), +(1584664467, '0002', 78.04, 2995), +(1584664470, '0002', 32.59, 2996), +(1584664473, '0002', 39.75, 2997), +(1584664476, '0002', 65.65, 2998), +(1584664479, '0002', 24.94, 2999), +(1584664482, '0002', 36.74, 3000), +(1584664485, '0002', 63.95, 3001), +(1584664488, '0002', 9.38, 3002), +(1584664491, '0002', 43.34, 3003), +(1584664494, '0002', 60.95, 3004), +(1584664497, '0002', 18.5, 3005), +(1584664500, '0002', 54.85, 3006), +(1584664503, '0002', 71.93, 3007), +(1584664506, '0002', 54.77, 3008), +(1584664509, '0002', 51.67, 3009), +(1584664512, '0002', 21.07, 3010), +(1584664515, '0002', 59.39, 3011), +(1584664518, '0002', 40.06, 3012), +(1584664521, '0002', 14.02, 3013), +(1584664524, '0002', 64.75, 3014), +(1584664527, '0002', 27.76, 3015), +(1584664530, '0002', 13.56, 3016), +(1584664533, '0002', 9.01, 3017), +(1584664536, '0002', 17.7, 3018), +(1584664539, '0002', 42.24, 3019), +(1584664542, '0002', 39.17, 3020), +(1584664545, '0002', 27.58, 3021), +(1584664548, '0002', 60.51, 3022), +(1584664551, '0002', 32.24, 3023), +(1584664554, '0002', 65.68, 3024), +(1584664557, '0002', 67.44, 3025), +(1584664560, '0002', 49.66, 3026), +(1584664563, '0002', 14.29, 3027), +(1584664566, '0002', 55.99, 3028), +(1584664569, '0002', 68.7, 3029), +(1584664572, '0002', 16.57, 3030), +(1584664575, '0002', 66.55, 3031), +(1584664578, '0002', 72.14, 3032), +(1584664581, '0002', 27.85, 3033), +(1584664584, '0002', 72.1, 3034), +(1584664587, '0002', 43.18, 3035), +(1584664590, '0002', 27, 3036), +(1584664593, '0002', 65.57, 3037), +(1584664596, '0002', 12.83, 3038), +(1584664599, '0002', 49.36, 3039), +(1584664602, '0002', 1.66, 3040), +(1584664605, '0002', 9.52, 3041), +(1584664608, '0002', 33.82, 3042), +(1584664611, '0002', 30.7, 3043), +(1584664614, '0002', 25.15, 3044), +(1584664617, '0002', 51.06, 3045), +(1584664620, '0002', 74.08, 3046), +(1584664623, '0002', 25.65, 3047), +(1584664626, '0002', 76.26, 3048), +(1584664629, '0002', 78.86, 3049), +(1584664632, '0002', 20.41, 3050), +(1584664635, '0002', 59.95, 3051), +(1584664638, '0002', 36.08, 3052), +(1584664641, '0002', 36.51, 3053), +(1584664644, '0002', 61.39, 3054), +(1584664647, '0002', 0.72, 3055), +(1584664650, '0002', 75.92, 3056), +(1584664653, '0002', 39.71, 3057), +(1584664656, '0002', 66.5, 3058), +(1584664659, '0002', 0.47, 3059), +(1584664662, '0002', 68.39, 3060), +(1584664665, '0002', 23.69, 3061), +(1584664668, '0002', 55.52, 3062), +(1584664671, '0002', 18.1, 3063), +(1584664674, '0002', 13.39, 3064), +(1584664677, '0002', 37.5, 3065), +(1584664680, '0002', 2.12, 3066), +(1584664683, '0002', 56.94, 3067), +(1584664686, '0002', 58.41, 3068), +(1584664689, '0002', 55.23, 3069), +(1584664692, '0002', 18.78, 3070), +(1584664695, '0002', 8.53, 3071), +(1584664698, '0002', 28.5, 3072), +(1584664701, '0002', 72.71, 3073), +(1584664704, '0002', 9.53, 3074), +(1584664707, '0002', 65.73, 3075), +(1584664710, '0002', 5.98, 3076), +(1584664713, '0002', 32.72, 3077), +(1584664716, '0002', 20.31, 3078), +(1584664719, '0002', 59.73, 3079), +(1584664722, '0002', 14.06, 3080); +INSERT INTO `bs_device_data_traffic_light` (`time`, `serial_num`, `data`, `id`) VALUES +(1584664725, '0002', 0.11, 3081), +(1584664728, '0002', 71.04, 3082), +(1584664731, '0002', 2.77, 3083), +(1584664734, '0002', 14.94, 3084), +(1584664737, '0002', 61.52, 3085), +(1584664740, '0002', 43.82, 3086), +(1584664743, '0002', 2.71, 3087), +(1584664746, '0002', 39.79, 3088), +(1584664749, '0002', 50.77, 3089), +(1584664752, '0002', 49.74, 3090), +(1584664755, '0002', 55.21, 3091), +(1584664758, '0002', 41.61, 3092), +(1584664761, '0002', 42.75, 3093), +(1584664764, '0002', 3.48, 3094), +(1584664767, '0002', 42.79, 3095), +(1584664770, '0002', 67.42, 3096), +(1584664773, '0002', 12.88, 3097), +(1584664776, '0002', 54.45, 3098), +(1584664779, '0002', 9.35, 3099), +(1584664782, '0002', 36.07, 3100), +(1584664785, '0002', 54.68, 3101), +(1584664788, '0002', 37.65, 3102), +(1584664791, '0002', 65.53, 3103), +(1584664794, '0002', 21.53, 3104), +(1584664797, '0002', 56.31, 3105), +(1584664800, '0002', 43.63, 3106), +(1584664803, '0002', 65.28, 3107), +(1584664806, '0002', 76.15, 3108), +(1584664809, '0002', 28.3, 3109), +(1584664812, '0002', 23.8, 3110), +(1584664815, '0002', 7.31, 3111), +(1584664818, '0002', 1.46, 3112), +(1584664821, '0002', 4.86, 3113), +(1584664824, '0002', 24.46, 3114), +(1584664827, '0002', 13.25, 3115), +(1584664830, '0002', 36.25, 3116), +(1584664833, '0002', 14.92, 3117), +(1584664836, '0002', 21.05, 3118), +(1584664839, '0002', 20.2, 3119), +(1584664842, '0002', 46.35, 3120), +(1584664845, '0002', 69.15, 3121), +(1584664848, '0002', 41.93, 3122), +(1584664851, '0002', 3.98, 3123), +(1584664854, '0002', 43.39, 3124), +(1584664857, '0002', 53.26, 3125), +(1584664860, '0002', 24.18, 3126), +(1584664863, '0002', 54.47, 3127), +(1584664866, '0002', 66.1, 3128), +(1584664869, '0002', 46.61, 3129), +(1584664872, '0002', 72.43, 3130), +(1584664875, '0002', 52.26, 3131), +(1584664878, '0002', 19.5, 3132), +(1584664881, '0002', 11.17, 3133), +(1584664884, '0002', 34.6, 3134), +(1584664887, '0002', 51.15, 3135), +(1584664890, '0002', 15.04, 3136), +(1584664893, '0002', 53.36, 3137), +(1584664896, '0002', 12.6, 3138), +(1584664899, '0002', 5.98, 3139), +(1584664902, '0002', 62, 3140), +(1584664905, '0002', 10.37, 3141), +(1584664908, '0002', 57.61, 3142), +(1584664911, '0002', 79.69, 3143), +(1584664914, '0002', 73.48, 3144), +(1584664917, '0002', 71.35, 3145), +(1584664920, '0002', 67.38, 3146), +(1584664923, '0002', 15.1, 3147), +(1584664926, '0002', 65.86, 3148), +(1584664929, '0002', 8.91, 3149), +(1584664932, '0002', 17.98, 3150), +(1584664935, '0002', 6.03, 3151), +(1584664938, '0002', 8.18, 3152), +(1584664941, '0002', 36.52, 3153), +(1584664944, '0002', 13.63, 3154), +(1584664947, '0002', 74.62, 3155), +(1584664950, '0002', 48.57, 3156), +(1584664953, '0002', 51.11, 3157), +(1584664956, '0002', 24.74, 3158), +(1584664959, '0002', 13.62, 3159), +(1584664962, '0002', 51.6, 3160), +(1584664965, '0002', 14.46, 3161), +(1584664968, '0002', 79.46, 3162), +(1584664971, '0002', 37.62, 3163), +(1584664974, '0002', 14.75, 3164), +(1584664977, '0002', 37.03, 3165), +(1584664980, '0002', 23.91, 3166), +(1584664983, '0002', 72.72, 3167), +(1584664986, '0002', 79.95, 3168), +(1584664989, '0002', 69.71, 3169), +(1584664992, '0002', 71.24, 3170), +(1584664995, '0002', 75.44, 3171), +(1584664998, '0002', 56.93, 3172), +(1584665001, '0002', 23.08, 3173), +(1584665004, '0002', 67.02, 3174), +(1584665007, '0002', 66.78, 3175), +(1584665010, '0002', 73.72, 3176), +(1584665013, '0002', 45.91, 3177), +(1584665016, '0002', 8.43, 3178), +(1584665019, '0002', 27.63, 3179), +(1584665022, '0002', 40.93, 3180), +(1584665025, '0002', 32.6, 3181), +(1584665028, '0002', 2.29, 3182), +(1584665031, '0002', 40.37, 3183), +(1584665034, '0002', 11.3, 3184), +(1584665037, '0002', 34.84, 3185), +(1584665040, '0002', 69.52, 3186), +(1584665043, '0002', 34.78, 3187), +(1584665046, '0002', 6.75, 3188), +(1584665049, '0002', 78.42, 3189), +(1584665052, '0002', 77.34, 3190), +(1584665055, '0002', 66.92, 3191), +(1584665058, '0002', 72.06, 3192), +(1584665061, '0002', 40.25, 3193), +(1584665064, '0002', 31.16, 3194), +(1584665067, '0002', 38.22, 3195), +(1584665070, '0002', 41.35, 3196), +(1584665073, '0002', 39.64, 3197), +(1584665076, '0002', 74.31, 3198), +(1584665079, '0002', 21.89, 3199), +(1584665082, '0002', 2.68, 3200), +(1584665085, '0002', 6.7, 3201), +(1584665088, '0002', 42.07, 3202), +(1584665091, '0002', 53.74, 3203), +(1584665094, '0002', 62.84, 3204), +(1584665097, '0002', 41.82, 3205), +(1584665100, '0002', 39.43, 3206), +(1584665103, '0002', 22.14, 3207), +(1584665106, '0002', 60.26, 3208), +(1584665109, '0002', 53.38, 3209), +(1584665112, '0002', 7, 3210), +(1584665115, '0002', 47.95, 3211), +(1584665118, '0002', 36.92, 3212), +(1584665121, '0002', 23.94, 3213), +(1584665124, '0002', 33.52, 3214), +(1584665127, '0002', 49.41, 3215), +(1584665130, '0002', 41.12, 3216), +(1584665133, '0002', 21.43, 3217), +(1584665136, '0002', 31.17, 3218), +(1584665139, '0002', 34.92, 3219), +(1584665142, '0002', 52.27, 3220), +(1584665145, '0002', 73.22, 3221), +(1584665148, '0002', 66.78, 3222), +(1584665151, '0002', 48.93, 3223), +(1584665154, '0002', 22.42, 3224), +(1584665157, '0002', 22.69, 3225), +(1584665160, '0002', 47.3, 3226), +(1584665163, '0002', 34.32, 3227), +(1584665166, '0002', 78.38, 3228), +(1584665169, '0002', 46.25, 3229), +(1584665172, '0002', 14.5, 3230), +(1584665175, '0002', 8.49, 3231), +(1584665178, '0002', 45.48, 3232), +(1584665181, '0002', 32.98, 3233), +(1584665184, '0002', 2.75, 3234), +(1584665187, '0002', 13.99, 3235), +(1584665190, '0002', 56.29, 3236), +(1584665193, '0002', 75.85, 3237), +(1584665196, '0002', 51.28, 3238), +(1584665199, '0002', 61.86, 3239), +(1584665202, '0002', 29.97, 3240), +(1584665205, '0002', 56.05, 3241), +(1584665208, '0002', 25.86, 3242), +(1584665211, '0002', 13.65, 3243), +(1584665214, '0002', 30.59, 3244), +(1584665217, '0002', 37.09, 3245), +(1584665220, '0002', 75, 3246), +(1584665223, '0002', 61.29, 3247), +(1584665226, '0002', 30.51, 3248), +(1584665229, '0002', 65.99, 3249), +(1584665232, '0002', 47.91, 3250), +(1584665235, '0002', 8.88, 3251), +(1584665238, '0002', 13.17, 3252), +(1584665241, '0002', 57.35, 3253), +(1584665244, '0002', 6.51, 3254), +(1584665247, '0002', 75.62, 3255), +(1584665250, '0002', 12.53, 3256), +(1584665253, '0002', 59.98, 3257), +(1584665256, '0002', 71.94, 3258), +(1584665259, '0002', 37.08, 3259), +(1584665262, '0002', 40.59, 3260), +(1584665265, '0002', 3.09, 3261), +(1584665268, '0002', 39.93, 3262), +(1584665271, '0002', 26.32, 3263), +(1584665274, '0002', 13.64, 3264), +(1584665277, '0002', 48.2, 3265), +(1584665280, '0002', 13.64, 3266), +(1584665283, '0002', 5.48, 3267), +(1584665286, '0002', 27.59, 3268), +(1584665289, '0002', 53.65, 3269), +(1584665292, '0002', 32.07, 3270), +(1584665295, '0002', 43.66, 3271), +(1584665298, '0002', 6.58, 3272), +(1584665301, '0002', 60.97, 3273), +(1584665304, '0002', 22.18, 3274), +(1584665307, '0002', 75.04, 3275), +(1584665310, '0002', 52.87, 3276), +(1584665313, '0002', 43.71, 3277), +(1584665316, '0002', 74.97, 3278), +(1584665319, '0002', 18.2, 3279), +(1584665322, '0002', 34.74, 3280), +(1584665325, '0002', 16.58, 3281), +(1584665328, '0002', 0.38, 3282), +(1584665331, '0002', 26.2, 3283), +(1584665334, '0002', 0.39, 3284), +(1584665337, '0002', 51.85, 3285), +(1584665340, '0002', 16.77, 3286), +(1584665343, '0002', 2.34, 3287), +(1584665346, '0002', 77.27, 3288), +(1584665349, '0002', 7.85, 3289), +(1584665352, '0002', 52.93, 3290), +(1584665355, '0002', 8.7, 3291), +(1584665358, '0002', 18.05, 3292), +(1584665361, '0002', 75.35, 3293), +(1584665364, '0002', 7.28, 3294), +(1584665367, '0002', 65.5, 3295), +(1584665370, '0002', 38.67, 3296), +(1584665373, '0002', 26.97, 3297), +(1584665376, '0002', 38.55, 3298), +(1584665379, '0002', 76.83, 3299), +(1584665382, '0002', 57.43, 3300), +(1584665385, '0002', 74.15, 3301), +(1584665389, '0002', 59.95, 3302), +(1584665391, '0002', 24.92, 3303), +(1584665394, '0002', 38.05, 3304), +(1584665397, '0002', 74.11, 3305), +(1584665400, '0002', 66.11, 3306), +(1584665403, '0002', 44.25, 3307), +(1584665406, '0002', 0.28, 3308), +(1584665409, '0002', 41.31, 3309), +(1584665412, '0002', 76.54, 3310), +(1584665415, '0002', 73.47, 3311), +(1584665418, '0002', 60.26, 3312), +(1584665421, '0002', 59.46, 3313), +(1584665424, '0002', 76.41, 3314), +(1584665427, '0002', 37.55, 3315), +(1584665430, '0002', 59.15, 3316), +(1584665433, '0002', 38.33, 3317), +(1584665436, '0002', 37.66, 3318), +(1584665439, '0002', 62.17, 3319), +(1584665442, '0002', 36.25, 3320), +(1584665445, '0002', 61.97, 3321), +(1584665448, '0002', 27.99, 3322), +(1584665451, '0002', 13.8, 3323), +(1584665454, '0002', 20.25, 3324), +(1584665457, '0002', 14.41, 3325), +(1584665460, '0002', 22.94, 3326), +(1584665463, '0002', 63.89, 3327), +(1584665466, '0002', 9.94, 3328), +(1584665469, '0002', 65.54, 3329), +(1584665472, '0002', 2.7, 3330), +(1584665475, '0002', 71.5, 3331), +(1584665478, '0002', 19.23, 3332), +(1584665481, '0002', 15.1, 3333), +(1584665484, '0002', 61.88, 3334), +(1584665487, '0002', 53.16, 3335), +(1584665490, '0002', 71.85, 3336), +(1584665493, '0002', 55.11, 3337), +(1584665496, '0002', 74.29, 3338), +(1584665499, '0002', 40.06, 3339), +(1584665502, '0002', 52.85, 3340), +(1584665505, '0002', 24.79, 3341), +(1584665508, '0002', 51.27, 3342), +(1584665511, '0002', 68.22, 3343), +(1584665514, '0002', 37.88, 3344), +(1584665517, '0002', 76.58, 3345), +(1584665520, '0002', 43.12, 3346), +(1584665523, '0002', 40.37, 3347), +(1584665526, '0002', 39.53, 3348), +(1584665529, '0002', 40.27, 3349), +(1584665532, '0002', 20.59, 3350), +(1584665535, '0002', 8.46, 3351), +(1584665538, '0002', 7.37, 3352), +(1584665541, '0002', 36.36, 3353), +(1584665544, '0002', 28.87, 3354), +(1584665547, '0002', 58.38, 3355), +(1584665550, '0002', 2.63, 3356), +(1584665553, '0002', 0.21, 3357), +(1584665556, '0002', 4.26, 3358), +(1584665559, '0002', 33.37, 3359), +(1584665562, '0002', 56.13, 3360), +(1584665565, '0002', 26.55, 3361), +(1584665568, '0002', 27.85, 3362), +(1584665571, '0002', 9.6, 3363), +(1584665574, '0002', 8.79, 3364), +(1584665577, '0002', 8.7, 3365), +(1584665580, '0002', 29.74, 3366), +(1584665583, '0002', 68.05, 3367), +(1584665586, '0002', 6.25, 3368), +(1584665589, '0002', 55.84, 3369), +(1584665592, '0002', 16.8, 3370), +(1584665595, '0002', 9.48, 3371), +(1584665598, '0002', 29.94, 3372), +(1584665601, '0002', 11.04, 3373), +(1584665604, '0002', 59.88, 3374), +(1584665607, '0002', 40.05, 3375), +(1584665610, '0002', 74.73, 3376), +(1584665613, '0002', 78.81, 3377), +(1584665616, '0002', 56.41, 3378), +(1584665619, '0002', 41.06, 3379), +(1584665622, '0002', 49.05, 3380), +(1584665625, '0002', 39.08, 3381), +(1584665628, '0002', 9.59, 3382), +(1584665631, '0002', 55.64, 3383), +(1584665634, '0002', 5.08, 3384), +(1584665637, '0002', 42.02, 3385), +(1584665640, '0002', 22.31, 3386), +(1584665643, '0002', 45.81, 3387), +(1584665646, '0002', 69.13, 3388), +(1584665649, '0002', 19.87, 3389), +(1584665652, '0002', 50, 3390), +(1584665655, '0002', 46.43, 3391), +(1584665658, '0002', 27.14, 3392), +(1584665661, '0002', 14.89, 3393), +(1584665664, '0002', 4.84, 3394), +(1584665667, '0002', 47.33, 3395), +(1584665670, '0002', 76.84, 3396), +(1584665673, '0002', 39.1, 3397), +(1584665676, '0002', 40.52, 3398), +(1584665679, '0002', 79.11, 3399), +(1584665682, '0002', 60.23, 3400), +(1584665685, '0002', 18.51, 3401), +(1584665688, '0002', 24.13, 3402), +(1584665691, '0002', 79.47, 3403), +(1584665694, '0002', 75.19, 3404), +(1584665697, '0002', 51.31, 3405), +(1584665700, '0002', 50.84, 3406), +(1584665703, '0002', 66.99, 3407), +(1584665706, '0002', 30.92, 3408), +(1584665709, '0002', 41.23, 3409), +(1584665712, '0002', 24.9, 3410), +(1584665715, '0002', 69.39, 3411), +(1584665718, '0002', 30.13, 3412), +(1584665721, '0002', 64.05, 3413), +(1584665724, '0002', 64.87, 3414), +(1584665727, '0002', 46.43, 3415), +(1584665730, '0002', 71.09, 3416), +(1584665733, '0002', 0.65, 3417), +(1584665736, '0002', 65.8, 3418), +(1584665739, '0002', 74.23, 3419), +(1584665742, '0002', 37.84, 3420), +(1584665745, '0002', 42.04, 3421), +(1584665748, '0002', 1.42, 3422), +(1584665751, '0002', 10.93, 3423), +(1584665754, '0002', 25.61, 3424), +(1584665757, '0002', 15.68, 3425), +(1584665760, '0002', 54.73, 3426), +(1584665763, '0002', 38.49, 3427), +(1584665766, '0002', 54.28, 3428), +(1584665769, '0002', 60.91, 3429), +(1584665772, '0002', 64.04, 3430), +(1584665775, '0002', 26.34, 3431), +(1584665778, '0002', 73.33, 3432), +(1584665781, '0002', 17.47, 3433), +(1584665784, '0002', 73.22, 3434), +(1584665787, '0002', 53.4, 3435), +(1584665790, '0002', 3.14, 3436), +(1584665793, '0002', 13.45, 3437), +(1584665796, '0002', 20.97, 3438), +(1584665799, '0002', 25.56, 3439), +(1584665802, '0002', 58.22, 3440), +(1584665805, '0002', 62.92, 3441), +(1584665808, '0002', 71.37, 3442), +(1584665811, '0002', 52.38, 3443), +(1584665814, '0002', 14.54, 3444), +(1584665817, '0002', 49.79, 3445), +(1584665820, '0002', 65.98, 3446), +(1584665823, '0002', 20.24, 3447), +(1584665826, '0002', 36.87, 3448), +(1584665829, '0002', 10.76, 3449), +(1584665832, '0002', 37.75, 3450), +(1584665835, '0002', 9.34, 3451), +(1584665838, '0002', 53.8, 3452), +(1584665841, '0002', 64.58, 3453), +(1584665844, '0002', 43.79, 3454), +(1584665847, '0002', 32.8, 3455), +(1584665850, '0002', 24.92, 3456), +(1584665853, '0002', 67.01, 3457), +(1584665856, '0002', 62.4, 3458), +(1584665859, '0002', 49.21, 3459), +(1584665862, '0002', 41.66, 3460), +(1584665865, '0002', 49.82, 3461), +(1584665868, '0002', 68.33, 3462), +(1584665871, '0002', 50.66, 3463), +(1584665874, '0002', 3.03, 3464), +(1584665877, '0002', 48.98, 3465), +(1584665880, '0002', 71.29, 3466), +(1584665883, '0002', 57.95, 3467), +(1584665886, '0002', 2.56, 3468), +(1584665889, '0002', 72.53, 3469), +(1584665892, '0002', 76.98, 3470), +(1584665895, '0002', 19.49, 3471), +(1584665898, '0002', 17.26, 3472), +(1584665901, '0002', 51.85, 3473), +(1584665904, '0002', 43.27, 3474), +(1584665907, '0002', 5.95, 3475), +(1584665910, '0002', 21.09, 3476), +(1584665913, '0002', 26.5, 3477), +(1584665916, '0002', 22.22, 3478), +(1584665919, '0002', 15.71, 3479), +(1584665922, '0002', 39.3, 3480), +(1584665925, '0002', 48.65, 3481), +(1584665928, '0002', 25.03, 3482), +(1584665931, '0002', 66.71, 3483), +(1584665934, '0002', 58.81, 3484), +(1584665937, '0002', 22.06, 3485), +(1584665940, '0002', 77.12, 3486), +(1584665943, '0002', 4.43, 3487), +(1584665946, '0002', 64.01, 3488), +(1584665949, '0002', 31.03, 3489), +(1584665952, '0002', 36.04, 3490), +(1584665955, '0002', 30.87, 3491), +(1584665958, '0002', 45.22, 3492), +(1584665961, '0002', 3.45, 3493), +(1584665964, '0002', 20.21, 3494), +(1584665967, '0002', 21.51, 3495), +(1584665970, '0002', 64.61, 3496), +(1584665973, '0002', 12.35, 3497), +(1584665976, '0002', 63.59, 3498), +(1584665979, '0002', 70.52, 3499), +(1584665982, '0002', 2.52, 3500), +(1584665985, '0002', 19.03, 3501), +(1584665988, '0002', 54.89, 3502), +(1584665991, '0002', 12.24, 3503), +(1584665994, '0002', 55.45, 3504), +(1584665997, '0002', 5.77, 3505), +(1584666000, '0002', 77.66, 3506), +(1584666003, '0002', 69.38, 3507), +(1584666006, '0002', 24.07, 3508), +(1584666009, '0002', 79.47, 3509), +(1584666012, '0002', 43.31, 3510), +(1584666015, '0002', 31.38, 3511), +(1584666018, '0002', 15.74, 3512), +(1584666021, '0002', 13.57, 3513), +(1584666024, '0002', 54.26, 3514), +(1584666027, '0002', 52.57, 3515), +(1584666030, '0002', 38.49, 3516), +(1584666033, '0002', 6.59, 3517), +(1584666036, '0002', 73.56, 3518), +(1584666039, '0002', 43.32, 3519), +(1584666042, '0002', 40.33, 3520), +(1584666045, '0002', 4.65, 3521), +(1584666048, '0002', 28.06, 3522), +(1584666051, '0002', 15.47, 3523), +(1584666054, '0002', 29.07, 3524), +(1584666057, '0002', 39.45, 3525), +(1584666060, '0002', 21.72, 3526), +(1584666063, '0002', 10.72, 3527), +(1584666066, '0002', 78.88, 3528), +(1584666069, '0002', 53.76, 3529), +(1584666072, '0002', 40.4, 3530), +(1584666075, '0002', 67.22, 3531), +(1584666078, '0002', 71.28, 3532), +(1584666081, '0002', 47.94, 3533), +(1584666084, '0002', 1.11, 3534), +(1584666087, '0002', 37.38, 3535), +(1584666090, '0002', 75.52, 3536), +(1584666093, '0002', 7.48, 3537), +(1584666096, '0002', 30.6, 3538), +(1584666099, '0002', 10.21, 3539), +(1584666102, '0002', 43.26, 3540), +(1584666105, '0002', 9.98, 3541), +(1584666108, '0002', 4.86, 3542), +(1584666111, '0002', 41.46, 3543), +(1584666114, '0002', 43.63, 3544), +(1584666117, '0002', 62.98, 3545), +(1584666120, '0002', 72.17, 3546), +(1584666123, '0002', 21.65, 3547), +(1584666126, '0002', 32.42, 3548), +(1584666129, '0002', 51.62, 3549), +(1584666132, '0002', 4.41, 3550), +(1584666135, '0002', 69.41, 3551), +(1584666138, '0002', 69.31, 3552), +(1584666141, '0002', 11.43, 3553), +(1584666144, '0002', 23.11, 3554), +(1584666147, '0002', 15.77, 3555), +(1584666150, '0002', 60.11, 3556), +(1584666153, '0002', 29.53, 3557), +(1584666156, '0002', 30.85, 3558), +(1584666159, '0002', 44.71, 3559), +(1584666162, '0002', 59.06, 3560), +(1584666165, '0002', 48.89, 3561), +(1584666168, '0002', 17.83, 3562), +(1584666171, '0002', 77.25, 3563), +(1584666174, '0002', 22.93, 3564), +(1584666177, '0002', 29.14, 3565), +(1584666180, '0002', 32.12, 3566), +(1584666183, '0002', 18.67, 3567), +(1584666186, '0002', 65.34, 3568), +(1584666189, '0002', 67.95, 3569), +(1584666192, '0002', 30.61, 3570), +(1584666195, '0002', 9.05, 3571), +(1584666198, '0002', 37.98, 3572), +(1584666201, '0002', 45.49, 3573), +(1584666204, '0002', 61.49, 3574), +(1584666207, '0002', 1.51, 3575), +(1584666210, '0002', 71.88, 3576), +(1584666213, '0002', 52.99, 3577), +(1584666216, '0002', 61.59, 3578), +(1584666219, '0002', 8.92, 3579), +(1584666222, '0002', 30.82, 3580), +(1584666225, '0002', 28.84, 3581), +(1584666228, '0002', 62.02, 3582), +(1584666231, '0002', 44.44, 3583), +(1584666234, '0002', 30.87, 3584), +(1584666237, '0002', 32.2, 3585), +(1584666240, '0002', 16.2, 3586), +(1584666243, '0002', 24.09, 3587), +(1584666246, '0002', 67.91, 3588), +(1584666249, '0002', 68.01, 3589), +(1584666252, '0002', 22.97, 3590), +(1584666255, '0002', 48.45, 3591), +(1584666258, '0002', 19.78, 3592), +(1584666261, '0002', 66.92, 3593), +(1584666264, '0002', 30.6, 3594), +(1584666267, '0002', 33.13, 3595), +(1584666270, '0002', 10.52, 3596), +(1584666273, '0002', 0.2, 3597), +(1584666276, '0002', 37.08, 3598), +(1584666279, '0002', 31.3, 3599), +(1584666282, '0002', 7.8, 3600), +(1584666285, '0002', 18.68, 3601), +(1584666288, '0002', 34.74, 3602), +(1584666291, '0002', 8.5, 3603), +(1584666294, '0002', 79.35, 3604), +(1584666297, '0002', 20.14, 3605), +(1584666300, '0002', 44.15, 3606), +(1584666303, '0002', 43.19, 3607), +(1584666306, '0002', 21.75, 3608), +(1584666309, '0002', 68.92, 3609), +(1584666312, '0002', 16.04, 3610), +(1584666315, '0002', 6.73, 3611), +(1584666318, '0002', 5.9, 3612), +(1584666321, '0002', 19.08, 3613), +(1584666324, '0002', 13.84, 3614), +(1584666327, '0002', 64.48, 3615), +(1584666330, '0002', 7.29, 3616), +(1584666333, '0002', 33.16, 3617), +(1584666336, '0002', 56.06, 3618), +(1584666339, '0002', 28.72, 3619), +(1584666342, '0002', 79.53, 3620), +(1584666345, '0002', 9.49, 3621), +(1584666348, '0002', 50.05, 3622), +(1584666351, '0002', 25.99, 3623), +(1584666354, '0002', 49.82, 3624), +(1584666357, '0002', 56.2, 3625), +(1584666360, '0002', 71.19, 3626), +(1584666363, '0002', 13.71, 3627), +(1584666366, '0002', 55.4, 3628), +(1584666369, '0002', 52.04, 3629), +(1584666372, '0002', 68.48, 3630), +(1584666375, '0002', 30.21, 3631), +(1584666378, '0002', 10.38, 3632), +(1584666381, '0002', 10.42, 3633), +(1584666384, '0002', 71.16, 3634), +(1584666387, '0002', 50.13, 3635), +(1584666390, '0002', 7.27, 3636), +(1584666393, '0002', 67.97, 3637), +(1584666396, '0002', 40.32, 3638), +(1584666399, '0002', 72.57, 3639), +(1584666402, '0002', 41.44, 3640), +(1584666405, '0002', 33.67, 3641), +(1584666408, '0002', 22.19, 3642), +(1584666411, '0002', 51.02, 3643), +(1584666414, '0002', 48.59, 3644), +(1584666417, '0002', 59.49, 3645), +(1584666420, '0002', 5.2, 3646), +(1584666423, '0002', 47.27, 3647), +(1584666426, '0002', 69.4, 3648), +(1584666429, '0002', 54.27, 3649), +(1584666432, '0002', 30.12, 3650), +(1584666435, '0002', 20.65, 3651), +(1584666438, '0002', 62.97, 3652), +(1584666441, '0002', 12.96, 3653), +(1584666444, '0002', 70.82, 3654), +(1584666447, '0002', 56.99, 3655), +(1584666450, '0002', 65.66, 3656), +(1584666453, '0002', 47.62, 3657), +(1584666456, '0002', 11.6, 3658), +(1584666459, '0002', 15.12, 3659), +(1584666462, '0002', 74.59, 3660), +(1584666465, '0002', 43.77, 3661), +(1584666468, '0002', 3.21, 3662), +(1584666471, '0002', 4.19, 3663), +(1584666474, '0002', 18.41, 3664), +(1584666477, '0002', 69.01, 3665), +(1584666480, '0002', 65.86, 3666), +(1584666483, '0002', 66.46, 3667), +(1584666486, '0002', 50.82, 3668), +(1584666489, '0002', 18.32, 3669), +(1584666492, '0002', 76.74, 3670), +(1584666495, '0002', 9.23, 3671), +(1584666498, '0002', 54.46, 3672), +(1584666501, '0002', 63.27, 3673), +(1584666504, '0002', 77.44, 3674), +(1584666507, '0002', 22.15, 3675), +(1584666510, '0002', 79.66, 3676), +(1584666513, '0002', 60.02, 3677), +(1584666516, '0002', 58.04, 3678), +(1584666519, '0002', 62.9, 3679), +(1584666522, '0002', 20.15, 3680), +(1584666525, '0002', 60.08, 3681), +(1584666528, '0002', 72.03, 3682), +(1584666531, '0002', 79.99, 3683), +(1584666534, '0002', 19.26, 3684), +(1584666537, '0002', 1.44, 3685), +(1584666540, '0002', 30.18, 3686), +(1584666543, '0002', 67.96, 3687), +(1584666546, '0002', 62.95, 3688), +(1584666549, '0002', 7, 3689), +(1584666552, '0002', 73.21, 3690), +(1584666555, '0002', 40.45, 3691), +(1584666558, '0002', 45.04, 3692), +(1584666561, '0002', 68.65, 3693), +(1584666564, '0002', 36.17, 3694), +(1584666567, '0002', 66.37, 3695), +(1584666570, '0002', 16.52, 3696), +(1584666573, '0002', 65.37, 3697), +(1584666576, '0002', 69.35, 3698), +(1584666579, '0002', 69.6, 3699), +(1584666582, '0002', 58.33, 3700), +(1584666585, '0002', 49.34, 3701), +(1584666588, '0002', 70.74, 3702), +(1584666591, '0002', 21.66, 3703), +(1584666594, '0002', 58.72, 3704), +(1584666597, '0002', 22.98, 3705), +(1584666600, '0002', 68.12, 3706), +(1584666603, '0002', 36.34, 3707), +(1584666606, '0002', 18.36, 3708), +(1584666609, '0002', 9.32, 3709), +(1584666612, '0002', 32.03, 3710), +(1584666615, '0002', 22.28, 3711), +(1584666618, '0002', 62.58, 3712), +(1584666621, '0002', 11.6, 3713), +(1584666624, '0002', 2.48, 3714), +(1584666627, '0002', 23.83, 3715), +(1584666630, '0002', 17.35, 3716), +(1584666633, '0002', 38.56, 3717), +(1584666636, '0002', 52.62, 3718), +(1584666639, '0002', 9.07, 3719), +(1584666642, '0002', 74.96, 3720), +(1584666645, '0002', 13.52, 3721), +(1584666648, '0002', 36.54, 3722), +(1584666651, '0002', 41.53, 3723), +(1584666654, '0002', 78.11, 3724), +(1584666657, '0002', 71.69, 3725), +(1584666660, '0002', 70.98, 3726), +(1584666663, '0002', 25.54, 3727), +(1584666666, '0002', 30.3, 3728), +(1584666669, '0002', 45.47, 3729), +(1584666672, '0002', 61.45, 3730), +(1584666675, '0002', 15.12, 3731), +(1584666678, '0002', 19.57, 3732), +(1584666681, '0002', 14.66, 3733), +(1584666684, '0002', 56.42, 3734), +(1584666687, '0002', 27.48, 3735), +(1584666690, '0002', 21.2, 3736), +(1584666693, '0002', 47.88, 3737), +(1584666696, '0002', 65.29, 3738), +(1584666699, '0002', 59.15, 3739), +(1584666702, '0002', 41.86, 3740), +(1584666705, '0002', 49.6, 3741), +(1584666708, '0002', 74.13, 3742), +(1584666711, '0002', 51.76, 3743), +(1584666714, '0002', 65.4, 3744), +(1584666717, '0002', 72.79, 3745), +(1584666720, '0002', 43.43, 3746), +(1584666723, '0002', 15.7, 3747), +(1584666726, '0002', 47.74, 3748), +(1584666729, '0002', 43.13, 3749), +(1584666732, '0002', 38.43, 3750), +(1584666735, '0002', 69.38, 3751), +(1584666738, '0002', 3.63, 3752), +(1584666741, '0002', 33.59, 3753), +(1584666744, '0002', 43.31, 3754), +(1584666747, '0002', 18.46, 3755), +(1584666750, '0002', 44.93, 3756), +(1584666753, '0002', 29.11, 3757), +(1584666756, '0002', 14.18, 3758), +(1584666759, '0002', 4.35, 3759), +(1584666762, '0002', 30.71, 3760), +(1584666765, '0002', 77.5, 3761), +(1584666768, '0002', 39.95, 3762), +(1584666771, '0002', 56.22, 3763), +(1584666774, '0002', 79.03, 3764), +(1584666777, '0002', 75.51, 3765), +(1584666780, '0002', 18.34, 3766), +(1584666783, '0002', 78.8, 3767), +(1584666786, '0002', 3.54, 3768), +(1584666789, '0002', 79.54, 3769), +(1584666792, '0002', 44.73, 3770), +(1584666795, '0002', 70.18, 3771), +(1584666798, '0002', 53.72, 3772), +(1584666801, '0002', 64.42, 3773), +(1584666804, '0002', 39.52, 3774), +(1584666807, '0002', 4.78, 3775), +(1584666810, '0002', 41.02, 3776), +(1584666813, '0002', 19.35, 3777), +(1584666816, '0002', 42.76, 3778), +(1584666819, '0002', 67.66, 3779), +(1584666822, '0002', 75.29, 3780), +(1584666825, '0002', 70.24, 3781), +(1584666828, '0002', 36.09, 3782), +(1584666831, '0002', 48.39, 3783), +(1584666834, '0002', 0.56, 3784), +(1584666837, '0002', 67.27, 3785), +(1584666840, '0002', 28.69, 3786), +(1584666843, '0002', 61.73, 3787), +(1584666846, '0002', 40.55, 3788), +(1584666849, '0002', 27.74, 3789), +(1584666852, '0002', 21.48, 3790), +(1584666855, '0002', 69.9, 3791), +(1584666858, '0002', 13.4, 3792), +(1584666861, '0002', 69.62, 3793), +(1584666864, '0002', 68.88, 3794), +(1584666867, '0002', 41.8, 3795), +(1584666870, '0002', 9.32, 3796), +(1584666873, '0002', 11.33, 3797), +(1584666876, '0002', 29.88, 3798), +(1584666879, '0002', 29.92, 3799), +(1584666882, '0002', 55.81, 3800), +(1584666885, '0002', 48.82, 3801), +(1584666889, '0002', 14.01, 3802), +(1584666891, '0002', 17.16, 3803), +(1584666894, '0002', 8.17, 3804), +(1584666897, '0002', 38.25, 3805), +(1584666900, '0002', 79.02, 3806), +(1584666903, '0002', 63.19, 3807), +(1584666906, '0002', 65.55, 3808), +(1584666909, '0002', 32.71, 3809), +(1584666912, '0002', 51.33, 3810), +(1584666915, '0002', 31.16, 3811), +(1584666918, '0002', 45.18, 3812), +(1584666921, '0002', 16.77, 3813), +(1584666924, '0002', 26.93, 3814), +(1584666927, '0002', 55.02, 3815), +(1584666930, '0002', 23.21, 3816), +(1584666933, '0002', 35.42, 3817), +(1584666936, '0002', 21.67, 3818), +(1584666939, '0002', 4.1, 3819), +(1584666942, '0002', 23.31, 3820), +(1584666945, '0002', 49.46, 3821), +(1584666948, '0002', 35.04, 3822), +(1584666951, '0002', 28.65, 3823), +(1584666954, '0002', 72.79, 3824), +(1584666957, '0002', 7.01, 3825), +(1584666960, '0002', 35.27, 3826), +(1584666963, '0002', 15.57, 3827), +(1584666966, '0002', 9.66, 3828), +(1584666969, '0002', 78.62, 3829), +(1584666972, '0002', 19.36, 3830), +(1584666975, '0002', 12.01, 3831), +(1584666978, '0002', 48.81, 3832), +(1584666981, '0002', 51.08, 3833), +(1584666984, '0002', 67.4, 3834), +(1584666987, '0002', 58.71, 3835), +(1584666990, '0002', 55.07, 3836), +(1584666993, '0002', 33.28, 3837), +(1584666996, '0002', 77.35, 3838), +(1584666999, '0002', 34.37, 3839), +(1584667002, '0002', 11.64, 3840), +(1584667005, '0002', 23.34, 3841), +(1584667008, '0002', 0.95, 3842), +(1584667011, '0002', 77.7, 3843), +(1584667014, '0002', 29.88, 3844), +(1584667017, '0002', 72.46, 3845), +(1584667020, '0002', 18.98, 3846), +(1584667023, '0002', 7.8, 3847), +(1584667026, '0002', 11.55, 3848), +(1584667029, '0002', 55.01, 3849), +(1584667032, '0002', 38.48, 3850), +(1584667035, '0002', 27.35, 3851), +(1584667038, '0002', 7.19, 3852), +(1584667041, '0002', 79.47, 3853), +(1584667044, '0002', 15.15, 3854), +(1584667047, '0002', 26.76, 3855), +(1584667050, '0002', 58.32, 3856), +(1584667053, '0002', 59.72, 3857), +(1584667056, '0002', 74.29, 3858), +(1584667059, '0002', 47.68, 3859), +(1584667062, '0002', 6.57, 3860), +(1584667065, '0002', 43.67, 3861), +(1584667068, '0002', 12.04, 3862), +(1584667071, '0002', 70.88, 3863), +(1584667074, '0002', 31.43, 3864), +(1584667077, '0002', 50.8, 3865), +(1584667080, '0002', 17.48, 3866), +(1584667083, '0002', 29.6, 3867), +(1584667086, '0002', 0.44, 3868), +(1584667089, '0002', 40.15, 3869), +(1584667092, '0002', 62.87, 3870), +(1584667095, '0002', 65.63, 3871), +(1584667098, '0002', 9.14, 3872), +(1584667101, '0002', 4.66, 3873), +(1584667104, '0002', 24.54, 3874), +(1584667107, '0002', 78.82, 3875), +(1584667110, '0002', 57.79, 3876), +(1584667113, '0002', 52.52, 3877), +(1584667116, '0002', 3.02, 3878), +(1584667119, '0002', 36.91, 3879), +(1584667122, '0002', 36.6, 3880), +(1584667125, '0002', 25.07, 3881), +(1584667128, '0002', 28.63, 3882), +(1584667131, '0002', 44.88, 3883), +(1584667134, '0002', 7.5, 3884), +(1584667137, '0002', 56, 3885), +(1584667140, '0002', 79.14, 3886), +(1584667143, '0002', 54.59, 3887), +(1584667146, '0002', 69.14, 3888), +(1584667149, '0002', 68.19, 3889), +(1584667152, '0002', 69.18, 3890), +(1584667155, '0002', 34.79, 3891), +(1584667158, '0002', 33.46, 3892), +(1584667161, '0002', 46.52, 3893), +(1584667164, '0002', 25.39, 3894), +(1584667167, '0002', 52.94, 3895), +(1584667170, '0002', 43.33, 3896), +(1584667173, '0002', 33.35, 3897), +(1584667176, '0002', 66.5, 3898), +(1584667179, '0002', 16.27, 3899), +(1584667182, '0002', 70.41, 3900), +(1584667185, '0002', 46.73, 3901), +(1584667188, '0002', 71.81, 3902), +(1584667191, '0002', 52.77, 3903), +(1584667194, '0002', 12.16, 3904), +(1584667197, '0002', 29.91, 3905), +(1584667200, '0002', 7.04, 3906), +(1584667203, '0002', 72.23, 3907), +(1584667206, '0002', 50.71, 3908), +(1584667209, '0002', 28.15, 3909), +(1584667212, '0002', 15.52, 3910), +(1584667215, '0002', 60.79, 3911), +(1584667218, '0002', 51.74, 3912), +(1584667221, '0002', 30.14, 3913), +(1584667224, '0002', 77.2, 3914), +(1584667227, '0002', 48.29, 3915), +(1584667230, '0002', 17.79, 3916), +(1584667233, '0002', 51.12, 3917), +(1584667236, '0002', 12.51, 3918), +(1584667239, '0002', 67.66, 3919), +(1584667242, '0002', 65.24, 3920), +(1584667245, '0002', 66.26, 3921), +(1584667248, '0002', 34.54, 3922), +(1584667251, '0002', 54.25, 3923), +(1584667254, '0002', 65.16, 3924), +(1584667257, '0002', 17.49, 3925), +(1584667260, '0002', 33.04, 3926), +(1584667263, '0002', 62.07, 3927), +(1584667266, '0002', 31.15, 3928), +(1584667269, '0002', 12.93, 3929), +(1584667272, '0002', 33.69, 3930), +(1584667275, '0002', 48.33, 3931), +(1584667278, '0002', 29.71, 3932), +(1584667281, '0002', 13.66, 3933), +(1584667284, '0002', 5.34, 3934), +(1584667287, '0002', 50.73, 3935), +(1584667290, '0002', 26.36, 3936), +(1584667293, '0002', 74.37, 3937), +(1584667296, '0002', 2.67, 3938), +(1584667299, '0002', 61.59, 3939), +(1584667302, '0002', 11.87, 3940), +(1584667305, '0002', 3.46, 3941), +(1584667308, '0002', 33.05, 3942), +(1584667311, '0002', 74.52, 3943), +(1584667314, '0002', 55.27, 3944), +(1584667317, '0002', 2.44, 3945), +(1584667320, '0002', 5.5, 3946), +(1584667323, '0002', 56.51, 3947), +(1584667326, '0002', 51.06, 3948), +(1584667329, '0002', 67.26, 3949), +(1584667332, '0002', 53.65, 3950), +(1584667335, '0002', 13.66, 3951), +(1584667338, '0002', 43.47, 3952), +(1584667341, '0002', 22.94, 3953), +(1584667344, '0002', 69.9, 3954), +(1584667347, '0002', 56.75, 3955), +(1584667350, '0002', 42.69, 3956), +(1584667353, '0002', 6.73, 3957), +(1584667356, '0002', 19.3, 3958), +(1584667359, '0002', 76.33, 3959), +(1584667362, '0002', 64.06, 3960), +(1584667365, '0002', 51.25, 3961), +(1584667368, '0002', 37.53, 3962), +(1584667371, '0002', 76.68, 3963), +(1584667374, '0002', 48.29, 3964), +(1584667377, '0002', 69.44, 3965), +(1584667380, '0002', 24.61, 3966), +(1584667383, '0002', 33.07, 3967), +(1584667386, '0002', 62.74, 3968), +(1584667389, '0002', 30.79, 3969), +(1584667392, '0002', 11.07, 3970), +(1584667395, '0002', 54.24, 3971), +(1584667398, '0002', 55.67, 3972), +(1584667401, '0002', 49.89, 3973), +(1584667404, '0002', 59.42, 3974), +(1584667407, '0002', 60.85, 3975), +(1584667410, '0002', 10.34, 3976), +(1584667413, '0002', 70.36, 3977), +(1584667416, '0002', 1.97, 3978), +(1584667419, '0002', 22.53, 3979), +(1584667422, '0002', 32.88, 3980), +(1584667425, '0002', 8.09, 3981), +(1584667428, '0002', 51.45, 3982), +(1584667431, '0002', 6.58, 3983), +(1584667434, '0002', 66.38, 3984), +(1584667437, '0002', 54.39, 3985), +(1584667440, '0002', 28.91, 3986), +(1584667443, '0002', 29.7, 3987), +(1584667446, '0002', 4.61, 3988), +(1584667449, '0002', 78.69, 3989), +(1584667452, '0002', 75.94, 3990), +(1584667455, '0002', 5.68, 3991), +(1584667458, '0002', 36.16, 3992), +(1584667461, '0002', 77.45, 3993), +(1584667464, '0002', 6.39, 3994), +(1584667467, '0002', 16.19, 3995), +(1584667470, '0002', 15.71, 3996), +(1584667473, '0002', 41.78, 3997), +(1584667476, '0002', 52.44, 3998), +(1584667479, '0002', 24.76, 3999), +(1584667482, '0002', 3.97, 4000), +(1584667485, '0002', 46.19, 4001), +(1584667488, '0002', 35.13, 4002), +(1584667491, '0002', 24.39, 4003), +(1584667494, '0002', 2.6, 4004), +(1584667497, '0002', 2.35, 4005), +(1584667500, '0002', 12.34, 4006), +(1584667503, '0002', 77.99, 4007), +(1584667506, '0002', 1.74, 4008), +(1584667509, '0002', 34.61, 4009), +(1584667512, '0002', 2.54, 4010), +(1584667515, '0002', 7.72, 4011), +(1584667518, '0002', 67.12, 4012), +(1584667521, '0002', 24.55, 4013), +(1584667524, '0002', 62.66, 4014), +(1584667527, '0002', 58.32, 4015), +(1584667530, '0002', 13.53, 4016), +(1584667533, '0002', 27.74, 4017), +(1584667536, '0002', 20.21, 4018), +(1584667539, '0002', 62.56, 4019), +(1584667542, '0002', 22.6, 4020), +(1584667545, '0002', 38.42, 4021), +(1584667548, '0002', 51.84, 4022), +(1584667551, '0002', 61.39, 4023), +(1584667554, '0002', 49.34, 4024), +(1584667557, '0002', 24.95, 4025), +(1584667560, '0002', 45.06, 4026), +(1584667563, '0002', 38.85, 4027), +(1584667566, '0002', 37.12, 4028), +(1584667569, '0002', 58.13, 4029), +(1584667572, '0002', 6.88, 4030), +(1584667575, '0002', 31.98, 4031), +(1584667578, '0002', 42.9, 4032), +(1584667581, '0002', 36.08, 4033), +(1584667584, '0002', 30.8, 4034), +(1584667587, '0002', 19.36, 4035), +(1584667590, '0002', 52.48, 4036), +(1584667593, '0002', 35.35, 4037), +(1584667596, '0002', 51.55, 4038), +(1584667599, '0002', 25.88, 4039), +(1584667602, '0002', 19.94, 4040), +(1584667605, '0002', 7.48, 4041), +(1584667608, '0002', 60.49, 4042), +(1584667611, '0002', 42.7, 4043), +(1584667614, '0002', 29.55, 4044), +(1584667617, '0002', 12.76, 4045), +(1584667620, '0002', 70.03, 4046), +(1584667623, '0002', 28.34, 4047), +(1584667626, '0002', 8.16, 4048), +(1584667629, '0002', 7.2, 4049), +(1584667632, '0002', 6.79, 4050), +(1584667635, '0002', 22.23, 4051), +(1584667638, '0002', 56.99, 4052), +(1584667641, '0002', 39.79, 4053), +(1584667644, '0002', 40.87, 4054), +(1584667647, '0002', 43.83, 4055), +(1584667650, '0002', 51.86, 4056), +(1584667653, '0002', 38.63, 4057), +(1584667656, '0002', 55.73, 4058), +(1584667659, '0002', 27.25, 4059), +(1584667662, '0002', 20.29, 4060), +(1584667665, '0002', 39.58, 4061), +(1584667668, '0002', 44.28, 4062), +(1584667671, '0002', 50.09, 4063), +(1584667674, '0002', 64.44, 4064), +(1584667677, '0002', 51.37, 4065), +(1584667680, '0002', 29.41, 4066), +(1584667683, '0002', 39.49, 4067), +(1584667686, '0002', 20.51, 4068), +(1584667689, '0002', 60.5, 4069), +(1584667692, '0002', 31.04, 4070), +(1584667695, '0002', 36.44, 4071), +(1584667698, '0002', 54.59, 4072), +(1584667701, '0002', 72.57, 4073), +(1584667704, '0002', 34.6, 4074), +(1584667707, '0002', 67.96, 4075), +(1584667710, '0002', 33.86, 4076), +(1584667713, '0002', 61.39, 4077), +(1584667716, '0002', 76.31, 4078), +(1584667719, '0002', 74.82, 4079), +(1584667722, '0002', 11.18, 4080), +(1584667725, '0002', 0.88, 4081), +(1584667728, '0002', 12.63, 4082), +(1584667731, '0002', 50.62, 4083), +(1584667734, '0002', 25.15, 4084), +(1584667737, '0002', 29.56, 4085), +(1584667740, '0002', 13.95, 4086), +(1584667743, '0002', 47.37, 4087), +(1584667746, '0002', 37.52, 4088), +(1584667749, '0002', 33.62, 4089), +(1584667752, '0002', 56.9, 4090), +(1584667755, '0002', 52.34, 4091), +(1584667758, '0002', 60.74, 4092), +(1584667761, '0002', 39.85, 4093), +(1584667764, '0002', 59.07, 4094), +(1584667767, '0002', 66.69, 4095), +(1584667770, '0002', 35.3, 4096), +(1584667773, '0002', 2.55, 4097), +(1584667776, '0002', 27.5, 4098), +(1584667779, '0002', 0.79, 4099), +(1584667782, '0002', 19.75, 4100), +(1584667785, '0002', 79.86, 4101), +(1584667788, '0002', 10.15, 4102), +(1584667791, '0002', 64.85, 4103), +(1584667794, '0002', 12.84, 4104), +(1584667797, '0002', 77.85, 4105), +(1584667800, '0002', 9.49, 4106), +(1584667803, '0002', 77.27, 4107), +(1584667806, '0002', 41.45, 4108), +(1584667809, '0002', 26.45, 4109), +(1584667812, '0002', 14.38, 4110), +(1584667815, '0002', 16.64, 4111), +(1584667818, '0002', 35.49, 4112), +(1584667821, '0002', 33.45, 4113), +(1584667824, '0002', 43.42, 4114), +(1584667827, '0002', 64.76, 4115), +(1584667830, '0002', 20.54, 4116), +(1584667833, '0002', 30.11, 4117), +(1584667836, '0002', 11.36, 4118), +(1584667839, '0002', 7.21, 4119), +(1584667842, '0002', 37.49, 4120), +(1584667845, '0002', 16.36, 4121), +(1584667848, '0002', 77.45, 4122), +(1584667851, '0002', 70.87, 4123), +(1584667854, '0002', 26.88, 4124), +(1584667857, '0002', 13.22, 4125), +(1584667860, '0002', 69.64, 4126), +(1584667863, '0002', 72.78, 4127), +(1584667866, '0002', 72.24, 4128), +(1584667869, '0002', 73.27, 4129), +(1584667872, '0002', 24.85, 4130), +(1584667875, '0002', 45.34, 4131), +(1584667878, '0002', 6.82, 4132), +(1584667881, '0002', 25.18, 4133), +(1584667884, '0002', 33.71, 4134), +(1584667887, '0002', 46.87, 4135), +(1584667890, '0002', 21.41, 4136), +(1584667893, '0002', 2.08, 4137), +(1584667896, '0002', 1.25, 4138), +(1584667899, '0002', 57.23, 4139), +(1584667902, '0002', 16.26, 4140), +(1584667905, '0002', 64.95, 4141), +(1584667908, '0002', 57.02, 4142), +(1584667911, '0002', 40.09, 4143), +(1584667914, '0002', 74.37, 4144), +(1584667917, '0002', 15.88, 4145), +(1584667920, '0002', 6.42, 4146), +(1584667923, '0002', 54.1, 4147), +(1584667926, '0002', 77.09, 4148), +(1584667929, '0002', 58.79, 4149), +(1584667932, '0002', 64.86, 4150), +(1584667935, '0002', 72.73, 4151), +(1584667938, '0002', 35.08, 4152), +(1584667941, '0002', 43.18, 4153), +(1584667944, '0002', 65.63, 4154), +(1584667947, '0002', 28.61, 4155), +(1584667950, '0002', 72.7, 4156), +(1584667953, '0002', 67.26, 4157), +(1584667956, '0002', 69.13, 4158), +(1584667959, '0002', 20.16, 4159), +(1584667962, '0002', 32.35, 4160), +(1584667965, '0002', 59.48, 4161), +(1584667968, '0002', 56.99, 4162), +(1584667971, '0002', 61.52, 4163), +(1584667974, '0002', 41.24, 4164), +(1584667977, '0002', 40.4, 4165), +(1584667980, '0002', 21.51, 4166), +(1584667983, '0002', 1.73, 4167), +(1584667986, '0002', 46.26, 4168), +(1584667989, '0002', 31.59, 4169), +(1584667992, '0002', 54.1, 4170), +(1584667995, '0002', 50.5, 4171), +(1584667998, '0002', 28.36, 4172), +(1584668001, '0002', 8.62, 4173), +(1584668004, '0002', 47.23, 4174), +(1584668007, '0002', 44.78, 4175), +(1584668010, '0002', 68.26, 4176), +(1584668013, '0002', 31.63, 4177), +(1584668016, '0002', 52.57, 4178), +(1584668019, '0002', 66.16, 4179), +(1584668022, '0002', 73.19, 4180), +(1584668025, '0002', 49.4, 4181), +(1584668028, '0002', 36.58, 4182), +(1584668031, '0002', 44.46, 4183), +(1584668034, '0002', 19.22, 4184), +(1584668037, '0002', 57.84, 4185), +(1584668040, '0002', 74.11, 4186), +(1584668043, '0002', 30.85, 4187), +(1584668046, '0002', 29.25, 4188), +(1584668049, '0002', 11.76, 4189), +(1584668052, '0002', 9.2, 4190), +(1584668055, '0002', 60.44, 4191), +(1584668058, '0002', 45.2, 4192), +(1584668061, '0002', 14.86, 4193), +(1584668064, '0002', 18.06, 4194), +(1584668067, '0002', 1.9, 4195), +(1584668070, '0002', 39.39, 4196), +(1584668073, '0002', 51.23, 4197), +(1584668076, '0002', 41.68, 4198), +(1584668079, '0002', 73.33, 4199), +(1584668082, '0002', 25.36, 4200), +(1584668085, '0002', 61.2, 4201), +(1584668088, '0002', 68.8, 4202), +(1584668091, '0002', 14.02, 4203), +(1584668094, '0002', 66.16, 4204), +(1584668097, '0002', 21.56, 4205), +(1584668100, '0002', 61.6, 4206), +(1584668103, '0002', 51.05, 4207), +(1584668106, '0002', 74.04, 4208), +(1584668109, '0002', 9.99, 4209), +(1584668112, '0002', 26.11, 4210), +(1584668115, '0002', 35.16, 4211), +(1584668118, '0002', 13.1, 4212), +(1584668120, '0002', 7.41, 4213), +(1584668123, '0002', 5.74, 4214), +(1584668126, '0002', 38.81, 4215), +(1584668129, '0002', 52.62, 4216), +(1584668132, '0002', 59.92, 4217), +(1584668135, '0002', 74.39, 4218), +(1584668138, '0002', 62.32, 4219), +(1584669302, '0001', 77.25, 4220), +(1584669304, '0001', 61.04, 4221), +(1584669308, '0001', 60.84, 4222), +(1584669311, '0001', 64.67, 4223), +(1584670626, '0001', 63.47, 4224), +(1584670629, '0001', 30.14, 4225); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_infrared_sensor` +-- + +CREATE TABLE `bs_device_infrared_sensor` ( + `uid` varchar(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT '1', + `create_time` datetime NOT NULL, + `area` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_light_sensor` +-- + +CREATE TABLE `bs_device_light_sensor` ( + `uid` varchar(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT '1', + `create_time` datetime NOT NULL, + `area` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_map` +-- + +CREATE TABLE `bs_device_map` ( + `id` int(11) NOT NULL, + `chinese` varchar(255) NOT NULL, + `function` varchar(255) NOT NULL, + `icon` varchar(255) NOT NULL, + `unit` varchar(255) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='设备信息映射表'; + +-- +-- 转存表中的数据 `bs_device_map` +-- + +INSERT INTO `bs_device_map` (`id`, `chinese`, `function`, `icon`, `unit`) VALUES +(1, '交通灯', 'traffic_light', '', NULL), +(2, '温度传感器', 'temperature_sensor', '', '°'), +(3, '红外传感器', 'infrared_sensor', '', NULL), +(4, '光线传感器', 'light_sensor', '', NULL), +(5, '蜂鸣器', 'buzzer', '', NULL); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_serial` +-- + +CREATE TABLE `bs_device_serial` ( + `id` int(11) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `type` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_device_serial` +-- + +INSERT INTO `bs_device_serial` (`id`, `serial_num`, `type`) VALUES +(1, '0001', 'traffic_light'), +(2, '0002', 'traffic_light'), +(3, '0004', 'traffic_light'), +(4, '0005', 'temperature_sensor'), +(5, '0006', 'infrared_sensor'), +(6, '0003', 'light_sensor'), +(7, '0007', 'buzzer'), +(8, '0008', 'buzzer'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_temperature_sensor` +-- + +CREATE TABLE `bs_device_temperature_sensor` ( + `uid` varchar(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT '1', + `create_time` datetime NOT NULL, + `area` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_device_temperature_sensor` +-- + +INSERT INTO `bs_device_temperature_sensor` (`uid`, `serial_num`, `status`, `create_time`, `area`) VALUES +('zjquser', '0005', 1, '2020-03-13 17:25:44', '天津市-天津市-西青区'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_device_traffic_light` +-- + +CREATE TABLE `bs_device_traffic_light` ( + `uid` varchar(255) NOT NULL, + `serial_num` varchar(255) NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT '1', + `create_time` datetime NOT NULL, + `area` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_device_traffic_light` +-- + +INSERT INTO `bs_device_traffic_light` (`uid`, `serial_num`, `status`, `create_time`, `area`) VALUES +('曾健起小号', '0001', 1, '2020-03-20 10:27:30', '河北省-石家庄市-桥西区'), +('zjquser', '0002', 0, '2020-03-20 10:27:58', '河北省-石家庄市-桥西区'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_icon_data` +-- + +CREATE TABLE `bs_icon_data` ( + `id` int(11) NOT NULL, + `icon_name` varchar(255) NOT NULL, + `icon_string` varchar(255) NOT NULL, + `icon_sym` varchar(255) NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='图标名称'; + +-- +-- 转存表中的数据 `bs_icon_data` +-- + +INSERT INTO `bs_icon_data` (`id`, `icon_name`, `icon_string`, `icon_sym`) VALUES +(1, '实心', '', 'layui-icon-heart-fill'), +(2, '空心', '', 'layui-icon-heart'), +(3, '亮度/晴', '', 'layui-icon-light'), +(4, '时间/历史', '', 'layui-icon-time'), +(5, '蓝牙', '', 'layui-icon-bluetooth'), +(6, '@艾特', '', 'layui-icon-at'), +(7, '静音', '', 'layui-icon-mute'), +(8, '录音/麦克风', '', 'layui-icon-mike'), +(9, '密钥/钥匙', '', 'layui-icon-key'), +(10, '礼物/活动', '', 'layui-icon-gift'), +(11, '邮箱', '', 'layui-icon-email'), +(12, 'RSS', '', 'layui-icon-rss'), +(13, 'WiFi', '', 'layui-icon-wifi'), +(14, '退出/注销', '', 'layui-icon-logout'), +(15, 'Android 安卓', '', 'layui-icon-android'), +(16, 'Apple IOS 苹果', '', 'layui-icon-ios'), +(17, 'Windows', '', 'layui-icon-windows'), +(18, '穿梭框', '', 'layui-icon-transfer'), +(19, '客服', '', 'layui-icon-service'), +(20, '减', '', 'layui-icon-subtraction'), +(21, '加', '', 'layui-icon-addition'), +(22, '滑块', '', 'layui-icon-slider'), +(23, '打印', '', 'layui-icon-print'), +(24, '导出', '', 'layui-icon-export'), +(25, '列', '', 'layui-icon-cols'), +(26, '退出全屏', '', 'layui-icon-screen-restore'), +(27, '全屏', '', 'layui-icon-screen-full'), +(28, '半星', '', 'layui-icon-rate-half'), +(29, '星星-空心', '', 'layui-icon-rate'), +(30, '星星-实心', '', 'layui-icon-rate-solid'), +(31, '手机', '', 'layui-icon-cellphone'), +(32, '验证码', '', 'layui-icon-vercode'), +(33, '微信', '', 'layui-icon-login-wechat'), +(34, 'QQ', '', 'layui-icon-login-qq'), +(35, '微博', '', 'layui-icon-login-weibo'), +(36, '密码', '', 'layui-icon-password'), +(37, '用户名', '', 'layui-icon-username'), +(38, '刷新-粗', '', 'layui-icon-refresh-3'), +(39, '授权', '', 'layui-icon-auz'), +(40, '左向右伸缩菜单', '', 'layui-icon-spread-left'), +(41, '右向左伸缩菜单', '', 'layui-icon-shrink-right'), +(42, '雪花', '', 'layui-icon-snowflake'), +(43, '提示说明', '', 'layui-icon-tips'), +(44, '便签', '', 'layui-icon-note'), +(45, '主页', '', 'layui-icon-home'), +(46, '高级', '', 'layui-icon-senior'), +(47, '刷新', '', 'layui-icon-refresh'), +(48, '刷新', '', 'layui-icon-refresh-1'), +(49, '旗帜', '', 'layui-icon-flag'), +(50, '主题', '', 'layui-icon-theme'), +(51, '消息-通知', '', 'layui-icon-notice'), +(52, '网站', '', 'layui-icon-website'), +(53, '控制台', '', 'layui-icon-console'), +(54, '表情-惊讶', '', 'layui-icon-face-surprised'), +(55, '设置-空心', '', 'layui-icon-set'), +(56, '模板', '', 'layui-icon-template-1'), +(57, '应用', '', 'layui-icon-app'), +(58, '模板', '', 'layui-icon-template'), +(59, '赞', '', 'layui-icon-praise'), +(60, '踩', '', 'layui-icon-tread'), +(61, '男', '', 'layui-icon-male'), +(62, '女', '', 'layui-icon-female'), +(63, '相机-空心', '', 'layui-icon-camera'), +(64, '相机-实心', '', 'layui-icon-camera-fill'), +(65, '菜单-水平', '', 'layui-icon-more'), +(66, '菜单-垂直', '', 'layui-icon-more-vertical'), +(67, '金额-人民币', '', 'layui-icon-rmb'), +(68, '金额-美元', '', 'layui-icon-dollar'), +(69, '钻石-等级', '', 'layui-icon-diamond'), +(70, '火', '', 'layui-icon-fire'), +(71, '返回', '', 'layui-icon-return'), +(72, '位置-地图', '', 'layui-icon-location'), +(73, '办公-阅读', '', 'layui-icon-read'), +(74, '调查', '', 'layui-icon-survey'), +(75, '表情-微笑', '', 'layui-icon-face-smile'), +(76, '表情-哭泣', '', 'layui-icon-face-cry'), +(77, '购物车', '', 'layui-icon-cart-simple'), +(78, '购物车', '', 'layui-icon-cart'), +(79, '下一页', '', 'layui-icon-next'), +(80, '上一页', '', 'layui-icon-prev'), +(81, '上传-空心-拖拽', '', 'layui-icon-upload-drag'), +(82, '上传-实心', '', 'layui-icon-upload'), +(83, '下载-圆圈', '', 'layui-icon-download-circle'), +(84, '组件', '', 'layui-icon-component'), +(85, '文件-粗', '', 'layui-icon-file-b'), +(86, '用户', '', 'layui-icon-user'), +(87, '发现-实心', '', 'layui-icon-find-fill'), +(88, 'loading', '', 'layui-icon-loading'), +(89, 'loading', '', 'layui-icon-loading-1'), +(90, '添加', '', 'layui-icon-add-1'), +(91, '播放', '', 'layui-icon-play'), +(92, '暂停', '', 'layui-icon-pause'), +(93, '音频-耳机', '', 'layui-icon-headset'), +(94, '视频', '', 'layui-icon-video'), +(95, '语音-声音', '', 'layui-icon-voice'), +(96, '消息-通知-喇叭', '', 'layui-icon-speaker'), +(97, '删除线', '', 'layui-icon-fonts-del'), +(98, '代码', '', 'layui-icon-fonts-code'), +(99, 'HTML', '', 'layui-icon-fonts-html'), +(100, '字体加粗', '', 'layui-icon-fonts-strong'), +(101, '删除链接', '', 'layui-icon-unlink'), +(102, '图片', '', 'layui-icon-picture'), +(103, '链接', '', 'layui-icon-link'), +(104, '表情-笑-粗', '', 'layui-icon-face-smile-b'), +(105, '左对齐', '', 'layui-icon-align-left'), +(106, '右对齐', '', 'layui-icon-align-right'), +(107, '居中对齐', '', 'layui-icon-align-center'), +(108, '字体-下划线', '', 'layui-icon-fonts-u'), +(109, '字体-斜体', '', 'layui-icon-fonts-i'), +(110, 'Tabs 选项卡', '', 'layui-icon-tabs'), +(111, '单选框-选中', '', 'layui-icon-radio'), +(112, '单选框-候选', '', 'layui-icon-circle'), +(113, '编辑', '', 'layui-icon-edit'), +(114, '分享', '', 'layui-icon-share'), +(115, '删除', '', 'layui-icon-delete'), +(116, '表单', '', 'layui-icon-form'), +(117, '手机-细体', '', 'layui-icon-cellphone-fine'), +(118, '聊天 对话 沟通', '', 'layui-icon-dialogue'), +(119, '文字格式化', '', 'layui-icon-fonts-clear'), +(120, '窗口', '', 'layui-icon-layer'), +(121, '日期', '', 'layui-icon-date'), +(122, '水 下雨', '', 'layui-icon-water'), +(123, '代码-圆圈', '', 'layui-icon-code-circle'), +(124, '轮播组图', '', 'layui-icon-carousel'), +(125, '翻页', '', 'layui-icon-prev-circle'), +(126, '布局', '', 'layui-icon-layouts'), +(127, '工具', '', 'layui-icon-util'), +(128, '选择模板', '', 'layui-icon-templeate-1'), +(129, '上传-圆圈', '', 'layui-icon-upload-circle'), +(130, '树', '', 'layui-icon-tree'), +(131, '表格', '', 'layui-icon-table'), +(132, '图表', '', 'layui-icon-chart'), +(133, '图标 报表 屏幕', '', 'layui-icon-chart-screen'), +(134, '引擎', '', 'layui-icon-engine'), +(135, '下三角', '', 'layui-icon-triangle-d'), +(136, '右三角', '', 'layui-icon-triangle-r'), +(137, '文件', '', 'layui-icon-file'), +(138, '设置-小型', '', 'layui-icon-set-sm'), +(139, '减少-圆圈', '', 'layui-icon-reduce-circle'), +(140, '添加-圆圈', '', 'layui-icon-add-circle'), +(141, '404', '', 'layui-icon-404'), +(142, '关于', '', 'layui-icon-about'), +(143, '箭头 向上', '', 'layui-icon-up'), +(144, '箭头 向下', '', 'layui-icon-down'), +(145, '箭头 向左', '', 'layui-icon-left'), +(146, '箭头 向右', '', 'layui-icon-right'), +(147, '圆点', '', 'layui-icon-circle-dot'), +(148, '搜索', '', 'layui-icon-search'), +(149, '设置-实心', '', 'layui-icon-set-fill'), +(150, '群组', '', 'layui-icon-group'), +(151, '好友', '', 'layui-icon-friends'), +(152, '回复 评论 实心', '', 'layui-icon-reply-fill'), +(153, '菜单 隐身 实心', '', 'layui-icon-menu-fill'), +(154, '记录', '', 'layui-icon-log'), +(155, '图片-细体', '', 'layui-icon-picture-fine'), +(156, '表情-笑-细体', '', 'layui-icon-face-smile-fine'), +(157, '列表', '', 'layui-icon-list'), +(158, '发布 纸飞机', '', 'layui-icon-release'), +(159, '对 OK', '', 'layui-icon-ok'), +(160, '帮助', '', 'layui-icon-help'), +(161, '客服', '', 'layui-icon-chat'), +(162, 'top 置顶', '', 'layui-icon-top'), +(163, '收藏-空心', '', 'layui-icon-star'), +(164, '收藏-实心', '', 'layui-icon-star-fill'), +(165, '关闭-实心', 'ဇ', 'layui-icon-close-fill'), +(166, '关闭-空心', 'ဆ', 'layui-icon-close'), +(167, '正确', 'စ', 'layui-icon-ok-circle'), +(168, '添加-圆圈-细体', '', 'layui-icon-add-circle-fine'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_log` +-- + +CREATE TABLE `bs_log` ( + `id` int(11) NOT NULL, + `time` datetime NOT NULL, + `operation` text NOT NULL, + `who` varchar(255) NOT NULL DEFAULT '制作人:曾健起' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_log` +-- + +INSERT INTO `bs_log` (`id`, `time`, `operation`, `who`) VALUES +(1, '2020-02-20 19:00:00', '完成物联网云平台系统主体框架的设计。', '制作人:曾健起'), +(2, '2020-02-21 19:00:00', '完成物联网云平台系统的数据库设计。', '制作人:曾健起'), +(3, '2020-02-22 19:00:00', '完成登录页面的设计和功能实现。', '制作人:曾健起'), +(4, '2020-02-23 19:00:00', '管理员管理界面设计完成。', '制作人:曾健起'), +(5, '2020-02-25 19:00:00', '完成管理员管理界面的所有功能。', '制作人:曾健起'), +(6, '2020-02-27 19:00:00', '用户管理界面设计完成。', '制作人:曾健起'), +(7, '2020-02-29 19:00:00', '完成用户管理界面的所有功能。', '制作人:曾健起'), +(8, '2020-03-03 19:00:00', '完成了增删设备界面的设计,并支持选择要添加设备的图标。', '制作人:曾健起'), +(9, '2020-03-04 19:00:00', '完成了增删设备界面的所有功能以及前后端数据的传输。', '制作人:曾健起'), +(10, '2020-03-05 19:00:00', '实现了设备列表的全动态加载,不同的设备共用一个模板显示不同的内容。彻底摆脱了静态网站的束缚。', '制作人:曾健起'), +(11, '2020-03-07 19:00:00', '完成每个设备ID动态化展示界面的设计,由于目前没有数据,所以在后台写了一个算法实时模拟数据生成用以提供给前台显示。', '制作人:曾健起'), +(12, '2020-03-08 19:00:00', '完成每个设备ID的可视化显示功能,并可实时更新数据,还可以控制更新频率。完成了每个设备的历史数据列表展示功能。', '制作人:曾健起'), +(13, '2020-03-09 19:00:00', '完成了设备详细信息中的根据日期搜索功能,重做了用户列表界面的部分模块。', '制作人:曾健起'), +(14, '2020-03-10 19:00:00', '重做了管理员列表界面的部分模块,优化了之前写的代码,增强了数据库各表之间的严谨性。', '制作人:曾健起'), +(15, '2020-03-11 19:00:00', '完成点击日志记录按钮的二级密码验证弹窗的设计,同时完成设计后台二级验证算法,实现了二级密码的动态化变更。', '制作人:曾健起'), +(16, '2020-03-12 10:38:00', '日志记录界面及功能设计完成。优化了数据表格的显示功能,增加了视觉效果,提升使用体验。', '制作人:曾健起'), +(17, '2020-03-13 19:25:00', '优化了每个设备ID的可视化显示界面,完成了所有设备ID的显示界面及其所有功能。完善了每个用户的设备统计功能。', '制作人:曾健起'), +(18, '2020-03-14 19:30:00', '完成统计界面中可视化数据图标的设计以及全部相关功能。', '制作人:曾健起'), +(19, '2020-03-15 19:30:00', '完成了我的桌面中全部功能及可视化设计。数字滚动显示、时间弹出式实时更新、图表单击切换更新、跨界面AJAX同步。', '制作人:曾健起'), +(20, '2020-03-16 19:30:00', '重新优化了登录界面的全分辨率适应性。将管理员与用户独立,可分别跳转至对应的操作界面。修复我的桌面中可视化图表的数值更新问题。', '制作人:曾健起'), +(21, '2020-03-17 23:30:00', '系统UI深度美化,为所有模块加入了显示动画,在切换标签时也加入了动画过渡。', '制作人:曾健起'), +(22, '2020-03-18 23:30:00', '实现了自定义提示通知,并为整套系统中的所有操作反馈、处理结果添加气泡通知提示103余个。优化了所有页面中的卡片样式风格。优化了右侧滚动条的样式。', '制作人:曾健起'), +(23, '2020-03-19 22:30:00', '增强了云平台系统的安全性,并为每个系统级别操作添加日志记录,可通过访问日志记录查看关键操作。', '制作人:曾健起'), +(24, '2020-03-20 19:30:00', '完善日志记录功能,合计18余项。', '制作人:曾健起'); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_people` +-- + +CREATE TABLE `bs_people` ( + `id` int(11) NOT NULL, + `name` varchar(255) NOT NULL, + `root` tinyint(1) NOT NULL DEFAULT '0' +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_people` +-- + +INSERT INTO `bs_people` (`id`, `name`, `root`) VALUES +(1, 'zjq', 1), +(2, '曾健起小号', 0), +(3, 'zjquser', 0), +(4, '叶文杰', 0), +(5, '罗辑', 0), +(6, '程心', 0), +(7, '云天明', 0), +(8, '史强', 0), +(9, '章北海', 0), +(10, '自然选择号', 0), +(11, '蓝色空间号', 0), +(12, '东方延绪', 0), +(13, '万有引力号', 0), +(14, '庄颜', 0), +(15, '泰勒', 0), +(16, '希恩斯', 0), +(17, '丁仪', 0), +(18, '杨冬', 0), +(19, '维德', 0), +(20, '上杉惠子', 0), +(21, '歌者', 0), +(22, '三体', 0), +(23, '智子', 0), +(24, '水滴', 0), +(25, '曾健起', 1), +(26, '王博君', 1), +(27, '范贤成', 1), +(28, '曾玉宝', 1); + +-- -------------------------------------------------------- + +-- +-- 表的结构 `bs_user` +-- + +CREATE TABLE `bs_user` ( + `id` int(11) NOT NULL, + `uid` varchar(255) NOT NULL, + `password` char(32) NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT '0', + `count_times` int(11) NOT NULL DEFAULT '0', + `login_time` datetime NOT NULL, + `tel` varchar(32) NOT NULL, + `email` varchar(255) NOT NULL, + `ip` varchar(50) NOT NULL, + `create_time` datetime NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- 转存表中的数据 `bs_user` +-- + +INSERT INTO `bs_user` (`id`, `uid`, `password`, `status`, `count_times`, `login_time`, `tel`, `email`, `ip`, `create_time`) VALUES +(1, '曾健起小号', '123456', 0, 12, '2020-02-26 13:52:08', '13032250167', 'zengjianqi163@163.com', '127.0.0.1', '2020-02-26 13:52:08'), +(2, 'zjquser', '123456', 0, 1, '2020-02-28 15:38:12', '111', '2223df@qq.com', '127.0.0.1', '2020-02-28 15:38:12'), +(3, '叶文杰', '123456', 0, 0, '2020-03-14 15:53:53', '137913791379', 'yewenjie@qq.com', '127.0.0.1', '2020-03-12 15:53:53'), +(4, '罗辑', '123456', 0, 0, '2020-03-14 15:54:55', '137913791379', 'luoji@qq.com', '127.0.0.1', '2020-03-12 15:54:55'), +(5, '程心', '123456', 0, 0, '2020-03-14 15:55:37', '137913791379', 'chengxin@qq.com', '127.0.0.1', '2020-03-13 15:55:37'), +(6, '云天明', '123456', 0, 0, '2020-03-14 15:56:07', '137913791379', 'yuntianming@qq.com', '127.0.0.1', '2020-03-13 15:56:07'), +(7, '史强', '123456', 0, 0, '2020-03-14 15:56:40', '137913791379', 'shiqiang@qq.com', '127.0.0.1', '2020-03-03 15:56:40'), +(8, '章北海', '123456', 0, 0, '2020-03-14 15:57:15', '137913791379', 'zhangbeihai@qq.com', '127.0.0.1', '2020-03-05 15:57:15'), +(9, '自然选择号', '123456', 0, 0, '2020-03-14 15:58:15', '137913791379', 'ziranxuanzehao@163.com', '127.0.0.1', '2020-03-06 15:58:15'), +(10, '蓝色空间号', '123456', 0, 0, '2020-03-14 15:58:44', '137913791379', 'lansekongjianhao@163.com', '127.0.0.1', '2020-03-14 15:58:44'), +(11, '东方延绪', '123456', 0, 0, '2020-03-14 16:00:06', '137913791379', 'dongfangyanxu@qq.com', '127.0.0.1', '2020-03-11 16:00:06'), +(12, '万有引力号', '123456', 0, 0, '2020-03-14 16:01:14', '137913791379', 'wanyouyinlihao@163.com', '127.0.0.1', '2020-03-06 16:01:14'), +(13, '庄颜', '123456', 0, 0, '2020-03-14 16:02:08', '137913791379', 'zhuangyan@qq.com', '127.0.0.1', '2020-03-12 16:02:08'), +(14, '泰勒', '123456', 0, 0, '2020-03-14 16:02:41', '137913791379', 'taile@qq.com', '127.0.0.1', '2020-03-01 16:02:41'), +(15, '希恩斯', '123456', 0, 0, '2020-03-14 16:03:39', '137913791379', 'xiensi@qq.com', '127.0.0.1', '2020-03-02 16:03:39'), +(16, '丁仪', '123456', 0, 0, '2020-03-14 16:04:15', '137913791379', 'dingyi@qq.com', '127.0.0.1', '2020-03-07 16:04:15'), +(17, '杨冬', '123456', 0, 0, '2020-03-14 16:05:26', '137913791379', 'yangdong@qq.com', '127.0.0.1', '2020-03-14 16:05:26'), +(18, '维德', '123456', 0, 0, '2020-03-14 16:06:03', '137913791379', 'weide@qq.com', '127.0.0.1', '2020-03-14 16:06:03'), +(19, '上杉惠子', '123456', 0, 0, '2020-03-14 16:07:18', '137913791379', 'shangshanhuizi@qq.com', '127.0.0.1', '2020-03-02 16:07:18'), +(20, '歌者', '123456', 0, 0, '2020-03-14 16:08:51', '137913791379', 'gezhe@hotmail.com', '127.0.0.1', '2020-03-09 16:08:51'), +(21, '三体', '123456', 0, 0, '2020-03-14 16:09:42', '137913791379', 'santi@hotmail.com', '127.0.0.1', '2020-03-09 16:09:42'), +(22, '智子', '123456', 0, 0, '2020-03-14 16:11:24', '137913791379', 'zhizi@163.com', '127.0.0.1', '2020-02-25 16:11:24'), +(23, '水滴', '123456', 0, 0, '2020-03-14 16:11:56', '137913791379', 'shuidi@163.com', '127.0.0.1', '2020-02-25 16:11:56'); + +-- +-- 转储表的索引 +-- + +-- +-- 表的索引 `bs_administrator` +-- +ALTER TABLE `bs_administrator` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `account` (`account`); + +-- +-- 表的索引 `bs_device` +-- +ALTER TABLE `bs_device` + ADD PRIMARY KEY (`uid`); + +-- +-- 表的索引 `bs_device_buzzer` +-- +ALTER TABLE `bs_device_buzzer` + ADD UNIQUE KEY `serial_num` (`serial_num`); + +-- +-- 表的索引 `bs_device_data_buzzer` +-- +ALTER TABLE `bs_device_data_buzzer` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `bs_device_data_infrared_sensor` +-- +ALTER TABLE `bs_device_data_infrared_sensor` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `bs_device_data_light_sensor` +-- +ALTER TABLE `bs_device_data_light_sensor` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `bs_device_data_temperature_sensor` +-- +ALTER TABLE `bs_device_data_temperature_sensor` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `bs_device_data_traffic_light` +-- +ALTER TABLE `bs_device_data_traffic_light` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `bs_device_infrared_sensor` +-- +ALTER TABLE `bs_device_infrared_sensor` + ADD UNIQUE KEY `serial_num` (`serial_num`); + +-- +-- 表的索引 `bs_device_light_sensor` +-- +ALTER TABLE `bs_device_light_sensor` + ADD UNIQUE KEY `serial_num` (`serial_num`); + +-- +-- 表的索引 `bs_device_map` +-- +ALTER TABLE `bs_device_map` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `function` (`function`); + +-- +-- 表的索引 `bs_device_serial` +-- +ALTER TABLE `bs_device_serial` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `bs_device_temperature_sensor` +-- +ALTER TABLE `bs_device_temperature_sensor` + ADD UNIQUE KEY `serial_num` (`serial_num`); + +-- +-- 表的索引 `bs_device_traffic_light` +-- +ALTER TABLE `bs_device_traffic_light` + ADD UNIQUE KEY `serial_num` (`serial_num`); + +-- +-- 表的索引 `bs_icon_data` +-- +ALTER TABLE `bs_icon_data` + ADD PRIMARY KEY (`id`); + +-- +-- 表的索引 `bs_log` +-- +ALTER TABLE `bs_log` + ADD PRIMARY KEY (`id`) USING BTREE; + +-- +-- 表的索引 `bs_people` +-- +ALTER TABLE `bs_people` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `name` (`name`); + +-- +-- 表的索引 `bs_user` +-- +ALTER TABLE `bs_user` + ADD PRIMARY KEY (`id`); + +-- +-- 在导出的表使用AUTO_INCREMENT +-- + +-- +-- 使用表AUTO_INCREMENT `bs_administrator` +-- +ALTER TABLE `bs_administrator` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; + +-- +-- 使用表AUTO_INCREMENT `bs_device_data_buzzer` +-- +ALTER TABLE `bs_device_data_buzzer` + MODIFY `id` int(255) NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `bs_device_data_infrared_sensor` +-- +ALTER TABLE `bs_device_data_infrared_sensor` + MODIFY `id` int(255) NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `bs_device_data_light_sensor` +-- +ALTER TABLE `bs_device_data_light_sensor` + MODIFY `id` int(255) NOT NULL AUTO_INCREMENT; + +-- +-- 使用表AUTO_INCREMENT `bs_device_data_temperature_sensor` +-- +ALTER TABLE `bs_device_data_temperature_sensor` + MODIFY `id` int(255) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=656; + +-- +-- 使用表AUTO_INCREMENT `bs_device_data_traffic_light` +-- +ALTER TABLE `bs_device_data_traffic_light` + MODIFY `id` int(255) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4226; + +-- +-- 使用表AUTO_INCREMENT `bs_device_map` +-- +ALTER TABLE `bs_device_map` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; + +-- +-- 使用表AUTO_INCREMENT `bs_device_serial` +-- +ALTER TABLE `bs_device_serial` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; + +-- +-- 使用表AUTO_INCREMENT `bs_icon_data` +-- +ALTER TABLE `bs_icon_data` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=169; + +-- +-- 使用表AUTO_INCREMENT `bs_log` +-- +ALTER TABLE `bs_log` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=25; + +-- +-- 使用表AUTO_INCREMENT `bs_people` +-- +ALTER TABLE `bs_people` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=29; + +-- +-- 使用表AUTO_INCREMENT `bs_user` +-- +ALTER TABLE `bs_user` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=24; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/extend/.gitignore b/extend/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/extend/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..337a549 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,12 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f +# RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + # 本地调试重定向URL地址 + RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1] + #部署环境重定向URL设置 + # RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L] + diff --git a/public/css/font.css b/public/css/font.css new file mode 100644 index 0000000..b83e5b4 --- /dev/null +++ b/public/css/font.css @@ -0,0 +1,16 @@ +@font-face { + font-family: 'iconfont'; + src: url('../fonts/iconfont.eot'); + src: url('../fonts/iconfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/iconfont.woff') format('woff'), + url('../fonts/iconfont.ttf') format('truetype'), + url('../fonts/iconfont.svg#iconfont') format('svg'); +} +.iconfont{ + font-family:"iconfont" !important; + font-size:16px;font-style:normal; + -webkit-font-smoothing: antialiased; + -webkit-text-stroke-width: 0.2px; + -moz-osx-font-smoothing: grayscale; +} + diff --git a/public/css/login.css b/public/css/login.css new file mode 100644 index 0000000..36ffd89 --- /dev/null +++ b/public/css/login.css @@ -0,0 +1,123 @@ +/* +* @Author: zengjianqi +* @Date: 2020-02-26 08:30:12 +* @Last Modified by: zengjianqi +* @Last Modified time: 2020-03-16 10:37:17 +*/ + +.login-bg { + /*background: url(../images/work.jpg) no-repeat center;*/ + /*background-size: cover;*/ + /*overflow: hidden;*/ + background-image: url(../images/work.jpg); + background-repeat: no-repeat; + background-position: center; + background-attachment: fixed; + background-size:cover; + /*height:100%;*/ + background-color: #b9c8ff; + +} +.login { + margin: 12vh auto 12vh auto; + min-height: 420px; + max-width: 420px; + padding: 40px; + background-color: rgba(255, 255, 255, 0.31); + margin-left: auto; + margin-right: auto; + border-radius: 4px; + /* overflow-x: hidden; */ + box-sizing: border-box; +} + +.login a.logo { + display: block; + height: 58px; + width: 167px; + margin: 0 auto 30px auto; + background-size: 167px 42px; +} + +.login .message { + margin: 10px 0 0 -58px; + padding: 18px 10px 18px 60px; + background: #4079d1; + position: relative; + color: #fff; + font-size: 40px; + font-family: "华文行楷"; +} + +.login #darkbannerwrap { + background: url(../images/aiwrap.png); + width: 18px; + height: 10px; + margin: 0 0 20px -58px; + position: relative; +} + +.login input[type=text], +.login input[type=file], +.login input[type=password], +.login input[type=email], select { + border: 1px solid #DCDEE0; + vertical-align: middle; + border-radius: 3px; + height: 50px; + padding: 0px 16px; + font-size: 14px; + color: #555555; + outline: none; + width: 100%; + box-sizing: border-box; +} + +.login input[type=text]:focus, +.login input[type=file]:focus, +.login input[type=password]:focus, +.login input[type=email]:focus, select:focus { + border: 1px solid #27A9E3; +} + +.login input[type=submit], +.login input[type=button] { + display: inline-block; + vertical-align: middle; + padding: 12px 24px; + margin: 0px; + font-size: 18px; + line-height: 24px; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + color: #ffffff; + background-color: #9f4546; + border-radius: 3px; + border: none; + -webkit-appearance: none; + outline: none; + width: 100%; +} + +.login hr { + /*background: #fff url("../images") 0 0 no-repeat;*/ + background-color: transparent; +} + +.login hr.hr15 { + height: 15px; + border: none; + margin: 0px; + padding: 0px; + width: 100%; +} + +.login hr.hr20 { + height: 20px; + border: none; + margin: 0px; + padding: 0px; + width: 100%; +} \ No newline at end of file diff --git a/public/css/theme1.css b/public/css/theme1.css new file mode 100644 index 0000000..79b2dc8 --- /dev/null +++ b/public/css/theme1.css @@ -0,0 +1,21 @@ +body{ + background:#F2F1F2; +} +.container{ + background:#1A1B20; +} +.left-nav{ + background:#1A1B20; +} + +.left-nav a{ + color:rgba(255,255,255,.7); +} +..left-nav a.active{ + background: #009688 !important; + color: #fff; +} +.left-nav a:hover{ + background: #009688 !important; + color: #fff; +} \ No newline at end of file diff --git a/public/css/theme2.css b/public/css/theme2.css new file mode 100644 index 0000000..04e9171 --- /dev/null +++ b/public/css/theme2.css @@ -0,0 +1,21 @@ +body{ + background:#EEF5F9; +} +.container{ + background:#323640; +} +.left-nav{ + background:#fff; +} + +.left-nav a{ + color:#686a76; +} +.left-nav a.active{ + background: #786AED !important; + color: #fff; +} +.left-nav a:hover{ + background: #786AED !important; + color: #fff; +} diff --git a/public/css/theme3.css b/public/css/theme3.css new file mode 100644 index 0000000..d5919c5 --- /dev/null +++ b/public/css/theme3.css @@ -0,0 +1,22 @@ +body{ + background:#E8E8E8; +} +.container{ + background:#F34743; +} + +.left-nav{ + background:#F4F4F4; +} + +.left-nav a{ + color:#686a76; +} +.left-nav a.active{ + background: #FEFEFE !important; + color: #F34743; +} +.left-nav a:hover{ + background: #FEFEFE !important; + color: #F34743; +} \ No newline at end of file diff --git a/public/css/theme4.css b/public/css/theme4.css new file mode 100644 index 0000000..0a0ff61 --- /dev/null +++ b/public/css/theme4.css @@ -0,0 +1,21 @@ +body{ + background:#E4E4E4; +} +.container{ + background:#019587; +} +.left-nav{ + background:#263035; +} + +.left-nav a{ + color:#fff; +} +.left-nav a.active{ + background: #212525 !important; + color: #fff !important; +} +.left-nav a:hover{ + background: #212525 !important; + color: #fff !important; +} \ No newline at end of file diff --git a/public/css/theme5.css b/public/css/theme5.css new file mode 100644 index 0000000..92c9a06 --- /dev/null +++ b/public/css/theme5.css @@ -0,0 +1,27 @@ +body{ + background:#EEF5F9 !important; +} +.container{ + background:linear-gradient(to left, #7b4397, #2196f3); +} + +.left-nav{ + background:#fff !important; +} + +.left-nav a{ + color:#686a76 !important; +} +.left-nav a.active{ + background: linear-gradient(to left, #7c8ce4, #2196f3) !important; + color: #fff !important; + border-color: #7b4397 !important; +} +.left-nav a:hover{ + background: linear-gradient(to left, #7c8ce4, #2196f3) !important; + color: #fff !important; + border-color: #7b4397 !important; +} +.container .logo a{ + background: rgba(0,0,0,0) !important; +} \ No newline at end of file diff --git a/public/css/xadmin.css b/public/css/xadmin.css new file mode 100644 index 0000000..d9433ee --- /dev/null +++ b/public/css/xadmin.css @@ -0,0 +1,633 @@ +@charset "utf-8"; +@import url(../lib/layui/css/layui.css); +*{ + margin: 0px; + padding: 0px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} +a{ + text-decoration: none; +} +html{ + width: 100%; + height: auto; + overflow-x:hidden; + overflow-y:auto; +} +body{ + width: 100%; + /*min-height: 100%;*/ + /*background-image: url("../images/wallhaven.jpg");*/ + /*background-position: center;*/ + /*background-size:100% 100%;*/ + /*background-repeat: no-repeat;*/ + /*background-attachment: fixed;*/ + background-color: rgba(228, 234, 241, 0); + /*background: #fff;*/ + +} +.index{ + background-image: url("../images/wallhaven.jpg"); + /*background-position: center;*/ + /*background-size:100% 100%;*/ + background-repeat: no-repeat; + background-attachment: fixed; + background-color: #e4eaf1; + background-position: center; + background-size:cover; +} +.layui-card-body{ + background-color: rgba(255, 255, 255, 0.67); +} +.x-red{ + color: red; +} + +.layui-form-switch{ + margin-top: 0px; +} +.layui-input:focus, .layui-textarea:focus { + border-color: #189f92!important; +} + +.layui-fluid{ + padding:15px; +} + +.layui-form-label{ + font-weight: 700; + font-size: 14px; +} + +.x-nav{ + padding: 0 20px; + position: relative; + z-index: 99; + /*border-bottom: 1px solid #e5e5e5;*/ + line-height: 39px; + height: 39px; + overflow: hidden; + /*background: rgba(168, 168, 168, 0.46);*/ + background-image: linear-gradient(rgba(213, 213, 213, 0.37), rgba(168, 168, 168, 0)); + + /*-moz-border-radius:50px 0 0 0;*/ + /*-webkit-border-radius:50px 0 0 0;*/ + /*border-radius:50px 0 0 0;*/ + -moz-border-radius:25px 25px 0 0; + -webkit-border-radius:25px 25px 0 0; + border-radius:25px 25px 0 0; + /*background-color: rgb(242, 255, 253);*/ +} +.layui-breadcrumb span[lay-separator] { + color: #282c93; +} +.layui-breadcrumb a { + color: #e8f1ff !important; +} +.page{ + text-align: center; + +} +.page a{ + display: inline-block; + background: #fff; + color: #888; + padding: 5px; + min-width: 15px; + border: 1px solid #E2E2E2; + +} +.page span{ + display: inline-block; + padding: 5px; + min-width: 15px; + border: 1px solid #E2E2E2; +} +.page span.current{ + display: inline-block; + background: #009688; + color: #fff; + padding: 5px; + min-width: 15px; + border: 1px solid #009688; +} +.page .pagination li{ + display: inline-block; + margin-right: 5px; + text-align: center; +} +.page .pagination li.active span{ + background: #009688; + color: #fff; + border: 1px solid #009688; + +} + +/*登录样式*/ +/*头部*/ +.container{ + width: 100%; + height: 45px; + /*background-color: #222;*/ + background-color: rgba(0, 0, 0, 0.38); + border-radius: 10px; +} +.container a,.layui-nav .layui-nav-item a{ + color: #fff; +} +.container .logo a{ + background-color: rgba(0,0,0,0); +} +.container .logo a{ + float: left; + font-size: 18px; + padding-left: 20px; + line-height: 45px; + width: 200px; +} +.container .right{ + background-color:rgba(0,0,0,0); + float: right; + +} +.container .left_open{ + height: 45px; + float: left; + margin-left: 10px; +} +.container .left_open i{ + display: block; + background: rgba(255,255,255,0.1); + width: 32px; + height: 32px; + line-height: 32px; + border-radius: 3px; + text-align: center; + margin-top: 7px; + cursor: pointer; +} +.container .left_open i:hover{ + background: rgba(255,255,255,0.3); +} + +.container .left{ + background-color:rgba(0,0,0,0); + float: left; + +} +.container .layui-nav-item{ + line-height: 45px; +} +.container .layui-nav-more{ + top: 20px; +} +.container .layui-nav-child{ + top: 50px; +} +.container .layui-nav-child i{ + margin-right: 10px; +} +.layui-nav .layui-nav-item a{ + cursor: pointer; +} +.layui-nav .layui-nav-child a{ + color: #333; + cursor: pointer; +} +.left-nav{ + position: absolute; + top: 45px; + bottom: 0px; + /*bottom: 42px;*/ + left: 0; + z-index: 2; + padding-top: 10px; + /*background-color: rgba(242, 255, 253, 0.65);*/ + background-image: linear-gradient(rgba(242, 255, 253, 1), rgba(242, 255, 253, 0)); + border-radius: 10px; + /*background-image: url(../images/buildings.jpg);*/ + width: 220px; + max-width: 220px; + overflow: auto; + /*overflow-x:hidden;*/ + /*overflow: hidden;*/ + + /*width: 0px;*/ +} +#side-nav{ + width: 220px; +} + +.left-nav #nav li:hover > a{ + /*color: blue;*/ +} +.left-nav #nav .current{ + background-color: rgba(0, 0, 0, 0.3); +} +.left-nav #nav li a{ + font-size: 14px; + padding: 10px 15px 10px 15px; + display: block; + cursor: pointer; + border-left: 4px solid transparent; + transition: all 0.3s; +} +.left-nav a:hover{ + background: #009688 !important; + color: #fff; + border-color: #04564e !important; +} +.left-nav a.active{ + background: #009688 !important; + color: #fff; + border-color: #04564e !important; +} +.left-nav #nav li a cite{ + font-size: 14px; +} + +.left-nav #nav li .sub-menu{ + display: none; +} +.left-nav #nav li .opened{ + display: block; +} +.left-nav #nav li .opened:hover{ + /*background: #fff ;*/ +} +.left-nav #nav li .opened .current{ + +} +.left-nav #nav li .sub-menu li:hover{ + /*color: blue;*/ + /*background: #fff ;*/ +} +.left-nav #nav li .sub-menu li a{ + padding: 12px 15px 12px 30px; + font-size: 14px; + cursor: pointer; +} +.left-nav #nav li .sub-menu li .sub-menu li a{ + padding-left: 45px; +} +/*.left-nav #nav li .sub-menu li a:hover{ + color: #148cf1; +}*/ +.left-nav #nav li .sub-menu li a i{ + font-size: 12px; +} +.left-nav #nav li a i{ + padding-right: 10px; + line-height: 14px; +} +.left-nav #nav li .nav_right{ + float: right; + font-size: 16px; +} +.x-slide_left { + width: 17px; + height: 61px; + /*background: url(../images/bg.png) 0 0 no-repeat;*/ + position: absolute; + top: 200px; + left: 220px; + cursor: pointer; + z-index: 3; +} +.page-content{ + position: absolute; + top: 45px; + right: 0; + /*bottom: 42px;*/ + bottom: 0px; + left: 220px; + overflow: hidden; + z-index: 1; +} +.page-content-bg{ + position: absolute; + top: 45px; + right: 0; + /*bottom: 42px;*/ + bottom: 0px; + left: 220px; + background: rgba(0,0,0,0.5); + overflow: hidden; + z-index: 100; + display: none; +} + +.page-content .tab{ + height: 100%; + width: 100%; + /*background: #EFEEF0;*/ + margin: 0px; +} +.page-content .layui-tab-title{ + /*padding-top: 5px;*/ + height: 35px; + /*background: #EFEEF0 ;*/ + background-color: rgba(255, 255, 253, 0.65); + position: relative; + z-index: 100; + width:100%; + border-radius: 10px; + /*-moz-border-radius:0 0 50px 50px;*/ + /*-webkit-border-radius:0 0 50px 50px;*/ + /*border-radius:0 0 50px 50px;*/ + + /*-moz-border-radius:40px/20px;*/ + /*-webkit-border-radius:40px/20px;*/ + /*border-radius:40px/20px;*/ + + /*-moz-border-radius:50px 0 0 50px;*/ + /*-webkit-border-radius:50px 0 0 50px;*/ + /*border-radius:50px 0 0 50px;*/ + + /*-moz-border-radius:50px 50px 0 0;*/ + /*-webkit-border-radius:50px 50px 0 0;*/ + /*border-radius:50px 50px 0 0;*/ + + +} +.page-content .layui-tab-title li.home i{ + padding-right: 5px; +} +.page-content .layui-tab-title li.home .layui-tab-close{ + display: none; +} +.page-content .layui-tab-title li{ + line-height: 35px; +} +.page-content .layui-tab-title .layui-this:after{ + height: 36px; + font-weight: 900; +} +.page-content .layui-tab-title li .layui-tab-close{ + border-radius: 50%; + color: #1886bb; + font-weight: 800; +} +.page-content .layui-tab-title .layui-this{ + background: #fff ; + font-weight: 700; +} +.page-content .layui-tab-bar{ + height:34px; + line-height: 35px; +} +.page-content .layui-tab-content{ + position: absolute; + top: 36px; + bottom: 0px; + width: 100%; + padding: 0px; + overflow: hidden; +} +.page-content .layui-tab-content .layui-tab-item{ + width: 100%; + height: 100%; + +} +.page-content .layui-tab-content .layui-tab-item iframe{ + width: 100%; + height: 100%; + +} +.x-admin-carousel,.layui-carousel,.x-admin-carousel>[carousel-item]>* { + background-color:#fff +} + +.x-admin-backlog .x-admin-backlog-body { + display:block; + padding:10px 15px; + background-color:#f8f8f8; + color:#999; + border-radius:2px; + transition:all .3s; + -webkit-transition:all .3s +} + +.x-admin-backlog-body h3 { + padding-bottom:10px; + font-size:12px +} +.x-admin-backlog-body p cite { + font-style:normal; + font-size:30px; + font-weight:300; + color:#009688 +} +.x-admin-backlog-body:hover { + background-color:#CFCFCF; + color:#888 +} + + +table th, table td { + word-break: break-all; +} + +/*404页面样式*/ +.fly-panel { + margin-bottom: 15px; + border-radius: 2px; + /*background-color: #fff;*/ + box-shadow: 0 1px 2px 0 rgba(0,0,0,.05); +} +.fly-none { + min-height: 600px; + text-align: center; + padding-top: 50px; + color: #999; +} +.fly-none .layui-icon { + line-height: 300px; + font-size: 300px; + color: #393D49; +} +.fly-none p { + margin-top: 50px; + padding: 0 15px; + font-size: 20px; + color: #999; + font-weight: 300; +} +#tab_right{ + display: none; + width: 80px; + position: absolute; + top: 35px; + left: 0px; +} +#tab_right dl{ + top: 0px; +} +#tab_show{ + position: absolute; + top: 36px; + bottom: 0px; + width: 100%; + background:rgb(255, 255, 255); + padding: 0px; + overflow: hidden; + display: none; +} + + +@media screen and (max-width: 768px){ + .fast-add{ + display: none; + } + .layui-nav .to-index{ + display: none; + } + .container .logo a{ + width: 140px; + } + .container .left_open { + /*float: right;*/ + } + .left-nav{ + width: 60px; + } + .left-nav #nav li a i{ + font-size: 18px; + } + .left-nav cite,.left-nav .nav_right{ + display: none; + } + .page-content{ + left: 60px; + } + .page-content .layui-tab-content .layui-tab-item{ + -webkit-overflow-scrolling: touch; + overflow-y: scroll; + } + .x-so input.layui-input{ + width: 100%; + margin: 10px; + } +} + +/*精细版样式*/ + +.x-admin-sm{ + font-size: 12px; +} +.x-admin-sm body{ + font-size: 12px; +} +/*滚动条样式*/ +.x-admin-sm body::-webkit-scrollbar {/*滚动条整体样式*/ + width: 10px; /*高宽分别对应横竖滚动条的尺寸*/ + height: 20px; + border-radius: 8px; +} +.x-admin-sm body::-moz-scrollbar {/*滚动条整体样式*/ + width: 8px; /*高宽分别对应横竖滚动条的尺寸*/ + height: 20px; + border-radius: 8px; +} +.x-admin-sm body::-webkit-scrollbar-thumb {/*滚动条里面小方块*/ + -webkit-box-shadow: inset 0 0 5px #3384ff; + background: rgba(1,187,218,0.2); + border-radius: 8px; +} +.x-admin-sm body::-moz-scrollbar-thumb {/*滚动条里面小方块*/ + -webkit-box-shadow: inset 0 0 5px #3384ff; + background: rgba(1,187,218,0.2); + border-radius: 8px; +} +.x-admin-sm body::-webkit-scrollbar-track {/*滚动条里面轨道*/ + -webkit-box-shadow: inset 0 0 5px rgba(4,231,250,0.6); + border-radius: 0; + background: rgba(3,10,28,0.6); +} +.x-admin-sm body::-moz-scrollbar-track {/*滚动条里面轨道*/ + -webkit-box-shadow: inset 0 0 5px rgba(4,231,250,0.6); + border-radius: 0; + background: rgba(3,10,28,0.6); +} + +/*登录页面样式*/ +.x-admin-sm .login input[type=submit],.x-admin-sm .login input[type=button]{ + font-size: 14px; +} +.x-admin-sm .login input[type=text], +.x-admin-sm .login input[type=file], +.x-admin-sm .login input[type=password], +.x-admin-sm .login input[type=email], .x-admin-sm select { + font-size: 12px; +} +.x-admin-sm .login .message{ + font-size: 14px; +} + +.x-admin-sm .layui-table td, .x-admin-sm .layui-table th{ + font-size: 12px; +} +.x-admin-sm .layui-elem-field legend{ + font-size: 18px; +} + +.x-admin-sm .x-admin-backlog-body p cite{ + font-size: 24px; +} +.x-admin-sm .left-nav #nav li a cite{ + font-size: 12px; +} +.x-admin-sm .iconfont{ + font-size: 14px; +} +.x-admin-sm .layui-tab-title li{ + font-size: 14px; +} +.x-admin-sm .layui-icon{ + font-size: 14px; +} +.x-admin-sm .layui-nav *{ + font-size: 12px; +} +.x-admin-sm .layui-breadcrumb>*{ + font-size: 12px; +} +.x-admin-sm .layui-btn,.x-admin-sm .layui-btn-xs,.x-admin-sm .layui-btn-sm{ + font-size: 12px; +} + +.x-admin-sm .layui-laydate{ + font-size: 12px; +} +.x-admin-sm .layui-btn{ + height: 30px; + line-height: 30px; + padding: 0 10px; +} + +.x-admin-sm .layui-btn-lg{ + height: 38px; + line-height: 38px; + padding: 0 18px; + font-size: 14px; +} +.x-admin-sm .layui-layer-title,.x-admin-sm .layui-layer-dialog .layui-layer-content{ + font-size: 12px; +} +.x-admin-sm .layui-input,.x-admin-sm .layui-select,.x-admin-sm .layui-textarea{ + height: 30px; +} + +.x-admin-sm .layui-form-pane .layui-form-label{ + height: 30px; + line-height: 14px; +} +.x-admin-sm .layui-form-checkbox span{ + font-size: 12px; +} +.x-admin-sm .fly-none .layui-icon { + line-height: 300px; + font-size: 300px; + color: #393D49; +} + diff --git a/public/dist/layarea.js b/public/dist/layarea.js new file mode 100644 index 0000000..c5633ba --- /dev/null +++ b/public/dist/layarea.js @@ -0,0 +1,4020 @@ +layui.define(['layer', 'form', 'laytpl'], function (exports) { + "use strict"; + + let $ = layui.$ + , form = layui.form + , layarea = { + _id: 0 + , config: {} + , set: function (options) { + let that = this; + that.config = $.extend({}, that.config, options); + return that; + } + , on: function (events, callback) { + return layui.onevent.call(this, 'layarea', events, callback); + } + } + , thisArea = function () { + let that = this; + return { + layarea: function (files) { + that.layarea.call(that, files); + } + , config: that.config + } + } + , Class = function (options) { + let that = this; + that.config = $.extend({}, that.config, layarea.config, options); + that.render(); + }; + + let areaList = { + province_list: { + 110000: '北京市', + 120000: '天津市', + 130000: '河北省', + 140000: '山西省', + 150000: '内蒙古自治区', + 210000: '辽宁省', + 220000: '吉林省', + 230000: '黑龙江省', + 310000: '上海市', + 320000: '江苏省', + 330000: '浙江省', + 340000: '安徽省', + 350000: '福建省', + 360000: '江西省', + 370000: '山东省', + 410000: '河南省', + 420000: '湖北省', + 430000: '湖南省', + 440000: '广东省', + 450000: '广西壮族自治区', + 460000: '海南省', + 500000: '重庆市', + 510000: '四川省', + 520000: '贵州省', + 530000: '云南省', + 540000: '西藏自治区', + 610000: '陕西省', + 620000: '甘肃省', + 630000: '青海省', + 640000: '宁夏回族自治区', + 650000: '新疆维吾尔自治区', + 710000: '台湾省', + 810000: '香港特别行政区', + 820000: '澳门特别行政区', + 900000: '海外' + }, + city_list: { + 110100: '北京市', + 120100: '天津市', + 130100: '石家庄市', + 130200: '唐山市', + 130300: '秦皇岛市', + 130400: '邯郸市', + 130500: '邢台市', + 130600: '保定市', + 130700: '张家口市', + 130800: '承德市', + 130900: '沧州市', + 131000: '廊坊市', + 131100: '衡水市', + 139000: '省直辖县', + 140100: '太原市', + 140200: '大同市', + 140300: '阳泉市', + 140400: '长治市', + 140500: '晋城市', + 140600: '朔州市', + 140700: '晋中市', + 140800: '运城市', + 140900: '忻州市', + 141000: '临汾市', + 141100: '吕梁市', + 150100: '呼和浩特市', + 150200: '包头市', + 150300: '乌海市', + 150400: '赤峰市', + 150500: '通辽市', + 150600: '鄂尔多斯市', + 150700: '呼伦贝尔市', + 150800: '巴彦淖尔市', + 150900: '乌兰察布市', + 152200: '兴安盟', + 152500: '锡林郭勒盟', + 152900: '阿拉善盟', + 210100: '沈阳市', + 210200: '大连市', + 210300: '鞍山市', + 210400: '抚顺市', + 210500: '本溪市', + 210600: '丹东市', + 210700: '锦州市', + 210800: '营口市', + 210900: '阜新市', + 211000: '辽阳市', + 211100: '盘锦市', + 211200: '铁岭市', + 211300: '朝阳市', + 211400: '葫芦岛市', + 220100: '长春市', + 220200: '吉林市', + 220300: '四平市', + 220400: '辽源市', + 220500: '通化市', + 220600: '白山市', + 220700: '松原市', + 220800: '白城市', + 222400: '延边朝鲜族自治州', + 230100: '哈尔滨市', + 230200: '齐齐哈尔市', + 230300: '鸡西市', + 230400: '鹤岗市', + 230500: '双鸭山市', + 230600: '大庆市', + 230700: '伊春市', + 230800: '佳木斯市', + 230900: '七台河市', + 231000: '牡丹江市', + 231100: '黑河市', + 231200: '绥化市', + 232700: '大兴安岭地区', + 310100: '上海市', + 320100: '南京市', + 320200: '无锡市', + 320300: '徐州市', + 320400: '常州市', + 320500: '苏州市', + 320600: '南通市', + 320700: '连云港市', + 320800: '淮安市', + 320900: '盐城市', + 321000: '扬州市', + 321100: '镇江市', + 321200: '泰州市', + 321300: '宿迁市', + 330100: '杭州市', + 330200: '宁波市', + 330300: '温州市', + 330400: '嘉兴市', + 330500: '湖州市', + 330600: '绍兴市', + 330700: '金华市', + 330800: '衢州市', + 330900: '舟山市', + 331000: '台州市', + 331100: '丽水市', + 340100: '合肥市', + 340200: '芜湖市', + 340300: '蚌埠市', + 340400: '淮南市', + 340500: '马鞍山市', + 340600: '淮北市', + 340700: '铜陵市', + 340800: '安庆市', + 341000: '黄山市', + 341100: '滁州市', + 341200: '阜阳市', + 341300: '宿州市', + 341500: '六安市', + 341600: '亳州市', + 341700: '池州市', + 341800: '宣城市', + 350100: '福州市', + 350200: '厦门市', + 350300: '莆田市', + 350400: '三明市', + 350500: '泉州市', + 350600: '漳州市', + 350700: '南平市', + 350800: '龙岩市', + 350900: '宁德市', + 360100: '南昌市', + 360200: '景德镇市', + 360300: '萍乡市', + 360400: '九江市', + 360500: '新余市', + 360600: '鹰潭市', + 360700: '赣州市', + 360800: '吉安市', + 360900: '宜春市', + 361000: '抚州市', + 361100: '上饶市', + 370100: '济南市', + 370200: '青岛市', + 370300: '淄博市', + 370400: '枣庄市', + 370500: '东营市', + 370600: '烟台市', + 370700: '潍坊市', + 370800: '济宁市', + 370900: '泰安市', + 371000: '威海市', + 371100: '日照市', + 371200: '莱芜市', + 371300: '临沂市', + 371400: '德州市', + 371500: '聊城市', + 371600: '滨州市', + 371700: '菏泽市', + 410100: '郑州市', + 410200: '开封市', + 410300: '洛阳市', + 410400: '平顶山市', + 410500: '安阳市', + 410600: '鹤壁市', + 410700: '新乡市', + 410800: '焦作市', + 410900: '濮阳市', + 411000: '许昌市', + 411100: '漯河市', + 411200: '三门峡市', + 411300: '南阳市', + 411400: '商丘市', + 411500: '信阳市', + 411600: '周口市', + 411700: '驻马店市', + 419000: '省直辖县', + 420100: '武汉市', + 420200: '黄石市', + 420300: '十堰市', + 420500: '宜昌市', + 420600: '襄阳市', + 420700: '鄂州市', + 420800: '荆门市', + 420900: '孝感市', + 421000: '荆州市', + 421100: '黄冈市', + 421200: '咸宁市', + 421300: '随州市', + 422800: '恩施土家族苗族自治州', + 429000: '省直辖县', + 430100: '长沙市', + 430200: '株洲市', + 430300: '湘潭市', + 430400: '衡阳市', + 430500: '邵阳市', + 430600: '岳阳市', + 430700: '常德市', + 430800: '张家界市', + 430900: '益阳市', + 431000: '郴州市', + 431100: '永州市', + 431200: '怀化市', + 431300: '娄底市', + 433100: '湘西土家族苗族自治州', + 440100: '广州市', + 440200: '韶关市', + 440300: '深圳市', + 440400: '珠海市', + 440500: '汕头市', + 440600: '佛山市', + 440700: '江门市', + 440800: '湛江市', + 440900: '茂名市', + 441200: '肇庆市', + 441300: '惠州市', + 441400: '梅州市', + 441500: '汕尾市', + 441600: '河源市', + 441700: '阳江市', + 441800: '清远市', + 441900: '东莞市', + 442000: '中山市', + 445100: '潮州市', + 445200: '揭阳市', + 445300: '云浮市', + 450100: '南宁市', + 450200: '柳州市', + 450300: '桂林市', + 450400: '梧州市', + 450500: '北海市', + 450600: '防城港市', + 450700: '钦州市', + 450800: '贵港市', + 450900: '玉林市', + 451000: '百色市', + 451100: '贺州市', + 451200: '河池市', + 451300: '来宾市', + 451400: '崇左市', + 460100: '海口市', + 460200: '三亚市', + 460300: '三沙市', + 460400: '儋州市', + 469000: '省直辖县', + 500100: '重庆市', + 500200: '县', + 510100: '成都市', + 510300: '自贡市', + 510400: '攀枝花市', + 510500: '泸州市', + 510600: '德阳市', + 510700: '绵阳市', + 510800: '广元市', + 510900: '遂宁市', + 511000: '内江市', + 511100: '乐山市', + 511300: '南充市', + 511400: '眉山市', + 511500: '宜宾市', + 511600: '广安市', + 511700: '达州市', + 511800: '雅安市', + 511900: '巴中市', + 512000: '资阳市', + 513200: '阿坝藏族羌族自治州', + 513300: '甘孜藏族自治州', + 513400: '凉山彝族自治州', + 520100: '贵阳市', + 520200: '六盘水市', + 520300: '遵义市', + 520400: '安顺市', + 520500: '毕节市', + 520600: '铜仁市', + 522300: '黔西南布依族苗族自治州', + 522600: '黔东南苗族侗族自治州', + 522700: '黔南布依族苗族自治州', + 530100: '昆明市', + 530300: '曲靖市', + 530400: '玉溪市', + 530500: '保山市', + 530600: '昭通市', + 530700: '丽江市', + 530800: '普洱市', + 530900: '临沧市', + 532300: '楚雄彝族自治州', + 532500: '红河哈尼族彝族自治州', + 532600: '文山壮族苗族自治州', + 532800: '西双版纳傣族自治州', + 532900: '大理白族自治州', + 533100: '德宏傣族景颇族自治州', + 533300: '怒江傈僳族自治州', + 533400: '迪庆藏族自治州', + 540100: '拉萨市', + 540200: '日喀则市', + 540300: '昌都市', + 540400: '林芝市', + 540500: '山南市', + 540600: '那曲市', + 542500: '阿里地区', + 610100: '西安市', + 610200: '铜川市', + 610300: '宝鸡市', + 610400: '咸阳市', + 610500: '渭南市', + 610600: '延安市', + 610700: '汉中市', + 610800: '榆林市', + 610900: '安康市', + 611000: '商洛市', + 620100: '兰州市', + 620200: '嘉峪关市', + 620300: '金昌市', + 620400: '白银市', + 620500: '天水市', + 620600: '武威市', + 620700: '张掖市', + 620800: '平凉市', + 620900: '酒泉市', + 621000: '庆阳市', + 621100: '定西市', + 621200: '陇南市', + 622900: '临夏回族自治州', + 623000: '甘南藏族自治州', + 630100: '西宁市', + 630200: '海东市', + 632200: '海北藏族自治州', + 632300: '黄南藏族自治州', + 632500: '海南藏族自治州', + 632600: '果洛藏族自治州', + 632700: '玉树藏族自治州', + 632800: '海西蒙古族藏族自治州', + 640100: '银川市', + 640200: '石嘴山市', + 640300: '吴忠市', + 640400: '固原市', + 640500: '中卫市', + 650100: '乌鲁木齐市', + 650200: '克拉玛依市', + 650400: '吐鲁番市', + 650500: '哈密市', + 652300: '昌吉回族自治州', + 652700: '博尔塔拉蒙古自治州', + 652800: '巴音郭楞蒙古自治州', + 652900: '阿克苏地区', + 653000: '克孜勒苏柯尔克孜自治州', + 653100: '喀什地区', + 653200: '和田地区', + 654000: '伊犁哈萨克自治州', + 654200: '塔城地区', + 654300: '阿勒泰地区', + 659000: '自治区直辖县级行政区划', + 710100: '台北市', + 710200: '高雄市', + 710300: '台南市', + 710400: '台中市', + 710500: '金门县', + 710600: '南投县', + 710700: '基隆市', + 710800: '新竹市', + 710900: '嘉义市', + 711100: '新北市', + 711200: '宜兰县', + 711300: '新竹县', + 711400: '桃园县', + 711500: '苗栗县', + 711700: '彰化县', + 711900: '嘉义县', + 712100: '云林县', + 712400: '屏东县', + 712500: '台东县', + 712600: '花莲县', + 712700: '澎湖县', + 712800: '连江县', + 810100: '香港岛', + 810200: '九龙', + 810300: '新界', + 820100: '澳门半岛', + 820200: '离岛', + 912400: '加拿大', + 941000: '韩国', + 984000: '美国' + }, + county_list: { + 110101: '东城区', + 110102: '西城区', + 110105: '朝阳区', + 110106: '丰台区', + 110107: '石景山区', + 110108: '海淀区', + 110109: '门头沟区', + 110111: '房山区', + 110112: '通州区', + 110113: '顺义区', + 110114: '昌平区', + 110115: '大兴区', + 110116: '怀柔区', + 110117: '平谷区', + 110118: '密云区', + 110119: '延庆区', + 120101: '和平区', + 120102: '河东区', + 120103: '河西区', + 120104: '南开区', + 120105: '河北区', + 120106: '红桥区', + 120110: '东丽区', + 120111: '西青区', + 120112: '津南区', + 120113: '北辰区', + 120114: '武清区', + 120115: '宝坻区', + 120116: '滨海新区', + 120117: '宁河区', + 120118: '静海区', + 120119: '蓟州区', + 130102: '长安区', + 130104: '桥西区', + 130105: '新华区', + 130107: '井陉矿区', + 130108: '裕华区', + 130109: '藁城区', + 130110: '鹿泉区', + 130111: '栾城区', + 130121: '井陉县', + 130123: '正定县', + 130125: '行唐县', + 130126: '灵寿县', + 130127: '高邑县', + 130128: '深泽县', + 130129: '赞皇县', + 130130: '无极县', + 130131: '平山县', + 130132: '元氏县', + 130133: '赵县', + 130181: '辛集市', + 130183: '晋州市', + 130184: '新乐市', + 130202: '路南区', + 130203: '路北区', + 130204: '古冶区', + 130205: '开平区', + 130207: '丰南区', + 130208: '丰润区', + 130209: '曹妃甸区', + 130223: '滦县', + 130224: '滦南县', + 130225: '乐亭县', + 130227: '迁西县', + 130229: '玉田县', + 130281: '遵化市', + 130283: '迁安市', + 130302: '海港区', + 130303: '山海关区', + 130304: '北戴河区', + 130306: '抚宁区', + 130321: '青龙满族自治县', + 130322: '昌黎县', + 130324: '卢龙县', + 130390: '经济技术开发区', + 130402: '邯山区', + 130403: '丛台区', + 130404: '复兴区', + 130406: '峰峰矿区', + 130407: '肥乡区', + 130408: '永年区', + 130423: '临漳县', + 130424: '成安县', + 130425: '大名县', + 130426: '涉县', + 130427: '磁县', + 130430: '邱县', + 130431: '鸡泽县', + 130432: '广平县', + 130433: '馆陶县', + 130434: '魏县', + 130435: '曲周县', + 130481: '武安市', + 130502: '桥东区', + 130503: '桥西区', + 130521: '邢台县', + 130522: '临城县', + 130523: '内丘县', + 130524: '柏乡县', + 130525: '隆尧县', + 130526: '任县', + 130527: '南和县', + 130528: '宁晋县', + 130529: '巨鹿县', + 130530: '新河县', + 130531: '广宗县', + 130532: '平乡县', + 130533: '威县', + 130534: '清河县', + 130535: '临西县', + 130581: '南宫市', + 130582: '沙河市', + 130602: '竞秀区', + 130606: '莲池区', + 130607: '满城区', + 130608: '清苑区', + 130609: '徐水区', + 130623: '涞水县', + 130624: '阜平县', + 130626: '定兴县', + 130627: '唐县', + 130628: '高阳县', + 130629: '容城县', + 130630: '涞源县', + 130631: '望都县', + 130632: '安新县', + 130633: '易县', + 130634: '曲阳县', + 130635: '蠡县', + 130636: '顺平县', + 130637: '博野县', + 130638: '雄县', + 130681: '涿州市', + 130682: '定州市', + 130683: '安国市', + 130684: '高碑店市', + 130702: '桥东区', + 130703: '桥西区', + 130705: '宣化区', + 130706: '下花园区', + 130708: '万全区', + 130709: '崇礼区', + 130722: '张北县', + 130723: '康保县', + 130724: '沽源县', + 130725: '尚义县', + 130726: '蔚县', + 130727: '阳原县', + 130728: '怀安县', + 130730: '怀来县', + 130731: '涿鹿县', + 130732: '赤城县', + 130802: '双桥区', + 130803: '双滦区', + 130804: '鹰手营子矿区', + 130821: '承德县', + 130822: '兴隆县', + 130824: '滦平县', + 130825: '隆化县', + 130826: '丰宁满族自治县', + 130827: '宽城满族自治县', + 130828: '围场满族蒙古族自治县', + 130881: '平泉市', + 130902: '新华区', + 130903: '运河区', + 130921: '沧县', + 130922: '青县', + 130923: '东光县', + 130924: '海兴县', + 130925: '盐山县', + 130926: '肃宁县', + 130927: '南皮县', + 130928: '吴桥县', + 130929: '献县', + 130930: '孟村回族自治县', + 130981: '泊头市', + 130982: '任丘市', + 130983: '黄骅市', + 130984: '河间市', + 131002: '安次区', + 131003: '广阳区', + 131022: '固安县', + 131023: '永清县', + 131024: '香河县', + 131025: '大城县', + 131026: '文安县', + 131028: '大厂回族自治县', + 131081: '霸州市', + 131082: '三河市', + 131090: '开发区', + 131102: '桃城区', + 131103: '冀州区', + 131121: '枣强县', + 131122: '武邑县', + 131123: '武强县', + 131124: '饶阳县', + 131125: '安平县', + 131126: '故城县', + 131127: '景县', + 131128: '阜城县', + 131182: '深州市', + 140105: '小店区', + 140106: '迎泽区', + 140107: '杏花岭区', + 140108: '尖草坪区', + 140109: '万柏林区', + 140110: '晋源区', + 140121: '清徐县', + 140122: '阳曲县', + 140123: '娄烦县', + 140181: '古交市', + 140202: '城区', + 140203: '矿区', + 140211: '南郊区', + 140212: '新荣区', + 140221: '阳高县', + 140222: '天镇县', + 140223: '广灵县', + 140224: '灵丘县', + 140225: '浑源县', + 140226: '左云县', + 140227: '大同县', + 140302: '城区', + 140303: '矿区', + 140311: '郊区', + 140321: '平定县', + 140322: '盂县', + 140402: '城区', + 140411: '郊区', + 140421: '长治县', + 140423: '襄垣县', + 140424: '屯留县', + 140425: '平顺县', + 140426: '黎城县', + 140427: '壶关县', + 140428: '长子县', + 140429: '武乡县', + 140430: '沁县', + 140431: '沁源县', + 140481: '潞城市', + 140502: '城区', + 140521: '沁水县', + 140522: '阳城县', + 140524: '陵川县', + 140525: '泽州县', + 140581: '高平市', + 140602: '朔城区', + 140603: '平鲁区', + 140621: '山阴县', + 140622: '应县', + 140623: '右玉县', + 140624: '怀仁县', + 140702: '榆次区', + 140721: '榆社县', + 140722: '左权县', + 140723: '和顺县', + 140724: '昔阳县', + 140725: '寿阳县', + 140726: '太谷县', + 140727: '祁县', + 140728: '平遥县', + 140729: '灵石县', + 140781: '介休市', + 140802: '盐湖区', + 140821: '临猗县', + 140822: '万荣县', + 140823: '闻喜县', + 140824: '稷山县', + 140825: '新绛县', + 140826: '绛县', + 140827: '垣曲县', + 140828: '夏县', + 140829: '平陆县', + 140830: '芮城县', + 140881: '永济市', + 140882: '河津市', + 140902: '忻府区', + 140921: '定襄县', + 140922: '五台县', + 140923: '代县', + 140924: '繁峙县', + 140925: '宁武县', + 140926: '静乐县', + 140927: '神池县', + 140928: '五寨县', + 140929: '岢岚县', + 140930: '河曲县', + 140931: '保德县', + 140932: '偏关县', + 140981: '原平市', + 141002: '尧都区', + 141021: '曲沃县', + 141022: '翼城县', + 141023: '襄汾县', + 141024: '洪洞县', + 141025: '古县', + 141026: '安泽县', + 141027: '浮山县', + 141028: '吉县', + 141029: '乡宁县', + 141030: '大宁县', + 141031: '隰县', + 141032: '永和县', + 141033: '蒲县', + 141034: '汾西县', + 141081: '侯马市', + 141082: '霍州市', + 141102: '离石区', + 141121: '文水县', + 141122: '交城县', + 141123: '兴县', + 141124: '临县', + 141125: '柳林县', + 141126: '石楼县', + 141127: '岚县', + 141128: '方山县', + 141129: '中阳县', + 141130: '交口县', + 141181: '孝义市', + 141182: '汾阳市', + 150102: '新城区', + 150103: '回民区', + 150104: '玉泉区', + 150105: '赛罕区', + 150121: '土默特左旗', + 150122: '托克托县', + 150123: '和林格尔县', + 150124: '清水河县', + 150125: '武川县', + 150202: '东河区', + 150203: '昆都仑区', + 150204: '青山区', + 150205: '石拐区', + 150206: '白云鄂博矿区', + 150207: '九原区', + 150221: '土默特右旗', + 150222: '固阳县', + 150223: '达尔罕茂明安联合旗', + 150302: '海勃湾区', + 150303: '海南区', + 150304: '乌达区', + 150402: '红山区', + 150403: '元宝山区', + 150404: '松山区', + 150421: '阿鲁科尔沁旗', + 150422: '巴林左旗', + 150423: '巴林右旗', + 150424: '林西县', + 150425: '克什克腾旗', + 150426: '翁牛特旗', + 150428: '喀喇沁旗', + 150429: '宁城县', + 150430: '敖汉旗', + 150502: '科尔沁区', + 150521: '科尔沁左翼中旗', + 150522: '科尔沁左翼后旗', + 150523: '开鲁县', + 150524: '库伦旗', + 150525: '奈曼旗', + 150526: '扎鲁特旗', + 150581: '霍林郭勒市', + 150602: '东胜区', + 150603: '康巴什区', + 150621: '达拉特旗', + 150622: '准格尔旗', + 150623: '鄂托克前旗', + 150624: '鄂托克旗', + 150625: '杭锦旗', + 150626: '乌审旗', + 150627: '伊金霍洛旗', + 150702: '海拉尔区', + 150703: '扎赉诺尔区', + 150721: '阿荣旗', + 150722: '莫力达瓦达斡尔族自治旗', + 150723: '鄂伦春自治旗', + 150724: '鄂温克族自治旗', + 150725: '陈巴尔虎旗', + 150726: '新巴尔虎左旗', + 150727: '新巴尔虎右旗', + 150781: '满洲里市', + 150782: '牙克石市', + 150783: '扎兰屯市', + 150784: '额尔古纳市', + 150785: '根河市', + 150802: '临河区', + 150821: '五原县', + 150822: '磴口县', + 150823: '乌拉特前旗', + 150824: '乌拉特中旗', + 150825: '乌拉特后旗', + 150826: '杭锦后旗', + 150902: '集宁区', + 150921: '卓资县', + 150922: '化德县', + 150923: '商都县', + 150924: '兴和县', + 150925: '凉城县', + 150926: '察哈尔右翼前旗', + 150927: '察哈尔右翼中旗', + 150928: '察哈尔右翼后旗', + 150929: '四子王旗', + 150981: '丰镇市', + 152201: '乌兰浩特市', + 152202: '阿尔山市', + 152221: '科尔沁右翼前旗', + 152222: '科尔沁右翼中旗', + 152223: '扎赉特旗', + 152224: '突泉县', + 152501: '二连浩特市', + 152502: '锡林浩特市', + 152522: '阿巴嘎旗', + 152523: '苏尼特左旗', + 152524: '苏尼特右旗', + 152525: '东乌珠穆沁旗', + 152526: '西乌珠穆沁旗', + 152527: '太仆寺旗', + 152528: '镶黄旗', + 152529: '正镶白旗', + 152530: '正蓝旗', + 152531: '多伦县', + 152921: '阿拉善左旗', + 152922: '阿拉善右旗', + 152923: '额济纳旗', + 210102: '和平区', + 210103: '沈河区', + 210104: '大东区', + 210105: '皇姑区', + 210106: '铁西区', + 210111: '苏家屯区', + 210112: '浑南区', + 210113: '沈北新区', + 210114: '于洪区', + 210115: '辽中区', + 210123: '康平县', + 210124: '法库县', + 210181: '新民市', + 210190: '经济技术开发区', + 210202: '中山区', + 210203: '西岗区', + 210204: '沙河口区', + 210211: '甘井子区', + 210212: '旅顺口区', + 210213: '金州区', + 210214: '普兰店区', + 210224: '长海县', + 210281: '瓦房店市', + 210283: '庄河市', + 210302: '铁东区', + 210303: '铁西区', + 210304: '立山区', + 210311: '千山区', + 210321: '台安县', + 210323: '岫岩满族自治县', + 210381: '海城市', + 210390: '高新区', + 210402: '新抚区', + 210403: '东洲区', + 210404: '望花区', + 210411: '顺城区', + 210421: '抚顺县', + 210422: '新宾满族自治县', + 210423: '清原满族自治县', + 210502: '平山区', + 210503: '溪湖区', + 210504: '明山区', + 210505: '南芬区', + 210521: '本溪满族自治县', + 210522: '桓仁满族自治县', + 210602: '元宝区', + 210603: '振兴区', + 210604: '振安区', + 210624: '宽甸满族自治县', + 210681: '东港市', + 210682: '凤城市', + 210702: '古塔区', + 210703: '凌河区', + 210711: '太和区', + 210726: '黑山县', + 210727: '义县', + 210781: '凌海市', + 210782: '北镇市', + 210793: '经济技术开发区', + 210802: '站前区', + 210803: '西市区', + 210804: '鲅鱼圈区', + 210811: '老边区', + 210881: '盖州市', + 210882: '大石桥市', + 210902: '海州区', + 210903: '新邱区', + 210904: '太平区', + 210905: '清河门区', + 210911: '细河区', + 210921: '阜新蒙古族自治县', + 210922: '彰武县', + 211002: '白塔区', + 211003: '文圣区', + 211004: '宏伟区', + 211005: '弓长岭区', + 211011: '太子河区', + 211021: '辽阳县', + 211081: '灯塔市', + 211102: '双台子区', + 211103: '兴隆台区', + 211104: '大洼区', + 211122: '盘山县', + 211202: '银州区', + 211204: '清河区', + 211221: '铁岭县', + 211223: '西丰县', + 211224: '昌图县', + 211281: '调兵山市', + 211282: '开原市', + 211302: '双塔区', + 211303: '龙城区', + 211321: '朝阳县', + 211322: '建平县', + 211324: '喀喇沁左翼蒙古族自治县', + 211381: '北票市', + 211382: '凌源市', + 211402: '连山区', + 211403: '龙港区', + 211404: '南票区', + 211421: '绥中县', + 211422: '建昌县', + 211481: '兴城市', + 215090: '工业园区', + 220102: '南关区', + 220103: '宽城区', + 220104: '朝阳区', + 220105: '二道区', + 220106: '绿园区', + 220112: '双阳区', + 220113: '九台区', + 220122: '农安县', + 220182: '榆树市', + 220183: '德惠市', + 220192: '经济技术开发区', + 220202: '昌邑区', + 220203: '龙潭区', + 220204: '船营区', + 220211: '丰满区', + 220221: '永吉县', + 220281: '蛟河市', + 220282: '桦甸市', + 220283: '舒兰市', + 220284: '磐石市', + 220302: '铁西区', + 220303: '铁东区', + 220322: '梨树县', + 220323: '伊通满族自治县', + 220381: '公主岭市', + 220382: '双辽市', + 220402: '龙山区', + 220403: '西安区', + 220421: '东丰县', + 220422: '东辽县', + 220502: '东昌区', + 220503: '二道江区', + 220521: '通化县', + 220523: '辉南县', + 220524: '柳河县', + 220581: '梅河口市', + 220582: '集安市', + 220602: '浑江区', + 220605: '江源区', + 220621: '抚松县', + 220622: '靖宇县', + 220623: '长白朝鲜族自治县', + 220681: '临江市', + 220702: '宁江区', + 220721: '前郭尔罗斯蒙古族自治县', + 220722: '长岭县', + 220723: '乾安县', + 220781: '扶余市', + 220802: '洮北区', + 220821: '镇赉县', + 220822: '通榆县', + 220881: '洮南市', + 220882: '大安市', + 221090: '工业园区', + 222401: '延吉市', + 222402: '图们市', + 222403: '敦化市', + 222404: '珲春市', + 222405: '龙井市', + 222406: '和龙市', + 222424: '汪清县', + 222426: '安图县', + 230102: '道里区', + 230103: '南岗区', + 230104: '道外区', + 230108: '平房区', + 230109: '松北区', + 230110: '香坊区', + 230111: '呼兰区', + 230112: '阿城区', + 230113: '双城区', + 230123: '依兰县', + 230124: '方正县', + 230125: '宾县', + 230126: '巴彦县', + 230127: '木兰县', + 230128: '通河县', + 230129: '延寿县', + 230183: '尚志市', + 230184: '五常市', + 230202: '龙沙区', + 230203: '建华区', + 230204: '铁锋区', + 230205: '昂昂溪区', + 230206: '富拉尔基区', + 230207: '碾子山区', + 230208: '梅里斯达斡尔族区', + 230221: '龙江县', + 230223: '依安县', + 230224: '泰来县', + 230225: '甘南县', + 230227: '富裕县', + 230229: '克山县', + 230230: '克东县', + 230231: '拜泉县', + 230281: '讷河市', + 230302: '鸡冠区', + 230303: '恒山区', + 230304: '滴道区', + 230305: '梨树区', + 230306: '城子河区', + 230307: '麻山区', + 230321: '鸡东县', + 230381: '虎林市', + 230382: '密山市', + 230402: '向阳区', + 230403: '工农区', + 230404: '南山区', + 230405: '兴安区', + 230406: '东山区', + 230407: '兴山区', + 230421: '萝北县', + 230422: '绥滨县', + 230502: '尖山区', + 230503: '岭东区', + 230505: '四方台区', + 230506: '宝山区', + 230521: '集贤县', + 230522: '友谊县', + 230523: '宝清县', + 230524: '饶河县', + 230602: '萨尔图区', + 230603: '龙凤区', + 230604: '让胡路区', + 230605: '红岗区', + 230606: '大同区', + 230621: '肇州县', + 230622: '肇源县', + 230623: '林甸县', + 230624: '杜尔伯特蒙古族自治县', + 230702: '伊春区', + 230703: '南岔区', + 230704: '友好区', + 230705: '西林区', + 230706: '翠峦区', + 230707: '新青区', + 230708: '美溪区', + 230709: '金山屯区', + 230710: '五营区', + 230711: '乌马河区', + 230712: '汤旺河区', + 230713: '带岭区', + 230714: '乌伊岭区', + 230715: '红星区', + 230716: '上甘岭区', + 230722: '嘉荫县', + 230781: '铁力市', + 230803: '向阳区', + 230804: '前进区', + 230805: '东风区', + 230811: '郊区', + 230822: '桦南县', + 230826: '桦川县', + 230828: '汤原县', + 230881: '同江市', + 230882: '富锦市', + 230883: '抚远市', + 230902: '新兴区', + 230903: '桃山区', + 230904: '茄子河区', + 230921: '勃利县', + 231002: '东安区', + 231003: '阳明区', + 231004: '爱民区', + 231005: '西安区', + 231025: '林口县', + 231081: '绥芬河市', + 231083: '海林市', + 231084: '宁安市', + 231085: '穆棱市', + 231086: '东宁市', + 231102: '爱辉区', + 231121: '嫩江县', + 231123: '逊克县', + 231124: '孙吴县', + 231181: '北安市', + 231182: '五大连池市', + 231202: '北林区', + 231221: '望奎县', + 231222: '兰西县', + 231223: '青冈县', + 231224: '庆安县', + 231225: '明水县', + 231226: '绥棱县', + 231281: '安达市', + 231282: '肇东市', + 231283: '海伦市', + 232721: '呼玛县', + 232722: '塔河县', + 232723: '漠河县', + 232790: '松岭区', + 232791: '呼中区', + 232792: '加格达奇区', + 232793: '新林区', + 310101: '黄浦区', + 310104: '徐汇区', + 310105: '长宁区', + 310106: '静安区', + 310107: '普陀区', + 310109: '虹口区', + 310110: '杨浦区', + 310112: '闵行区', + 310113: '宝山区', + 310114: '嘉定区', + 310115: '浦东新区', + 310116: '金山区', + 310117: '松江区', + 310118: '青浦区', + 310120: '奉贤区', + 310151: '崇明区', + 320102: '玄武区', + 320104: '秦淮区', + 320105: '建邺区', + 320106: '鼓楼区', + 320111: '浦口区', + 320113: '栖霞区', + 320114: '雨花台区', + 320115: '江宁区', + 320116: '六合区', + 320117: '溧水区', + 320118: '高淳区', + 320205: '锡山区', + 320206: '惠山区', + 320211: '滨湖区', + 320213: '梁溪区', + 320214: '新吴区', + 320281: '江阴市', + 320282: '宜兴市', + 320302: '鼓楼区', + 320303: '云龙区', + 320305: '贾汪区', + 320311: '泉山区', + 320312: '铜山区', + 320321: '丰县', + 320322: '沛县', + 320324: '睢宁县', + 320381: '新沂市', + 320382: '邳州市', + 320391: '工业园区', + 320402: '天宁区', + 320404: '钟楼区', + 320411: '新北区', + 320412: '武进区', + 320413: '金坛区', + 320481: '溧阳市', + 320505: '虎丘区', + 320506: '吴中区', + 320507: '相城区', + 320508: '姑苏区', + 320509: '吴江区', + 320581: '常熟市', + 320582: '张家港市', + 320583: '昆山市', + 320585: '太仓市', + 320590: '工业园区', + 320591: '高新区', + 320602: '崇川区', + 320611: '港闸区', + 320612: '通州区', + 320621: '海安县', + 320623: '如东县', + 320681: '启东市', + 320682: '如皋市', + 320684: '海门市', + 320691: '高新区', + 320703: '连云区', + 320706: '海州区', + 320707: '赣榆区', + 320722: '东海县', + 320723: '灌云县', + 320724: '灌南县', + 320803: '淮安区', + 320804: '淮阴区', + 320812: '清江浦区', + 320813: '洪泽区', + 320826: '涟水县', + 320830: '盱眙县', + 320831: '金湖县', + 320890: '经济开发区', + 320902: '亭湖区', + 320903: '盐都区', + 320904: '大丰区', + 320921: '响水县', + 320922: '滨海县', + 320923: '阜宁县', + 320924: '射阳县', + 320925: '建湖县', + 320981: '东台市', + 321002: '广陵区', + 321003: '邗江区', + 321012: '江都区', + 321023: '宝应县', + 321081: '仪征市', + 321084: '高邮市', + 321090: '经济开发区', + 321102: '京口区', + 321111: '润州区', + 321112: '丹徒区', + 321181: '丹阳市', + 321182: '扬中市', + 321183: '句容市', + 321202: '海陵区', + 321203: '高港区', + 321204: '姜堰区', + 321281: '兴化市', + 321282: '靖江市', + 321283: '泰兴市', + 321302: '宿城区', + 321311: '宿豫区', + 321322: '沭阳县', + 321323: '泗阳县', + 321324: '泗洪县', + 330102: '上城区', + 330103: '下城区', + 330104: '江干区', + 330105: '拱墅区', + 330106: '西湖区', + 330108: '滨江区', + 330109: '萧山区', + 330110: '余杭区', + 330111: '富阳区', + 330112: '临安区', + 330122: '桐庐县', + 330127: '淳安县', + 330182: '建德市', + 330203: '海曙区', + 330205: '江北区', + 330206: '北仑区', + 330211: '镇海区', + 330212: '鄞州区', + 330213: '奉化区', + 330225: '象山县', + 330226: '宁海县', + 330281: '余姚市', + 330282: '慈溪市', + 330302: '鹿城区', + 330303: '龙湾区', + 330304: '瓯海区', + 330305: '洞头区', + 330324: '永嘉县', + 330326: '平阳县', + 330327: '苍南县', + 330328: '文成县', + 330329: '泰顺县', + 330381: '瑞安市', + 330382: '乐清市', + 330402: '南湖区', + 330411: '秀洲区', + 330421: '嘉善县', + 330424: '海盐县', + 330481: '海宁市', + 330482: '平湖市', + 330483: '桐乡市', + 330502: '吴兴区', + 330503: '南浔区', + 330521: '德清县', + 330522: '长兴县', + 330523: '安吉县', + 330602: '越城区', + 330603: '柯桥区', + 330604: '上虞区', + 330624: '新昌县', + 330681: '诸暨市', + 330683: '嵊州市', + 330702: '婺城区', + 330703: '金东区', + 330723: '武义县', + 330726: '浦江县', + 330727: '磐安县', + 330781: '兰溪市', + 330782: '义乌市', + 330783: '东阳市', + 330784: '永康市', + 330802: '柯城区', + 330803: '衢江区', + 330822: '常山县', + 330824: '开化县', + 330825: '龙游县', + 330881: '江山市', + 330902: '定海区', + 330903: '普陀区', + 330921: '岱山县', + 330922: '嵊泗县', + 331002: '椒江区', + 331003: '黄岩区', + 331004: '路桥区', + 331022: '三门县', + 331023: '天台县', + 331024: '仙居县', + 331081: '温岭市', + 331082: '临海市', + 331083: '玉环市', + 331102: '莲都区', + 331121: '青田县', + 331122: '缙云县', + 331123: '遂昌县', + 331124: '松阳县', + 331125: '云和县', + 331126: '庆元县', + 331127: '景宁畲族自治县', + 331181: '龙泉市', + 340102: '瑶海区', + 340103: '庐阳区', + 340104: '蜀山区', + 340111: '包河区', + 340121: '长丰县', + 340122: '肥东县', + 340123: '肥西县', + 340124: '庐江县', + 340181: '巢湖市', + 340190: '高新技术开发区', + 340191: '经济技术开发区', + 340202: '镜湖区', + 340203: '弋江区', + 340207: '鸠江区', + 340208: '三山区', + 340221: '芜湖县', + 340222: '繁昌县', + 340223: '南陵县', + 340225: '无为县', + 340302: '龙子湖区', + 340303: '蚌山区', + 340304: '禹会区', + 340311: '淮上区', + 340321: '怀远县', + 340322: '五河县', + 340323: '固镇县', + 340402: '大通区', + 340403: '田家庵区', + 340404: '谢家集区', + 340405: '八公山区', + 340406: '潘集区', + 340421: '凤台县', + 340422: '寿县', + 340503: '花山区', + 340504: '雨山区', + 340506: '博望区', + 340521: '当涂县', + 340522: '含山县', + 340523: '和县', + 340602: '杜集区', + 340603: '相山区', + 340604: '烈山区', + 340621: '濉溪县', + 340705: '铜官区', + 340706: '义安区', + 340711: '郊区', + 340722: '枞阳县', + 340802: '迎江区', + 340803: '大观区', + 340811: '宜秀区', + 340822: '怀宁县', + 340824: '潜山县', + 340825: '太湖县', + 340826: '宿松县', + 340827: '望江县', + 340828: '岳西县', + 340881: '桐城市', + 341002: '屯溪区', + 341003: '黄山区', + 341004: '徽州区', + 341021: '歙县', + 341022: '休宁县', + 341023: '黟县', + 341024: '祁门县', + 341102: '琅琊区', + 341103: '南谯区', + 341122: '来安县', + 341124: '全椒县', + 341125: '定远县', + 341126: '凤阳县', + 341181: '天长市', + 341182: '明光市', + 341202: '颍州区', + 341203: '颍东区', + 341204: '颍泉区', + 341221: '临泉县', + 341222: '太和县', + 341225: '阜南县', + 341226: '颍上县', + 341282: '界首市', + 341302: '埇桥区', + 341321: '砀山县', + 341322: '萧县', + 341323: '灵璧县', + 341324: '泗县', + 341390: '经济开发区', + 341502: '金安区', + 341503: '裕安区', + 341504: '叶集区', + 341522: '霍邱县', + 341523: '舒城县', + 341524: '金寨县', + 341525: '霍山县', + 341602: '谯城区', + 341621: '涡阳县', + 341622: '蒙城县', + 341623: '利辛县', + 341702: '贵池区', + 341721: '东至县', + 341722: '石台县', + 341723: '青阳县', + 341802: '宣州区', + 341821: '郎溪县', + 341822: '广德县', + 341823: '泾县', + 341824: '绩溪县', + 341825: '旌德县', + 341881: '宁国市', + 350102: '鼓楼区', + 350103: '台江区', + 350104: '仓山区', + 350105: '马尾区', + 350111: '晋安区', + 350112: '长乐区', + 350121: '闽侯县', + 350122: '连江县', + 350123: '罗源县', + 350124: '闽清县', + 350125: '永泰县', + 350128: '平潭县', + 350181: '福清市', + 350203: '思明区', + 350205: '海沧区', + 350206: '湖里区', + 350211: '集美区', + 350212: '同安区', + 350213: '翔安区', + 350302: '城厢区', + 350303: '涵江区', + 350304: '荔城区', + 350305: '秀屿区', + 350322: '仙游县', + 350402: '梅列区', + 350403: '三元区', + 350421: '明溪县', + 350423: '清流县', + 350424: '宁化县', + 350425: '大田县', + 350426: '尤溪县', + 350427: '沙县', + 350428: '将乐县', + 350429: '泰宁县', + 350430: '建宁县', + 350481: '永安市', + 350502: '鲤城区', + 350503: '丰泽区', + 350504: '洛江区', + 350505: '泉港区', + 350521: '惠安县', + 350524: '安溪县', + 350525: '永春县', + 350526: '德化县', + 350527: '金门县', + 350581: '石狮市', + 350582: '晋江市', + 350583: '南安市', + 350602: '芗城区', + 350603: '龙文区', + 350622: '云霄县', + 350623: '漳浦县', + 350624: '诏安县', + 350625: '长泰县', + 350626: '东山县', + 350627: '南靖县', + 350628: '平和县', + 350629: '华安县', + 350681: '龙海市', + 350702: '延平区', + 350703: '建阳区', + 350721: '顺昌县', + 350722: '浦城县', + 350723: '光泽县', + 350724: '松溪县', + 350725: '政和县', + 350781: '邵武市', + 350782: '武夷山市', + 350783: '建瓯市', + 350802: '新罗区', + 350803: '永定区', + 350821: '长汀县', + 350823: '上杭县', + 350824: '武平县', + 350825: '连城县', + 350881: '漳平市', + 350902: '蕉城区', + 350921: '霞浦县', + 350922: '古田县', + 350923: '屏南县', + 350924: '寿宁县', + 350925: '周宁县', + 350926: '柘荣县', + 350981: '福安市', + 350982: '福鼎市', + 360102: '东湖区', + 360103: '西湖区', + 360104: '青云谱区', + 360105: '湾里区', + 360111: '青山湖区', + 360112: '新建区', + 360121: '南昌县', + 360123: '安义县', + 360124: '进贤县', + 360190: '经济技术开发区', + 360192: '高新区', + 360202: '昌江区', + 360203: '珠山区', + 360222: '浮梁县', + 360281: '乐平市', + 360302: '安源区', + 360313: '湘东区', + 360321: '莲花县', + 360322: '上栗县', + 360323: '芦溪县', + 360402: '濂溪区', + 360403: '浔阳区', + 360404: '柴桑区', + 360423: '武宁县', + 360424: '修水县', + 360425: '永修县', + 360426: '德安县', + 360428: '都昌县', + 360429: '湖口县', + 360430: '彭泽县', + 360481: '瑞昌市', + 360482: '共青城市', + 360483: '庐山市', + 360490: '经济技术开发区', + 360502: '渝水区', + 360521: '分宜县', + 360602: '月湖区', + 360622: '余江县', + 360681: '贵溪市', + 360702: '章贡区', + 360703: '南康区', + 360704: '赣县区', + 360722: '信丰县', + 360723: '大余县', + 360724: '上犹县', + 360725: '崇义县', + 360726: '安远县', + 360727: '龙南县', + 360728: '定南县', + 360729: '全南县', + 360730: '宁都县', + 360731: '于都县', + 360732: '兴国县', + 360733: '会昌县', + 360734: '寻乌县', + 360735: '石城县', + 360781: '瑞金市', + 360802: '吉州区', + 360803: '青原区', + 360821: '吉安县', + 360822: '吉水县', + 360823: '峡江县', + 360824: '新干县', + 360825: '永丰县', + 360826: '泰和县', + 360827: '遂川县', + 360828: '万安县', + 360829: '安福县', + 360830: '永新县', + 360881: '井冈山市', + 360902: '袁州区', + 360921: '奉新县', + 360922: '万载县', + 360923: '上高县', + 360924: '宜丰县', + 360925: '靖安县', + 360926: '铜鼓县', + 360981: '丰城市', + 360982: '樟树市', + 360983: '高安市', + 361002: '临川区', + 361003: '东乡区', + 361021: '南城县', + 361022: '黎川县', + 361023: '南丰县', + 361024: '崇仁县', + 361025: '乐安县', + 361026: '宜黄县', + 361027: '金溪县', + 361028: '资溪县', + 361030: '广昌县', + 361102: '信州区', + 361103: '广丰区', + 361121: '上饶县', + 361123: '玉山县', + 361124: '铅山县', + 361125: '横峰县', + 361126: '弋阳县', + 361127: '余干县', + 361128: '鄱阳县', + 361129: '万年县', + 361130: '婺源县', + 361181: '德兴市', + 370102: '历下区', + 370103: '市中区', + 370104: '槐荫区', + 370105: '天桥区', + 370112: '历城区', + 370113: '长清区', + 370114: '章丘区', + 370124: '平阴县', + 370125: '济阳县', + 370126: '商河县', + 370190: '高新区', + 370202: '市南区', + 370203: '市北区', + 370211: '黄岛区', + 370212: '崂山区', + 370213: '李沧区', + 370214: '城阳区', + 370215: '即墨区', + 370281: '胶州市', + 370283: '平度市', + 370285: '莱西市', + 370290: '开发区', + 370302: '淄川区', + 370303: '张店区', + 370304: '博山区', + 370305: '临淄区', + 370306: '周村区', + 370321: '桓台县', + 370322: '高青县', + 370323: '沂源县', + 370402: '市中区', + 370403: '薛城区', + 370404: '峄城区', + 370405: '台儿庄区', + 370406: '山亭区', + 370481: '滕州市', + 370502: '东营区', + 370503: '河口区', + 370505: '垦利区', + 370522: '利津县', + 370523: '广饶县', + 370602: '芝罘区', + 370611: '福山区', + 370612: '牟平区', + 370613: '莱山区', + 370634: '长岛县', + 370681: '龙口市', + 370682: '莱阳市', + 370683: '莱州市', + 370684: '蓬莱市', + 370685: '招远市', + 370686: '栖霞市', + 370687: '海阳市', + 370690: '开发区', + 370702: '潍城区', + 370703: '寒亭区', + 370704: '坊子区', + 370705: '奎文区', + 370724: '临朐县', + 370725: '昌乐县', + 370781: '青州市', + 370782: '诸城市', + 370783: '寿光市', + 370784: '安丘市', + 370785: '高密市', + 370786: '昌邑市', + 370790: '开发区', + 370791: '高新区', + 370811: '任城区', + 370812: '兖州区', + 370826: '微山县', + 370827: '鱼台县', + 370828: '金乡县', + 370829: '嘉祥县', + 370830: '汶上县', + 370831: '泗水县', + 370832: '梁山县', + 370881: '曲阜市', + 370883: '邹城市', + 370890: '高新区', + 370902: '泰山区', + 370911: '岱岳区', + 370921: '宁阳县', + 370923: '东平县', + 370982: '新泰市', + 370983: '肥城市', + 371002: '环翠区', + 371003: '文登区', + 371082: '荣成市', + 371083: '乳山市', + 371091: '经济技术开发区', + 371102: '东港区', + 371103: '岚山区', + 371121: '五莲县', + 371122: '莒县', + 371202: '莱城区', + 371203: '钢城区', + 371302: '兰山区', + 371311: '罗庄区', + 371312: '河东区', + 371321: '沂南县', + 371322: '郯城县', + 371323: '沂水县', + 371324: '兰陵县', + 371325: '费县', + 371326: '平邑县', + 371327: '莒南县', + 371328: '蒙阴县', + 371329: '临沭县', + 371402: '德城区', + 371403: '陵城区', + 371422: '宁津县', + 371423: '庆云县', + 371424: '临邑县', + 371425: '齐河县', + 371426: '平原县', + 371427: '夏津县', + 371428: '武城县', + 371481: '乐陵市', + 371482: '禹城市', + 371502: '东昌府区', + 371521: '阳谷县', + 371522: '莘县', + 371523: '茌平县', + 371524: '东阿县', + 371525: '冠县', + 371526: '高唐县', + 371581: '临清市', + 371602: '滨城区', + 371603: '沾化区', + 371621: '惠民县', + 371622: '阳信县', + 371623: '无棣县', + 371625: '博兴县', + 371626: '邹平县', + 371702: '牡丹区', + 371703: '定陶区', + 371721: '曹县', + 371722: '单县', + 371723: '成武县', + 371724: '巨野县', + 371725: '郓城县', + 371726: '鄄城县', + 371728: '东明县', + 410102: '中原区', + 410103: '二七区', + 410104: '管城回族区', + 410105: '金水区', + 410106: '上街区', + 410108: '惠济区', + 410122: '中牟县', + 410181: '巩义市', + 410182: '荥阳市', + 410183: '新密市', + 410184: '新郑市', + 410185: '登封市', + 410190: '高新技术开发区', + 410191: '经济技术开发区', + 410202: '龙亭区', + 410203: '顺河回族区', + 410204: '鼓楼区', + 410205: '禹王台区', + 410212: '祥符区', + 410221: '杞县', + 410222: '通许县', + 410223: '尉氏县', + 410225: '兰考县', + 410302: '老城区', + 410303: '西工区', + 410304: '瀍河回族区', + 410305: '涧西区', + 410306: '吉利区', + 410311: '洛龙区', + 410322: '孟津县', + 410323: '新安县', + 410324: '栾川县', + 410325: '嵩县', + 410326: '汝阳县', + 410327: '宜阳县', + 410328: '洛宁县', + 410329: '伊川县', + 410381: '偃师市', + 410402: '新华区', + 410403: '卫东区', + 410404: '石龙区', + 410411: '湛河区', + 410421: '宝丰县', + 410422: '叶县', + 410423: '鲁山县', + 410425: '郏县', + 410481: '舞钢市', + 410482: '汝州市', + 410502: '文峰区', + 410503: '北关区', + 410505: '殷都区', + 410506: '龙安区', + 410522: '安阳县', + 410523: '汤阴县', + 410526: '滑县', + 410527: '内黄县', + 410581: '林州市', + 410590: '开发区', + 410602: '鹤山区', + 410603: '山城区', + 410611: '淇滨区', + 410621: '浚县', + 410622: '淇县', + 410702: '红旗区', + 410703: '卫滨区', + 410704: '凤泉区', + 410711: '牧野区', + 410721: '新乡县', + 410724: '获嘉县', + 410725: '原阳县', + 410726: '延津县', + 410727: '封丘县', + 410728: '长垣县', + 410781: '卫辉市', + 410782: '辉县市', + 410802: '解放区', + 410803: '中站区', + 410804: '马村区', + 410811: '山阳区', + 410821: '修武县', + 410822: '博爱县', + 410823: '武陟县', + 410825: '温县', + 410882: '沁阳市', + 410883: '孟州市', + 410902: '华龙区', + 410922: '清丰县', + 410923: '南乐县', + 410926: '范县', + 410927: '台前县', + 410928: '濮阳县', + 411002: '魏都区', + 411003: '建安区', + 411024: '鄢陵县', + 411025: '襄城县', + 411081: '禹州市', + 411082: '长葛市', + 411102: '源汇区', + 411103: '郾城区', + 411104: '召陵区', + 411121: '舞阳县', + 411122: '临颍县', + 411202: '湖滨区', + 411203: '陕州区', + 411221: '渑池县', + 411224: '卢氏县', + 411281: '义马市', + 411282: '灵宝市', + 411302: '宛城区', + 411303: '卧龙区', + 411321: '南召县', + 411322: '方城县', + 411323: '西峡县', + 411324: '镇平县', + 411325: '内乡县', + 411326: '淅川县', + 411327: '社旗县', + 411328: '唐河县', + 411329: '新野县', + 411330: '桐柏县', + 411381: '邓州市', + 411402: '梁园区', + 411403: '睢阳区', + 411421: '民权县', + 411422: '睢县', + 411423: '宁陵县', + 411424: '柘城县', + 411425: '虞城县', + 411426: '夏邑县', + 411481: '永城市', + 411502: '浉河区', + 411503: '平桥区', + 411521: '罗山县', + 411522: '光山县', + 411523: '新县', + 411524: '商城县', + 411525: '固始县', + 411526: '潢川县', + 411527: '淮滨县', + 411528: '息县', + 411602: '川汇区', + 411621: '扶沟县', + 411622: '西华县', + 411623: '商水县', + 411624: '沈丘县', + 411625: '郸城县', + 411626: '淮阳县', + 411627: '太康县', + 411628: '鹿邑县', + 411681: '项城市', + 411690: '经济开发区', + 411702: '驿城区', + 411721: '西平县', + 411722: '上蔡县', + 411723: '平舆县', + 411724: '正阳县', + 411725: '确山县', + 411726: '泌阳县', + 411727: '汝南县', + 411728: '遂平县', + 411729: '新蔡县', + 419001: '济源市', + 420102: '江岸区', + 420103: '江汉区', + 420104: '硚口区', + 420105: '汉阳区', + 420106: '武昌区', + 420107: '青山区', + 420111: '洪山区', + 420112: '东西湖区', + 420113: '汉南区', + 420114: '蔡甸区', + 420115: '江夏区', + 420116: '黄陂区', + 420117: '新洲区', + 420202: '黄石港区', + 420203: '西塞山区', + 420204: '下陆区', + 420205: '铁山区', + 420222: '阳新县', + 420281: '大冶市', + 420302: '茅箭区', + 420303: '张湾区', + 420304: '郧阳区', + 420322: '郧西县', + 420323: '竹山县', + 420324: '竹溪县', + 420325: '房县', + 420381: '丹江口市', + 420502: '西陵区', + 420503: '伍家岗区', + 420504: '点军区', + 420505: '猇亭区', + 420506: '夷陵区', + 420525: '远安县', + 420526: '兴山县', + 420527: '秭归县', + 420528: '长阳土家族自治县', + 420529: '五峰土家族自治县', + 420581: '宜都市', + 420582: '当阳市', + 420583: '枝江市', + 420590: '经济开发区', + 420602: '襄城区', + 420606: '樊城区', + 420607: '襄州区', + 420624: '南漳县', + 420625: '谷城县', + 420626: '保康县', + 420682: '老河口市', + 420683: '枣阳市', + 420684: '宜城市', + 420702: '梁子湖区', + 420703: '华容区', + 420704: '鄂城区', + 420802: '东宝区', + 420804: '掇刀区', + 420821: '京山县', + 420822: '沙洋县', + 420881: '钟祥市', + 420902: '孝南区', + 420921: '孝昌县', + 420922: '大悟县', + 420923: '云梦县', + 420981: '应城市', + 420982: '安陆市', + 420984: '汉川市', + 421002: '沙市区', + 421003: '荆州区', + 421022: '公安县', + 421023: '监利县', + 421024: '江陵县', + 421081: '石首市', + 421083: '洪湖市', + 421087: '松滋市', + 421102: '黄州区', + 421121: '团风县', + 421122: '红安县', + 421123: '罗田县', + 421124: '英山县', + 421125: '浠水县', + 421126: '蕲春县', + 421127: '黄梅县', + 421181: '麻城市', + 421182: '武穴市', + 421202: '咸安区', + 421221: '嘉鱼县', + 421222: '通城县', + 421223: '崇阳县', + 421224: '通山县', + 421281: '赤壁市', + 421303: '曾都区', + 421321: '随县', + 421381: '广水市', + 422801: '恩施市', + 422802: '利川市', + 422822: '建始县', + 422823: '巴东县', + 422825: '宣恩县', + 422826: '咸丰县', + 422827: '来凤县', + 422828: '鹤峰县', + 429004: '仙桃市', + 429005: '潜江市', + 429006: '天门市', + 429021: '神农架林区', + 430102: '芙蓉区', + 430103: '天心区', + 430104: '岳麓区', + 430105: '开福区', + 430111: '雨花区', + 430112: '望城区', + 430121: '长沙县', + 430181: '浏阳市', + 430182: '宁乡市', + 430202: '荷塘区', + 430203: '芦淞区', + 430204: '石峰区', + 430211: '天元区', + 430221: '株洲县', + 430223: '攸县', + 430224: '茶陵县', + 430225: '炎陵县', + 430281: '醴陵市', + 430302: '雨湖区', + 430304: '岳塘区', + 430321: '湘潭县', + 430381: '湘乡市', + 430382: '韶山市', + 430405: '珠晖区', + 430406: '雁峰区', + 430407: '石鼓区', + 430408: '蒸湘区', + 430412: '南岳区', + 430421: '衡阳县', + 430422: '衡南县', + 430423: '衡山县', + 430424: '衡东县', + 430426: '祁东县', + 430481: '耒阳市', + 430482: '常宁市', + 430502: '双清区', + 430503: '大祥区', + 430511: '北塔区', + 430521: '邵东县', + 430522: '新邵县', + 430523: '邵阳县', + 430524: '隆回县', + 430525: '洞口县', + 430527: '绥宁县', + 430528: '新宁县', + 430529: '城步苗族自治县', + 430581: '武冈市', + 430602: '岳阳楼区', + 430603: '云溪区', + 430611: '君山区', + 430621: '岳阳县', + 430623: '华容县', + 430624: '湘阴县', + 430626: '平江县', + 430681: '汨罗市', + 430682: '临湘市', + 430702: '武陵区', + 430703: '鼎城区', + 430721: '安乡县', + 430722: '汉寿县', + 430723: '澧县', + 430724: '临澧县', + 430725: '桃源县', + 430726: '石门县', + 430781: '津市市', + 430802: '永定区', + 430811: '武陵源区', + 430821: '慈利县', + 430822: '桑植县', + 430902: '资阳区', + 430903: '赫山区', + 430921: '南县', + 430922: '桃江县', + 430923: '安化县', + 430981: '沅江市', + 431002: '北湖区', + 431003: '苏仙区', + 431021: '桂阳县', + 431022: '宜章县', + 431023: '永兴县', + 431024: '嘉禾县', + 431025: '临武县', + 431026: '汝城县', + 431027: '桂东县', + 431028: '安仁县', + 431081: '资兴市', + 431102: '零陵区', + 431103: '冷水滩区', + 431121: '祁阳县', + 431122: '东安县', + 431123: '双牌县', + 431124: '道县', + 431125: '江永县', + 431126: '宁远县', + 431127: '蓝山县', + 431128: '新田县', + 431129: '江华瑶族自治县', + 431202: '鹤城区', + 431221: '中方县', + 431222: '沅陵县', + 431223: '辰溪县', + 431224: '溆浦县', + 431225: '会同县', + 431226: '麻阳苗族自治县', + 431227: '新晃侗族自治县', + 431228: '芷江侗族自治县', + 431229: '靖州苗族侗族自治县', + 431230: '通道侗族自治县', + 431281: '洪江市', + 431302: '娄星区', + 431321: '双峰县', + 431322: '新化县', + 431381: '冷水江市', + 431382: '涟源市', + 433101: '吉首市', + 433122: '泸溪县', + 433123: '凤凰县', + 433124: '花垣县', + 433125: '保靖县', + 433126: '古丈县', + 433127: '永顺县', + 433130: '龙山县', + 440103: '荔湾区', + 440104: '越秀区', + 440105: '海珠区', + 440106: '天河区', + 440111: '白云区', + 440112: '黄埔区', + 440113: '番禺区', + 440114: '花都区', + 440115: '南沙区', + 440117: '从化区', + 440118: '增城区', + 440203: '武江区', + 440204: '浈江区', + 440205: '曲江区', + 440222: '始兴县', + 440224: '仁化县', + 440229: '翁源县', + 440232: '乳源瑶族自治县', + 440233: '新丰县', + 440281: '乐昌市', + 440282: '南雄市', + 440303: '罗湖区', + 440304: '福田区', + 440305: '南山区', + 440306: '宝安区', + 440307: '龙岗区', + 440308: '盐田区', + 440309: '龙华区', + 440310: '坪山区', + 440402: '香洲区', + 440403: '斗门区', + 440404: '金湾区', + 440507: '龙湖区', + 440511: '金平区', + 440512: '濠江区', + 440513: '潮阳区', + 440514: '潮南区', + 440515: '澄海区', + 440523: '南澳县', + 440604: '禅城区', + 440605: '南海区', + 440606: '顺德区', + 440607: '三水区', + 440608: '高明区', + 440703: '蓬江区', + 440704: '江海区', + 440705: '新会区', + 440781: '台山市', + 440783: '开平市', + 440784: '鹤山市', + 440785: '恩平市', + 440802: '赤坎区', + 440803: '霞山区', + 440804: '坡头区', + 440811: '麻章区', + 440823: '遂溪县', + 440825: '徐闻县', + 440881: '廉江市', + 440882: '雷州市', + 440883: '吴川市', + 440890: '经济技术开发区', + 440902: '茂南区', + 440904: '电白区', + 440981: '高州市', + 440982: '化州市', + 440983: '信宜市', + 441202: '端州区', + 441203: '鼎湖区', + 441204: '高要区', + 441223: '广宁县', + 441224: '怀集县', + 441225: '封开县', + 441226: '德庆县', + 441284: '四会市', + 441302: '惠城区', + 441303: '惠阳区', + 441322: '博罗县', + 441323: '惠东县', + 441324: '龙门县', + 441402: '梅江区', + 441403: '梅县区', + 441422: '大埔县', + 441423: '丰顺县', + 441424: '五华县', + 441426: '平远县', + 441427: '蕉岭县', + 441481: '兴宁市', + 441502: '城区', + 441521: '海丰县', + 441523: '陆河县', + 441581: '陆丰市', + 441602: '源城区', + 441621: '紫金县', + 441622: '龙川县', + 441623: '连平县', + 441624: '和平县', + 441625: '东源县', + 441702: '江城区', + 441704: '阳东区', + 441721: '阳西县', + 441781: '阳春市', + 441802: '清城区', + 441803: '清新区', + 441821: '佛冈县', + 441823: '阳山县', + 441825: '连山壮族瑶族自治县', + 441826: '连南瑶族自治县', + 441881: '英德市', + 441882: '连州市', + 441901: '中堂镇', + 441903: '南城区', + 441904: '长安镇', + 441905: '东坑镇', + 441906: '樟木头镇', + 441907: '莞城区', + 441908: '石龙镇', + 441909: '桥头镇', + 441910: '万江区', + 441911: '麻涌镇', + 441912: '虎门镇', + 441913: '谢岗镇', + 441914: '石碣镇', + 441915: '茶山镇', + 441916: '东城区', + 441917: '洪梅镇', + 441918: '道滘镇', + 441919: '高埗镇', + 441920: '企石镇', + 441921: '凤岗镇', + 441922: '大岭山镇', + 441923: '松山湖', + 441924: '清溪镇', + 441925: '望牛墩镇', + 441926: '厚街镇', + 441927: '常平镇', + 441928: '寮步镇', + 441929: '石排镇', + 441930: '横沥镇', + 441931: '塘厦镇', + 441932: '黄江镇', + 441933: '大朗镇', + 441990: '沙田镇', + 442001: '南头镇', + 442002: '神湾镇', + 442003: '东凤镇', + 442004: '五桂山镇', + 442005: '黄圃镇', + 442006: '小榄镇', + 442007: '石岐区街道', + 442008: '横栏镇', + 442009: '三角镇', + 442010: '三乡镇', + 442011: '港口镇', + 442012: '沙溪镇', + 442013: '板芙镇', + 442014: '沙朗镇', + 442015: '东升镇', + 442016: '阜沙镇', + 442017: '民众镇', + 442018: '东区街道', + 442019: '火炬开发区', + 442020: '西区街道', + 442021: '南区街道', + 442022: '古镇', + 442023: '坦洲镇', + 442024: '大涌镇', + 442025: '南朗镇', + 445102: '湘桥区', + 445103: '潮安区', + 445122: '饶平县', + 445202: '榕城区', + 445203: '揭东区', + 445222: '揭西县', + 445224: '惠来县', + 445281: '普宁市', + 445302: '云城区', + 445303: '云安区', + 445321: '新兴县', + 445322: '郁南县', + 445381: '罗定市', + 450102: '兴宁区', + 450103: '青秀区', + 450105: '江南区', + 450107: '西乡塘区', + 450108: '良庆区', + 450109: '邕宁区', + 450110: '武鸣区', + 450123: '隆安县', + 450124: '马山县', + 450125: '上林县', + 450126: '宾阳县', + 450127: '横县', + 450202: '城中区', + 450203: '鱼峰区', + 450204: '柳南区', + 450205: '柳北区', + 450206: '柳江区', + 450222: '柳城县', + 450223: '鹿寨县', + 450224: '融安县', + 450225: '融水苗族自治县', + 450226: '三江侗族自治县', + 450302: '秀峰区', + 450303: '叠彩区', + 450304: '象山区', + 450305: '七星区', + 450311: '雁山区', + 450312: '临桂区', + 450321: '阳朔县', + 450323: '灵川县', + 450324: '全州县', + 450325: '兴安县', + 450326: '永福县', + 450327: '灌阳县', + 450328: '龙胜各族自治县', + 450329: '资源县', + 450330: '平乐县', + 450331: '荔浦县', + 450332: '恭城瑶族自治县', + 450403: '万秀区', + 450405: '长洲区', + 450406: '龙圩区', + 450421: '苍梧县', + 450422: '藤县', + 450423: '蒙山县', + 450481: '岑溪市', + 450502: '海城区', + 450503: '银海区', + 450512: '铁山港区', + 450521: '合浦县', + 450602: '港口区', + 450603: '防城区', + 450621: '上思县', + 450681: '东兴市', + 450702: '钦南区', + 450703: '钦北区', + 450721: '灵山县', + 450722: '浦北县', + 450802: '港北区', + 450803: '港南区', + 450804: '覃塘区', + 450821: '平南县', + 450881: '桂平市', + 450902: '玉州区', + 450903: '福绵区', + 450921: '容县', + 450922: '陆川县', + 450923: '博白县', + 450924: '兴业县', + 450981: '北流市', + 451002: '右江区', + 451021: '田阳县', + 451022: '田东县', + 451023: '平果县', + 451024: '德保县', + 451026: '那坡县', + 451027: '凌云县', + 451028: '乐业县', + 451029: '田林县', + 451030: '西林县', + 451031: '隆林各族自治县', + 451081: '靖西市', + 451102: '八步区', + 451103: '平桂区', + 451121: '昭平县', + 451122: '钟山县', + 451123: '富川瑶族自治县', + 451202: '金城江区', + 451203: '宜州区', + 451221: '南丹县', + 451222: '天峨县', + 451223: '凤山县', + 451224: '东兰县', + 451225: '罗城仫佬族自治县', + 451226: '环江毛南族自治县', + 451227: '巴马瑶族自治县', + 451228: '都安瑶族自治县', + 451229: '大化瑶族自治县', + 451302: '兴宾区', + 451321: '忻城县', + 451322: '象州县', + 451323: '武宣县', + 451324: '金秀瑶族自治县', + 451381: '合山市', + 451402: '江州区', + 451421: '扶绥县', + 451422: '宁明县', + 451423: '龙州县', + 451424: '大新县', + 451425: '天等县', + 451481: '凭祥市', + 460105: '秀英区', + 460106: '龙华区', + 460107: '琼山区', + 460108: '美兰区', + 460202: '海棠区', + 460203: '吉阳区', + 460204: '天涯区', + 460205: '崖州区', + 460321: '西沙群岛', + 460322: '南沙群岛', + 460323: '中沙群岛的岛礁及其海域', + 460401: '那大镇', + 460402: '和庆镇', + 460403: '南丰镇', + 460404: '大成镇', + 460405: '雅星镇', + 460406: '兰洋镇', + 460407: '光村镇', + 460408: '木棠镇', + 460409: '海头镇', + 460410: '峨蔓镇', + 460411: '王五镇', + 460412: '白马井镇', + 460413: '中和镇', + 460414: '排浦镇', + 460415: '东成镇', + 460416: '新州镇', + 469001: '五指山市', + 469002: '琼海市', + 469005: '文昌市', + 469006: '万宁市', + 469007: '东方市', + 469021: '定安县', + 469022: '屯昌县', + 469023: '澄迈县', + 469024: '临高县', + 469025: '白沙黎族自治县', + 469026: '昌江黎族自治县', + 469027: '乐东黎族自治县', + 469028: '陵水黎族自治县', + 469029: '保亭黎族苗族自治县', + 469030: '琼中黎族苗族自治县', + 500101: '万州区', + 500102: '涪陵区', + 500103: '渝中区', + 500104: '大渡口区', + 500105: '江北区', + 500106: '沙坪坝区', + 500107: '九龙坡区', + 500108: '南岸区', + 500109: '北碚区', + 500110: '綦江区', + 500111: '大足区', + 500112: '渝北区', + 500113: '巴南区', + 500114: '黔江区', + 500115: '长寿区', + 500116: '江津区', + 500117: '合川区', + 500118: '永川区', + 500119: '南川区', + 500120: '璧山区', + 500151: '铜梁区', + 500152: '潼南区', + 500153: '荣昌区', + 500154: '开州区', + 500155: '梁平区', + 500156: '武隆区', + 500229: '城口县', + 500230: '丰都县', + 500231: '垫江县', + 500233: '忠县', + 500235: '云阳县', + 500236: '奉节县', + 500237: '巫山县', + 500238: '巫溪县', + 500240: '石柱土家族自治县', + 500241: '秀山土家族苗族自治县', + 500242: '酉阳土家族苗族自治县', + 500243: '彭水苗族土家族自治县', + 510104: '锦江区', + 510105: '青羊区', + 510106: '金牛区', + 510107: '武侯区', + 510108: '成华区', + 510112: '龙泉驿区', + 510113: '青白江区', + 510114: '新都区', + 510115: '温江区', + 510116: '双流区', + 510117: '郫都区', + 510121: '金堂县', + 510129: '大邑县', + 510131: '蒲江县', + 510132: '新津县', + 510181: '都江堰市', + 510182: '彭州市', + 510183: '邛崃市', + 510184: '崇州市', + 510185: '简阳市', + 510191: '高新区', + 510302: '自流井区', + 510303: '贡井区', + 510304: '大安区', + 510311: '沿滩区', + 510321: '荣县', + 510322: '富顺县', + 510402: '东区', + 510403: '西区', + 510411: '仁和区', + 510421: '米易县', + 510422: '盐边县', + 510502: '江阳区', + 510503: '纳溪区', + 510504: '龙马潭区', + 510521: '泸县', + 510522: '合江县', + 510524: '叙永县', + 510525: '古蔺县', + 510603: '旌阳区', + 510604: '罗江区', + 510623: '中江县', + 510681: '广汉市', + 510682: '什邡市', + 510683: '绵竹市', + 510703: '涪城区', + 510704: '游仙区', + 510705: '安州区', + 510722: '三台县', + 510723: '盐亭县', + 510725: '梓潼县', + 510726: '北川羌族自治县', + 510727: '平武县', + 510781: '江油市', + 510791: '高新区', + 510802: '利州区', + 510811: '昭化区', + 510812: '朝天区', + 510821: '旺苍县', + 510822: '青川县', + 510823: '剑阁县', + 510824: '苍溪县', + 510903: '船山区', + 510904: '安居区', + 510921: '蓬溪县', + 510922: '射洪县', + 510923: '大英县', + 511002: '市中区', + 511011: '东兴区', + 511024: '威远县', + 511025: '资中县', + 511083: '隆昌市', + 511102: '市中区', + 511111: '沙湾区', + 511112: '五通桥区', + 511113: '金口河区', + 511123: '犍为县', + 511124: '井研县', + 511126: '夹江县', + 511129: '沐川县', + 511132: '峨边彝族自治县', + 511133: '马边彝族自治县', + 511181: '峨眉山市', + 511302: '顺庆区', + 511303: '高坪区', + 511304: '嘉陵区', + 511321: '南部县', + 511322: '营山县', + 511323: '蓬安县', + 511324: '仪陇县', + 511325: '西充县', + 511381: '阆中市', + 511402: '东坡区', + 511403: '彭山区', + 511421: '仁寿县', + 511423: '洪雅县', + 511424: '丹棱县', + 511425: '青神县', + 511502: '翠屏区', + 511503: '南溪区', + 511521: '宜宾县', + 511523: '江安县', + 511524: '长宁县', + 511525: '高县', + 511526: '珙县', + 511527: '筠连县', + 511528: '兴文县', + 511529: '屏山县', + 511602: '广安区', + 511603: '前锋区', + 511621: '岳池县', + 511622: '武胜县', + 511623: '邻水县', + 511681: '华蓥市', + 511702: '通川区', + 511703: '达川区', + 511722: '宣汉县', + 511723: '开江县', + 511724: '大竹县', + 511725: '渠县', + 511781: '万源市', + 511802: '雨城区', + 511803: '名山区', + 511822: '荥经县', + 511823: '汉源县', + 511824: '石棉县', + 511825: '天全县', + 511826: '芦山县', + 511827: '宝兴县', + 511902: '巴州区', + 511903: '恩阳区', + 511921: '通江县', + 511922: '南江县', + 511923: '平昌县', + 512002: '雁江区', + 512021: '安岳县', + 512022: '乐至县', + 513201: '马尔康市', + 513221: '汶川县', + 513222: '理县', + 513223: '茂县', + 513224: '松潘县', + 513225: '九寨沟县', + 513226: '金川县', + 513227: '小金县', + 513228: '黑水县', + 513230: '壤塘县', + 513231: '阿坝县', + 513232: '若尔盖县', + 513233: '红原县', + 513301: '康定市', + 513322: '泸定县', + 513323: '丹巴县', + 513324: '九龙县', + 513325: '雅江县', + 513326: '道孚县', + 513327: '炉霍县', + 513328: '甘孜县', + 513329: '新龙县', + 513330: '德格县', + 513331: '白玉县', + 513332: '石渠县', + 513333: '色达县', + 513334: '理塘县', + 513335: '巴塘县', + 513336: '乡城县', + 513337: '稻城县', + 513338: '得荣县', + 513401: '西昌市', + 513422: '木里藏族自治县', + 513423: '盐源县', + 513424: '德昌县', + 513425: '会理县', + 513426: '会东县', + 513427: '宁南县', + 513428: '普格县', + 513429: '布拖县', + 513430: '金阳县', + 513431: '昭觉县', + 513432: '喜德县', + 513433: '冕宁县', + 513434: '越西县', + 513435: '甘洛县', + 513436: '美姑县', + 513437: '雷波县', + 520102: '南明区', + 520103: '云岩区', + 520111: '花溪区', + 520112: '乌当区', + 520113: '白云区', + 520115: '观山湖区', + 520121: '开阳县', + 520122: '息烽县', + 520123: '修文县', + 520181: '清镇市', + 520201: '钟山区', + 520203: '六枝特区', + 520221: '水城县', + 520281: '盘州市', + 520302: '红花岗区', + 520303: '汇川区', + 520304: '播州区', + 520322: '桐梓县', + 520323: '绥阳县', + 520324: '正安县', + 520325: '道真仡佬族苗族自治县', + 520326: '务川仡佬族苗族自治县', + 520327: '凤冈县', + 520328: '湄潭县', + 520329: '余庆县', + 520330: '习水县', + 520381: '赤水市', + 520382: '仁怀市', + 520402: '西秀区', + 520403: '平坝区', + 520422: '普定县', + 520423: '镇宁布依族苗族自治县', + 520424: '关岭布依族苗族自治县', + 520425: '紫云苗族布依族自治县', + 520502: '七星关区', + 520521: '大方县', + 520522: '黔西县', + 520523: '金沙县', + 520524: '织金县', + 520525: '纳雍县', + 520526: '威宁彝族回族苗族自治县', + 520527: '赫章县', + 520602: '碧江区', + 520603: '万山区', + 520621: '江口县', + 520622: '玉屏侗族自治县', + 520623: '石阡县', + 520624: '思南县', + 520625: '印江土家族苗族自治县', + 520626: '德江县', + 520627: '沿河土家族自治县', + 520628: '松桃苗族自治县', + 522301: '兴义市', + 522322: '兴仁县', + 522323: '普安县', + 522324: '晴隆县', + 522325: '贞丰县', + 522326: '望谟县', + 522327: '册亨县', + 522328: '安龙县', + 522601: '凯里市', + 522622: '黄平县', + 522623: '施秉县', + 522624: '三穗县', + 522625: '镇远县', + 522626: '岑巩县', + 522627: '天柱县', + 522628: '锦屏县', + 522629: '剑河县', + 522630: '台江县', + 522631: '黎平县', + 522632: '榕江县', + 522633: '从江县', + 522634: '雷山县', + 522635: '麻江县', + 522636: '丹寨县', + 522701: '都匀市', + 522702: '福泉市', + 522722: '荔波县', + 522723: '贵定县', + 522725: '瓮安县', + 522726: '独山县', + 522727: '平塘县', + 522728: '罗甸县', + 522729: '长顺县', + 522730: '龙里县', + 522731: '惠水县', + 522732: '三都水族自治县', + 530102: '五华区', + 530103: '盘龙区', + 530111: '官渡区', + 530112: '西山区', + 530113: '东川区', + 530114: '呈贡区', + 530115: '晋宁区', + 530124: '富民县', + 530125: '宜良县', + 530126: '石林彝族自治县', + 530127: '嵩明县', + 530128: '禄劝彝族苗族自治县', + 530129: '寻甸回族彝族自治县', + 530181: '安宁市', + 530302: '麒麟区', + 530303: '沾益区', + 530321: '马龙县', + 530322: '陆良县', + 530323: '师宗县', + 530324: '罗平县', + 530325: '富源县', + 530326: '会泽县', + 530381: '宣威市', + 530402: '红塔区', + 530403: '江川区', + 530422: '澄江县', + 530423: '通海县', + 530424: '华宁县', + 530425: '易门县', + 530426: '峨山彝族自治县', + 530427: '新平彝族傣族自治县', + 530428: '元江哈尼族彝族傣族自治县', + 530502: '隆阳区', + 530521: '施甸县', + 530523: '龙陵县', + 530524: '昌宁县', + 530581: '腾冲市', + 530602: '昭阳区', + 530621: '鲁甸县', + 530622: '巧家县', + 530623: '盐津县', + 530624: '大关县', + 530625: '永善县', + 530626: '绥江县', + 530627: '镇雄县', + 530628: '彝良县', + 530629: '威信县', + 530630: '水富县', + 530702: '古城区', + 530721: '玉龙纳西族自治县', + 530722: '永胜县', + 530723: '华坪县', + 530724: '宁蒗彝族自治县', + 530802: '思茅区', + 530821: '宁洱哈尼族彝族自治县', + 530822: '墨江哈尼族自治县', + 530823: '景东彝族自治县', + 530824: '景谷傣族彝族自治县', + 530825: '镇沅彝族哈尼族拉祜族自治县', + 530826: '江城哈尼族彝族自治县', + 530827: '孟连傣族拉祜族佤族自治县', + 530828: '澜沧拉祜族自治县', + 530829: '西盟佤族自治县', + 530902: '临翔区', + 530921: '凤庆县', + 530922: '云县', + 530923: '永德县', + 530924: '镇康县', + 530925: '双江拉祜族佤族布朗族傣族自治县', + 530926: '耿马傣族佤族自治县', + 530927: '沧源佤族自治县', + 532301: '楚雄市', + 532322: '双柏县', + 532323: '牟定县', + 532324: '南华县', + 532325: '姚安县', + 532326: '大姚县', + 532327: '永仁县', + 532328: '元谋县', + 532329: '武定县', + 532331: '禄丰县', + 532501: '个旧市', + 532502: '开远市', + 532503: '蒙自市', + 532504: '弥勒市', + 532523: '屏边苗族自治县', + 532524: '建水县', + 532525: '石屏县', + 532527: '泸西县', + 532528: '元阳县', + 532529: '红河县', + 532530: '金平苗族瑶族傣族自治县', + 532531: '绿春县', + 532532: '河口瑶族自治县', + 532601: '文山市', + 532622: '砚山县', + 532623: '西畴县', + 532624: '麻栗坡县', + 532625: '马关县', + 532626: '丘北县', + 532627: '广南县', + 532628: '富宁县', + 532801: '景洪市', + 532822: '勐海县', + 532823: '勐腊县', + 532901: '大理市', + 532922: '漾濞彝族自治县', + 532923: '祥云县', + 532924: '宾川县', + 532925: '弥渡县', + 532926: '南涧彝族自治县', + 532927: '巍山彝族回族自治县', + 532928: '永平县', + 532929: '云龙县', + 532930: '洱源县', + 532931: '剑川县', + 532932: '鹤庆县', + 533102: '瑞丽市', + 533103: '芒市', + 533122: '梁河县', + 533123: '盈江县', + 533124: '陇川县', + 533301: '泸水市', + 533323: '福贡县', + 533324: '贡山独龙族怒族自治县', + 533325: '兰坪白族普米族自治县', + 533401: '香格里拉市', + 533422: '德钦县', + 533423: '维西傈僳族自治县', + 540102: '城关区', + 540103: '堆龙德庆区', + 540104: '达孜区', + 540121: '林周县', + 540122: '当雄县', + 540123: '尼木县', + 540124: '曲水县', + 540127: '墨竹工卡县', + 540202: '桑珠孜区', + 540221: '南木林县', + 540222: '江孜县', + 540223: '定日县', + 540224: '萨迦县', + 540225: '拉孜县', + 540226: '昂仁县', + 540227: '谢通门县', + 540228: '白朗县', + 540229: '仁布县', + 540230: '康马县', + 540231: '定结县', + 540232: '仲巴县', + 540233: '亚东县', + 540234: '吉隆县', + 540235: '聂拉木县', + 540236: '萨嘎县', + 540237: '岗巴县', + 540302: '卡若区', + 540321: '江达县', + 540322: '贡觉县', + 540323: '类乌齐县', + 540324: '丁青县', + 540325: '察雅县', + 540326: '八宿县', + 540327: '左贡县', + 540328: '芒康县', + 540329: '洛隆县', + 540330: '边坝县', + 540402: '巴宜区', + 540421: '工布江达县', + 540422: '米林县', + 540423: '墨脱县', + 540424: '波密县', + 540425: '察隅县', + 540426: '朗县', + 540502: '乃东区', + 540521: '扎囊县', + 540522: '贡嘎县', + 540523: '桑日县', + 540524: '琼结县', + 540525: '曲松县', + 540526: '措美县', + 540527: '洛扎县', + 540528: '加查县', + 540529: '隆子县', + 540530: '错那县', + 540531: '浪卡子县', + 540602: '色尼区', + 542421: '那曲县', + 542422: '嘉黎县', + 542423: '比如县', + 542424: '聂荣县', + 542425: '安多县', + 542426: '申扎县', + 542427: '索县', + 542428: '班戈县', + 542429: '巴青县', + 542430: '尼玛县', + 542431: '双湖县', + 542521: '普兰县', + 542522: '札达县', + 542523: '噶尔县', + 542524: '日土县', + 542525: '革吉县', + 542526: '改则县', + 542527: '措勤县', + 610102: '新城区', + 610103: '碑林区', + 610104: '莲湖区', + 610111: '灞桥区', + 610112: '未央区', + 610113: '雁塔区', + 610114: '阎良区', + 610115: '临潼区', + 610116: '长安区', + 610117: '高陵区', + 610118: '鄠邑区', + 610122: '蓝田县', + 610124: '周至县', + 610202: '王益区', + 610203: '印台区', + 610204: '耀州区', + 610222: '宜君县', + 610302: '渭滨区', + 610303: '金台区', + 610304: '陈仓区', + 610322: '凤翔县', + 610323: '岐山县', + 610324: '扶风县', + 610326: '眉县', + 610327: '陇县', + 610328: '千阳县', + 610329: '麟游县', + 610330: '凤县', + 610331: '太白县', + 610402: '秦都区', + 610403: '杨陵区', + 610404: '渭城区', + 610422: '三原县', + 610423: '泾阳县', + 610424: '乾县', + 610425: '礼泉县', + 610426: '永寿县', + 610427: '彬县', + 610428: '长武县', + 610429: '旬邑县', + 610430: '淳化县', + 610431: '武功县', + 610481: '兴平市', + 610502: '临渭区', + 610503: '华州区', + 610522: '潼关县', + 610523: '大荔县', + 610524: '合阳县', + 610525: '澄城县', + 610526: '蒲城县', + 610527: '白水县', + 610528: '富平县', + 610581: '韩城市', + 610582: '华阴市', + 610602: '宝塔区', + 610603: '安塞区', + 610621: '延长县', + 610622: '延川县', + 610623: '子长县', + 610625: '志丹县', + 610626: '吴起县', + 610627: '甘泉县', + 610628: '富县', + 610629: '洛川县', + 610630: '宜川县', + 610631: '黄龙县', + 610632: '黄陵县', + 610702: '汉台区', + 610703: '南郑区', + 610722: '城固县', + 610723: '洋县', + 610724: '西乡县', + 610725: '勉县', + 610726: '宁强县', + 610727: '略阳县', + 610728: '镇巴县', + 610729: '留坝县', + 610730: '佛坪县', + 610802: '榆阳区', + 610803: '横山区', + 610822: '府谷县', + 610824: '靖边县', + 610825: '定边县', + 610826: '绥德县', + 610827: '米脂县', + 610828: '佳县', + 610829: '吴堡县', + 610830: '清涧县', + 610831: '子洲县', + 610881: '神木市', + 610902: '汉滨区', + 610921: '汉阴县', + 610922: '石泉县', + 610923: '宁陕县', + 610924: '紫阳县', + 610925: '岚皋县', + 610926: '平利县', + 610927: '镇坪县', + 610928: '旬阳县', + 610929: '白河县', + 611002: '商州区', + 611021: '洛南县', + 611022: '丹凤县', + 611023: '商南县', + 611024: '山阳县', + 611025: '镇安县', + 611026: '柞水县', + 620102: '城关区', + 620103: '七里河区', + 620104: '西固区', + 620105: '安宁区', + 620111: '红古区', + 620121: '永登县', + 620122: '皋兰县', + 620123: '榆中县', + 620201: '市辖区', + 620290: '雄关区', + 620291: '长城区', + 620292: '镜铁区', + 620293: '新城镇', + 620294: '峪泉镇', + 620295: '文殊镇', + 620302: '金川区', + 620321: '永昌县', + 620402: '白银区', + 620403: '平川区', + 620421: '靖远县', + 620422: '会宁县', + 620423: '景泰县', + 620502: '秦州区', + 620503: '麦积区', + 620521: '清水县', + 620522: '秦安县', + 620523: '甘谷县', + 620524: '武山县', + 620525: '张家川回族自治县', + 620602: '凉州区', + 620621: '民勤县', + 620622: '古浪县', + 620623: '天祝藏族自治县', + 620702: '甘州区', + 620721: '肃南裕固族自治县', + 620722: '民乐县', + 620723: '临泽县', + 620724: '高台县', + 620725: '山丹县', + 620802: '崆峒区', + 620821: '泾川县', + 620822: '灵台县', + 620823: '崇信县', + 620824: '华亭县', + 620825: '庄浪县', + 620826: '静宁县', + 620902: '肃州区', + 620921: '金塔县', + 620922: '瓜州县', + 620923: '肃北蒙古族自治县', + 620924: '阿克塞哈萨克族自治县', + 620981: '玉门市', + 620982: '敦煌市', + 621002: '西峰区', + 621021: '庆城县', + 621022: '环县', + 621023: '华池县', + 621024: '合水县', + 621025: '正宁县', + 621026: '宁县', + 621027: '镇原县', + 621102: '安定区', + 621121: '通渭县', + 621122: '陇西县', + 621123: '渭源县', + 621124: '临洮县', + 621125: '漳县', + 621126: '岷县', + 621202: '武都区', + 621221: '成县', + 621222: '文县', + 621223: '宕昌县', + 621224: '康县', + 621225: '西和县', + 621226: '礼县', + 621227: '徽县', + 621228: '两当县', + 622901: '临夏市', + 622921: '临夏县', + 622922: '康乐县', + 622923: '永靖县', + 622924: '广河县', + 622925: '和政县', + 622926: '东乡族自治县', + 622927: '积石山保安族东乡族撒拉族自治县', + 623001: '合作市', + 623021: '临潭县', + 623022: '卓尼县', + 623023: '舟曲县', + 623024: '迭部县', + 623025: '玛曲县', + 623026: '碌曲县', + 623027: '夏河县', + 630102: '城东区', + 630103: '城中区', + 630104: '城西区', + 630105: '城北区', + 630121: '大通回族土族自治县', + 630122: '湟中县', + 630123: '湟源县', + 630202: '乐都区', + 630203: '平安区', + 630222: '民和回族土族自治县', + 630223: '互助土族自治县', + 630224: '化隆回族自治县', + 630225: '循化撒拉族自治县', + 632221: '门源回族自治县', + 632222: '祁连县', + 632223: '海晏县', + 632224: '刚察县', + 632321: '同仁县', + 632322: '尖扎县', + 632323: '泽库县', + 632324: '河南蒙古族自治县', + 632521: '共和县', + 632522: '同德县', + 632523: '贵德县', + 632524: '兴海县', + 632525: '贵南县', + 632621: '玛沁县', + 632622: '班玛县', + 632623: '甘德县', + 632624: '达日县', + 632625: '久治县', + 632626: '玛多县', + 632701: '玉树市', + 632722: '杂多县', + 632723: '称多县', + 632724: '治多县', + 632725: '囊谦县', + 632726: '曲麻莱县', + 632801: '格尔木市', + 632802: '德令哈市', + 632821: '乌兰县', + 632822: '都兰县', + 632823: '天峻县', + 640104: '兴庆区', + 640105: '西夏区', + 640106: '金凤区', + 640121: '永宁县', + 640122: '贺兰县', + 640181: '灵武市', + 640202: '大武口区', + 640205: '惠农区', + 640221: '平罗县', + 640302: '利通区', + 640303: '红寺堡区', + 640323: '盐池县', + 640324: '同心县', + 640381: '青铜峡市', + 640402: '原州区', + 640422: '西吉县', + 640423: '隆德县', + 640424: '泾源县', + 640425: '彭阳县', + 640502: '沙坡头区', + 640521: '中宁县', + 640522: '海原县', + 650102: '天山区', + 650103: '沙依巴克区', + 650104: '新市区', + 650105: '水磨沟区', + 650106: '头屯河区', + 650107: '达坂城区', + 650109: '米东区', + 650121: '乌鲁木齐县', + 650202: '独山子区', + 650203: '克拉玛依区', + 650204: '白碱滩区', + 650205: '乌尔禾区', + 650402: '高昌区', + 650421: '鄯善县', + 650422: '托克逊县', + 650502: '伊州区', + 650521: '巴里坤哈萨克自治县', + 650522: '伊吾县', + 652301: '昌吉市', + 652302: '阜康市', + 652323: '呼图壁县', + 652324: '玛纳斯县', + 652325: '奇台县', + 652327: '吉木萨尔县', + 652328: '木垒哈萨克自治县', + 652701: '博乐市', + 652702: '阿拉山口市', + 652722: '精河县', + 652723: '温泉县', + 652801: '库尔勒市', + 652822: '轮台县', + 652823: '尉犁县', + 652824: '若羌县', + 652825: '且末县', + 652826: '焉耆回族自治县', + 652827: '和静县', + 652828: '和硕县', + 652829: '博湖县', + 652901: '阿克苏市', + 652922: '温宿县', + 652923: '库车县', + 652924: '沙雅县', + 652925: '新和县', + 652926: '拜城县', + 652927: '乌什县', + 652928: '阿瓦提县', + 652929: '柯坪县', + 653001: '阿图什市', + 653022: '阿克陶县', + 653023: '阿合奇县', + 653024: '乌恰县', + 653101: '喀什市', + 653121: '疏附县', + 653122: '疏勒县', + 653123: '英吉沙县', + 653124: '泽普县', + 653125: '莎车县', + 653126: '叶城县', + 653127: '麦盖提县', + 653128: '岳普湖县', + 653129: '伽师县', + 653130: '巴楚县', + 653131: '塔什库尔干塔吉克自治县', + 653201: '和田市', + 653221: '和田县', + 653222: '墨玉县', + 653223: '皮山县', + 653224: '洛浦县', + 653225: '策勒县', + 653226: '于田县', + 653227: '民丰县', + 654002: '伊宁市', + 654003: '奎屯市', + 654004: '霍尔果斯市', + 654021: '伊宁县', + 654022: '察布查尔锡伯自治县', + 654023: '霍城县', + 654024: '巩留县', + 654025: '新源县', + 654026: '昭苏县', + 654027: '特克斯县', + 654028: '尼勒克县', + 654201: '塔城市', + 654202: '乌苏市', + 654221: '额敏县', + 654223: '沙湾县', + 654224: '托里县', + 654225: '裕民县', + 654226: '和布克赛尔蒙古自治县', + 654301: '阿勒泰市', + 654321: '布尔津县', + 654322: '富蕴县', + 654323: '福海县', + 654324: '哈巴河县', + 654325: '青河县', + 654326: '吉木乃县', + 659001: '石河子市', + 659002: '阿拉尔市', + 659003: '图木舒克市', + 659004: '五家渠市', + 659005: '北屯市', + 659006: '铁门关市', + 659007: '双河市', + 659008: '可克达拉市', + 659009: '昆玉市', + 710101: '中正区', + 710102: '大同区', + 710103: '中山区', + 710104: '松山区', + 710105: '大安区', + 710106: '万华区', + 710107: '信义区', + 710108: '士林区', + 710109: '北投区', + 710110: '内湖区', + 710111: '南港区', + 710112: '文山区', + 710199: '其它区', + 710201: '新兴区', + 710202: '前金区', + 710203: '芩雅区', + 710204: '盐埕区', + 710205: '鼓山区', + 710206: '旗津区', + 710207: '前镇区', + 710208: '三民区', + 710209: '左营区', + 710210: '楠梓区', + 710211: '小港区', + 710241: '苓雅区', + 710242: '仁武区', + 710243: '大社区', + 710244: '冈山区', + 710245: '路竹区', + 710246: '阿莲区', + 710247: '田寮区', + 710248: '燕巢区', + 710249: '桥头区', + 710250: '梓官区', + 710251: '弥陀区', + 710252: '永安区', + 710253: '湖内区', + 710254: '凤山区', + 710255: '大寮区', + 710256: '林园区', + 710257: '鸟松区', + 710258: '大树区', + 710259: '旗山区', + 710260: '美浓区', + 710261: '六龟区', + 710262: '内门区', + 710263: '杉林区', + 710264: '甲仙区', + 710265: '桃源区', + 710266: '那玛夏区', + 710267: '茂林区', + 710268: '茄萣区', + 710299: '其它区', + 710301: '中西区', + 710302: '东区', + 710303: '南区', + 710304: '北区', + 710305: '安平区', + 710306: '安南区', + 710339: '永康区', + 710340: '归仁区', + 710341: '新化区', + 710342: '左镇区', + 710343: '玉井区', + 710344: '楠西区', + 710345: '南化区', + 710346: '仁德区', + 710347: '关庙区', + 710348: '龙崎区', + 710349: '官田区', + 710350: '麻豆区', + 710351: '佳里区', + 710352: '西港区', + 710353: '七股区', + 710354: '将军区', + 710355: '学甲区', + 710356: '北门区', + 710357: '新营区', + 710358: '后壁区', + 710359: '白河区', + 710360: '东山区', + 710361: '六甲区', + 710362: '下营区', + 710363: '柳营区', + 710364: '盐水区', + 710365: '善化区', + 710366: '大内区', + 710367: '山上区', + 710368: '新市区', + 710369: '安定区', + 710399: '其它区', + 710401: '中区', + 710402: '东区', + 710403: '南区', + 710404: '西区', + 710405: '北区', + 710406: '北屯区', + 710407: '西屯区', + 710408: '南屯区', + 710431: '太平区', + 710432: '大里区', + 710433: '雾峰区', + 710434: '乌日区', + 710435: '丰原区', + 710436: '后里区', + 710437: '石冈区', + 710438: '东势区', + 710439: '和平区', + 710440: '新社区', + 710441: '潭子区', + 710442: '大雅区', + 710443: '神冈区', + 710444: '大肚区', + 710445: '沙鹿区', + 710446: '龙井区', + 710447: '梧栖区', + 710448: '清水区', + 710449: '大甲区', + 710450: '外埔区', + 710451: '大安区', + 710499: '其它区', + 710507: '金沙镇', + 710508: '金湖镇', + 710509: '金宁乡', + 710510: '金城镇', + 710511: '烈屿乡', + 710512: '乌坵乡', + 710614: '南投市', + 710615: '中寮乡', + 710616: '草屯镇', + 710617: '国姓乡', + 710618: '埔里镇', + 710619: '仁爱乡', + 710620: '名间乡', + 710621: '集集镇', + 710622: '水里乡', + 710623: '鱼池乡', + 710624: '信义乡', + 710625: '竹山镇', + 710626: '鹿谷乡', + 710701: '仁爱区', + 710702: '信义区', + 710703: '中正区', + 710704: '中山区', + 710705: '安乐区', + 710706: '暖暖区', + 710707: '七堵区', + 710799: '其它区', + 710801: '东区', + 710802: '北区', + 710803: '香山区', + 710899: '其它区', + 710901: '东区', + 710902: '西区', + 710999: '其它区', + 711130: '万里区', + 711132: '板桥区', + 711133: '汐止区', + 711134: '深坑区', + 711136: '瑞芳区', + 711137: '平溪区', + 711138: '双溪区', + 711140: '新店区', + 711141: '坪林区', + 711142: '乌来区', + 711143: '永和区', + 711144: '中和区', + 711145: '土城区', + 711146: '三峡区', + 711147: '树林区', + 711149: '三重区', + 711150: '新庄区', + 711151: '泰山区', + 711152: '林口区', + 711154: '五股区', + 711155: '八里区', + 711156: '淡水区', + 711157: '三芝区', + 711287: '宜兰市', + 711288: '头城镇', + 711289: '礁溪乡', + 711290: '壮围乡', + 711291: '员山乡', + 711292: '罗东镇', + 711293: '三星乡', + 711294: '大同乡', + 711295: '五结乡', + 711296: '冬山乡', + 711297: '苏澳镇', + 711298: '南澳乡', + 711299: '钓鱼台', + 711387: '竹北市', + 711388: '湖口乡', + 711389: '新丰乡', + 711390: '新埔镇', + 711391: '关西镇', + 711392: '芎林乡', + 711393: '宝山乡', + 711394: '竹东镇', + 711395: '五峰乡', + 711396: '横山乡', + 711397: '尖石乡', + 711398: '北埔乡', + 711399: '峨眉乡', + 711487: '中坜市', + 711488: '平镇市', + 711489: '龙潭乡', + 711490: '杨梅市', + 711491: '新屋乡', + 711492: '观音乡', + 711493: '桃园市', + 711494: '龟山乡', + 711495: '八德市', + 711496: '大溪镇', + 711497: '复兴乡', + 711498: '大园乡', + 711499: '芦竹乡', + 711582: '竹南镇', + 711583: '头份镇', + 711584: '三湾乡', + 711585: '南庄乡', + 711586: '狮潭乡', + 711587: '后龙镇', + 711588: '通霄镇', + 711589: '苑里镇', + 711590: '苗栗市', + 711591: '造桥乡', + 711592: '头屋乡', + 711593: '公馆乡', + 711594: '大湖乡', + 711595: '泰安乡', + 711596: '铜锣乡', + 711597: '三义乡', + 711598: '西湖乡', + 711599: '卓兰镇', + 711774: '彰化市', + 711775: '芬园乡', + 711776: '花坛乡', + 711777: '秀水乡', + 711778: '鹿港镇', + 711779: '福兴乡', + 711780: '线西乡', + 711781: '和美镇', + 711782: '伸港乡', + 711783: '员林镇', + 711784: '社头乡', + 711785: '永靖乡', + 711786: '埔心乡', + 711787: '溪湖镇', + 711788: '大村乡', + 711789: '埔盐乡', + 711790: '田中镇', + 711791: '北斗镇', + 711792: '田尾乡', + 711793: '埤头乡', + 711794: '溪州乡', + 711795: '竹塘乡', + 711796: '二林镇', + 711797: '大城乡', + 711798: '芳苑乡', + 711799: '二水乡', + 711982: '番路乡', + 711983: '梅山乡', + 711984: '竹崎乡', + 711985: '阿里山乡', + 711986: '中埔乡', + 711987: '大埔乡', + 711988: '水上乡', + 711989: '鹿草乡', + 711990: '太保市', + 711991: '朴子市', + 711992: '东石乡', + 711993: '六脚乡', + 711994: '新港乡', + 711995: '民雄乡', + 711996: '大林镇', + 711997: '溪口乡', + 711998: '义竹乡', + 711999: '布袋镇', + 712180: '斗南镇', + 712181: '大埤乡', + 712182: '虎尾镇', + 712183: '土库镇', + 712184: '褒忠乡', + 712185: '东势乡', + 712186: '台西乡', + 712187: '仑背乡', + 712188: '麦寮乡', + 712189: '斗六市', + 712190: '林内乡', + 712191: '古坑乡', + 712192: '莿桐乡', + 712193: '西螺镇', + 712194: '二仑乡', + 712195: '北港镇', + 712196: '水林乡', + 712197: '口湖乡', + 712198: '四湖乡', + 712199: '元长乡', + 712467: '屏东市', + 712468: '三地门乡', + 712469: '雾台乡', + 712470: '玛家乡', + 712471: '九如乡', + 712472: '里港乡', + 712473: '高树乡', + 712474: '盐埔乡', + 712475: '长治乡', + 712476: '麟洛乡', + 712477: '竹田乡', + 712478: '内埔乡', + 712479: '万丹乡', + 712480: '潮州镇', + 712481: '泰武乡', + 712482: '来义乡', + 712483: '万峦乡', + 712484: '莰顶乡', + 712485: '新埤乡', + 712486: '南州乡', + 712487: '林边乡', + 712488: '东港镇', + 712489: '琉球乡', + 712490: '佳冬乡', + 712491: '新园乡', + 712492: '枋寮乡', + 712493: '枋山乡', + 712494: '春日乡', + 712495: '狮子乡', + 712496: '车城乡', + 712497: '牡丹乡', + 712498: '恒春镇', + 712499: '满州乡', + 712584: '台东市', + 712585: '绿岛乡', + 712586: '兰屿乡', + 712587: '延平乡', + 712588: '卑南乡', + 712589: '鹿野乡', + 712590: '关山镇', + 712591: '海端乡', + 712592: '池上乡', + 712593: '东河乡', + 712594: '成功镇', + 712595: '长滨乡', + 712596: '金峰乡', + 712597: '大武乡', + 712598: '达仁乡', + 712599: '太麻里乡', + 712686: '花莲市', + 712687: '新城乡', + 712688: '太鲁阁', + 712689: '秀林乡', + 712690: '吉安乡', + 712691: '寿丰乡', + 712692: '凤林镇', + 712693: '光复乡', + 712694: '丰滨乡', + 712695: '瑞穗乡', + 712696: '万荣乡', + 712697: '玉里镇', + 712698: '卓溪乡', + 712699: '富里乡', + 712794: '马公市', + 712795: '西屿乡', + 712796: '望安乡', + 712797: '七美乡', + 712798: '白沙乡', + 712799: '湖西乡', + 712896: '南竿乡', + 712897: '北竿乡', + 712898: '东引乡', + 712899: '莒光乡', + 810101: '中西区', + 810102: '湾仔', + 810103: '东区', + 810104: '南区', + 810201: '九龙城区', + 810202: '油尖旺区', + 810203: '深水埗区', + 810204: '黄大仙区', + 810205: '观塘区', + 810301: '北区', + 810302: '大埔区', + 810303: '沙田区', + 810304: '西贡区', + 810305: '元朗区', + 810306: '屯门区', + 810307: '荃湾区', + 810308: '葵青区', + 810309: '离岛区', + 820101: '澳门半岛', + 820201: '离岛' + } + }; + + + Class.prototype.config = { + elem: '', + data: { + province: '--选择省--', + city: '--选择市--', + county: '--选择区--', + }, + change: function(result){} + }; + + Class.prototype.index = 0; + + Class.prototype.render = function () { + let that = this, options = that.config; + options.elem = $(options.elem); + options.bindAction = $(options.bindAction); + + that.events(); + }; + + Class.prototype.events = function () { + let that = this, options = that.config, index; + let provinceFilter = 'province-' + layarea._id; + let cityFilter = 'city-' + layarea._id; + let countyFilter = 'county-' + layarea._id; + + let provinceEl = options.elem.find('.province-selector'); + let cityEl = options.elem.find('.city-selector'); + let countyEl = options.elem.find('.county-selector'); + + //filter + if(provinceEl.attr('lay-filter')){ + provinceFilter = provinceEl.attr('lay-filter'); + } + if(cityEl.attr('lay-filter')){ + cityFilter = cityEl.attr('lay-filter'); + } + if(countyEl.attr('lay-filter')){ + countyFilter = countyEl.attr('lay-filter'); + } + provinceEl.attr('lay-filter', provinceFilter); + cityEl.attr('lay-filter', cityFilter); + countyEl.attr('lay-filter', countyFilter); + + //获取默认值 + if(provinceEl.data('value')){ + options.data.province = provinceEl.data('value'); + } + if(cityEl.data('value')){ + options.data.city = cityEl.data('value'); + } + if(countyEl.data('value')){ + options.data.county = countyEl.data('value'); + } + provinceEl.attr('lay-filter', provinceFilter); + cityEl.attr('lay-filter', cityFilter); + countyEl.attr('lay-filter', countyFilter); + + //监听结果 + form.on('select('+provinceFilter+')', function(data){ + options.data.province = data.value; + let code = getCode('province', data.value); + renderCity(code); + + options.change(options.data); + }); + form.on('select('+cityFilter+')', function(data){ + options.data.city = data.value; + let code = getCode('city', data.value); + renderCounty(code); + + options.change(options.data); + }); + form.on('select('+countyFilter+')', function(data){ + options.data.county = data.value; + + options.change(options.data); + }); + + renderProvince(); + + //查找province + function renderProvince(){ + let tpl = ''; + let provinceList = getList("province"); + let currentCode = ''; + let currentName = ''; + provinceList.forEach(function(_item){ + if (!currentCode){ + currentCode = _item.code; + currentName = _item.name; + } + if(_item.name === options.data.province){ + currentCode = _item.code; + currentName = _item.name; + } + tpl += ''; + }); + options.data.province = currentName; + provinceEl.html(tpl); + provinceEl.val(options.data.province); + form.render('select'); + renderCity(currentCode); + } + + function renderCity(provinceCode){ + let tpl = ''; + let cityList = getList('city', provinceCode.slice(0, 2)); + let currentCode = ''; + let currentName = ''; + cityList.forEach(function(_item){ + if (!currentCode){ + currentCode = _item.code; + currentName = _item.name; + } + if(_item.name === options.data.city){ + currentCode = _item.code; + currentName = _item.name; + } + tpl += ''; + }); + options.data.city = currentName; + cityEl.html(tpl); + cityEl.val(options.data.city); + form.render('select'); + renderCounty(currentCode); + } + + function renderCounty(cityCode){ + let tpl = ''; + let countyList = getList('county', cityCode.slice(0, 4)); + let currentCode = ''; + let currentName = ''; + countyList.forEach(function(_item){ + if (!currentCode){ + currentCode = _item.code; + currentName = _item.name; + } + if(_item.name === options.data.county){ + currentCode = _item.code; + currentName = _item.name; + } + tpl += ''; + }); + options.data.county = currentName; + countyEl.html(tpl); + countyEl.val(options.data.county); + + form.render('select'); + } + + function getList(type, code) { + let result = []; + + if (type !== 'province' && !code) { + return result; + } + + let list = areaList[type + "_list"] || {}; + result = Object.keys(list).map(function (code) { + return { + code: code, + name: list[code] + }; + }); + + if (code) { + // oversea code + if (code[0] === '9' && type === 'city') { + code = '9'; + } + + result = result.filter(function (item) { + return item.code.indexOf(code) === 0; + }); + } + + return result; + } + + function getCode(type, name){ + let code = ''; + let list = areaList[type + "_list"] || {}; + layui.each(list, function(_code, _name){ + if(_name === name){ + code = _code; + } + }); + + return code; + } + }; + + layarea.render = function (options) { + let inst = new Class(options); + layarea._id++; + return thisArea.call(inst); + }; + + //暴露接口 + exports('layarea', layarea); + }); \ No newline at end of file diff --git a/public/dist/notice.css b/public/dist/notice.css new file mode 100644 index 0000000..65c9fea --- /dev/null +++ b/public/dist/notice.css @@ -0,0 +1,228 @@ +.toast-title { + font-weight: bold; +} +.toast-message { + -ms-word-wrap: break-word; + word-wrap: break-word; +} +.toast-message a, +.toast-message label { + color: #FFFFFF; +} +.toast-message a:hover { + color: #CCCCCC; + text-decoration: none; +} +.toast-close-button { + position: relative; + right: -0.3em; + top: -0.3em; + float: right; + font-size: 20px; + font-weight: bold; + color: #FFFFFF; + -webkit-text-shadow: 0 1px 0 #ffffff; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); + line-height: 1; +} +.toast-close-button:hover, +.toast-close-button:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +.rtl .toast-close-button { + left: -0.3em; + float: left; + right: 0.3em; +} +/*Additional properties for button version + iOS requires the button element instead of an anchor tag. + If you want the anchor version, it requires `href="#"`.*/ +button.toast-close-button { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.toast-top-center { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-center { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-full-width { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-full-width { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-left { + top: 12px; + left: 12px; +} +.toast-top-right { + top: 12px; + right: 12px; +} +.toast-bottom-right { + right: 12px; + bottom: 12px; +} +.toast-bottom-left { + bottom: 12px; + left: 12px; +} +#toast-container { + position: fixed; + z-index: 999999; + pointer-events: none; + /*overrides*/ +} +#toast-container * { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +#toast-container > div { + position: relative; + pointer-events: auto; + overflow: hidden; + margin: 0 0 6px; + padding: 15px 15px 15px 50px; + width: 300px; + -moz-border-radius: 3px 3px 3px 3px; + -webkit-border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; + background-position: 15px center; + background-repeat: no-repeat; + -moz-box-shadow: 0 0 12px #999999; + -webkit-box-shadow: 0 0 12px #999999; + box-shadow: 0 0 12px #999999; + color: #FFFFFF; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} +#toast-container > div.rtl { + direction: rtl; + padding: 15px 50px 15px 15px; + background-position: right 15px center; +} +#toast-container > div:hover { + -moz-box-shadow: 0 0 12px #000000; + -webkit-box-shadow: 0 0 12px #000000; + box-shadow: 0 0 12px #000000; + opacity: 1; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + filter: alpha(opacity=100); + cursor: pointer; +} +#toast-container > .toast-info { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; +} +#toast-container > .toast-success { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; +} +#toast-container > .toast-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; +} +#toast-container.toast-top-center > div, +#toast-container.toast-bottom-center > div { + width: 300px; + margin-left: auto; + margin-right: auto; +} +#toast-container.toast-top-full-width > div, +#toast-container.toast-bottom-full-width > div { + width: 96%; + margin-left: auto; + margin-right: auto; +} +.toast { + background-color: #030303; +} +.toast-success { + background-color: #51A351; +} +.toast-error { + background-color: #BD362F; +} +.toast-info { + background-color: #2F96B4; +} +.toast-warning { + background-color: #F89406; +} +.toast-progress { + position: absolute; + left: 0; + bottom: 0; + height: 4px; + background-color: #000000; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} +/*Responsive Design*/ +@media all and (max-width: 240px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 11em; + } + #toast-container > div.rtl { + padding: 8px 50px 8px 8px; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } + #toast-container .rtl .toast-close-button { + left: -0.2em; + right: 0.2em; + } +} +@media all and (min-width: 241px) and (max-width: 480px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 18em; + } + #toast-container > div.rtl { + padding: 8px 50px 8px 8px; + } + #toast-container .toast-close-button { + right: -0.2em; + top: -0.2em; + } + #toast-container .rtl .toast-close-button { + left: -0.2em; + right: 0.2em; + } +} +@media all and (min-width: 481px) and (max-width: 768px) { + #toast-container > div { + padding: 15px 15px 15px 50px; + width: 25em; + } + #toast-container > div.rtl { + padding: 15px 50px 15px 15px; + } +} \ No newline at end of file diff --git a/public/dist/notice.js b/public/dist/notice.js new file mode 100644 index 0000000..475070a --- /dev/null +++ b/public/dist/notice.js @@ -0,0 +1,486 @@ +/* + * Toastr + * Copyright 2012-2015 + * Authors: John Papa, Hans Fjällemark, and Tim Ferrell. + * All Rights Reserved. + * Use, reproduction, distribution, and modification of this code is subject to the terms and + * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php + * + * ARIA Support: Greta Krafsig + * + * Project: https://github.com/CodeSeven/toastr + */ +/* global define */ +(function (define) { + define(['jquery'], function ($) { + return (function () { + var $container; + var listener; + var toastId = 0; + var toastType = { + error: 'error', + info: 'info', + success: 'success', + warning: 'warning' + }; + + var cssStyle = $(''); + $("body").append(cssStyle); + + var toastr = { + clear: clear, + remove: remove, + error: error, + getContainer: getContainer, + info: info, + options: {}, + subscribe: subscribe, + success: success, + version: '2.1.4', + warning: warning + }; + + var previousToast; + + return toastr; + + //////////////// + + function error(message, title, optionsOverride) { + return notify({ + type: toastType.error, + iconClass: getOptions().iconClasses.error, + message: message, + optionsOverride: optionsOverride, + title: title + }); + } + + function getContainer(options, create) { + if (!options) { options = getOptions(); } + $container = $('#' + options.containerId); + if ($container.length) { + return $container; + } + if (create) { + $container = createContainer(options); + } + return $container; + } + + function info(message, title, optionsOverride) { + return notify({ + type: toastType.info, + iconClass: getOptions().iconClasses.info, + message: message, + optionsOverride: optionsOverride, + title: title + }); + } + + function subscribe(callback) { + listener = callback; + } + + function success(message, title, optionsOverride) { + return notify({ + type: toastType.success, + iconClass: getOptions().iconClasses.success, + message: message, + optionsOverride: optionsOverride, + title: title + }); + } + + function warning(message, title, optionsOverride) { + return notify({ + type: toastType.warning, + iconClass: getOptions().iconClasses.warning, + message: message, + optionsOverride: optionsOverride, + title: title + }); + } + + function clear($toastElement, clearOptions) { + var options = getOptions(); + if (!$container) { getContainer(options); } + if (!clearToast($toastElement, options, clearOptions)) { + clearContainer(options); + } + } + + function remove($toastElement) { + var options = getOptions(); + if (!$container) { getContainer(options); } + if ($toastElement && $(':focus', $toastElement).length === 0) { + removeToast($toastElement); + return; + } + if ($container.children().length) { + $container.remove(); + } + } + + // internal functions + + function clearContainer (options) { + var toastsToClear = $container.children(); + for (var i = toastsToClear.length - 1; i >= 0; i--) { + clearToast($(toastsToClear[i]), options); + } + } + + function clearToast ($toastElement, options, clearOptions) { + var force = clearOptions && clearOptions.force ? clearOptions.force : false; + if ($toastElement && (force || $(':focus', $toastElement).length === 0)) { + $toastElement[options.hideMethod]({ + duration: options.hideDuration, + easing: options.hideEasing, + complete: function () { removeToast($toastElement); } + }); + return true; + } + return false; + } + + function createContainer(options) { + $container = $('
') + .attr('id', options.containerId) + .addClass(options.positionClass); + + $container.appendTo($(options.target)); + return $container; + } + + function getDefaults() { + return { + tapToDismiss: true, + toastClass: 'toast', + containerId: 'toast-container', + debug: false, + + showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery + showDuration: 300, + showEasing: 'swing', //swing and linear are built into jQuery + onShown: undefined, + hideMethod: 'fadeOut', + hideDuration: 1000, + hideEasing: 'swing', + onHidden: undefined, + closeMethod: false, + closeDuration: false, + closeEasing: false, + closeOnHover: true, + + extendedTimeOut: 1000, + iconClasses: { + error: 'toast-error', + info: 'toast-info', + success: 'toast-success', + warning: 'toast-warning' + }, + iconClass: 'toast-info', + positionClass: 'toast-top-right', + timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky + titleClass: 'toast-title', + messageClass: 'toast-message', + escapeHtml: false, + target: 'body', + closeHtml: '', + closeClass: 'toast-close-button', + newestOnTop: true, + preventDuplicates: false, + progressBar: false, + progressClass: 'toast-progress', + rtl: false + }; + } + + function publish(args) { + if (!listener) { return; } + listener(args); + } + + function notify(map) { + var options = getOptions(); + var iconClass = map.iconClass || options.iconClass; + + if (typeof (map.optionsOverride) !== 'undefined') { + options = $.extend(options, map.optionsOverride); + iconClass = map.optionsOverride.iconClass || iconClass; + } + + if (shouldExit(options, map)) { return; } + + toastId++; + + $container = getContainer(options, true); + + var intervalId = null; + var $toastElement = $('
'); + var $titleElement = $('
'); + var $messageElement = $('
'); + var $progressElement = $('
'); + var $closeElement = $(options.closeHtml); + var progressBar = { + intervalId: null, + hideEta: null, + maxHideTime: null + }; + var response = { + toastId: toastId, + state: 'visible', + startTime: new Date(), + options: options, + map: map + }; + + personalizeToast(); + + displayToast(); + + handleEvents(); + + publish(response); + + if (options.debug && console) { + console.log(response); + } + + return $toastElement; + + function escapeHtml(source) { + if (source == null) { + source = ''; + } + + return source + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + } + + function personalizeToast() { + setIcon(); + setTitle(); + setMessage(); + setCloseButton(); + setProgressBar(); + setRTL(); + setSequence(); + setAria(); + } + + function setAria() { + var ariaValue = ''; + switch (map.iconClass) { + case 'toast-success': + case 'toast-info': + ariaValue = 'polite'; + break; + default: + ariaValue = 'assertive'; + } + $toastElement.attr('aria-live', ariaValue); + } + + function handleEvents() { + if (options.closeOnHover) { + $toastElement.hover(stickAround, delayedHideToast); + } + + if (!options.onclick && options.tapToDismiss) { + $toastElement.click(hideToast); + } + + if (options.closeButton && $closeElement) { + $closeElement.click(function (event) { + if (event.stopPropagation) { + event.stopPropagation(); + } else if (event.cancelBubble !== undefined && event.cancelBubble !== true) { + event.cancelBubble = true; + } + + if (options.onCloseClick) { + options.onCloseClick(event); + } + + hideToast(true); + }); + } + + if (options.onclick) { + $toastElement.click(function (event) { + options.onclick(event); + hideToast(); + }); + } + } + + function displayToast() { + $toastElement.hide(); + + $toastElement[options.showMethod]( + {duration: options.showDuration, easing: options.showEasing, complete: options.onShown} + ); + + if (options.timeOut > 0) { + intervalId = setTimeout(hideToast, options.timeOut); + progressBar.maxHideTime = parseFloat(options.timeOut); + progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime; + if (options.progressBar) { + progressBar.intervalId = setInterval(updateProgress, 10); + } + } + } + + function setIcon() { + if (map.iconClass) { + $toastElement.addClass(options.toastClass).addClass(iconClass); + } + } + + function setSequence() { + if (options.newestOnTop) { + $container.prepend($toastElement); + } else { + $container.append($toastElement); + } + } + + function setTitle() { + if (map.title) { + var suffix = map.title; + if (options.escapeHtml) { + suffix = escapeHtml(map.title); + } + $titleElement.append(suffix).addClass(options.titleClass); + $toastElement.append($titleElement); + } + } + + function setMessage() { + if (map.message) { + var suffix = map.message; + if (options.escapeHtml) { + suffix = escapeHtml(map.message); + } + $messageElement.append(suffix).addClass(options.messageClass); + $toastElement.append($messageElement); + } + } + + function setCloseButton() { + if (options.closeButton) { + $closeElement.addClass(options.closeClass).attr('role', 'button'); + $toastElement.prepend($closeElement); + } + } + + function setProgressBar() { + if (options.progressBar) { + $progressElement.addClass(options.progressClass); + $toastElement.prepend($progressElement); + } + } + + function setRTL() { + if (options.rtl) { + $toastElement.addClass('rtl'); + } + } + + function shouldExit(options, map) { + if (options.preventDuplicates) { + if (map.message === previousToast) { + return true; + } else { + previousToast = map.message; + } + } + return false; + } + + function hideToast(override) { + var method = override && options.closeMethod !== false ? options.closeMethod : options.hideMethod; + var duration = override && options.closeDuration !== false ? + options.closeDuration : options.hideDuration; + var easing = override && options.closeEasing !== false ? options.closeEasing : options.hideEasing; + if ($(':focus', $toastElement).length && !override) { + return; + } + clearTimeout(progressBar.intervalId); + return $toastElement[method]({ + duration: duration, + easing: easing, + complete: function () { + removeToast($toastElement); + clearTimeout(intervalId); + if (options.onHidden && response.state !== 'hidden') { + options.onHidden(); + } + response.state = 'hidden'; + response.endTime = new Date(); + publish(response); + } + }); + } + + function delayedHideToast() { + if (options.timeOut > 0 || options.extendedTimeOut > 0) { + intervalId = setTimeout(hideToast, options.extendedTimeOut); + progressBar.maxHideTime = parseFloat(options.extendedTimeOut); + progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime; + } + } + + function stickAround() { + clearTimeout(intervalId); + progressBar.hideEta = 0; + $toastElement.stop(true, true)[options.showMethod]( + {duration: options.showDuration, easing: options.showEasing} + ); + } + + function updateProgress() { + var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100; + $progressElement.width(percentage + '%'); + } + } + + function getOptions() { + return $.extend({}, getDefaults(), toastr.options); + } + + function removeToast($toastElement) { + if (!$container) { $container = getContainer(); } + if ($toastElement.is(':visible')) { + return; + } + $toastElement.remove(); + $toastElement = null; + if ($container.children().length === 0) { + $container.remove(); + previousToast = undefined; + } + } + + })(); + }); +}(typeof define === 'function' && define.amd ? define : function (deps, factory) { + if (typeof module !== 'undefined' && module.exports) { //Node + module.exports = factory(require('jquery')); + } + else if (window.layui && layui.define){ + layui.define('jquery', function (exports) { //layui加载 + exports('toastr', factory(layui.jquery)); + exports('notice', factory(layui.jquery)); + }); + } + else { + window.toastr = factory(window.jQuery); + } +})); \ No newline at end of file diff --git a/public/dist/sliderVerify/sliderVerify.js b/public/dist/sliderVerify/sliderVerify.js new file mode 100644 index 0000000..540c0bb --- /dev/null +++ b/public/dist/sliderVerify/sliderVerify.js @@ -0,0 +1 @@ +layui.define(["jquery","layer","form"],function(s){var d=layui.jquery,b=layui.form,r=layui.layer,k=layui.device(),h={read:(function(){var u=".slider-item{height:38px;line-height:38px;background-color:#d0d0d0;position:relative;border: 1px solid white;}.slider-bg{position:absolute;width:40px;height:100%;z-index:100}.slider-btn{width:40px;height:96%;position:absolute;border:1px solid #ccc;cursor:move;text-align:center;background-color:#fff;user-select:none;color:#666;z-index:120}.slider-btn-success{font-size:26px}.slider-text{position:absolute;text-align:center;width:100%;height:100%;user-select:none;font-size:14px;color:#fff;z-index:120}.slider-error{animation:glow 800ms ease-out infinite alternate;}@keyframes glow{0%{border-color:#e6e6e6}100%{border-color:#ff5722}}",v=document.createElement("style");v.innerHTML=u;v.type="text/css";(d("head link:last")[0]&&d("head link:last").after(v))||d("head").append(v)})()},m=function(u){return u[0]},o=function(){var u=this;return{isOk:function(){return u.isOk.call(u)},reset:function(){return u.reset.call(u)},version:"1.6"}},g="sliderVerify",i="slider-btn",j="slider-bg",q="slider-text",a="layui-icon-next",t="layui-icon-ok-circle",f="slider-btn-success",n="layui-bg-green",c="slider-error",p="请拖动滑块验证",e="验证通过",l=function(u){var v=this;v.config=d.extend({},v.config,u);v.render()};l.prototype.config={elem:"",onOk:null,isOk:false,isAutoVerify:true,bg:n,text:p};l.prototype.render=function(){var w=this,u=w.config,v=d(u.elem);if(!v[0]){return}if(u.domid){u.domid.remove()}u.domid=w.createIdNum();var x=d(['
','
','
'+u.text+"
",'
'].join(""));v.hide().after(x);u.domid=d("#"+u.domid);w.events();if(u.isAutoVerify){b.verify({sliderVerify:function(y,z){if(!y){z.classList.add(c);return u.text}}})}};l.prototype.isMobile=function(){return(k.os=="ios"||k.os=="android"||k.android||k.ios)||(k.weixin&&typeof k.weixin===Boolean)};l.prototype.createIdNum=function(){return(g+(+new Date()).toString()+Math.random().toString().substr(2,7))};l.prototype.isOk=function(){return this.config.isOk};l.prototype.error=function(u){return r.msg(u,{icon:5})};l.prototype.distance=function(){var u=this.config.container;return u.box.offsetWidth-u.btn.offsetWidth};l.prototype.reset=function(){this.config.isOk=false;return this.render()};l.prototype.resize=function(w){var v=this,u=v.config.container;var w=w||v.distance();u.btn.style.left=w+"px";u.bg.style.width=w+"px"};l.prototype.cancelTransition=function(){var u=this.config.container;this.config.domid[0].classList.remove(c);u.btn.style.transition="";u.bg.style.transition=""};l.prototype.down=function(y){var x=this,w=x.config,v=w.container,y=y||window.event,z=y.clientX||y.touches[0].clientX;x.cancelTransition();var u=function(A){x.move(z,A)};x.events.move=u;if(x.isMobile()){document.addEventListener("touchmove",x.events.move)}else{document.onmousemove=u}if(navigator.userAgent.indexOf("UCBrowser")>-1){y.preventDefault()}};l.prototype.move=function(B,z){var y=this,x=y.config,v=x.container;var z=z||window.event;let moveX=z.clientX||z.touches[0].clientX;var u=moveX-B;if(u>v.distance){u=v.distance}else{if(u<0){u=0}}v.btn.style.left=u+"px";v.bg.style.width=u+"px";if(u==v.distance){v.text.innerHTML=e;var w=window.getComputedStyle?window.getComputedStyle(v.bg,null):v.bg.currentStyle;v.btn.style.border="1px solid "+w.backgroundColor;v.btn.style.color=w.backgroundColor;v.btn.classList.remove(a);v.btn.classList.add(t,f);x.isOk=true;v.box.value=true;if(y.isMobile()){v.btn.removeEventListener("touchstart",y.events.down,false);document.removeEventListener("touchmove",y.events.move,false)}else{v.btn.onmousedown=null;document.onmousemove=null}x.onOk&&typeof x.onOk=="function"&&x.onOk();return}var A=function(C){y.stop(C)};y.events.seup=A;if(y.isMobile()){document.addEventListener("touchend",A)}else{document.onmouseup=A}};l.prototype.stop=function(x){var w=this,v=w.config,u=v.container;if(w.isOk()){return}else{u.btn.style.left=0;u.bg.style.width=0;u.btn.style.transition="left 1s";u.bg.style.transition="width 1s"}document.onmousemove=null;document.onmouseup=null;if(w.isMobile()){document.removeEventListener("touchmove",w.events.move,false);document.removeEventListener("touchend",w.events.seup,false)}};l.prototype.events=function(){var z=this,y=z.config;if(!y.domid){return z.error("创建滑块验证失败")}var x=y.domid.find("."+i),w=y.domid.find("."+j),A=y.domid.find("."+q),v={box:m(y.domid),btn:m(x),bg:m(w),text:m(A)};y.container=v;v.distance=z.distance();var B=function(C){z.down(C)};z.events.down=B;if(z.isMobile()){v.btn.addEventListener("touchstart",z.events.down)}else{v.btn.onmousedown=B}var u=d(window);u.on("resize",y.domid,function(){if(z.config.isOk){z.resize()}})};h.render=function(u){var v=new l(u);return o.call(v)};s(g,h)}); \ No newline at end of file diff --git a/public/dist/xm-select.js b/public/dist/xm-select.js new file mode 100644 index 0000000..2f10468 --- /dev/null +++ b/public/dist/xm-select.js @@ -0,0 +1,8 @@ +/*! + * @Title: xm-select + * @Version: 1.1.8 + * @Description:基于layui的多选解决方案 + * @Site: https://gitee.com/maplemei/xm-select + * @Author: maplemei + * @License:Apache License 2.0 + */!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="./",n(n.s=213)}({104:function(e,t){e.exports=function(e){var t="undefined"!=typeof window&&window.location;if(!t)throw new Error("fixUrls requires window.location");if(!e||"string"!=typeof e)return e;var n=t.protocol+"//"+t.host,o=n+t.pathname.replace(/\/[^\/]*$/,"/");return e.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi,(function(e,t){var r,i=t.trim().replace(/^"(.*)"$/,(function(e,t){return t})).replace(/^'(.*)'$/,(function(e,t){return t}));return/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/|\s*$)/i.test(i)?e:(r=0===i.indexOf("//")?i:0===i.indexOf("/")?n+i:o+i.replace(/^\.\//,""),"url("+JSON.stringify(r)+")")}))}},213:function(e,t,n){"use strict";n.r(t),function(e){n(215),n(216),n(218);var t=n(65);window.addEventListener("click",(function(){Object.keys(t.b).forEach((function(e){var n=t.b[e];n&&n.closed&&n.closed()}))})),"object"===("undefined"==typeof exports?"undefined":_typeof(exports))?e.exports=t.c:"function"==typeof define&&n(220)?define(xmSelect):window.layui&&layui.define&&layui.define((function(e){e("xmSelect",t.c)})),window.xmSelect=t.c}.call(this,n(214)(e))},214:function(e,t){e.exports=function(e){if(!e.webpackPolyfill){var t=Object.create(e);t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),Object.defineProperty(t,"exports",{enumerable:!0}),t.webpackPolyfill=1}return t}},215:function(e,t){Array.prototype.map||(Array.prototype.map=function(e,t){var n,o,r,i=Object(this),l=i.length>>>0;for(t&&(n=t),o=new Array(l),r=0;r>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");for(arguments.length>1&&(n=t),o=0;o>>0;if("function"!=typeof e)throw new TypeError;for(var o=[],r=arguments[1],i=0;i>>0,r=arguments[1],i=0;i .xm-tips {\n color: #999999;\n padding: 0 10px;\n position: absolute;\n display: flex;\n height: 100%;\n align-items: center;\n}\nxm-select > .xm-icon {\n display: inline-block;\n overflow: hidden;\n position: absolute;\n width: 0;\n height: 0;\n right: 10px;\n top: 50%;\n margin-top: -3px;\n cursor: pointer;\n border: 6px dashed transparent;\n border-top-color: #C2C2C2;\n border-top-style: solid;\n transition: all 0.3s;\n -webkit-transition: all 0.3s;\n}\nxm-select > .xm-icon-expand {\n margin-top: -9px;\n transform: rotate(180deg);\n}\nxm-select > .xm-label.single-row {\n position: absolute;\n top: 0;\n bottom: 0px;\n left: 0px;\n right: 30px;\n overflow: auto hidden;\n}\nxm-select > .xm-label.single-row .scroll {\n overflow-y: hidden;\n}\nxm-select > .xm-label.single-row .label-content {\n flex-wrap: nowrap;\n}\nxm-select > .xm-label.auto-row .label-content {\n flex-wrap: wrap;\n}\nxm-select > .xm-label.auto-row .xm-label-block > span {\n white-space: unset;\n height: 100%;\n}\nxm-select > .xm-label .scroll .label-content {\n display: flex;\n padding: 3px 30px 3px 10px;\n}\nxm-select > .xm-label .xm-label-block {\n display: flex;\n position: relative;\n padding: 0px 5px;\n margin: 2px 5px 2px 0;\n border-radius: 3px;\n align-items: baseline;\n color: #FFF;\n}\nxm-select > .xm-label .xm-label-block > span {\n display: flex;\n color: #FFF;\n white-space: nowrap;\n}\nxm-select > .xm-label .xm-label-block > i {\n color: #FFF;\n margin-left: 8px;\n font-size: 12px;\n cursor: pointer;\n display: flex;\n}\nxm-select > .xm-label .xm-label-block.disabled {\n background-color: #C2C2C2 !important;\n cursor: no-drop !important;\n}\nxm-select > .xm-label .xm-label-block.disabled > i {\n cursor: no-drop !important;\n}\nxm-select > .xm-body {\n position: absolute;\n left: 0;\n top: 42px;\n padding: 5px 0;\n z-index: 999;\n width: 100%;\n min-width: fit-content;\n border: 1px solid #E6E6E6;\n background-color: #fff;\n border-radius: 2px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);\n animation-name: xm-upbit;\n animation-duration: 0.3s;\n animation-fill-mode: both;\n}\nxm-select > .xm-body .scroll-body {\n overflow-x: hidden;\n overflow-y: auto;\n}\nxm-select > .xm-body .scroll-body::-webkit-scrollbar {\n width: 8px;\n}\nxm-select > .xm-body .scroll-body::-webkit-scrollbar-track {\n -webkit-border-radius: 2em;\n -moz-border-radius: 2em;\n -ms-border-radius: 2em;\n border-radius: 2em;\n background-color: #FFF;\n}\nxm-select > .xm-body .scroll-body::-webkit-scrollbar-thumb {\n -webkit-border-radius: 2em;\n -moz-border-radius: 2em;\n -ms-border-radius: 2em;\n border-radius: 2em;\n background-color: #C2C2C2;\n}\nxm-select > .xm-body.up {\n top: auto;\n bottom: 42px;\n}\nxm-select > .xm-body.relative {\n position: relative;\n display: block !important;\n top: 0;\n box-shadow: none;\n border: none;\n animation-name: none;\n animation-duration: 0;\n min-width: 100%;\n}\nxm-select > .xm-body .xm-group {\n cursor: default;\n}\nxm-select > .xm-body .xm-group-item {\n display: inline-block;\n cursor: pointer;\n padding: 0 10px;\n color: #999;\n font-size: 12px;\n}\nxm-select > .xm-body .xm-option {\n display: flex;\n align-items: center;\n position: relative;\n padding: 0 10px;\n cursor: pointer;\n}\nxm-select > .xm-body .xm-option-icon {\n color: transparent;\n display: flex;\n border: 1px solid #E6E6E6;\n border-radius: 3px;\n justify-content: center;\n align-items: center;\n}\nxm-select > .xm-body .xm-option-icon.xm-icon-danx {\n border-radius: 100%;\n}\nxm-select > .xm-body .xm-option-content {\n display: flex;\n position: relative;\n padding-left: 15px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n color: #666;\n width: calc(100% - 20px);\n}\nxm-select > .xm-body .xm-option.hide-icon .xm-option-content {\n padding-left: 0;\n}\nxm-select > .xm-body .xm-option.selected.hide-icon .xm-option-content {\n color: #FFF !important;\n}\nxm-select > .xm-body .xm-option .loader {\n width: 0.8em;\n height: 0.8em;\n margin-right: 6px;\n color: #C2C2C2;\n}\nxm-select > .xm-body .xm-select-empty {\n text-align: center;\n color: #999;\n}\nxm-select > .xm-body .disabled {\n cursor: no-drop;\n}\nxm-select > .xm-body .disabled:hover {\n background-color: #FFF;\n}\nxm-select > .xm-body .disabled .xm-option-icon {\n border-color: #C2C2C2 !important;\n}\nxm-select > .xm-body .disabled .xm-option-content {\n color: #C2C2C2 !important;\n}\nxm-select > .xm-body .disabled.selected > .xm-option-icon {\n color: #C2C2C2 !important;\n}\nxm-select > .xm-body .xm-search {\n background-color: #FFF !important;\n position: relative;\n padding: 0 10px;\n margin-bottom: 5px;\n cursor: pointer;\n}\nxm-select > .xm-body .xm-search > i {\n position: absolute;\n color: #666;\n}\nxm-select > .xm-body .xm-search-input {\n border: none;\n border-bottom: 1px solid #E6E6E6;\n padding-left: 27px;\n cursor: text;\n}\nxm-select > .xm-body .xm-paging {\n padding: 0 10px;\n display: flex;\n margin-top: 5px;\n}\nxm-select > .xm-body .xm-paging > span:first-child {\n border-radius: 2px 0 0 2px;\n}\nxm-select > .xm-body .xm-paging > span:last-child {\n border-radius: 0 2px 2px 0;\n}\nxm-select > .xm-body .xm-paging > span {\n display: flex;\n flex: auto;\n justify-content: center;\n vertical-align: middle;\n margin: 0 -1px 0 0;\n background-color: #fff;\n color: #333;\n font-size: 12px;\n border: 1px solid #e2e2e2;\n flex-wrap: nowrap;\n width: 100%;\n overflow: hidden;\n min-width: 50px;\n}\nxm-select > .xm-body .xm-toolbar {\n padding: 0 10px;\n display: flex;\n margin: -3px 0;\n cursor: default;\n}\nxm-select > .xm-body .xm-toolbar .toolbar-tag {\n cursor: pointer;\n display: flex;\n margin-right: 20px;\n color: #666;\n align-items: baseline;\n}\nxm-select > .xm-body .xm-toolbar .toolbar-tag:hover {\n opacity: 0.8;\n}\nxm-select > .xm-body .xm-toolbar .toolbar-tag:active {\n opacity: 1;\n}\nxm-select > .xm-body .xm-toolbar .toolbar-tag > i {\n margin-right: 2px;\n font-size: 14px;\n}\nxm-select > .xm-body .xm-toolbar .toolbar-tag:last-child {\n margin-right: 0;\n}\nxm-select > .xm-body .xm-body-custom {\n line-height: initial;\n cursor: default;\n}\nxm-select > .xm-body .xm-body-custom * {\n box-sizing: initial;\n}\nxm-select > .xm-body .xm-tree {\n position: relative;\n}\nxm-select > .xm-body .xm-tree-icon {\n display: inline-block;\n margin-right: 3px;\n cursor: pointer;\n border: 6px dashed transparent;\n border-left-color: #C2C2C2;\n border-left-style: solid;\n transition: all 0.3s;\n -webkit-transition: all 0.3s;\n z-index: 2;\n visibility: hidden;\n}\nxm-select > .xm-body .xm-tree-icon.expand {\n margin-top: 3px;\n margin-right: 5px;\n margin-left: -2px;\n transform: rotate(90deg);\n}\nxm-select > .xm-body .xm-tree-icon.xm-visible {\n visibility: visible;\n}\nxm-select > .xm-body .xm-tree .left-line {\n position: absolute;\n left: 13px;\n width: 0;\n z-index: 1;\n border-left: 1px dotted #c0c4cc !important;\n}\nxm-select > .xm-body .xm-tree .top-line {\n position: absolute;\n left: 13px;\n height: 0;\n z-index: 1;\n border-top: 1px dotted #c0c4cc !important;\n}\nxm-select > .xm-body .xm-tree .xm-tree-icon + .top-line {\n margin-left: 1px;\n}\nxm-select > .xm-body .scroll-body > .xm-tree > .xm-option > .top-line,\nxm-select > .xm-body .scroll-body > .xm-option > .top-line {\n width: 0 !important;\n}\nxm-select > .xm-body .xm-cascader-box {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n padding: 5px 0;\n border: 1px solid #E6E6E6;\n background-color: #fff;\n border-radius: 2px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);\n margin: -1px;\n}\nxm-select > .xm-body .xm-cascader-box::before {\n content: ' ';\n position: absolute;\n width: 0;\n height: 0;\n border: 6px solid transparent;\n border-right-color: #E6E6E6;\n top: 10px;\n left: -12px;\n}\nxm-select > .xm-body .xm-cascader-box::after {\n content: ' ';\n position: absolute;\n width: 0;\n height: 0;\n border: 6px solid transparent;\n border-right-color: #fff;\n top: 10px;\n left: -11px;\n}\nxm-select > .xm-body .xm-cascader-scroll {\n height: 100%;\n overflow-x: hidden;\n overflow-y: auto;\n}\nxm-select > .xm-body.cascader {\n width: unset;\n min-width: unset;\n}\nxm-select > .xm-body.cascader .xm-option-content {\n padding-left: 8px;\n}\nxm-select > .xm-body.cascader .disabled .xm-right-arrow {\n color: #C2C2C2 !important;\n}\nxm-select .xm-input {\n cursor: pointer;\n border-radius: 2px;\n border-width: 1px;\n border-style: solid;\n border-color: #E6E6E6;\n display: block;\n width: 100%;\n box-sizing: border-box;\n background-color: #FFF;\n line-height: 1.3;\n padding-left: 10px;\n outline: 0;\n user-select: text;\n -ms-user-select: text;\n -moz-user-select: text;\n -webkit-user-select: text;\n}\nxm-select .dis {\n display: none;\n}\nxm-select .loading {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(255, 255, 255, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n}\nxm-select .loader {\n border: 0.2em dotted currentcolor;\n border-radius: 50%;\n -webkit-animation: 1s loader linear infinite;\n animation: 1s loader linear infinite;\n display: inline-block;\n width: 1em;\n height: 1em;\n color: inherit;\n vertical-align: middle;\n pointer-events: none;\n}\nxm-select .xm-select-default {\n position: absolute;\n width: 100%;\n height: 100%;\n border: none;\n visibility: hidden;\n}\nxm-select .xm-select-disabled {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n cursor: no-drop;\n z-index: 2;\n opacity: 0.3;\n background-color: #FFF;\n}\nxm-select .item--divided {\n border-top: 1px solid #ebeef5;\n width: calc(100% - 20px);\n cursor: initial;\n}\nxm-select .xm-right-arrow {\n position: absolute;\n color: #666;\n right: 5px;\n top: -1px;\n font-weight: 700;\n transform: scale(0.6, 1);\n}\nxm-select .xm-right-arrow::after {\n content: '>';\n}\nxm-select[size='large'] {\n min-height: 40px;\n line-height: 40px;\n}\nxm-select[size='large'] .xm-input {\n height: 40px;\n}\nxm-select[size='large'] .xm-label .scroll .label-content {\n line-height: 34px;\n}\nxm-select[size='large'] .xm-label .xm-label-block {\n height: 30px;\n line-height: 30px;\n}\nxm-select[size='large'] .xm-body .xm-option .xm-option-icon {\n height: 20px;\n width: 20px;\n font-size: 20px;\n}\nxm-select[size='large'] .xm-paging > span {\n height: 34px;\n line-height: 34px;\n}\nxm-select[size='large'] .xm-tree .left-line {\n height: 100%;\n bottom: 20px;\n}\nxm-select[size='large'] .xm-tree .left-line-group {\n height: calc(100% - 40px);\n}\nxm-select[size='large'] .xm-tree .xm-tree-icon.xm-hidden + .top-line {\n top: 19px;\n}\nxm-select[size='large'] .item--divided {\n margin: 10px;\n}\nxm-select {\n min-height: 36px;\n line-height: 36px;\n}\nxm-select .xm-input {\n height: 36px;\n}\nxm-select .xm-label .scroll .label-content {\n line-height: 30px;\n}\nxm-select .xm-label .xm-label-block {\n height: 26px;\n line-height: 26px;\n}\nxm-select .xm-body .xm-option .xm-option-icon {\n height: 18px;\n width: 18px;\n font-size: 18px;\n}\nxm-select .xm-paging > span {\n height: 30px;\n line-height: 30px;\n}\nxm-select .xm-tree .left-line {\n height: 100%;\n bottom: 18px;\n}\nxm-select .xm-tree .left-line-group {\n height: calc(100% - 36px);\n}\nxm-select .xm-tree .xm-tree-icon.xm-hidden + .top-line {\n top: 17px;\n}\nxm-select .item--divided {\n margin: 9px;\n}\nxm-select[size='small'] {\n min-height: 32px;\n line-height: 32px;\n}\nxm-select[size='small'] .xm-input {\n height: 32px;\n}\nxm-select[size='small'] .xm-label .scroll .label-content {\n line-height: 26px;\n}\nxm-select[size='small'] .xm-label .xm-label-block {\n height: 22px;\n line-height: 22px;\n}\nxm-select[size='small'] .xm-body .xm-option .xm-option-icon {\n height: 16px;\n width: 16px;\n font-size: 16px;\n}\nxm-select[size='small'] .xm-paging > span {\n height: 26px;\n line-height: 26px;\n}\nxm-select[size='small'] .xm-tree .left-line {\n height: 100%;\n bottom: 16px;\n}\nxm-select[size='small'] .xm-tree .left-line-group {\n height: calc(100% - 32px);\n}\nxm-select[size='small'] .xm-tree .xm-tree-icon.xm-hidden + .top-line {\n top: 15px;\n}\nxm-select[size='small'] .item--divided {\n margin: 8px;\n}\nxm-select[size='mini'] {\n min-height: 28px;\n line-height: 28px;\n}\nxm-select[size='mini'] .xm-input {\n height: 28px;\n}\nxm-select[size='mini'] .xm-label .scroll .label-content {\n line-height: 22px;\n}\nxm-select[size='mini'] .xm-label .xm-label-block {\n height: 18px;\n line-height: 18px;\n}\nxm-select[size='mini'] .xm-body .xm-option .xm-option-icon {\n height: 14px;\n width: 14px;\n font-size: 14px;\n}\nxm-select[size='mini'] .xm-paging > span {\n height: 22px;\n line-height: 22px;\n}\nxm-select[size='mini'] .xm-tree .left-line {\n height: 100%;\n bottom: 14px;\n}\nxm-select[size='mini'] .xm-tree .left-line-group {\n height: calc(100% - 28px);\n}\nxm-select[size='mini'] .xm-tree .xm-tree-icon.xm-hidden + .top-line {\n top: 13px;\n}\nxm-select[size='mini'] .item--divided {\n margin: 7px;\n}\n.layui-form-pane xm-select {\n margin: -1px -1px -1px 0;\n}\n",""])},218:function(e,t,n){var o=n(219);"string"==typeof o&&(o=[[e.i,o,""]]);var r={hmr:!0,transform:void 0,insertInto:void 0};n(27)(o,r);o.locals&&(e.exports=o.locals)},219:function(e,t,n){(e.exports=n(26)(!1)).push([e.i,'@font-face {\n font-family: "xm-iconfont";\n src: url(\'//at.alicdn.com/t/font_792691_ptvyboo0bno.eot?t=1574048839056\');\n /* IE9 */\n src: url(\'//at.alicdn.com/t/font_792691_ptvyboo0bno.eot?t=1574048839056#iefix\') format(\'embedded-opentype\'), /* IE6-IE8 */ url(\'data:application/x-font-woff2;charset=utf-8;base64,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\') format(\'woff2\'), url(\'//at.alicdn.com/t/font_792691_ptvyboo0bno.woff?t=1574048839056\') format(\'woff\'), url(\'//at.alicdn.com/t/font_792691_ptvyboo0bno.ttf?t=1574048839056\') format(\'truetype\'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url(\'//at.alicdn.com/t/font_792691_ptvyboo0bno.svg?t=1574048839056#iconfont\') format(\'svg\');\n /* iOS 4.1- */\n}\n.xm-iconfont {\n font-family: "xm-iconfont" !important;\n font-size: 16px;\n font-style: normal;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.xm-icon-quanxuan:before {\n content: "\\e62c";\n}\n.xm-icon-caidan:before {\n content: "\\e610";\n}\n.xm-icon-fanxuan:before {\n content: "\\e837";\n}\n.xm-icon-pifu:before {\n content: "\\e668";\n}\n.xm-icon-qingkong:before {\n content: "\\e63e";\n}\n.xm-icon-sousuo:before {\n content: "\\e600";\n}\n.xm-icon-danx:before {\n content: "\\e62b";\n}\n.xm-icon-duox:before {\n content: "\\e613";\n}\n.xm-icon-close:before {\n content: "\\e601";\n}\n.xm-icon-expand:before {\n content: "\\e641";\n}\n.xm-icon-banxuan:before {\n content: "\\e60d";\n}\n',""])},220:function(e,t){(function(t){e.exports=t}).call(this,{})},26:function(e,t,n){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=function(e,t){var n=e[1]||"",o=e[3];if(!o)return n;if(t&&"function"==typeof btoa){var r=function(e){var t=btoa(unescape(encodeURIComponent(JSON.stringify(e)))),n="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(t);return"/*# ".concat(n," */")}(o),i=o.sources.map((function(e){return"/*# sourceURL=".concat(o.sourceRoot).concat(e," */")}));return[n].concat(i).concat([r]).join("\n")}return[n].join("\n")}(t,e);return t[2]?"@media ".concat(t[2],"{").concat(n,"}"):n})).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var o={},r=0;r=0&&p.splice(t,1)}function x(e){var t=document.createElement("style");if(void 0===e.attrs.type&&(e.attrs.type="text/css"),void 0===e.attrs.nonce){var o=function(){0;return n.nc}();o&&(e.attrs.nonce=o)}return y(t,e.attrs),m(e,t),t}function y(e,t){Object.keys(t).forEach((function(n){e.setAttribute(n,t[n])}))}function v(e,t){var n,o,r,i;if(t.transform&&e.css){if(!(i="function"==typeof t.transform?t.transform(e.css):t.transform.default(e.css)))return function(){};e.css=i}if(t.singleton){var l=u++;n=c||(c=x(t)),o=w.bind(null,n,l,!1),r=w.bind(null,n,l,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return void 0===e.attrs.type&&(e.attrs.type="text/css"),e.attrs.rel="stylesheet",y(t,e.attrs),m(e,t),t}(t),o=C.bind(null,n,t),r=function(){b(n),n.href&&URL.revokeObjectURL(n.href)}):(n=x(t),o=k.bind(null,n),r=function(){b(n)});return o(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;o(e=t)}else r()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=l()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=h(e,t);return f(n,t),function(e){for(var o=[],r=0;r3)for(n=[n],o=3;or?n-r:r,l=this.labelRef.scrollLeft+e.deltaY;l<0&&(l=0),l>i&&(l=i),this.labelRef.scrollLeft=l}}},{key:"componentDidMount",value:function(){this.labelRef.addEventListener&&this.labelRef.addEventListener("DOMMouseScroll",this.scrollFunc.bind(this),!1),this.labelRef.attachEvent&&this.labelRef.attachEvent("onmousewheel",this.scrollFunc.bind(this)),this.labelRef.onmousewheel=this.scrollFunc.bind(this)}},{key:"render",value:function(e){var t=this,n=e.data,o=e.prop,r=e.theme,i=e.model,l=e.sels,a=e.autoRow,c=o.name,u=o.disabled,p=i.label,d=p.type,f=p[d],h="",m=!0,b=l.map((function(e){return e[c]})).join(",");if("text"===d)h=l.map((function(e){return"".concat(f.left).concat(e[c]).concat(f.right)})).join(f.separator);else if("block"===d){m=!1;var x=N(l),y={backgroundColor:r.color},v=f.showCount<=0?x.length:f.showCount;h=x.splice(0,v).map((function(e){var n={width:f.showIcon?"calc(100% - 20px)":"100%"};return _("div",{class:["xm-label-block",e[u]?"disabled":""].join(" "),style:y},f.template&&s(f.template)?_("span",{style:n,dangerouslySetInnerHTML:{__html:f.template(e,x)}}):_("span",{style:n},e[c]),f.showIcon&&_("i",{class:"xm-iconfont xm-icon-close",onClick:t.iconClick.bind(t,e,!0,e[u])}))})),x.length&&h.push(_("div",{class:"xm-label-block",style:y},"+ ",x.length))}else h=l.length&&f&&f.template?f.template(n,l):l.map((function(e){return e[c]})).join(",");return _("div",{class:["xm-label",a?"auto-row":"single-row"].join(" ")},_("div",{class:"scroll",ref:function(e){return t.labelRef=e}},m?_("div",{class:"label-content",dangerouslySetInnerHTML:{__html:h}}):_("div",{class:"label-content",title:b},h)))}}])&&H(n.prototype,o),r&&H(n,r),t}(C);function Q(e){return(Q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function J(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function W(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function G(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:this.size;var e=this.state.pageIndex;e<=1||(this.changePageIndex(e-1),this.props.pageRemote&&this.postData(e-1,!0))}},{key:"pageNextClick",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.size,t=this.state.pageIndex;t>=e||(this.changePageIndex(t+1),this.props.pageRemote&&this.postData(t+1,!0))}},{key:"changePageIndex",value:function(e){this.setState({pageIndex:e})}},{key:"searchInput",value:function(e){var t=this,n=e.target.value;n!==this.__value&&(clearTimeout(this.searchCid),this.inputOver&&(this.__value=n,this.searchCid=setTimeout((function(){t.callback=!0,t.setState({filterValue:t.__value,remote:!0,pageIndex:1})}),this.props.delay)))}},{key:"focus",value:function(){this.searchInputRef&&this.searchInputRef.focus()}},{key:"blur",value:function(){this.searchInputRef&&this.searchInputRef.blur()}},{key:"handleComposition",value:function(e){var t=e.type;"compositionstart"===t?(this.inputOver=!1,clearTimeout(this.searchCid)):"compositionend"===t&&(this.inputOver=!0,this.searchInput(e))}},{key:"postData",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.state.pageIndex,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];(this.state.remote||n)&&(this.callback=!1,this.setState({loading:!0,remote:!1}),this.blur(),this.props.remoteMethod(this.state.filterValue,(function(t,n){e.focus(),e.callback=!0,e.setState({loading:!1,totalSize:n}),e.props.onReset(t,"data")}),this.props.show,t))}},{key:"keydown",value:function(e,t){var n=this,o=t.keyCode;"div"===e&&(27===o||9===o?this.props.onReset(!1,"close"):37===o?this.pagePrevClick():39===o&&this.pageNextClick());var r=this.props.prop,i=r.value,l=r.optgroup,a=r.disabled,s=this.tempData.filter((function(e){return!e[l]&&!e[a]})),c=s.length-1;if(-1!==c){var u=s.findIndex((function(e){return e[i]===n.state.val}));if(38===o){u<=0?u=c:u>0&&(u-=1);var p=s[u][i];this.setState({val:p})}else if(40===o){-1===u||u===c?u=0:uD&&(M=D),D>0&&M<=0&&(M=1),!v){var T=(M-1)*e.pageSize,z=T+e.pageSize;S=S.slice(T,z)}var L={cursor:"no-drop",color:"#d2d2d2"},V={},F={};M<=1&&(V=L),M==D&&(F=L),this.state.pageIndex!==M&&this.changePageIndex(M),this.size=D,I=_("div",{class:"xm-paging"},_("span",{style:V,onClick:this.pagePrevClick.bind(this,D)},"上一页"),_("span",null,this.state.pageIndex," / ",D),_("span",{style:F,onClick:this.pageNextClick.bind(this,D)},"下一页"))}else e.showCount>0&&(S=S.slice(0,e.showCount));var U,B=[],N={__tmp:!0};N[O]=!0,S.forEach((function(e){var t=P[e[w]];U&&!t&&(t=N),t!=U&&(U=t,t&&B.push(U)),B.push(e)})),S=B,t&&(t=y(this.state.filterValue,c([],S)))&&S.splice(0,0,function(e){for(var t=1;t0||f.lazy&&!1!==e.__node.loading)?"xm-visible":"xm-hidden"].join(" "),C=[];f.showFolderIcon&&(C.push(_("i",{class:w})),f.showLine&&(o&&C.push(_("i",{class:"left-line",style:{left:t-f.indent+3+"px"}})),C.push(_("i",{class:"top-line",style:{left:t-f.indent+3+"px",width:f.indent+(0===o?10:-2)+"px"}}))));var O=function(t){"mouseenter"===t.type&&(e[v]||n.setState({val:e[y]}))};return _("div",{class:m,style:h,value:e[y],onClick:n.optionClick.bind(n,e,r,e[v],"line"),onMouseEnter:O,onMouseLeave:O},C,e.__node.loading&&_("span",{class:"loader"}),k&&_("i",{class:b,style:u,onClick:n.optionClick.bind(n,e,r,e[v],"checkbox")}),_("div",{class:"xm-option-content",dangerouslySetInnerHTML:{__html:p({data:d,item:e,arr:i,name:e[x],value:e[y]})}}))};h&&(m?this.postData():this.filterData(d,this.state.filterValue));var O=c([],d),S=c([],i);this.tempData=O;var j=d.map((function(e){return function e(t,o){if(!t.__node.hidn){var r=t[g];if(o+=f.indent,r){var i=-1!==n.state.expandedKeys.findIndex((function(e){return t[y]===e}));return 0===r.length&&(i=!1),_("div",{class:"xm-tree"},f.showFolderIcon&&f.showLine&&i&&r.length>0&&_("i",{class:"left-line left-line-group",style:{left:o+3+"px"}}),C(t,o,0===r.length&&(!f.lazy||f.lazy&&!1===t.__node.loading)?0:i),i&&_("div",{class:"xm-tree-box"},r.map((function(t){return e(t,o)}))))}return C(t,o,0)}}(e,10-f.indent)})).filter((function(e){return e}));function E(e,t){t.forEach((function(t){return t[w]?(!f.strict&&e.push(t),E(e,t[g])):e.push(t)}))}var A=_("div",{class:"xm-toolbar"},e.toolbar.list.map((function(t){var r,c=e.languageProp.toolbar[t];r="ALL"===t?{icon:"xm-iconfont xm-icon-quanxuan",name:c,method:function(e){var t=[];E(t,e),t=t.filter((function(e){return!e[v]})),n.props.onReset(a?t.slice(0,1):u(t,i,o),"treeData")}}:"CLEAR"===t?{icon:"xm-iconfont xm-icon-qingkong",name:c,method:function(e){n.props.onReset(i.filter((function(e){return e[o.disabled]})),"treeData")}}:"REVERSE"===t?{icon:"xm-iconfont xm-icon-fanxuan",name:c,method:function(e){var t=[];E(t,e),t=t.filter((function(e){return!e[v]}));var r=[];i.forEach((function(e){var n=t.findIndex((function(t){return t[y]===e[y]}));-1==n?r.push(e):t.splice(n,1)})),n.props.onReset(a?r.slice(0,1):u(t,r,o),"treeData")}}:t;var p=function(e){"mouseenter"===e.type&&(e.target.style.color=l.color),"mouseleave"===e.type&&(e.target.style.color="")};return _("div",{class:"toolbar-tag",onClick:function(){s(r.method)&&r.method(O,S)},onMouseEnter:p,onMouseLeave:p},e.toolbar.showIcon&&_("i",{class:r.icon}),_("span",null,r.name))})).filter((function(e){return e}))),R=_("div",{class:h?"xm-search":"xm-search dis"},_("i",{class:"xm-iconfont xm-icon-sousuo"}),_("input",{class:"xm-input xm-search-input",placeholder:b}));return j.length||j.push(_("div",{class:"xm-select-empty"},r)),_("div",{onClick:this.blockClick,class:"xm-body-tree"},e.toolbar.show&&A,R,_("div",{class:"scroll-body",style:{maxHeight:e.height}},j),this.state.loading&&_("div",{class:"loading"},_("span",{class:"loader"})))}},{key:"componentDidMount",value:function(){var e=this.base.querySelector(".xm-search-input");e&&(e.addEventListener("compositionstart",this.handleComposition.bind(this)),e.addEventListener("compositionupdate",this.handleComposition.bind(this)),e.addEventListener("compositionend",this.handleComposition.bind(this)),e.addEventListener("input",this.searchInput.bind(this)),this.searchInputRef=e)}},{key:"componentDidUpdate",value:function(){if(this.callback){this.callback=!1;var e=this.props.filterDone;s(e)&&e(this.state.filterValue,this.tempData||[])}}}])&&ue(n.prototype,o),r&&ue(n,r),t}(C);function be(e){return(be="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function xe(e,t){for(var n=0;n1&&(n=n.slice(0,1)),this.setState({sels:n,dataObj:a,flatData:s})}return this.setState({data:o}),n}},{key:"exchangeValue",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.state.dataObj,o=e.map((function(e){return"object"===Se(e)?Ce({},e,{__node:{}}):n[e]})).filter((function(e){return e})),r=!0,i=this.props.tree;return i.show&&!1===i.strict&&(r=!1),r&&(o=o.filter((function(e){return!0!==e[t.props.prop.optgroup]}))),o}},{key:"value",value:function(e,t,n){!1!==t&&!0!==t&&(t=this.state.show);var o=this.props,r=o.prop,i=o.tree,l=this.exchangeValue(e);if(i.show&&i.strict){var a=this.state.data;this.clearAndReset(a,l),l=this.init({data:a,prop:r},!0)}this.resetSelectValue(l,l,!0,n),this.setState({show:t})}},{key:"clearAndReset",value:function(e,t){var n=this,o=this.props.prop,r=o.selected,i=o.children,l=o.value;e.forEach((function(e){e[r]=-1!=t.findIndex((function(t){return t[l]===e[l]}));var o=e[i];o&&a(o)&&n.clearAndReset(o,t)}))}},{key:"load",value:function(e,t,n,o){var r=this,i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,l=this.props,s=l.prop,c=l.tree,u=s.children,p=s.optgroup,d=s.value,f=s.selected,h=s.disabled;e.forEach((function(e){e.__node={parent:o,level:i,loading:e.__node&&e.__node.loading},t[e[d]]=e,n.push(e);var l=e[u];if(l&&a(l)){var s=l.length;if(s>0){r.load(l,t,n,e,i+1),e[p]=!0,c.strict&&(!0===e[f]&&(delete e[f],l.forEach((function(e){return e[f]=!0}))),!0===e[h]&&(delete e[h],l.forEach((function(e){return e[h]=!0}))));var m=l.filter((function(e){return!0===e[f]||!0===e.__node.selected})).length;e.__node.selected=m===s,e.__node.half=m>0&&m0,e.__node.disabled=l.filter((function(e){return!0===e[h]||!0===e.__node.disabled})).length===s}}}))}},{key:"resetSelectValue",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2?arguments[2]:void 0,o=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],r=this.props.on;if(s(r)&&this.prepare&&o){var i=r({arr:e,change:t,isAdd:n});if(a(i))return this.value(i,null,!1)}this.setState({sels:e})}},{key:"updateBorderColor",value:function(e){this.setState({tmpColor:e})}},{key:"treeHandler",value:function(e,t,n,o){var r=this,i=this.props.prop,l=i.value,a=(i.selected,i.disabled),s=i.children,c=i.optgroup,u=t[s];u.filter((function(e){return!(e[a]||e.__node.disabled)})).forEach((function(t){if(t[c])r.treeHandler(e,t,n,o);else{var i=e.findIndex((function(e){return e[l]==t[l]}));"del"===o?-1!=i&&(e.splice(i,1),n.push(t)):"half"!==o&&"add"!==o||-1==i&&(e.push(t),n.push(t))}}));var p=u.length,d=u.filter((function(t){return-1!==e.findIndex((function(e){return e[l]===t[l]}))||!0===t.__node.selected})).length;t.__node.selected=d===p,t.__node.half=d>0&&d0&&h.length>=g)return this.updateBorderColor(i.maxColor),void(d&&s(d)&&d(h,e));h=a?[e]:[].concat(we(h),[e]),this.resetSelectValue(h,[e],!t)}else{var _=h.findIndex((function(t){return t[m]==e[m]}));-1!=_&&(h.splice(_,1),this.resetSelectValue(h,[e],!t))}var w,k=e.__node.parent;if(k){for(;k;){var C=k[b],O=C.length,S=C.filter((function(e){return-1!==h.findIndex((function(t){return t[m]===e[m]}))||!0===e.__node.selected})).length;k.__node.selected=S===O,k.__node.half=S>0&&S0,k=k.__node.parent}this.setState({data:this.state.data})}u&&!o&&this.onClick()}}},{key:"onClick",value:function(e){var t=this;if("relative"!==this.props.model.type)if(this.props.disabled)!1!==this.state.show&&this.setState({show:!1});else{var n=!this.state.show;if(n){if(this.props.show&&0==this.props.show())return;Object.keys(Be).filter((function(e){return e!=t.props.el})).forEach((function(e){return Be[e].closed()}))}else{if(this.props.hide&&0==this.props.hide())return;this.bodyView.scroll&&this.bodyView.scroll(0,0)}this.setState({show:n}),e&&e.stopPropagation()}}},{key:"onReset",value:function(e,t){var n=this;if("data"===t){var o=e.filter((function(e){return!0===e[n.props.prop.selected]}));this.resetSelectValue(u(o,this.state.sels,this.props.prop),o,!0);var r=[];this.load(e,{},r),this.setState({data:e,flatData:r})}else"sels"===t?this.resetSelectValue(e,e,!0):"append"===t?this.append(e):"delete"===t?this.del(e):"auto"===t?this.auto(e):"treeData"===t?this.value(e,null,!0):"close"===t?this.onClick():"class"===t&&this.setState({bodyClass:e})}},{key:"append",value:function(e){var t=this.exchangeValue(e);this.resetSelectValue(u(t,this.state.sels,this.props.prop),t,!0)}},{key:"del",value:function(e){var t=this.props.prop.value,n=this.state.sels;(e=this.exchangeValue(e)).forEach((function(e){var o=n.findIndex((function(n){return n[t]===e[t]}));-1!=o&&n.splice(o,1)})),this.resetSelectValue(n,e,!1)}},{key:"auto",value:function(e){var t=this,n=this.props.prop.value;e.filter((function(e){return-1!=t.state.sels.findIndex((function(t){return t[n]===e[n]}))})).length==e.length?this.del(e):this.append(e)}},{key:"componentWillReceiveProps",value:function(e){this.init(e,e.updateData)}},{key:"componentWillMount",value:function(){this.init(this.props,!0)}},{key:"render",value:function(e,t){var n=this,o=e.theme,r=e.prop,i=(e.radio,e.repeat,e.clickClose,e.on,e.max,e.maxMethod,e.content),l=e.disabled,a=e.tree,s={borderColor:o.color},c=t.data,u=t.dataObj,p=t.flatData,d=t.sels,f=t.show,h=t.tmpColor,m=t.bodyClass;l&&(f=!1);var b={style:Ce({},e.style,{},f?s:{}),onClick:this.onClick.bind(this),ua:-1!=navigator.userAgent.indexOf("Mac OS")?"mac":"win",size:e.size,tabindex:1};h&&(b.style.borderColor=h,setTimeout((function(){b.style.borderColor="",n.updateBorderColor("")}),300)),r.value;var x=Ce({},e,{data:c,sels:d,ck:this.itemClick.bind(this),title:d.map((function(e){return e[r.name]})).join(",")}),y=Ce({},e,{data:c,dataObj:u,flatData:p,sels:d,ck:this.itemClick.bind(this),show:f,onReset:this.onReset.bind(this)}),v=i?_(se,y):a.show?_(me,y):e.cascader.show?_(_e,y):_(ne,y);return _("xm-select",b,_("input",{class:"xm-select-default","lay-verify":e.layVerify,"lay-verType":e.layVerType,name:e.name,value:d.map((function(e){return e[r.value]})).join(",")}),_("i",{class:f?"xm-icon xm-icon-expand":"xm-icon"}),0===d.length&&_("div",{class:"xm-tips"},e.tips),_(Z,x),_("div",{class:["xm-body",m,e.model.type,f?"":"dis"].join(" "),ref:function(e){return n.bodyView=e}},v),l&&_("div",{class:"xm-select-disabled"}))}},{key:"componentDidMount",value:function(){var e=this;this.prepare=!0,this.base.addEventListener("keydown",(function(t){13===t.keyCode&&e.onClick()})),this.input=this.base.querySelector(".xm-select-default");var t=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;t&&new t((function(t){t.forEach((function(t){"attributes"==t.type&&"class"===t.attributeName&&-1!==e.input.className.indexOf("layui-form-danger")&&(e.input.className="xm-select-default",e.base.style.borderColor=e.props.theme.maxColor)}))})).observe(this.input,{attributes:!0});for(var n=this.base;n;){if("FORM"===n.tagName){var o=n.querySelector('button[type="reset"]');o&&o.addEventListener("click",(function(t){e.init(e.props,!0)}));break}n=n.parentElement}}},{key:"componentDidUpdate",value:function(){var e=this.props,t=e.direction;if("relative"!==e.model.type){var n=this.base.getBoundingClientRect();if("auto"===t){this.bodyView.style.display="block",this.bodyView.style.visibility="hidden";var o=this.bodyView.getBoundingClientRect().height;this.bodyView.style.display="",this.bodyView.style.visibility="";var r=n.y||n.top||0,i=document.documentElement.clientHeight-r-n.height-20;t=i>o||r0&&void 0!==arguments[0]?arguments[0]:"zn",t=De[e]||Ie;return{language:e,languageProp:t,data:[],content:"",name:"select",layVerify:"",layVerType:"",size:"medium",disabled:!1,initValue:null,create:null,tips:t.tips,empty:t.empty,delay:500,searchTips:t.searchTips,filterable:!1,filterMethod:function(e,t,n,o){return!e||-1!=t[o.name].indexOf(e)},remoteSearch:!1,remoteMethod:function(e,t){t([])},direction:"auto",style:{},height:"200px",autoRow:!1,paging:!1,pageSize:10,pageEmptyShow:!0,pageRemote:!1,radio:!1,repeat:!1,clickClose:!1,max:0,maxMethod:function(e,t){},showCount:0,toolbar:{show:!1,showIcon:!0,list:["ALL","CLEAR"]},tree:{show:!1,showFolderIcon:!0,showLine:!0,indent:20,expandedKeys:[],strict:!0,lazy:!1,load:null},cascader:{show:!1,indent:100,strict:!0},prop:{name:"name",value:"value",selected:"selected",disabled:"disabled",children:"children",optgroup:"optgroup",click:"click"},theme:{color:"#009688",maxColor:"#e54d42",hover:"#f2f2f2"},model:{label:{type:"block",text:{left:"",right:"",separator:", "},block:{showCount:0,showIcon:!0,template:null},count:{template:function(e,t){return"已选中 ".concat(t.length," 项, 共 ").concat(e.length," 项")}}},icon:"show",type:"absolute"},show:function(){},hide:function(){},template:function(e){e.item,e.sels;var t=e.name;return e.value,t},on:function(e){e.arr,e.item,e.selected}}}(e.language),this.update(e)}},{key:"update",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=!!e.data;this.options=c(this.options,e);var n=this.options.dom;if(n){var o=this.options.data||[];if("function"==typeof o&&(o=o(),this.options.data=o),a(o))return U(_(Pe,ze({},this.options,{updateData:t})),n),this;l("data数据必须为数组类型, 不能是".concat("undefined"==typeof data?"undefined":Le(data),"类型"))}else l("没有找到渲染对象: ".concat(e.el,", 请检查"))}},{key:"reset",value:function(){var e=this.options.el;return this.init(Ne[e]),He[e].init(this.options,!0),this}},{key:"opened",value:function(){var e=He[this.options.el];return!e.state.show&&e.onClick(),this}},{key:"closed",value:function(){var e=He[this.options.el];return e.state.show&&e.onClick(),this}},{key:"getValue",value:function(e){var t=this,n=He[this.options.el].state.sels.map((function(e){return delete(e=function(e){for(var t=1;t2&&void 0!==arguments[2]&&arguments[2];if(a(e))return He[this.options.el].value(this.options.radio?e.slice(0,1):e,t,n),this;l("请传入数组结构...")}},{key:"append",value:function(e){if(a(e))return He[this.options.el].append(e),this;l("请传入数组结构...")}},{key:"delete",value:function(e){if(a(e))return He[this.options.el].del(e),this;l("请传入数组结构...")}},{key:"warning",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=e||this.options.theme.maxColor;return!0===t?He[this.options.el].base.style.borderColor=n:He[this.options.el].updateBorderColor(n),this}}])&&Ve(t.prototype,n),o&&Ve(t,o),e}();function Ue(e){return function(e){if(Array.isArray(e)){for(var t=0,n=new Array(e.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

Ho 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 c$KSw@zkwfr13&%-e*6vm_#62D?lZB(>aLWVXo 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 CY%vA^ literal 0 HcmV?d00001 diff --git a/public/fonts/iconfont.svg b/public/fonts/iconfont.svg new file mode 100644 index 0000000..f6f082e --- /dev/null +++ b/public/fonts/iconfont.svg @@ -0,0 +1,477 @@ + + + + + +Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/iconfont.ttf b/public/fonts/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ccf533ec5e6b778fe8ffe17753dbafde5ad3ab90 GIT binary patch literal 49432 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 Wq_gOFI@Jmyj-<^eJwU6rJp6x-RUf?o literal 0 HcmV?d00001 diff --git a/public/fonts/iconfont.woff b/public/fonts/iconfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..d06756cabafdf52e80d4b9e3119ba1ee45a2ebe7 GIT binary patch literal 30200 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
|q2hSViV%dX`aB1@ z^7Dwylbb3@N#hj9PwZaSI6H?iA<>3zb&k_1J|wPeO{2x!8YO*Fsv`JQtFq#cZ>$SG z4z}|#N7`(gW~5!W&WYr-DLm&TjHGnp?95ZbWbD?Xer$yaDs&km7c+6<1|XnPgiPWQMjOH&;rwD9P5ElGfyQX0#P~REs9!;JCClkNu8n#{F_( zO=z^}4zDd|oOH9G!+RExY*~_B;wxq+=?S4xEet1QT~2Q7XnU@(`7CJ|5v@&H?-{iB zypaPf`B7~rEWX9^T8Qzz=#*+Q z&&pt5EN#=pUQB6SncY^oE>O)W23lVlITeBlPPCdhku5r5EA|z0at1s1Zyr@P#JLC} znX7Ba=o`-b$kCx!rI}dXvf-j%n75S52tC)*K--dqrR3@{p;;<|%aTl0PB!7b57l}QerI!0roIsw we(S@aujq^ml`?>~HkoL8$@lkF>C^s7A(t(dw&MOqSvw2B*2>C%`%d}-0Li%S{{R30 literal 0 HcmV?d00001 diff --git a/public/icon.json b/public/icon.json new file mode 100644 index 0000000..f5153d4 --- /dev/null +++ b/public/icon.json @@ -0,0 +1 @@ +[{"icon_id":0,"icon_name":"实心","icon_string":"","icon_sym":"layui-icon-heart-fill"},{"icon_id":1,"icon_name":"空心","icon_string":"","icon_sym":"layui-icon-heart"},{"icon_id":2,"icon_name":"亮度/晴","icon_string":"","icon_sym":"layui-icon-light"},{"icon_id":3,"icon_name":"时间/历史","icon_string":"","icon_sym":"layui-icon-time"},{"icon_id":4,"icon_name":"蓝牙","icon_string":"","icon_sym":"layui-icon-bluetooth"},{"icon_id":5,"icon_name":"@艾特","icon_string":"","icon_sym":"layui-icon-at"},{"icon_id":6,"icon_name":"静音","icon_string":"","icon_sym":"layui-icon-mute"},{"icon_id":7,"icon_name":"录音/麦克风","icon_string":"","icon_sym":"layui-icon-mike"},{"icon_id":8,"icon_name":"密钥/钥匙","icon_string":"","icon_sym":"layui-icon-key"},{"icon_id":9,"icon_name":"礼物/活动","icon_string":"","icon_sym":"layui-icon-gift"},{"icon_id":10,"icon_name":"邮箱","icon_string":"","icon_sym":"layui-icon-email"},{"icon_id":11,"icon_name":"RSS","icon_string":"","icon_sym":"layui-icon-rss"},{"icon_id":12,"icon_name":"WiFi","icon_string":"","icon_sym":"layui-icon-wifi"},{"icon_id":13,"icon_name":"退出/注销","icon_string":"","icon_sym":"layui-icon-logout"},{"icon_id":14,"icon_name":"Android 安卓","icon_string":"","icon_sym":"layui-icon-android"},{"icon_id":15,"icon_name":"Apple IOS 苹果","icon_string":"","icon_sym":"layui-icon-ios"},{"icon_id":16,"icon_name":"Windows","icon_string":"","icon_sym":"layui-icon-windows"},{"icon_id":17,"icon_name":"穿梭框","icon_string":"","icon_sym":"layui-icon-transfer"},{"icon_id":18,"icon_name":"客服","icon_string":"","icon_sym":"layui-icon-service"},{"icon_id":19,"icon_name":"减","icon_string":"","icon_sym":"layui-icon-subtraction"},{"icon_id":20,"icon_name":"加","icon_string":"","icon_sym":"layui-icon-addition"},{"icon_id":21,"icon_name":"滑块","icon_string":"","icon_sym":"layui-icon-slider"},{"icon_id":22,"icon_name":"打印","icon_string":"","icon_sym":"layui-icon-print"},{"icon_id":23,"icon_name":"导出","icon_string":"","icon_sym":"layui-icon-export"},{"icon_id":24,"icon_name":"列","icon_string":"","icon_sym":"layui-icon-cols"},{"icon_id":25,"icon_name":"退出全屏","icon_string":"","icon_sym":"layui-icon-screen-restore"},{"icon_id":26,"icon_name":"全屏","icon_string":"","icon_sym":"layui-icon-screen-full"},{"icon_id":27,"icon_name":"半星","icon_string":"","icon_sym":"layui-icon-rate-half"},{"icon_id":28,"icon_name":"星星-空心","icon_string":"","icon_sym":"layui-icon-rate"},{"icon_id":29,"icon_name":"星星-实心","icon_string":"","icon_sym":"layui-icon-rate-solid"},{"icon_id":30,"icon_name":"手机","icon_string":"","icon_sym":"layui-icon-cellphone"},{"icon_id":31,"icon_name":"验证码","icon_string":"","icon_sym":"layui-icon-vercode"},{"icon_id":32,"icon_name":"微信","icon_string":"","icon_sym":"layui-icon-login-wechat"},{"icon_id":33,"icon_name":"QQ","icon_string":"","icon_sym":"layui-icon-login-qq"},{"icon_id":34,"icon_name":"微博","icon_string":"","icon_sym":"layui-icon-login-weibo"},{"icon_id":35,"icon_name":"密码","icon_string":"","icon_sym":"layui-icon-password"},{"icon_id":36,"icon_name":"用户名","icon_string":"","icon_sym":"layui-icon-username"},{"icon_id":37,"icon_name":"刷新-粗","icon_string":"","icon_sym":"layui-icon-refresh-3"},{"icon_id":38,"icon_name":"授权","icon_string":"","icon_sym":"layui-icon-auz"},{"icon_id":39,"icon_name":"左向右伸缩菜单","icon_string":"","icon_sym":"layui-icon-spread-left"},{"icon_id":40,"icon_name":"右向左伸缩菜单","icon_string":"","icon_sym":"layui-icon-shrink-right"},{"icon_id":41,"icon_name":"雪花","icon_string":"","icon_sym":"layui-icon-snowflake"},{"icon_id":42,"icon_name":"提示说明","icon_string":"","icon_sym":"layui-icon-tips"},{"icon_id":43,"icon_name":"便签","icon_string":"","icon_sym":"layui-icon-note"},{"icon_id":44,"icon_name":"主页","icon_string":"","icon_sym":"layui-icon-home"},{"icon_id":45,"icon_name":"高级","icon_string":"","icon_sym":"layui-icon-senior"},{"icon_id":46,"icon_name":"刷新","icon_string":"","icon_sym":"layui-icon-refresh"},{"icon_id":47,"icon_name":"刷新","icon_string":"","icon_sym":"layui-icon-refresh-1"},{"icon_id":48,"icon_name":"旗帜","icon_string":"","icon_sym":"layui-icon-flag"},{"icon_id":49,"icon_name":"主题","icon_string":"","icon_sym":"layui-icon-theme"},{"icon_id":50,"icon_name":"消息-通知","icon_string":"","icon_sym":"layui-icon-notice"},{"icon_id":51,"icon_name":"网站","icon_string":"","icon_sym":"layui-icon-website"},{"icon_id":52,"icon_name":"控制台","icon_string":"","icon_sym":"layui-icon-console"},{"icon_id":53,"icon_name":"表情-惊讶","icon_string":"","icon_sym":"layui-icon-face-surprised"},{"icon_id":54,"icon_name":"设置-空心","icon_string":"","icon_sym":"layui-icon-set"},{"icon_id":55,"icon_name":"模板","icon_string":"","icon_sym":"layui-icon-template-1"},{"icon_id":56,"icon_name":"应用","icon_string":"","icon_sym":"layui-icon-app"},{"icon_id":57,"icon_name":"模板","icon_string":"","icon_sym":"layui-icon-template"},{"icon_id":58,"icon_name":"赞","icon_string":"","icon_sym":"layui-icon-praise"},{"icon_id":59,"icon_name":"踩","icon_string":"","icon_sym":"layui-icon-tread"},{"icon_id":60,"icon_name":"男","icon_string":"","icon_sym":"layui-icon-male"},{"icon_id":61,"icon_name":"女","icon_string":"","icon_sym":"layui-icon-female"},{"icon_id":62,"icon_name":"相机-空心","icon_string":"","icon_sym":"layui-icon-camera"},{"icon_id":63,"icon_name":"相机-实心","icon_string":"","icon_sym":"layui-icon-camera-fill"},{"icon_id":64,"icon_name":"菜单-水平","icon_string":"","icon_sym":"layui-icon-more"},{"icon_id":65,"icon_name":"菜单-垂直","icon_string":"","icon_sym":"layui-icon-more-vertical"},{"icon_id":66,"icon_name":"金额-人民币","icon_string":"","icon_sym":"layui-icon-rmb"},{"icon_id":67,"icon_name":"金额-美元","icon_string":"","icon_sym":"layui-icon-dollar"},{"icon_id":68,"icon_name":"钻石-等级","icon_string":"","icon_sym":"layui-icon-diamond"},{"icon_id":69,"icon_name":"火","icon_string":"","icon_sym":"layui-icon-fire"},{"icon_id":70,"icon_name":"返回","icon_string":"","icon_sym":"layui-icon-return"},{"icon_id":71,"icon_name":"位置-地图","icon_string":"","icon_sym":"layui-icon-location"},{"icon_id":72,"icon_name":"办公-阅读","icon_string":"","icon_sym":"layui-icon-read"},{"icon_id":73,"icon_name":"调查","icon_string":"","icon_sym":"layui-icon-survey"},{"icon_id":74,"icon_name":"表情-微笑","icon_string":"","icon_sym":"layui-icon-face-smile"},{"icon_id":75,"icon_name":"表情-哭泣","icon_string":"","icon_sym":"layui-icon-face-cry"},{"icon_id":76,"icon_name":"购物车","icon_string":"","icon_sym":"layui-icon-cart-simple"},{"icon_id":77,"icon_name":"购物车","icon_string":"","icon_sym":"layui-icon-cart"},{"icon_id":78,"icon_name":"下一页","icon_string":"","icon_sym":"layui-icon-next"},{"icon_id":79,"icon_name":"上一页","icon_string":"","icon_sym":"layui-icon-prev"},{"icon_id":80,"icon_name":"上传-空心-拖拽","icon_string":"","icon_sym":"layui-icon-upload-drag"},{"icon_id":81,"icon_name":"上传-实心","icon_string":"","icon_sym":"layui-icon-upload"},{"icon_id":82,"icon_name":"下载-圆圈","icon_string":"","icon_sym":"layui-icon-download-circle"},{"icon_id":83,"icon_name":"组件","icon_string":"","icon_sym":"layui-icon-component"},{"icon_id":84,"icon_name":"文件-粗","icon_string":"","icon_sym":"layui-icon-file-b"},{"icon_id":85,"icon_name":"用户","icon_string":"","icon_sym":"layui-icon-user"},{"icon_id":86,"icon_name":"发现-实心","icon_string":"","icon_sym":"layui-icon-find-fill"},{"icon_id":87,"icon_name":"loading","icon_string":"","icon_sym":"layui-icon-loading"},{"icon_id":88,"icon_name":"loading","icon_string":"","icon_sym":"layui-icon-loading-1"},{"icon_id":89,"icon_name":"添加","icon_string":"","icon_sym":"layui-icon-add-1"},{"icon_id":90,"icon_name":"播放","icon_string":"","icon_sym":"layui-icon-play"},{"icon_id":91,"icon_name":"暂停","icon_string":"","icon_sym":"layui-icon-pause"},{"icon_id":92,"icon_name":"音频-耳机","icon_string":"","icon_sym":"layui-icon-headset"},{"icon_id":93,"icon_name":"视频","icon_string":"","icon_sym":"layui-icon-video"},{"icon_id":94,"icon_name":"语音-声音","icon_string":"","icon_sym":"layui-icon-voice"},{"icon_id":95,"icon_name":"消息-通知-喇叭","icon_string":"","icon_sym":"layui-icon-speaker"},{"icon_id":96,"icon_name":"删除线","icon_string":"","icon_sym":"layui-icon-fonts-del"},{"icon_id":97,"icon_name":"代码","icon_string":"","icon_sym":"layui-icon-fonts-code"},{"icon_id":98,"icon_name":"HTML","icon_string":"","icon_sym":"layui-icon-fonts-html"},{"icon_id":99,"icon_name":"字体加粗","icon_string":"","icon_sym":"layui-icon-fonts-strong"},{"icon_id":100,"icon_name":"删除链接","icon_string":"","icon_sym":"layui-icon-unlink"},{"icon_id":101,"icon_name":"图片","icon_string":"","icon_sym":"layui-icon-picture"},{"icon_id":102,"icon_name":"链接","icon_string":"","icon_sym":"layui-icon-link"},{"icon_id":103,"icon_name":"表情-笑-粗","icon_string":"","icon_sym":"layui-icon-face-smile-b"},{"icon_id":104,"icon_name":"左对齐","icon_string":"","icon_sym":"layui-icon-align-left"},{"icon_id":105,"icon_name":"右对齐","icon_string":"","icon_sym":"layui-icon-align-right"},{"icon_id":106,"icon_name":"居中对齐","icon_string":"","icon_sym":"layui-icon-align-center"},{"icon_id":107,"icon_name":"字体-下划线","icon_string":"","icon_sym":"layui-icon-fonts-u"},{"icon_id":108,"icon_name":"字体-斜体","icon_string":"","icon_sym":"layui-icon-fonts-i"},{"icon_id":109,"icon_name":"Tabs 选项卡","icon_string":"","icon_sym":"layui-icon-tabs"},{"icon_id":110,"icon_name":"单选框-选中","icon_string":"","icon_sym":"layui-icon-radio"},{"icon_id":111,"icon_name":"单选框-候选","icon_string":"","icon_sym":"layui-icon-circle"},{"icon_id":112,"icon_name":"编辑","icon_string":"","icon_sym":"layui-icon-edit"},{"icon_id":113,"icon_name":"分享","icon_string":"","icon_sym":"layui-icon-share"},{"icon_id":114,"icon_name":"删除","icon_string":"","icon_sym":"layui-icon-delete"},{"icon_id":115,"icon_name":"表单","icon_string":"","icon_sym":"layui-icon-form"},{"icon_id":116,"icon_name":"手机-细体","icon_string":"","icon_sym":"layui-icon-cellphone-fine"},{"icon_id":117,"icon_name":"聊天 对话 沟通","icon_string":"","icon_sym":"layui-icon-dialogue"},{"icon_id":118,"icon_name":"文字格式化","icon_string":"","icon_sym":"layui-icon-fonts-clear"},{"icon_id":119,"icon_name":"窗口","icon_string":"","icon_sym":"layui-icon-layer"},{"icon_id":120,"icon_name":"日期","icon_string":"","icon_sym":"layui-icon-date"},{"icon_id":121,"icon_name":"水 下雨","icon_string":"","icon_sym":"layui-icon-water"},{"icon_id":122,"icon_name":"代码-圆圈","icon_string":"","icon_sym":"layui-icon-code-circle"},{"icon_id":123,"icon_name":"轮播组图","icon_string":"","icon_sym":"layui-icon-carousel"},{"icon_id":124,"icon_name":"翻页","icon_string":"","icon_sym":"layui-icon-prev-circle"},{"icon_id":125,"icon_name":"布局","icon_string":"","icon_sym":"layui-icon-layouts"},{"icon_id":126,"icon_name":"工具","icon_string":"","icon_sym":"layui-icon-util"},{"icon_id":127,"icon_name":"选择模板","icon_string":"","icon_sym":"layui-icon-templeate-1"},{"icon_id":128,"icon_name":"上传-圆圈","icon_string":"","icon_sym":"layui-icon-upload-circle"},{"icon_id":129,"icon_name":"树","icon_string":"","icon_sym":"layui-icon-tree"},{"icon_id":130,"icon_name":"表格","icon_string":"","icon_sym":"layui-icon-table"},{"icon_id":131,"icon_name":"图表","icon_string":"","icon_sym":"layui-icon-chart"},{"icon_id":132,"icon_name":"图标 报表 屏幕","icon_string":"","icon_sym":"layui-icon-chart-screen"},{"icon_id":133,"icon_name":"引擎","icon_string":"","icon_sym":"layui-icon-engine"},{"icon_id":134,"icon_name":"下三角","icon_string":"","icon_sym":"layui-icon-triangle-d"},{"icon_id":135,"icon_name":"右三角","icon_string":"","icon_sym":"layui-icon-triangle-r"},{"icon_id":136,"icon_name":"文件","icon_string":"","icon_sym":"layui-icon-file"},{"icon_id":137,"icon_name":"设置-小型","icon_string":"","icon_sym":"layui-icon-set-sm"},{"icon_id":138,"icon_name":"减少-圆圈","icon_string":"","icon_sym":"layui-icon-reduce-circle"},{"icon_id":139,"icon_name":"添加-圆圈","icon_string":"","icon_sym":"layui-icon-add-circle"},{"icon_id":140,"icon_name":"404","icon_string":"","icon_sym":"layui-icon-404"},{"icon_id":141,"icon_name":"关于","icon_string":"","icon_sym":"layui-icon-about"},{"icon_id":142,"icon_name":"箭头 向上","icon_string":"","icon_sym":"layui-icon-up"},{"icon_id":143,"icon_name":"箭头 向下","icon_string":"","icon_sym":"layui-icon-down"},{"icon_id":144,"icon_name":"箭头 向左","icon_string":"","icon_sym":"layui-icon-left"},{"icon_id":145,"icon_name":"箭头 向右","icon_string":"","icon_sym":"layui-icon-right"},{"icon_id":146,"icon_name":"圆点","icon_string":"","icon_sym":"layui-icon-circle-dot"},{"icon_id":147,"icon_name":"搜索","icon_string":"","icon_sym":"layui-icon-search"},{"icon_id":148,"icon_name":"设置-实心","icon_string":"","icon_sym":"layui-icon-set-fill"},{"icon_id":149,"icon_name":"群组","icon_string":"","icon_sym":"layui-icon-group"},{"icon_id":150,"icon_name":"好友","icon_string":"","icon_sym":"layui-icon-friends"},{"icon_id":151,"icon_name":"回复 评论 实心","icon_string":"","icon_sym":"layui-icon-reply-fill"},{"icon_id":152,"icon_name":"菜单 隐身 实心","icon_string":"","icon_sym":"layui-icon-menu-fill"},{"icon_id":153,"icon_name":"记录","icon_string":"","icon_sym":"layui-icon-log"},{"icon_id":154,"icon_name":"图片-细体","icon_string":"","icon_sym":"layui-icon-picture-fine"},{"icon_id":155,"icon_name":"表情-笑-细体","icon_string":"","icon_sym":"layui-icon-face-smile-fine"},{"icon_id":156,"icon_name":"列表","icon_string":"","icon_sym":"layui-icon-list"},{"icon_id":157,"icon_name":"发布 纸飞机","icon_string":"","icon_sym":"layui-icon-release"},{"icon_id":158,"icon_name":"对 OK","icon_string":"","icon_sym":"layui-icon-ok"},{"icon_id":159,"icon_name":"帮助","icon_string":"","icon_sym":"layui-icon-help"},{"icon_id":160,"icon_name":"客服","icon_string":"","icon_sym":"layui-icon-chat"},{"icon_id":161,"icon_name":"top 置顶","icon_string":"","icon_sym":"layui-icon-top"},{"icon_id":162,"icon_name":"收藏-空心","icon_string":"","icon_sym":"layui-icon-star"},{"icon_id":163,"icon_name":"收藏-实心","icon_string":"","icon_sym":"layui-icon-star-fill"},{"icon_id":164,"icon_name":"关闭-实心","icon_string":"ဇ","icon_sym":"layui-icon-close-fill"},{"icon_id":165,"icon_name":"关闭-空心","icon_string":"ဆ","icon_sym":"layui-icon-close"},{"icon_id":166,"icon_name":"正确","icon_string":"စ","icon_sym":"layui-icon-ok-circle"},{"icon_id":167,"icon_name":"添加-圆圈-细体","icon_string":"","icon_sym":"layui-icon-add-circle-fine"}] \ No newline at end of file diff --git a/public/images/aiwrap.png b/public/images/aiwrap.png new file mode 100644 index 0000000000000000000000000000000000000000..36fd481b048959bb4d6fbbbc932384e0a4b424e9 GIT binary patch literal 3032 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

Ho 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 z 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�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

;K*gC<{K>q-VlAkWQayjgkFFvN0Wi;*1zxq%* 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

xdZ$VuF(Navy{?2fbeF*w=89n;o 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Ƹg@&5pwDCAT7+#7VE?_;L7MV2NI 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 buM#N?wf74~)u$GRFKj3UOtj1zJOa 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
|#@xs*3gd{{Ucwd4n%4{$`ZM)Knm-cK{R3-F^Pu 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%0Dpt*8A^`WCuuv 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

KQ3>l>kB- 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ÐER6q=BHMcOsrvmBp^DxhvNSL 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 gKWwB5L3 +// +---------------------------------------------------------------------- + +// [ 应用入口文件 ] + +// 定义应用目录 +define('APP_PATH', __DIR__ . '/../application/'); +// 加载框架引导文件 +require __DIR__ . '/../thinkphp/start.php'; diff --git a/public/js/china.js b/public/js/china.js new file mode 100644 index 0000000..8d05f2f --- /dev/null +++ b/public/js/china.js @@ -0,0 +1,492 @@ +/* +* 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. +*/ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['exports', 'echarts'], factory); + } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { + // CommonJS + factory(exports, require('echarts')); + } else { + // Browser globals + factory({}, root.echarts); + } +}(this, function(exports, echarts) { + var log = function(msg) { + if (typeof console !== 'undefined') { + console && console.error && console.error(msg); + } + } + if (!echarts) { + log('ECharts is not Loaded'); + return; + } + if (!echarts.registerMap) { + log('ECharts Map is not loaded') + return; + } + echarts.registerMap('china', { + "type": "FeatureCollection", + "features": [{ + "id": "710000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@°Ü¯Û"], ["@@ƛĴÕƊÉɼģºðʀ\\ƎsÆNŌÔĚäœnÜƤɊĂǀĆĴžĤNJŨxĚĮǂƺòƌ‚–âÔ®ĮXŦţƸZûЋƕƑGđ¨ĭMó·ęcëƝɉlÝƯֹÅŃ^Ó·śŃNjƏďíåɛGɉ™¿@ăƑŽ¥ĘWǬÏĶŁâ"], ["@@\\p|WoYG¿¥I†j@¢"], ["@@…¡‰@ˆV^RqˆBbAŒnTXeRz¤Lž«³I"], ["@@ÆEE—„kWqë @œ"], ["@@fced"]], + "encodeOffsets": [[[122886, 24033]], [[123335, 22980]], [[122375, 24193]], [[122518, 24117]], [[124427, 22618]], [[124862, 26043]]] + }, + "properties": { + "cp": [121.509062, 24.044332], + "name": "台湾", + "childNum": 6 + } + }, { + "id": "130000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@o~†Z]‚ªr‰ºc_ħ²G¼s`jΟnüsœłNX_“M`ǽÓnUK…Ĝēs¤­©yrý§uģŒc†JŠ›e"], ["@@U`Ts¿m‚"], ["@@oºƋÄd–eVŽDJj£€J|Ådz•Ft~žKŨ¸IÆv|”‡¢r}膎onb˜}`RÎÄn°ÒdÞ²„^®’lnÐèĄlðӜ×]ªÆ}LiĂ±Ö`^°Ç¶p®đDcœŋ`–ZÔ’¶êqvFƚ†N®ĆTH®¦O’¾ŠIbÐã´BĐɢŴÆíȦp–ĐÞXR€·nndOž¤’OÀĈƒ­Qg˜µFo|gȒęSWb©osx|hYh•gŃfmÖĩnº€T̒Sp›¢dYĤ¶UĈjl’ǐpäìë|³kÛfw²Xjz~ÂqbTŠÑ„ěŨ@|oM‡’zv¢ZrÃVw¬ŧˏfŒ°ÐT€ªqŽs{Sž¯r æÝlNd®²Ğ džiGʂJ™¼lr}~K¨ŸƐÌWö€™ÆŠzRš¤lêmĞL΄’@¡|q]SvK€ÑcwpÏρ†ĿćènĪWlĄkT}ˆJ”¤~ƒÈT„d„™pddʾĬŠ”ŽBVt„EÀ¢ôPĎƗè@~‚k–ü\\rÊĔÖæW_§¼F˜†´©òDòj’ˆYÈrbĞāøŀG{ƀ|¦ðrb|ÀH`pʞkv‚GpuARhÞÆǶgƊTǼƹS£¨¡ù³ŘÍ]¿Ây™ôEP xX¶¹܇O¡“gÚ¡IwÃ鑦ÅB‡Ï|ǰ…N«úmH¯‹âŸDùŽyŜžŲIÄuШDž•¸dɂ‡‚FŸƒ•›Oh‡đ©OŸ›iÃ`ww^ƒÌkŸ‘ÑH«ƇǤŗĺtFu…{Z}Ö@U‡´…ʚLg®¯Oı°ÃwŸ ^˜—€VbÉs‡ˆmA…ê]]w„§›RRl£‡ȭµu¯b{ÍDěïÿȧŽuT£ġƒěŗƃĝ“Q¨fV†Ƌ•ƅn­a@‘³@šď„yýIĹÊKšŭfċŰóŒxV@tˆƯŒJ”]eƒR¾fe|rHA˜|h~Ėƍl§ÏŠlTíb ØoˆÅbbx³^zÃ͚¶Sj®A”yÂhðk`š«P€”ˈµEF†Û¬Y¨Ļrõqi¼‰Wi°§’б´°^[ˆÀ|ĠO@ÆxO\\tŽa\\tĕtû{ġŒȧXýĪÓjùÎRb›š^ΛfK[ݏděYfíÙTyŽuUSyŌŏů@Oi½’éŅ­aVcř§ax¹XŻác‡žWU£ôãºQ¨÷Ñws¥qEH‰Ù|‰›šYQoŕÇyáĂ£MðoťÊ‰P¡mšWO¡€v†{ôvîēÜISpÌhp¨ ‘j†deŔQÖj˜X³à™Ĉ[n`Yp@Už–cM`’RKhŒEbœ”pŞlNut®Etq‚nsÁŠgA‹iú‹oH‡qCX‡”hfgu“~ϋWP½¢G^}¯ÅīGCŸÑ^ãziMáļMTÃƘrMc|O_ž¯Ŏ´|‡morDkO\\mĆJfl@c̬¢aĦtRıҙ¾ùƀ^juųœK­ƒUFy™—Ɲ…›īÛ÷ąV×qƥV¿aȉd³B›qPBm›aËđŻģm“Å®Vйd^K‡KoŸnYg“¯Xhqa”Ldu¥•ÍpDž¡KąÅƒkĝęěhq‡}HyÓ]¹ǧ£…Í÷¿qáµ§š™g‘¤o^á¾ZE‡¤i`ij{n•ƒOl»ŸWÝĔįhg›F[¿¡—ßkOüš_‰€ū‹i„DZàUtėGylƒ}ŒÓM}€jpEC~¡FtoQi‘šHkk{Ãmï‚"]], + "encodeOffsets": [[[119712, 40641]], [[121616, 39981]], [[116462, 37237]]] + }, + "properties": { + "cp": [114.502461, 38.045474], + "name": "河北", + "childNum": 3 + } + }, { + "id": "140000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@Þĩ҃S‰ra}Á€yWix±Üe´lè“ßÓǏok‘ćiµVZģ¡coœ‘TS˹ĪmnÕńe–hZg{gtwªpXaĚThȑp{¶Eh—®RćƑP¿£‘Pmc¸mQÝW•ďȥoÅîɡųAďä³aωJ‘½¥PG­ąSM­™…EÅruµé€‘Yӎ•Ō_d›ĒCo­Èµ]¯_²ÕjāŽK~©ÅØ^ԛkïçămϑk]­±ƒcݯÑÃmQÍ~_a—pm…~ç¡q“ˆu{JÅŧ·Ls}–EyÁÆcI{¤IiCfUc•ƌÃp§]웫vD@¡SÀ‘µM‚ÅwuŽYY‡¡DbÑc¡hƒ×]nkoQdaMç~eD•ÛtT‰©±@¥ù@É¡‰ZcW|WqOJmĩl«ħşvOÓ«IqăV—¥ŸD[mI~Ó¢cehiÍ]Ɠ~ĥqXŠ·eƷœn±“}v•[ěďŽŕ]_‘œ•`‰¹ƒ§ÕōI™o©b­s^}Ét±ū«³p£ÿ·Wµ|¡¥ăFÏs׌¥ŅxŸÊdÒ{ºvĴÎêÌɊ²¶€ü¨|ÞƸµȲ‘LLúÉƎ¤ϊęĔV`„_bª‹S^|ŸdŠzY|dz¥p†ZbÆ£¶ÒK}tĦÔņƠ‚PYzn€ÍvX¶Ěn ĠÔ„zý¦ª˜÷žÑĸَUȌ¸‚dòÜJð´’ìúNM¬ŒXZ´‘¤ŊǸ_tldIš{¦ƀðĠȤ¥NehXnYG‚‡R° ƬDj¬¸|CĞ„Kq‚ºfƐiĺ©ª~ĆOQª ¤@ìǦɌ²æBŒÊ”TœŸ˜ʂōĖ’šĴŞ–ȀœÆÿȄlŤĒö„t”νî¼ĨXhŒ‘˜|ªM¤Ðz"], + "encodeOffsets": [[116874, 41716]] + }, + "properties": { + "cp": [111.849248, 36.857014], + "name": "山西", + "childNum": 1 + } + }, { + "id": "150000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@Č^â£Ăh–šĖMÈÄw‚\\fŦ°W ¢¾luŸD„wŠ\\̀ʉÌÛM…Ā[bӞEn}¶Vc…ê“sƒ–›¯PqƒFB…‰|S•³C|kñ•H‹d‘iÄ¥sˆʼnő…PóÑÑE^‘ÅPpy_YtS™hQ·aHwsOnʼnÚs©iqj›‰€USiº]ïWš‰«gW¡A–R붛ijʕ…Œů`çõh]y»ǃŸǛҤxÒm~zf}pf|ÜroÈzrKÈĵSƧ„ż؜Ġu¦ö"], ["@@sKCš…GS|úþX”gp›{ÁX¿Ÿć{ƱȏñZáĔyoÁhA™}ŅĆfdʼn„_¹„Y°ėǩÑ¡H¯¶oMQqð¡Ë™|‘Ñ`ƭŁX½·óۓxğįÅcQ‡ˆ“ƒs«tȋDžF“Ÿù^i‘t«Č¯[›hAi©á¥ÇĚ×l|¹y¯YȵƓ‹ñǙµï‚ċ™Ļ|Dœ™üȭ¶¡˜›oŽäÕG\\ďT¿Òõr¯œŸLguÏYęRƩšɷŌO\\İТæ^Ŋ IJȶȆbÜGŽĝ¬¿ĚVĎgª^íu½jÿĕęjık@Ľƒ]ėl¥Ë‡ĭûÁ„ƒėéV©±ćn©­ȇžÍq¯½•YÃÔʼn“ÉNѝÅÝy¹NqáʅDǡËñ­ƁYÅy̱os§ȋµʽǘǏƬɱà‘ưN¢ƔÊuľýľώȪƺɂļžxœZĈ}ÌʼnŪ˜ĺœŽĭFЛĽ̅ȣͽÒŵìƩÇϋÿȮǡŏçƑůĕ~Ǎ›¼ȳÐUf†dIxÿ\\G ˆzâɏÙOº·pqy£†@ŒŠqþ@Ǟ˽IBäƣzsÂZ†ÁàĻdñ°ŕzéØűzșCìDȐĴĺf®ŽÀľưø@ɜÖÞKĊŇƄ§‚͑těï͡VAġÑÑ»d³öǍÝXĉĕÖ{þĉu¸ËʅğU̎éhɹƆ̗̮ȘNJ֥ड़ࡰţાíϲäʮW¬®ҌeרūȠkɬɻ̼ãüfƠSצɩςåȈHϚÎKdzͲOðÏȆƘ¼CϚǚ࢚˼ФԂ¤ƌžĞ̪Qʤ´¼mȠJˀŸƲÀɠmǐnǔĎȆÞǠN~€ʢĜ‚¶ƌĆĘźʆȬ˪ĚǏĞGȖƴƀj`ĢçĶāàŃºē̃ĖćšYŒÀŎüôQÐÂŎŞdžŞêƖš˜oˆDĤÕºÑǘÛˤ³̀gńƘĔÀ^žªƂ`ªt¾äƚêĦĀ¼Ð€Ĕǎ¨Ȕ»͠^ˮÊȦƤøxRrŜH¤¸ÂxDĝŒ|ø˂˜ƮÐ¬ɚwɲFjĔ²Äw°dždÀɞ_ĸdîàŎjʜêTĞªŌ‡ŜWÈ|tqĢUB~´°ÎFC•ŽU¼pĀēƄN¦¾O¶ŠłKĊOj“Ě”j´ĜYp˜{¦„ˆSĚÍ\\Tš×ªV–÷Ší¨ÅDK°ßtŇĔKš¨ǵÂcḷ̌ĚǣȄĽF‡lġUĵœŇ‹ȣFʉɁƒMğįʏƶɷØŭOǽ«ƽū¹Ʊő̝Ȩ§ȞʘĖiɜɶʦ}¨֪ࠜ̀ƇǬ¹ǨE˦ĥªÔêFŽxúQ„Er´W„rh¤Ɛ \\talĈDJ˜Ü|[Pll̚¸ƎGú´Pž¬W¦†^¦–H]prR“n|or¾wLVnÇIujkmon£cX^Bh`¥V”„¦U¤¸}€xRj–[^xN[~ªŠxQ„‚[`ªHÆÂExx^wšN¶Ê˜|¨ì†˜€MrœdYp‚oRzNy˜ÀDs~€bcfÌ`L–¾n‹|¾T‚°c¨È¢a‚r¤–`[|òDŞĔöxElÖdH„ÀI`„Ď\\Àì~ƎR¼tf•¦^¢ķ¶e”ÐÚMŒptgj–„ɡČÅyġLû™ŇV®ŠÄÈƀ†Ď°P|ªVV†ªj–¬ĚÒêp¬–E|ŬÂc|ÀtƐK fˆ{ĘFǜƌXƲąo½Ę‘\\¥–o}›Ûu£ç­kX‘{uĩ«āíÓUŅßŢq€Ť¥lyň[€oi{¦‹L‡ń‡ðFȪȖ”ĒL„¿Ì‹ˆfŒ£K£ʺ™oqNŸƒwğc`ue—tOj×°KJ±qƒÆġm‰Ěŗos¬…qehqsuœƒH{¸kH¡Š…ÊRǪÇƌbȆ¢´ä܍¢NìÉʖ¦â©Ż؛Ç@Vu»A—ylßí¹ĵê…ÝlISò³C¹Ìâ„²i¶’Ìoú^H“²CǜңDŽ z¼g^èöŰ_‹‚–†IJĕꄜ}gÁnUI«m‰…„‹]j‡vV¼euhwqA„aW˜ƒ_µj…»çjioQR¹ēÃßt@r³[ÛlćË^ÍÉáG“›OUۗOB±•XŸkŇ¹£k|e]ol™ŸkVͼÕqtaÏõjgÁ£§U^Œ”RLˆËnX°Ç’Bz†^~wfvˆypV ¯„ƫĉ˭ȫƗŷɿÿĿƑ˃ĝÿÃǃßËőó©ǐȍŒĖM×ÍEyx‹þp]Évïè‘vƀnÂĴÖ@‚‰†V~Ĉ™Š³MEˆĸÅĖt—ējyÄDXÄxGQuv_›i¦aBçw‘˛wD™©{ŸtāmQ€{EJ§KPśƘƿ¥@‰sCT•É}ɃwˆƇy±ŸgÑ“}T[÷kÐ禫…SÒ¥¸ëBX½‰HáŵÀğtSÝÂa[ƣ°¯¦P]£ġ“–“Òk®G²„èQ°óMq}EŠóƐÇ\\ƒ‡@áügQ͋u¥Fƒ“T՛¿Jû‡]|mvāÎYua^WoÀa·­ząÒot×¶CLƗi¯¤mƎHNJ¤îìɾŊìTdåwsRÖgĒųúÍġäÕ}Q¶—ˆ¿A•†‹[¡Œ{d×uQAƒ›M•xV‹vMOmăl«ct[wº_šÇʊŽŸjb£ĦS_é“QZ“_lwgOiýe`YYLq§IÁˆdz£ÙË[ÕªuƏ³ÍT—s·bÁĽäė[›b[ˆŗfãcn¥îC¿÷µ[ŏÀQ­ōšĉm¿Á^£mJVm‡—L[{Ï_£›F¥Ö{ŹA}…×Wu©ÅaųijƳhB{·TQqÙIķˑZđ©Yc|M¡…L•eVUóK_QWk’_ĥ‘¿ãZ•»X\\ĴuUƒè‡lG®ěłTĠğDєOrÍd‚ÆÍz]‹±…ŭ©ŸÅ’]ŒÅÐ}UË¥©Tċ™ïxgckfWgi\\ÏĒ¥HkµE˜ë{»ÏetcG±ahUiñiWsɁˆ·c–C‚Õk]wȑ|ća}w…VaĚ᠞ŒG°ùnM¬¯†{ÈˆÐÆA’¥ÄêJxÙ¢”hP¢Ûˆº€µwWOŸóFŽšÁz^ÀŗÎú´§¢T¤ǻƺSė‰ǵhÝÅQgvBHouʝl_o¿Ga{ïq{¥|ſĿHĂ÷aĝÇq‡Z‘ñiñC³ª—…»E`¨åXēÕqÉû[l•}ç@čƘóO¿¡ƒFUsA‰“ʽīccšocƒ‚ƒÇS}„“£‡IS~ălkĩXçmĈ…ŀЂoÐdxÒuL^T{r@¢‘žÍƒĝKén£kQ™‰yšÅõËXŷƏL§~}kqš»IHėDžjĝŸ»ÑÞoŸå°qTt|r©ÏS‹¯·eŨĕx«È[eMˆ¿yuˆ‘pN~¹ÏyN£{©’—g‹ħWí»Í¾s“əšDž_ÃĀɗ±ą™ijĉʍŌŷ—S›É“A‹±åǥɋ@럣R©ąP©}ĹªƏj¹erƒLDĝ·{i«ƫC£µ"]], + "encodeOffsets": [[[127444, 52594]], [[113793, 40312]]] + }, + "properties": { + "cp": [111.670801, 41.818311], + "name": "内蒙古", + "childNum": 2 + } + }, { + "id": "210000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@L–Ž@@s™a"], ["@@MnNm"], ["@@d‚c"], ["@@eÀ‚C@b‚“‰"], ["@@f‡…Xwkbr–Ä`qg"], ["@@^jtW‘Q"], ["@@~ Y]c"], ["@@G`ĔN^_¿Z‚ÃM"], ["@@iX¶B‹Y"], ["@@„YƒZ"], ["@@L_{Epf"], ["@@^WqCT\\"], ["@@\\[“‹§t|”¤_"], ["@@m`n_"], ["@@Ïxnj{q_×^Giip"], ["@@@œé^B†‡ntˆaÊU—˜Ÿ]x ¯ÄPIJ­°h€ʙK³†VˆÕ@Y~†|EvĹsDŽ¦­L^p²ŸÒG ’Ël]„xxÄ_˜fT¤Ď¤cŽœP„–C¨¸TVjbgH²sdÎdHt`Bˆ—²¬GJję¶[ÐhjeXdlwhšðSȦªVÊπ‹Æ‘Z˜ÆŶ®²†^ŒÎyÅÎcPqń“ĚDMħĜŁH­ˆk„çvV[ij¼W–‚YÀäĦ’‘`XlžR`žôLUVžfK–¢†{NZdĒª’YĸÌÚJRr¸SA|ƴgŴĴÆbvªØX~†źBŽ|¦ÕœEž¤Ð`\\|Kˆ˜UnnI]¤ÀÂĊnŎ™R®Ő¿¶\\ÀøíDm¦ÎbŨab‰œaĘ\\ľã‚¸a˜tÎSƐ´©v\\ÖÚÌǴ¤Â‡¨JKr€Z_Z€fjþhPkx€`Y”’RIŒjJcVf~sCN¤ ˆE‚œhæm‰–sHy¨SðÑÌ\\\\ŸĐRZk°IS§fqŒßýáЍÙÉÖ[^¯ǤŲ„ê´\\¦¬ĆPM¯£Ÿˆ»uïpùzEx€žanµyoluqe¦W^£ÊL}ñrkqWňûP™‰UP¡ôJŠoo·ŒU}£Œ„[·¨@XŒĸŸ“‹‹DXm­Ûݏº‡›GU‹CÁª½{íĂ^cj‡k“¶Ã[q¤“LÉö³cux«zZfƒ²BWÇ®Yß½ve±ÃC•ý£W{Ú^’q^sÑ·¨‹ÍOt“¹·C¥‡GD›rí@wÕKţ݋˜Ÿ«V·i}xËÍ÷‘i©ĝ‡ɝǡ]ƒˆ{c™±OW‹³Ya±Ÿ‰_穂Hžĕoƫ€Ňqƒr³‰Lys[„ñ³¯OS–ďOMisZ†±ÅFC¥Pq{‚Ã[Pg}\\—¿ghćO…•k^ģÁFıĉĥM­oEqqZûěʼn³F‘¦oĵ—hŸÕP{¯~TÍlª‰N‰ßY“Ð{Ps{ÃVU™™eĎwk±ʼnVÓ½ŽJãÇÇ»Jm°dhcÀff‘dF~ˆ€ĀeĖ€d`sx² šƒ®EżĀdQ‹Âd^~ăÔHˆ¦\\›LKpĄVez¤NP ǹӗR™ÆąJSh­a[¦´Âghwm€BÐ¨źhI|žVVŽ—Ž|p] Â¼èNä¶ÜBÖ¼“L`‚¼bØæŒKV”ŸpoœúNZÞÒKxpw|ÊEMnzEQšŽIZ”ŽZ‡NBˆčÚFÜçmĩ‚WĪñt‘ÞĵÇñZ«uD‚±|Əlij¥ãn·±PmÍa‰–da‡ CL‡Ǒkùó¡³Ï«QaċϑOÃ¥ÕđQȥċƭy‹³ÃA"]], + "encodeOffsets": [[[123686, 41445]], [[126019, 40435]], [[124393, 40128]], [[126117, 39963]], [[125322, 40140]], [[126686, 40700]], [[126041, 40374]], [[125584, 40168]], [[125453, 40165]], [[125362, 40214]], [[125280, 40291]], [[125774, 39997]], [[125976, 40496]], [[125822, 39993]], [[125509, 40217]], [[122731, 40949]]] + }, + "properties": { + "cp": [123.429096, 41.796767], + "name": "辽宁", + "childNum": 16 + } + }, { + "id": "220000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@‘p䔳PClƒFbbÍzš€wBG’ĭ€Z„Åi“»ƒlY­ċ²SgŽkÇ£—^S‰“qd¯•‹R…©éŽ£¯S†\\cZ¹iűƏCuƍÓX‡oR}“M^o•£…R}oªU­F…uuXHlEŕ‡€Ï©¤ÛmTŽþ¤D–²ÄufàÀ­XXȱAe„yYw¬dvõ´KÊ£”\\rµÄl”iˆdā]|DÂVŒœH¹ˆÞ®ÜWnŒC”Œķ W‹§@\\¸‹ƒ~¤‹Vp¸‰póIO¢ŠVOšŇürXql~òÉK]¤¥Xrfkvzpm¶bwyFoúvð‡¼¤ N°ąO¥«³[ƒéǡű_°Õ\\ÚÊĝŽþâőàerR¨­JYlďQ[ ÏYëЧTGz•tnŠß¡gFkMŸāGÁ¤ia É‰™È¹`\\xs€¬dĆkNnuNUŠ–užP@‚vRY¾•–\\¢…ŒGªóĄ~RãÖÎĢù‚đŴÕhQŽxtcæëSɽʼníëlj£ƍG£nj°KƘµDsØÑpyƸ®¿bXp‚]vbÍZuĂ{nˆ^IüœÀSք”¦EŒvRÎûh@℈[‚Əȉô~FNr¯ôçR±ƒ­HÑl•’Ģ–^¤¢‚OðŸŒævxsŒ]ÞÁTĠs¶¿âƊGW¾ìA¦·TѬ†è¥€ÏÐJ¨¼ÒÖ¼ƒƦɄxÊ~S–tD@ŠĂ¼Ŵ¡jlºWžvЉˆzƦZЎ²CH— „Axiukd‹ŒGgetqmcžÛ£Ozy¥cE}|…¾cZ…k‚‰¿uŐã[oxGikfeäT@…šSUwpiÚFM©’£è^ڟ‚`@v¶eň†f h˜eP¶žt“äOlÔUgƒÞzŸU`lœ}ÔÆUvØ_Ō¬Öi^ĉi§²ÃŠB~¡Ĉ™ÚEgc|DC_Ȧm²rBx¼MÔ¦ŮdĨÃâYx‘ƘDVÇĺĿg¿cwÅ\\¹˜¥Yĭlœ¤žOv†šLjM_a W`zļMž·\\swqÝSA‡š—q‰Śij¯Š‘°kŠRē°wx^Đkǂғ„œž“œŽ„‹\\]˜nrĂ}²ĊŲÒøãh·M{yMzysěnĒġV·°“G³¼XÀ““™¤¹i´o¤ŃšŸÈ`̃DzÄUĞd\\i֚ŒˆmÈBĤÜɲDEh LG¾ƀľ{WaŒYÍȏĢĘÔRîĐj‹}Ǟ“ccj‡oUb½š{“h§Ǿ{K‹ƖµÎ÷žGĀÖŠåưÎs­l›•yiē«‹`姝H¥Ae^§„GK}iã\\c]v©ģZ“mÃ|“[M}ģTɟĵ‘Â`À–çm‰‘FK¥ÚíÁbXš³ÌQґHof{‰]e€pt·GŋĜYünĎųVY^’˜ydõkÅZW„«WUa~U·Sb•wGçǑ‚“iW^q‹F‚“›uNĝ—·Ew„‹UtW·Ýďæ©PuqEzwAV•—XR‰ãQ`­©GŒM‡ehc›c”ďϝd‡©ÑW_ϗYƅŒ»…é\\ƒɹ~ǙG³mØ©BšuT§Ĥ½¢Ã_ý‘L¡‘ýŸqT^rme™\\Pp•ZZbƒyŸ’uybQ—efµ]UhĿDCmûvašÙNSkCwn‰cćfv~…Y‹„ÇG"], + "encodeOffsets": [[130196, 42528]] + }, + "properties": { + "cp": [125.3245, 43.886841], + "name": "吉林", + "childNum": 1 + } + }, { + "id": "230000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@ƨƒĶTLÇyqpÇÛqe{~oyen}s‰`q‡iXG”ù]Ëp½“©lɇÁp]Þñ´FÔ^f‘äîºkà˜z¼BUvÈ@"], ["@@UƒµNÿ¥īè灋•HÍøƕ¶LŒǽ|g¨|”™Ža¾pViˆdd”~ÈiŒíďÓQġėǐZ΋ŽXb½|ſÃH½ŸKFgɱCģÛÇA‡n™‹jÕc[VĝDZÃ˄Ç_™ £ń³pŽj£º”š¿”»WH´¯”U¸đĢmžtĜyzzNN|g¸÷äűѱĉā~mq^—Œ[ƒ”››”ƒǁÑďlw]¯xQĔ‰¯l‰’€°řĴrŠ™˜BˆÞTxr[tޏĻN_yŸX`biN™Ku…P›£k‚ZĮ—¦[ºxÆÀdhŽĹŀUÈƗCw’áZħÄŭcÓ¥»NAw±qȥnD`{ChdÙFćš}¢‰A±Äj¨]ĊÕjŋ«×`VuÓś~_kŷVÝyh„“VkÄãPs”Oµ—fŸge‚Ň…µf@u_Ù ÙcŸªNªÙEojVx™T@†ãSefjlwH\\pŏäÀvŠŽlY†½d{†F~¦dyz¤PÜndsrhf‹HcŒvlwjFœ£G˜±DύƥY‡yϊu¹XikĿ¦ÏqƗǀOŜ¨LI|FRĂn sª|Cš˜zxAè¥bœfudTrFWÁ¹Am|˜ĔĕsķÆF‡´Nš‰}ć…UŠÕ@Áijſmužç’uð^ÊýowŒFzØÎĕNőžǏȎôªÌŒDŽàĀÄ˄ĞŀƒʀĀƘŸˮȬƬĊ°ƒUŸzou‡xe]}Ž…AyȑW¯ÌmK‡“Q]‹Īºif¸ÄX|sZt|½ÚUΠlkš^p{f¤lˆºlÆW –€A²˜PVܜPH”Êâ]ÎĈÌÜk´\\@qàsĔÄQºpRij¼èi†`¶—„bXƒrBgxfv»ŽuUiˆŒ^v~”J¬mVp´£Œ´VWrnP½ì¢BX‚¬h™ŠðX¹^TjVœŠriªj™tŊÄm€tPGx¸bgRšŽsT`ZozÆO]’ÒFô҆Oƒ‡ŊŒvŞ”p’cGŒêŠsx´DR–Œ{A†„EOr°Œ•žx|íœbˆ³Wm~DVjºéNN†Ëܲɶ­GƒxŷCStŸ}]ûō•SmtuÇÃĕN•™āg»šíT«u}ç½BĵÞʣ¥ëÊ¡Mێ³ãȅ¡ƋaǩÈÉQ‰†G¢·lG|›„tvgrrf«†ptęŘnŠÅĢr„I²¯LiØsPf˜_vĠd„xM prʹšL¤‹¤‡eˌƒÀđK“žïÙVY§]I‡óáĥ]ķ†Kˆ¥Œj|pŇ\\kzţ¦šnņäÔVĂîά|vW’®l¤èØr‚˜•xm¶ă~lÄƯĄ̈́öȄEÔ¤ØQĄ–Ą»ƢjȦOǺ¨ìSŖÆƬy”Qœv`–cwƒZSÌ®ü±DŽ]ŀç¬B¬©ńzƺŷɄeeOĨS’Œfm Ċ‚ƀP̎ēz©Ċ‚ÄÕÊmgŸÇsJ¥ƔˆŊśæ’΁Ñqv¿íUOµª‰ÂnĦÁ_½ä@ê텣P}Ġ[@gġ}g“ɊדûÏWXá¢užƻÌsNͽƎÁ§č՛AēeL³àydl›¦ĘVçŁpśdžĽĺſʃQíÜçÛġԏsĕ¬—Ǹ¯YßċġHµ ¡eå`ļƒrĉŘóƢFì“ĎWøxÊk†”ƈdƬv|–I|·©NqńRŀƒ¤é”eŊœŀ›ˆàŀU²ŕƀB‚Q£Ď}L¹Îk@©ĈuǰųǨ”Ú§ƈnTËÇéƟÊcfčŤ^Xm‡—HĊĕË«W·ċëx³ǔķÐċJā‚wİ_ĸ˜Ȁ^ôWr­°oú¬Ħ…ŨK~”ȰCĐ´Ƕ£’fNÎèâw¢XnŮeÂÆĶŽ¾¾xäLĴĘlļO¤ÒĨA¢Êɚ¨®‚ØCÔ ŬGƠ”ƦYĜ‡ĘÜƬDJ—g_ͥœ@čŅĻA“¶¯@wÎqC½Ĉ»NŸăëK™ďÍQ“Ùƫ[«Ãí•gßÔÇOÝáW‘ñuZ“¯ĥ€Ÿŕā¡ÑķJu¤E Ÿå¯°WKɱ_d_}}vyŸõu¬ï¹ÓU±½@gÏ¿rýD‰†g…Cd‰µ—°MFYxw¿CG£‹Rƛ½Õ{]L§{qqąš¿BÇƻğëšܭNJË|c²}Fµ}›ÙRsÓpg±ŠQNqǫŋRwŕnéÑÉKŸ†«SeYR…ŋ‹@{¤SJ}šD Ûǖ֍Ÿ]gr¡µŷjqWÛham³~S«“„›Þ]"]], + "encodeOffsets": [[[127123, 51780]], [[134456, 44547]]] + }, + "properties": { + "cp": [128.642464, 46.756967], + "name": "黑龙江", + "childNum": 2 + } + }, { + "id": "320000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@cþÅPiŠ`ZŸRu¥É\\]~°ŽY`µ†Óƒ^phÁbnÀşúŽòa–ĬºTÖŒb‚˜e¦¦€{¸ZâćNpŒ©žHr|^ˆmjhŠSEb\\afv`sz^lkŽlj‹Ätg‹¤D˜­¾Xš¿À’|ДiZ„ȀåB·î}GL¢õcßjaŸyBFµÏC^ĭ•cÙt¿sğH]j{s©HM¢ƒQnDÀ©DaÜތ·jgàiDbPufjDk`dPOîƒhw¡ĥ‡¥šG˜ŸP²ĐobºrY†„î¶aHŢ´ ]´‚rılw³r_{£DB_Ûdåuk|ˆŨ¯F Cºyr{XFy™e³Þċ‡¿Â™kĭB¿„MvÛpm`rÚã”@ƹhågËÖƿxnlč¶Åì½Ot¾dJlŠVJʜǀœŞqvnOŠ^ŸJ”Z‘ż·Q}ê͎ÅmµÒ]Žƍ¦Dq}¬R^èĂ´ŀĻĊIԒtžIJyQŐĠMNtœR®òLh‰›Ěs©»œ}OӌGZz¶A\\jĨFˆäOĤ˜HYš†JvÞHNiÜaϚɖnFQlšNM¤ˆB´ĄNöɂtp–Ŭdf先‹qm¿QûŠùއÚb¤uŃJŴu»¹Ą•lȖħŴw̌ŵ²ǹǠ͛hĭłƕrçü±Y™xci‡tğ®jű¢KOķ•Coy`å®VTa­_Ā]ŐÝɞï²ʯÊ^]afYǸÃĆēĪȣJđ͍ôƋĝÄ͎ī‰çÛɈǥ£­ÛmY`ó£Z«§°Ó³QafusNıDž_k}¢m[ÝóDµ—¡RLčiXy‡ÅNïă¡¸iĔϑNÌŕoēdōîåŤûHcs}~Ûwbù¹£¦ÓCt‹OPrƒE^ÒoŠg™ĉIµžÛÅʹK…¤½phMŠü`o怆ŀ"], + "encodeOffsets": [[121740, 32276]] + }, + "properties": { + "cp": [119.767413, 33.041544], + "name": "江苏", + "childNum": 1 + } + }, { + "id": "330000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@E^dQ]K"], ["@@jX^j‡"], ["@@sfŠbU‡"], ["@@qP\\xz[ck"], ["@@‘Rƒ¢‚FX}°[s_"], ["@@Cbœ\\—}"], ["@@e|v\\la{u"], ["@@v~u}"], ["@@QxÂF¯}"], ["@@¹nŒvÞs¯o"], ["@@rSkUEj"], ["@@bi­ZŒP"], ["@@p[}INf"], ["@@À¿€"], ["@@¹dnbŒ…"], ["@@rSŸBnR"], ["@@g~h}"], ["@@FlEk"], ["@@OdPc"], ["@@v[u\\"], ["@@FjâL~wyoo~›sµL–\\"], ["@@¬e¹aNˆ"], ["@@\\nÔ¡q]L³ë\\ÿ®ŒQ֎"], ["@@ÊA­©[¬"], ["@@KxŒv­"], ["@@@hlIk]"], ["@@pW{o||j"], ["@@Md|_mC"], ["@@¢…X£ÏylD¼XˆtH"], ["@@hlÜ[LykAvyfw^Ež›¤"], ["@@fp¤Mus“R"], ["@@®_ma~•LÁ¬šZ"], ["@@iM„xZ"], ["@@ZcYd"], ["@@Z~dOSo|A¿qZv"], ["@@@`”EN¡v"], ["@@|–TY{"], ["@@@n@m"], ["@@XWkCT\\"], ["@@ºwšZRkĕWO¢"], ["@@™X®±Grƪ\\ÔáXq{‹"], ["@@ůTG°ĄLHm°UC‹"], ["@@¤Ž€aÜx~}dtüGæţŎíĔcŖpMËВj碷ðĄÆMzˆjWKĎ¢Q¶˜À_꒔_Bı€i«pZ€gf€¤Nrq]§ĂN®«H±‡yƳí¾×ŸīàLłčŴǝĂíÀBŖÕªˆŠÁŖHŗʼnåqûõi¨hÜ·ƒñt»¹ýv_[«¸m‰YL¯‰Qª…mĉÅdMˆ•gÇjcº«•ęœ¬­K­´ƒB«Âącoċ\\xKd¡gěŧ«®á’[~ıxu·Å”KsËɏc¢Ù\\ĭƛëbf¹­ģSƒĜkáƉÔ­ĈZB{ŠaM‘µ‰fzʼnfåÂŧįƋǝÊĕġć£g³ne­ą»@­¦S®‚\\ßðCšh™iqªĭiAu‡A­µ”_W¥ƣO\\lċĢttC¨£t`ˆ™PZäuXßBs‡Ļyek€OđġĵHuXBšµ]׌‡­­\\›°®¬F¢¾pµ¼kŘó¬Wät’¸|@ž•L¨¸µr“ºù³Ù~§WI‹ŸZWŽ®’±Ð¨ÒÉx€`‰²pĜ•rOògtÁZ}þÙ]„’¡ŒŸFK‚wsPlU[}¦Rvn`hq¬\\”nQ´ĘRWb”‚_ rtČFI֊kŠŠĦPJ¶ÖÀÖJĈĄTĚòžC ²@Pú…Øzœ©PœCÈÚœĒ±„hŖ‡l¬â~nm¨f©–iļ«m‡nt–u†ÖZÜÄj“ŠLŽ®E̜Fª²iÊxبžIÈhhst"], ["@@o\\V’zRZ}y"], ["@@†@°¡mۛGĕ¨§Ianá[ýƤjfæ‡ØL–•äGr™"]], + "encodeOffsets": [[[125592, 31553]], [[125785, 31436]], [[125729, 31431]], [[125513, 31380]], [[125223, 30438]], [[125115, 30114]], [[124815, 29155]], [[124419, 28746]], [[124095, 28635]], [[124005, 28609]], [[125000, 30713]], [[125111, 30698]], [[125078, 30682]], [[125150, 30684]], [[124014, 28103]], [[125008, 31331]], [[125411, 31468]], [[125329, 31479]], [[125626, 30916]], [[125417, 30956]], [[125254, 30976]], [[125199, 30997]], [[125095, 31058]], [[125083, 30915]], [[124885, 31015]], [[125218, 30798]], [[124867, 30838]], [[124755, 30788]], [[124802, 30809]], [[125267, 30657]], [[125218, 30578]], [[125200, 30562]], [[124968, 30474]], [[125167, 30396]], [[124955, 29879]], [[124714, 29781]], [[124762, 29462]], [[124325, 28754]], [[123990, 28459]], [[125366, 31477]], [[125115, 30363]], [[125369, 31139]], [[122495, 31878]], [[125329, 30690]], [[125192, 30787]]] + }, + "properties": { + "cp": [120.153576, 29.287459], + "name": "浙江", + "childNum": 45 + } + }, { + "id": "340000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@^iuLX^"], ["@@‚e©Ehl"], ["@@°ZÆëϵmkǀwÌÕæhºgBĝâqÙĊz›ÖgņtÀÁÊÆá’hEz|WzqD¹€Ÿ°E‡ŧl{ævÜcA`¤C`|´qžxIJkq^³³ŸGšµbƒíZ…¹qpa±ď OH—¦™Ħˆx¢„gPícOl_iCveaOjCh߸i݋bÛªCC¿€m„RV§¢A|t^iĠGÀtÚs–d]ĮÐDE¶zAb àiödK¡~H¸íæAžǿYƒ“j{ď¿‘™À½W—®£ChŒÃsiŒkkly]_teu[bFa‰Tig‡n{]Gqªo‹ĈMYá|·¥f¥—őaSÕė™NµñĞ«ImŒ_m¿Âa]uĜp …Z_§{Cƒäg¤°r[_Yj‰ÆOdý“[ŽI[á·¥“Q_n‡ùgL¾mv™ˊBÜÆ¶ĊJhšp“c¹˜O]iŠ]œ¥ jtsggJǧw×jÉ©±›EFˍ­‰Ki”ÛÃÕYv…s•ˆm¬njĻª•§emná}k«ŕˆƒgđ²Ù›DǤ›í¡ªOy›†×Où±@DŸñSęćăÕIÕ¿IµĥO‰‰jNÕËT¡¿tNæŇàåyķrĕq§ÄĩsWÆßŽF¶žX®¿‰mŒ™w…RIޓfßoG‘³¾©uyH‘į{Ɓħ¯AFnuP…ÍÔzšŒV—dàôº^Ðæd´€‡oG¤{S‰¬ćxã}›ŧ×Kǥĩ«žÕOEзÖdÖsƘѨ[’Û^Xr¢¼˜§xvěƵ`K”§ tÒ´Cvlo¸fzŨð¾NY´ı~ÉĔē…ßúLÃϖ_ÈÏ|]ÂÏFl”g`bšežž€n¾¢pU‚h~ƴ˶_‚r sĄ~cž”ƈ]|r c~`¼{À{ȒiJjz`îÀT¥Û³…]’u}›f…ïQl{skl“oNdŸjŸäËzDvčoQŠďHI¦rb“tHĔ~BmlRš—V_„ħTLnñH±’DžœL‘¼L˜ªl§Ťa¸ŒĚlK²€\\RòvDcÎJbt[¤€D@®hh~kt°ǾzÖ@¾ªdb„YhüóZ ň¶vHrľ\\ʗJuxAT|dmÀO„‹[ÃԋG·ĚąĐlŪÚpSJ¨ĸˆLvÞcPæķŨŽ®mАˆálŸwKhïgA¢ųƩޖ¤OȜm’°ŒK´"]], + "encodeOffsets": [[[121722, 32278]], [[119475, 30423]], [[119168, 35472]]] + }, + "properties": { + "cp": [117.283042, 31.26119], + "name": "安徽", + "childNum": 3 + } + }, { + "id": "350000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@“zht´‡]"], ["@@aj^~ĆG—©O"], ["@@ed¨„C}}i"], ["@@@vˆPGsQ"], ["@@‰sBz‚ddW]Q"], ["@@SލQ“{"], ["@@NŽVucW"], ["@@qptBAq"], ["@@‰’¸[mu"], ["@@Q\\pD]_"], ["@@jSwUadpF"], ["@@eXª~ƒ•"], ["@@AjvFso"], ["@@fT–›_Çí\\Ÿ™—v|ba¦jZÆy€°"], ["@@IjJi"], ["@@wJI€ˆxš«¼AoNe{M­"], ["@@K‰±¡Óˆ”ČäeZ"], ["@@k¡¹Eh~c®wBk‹UplÀ¡I•~Māe£bN¨gZý¡a±Öcp©PhžI”Ÿ¢Qq…ÇGj‹|¥U™ g[Ky¬ŏ–v@OpˆtÉEŸF„\\@ åA¬ˆV{Xģ‰ĐBy…cpě…¼³Ăp·¤ƒ¥o“hqqÚ¡ŅLsƒ^ᗞ§qlŸÀhH¨MCe»åÇGD¥zPO£čÙkJA¼ß–ėu›ĕeûҍiÁŧSW¥˜QŠûŗ½ùěcݧSùĩąSWó«íęACµ›eR—åǃRCÒÇZÍ¢‹ź±^dlsŒtjD¸•‚ZpužÔâÒH¾oLUêÃÔjjēò´ĄW‚ƛ…^Ñ¥‹ĦŸ@Çò–ŠmŒƒOw¡õyJ†yD}¢ďÑÈġfŠZd–a©º²z£šN–ƒjD°Ötj¶¬ZSÎ~¾c°¶Ðm˜x‚O¸¢Pl´žSL|¥žA†ȪĖM’ņIJg®áIJČĒü` ŽQF‡¬h|ÓJ@zµ |ê³È ¸UÖŬŬÀEttĸr‚]€˜ðŽM¤ĶIJHtÏ A’†žĬkvsq‡^aÎbvŒd–™fÊòSD€´Z^’xPsÞrv‹ƞŀ˜jJd×ŘÉ ®A–ΦĤd€xĆqAŒ†ZR”ÀMźŒnĊ»ŒİÐZ— YX–æJŠyĊ²ˆ·¶q§·–K@·{s‘Xãô«lŗ¶»o½E¡­«¢±¨Yˆ®Ø‹¶^A™vWĶGĒĢžPlzfˆļŽtàAvWYãšO_‡¤sD§ssČġ[kƤPX¦Ž`¶“ž®ˆBBvĪjv©šjx[L¥àï[F…¼ÍË»ğV`«•Ip™}ccÅĥZE‹ãoP…´B@ŠD—¸m±“z«Ƴ—¿å³BRضˆœWlâþäą`“]Z£Tc— ĹGµ¶H™m@_©—kŒ‰¾xĨ‡ôȉðX«½đCIbćqK³Á‹Äš¬OAwã»aLʼn‡ËĥW[“ÂGI—ÂNxij¤D¢ŽîĎÎB§°_JœGsƒ¥E@…¤uć…P‘å†cuMuw¢BI¿‡]zG¹guĮck\\_"]], + "encodeOffsets": [[[123250, 27563]], [[122541, 27268]], [[123020, 27189]], [[122916, 27125]], [[122887, 26845]], [[122808, 26762]], [[122568, 25912]], [[122778, 26197]], [[122515, 26757]], [[122816, 26587]], [[123388, 27005]], [[122450, 26243]], [[122578, 25962]], [[121255, 25103]], [[120987, 24903]], [[122339, 25802]], [[121042, 25093]], [[122439, 26024]]] + }, + "properties": { + "cp": [118.306239, 26.075302], + "name": "福建", + "childNum": 18 + } + }, { + "id": "360000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@ĢĨƐgÂMD~ņªe^\\^§„ý©j׍cZ†Ø¨zdÒa¶ˆlҍJŒìõ`oz÷@¤u޸´†ôęöY¼‰HČƶajlÞƩ¥éZ[”|h}^U Œ ¥p„ĄžƦO lt¸Æ €Q\\€ŠaÆ|CnÂOjt­ĚĤd’ÈŒF`’¶„@Ð딠¦ōҞ¨Sêv†HĢûXD®…QgėWiØPÞìºr¤dž€NĠ¢l–•ĄtZoœCƞÔºCxrpĠV®Ê{f_Y`_ƒeq’’®Aot`@o‚DXfkp¨|Šs¬\\D‘ÄSfè©Hn¬…^DhÆyøJh“ØxĢĀLʈ„ƠPżċĄwȠ̦G®ǒĤäTŠÆ~ĦwŠ«|TF¡Šn€c³Ïå¹]ĉđxe{ÎӐ†vOEm°BƂĨİ|G’vz½ª´€H’àp”eJ݆Qšxn‹ÀŠW­žEµàXÅĪt¨ÃĖrÄwÀFÎ|ňÓMå¼ibµ¯»åDT±m[“r«_gŽmQu~¥V\\OkxtL E¢‹ƒ‘Ú^~ýê‹Pó–qo슱_Êw§ÑªåƗ⼋mĉŹ‹¿NQ“…YB‹ąrwģcÍ¥B•Ÿ­ŗÊcØiI—žƝĿuŒqtāwO]‘³YCñTeɕš‹caub͈]trlu€ī…B‘ПGsĵıN£ï—^ķqss¿FūūV՟·´Ç{éĈý‰ÿ›OEˆR_ŸđûIċâJh­ŅıN‘ȩĕB…¦K{Tk³¡OP·wn—µÏd¯}½TÍ«YiµÕsC¯„iM•¤™­•¦¯P|ÿUHv“he¥oFTu‰õ\\ŽOSs‹MòđƇiaºćXŸĊĵà·çhƃ÷ǜ{‘ígu^›đg’m[×zkKN‘¶Õ»lčÓ{XSƉv©_ÈëJbVk„ĔVÀ¤P¾ºÈMÖxlò~ªÚàGĂ¢B„±’ÌŒK˜y’áV‡¼Ã~­…`g›ŸsÙfI›Ƌlę¹e|–~udjˆuTlXµf`¿JdŠ[\\˜„L‚‘²"], + "encodeOffsets": [[116689, 26234]] + }, + "properties": { + "cp": [115.592151, 27.676493], + "name": "江西", + "childNum": 1 + } + }, { + "id": "370000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@Xjd]{K"], ["@@itbFHy"], ["@@HlGk"], ["@@T‚ŒGŸy"], ["@@K¬˜•‹U"], ["@@WdXc"], ["@@PtOs"], ["@@•LnXhc"], ["@@ppVƒu]Or"], ["@@cdzAUa"], ["@@udRhnCI‡"], ["@@ˆoIƒpR„"], ["@@Ľč{fzƤî’Kš–ÎMĮ]†—ZFˆ½Y]â£ph’™š¶¨râøÀ†ÎǨ¤^ºÄ”Gzˆ~grĚĜlĞÆ„LĆdž¢Îo¦–cv“Kb€gr°Wh”mZp ˆL]LºcU‰Æ­n”żĤÌǜbAnrOAœ´žȊcÀbƦUØrĆUÜøœĬƞ†š˜Ez„VL®öØBkŖÝĐ˹ŧ̄±ÀbÎɜnb²ĦhņBĖ›žįĦåXćì@L¯´ywƕCéõė ƿ¸‘lµ¾Z|†ZWyFYŸ¨Mf~C¿`€à_RÇzwƌfQnny´INoƬˆèôº|sT„JUš›‚L„îVj„ǎ¾Ē؍‚Dz²XPn±ŴPè¸ŔLƔÜƺ_T‘üÃĤBBċȉöA´fa„˜M¨{«M`‡¶d¡ô‰Ö°šmȰBÔjjŒ´PM|”c^d¤u•ƒ¤Û´Œä«ƢfPk¶Môlˆ]Lb„}su^ke{lC‘…M•rDŠÇ­]NÑFsmoõľH‰yGă{{çrnÓE‰‹ƕZGª¹Fj¢ïW…uøCǷ돡ąuhÛ¡^Kx•C`C\\bÅxì²ĝÝ¿_N‰īCȽĿåB¥¢·IŖÕy\\‡¹kx‡Ã£Č×GDyÕ¤ÁçFQ¡„KtŵƋ]CgÏAùSed‡cÚź—ŠuYfƒyMmhUWpSyGwMPqŀ—›Á¼zK›¶†G•­Y§Ëƒ@–´śÇµƕBmœ@Io‚g——Z¯u‹TMx}C‘‰VK‚ï{éƵP—™_K«™pÛÙqċtkkù]gŽ‹Tğwo•ɁsMõ³ă‡AN£™MRkmEʕč™ÛbMjÝGu…IZ™—GPģ‡ãħE[iµBEuŸDPԛ~ª¼ętŠœ]ŒûG§€¡QMsğNPŏįzs£Ug{đJĿļā³]ç«Qr~¥CƎÑ^n¶ÆéÎR~ݏY’I“] P‰umŝrƿ›‰›Iā‹[x‰edz‹L‘¯v¯s¬ÁY…~}…ťuٌg›ƋpÝĄ_ņī¶ÏSR´ÁP~ž¿Cyžċßdwk´Ss•X|t‰`Ä Èð€AªìÎT°¦Dd–€a^lĎDĶÚY°Ž`ĪŴǒˆ”àŠv\\ebŒZH„ŖR¬ŢƱùęO•ÑM­³FۃWp[ƒ"]], + "encodeOffsets": [[[123806, 39303]], [[123821, 39266]], [[123742, 39256]], [[123702, 39203]], [[123649, 39066]], [[123847, 38933]], [[123580, 38839]], [[123894, 37288]], [[123043, 36624]], [[123344, 38676]], [[123522, 38857]], [[123628, 38858]], [[118260, 36742]]] + }, + "properties": { + "cp": [118.000923, 36.275807], + "name": "山东", + "childNum": 13 + } + }, { + "id": "410000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@•ýL™ùµP³swIÓxcŢĞð†´E®žÚPt†ĴXØx¶˜@«ŕŕQGƒ‹Yfa[şu“ßǩ™đš_X³ijÕčC]kbc•¥CS¯ëÍB©÷‹–³­Siˆ_}m˜YTtž³xlàcȂzÀD}ÂOQ³ÐTĨ¯†ƗòËŖ[hœł‹Ŧv~††}ÂZž«¤lPǕ£ªÝŴÅR§ØnhcŒtâk‡nύ­ľŹUÓÝdKuķ‡I§oTũÙďkęĆH¸ÓŒ\\ăŒ¿PcnS{wBIvɘĽ[GqµuŸŇôYgûƒZcaŽ©@½Õǽys¯}lgg@­C\\£as€IdÍuCQñ[L±ęk·‹ţb¨©kK—’»›KC²‘òGKmĨS`ƒ˜UQ™nk}AGē”sqaJ¥ĐGR‰ĎpCuÌy ã iMc”plk|tRk†ðœev~^‘´†¦ÜŽSí¿_iyjI|ȑ|¿_»d}qŸ^{“Ƈdă}Ÿtqµ`Ƴĕg}V¡om½fa™Ço³TTj¥„tĠ—Ry”K{ùÓjuµ{t}uËR‘iŸvGŠçJFjµŠÍyqΘàQÂFewixGw½Yŷpµú³XU›½ġy™łå‰kÚwZXˆ·l„¢Á¢K”zO„Λ΀jc¼htoDHr…|­J“½}JZ_¯iPq{tę½ĕ¦Zpĵø«kQ…Ťƒ]MÛfaQpě±ǽ¾]u­Fu‹÷nƒ™čįADp}AjmcEǒaª³o³ÆÍSƇĈÙDIzˑ赟^ˆKLœ—i—Þñ€[œƒaA²zz‰Ì÷Dœ|[šíijgf‚ÕÞd®|`ƒĆ~„oĠƑô³Ŋ‘D×°¯CsŠøÀ«ì‰UMhTº¨¸ǡîS–Ô„DruÂÇZ•ÖEŽ’vPZ„žW”~؋ÐtĄE¢¦Ðy¸bŠô´oŬ¬Ž²Ês~€€]®tªašpŎJ¨Öº„_ŠŔ–`’Ŗ^Ѝ\\Ĝu–”~m²Ƹ›¸fW‰ĦrƔ}Î^gjdfÔ¡J}\\n C˜¦þWxªJRÔŠu¬ĨĨmF†dM{\\d\\ŠYÊ¢ú@@¦ª²SŠÜsC–}fNècbpRmlØ^g„d¢aÒ¢CZˆZxvÆ¶N¿’¢T@€uCœ¬^ĊðÄn|žlGl’™Rjsp¢ED}€Fio~ÔNŽ‹„~zkĘHVsDzßjƒŬŒŠŢ`Pûàl¢˜\\ÀœEhŽİgÞē X¼Pk–„|m"], + "encodeOffsets": [[118256, 37017]] + }, + "properties": { + "cp": [113.665412, 33.757975], + "name": "河南", + "childNum": 1 + } + }, { + "id": "420000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@AB‚"], ["@@lskt"], ["@@¾«}{ra®pîÃ\\™›{øCŠËyyB±„b\\›ò˜Ý˜jK›‡L ]ĎĽÌ’JyÚCƈćÎT´Å´pb©È‘dFin~BCo°BĎĚømvŒ®E^vǾ½Ĝ²Ro‚bÜeNŽ„^ĺ£R†¬lĶ÷YoĖ¥Ě¾|sOr°jY`~I”¾®I†{GqpCgyl{‡£œÍƒÍyPL“¡ƒ¡¸kW‡xYlÙæŠšŁĢzœ¾žV´W¶ùŸo¾ZHxjwfx„GNÁ•³Xéæl¶‰EièIH‰ u’jÌQ~v|sv¶Ôi|ú¢Fh˜Qsğ¦ƒSiŠBg™ÐE^ÁÐ{–čnOÂȞUÎóĔ†ÊēIJ}Z³½Mŧïeyp·uk³DsѨŸL“¶_œÅuèw»—€¡WqÜ]\\‘Ò§tƗcÕ¸ÕFÏǝĉăxŻČƟO‡ƒKÉġÿ×wg”÷IÅzCg†]m«ªGeçÃTC’«[‰t§{loWeC@ps_Bp‘­r‘„f_``Z|ei¡—oċMqow€¹DƝӛDYpûs•–‹Ykıǃ}s¥ç³[§ŸcYЧHK„«Qy‰]¢“wwö€¸ïx¼ņ¾Xv®ÇÀµRĠЋžHMž±cÏd„ƒǍũȅȷ±DSyúĝ£ŤĀàtÖÿï[îb\\}pĭÉI±Ñy…¿³x¯N‰o‰|¹H™ÏÛm‹júË~Tš•u˜ęjCöAwě¬R’đl¯ Ñb­‰ŇT†Ŀ_[Œ‘IčĄʿnM¦ğ\\É[T·™k¹œ©oĕ@A¾w•ya¥Y\\¥Âaz¯ãÁ¡k¥ne£Ûw†E©Êō¶˓uoj_Uƒ¡cF¹­[Wv“P©w—huÕyBF“ƒ`R‹qJUw\\i¡{jŸŸEPïÿ½fć…QÑÀQ{ž‚°‡fLԁ~wXg—ītêݾ–ĺ‘Hdˆ³fJd]‹HJ²…E€ƒoU¥†HhwQsƐ»Xmg±çve›]Dm͂PˆoCc¾‹_h”–høYrŊU¶eD°Č_N~øĹĚ·`z’]Äþp¼…äÌQŒv\\rCŒé¾TnkžŐڀÜa‡“¼ÝƆ̶Ûo…d…ĔňТJq’Pb ¾|JŒ¾fXŠƐîĨ_Z¯À}úƲ‹N_ĒĊ^„‘ĈaŐyp»CÇĕKŠšñL³ŠġMŒ²wrIÒŭxjb[œžn«øœ˜—æˆàƒ ^²­h¯Ú€ŐªÞ¸€Y²ĒVø}Ā^İ™´‚LŠÚm„¥ÀJÞ{JVŒųÞŃx×sxxƈē ģMř–ÚðòIf–Ċ“Œ\\Ʈ±ŒdʧĘD†vČ_Àæ~DŒċ´A®µ†¨ØLV¦êHÒ¤"]], + "encodeOffsets": [[[113712, 34000]], [[115612, 30507]], [[113649, 34054]]] + }, + "properties": { + "cp": [113.298572, 30.684355], + "name": "湖北", + "childNum": 3 + } + }, { + "id": "430000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@—n„FTs"], ["@@ßÅÆá‰½ÔXr—†CO™“…ËR‘ïÿĩ­TooQyšÓ[‹ŅBE¬–ÎÓXa„į§Ã¸G °ITxp‰úxÚij¥Ïš–̾ŠedžÄ©ĸG…œàGh‚€M¤–Â_U}Ċ}¢pczfŠþg¤€”ÇòAV‘‹M"], ["@@©K—ƒA·³CQ±Á«³BUŠƑ¹AŠtćOw™D]ŒJiØSm¯b£‘ylƒ›X…HËѱH•«–‘C^õľA–Å§¤É¥„ïyuǙuA¢^{ÌC´­¦ŷJ£^[†“ª¿‡ĕ~•Ƈ…•N… skóā‡¹¿€ï]ă~÷O§­@—Vm¡‹Qđ¦¢Ĥ{ºjԏŽŒª¥nf´•~ÕoŸž×Ûą‹MąıuZœmZcÒ IJβSÊDŽŶ¨ƚƒ’CÖŎªQؼrŭŽ­«}NÏürʬŒmjr€@ĘrTW ­SsdHzƓ^ÇÂyUi¯DÅYlŹu{hTœ}mĉ–¹¥ě‰Dÿë©ıÓ[Oº£ž“¥ót€ł¹MՄžƪƒ`Pš…Di–ÛUоÅ‌ìˆU’ñB“È£ýhe‰dy¡oċ€`pfmjP~‚kZa…ZsÐd°wj§ƒ@€Ĵ®w~^‚kÀÅKvNmX\\¨a“”сqvíó¿F„¤¡@ũÑVw}S@j}¾«pĂr–ªg àÀ²NJ¶¶Dô…K‚|^ª†Ž°LX¾ŴäPᜣEXd›”^¶›IJÞܓ~‘u¸ǔ˜Ž›MRhsR…e†`ÄofIÔ\\Ø  i”ćymnú¨cj ¢»–GČìƊÿШXeĈ¾Oð Fi ¢|[jVxrIQŒ„_E”zAN¦zLU`œcªx”OTu RLÄ¢dV„i`p˔vŎµªÉžF~ƒØ€d¢ºgİàw¸Áb[¦Zb¦–z½xBĖ@ªpº›šlS¸Ö\\Ĕ[N¥ˀmĎă’J\\‹ŀ`€…ňSڊĖÁĐiO“Ĝ«BxDõĚiv—ž–S™Ì}iùŒžÜnšÐºGŠ{Šp°M´w†ÀÒzJ²ò¨ oTçüöoÛÿñŽőФ‚ùTz²CȆȸǎۃƑÐc°dPÎŸğ˶[Ƚu¯½WM¡­Éž“’B·rížnZŸÒ `‡¨GA¾\\pē˜XhÆRC­üWGġu…T靧Ŏѝ©ò³I±³}_‘‹EÃħg®ęisÁPDmÅ{‰b[Rşs·€kPŸŽƥƒóRo”O‹ŸVŸ~]{g\\“êYƪ¦kÝbiċƵŠGZ»Ěõ…ó·³vŝž£ø@pyö_‹ëŽIkѵ‡bcѧy…×dY؎ªiþž¨ƒ[]f]Ņ©C}ÁN‡»hĻħƏ’ĩ"]], + "encodeOffsets": [[[115640, 30489]], [[112543, 27312]], [[116690, 26230]]] + }, + "properties": { + "cp": [111.782279, 28.09409], + "name": "湖南", + "childNum": 3 + } + }, { + "id": "440000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@QdˆAua"], ["@@ƒlxDLo"], ["@@sbhNLo"], ["@@Ă āŸ"], ["@@WltO[["], ["@@Krœ]S"], ["@@e„„I]y"], ["@@I|„Mym"], ["@@ƒÛ³LSŒž¼Y"], ["@@nvºB–ëui©`¾"], ["@@zdšÛ›Jw®"], ["@@†°…¯"], ["@@a yAª¸ËJIx،@€ĀHAmßV¡o•fu•o"], ["@@šs‰ŗÃÔėAƁ›ZšÄ ~°ČP‚‹äh"], ["@@‹¶Ý’Ì‚vmĞh­ı‡Q"], ["@@HœŠdSjĒ¢D}war…“u«ZqadYM"], ["@@elŒ\\LqqU"], ["@@~rMo\\"], ["@@f„^ƒC"], ["@@øPªoj÷ÍÝħXČx”°Q¨ıXNv"], ["@@gÇƳˆŽˆ”oˆŠˆ[~tly"], ["@@E–ÆC¿‘"], ["@@OŽP"], ["@@w‹†đóg‰™ĝ—[³‹¡VÙæÅöM̳¹pÁaËýý©D©Ü“JŹƕģGą¤{Ùū…ǘO²«BƱéA—Ò‰ĥ‡¡«BhlmtÃPµyU¯uc“d·w_bŝcīímGOŽ|KP’ȏ‡ŹãŝIŕŭŕ@Óoo¿ē‹±ß}Ž…ŭ‚ŸIJWÈCőâUâǙI›ğʼn©I›ijEׅÁ”³Aó›wXJþ±ÌŒÜӔĨ£L]ĈÙƺZǾĆĖMĸĤfŒÎĵl•ŨnȈ‘ĐtF”Š–FĤ–‚êk¶œ^k°f¶gŠŽœ}®Fa˜f`vXŲxl˜„¦–ÔÁ²¬ÐŸ¦pqÊ̲ˆi€XŸØRDÎ}†Ä@ZĠ’s„x®AR~®ETtĄZ†–ƈfŠŠHâÒÐA†µ\\S¸„^wĖkRzŠalŽŜ|E¨ÈNĀňZTŒ’pBh£\\ŒĎƀuXĖtKL–¶G|Ž»ĺEļĞ~ÜĢÛĊrˆO˜Ùîvd]nˆ¬VœÊĜ°R֟pM††–‚ƂªFbwžEÀˆ˜©Œž\\…¤]ŸI®¥D³|ˎ]CöAŤ¦…æ’´¥¸Lv¼€•¢ĽBaô–F~—š®²GÌҐEY„„œzk¤’°ahlV՞I^‹šCxĈPŽsB‰ƒºV‰¸@¾ªR²ĨN]´_eavSi‡vc•}p}Đ¼ƌkJœÚe thœ†_¸ ºx±ò_xN›Ë‹²‘@ƒă¡ßH©Ùñ}wkNÕ¹ÇO½¿£ĕ]ly_WìIžÇª`ŠuTÅxYĒÖ¼k֞’µ‚MžjJÚwn\\h‘œĒv]îh|’È›Ƅøègž¸Ķß ĉĈWb¹ƀdéƌNTtP[ŠöSvrCZžžaGuœbo´ŖÒÇА~¡zCI…özx¢„Pn‹•‰Èñ @ŒĥÒ¦†]ƞŠV}³ăĔñiiÄÓVépKG½Ä‘ÓávYo–C·sit‹iaÀy„ŧΡÈYDÑům}‰ý|m[węõĉZÅxUO}÷N¹³ĉo_qtă“qwµŁYلǝŕ¹tïÛUïmRCº…ˆĭ|µ›ÕÊK™½R‘ē ó]‘–GªęAx–»HO£|ām‡¡diď×YïYWªʼnOeÚtĐ«zđ¹T…ā‡úE™á²\\‹ķÍ}jYàÙÆſ¿Çdğ·ùTßÇţʄ¡XgWÀLJğ·¿ÃˆOj YÇ÷Qě‹i"]], + "encodeOffsets": [[[117381, 22988]], [[116552, 22934]], [[116790, 22617]], [[116973, 22545]], [[116444, 22536]], [[116931, 22515]], [[116496, 22490]], [[116453, 22449]], [[113301, 21439]], [[118726, 21604]], [[118709, 21486]], [[113210, 20816]], [[115482, 22082]], [[113171, 21585]], [[113199, 21590]], [[115232, 22102]], [[115739, 22373]], [[115134, 22184]], [[113056, 21175]], [[119573, 21271]], [[119957, 24020]], [[115859, 22356]], [[116561, 22649]], [[116285, 22746]]] + }, + "properties": { + "cp": [113.280637, 23.125178], + "name": "广东", + "childNum": 24 + } + }, { + "id": "450000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@H– TQ§•A"], ["@@ĨʪƒLƒƊDÎĹĐCǦė¸zÚGn£¾›rªŀÜt¬@֛ڈSx~øOŒ˜ŶÐÂæȠ\\„ÈÜObĖw^oބLf¬°bI lTØB̈F£Ć¹gñĤaY“t¿¤VSñœK¸¤nM†¼‚JE±„½¸šŠño‹ÜCƆæĪ^ŠĚQÖ¦^‡ˆˆf´Q†üÜʝz¯šlzUĺš@쇀p¶n]sxtx¶@„~ÒĂJb©gk‚{°‚~c°`ԙ¬rV\\“la¼¤ôá`¯¹LC†ÆbŒxEræO‚v[H­˜„[~|aB£ÖsºdAĐzNÂðsŽÞƔ…Ĥªbƒ–ab`ho¡³F«èVloޤ™ÔRzpp®SŽĪº¨ÖƒºN…ij„d`’a”¦¤F³ºDÎńĀìŠCžĜº¦Ċ•~nS›|gźvZkCÆj°zVÈÁƔ]LÊFZg…čP­kini«‹qǀcz͔Y®¬Ů»qR×ō©DՄ‘§ƙǃŵTÉĩ±ŸıdÑnYY›IJvNĆÌØÜ Öp–}e³¦m‹©iÓ|¹Ÿħņ›|ª¦QF¢Â¬ʖovg¿em‡^ucà÷gՎuŒíÙćĝ}FϼĹ{µHK•sLSđƃr‹č¤[Ag‘oS‹ŇYMÿ§Ç{Fśbky‰lQxĕƒ]T·¶[B…ÑÏGáşşƇe€…•ăYSs­FQ}­Bƒw‘tYğÃ@~…C̀Q ×W‡j˱rÉ¥oÏ ±«ÓÂ¥•ƒ€k—ŽwWűŒmcih³K›~‰µh¯e]lµ›él•E쉕E“ďs‡’mǖŧē`ãògK_ÛsUʝ“ćğ¶hŒöŒO¤Ǜn³Žc‘`¡y‹¦C‘ez€YŠwa™–‘[ďĵűMę§]X˜Î_‚훘Û]é’ÛUćİÕBƣ±…dƒy¹T^džûÅÑŦ·‡PĻþÙ`K€¦˜…¢ÍeœĥR¿Œ³£[~Œäu¼dl‰t‚†W¸oRM¢ď\\zœ}Æzdvň–{ÎXF¶°Â_„ÒÂÏL©Ö•TmuŸ¼ãl‰›īkiqéfA„·Êµ\\őDc¥ÝF“y›Ôć˜c€űH_hL܋êĺШc}rn`½„Ì@¸¶ªVLŒŠhŒ‹\\•Ţĺk~ŽĠið°|gŒtTĭĸ^x‘vK˜VGréAé‘bUu›MJ‰VÃO¡…qĂXËS‰ģãlýàŸ_ju‡YÛÒB†œG^˜é֊¶§ŽƒEG”ÅzěƒƯ¤Ek‡N[kdåucé¬dnYpAyČ{`]þ¯T’bÜÈk‚¡Ġ•vŒàh„ÂƄ¢Jî¶²"]], + "encodeOffsets": [[[111707, 21520]], [[107619, 25527]]] + }, + "properties": { + "cp": [108.320004, 22.82402], + "name": "广西", + "childNum": 2 + } + }, { + "id": "460000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@š¦Ŝil¢”XƦ‘ƞò–ïè§ŞCêɕrŧůÇąĻõ™·ĉ³œ̅kÇm@ċȧƒŧĥ‰Ľʉ­ƅſ“ȓÒ˦ŝE}ºƑ[ÍĜȋ gÎfǐÏĤ¨êƺ\\Ɔ¸ĠĎvʄȀœÐ¾jNðĀÒRŒšZdž™zÐŘΰH¨Ƣb²_Ġ "], + "encodeOffsets": [[112750, 20508]] + }, + "properties": { + "cp": [109.83119, 19.031971], + "name": "海南", + "childNum": 1 + } + }, { + "id": "510000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@LqKr"], ["@@Š[ĻéV£ž_ţġñpG •réÏ·~ąSfy×͂·ºſƽiÍıƣıĻmHH}siaX@iǰÁÃ×t«ƒ­Tƒ¤J–JJŒyJ•ÈŠ`Ohߦ¡uËhIyCjmÿw…ZG……Ti‹SˆsO‰žB²ŸfNmsPaˆ{M{ŠõE‘^Hj}gYpaeuž¯‘oáwHjÁ½M¡pM“–uå‡mni{fk”\\oƒÎqCw†EZ¼K›ĝŠƒAy{m÷L‡wO×SimRI¯rK™õBS«sFe‡]fµ¢óY_ÆPRcue°Cbo׌bd£ŌIHgtrnyPt¦foaXďx›lBowz‹_{ÊéWiêE„GhܸºuFĈIxf®Ž•Y½ĀǙ]¤EyŸF²ċ’w¸¿@g¢§RGv»–áŸW`ÃĵJwi]t¥wO­½a[׈]`Ãi­üL€¦LabbTÀå’c}Íh™Æhˆ‹®BH€î|Ék­¤S†y£„ia©taį·Ɖ`ō¥Uh“O…ƒĝLk}©Fos‰´›Jm„µlŁu—…ø–nÑJWΪ–YÀïAetTžŅ‚ӍG™Ë«bo‰{ıwodƟ½ƒžOġܑµxàNÖ¾P²§HKv¾–]|•B‡ÆåoZ`¡Ø`ÀmºĠ~ÌЧnDž¿¤]wğ@sƒ‰rğu‰~‘Io”[é±¹ ¿žſđӉ@q‹gˆ¹zƱřaí°KtǤV»Ã[ĩǭƑ^ÇÓ@ỗs›Zϕ‹œÅĭ€Ƌ•ěpwDóÖሯneQˌq·•GCœýS]xŸ·ý‹q³•O՜Œ¶Qzßti{ř‰áÍÇWŝŭñzÇW‹pç¿JŒ™‚Xœĩè½cŒF–ÂLiVjx}\\N†ŇĖ¥Ge–“JA¼ÄHfÈu~¸Æ«dE³ÉMA|b˜Ò…˜ćhG¬CM‚õŠ„ƤąAvƒüV€éŀ‰_V̳ĐwQj´·ZeÈÁ¨X´Æ¡Qu·»Ÿ“˜ÕZ³ġqDo‰y`L¬gdp°şŠp¦ėìÅĮZްIä”h‚‘ˆzŠĵœf²å ›ĚрKp‹IN|‹„Ñz]ń……·FU×é»R³™MƒÉ»GM«€ki€™ér™}Ã`¹ăÞmȝnÁîRǀ³ĜoİzŔwǶVÚ£À]ɜ»ĆlƂ²Ġ…þTº·àUȞÏʦ¶†I’«dĽĢdĬ¿–»Ĕ׊h\\c¬†ä²GêëĤł¥ÀǿżÃÆMº}BÕĢyFVvw–ˆxBèĻĒ©Ĉ“tCĢɽŠȣ¦āæ·HĽî“ôNԓ~^¤Ɗœu„œ^s¼{TA¼ø°¢İªDè¾Ň¶ÝJ‘®Z´ğ~Sn|ªWÚ©òzPOȸ‚bð¢|‹øĞŠŒœŒQìÛÐ@Ğ™ǎRS¤Á§d…i“´ezÝúØã]Hq„kIŸþËQǦÃsǤ[E¬ÉŪÍxXƒ·ÖƁİlƞ¹ª¹|XÊwn‘ÆƄmÀêErĒtD®ċæcQƒ”E®³^ĭ¥©l}äQto˜ŖÜqƎkµ–„ªÔĻĴ¡@Ċ°B²Èw^^RsºT£ڿœQP‘JvÄz„^Đ¹Æ¯fLà´GC²‘dt˜­ĀRt¼¤ĦOðğfÔðDŨŁĞƘïžPȆ®âbMüÀXZ ¸£@Ś›»»QÉ­™]d“sÖ×_͖_ÌêŮPrĔĐÕGĂeZÜîĘqBhtO ¤tE[h|Y‹Ô‚ZśÎs´xº±UŒ’ñˆt|O’ĩĠºNbgþŠJy^dÂY Į„]Řz¦gC‚³€R`Šz’¢AjŒ¸CL„¤RÆ»@­Ŏk\\Ç´£YW}z@Z}‰Ã¶“oû¶]´^N‡Ò}èN‚ª–P˜Íy¹`S°´†ATe€VamdUĐwʄvĮÕ\\ƒu‹Æŗ¨Yp¹àZÂm™Wh{á„}WØǍ•Éüw™ga§áCNęÎ[ĀÕĪgÖɪX˜øx¬½Ů¦¦[€—„NΆL€ÜUÖ´òrÙŠxR^–†J˜k„ijnDX{Uƒ~ET{ļº¦PZc”jF²Ė@Žp˜g€ˆ¨“B{ƒu¨ŦyhoÚD®¯¢˜ WòàFΤ¨GDäz¦kŮPœġq˚¥À]€Ÿ˜eŽâÚ´ªKxī„Pˆ—Ö|æ[xäJÞĥ‚s’NÖ½ž€I†¬nĨY´®Ð—ƐŠ€mD™ŝuäđđEb…e’e_™v¡}ìęNJē}q”É埁T¯µRs¡M@}ůa†a­¯wvƉåZwž\\Z{åû^›"]], + "encodeOffsets": [[[108815, 30935]], [[110617, 31811]]] + }, + "properties": { + "cp": [104.065735, 30.659462], + "name": "四川", + "childNum": 2 + } + }, { + "id": "520000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@†G\\†lY£‘in"], ["@@q‚|ˆ‚mc¯tχVSÎ"], ["@@hÑ£Is‡NgßH†›HªķÃh_¹ƒ¡ĝħń¦uيùŽgS¯JHŸ|sÝÅtÁïyMDč»eÕtA¤{b\\}—ƒG®u\\åPFq‹wÅaD…žK°ºâ_£ùbµ”mÁ‹ÛœĹM[q|hlaªāI}тƒµ@swtwm^oµˆD鼊yV™ky°ÉžûÛR…³‚‡eˆ‡¥]RՋěħ[ƅåÛDpŒ”J„iV™™‰ÂF²I…»mN·£›LbÒYb—WsÀbŽ™pki™TZĄă¶HŒq`……ĥ_JŸ¯ae«ƒKpÝx]aĕÛPƒÇȟ[ÁåŵÏő—÷Pw}‡TœÙ@Õs«ĿÛq©½œm¤ÙH·yǥĘĉBµĨÕnđ]K„©„œá‹ŸG纍§Õßg‡ǗĦTèƤƺ{¶ÉHÎd¾ŚÊ·OÐjXWrãLyzÉAL¾ę¢bĶėy_qMĔąro¼hĊžw¶øV¤w”²Ĉ]ʚKx|`ź¦ÂÈdr„cȁbe¸›`I¼čTF´¼Óýȃr¹ÍJ©k_șl³´_pН`oÒh޶pa‚^ÓĔ}D»^Xyœ`d˜[Kv…JPhèhCrĂĚÂ^Êƌ wˆZL­Ġ£šÁbrzOIl’MM”ĪŐžËr×ÎeŦŽtw|Œ¢mKjSǘňĂStÎŦEtqFT†¾†E쬬ôxÌO¢Ÿ KгŀºäY†„”PVgŎ¦Ŋm޼VZwVlŒ„z¤…ž£Tl®ctĽÚó{G­A‡ŒÇgeš~Αd¿æaSba¥KKûj®_ć^\\ؾbP®¦x^sxjĶI_Ä X‚⼕Hu¨Qh¡À@Ëô}ޱžGNìĎlT¸ˆ…`V~R°tbÕĊ`¸úÛtπFDu€[ƒMfqGH·¥yA‰ztMFe|R‚_Gk†ChZeÚ°to˜v`x‹b„ŒDnÐ{E}šZ˜è€x—†NEފREn˜[Pv@{~rĆAB§‚EO¿|UZ~ì„Uf¨J²ĂÝÆ€‚sª–B`„s¶œfvö¦ŠÕ~dÔq¨¸º»uù[[§´sb¤¢zþFœ¢Æ…Àhˆ™ÂˆW\\ıŽËI݊o±ĭŠ£þˆÊs}¡R]ŒěƒD‚g´VG¢‚j±®è†ºÃmpU[Á›‘Œëº°r›ÜbNu¸}Žº¼‡`ni”ºÔXĄ¤¼Ôdaµ€Á_À…†ftQQgœR—‘·Ǔ’v”}Ýלĵ]µœ“Wc¤F²›OĩųãW½¯K‚©…]€{†LóµCIµ±Mß¿hŸ•©āq¬o‚½ž~@i~TUxŪÒ¢@ƒ£ÀEîôruń‚”“‚b[§nWuMÆLl¿]x}ij­€½"]], + "encodeOffsets": [[[112158, 27383]], [[112105, 27474]], [[112095, 27476]]] + }, + "properties": { + "cp": [106.713478, 26.578343], + "name": "贵州", + "childNum": 3 + } + }, { + "id": "530000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@[„ùx½}ÑRH‘YīĺûsÍn‘iEoã½Ya²ė{c¬ĝg•ĂsA•ØÅwď‚õzFjw}—«Dx¿}UũlŸê™@•HÅ­F‰¨ÇoJ´Ónũuą¡Ã¢pÒŌ“Ø TF²‚xa²ËX€‚cʋlHîAßËŁkŻƑŷÉ©h™W­æßU‡“Ës¡¦}•teèÆ¶StǀÇ}Fd£j‹ĈZĆÆ‹¤T‚č\\Dƒ}O÷š£Uˆ§~ŃG™‚åŃDĝ¸œTsd¶¶Bªš¤u¢ŌĎo~t¾ÍŶÒtD¦Ú„iôö‰€z›ØX²ghįh½Û±¯€ÿm·zR¦Ɵ`ªŊÃh¢rOԍ´£Ym¼èêf¯ŪĽn„†cÚbŒw\\zlvWžªâˆ ¦g–mĿBş£¢ƹřbĥkǫßeeZkÙIKueT»sVesb‘aĕ  ¶®dNœĄÄpªyސ¼—„³BE˜®l‡ŽGœŭCœǶwêżĔÂe„pÍÀQƞpC„–¼ŲÈ­AÎô¶R„ä’Q^Øu¬°š_Èôc´¹ò¨P΢hlϦ´Ħ“Æ´sâDŽŲPnÊD^¯°’Upv†}®BP̪–jǬx–Söwlfòªv€qĸ|`H€­viļ€ndĜ­Ćhň•‚em·FyށqóžSᝑ³X_ĞçêtryvL¤§z„¦c¦¥jnŞk˜ˆlD¤øz½ĜàžĂŧMÅ|áƆàÊcðÂF܎‚áŢ¥\\\\º™İøÒÐJĴ‡„îD¦zK²ǏÎEh~’CD­hMn^ÌöÄ©ČZÀžaü„fɭyœpį´ěFűk]Ôě¢qlÅĆÙa¶~Äqššê€ljN¬¼H„ÊšNQ´ê¼VظE††^ŃÒyŒƒM{ŒJLoÒœęæŸe±Ķ›y‰’‡gã“¯JYÆĭĘëo¥Š‰o¯hcK«z_pŠrC´ĢÖY”—¼ v¸¢RŽÅW³Â§fǸYi³xR´ďUˊ`êĿU„û€uĆBƒƣö‰N€DH«Ĉg†——Ñ‚aB{ÊNF´¬c·Åv}eÇÃGB»”If•¦HňĕM…~[iwjUÁKE•Ž‹¾dĪçW›šI‹èÀŒoÈXòyŞŮÈXâÎŚŠj|àsRy‹µÖ›–Pr´þŒ ¸^wþTDŔ–Hr¸‹žRÌmf‡żÕâCôox–ĜƌÆĮŒ›Ð–œY˜tâŦÔ@]ÈǮƒ\\μģUsȯLbîƲŚºyh‡rŒŠ@ĒԝƀŸÀ²º\\êp“’JŠ}ĠvŠqt„Ġ@^xÀ£È†¨mËÏğ}n¹_¿¢×Y_æpˆÅ–A^{½•Lu¨GO±Õ½ßM¶w’ÁĢۂP‚›Ƣ¼pcIJxŠ|ap̬HšÐŒŊSfsðBZ¿©“XÏÒK•k†÷Eû¿‰S…rEFsÕūk”óVǥʼniTL‚¡n{‹uxţÏh™ôŝ¬ğōN“‘NJkyPaq™Âğ¤K®‡YŸxÉƋÁ]āęDqçgOg†ILu—\\_gz—]W¼ž~CÔē]bµogpў_oď`´³Țkl`IªºÎȄqÔþž»E³ĎSJ»œ_f·‚adÇqƒÇc¥Á_Źw{™L^ɱćx“U£µ÷xgĉp»ĆqNē`rĘzaĵĚ¡K½ÊBzyäKXqiWPÏɸ½řÍcÊG|µƕƣG˛÷Ÿk°_^ý|_zċBZocmø¯hhcæ\\lˆMFlư£Ĝ„ÆyH“„F¨‰µêÕ]—›HA…àӄ^it `þßäkŠĤÎT~Wlÿ¨„ÔPzUC–NVv [jâôDôď[}ž‰z¿–msSh‹¯{jïğl}šĹ[–őŒ‰gK‹©U·µË@¾ƒm_~q¡f¹…ÅË^»‘f³ø}Q•„¡Ö˳gͱ^ǁ…\\ëÃA_—¿bW›Ï[¶ƛ鏝£F{īZgm@|kHǭƁć¦UĔťƒ×ë}ǝƒeďºȡȘÏíBə£āĘPªij¶“ʼnÿ‡y©n‰ď£G¹¡I›Š±LÉĺÑdĉ܇W¥˜‰}g˜Á†{aqÃ¥aŠıęÏZ—ï`"], + "encodeOffsets": [[104636, 22969]] + }, + "properties": { + "cp": [101.512251, 24.740609], + "name": "云南", + "childNum": 1 + } + }, { + "id": "540000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@hžľxŽŖ‰xƒÒVކºÅâAĪÝȆµę¯Ňa±r_w~uSÕň‘qOj]ɄQ…£Z……UDûoY’»©M[‹L¼qãË{V͕çWViŽ]ë©Ä÷àyƛh›ÚU°ŒŒa”d„cQƒ~Mx¥™cc¡ÙaSyF—ցk­ŒuRýq¿Ôµ•QĽ³aG{¿FµëªéĜÿª@¬·–K‰·àariĕĀ«V»Ŷ™Ĵū˜gèLǴŇƶaf‹tŒèBŚ£^Šâ†ǐÝ®–šM¦ÁǞÿ¬LhŸŽJ¾óƾƺcxw‹f]Y…´ƒ¦|œQLn°aœdĊ…œ\\¨o’œǀÍŎœ´ĩĀd`tÊQŞŕ|‚¨C^©œĈ¦„¦ÎJĊ{ŽëĎjª²rЉšl`¼Ą[t|¦St辉PŒÜK¸€d˜Ƅı]s¤—î_v¹ÎVòŦj˜£Əsc—¬_Ğ´|٘¦Avަw`ăaÝaa­¢e¤ı²©ªSªšÈMĄwžÉØŔì@T‘¤—Ę™\\õª@”þo´­xA s”ÂtŎKzó´ÇĊµ¢rž^nĊ­Æ¬×üGž¢‚³ {âĊ]š™G‚~bÀgVjzlhǶf€žOšfdЉªB]pj„•TO–tĊ‚n¤}®¦ƒČ¥d¢¼»ddš”Y¼Žt—¢eȤJ¤}Ǿ¡°§¤AГlc@ĝ”sªćļđAç‡wx•UuzEÖġ~AN¹ÄÅȀݦ¿ģŁéì±H…ãd«g[؉¼ēÀ•cīľġ¬cJ‘µ…ÐʥVȝ¸ßS¹†ý±ğkƁ¼ą^ɛ¤Ûÿ‰b[}¬ōõÃ]ËNm®g@•Bg}ÍF±ǐyL¥íCˆƒIij€Ï÷њį[¹¦[⚍EÛïÁÉdƅß{âNÆāŨߝ¾ě÷yC£‡k­´ÓH@¹†TZ¥¢įƒ·ÌAЧ®—Zc…v½ŸZ­¹|ŕWZqgW“|ieZÅYVӁqdq•bc²R@†c‡¥Rã»Ge†ŸeƃīQ•}J[ғK…¬Ə|o’ėjġĠÑN¡ð¯EBčnwôɍėªƒ²•CλŹġǝʅįĭạ̃ūȹ]ΓͧgšsgȽóϧµǛ†ęgſ¶ҍć`ĘąŌJޚä¤rÅň¥ÖÁUětęuůÞiĊÄÀ\\Æs¦ÓRb|Â^řÌkÄŷ¶½÷‡f±iMݑ›‰@ĥ°G¬ÃM¥n£Øą‚ğ¯ß”§aëbéüÑOčœk£{\\‘eµª×M‘šÉfm«Ƒ{Å׃Gŏǩãy³©WÑăû‚··‘Q—òı}¯ã‰I•éÕÂZ¨īès¶ZÈsŽæĔTŘvŽgÌsN@îá¾ó@‰˜ÙwU±ÉT廣TđŸWxq¹Zo‘b‹s[׌¯cĩv‡Œėŧ³BM|¹k‰ªħ—¥TzNYnݍßpęrñĠĉRS~½ŠěVVе‚õ‡«ŒM££µB•ĉ¥áºae~³AuĐh`Ü³ç@BۘïĿa©|z²Ý¼D”£à貋ŸƒIƒû›I ā€óK¥}rÝ_Á´éMaň¨€~ªSĈ½Ž½KÙóĿeƃÆBŽ·¬ën×W|Uº}LJrƳ˜lŒµ`bÔ`QˆˆÐÓ@s¬ñIŒÍ@ûws¡åQÑßÁ`ŋĴ{Ī“T•ÚÅTSij‚‹Yo|Ç[ǾµMW¢ĭiÕØ¿@˜šMh…pÕ]j†éò¿OƇĆƇp€êĉâlØw–ěsˆǩ‚ĵ¸c…bU¹ř¨WavquSMzeo_^gsÏ·¥Ó@~¯¿RiīB™Š\\”qTGªÇĜçPoŠÿfñòą¦óQīÈáP•œābß{ƒZŗĸIæÅ„hnszÁCËìñšÏ·ąĚÝUm®ó­L·ăU›Èíoù´Êj°ŁŤ_uµ^‘°Œìǖ@tĶĒ¡Æ‡M³Ģ«˜İĨÅ®ğ†RŽāð“ggheÆ¢z‚Ê©Ô\\°ÝĎz~ź¤Pn–MĪÖB£Ÿk™n鄧żćŠ˜ĆK„ǰ¼L¶è‰âz¨u¦¥LDĘz¬ýÎmĘd¾ß”Fz“hg²™Fy¦ĝ¤ċņbΛ@y‚Ąæm°NĮZRÖíŽJ²öLĸÒ¨Y®ƌÐV‰à˜tt_ڀÂyĠzž]Ţh€zĎ{†ĢX”ˆc|šÐqŽšfO¢¤ög‚ÌHNŽ„PKŖœŽ˜Uú´xx[xˆvĐCûŠìÖT¬¸^}Ìsòd´_އKgžLĴ…ÀBon|H@–Êx˜—¦BpŰˆŌ¿fµƌA¾zLjRxжF”œkĄźRzŀˆ~¶[”´Hnª–VƞuĒ­È¨ƎcƽÌm¸ÁÈM¦x͊ëÀxdžB’šú^´W†£–d„kɾĬpœw‚˂ØɦļĬIŚœÊ•n›Ŕa¸™~J°î”lɌxĤÊÈðhÌ®‚g˜T´øŽàCˆŽÀ^ªerrƘdž¢İP|Ė ŸWœªĦ^¶´ÂL„aT±üWƜ˜ǀRšŶUńšĖ[QhlLüA†‹Ü\\†qR›Ą©"], + "encodeOffsets": [[90849, 37210]] + }, + "properties": { + "cp": [89.132212, 30.860361], + "name": "西藏", + "childNum": 1 + } + }, { + "id": "610000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@˜p¢—ȮµšûG™Ħ}Ħšðǚ¶òƄ€jɂz°{ºØkÈęâ¦jª‚Bg‚\\œċ°s¬Ž’]jžú ‚E”Ȍdž¬s„t‡”RˆÆdĠݎwܔ¸ôW¾ƮłÒ_{’Ìšû¼„jº¹¢GǪÒ¯ĘƒZ`ºŊƒecņąš~BÂgzpâēòYǠȰÌTΨÂWœ|fcŸă§uF—Œ@NŸ¢XLƒŠRMº[ğȣſï|¥J™kc`sʼnǷ’Y¹‹W@µ÷K…ãï³ÛIcñ·VȋڍÒķø©—þ¥ƒy‚ÓŸğęmWµÎumZyOŅƟĥÓ~sÑL¤µaŅY¦ocyZ{‰y c]{ŒTa©ƒ`U_Ěē£ωÊƍKù’K¶ȱÝƷ§{û»ÅÁȹÍéuij|¹cÑd‘ŠìUYƒŽO‘uF–ÕÈYvÁCqӃT•Ǣí§·S¹NgŠV¬ë÷Át‡°Dد’C´ʼnƒópģ}„ċcE˅FŸŸéGU¥×K…§­¶³B‹Č}C¿åċ`wġB·¤őcƭ²ő[Å^axwQO…ÿEËߌ•ĤNĔŸwƇˆÄŠńwĪ­Šo[„_KÓª³“ÙnK‰Çƒěœÿ]ď€ă_d©·©Ýŏ°Ù®g]±„Ÿ‡ß˜å›—¬÷m\\›iaǑkěX{¢|ZKlçhLt€Ňîŵ€œè[€É@ƉĄEœ‡tƇÏ˜³­ħZ«mJ…›×¾‘MtÝĦ£IwÄå\\Õ{‡˜ƒOwĬ©LÙ³ÙgBƕŀr̛ĢŭO¥lãyC§HÍ£ßEñŸX¡—­°ÙCgpťz‘ˆb`wI„vA|§”‡—hoĕ@E±“iYd¥OϹS|}F@¾oAO²{tfžÜ—¢Fǂ҈W²°BĤh^Wx{@„¬‚­F¸¡„ķn£P|ŸªĴ@^ĠĈæb–Ôc¶l˜Yi…–^Mi˜cϰÂ[ä€vï¶gv@À“Ĭ·lJ¸sn|¼u~a]’ÆÈtŌºJp’ƒþ£KKf~ЦUbyäIšĺãn‡Ô¿^­žŵMT–hĠܤko¼Ŏìąǜh`[tŒRd²IJ_œXPrɲ‰l‘‚XžiL§àƒ–¹ŽH˜°Ȧqº®QC—bA†„ŌJ¸ĕÚ³ĺ§ `d¨YjžiZvRĺ±öVKkjGȊĐePОZmļKÀ€‚[ŠŽ`ösìh†ïÎoĬdtKÞ{¬èÒÒBŒÔpIJÇĬJŊ¦±J«ˆY§‹@·pH€µàåVKe›pW†ftsAÅqC·¬ko«pHÆuK@oŸHĆۄķhx“e‘n›S³àǍrqƶRbzy€¸ËАl›¼EºpĤ¼Œx¼½~Ğ’”à@†ÚüdK^ˆmÌSj"], + "encodeOffsets": [[110234, 38774]] + }, + "properties": { + "cp": [108.948024, 34.263161], + "name": "陕西", + "childNum": 1 + } + }, { + "id": "620000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@VuUv"], ["@@ũ‹EĠtt~nkh`Q‰¦ÅÄÜdw˜Ab×ĠąJˆ¤DüègĺqBqœj°lI¡ĨÒ¤úSHbš‡ŠjΑBаaZˆ¢KJŽ’O[|A£žDx}Nì•HUnrk„ kp€¼Y kMJn[aG‚áÚÏ[½rc†}aQxOgsPMnUs‡nc‹Z…ž–sKúvA›t„Þġ’£®ĀYKdnFwš¢JE°”Latf`¼h¬we|€Æ‡šbj}GA€·~WŽ”—`†¢MC¤tL©IJ°qdf”O‚“bÞĬ¹ttu`^ZúE`Œ[@„Æsîz®¡’C„ƳƜG²“R‘¢R’m”fŽwĸg܃‚ą G@pzJM½mŠhVy¸uÈÔO±¨{LfæU¶ßGĂq\\ª¬‡²I‚¥IʼnÈīoı‹ÓÑAçÑ|«LÝcspīðÍg…të_õ‰\\ĉñLYnĝg’ŸRǡÁiHLlõUĹ²uQjYi§Z_c¨Ÿ´ĹĖÙ·ŋI…ƒaBD˜­R¹ȥr—¯G•ºß„K¨jWk’ɱŠOq›Wij\\a­‹Q\\sg_ĆǛōëp»£lğۀgS•ŶN®À]ˆÓäm™ĹãJaz¥V}‰Le¤L„ýo‘¹IsŋÅÇ^‘Žbz…³tmEÁ´aйcčecÇN•ĊãÁ\\蝗dNj•]j†—ZµkÓda•ćå]ğij@ ©O{¤ĸm¢ƒE·®ƒ«|@Xwg]A챝‡XǁÑdzªc›wQÚŝñsÕ³ÛV_ýƒ˜¥\\ů¥©¾÷w—Ž©WÕÊĩhÿÖÁRo¸V¬âDb¨šhûx–Ê×nj~Zâƒg|šXÁnßYoº§ZÅŘvŒ[„ĭÖʃuďxcVbnUSf…B¯³_Tzº—ΕO©çMÑ~Mˆ³]µ^püµ”ŠÄY~y@X~¤Z³€[Èōl@®Å¼£QKƒ·Di‹¡By‘ÿ‰Q_´D¥hŗyƒ^ŸĭÁZ]cIzý‰ah¹MĪğP‘s{ò‡‹‘²Vw¹t³Ŝˁ[ŽÑ}X\\gsFŸ£sPAgěp×ëfYHāďÖqēŭOÏë“dLü•\\iŒ”t^c®šRʺ¶—¢H°mˆ‘rYŸ£BŸ¹čIoľu¶uI]vģSQ{ƒUŻ”Å}QÂ|̋°ƅ¤ĩŪU ęĄžÌZҞ\\v˜²PĔ»ƢNHƒĂyAmƂwVmž`”]ȏb•”H`‰Ì¢²ILvĜ—H®¤Dlt_„¢JJÄämèÔDëþgºƫ™”aʎÌrêYi~ ÎݤNpÀA¾Ĕ¼b…ð÷’Žˆ‡®‚”üs”zMzÖĖQdȨý†v§Tè|ªH’þa¸|šÐ ƒwKĢx¦ivr^ÿ ¸l öæfƟĴ·PJv}n\\h¹¶v†·À|\\ƁĚN´Ĝ€çèÁz]ġ¤²¨QÒŨTIl‡ªťØ}¼˗ƦvÄùØE‹’«Fï˛Iq”ōŒTvāÜŏ‚íÛߜÛV—j³âwGăÂíNOŠˆŠPìyV³ʼnĖýZso§HіiYw[߆\\X¦¥c]ÔƩÜ·«j‡ÐqvÁ¦m^ċ±R™¦΋ƈťĚgÀ»IïĨʗƮްƝ˜ĻþÍAƉſ±tÍEÕÞāNU͗¡\\ſčåÒʻĘm ƭÌŹöʥ’ëQ¤µ­ÇcƕªoIýˆ‰Iɐ_mkl³ă‰Ɠ¦j—¡Yz•Ňi–}Msßõ–īʋ —}ƒÁVmŸ_[n}eı­Uĥ¼‘ª•I{ΧDӜƻėoj‘qYhĹT©oūĶ£]ďxĩ‹ǑMĝ‰q`B´ƃ˺Ч—ç~™²ņj@”¥@đ´ί}ĥtPńǾV¬ufӃÉC‹tÓ̻‰…¹£G³€]ƖƾŎĪŪĘ̖¨ʈĢƂlɘ۪üºňUðǜȢƢż̌ȦǼ‚ĤŊɲĖ­Kq´ï¦—ºĒDzņɾªǀÞĈĂD†½ĄĎÌŗĞrôñnŽœN¼â¾ʄľԆ|DŽŽ֦ज़ȗlj̘̭ɺƅêgV̍ʆĠ·ÌĊv|ýĖÕWĊǎÞ´õ¼cÒÒBĢ͢UĜð͒s¨ňƃLĉÕÝ@ɛƯ÷¿Ľ­ĹeȏijëCȚDŲyê×Ŗyò¯ļcÂßY…tÁƤyAã˾J@ǝrý‹‰@¤…rz¸oP¹ɐÚyᐇHŸĀ[Jw…cVeȴϜ»ÈŽĖ}ƒŰŐèȭǢόĀƪÈŶë;Ñ̆ȤМľĮEŔ—ĹŊũ~ËUă{ŸĻƹɁύȩþĽvĽƓÉ@ē„ĽɲßǐƫʾǗĒpäWÐxnsÀ^ƆwW©¦cÅ¡Ji§vúF¶Ž¨c~c¼īŒeXǚ‹\\đ¾JŽwÀďksãA‹fÕ¦L}wa‚o”Z’‹D½†Ml«]eÒÅaɲáo½FõÛ]ĻÒ¡wYR£¢rvÓ®y®LF‹LzĈ„ôe]gx}•|KK}xklL]c¦£fRtív¦†PĤoH{tK"]], + "encodeOffsets": [[[108619, 36299]], [[108589, 36341]]] + }, + "properties": { + "cp": [103.823557, 36.058039], + "name": "甘肃", + "childNum": 2 + } + }, { + "id": "630000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@InJm"], ["@@CƒÆ½OŃĦsΰ~dz¦@@“Ņiš±è}ؘƄ˹A³r_ĞŠǒNΌĐw¤^ŬĵªpĺSZg’rpiƼĘԛ¨C|͖J’©Ħ»®VIJ~f\\m `Un„˜~ʌŸ•ĬàöNt•~ňjy–¢Zi˜Ɣ¥ĄŠk´nl`JʇŠJþ©pdƖ®È£¶ìRʦ‘źõƮËnŸʼėæÑƀĎ[‚˜¢VÎĂMÖÝÎF²sƊƀÎBļýƞ—¯ʘƭðħ¼Jh¿ŦęΌƇš¥²Q]Č¥nuÂÏriˆ¸¬ƪÛ^Ó¦d€¥[Wà…x\\ZŽjҕ¨GtpþYŊĕ´€zUO뇉P‰îMĄÁxH´á˜iÜUà›îÜՁĂÛSuŎ‹r“œJð̬EŒ‘FÁú×uÃÎkr“Ē{V}İ«O_ÌËĬ©ŽÓŧSRѱ§Ģ£^ÂyèçěM³Ƃę{[¸¿u…ºµ[gt£¸OƤĿéYŸõ·kŸq]juw¥Dĩƍ€õÇPéĽG‘ž©ã‡¤G…uȧþRcÕĕNy“yût“ˆ­‡ø‘†ï»a½ē¿BMoᣟÍj}éZËqbʍš“Ƭh¹ìÿÓAçãnIáI`ƒks£CG­ě˜Uy×Cy•…’Ÿ@¶ʡÊBnāzG„ơMē¼±O÷õJËĚăVŸĪũƆ£Œ¯{ËL½Ìzż“„VR|ĠTbuvJvµhĻĖH”Aëáa…­OÇðñęNw‡…œľ·L›mI±íĠĩPÉ×®ÿs—’cB³±JKßĊ«`…ađ»·QAmO’‘Vţéÿ¤¹SQt]]Çx€±¯A@ĉij¢Ó祖•ƒl¶ÅÛr—ŕspãRk~¦ª]Į­´“FR„åd­ČsCqđéFn¿Åƃm’Éx{W©ºƝºįkÕƂƑ¸wWūЩÈFž£\\tÈ¥ÄRÈýÌJ ƒlGr^×äùyÞ³fj”c†€¨£ÂZ|ǓMĝšÏ@ëÜőR‹›ĝ‰Œ÷¡{aïȷPu°ËXÙ{©TmĠ}Y³’­ÞIňµç½©C¡į÷¯B»|St»›]vƒųƒs»”}MÓ ÿʪƟǭA¡fs˜»PY¼c¡»¦c„ċ­¥£~msĉP•–Siƒ^o©A‰Šec‚™PeǵŽkg‚yUi¿h}aH™šĉ^|ᴟ¡HØûÅ«ĉ®]m€¡qĉ¶³ÈyôōLÁst“BŸ®wn±ă¥HSò뚣˜S’ë@לÊăxÇN©™©T±ª£IJ¡fb®ÞbŽb_Ą¥xu¥B—ž{łĝ³«`d˜Ɛt—¤ťiñžÍUuºí`£˜^tƃIJc—·ÛLO‹½Šsç¥Ts{ă\\_»™kϊ±q©čiìĉ|ÍIƒ¥ć¥›€]ª§D{ŝŖÉR_sÿc³Īō›ƿΑ›§p›[ĉ†›c¯bKm›R¥{³„Z†e^ŽŒwx¹dƽŽôIg §Mĕ ƹĴ¿—ǣÜ̓]‹Ý–]snåA{‹eŒƭ`ǻŊĿ\\ijŬű”YÂÿ¬jĖqŽßbЏ•L«¸©@ěĀ©ê¶ìÀEH|´bRľž–Ó¶rÀQþ‹vl®Õ‚E˜TzÜdb ˜hw¤{LR„ƒd“c‹b¯‹ÙVgœ‚ƜßzÃô쮍^jUèXΖ|UäÌ»rKŽ\\ŒªN‘¼pZCü†VY††¤ɃRi^rPҒTÖ}|br°qňb̰ªiƶGQ¾²„x¦PœmlŜ‘[Ĥ¡ΞsĦŸÔÏâ\\ªÚŒU\\f…¢N²§x|¤§„xĔsZPòʛ²SÐqF`ª„VƒÞŜĶƨVZŒÌL`ˆ¢dŐIqr\\oäõ–F礻Ŷ×h¹]Clـ\\¦ďÌį¬řtTӺƙgQÇÓHţĒ”´ÃbEÄlbʔC”|CˆŮˆk„Ʈ[ʼ¬ňœ´KŮÈΰÌζƶlð”ļA†TUvdTŠG†º̼ŠÔ€ŒsÊDԄveOg"]], + "encodeOffsets": [[[105308, 37219]], [[95370, 40081]]] + }, + "properties": { + "cp": [96.778916, 35.623178], + "name": "青海", + "childNum": 2 + } + }, { + "id": "640000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@KëÀęĞ«OęȿȕŸı]ʼn¡åįÕÔ«Ǵõƪ™ĚQÐZhv K°›öqÀѐS[ÃÖHƖčË‡nL]ûc…Ùß@‚“ĝ‘¾}w»»‹oģF¹œ»kÌÏ·{zPƒ§B­¢íyÅt@ƒ@áš]Yv_ssģ¼i߁”ĻL¾ġsKD£¡N_…“˜X¸}B~Haiˆ™Åf{«x»ge_bs“KF¯¡Ix™mELcÿZ¤­Ģ‘ƒÝœsuBLù•t†ŒYdˆmVtNmtOPhRw~bd…¾qÐ\\âÙH\\bImlNZŸ»loƒŸqlVm–Gā§~QCw¤™{A\\‘PKŸNY‡¯bF‡kC¥’sk‹Šs_Ã\\ă«¢ħkJi¯r›rAhĹûç£CU‡ĕĊ_ԗBixÅُĄnªÑaM~ħpOu¥sîeQ¥¤^dkKwlL~{L~–hw^‚ófćƒKyEŒ­K­zuÔ¡qQ¤xZÑ¢^ļöܾEpž±âbÊÑÆ^fk¬…NC¾‘Œ“YpxbK~¥Že֎ŒäBlt¿Đx½I[ĒǙŒWž‹f»Ĭ}d§dµùEuj¨‚IÆ¢¥dXªƅx¿]mtÏwßR͌X¢͎vÆzƂZò®ǢÌʆCrâºMÞzžÆMҔÊÓŊZľ–r°Î®Ȉmª²ĈUªĚøºˆĮ¦ÌĘk„^FłĬhĚiĀ˾iİbjÕ"], ["@@mfwěwMrŢªv@G‰"]], + "encodeOffsets": [[[109366, 40242]], [[108600, 36303]]] + }, + "properties": { + "cp": [106.278179, 37.26637], + "name": "宁夏", + "childNum": 2 + } + }, { + "id": "650000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@QØĔ²X¨”~ǘBºjʐߨvK”ƔX¨vĊOžÃƒ·¢i@~c—‡ĝe_«”Eš“}QxgɪëÏÃ@sÅyXoŖ{ô«ŸuX…ê•Îf`œC‚¹ÂÿÐGĮÕĞXŪōŸMźÈƺQèĽôe|¿ƸJR¤ĘEjcUóº¯Ĩ_ŘÁMª÷Ð¥Oéȇ¿ÖğǤǷÂF҇zÉx[]­Ĥĝ‰œ¦EP}ûƥé¿İƷTėƫœŕƅ™ƱB»Đ±’ēO…¦E–•}‘`cȺrĦáŖuҞª«IJ‡πdƺÏØZƴwʄ¤ĖGЙǂZ̓èH¶}ÚZצʥĪï|ÇĦMŔ»İĝLj‹ì¥Βœba­¯¥ǕǚkĆŵĦɑĺƯxūД̵nơʃĽá½M»›òmqóŘĝč˾ăC…ćāƿÝɽ©DZŅ¹đ¥˜³ðLrÁ®ɱĕģʼnǻ̋ȥơŻǛȡVï¹Ň۩ûkɗġƁ§ʇė̕ĩũƽō^ƕŠUv£ƁQï“Ƶkŏ½ΉÃŭdzLқʻ«ƭ\\lƒ‡ŭD‡“{ʓDkaFÃÄa“³ŤđÔGRÈƚhSӹŚsİ«ĐË[¥ÚDkº^Øg¼ŵ¸£EÍö•€ůʼnT¡c_‡ËKY‹ƧUśĵ„݃U_©rETÏʜ±OñtYw獃{£¨uM³x½şL©Ùá[ÓÐĥ Νtģ¢\\‚ś’nkO›w¥±ƒT»ƷFɯàĩÞáB¹Æ…ÑUw„੍žĽw[“mG½Èå~‡Æ÷QyŠěCFmĭZī—ŵVÁ™ƿQƛ—ûXS²‰b½KϽĉS›©ŷXĕŸ{ŽĕK·¥Ɨcqq©f¿]‡ßDõU³h—­gËÇïģÉɋw“k¯í}I·šœbmœÉ–ř›īJɥĻˁ×xo›ɹī‡l•c…¤³Xù]‘™DžA¿w͉ì¥wÇN·ÂËnƾƍdǧđ®Ɲv•Um©³G\\“}µĿ‡QyŹl㓛µEw‰LJQ½yƋBe¶ŋÀů‡ož¥A—˜Éw@•{Gpm¿Aij†ŽKLhˆ³`ñcËtW‚±»ÕS‰ëüÿďD‡u\\wwwù³—V›LŕƒOMËGh£õP¡™er™Ïd{“‡ġWÁ…č|yšg^ğyÁzÙs`—s|ÉåªÇ}m¢Ń¨`x¥’ù^•}ƒÌ¥H«‰Yªƅ”Aйn~Ꝛf¤áÀz„gŠÇDIԝ´AňĀ҄¶ûEYospõD[{ù°]u›Jq•U•|Soċxţ[õÔĥkŋÞŭZ˺óYËüċrw €ÞkrťË¿XGÉbřaDü·Ē÷Aê[Ää€I®BÕИÞ_¢āĠpŠÛÄȉĖġDKwbm‡ÄNô‡ŠfœƫVÉvi†dz—H‘‹QµâFšù­Âœ³¦{YGžƒd¢ĚÜO „€{Ö¦ÞÍÀPŒ^b–ƾŠlŽ[„vt×ĈÍE˨¡Đ~´î¸ùÎh€uè`¸ŸHÕŔVºwĠââWò‡@{œÙNÝ´ə²ȕn{¿¥{l—÷eé^e’ďˆXj©î\\ªÑò˜Üìc\\üqˆÕ[Č¡xoÂċªbØ­Œø|€¶ȴZdÆÂšońéŒGš\\”¼C°ÌƁn´nxšÊOĨ’ہƴĸ¢¸òTxÊǪMīИÖŲÃɎOvˆʦƢ~FއRěò—¿ġ~åŊœú‰Nšžš¸qŽ’Ę[Ĕ¶ÂćnÒPĒÜvúĀÊbÖ{Äî¸~Ŕünp¤ÂH¾œĄYÒ©ÊfºmԈĘcDoĬMŬ’˜S¤„s²‚”ʘچžȂVŦ –ŽèW°ªB|IJXŔþÈJĦÆæFĚêŠYĂªĂ]øªŖNÞüA€’fɨJ€˜¯ÎrDDšĤ€`€mz\\„§~D¬{vJÂ˜«lµĂb–¤p€ŌŰNĄ¨ĊXW|ų ¿¾ɄĦƐMT”‡òP˜÷fØĶK¢ȝ˔Sô¹òEð­”`Ɩ½ǒÂň×äı–§ĤƝ§C~¡‚hlå‚ǺŦŞkâ’~}ŽFøàIJaĞ‚fƠ¥Ž„Ŕdž˜®U¸ˆźXœv¢aƆúŪtŠųƠjd•ƺŠƺÅìnrh\\ĺ¯äɝĦ]èpĄ¦´LƞĬŠ´ƤǬ˼Ēɸ¤rºǼ²¨zÌPðŀbþ¹ļD¢¹œ\\ĜÑŚŸ¶ZƄ³àjĨoâŠȴLʉȮŒĐ­ĚăŽÀêZǚŐ¤qȂ\\L¢ŌİfÆs|zºeªÙæ§΢{Ā´ƐÚ¬¨Ĵà²łhʺKÞºÖTŠiƢ¾ªì°`öøu®Ê¾ãØ"], + "encodeOffsets": [[88824, 50096]] + }, + "properties": { + "cp": [85.617733, 40.792818], + "name": "新疆", + "childNum": 1 + } + }, { + "id": "110000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@ĽOÁ›ûtŷmiÍt_H»Ĩ±d`й­{bw…Yr“³S]§§o¹€qGtm_Sŧ€“oa›‹FLg‘QN_•dV€@Zom_ć\\ߚc±x¯oœRcfe…£’o§ËgToÛJíĔóu…|wP¤™XnO¢ÉˆŦ¯rNÄā¤zâŖÈRpŢZŠœÚ{GŠrFt¦Òx§ø¹RóäV¤XdˆżâºWbwڍUd®bêņ¾‘jnŎGŃŶŠnzÚSeîĜZczî¾i]͜™QaúÍÔiþĩȨWĢ‹ü|Ėu[qb[swP@ÅğP¿{\\‡¥A¨Ï‘Ѩj¯ŠX\\¯œMK‘pA³[H…īu}}"], + "encodeOffsets": [[120023, 41045]] + }, + "properties": { + "cp": [116.405285, 39.904989], + "name": "北京", + "childNum": 1 + } + }, { + "id": "120000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@ŬgX§Ü«E…¶Ḟ“¬O_™ïlÁg“z±AXe™µÄĵ{¶]gitgšIj·›¥îakS€‰¨ÐƎk}ĕ{gB—qGf{¿a†U^fI“ư‹³õ{YƒıëNĿžk©ïËZŏ‘R§òoY×Ógc…ĥs¡bġ«@dekąI[nlPqCnp{ˆō³°`{PNdƗqSÄĻNNâyj]äžÒD ĬH°Æ]~¡HO¾ŒX}ÐxŒgp“gWˆrDGˆŒpù‚Š^L‚ˆrzWxˆZ^¨´T\\|~@I‰zƒ–bĤ‹œjeĊªz£®Ĕvě€L†mV¾Ô_ȔNW~zbĬvG†²ZmDM~”~"], + "encodeOffsets": [[120237, 41215]] + }, + "properties": { + "cp": [117.190182, 39.125596], + "name": "天津", + "childNum": 1 + } + }, { + "id": "310000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@ɧư¬EpƸÁxc‡"], ["@@©„ªƒ"], ["@@”MA‹‘š"], ["@@Qp݁E§ÉC¾"], ["@@bŝՕÕEȣÚƥêImɇǦèÜĠŒÚžÃƌÃ͎ó"], ["@@ǜûȬɋŠŭ™×^‰sYŒɍDŋ‘ŽąñCG²«ªč@h–_p¯A{‡oloY€¬j@IJ`•gQڛhr|ǀ^MIJvtbe´R¯Ô¬¨YŽô¤r]ì†Ƭį"]], + "encodeOffsets": [[[124702, 32062]], [[124547, 32200]], [[124808, 31991]], [[124726, 32110]], [[124903, 32376]], [[124438, 32149]]] + }, + "properties": { + "cp": [121.472644, 31.231706], + "name": "上海", + "childNum": 6 + } + }, { + "id": "500000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@vjG~nGŘŬĶȂƀƾ¹¸ØÎezĆT¸}êЖqHŸðqĖ䒊¥^CƒIj–²p…\\_ æüY|[YxƊæuž°xb®…Űb@~¢NQt°¶‚S栓Ê~rljĔëĚ¢~šuf`‘‚†fa‚ĔJåĊ„nÖ]„jƎćÊ@Š£¾a®£Ű{ŶĕF‹ègLk{Y|¡ĜWƔtƬJÑxq‹±ĢN´‰òK‰™–LÈüD|s`ŋ’ć]ƒÃ‰`đŒMûƱ½~Y°ħ`ƏíW‰½eI‹½{aŸ‘OIrÏ¡ĕŇa†p†µÜƅġ‘œ^ÖÛbÙŽŏml½S‹êqDu[R‹ãË»†ÿw`»y‘¸_ĺę}÷`M¯ċfCVµqʼn÷Z•gg“Œ`d½pDO‡ÎCnœ^uf²ènh¼WtƏxRGg¦…pV„†FI±ŽG^ŒIc´ec‡’G•ĹÞ½sëĬ„h˜xW‚}Kӈe­Xsbk”F¦›L‘ØgTkïƵNï¶}Gy“w\\oñ¡nmĈzjŸ•@™Óc£»Wă¹Ój“_m»ˆ¹·~MvÛaqœ»­‰êœ’\\ÂoVnŽÓØÍ™²«‹bq¿efE „€‹Ĝ^Qž~ Évý‡ş¤²Į‰pEİ}zcĺƒL‹½‡š¿gņ›¡ýE¡ya£³t\\¨\\vú»¼§·Ñr_oÒý¥u‚•_n»_ƒ•At©Þűā§IVeëƒY}{VPÀFA¨ąB}q@|Ou—\\Fm‰QF݅Mw˜å}]•€|FmϋCaƒwŒu_p—¯sfÙgY…DHl`{QEfNysBЦzG¸rHe‚„N\\CvEsÐùÜ_·ÖĉsaQ¯€}_U‡†xÃđŠq›NH¬•Äd^ÝŰR¬ã°wećJEž·vÝ·Hgƒ‚éFXjÉê`|yŒpxkAwœWĐpb¥eOsmzwqChóUQl¥F^laf‹anòsr›EvfQdÁUVf—ÎvÜ^efˆtET¬ôA\\œ¢sJŽnQTjP؈xøK|nBz‰„œĞ»LY‚…FDxӄvr“[ehľš•vN”¢o¾NiÂxGp⬐z›bfZo~hGi’]öF|‰|Nb‡tOMn eA±ŠtPT‡LjpYQ|†SH††YĀxinzDJ€Ìg¢và¥Pg‰_–ÇzII‹€II•„£®S¬„Øs쐣ŒN"], ["@@ifjN@s"]], + "encodeOffsets": [[[109628, 30765]], [[111725, 31320]]] + }, + "properties": { + "cp": [107.304962, 29.533155], + "name": "重庆", + "childNum": 2 + } + }, { + "id": "810000", + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [["@@AlBk"], ["@@mŽn"], ["@@EpFo"], ["@@ea¢pl¸Eõ¹‡hj[ƒ]ÔCΖ@lj˜¡uBXŸ…•´‹AI¹…[‹yDUˆ]W`çwZkmc–…M›žp€Åv›}I‹oJlcaƒfёKްä¬XJmРđhI®æÔtSHn€Eˆ„ÒrÈc"], ["@@rMUw‡AS®€e"]], + "encodeOffsets": [[[117111, 23002]], [[117072, 22876]], [[117045, 22887]], [[116975, 23082]], [[116882, 22747]]] + }, + "properties": { + "cp": [114.173355, 22.320048], + "name": "香港", + "childNum": 5 + } + }, { + "id": "820000", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": ["@@kÊd°å§s"], + "encodeOffsets": [[116279, 22639]] + }, + "properties": { + "cp": [113.54909, 22.198951], + "name": "澳门", + "childNum": 1 + } + }], + "UTF8Encoding": true + }); +})); diff --git a/public/js/countUp.min.js b/public/js/countUp.min.js new file mode 100644 index 0000000..cd2ff25 --- /dev/null +++ b/public/js/countUp.min.js @@ -0,0 +1 @@ +var CountUp=function(target,startVal,endVal,decimals,duration,options){var self=this;self.version=function(){return"1.9.2"};self.options={useEasing:true,useGrouping:true,separator:",",decimal:".",easingFn:easeOutExpo,formattingFn:formatNumber,prefix:"",suffix:"",numerals:[]};if(options&&typeof options==="object"){for(var key in self.options){if(options.hasOwnProperty(key)&&options[key]!==null){self.options[key]=options[key]}}}if(self.options.separator===""){self.options.useGrouping=false}else{self.options.separator=""+self.options.separator}var lastTime=0;var vendors=["webkit","moz","ms","o"];for(var x=0;x1?self.options.decimal+x[1]:"";if(self.options.useGrouping){x3="";for(i=0,l=x1.length;iself.endVal);self.frameVal=self.startVal;self.initialized=true;return true}else{self.error="[CountUp] startVal ("+startVal+") or endVal ("+endVal+") is not a number";return false}};self.printValue=function(value){var result=self.options.formattingFn(value);if(self.d.tagName==="INPUT"){this.d.value=result}else{if(self.d.tagName==="text"||self.d.tagName==="tspan"){this.d.textContent=result}else{this.d.innerHTML=result}}};self.count=function(timestamp){if(!self.startTime){self.startTime=timestamp}self.timestamp=timestamp;var progress=timestamp-self.startTime;self.remaining=self.duration-progress;if(self.options.useEasing){if(self.countDown){self.frameVal=self.startVal-self.options.easingFn(progress,0,self.startVal-self.endVal,self.duration)}else{self.frameVal=self.options.easingFn(progress,self.startVal,self.endVal-self.startVal,self.duration)}}else{if(self.countDown){self.frameVal=self.startVal-((self.startVal-self.endVal)*(progress/self.duration))}else{self.frameVal=self.startVal+(self.endVal-self.startVal)*(progress/self.duration)}}if(self.countDown){self.frameVal=(self.frameValself.endVal)?self.endVal:self.frameVal}self.frameVal=Math.round(self.frameVal*self.dec)/self.dec;self.printValue(self.frameVal);if(progressself.endVal);self.rAF=requestAnimationFrame(self.count)};if(self.initialize()){self.printValue(self.startVal)}}; \ No newline at end of file diff --git a/public/js/echarts.js b/public/js/echarts.js new file mode 100644 index 0000000..187186c --- /dev/null +++ b/public/js/echarts.js @@ -0,0 +1,97412 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.echarts = {}))); +}(this, (function (exports) { 'use strict'; + +/* +* 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. +*/ + +// (1) The code `if (__DEV__) ...` can be removed by build tool. +// (2) If intend to use `__DEV__`, this module should be imported. Use a global +// variable `__DEV__` may cause that miss the declaration (see #6535), or the +// declaration is behind of the using position (for example in `Model.extent`, +// And tools like rollup can not analysis the dependency if not import). + +var dev; + +// In browser +if (typeof window !== 'undefined') { + dev = window.__DEV__; +} +// In node +else if (typeof global !== 'undefined') { + dev = global.__DEV__; +} + +if (typeof dev === 'undefined') { + dev = true; +} + +var __DEV__ = dev; + +/** + * zrender: 生成唯一id + * + * @author errorrik (errorrik@gmail.com) + */ + +var idStart = 0x0907; + +var guid = function () { + return idStart++; +}; + +/** + * echarts设备环境识别 + * + * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 + * @author firede[firede@firede.us] + * @desc thanks zepto. + */ + +/* global wx */ + +var env = {}; + +if (typeof wx === 'object' && typeof wx.getSystemInfoSync === 'function') { + // In Weixin Application + env = { + browser: {}, + os: {}, + node: false, + wxa: true, // Weixin Application + canvasSupported: true, + svgSupported: false, + touchEventsSupported: true, + domSupported: false + }; +} +else if (typeof document === 'undefined' && typeof self !== 'undefined') { + // In worker + env = { + browser: {}, + os: {}, + node: false, + worker: true, + canvasSupported: true, + domSupported: false + }; +} +else if (typeof navigator === 'undefined') { + // In node + env = { + browser: {}, + os: {}, + node: true, + worker: false, + // Assume canvas is supported + canvasSupported: true, + svgSupported: true, + domSupported: false + }; +} +else { + env = detect(navigator.userAgent); +} + +var env$1 = env; + +// Zepto.js +// (c) 2010-2013 Thomas Fuchs +// Zepto.js may be freely distributed under the MIT license. + +function detect(ua) { + var os = {}; + var browser = {}; + // var webkit = ua.match(/Web[kK]it[\/]{0,1}([\d.]+)/); + // var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/); + // var ipad = ua.match(/(iPad).*OS\s([\d_]+)/); + // var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/); + // var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/); + // var webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/); + // var touchpad = webos && ua.match(/TouchPad/); + // var kindle = ua.match(/Kindle\/([\d.]+)/); + // var silk = ua.match(/Silk\/([\d._]+)/); + // var blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/); + // var bb10 = ua.match(/(BB10).*Version\/([\d.]+)/); + // var rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/); + // var playbook = ua.match(/PlayBook/); + // var chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/); + var firefox = ua.match(/Firefox\/([\d.]+)/); + // var safari = webkit && ua.match(/Mobile\//) && !chrome; + // var webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !chrome; + var ie = ua.match(/MSIE\s([\d.]+)/) + // IE 11 Trident/7.0; rv:11.0 + || ua.match(/Trident\/.+?rv:(([\d.]+))/); + var edge = ua.match(/Edge\/([\d.]+)/); // IE 12 and 12+ + + var weChat = (/micromessenger/i).test(ua); + + // Todo: clean this up with a better OS/browser seperation: + // - discern (more) between multiple browsers on android + // - decide if kindle fire in silk mode is android or not + // - Firefox on Android doesn't specify the Android version + // - possibly devide in os, device and browser hashes + + // if (browser.webkit = !!webkit) browser.version = webkit[1]; + + // if (android) os.android = true, os.version = android[2]; + // if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.'); + // if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.'); + // if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null; + // if (webos) os.webos = true, os.version = webos[2]; + // if (touchpad) os.touchpad = true; + // if (blackberry) os.blackberry = true, os.version = blackberry[2]; + // if (bb10) os.bb10 = true, os.version = bb10[2]; + // if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2]; + // if (playbook) browser.playbook = true; + // if (kindle) os.kindle = true, os.version = kindle[1]; + // if (silk) browser.silk = true, browser.version = silk[1]; + // if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true; + // if (chrome) browser.chrome = true, browser.version = chrome[1]; + if (firefox) { + browser.firefox = true; + browser.version = firefox[1]; + } + // if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true; + // if (webview) browser.webview = true; + + if (ie) { + browser.ie = true; + browser.version = ie[1]; + } + + if (edge) { + browser.edge = true; + browser.version = edge[1]; + } + + // It is difficult to detect WeChat in Win Phone precisely, because ua can + // not be set on win phone. So we do not consider Win Phone. + if (weChat) { + browser.weChat = true; + } + + // os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) || + // (firefox && ua.match(/Tablet/)) || (ie && !ua.match(/Phone/) && ua.match(/Touch/))); + // os.phone = !!(!os.tablet && !os.ipod && (android || iphone || webos || + // (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || + // (firefox && ua.match(/Mobile/)) || (ie && ua.match(/Touch/)))); + + return { + browser: browser, + os: os, + node: false, + // 原生canvas支持,改极端点了 + // canvasSupported : !(browser.ie && parseFloat(browser.version) < 9) + canvasSupported: !!document.createElement('canvas').getContext, + svgSupported: typeof SVGRect !== 'undefined', + // works on most browsers + // IE10/11 does not support touch event, and MS Edge supports them but not by + // default, so we dont check navigator.maxTouchPoints for them here. + touchEventsSupported: 'ontouchstart' in window && !browser.ie && !browser.edge, + // . + pointerEventsSupported: + // (1) Firefox supports pointer but not by default, only MS browsers are reliable on pointer + // events currently. So we dont use that on other browsers unless tested sufficiently. + // For example, in iOS 13 Mobile Chromium 78, if the touching behavior starts page + // scroll, the `pointermove` event can not be fired any more. That will break some + // features like "pan horizontally to move something and pan vertically to page scroll". + // The horizontal pan probably be interrupted by the casually triggered page scroll. + // (2) Although IE 10 supports pointer event, it use old style and is different from the + // standard. So we exclude that. (IE 10 is hardly used on touch device) + 'onpointerdown' in window + && (browser.edge || (browser.ie && browser.version >= 11)), + // passiveSupported: detectPassiveSupport() + domSupported: typeof document !== 'undefined' + }; +} + +// See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection +// function detectPassiveSupport() { +// // Test via a getter in the options object to see if the passive property is accessed +// var supportsPassive = false; +// try { +// var opts = Object.defineProperty({}, 'passive', { +// get: function() { +// supportsPassive = true; +// } +// }); +// window.addEventListener('testPassive', function() {}, opts); +// } catch (e) { +// } +// return supportsPassive; +// } + +/** + * @module zrender/core/util + */ + +// 用于处理merge时无法遍历Date等对象的问题 +var BUILTIN_OBJECT = { + '[object Function]': 1, + '[object RegExp]': 1, + '[object Date]': 1, + '[object Error]': 1, + '[object CanvasGradient]': 1, + '[object CanvasPattern]': 1, + // For node-canvas + '[object Image]': 1, + '[object Canvas]': 1 +}; + +var TYPED_ARRAY = { + '[object Int8Array]': 1, + '[object Uint8Array]': 1, + '[object Uint8ClampedArray]': 1, + '[object Int16Array]': 1, + '[object Uint16Array]': 1, + '[object Int32Array]': 1, + '[object Uint32Array]': 1, + '[object Float32Array]': 1, + '[object Float64Array]': 1 +}; + +var objToString = Object.prototype.toString; + +var arrayProto = Array.prototype; +var nativeForEach = arrayProto.forEach; +var nativeFilter = arrayProto.filter; +var nativeSlice = arrayProto.slice; +var nativeMap = arrayProto.map; +var nativeReduce = arrayProto.reduce; + +// Avoid assign to an exported variable, for transforming to cjs. +var methods = {}; + +function $override(name, fn) { + // Clear ctx instance for different environment + if (name === 'createCanvas') { + _ctx = null; + } + + methods[name] = fn; +} + +/** + * Those data types can be cloned: + * Plain object, Array, TypedArray, number, string, null, undefined. + * Those data types will be assgined using the orginal data: + * BUILTIN_OBJECT + * Instance of user defined class will be cloned to a plain object, without + * properties in prototype. + * Other data types is not supported (not sure what will happen). + * + * Caution: do not support clone Date, for performance consideration. + * (There might be a large number of date in `series.data`). + * So date should not be modified in and out of echarts. + * + * @param {*} source + * @return {*} new + */ +function clone(source) { + if (source == null || typeof source !== 'object') { + return source; + } + + var result = source; + var typeStr = objToString.call(source); + + if (typeStr === '[object Array]') { + if (!isPrimitive(source)) { + result = []; + for (var i = 0, len = source.length; i < len; i++) { + result[i] = clone(source[i]); + } + } + } + else if (TYPED_ARRAY[typeStr]) { + if (!isPrimitive(source)) { + var Ctor = source.constructor; + if (source.constructor.from) { + result = Ctor.from(source); + } + else { + result = new Ctor(source.length); + for (var i = 0, len = source.length; i < len; i++) { + result[i] = clone(source[i]); + } + } + } + } + else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) { + result = {}; + for (var key in source) { + if (source.hasOwnProperty(key)) { + result[key] = clone(source[key]); + } + } + } + + return result; +} + +/** + * @memberOf module:zrender/core/util + * @param {*} target + * @param {*} source + * @param {boolean} [overwrite=false] + */ +function merge(target, source, overwrite) { + // We should escapse that source is string + // and enter for ... in ... + if (!isObject$1(source) || !isObject$1(target)) { + return overwrite ? clone(source) : target; + } + + for (var key in source) { + if (source.hasOwnProperty(key)) { + var targetProp = target[key]; + var sourceProp = source[key]; + + if (isObject$1(sourceProp) + && isObject$1(targetProp) + && !isArray(sourceProp) + && !isArray(targetProp) + && !isDom(sourceProp) + && !isDom(targetProp) + && !isBuiltInObject(sourceProp) + && !isBuiltInObject(targetProp) + && !isPrimitive(sourceProp) + && !isPrimitive(targetProp) + ) { + // 如果需要递归覆盖,就递归调用merge + merge(targetProp, sourceProp, overwrite); + } + else if (overwrite || !(key in target)) { + // 否则只处理overwrite为true,或者在目标对象中没有此属性的情况 + // NOTE,在 target[key] 不存在的时候也是直接覆盖 + target[key] = clone(source[key], true); + } + } + } + + return target; +} + +/** + * @param {Array} targetAndSources The first item is target, and the rests are source. + * @param {boolean} [overwrite=false] + * @return {*} target + */ +function mergeAll(targetAndSources, overwrite) { + var result = targetAndSources[0]; + for (var i = 1, len = targetAndSources.length; i < len; i++) { + result = merge(result, targetAndSources[i], overwrite); + } + return result; +} + +/** + * @param {*} target + * @param {*} source + * @memberOf module:zrender/core/util + */ +function extend(target, source) { + for (var key in source) { + if (source.hasOwnProperty(key)) { + target[key] = source[key]; + } + } + return target; +} + +/** + * @param {*} target + * @param {*} source + * @param {boolean} [overlay=false] + * @memberOf module:zrender/core/util + */ +function defaults(target, source, overlay) { + for (var key in source) { + if (source.hasOwnProperty(key) + && (overlay ? source[key] != null : target[key] == null) + ) { + target[key] = source[key]; + } + } + return target; +} + +var createCanvas = function () { + return methods.createCanvas(); +}; + +methods.createCanvas = function () { + return document.createElement('canvas'); +}; + +// FIXME +var _ctx; + +function getContext() { + if (!_ctx) { + // Use util.createCanvas instead of createCanvas + // because createCanvas may be overwritten in different environment + _ctx = createCanvas().getContext('2d'); + } + return _ctx; +} + +/** + * 查询数组中元素的index + * @memberOf module:zrender/core/util + */ +function indexOf(array, value) { + if (array) { + if (array.indexOf) { + return array.indexOf(value); + } + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + } + return -1; +} + +/** + * 构造类继承关系 + * + * @memberOf module:zrender/core/util + * @param {Function} clazz 源类 + * @param {Function} baseClazz 基类 + */ +function inherits(clazz, baseClazz) { + var clazzPrototype = clazz.prototype; + function F() {} + F.prototype = baseClazz.prototype; + clazz.prototype = new F(); + + for (var prop in clazzPrototype) { + if (clazzPrototype.hasOwnProperty(prop)) { + clazz.prototype[prop] = clazzPrototype[prop]; + } + } + clazz.prototype.constructor = clazz; + clazz.superClass = baseClazz; +} + +/** + * @memberOf module:zrender/core/util + * @param {Object|Function} target + * @param {Object|Function} sorce + * @param {boolean} overlay + */ +function mixin(target, source, overlay) { + target = 'prototype' in target ? target.prototype : target; + source = 'prototype' in source ? source.prototype : source; + + defaults(target, source, overlay); +} + +/** + * Consider typed array. + * @param {Array|TypedArray} data + */ +function isArrayLike(data) { + if (!data) { + return; + } + if (typeof data === 'string') { + return false; + } + return typeof data.length === 'number'; +} + +/** + * 数组或对象遍历 + * @memberOf module:zrender/core/util + * @param {Object|Array} obj + * @param {Function} cb + * @param {*} [context] + */ +function each$1(obj, cb, context) { + if (!(obj && cb)) { + return; + } + if (obj.forEach && obj.forEach === nativeForEach) { + obj.forEach(cb, context); + } + else if (obj.length === +obj.length) { + for (var i = 0, len = obj.length; i < len; i++) { + cb.call(context, obj[i], i, obj); + } + } + else { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + cb.call(context, obj[key], key, obj); + } + } + } +} + +/** + * 数组映射 + * @memberOf module:zrender/core/util + * @param {Array} obj + * @param {Function} cb + * @param {*} [context] + * @return {Array} + */ +function map(obj, cb, context) { + if (!(obj && cb)) { + return; + } + if (obj.map && obj.map === nativeMap) { + return obj.map(cb, context); + } + else { + var result = []; + for (var i = 0, len = obj.length; i < len; i++) { + result.push(cb.call(context, obj[i], i, obj)); + } + return result; + } +} + +/** + * @memberOf module:zrender/core/util + * @param {Array} obj + * @param {Function} cb + * @param {Object} [memo] + * @param {*} [context] + * @return {Array} + */ +function reduce(obj, cb, memo, context) { + if (!(obj && cb)) { + return; + } + if (obj.reduce && obj.reduce === nativeReduce) { + return obj.reduce(cb, memo, context); + } + else { + for (var i = 0, len = obj.length; i < len; i++) { + memo = cb.call(context, memo, obj[i], i, obj); + } + return memo; + } +} + +/** + * 数组过滤 + * @memberOf module:zrender/core/util + * @param {Array} obj + * @param {Function} cb + * @param {*} [context] + * @return {Array} + */ +function filter(obj, cb, context) { + if (!(obj && cb)) { + return; + } + if (obj.filter && obj.filter === nativeFilter) { + return obj.filter(cb, context); + } + else { + var result = []; + for (var i = 0, len = obj.length; i < len; i++) { + if (cb.call(context, obj[i], i, obj)) { + result.push(obj[i]); + } + } + return result; + } +} + +/** + * 数组项查找 + * @memberOf module:zrender/core/util + * @param {Array} obj + * @param {Function} cb + * @param {*} [context] + * @return {*} + */ +function find(obj, cb, context) { + if (!(obj && cb)) { + return; + } + for (var i = 0, len = obj.length; i < len; i++) { + if (cb.call(context, obj[i], i, obj)) { + return obj[i]; + } + } +} + +/** + * @memberOf module:zrender/core/util + * @param {Function} func + * @param {*} context + * @return {Function} + */ +function bind(func, context) { + var args = nativeSlice.call(arguments, 2); + return function () { + return func.apply(context, args.concat(nativeSlice.call(arguments))); + }; +} + +/** + * @memberOf module:zrender/core/util + * @param {Function} func + * @return {Function} + */ +function curry(func) { + var args = nativeSlice.call(arguments, 1); + return function () { + return func.apply(this, args.concat(nativeSlice.call(arguments))); + }; +} + +/** + * @memberOf module:zrender/core/util + * @param {*} value + * @return {boolean} + */ +function isArray(value) { + return objToString.call(value) === '[object Array]'; +} + +/** + * @memberOf module:zrender/core/util + * @param {*} value + * @return {boolean} + */ +function isFunction$1(value) { + return typeof value === 'function'; +} + +/** + * @memberOf module:zrender/core/util + * @param {*} value + * @return {boolean} + */ +function isString(value) { + return objToString.call(value) === '[object String]'; +} + +/** + * @memberOf module:zrender/core/util + * @param {*} value + * @return {boolean} + */ +function isObject$1(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return type === 'function' || (!!value && type === 'object'); +} + +/** + * @memberOf module:zrender/core/util + * @param {*} value + * @return {boolean} + */ +function isBuiltInObject(value) { + return !!BUILTIN_OBJECT[objToString.call(value)]; +} + +/** + * @memberOf module:zrender/core/util + * @param {*} value + * @return {boolean} + */ +function isTypedArray(value) { + return !!TYPED_ARRAY[objToString.call(value)]; +} + +/** + * @memberOf module:zrender/core/util + * @param {*} value + * @return {boolean} + */ +function isDom(value) { + return typeof value === 'object' + && typeof value.nodeType === 'number' + && typeof value.ownerDocument === 'object'; +} + +/** + * Whether is exactly NaN. Notice isNaN('a') returns true. + * @param {*} value + * @return {boolean} + */ +function eqNaN(value) { + /* eslint-disable-next-line no-self-compare */ + return value !== value; +} + +/** + * If value1 is not null, then return value1, otherwise judget rest of values. + * Low performance. + * @memberOf module:zrender/core/util + * @return {*} Final value + */ +function retrieve(values) { + for (var i = 0, len = arguments.length; i < len; i++) { + if (arguments[i] != null) { + return arguments[i]; + } + } +} + +function retrieve2(value0, value1) { + return value0 != null + ? value0 + : value1; +} + +function retrieve3(value0, value1, value2) { + return value0 != null + ? value0 + : value1 != null + ? value1 + : value2; +} + +/** + * @memberOf module:zrender/core/util + * @param {Array} arr + * @param {number} startIndex + * @param {number} endIndex + * @return {Array} + */ +function slice() { + return Function.call.apply(nativeSlice, arguments); +} + +/** + * Normalize css liked array configuration + * e.g. + * 3 => [3, 3, 3, 3] + * [4, 2] => [4, 2, 4, 2] + * [4, 3, 2] => [4, 3, 2, 3] + * @param {number|Array.} val + * @return {Array.} + */ +function normalizeCssArray(val) { + if (typeof (val) === 'number') { + return [val, val, val, val]; + } + var len = val.length; + if (len === 2) { + // vertical | horizontal + return [val[0], val[1], val[0], val[1]]; + } + else if (len === 3) { + // top | horizontal | bottom + return [val[0], val[1], val[2], val[1]]; + } + return val; +} + +/** + * @memberOf module:zrender/core/util + * @param {boolean} condition + * @param {string} message + */ +function assert$1(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +/** + * @memberOf module:zrender/core/util + * @param {string} str string to be trimed + * @return {string} trimed string + */ +function trim(str) { + if (str == null) { + return null; + } + else if (typeof str.trim === 'function') { + return str.trim(); + } + else { + return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + } +} + +var primitiveKey = '__ec_primitive__'; +/** + * Set an object as primitive to be ignored traversing children in clone or merge + */ +function setAsPrimitive(obj) { + obj[primitiveKey] = true; +} + +function isPrimitive(obj) { + return obj[primitiveKey]; +} + +/** + * @constructor + * @param {Object} obj Only apply `ownProperty`. + */ +function HashMap(obj) { + var isArr = isArray(obj); + // Key should not be set on this, otherwise + // methods get/set/... may be overrided. + this.data = {}; + var thisMap = this; + + (obj instanceof HashMap) + ? obj.each(visit) + : (obj && each$1(obj, visit)); + + function visit(value, key) { + isArr ? thisMap.set(value, key) : thisMap.set(key, value); + } +} + +HashMap.prototype = { + constructor: HashMap, + // Do not provide `has` method to avoid defining what is `has`. + // (We usually treat `null` and `undefined` as the same, different + // from ES6 Map). + get: function (key) { + return this.data.hasOwnProperty(key) ? this.data[key] : null; + }, + set: function (key, value) { + // Comparing with invocation chaining, `return value` is more commonly + // used in this case: `var someVal = map.set('a', genVal());` + return (this.data[key] = value); + }, + // Although util.each can be performed on this hashMap directly, user + // should not use the exposed keys, who are prefixed. + each: function (cb, context) { + context !== void 0 && (cb = bind(cb, context)); + /* eslint-disable guard-for-in */ + for (var key in this.data) { + this.data.hasOwnProperty(key) && cb(this.data[key], key); + } + /* eslint-enable guard-for-in */ + }, + // Do not use this method if performance sensitive. + removeKey: function (key) { + delete this.data[key]; + } +}; + +function createHashMap(obj) { + return new HashMap(obj); +} + +function concatArray(a, b) { + var newArray = new a.constructor(a.length + b.length); + for (var i = 0; i < a.length; i++) { + newArray[i] = a[i]; + } + var offset = a.length; + for (i = 0; i < b.length; i++) { + newArray[i + offset] = b[i]; + } + return newArray; +} + + +function noop() {} + + +var zrUtil = (Object.freeze || Object)({ + $override: $override, + clone: clone, + merge: merge, + mergeAll: mergeAll, + extend: extend, + defaults: defaults, + createCanvas: createCanvas, + getContext: getContext, + indexOf: indexOf, + inherits: inherits, + mixin: mixin, + isArrayLike: isArrayLike, + each: each$1, + map: map, + reduce: reduce, + filter: filter, + find: find, + bind: bind, + curry: curry, + isArray: isArray, + isFunction: isFunction$1, + isString: isString, + isObject: isObject$1, + isBuiltInObject: isBuiltInObject, + isTypedArray: isTypedArray, + isDom: isDom, + eqNaN: eqNaN, + retrieve: retrieve, + retrieve2: retrieve2, + retrieve3: retrieve3, + slice: slice, + normalizeCssArray: normalizeCssArray, + assert: assert$1, + trim: trim, + setAsPrimitive: setAsPrimitive, + isPrimitive: isPrimitive, + createHashMap: createHashMap, + concatArray: concatArray, + noop: noop +}); + +/* global Float32Array */ + +var ArrayCtor = typeof Float32Array === 'undefined' + ? Array + : Float32Array; + +/** + * 创建一个向量 + * @param {number} [x=0] + * @param {number} [y=0] + * @return {Vector2} + */ +function create(x, y) { + var out = new ArrayCtor(2); + if (x == null) { + x = 0; + } + if (y == null) { + y = 0; + } + out[0] = x; + out[1] = y; + return out; +} + +/** + * 复制向量数据 + * @param {Vector2} out + * @param {Vector2} v + * @return {Vector2} + */ +function copy(out, v) { + out[0] = v[0]; + out[1] = v[1]; + return out; +} + +/** + * 克隆一个向量 + * @param {Vector2} v + * @return {Vector2} + */ +function clone$1(v) { + var out = new ArrayCtor(2); + out[0] = v[0]; + out[1] = v[1]; + return out; +} + +/** + * 设置向量的两个项 + * @param {Vector2} out + * @param {number} a + * @param {number} b + * @return {Vector2} 结果 + */ +function set(out, a, b) { + out[0] = a; + out[1] = b; + return out; +} + +/** + * 向量相加 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + */ +function add(out, v1, v2) { + out[0] = v1[0] + v2[0]; + out[1] = v1[1] + v2[1]; + return out; +} + +/** + * 向量缩放后相加 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + * @param {number} a + */ +function scaleAndAdd(out, v1, v2, a) { + out[0] = v1[0] + v2[0] * a; + out[1] = v1[1] + v2[1] * a; + return out; +} + +/** + * 向量相减 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + */ +function sub(out, v1, v2) { + out[0] = v1[0] - v2[0]; + out[1] = v1[1] - v2[1]; + return out; +} + +/** + * 向量长度 + * @param {Vector2} v + * @return {number} + */ +function len(v) { + return Math.sqrt(lenSquare(v)); +} +var length = len; // jshint ignore:line + +/** + * 向量长度平方 + * @param {Vector2} v + * @return {number} + */ +function lenSquare(v) { + return v[0] * v[0] + v[1] * v[1]; +} +var lengthSquare = lenSquare; + +/** + * 向量乘法 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + */ +function mul(out, v1, v2) { + out[0] = v1[0] * v2[0]; + out[1] = v1[1] * v2[1]; + return out; +} + +/** + * 向量除法 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + */ +function div(out, v1, v2) { + out[0] = v1[0] / v2[0]; + out[1] = v1[1] / v2[1]; + return out; +} + +/** + * 向量点乘 + * @param {Vector2} v1 + * @param {Vector2} v2 + * @return {number} + */ +function dot(v1, v2) { + return v1[0] * v2[0] + v1[1] * v2[1]; +} + +/** + * 向量缩放 + * @param {Vector2} out + * @param {Vector2} v + * @param {number} s + */ +function scale(out, v, s) { + out[0] = v[0] * s; + out[1] = v[1] * s; + return out; +} + +/** + * 向量归一化 + * @param {Vector2} out + * @param {Vector2} v + */ +function normalize(out, v) { + var d = len(v); + if (d === 0) { + out[0] = 0; + out[1] = 0; + } + else { + out[0] = v[0] / d; + out[1] = v[1] / d; + } + return out; +} + +/** + * 计算向量间距离 + * @param {Vector2} v1 + * @param {Vector2} v2 + * @return {number} + */ +function distance(v1, v2) { + return Math.sqrt( + (v1[0] - v2[0]) * (v1[0] - v2[0]) + + (v1[1] - v2[1]) * (v1[1] - v2[1]) + ); +} +var dist = distance; + +/** + * 向量距离平方 + * @param {Vector2} v1 + * @param {Vector2} v2 + * @return {number} + */ +function distanceSquare(v1, v2) { + return (v1[0] - v2[0]) * (v1[0] - v2[0]) + + (v1[1] - v2[1]) * (v1[1] - v2[1]); +} +var distSquare = distanceSquare; + +/** + * 求负向量 + * @param {Vector2} out + * @param {Vector2} v + */ +function negate(out, v) { + out[0] = -v[0]; + out[1] = -v[1]; + return out; +} + +/** + * 插值两个点 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + * @param {number} t + */ +function lerp(out, v1, v2, t) { + out[0] = v1[0] + t * (v2[0] - v1[0]); + out[1] = v1[1] + t * (v2[1] - v1[1]); + return out; +} + +/** + * 矩阵左乘向量 + * @param {Vector2} out + * @param {Vector2} v + * @param {Vector2} m + */ +function applyTransform(out, v, m) { + var x = v[0]; + var y = v[1]; + out[0] = m[0] * x + m[2] * y + m[4]; + out[1] = m[1] * x + m[3] * y + m[5]; + return out; +} + +/** + * 求两个向量最小值 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + */ +function min(out, v1, v2) { + out[0] = Math.min(v1[0], v2[0]); + out[1] = Math.min(v1[1], v2[1]); + return out; +} + +/** + * 求两个向量最大值 + * @param {Vector2} out + * @param {Vector2} v1 + * @param {Vector2} v2 + */ +function max(out, v1, v2) { + out[0] = Math.max(v1[0], v2[0]); + out[1] = Math.max(v1[1], v2[1]); + return out; +} + + +var vector = (Object.freeze || Object)({ + create: create, + copy: copy, + clone: clone$1, + set: set, + add: add, + scaleAndAdd: scaleAndAdd, + sub: sub, + len: len, + length: length, + lenSquare: lenSquare, + lengthSquare: lengthSquare, + mul: mul, + div: div, + dot: dot, + scale: scale, + normalize: normalize, + distance: distance, + dist: dist, + distanceSquare: distanceSquare, + distSquare: distSquare, + negate: negate, + lerp: lerp, + applyTransform: applyTransform, + min: min, + max: max +}); + +// TODO Draggable for group +// FIXME Draggable on element which has parent rotation or scale +function Draggable() { + + this.on('mousedown', this._dragStart, this); + this.on('mousemove', this._drag, this); + this.on('mouseup', this._dragEnd, this); + // `mosuemove` and `mouseup` can be continue to fire when dragging. + // See [Drag outside] in `Handler.js`. So we do not need to trigger + // `_dragEnd` when globalout. That would brings better user experience. + // this.on('globalout', this._dragEnd, this); + + // this._dropTarget = null; + // this._draggingTarget = null; + + // this._x = 0; + // this._y = 0; +} + +Draggable.prototype = { + + constructor: Draggable, + + _dragStart: function (e) { + var draggingTarget = e.target; + if (draggingTarget && draggingTarget.draggable) { + this._draggingTarget = draggingTarget; + draggingTarget.dragging = true; + this._x = e.offsetX; + this._y = e.offsetY; + + this.dispatchToElement(param(draggingTarget, e), 'dragstart', e.event); + } + }, + + _drag: function (e) { + var draggingTarget = this._draggingTarget; + if (draggingTarget) { + + var x = e.offsetX; + var y = e.offsetY; + + var dx = x - this._x; + var dy = y - this._y; + this._x = x; + this._y = y; + + draggingTarget.drift(dx, dy, e); + this.dispatchToElement(param(draggingTarget, e), 'drag', e.event); + + var dropTarget = this.findHover(x, y, draggingTarget).target; + var lastDropTarget = this._dropTarget; + this._dropTarget = dropTarget; + + if (draggingTarget !== dropTarget) { + if (lastDropTarget && dropTarget !== lastDropTarget) { + this.dispatchToElement(param(lastDropTarget, e), 'dragleave', e.event); + } + if (dropTarget && dropTarget !== lastDropTarget) { + this.dispatchToElement(param(dropTarget, e), 'dragenter', e.event); + } + } + } + }, + + _dragEnd: function (e) { + var draggingTarget = this._draggingTarget; + + if (draggingTarget) { + draggingTarget.dragging = false; + } + + this.dispatchToElement(param(draggingTarget, e), 'dragend', e.event); + + if (this._dropTarget) { + this.dispatchToElement(param(this._dropTarget, e), 'drop', e.event); + } + + this._draggingTarget = null; + this._dropTarget = null; + } + +}; + +function param(target, e) { + return {target: target, topTarget: e && e.topTarget}; +} + +/** + * Event Mixin + * @module zrender/mixin/Eventful + * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) + * pissang (https://www.github.com/pissang) + */ + +var arrySlice = Array.prototype.slice; + +/** + * Event dispatcher. + * + * @alias module:zrender/mixin/Eventful + * @constructor + * @param {Object} [eventProcessor] The object eventProcessor is the scope when + * `eventProcessor.xxx` called. + * @param {Function} [eventProcessor.normalizeQuery] + * param: {string|Object} Raw query. + * return: {string|Object} Normalized query. + * @param {Function} [eventProcessor.filter] Event will be dispatched only + * if it returns `true`. + * param: {string} eventType + * param: {string|Object} query + * return: {boolean} + * @param {Function} [eventProcessor.afterTrigger] Called after all handlers called. + * param: {string} eventType + */ +var Eventful = function (eventProcessor) { + this._$handlers = {}; + this._$eventProcessor = eventProcessor; +}; + +Eventful.prototype = { + + constructor: Eventful, + + /** + * The handler can only be triggered once, then removed. + * + * @param {string} event The event name. + * @param {string|Object} [query] Condition used on event filter. + * @param {Function} handler The event handler. + * @param {Object} context + */ + one: function (event, query, handler, context) { + return on(this, event, query, handler, context, true); + }, + + /** + * Bind a handler. + * + * @param {string} event The event name. + * @param {string|Object} [query] Condition used on event filter. + * @param {Function} handler The event handler. + * @param {Object} [context] + */ + on: function (event, query, handler, context) { + return on(this, event, query, handler, context, false); + }, + + /** + * Whether any handler has bound. + * + * @param {string} event + * @return {boolean} + */ + isSilent: function (event) { + var _h = this._$handlers; + return !_h[event] || !_h[event].length; + }, + + /** + * Unbind a event. + * + * @param {string} [event] The event name. + * If no `event` input, "off" all listeners. + * @param {Function} [handler] The event handler. + * If no `handler` input, "off" all listeners of the `event`. + */ + off: function (event, handler) { + var _h = this._$handlers; + + if (!event) { + this._$handlers = {}; + return this; + } + + if (handler) { + if (_h[event]) { + var newList = []; + for (var i = 0, l = _h[event].length; i < l; i++) { + if (_h[event][i].h !== handler) { + newList.push(_h[event][i]); + } + } + _h[event] = newList; + } + + if (_h[event] && _h[event].length === 0) { + delete _h[event]; + } + } + else { + delete _h[event]; + } + + return this; + }, + + /** + * Dispatch a event. + * + * @param {string} type The event name. + */ + trigger: function (type) { + var _h = this._$handlers[type]; + var eventProcessor = this._$eventProcessor; + + if (_h) { + var args = arguments; + var argLen = args.length; + + if (argLen > 3) { + args = arrySlice.call(args, 1); + } + + var len = _h.length; + for (var i = 0; i < len;) { + var hItem = _h[i]; + if (eventProcessor + && eventProcessor.filter + && hItem.query != null + && !eventProcessor.filter(type, hItem.query) + ) { + i++; + continue; + } + + // Optimize advise from backbone + switch (argLen) { + case 1: + hItem.h.call(hItem.ctx); + break; + case 2: + hItem.h.call(hItem.ctx, args[1]); + break; + case 3: + hItem.h.call(hItem.ctx, args[1], args[2]); + break; + default: + // have more than 2 given arguments + hItem.h.apply(hItem.ctx, args); + break; + } + + if (hItem.one) { + _h.splice(i, 1); + len--; + } + else { + i++; + } + } + } + + eventProcessor && eventProcessor.afterTrigger + && eventProcessor.afterTrigger(type); + + return this; + }, + + /** + * Dispatch a event with context, which is specified at the last parameter. + * + * @param {string} type The event name. + */ + triggerWithContext: function (type) { + var _h = this._$handlers[type]; + var eventProcessor = this._$eventProcessor; + + if (_h) { + var args = arguments; + var argLen = args.length; + + if (argLen > 4) { + args = arrySlice.call(args, 1, args.length - 1); + } + var ctx = args[args.length - 1]; + + var len = _h.length; + for (var i = 0; i < len;) { + var hItem = _h[i]; + if (eventProcessor + && eventProcessor.filter + && hItem.query != null + && !eventProcessor.filter(type, hItem.query) + ) { + i++; + continue; + } + + // Optimize advise from backbone + switch (argLen) { + case 1: + hItem.h.call(ctx); + break; + case 2: + hItem.h.call(ctx, args[1]); + break; + case 3: + hItem.h.call(ctx, args[1], args[2]); + break; + default: + // have more than 2 given arguments + hItem.h.apply(ctx, args); + break; + } + + if (hItem.one) { + _h.splice(i, 1); + len--; + } + else { + i++; + } + } + } + + eventProcessor && eventProcessor.afterTrigger + && eventProcessor.afterTrigger(type); + + return this; + } +}; + + +function normalizeQuery(host, query) { + var eventProcessor = host._$eventProcessor; + if (query != null && eventProcessor && eventProcessor.normalizeQuery) { + query = eventProcessor.normalizeQuery(query); + } + return query; +} + +function on(eventful, event, query, handler, context, isOnce) { + var _h = eventful._$handlers; + + if (typeof query === 'function') { + context = handler; + handler = query; + query = null; + } + + if (!handler || !event) { + return eventful; + } + + query = normalizeQuery(eventful, query); + + if (!_h[event]) { + _h[event] = []; + } + + for (var i = 0; i < _h[event].length; i++) { + if (_h[event][i].h === handler) { + return eventful; + } + } + + var wrap = { + h: handler, + one: isOnce, + query: query, + ctx: context || eventful, + // FIXME + // Do not publish this feature util it is proved that it makes sense. + callAtLast: handler.zrEventfulCallAtLast + }; + + var lastIndex = _h[event].length - 1; + var lastWrap = _h[event][lastIndex]; + (lastWrap && lastWrap.callAtLast) + ? _h[event].splice(lastIndex, 0, wrap) + : _h[event].push(wrap); + + return eventful; +} + +/** + * The algoritm is learnt from + * https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/ + * And we made some optimization for matrix inversion. + * Other similar approaches: + * "cv::getPerspectiveTransform", "Direct Linear Transformation". + */ + +var LN2 = Math.log(2); + +function determinant(rows, rank, rowStart, rowMask, colMask, detCache) { + var cacheKey = rowMask + '-' + colMask; + var fullRank = rows.length; + + if (detCache.hasOwnProperty(cacheKey)) { + return detCache[cacheKey]; + } + + if (rank === 1) { + // In this case the colMask must be like: `11101111`. We can find the place of `0`. + var colStart = Math.round(Math.log(((1 << fullRank) - 1) & ~colMask) / LN2); + return rows[rowStart][colStart]; + } + + var subRowMask = rowMask | (1 << rowStart); + var subRowStart = rowStart + 1; + while (rowMask & (1 << subRowStart)) { + subRowStart++; + } + + var sum = 0; + for (var j = 0, colLocalIdx = 0; j < fullRank; j++) { + var colTag = 1 << j; + if (!(colTag & colMask)) { + sum += (colLocalIdx % 2 ? -1 : 1) * rows[rowStart][j] + // det(subMatrix(0, j)) + * determinant(rows, rank - 1, subRowStart, subRowMask, colMask | colTag, detCache); + colLocalIdx++; + } + } + + detCache[cacheKey] = sum; + + return sum; +} + +/** + * Usage: + * ```js + * var transformer = buildTransformer( + * [10, 44, 100, 44, 100, 300, 10, 300], + * [50, 54, 130, 14, 140, 330, 14, 220] + * ); + * var out = []; + * transformer && transformer([11, 33], out); + * ``` + * + * Notice: `buildTransformer` may take more than 10ms in some Android device. + * + * @param {Array.} src source four points, [x0, y0, x1, y1, x2, y2, x3, y3] + * @param {Array.} dest destination four points, [x0, y0, x1, y1, x2, y2, x3, y3] + * @return {Function} transformer If fail, return null/undefined. + */ +function buildTransformer(src, dest) { + var mA = [ + [src[0], src[1], 1, 0, 0, 0, -dest[0] * src[0], -dest[0] * src[1]], + [0, 0, 0, src[0], src[1], 1, -dest[1] * src[0], -dest[1] * src[1]], + [src[2], src[3], 1, 0, 0, 0, -dest[2] * src[2], -dest[2] * src[3]], + [0, 0, 0, src[2], src[3], 1, -dest[3] * src[2], -dest[3] * src[3]], + [src[4], src[5], 1, 0, 0, 0, -dest[4] * src[4], -dest[4] * src[5]], + [0, 0, 0, src[4], src[5], 1, -dest[5] * src[4], -dest[5] * src[5]], + [src[6], src[7], 1, 0, 0, 0, -dest[6] * src[6], -dest[6] * src[7]], + [0, 0, 0, src[6], src[7], 1, -dest[7] * src[6], -dest[7] * src[7]] + ]; + + var detCache = {}; + var det = determinant(mA, 8, 0, 0, 0, detCache); + if (det === 0) { + return; + } + + // `invert(mA) * dest`, that is, `adj(mA) / det * dest`. + var vh = []; + for (var i = 0; i < 8; i++) { + for (var j = 0; j < 8; j++) { + vh[j] == null && (vh[j] = 0); + vh[j] += ((i + j) % 2 ? -1 : 1) + // det(subMatrix(i, j)) + * determinant(mA, 7, i === 0 ? 1 : 0, 1 << i, 1 << j, detCache) + / det * dest[i]; + } + } + + return function (out, srcPointX, srcPointY) { + var pk = srcPointX * vh[6] + srcPointY * vh[7] + 1; + out[0] = (srcPointX * vh[0] + srcPointY * vh[1] + vh[2]) / pk; + out[1] = (srcPointX * vh[3] + srcPointY * vh[4] + vh[5]) / pk; + }; +} + +/** + * Utilities for mouse or touch events. + */ + +var isDomLevel2 = (typeof window !== 'undefined') && !!window.addEventListener; + +var MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/; +var EVENT_SAVED_PROP = '___zrEVENTSAVED'; +var _calcOut = []; + +/** + * Get the `zrX` and `zrY`, which are relative to the top-left of + * the input `el`. + * CSS transform (2D & 3D) is supported. + * + * The strategy to fetch the coords: + * + If `calculate` is not set as `true`, users of this method should + * ensure that `el` is the same or the same size & location as `e.target`. + * Otherwise the result coords are probably not expected. Because we + * firstly try to get coords from e.offsetX/e.offsetY. + * + If `calculate` is set as `true`, the input `el` can be any element + * and we force to calculate the coords based on `el`. + * + The input `el` should be positionable (not position:static). + * + * The force `calculate` can be used in case like: + * When mousemove event triggered on ec tooltip, `e.target` is not `el`(zr painter.dom). + * + * @param {HTMLElement} el DOM element. + * @param {Event} e Mouse event or touch event. + * @param {Object} out Get `out.zrX` and `out.zrY` as the result. + * @param {boolean} [calculate=false] Whether to force calculate + * the coordinates but not use ones provided by browser. + */ +function clientToLocal(el, e, out, calculate) { + out = out || {}; + + // According to the W3C Working Draft, offsetX and offsetY should be relative + // to the padding edge of the target element. The only browser using this convention + // is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox does + // not support the properties. + // (see http://www.jacklmoore.com/notes/mouse-position/) + // In zr painter.dom, padding edge equals to border edge. + + if (calculate || !env$1.canvasSupported) { + calculateZrXY(el, e, out); + } + // Caution: In FireFox, layerX/layerY Mouse position relative to the closest positioned + // ancestor element, so we should make sure el is positioned (e.g., not position:static). + // BTW1, Webkit don't return the same results as FF in non-simple cases (like add + // zoom-factor, overflow / opacity layers, transforms ...) + // BTW2, (ev.offsetY || ev.pageY - $(ev.target).offset().top) is not correct in preserve-3d. + // + // BTW3, In ff, offsetX/offsetY is always 0. + else if (env$1.browser.firefox && e.layerX != null && e.layerX !== e.offsetX) { + out.zrX = e.layerX; + out.zrY = e.layerY; + } + // For IE6+, chrome, safari, opera. (When will ff support offsetX?) + else if (e.offsetX != null) { + out.zrX = e.offsetX; + out.zrY = e.offsetY; + } + // For some other device, e.g., IOS safari. + else { + calculateZrXY(el, e, out); + } + + return out; +} + +function calculateZrXY(el, e, out) { + // BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect. + if (el.getBoundingClientRect && env$1.domSupported) { + var ex = e.clientX; + var ey = e.clientY; + + if (el.nodeName.toUpperCase() === 'CANVAS') { + // Original approach, which do not support CSS transform. + // marker can not be locationed in a canvas container + // (getBoundingClientRect is always 0). We do not support + // that input a pre-created canvas to zr while using css + // transform in iOS. + var box = el.getBoundingClientRect(); + out.zrX = ex - box.left; + out.zrY = ey - box.top; + return; + } + else { + var saved = el[EVENT_SAVED_PROP] || (el[EVENT_SAVED_PROP] = {}); + var transformer = preparePointerTransformer(prepareCoordMarkers(el, saved), saved); + if (transformer) { + transformer(_calcOut, ex, ey); + out.zrX = _calcOut[0]; + out.zrY = _calcOut[1]; + return; + } + } + } + out.zrX = out.zrY = 0; +} + +function prepareCoordMarkers(el, saved) { + var markers = saved.markers; + if (markers) { + return markers; + } + + markers = saved.markers = []; + var propLR = ['left', 'right']; + var propTB = ['top', 'bottom']; + + for (var i = 0; i < 4; i++) { + var marker = document.createElement('div'); + var stl = marker.style; + var idxLR = i % 2; + var idxTB = (i >> 1) % 2; + stl.cssText = [ + 'position:absolute', + 'visibility: hidden', + 'padding: 0', + 'margin: 0', + 'border-width: 0', + 'width:0', + 'height:0', + // 'width: 5px', + // 'height: 5px', + propLR[idxLR] + ':0', + propTB[idxTB] + ':0', + propLR[1 - idxLR] + ':auto', + propTB[1 - idxTB] + ':auto', + '' + ].join('!important;'); + el.appendChild(marker); + markers.push(marker); + } + + return markers; +} + +function preparePointerTransformer(markers, saved) { + var transformer = saved.transformer; + var oldSrcCoords = saved.srcCoords; + var useOld = true; + var srcCoords = []; + var destCoords = []; + + for (var i = 0; i < 4; i++) { + var rect = markers[i].getBoundingClientRect(); + var ii = 2 * i; + var x = rect.left; + var y = rect.top; + srcCoords.push(x, y); + useOld &= oldSrcCoords && x === oldSrcCoords[ii] && y === oldSrcCoords[ii + 1]; + destCoords.push(markers[i].offsetLeft, markers[i].offsetTop); + } + + // Cache to avoid time consuming of `buildTransformer`. + return useOld + ? transformer + : ( + saved.srcCoords = srcCoords, + saved.transformer = buildTransformer(srcCoords, destCoords) + ); +} + +/** + * Find native event compat for legency IE. + * Should be called at the begining of a native event listener. + * + * @param {Event} [e] Mouse event or touch event or pointer event. + * For lagency IE, we use `window.event` is used. + * @return {Event} The native event. + */ +function getNativeEvent(e) { + return e || window.event; +} + +/** + * Normalize the coordinates of the input event. + * + * Get the `e.zrX` and `e.zrY`, which are relative to the top-left of + * the input `el`. + * Get `e.zrDelta` if using mouse wheel. + * Get `e.which`, see the comment inside this function. + * + * Do not calculate repeatly if `zrX` and `zrY` already exist. + * + * Notice: see comments in `clientToLocal`. check the relationship + * between the result coords and the parameters `el` and `calculate`. + * + * @param {HTMLElement} el DOM element. + * @param {Event} [e] See `getNativeEvent`. + * @param {boolean} [calculate=false] Whether to force calculate + * the coordinates but not use ones provided by browser. + * @return {UIEvent} The normalized native UIEvent. + */ +function normalizeEvent(el, e, calculate) { + + e = getNativeEvent(e); + + if (e.zrX != null) { + return e; + } + + var eventType = e.type; + var isTouch = eventType && eventType.indexOf('touch') >= 0; + + if (!isTouch) { + clientToLocal(el, e, e, calculate); + e.zrDelta = (e.wheelDelta) ? e.wheelDelta / 120 : -(e.detail || 0) / 3; + } + else { + var touch = eventType !== 'touchend' + ? e.targetTouches[0] + : e.changedTouches[0]; + touch && clientToLocal(el, touch, e, calculate); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right; otherwise: 0; + // See jQuery: https://github.com/jquery/jquery/blob/master/src/event.js + // If e.which has been defined, it may be readonly, + // see: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which + var button = e.button; + if (e.which == null && button !== undefined && MOUSE_EVENT_REG.test(e.type)) { + e.which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0))); + } + // [Caution]: `e.which` from browser is not always reliable. For example, + // when press left button and `mousemove (pointermove)` in Edge, the `e.which` + // is 65536 and the `e.button` is -1. But the `mouseup (pointerup)` and + // `mousedown (pointerdown)` is the same as Chrome does. + + return e; +} + +/** + * @param {HTMLElement} el + * @param {string} name + * @param {Function} handler + * @param {Object|boolean} opt If boolean, means `opt.capture` + * @param {boolean} [opt.capture=false] + * @param {boolean} [opt.passive=false] + */ +function addEventListener(el, name, handler, opt) { + if (isDomLevel2) { + // Reproduct the console warning: + // [Violation] Added non-passive event listener to a scroll-blocking event. + // Consider marking event handler as 'passive' to make the page more responsive. + // Just set console log level: verbose in chrome dev tool. + // then the warning log will be printed when addEventListener called. + // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md + // We have not yet found a neat way to using passive. Because in zrender the dom event + // listener delegate all of the upper events of element. Some of those events need + // to prevent default. For example, the feature `preventDefaultMouseMove` of echarts. + // Before passive can be adopted, these issues should be considered: + // (1) Whether and how a zrender user specifies an event listener passive. And by default, + // passive or not. + // (2) How to tread that some zrender event listener is passive, and some is not. If + // we use other way but not preventDefault of mousewheel and touchmove, browser + // compatibility should be handled. + + // var opts = (env.passiveSupported && name === 'mousewheel') + // ? {passive: true} + // // By default, the third param of el.addEventListener is `capture: false`. + // : void 0; + // el.addEventListener(name, handler /* , opts */); + el.addEventListener(name, handler, opt); + } + else { + // For simplicity, do not implement `setCapture` for IE9-. + el.attachEvent('on' + name, handler); + } +} + +/** + * Parameter are the same as `addEventListener`. + * + * Notice that if a listener is registered twice, one with capture and one without, + * remove each one separately. Removal of a capturing listener does not affect a + * non-capturing version of the same listener, and vice versa. + */ +function removeEventListener(el, name, handler, opt) { + if (isDomLevel2) { + el.removeEventListener(name, handler, opt); + } + else { + el.detachEvent('on' + name, handler); + } +} + +/** + * preventDefault and stopPropagation. + * Notice: do not use this method in zrender. It can only be + * used by upper applications if necessary. + * + * @param {Event} e A mouse or touch event. + */ +var stop = isDomLevel2 + ? function (e) { + e.preventDefault(); + e.stopPropagation(); + e.cancelBubble = true; + } + : function (e) { + e.returnValue = false; + e.cancelBubble = true; + }; + +/** + * This method only works for mouseup and mousedown. The functionality is restricted + * for fault tolerance, See the `e.which` compatibility above. + * + * @param {MouseEvent} e + * @return {boolean} + */ +function isMiddleOrRightButtonOnMouseUpDown(e) { + return e.which === 2 || e.which === 3; +} + +/** + * To be removed. + * @deprecated + */ + +/** + * Only implements needed gestures for mobile. + */ + +var GestureMgr = function () { + + /** + * @private + * @type {Array.} + */ + this._track = []; +}; + +GestureMgr.prototype = { + + constructor: GestureMgr, + + recognize: function (event, target, root) { + this._doTrack(event, target, root); + return this._recognize(event); + }, + + clear: function () { + this._track.length = 0; + return this; + }, + + _doTrack: function (event, target, root) { + var touches = event.touches; + + if (!touches) { + return; + } + + var trackItem = { + points: [], + touches: [], + target: target, + event: event + }; + + for (var i = 0, len = touches.length; i < len; i++) { + var touch = touches[i]; + var pos = clientToLocal(root, touch, {}); + trackItem.points.push([pos.zrX, pos.zrY]); + trackItem.touches.push(touch); + } + + this._track.push(trackItem); + }, + + _recognize: function (event) { + for (var eventName in recognizers) { + if (recognizers.hasOwnProperty(eventName)) { + var gestureInfo = recognizers[eventName](this._track, event); + if (gestureInfo) { + return gestureInfo; + } + } + } + } +}; + +function dist$1(pointPair) { + var dx = pointPair[1][0] - pointPair[0][0]; + var dy = pointPair[1][1] - pointPair[0][1]; + + return Math.sqrt(dx * dx + dy * dy); +} + +function center(pointPair) { + return [ + (pointPair[0][0] + pointPair[1][0]) / 2, + (pointPair[0][1] + pointPair[1][1]) / 2 + ]; +} + +var recognizers = { + + pinch: function (track, event) { + var trackLen = track.length; + + if (!trackLen) { + return; + } + + var pinchEnd = (track[trackLen - 1] || {}).points; + var pinchPre = (track[trackLen - 2] || {}).points || pinchEnd; + + if (pinchPre + && pinchPre.length > 1 + && pinchEnd + && pinchEnd.length > 1 + ) { + var pinchScale = dist$1(pinchEnd) / dist$1(pinchPre); + !isFinite(pinchScale) && (pinchScale = 1); + + event.pinchScale = pinchScale; + + var pinchCenter = center(pinchEnd); + event.pinchX = pinchCenter[0]; + event.pinchY = pinchCenter[1]; + + return { + type: 'pinch', + target: track[0].target, + event: event + }; + } + } + + // Only pinch currently. +}; + +/** + * [The interface between `Handler` and `HandlerProxy`]: + * + * The default `HandlerProxy` only support the common standard web environment + * (e.g., standalone browser, headless browser, embed browser in mobild APP, ...). + * But `HandlerProxy` can be replaced to support more non-standard environment + * (e.g., mini app), or to support more feature that the default `HandlerProxy` + * not provided (like echarts-gl did). + * So the interface between `Handler` and `HandlerProxy` should be stable. Do not + * make break changes util inevitable. The interface include the public methods + * of `Handler` and the events listed in `handlerNames` below, by which `HandlerProxy` + * drives `Handler`. + */ + +/** + * [Drag outside]: + * + * That is, triggering `mousemove` and `mouseup` event when the pointer is out of the + * zrender area when dragging. That is important for the improvement of the user experience + * when dragging something near the boundary without being terminated unexpectedly. + * + * We originally consider to introduce new events like `pagemovemove` and `pagemouseup` + * to resolve this issue. But some drawbacks of it is described in + * https://github.com/ecomfe/zrender/pull/536#issuecomment-560286899 + * + * Instead, we referenced the specifications: + * https://www.w3.org/TR/touch-events/#the-touchmove-event + * https://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#event-type-mousemove + * where the the mousemove/touchmove can be continue to fire if the user began a drag + * operation and the pointer has left the boundary. (for the mouse event, browsers + * only do it on `document` and when the pointer has left the boundary of the browser.) + * + * So the default `HandlerProxy` supports this feature similarly: if it is in the dragging + * state (see `pointerCapture` in `HandlerProxy`), the `mousemove` and `mouseup` continue + * to fire until release the pointer. That is implemented by listen to those event on + * `document`. + * If we implement some other `HandlerProxy` only for touch device, that would be easier. + * The touch event support this feature by default. + * + * Note: + * There might be some cases that the mouse event can not be + * received on `document`. For example, + * (A) `useCapture` is not supported and some user defined event listeners on the ancestor + * of zr dom throw Error . + * (B) `useCapture` is not supported Some user defined event listeners on the ancestor of + * zr dom call `stopPropagation`. + * In these cases, the `mousemove` event might be keep triggered event + * if the mouse is released. We try to reduce the side-effect in those cases. + * That is, do nothing (especially, `findHover`) in those cases. See `isOutsideBoundary`. + * + * Note: + * If `HandlerProxy` listens to `document` with `useCapture`, `HandlerProxy` needs to + * make sure `stopPropagation` and `preventDefault` doing nothing if and only if the event + * target is not zrender dom. Becuase it is dangerous to enable users to call them in + * `document` capture phase to prevent the propagation to any listener of the webpage. + * But they are needed to work when the pointer inside the zrender dom. + */ + + +var SILENT = 'silent'; + +function makeEventPacket(eveType, targetInfo, event) { + return { + type: eveType, + event: event, + // target can only be an element that is not silent. + target: targetInfo.target, + // topTarget can be a silent element. + topTarget: targetInfo.topTarget, + cancelBubble: false, + offsetX: event.zrX, + offsetY: event.zrY, + gestureEvent: event.gestureEvent, + pinchX: event.pinchX, + pinchY: event.pinchY, + pinchScale: event.pinchScale, + wheelDelta: event.zrDelta, + zrByTouch: event.zrByTouch, + which: event.which, + stop: stopEvent + }; +} + +function stopEvent() { + stop(this.event); +} + +function EmptyProxy() {} +EmptyProxy.prototype.dispose = function () {}; + + +var handlerNames = [ + 'click', 'dblclick', 'mousewheel', 'mouseout', + 'mouseup', 'mousedown', 'mousemove', 'contextmenu' +]; + +/** + * @alias module:zrender/Handler + * @constructor + * @extends module:zrender/mixin/Eventful + * @param {module:zrender/Storage} storage Storage instance. + * @param {module:zrender/Painter} painter Painter instance. + * @param {module:zrender/dom/HandlerProxy} proxy HandlerProxy instance. + * @param {HTMLElement} painterRoot painter.root (not painter.getViewportRoot()). + */ +var Handler = function (storage, painter, proxy, painterRoot) { + Eventful.call(this); + + this.storage = storage; + + this.painter = painter; + + this.painterRoot = painterRoot; + + proxy = proxy || new EmptyProxy(); + + /** + * Proxy of event. can be Dom, WebGLSurface, etc. + */ + this.proxy = null; + + /** + * {target, topTarget, x, y} + * @private + * @type {Object} + */ + this._hovered = {}; + + /** + * @private + * @type {Date} + */ + this._lastTouchMoment; + + /** + * @private + * @type {number} + */ + this._lastX; + + /** + * @private + * @type {number} + */ + this._lastY; + + /** + * @private + * @type {module:zrender/core/GestureMgr} + */ + this._gestureMgr; + + Draggable.call(this); + + this.setHandlerProxy(proxy); +}; + +Handler.prototype = { + + constructor: Handler, + + setHandlerProxy: function (proxy) { + if (this.proxy) { + this.proxy.dispose(); + } + + if (proxy) { + each$1(handlerNames, function (name) { + proxy.on && proxy.on(name, this[name], this); + }, this); + // Attach handler + proxy.handler = this; + } + this.proxy = proxy; + }, + + mousemove: function (event) { + var x = event.zrX; + var y = event.zrY; + + var isOutside = isOutsideBoundary(this, x, y); + + var lastHovered = this._hovered; + var lastHoveredTarget = lastHovered.target; + + // If lastHoveredTarget is removed from zr (detected by '__zr') by some API call + // (like 'setOption' or 'dispatchAction') in event handlers, we should find + // lastHovered again here. Otherwise 'mouseout' can not be triggered normally. + // See #6198. + if (lastHoveredTarget && !lastHoveredTarget.__zr) { + lastHovered = this.findHover(lastHovered.x, lastHovered.y); + lastHoveredTarget = lastHovered.target; + } + + var hovered = this._hovered = isOutside ? {x: x, y: y} : this.findHover(x, y); + var hoveredTarget = hovered.target; + + var proxy = this.proxy; + proxy.setCursor && proxy.setCursor(hoveredTarget ? hoveredTarget.cursor : 'default'); + + // Mouse out on previous hovered element + if (lastHoveredTarget && hoveredTarget !== lastHoveredTarget) { + this.dispatchToElement(lastHovered, 'mouseout', event); + } + + // Mouse moving on one element + this.dispatchToElement(hovered, 'mousemove', event); + + // Mouse over on a new element + if (hoveredTarget && hoveredTarget !== lastHoveredTarget) { + this.dispatchToElement(hovered, 'mouseover', event); + } + }, + + mouseout: function (event) { + var eventControl = event.zrEventControl; + var zrIsToLocalDOM = event.zrIsToLocalDOM; + + if (eventControl !== 'only_globalout') { + this.dispatchToElement(this._hovered, 'mouseout', event); + } + + if (eventControl !== 'no_globalout') { + // FIXME: if the pointer moving from the extra doms to realy "outside", + // the `globalout` should have been triggered. But currently not. + !zrIsToLocalDOM && this.trigger('globalout', {type: 'globalout', event: event}); + } + }, + + /** + * Resize + */ + resize: function (event) { + this._hovered = {}; + }, + + /** + * Dispatch event + * @param {string} eventName + * @param {event=} eventArgs + */ + dispatch: function (eventName, eventArgs) { + var handler = this[eventName]; + handler && handler.call(this, eventArgs); + }, + + /** + * Dispose + */ + dispose: function () { + + this.proxy.dispose(); + + this.storage = + this.proxy = + this.painter = null; + }, + + /** + * 设置默认的cursor style + * @param {string} [cursorStyle='default'] 例如 crosshair + */ + setCursorStyle: function (cursorStyle) { + var proxy = this.proxy; + proxy.setCursor && proxy.setCursor(cursorStyle); + }, + + /** + * 事件分发代理 + * + * @private + * @param {Object} targetInfo {target, topTarget} 目标图形元素 + * @param {string} eventName 事件名称 + * @param {Object} event 事件对象 + */ + dispatchToElement: function (targetInfo, eventName, event) { + targetInfo = targetInfo || {}; + var el = targetInfo.target; + if (el && el.silent) { + return; + } + var eventHandler = 'on' + eventName; + var eventPacket = makeEventPacket(eventName, targetInfo, event); + + while (el) { + el[eventHandler] + && (eventPacket.cancelBubble = el[eventHandler].call(el, eventPacket)); + + el.trigger(eventName, eventPacket); + + el = el.parent; + + if (eventPacket.cancelBubble) { + break; + } + } + + if (!eventPacket.cancelBubble) { + // 冒泡到顶级 zrender 对象 + this.trigger(eventName, eventPacket); + // 分发事件到用户自定义层 + // 用户有可能在全局 click 事件中 dispose,所以需要判断下 painter 是否存在 + this.painter && this.painter.eachOtherLayer(function (layer) { + if (typeof (layer[eventHandler]) === 'function') { + layer[eventHandler].call(layer, eventPacket); + } + if (layer.trigger) { + layer.trigger(eventName, eventPacket); + } + }); + } + }, + + /** + * @private + * @param {number} x + * @param {number} y + * @param {module:zrender/graphic/Displayable} exclude + * @return {model:zrender/Element} + * @method + */ + findHover: function (x, y, exclude) { + var list = this.storage.getDisplayList(); + var out = {x: x, y: y}; + + for (var i = list.length - 1; i >= 0; i--) { + var hoverCheckResult; + if (list[i] !== exclude + // getDisplayList may include ignored item in VML mode + && !list[i].ignore + && (hoverCheckResult = isHover(list[i], x, y)) + ) { + !out.topTarget && (out.topTarget = list[i]); + if (hoverCheckResult !== SILENT) { + out.target = list[i]; + break; + } + } + } + + return out; + }, + + processGesture: function (event, stage) { + if (!this._gestureMgr) { + this._gestureMgr = new GestureMgr(); + } + var gestureMgr = this._gestureMgr; + + stage === 'start' && gestureMgr.clear(); + + var gestureInfo = gestureMgr.recognize( + event, + this.findHover(event.zrX, event.zrY, null).target, + this.proxy.dom + ); + + stage === 'end' && gestureMgr.clear(); + + // Do not do any preventDefault here. Upper application do that if necessary. + if (gestureInfo) { + var type = gestureInfo.type; + event.gestureEvent = type; + + this.dispatchToElement({target: gestureInfo.target}, type, gestureInfo.event); + } + } +}; + +// Common handlers +each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) { + Handler.prototype[name] = function (event) { + var x = event.zrX; + var y = event.zrY; + var isOutside = isOutsideBoundary(this, x, y); + + var hovered; + var hoveredTarget; + + if (name !== 'mouseup' || !isOutside) { + // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover + hovered = this.findHover(x, y); + hoveredTarget = hovered.target; + } + + if (name === 'mousedown') { + this._downEl = hoveredTarget; + this._downPoint = [event.zrX, event.zrY]; + // In case click triggered before mouseup + this._upEl = hoveredTarget; + } + else if (name === 'mouseup') { + this._upEl = hoveredTarget; + } + else if (name === 'click') { + if (this._downEl !== this._upEl + // Original click event is triggered on the whole canvas element, + // including the case that `mousedown` - `mousemove` - `mouseup`, + // which should be filtered, otherwise it will bring trouble to + // pan and zoom. + || !this._downPoint + // Arbitrary value + || dist(this._downPoint, [event.zrX, event.zrY]) > 4 + ) { + return; + } + this._downPoint = null; + } + + this.dispatchToElement(hovered, name, event); + }; +}); + +function isHover(displayable, x, y) { + if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) { + var el = displayable; + var isSilent; + while (el) { + // If clipped by ancestor. + // FIXME: If clipPath has neither stroke nor fill, + // el.clipPath.contain(x, y) will always return false. + if (el.clipPath && !el.clipPath.contain(x, y)) { + return false; + } + if (el.silent) { + isSilent = true; + } + el = el.parent; + } + return isSilent ? SILENT : true; + } + + return false; +} + +/** + * See [Drag outside]. + */ +function isOutsideBoundary(handlerInstance, x, y) { + var painter = handlerInstance.painter; + return x < 0 || x > painter.getWidth() || y < 0 || y > painter.getHeight(); +} + +mixin(Handler, Eventful); +mixin(Handler, Draggable); + +/** + * 3x2矩阵操作类 + * @exports zrender/tool/matrix + */ + +/* global Float32Array */ + +var ArrayCtor$1 = typeof Float32Array === 'undefined' + ? Array + : Float32Array; + +/** + * Create a identity matrix. + * @return {Float32Array|Array.} + */ +function create$1() { + var out = new ArrayCtor$1(6); + identity(out); + + return out; +} + +/** + * 设置矩阵为单位矩阵 + * @param {Float32Array|Array.} out + */ +function identity(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = 0; + out[5] = 0; + return out; +} + +/** + * 复制矩阵 + * @param {Float32Array|Array.} out + * @param {Float32Array|Array.} m + */ +function copy$1(out, m) { + out[0] = m[0]; + out[1] = m[1]; + out[2] = m[2]; + out[3] = m[3]; + out[4] = m[4]; + out[5] = m[5]; + return out; +} + +/** + * 矩阵相乘 + * @param {Float32Array|Array.} out + * @param {Float32Array|Array.} m1 + * @param {Float32Array|Array.} m2 + */ +function mul$1(out, m1, m2) { + // Consider matrix.mul(m, m2, m); + // where out is the same as m2. + // So use temp variable to escape error. + var out0 = m1[0] * m2[0] + m1[2] * m2[1]; + var out1 = m1[1] * m2[0] + m1[3] * m2[1]; + var out2 = m1[0] * m2[2] + m1[2] * m2[3]; + var out3 = m1[1] * m2[2] + m1[3] * m2[3]; + var out4 = m1[0] * m2[4] + m1[2] * m2[5] + m1[4]; + var out5 = m1[1] * m2[4] + m1[3] * m2[5] + m1[5]; + out[0] = out0; + out[1] = out1; + out[2] = out2; + out[3] = out3; + out[4] = out4; + out[5] = out5; + return out; +} + +/** + * 平移变换 + * @param {Float32Array|Array.} out + * @param {Float32Array|Array.} a + * @param {Float32Array|Array.} v + */ +function translate(out, a, v) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4] + v[0]; + out[5] = a[5] + v[1]; + return out; +} + +/** + * 旋转变换 + * @param {Float32Array|Array.} out + * @param {Float32Array|Array.} a + * @param {number} rad + */ +function rotate(out, a, rad) { + var aa = a[0]; + var ac = a[2]; + var atx = a[4]; + var ab = a[1]; + var ad = a[3]; + var aty = a[5]; + var st = Math.sin(rad); + var ct = Math.cos(rad); + + out[0] = aa * ct + ab * st; + out[1] = -aa * st + ab * ct; + out[2] = ac * ct + ad * st; + out[3] = -ac * st + ct * ad; + out[4] = ct * atx + st * aty; + out[5] = ct * aty - st * atx; + return out; +} + +/** + * 缩放变换 + * @param {Float32Array|Array.} out + * @param {Float32Array|Array.} a + * @param {Float32Array|Array.} v + */ +function scale$1(out, a, v) { + var vx = v[0]; + var vy = v[1]; + out[0] = a[0] * vx; + out[1] = a[1] * vy; + out[2] = a[2] * vx; + out[3] = a[3] * vy; + out[4] = a[4] * vx; + out[5] = a[5] * vy; + return out; +} + +/** + * 求逆矩阵 + * @param {Float32Array|Array.} out + * @param {Float32Array|Array.} a + */ +function invert(out, a) { + + var aa = a[0]; + var ac = a[2]; + var atx = a[4]; + var ab = a[1]; + var ad = a[3]; + var aty = a[5]; + + var det = aa * ad - ab * ac; + if (!det) { + return null; + } + det = 1.0 / det; + + out[0] = ad * det; + out[1] = -ab * det; + out[2] = -ac * det; + out[3] = aa * det; + out[4] = (ac * aty - ad * atx) * det; + out[5] = (ab * atx - aa * aty) * det; + return out; +} + +/** + * Clone a new matrix. + * @param {Float32Array|Array.} a + */ +function clone$2(a) { + var b = create$1(); + copy$1(b, a); + return b; +} + +var matrix = (Object.freeze || Object)({ + create: create$1, + identity: identity, + copy: copy$1, + mul: mul$1, + translate: translate, + rotate: rotate, + scale: scale$1, + invert: invert, + clone: clone$2 +}); + +/** + * 提供变换扩展 + * @module zrender/mixin/Transformable + * @author pissang (https://www.github.com/pissang) + */ + +var mIdentity = identity; + +var EPSILON = 5e-5; + +function isNotAroundZero(val) { + return val > EPSILON || val < -EPSILON; +} + +/** + * @alias module:zrender/mixin/Transformable + * @constructor + */ +var Transformable = function (opts) { + opts = opts || {}; + // If there are no given position, rotation, scale + if (!opts.position) { + /** + * 平移 + * @type {Array.} + * @default [0, 0] + */ + this.position = [0, 0]; + } + if (opts.rotation == null) { + /** + * 旋转 + * @type {Array.} + * @default 0 + */ + this.rotation = 0; + } + if (!opts.scale) { + /** + * 缩放 + * @type {Array.} + * @default [1, 1] + */ + this.scale = [1, 1]; + } + /** + * 旋转和缩放的原点 + * @type {Array.} + * @default null + */ + this.origin = this.origin || null; +}; + +var transformableProto = Transformable.prototype; +transformableProto.transform = null; + +/** + * 判断是否需要有坐标变换 + * 如果有坐标变换, 则从position, rotation, scale以及父节点的transform计算出自身的transform矩阵 + */ +transformableProto.needLocalTransform = function () { + return isNotAroundZero(this.rotation) + || isNotAroundZero(this.position[0]) + || isNotAroundZero(this.position[1]) + || isNotAroundZero(this.scale[0] - 1) + || isNotAroundZero(this.scale[1] - 1); +}; + +var scaleTmp = []; +transformableProto.updateTransform = function () { + var parent = this.parent; + var parentHasTransform = parent && parent.transform; + var needLocalTransform = this.needLocalTransform(); + + var m = this.transform; + if (!(needLocalTransform || parentHasTransform)) { + m && mIdentity(m); + return; + } + + m = m || create$1(); + + if (needLocalTransform) { + this.getLocalTransform(m); + } + else { + mIdentity(m); + } + + // 应用父节点变换 + if (parentHasTransform) { + if (needLocalTransform) { + mul$1(m, parent.transform, m); + } + else { + copy$1(m, parent.transform); + } + } + // 保存这个变换矩阵 + this.transform = m; + + var globalScaleRatio = this.globalScaleRatio; + if (globalScaleRatio != null && globalScaleRatio !== 1) { + this.getGlobalScale(scaleTmp); + var relX = scaleTmp[0] < 0 ? -1 : 1; + var relY = scaleTmp[1] < 0 ? -1 : 1; + var sx = ((scaleTmp[0] - relX) * globalScaleRatio + relX) / scaleTmp[0] || 0; + var sy = ((scaleTmp[1] - relY) * globalScaleRatio + relY) / scaleTmp[1] || 0; + + m[0] *= sx; + m[1] *= sx; + m[2] *= sy; + m[3] *= sy; + } + + this.invTransform = this.invTransform || create$1(); + invert(this.invTransform, m); +}; + +transformableProto.getLocalTransform = function (m) { + return Transformable.getLocalTransform(this, m); +}; + +/** + * 将自己的transform应用到context上 + * @param {CanvasRenderingContext2D} ctx + */ +transformableProto.setTransform = function (ctx) { + var m = this.transform; + var dpr = ctx.dpr || 1; + if (m) { + ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]); + } + else { + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + } +}; + +transformableProto.restoreTransform = function (ctx) { + var dpr = ctx.dpr || 1; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); +}; + +var tmpTransform = []; +var originTransform = create$1(); + +transformableProto.setLocalTransform = function (m) { + if (!m) { + // TODO return or set identity? + return; + } + var sx = m[0] * m[0] + m[1] * m[1]; + var sy = m[2] * m[2] + m[3] * m[3]; + var position = this.position; + var scale$$1 = this.scale; + if (isNotAroundZero(sx - 1)) { + sx = Math.sqrt(sx); + } + if (isNotAroundZero(sy - 1)) { + sy = Math.sqrt(sy); + } + if (m[0] < 0) { + sx = -sx; + } + if (m[3] < 0) { + sy = -sy; + } + + position[0] = m[4]; + position[1] = m[5]; + scale$$1[0] = sx; + scale$$1[1] = sy; + this.rotation = Math.atan2(-m[1] / sy, m[0] / sx); +}; +/** + * 分解`transform`矩阵到`position`, `rotation`, `scale` + */ +transformableProto.decomposeTransform = function () { + if (!this.transform) { + return; + } + var parent = this.parent; + var m = this.transform; + if (parent && parent.transform) { + // Get local transform and decompose them to position, scale, rotation + mul$1(tmpTransform, parent.invTransform, m); + m = tmpTransform; + } + var origin = this.origin; + if (origin && (origin[0] || origin[1])) { + originTransform[4] = origin[0]; + originTransform[5] = origin[1]; + mul$1(tmpTransform, m, originTransform); + tmpTransform[4] -= origin[0]; + tmpTransform[5] -= origin[1]; + m = tmpTransform; + } + + this.setLocalTransform(m); +}; + +/** + * Get global scale + * @return {Array.} + */ +transformableProto.getGlobalScale = function (out) { + var m = this.transform; + out = out || []; + if (!m) { + out[0] = 1; + out[1] = 1; + return out; + } + out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]); + out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]); + if (m[0] < 0) { + out[0] = -out[0]; + } + if (m[3] < 0) { + out[1] = -out[1]; + } + return out; +}; +/** + * 变换坐标位置到 shape 的局部坐标空间 + * @method + * @param {number} x + * @param {number} y + * @return {Array.} + */ +transformableProto.transformCoordToLocal = function (x, y) { + var v2 = [x, y]; + var invTransform = this.invTransform; + if (invTransform) { + applyTransform(v2, v2, invTransform); + } + return v2; +}; + +/** + * 变换局部坐标位置到全局坐标空间 + * @method + * @param {number} x + * @param {number} y + * @return {Array.} + */ +transformableProto.transformCoordToGlobal = function (x, y) { + var v2 = [x, y]; + var transform = this.transform; + if (transform) { + applyTransform(v2, v2, transform); + } + return v2; +}; + +/** + * @static + * @param {Object} target + * @param {Array.} target.origin + * @param {number} target.rotation + * @param {Array.} target.position + * @param {Array.} [m] + */ +Transformable.getLocalTransform = function (target, m) { + m = m || []; + mIdentity(m); + + var origin = target.origin; + var scale$$1 = target.scale || [1, 1]; + var rotation = target.rotation || 0; + var position = target.position || [0, 0]; + + if (origin) { + // Translate to origin + m[4] -= origin[0]; + m[5] -= origin[1]; + } + scale$1(m, m, scale$$1); + if (rotation) { + rotate(m, m, rotation); + } + if (origin) { + // Translate back from origin + m[4] += origin[0]; + m[5] += origin[1]; + } + + m[4] += position[0]; + m[5] += position[1]; + + return m; +}; + +/** + * 缓动代码来自 https://github.com/sole/tween.js/blob/master/src/Tween.js + * @see http://sole.github.io/tween.js/examples/03_graphs.html + * @exports zrender/animation/easing + */ +var easing = { + /** + * @param {number} k + * @return {number} + */ + linear: function (k) { + return k; + }, + + /** + * @param {number} k + * @return {number} + */ + quadraticIn: function (k) { + return k * k; + }, + /** + * @param {number} k + * @return {number} + */ + quadraticOut: function (k) { + return k * (2 - k); + }, + /** + * @param {number} k + * @return {number} + */ + quadraticInOut: function (k) { + if ((k *= 2) < 1) { + return 0.5 * k * k; + } + return -0.5 * (--k * (k - 2) - 1); + }, + + // 三次方的缓动(t^3) + /** + * @param {number} k + * @return {number} + */ + cubicIn: function (k) { + return k * k * k; + }, + /** + * @param {number} k + * @return {number} + */ + cubicOut: function (k) { + return --k * k * k + 1; + }, + /** + * @param {number} k + * @return {number} + */ + cubicInOut: function (k) { + if ((k *= 2) < 1) { + return 0.5 * k * k * k; + } + return 0.5 * ((k -= 2) * k * k + 2); + }, + + // 四次方的缓动(t^4) + /** + * @param {number} k + * @return {number} + */ + quarticIn: function (k) { + return k * k * k * k; + }, + /** + * @param {number} k + * @return {number} + */ + quarticOut: function (k) { + return 1 - (--k * k * k * k); + }, + /** + * @param {number} k + * @return {number} + */ + quarticInOut: function (k) { + if ((k *= 2) < 1) { + return 0.5 * k * k * k * k; + } + return -0.5 * ((k -= 2) * k * k * k - 2); + }, + + // 五次方的缓动(t^5) + /** + * @param {number} k + * @return {number} + */ + quinticIn: function (k) { + return k * k * k * k * k; + }, + /** + * @param {number} k + * @return {number} + */ + quinticOut: function (k) { + return --k * k * k * k * k + 1; + }, + /** + * @param {number} k + * @return {number} + */ + quinticInOut: function (k) { + if ((k *= 2) < 1) { + return 0.5 * k * k * k * k * k; + } + return 0.5 * ((k -= 2) * k * k * k * k + 2); + }, + + // 正弦曲线的缓动(sin(t)) + /** + * @param {number} k + * @return {number} + */ + sinusoidalIn: function (k) { + return 1 - Math.cos(k * Math.PI / 2); + }, + /** + * @param {number} k + * @return {number} + */ + sinusoidalOut: function (k) { + return Math.sin(k * Math.PI / 2); + }, + /** + * @param {number} k + * @return {number} + */ + sinusoidalInOut: function (k) { + return 0.5 * (1 - Math.cos(Math.PI * k)); + }, + + // 指数曲线的缓动(2^t) + /** + * @param {number} k + * @return {number} + */ + exponentialIn: function (k) { + return k === 0 ? 0 : Math.pow(1024, k - 1); + }, + /** + * @param {number} k + * @return {number} + */ + exponentialOut: function (k) { + return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); + }, + /** + * @param {number} k + * @return {number} + */ + exponentialInOut: function (k) { + if (k === 0) { + return 0; + } + if (k === 1) { + return 1; + } + if ((k *= 2) < 1) { + return 0.5 * Math.pow(1024, k - 1); + } + return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2); + }, + + // 圆形曲线的缓动(sqrt(1-t^2)) + /** + * @param {number} k + * @return {number} + */ + circularIn: function (k) { + return 1 - Math.sqrt(1 - k * k); + }, + /** + * @param {number} k + * @return {number} + */ + circularOut: function (k) { + return Math.sqrt(1 - (--k * k)); + }, + /** + * @param {number} k + * @return {number} + */ + circularInOut: function (k) { + if ((k *= 2) < 1) { + return -0.5 * (Math.sqrt(1 - k * k) - 1); + } + return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); + }, + + // 创建类似于弹簧在停止前来回振荡的动画 + /** + * @param {number} k + * @return {number} + */ + elasticIn: function (k) { + var s; + var a = 0.1; + var p = 0.4; + if (k === 0) { + return 0; + } + if (k === 1) { + return 1; + } + if (!a || a < 1) { + a = 1; + s = p / 4; + } + else { + s = p * Math.asin(1 / a) / (2 * Math.PI); + } + return -(a * Math.pow(2, 10 * (k -= 1)) + * Math.sin((k - s) * (2 * Math.PI) / p)); + }, + /** + * @param {number} k + * @return {number} + */ + elasticOut: function (k) { + var s; + var a = 0.1; + var p = 0.4; + if (k === 0) { + return 0; + } + if (k === 1) { + return 1; + } + if (!a || a < 1) { + a = 1; + s = p / 4; + } + else { + s = p * Math.asin(1 / a) / (2 * Math.PI); + } + return (a * Math.pow(2, -10 * k) + * Math.sin((k - s) * (2 * Math.PI) / p) + 1); + }, + /** + * @param {number} k + * @return {number} + */ + elasticInOut: function (k) { + var s; + var a = 0.1; + var p = 0.4; + if (k === 0) { + return 0; + } + if (k === 1) { + return 1; + } + if (!a || a < 1) { + a = 1; + s = p / 4; + } + else { + s = p * Math.asin(1 / a) / (2 * Math.PI); + } + if ((k *= 2) < 1) { + return -0.5 * (a * Math.pow(2, 10 * (k -= 1)) + * Math.sin((k - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (k -= 1)) + * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1; + + }, + + // 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动 + /** + * @param {number} k + * @return {number} + */ + backIn: function (k) { + var s = 1.70158; + return k * k * ((s + 1) * k - s); + }, + /** + * @param {number} k + * @return {number} + */ + backOut: function (k) { + var s = 1.70158; + return --k * k * ((s + 1) * k + s) + 1; + }, + /** + * @param {number} k + * @return {number} + */ + backInOut: function (k) { + var s = 1.70158 * 1.525; + if ((k *= 2) < 1) { + return 0.5 * (k * k * ((s + 1) * k - s)); + } + return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); + }, + + // 创建弹跳效果 + /** + * @param {number} k + * @return {number} + */ + bounceIn: function (k) { + return 1 - easing.bounceOut(1 - k); + }, + /** + * @param {number} k + * @return {number} + */ + bounceOut: function (k) { + if (k < (1 / 2.75)) { + return 7.5625 * k * k; + } + else if (k < (2 / 2.75)) { + return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; + } + else if (k < (2.5 / 2.75)) { + return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; + } + else { + return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; + } + }, + /** + * @param {number} k + * @return {number} + */ + bounceInOut: function (k) { + if (k < 0.5) { + return easing.bounceIn(k * 2) * 0.5; + } + return easing.bounceOut(k * 2 - 1) * 0.5 + 0.5; + } +}; + +/** + * 动画主控制器 + * @config target 动画对象,可以是数组,如果是数组的话会批量分发onframe等事件 + * @config life(1000) 动画时长 + * @config delay(0) 动画延迟时间 + * @config loop(true) + * @config gap(0) 循环的间隔时间 + * @config onframe + * @config easing(optional) + * @config ondestroy(optional) + * @config onrestart(optional) + * + * TODO pause + */ + +function Clip(options) { + + this._target = options.target; + + // 生命周期 + this._life = options.life || 1000; + // 延时 + this._delay = options.delay || 0; + // 开始时间 + // this._startTime = new Date().getTime() + this._delay;// 单位毫秒 + this._initialized = false; + + // 是否循环 + this.loop = options.loop == null ? false : options.loop; + + this.gap = options.gap || 0; + + this.easing = options.easing || 'Linear'; + + this.onframe = options.onframe; + this.ondestroy = options.ondestroy; + this.onrestart = options.onrestart; + + this._pausedTime = 0; + this._paused = false; +} + +Clip.prototype = { + + constructor: Clip, + + step: function (globalTime, deltaTime) { + // Set startTime on first step, or _startTime may has milleseconds different between clips + // PENDING + if (!this._initialized) { + this._startTime = globalTime + this._delay; + this._initialized = true; + } + + if (this._paused) { + this._pausedTime += deltaTime; + return; + } + + var percent = (globalTime - this._startTime - this._pausedTime) / this._life; + + // 还没开始 + if (percent < 0) { + return; + } + + percent = Math.min(percent, 1); + + var easing$$1 = this.easing; + var easingFunc = typeof easing$$1 === 'string' ? easing[easing$$1] : easing$$1; + var schedule = typeof easingFunc === 'function' + ? easingFunc(percent) + : percent; + + this.fire('frame', schedule); + + // 结束 + if (percent === 1) { + if (this.loop) { + this.restart(globalTime); + // 重新开始周期 + // 抛出而不是直接调用事件直到 stage.update 后再统一调用这些事件 + return 'restart'; + } + + // 动画完成将这个控制器标识为待删除 + // 在Animation.update中进行批量删除 + this._needsRemove = true; + return 'destroy'; + } + + return null; + }, + + restart: function (globalTime) { + var remainder = (globalTime - this._startTime - this._pausedTime) % this._life; + this._startTime = globalTime - remainder + this.gap; + this._pausedTime = 0; + + this._needsRemove = false; + }, + + fire: function (eventType, arg) { + eventType = 'on' + eventType; + if (this[eventType]) { + this[eventType](this._target, arg); + } + }, + + pause: function () { + this._paused = true; + }, + + resume: function () { + this._paused = false; + } +}; + +// Simple LRU cache use doubly linked list +// @module zrender/core/LRU + +/** + * Simple double linked list. Compared with array, it has O(1) remove operation. + * @constructor + */ +var LinkedList = function () { + + /** + * @type {module:zrender/core/LRU~Entry} + */ + this.head = null; + + /** + * @type {module:zrender/core/LRU~Entry} + */ + this.tail = null; + + this._len = 0; +}; + +var linkedListProto = LinkedList.prototype; +/** + * Insert a new value at the tail + * @param {} val + * @return {module:zrender/core/LRU~Entry} + */ +linkedListProto.insert = function (val) { + var entry = new Entry(val); + this.insertEntry(entry); + return entry; +}; + +/** + * Insert an entry at the tail + * @param {module:zrender/core/LRU~Entry} entry + */ +linkedListProto.insertEntry = function (entry) { + if (!this.head) { + this.head = this.tail = entry; + } + else { + this.tail.next = entry; + entry.prev = this.tail; + entry.next = null; + this.tail = entry; + } + this._len++; +}; + +/** + * Remove entry. + * @param {module:zrender/core/LRU~Entry} entry + */ +linkedListProto.remove = function (entry) { + var prev = entry.prev; + var next = entry.next; + if (prev) { + prev.next = next; + } + else { + // Is head + this.head = next; + } + if (next) { + next.prev = prev; + } + else { + // Is tail + this.tail = prev; + } + entry.next = entry.prev = null; + this._len--; +}; + +/** + * @return {number} + */ +linkedListProto.len = function () { + return this._len; +}; + +/** + * Clear list + */ +linkedListProto.clear = function () { + this.head = this.tail = null; + this._len = 0; +}; + +/** + * @constructor + * @param {} val + */ +var Entry = function (val) { + /** + * @type {} + */ + this.value = val; + + /** + * @type {module:zrender/core/LRU~Entry} + */ + this.next; + + /** + * @type {module:zrender/core/LRU~Entry} + */ + this.prev; +}; + +/** + * LRU Cache + * @constructor + * @alias module:zrender/core/LRU + */ +var LRU = function (maxSize) { + + this._list = new LinkedList(); + + this._map = {}; + + this._maxSize = maxSize || 10; + + this._lastRemovedEntry = null; +}; + +var LRUProto = LRU.prototype; + +/** + * @param {string} key + * @param {} value + * @return {} Removed value + */ +LRUProto.put = function (key, value) { + var list = this._list; + var map = this._map; + var removed = null; + if (map[key] == null) { + var len = list.len(); + // Reuse last removed entry + var entry = this._lastRemovedEntry; + + if (len >= this._maxSize && len > 0) { + // Remove the least recently used + var leastUsedEntry = list.head; + list.remove(leastUsedEntry); + delete map[leastUsedEntry.key]; + + removed = leastUsedEntry.value; + this._lastRemovedEntry = leastUsedEntry; + } + + if (entry) { + entry.value = value; + } + else { + entry = new Entry(value); + } + entry.key = key; + list.insertEntry(entry); + map[key] = entry; + } + + return removed; +}; + +/** + * @param {string} key + * @return {} + */ +LRUProto.get = function (key) { + var entry = this._map[key]; + var list = this._list; + if (entry != null) { + // Put the latest used entry in the tail + if (entry !== list.tail) { + list.remove(entry); + list.insertEntry(entry); + } + + return entry.value; + } +}; + +/** + * Clear the cache + */ +LRUProto.clear = function () { + this._list.clear(); + this._map = {}; +}; + +var kCSSColorTable = { + 'transparent': [0, 0, 0, 0], 'aliceblue': [240, 248, 255, 1], + 'antiquewhite': [250, 235, 215, 1], 'aqua': [0, 255, 255, 1], + 'aquamarine': [127, 255, 212, 1], 'azure': [240, 255, 255, 1], + 'beige': [245, 245, 220, 1], 'bisque': [255, 228, 196, 1], + 'black': [0, 0, 0, 1], 'blanchedalmond': [255, 235, 205, 1], + 'blue': [0, 0, 255, 1], 'blueviolet': [138, 43, 226, 1], + 'brown': [165, 42, 42, 1], 'burlywood': [222, 184, 135, 1], + 'cadetblue': [95, 158, 160, 1], 'chartreuse': [127, 255, 0, 1], + 'chocolate': [210, 105, 30, 1], 'coral': [255, 127, 80, 1], + 'cornflowerblue': [100, 149, 237, 1], 'cornsilk': [255, 248, 220, 1], + 'crimson': [220, 20, 60, 1], 'cyan': [0, 255, 255, 1], + 'darkblue': [0, 0, 139, 1], 'darkcyan': [0, 139, 139, 1], + 'darkgoldenrod': [184, 134, 11, 1], 'darkgray': [169, 169, 169, 1], + 'darkgreen': [0, 100, 0, 1], 'darkgrey': [169, 169, 169, 1], + 'darkkhaki': [189, 183, 107, 1], 'darkmagenta': [139, 0, 139, 1], + 'darkolivegreen': [85, 107, 47, 1], 'darkorange': [255, 140, 0, 1], + 'darkorchid': [153, 50, 204, 1], 'darkred': [139, 0, 0, 1], + 'darksalmon': [233, 150, 122, 1], 'darkseagreen': [143, 188, 143, 1], + 'darkslateblue': [72, 61, 139, 1], 'darkslategray': [47, 79, 79, 1], + 'darkslategrey': [47, 79, 79, 1], 'darkturquoise': [0, 206, 209, 1], + 'darkviolet': [148, 0, 211, 1], 'deeppink': [255, 20, 147, 1], + 'deepskyblue': [0, 191, 255, 1], 'dimgray': [105, 105, 105, 1], + 'dimgrey': [105, 105, 105, 1], 'dodgerblue': [30, 144, 255, 1], + 'firebrick': [178, 34, 34, 1], 'floralwhite': [255, 250, 240, 1], + 'forestgreen': [34, 139, 34, 1], 'fuchsia': [255, 0, 255, 1], + 'gainsboro': [220, 220, 220, 1], 'ghostwhite': [248, 248, 255, 1], + 'gold': [255, 215, 0, 1], 'goldenrod': [218, 165, 32, 1], + 'gray': [128, 128, 128, 1], 'green': [0, 128, 0, 1], + 'greenyellow': [173, 255, 47, 1], 'grey': [128, 128, 128, 1], + 'honeydew': [240, 255, 240, 1], 'hotpink': [255, 105, 180, 1], + 'indianred': [205, 92, 92, 1], 'indigo': [75, 0, 130, 1], + 'ivory': [255, 255, 240, 1], 'khaki': [240, 230, 140, 1], + 'lavender': [230, 230, 250, 1], 'lavenderblush': [255, 240, 245, 1], + 'lawngreen': [124, 252, 0, 1], 'lemonchiffon': [255, 250, 205, 1], + 'lightblue': [173, 216, 230, 1], 'lightcoral': [240, 128, 128, 1], + 'lightcyan': [224, 255, 255, 1], 'lightgoldenrodyellow': [250, 250, 210, 1], + 'lightgray': [211, 211, 211, 1], 'lightgreen': [144, 238, 144, 1], + 'lightgrey': [211, 211, 211, 1], 'lightpink': [255, 182, 193, 1], + 'lightsalmon': [255, 160, 122, 1], 'lightseagreen': [32, 178, 170, 1], + 'lightskyblue': [135, 206, 250, 1], 'lightslategray': [119, 136, 153, 1], + 'lightslategrey': [119, 136, 153, 1], 'lightsteelblue': [176, 196, 222, 1], + 'lightyellow': [255, 255, 224, 1], 'lime': [0, 255, 0, 1], + 'limegreen': [50, 205, 50, 1], 'linen': [250, 240, 230, 1], + 'magenta': [255, 0, 255, 1], 'maroon': [128, 0, 0, 1], + 'mediumaquamarine': [102, 205, 170, 1], 'mediumblue': [0, 0, 205, 1], + 'mediumorchid': [186, 85, 211, 1], 'mediumpurple': [147, 112, 219, 1], + 'mediumseagreen': [60, 179, 113, 1], 'mediumslateblue': [123, 104, 238, 1], + 'mediumspringgreen': [0, 250, 154, 1], 'mediumturquoise': [72, 209, 204, 1], + 'mediumvioletred': [199, 21, 133, 1], 'midnightblue': [25, 25, 112, 1], + 'mintcream': [245, 255, 250, 1], 'mistyrose': [255, 228, 225, 1], + 'moccasin': [255, 228, 181, 1], 'navajowhite': [255, 222, 173, 1], + 'navy': [0, 0, 128, 1], 'oldlace': [253, 245, 230, 1], + 'olive': [128, 128, 0, 1], 'olivedrab': [107, 142, 35, 1], + 'orange': [255, 165, 0, 1], 'orangered': [255, 69, 0, 1], + 'orchid': [218, 112, 214, 1], 'palegoldenrod': [238, 232, 170, 1], + 'palegreen': [152, 251, 152, 1], 'paleturquoise': [175, 238, 238, 1], + 'palevioletred': [219, 112, 147, 1], 'papayawhip': [255, 239, 213, 1], + 'peachpuff': [255, 218, 185, 1], 'peru': [205, 133, 63, 1], + 'pink': [255, 192, 203, 1], 'plum': [221, 160, 221, 1], + 'powderblue': [176, 224, 230, 1], 'purple': [128, 0, 128, 1], + 'red': [255, 0, 0, 1], 'rosybrown': [188, 143, 143, 1], + 'royalblue': [65, 105, 225, 1], 'saddlebrown': [139, 69, 19, 1], + 'salmon': [250, 128, 114, 1], 'sandybrown': [244, 164, 96, 1], + 'seagreen': [46, 139, 87, 1], 'seashell': [255, 245, 238, 1], + 'sienna': [160, 82, 45, 1], 'silver': [192, 192, 192, 1], + 'skyblue': [135, 206, 235, 1], 'slateblue': [106, 90, 205, 1], + 'slategray': [112, 128, 144, 1], 'slategrey': [112, 128, 144, 1], + 'snow': [255, 250, 250, 1], 'springgreen': [0, 255, 127, 1], + 'steelblue': [70, 130, 180, 1], 'tan': [210, 180, 140, 1], + 'teal': [0, 128, 128, 1], 'thistle': [216, 191, 216, 1], + 'tomato': [255, 99, 71, 1], 'turquoise': [64, 224, 208, 1], + 'violet': [238, 130, 238, 1], 'wheat': [245, 222, 179, 1], + 'white': [255, 255, 255, 1], 'whitesmoke': [245, 245, 245, 1], + 'yellow': [255, 255, 0, 1], 'yellowgreen': [154, 205, 50, 1] +}; + +function clampCssByte(i) { // Clamp to integer 0 .. 255. + i = Math.round(i); // Seems to be what Chrome does (vs truncation). + return i < 0 ? 0 : i > 255 ? 255 : i; +} + +function clampCssAngle(i) { // Clamp to integer 0 .. 360. + i = Math.round(i); // Seems to be what Chrome does (vs truncation). + return i < 0 ? 0 : i > 360 ? 360 : i; +} + +function clampCssFloat(f) { // Clamp to float 0.0 .. 1.0. + return f < 0 ? 0 : f > 1 ? 1 : f; +} + +function parseCssInt(str) { // int or percentage. + if (str.length && str.charAt(str.length - 1) === '%') { + return clampCssByte(parseFloat(str) / 100 * 255); + } + return clampCssByte(parseInt(str, 10)); +} + +function parseCssFloat(str) { // float or percentage. + if (str.length && str.charAt(str.length - 1) === '%') { + return clampCssFloat(parseFloat(str) / 100); + } + return clampCssFloat(parseFloat(str)); +} + +function cssHueToRgb(m1, m2, h) { + if (h < 0) { + h += 1; + } + else if (h > 1) { + h -= 1; + } + + if (h * 6 < 1) { + return m1 + (m2 - m1) * h * 6; + } + if (h * 2 < 1) { + return m2; + } + if (h * 3 < 2) { + return m1 + (m2 - m1) * (2 / 3 - h) * 6; + } + return m1; +} + +function lerpNumber(a, b, p) { + return a + (b - a) * p; +} + +function setRgba(out, r, g, b, a) { + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = a; + return out; +} +function copyRgba(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; +} + +var colorCache = new LRU(20); +var lastRemovedArr = null; + +function putToCache(colorStr, rgbaArr) { + // Reuse removed array + if (lastRemovedArr) { + copyRgba(lastRemovedArr, rgbaArr); + } + lastRemovedArr = colorCache.put(colorStr, lastRemovedArr || (rgbaArr.slice())); +} + +/** + * @param {string} colorStr + * @param {Array.} out + * @return {Array.} + * @memberOf module:zrender/util/color + */ +function parse(colorStr, rgbaArr) { + if (!colorStr) { + return; + } + rgbaArr = rgbaArr || []; + + var cached = colorCache.get(colorStr); + if (cached) { + return copyRgba(rgbaArr, cached); + } + + // colorStr may be not string + colorStr = colorStr + ''; + // Remove all whitespace, not compliant, but should just be more accepting. + var str = colorStr.replace(/ /g, '').toLowerCase(); + + // Color keywords (and transparent) lookup. + if (str in kCSSColorTable) { + copyRgba(rgbaArr, kCSSColorTable[str]); + putToCache(colorStr, rgbaArr); + return rgbaArr; + } + + // #abc and #abc123 syntax. + if (str.charAt(0) === '#') { + if (str.length === 4) { + var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. + if (!(iv >= 0 && iv <= 0xfff)) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; // Covers NaN. + } + setRgba(rgbaArr, + ((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8), + (iv & 0xf0) | ((iv & 0xf0) >> 4), + (iv & 0xf) | ((iv & 0xf) << 4), + 1 + ); + putToCache(colorStr, rgbaArr); + return rgbaArr; + } + else if (str.length === 7) { + var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. + if (!(iv >= 0 && iv <= 0xffffff)) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; // Covers NaN. + } + setRgba(rgbaArr, + (iv & 0xff0000) >> 16, + (iv & 0xff00) >> 8, + iv & 0xff, + 1 + ); + putToCache(colorStr, rgbaArr); + return rgbaArr; + } + + return; + } + var op = str.indexOf('('); + var ep = str.indexOf(')'); + if (op !== -1 && ep + 1 === str.length) { + var fname = str.substr(0, op); + var params = str.substr(op + 1, ep - (op + 1)).split(','); + var alpha = 1; // To allow case fallthrough. + switch (fname) { + case 'rgba': + if (params.length !== 4) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; + } + alpha = parseCssFloat(params.pop()); // jshint ignore:line + // Fall through. + case 'rgb': + if (params.length !== 3) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; + } + setRgba(rgbaArr, + parseCssInt(params[0]), + parseCssInt(params[1]), + parseCssInt(params[2]), + alpha + ); + putToCache(colorStr, rgbaArr); + return rgbaArr; + case 'hsla': + if (params.length !== 4) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; + } + params[3] = parseCssFloat(params[3]); + hsla2rgba(params, rgbaArr); + putToCache(colorStr, rgbaArr); + return rgbaArr; + case 'hsl': + if (params.length !== 3) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; + } + hsla2rgba(params, rgbaArr); + putToCache(colorStr, rgbaArr); + return rgbaArr; + default: + return; + } + } + + setRgba(rgbaArr, 0, 0, 0, 1); + return; +} + +/** + * @param {Array.} hsla + * @param {Array.} rgba + * @return {Array.} rgba + */ +function hsla2rgba(hsla, rgba) { + var h = (((parseFloat(hsla[0]) % 360) + 360) % 360) / 360; // 0 .. 1 + // NOTE(deanm): According to the CSS spec s/l should only be + // percentages, but we don't bother and let float or percentage. + var s = parseCssFloat(hsla[1]); + var l = parseCssFloat(hsla[2]); + var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + var m1 = l * 2 - m2; + + rgba = rgba || []; + setRgba(rgba, + clampCssByte(cssHueToRgb(m1, m2, h + 1 / 3) * 255), + clampCssByte(cssHueToRgb(m1, m2, h) * 255), + clampCssByte(cssHueToRgb(m1, m2, h - 1 / 3) * 255), + 1 + ); + + if (hsla.length === 4) { + rgba[3] = hsla[3]; + } + + return rgba; +} + +/** + * @param {Array.} rgba + * @return {Array.} hsla + */ +function rgba2hsla(rgba) { + if (!rgba) { + return; + } + + // RGB from 0 to 255 + var R = rgba[0] / 255; + var G = rgba[1] / 255; + var B = rgba[2] / 255; + + var vMin = Math.min(R, G, B); // Min. value of RGB + var vMax = Math.max(R, G, B); // Max. value of RGB + var delta = vMax - vMin; // Delta RGB value + + var L = (vMax + vMin) / 2; + var H; + var S; + // HSL results from 0 to 1 + if (delta === 0) { + H = 0; + S = 0; + } + else { + if (L < 0.5) { + S = delta / (vMax + vMin); + } + else { + S = delta / (2 - vMax - vMin); + } + + var deltaR = (((vMax - R) / 6) + (delta / 2)) / delta; + var deltaG = (((vMax - G) / 6) + (delta / 2)) / delta; + var deltaB = (((vMax - B) / 6) + (delta / 2)) / delta; + + if (R === vMax) { + H = deltaB - deltaG; + } + else if (G === vMax) { + H = (1 / 3) + deltaR - deltaB; + } + else if (B === vMax) { + H = (2 / 3) + deltaG - deltaR; + } + + if (H < 0) { + H += 1; + } + + if (H > 1) { + H -= 1; + } + } + + var hsla = [H * 360, S, L]; + + if (rgba[3] != null) { + hsla.push(rgba[3]); + } + + return hsla; +} + +/** + * @param {string} color + * @param {number} level + * @return {string} + * @memberOf module:zrender/util/color + */ +function lift(color, level) { + var colorArr = parse(color); + if (colorArr) { + for (var i = 0; i < 3; i++) { + if (level < 0) { + colorArr[i] = colorArr[i] * (1 - level) | 0; + } + else { + colorArr[i] = ((255 - colorArr[i]) * level + colorArr[i]) | 0; + } + if (colorArr[i] > 255) { + colorArr[i] = 255; + } + else if (color[i] < 0) { + colorArr[i] = 0; + } + } + return stringify(colorArr, colorArr.length === 4 ? 'rgba' : 'rgb'); + } +} + +/** + * @param {string} color + * @return {string} + * @memberOf module:zrender/util/color + */ +function toHex(color) { + var colorArr = parse(color); + if (colorArr) { + return ((1 << 24) + (colorArr[0] << 16) + (colorArr[1] << 8) + (+colorArr[2])).toString(16).slice(1); + } +} + +/** + * Map value to color. Faster than lerp methods because color is represented by rgba array. + * @param {number} normalizedValue A float between 0 and 1. + * @param {Array.>} colors List of rgba color array + * @param {Array.} [out] Mapped gba color array + * @return {Array.} will be null/undefined if input illegal. + */ +function fastLerp(normalizedValue, colors, out) { + if (!(colors && colors.length) + || !(normalizedValue >= 0 && normalizedValue <= 1) + ) { + return; + } + + out = out || []; + + var value = normalizedValue * (colors.length - 1); + var leftIndex = Math.floor(value); + var rightIndex = Math.ceil(value); + var leftColor = colors[leftIndex]; + var rightColor = colors[rightIndex]; + var dv = value - leftIndex; + out[0] = clampCssByte(lerpNumber(leftColor[0], rightColor[0], dv)); + out[1] = clampCssByte(lerpNumber(leftColor[1], rightColor[1], dv)); + out[2] = clampCssByte(lerpNumber(leftColor[2], rightColor[2], dv)); + out[3] = clampCssFloat(lerpNumber(leftColor[3], rightColor[3], dv)); + + return out; +} + +/** + * @deprecated + */ +var fastMapToColor = fastLerp; + +/** + * @param {number} normalizedValue A float between 0 and 1. + * @param {Array.} colors Color list. + * @param {boolean=} fullOutput Default false. + * @return {(string|Object)} Result color. If fullOutput, + * return {color: ..., leftIndex: ..., rightIndex: ..., value: ...}, + * @memberOf module:zrender/util/color + */ +function lerp$1(normalizedValue, colors, fullOutput) { + if (!(colors && colors.length) + || !(normalizedValue >= 0 && normalizedValue <= 1) + ) { + return; + } + + var value = normalizedValue * (colors.length - 1); + var leftIndex = Math.floor(value); + var rightIndex = Math.ceil(value); + var leftColor = parse(colors[leftIndex]); + var rightColor = parse(colors[rightIndex]); + var dv = value - leftIndex; + + var color = stringify( + [ + clampCssByte(lerpNumber(leftColor[0], rightColor[0], dv)), + clampCssByte(lerpNumber(leftColor[1], rightColor[1], dv)), + clampCssByte(lerpNumber(leftColor[2], rightColor[2], dv)), + clampCssFloat(lerpNumber(leftColor[3], rightColor[3], dv)) + ], + 'rgba' + ); + + return fullOutput + ? { + color: color, + leftIndex: leftIndex, + rightIndex: rightIndex, + value: value + } + : color; +} + +/** + * @deprecated + */ +var mapToColor = lerp$1; + +/** + * @param {string} color + * @param {number=} h 0 ~ 360, ignore when null. + * @param {number=} s 0 ~ 1, ignore when null. + * @param {number=} l 0 ~ 1, ignore when null. + * @return {string} Color string in rgba format. + * @memberOf module:zrender/util/color + */ +function modifyHSL(color, h, s, l) { + color = parse(color); + + if (color) { + color = rgba2hsla(color); + h != null && (color[0] = clampCssAngle(h)); + s != null && (color[1] = parseCssFloat(s)); + l != null && (color[2] = parseCssFloat(l)); + + return stringify(hsla2rgba(color), 'rgba'); + } +} + +/** + * @param {string} color + * @param {number=} alpha 0 ~ 1 + * @return {string} Color string in rgba format. + * @memberOf module:zrender/util/color + */ +function modifyAlpha(color, alpha) { + color = parse(color); + + if (color && alpha != null) { + color[3] = clampCssFloat(alpha); + return stringify(color, 'rgba'); + } +} + +/** + * @param {Array.} arrColor like [12,33,44,0.4] + * @param {string} type 'rgba', 'hsva', ... + * @return {string} Result color. (If input illegal, return undefined). + */ +function stringify(arrColor, type) { + if (!arrColor || !arrColor.length) { + return; + } + var colorStr = arrColor[0] + ',' + arrColor[1] + ',' + arrColor[2]; + if (type === 'rgba' || type === 'hsva' || type === 'hsla') { + colorStr += ',' + arrColor[3]; + } + return type + '(' + colorStr + ')'; +} + + +var color = (Object.freeze || Object)({ + parse: parse, + lift: lift, + toHex: toHex, + fastLerp: fastLerp, + fastMapToColor: fastMapToColor, + lerp: lerp$1, + mapToColor: mapToColor, + modifyHSL: modifyHSL, + modifyAlpha: modifyAlpha, + stringify: stringify +}); + +/** + * @module echarts/animation/Animator + */ + +var arraySlice = Array.prototype.slice; + +function defaultGetter(target, key) { + return target[key]; +} + +function defaultSetter(target, key, value) { + target[key] = value; +} + +/** + * @param {number} p0 + * @param {number} p1 + * @param {number} percent + * @return {number} + */ +function interpolateNumber(p0, p1, percent) { + return (p1 - p0) * percent + p0; +} + +/** + * @param {string} p0 + * @param {string} p1 + * @param {number} percent + * @return {string} + */ +function interpolateString(p0, p1, percent) { + return percent > 0.5 ? p1 : p0; +} + +/** + * @param {Array} p0 + * @param {Array} p1 + * @param {number} percent + * @param {Array} out + * @param {number} arrDim + */ +function interpolateArray(p0, p1, percent, out, arrDim) { + var len = p0.length; + if (arrDim === 1) { + for (var i = 0; i < len; i++) { + out[i] = interpolateNumber(p0[i], p1[i], percent); + } + } + else { + var len2 = len && p0[0].length; + for (var i = 0; i < len; i++) { + for (var j = 0; j < len2; j++) { + out[i][j] = interpolateNumber( + p0[i][j], p1[i][j], percent + ); + } + } + } +} + +// arr0 is source array, arr1 is target array. +// Do some preprocess to avoid error happened when interpolating from arr0 to arr1 +function fillArr(arr0, arr1, arrDim) { + var arr0Len = arr0.length; + var arr1Len = arr1.length; + if (arr0Len !== arr1Len) { + // FIXME Not work for TypedArray + var isPreviousLarger = arr0Len > arr1Len; + if (isPreviousLarger) { + // Cut the previous + arr0.length = arr1Len; + } + else { + // Fill the previous + for (var i = arr0Len; i < arr1Len; i++) { + arr0.push( + arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i]) + ); + } + } + } + // Handling NaN value + var len2 = arr0[0] && arr0[0].length; + for (var i = 0; i < arr0.length; i++) { + if (arrDim === 1) { + if (isNaN(arr0[i])) { + arr0[i] = arr1[i]; + } + } + else { + for (var j = 0; j < len2; j++) { + if (isNaN(arr0[i][j])) { + arr0[i][j] = arr1[i][j]; + } + } + } + } +} + +/** + * @param {Array} arr0 + * @param {Array} arr1 + * @param {number} arrDim + * @return {boolean} + */ +function isArraySame(arr0, arr1, arrDim) { + if (arr0 === arr1) { + return true; + } + var len = arr0.length; + if (len !== arr1.length) { + return false; + } + if (arrDim === 1) { + for (var i = 0; i < len; i++) { + if (arr0[i] !== arr1[i]) { + return false; + } + } + } + else { + var len2 = arr0[0].length; + for (var i = 0; i < len; i++) { + for (var j = 0; j < len2; j++) { + if (arr0[i][j] !== arr1[i][j]) { + return false; + } + } + } + } + return true; +} + +/** + * Catmull Rom interpolate array + * @param {Array} p0 + * @param {Array} p1 + * @param {Array} p2 + * @param {Array} p3 + * @param {number} t + * @param {number} t2 + * @param {number} t3 + * @param {Array} out + * @param {number} arrDim + */ +function catmullRomInterpolateArray( + p0, p1, p2, p3, t, t2, t3, out, arrDim +) { + var len = p0.length; + if (arrDim === 1) { + for (var i = 0; i < len; i++) { + out[i] = catmullRomInterpolate( + p0[i], p1[i], p2[i], p3[i], t, t2, t3 + ); + } + } + else { + var len2 = p0[0].length; + for (var i = 0; i < len; i++) { + for (var j = 0; j < len2; j++) { + out[i][j] = catmullRomInterpolate( + p0[i][j], p1[i][j], p2[i][j], p3[i][j], + t, t2, t3 + ); + } + } + } +} + +/** + * Catmull Rom interpolate number + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} p3 + * @param {number} t + * @param {number} t2 + * @param {number} t3 + * @return {number} + */ +function catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) { + var v0 = (p2 - p0) * 0.5; + var v1 = (p3 - p1) * 0.5; + return (2 * (p1 - p2) + v0 + v1) * t3 + + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + + v0 * t + p1; +} + +function cloneValue(value) { + if (isArrayLike(value)) { + var len = value.length; + if (isArrayLike(value[0])) { + var ret = []; + for (var i = 0; i < len; i++) { + ret.push(arraySlice.call(value[i])); + } + return ret; + } + + return arraySlice.call(value); + } + + return value; +} + +function rgba2String(rgba) { + rgba[0] = Math.floor(rgba[0]); + rgba[1] = Math.floor(rgba[1]); + rgba[2] = Math.floor(rgba[2]); + + return 'rgba(' + rgba.join(',') + ')'; +} + +function getArrayDim(keyframes) { + var lastValue = keyframes[keyframes.length - 1].value; + return isArrayLike(lastValue && lastValue[0]) ? 2 : 1; +} + +function createTrackClip(animator, easing, oneTrackDone, keyframes, propName, forceAnimate) { + var getter = animator._getter; + var setter = animator._setter; + var useSpline = easing === 'spline'; + + var trackLen = keyframes.length; + if (!trackLen) { + return; + } + // Guess data type + var firstVal = keyframes[0].value; + var isValueArray = isArrayLike(firstVal); + var isValueColor = false; + var isValueString = false; + + // For vertices morphing + var arrDim = isValueArray ? getArrayDim(keyframes) : 0; + + var trackMaxTime; + // Sort keyframe as ascending + keyframes.sort(function (a, b) { + return a.time - b.time; + }); + + trackMaxTime = keyframes[trackLen - 1].time; + // Percents of each keyframe + var kfPercents = []; + // Value of each keyframe + var kfValues = []; + var prevValue = keyframes[0].value; + var isAllValueEqual = true; + for (var i = 0; i < trackLen; i++) { + kfPercents.push(keyframes[i].time / trackMaxTime); + // Assume value is a color when it is a string + var value = keyframes[i].value; + + // Check if value is equal, deep check if value is array + if (!((isValueArray && isArraySame(value, prevValue, arrDim)) + || (!isValueArray && value === prevValue))) { + isAllValueEqual = false; + } + prevValue = value; + + // Try converting a string to a color array + if (typeof value === 'string') { + var colorArray = parse(value); + if (colorArray) { + value = colorArray; + isValueColor = true; + } + else { + isValueString = true; + } + } + kfValues.push(value); + } + if (!forceAnimate && isAllValueEqual) { + return; + } + + var lastValue = kfValues[trackLen - 1]; + // Polyfill array and NaN value + for (var i = 0; i < trackLen - 1; i++) { + if (isValueArray) { + fillArr(kfValues[i], lastValue, arrDim); + } + else { + if (isNaN(kfValues[i]) && !isNaN(lastValue) && !isValueString && !isValueColor) { + kfValues[i] = lastValue; + } + } + } + isValueArray && fillArr(getter(animator._target, propName), lastValue, arrDim); + + // Cache the key of last frame to speed up when + // animation playback is sequency + var lastFrame = 0; + var lastFramePercent = 0; + var start; + var w; + var p0; + var p1; + var p2; + var p3; + + if (isValueColor) { + var rgba = [0, 0, 0, 0]; + } + + var onframe = function (target, percent) { + // Find the range keyframes + // kf1-----kf2---------current--------kf3 + // find kf2 and kf3 and do interpolation + var frame; + // In the easing function like elasticOut, percent may less than 0 + if (percent < 0) { + frame = 0; + } + else if (percent < lastFramePercent) { + // Start from next key + // PENDING start from lastFrame ? + start = Math.min(lastFrame + 1, trackLen - 1); + for (frame = start; frame >= 0; frame--) { + if (kfPercents[frame] <= percent) { + break; + } + } + // PENDING really need to do this ? + frame = Math.min(frame, trackLen - 2); + } + else { + for (frame = lastFrame; frame < trackLen; frame++) { + if (kfPercents[frame] > percent) { + break; + } + } + frame = Math.min(frame - 1, trackLen - 2); + } + lastFrame = frame; + lastFramePercent = percent; + + var range = (kfPercents[frame + 1] - kfPercents[frame]); + if (range === 0) { + return; + } + else { + w = (percent - kfPercents[frame]) / range; + } + if (useSpline) { + p1 = kfValues[frame]; + p0 = kfValues[frame === 0 ? frame : frame - 1]; + p2 = kfValues[frame > trackLen - 2 ? trackLen - 1 : frame + 1]; + p3 = kfValues[frame > trackLen - 3 ? trackLen - 1 : frame + 2]; + if (isValueArray) { + catmullRomInterpolateArray( + p0, p1, p2, p3, w, w * w, w * w * w, + getter(target, propName), + arrDim + ); + } + else { + var value; + if (isValueColor) { + value = catmullRomInterpolateArray( + p0, p1, p2, p3, w, w * w, w * w * w, + rgba, 1 + ); + value = rgba2String(rgba); + } + else if (isValueString) { + // String is step(0.5) + return interpolateString(p1, p2, w); + } + else { + value = catmullRomInterpolate( + p0, p1, p2, p3, w, w * w, w * w * w + ); + } + setter( + target, + propName, + value + ); + } + } + else { + if (isValueArray) { + interpolateArray( + kfValues[frame], kfValues[frame + 1], w, + getter(target, propName), + arrDim + ); + } + else { + var value; + if (isValueColor) { + interpolateArray( + kfValues[frame], kfValues[frame + 1], w, + rgba, 1 + ); + value = rgba2String(rgba); + } + else if (isValueString) { + // String is step(0.5) + return interpolateString(kfValues[frame], kfValues[frame + 1], w); + } + else { + value = interpolateNumber(kfValues[frame], kfValues[frame + 1], w); + } + setter( + target, + propName, + value + ); + } + } + }; + + var clip = new Clip({ + target: animator._target, + life: trackMaxTime, + loop: animator._loop, + delay: animator._delay, + onframe: onframe, + ondestroy: oneTrackDone + }); + + if (easing && easing !== 'spline') { + clip.easing = easing; + } + + return clip; +} + +/** + * @alias module:zrender/animation/Animator + * @constructor + * @param {Object} target + * @param {boolean} loop + * @param {Function} getter + * @param {Function} setter + */ +var Animator = function (target, loop, getter, setter) { + this._tracks = {}; + this._target = target; + + this._loop = loop || false; + + this._getter = getter || defaultGetter; + this._setter = setter || defaultSetter; + + this._clipCount = 0; + + this._delay = 0; + + this._doneList = []; + + this._onframeList = []; + + this._clipList = []; +}; + +Animator.prototype = { + /** + * 设置动画关键帧 + * @param {number} time 关键帧时间,单位是ms + * @param {Object} props 关键帧的属性值,key-value表示 + * @return {module:zrender/animation/Animator} + */ + when: function (time /* ms */, props) { + var tracks = this._tracks; + for (var propName in props) { + if (!props.hasOwnProperty(propName)) { + continue; + } + + if (!tracks[propName]) { + tracks[propName] = []; + // Invalid value + var value = this._getter(this._target, propName); + if (value == null) { + // zrLog('Invalid property ' + propName); + continue; + } + // If time is 0 + // Then props is given initialize value + // Else + // Initialize value from current prop value + if (time !== 0) { + tracks[propName].push({ + time: 0, + value: cloneValue(value) + }); + } + } + tracks[propName].push({ + time: time, + value: props[propName] + }); + } + return this; + }, + /** + * 添加动画每一帧的回调函数 + * @param {Function} callback + * @return {module:zrender/animation/Animator} + */ + during: function (callback) { + this._onframeList.push(callback); + return this; + }, + + pause: function () { + for (var i = 0; i < this._clipList.length; i++) { + this._clipList[i].pause(); + } + this._paused = true; + }, + + resume: function () { + for (var i = 0; i < this._clipList.length; i++) { + this._clipList[i].resume(); + } + this._paused = false; + }, + + isPaused: function () { + return !!this._paused; + }, + + _doneCallback: function () { + // Clear all tracks + this._tracks = {}; + // Clear all clips + this._clipList.length = 0; + + var doneList = this._doneList; + var len = doneList.length; + for (var i = 0; i < len; i++) { + doneList[i].call(this); + } + }, + /** + * 开始执行动画 + * @param {string|Function} [easing] + * 动画缓动函数,详见{@link module:zrender/animation/easing} + * @param {boolean} forceAnimate + * @return {module:zrender/animation/Animator} + */ + start: function (easing, forceAnimate) { + + var self = this; + var clipCount = 0; + + var oneTrackDone = function () { + clipCount--; + if (!clipCount) { + self._doneCallback(); + } + }; + + var lastClip; + for (var propName in this._tracks) { + if (!this._tracks.hasOwnProperty(propName)) { + continue; + } + var clip = createTrackClip( + this, easing, oneTrackDone, + this._tracks[propName], propName, forceAnimate + ); + if (clip) { + this._clipList.push(clip); + clipCount++; + + // If start after added to animation + if (this.animation) { + this.animation.addClip(clip); + } + + lastClip = clip; + } + } + + // Add during callback on the last clip + if (lastClip) { + var oldOnFrame = lastClip.onframe; + lastClip.onframe = function (target, percent) { + oldOnFrame(target, percent); + + for (var i = 0; i < self._onframeList.length; i++) { + self._onframeList[i](target, percent); + } + }; + } + + // This optimization will help the case that in the upper application + // the view may be refreshed frequently, where animation will be + // called repeatly but nothing changed. + if (!clipCount) { + this._doneCallback(); + } + return this; + }, + /** + * 停止动画 + * @param {boolean} forwardToLast If move to last frame before stop + */ + stop: function (forwardToLast) { + var clipList = this._clipList; + var animation = this.animation; + for (var i = 0; i < clipList.length; i++) { + var clip = clipList[i]; + if (forwardToLast) { + // Move to last frame before stop + clip.onframe(this._target, 1); + } + animation && animation.removeClip(clip); + } + clipList.length = 0; + }, + /** + * 设置动画延迟开始的时间 + * @param {number} time 单位ms + * @return {module:zrender/animation/Animator} + */ + delay: function (time) { + this._delay = time; + return this; + }, + /** + * 添加动画结束的回调 + * @param {Function} cb + * @return {module:zrender/animation/Animator} + */ + done: function (cb) { + if (cb) { + this._doneList.push(cb); + } + return this; + }, + + /** + * @return {Array.} + */ + getClips: function () { + return this._clipList; + } +}; + +var dpr = 1; + +// If in browser environment +if (typeof window !== 'undefined') { + dpr = Math.max(window.devicePixelRatio || 1, 1); +} + +/** + * config默认配置项 + * @exports zrender/config + * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) + */ + +/** + * Debug log mode: + * 0: Do nothing, for release. + * 1: console.error, for debug. + */ +var debugMode = 0; + +// retina 屏幕优化 +var devicePixelRatio = dpr; + +var logError = function () { +}; + +if (debugMode === 1) { + logError = console.error; +} + +var logError$1 = logError; + +/** + * @alias modue:zrender/mixin/Animatable + * @constructor + */ +var Animatable = function () { + + /** + * @type {Array.} + * @readOnly + */ + this.animators = []; +}; + +Animatable.prototype = { + + constructor: Animatable, + + /** + * 动画 + * + * @param {string} path The path to fetch value from object, like 'a.b.c'. + * @param {boolean} [loop] Whether to loop animation. + * @return {module:zrender/animation/Animator} + * @example: + * el.animate('style', false) + * .when(1000, {x: 10} ) + * .done(function(){ // Animation done }) + * .start() + */ + animate: function (path, loop) { + var target; + var animatingShape = false; + var el = this; + var zr = this.__zr; + if (path) { + var pathSplitted = path.split('.'); + var prop = el; + // If animating shape + animatingShape = pathSplitted[0] === 'shape'; + for (var i = 0, l = pathSplitted.length; i < l; i++) { + if (!prop) { + continue; + } + prop = prop[pathSplitted[i]]; + } + if (prop) { + target = prop; + } + } + else { + target = el; + } + + if (!target) { + logError$1( + 'Property "' + + path + + '" is not existed in element ' + + el.id + ); + return; + } + + var animators = el.animators; + + var animator = new Animator(target, loop); + + animator.during(function (target) { + el.dirty(animatingShape); + }) + .done(function () { + // FIXME Animator will not be removed if use `Animator#stop` to stop animation + animators.splice(indexOf(animators, animator), 1); + }); + + animators.push(animator); + + // If animate after added to the zrender + if (zr) { + zr.animation.addAnimator(animator); + } + + return animator; + }, + + /** + * 停止动画 + * @param {boolean} forwardToLast If move to last frame before stop + */ + stopAnimation: function (forwardToLast) { + var animators = this.animators; + var len = animators.length; + for (var i = 0; i < len; i++) { + animators[i].stop(forwardToLast); + } + animators.length = 0; + + return this; + }, + + /** + * Caution: this method will stop previous animation. + * So do not use this method to one element twice before + * animation starts, unless you know what you are doing. + * @param {Object} target + * @param {number} [time=500] Time in ms + * @param {string} [easing='linear'] + * @param {number} [delay=0] + * @param {Function} [callback] + * @param {Function} [forceAnimate] Prevent stop animation and callback + * immediently when target values are the same as current values. + * + * @example + * // Animate position + * el.animateTo({ + * position: [10, 10] + * }, function () { // done }) + * + * // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing + * el.animateTo({ + * shape: { + * width: 500 + * }, + * style: { + * fill: 'red' + * } + * position: [10, 10] + * }, 100, 100, 'cubicOut', function () { // done }) + */ + // TODO Return animation key + animateTo: function (target, time, delay, easing, callback, forceAnimate) { + animateTo(this, target, time, delay, easing, callback, forceAnimate); + }, + + /** + * Animate from the target state to current state. + * The params and the return value are the same as `this.animateTo`. + */ + animateFrom: function (target, time, delay, easing, callback, forceAnimate) { + animateTo(this, target, time, delay, easing, callback, forceAnimate, true); + } +}; + +function animateTo(animatable, target, time, delay, easing, callback, forceAnimate, reverse) { + // animateTo(target, time, easing, callback); + if (isString(delay)) { + callback = easing; + easing = delay; + delay = 0; + } + // animateTo(target, time, delay, callback); + else if (isFunction$1(easing)) { + callback = easing; + easing = 'linear'; + delay = 0; + } + // animateTo(target, time, callback); + else if (isFunction$1(delay)) { + callback = delay; + delay = 0; + } + // animateTo(target, callback) + else if (isFunction$1(time)) { + callback = time; + time = 500; + } + // animateTo(target) + else if (!time) { + time = 500; + } + // Stop all previous animations + animatable.stopAnimation(); + animateToShallow(animatable, '', animatable, target, time, delay, reverse); + + // Animators may be removed immediately after start + // if there is nothing to animate + var animators = animatable.animators.slice(); + var count = animators.length; + function done() { + count--; + if (!count) { + callback && callback(); + } + } + + // No animators. This should be checked before animators[i].start(), + // because 'done' may be executed immediately if no need to animate. + if (!count) { + callback && callback(); + } + // Start after all animators created + // Incase any animator is done immediately when all animation properties are not changed + for (var i = 0; i < animators.length; i++) { + animators[i] + .done(done) + .start(easing, forceAnimate); + } +} + +/** + * @param {string} path='' + * @param {Object} source=animatable + * @param {Object} target + * @param {number} [time=500] + * @param {number} [delay=0] + * @param {boolean} [reverse] If `true`, animate + * from the `target` to current state. + * + * @example + * // Animate position + * el._animateToShallow({ + * position: [10, 10] + * }) + * + * // Animate shape, style and position in 100ms, delayed 100ms + * el._animateToShallow({ + * shape: { + * width: 500 + * }, + * style: { + * fill: 'red' + * } + * position: [10, 10] + * }, 100, 100) + */ +function animateToShallow(animatable, path, source, target, time, delay, reverse) { + var objShallow = {}; + var propertyCount = 0; + for (var name in target) { + if (!target.hasOwnProperty(name)) { + continue; + } + + if (source[name] != null) { + if (isObject$1(target[name]) && !isArrayLike(target[name])) { + animateToShallow( + animatable, + path ? path + '.' + name : name, + source[name], + target[name], + time, + delay, + reverse + ); + } + else { + if (reverse) { + objShallow[name] = source[name]; + setAttrByPath(animatable, path, name, target[name]); + } + else { + objShallow[name] = target[name]; + } + propertyCount++; + } + } + else if (target[name] != null && !reverse) { + setAttrByPath(animatable, path, name, target[name]); + } + } + + if (propertyCount > 0) { + animatable.animate(path, false) + .when(time == null ? 500 : time, objShallow) + .delay(delay || 0); + } +} + +function setAttrByPath(el, path, name, value) { + // Attr directly if not has property + // FIXME, if some property not needed for element ? + if (!path) { + el.attr(name, value); + } + else { + // Only support set shape or style + var props = {}; + props[path] = {}; + props[path][name] = value; + el.attr(props); + } +} + +/** + * @alias module:zrender/Element + * @constructor + * @extends {module:zrender/mixin/Animatable} + * @extends {module:zrender/mixin/Transformable} + * @extends {module:zrender/mixin/Eventful} + */ +var Element = function (opts) { // jshint ignore:line + + Transformable.call(this, opts); + Eventful.call(this, opts); + Animatable.call(this, opts); + + /** + * 画布元素ID + * @type {string} + */ + this.id = opts.id || guid(); +}; + +Element.prototype = { + + /** + * 元素类型 + * Element type + * @type {string} + */ + type: 'element', + + /** + * 元素名字 + * Element name + * @type {string} + */ + name: '', + + /** + * ZRender 实例对象,会在 element 添加到 zrender 实例中后自动赋值 + * ZRender instance will be assigned when element is associated with zrender + * @name module:/zrender/Element#__zr + * @type {module:zrender/ZRender} + */ + __zr: null, + + /** + * 图形是否忽略,为true时忽略图形的绘制以及事件触发 + * If ignore drawing and events of the element object + * @name module:/zrender/Element#ignore + * @type {boolean} + * @default false + */ + ignore: false, + + /** + * 用于裁剪的路径(shape),所有 Group 内的路径在绘制时都会被这个路径裁剪 + * 该路径会继承被裁减对象的变换 + * @type {module:zrender/graphic/Path} + * @see http://www.w3.org/TR/2dcontext/#clipping-region + * @readOnly + */ + clipPath: null, + + /** + * 是否是 Group + * @type {boolean} + */ + isGroup: false, + + /** + * Drift element + * @param {number} dx dx on the global space + * @param {number} dy dy on the global space + */ + drift: function (dx, dy) { + switch (this.draggable) { + case 'horizontal': + dy = 0; + break; + case 'vertical': + dx = 0; + break; + } + + var m = this.transform; + if (!m) { + m = this.transform = [1, 0, 0, 1, 0, 0]; + } + m[4] += dx; + m[5] += dy; + + this.decomposeTransform(); + this.dirty(false); + }, + + /** + * Hook before update + */ + beforeUpdate: function () {}, + /** + * Hook after update + */ + afterUpdate: function () {}, + /** + * Update each frame + */ + update: function () { + this.updateTransform(); + }, + + /** + * @param {Function} cb + * @param {} context + */ + traverse: function (cb, context) {}, + + /** + * @protected + */ + attrKV: function (key, value) { + if (key === 'position' || key === 'scale' || key === 'origin') { + // Copy the array + if (value) { + var target = this[key]; + if (!target) { + target = this[key] = []; + } + target[0] = value[0]; + target[1] = value[1]; + } + } + else { + this[key] = value; + } + }, + + /** + * Hide the element + */ + hide: function () { + this.ignore = true; + this.__zr && this.__zr.refresh(); + }, + + /** + * Show the element + */ + show: function () { + this.ignore = false; + this.__zr && this.__zr.refresh(); + }, + + /** + * @param {string|Object} key + * @param {*} value + */ + attr: function (key, value) { + if (typeof key === 'string') { + this.attrKV(key, value); + } + else if (isObject$1(key)) { + for (var name in key) { + if (key.hasOwnProperty(name)) { + this.attrKV(name, key[name]); + } + } + } + + this.dirty(false); + + return this; + }, + + /** + * @param {module:zrender/graphic/Path} clipPath + */ + setClipPath: function (clipPath) { + var zr = this.__zr; + if (zr) { + clipPath.addSelfToZr(zr); + } + + // Remove previous clip path + if (this.clipPath && this.clipPath !== clipPath) { + this.removeClipPath(); + } + + this.clipPath = clipPath; + clipPath.__zr = zr; + clipPath.__clipTarget = this; + + this.dirty(false); + }, + + /** + */ + removeClipPath: function () { + var clipPath = this.clipPath; + if (clipPath) { + if (clipPath.__zr) { + clipPath.removeSelfFromZr(clipPath.__zr); + } + + clipPath.__zr = null; + clipPath.__clipTarget = null; + this.clipPath = null; + + this.dirty(false); + } + }, + + /** + * Add self from zrender instance. + * Not recursively because it will be invoked when element added to storage. + * @param {module:zrender/ZRender} zr + */ + addSelfToZr: function (zr) { + this.__zr = zr; + // 添加动画 + var animators = this.animators; + if (animators) { + for (var i = 0; i < animators.length; i++) { + zr.animation.addAnimator(animators[i]); + } + } + + if (this.clipPath) { + this.clipPath.addSelfToZr(zr); + } + }, + + /** + * Remove self from zrender instance. + * Not recursively because it will be invoked when element added to storage. + * @param {module:zrender/ZRender} zr + */ + removeSelfFromZr: function (zr) { + this.__zr = null; + // 移除动画 + var animators = this.animators; + if (animators) { + for (var i = 0; i < animators.length; i++) { + zr.animation.removeAnimator(animators[i]); + } + } + + if (this.clipPath) { + this.clipPath.removeSelfFromZr(zr); + } + } +}; + +mixin(Element, Animatable); +mixin(Element, Transformable); +mixin(Element, Eventful); + +/** + * @module echarts/core/BoundingRect + */ + +var v2ApplyTransform = applyTransform; +var mathMin = Math.min; +var mathMax = Math.max; + +/** + * @alias module:echarts/core/BoundingRect + */ +function BoundingRect(x, y, width, height) { + + if (width < 0) { + x = x + width; + width = -width; + } + if (height < 0) { + y = y + height; + height = -height; + } + + /** + * @type {number} + */ + this.x = x; + /** + * @type {number} + */ + this.y = y; + /** + * @type {number} + */ + this.width = width; + /** + * @type {number} + */ + this.height = height; +} + +BoundingRect.prototype = { + + constructor: BoundingRect, + + /** + * @param {module:echarts/core/BoundingRect} other + */ + union: function (other) { + var x = mathMin(other.x, this.x); + var y = mathMin(other.y, this.y); + + this.width = mathMax( + other.x + other.width, + this.x + this.width + ) - x; + this.height = mathMax( + other.y + other.height, + this.y + this.height + ) - y; + this.x = x; + this.y = y; + }, + + /** + * @param {Array.} m + * @methods + */ + applyTransform: (function () { + var lt = []; + var rb = []; + var lb = []; + var rt = []; + return function (m) { + // In case usage like this + // el.getBoundingRect().applyTransform(el.transform) + // And element has no transform + if (!m) { + return; + } + lt[0] = lb[0] = this.x; + lt[1] = rt[1] = this.y; + rb[0] = rt[0] = this.x + this.width; + rb[1] = lb[1] = this.y + this.height; + + v2ApplyTransform(lt, lt, m); + v2ApplyTransform(rb, rb, m); + v2ApplyTransform(lb, lb, m); + v2ApplyTransform(rt, rt, m); + + this.x = mathMin(lt[0], rb[0], lb[0], rt[0]); + this.y = mathMin(lt[1], rb[1], lb[1], rt[1]); + var maxX = mathMax(lt[0], rb[0], lb[0], rt[0]); + var maxY = mathMax(lt[1], rb[1], lb[1], rt[1]); + this.width = maxX - this.x; + this.height = maxY - this.y; + }; + })(), + + /** + * Calculate matrix of transforming from self to target rect + * @param {module:zrender/core/BoundingRect} b + * @return {Array.} + */ + calculateTransform: function (b) { + var a = this; + var sx = b.width / a.width; + var sy = b.height / a.height; + + var m = create$1(); + + // 矩阵右乘 + translate(m, m, [-a.x, -a.y]); + scale$1(m, m, [sx, sy]); + translate(m, m, [b.x, b.y]); + + return m; + }, + + /** + * @param {(module:echarts/core/BoundingRect|Object)} b + * @return {boolean} + */ + intersect: function (b) { + if (!b) { + return false; + } + + if (!(b instanceof BoundingRect)) { + // Normalize negative width/height. + b = BoundingRect.create(b); + } + + var a = this; + var ax0 = a.x; + var ax1 = a.x + a.width; + var ay0 = a.y; + var ay1 = a.y + a.height; + + var bx0 = b.x; + var bx1 = b.x + b.width; + var by0 = b.y; + var by1 = b.y + b.height; + + return !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0); + }, + + contain: function (x, y) { + var rect = this; + return x >= rect.x + && x <= (rect.x + rect.width) + && y >= rect.y + && y <= (rect.y + rect.height); + }, + + /** + * @return {module:echarts/core/BoundingRect} + */ + clone: function () { + return new BoundingRect(this.x, this.y, this.width, this.height); + }, + + /** + * Copy from another rect + */ + copy: function (other) { + this.x = other.x; + this.y = other.y; + this.width = other.width; + this.height = other.height; + }, + + plain: function () { + return { + x: this.x, + y: this.y, + width: this.width, + height: this.height + }; + } +}; + +/** + * @param {Object|module:zrender/core/BoundingRect} rect + * @param {number} rect.x + * @param {number} rect.y + * @param {number} rect.width + * @param {number} rect.height + * @return {module:zrender/core/BoundingRect} + */ +BoundingRect.create = function (rect) { + return new BoundingRect(rect.x, rect.y, rect.width, rect.height); +}; + +/** + * Group是一个容器,可以插入子节点,Group的变换也会被应用到子节点上 + * @module zrender/graphic/Group + * @example + * var Group = require('zrender/container/Group'); + * var Circle = require('zrender/graphic/shape/Circle'); + * var g = new Group(); + * g.position[0] = 100; + * g.position[1] = 100; + * g.add(new Circle({ + * style: { + * x: 100, + * y: 100, + * r: 20, + * } + * })); + * zr.add(g); + */ + +/** + * @alias module:zrender/graphic/Group + * @constructor + * @extends module:zrender/mixin/Transformable + * @extends module:zrender/mixin/Eventful + */ +var Group = function (opts) { + + opts = opts || {}; + + Element.call(this, opts); + + for (var key in opts) { + if (opts.hasOwnProperty(key)) { + this[key] = opts[key]; + } + } + + this._children = []; + + this.__storage = null; + + this.__dirty = true; +}; + +Group.prototype = { + + constructor: Group, + + isGroup: true, + + /** + * @type {string} + */ + type: 'group', + + /** + * 所有子孙元素是否响应鼠标事件 + * @name module:/zrender/container/Group#silent + * @type {boolean} + * @default false + */ + silent: false, + + /** + * @return {Array.} + */ + children: function () { + return this._children.slice(); + }, + + /** + * 获取指定 index 的儿子节点 + * @param {number} idx + * @return {module:zrender/Element} + */ + childAt: function (idx) { + return this._children[idx]; + }, + + /** + * 获取指定名字的儿子节点 + * @param {string} name + * @return {module:zrender/Element} + */ + childOfName: function (name) { + var children = this._children; + for (var i = 0; i < children.length; i++) { + if (children[i].name === name) { + return children[i]; + } + } + }, + + /** + * @return {number} + */ + childCount: function () { + return this._children.length; + }, + + /** + * 添加子节点到最后 + * @param {module:zrender/Element} child + */ + add: function (child) { + if (child && child !== this && child.parent !== this) { + + this._children.push(child); + + this._doAdd(child); + } + + return this; + }, + + /** + * 添加子节点在 nextSibling 之前 + * @param {module:zrender/Element} child + * @param {module:zrender/Element} nextSibling + */ + addBefore: function (child, nextSibling) { + if (child && child !== this && child.parent !== this + && nextSibling && nextSibling.parent === this) { + + var children = this._children; + var idx = children.indexOf(nextSibling); + + if (idx >= 0) { + children.splice(idx, 0, child); + this._doAdd(child); + } + } + + return this; + }, + + _doAdd: function (child) { + if (child.parent) { + child.parent.remove(child); + } + + child.parent = this; + + var storage = this.__storage; + var zr = this.__zr; + if (storage && storage !== child.__storage) { + + storage.addToStorage(child); + + if (child instanceof Group) { + child.addChildrenToStorage(storage); + } + } + + zr && zr.refresh(); + }, + + /** + * 移除子节点 + * @param {module:zrender/Element} child + */ + remove: function (child) { + var zr = this.__zr; + var storage = this.__storage; + var children = this._children; + + var idx = indexOf(children, child); + if (idx < 0) { + return this; + } + children.splice(idx, 1); + + child.parent = null; + + if (storage) { + + storage.delFromStorage(child); + + if (child instanceof Group) { + child.delChildrenFromStorage(storage); + } + } + + zr && zr.refresh(); + + return this; + }, + + /** + * 移除所有子节点 + */ + removeAll: function () { + var children = this._children; + var storage = this.__storage; + var child; + var i; + for (i = 0; i < children.length; i++) { + child = children[i]; + if (storage) { + storage.delFromStorage(child); + if (child instanceof Group) { + child.delChildrenFromStorage(storage); + } + } + child.parent = null; + } + children.length = 0; + + return this; + }, + + /** + * 遍历所有子节点 + * @param {Function} cb + * @param {} context + */ + eachChild: function (cb, context) { + var children = this._children; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + cb.call(context, child, i); + } + return this; + }, + + /** + * 深度优先遍历所有子孙节点 + * @param {Function} cb + * @param {} context + */ + traverse: function (cb, context) { + for (var i = 0; i < this._children.length; i++) { + var child = this._children[i]; + cb.call(context, child); + + if (child.type === 'group') { + child.traverse(cb, context); + } + } + return this; + }, + + addChildrenToStorage: function (storage) { + for (var i = 0; i < this._children.length; i++) { + var child = this._children[i]; + storage.addToStorage(child); + if (child instanceof Group) { + child.addChildrenToStorage(storage); + } + } + }, + + delChildrenFromStorage: function (storage) { + for (var i = 0; i < this._children.length; i++) { + var child = this._children[i]; + storage.delFromStorage(child); + if (child instanceof Group) { + child.delChildrenFromStorage(storage); + } + } + }, + + dirty: function () { + this.__dirty = true; + this.__zr && this.__zr.refresh(); + return this; + }, + + /** + * @return {module:zrender/core/BoundingRect} + */ + getBoundingRect: function (includeChildren) { + // TODO Caching + var rect = null; + var tmpRect = new BoundingRect(0, 0, 0, 0); + var children = includeChildren || this._children; + var tmpMat = []; + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (child.ignore || child.invisible) { + continue; + } + + var childRect = child.getBoundingRect(); + var transform = child.getLocalTransform(tmpMat); + // TODO + // The boundingRect cacluated by transforming original + // rect may be bigger than the actual bundingRect when rotation + // is used. (Consider a circle rotated aginst its center, where + // the actual boundingRect should be the same as that not be + // rotated.) But we can not find better approach to calculate + // actual boundingRect yet, considering performance. + if (transform) { + tmpRect.copy(childRect); + tmpRect.applyTransform(transform); + rect = rect || tmpRect.clone(); + rect.union(tmpRect); + } + else { + rect = rect || childRect.clone(); + rect.union(childRect); + } + } + return rect || tmpRect; + } +}; + +inherits(Group, Element); + +// https://github.com/mziccard/node-timsort +var DEFAULT_MIN_MERGE = 32; + +var DEFAULT_MIN_GALLOPING = 7; + +function minRunLength(n) { + var r = 0; + + while (n >= DEFAULT_MIN_MERGE) { + r |= n & 1; + n >>= 1; + } + + return n + r; +} + +function makeAscendingRun(array, lo, hi, compare) { + var runHi = lo + 1; + + if (runHi === hi) { + return 1; + } + + if (compare(array[runHi++], array[lo]) < 0) { + while (runHi < hi && compare(array[runHi], array[runHi - 1]) < 0) { + runHi++; + } + + reverseRun(array, lo, runHi); + } + else { + while (runHi < hi && compare(array[runHi], array[runHi - 1]) >= 0) { + runHi++; + } + } + + return runHi - lo; +} + +function reverseRun(array, lo, hi) { + hi--; + + while (lo < hi) { + var t = array[lo]; + array[lo++] = array[hi]; + array[hi--] = t; + } +} + +function binaryInsertionSort(array, lo, hi, start, compare) { + if (start === lo) { + start++; + } + + for (; start < hi; start++) { + var pivot = array[start]; + + var left = lo; + var right = start; + var mid; + + while (left < right) { + mid = left + right >>> 1; + + if (compare(pivot, array[mid]) < 0) { + right = mid; + } + else { + left = mid + 1; + } + } + + var n = start - left; + + switch (n) { + case 3: + array[left + 3] = array[left + 2]; + + case 2: + array[left + 2] = array[left + 1]; + + case 1: + array[left + 1] = array[left]; + break; + default: + while (n > 0) { + array[left + n] = array[left + n - 1]; + n--; + } + } + + array[left] = pivot; + } +} + +function gallopLeft(value, array, start, length, hint, compare) { + var lastOffset = 0; + var maxOffset = 0; + var offset = 1; + + if (compare(value, array[start + hint]) > 0) { + maxOffset = length - hint; + + while (offset < maxOffset && compare(value, array[start + hint + offset]) > 0) { + lastOffset = offset; + offset = (offset << 1) + 1; + + if (offset <= 0) { + offset = maxOffset; + } + } + + if (offset > maxOffset) { + offset = maxOffset; + } + + lastOffset += hint; + offset += hint; + } + else { + maxOffset = hint + 1; + while (offset < maxOffset && compare(value, array[start + hint - offset]) <= 0) { + lastOffset = offset; + offset = (offset << 1) + 1; + + if (offset <= 0) { + offset = maxOffset; + } + } + if (offset > maxOffset) { + offset = maxOffset; + } + + var tmp = lastOffset; + lastOffset = hint - offset; + offset = hint - tmp; + } + + lastOffset++; + while (lastOffset < offset) { + var m = lastOffset + (offset - lastOffset >>> 1); + + if (compare(value, array[start + m]) > 0) { + lastOffset = m + 1; + } + else { + offset = m; + } + } + return offset; +} + +function gallopRight(value, array, start, length, hint, compare) { + var lastOffset = 0; + var maxOffset = 0; + var offset = 1; + + if (compare(value, array[start + hint]) < 0) { + maxOffset = hint + 1; + + while (offset < maxOffset && compare(value, array[start + hint - offset]) < 0) { + lastOffset = offset; + offset = (offset << 1) + 1; + + if (offset <= 0) { + offset = maxOffset; + } + } + + if (offset > maxOffset) { + offset = maxOffset; + } + + var tmp = lastOffset; + lastOffset = hint - offset; + offset = hint - tmp; + } + else { + maxOffset = length - hint; + + while (offset < maxOffset && compare(value, array[start + hint + offset]) >= 0) { + lastOffset = offset; + offset = (offset << 1) + 1; + + if (offset <= 0) { + offset = maxOffset; + } + } + + if (offset > maxOffset) { + offset = maxOffset; + } + + lastOffset += hint; + offset += hint; + } + + lastOffset++; + + while (lastOffset < offset) { + var m = lastOffset + (offset - lastOffset >>> 1); + + if (compare(value, array[start + m]) < 0) { + offset = m; + } + else { + lastOffset = m + 1; + } + } + + return offset; +} + +function TimSort(array, compare) { + var minGallop = DEFAULT_MIN_GALLOPING; + var runStart; + var runLength; + var stackSize = 0; + + var tmp = []; + + runStart = []; + runLength = []; + + function pushRun(_runStart, _runLength) { + runStart[stackSize] = _runStart; + runLength[stackSize] = _runLength; + stackSize += 1; + } + + function mergeRuns() { + while (stackSize > 1) { + var n = stackSize - 2; + + if ( + (n >= 1 && runLength[n - 1] <= runLength[n] + runLength[n + 1]) + || (n >= 2 && runLength[n - 2] <= runLength[n] + runLength[n - 1]) + ) { + if (runLength[n - 1] < runLength[n + 1]) { + n--; + } + } + else if (runLength[n] > runLength[n + 1]) { + break; + } + mergeAt(n); + } + } + + function forceMergeRuns() { + while (stackSize > 1) { + var n = stackSize - 2; + + if (n > 0 && runLength[n - 1] < runLength[n + 1]) { + n--; + } + + mergeAt(n); + } + } + + function mergeAt(i) { + var start1 = runStart[i]; + var length1 = runLength[i]; + var start2 = runStart[i + 1]; + var length2 = runLength[i + 1]; + + runLength[i] = length1 + length2; + + if (i === stackSize - 3) { + runStart[i + 1] = runStart[i + 2]; + runLength[i + 1] = runLength[i + 2]; + } + + stackSize--; + + var k = gallopRight(array[start2], array, start1, length1, 0, compare); + start1 += k; + length1 -= k; + + if (length1 === 0) { + return; + } + + length2 = gallopLeft(array[start1 + length1 - 1], array, start2, length2, length2 - 1, compare); + + if (length2 === 0) { + return; + } + + if (length1 <= length2) { + mergeLow(start1, length1, start2, length2); + } + else { + mergeHigh(start1, length1, start2, length2); + } + } + + function mergeLow(start1, length1, start2, length2) { + var i = 0; + + for (i = 0; i < length1; i++) { + tmp[i] = array[start1 + i]; + } + + var cursor1 = 0; + var cursor2 = start2; + var dest = start1; + + array[dest++] = array[cursor2++]; + + if (--length2 === 0) { + for (i = 0; i < length1; i++) { + array[dest + i] = tmp[cursor1 + i]; + } + return; + } + + if (length1 === 1) { + for (i = 0; i < length2; i++) { + array[dest + i] = array[cursor2 + i]; + } + array[dest + length2] = tmp[cursor1]; + return; + } + + var _minGallop = minGallop; + var count1; + var count2; + var exit; + + while (1) { + count1 = 0; + count2 = 0; + exit = false; + + do { + if (compare(array[cursor2], tmp[cursor1]) < 0) { + array[dest++] = array[cursor2++]; + count2++; + count1 = 0; + + if (--length2 === 0) { + exit = true; + break; + } + } + else { + array[dest++] = tmp[cursor1++]; + count1++; + count2 = 0; + if (--length1 === 1) { + exit = true; + break; + } + } + } while ((count1 | count2) < _minGallop); + + if (exit) { + break; + } + + do { + count1 = gallopRight(array[cursor2], tmp, cursor1, length1, 0, compare); + + if (count1 !== 0) { + for (i = 0; i < count1; i++) { + array[dest + i] = tmp[cursor1 + i]; + } + + dest += count1; + cursor1 += count1; + length1 -= count1; + if (length1 <= 1) { + exit = true; + break; + } + } + + array[dest++] = array[cursor2++]; + + if (--length2 === 0) { + exit = true; + break; + } + + count2 = gallopLeft(tmp[cursor1], array, cursor2, length2, 0, compare); + + if (count2 !== 0) { + for (i = 0; i < count2; i++) { + array[dest + i] = array[cursor2 + i]; + } + + dest += count2; + cursor2 += count2; + length2 -= count2; + + if (length2 === 0) { + exit = true; + break; + } + } + array[dest++] = tmp[cursor1++]; + + if (--length1 === 1) { + exit = true; + break; + } + + _minGallop--; + } while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING); + + if (exit) { + break; + } + + if (_minGallop < 0) { + _minGallop = 0; + } + + _minGallop += 2; + } + + minGallop = _minGallop; + + minGallop < 1 && (minGallop = 1); + + if (length1 === 1) { + for (i = 0; i < length2; i++) { + array[dest + i] = array[cursor2 + i]; + } + array[dest + length2] = tmp[cursor1]; + } + else if (length1 === 0) { + throw new Error(); + // throw new Error('mergeLow preconditions were not respected'); + } + else { + for (i = 0; i < length1; i++) { + array[dest + i] = tmp[cursor1 + i]; + } + } + } + + function mergeHigh(start1, length1, start2, length2) { + var i = 0; + + for (i = 0; i < length2; i++) { + tmp[i] = array[start2 + i]; + } + + var cursor1 = start1 + length1 - 1; + var cursor2 = length2 - 1; + var dest = start2 + length2 - 1; + var customCursor = 0; + var customDest = 0; + + array[dest--] = array[cursor1--]; + + if (--length1 === 0) { + customCursor = dest - (length2 - 1); + + for (i = 0; i < length2; i++) { + array[customCursor + i] = tmp[i]; + } + + return; + } + + if (length2 === 1) { + dest -= length1; + cursor1 -= length1; + customDest = dest + 1; + customCursor = cursor1 + 1; + + for (i = length1 - 1; i >= 0; i--) { + array[customDest + i] = array[customCursor + i]; + } + + array[dest] = tmp[cursor2]; + return; + } + + var _minGallop = minGallop; + + while (true) { + var count1 = 0; + var count2 = 0; + var exit = false; + + do { + if (compare(tmp[cursor2], array[cursor1]) < 0) { + array[dest--] = array[cursor1--]; + count1++; + count2 = 0; + if (--length1 === 0) { + exit = true; + break; + } + } + else { + array[dest--] = tmp[cursor2--]; + count2++; + count1 = 0; + if (--length2 === 1) { + exit = true; + break; + } + } + } while ((count1 | count2) < _minGallop); + + if (exit) { + break; + } + + do { + count1 = length1 - gallopRight(tmp[cursor2], array, start1, length1, length1 - 1, compare); + + if (count1 !== 0) { + dest -= count1; + cursor1 -= count1; + length1 -= count1; + customDest = dest + 1; + customCursor = cursor1 + 1; + + for (i = count1 - 1; i >= 0; i--) { + array[customDest + i] = array[customCursor + i]; + } + + if (length1 === 0) { + exit = true; + break; + } + } + + array[dest--] = tmp[cursor2--]; + + if (--length2 === 1) { + exit = true; + break; + } + + count2 = length2 - gallopLeft(array[cursor1], tmp, 0, length2, length2 - 1, compare); + + if (count2 !== 0) { + dest -= count2; + cursor2 -= count2; + length2 -= count2; + customDest = dest + 1; + customCursor = cursor2 + 1; + + for (i = 0; i < count2; i++) { + array[customDest + i] = tmp[customCursor + i]; + } + + if (length2 <= 1) { + exit = true; + break; + } + } + + array[dest--] = array[cursor1--]; + + if (--length1 === 0) { + exit = true; + break; + } + + _minGallop--; + } while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING); + + if (exit) { + break; + } + + if (_minGallop < 0) { + _minGallop = 0; + } + + _minGallop += 2; + } + + minGallop = _minGallop; + + if (minGallop < 1) { + minGallop = 1; + } + + if (length2 === 1) { + dest -= length1; + cursor1 -= length1; + customDest = dest + 1; + customCursor = cursor1 + 1; + + for (i = length1 - 1; i >= 0; i--) { + array[customDest + i] = array[customCursor + i]; + } + + array[dest] = tmp[cursor2]; + } + else if (length2 === 0) { + throw new Error(); + // throw new Error('mergeHigh preconditions were not respected'); + } + else { + customCursor = dest - (length2 - 1); + for (i = 0; i < length2; i++) { + array[customCursor + i] = tmp[i]; + } + } + } + + this.mergeRuns = mergeRuns; + this.forceMergeRuns = forceMergeRuns; + this.pushRun = pushRun; +} + +function sort(array, compare, lo, hi) { + if (!lo) { + lo = 0; + } + if (!hi) { + hi = array.length; + } + + var remaining = hi - lo; + + if (remaining < 2) { + return; + } + + var runLength = 0; + + if (remaining < DEFAULT_MIN_MERGE) { + runLength = makeAscendingRun(array, lo, hi, compare); + binaryInsertionSort(array, lo, hi, lo + runLength, compare); + return; + } + + var ts = new TimSort(array, compare); + + var minRun = minRunLength(remaining); + + do { + runLength = makeAscendingRun(array, lo, hi, compare); + if (runLength < minRun) { + var force = remaining; + if (force > minRun) { + force = minRun; + } + + binaryInsertionSort(array, lo, lo + force, lo + runLength, compare); + runLength = force; + } + + ts.pushRun(lo, runLength); + ts.mergeRuns(); + + remaining -= runLength; + lo += runLength; + } while (remaining !== 0); + + ts.forceMergeRuns(); +} + +// Use timsort because in most case elements are partially sorted +// https://jsfiddle.net/pissang/jr4x7mdm/8/ +function shapeCompareFunc(a, b) { + if (a.zlevel === b.zlevel) { + if (a.z === b.z) { + // if (a.z2 === b.z2) { + // // FIXME Slow has renderidx compare + // // http://stackoverflow.com/questions/20883421/sorting-in-javascript-should-every-compare-function-have-a-return-0-statement + // // https://github.com/v8/v8/blob/47cce544a31ed5577ffe2963f67acb4144ee0232/src/js/array.js#L1012 + // return a.__renderidx - b.__renderidx; + // } + return a.z2 - b.z2; + } + return a.z - b.z; + } + return a.zlevel - b.zlevel; +} +/** + * 内容仓库 (M) + * @alias module:zrender/Storage + * @constructor + */ +var Storage = function () { // jshint ignore:line + this._roots = []; + + this._displayList = []; + + this._displayListLen = 0; +}; + +Storage.prototype = { + + constructor: Storage, + + /** + * @param {Function} cb + * + */ + traverse: function (cb, context) { + for (var i = 0; i < this._roots.length; i++) { + this._roots[i].traverse(cb, context); + } + }, + + /** + * 返回所有图形的绘制队列 + * @param {boolean} [update=false] 是否在返回前更新该数组 + * @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组, 在 update 为 true 的时候有效 + * + * 详见{@link module:zrender/graphic/Displayable.prototype.updateDisplayList} + * @return {Array.} + */ + getDisplayList: function (update, includeIgnore) { + includeIgnore = includeIgnore || false; + if (update) { + this.updateDisplayList(includeIgnore); + } + return this._displayList; + }, + + /** + * 更新图形的绘制队列。 + * 每次绘制前都会调用,该方法会先深度优先遍历整个树,更新所有Group和Shape的变换并且把所有可见的Shape保存到数组中, + * 最后根据绘制的优先级(zlevel > z > 插入顺序)排序得到绘制队列 + * @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组 + */ + updateDisplayList: function (includeIgnore) { + this._displayListLen = 0; + + var roots = this._roots; + var displayList = this._displayList; + for (var i = 0, len = roots.length; i < len; i++) { + this._updateAndAddDisplayable(roots[i], null, includeIgnore); + } + + displayList.length = this._displayListLen; + + env$1.canvasSupported && sort(displayList, shapeCompareFunc); + }, + + _updateAndAddDisplayable: function (el, clipPaths, includeIgnore) { + + if (el.ignore && !includeIgnore) { + return; + } + + el.beforeUpdate(); + + if (el.__dirty) { + + el.update(); + + } + + el.afterUpdate(); + + var userSetClipPath = el.clipPath; + if (userSetClipPath) { + + // FIXME 效率影响 + if (clipPaths) { + clipPaths = clipPaths.slice(); + } + else { + clipPaths = []; + } + + var currentClipPath = userSetClipPath; + var parentClipPath = el; + // Recursively add clip path + while (currentClipPath) { + // clipPath 的变换是基于使用这个 clipPath 的元素 + currentClipPath.parent = parentClipPath; + currentClipPath.updateTransform(); + + clipPaths.push(currentClipPath); + + parentClipPath = currentClipPath; + currentClipPath = currentClipPath.clipPath; + } + } + + if (el.isGroup) { + var children = el._children; + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + + // Force to mark as dirty if group is dirty + // FIXME __dirtyPath ? + if (el.__dirty) { + child.__dirty = true; + } + + this._updateAndAddDisplayable(child, clipPaths, includeIgnore); + } + + // Mark group clean here + el.__dirty = false; + + } + else { + el.__clipPaths = clipPaths; + + this._displayList[this._displayListLen++] = el; + } + }, + + /** + * 添加图形(Shape)或者组(Group)到根节点 + * @param {module:zrender/Element} el + */ + addRoot: function (el) { + if (el.__storage === this) { + return; + } + + if (el instanceof Group) { + el.addChildrenToStorage(this); + } + + this.addToStorage(el); + this._roots.push(el); + }, + + /** + * 删除指定的图形(Shape)或者组(Group) + * @param {string|Array.} [el] 如果为空清空整个Storage + */ + delRoot: function (el) { + if (el == null) { + // 不指定el清空 + for (var i = 0; i < this._roots.length; i++) { + var root = this._roots[i]; + if (root instanceof Group) { + root.delChildrenFromStorage(this); + } + } + + this._roots = []; + this._displayList = []; + this._displayListLen = 0; + + return; + } + + if (el instanceof Array) { + for (var i = 0, l = el.length; i < l; i++) { + this.delRoot(el[i]); + } + return; + } + + + var idx = indexOf(this._roots, el); + if (idx >= 0) { + this.delFromStorage(el); + this._roots.splice(idx, 1); + if (el instanceof Group) { + el.delChildrenFromStorage(this); + } + } + }, + + addToStorage: function (el) { + if (el) { + el.__storage = this; + el.dirty(false); + } + return this; + }, + + delFromStorage: function (el) { + if (el) { + el.__storage = null; + } + + return this; + }, + + /** + * 清空并且释放Storage + */ + dispose: function () { + this._renderList = + this._roots = null; + }, + + displayableSortFunc: shapeCompareFunc +}; + +var SHADOW_PROPS = { + 'shadowBlur': 1, + 'shadowOffsetX': 1, + 'shadowOffsetY': 1, + 'textShadowBlur': 1, + 'textShadowOffsetX': 1, + 'textShadowOffsetY': 1, + 'textBoxShadowBlur': 1, + 'textBoxShadowOffsetX': 1, + 'textBoxShadowOffsetY': 1 +}; + +var fixShadow = function (ctx, propName, value) { + if (SHADOW_PROPS.hasOwnProperty(propName)) { + return value *= ctx.dpr; + } + return value; +}; + +var ContextCachedBy = { + NONE: 0, + STYLE_BIND: 1, + PLAIN_TEXT: 2 +}; + +// Avoid confused with 0/false. +var WILL_BE_RESTORED = 9; + +var STYLE_COMMON_PROPS = [ + ['shadowBlur', 0], ['shadowOffsetX', 0], ['shadowOffsetY', 0], ['shadowColor', '#000'], + ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10] +]; + +// var SHADOW_PROPS = STYLE_COMMON_PROPS.slice(0, 4); +// var LINE_PROPS = STYLE_COMMON_PROPS.slice(4); + +var Style = function (opts) { + this.extendFrom(opts, false); +}; + +function createLinearGradient(ctx, obj, rect) { + var x = obj.x == null ? 0 : obj.x; + var x2 = obj.x2 == null ? 1 : obj.x2; + var y = obj.y == null ? 0 : obj.y; + var y2 = obj.y2 == null ? 0 : obj.y2; + + if (!obj.global) { + x = x * rect.width + rect.x; + x2 = x2 * rect.width + rect.x; + y = y * rect.height + rect.y; + y2 = y2 * rect.height + rect.y; + } + + // Fix NaN when rect is Infinity + x = isNaN(x) ? 0 : x; + x2 = isNaN(x2) ? 1 : x2; + y = isNaN(y) ? 0 : y; + y2 = isNaN(y2) ? 0 : y2; + + var canvasGradient = ctx.createLinearGradient(x, y, x2, y2); + + return canvasGradient; +} + +function createRadialGradient(ctx, obj, rect) { + var width = rect.width; + var height = rect.height; + var min = Math.min(width, height); + + var x = obj.x == null ? 0.5 : obj.x; + var y = obj.y == null ? 0.5 : obj.y; + var r = obj.r == null ? 0.5 : obj.r; + if (!obj.global) { + x = x * width + rect.x; + y = y * height + rect.y; + r = r * min; + } + + var canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r); + + return canvasGradient; +} + + +Style.prototype = { + + constructor: Style, + + /** + * @type {string} + */ + fill: '#000', + + /** + * @type {string} + */ + stroke: null, + + /** + * @type {number} + */ + opacity: 1, + + /** + * @type {number} + */ + fillOpacity: null, + + /** + * @type {number} + */ + strokeOpacity: null, + + /** + * `true` is not supported. + * `false`/`null`/`undefined` are the same. + * `false` is used to remove lineDash in some + * case that `null`/`undefined` can not be set. + * (e.g., emphasis.lineStyle in echarts) + * @type {Array.|boolean} + */ + lineDash: null, + + /** + * @type {number} + */ + lineDashOffset: 0, + + /** + * @type {number} + */ + shadowBlur: 0, + + /** + * @type {number} + */ + shadowOffsetX: 0, + + /** + * @type {number} + */ + shadowOffsetY: 0, + + /** + * @type {number} + */ + lineWidth: 1, + + /** + * If stroke ignore scale + * @type {Boolean} + */ + strokeNoScale: false, + + // Bounding rect text configuration + // Not affected by element transform + /** + * @type {string} + */ + text: null, + + /** + * If `fontSize` or `fontFamily` exists, `font` will be reset by + * `fontSize`, `fontStyle`, `fontWeight`, `fontFamily`. + * So do not visit it directly in upper application (like echarts), + * but use `contain/text#makeFont` instead. + * @type {string} + */ + font: null, + + /** + * The same as font. Use font please. + * @deprecated + * @type {string} + */ + textFont: null, + + /** + * It helps merging respectively, rather than parsing an entire font string. + * @type {string} + */ + fontStyle: null, + + /** + * It helps merging respectively, rather than parsing an entire font string. + * @type {string} + */ + fontWeight: null, + + /** + * It helps merging respectively, rather than parsing an entire font string. + * Should be 12 but not '12px'. + * @type {number} + */ + fontSize: null, + + /** + * It helps merging respectively, rather than parsing an entire font string. + * @type {string} + */ + fontFamily: null, + + /** + * Reserved for special functinality, like 'hr'. + * @type {string} + */ + textTag: null, + + /** + * @type {string} + */ + textFill: '#000', + + /** + * @type {string} + */ + textStroke: null, + + /** + * @type {number} + */ + textWidth: null, + + /** + * Only for textBackground. + * @type {number} + */ + textHeight: null, + + /** + * textStroke may be set as some color as a default + * value in upper applicaion, where the default value + * of textStrokeWidth should be 0 to make sure that + * user can choose to do not use text stroke. + * @type {number} + */ + textStrokeWidth: 0, + + /** + * @type {number} + */ + textLineHeight: null, + + /** + * 'inside', 'left', 'right', 'top', 'bottom' + * [x, y] + * Based on x, y of rect. + * @type {string|Array.} + * @default 'inside' + */ + textPosition: 'inside', + + /** + * If not specified, use the boundingRect of a `displayable`. + * @type {Object} + */ + textRect: null, + + /** + * [x, y] + * @type {Array.} + */ + textOffset: null, + + /** + * @type {string} + */ + textAlign: null, + + /** + * @type {string} + */ + textVerticalAlign: null, + + /** + * @type {number} + */ + textDistance: 5, + + /** + * @type {string} + */ + textShadowColor: 'transparent', + + /** + * @type {number} + */ + textShadowBlur: 0, + + /** + * @type {number} + */ + textShadowOffsetX: 0, + + /** + * @type {number} + */ + textShadowOffsetY: 0, + + /** + * @type {string} + */ + textBoxShadowColor: 'transparent', + + /** + * @type {number} + */ + textBoxShadowBlur: 0, + + /** + * @type {number} + */ + textBoxShadowOffsetX: 0, + + /** + * @type {number} + */ + textBoxShadowOffsetY: 0, + + /** + * Whether transform text. + * Only available in Path and Image element, + * where the text is called as `RectText`. + * @type {boolean} + */ + transformText: false, + + /** + * Text rotate around position of Path or Image. + * The origin of the rotation can be specified by `textOrigin`. + * Only available in Path and Image element, + * where the text is called as `RectText`. + */ + textRotation: 0, + + /** + * Text origin of text rotation. + * Useful in the case like label rotation of circular symbol. + * Only available in Path and Image element, where the text is called + * as `RectText` and the element is called as "host element". + * The value can be: + * + If specified as a coordinate like `[10, 40]`, it is the `[x, y]` + * base on the left-top corner of the rect of its host element. + * + If specified as a string `center`, it is the center of the rect of + * its host element. + * + By default, this origin is the `textPosition`. + * @type {string|Array.} + */ + textOrigin: null, + + /** + * @type {string} + */ + textBackgroundColor: null, + + /** + * @type {string} + */ + textBorderColor: null, + + /** + * @type {number} + */ + textBorderWidth: 0, + + /** + * @type {number} + */ + textBorderRadius: 0, + + /** + * Can be `2` or `[2, 4]` or `[2, 3, 4, 5]` + * @type {number|Array.} + */ + textPadding: null, + + /** + * Text styles for rich text. + * @type {Object} + */ + rich: null, + + /** + * {outerWidth, outerHeight, ellipsis, placeholder} + * @type {Object} + */ + truncate: null, + + /** + * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation + * @type {string} + */ + blend: null, + + /** + * @param {CanvasRenderingContext2D} ctx + */ + bind: function (ctx, el, prevEl) { + var style = this; + var prevStyle = prevEl && prevEl.style; + // If no prevStyle, it means first draw. + // Only apply cache if the last time cachced by this function. + var notCheckCache = !prevStyle || ctx.__attrCachedBy !== ContextCachedBy.STYLE_BIND; + + ctx.__attrCachedBy = ContextCachedBy.STYLE_BIND; + + for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) { + var prop = STYLE_COMMON_PROPS[i]; + var styleName = prop[0]; + + if (notCheckCache || style[styleName] !== prevStyle[styleName]) { + // FIXME Invalid property value will cause style leak from previous element. + ctx[styleName] = + fixShadow(ctx, styleName, style[styleName] || prop[1]); + } + } + + if ((notCheckCache || style.fill !== prevStyle.fill)) { + ctx.fillStyle = style.fill; + } + if ((notCheckCache || style.stroke !== prevStyle.stroke)) { + ctx.strokeStyle = style.stroke; + } + if ((notCheckCache || style.opacity !== prevStyle.opacity)) { + ctx.globalAlpha = style.opacity == null ? 1 : style.opacity; + } + + if ((notCheckCache || style.blend !== prevStyle.blend)) { + ctx.globalCompositeOperation = style.blend || 'source-over'; + } + if (this.hasStroke()) { + var lineWidth = style.lineWidth; + ctx.lineWidth = lineWidth / ( + (this.strokeNoScale && el && el.getLineScale) ? el.getLineScale() : 1 + ); + } + }, + + hasFill: function () { + var fill = this.fill; + return fill != null && fill !== 'none'; + }, + + hasStroke: function () { + var stroke = this.stroke; + return stroke != null && stroke !== 'none' && this.lineWidth > 0; + }, + + /** + * Extend from other style + * @param {zrender/graphic/Style} otherStyle + * @param {boolean} overwrite true: overwrirte any way. + * false: overwrite only when !target.hasOwnProperty + * others: overwrite when property is not null/undefined. + */ + extendFrom: function (otherStyle, overwrite) { + if (otherStyle) { + for (var name in otherStyle) { + if (otherStyle.hasOwnProperty(name) + && (overwrite === true + || ( + overwrite === false + ? !this.hasOwnProperty(name) + : otherStyle[name] != null + ) + ) + ) { + this[name] = otherStyle[name]; + } + } + } + }, + + /** + * Batch setting style with a given object + * @param {Object|string} obj + * @param {*} [obj] + */ + set: function (obj, value) { + if (typeof obj === 'string') { + this[obj] = value; + } + else { + this.extendFrom(obj, true); + } + }, + + /** + * Clone + * @return {zrender/graphic/Style} [description] + */ + clone: function () { + var newStyle = new this.constructor(); + newStyle.extendFrom(this, true); + return newStyle; + }, + + getGradient: function (ctx, obj, rect) { + var method = obj.type === 'radial' ? createRadialGradient : createLinearGradient; + var canvasGradient = method(ctx, obj, rect); + var colorStops = obj.colorStops; + for (var i = 0; i < colorStops.length; i++) { + canvasGradient.addColorStop( + colorStops[i].offset, colorStops[i].color + ); + } + return canvasGradient; + } + +}; + +var styleProto = Style.prototype; +for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) { + var prop = STYLE_COMMON_PROPS[i]; + if (!(prop[0] in styleProto)) { + styleProto[prop[0]] = prop[1]; + } +} + +// Provide for others +Style.getGradient = styleProto.getGradient; + +var Pattern = function (image, repeat) { + // Should do nothing more in this constructor. Because gradient can be + // declard by `color: {image: ...}`, where this constructor will not be called. + + this.image = image; + this.repeat = repeat; + + // Can be cloned + this.type = 'pattern'; +}; + +Pattern.prototype.getCanvasPattern = function (ctx) { + return ctx.createPattern(this.image, this.repeat || 'repeat'); +}; + +/** + * @module zrender/Layer + * @author pissang(https://www.github.com/pissang) + */ + +function returnFalse() { + return false; +} + +/** + * 创建dom + * + * @inner + * @param {string} id dom id 待用 + * @param {Painter} painter painter instance + * @param {number} number + */ +function createDom(id, painter, dpr) { + var newDom = createCanvas(); + var width = painter.getWidth(); + var height = painter.getHeight(); + + var newDomStyle = newDom.style; + if (newDomStyle) { // In node or some other non-browser environment + newDomStyle.position = 'absolute'; + newDomStyle.left = 0; + newDomStyle.top = 0; + newDomStyle.width = width + 'px'; + newDomStyle.height = height + 'px'; + + newDom.setAttribute('data-zr-dom-id', id); + } + + newDom.width = width * dpr; + newDom.height = height * dpr; + + return newDom; +} + +/** + * @alias module:zrender/Layer + * @constructor + * @extends module:zrender/mixin/Transformable + * @param {string} id + * @param {module:zrender/Painter} painter + * @param {number} [dpr] + */ +var Layer = function (id, painter, dpr) { + var dom; + dpr = dpr || devicePixelRatio; + if (typeof id === 'string') { + dom = createDom(id, painter, dpr); + } + // Not using isDom because in node it will return false + else if (isObject$1(id)) { + dom = id; + id = dom.id; + } + this.id = id; + this.dom = dom; + + var domStyle = dom.style; + if (domStyle) { // Not in node + dom.onselectstart = returnFalse; // 避免页面选中的尴尬 + domStyle['-webkit-user-select'] = 'none'; + domStyle['user-select'] = 'none'; + domStyle['-webkit-touch-callout'] = 'none'; + domStyle['-webkit-tap-highlight-color'] = 'rgba(0,0,0,0)'; + domStyle['padding'] = 0; // eslint-disable-line dot-notation + domStyle['margin'] = 0; // eslint-disable-line dot-notation + domStyle['border-width'] = 0; + } + + this.domBack = null; + this.ctxBack = null; + + this.painter = painter; + + this.config = null; + + // Configs + /** + * 每次清空画布的颜色 + * @type {string} + * @default 0 + */ + this.clearColor = 0; + /** + * 是否开启动态模糊 + * @type {boolean} + * @default false + */ + this.motionBlur = false; + /** + * 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显 + * @type {number} + * @default 0.7 + */ + this.lastFrameAlpha = 0.7; + + /** + * Layer dpr + * @type {number} + */ + this.dpr = dpr; +}; + +Layer.prototype = { + + constructor: Layer, + + __dirty: true, + + __used: false, + + __drawIndex: 0, + __startIndex: 0, + __endIndex: 0, + + incremental: false, + + getElementCount: function () { + return this.__endIndex - this.__startIndex; + }, + + initContext: function () { + this.ctx = this.dom.getContext('2d'); + this.ctx.dpr = this.dpr; + }, + + createBackBuffer: function () { + var dpr = this.dpr; + + this.domBack = createDom('back-' + this.id, this.painter, dpr); + this.ctxBack = this.domBack.getContext('2d'); + + if (dpr !== 1) { + this.ctxBack.scale(dpr, dpr); + } + }, + + /** + * @param {number} width + * @param {number} height + */ + resize: function (width, height) { + var dpr = this.dpr; + + var dom = this.dom; + var domStyle = dom.style; + var domBack = this.domBack; + + if (domStyle) { + domStyle.width = width + 'px'; + domStyle.height = height + 'px'; + } + + dom.width = width * dpr; + dom.height = height * dpr; + + if (domBack) { + domBack.width = width * dpr; + domBack.height = height * dpr; + + if (dpr !== 1) { + this.ctxBack.scale(dpr, dpr); + } + } + }, + + /** + * 清空该层画布 + * @param {boolean} [clearAll]=false Clear all with out motion blur + * @param {Color} [clearColor] + */ + clear: function (clearAll, clearColor) { + var dom = this.dom; + var ctx = this.ctx; + var width = dom.width; + var height = dom.height; + + var clearColor = clearColor || this.clearColor; + var haveMotionBLur = this.motionBlur && !clearAll; + var lastFrameAlpha = this.lastFrameAlpha; + + var dpr = this.dpr; + + if (haveMotionBLur) { + if (!this.domBack) { + this.createBackBuffer(); + } + + this.ctxBack.globalCompositeOperation = 'copy'; + this.ctxBack.drawImage( + dom, 0, 0, + width / dpr, + height / dpr + ); + } + + ctx.clearRect(0, 0, width, height); + if (clearColor && clearColor !== 'transparent') { + var clearColorGradientOrPattern; + // Gradient + if (clearColor.colorStops) { + // Cache canvas gradient + clearColorGradientOrPattern = clearColor.__canvasGradient || Style.getGradient(ctx, clearColor, { + x: 0, + y: 0, + width: width, + height: height + }); + + clearColor.__canvasGradient = clearColorGradientOrPattern; + } + // Pattern + else if (clearColor.image) { + clearColorGradientOrPattern = Pattern.prototype.getCanvasPattern.call(clearColor, ctx); + } + ctx.save(); + ctx.fillStyle = clearColorGradientOrPattern || clearColor; + ctx.fillRect(0, 0, width, height); + ctx.restore(); + } + + if (haveMotionBLur) { + var domBack = this.domBack; + ctx.save(); + ctx.globalAlpha = lastFrameAlpha; + ctx.drawImage(domBack, 0, 0, width, height); + ctx.restore(); + } + } +}; + +var requestAnimationFrame = ( + typeof window !== 'undefined' + && ( + (window.requestAnimationFrame && window.requestAnimationFrame.bind(window)) + // https://github.com/ecomfe/zrender/issues/189#issuecomment-224919809 + || (window.msRequestAnimationFrame && window.msRequestAnimationFrame.bind(window)) + || window.mozRequestAnimationFrame + || window.webkitRequestAnimationFrame + ) +) || function (func) { + setTimeout(func, 16); +}; + +var globalImageCache = new LRU(50); + +/** + * @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc + * @return {HTMLImageElement|HTMLCanvasElement|Canvas} image + */ +function findExistImage(newImageOrSrc) { + if (typeof newImageOrSrc === 'string') { + var cachedImgObj = globalImageCache.get(newImageOrSrc); + return cachedImgObj && cachedImgObj.image; + } + else { + return newImageOrSrc; + } +} + +/** + * Caution: User should cache loaded images, but not just count on LRU. + * Consider if required images more than LRU size, will dead loop occur? + * + * @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc + * @param {HTMLImageElement|HTMLCanvasElement|Canvas} image Existent image. + * @param {module:zrender/Element} [hostEl] For calling `dirty`. + * @param {Function} [cb] params: (image, cbPayload) + * @param {Object} [cbPayload] Payload on cb calling. + * @return {HTMLImageElement|HTMLCanvasElement|Canvas} image + */ +function createOrUpdateImage(newImageOrSrc, image, hostEl, cb, cbPayload) { + if (!newImageOrSrc) { + return image; + } + else if (typeof newImageOrSrc === 'string') { + + // Image should not be loaded repeatly. + if ((image && image.__zrImageSrc === newImageOrSrc) || !hostEl) { + return image; + } + + // Only when there is no existent image or existent image src + // is different, this method is responsible for load. + var cachedImgObj = globalImageCache.get(newImageOrSrc); + + var pendingWrap = {hostEl: hostEl, cb: cb, cbPayload: cbPayload}; + + if (cachedImgObj) { + image = cachedImgObj.image; + !isImageReady(image) && cachedImgObj.pending.push(pendingWrap); + } + else { + image = new Image(); + image.onload = image.onerror = imageOnLoad; + + globalImageCache.put( + newImageOrSrc, + image.__cachedImgObj = { + image: image, + pending: [pendingWrap] + } + ); + + image.src = image.__zrImageSrc = newImageOrSrc; + } + + return image; + } + // newImageOrSrc is an HTMLImageElement or HTMLCanvasElement or Canvas + else { + return newImageOrSrc; + } +} + +function imageOnLoad() { + var cachedImgObj = this.__cachedImgObj; + this.onload = this.onerror = this.__cachedImgObj = null; + + for (var i = 0; i < cachedImgObj.pending.length; i++) { + var pendingWrap = cachedImgObj.pending[i]; + var cb = pendingWrap.cb; + cb && cb(this, pendingWrap.cbPayload); + pendingWrap.hostEl.dirty(); + } + cachedImgObj.pending.length = 0; +} + +function isImageReady(image) { + return image && image.width && image.height; +} + +var textWidthCache = {}; +var textWidthCacheCounter = 0; + +var TEXT_CACHE_MAX = 5000; +var STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g; + +var DEFAULT_FONT$1 = '12px sans-serif'; + +// Avoid assign to an exported variable, for transforming to cjs. +var methods$1 = {}; + +function $override$1(name, fn) { + methods$1[name] = fn; +} + +/** + * @public + * @param {string} text + * @param {string} font + * @return {number} width + */ +function getWidth(text, font) { + font = font || DEFAULT_FONT$1; + var key = text + ':' + font; + if (textWidthCache[key]) { + return textWidthCache[key]; + } + + var textLines = (text + '').split('\n'); + var width = 0; + + for (var i = 0, l = textLines.length; i < l; i++) { + // textContain.measureText may be overrided in SVG or VML + width = Math.max(measureText(textLines[i], font).width, width); + } + + if (textWidthCacheCounter > TEXT_CACHE_MAX) { + textWidthCacheCounter = 0; + textWidthCache = {}; + } + textWidthCacheCounter++; + textWidthCache[key] = width; + + return width; +} + +/** + * @public + * @param {string} text + * @param {string} font + * @param {string} [textAlign='left'] + * @param {string} [textVerticalAlign='top'] + * @param {Array.} [textPadding] + * @param {Object} [rich] + * @param {Object} [truncate] + * @return {Object} {x, y, width, height, lineHeight} + */ +function getBoundingRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) { + return rich + ? getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) + : getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, truncate); +} + +function getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, truncate) { + var contentBlock = parsePlainText(text, font, textPadding, textLineHeight, truncate); + var outerWidth = getWidth(text, font); + if (textPadding) { + outerWidth += textPadding[1] + textPadding[3]; + } + var outerHeight = contentBlock.outerHeight; + + var x = adjustTextX(0, outerWidth, textAlign); + var y = adjustTextY(0, outerHeight, textVerticalAlign); + + var rect = new BoundingRect(x, y, outerWidth, outerHeight); + rect.lineHeight = contentBlock.lineHeight; + + return rect; +} + +function getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) { + var contentBlock = parseRichText(text, { + rich: rich, + truncate: truncate, + font: font, + textAlign: textAlign, + textPadding: textPadding, + textLineHeight: textLineHeight + }); + var outerWidth = contentBlock.outerWidth; + var outerHeight = contentBlock.outerHeight; + + var x = adjustTextX(0, outerWidth, textAlign); + var y = adjustTextY(0, outerHeight, textVerticalAlign); + + return new BoundingRect(x, y, outerWidth, outerHeight); +} + +/** + * @public + * @param {number} x + * @param {number} width + * @param {string} [textAlign='left'] + * @return {number} Adjusted x. + */ +function adjustTextX(x, width, textAlign) { + // FIXME Right to left language + if (textAlign === 'right') { + x -= width; + } + else if (textAlign === 'center') { + x -= width / 2; + } + return x; +} + +/** + * @public + * @param {number} y + * @param {number} height + * @param {string} [textVerticalAlign='top'] + * @return {number} Adjusted y. + */ +function adjustTextY(y, height, textVerticalAlign) { + if (textVerticalAlign === 'middle') { + y -= height / 2; + } + else if (textVerticalAlign === 'bottom') { + y -= height; + } + return y; +} + +/** + * Follow same interface to `Displayable.prototype.calculateTextPosition`. + * @public + * @param {Obejct} [out] Prepared out object. If not input, auto created in the method. + * @param {module:zrender/graphic/Style} style where `textPosition` and `textDistance` are visited. + * @param {Object} rect {x, y, width, height} Rect of the host elment, according to which the text positioned. + * @return {Object} The input `out`. Set: {x, y, textAlign, textVerticalAlign} + */ +function calculateTextPosition(out, style, rect) { + var textPosition = style.textPosition; + var distance = style.textDistance; + + var x = rect.x; + var y = rect.y; + distance = distance || 0; + + var height = rect.height; + var width = rect.width; + var halfHeight = height / 2; + + var textAlign = 'left'; + var textVerticalAlign = 'top'; + + switch (textPosition) { + case 'left': + x -= distance; + y += halfHeight; + textAlign = 'right'; + textVerticalAlign = 'middle'; + break; + case 'right': + x += distance + width; + y += halfHeight; + textVerticalAlign = 'middle'; + break; + case 'top': + x += width / 2; + y -= distance; + textAlign = 'center'; + textVerticalAlign = 'bottom'; + break; + case 'bottom': + x += width / 2; + y += height + distance; + textAlign = 'center'; + break; + case 'inside': + x += width / 2; + y += halfHeight; + textAlign = 'center'; + textVerticalAlign = 'middle'; + break; + case 'insideLeft': + x += distance; + y += halfHeight; + textVerticalAlign = 'middle'; + break; + case 'insideRight': + x += width - distance; + y += halfHeight; + textAlign = 'right'; + textVerticalAlign = 'middle'; + break; + case 'insideTop': + x += width / 2; + y += distance; + textAlign = 'center'; + break; + case 'insideBottom': + x += width / 2; + y += height - distance; + textAlign = 'center'; + textVerticalAlign = 'bottom'; + break; + case 'insideTopLeft': + x += distance; + y += distance; + break; + case 'insideTopRight': + x += width - distance; + y += distance; + textAlign = 'right'; + break; + case 'insideBottomLeft': + x += distance; + y += height - distance; + textVerticalAlign = 'bottom'; + break; + case 'insideBottomRight': + x += width - distance; + y += height - distance; + textAlign = 'right'; + textVerticalAlign = 'bottom'; + break; + } + + out = out || {}; + out.x = x; + out.y = y; + out.textAlign = textAlign; + out.textVerticalAlign = textVerticalAlign; + + return out; +} + +/** + * To be removed. But still do not remove in case that some one has imported it. + * @deprecated + * @public + * @param {stirng} textPosition + * @param {Object} rect {x, y, width, height} + * @param {number} distance + * @return {Object} {x, y, textAlign, textVerticalAlign} + */ + + +/** + * Show ellipsis if overflow. + * + * @public + * @param {string} text + * @param {string} containerWidth + * @param {string} font + * @param {number} [ellipsis='...'] + * @param {Object} [options] + * @param {number} [options.maxIterations=3] + * @param {number} [options.minChar=0] If truncate result are less + * then minChar, ellipsis will not show, which is + * better for user hint in some cases. + * @param {number} [options.placeholder=''] When all truncated, use the placeholder. + * @return {string} + */ +function truncateText(text, containerWidth, font, ellipsis, options) { + if (!containerWidth) { + return ''; + } + + var textLines = (text + '').split('\n'); + options = prepareTruncateOptions(containerWidth, font, ellipsis, options); + + // FIXME + // It is not appropriate that every line has '...' when truncate multiple lines. + for (var i = 0, len = textLines.length; i < len; i++) { + textLines[i] = truncateSingleLine(textLines[i], options); + } + + return textLines.join('\n'); +} + +function prepareTruncateOptions(containerWidth, font, ellipsis, options) { + options = extend({}, options); + + options.font = font; + var ellipsis = retrieve2(ellipsis, '...'); + options.maxIterations = retrieve2(options.maxIterations, 2); + var minChar = options.minChar = retrieve2(options.minChar, 0); + // FIXME + // Other languages? + options.cnCharWidth = getWidth('国', font); + // FIXME + // Consider proportional font? + var ascCharWidth = options.ascCharWidth = getWidth('a', font); + options.placeholder = retrieve2(options.placeholder, ''); + + // Example 1: minChar: 3, text: 'asdfzxcv', truncate result: 'asdf', but not: 'a...'. + // Example 2: minChar: 3, text: '维度', truncate result: '维', but not: '...'. + var contentWidth = containerWidth = Math.max(0, containerWidth - 1); // Reserve some gap. + for (var i = 0; i < minChar && contentWidth >= ascCharWidth; i++) { + contentWidth -= ascCharWidth; + } + + var ellipsisWidth = getWidth(ellipsis, font); + if (ellipsisWidth > contentWidth) { + ellipsis = ''; + ellipsisWidth = 0; + } + + contentWidth = containerWidth - ellipsisWidth; + + options.ellipsis = ellipsis; + options.ellipsisWidth = ellipsisWidth; + options.contentWidth = contentWidth; + options.containerWidth = containerWidth; + + return options; +} + +function truncateSingleLine(textLine, options) { + var containerWidth = options.containerWidth; + var font = options.font; + var contentWidth = options.contentWidth; + + if (!containerWidth) { + return ''; + } + + var lineWidth = getWidth(textLine, font); + + if (lineWidth <= containerWidth) { + return textLine; + } + + for (var j = 0; ; j++) { + if (lineWidth <= contentWidth || j >= options.maxIterations) { + textLine += options.ellipsis; + break; + } + + var subLength = j === 0 + ? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth) + : lineWidth > 0 + ? Math.floor(textLine.length * contentWidth / lineWidth) + : 0; + + textLine = textLine.substr(0, subLength); + lineWidth = getWidth(textLine, font); + } + + if (textLine === '') { + textLine = options.placeholder; + } + + return textLine; +} + +function estimateLength(text, contentWidth, ascCharWidth, cnCharWidth) { + var width = 0; + var i = 0; + for (var len = text.length; i < len && width < contentWidth; i++) { + var charCode = text.charCodeAt(i); + width += (0 <= charCode && charCode <= 127) ? ascCharWidth : cnCharWidth; + } + return i; +} + +/** + * @public + * @param {string} font + * @return {number} line height + */ +function getLineHeight(font) { + // FIXME A rough approach. + return getWidth('国', font); +} + +/** + * @public + * @param {string} text + * @param {string} font + * @return {Object} width + */ +function measureText(text, font) { + return methods$1.measureText(text, font); +} + +// Avoid assign to an exported variable, for transforming to cjs. +methods$1.measureText = function (text, font) { + var ctx = getContext(); + ctx.font = font || DEFAULT_FONT$1; + return ctx.measureText(text); +}; + +/** + * @public + * @param {string} text + * @param {string} font + * @param {Object} [truncate] + * @return {Object} block: {lineHeight, lines, height, outerHeight, canCacheByTextString} + * Notice: for performance, do not calculate outerWidth util needed. + * `canCacheByTextString` means the result `lines` is only determined by the input `text`. + * Thus we can simply comparing the `input` text to determin whether the result changed, + * without travel the result `lines`. + */ +function parsePlainText(text, font, padding, textLineHeight, truncate) { + text != null && (text += ''); + + var lineHeight = retrieve2(textLineHeight, getLineHeight(font)); + var lines = text ? text.split('\n') : []; + var height = lines.length * lineHeight; + var outerHeight = height; + var canCacheByTextString = true; + + if (padding) { + outerHeight += padding[0] + padding[2]; + } + + if (text && truncate) { + canCacheByTextString = false; + var truncOuterHeight = truncate.outerHeight; + var truncOuterWidth = truncate.outerWidth; + if (truncOuterHeight != null && outerHeight > truncOuterHeight) { + text = ''; + lines = []; + } + else if (truncOuterWidth != null) { + var options = prepareTruncateOptions( + truncOuterWidth - (padding ? padding[1] + padding[3] : 0), + font, + truncate.ellipsis, + {minChar: truncate.minChar, placeholder: truncate.placeholder} + ); + + // FIXME + // It is not appropriate that every line has '...' when truncate multiple lines. + for (var i = 0, len = lines.length; i < len; i++) { + lines[i] = truncateSingleLine(lines[i], options); + } + } + } + + return { + lines: lines, + height: height, + outerHeight: outerHeight, + lineHeight: lineHeight, + canCacheByTextString: canCacheByTextString + }; +} + +/** + * For example: 'some text {a|some text}other text{b|some text}xxx{c|}xxx' + * Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'. + * + * @public + * @param {string} text + * @param {Object} style + * @return {Object} block + * { + * width, + * height, + * lines: [{ + * lineHeight, + * width, + * tokens: [[{ + * styleName, + * text, + * width, // include textPadding + * height, // include textPadding + * textWidth, // pure text width + * textHeight, // pure text height + * lineHeihgt, + * font, + * textAlign, + * textVerticalAlign + * }], [...], ...] + * }, ...] + * } + * If styleName is undefined, it is plain text. + */ +function parseRichText(text, style) { + var contentBlock = {lines: [], width: 0, height: 0}; + + text != null && (text += ''); + if (!text) { + return contentBlock; + } + + var lastIndex = STYLE_REG.lastIndex = 0; + var result; + while ((result = STYLE_REG.exec(text)) != null) { + var matchedIndex = result.index; + if (matchedIndex > lastIndex) { + pushTokens(contentBlock, text.substring(lastIndex, matchedIndex)); + } + pushTokens(contentBlock, result[2], result[1]); + lastIndex = STYLE_REG.lastIndex; + } + + if (lastIndex < text.length) { + pushTokens(contentBlock, text.substring(lastIndex, text.length)); + } + + var lines = contentBlock.lines; + var contentHeight = 0; + var contentWidth = 0; + // For `textWidth: 100%` + var pendingList = []; + + var stlPadding = style.textPadding; + + var truncate = style.truncate; + var truncateWidth = truncate && truncate.outerWidth; + var truncateHeight = truncate && truncate.outerHeight; + if (stlPadding) { + truncateWidth != null && (truncateWidth -= stlPadding[1] + stlPadding[3]); + truncateHeight != null && (truncateHeight -= stlPadding[0] + stlPadding[2]); + } + + // Calculate layout info of tokens. + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var lineHeight = 0; + var lineWidth = 0; + + for (var j = 0; j < line.tokens.length; j++) { + var token = line.tokens[j]; + var tokenStyle = token.styleName && style.rich[token.styleName] || {}; + // textPadding should not inherit from style. + var textPadding = token.textPadding = tokenStyle.textPadding; + + // textFont has been asigned to font by `normalizeStyle`. + var font = token.font = tokenStyle.font || style.font; + + // textHeight can be used when textVerticalAlign is specified in token. + var tokenHeight = token.textHeight = retrieve2( + // textHeight should not be inherited, consider it can be specified + // as box height of the block. + tokenStyle.textHeight, getLineHeight(font) + ); + textPadding && (tokenHeight += textPadding[0] + textPadding[2]); + token.height = tokenHeight; + token.lineHeight = retrieve3( + tokenStyle.textLineHeight, style.textLineHeight, tokenHeight + ); + + token.textAlign = tokenStyle && tokenStyle.textAlign || style.textAlign; + token.textVerticalAlign = tokenStyle && tokenStyle.textVerticalAlign || 'middle'; + + if (truncateHeight != null && contentHeight + token.lineHeight > truncateHeight) { + return {lines: [], width: 0, height: 0}; + } + + token.textWidth = getWidth(token.text, font); + var tokenWidth = tokenStyle.textWidth; + var tokenWidthNotSpecified = tokenWidth == null || tokenWidth === 'auto'; + + // Percent width, can be `100%`, can be used in drawing separate + // line when box width is needed to be auto. + if (typeof tokenWidth === 'string' && tokenWidth.charAt(tokenWidth.length - 1) === '%') { + token.percentWidth = tokenWidth; + pendingList.push(token); + tokenWidth = 0; + // Do not truncate in this case, because there is no user case + // and it is too complicated. + } + else { + if (tokenWidthNotSpecified) { + tokenWidth = token.textWidth; + + // FIXME: If image is not loaded and textWidth is not specified, calling + // `getBoundingRect()` will not get correct result. + var textBackgroundColor = tokenStyle.textBackgroundColor; + var bgImg = textBackgroundColor && textBackgroundColor.image; + + // Use cases: + // (1) If image is not loaded, it will be loaded at render phase and call + // `dirty()` and `textBackgroundColor.image` will be replaced with the loaded + // image, and then the right size will be calculated here at the next tick. + // See `graphic/helper/text.js`. + // (2) If image loaded, and `textBackgroundColor.image` is image src string, + // use `imageHelper.findExistImage` to find cached image. + // `imageHelper.findExistImage` will always be called here before + // `imageHelper.createOrUpdateImage` in `graphic/helper/text.js#renderRichText` + // which ensures that image will not be rendered before correct size calcualted. + if (bgImg) { + bgImg = findExistImage(bgImg); + if (isImageReady(bgImg)) { + tokenWidth = Math.max(tokenWidth, bgImg.width * tokenHeight / bgImg.height); + } + } + } + + var paddingW = textPadding ? textPadding[1] + textPadding[3] : 0; + tokenWidth += paddingW; + + var remianTruncWidth = truncateWidth != null ? truncateWidth - lineWidth : null; + + if (remianTruncWidth != null && remianTruncWidth < tokenWidth) { + if (!tokenWidthNotSpecified || remianTruncWidth < paddingW) { + token.text = ''; + token.textWidth = tokenWidth = 0; + } + else { + token.text = truncateText( + token.text, remianTruncWidth - paddingW, font, truncate.ellipsis, + {minChar: truncate.minChar} + ); + token.textWidth = getWidth(token.text, font); + tokenWidth = token.textWidth + paddingW; + } + } + } + + lineWidth += (token.width = tokenWidth); + tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight)); + } + + line.width = lineWidth; + line.lineHeight = lineHeight; + contentHeight += lineHeight; + contentWidth = Math.max(contentWidth, lineWidth); + } + + contentBlock.outerWidth = contentBlock.width = retrieve2(style.textWidth, contentWidth); + contentBlock.outerHeight = contentBlock.height = retrieve2(style.textHeight, contentHeight); + + if (stlPadding) { + contentBlock.outerWidth += stlPadding[1] + stlPadding[3]; + contentBlock.outerHeight += stlPadding[0] + stlPadding[2]; + } + + for (var i = 0; i < pendingList.length; i++) { + var token = pendingList[i]; + var percentWidth = token.percentWidth; + // Should not base on outerWidth, because token can not be placed out of padding. + token.width = parseInt(percentWidth, 10) / 100 * contentWidth; + } + + return contentBlock; +} + +function pushTokens(block, str, styleName) { + var isEmptyStr = str === ''; + var strs = str.split('\n'); + var lines = block.lines; + + for (var i = 0; i < strs.length; i++) { + var text = strs[i]; + var token = { + styleName: styleName, + text: text, + isLineHolder: !text && !isEmptyStr + }; + + // The first token should be appended to the last line. + if (!i) { + var tokens = (lines[lines.length - 1] || (lines[0] = {tokens: []})).tokens; + + // Consider cases: + // (1) ''.split('\n') => ['', '\n', ''], the '' at the first item + // (which is a placeholder) should be replaced by new token. + // (2) A image backage, where token likes {a|}. + // (3) A redundant '' will affect textAlign in line. + // (4) tokens with the same tplName should not be merged, because + // they should be displayed in different box (with border and padding). + var tokensLen = tokens.length; + (tokensLen === 1 && tokens[0].isLineHolder) + ? (tokens[0] = token) + // Consider text is '', only insert when it is the "lineHolder" or + // "emptyStr". Otherwise a redundant '' will affect textAlign in line. + : ((text || !tokensLen || isEmptyStr) && tokens.push(token)); + } + // Other tokens always start a new line. + else { + // If there is '', insert it as a placeholder. + lines.push({tokens: [token]}); + } + } +} + +function makeFont(style) { + // FIXME in node-canvas fontWeight is before fontStyle + // Use `fontSize` `fontFamily` to check whether font properties are defined. + var font = (style.fontSize || style.fontFamily) && [ + style.fontStyle, + style.fontWeight, + (style.fontSize || 12) + 'px', + // If font properties are defined, `fontFamily` should not be ignored. + style.fontFamily || 'sans-serif' + ].join(' '); + return font && trim(font) || style.textFont || style.font; +} + +/** + * @param {Object} ctx + * @param {Object} shape + * @param {number} shape.x + * @param {number} shape.y + * @param {number} shape.width + * @param {number} shape.height + * @param {number} shape.r + */ +function buildPath(ctx, shape) { + var x = shape.x; + var y = shape.y; + var width = shape.width; + var height = shape.height; + var r = shape.r; + var r1; + var r2; + var r3; + var r4; + + // Convert width and height to positive for better borderRadius + if (width < 0) { + x = x + width; + width = -width; + } + if (height < 0) { + y = y + height; + height = -height; + } + + if (typeof r === 'number') { + r1 = r2 = r3 = r4 = r; + } + else if (r instanceof Array) { + if (r.length === 1) { + r1 = r2 = r3 = r4 = r[0]; + } + else if (r.length === 2) { + r1 = r3 = r[0]; + r2 = r4 = r[1]; + } + else if (r.length === 3) { + r1 = r[0]; + r2 = r4 = r[1]; + r3 = r[2]; + } + else { + r1 = r[0]; + r2 = r[1]; + r3 = r[2]; + r4 = r[3]; + } + } + else { + r1 = r2 = r3 = r4 = 0; + } + + var total; + if (r1 + r2 > width) { + total = r1 + r2; + r1 *= width / total; + r2 *= width / total; + } + if (r3 + r4 > width) { + total = r3 + r4; + r3 *= width / total; + r4 *= width / total; + } + if (r2 + r3 > height) { + total = r2 + r3; + r2 *= height / total; + r3 *= height / total; + } + if (r1 + r4 > height) { + total = r1 + r4; + r1 *= height / total; + r4 *= height / total; + } + ctx.moveTo(x + r1, y); + ctx.lineTo(x + width - r2, y); + r2 !== 0 && ctx.arc(x + width - r2, y + r2, r2, -Math.PI / 2, 0); + ctx.lineTo(x + width, y + height - r3); + r3 !== 0 && ctx.arc(x + width - r3, y + height - r3, r3, 0, Math.PI / 2); + ctx.lineTo(x + r4, y + height); + r4 !== 0 && ctx.arc(x + r4, y + height - r4, r4, Math.PI / 2, Math.PI); + ctx.lineTo(x, y + r1); + r1 !== 0 && ctx.arc(x + r1, y + r1, r1, Math.PI, Math.PI * 1.5); +} + +var DEFAULT_FONT = DEFAULT_FONT$1; + +// TODO: Have not support 'start', 'end' yet. +var VALID_TEXT_ALIGN = {left: 1, right: 1, center: 1}; +var VALID_TEXT_VERTICAL_ALIGN = {top: 1, bottom: 1, middle: 1}; +// Different from `STYLE_COMMON_PROPS` of `graphic/Style`, +// the default value of shadowColor is `'transparent'`. +var SHADOW_STYLE_COMMON_PROPS = [ + ['textShadowBlur', 'shadowBlur', 0], + ['textShadowOffsetX', 'shadowOffsetX', 0], + ['textShadowOffsetY', 'shadowOffsetY', 0], + ['textShadowColor', 'shadowColor', 'transparent'] +]; +var _tmpTextPositionResult = {}; +var _tmpBoxPositionResult = {}; + +/** + * @param {module:zrender/graphic/Style} style + * @return {module:zrender/graphic/Style} The input style. + */ +function normalizeTextStyle(style) { + normalizeStyle(style); + each$1(style.rich, normalizeStyle); + return style; +} + +function normalizeStyle(style) { + if (style) { + + style.font = makeFont(style); + + var textAlign = style.textAlign; + textAlign === 'middle' && (textAlign = 'center'); + style.textAlign = ( + textAlign == null || VALID_TEXT_ALIGN[textAlign] + ) ? textAlign : 'left'; + + // Compatible with textBaseline. + var textVerticalAlign = style.textVerticalAlign || style.textBaseline; + textVerticalAlign === 'center' && (textVerticalAlign = 'middle'); + style.textVerticalAlign = ( + textVerticalAlign == null || VALID_TEXT_VERTICAL_ALIGN[textVerticalAlign] + ) ? textVerticalAlign : 'top'; + + var textPadding = style.textPadding; + if (textPadding) { + style.textPadding = normalizeCssArray(style.textPadding); + } + } +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {string} text + * @param {module:zrender/graphic/Style} style + * @param {Object|boolean} [rect] {x, y, width, height} + * If set false, rect text is not used. + * @param {Element|module:zrender/graphic/helper/constant.WILL_BE_RESTORED} [prevEl] For ctx prop cache. + */ +function renderText(hostEl, ctx, text, style, rect, prevEl) { + style.rich + ? renderRichText(hostEl, ctx, text, style, rect, prevEl) + : renderPlainText(hostEl, ctx, text, style, rect, prevEl); +} + +// Avoid setting to ctx according to prevEl if possible for +// performance in scenarios of large amount text. +function renderPlainText(hostEl, ctx, text, style, rect, prevEl) { + 'use strict'; + + var needDrawBg = needDrawBackground(style); + + var prevStyle; + var checkCache = false; + var cachedByMe = ctx.__attrCachedBy === ContextCachedBy.PLAIN_TEXT; + + // Only take and check cache for `Text` el, but not RectText. + if (prevEl !== WILL_BE_RESTORED) { + if (prevEl) { + prevStyle = prevEl.style; + checkCache = !needDrawBg && cachedByMe && prevStyle; + } + + // Prevent from using cache in `Style::bind`, because of the case: + // ctx property is modified by other properties than `Style::bind` + // used, and Style::bind is called next. + ctx.__attrCachedBy = needDrawBg ? ContextCachedBy.NONE : ContextCachedBy.PLAIN_TEXT; + } + // Since this will be restored, prevent from using these props to check cache in the next + // entering of this method. But do not need to clear other cache like `Style::bind`. + else if (cachedByMe) { + ctx.__attrCachedBy = ContextCachedBy.NONE; + } + + var styleFont = style.font || DEFAULT_FONT; + // PENDING + // Only `Text` el set `font` and keep it (`RectText` will restore). So theoretically + // we can make font cache on ctx, which can cache for text el that are discontinuous. + // But layer save/restore needed to be considered. + // if (styleFont !== ctx.__fontCache) { + // ctx.font = styleFont; + // if (prevEl !== WILL_BE_RESTORED) { + // ctx.__fontCache = styleFont; + // } + // } + if (!checkCache || styleFont !== (prevStyle.font || DEFAULT_FONT)) { + ctx.font = styleFont; + } + + // Use the final font from context-2d, because the final + // font might not be the style.font when it is illegal. + // But get `ctx.font` might be time consuming. + var computedFont = hostEl.__computedFont; + if (hostEl.__styleFont !== styleFont) { + hostEl.__styleFont = styleFont; + computedFont = hostEl.__computedFont = ctx.font; + } + + var textPadding = style.textPadding; + var textLineHeight = style.textLineHeight; + + var contentBlock = hostEl.__textCotentBlock; + if (!contentBlock || hostEl.__dirtyText) { + contentBlock = hostEl.__textCotentBlock = parsePlainText( + text, computedFont, textPadding, textLineHeight, style.truncate + ); + } + + var outerHeight = contentBlock.outerHeight; + + var textLines = contentBlock.lines; + var lineHeight = contentBlock.lineHeight; + + var boxPos = getBoxPosition(_tmpBoxPositionResult, hostEl, style, rect); + var baseX = boxPos.baseX; + var baseY = boxPos.baseY; + var textAlign = boxPos.textAlign || 'left'; + var textVerticalAlign = boxPos.textVerticalAlign; + + // Origin of textRotation should be the base point of text drawing. + applyTextRotation(ctx, style, rect, baseX, baseY); + + var boxY = adjustTextY(baseY, outerHeight, textVerticalAlign); + var textX = baseX; + var textY = boxY; + + if (needDrawBg || textPadding) { + // Consider performance, do not call getTextWidth util necessary. + var textWidth = getWidth(text, computedFont); + var outerWidth = textWidth; + textPadding && (outerWidth += textPadding[1] + textPadding[3]); + var boxX = adjustTextX(baseX, outerWidth, textAlign); + + needDrawBg && drawBackground(hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight); + + if (textPadding) { + textX = getTextXForPadding(baseX, textAlign, textPadding); + textY += textPadding[0]; + } + } + + // Always set textAlign and textBase line, because it is difficute to calculate + // textAlign from prevEl, and we dont sure whether textAlign will be reset if + // font set happened. + ctx.textAlign = textAlign; + // Force baseline to be "middle". Otherwise, if using "top", the + // text will offset downward a little bit in font "Microsoft YaHei". + ctx.textBaseline = 'middle'; + // Set text opacity + ctx.globalAlpha = style.opacity || 1; + + // Always set shadowBlur and shadowOffset to avoid leak from displayable. + for (var i = 0; i < SHADOW_STYLE_COMMON_PROPS.length; i++) { + var propItem = SHADOW_STYLE_COMMON_PROPS[i]; + var styleProp = propItem[0]; + var ctxProp = propItem[1]; + var val = style[styleProp]; + if (!checkCache || val !== prevStyle[styleProp]) { + ctx[ctxProp] = fixShadow(ctx, ctxProp, val || propItem[2]); + } + } + + // `textBaseline` is set as 'middle'. + textY += lineHeight / 2; + + var textStrokeWidth = style.textStrokeWidth; + var textStrokeWidthPrev = checkCache ? prevStyle.textStrokeWidth : null; + var strokeWidthChanged = !checkCache || textStrokeWidth !== textStrokeWidthPrev; + var strokeChanged = !checkCache || strokeWidthChanged || style.textStroke !== prevStyle.textStroke; + var textStroke = getStroke(style.textStroke, textStrokeWidth); + var textFill = getFill(style.textFill); + + if (textStroke) { + if (strokeWidthChanged) { + ctx.lineWidth = textStrokeWidth; + } + if (strokeChanged) { + ctx.strokeStyle = textStroke; + } + } + if (textFill) { + if (!checkCache || style.textFill !== prevStyle.textFill) { + ctx.fillStyle = textFill; + } + } + + // Optimize simply, in most cases only one line exists. + if (textLines.length === 1) { + // Fill after stroke so the outline will not cover the main part. + textStroke && ctx.strokeText(textLines[0], textX, textY); + textFill && ctx.fillText(textLines[0], textX, textY); + } + else { + for (var i = 0; i < textLines.length; i++) { + // Fill after stroke so the outline will not cover the main part. + textStroke && ctx.strokeText(textLines[i], textX, textY); + textFill && ctx.fillText(textLines[i], textX, textY); + textY += lineHeight; + } + } +} + +function renderRichText(hostEl, ctx, text, style, rect, prevEl) { + // Do not do cache for rich text because of the complexity. + // But `RectText` this will be restored, do not need to clear other cache like `Style::bind`. + if (prevEl !== WILL_BE_RESTORED) { + ctx.__attrCachedBy = ContextCachedBy.NONE; + } + + var contentBlock = hostEl.__textCotentBlock; + + if (!contentBlock || hostEl.__dirtyText) { + contentBlock = hostEl.__textCotentBlock = parseRichText(text, style); + } + + drawRichText(hostEl, ctx, contentBlock, style, rect); +} + +function drawRichText(hostEl, ctx, contentBlock, style, rect) { + var contentWidth = contentBlock.width; + var outerWidth = contentBlock.outerWidth; + var outerHeight = contentBlock.outerHeight; + var textPadding = style.textPadding; + + var boxPos = getBoxPosition(_tmpBoxPositionResult, hostEl, style, rect); + var baseX = boxPos.baseX; + var baseY = boxPos.baseY; + var textAlign = boxPos.textAlign; + var textVerticalAlign = boxPos.textVerticalAlign; + + // Origin of textRotation should be the base point of text drawing. + applyTextRotation(ctx, style, rect, baseX, baseY); + + var boxX = adjustTextX(baseX, outerWidth, textAlign); + var boxY = adjustTextY(baseY, outerHeight, textVerticalAlign); + var xLeft = boxX; + var lineTop = boxY; + if (textPadding) { + xLeft += textPadding[3]; + lineTop += textPadding[0]; + } + var xRight = xLeft + contentWidth; + + needDrawBackground(style) && drawBackground( + hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight + ); + + for (var i = 0; i < contentBlock.lines.length; i++) { + var line = contentBlock.lines[i]; + var tokens = line.tokens; + var tokenCount = tokens.length; + var lineHeight = line.lineHeight; + var usedWidth = line.width; + + var leftIndex = 0; + var lineXLeft = xLeft; + var lineXRight = xRight; + var rightIndex = tokenCount - 1; + var token; + + while ( + leftIndex < tokenCount + && (token = tokens[leftIndex], !token.textAlign || token.textAlign === 'left') + ) { + placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft, 'left'); + usedWidth -= token.width; + lineXLeft += token.width; + leftIndex++; + } + + while ( + rightIndex >= 0 + && (token = tokens[rightIndex], token.textAlign === 'right') + ) { + placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXRight, 'right'); + usedWidth -= token.width; + lineXRight -= token.width; + rightIndex--; + } + + // The other tokens are placed as textAlign 'center' if there is enough space. + lineXLeft += (contentWidth - (lineXLeft - xLeft) - (xRight - lineXRight) - usedWidth) / 2; + while (leftIndex <= rightIndex) { + token = tokens[leftIndex]; + // Consider width specified by user, use 'center' rather than 'left'. + placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft + token.width / 2, 'center'); + lineXLeft += token.width; + leftIndex++; + } + + lineTop += lineHeight; + } +} + +function applyTextRotation(ctx, style, rect, x, y) { + // textRotation only apply in RectText. + if (rect && style.textRotation) { + var origin = style.textOrigin; + if (origin === 'center') { + x = rect.width / 2 + rect.x; + y = rect.height / 2 + rect.y; + } + else if (origin) { + x = origin[0] + rect.x; + y = origin[1] + rect.y; + } + + ctx.translate(x, y); + // Positive: anticlockwise + ctx.rotate(-style.textRotation); + ctx.translate(-x, -y); + } +} + +function placeToken(hostEl, ctx, token, style, lineHeight, lineTop, x, textAlign) { + var tokenStyle = style.rich[token.styleName] || {}; + tokenStyle.text = token.text; + + // 'ctx.textBaseline' is always set as 'middle', for sake of + // the bias of "Microsoft YaHei". + var textVerticalAlign = token.textVerticalAlign; + var y = lineTop + lineHeight / 2; + if (textVerticalAlign === 'top') { + y = lineTop + token.height / 2; + } + else if (textVerticalAlign === 'bottom') { + y = lineTop + lineHeight - token.height / 2; + } + + !token.isLineHolder && needDrawBackground(tokenStyle) && drawBackground( + hostEl, + ctx, + tokenStyle, + textAlign === 'right' + ? x - token.width + : textAlign === 'center' + ? x - token.width / 2 + : x, + y - token.height / 2, + token.width, + token.height + ); + + var textPadding = token.textPadding; + if (textPadding) { + x = getTextXForPadding(x, textAlign, textPadding); + y -= token.height / 2 - textPadding[2] - token.textHeight / 2; + } + + setCtx(ctx, 'shadowBlur', retrieve3(tokenStyle.textShadowBlur, style.textShadowBlur, 0)); + setCtx(ctx, 'shadowColor', tokenStyle.textShadowColor || style.textShadowColor || 'transparent'); + setCtx(ctx, 'shadowOffsetX', retrieve3(tokenStyle.textShadowOffsetX, style.textShadowOffsetX, 0)); + setCtx(ctx, 'shadowOffsetY', retrieve3(tokenStyle.textShadowOffsetY, style.textShadowOffsetY, 0)); + + setCtx(ctx, 'textAlign', textAlign); + // Force baseline to be "middle". Otherwise, if using "top", the + // text will offset downward a little bit in font "Microsoft YaHei". + setCtx(ctx, 'textBaseline', 'middle'); + + setCtx(ctx, 'font', token.font || DEFAULT_FONT); + + var textStroke = getStroke(tokenStyle.textStroke || style.textStroke, textStrokeWidth); + var textFill = getFill(tokenStyle.textFill || style.textFill); + var textStrokeWidth = retrieve2(tokenStyle.textStrokeWidth, style.textStrokeWidth); + + // Fill after stroke so the outline will not cover the main part. + if (textStroke) { + setCtx(ctx, 'lineWidth', textStrokeWidth); + setCtx(ctx, 'strokeStyle', textStroke); + ctx.strokeText(token.text, x, y); + } + if (textFill) { + setCtx(ctx, 'fillStyle', textFill); + ctx.fillText(token.text, x, y); + } +} + +function needDrawBackground(style) { + return !!( + style.textBackgroundColor + || (style.textBorderWidth && style.textBorderColor) + ); +} + +// style: {textBackgroundColor, textBorderWidth, textBorderColor, textBorderRadius, text} +// shape: {x, y, width, height} +function drawBackground(hostEl, ctx, style, x, y, width, height) { + var textBackgroundColor = style.textBackgroundColor; + var textBorderWidth = style.textBorderWidth; + var textBorderColor = style.textBorderColor; + var isPlainBg = isString(textBackgroundColor); + + setCtx(ctx, 'shadowBlur', style.textBoxShadowBlur || 0); + setCtx(ctx, 'shadowColor', style.textBoxShadowColor || 'transparent'); + setCtx(ctx, 'shadowOffsetX', style.textBoxShadowOffsetX || 0); + setCtx(ctx, 'shadowOffsetY', style.textBoxShadowOffsetY || 0); + + if (isPlainBg || (textBorderWidth && textBorderColor)) { + ctx.beginPath(); + var textBorderRadius = style.textBorderRadius; + if (!textBorderRadius) { + ctx.rect(x, y, width, height); + } + else { + buildPath(ctx, { + x: x, y: y, width: width, height: height, r: textBorderRadius + }); + } + ctx.closePath(); + } + + if (isPlainBg) { + setCtx(ctx, 'fillStyle', textBackgroundColor); + + if (style.fillOpacity != null) { + var originalGlobalAlpha = ctx.globalAlpha; + ctx.globalAlpha = style.fillOpacity * style.opacity; + ctx.fill(); + ctx.globalAlpha = originalGlobalAlpha; + } + else { + ctx.fill(); + } + } + else if (isObject$1(textBackgroundColor)) { + var image = textBackgroundColor.image; + + image = createOrUpdateImage( + image, null, hostEl, onBgImageLoaded, textBackgroundColor + ); + if (image && isImageReady(image)) { + ctx.drawImage(image, x, y, width, height); + } + } + + if (textBorderWidth && textBorderColor) { + setCtx(ctx, 'lineWidth', textBorderWidth); + setCtx(ctx, 'strokeStyle', textBorderColor); + + if (style.strokeOpacity != null) { + var originalGlobalAlpha = ctx.globalAlpha; + ctx.globalAlpha = style.strokeOpacity * style.opacity; + ctx.stroke(); + ctx.globalAlpha = originalGlobalAlpha; + } + else { + ctx.stroke(); + } + } +} + +function onBgImageLoaded(image, textBackgroundColor) { + // Replace image, so that `contain/text.js#parseRichText` + // will get correct result in next tick. + textBackgroundColor.image = image; +} + +function getBoxPosition(out, hostEl, style, rect) { + var baseX = style.x || 0; + var baseY = style.y || 0; + var textAlign = style.textAlign; + var textVerticalAlign = style.textVerticalAlign; + + // Text position represented by coord + if (rect) { + var textPosition = style.textPosition; + if (textPosition instanceof Array) { + // Percent + baseX = rect.x + parsePercent(textPosition[0], rect.width); + baseY = rect.y + parsePercent(textPosition[1], rect.height); + } + else { + var res = (hostEl && hostEl.calculateTextPosition) + ? hostEl.calculateTextPosition(_tmpTextPositionResult, style, rect) + : calculateTextPosition(_tmpTextPositionResult, style, rect); + baseX = res.x; + baseY = res.y; + // Default align and baseline when has textPosition + textAlign = textAlign || res.textAlign; + textVerticalAlign = textVerticalAlign || res.textVerticalAlign; + } + + // textOffset is only support in RectText, otherwise + // we have to adjust boundingRect for textOffset. + var textOffset = style.textOffset; + if (textOffset) { + baseX += textOffset[0]; + baseY += textOffset[1]; + } + } + + out = out || {}; + out.baseX = baseX; + out.baseY = baseY; + out.textAlign = textAlign; + out.textVerticalAlign = textVerticalAlign; + + return out; +} + + +function setCtx(ctx, prop, value) { + ctx[prop] = fixShadow(ctx, prop, value); + return ctx[prop]; +} + +/** + * @param {string} [stroke] If specified, do not check style.textStroke. + * @param {string} [lineWidth] If specified, do not check style.textStroke. + * @param {number} style + */ +function getStroke(stroke, lineWidth) { + return (stroke == null || lineWidth <= 0 || stroke === 'transparent' || stroke === 'none') + ? null + // TODO pattern and gradient? + : (stroke.image || stroke.colorStops) + ? '#000' + : stroke; +} + +function getFill(fill) { + return (fill == null || fill === 'none') + ? null + // TODO pattern and gradient? + : (fill.image || fill.colorStops) + ? '#000' + : fill; +} + +function parsePercent(value, maxValue) { + if (typeof value === 'string') { + if (value.lastIndexOf('%') >= 0) { + return parseFloat(value) / 100 * maxValue; + } + return parseFloat(value); + } + return value; +} + +function getTextXForPadding(x, textAlign, textPadding) { + return textAlign === 'right' + ? (x - textPadding[1]) + : textAlign === 'center' + ? (x + textPadding[3] / 2 - textPadding[1] / 2) + : (x + textPadding[3]); +} + +/** + * @param {string} text + * @param {module:zrender/Style} style + * @return {boolean} + */ +function needDrawText(text, style) { + return text != null + && (text + || style.textBackgroundColor + || (style.textBorderWidth && style.textBorderColor) + || style.textPadding + ); +} + +/** + * Mixin for drawing text in a element bounding rect + * @module zrender/mixin/RectText + */ + +var tmpRect$1 = new BoundingRect(); + +var RectText = function () {}; + +RectText.prototype = { + + constructor: RectText, + + /** + * Draw text in a rect with specified position. + * @param {CanvasRenderingContext2D} ctx + * @param {Object} rect Displayable rect + */ + drawRectText: function (ctx, rect) { + var style = this.style; + + rect = style.textRect || rect; + + // Optimize, avoid normalize every time. + this.__dirty && normalizeTextStyle(style, true); + + var text = style.text; + + // Convert to string + text != null && (text += ''); + + if (!needDrawText(text, style)) { + return; + } + + // FIXME + // Do not provide prevEl to `textHelper.renderText` for ctx prop cache, + // but use `ctx.save()` and `ctx.restore()`. Because the cache for rect + // text propably break the cache for its host elements. + ctx.save(); + + // Transform rect to view space + var transform = this.transform; + if (!style.transformText) { + if (transform) { + tmpRect$1.copy(rect); + tmpRect$1.applyTransform(transform); + rect = tmpRect$1; + } + } + else { + this.setTransform(ctx); + } + + // transformText and textRotation can not be used at the same time. + renderText(this, ctx, text, style, rect, WILL_BE_RESTORED); + + ctx.restore(); + } +}; + +/** + * Base class of all displayable graphic objects + * @module zrender/graphic/Displayable + */ + + +/** + * @alias module:zrender/graphic/Displayable + * @extends module:zrender/Element + * @extends module:zrender/graphic/mixin/RectText + */ +function Displayable(opts) { + + opts = opts || {}; + + Element.call(this, opts); + + // Extend properties + for (var name in opts) { + if ( + opts.hasOwnProperty(name) + && name !== 'style' + ) { + this[name] = opts[name]; + } + } + + /** + * @type {module:zrender/graphic/Style} + */ + this.style = new Style(opts.style, this); + + this._rect = null; + // Shapes for cascade clipping. + // Can only be `null`/`undefined` or an non-empty array, MUST NOT be an empty array. + // because it is easy to only using null to check whether clipPaths changed. + this.__clipPaths = null; + + // FIXME Stateful must be mixined after style is setted + // Stateful.call(this, opts); +} + +Displayable.prototype = { + + constructor: Displayable, + + type: 'displayable', + + /** + * Dirty flag. From which painter will determine if this displayable object needs brush. + * @name module:zrender/graphic/Displayable#__dirty + * @type {boolean} + */ + __dirty: true, + + /** + * Whether the displayable object is visible. when it is true, the displayable object + * is not drawn, but the mouse event can still trigger the object. + * @name module:/zrender/graphic/Displayable#invisible + * @type {boolean} + * @default false + */ + invisible: false, + + /** + * @name module:/zrender/graphic/Displayable#z + * @type {number} + * @default 0 + */ + z: 0, + + /** + * @name module:/zrender/graphic/Displayable#z + * @type {number} + * @default 0 + */ + z2: 0, + + /** + * The z level determines the displayable object can be drawn in which layer canvas. + * @name module:/zrender/graphic/Displayable#zlevel + * @type {number} + * @default 0 + */ + zlevel: 0, + + /** + * Whether it can be dragged. + * @name module:/zrender/graphic/Displayable#draggable + * @type {boolean} + * @default false + */ + draggable: false, + + /** + * Whether is it dragging. + * @name module:/zrender/graphic/Displayable#draggable + * @type {boolean} + * @default false + */ + dragging: false, + + /** + * Whether to respond to mouse events. + * @name module:/zrender/graphic/Displayable#silent + * @type {boolean} + * @default false + */ + silent: false, + + /** + * If enable culling + * @type {boolean} + * @default false + */ + culling: false, + + /** + * Mouse cursor when hovered + * @name module:/zrender/graphic/Displayable#cursor + * @type {string} + */ + cursor: 'pointer', + + /** + * If hover area is bounding rect + * @name module:/zrender/graphic/Displayable#rectHover + * @type {string} + */ + rectHover: false, + + /** + * Render the element progressively when the value >= 0, + * usefull for large data. + * @type {boolean} + */ + progressive: false, + + /** + * @type {boolean} + */ + incremental: false, + /** + * Scale ratio for global scale. + * @type {boolean} + */ + globalScaleRatio: 1, + + beforeBrush: function (ctx) {}, + + afterBrush: function (ctx) {}, + + /** + * Graphic drawing method. + * @param {CanvasRenderingContext2D} ctx + */ + // Interface + brush: function (ctx, prevEl) {}, + + /** + * Get the minimum bounding box. + * @return {module:zrender/core/BoundingRect} + */ + // Interface + getBoundingRect: function () {}, + + /** + * If displayable element contain coord x, y + * @param {number} x + * @param {number} y + * @return {boolean} + */ + contain: function (x, y) { + return this.rectContain(x, y); + }, + + /** + * @param {Function} cb + * @param {} context + */ + traverse: function (cb, context) { + cb.call(context, this); + }, + + /** + * If bounding rect of element contain coord x, y + * @param {number} x + * @param {number} y + * @return {boolean} + */ + rectContain: function (x, y) { + var coord = this.transformCoordToLocal(x, y); + var rect = this.getBoundingRect(); + return rect.contain(coord[0], coord[1]); + }, + + /** + * Mark displayable element dirty and refresh next frame + */ + dirty: function () { + this.__dirty = this.__dirtyText = true; + + this._rect = null; + + this.__zr && this.__zr.refresh(); + }, + + /** + * If displayable object binded any event + * @return {boolean} + */ + // TODO, events bound by bind + // isSilent: function () { + // return !( + // this.hoverable || this.draggable + // || this.onmousemove || this.onmouseover || this.onmouseout + // || this.onmousedown || this.onmouseup || this.onclick + // || this.ondragenter || this.ondragover || this.ondragleave + // || this.ondrop + // ); + // }, + /** + * Alias for animate('style') + * @param {boolean} loop + */ + animateStyle: function (loop) { + return this.animate('style', loop); + }, + + attrKV: function (key, value) { + if (key !== 'style') { + Element.prototype.attrKV.call(this, key, value); + } + else { + this.style.set(value); + } + }, + + /** + * @param {Object|string} key + * @param {*} value + */ + setStyle: function (key, value) { + this.style.set(key, value); + this.dirty(false); + return this; + }, + + /** + * Use given style object + * @param {Object} obj + */ + useStyle: function (obj) { + this.style = new Style(obj, this); + this.dirty(false); + return this; + }, + + /** + * The string value of `textPosition` needs to be calculated to a real postion. + * For example, `'inside'` is calculated to `[rect.width/2, rect.height/2]` + * by default. See `contain/text.js#calculateTextPosition` for more details. + * But some coutom shapes like "pin", "flag" have center that is not exactly + * `[width/2, height/2]`. So we provide this hook to customize the calculation + * for those shapes. It will be called if the `style.textPosition` is a string. + * @param {Obejct} [out] Prepared out object. If not provided, this method should + * be responsible for creating one. + * @param {module:zrender/graphic/Style} style + * @param {Object} rect {x, y, width, height} + * @return {Obejct} out The same as the input out. + * { + * x: number. mandatory. + * y: number. mandatory. + * textAlign: string. optional. use style.textAlign by default. + * textVerticalAlign: string. optional. use style.textVerticalAlign by default. + * } + */ + calculateTextPosition: null +}; + +inherits(Displayable, Element); + +mixin(Displayable, RectText); + +/** + * @alias zrender/graphic/Image + * @extends module:zrender/graphic/Displayable + * @constructor + * @param {Object} opts + */ +function ZImage(opts) { + Displayable.call(this, opts); +} + +ZImage.prototype = { + + constructor: ZImage, + + type: 'image', + + brush: function (ctx, prevEl) { + var style = this.style; + var src = style.image; + + // Must bind each time + style.bind(ctx, this, prevEl); + + var image = this._image = createOrUpdateImage( + src, + this._image, + this, + this.onload + ); + + if (!image || !isImageReady(image)) { + return; + } + + // 图片已经加载完成 + // if (image.nodeName.toUpperCase() == 'IMG') { + // if (!image.complete) { + // return; + // } + // } + // Else is canvas + + var x = style.x || 0; + var y = style.y || 0; + var width = style.width; + var height = style.height; + var aspect = image.width / image.height; + if (width == null && height != null) { + // Keep image/height ratio + width = height * aspect; + } + else if (height == null && width != null) { + height = width / aspect; + } + else if (width == null && height == null) { + width = image.width; + height = image.height; + } + + // 设置transform + this.setTransform(ctx); + + if (style.sWidth && style.sHeight) { + var sx = style.sx || 0; + var sy = style.sy || 0; + ctx.drawImage( + image, + sx, sy, style.sWidth, style.sHeight, + x, y, width, height + ); + } + else if (style.sx && style.sy) { + var sx = style.sx; + var sy = style.sy; + var sWidth = width - sx; + var sHeight = height - sy; + ctx.drawImage( + image, + sx, sy, sWidth, sHeight, + x, y, width, height + ); + } + else { + ctx.drawImage(image, x, y, width, height); + } + + // Draw rect text + if (style.text != null) { + // Only restore transform when needs draw text. + this.restoreTransform(ctx); + this.drawRectText(ctx, this.getBoundingRect()); + } + }, + + getBoundingRect: function () { + var style = this.style; + if (!this._rect) { + this._rect = new BoundingRect( + style.x || 0, style.y || 0, style.width || 0, style.height || 0 + ); + } + return this._rect; + } +}; + +inherits(ZImage, Displayable); + +var HOVER_LAYER_ZLEVEL = 1e5; +var CANVAS_ZLEVEL = 314159; + +var EL_AFTER_INCREMENTAL_INC = 0.01; +var INCREMENTAL_INC = 0.001; + +function parseInt10(val) { + return parseInt(val, 10); +} + +function isLayerValid(layer) { + if (!layer) { + return false; + } + + if (layer.__builtin__) { + return true; + } + + if (typeof (layer.resize) !== 'function' + || typeof (layer.refresh) !== 'function' + ) { + return false; + } + + return true; +} + +var tmpRect = new BoundingRect(0, 0, 0, 0); +var viewRect = new BoundingRect(0, 0, 0, 0); +function isDisplayableCulled(el, width, height) { + tmpRect.copy(el.getBoundingRect()); + if (el.transform) { + tmpRect.applyTransform(el.transform); + } + viewRect.width = width; + viewRect.height = height; + return !tmpRect.intersect(viewRect); +} + +function isClipPathChanged(clipPaths, prevClipPaths) { + // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array. + if (clipPaths === prevClipPaths) { + return false; + } + if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) { + return true; + } + for (var i = 0; i < clipPaths.length; i++) { + if (clipPaths[i] !== prevClipPaths[i]) { + return true; + } + } + return false; +} + +function doClip(clipPaths, ctx) { + for (var i = 0; i < clipPaths.length; i++) { + var clipPath = clipPaths[i]; + + clipPath.setTransform(ctx); + ctx.beginPath(); + clipPath.buildPath(ctx, clipPath.shape); + ctx.clip(); + // Transform back + clipPath.restoreTransform(ctx); + } +} + +function createRoot(width, height) { + var domRoot = document.createElement('div'); + + // domRoot.onselectstart = returnFalse; // Avoid page selected + domRoot.style.cssText = [ + 'position:relative', + // IOS13 safari probably has a compositing bug (z order of the canvas and the consequent + // dom does not act as expected) when some of the parent dom has + // `-webkit-overflow-scrolling: touch;` and the webpage is longer than one screen and + // the canvas is not at the top part of the page. + // Check `https://bugs.webkit.org/show_bug.cgi?id=203681` for more details. We remove + // this `overflow:hidden` to avoid the bug. + // 'overflow:hidden', + 'width:' + width + 'px', + 'height:' + height + 'px', + 'padding:0', + 'margin:0', + 'border-width:0' + ].join(';') + ';'; + + return domRoot; +} + + +/** + * @alias module:zrender/Painter + * @constructor + * @param {HTMLElement} root 绘图容器 + * @param {module:zrender/Storage} storage + * @param {Object} opts + */ +var Painter = function (root, storage, opts) { + + this.type = 'canvas'; + + // In node environment using node-canvas + var singleCanvas = !root.nodeName // In node ? + || root.nodeName.toUpperCase() === 'CANVAS'; + + this._opts = opts = extend({}, opts || {}); + + /** + * @type {number} + */ + this.dpr = opts.devicePixelRatio || devicePixelRatio; + /** + * @type {boolean} + * @private + */ + this._singleCanvas = singleCanvas; + /** + * 绘图容器 + * @type {HTMLElement} + */ + this.root = root; + + var rootStyle = root.style; + + if (rootStyle) { + rootStyle['-webkit-tap-highlight-color'] = 'transparent'; + rootStyle['-webkit-user-select'] = + rootStyle['user-select'] = + rootStyle['-webkit-touch-callout'] = 'none'; + + root.innerHTML = ''; + } + + /** + * @type {module:zrender/Storage} + */ + this.storage = storage; + + /** + * @type {Array.} + * @private + */ + var zlevelList = this._zlevelList = []; + + /** + * @type {Object.} + * @private + */ + var layers = this._layers = {}; + + /** + * @type {Object.} + * @private + */ + this._layerConfig = {}; + + /** + * zrender will do compositing when root is a canvas and have multiple zlevels. + */ + this._needsManuallyCompositing = false; + + if (!singleCanvas) { + this._width = this._getSize(0); + this._height = this._getSize(1); + + var domRoot = this._domRoot = createRoot( + this._width, this._height + ); + root.appendChild(domRoot); + } + else { + var width = root.width; + var height = root.height; + + if (opts.width != null) { + width = opts.width; + } + if (opts.height != null) { + height = opts.height; + } + this.dpr = opts.devicePixelRatio || 1; + + // Use canvas width and height directly + root.width = width * this.dpr; + root.height = height * this.dpr; + + this._width = width; + this._height = height; + + // Create layer if only one given canvas + // Device can be specified to create a high dpi image. + var mainLayer = new Layer(root, this, this.dpr); + mainLayer.__builtin__ = true; + mainLayer.initContext(); + // FIXME Use canvas width and height + // mainLayer.resize(width, height); + layers[CANVAS_ZLEVEL] = mainLayer; + mainLayer.zlevel = CANVAS_ZLEVEL; + // Not use common zlevel. + zlevelList.push(CANVAS_ZLEVEL); + + this._domRoot = root; + } + + /** + * @type {module:zrender/Layer} + * @private + */ + this._hoverlayer = null; + + this._hoverElements = []; +}; + +Painter.prototype = { + + constructor: Painter, + + getType: function () { + return 'canvas'; + }, + + /** + * If painter use a single canvas + * @return {boolean} + */ + isSingleCanvas: function () { + return this._singleCanvas; + }, + /** + * @return {HTMLDivElement} + */ + getViewportRoot: function () { + return this._domRoot; + }, + + getViewportRootOffset: function () { + var viewportRoot = this.getViewportRoot(); + if (viewportRoot) { + return { + offsetLeft: viewportRoot.offsetLeft || 0, + offsetTop: viewportRoot.offsetTop || 0 + }; + } + }, + + /** + * 刷新 + * @param {boolean} [paintAll=false] 强制绘制所有displayable + */ + refresh: function (paintAll) { + + var list = this.storage.getDisplayList(true); + + var zlevelList = this._zlevelList; + + this._redrawId = Math.random(); + + this._paintList(list, paintAll, this._redrawId); + + // Paint custum layers + for (var i = 0; i < zlevelList.length; i++) { + var z = zlevelList[i]; + var layer = this._layers[z]; + if (!layer.__builtin__ && layer.refresh) { + var clearColor = i === 0 ? this._backgroundColor : null; + layer.refresh(clearColor); + } + } + + this.refreshHover(); + + return this; + }, + + addHover: function (el, hoverStyle) { + if (el.__hoverMir) { + return; + } + var elMirror = new el.constructor({ + style: el.style, + shape: el.shape, + z: el.z, + z2: el.z2, + silent: el.silent + }); + elMirror.__from = el; + el.__hoverMir = elMirror; + hoverStyle && elMirror.setStyle(hoverStyle); + this._hoverElements.push(elMirror); + + return elMirror; + }, + + removeHover: function (el) { + var elMirror = el.__hoverMir; + var hoverElements = this._hoverElements; + var idx = indexOf(hoverElements, elMirror); + if (idx >= 0) { + hoverElements.splice(idx, 1); + } + el.__hoverMir = null; + }, + + clearHover: function (el) { + var hoverElements = this._hoverElements; + for (var i = 0; i < hoverElements.length; i++) { + var from = hoverElements[i].__from; + if (from) { + from.__hoverMir = null; + } + } + hoverElements.length = 0; + }, + + refreshHover: function () { + var hoverElements = this._hoverElements; + var len = hoverElements.length; + var hoverLayer = this._hoverlayer; + hoverLayer && hoverLayer.clear(); + + if (!len) { + return; + } + sort(hoverElements, this.storage.displayableSortFunc); + + // Use a extream large zlevel + // FIXME? + if (!hoverLayer) { + hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL); + } + + var scope = {}; + hoverLayer.ctx.save(); + for (var i = 0; i < len;) { + var el = hoverElements[i]; + var originalEl = el.__from; + // Original el is removed + // PENDING + if (!(originalEl && originalEl.__zr)) { + hoverElements.splice(i, 1); + originalEl.__hoverMir = null; + len--; + continue; + } + i++; + + // Use transform + // FIXME style and shape ? + if (!originalEl.invisible) { + el.transform = originalEl.transform; + el.invTransform = originalEl.invTransform; + el.__clipPaths = originalEl.__clipPaths; + // el. + this._doPaintEl(el, hoverLayer, true, scope); + } + } + + hoverLayer.ctx.restore(); + }, + + getHoverLayer: function () { + return this.getLayer(HOVER_LAYER_ZLEVEL); + }, + + _paintList: function (list, paintAll, redrawId) { + if (this._redrawId !== redrawId) { + return; + } + + paintAll = paintAll || false; + + this._updateLayerStatus(list); + + var finished = this._doPaintList(list, paintAll); + + if (this._needsManuallyCompositing) { + this._compositeManually(); + } + + if (!finished) { + var self = this; + requestAnimationFrame(function () { + self._paintList(list, paintAll, redrawId); + }); + } + }, + + _compositeManually: function () { + var ctx = this.getLayer(CANVAS_ZLEVEL).ctx; + var width = this._domRoot.width; + var height = this._domRoot.height; + ctx.clearRect(0, 0, width, height); + // PENDING, If only builtin layer? + this.eachBuiltinLayer(function (layer) { + if (layer.virtual) { + ctx.drawImage(layer.dom, 0, 0, width, height); + } + }); + }, + + _doPaintList: function (list, paintAll) { + var layerList = []; + for (var zi = 0; zi < this._zlevelList.length; zi++) { + var zlevel = this._zlevelList[zi]; + var layer = this._layers[zlevel]; + if (layer.__builtin__ + && layer !== this._hoverlayer + && (layer.__dirty || paintAll) + ) { + layerList.push(layer); + } + } + + var finished = true; + + for (var k = 0; k < layerList.length; k++) { + var layer = layerList[k]; + var ctx = layer.ctx; + var scope = {}; + ctx.save(); + + var start = paintAll ? layer.__startIndex : layer.__drawIndex; + + var useTimer = !paintAll && layer.incremental && Date.now; + var startTime = useTimer && Date.now(); + + var clearColor = layer.zlevel === this._zlevelList[0] + ? this._backgroundColor : null; + // All elements in this layer are cleared. + if (layer.__startIndex === layer.__endIndex) { + layer.clear(false, clearColor); + } + else if (start === layer.__startIndex) { + var firstEl = list[start]; + if (!firstEl.incremental || !firstEl.notClear || paintAll) { + layer.clear(false, clearColor); + } + } + if (start === -1) { + console.error('For some unknown reason. drawIndex is -1'); + start = layer.__startIndex; + } + for (var i = start; i < layer.__endIndex; i++) { + var el = list[i]; + this._doPaintEl(el, layer, paintAll, scope); + el.__dirty = el.__dirtyText = false; + + if (useTimer) { + // Date.now can be executed in 13,025,305 ops/second. + var dTime = Date.now() - startTime; + // Give 15 millisecond to draw. + // The rest elements will be drawn in the next frame. + if (dTime > 15) { + break; + } + } + } + + layer.__drawIndex = i; + + if (layer.__drawIndex < layer.__endIndex) { + finished = false; + } + + if (scope.prevElClipPaths) { + // Needs restore the state. If last drawn element is in the clipping area. + ctx.restore(); + } + + ctx.restore(); + } + + if (env$1.wxa) { + // Flush for weixin application + each$1(this._layers, function (layer) { + if (layer && layer.ctx && layer.ctx.draw) { + layer.ctx.draw(); + } + }); + } + + return finished; + }, + + _doPaintEl: function (el, currentLayer, forcePaint, scope) { + var ctx = currentLayer.ctx; + var m = el.transform; + if ( + (currentLayer.__dirty || forcePaint) + // Ignore invisible element + && !el.invisible + // Ignore transparent element + && el.style.opacity !== 0 + // Ignore scale 0 element, in some environment like node-canvas + // Draw a scale 0 element can cause all following draw wrong + // And setTransform with scale 0 will cause set back transform failed. + && !(m && !m[0] && !m[3]) + // Ignore culled element + && !(el.culling && isDisplayableCulled(el, this._width, this._height)) + ) { + + var clipPaths = el.__clipPaths; + var prevElClipPaths = scope.prevElClipPaths; + + // Optimize when clipping on group with several elements + if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) { + // If has previous clipping state, restore from it + if (prevElClipPaths) { + ctx.restore(); + scope.prevElClipPaths = null; + // Reset prevEl since context has been restored + scope.prevEl = null; + } + // New clipping state + if (clipPaths) { + ctx.save(); + doClip(clipPaths, ctx); + scope.prevElClipPaths = clipPaths; + } + } + el.beforeBrush && el.beforeBrush(ctx); + + el.brush(ctx, scope.prevEl || null); + scope.prevEl = el; + + el.afterBrush && el.afterBrush(ctx); + } + }, + + /** + * 获取 zlevel 所在层,如果不存在则会创建一个新的层 + * @param {number} zlevel + * @param {boolean} virtual Virtual layer will not be inserted into dom. + * @return {module:zrender/Layer} + */ + getLayer: function (zlevel, virtual) { + if (this._singleCanvas && !this._needsManuallyCompositing) { + zlevel = CANVAS_ZLEVEL; + } + var layer = this._layers[zlevel]; + if (!layer) { + // Create a new layer + layer = new Layer('zr_' + zlevel, this, this.dpr); + layer.zlevel = zlevel; + layer.__builtin__ = true; + + if (this._layerConfig[zlevel]) { + merge(layer, this._layerConfig[zlevel], true); + } + + if (virtual) { + layer.virtual = virtual; + } + + this.insertLayer(zlevel, layer); + + // Context is created after dom inserted to document + // Or excanvas will get 0px clientWidth and clientHeight + layer.initContext(); + } + + return layer; + }, + + insertLayer: function (zlevel, layer) { + + var layersMap = this._layers; + var zlevelList = this._zlevelList; + var len = zlevelList.length; + var prevLayer = null; + var i = -1; + var domRoot = this._domRoot; + + if (layersMap[zlevel]) { + logError$1('ZLevel ' + zlevel + ' has been used already'); + return; + } + // Check if is a valid layer + if (!isLayerValid(layer)) { + logError$1('Layer of zlevel ' + zlevel + ' is not valid'); + return; + } + + if (len > 0 && zlevel > zlevelList[0]) { + for (i = 0; i < len - 1; i++) { + if ( + zlevelList[i] < zlevel + && zlevelList[i + 1] > zlevel + ) { + break; + } + } + prevLayer = layersMap[zlevelList[i]]; + } + zlevelList.splice(i + 1, 0, zlevel); + + layersMap[zlevel] = layer; + + // Vitual layer will not directly show on the screen. + // (It can be a WebGL layer and assigned to a ZImage element) + // But it still under management of zrender. + if (!layer.virtual) { + if (prevLayer) { + var prevDom = prevLayer.dom; + if (prevDom.nextSibling) { + domRoot.insertBefore( + layer.dom, + prevDom.nextSibling + ); + } + else { + domRoot.appendChild(layer.dom); + } + } + else { + if (domRoot.firstChild) { + domRoot.insertBefore(layer.dom, domRoot.firstChild); + } + else { + domRoot.appendChild(layer.dom); + } + } + } + }, + + // Iterate each layer + eachLayer: function (cb, context) { + var zlevelList = this._zlevelList; + var z; + var i; + for (i = 0; i < zlevelList.length; i++) { + z = zlevelList[i]; + cb.call(context, this._layers[z], z); + } + }, + + // Iterate each buildin layer + eachBuiltinLayer: function (cb, context) { + var zlevelList = this._zlevelList; + var layer; + var z; + var i; + for (i = 0; i < zlevelList.length; i++) { + z = zlevelList[i]; + layer = this._layers[z]; + if (layer.__builtin__) { + cb.call(context, layer, z); + } + } + }, + + // Iterate each other layer except buildin layer + eachOtherLayer: function (cb, context) { + var zlevelList = this._zlevelList; + var layer; + var z; + var i; + for (i = 0; i < zlevelList.length; i++) { + z = zlevelList[i]; + layer = this._layers[z]; + if (!layer.__builtin__) { + cb.call(context, layer, z); + } + } + }, + + /** + * 获取所有已创建的层 + * @param {Array.} [prevLayer] + */ + getLayers: function () { + return this._layers; + }, + + _updateLayerStatus: function (list) { + + this.eachBuiltinLayer(function (layer, z) { + layer.__dirty = layer.__used = false; + }); + + function updatePrevLayer(idx) { + if (prevLayer) { + if (prevLayer.__endIndex !== idx) { + prevLayer.__dirty = true; + } + prevLayer.__endIndex = idx; + } + } + + if (this._singleCanvas) { + for (var i = 1; i < list.length; i++) { + var el = list[i]; + if (el.zlevel !== list[i - 1].zlevel || el.incremental) { + this._needsManuallyCompositing = true; + break; + } + } + } + + var prevLayer = null; + var incrementalLayerCount = 0; + for (var i = 0; i < list.length; i++) { + var el = list[i]; + var zlevel = el.zlevel; + var layer; + // PENDING If change one incremental element style ? + // TODO Where there are non-incremental elements between incremental elements. + if (el.incremental) { + layer = this.getLayer(zlevel + INCREMENTAL_INC, this._needsManuallyCompositing); + layer.incremental = true; + incrementalLayerCount = 1; + } + else { + layer = this.getLayer( + zlevel + (incrementalLayerCount > 0 ? EL_AFTER_INCREMENTAL_INC : 0), + this._needsManuallyCompositing + ); + } + + if (!layer.__builtin__) { + logError$1('ZLevel ' + zlevel + ' has been used by unkown layer ' + layer.id); + } + + if (layer !== prevLayer) { + layer.__used = true; + if (layer.__startIndex !== i) { + layer.__dirty = true; + } + layer.__startIndex = i; + if (!layer.incremental) { + layer.__drawIndex = i; + } + else { + // Mark layer draw index needs to update. + layer.__drawIndex = -1; + } + updatePrevLayer(i); + prevLayer = layer; + } + if (el.__dirty) { + layer.__dirty = true; + if (layer.incremental && layer.__drawIndex < 0) { + // Start draw from the first dirty element. + layer.__drawIndex = i; + } + } + } + + updatePrevLayer(i); + + this.eachBuiltinLayer(function (layer, z) { + // Used in last frame but not in this frame. Needs clear + if (!layer.__used && layer.getElementCount() > 0) { + layer.__dirty = true; + layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0; + } + // For incremental layer. In case start index changed and no elements are dirty. + if (layer.__dirty && layer.__drawIndex < 0) { + layer.__drawIndex = layer.__startIndex; + } + }); + }, + + /** + * 清除hover层外所有内容 + */ + clear: function () { + this.eachBuiltinLayer(this._clearLayer); + return this; + }, + + _clearLayer: function (layer) { + layer.clear(); + }, + + setBackgroundColor: function (backgroundColor) { + this._backgroundColor = backgroundColor; + }, + + /** + * 修改指定zlevel的绘制参数 + * + * @param {string} zlevel + * @param {Object} config 配置对象 + * @param {string} [config.clearColor=0] 每次清空画布的颜色 + * @param {string} [config.motionBlur=false] 是否开启动态模糊 + * @param {number} [config.lastFrameAlpha=0.7] + * 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显 + */ + configLayer: function (zlevel, config) { + if (config) { + var layerConfig = this._layerConfig; + if (!layerConfig[zlevel]) { + layerConfig[zlevel] = config; + } + else { + merge(layerConfig[zlevel], config, true); + } + + for (var i = 0; i < this._zlevelList.length; i++) { + var _zlevel = this._zlevelList[i]; + if (_zlevel === zlevel || _zlevel === zlevel + EL_AFTER_INCREMENTAL_INC) { + var layer = this._layers[_zlevel]; + merge(layer, layerConfig[zlevel], true); + } + } + } + }, + + /** + * 删除指定层 + * @param {number} zlevel 层所在的zlevel + */ + delLayer: function (zlevel) { + var layers = this._layers; + var zlevelList = this._zlevelList; + var layer = layers[zlevel]; + if (!layer) { + return; + } + layer.dom.parentNode.removeChild(layer.dom); + delete layers[zlevel]; + + zlevelList.splice(indexOf(zlevelList, zlevel), 1); + }, + + /** + * 区域大小变化后重绘 + */ + resize: function (width, height) { + if (!this._domRoot.style) { // Maybe in node or worker + if (width == null || height == null) { + return; + } + this._width = width; + this._height = height; + + this.getLayer(CANVAS_ZLEVEL).resize(width, height); + } + else { + var domRoot = this._domRoot; + // FIXME Why ? + domRoot.style.display = 'none'; + + // Save input w/h + var opts = this._opts; + width != null && (opts.width = width); + height != null && (opts.height = height); + + width = this._getSize(0); + height = this._getSize(1); + + domRoot.style.display = ''; + + // 优化没有实际改变的resize + if (this._width !== width || height !== this._height) { + domRoot.style.width = width + 'px'; + domRoot.style.height = height + 'px'; + + for (var id in this._layers) { + if (this._layers.hasOwnProperty(id)) { + this._layers[id].resize(width, height); + } + } + each$1(this._progressiveLayers, function (layer) { + layer.resize(width, height); + }); + + this.refresh(true); + } + + this._width = width; + this._height = height; + + } + return this; + }, + + /** + * 清除单独的一个层 + * @param {number} zlevel + */ + clearLayer: function (zlevel) { + var layer = this._layers[zlevel]; + if (layer) { + layer.clear(); + } + }, + + /** + * 释放 + */ + dispose: function () { + this.root.innerHTML = ''; + + this.root = + this.storage = + + this._domRoot = + this._layers = null; + }, + + /** + * Get canvas which has all thing rendered + * @param {Object} opts + * @param {string} [opts.backgroundColor] + * @param {number} [opts.pixelRatio] + */ + getRenderedCanvas: function (opts) { + opts = opts || {}; + if (this._singleCanvas && !this._compositeManually) { + return this._layers[CANVAS_ZLEVEL].dom; + } + + var imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr); + imageLayer.initContext(); + imageLayer.clear(false, opts.backgroundColor || this._backgroundColor); + + if (opts.pixelRatio <= this.dpr) { + this.refresh(); + + var width = imageLayer.dom.width; + var height = imageLayer.dom.height; + var ctx = imageLayer.ctx; + this.eachLayer(function (layer) { + if (layer.__builtin__) { + ctx.drawImage(layer.dom, 0, 0, width, height); + } + else if (layer.renderToCanvas) { + imageLayer.ctx.save(); + layer.renderToCanvas(imageLayer.ctx); + imageLayer.ctx.restore(); + } + }); + } + else { + // PENDING, echarts-gl and incremental rendering. + var scope = {}; + var displayList = this.storage.getDisplayList(true); + for (var i = 0; i < displayList.length; i++) { + var el = displayList[i]; + this._doPaintEl(el, imageLayer, true, scope); + } + } + + return imageLayer.dom; + }, + /** + * 获取绘图区域宽度 + */ + getWidth: function () { + return this._width; + }, + + /** + * 获取绘图区域高度 + */ + getHeight: function () { + return this._height; + }, + + _getSize: function (whIdx) { + var opts = this._opts; + var wh = ['width', 'height'][whIdx]; + var cwh = ['clientWidth', 'clientHeight'][whIdx]; + var plt = ['paddingLeft', 'paddingTop'][whIdx]; + var prb = ['paddingRight', 'paddingBottom'][whIdx]; + + if (opts[wh] != null && opts[wh] !== 'auto') { + return parseFloat(opts[wh]); + } + + var root = this.root; + // IE8 does not support getComputedStyle, but it use VML. + var stl = document.defaultView.getComputedStyle(root); + + return ( + (root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh])) + - (parseInt10(stl[plt]) || 0) + - (parseInt10(stl[prb]) || 0) + ) | 0; + }, + + pathToImage: function (path, dpr) { + dpr = dpr || this.dpr; + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + var rect = path.getBoundingRect(); + var style = path.style; + var shadowBlurSize = style.shadowBlur * dpr; + var shadowOffsetX = style.shadowOffsetX * dpr; + var shadowOffsetY = style.shadowOffsetY * dpr; + var lineWidth = style.hasStroke() ? style.lineWidth : 0; + + var leftMargin = Math.max(lineWidth / 2, -shadowOffsetX + shadowBlurSize); + var rightMargin = Math.max(lineWidth / 2, shadowOffsetX + shadowBlurSize); + var topMargin = Math.max(lineWidth / 2, -shadowOffsetY + shadowBlurSize); + var bottomMargin = Math.max(lineWidth / 2, shadowOffsetY + shadowBlurSize); + var width = rect.width + leftMargin + rightMargin; + var height = rect.height + topMargin + bottomMargin; + + canvas.width = width * dpr; + canvas.height = height * dpr; + + ctx.scale(dpr, dpr); + ctx.clearRect(0, 0, width, height); + ctx.dpr = dpr; + + var pathTransform = { + position: path.position, + rotation: path.rotation, + scale: path.scale + }; + path.position = [leftMargin - rect.x, topMargin - rect.y]; + path.rotation = 0; + path.scale = [1, 1]; + path.updateTransform(); + if (path) { + path.brush(ctx); + } + + var ImageShape = ZImage; + var imgShape = new ImageShape({ + style: { + x: 0, + y: 0, + image: canvas + } + }); + + if (pathTransform.position != null) { + imgShape.position = path.position = pathTransform.position; + } + + if (pathTransform.rotation != null) { + imgShape.rotation = path.rotation = pathTransform.rotation; + } + + if (pathTransform.scale != null) { + imgShape.scale = path.scale = pathTransform.scale; + } + + return imgShape; + } +}; + +/** + * 动画主类, 调度和管理所有动画控制器 + * + * @module zrender/animation/Animation + * @author pissang(https://github.com/pissang) + */ +// TODO Additive animation +// http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/ +// https://developer.apple.com/videos/wwdc2014/#236 + +/** + * @typedef {Object} IZRenderStage + * @property {Function} update + */ + +/** + * @alias module:zrender/animation/Animation + * @constructor + * @param {Object} [options] + * @param {Function} [options.onframe] + * @param {IZRenderStage} [options.stage] + * @example + * var animation = new Animation(); + * var obj = { + * x: 100, + * y: 100 + * }; + * animation.animate(node.position) + * .when(1000, { + * x: 500, + * y: 500 + * }) + * .when(2000, { + * x: 100, + * y: 100 + * }) + * .start('spline'); + */ +var Animation = function (options) { + + options = options || {}; + + this.stage = options.stage || {}; + + this.onframe = options.onframe || function () {}; + + // private properties + this._clips = []; + + this._running = false; + + this._time; + + this._pausedTime; + + this._pauseStart; + + this._paused = false; + + Eventful.call(this); +}; + +Animation.prototype = { + + constructor: Animation, + /** + * 添加 clip + * @param {module:zrender/animation/Clip} clip + */ + addClip: function (clip) { + this._clips.push(clip); + }, + /** + * 添加 animator + * @param {module:zrender/animation/Animator} animator + */ + addAnimator: function (animator) { + animator.animation = this; + var clips = animator.getClips(); + for (var i = 0; i < clips.length; i++) { + this.addClip(clips[i]); + } + }, + /** + * 删除动画片段 + * @param {module:zrender/animation/Clip} clip + */ + removeClip: function (clip) { + var idx = indexOf(this._clips, clip); + if (idx >= 0) { + this._clips.splice(idx, 1); + } + }, + + /** + * 删除动画片段 + * @param {module:zrender/animation/Animator} animator + */ + removeAnimator: function (animator) { + var clips = animator.getClips(); + for (var i = 0; i < clips.length; i++) { + this.removeClip(clips[i]); + } + animator.animation = null; + }, + + _update: function () { + var time = new Date().getTime() - this._pausedTime; + var delta = time - this._time; + var clips = this._clips; + var len = clips.length; + + var deferredEvents = []; + var deferredClips = []; + for (var i = 0; i < len; i++) { + var clip = clips[i]; + var e = clip.step(time, delta); + // Throw out the events need to be called after + // stage.update, like destroy + if (e) { + deferredEvents.push(e); + deferredClips.push(clip); + } + } + + // Remove the finished clip + for (var i = 0; i < len;) { + if (clips[i]._needsRemove) { + clips[i] = clips[len - 1]; + clips.pop(); + len--; + } + else { + i++; + } + } + + len = deferredEvents.length; + for (var i = 0; i < len; i++) { + deferredClips[i].fire(deferredEvents[i]); + } + + this._time = time; + + this.onframe(delta); + + // 'frame' should be triggered before stage, because upper application + // depends on the sequence (e.g., echarts-stream and finish + // event judge) + this.trigger('frame', delta); + + if (this.stage.update) { + this.stage.update(); + } + }, + + _startLoop: function () { + var self = this; + + this._running = true; + + function step() { + if (self._running) { + + requestAnimationFrame(step); + + !self._paused && self._update(); + } + } + + requestAnimationFrame(step); + }, + + /** + * Start animation. + */ + start: function () { + + this._time = new Date().getTime(); + this._pausedTime = 0; + + this._startLoop(); + }, + + /** + * Stop animation. + */ + stop: function () { + this._running = false; + }, + + /** + * Pause animation. + */ + pause: function () { + if (!this._paused) { + this._pauseStart = new Date().getTime(); + this._paused = true; + } + }, + + /** + * Resume animation. + */ + resume: function () { + if (this._paused) { + this._pausedTime += (new Date().getTime()) - this._pauseStart; + this._paused = false; + } + }, + + /** + * Clear animation. + */ + clear: function () { + this._clips = []; + }, + + /** + * Whether animation finished. + */ + isFinished: function () { + return !this._clips.length; + }, + + /** + * Creat animator for a target, whose props can be animated. + * + * @param {Object} target + * @param {Object} options + * @param {boolean} [options.loop=false] Whether loop animation. + * @param {Function} [options.getter=null] Get value from target. + * @param {Function} [options.setter=null] Set value to target. + * @return {module:zrender/animation/Animation~Animator} + */ + // TODO Gap + animate: function (target, options) { + options = options || {}; + + var animator = new Animator( + target, + options.loop, + options.getter, + options.setter + ); + + this.addAnimator(animator); + + return animator; + } +}; + +mixin(Animation, Eventful); + +/* global document */ + +var TOUCH_CLICK_DELAY = 300; + +var globalEventSupported = env$1.domSupported; + + +var localNativeListenerNames = (function () { + var mouseHandlerNames = [ + 'click', 'dblclick', 'mousewheel', 'mouseout', + 'mouseup', 'mousedown', 'mousemove', 'contextmenu' + ]; + var touchHandlerNames = [ + 'touchstart', 'touchend', 'touchmove' + ]; + var pointerEventNameMap = { + pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1 + }; + var pointerHandlerNames = map(mouseHandlerNames, function (name) { + var nm = name.replace('mouse', 'pointer'); + return pointerEventNameMap.hasOwnProperty(nm) ? nm : name; + }); + + return { + mouse: mouseHandlerNames, + touch: touchHandlerNames, + pointer: pointerHandlerNames + }; +})(); + +var globalNativeListenerNames = { + mouse: ['mousemove', 'mouseup'], + pointer: ['pointermove', 'pointerup'] +}; + + +function eventNameFix(name) { + return (name === 'mousewheel' && env$1.browser.firefox) ? 'DOMMouseScroll' : name; +} + +function isPointerFromTouch(event) { + var pointerType = event.pointerType; + return pointerType === 'pen' || pointerType === 'touch'; +} + +// function useMSGuesture(handlerProxy, event) { +// return isPointerFromTouch(event) && !!handlerProxy._msGesture; +// } + +// function onMSGestureChange(proxy, event) { +// if (event.translationX || event.translationY) { +// // mousemove is carried by MSGesture to reduce the sensitivity. +// proxy.handler.dispatchToElement(event.target, 'mousemove', event); +// } +// if (event.scale !== 1) { +// event.pinchX = event.offsetX; +// event.pinchY = event.offsetY; +// event.pinchScale = event.scale; +// proxy.handler.dispatchToElement(event.target, 'pinch', event); +// } +// } + +/** + * Prevent mouse event from being dispatched after Touch Events action + * @see + * 1. Mobile browsers dispatch mouse events 300ms after touchend. + * 2. Chrome for Android dispatch mousedown for long-touch about 650ms + * Result: Blocking Mouse Events for 700ms. + * + * @param {DOMHandlerScope} scope + */ +function setTouchTimer(scope) { + scope.touching = true; + if (scope.touchTimer != null) { + clearTimeout(scope.touchTimer); + scope.touchTimer = null; + } + scope.touchTimer = setTimeout(function () { + scope.touching = false; + scope.touchTimer = null; + }, 700); +} + +// Mark touch, which is useful in distinguish touch and +// mouse event in upper applicatoin. +function markTouch(event) { + event && (event.zrByTouch = true); +} + + +// function markTriggeredFromLocal(event) { +// event && (event.__zrIsFromLocal = true); +// } + +// function isTriggeredFromLocal(instance, event) { +// return !!(event && event.__zrIsFromLocal); +// } + +function normalizeGlobalEvent(instance, event) { + // offsetX, offsetY still need to be calculated. They are necessary in the event + // handlers of the upper applications. Set `true` to force calculate them. + return normalizeEvent(instance.dom, new FakeGlobalEvent(instance, event), true); +} + +/** + * Detect whether the given el is in `painterRoot`. + */ +function isLocalEl(instance, el) { + var isLocal = false; + do { + el = el && el.parentNode; + } + while (el && el.nodeType !== 9 && !( + isLocal = el === instance.painterRoot + )); + return isLocal; +} + +/** + * Make a fake event but not change the original event, + * becuase the global event probably be used by other + * listeners not belonging to zrender. + * @class + */ +function FakeGlobalEvent(instance, event) { + this.type = event.type; + this.target = this.currentTarget = instance.dom; + this.pointerType = event.pointerType; + // Necessray for the force calculation of zrX, zrY + this.clientX = event.clientX; + this.clientY = event.clientY; + // Because we do not mount global listeners to touch events, + // we do not copy `targetTouches` and `changedTouches` here. +} +var fakeGlobalEventProto = FakeGlobalEvent.prototype; +// we make the default methods on the event do nothing, +// otherwise it is dangerous. See more details in +// [Drag outside] in `Handler.js`. +fakeGlobalEventProto.stopPropagation = + fakeGlobalEventProto.stopImmediatePropagation = + fakeGlobalEventProto.preventDefault = noop; + + +/** + * Local DOM Handlers + * @this {HandlerProxy} + */ +var localDOMHandlers = { + + mousedown: function (event) { + event = normalizeEvent(this.dom, event); + + this._mayPointerCapture = [event.zrX, event.zrY]; + + this.trigger('mousedown', event); + }, + + mousemove: function (event) { + event = normalizeEvent(this.dom, event); + + var downPoint = this._mayPointerCapture; + if (downPoint && (event.zrX !== downPoint[0] || event.zrY !== downPoint[1])) { + togglePointerCapture(this, true); + } + + this.trigger('mousemove', event); + }, + + mouseup: function (event) { + event = normalizeEvent(this.dom, event); + + togglePointerCapture(this, false); + + this.trigger('mouseup', event); + }, + + mouseout: function (event) { + event = normalizeEvent(this.dom, event); + + // Similarly to the browser did on `document` and touch event, + // `globalout` will be delayed to final pointer cature release. + if (this._pointerCapturing) { + event.zrEventControl = 'no_globalout'; + } + + // There might be some doms created by upper layer application + // at the same level of painter.getViewportRoot() (e.g., tooltip + // dom created by echarts), where 'globalout' event should not + // be triggered when mouse enters these doms. (But 'mouseout' + // should be triggered at the original hovered element as usual). + var element = event.toElement || event.relatedTarget; + event.zrIsToLocalDOM = isLocalEl(this, element); + + this.trigger('mouseout', event); + }, + + touchstart: function (event) { + // Default mouse behaviour should not be disabled here. + // For example, page may needs to be slided. + event = normalizeEvent(this.dom, event); + + markTouch(event); + + this._lastTouchMoment = new Date(); + + this.handler.processGesture(event, 'start'); + + // For consistent event listener for both touch device and mouse device, + // we simulate "mouseover-->mousedown" in touch device. So we trigger + // `mousemove` here (to trigger `mouseover` inside), and then trigger + // `mousedown`. + localDOMHandlers.mousemove.call(this, event); + localDOMHandlers.mousedown.call(this, event); + }, + + touchmove: function (event) { + event = normalizeEvent(this.dom, event); + + markTouch(event); + + this.handler.processGesture(event, 'change'); + + // Mouse move should always be triggered no matter whether + // there is gestrue event, because mouse move and pinch may + // be used at the same time. + localDOMHandlers.mousemove.call(this, event); + }, + + touchend: function (event) { + event = normalizeEvent(this.dom, event); + + markTouch(event); + + this.handler.processGesture(event, 'end'); + + localDOMHandlers.mouseup.call(this, event); + + // Do not trigger `mouseout` here, in spite of `mousemove`(`mouseover`) is + // triggered in `touchstart`. This seems to be illogical, but by this mechanism, + // we can conveniently implement "hover style" in both PC and touch device just + // by listening to `mouseover` to add "hover style" and listening to `mouseout` + // to remove "hover style" on an element, without any additional code for + // compatibility. (`mouseout` will not be triggered in `touchend`, so "hover + // style" will remain for user view) + + // click event should always be triggered no matter whether + // there is gestrue event. System click can not be prevented. + if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) { + localDOMHandlers.click.call(this, event); + } + }, + + pointerdown: function (event) { + localDOMHandlers.mousedown.call(this, event); + + // if (useMSGuesture(this, event)) { + // this._msGesture.addPointer(event.pointerId); + // } + }, + + pointermove: function (event) { + // FIXME + // pointermove is so sensitive that it always triggered when + // tap(click) on touch screen, which affect some judgement in + // upper application. So, we dont support mousemove on MS touch + // device yet. + if (!isPointerFromTouch(event)) { + localDOMHandlers.mousemove.call(this, event); + } + }, + + pointerup: function (event) { + localDOMHandlers.mouseup.call(this, event); + }, + + pointerout: function (event) { + // pointerout will be triggered when tap on touch screen + // (IE11+/Edge on MS Surface) after click event triggered, + // which is inconsistent with the mousout behavior we defined + // in touchend. So we unify them. + // (check localDOMHandlers.touchend for detailed explanation) + if (!isPointerFromTouch(event)) { + localDOMHandlers.mouseout.call(this, event); + } + } + +}; + +/** + * Othere DOM UI Event handlers for zr dom. + * @this {HandlerProxy} + */ +each$1(['click', 'mousewheel', 'dblclick', 'contextmenu'], function (name) { + localDOMHandlers[name] = function (event) { + event = normalizeEvent(this.dom, event); + this.trigger(name, event); + }; +}); + + +/** + * DOM UI Event handlers for global page. + * + * [Caution]: + * those handlers should both support in capture phase and bubble phase! + * + * @this {HandlerProxy} + */ +var globalDOMHandlers = { + + pointermove: function (event) { + // FIXME + // pointermove is so sensitive that it always triggered when + // tap(click) on touch screen, which affect some judgement in + // upper application. So, we dont support mousemove on MS touch + // device yet. + if (!isPointerFromTouch(event)) { + globalDOMHandlers.mousemove.call(this, event); + } + }, + + pointerup: function (event) { + globalDOMHandlers.mouseup.call(this, event); + }, + + mousemove: function (event) { + this.trigger('mousemove', event); + }, + + mouseup: function (event) { + var pointerCaptureReleasing = this._pointerCapturing; + + togglePointerCapture(this, false); + + this.trigger('mouseup', event); + + if (pointerCaptureReleasing) { + event.zrEventControl = 'only_globalout'; + this.trigger('mouseout', event); + } + } + +}; + + +/** + * @param {HandlerProxy} instance + * @param {DOMHandlerScope} scope + */ +function mountLocalDOMEventListeners(instance, scope) { + var domHandlers = scope.domHandlers; + + if (env$1.pointerEventsSupported) { // Only IE11+/Edge + // 1. On devices that both enable touch and mouse (e.g., MS Surface and lenovo X240), + // IE11+/Edge do not trigger touch event, but trigger pointer event and mouse event + // at the same time. + // 2. On MS Surface, it probablely only trigger mousedown but no mouseup when tap on + // screen, which do not occurs in pointer event. + // So we use pointer event to both detect touch gesture and mouse behavior. + each$1(localNativeListenerNames.pointer, function (nativeEventName) { + mountSingleDOMEventListener(scope, nativeEventName, function (event) { + // markTriggeredFromLocal(event); + domHandlers[nativeEventName].call(instance, event); + }); + }); + + // FIXME + // Note: MS Gesture require CSS touch-action set. But touch-action is not reliable, + // which does not prevent defuault behavior occasionally (which may cause view port + // zoomed in but use can not zoom it back). And event.preventDefault() does not work. + // So we have to not to use MSGesture and not to support touchmove and pinch on MS + // touch screen. And we only support click behavior on MS touch screen now. + + // MS Gesture Event is only supported on IE11+/Edge and on Windows 8+. + // We dont support touch on IE on win7. + // See + // if (typeof MSGesture === 'function') { + // (this._msGesture = new MSGesture()).target = dom; // jshint ignore:line + // dom.addEventListener('MSGestureChange', onMSGestureChange); + // } + } + else { + if (env$1.touchEventsSupported) { + each$1(localNativeListenerNames.touch, function (nativeEventName) { + mountSingleDOMEventListener(scope, nativeEventName, function (event) { + // markTriggeredFromLocal(event); + domHandlers[nativeEventName].call(instance, event); + setTouchTimer(scope); + }); + }); + // Handler of 'mouseout' event is needed in touch mode, which will be mounted below. + // addEventListener(root, 'mouseout', this._mouseoutHandler); + } + + // 1. Considering some devices that both enable touch and mouse event (like on MS Surface + // and lenovo X240, @see #2350), we make mouse event be always listened, otherwise + // mouse event can not be handle in those devices. + // 2. On MS Surface, Chrome will trigger both touch event and mouse event. How to prevent + // mouseevent after touch event triggered, see `setTouchTimer`. + each$1(localNativeListenerNames.mouse, function (nativeEventName) { + mountSingleDOMEventListener(scope, nativeEventName, function (event) { + event = getNativeEvent(event); + if (!scope.touching) { + // markTriggeredFromLocal(event); + domHandlers[nativeEventName].call(instance, event); + } + }); + }); + } +} + +/** + * @param {HandlerProxy} instance + * @param {DOMHandlerScope} scope + */ +function mountGlobalDOMEventListeners(instance, scope) { + // Only IE11+/Edge. See the comment in `mountLocalDOMEventListeners`. + if (env$1.pointerEventsSupported) { + each$1(globalNativeListenerNames.pointer, mount); + } + // Touch event has implemented "drag outside" so we do not mount global listener for touch event. + // (see https://www.w3.org/TR/touch-events/#the-touchmove-event) + // We do not consider "both-support-touch-and-mouse device" for this feature (see the comment of + // `mountLocalDOMEventListeners`) to avoid bugs util some requirements come. + else if (!env$1.touchEventsSupported) { + each$1(globalNativeListenerNames.mouse, mount); + } + + function mount(nativeEventName) { + function nativeEventListener(event) { + event = getNativeEvent(event); + // See the reason in [Drag outside] in `Handler.js` + // This checking supports both `useCapture` or not. + // PENDING: if there is performance issue in some devices, + // we probably can not use `useCapture` and change a easier + // to judes whether local (mark). + if (!isLocalEl(instance, event.target)) { + event = normalizeGlobalEvent(instance, event); + scope.domHandlers[nativeEventName].call(instance, event); + } + } + mountSingleDOMEventListener( + scope, nativeEventName, nativeEventListener, + {capture: true} // See [Drag Outside] in `Handler.js` + ); + } +} + +function mountSingleDOMEventListener(scope, nativeEventName, listener, opt) { + scope.mounted[nativeEventName] = listener; + scope.listenerOpts[nativeEventName] = opt; + addEventListener(scope.domTarget, eventNameFix(nativeEventName), listener, opt); +} + +function unmountDOMEventListeners(scope) { + var mounted = scope.mounted; + for (var nativeEventName in mounted) { + if (mounted.hasOwnProperty(nativeEventName)) { + removeEventListener( + scope.domTarget, eventNameFix(nativeEventName), mounted[nativeEventName], + scope.listenerOpts[nativeEventName] + ); + } + } + scope.mounted = {}; +} + +/** + * See [Drag Outside] in `Handler.js`. + * @implement + * @param {boolean} isPointerCapturing Should never be `null`/`undefined`. + * `true`: start to capture pointer if it is not capturing. + * `false`: end the capture if it is capturing. + */ +function togglePointerCapture(instance, isPointerCapturing) { + instance._mayPointerCapture = null; + + if (globalEventSupported && (instance._pointerCapturing ^ isPointerCapturing)) { + instance._pointerCapturing = isPointerCapturing; + + var globalHandlerScope = instance._globalHandlerScope; + isPointerCapturing + ? mountGlobalDOMEventListeners(instance, globalHandlerScope) + : unmountDOMEventListeners(globalHandlerScope); + } +} + +/** + * @inner + * @class + */ +function DOMHandlerScope(domTarget, domHandlers) { + this.domTarget = domTarget; + this.domHandlers = domHandlers; + + // Key: eventName, value: mounted handler funcitons. + // Used for unmount. + this.mounted = {}; + this.listenerOpts = {}; + + this.touchTimer = null; + this.touching = false; +} + +/** + * @public + * @class + */ +function HandlerDomProxy(dom, painterRoot) { + Eventful.call(this); + + this.dom = dom; + this.painterRoot = painterRoot; + + this._localHandlerScope = new DOMHandlerScope(dom, localDOMHandlers); + + if (globalEventSupported) { + this._globalHandlerScope = new DOMHandlerScope(document, globalDOMHandlers); + } + + /** + * @type {boolean} + */ + this._pointerCapturing = false; + /** + * @type {Array.} [x, y] or null. + */ + this._mayPointerCapture = null; + + mountLocalDOMEventListeners(this, this._localHandlerScope); +} + +var handlerDomProxyProto = HandlerDomProxy.prototype; + +handlerDomProxyProto.dispose = function () { + unmountDOMEventListeners(this._localHandlerScope); + if (globalEventSupported) { + unmountDOMEventListeners(this._globalHandlerScope); + } +}; + +handlerDomProxyProto.setCursor = function (cursorStyle) { + this.dom.style && (this.dom.style.cursor = cursorStyle || 'default'); +}; + + +mixin(HandlerDomProxy, Eventful); + +/*! +* ZRender, a high performance 2d drawing library. +* +* Copyright (c) 2013, Baidu Inc. +* All rights reserved. +* +* LICENSE +* https://github.com/ecomfe/zrender/blob/master/LICENSE.txt +*/ + +var useVML = !env$1.canvasSupported; + +var painterCtors = { + canvas: Painter +}; + +var instances$1 = {}; // ZRender实例map索引 + +/** + * @type {string} + */ +var version$1 = '4.2.0'; + +/** + * Initializing a zrender instance + * @param {HTMLElement} dom + * @param {Object} [opts] + * @param {string} [opts.renderer='canvas'] 'canvas' or 'svg' + * @param {number} [opts.devicePixelRatio] + * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined) + * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined) + * @return {module:zrender/ZRender} + */ +function init$1(dom, opts) { + var zr = new ZRender(guid(), dom, opts); + instances$1[zr.id] = zr; + return zr; +} + +/** + * Dispose zrender instance + * @param {module:zrender/ZRender} zr + */ +function dispose$1(zr) { + if (zr) { + zr.dispose(); + } + else { + for (var key in instances$1) { + if (instances$1.hasOwnProperty(key)) { + instances$1[key].dispose(); + } + } + instances$1 = {}; + } + + return this; +} + +/** + * Get zrender instance by id + * @param {string} id zrender instance id + * @return {module:zrender/ZRender} + */ +function getInstance(id) { + return instances$1[id]; +} + +function registerPainter(name, Ctor) { + painterCtors[name] = Ctor; +} + +function delInstance(id) { + delete instances$1[id]; +} + +/** + * @module zrender/ZRender + */ +/** + * @constructor + * @alias module:zrender/ZRender + * @param {string} id + * @param {HTMLElement} dom + * @param {Object} opts + * @param {string} [opts.renderer='canvas'] 'canvas' or 'svg' + * @param {number} [opts.devicePixelRatio] + * @param {number} [opts.width] Can be 'auto' (the same as null/undefined) + * @param {number} [opts.height] Can be 'auto' (the same as null/undefined) + */ +var ZRender = function (id, dom, opts) { + + opts = opts || {}; + + /** + * @type {HTMLDomElement} + */ + this.dom = dom; + + /** + * @type {string} + */ + this.id = id; + + var self = this; + var storage = new Storage(); + + var rendererType = opts.renderer; + // TODO WebGL + if (useVML) { + if (!painterCtors.vml) { + throw new Error('You need to require \'zrender/vml/vml\' to support IE8'); + } + rendererType = 'vml'; + } + else if (!rendererType || !painterCtors[rendererType]) { + rendererType = 'canvas'; + } + var painter = new painterCtors[rendererType](dom, storage, opts, id); + + this.storage = storage; + this.painter = painter; + + var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot(), painter.root) : null; + this.handler = new Handler(storage, painter, handerProxy, painter.root); + + /** + * @type {module:zrender/animation/Animation} + */ + this.animation = new Animation({ + stage: { + update: bind(this.flush, this) + } + }); + this.animation.start(); + + /** + * @type {boolean} + * @private + */ + this._needsRefresh; + + // 修改 storage.delFromStorage, 每次删除元素之前删除动画 + // FIXME 有点ugly + var oldDelFromStorage = storage.delFromStorage; + var oldAddToStorage = storage.addToStorage; + + storage.delFromStorage = function (el) { + oldDelFromStorage.call(storage, el); + + el && el.removeSelfFromZr(self); + }; + + storage.addToStorage = function (el) { + oldAddToStorage.call(storage, el); + + el.addSelfToZr(self); + }; +}; + +ZRender.prototype = { + + constructor: ZRender, + /** + * 获取实例唯一标识 + * @return {string} + */ + getId: function () { + return this.id; + }, + + /** + * 添加元素 + * @param {module:zrender/Element} el + */ + add: function (el) { + this.storage.addRoot(el); + this._needsRefresh = true; + }, + + /** + * 删除元素 + * @param {module:zrender/Element} el + */ + remove: function (el) { + this.storage.delRoot(el); + this._needsRefresh = true; + }, + + /** + * Change configuration of layer + * @param {string} zLevel + * @param {Object} config + * @param {string} [config.clearColor=0] Clear color + * @param {string} [config.motionBlur=false] If enable motion blur + * @param {number} [config.lastFrameAlpha=0.7] Motion blur factor. Larger value cause longer trailer + */ + configLayer: function (zLevel, config) { + if (this.painter.configLayer) { + this.painter.configLayer(zLevel, config); + } + this._needsRefresh = true; + }, + + /** + * Set background color + * @param {string} backgroundColor + */ + setBackgroundColor: function (backgroundColor) { + if (this.painter.setBackgroundColor) { + this.painter.setBackgroundColor(backgroundColor); + } + this._needsRefresh = true; + }, + + /** + * Repaint the canvas immediately + */ + refreshImmediately: function () { + // var start = new Date(); + + // Clear needsRefresh ahead to avoid something wrong happens in refresh + // Or it will cause zrender refreshes again and again. + this._needsRefresh = this._needsRefreshHover = false; + this.painter.refresh(); + // Avoid trigger zr.refresh in Element#beforeUpdate hook + this._needsRefresh = this._needsRefreshHover = false; + + // var end = new Date(); + // var log = document.getElementById('log'); + // if (log) { + // log.innerHTML = log.innerHTML + '
    ' + (end - start); + // } + }, + + /** + * Mark and repaint the canvas in the next frame of browser + */ + refresh: function () { + this._needsRefresh = true; + }, + + /** + * Perform all refresh + */ + flush: function () { + var triggerRendered; + + if (this._needsRefresh) { + triggerRendered = true; + this.refreshImmediately(); + } + if (this._needsRefreshHover) { + triggerRendered = true; + this.refreshHoverImmediately(); + } + + triggerRendered && this.trigger('rendered'); + }, + + /** + * Add element to hover layer + * @param {module:zrender/Element} el + * @param {Object} style + */ + addHover: function (el, style) { + if (this.painter.addHover) { + var elMirror = this.painter.addHover(el, style); + this.refreshHover(); + return elMirror; + } + }, + + /** + * Add element from hover layer + * @param {module:zrender/Element} el + */ + removeHover: function (el) { + if (this.painter.removeHover) { + this.painter.removeHover(el); + this.refreshHover(); + } + }, + + /** + * Clear all hover elements in hover layer + * @param {module:zrender/Element} el + */ + clearHover: function () { + if (this.painter.clearHover) { + this.painter.clearHover(); + this.refreshHover(); + } + }, + + /** + * Refresh hover in next frame + */ + refreshHover: function () { + this._needsRefreshHover = true; + }, + + /** + * Refresh hover immediately + */ + refreshHoverImmediately: function () { + this._needsRefreshHover = false; + this.painter.refreshHover && this.painter.refreshHover(); + }, + + /** + * Resize the canvas. + * Should be invoked when container size is changed + * @param {Object} [opts] + * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined) + * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined) + */ + resize: function (opts) { + opts = opts || {}; + this.painter.resize(opts.width, opts.height); + this.handler.resize(); + }, + + /** + * Stop and clear all animation immediately + */ + clearAnimation: function () { + this.animation.clear(); + }, + + /** + * Get container width + */ + getWidth: function () { + return this.painter.getWidth(); + }, + + /** + * Get container height + */ + getHeight: function () { + return this.painter.getHeight(); + }, + + /** + * Export the canvas as Base64 URL + * @param {string} type + * @param {string} [backgroundColor='#fff'] + * @return {string} Base64 URL + */ + // toDataURL: function(type, backgroundColor) { + // return this.painter.getRenderedCanvas({ + // backgroundColor: backgroundColor + // }).toDataURL(type); + // }, + + /** + * Converting a path to image. + * It has much better performance of drawing image rather than drawing a vector path. + * @param {module:zrender/graphic/Path} e + * @param {number} width + * @param {number} height + */ + pathToImage: function (e, dpr) { + return this.painter.pathToImage(e, dpr); + }, + + /** + * Set default cursor + * @param {string} [cursorStyle='default'] 例如 crosshair + */ + setCursorStyle: function (cursorStyle) { + this.handler.setCursorStyle(cursorStyle); + }, + + /** + * Find hovered element + * @param {number} x + * @param {number} y + * @return {Object} {target, topTarget} + */ + findHover: function (x, y) { + return this.handler.findHover(x, y); + }, + + /** + * Bind event + * + * @param {string} eventName Event name + * @param {Function} eventHandler Handler function + * @param {Object} [context] Context object + */ + on: function (eventName, eventHandler, context) { + this.handler.on(eventName, eventHandler, context); + }, + + /** + * Unbind event + * @param {string} eventName Event name + * @param {Function} [eventHandler] Handler function + */ + off: function (eventName, eventHandler) { + this.handler.off(eventName, eventHandler); + }, + + /** + * Trigger event manually + * + * @param {string} eventName Event name + * @param {event=} event Event object + */ + trigger: function (eventName, event) { + this.handler.trigger(eventName, event); + }, + + + /** + * Clear all objects and the canvas. + */ + clear: function () { + this.storage.delRoot(); + this.painter.clear(); + }, + + /** + * Dispose self. + */ + dispose: function () { + this.animation.stop(); + + this.clear(); + this.storage.dispose(); + this.painter.dispose(); + this.handler.dispose(); + + this.animation = + this.storage = + this.painter = + this.handler = null; + + delInstance(this.id); + } +}; + + + +var zrender = (Object.freeze || Object)({ + version: version$1, + init: init$1, + dispose: dispose$1, + getInstance: getInstance, + registerPainter: registerPainter +}); + +/* +* 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. +*/ + +var each$2 = each$1; +var isObject$2 = isObject$1; +var isArray$1 = isArray; + +/** + * Make the name displayable. But we should + * make sure it is not duplicated with user + * specified name, so use '\0'; + */ +var DUMMY_COMPONENT_NAME_PREFIX = 'series\0'; + +/** + * If value is not array, then translate it to array. + * @param {*} value + * @return {Array} [value] or value + */ +function normalizeToArray(value) { + return value instanceof Array + ? value + : value == null + ? [] + : [value]; +} + +/** + * Sync default option between normal and emphasis like `position` and `show` + * In case some one will write code like + * label: { + * show: false, + * position: 'outside', + * fontSize: 18 + * }, + * emphasis: { + * label: { show: true } + * } + * @param {Object} opt + * @param {string} key + * @param {Array.} subOpts + */ +function defaultEmphasis(opt, key, subOpts) { + // Caution: performance sensitive. + if (opt) { + opt[key] = opt[key] || {}; + opt.emphasis = opt.emphasis || {}; + opt.emphasis[key] = opt.emphasis[key] || {}; + + // Default emphasis option from normal + for (var i = 0, len = subOpts.length; i < len; i++) { + var subOptName = subOpts[i]; + if (!opt.emphasis[key].hasOwnProperty(subOptName) + && opt[key].hasOwnProperty(subOptName) + ) { + opt.emphasis[key][subOptName] = opt[key][subOptName]; + } + } + } +} + +var TEXT_STYLE_OPTIONS = [ + 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily', + 'rich', 'tag', 'color', 'textBorderColor', 'textBorderWidth', + 'width', 'height', 'lineHeight', 'align', 'verticalAlign', 'baseline', + 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY', + 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY', + 'backgroundColor', 'borderColor', 'borderWidth', 'borderRadius', 'padding' +]; + +// modelUtil.LABEL_OPTIONS = modelUtil.TEXT_STYLE_OPTIONS.concat([ +// 'position', 'offset', 'rotate', 'origin', 'show', 'distance', 'formatter', +// 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily', +// // FIXME: deprecated, check and remove it. +// 'textStyle' +// ]); + +/** + * The method do not ensure performance. + * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}] + * This helper method retieves value from data. + * @param {string|number|Date|Array|Object} dataItem + * @return {number|string|Date|Array.} + */ +function getDataItemValue(dataItem) { + return (isObject$2(dataItem) && !isArray$1(dataItem) && !(dataItem instanceof Date)) + ? dataItem.value : dataItem; +} + +/** + * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}] + * This helper method determine if dataItem has extra option besides value + * @param {string|number|Date|Array|Object} dataItem + */ +function isDataItemOption(dataItem) { + return isObject$2(dataItem) + && !(dataItem instanceof Array); + // // markLine data can be array + // && !(dataItem[0] && isObject(dataItem[0]) && !(dataItem[0] instanceof Array)); +} + +/** + * Mapping to exists for merge. + * + * @public + * @param {Array.|Array.} exists + * @param {Object|Array.} newCptOptions + * @return {Array.} Result, like [{exist: ..., option: ...}, {}], + * index of which is the same as exists. + */ +function mappingToExists(exists, newCptOptions) { + // Mapping by the order by original option (but not order of + // new option) in merge mode. Because we should ensure + // some specified index (like xAxisIndex) is consistent with + // original option, which is easy to understand, espatially in + // media query. And in most case, merge option is used to + // update partial option but not be expected to change order. + newCptOptions = (newCptOptions || []).slice(); + + var result = map(exists || [], function (obj, index) { + return {exist: obj}; + }); + + // Mapping by id or name if specified. + each$2(newCptOptions, function (cptOption, index) { + if (!isObject$2(cptOption)) { + return; + } + + // id has highest priority. + for (var i = 0; i < result.length; i++) { + if (!result[i].option // Consider name: two map to one. + && cptOption.id != null + && result[i].exist.id === cptOption.id + '' + ) { + result[i].option = cptOption; + newCptOptions[index] = null; + return; + } + } + + for (var i = 0; i < result.length; i++) { + var exist = result[i].exist; + if (!result[i].option // Consider name: two map to one. + // Can not match when both ids exist but different. + && (exist.id == null || cptOption.id == null) + && cptOption.name != null + && !isIdInner(cptOption) + && !isIdInner(exist) + && exist.name === cptOption.name + '' + ) { + result[i].option = cptOption; + newCptOptions[index] = null; + return; + } + } + }); + + // Otherwise mapping by index. + each$2(newCptOptions, function (cptOption, index) { + if (!isObject$2(cptOption)) { + return; + } + + var i = 0; + for (; i < result.length; i++) { + var exist = result[i].exist; + if (!result[i].option + // Existing model that already has id should be able to + // mapped to (because after mapping performed model may + // be assigned with a id, whish should not affect next + // mapping), except those has inner id. + && !isIdInner(exist) + // Caution: + // Do not overwrite id. But name can be overwritten, + // because axis use name as 'show label text'. + // 'exist' always has id and name and we dont + // need to check it. + && cptOption.id == null + ) { + result[i].option = cptOption; + break; + } + } + + if (i >= result.length) { + result.push({option: cptOption}); + } + }); + + return result; +} + +/** + * Make id and name for mapping result (result of mappingToExists) + * into `keyInfo` field. + * + * @public + * @param {Array.} Result, like [{exist: ..., option: ...}, {}], + * which order is the same as exists. + * @return {Array.} The input. + */ +function makeIdAndName(mapResult) { + // We use this id to hash component models and view instances + // in echarts. id can be specified by user, or auto generated. + + // The id generation rule ensures new view instance are able + // to mapped to old instance when setOption are called in + // no-merge mode. So we generate model id by name and plus + // type in view id. + + // name can be duplicated among components, which is convenient + // to specify multi components (like series) by one name. + + // Ensure that each id is distinct. + var idMap = createHashMap(); + + each$2(mapResult, function (item, index) { + var existCpt = item.exist; + existCpt && idMap.set(existCpt.id, item); + }); + + each$2(mapResult, function (item, index) { + var opt = item.option; + + assert$1( + !opt || opt.id == null || !idMap.get(opt.id) || idMap.get(opt.id) === item, + 'id duplicates: ' + (opt && opt.id) + ); + + opt && opt.id != null && idMap.set(opt.id, item); + !item.keyInfo && (item.keyInfo = {}); + }); + + // Make name and id. + each$2(mapResult, function (item, index) { + var existCpt = item.exist; + var opt = item.option; + var keyInfo = item.keyInfo; + + if (!isObject$2(opt)) { + return; + } + + // name can be overwitten. Consider case: axis.name = '20km'. + // But id generated by name will not be changed, which affect + // only in that case: setOption with 'not merge mode' and view + // instance will be recreated, which can be accepted. + keyInfo.name = opt.name != null + ? opt.name + '' + : existCpt + ? existCpt.name + // Avoid diffferent series has the same name, + // because name may be used like in color pallet. + : DUMMY_COMPONENT_NAME_PREFIX + index; + + if (existCpt) { + keyInfo.id = existCpt.id; + } + else if (opt.id != null) { + keyInfo.id = opt.id + ''; + } + else { + // Consider this situatoin: + // optionA: [{name: 'a'}, {name: 'a'}, {..}] + // optionB [{..}, {name: 'a'}, {name: 'a'}] + // Series with the same name between optionA and optionB + // should be mapped. + var idNum = 0; + do { + keyInfo.id = '\0' + keyInfo.name + '\0' + idNum++; + } + while (idMap.get(keyInfo.id)); + } + + idMap.set(keyInfo.id, item); + }); +} + +function isNameSpecified(componentModel) { + var name = componentModel.name; + // Is specified when `indexOf` get -1 or > 0. + return !!(name && name.indexOf(DUMMY_COMPONENT_NAME_PREFIX)); +} + +/** + * @public + * @param {Object} cptOption + * @return {boolean} + */ +function isIdInner(cptOption) { + return isObject$2(cptOption) + && cptOption.id + && (cptOption.id + '').indexOf('\0_ec_\0') === 0; +} + +/** + * A helper for removing duplicate items between batchA and batchB, + * and in themselves, and categorize by series. + * + * @param {Array.} batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...] + * @param {Array.} batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...] + * @return {Array., Array.>} result: [resultBatchA, resultBatchB] + */ +function compressBatches(batchA, batchB) { + var mapA = {}; + var mapB = {}; + + makeMap(batchA || [], mapA); + makeMap(batchB || [], mapB, mapA); + + return [mapToArray(mapA), mapToArray(mapB)]; + + function makeMap(sourceBatch, map$$1, otherMap) { + for (var i = 0, len = sourceBatch.length; i < len; i++) { + var seriesId = sourceBatch[i].seriesId; + var dataIndices = normalizeToArray(sourceBatch[i].dataIndex); + var otherDataIndices = otherMap && otherMap[seriesId]; + + for (var j = 0, lenj = dataIndices.length; j < lenj; j++) { + var dataIndex = dataIndices[j]; + + if (otherDataIndices && otherDataIndices[dataIndex]) { + otherDataIndices[dataIndex] = null; + } + else { + (map$$1[seriesId] || (map$$1[seriesId] = {}))[dataIndex] = 1; + } + } + } + } + + function mapToArray(map$$1, isData) { + var result = []; + for (var i in map$$1) { + if (map$$1.hasOwnProperty(i) && map$$1[i] != null) { + if (isData) { + result.push(+i); + } + else { + var dataIndices = mapToArray(map$$1[i], true); + dataIndices.length && result.push({seriesId: i, dataIndex: dataIndices}); + } + } + } + return result; + } +} + +/** + * @param {module:echarts/data/List} data + * @param {Object} payload Contains dataIndex (means rawIndex) / dataIndexInside / name + * each of which can be Array or primary type. + * @return {number|Array.} dataIndex If not found, return undefined/null. + */ +function queryDataIndex(data, payload) { + if (payload.dataIndexInside != null) { + return payload.dataIndexInside; + } + else if (payload.dataIndex != null) { + return isArray(payload.dataIndex) + ? map(payload.dataIndex, function (value) { + return data.indexOfRawIndex(value); + }) + : data.indexOfRawIndex(payload.dataIndex); + } + else if (payload.name != null) { + return isArray(payload.name) + ? map(payload.name, function (value) { + return data.indexOfName(value); + }) + : data.indexOfName(payload.name); + } +} + +/** + * Enable property storage to any host object. + * Notice: Serialization is not supported. + * + * For example: + * var inner = zrUitl.makeInner(); + * + * function some1(hostObj) { + * inner(hostObj).someProperty = 1212; + * ... + * } + * function some2() { + * var fields = inner(this); + * fields.someProperty1 = 1212; + * fields.someProperty2 = 'xx'; + * ... + * } + * + * @return {Function} + */ +function makeInner() { + // Consider different scope by es module import. + var key = '__\0ec_inner_' + innerUniqueIndex++ + '_' + Math.random().toFixed(5); + return function (hostObj) { + return hostObj[key] || (hostObj[key] = {}); + }; +} +var innerUniqueIndex = 0; + +/** + * @param {module:echarts/model/Global} ecModel + * @param {string|Object} finder + * If string, e.g., 'geo', means {geoIndex: 0}. + * If Object, could contain some of these properties below: + * { + * seriesIndex, seriesId, seriesName, + * geoIndex, geoId, geoName, + * bmapIndex, bmapId, bmapName, + * xAxisIndex, xAxisId, xAxisName, + * yAxisIndex, yAxisId, yAxisName, + * gridIndex, gridId, gridName, + * ... (can be extended) + * } + * Each properties can be number|string|Array.|Array. + * For example, a finder could be + * { + * seriesIndex: 3, + * geoId: ['aa', 'cc'], + * gridName: ['xx', 'rr'] + * } + * xxxIndex can be set as 'all' (means all xxx) or 'none' (means not specify) + * If nothing or null/undefined specified, return nothing. + * @param {Object} [opt] + * @param {string} [opt.defaultMainType] + * @param {Array.} [opt.includeMainTypes] + * @return {Object} result like: + * { + * seriesModels: [seriesModel1, seriesModel2], + * seriesModel: seriesModel1, // The first model + * geoModels: [geoModel1, geoModel2], + * geoModel: geoModel1, // The first model + * ... + * } + */ +function parseFinder(ecModel, finder, opt) { + if (isString(finder)) { + var obj = {}; + obj[finder + 'Index'] = 0; + finder = obj; + } + + var defaultMainType = opt && opt.defaultMainType; + if (defaultMainType + && !has(finder, defaultMainType + 'Index') + && !has(finder, defaultMainType + 'Id') + && !has(finder, defaultMainType + 'Name') + ) { + finder[defaultMainType + 'Index'] = 0; + } + + var result = {}; + + each$2(finder, function (value, key) { + var value = finder[key]; + + // Exclude 'dataIndex' and other illgal keys. + if (key === 'dataIndex' || key === 'dataIndexInside') { + result[key] = value; + return; + } + + var parsedKey = key.match(/^(\w+)(Index|Id|Name)$/) || []; + var mainType = parsedKey[1]; + var queryType = (parsedKey[2] || '').toLowerCase(); + + if (!mainType + || !queryType + || value == null + || (queryType === 'index' && value === 'none') + || (opt && opt.includeMainTypes && indexOf(opt.includeMainTypes, mainType) < 0) + ) { + return; + } + + var queryParam = {mainType: mainType}; + if (queryType !== 'index' || value !== 'all') { + queryParam[queryType] = value; + } + + var models = ecModel.queryComponents(queryParam); + result[mainType + 'Models'] = models; + result[mainType + 'Model'] = models[0]; + }); + + return result; +} + +function has(obj, prop) { + return obj && obj.hasOwnProperty(prop); +} + +function setAttribute(dom, key, value) { + dom.setAttribute + ? dom.setAttribute(key, value) + : (dom[key] = value); +} + +function getAttribute(dom, key) { + return dom.getAttribute + ? dom.getAttribute(key) + : dom[key]; +} + +function getTooltipRenderMode(renderModeOption) { + if (renderModeOption === 'auto') { + // Using html when `document` exists, use richText otherwise + return env$1.domSupported ? 'html' : 'richText'; + } + else { + return renderModeOption || 'html'; + } +} + +/** + * Group a list by key. + * + * @param {Array} array + * @param {Function} getKey + * param {*} Array item + * return {string} key + * @return {Object} Result + * {Array}: keys, + * {module:zrender/core/util/HashMap} buckets: {key -> Array} + */ +function groupData(array, getKey) { + var buckets = createHashMap(); + var keys = []; + + each$1(array, function (item) { + var key = getKey(item); + (buckets.get(key) + || (keys.push(key), buckets.set(key, [])) + ).push(item); + }); + + return {keys: keys, buckets: buckets}; +} + +/* +* 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. +*/ + +var TYPE_DELIMITER = '.'; +var IS_CONTAINER = '___EC__COMPONENT__CONTAINER___'; + +/** + * Notice, parseClassType('') should returns {main: '', sub: ''} + * @public + */ +function parseClassType$1(componentType) { + var ret = {main: '', sub: ''}; + if (componentType) { + componentType = componentType.split(TYPE_DELIMITER); + ret.main = componentType[0] || ''; + ret.sub = componentType[1] || ''; + } + return ret; +} + +/** + * @public + */ +function checkClassType(componentType) { + assert$1( + /^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(componentType), + 'componentType "' + componentType + '" illegal' + ); +} + +/** + * @public + */ +function enableClassExtend(RootClass, mandatoryMethods) { + + RootClass.$constructor = RootClass; + RootClass.extend = function (proto) { + + if (__DEV__) { + each$1(mandatoryMethods, function (method) { + if (!proto[method]) { + console.warn( + 'Method `' + method + '` should be implemented' + + (proto.type ? ' in ' + proto.type : '') + '.' + ); + } + }); + } + + var superClass = this; + var ExtendedClass = function () { + if (!proto.$constructor) { + superClass.apply(this, arguments); + } + else { + proto.$constructor.apply(this, arguments); + } + }; + + extend(ExtendedClass.prototype, proto); + + ExtendedClass.extend = this.extend; + ExtendedClass.superCall = superCall; + ExtendedClass.superApply = superApply; + inherits(ExtendedClass, this); + ExtendedClass.superClass = superClass; + + return ExtendedClass; + }; +} + +var classBase = 0; + +/** + * Can not use instanceof, consider different scope by + * cross domain or es module import in ec extensions. + * Mount a method "isInstance()" to Clz. + */ +function enableClassCheck(Clz) { + var classAttr = ['__\0is_clz', classBase++, Math.random().toFixed(3)].join('_'); + Clz.prototype[classAttr] = true; + + if (__DEV__) { + assert$1(!Clz.isInstance, 'The method "is" can not be defined.'); + } + + Clz.isInstance = function (obj) { + return !!(obj && obj[classAttr]); + }; +} + +// superCall should have class info, which can not be fetch from 'this'. +// Consider this case: +// class A has method f, +// class B inherits class A, overrides method f, f call superApply('f'), +// class C inherits class B, do not overrides method f, +// then when method of class C is called, dead loop occured. +function superCall(context, methodName) { + var args = slice(arguments, 2); + return this.superClass.prototype[methodName].apply(context, args); +} + +function superApply(context, methodName, args) { + return this.superClass.prototype[methodName].apply(context, args); +} + +/** + * @param {Object} entity + * @param {Object} options + * @param {boolean} [options.registerWhenExtend] + * @public + */ +function enableClassManagement(entity, options) { + options = options || {}; + + /** + * Component model classes + * key: componentType, + * value: + * componentClass, when componentType is 'xxx' + * or Object., when componentType is 'xxx.yy' + * @type {Object} + */ + var storage = {}; + + entity.registerClass = function (Clazz, componentType) { + if (componentType) { + checkClassType(componentType); + componentType = parseClassType$1(componentType); + + if (!componentType.sub) { + if (__DEV__) { + if (storage[componentType.main]) { + console.warn(componentType.main + ' exists.'); + } + } + storage[componentType.main] = Clazz; + } + else if (componentType.sub !== IS_CONTAINER) { + var container = makeContainer(componentType); + container[componentType.sub] = Clazz; + } + } + return Clazz; + }; + + entity.getClass = function (componentMainType, subType, throwWhenNotFound) { + var Clazz = storage[componentMainType]; + + if (Clazz && Clazz[IS_CONTAINER]) { + Clazz = subType ? Clazz[subType] : null; + } + + if (throwWhenNotFound && !Clazz) { + throw new Error( + !subType + ? componentMainType + '.' + 'type should be specified.' + : 'Component ' + componentMainType + '.' + (subType || '') + ' not exists. Load it first.' + ); + } + + return Clazz; + }; + + entity.getClassesByMainType = function (componentType) { + componentType = parseClassType$1(componentType); + + var result = []; + var obj = storage[componentType.main]; + + if (obj && obj[IS_CONTAINER]) { + each$1(obj, function (o, type) { + type !== IS_CONTAINER && result.push(o); + }); + } + else { + result.push(obj); + } + + return result; + }; + + entity.hasClass = function (componentType) { + // Just consider componentType.main. + componentType = parseClassType$1(componentType); + return !!storage[componentType.main]; + }; + + /** + * @return {Array.} Like ['aa', 'bb'], but can not be ['aa.xx'] + */ + entity.getAllClassMainTypes = function () { + var types = []; + each$1(storage, function (obj, type) { + types.push(type); + }); + return types; + }; + + /** + * If a main type is container and has sub types + * @param {string} mainType + * @return {boolean} + */ + entity.hasSubTypes = function (componentType) { + componentType = parseClassType$1(componentType); + var obj = storage[componentType.main]; + return obj && obj[IS_CONTAINER]; + }; + + entity.parseClassType = parseClassType$1; + + function makeContainer(componentType) { + var container = storage[componentType.main]; + if (!container || !container[IS_CONTAINER]) { + container = storage[componentType.main] = {}; + container[IS_CONTAINER] = true; + } + return container; + } + + if (options.registerWhenExtend) { + var originalExtend = entity.extend; + if (originalExtend) { + entity.extend = function (proto) { + var ExtendedClass = originalExtend.call(this, proto); + return entity.registerClass(ExtendedClass, proto.type); + }; + } + } + + return entity; +} + +/** + * @param {string|Array.} properties + */ + +/* +* 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. +*/ + +// TODO Parse shadow style +// TODO Only shallow path support +var makeStyleMapper = function (properties) { + // Normalize + for (var i = 0; i < properties.length; i++) { + if (!properties[i][1]) { + properties[i][1] = properties[i][0]; + } + } + return function (model, excludes, includes) { + var style = {}; + for (var i = 0; i < properties.length; i++) { + var propName = properties[i][1]; + if ((excludes && indexOf(excludes, propName) >= 0) + || (includes && indexOf(includes, propName) < 0) + ) { + continue; + } + var val = model.getShallow(propName); + if (val != null) { + style[properties[i][0]] = val; + } + } + return style; + }; +}; + +/* +* 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. +*/ + +var getLineStyle = makeStyleMapper( + [ + ['lineWidth', 'width'], + ['stroke', 'color'], + ['opacity'], + ['shadowBlur'], + ['shadowOffsetX'], + ['shadowOffsetY'], + ['shadowColor'] + ] +); + +var lineStyleMixin = { + getLineStyle: function (excludes) { + var style = getLineStyle(this, excludes); + // Always set lineDash whether dashed, otherwise we can not + // erase the previous style when assigning to el.style. + style.lineDash = this.getLineDash(style.lineWidth); + return style; + }, + + getLineDash: function (lineWidth) { + if (lineWidth == null) { + lineWidth = 1; + } + var lineType = this.get('type'); + var dotSize = Math.max(lineWidth, 2); + var dashSize = lineWidth * 4; + return (lineType === 'solid' || lineType == null) + // Use `false` but not `null` for the solid line here, because `null` might be + // ignored when assigning to `el.style`. e.g., when setting `lineStyle.type` as + // `'dashed'` and `emphasis.lineStyle.type` as `'solid'` in graph series, the + // `lineDash` gotten form the latter one is not able to erase that from the former + // one if using `null` here according to the emhpsis strategy in `util/graphic.js`. + ? false + : lineType === 'dashed' + ? [dashSize, dashSize] + : [dotSize, dotSize]; + } +}; + +/* +* 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. +*/ + +var getAreaStyle = makeStyleMapper( + [ + ['fill', 'color'], + ['shadowBlur'], + ['shadowOffsetX'], + ['shadowOffsetY'], + ['opacity'], + ['shadowColor'] + ] +); + +var areaStyleMixin = { + getAreaStyle: function (excludes, includes) { + return getAreaStyle(this, excludes, includes); + } +}; + +/** + * 曲线辅助模块 + * @module zrender/core/curve + * @author pissang(https://www.github.com/pissang) + */ + +var mathPow = Math.pow; +var mathSqrt$2 = Math.sqrt; + +var EPSILON$1 = 1e-8; +var EPSILON_NUMERIC = 1e-4; + +var THREE_SQRT = mathSqrt$2(3); +var ONE_THIRD = 1 / 3; + +// 临时变量 +var _v0 = create(); +var _v1 = create(); +var _v2 = create(); + +function isAroundZero(val) { + return val > -EPSILON$1 && val < EPSILON$1; +} +function isNotAroundZero$1(val) { + return val > EPSILON$1 || val < -EPSILON$1; +} +/** + * 计算三次贝塞尔值 + * @memberOf module:zrender/core/curve + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} p3 + * @param {number} t + * @return {number} + */ +function cubicAt(p0, p1, p2, p3, t) { + var onet = 1 - t; + return onet * onet * (onet * p0 + 3 * t * p1) + + t * t * (t * p3 + 3 * onet * p2); +} + +/** + * 计算三次贝塞尔导数值 + * @memberOf module:zrender/core/curve + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} p3 + * @param {number} t + * @return {number} + */ +function cubicDerivativeAt(p0, p1, p2, p3, t) { + var onet = 1 - t; + return 3 * ( + ((p1 - p0) * onet + 2 * (p2 - p1) * t) * onet + + (p3 - p2) * t * t + ); +} + +/** + * 计算三次贝塞尔方程根,使用盛金公式 + * @memberOf module:zrender/core/curve + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} p3 + * @param {number} val + * @param {Array.} roots + * @return {number} 有效根数目 + */ +function cubicRootAt(p0, p1, p2, p3, val, roots) { + // Evaluate roots of cubic functions + var a = p3 + 3 * (p1 - p2) - p0; + var b = 3 * (p2 - p1 * 2 + p0); + var c = 3 * (p1 - p0); + var d = p0 - val; + + var A = b * b - 3 * a * c; + var B = b * c - 9 * a * d; + var C = c * c - 3 * b * d; + + var n = 0; + + if (isAroundZero(A) && isAroundZero(B)) { + if (isAroundZero(b)) { + roots[0] = 0; + } + else { + var t1 = -c / b; //t1, t2, t3, b is not zero + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + } + } + else { + var disc = B * B - 4 * A * C; + + if (isAroundZero(disc)) { + var K = B / A; + var t1 = -b / a + K; // t1, a is not zero + var t2 = -K / 2; // t2, t3 + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + if (t2 >= 0 && t2 <= 1) { + roots[n++] = t2; + } + } + else if (disc > 0) { + var discSqrt = mathSqrt$2(disc); + var Y1 = A * b + 1.5 * a * (-B + discSqrt); + var Y2 = A * b + 1.5 * a * (-B - discSqrt); + if (Y1 < 0) { + Y1 = -mathPow(-Y1, ONE_THIRD); + } + else { + Y1 = mathPow(Y1, ONE_THIRD); + } + if (Y2 < 0) { + Y2 = -mathPow(-Y2, ONE_THIRD); + } + else { + Y2 = mathPow(Y2, ONE_THIRD); + } + var t1 = (-b - (Y1 + Y2)) / (3 * a); + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + } + else { + var T = (2 * A * b - 3 * a * B) / (2 * mathSqrt$2(A * A * A)); + var theta = Math.acos(T) / 3; + var ASqrt = mathSqrt$2(A); + var tmp = Math.cos(theta); + + var t1 = (-b - 2 * ASqrt * tmp) / (3 * a); + var t2 = (-b + ASqrt * (tmp + THREE_SQRT * Math.sin(theta))) / (3 * a); + var t3 = (-b + ASqrt * (tmp - THREE_SQRT * Math.sin(theta))) / (3 * a); + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + if (t2 >= 0 && t2 <= 1) { + roots[n++] = t2; + } + if (t3 >= 0 && t3 <= 1) { + roots[n++] = t3; + } + } + } + return n; +} + +/** + * 计算三次贝塞尔方程极限值的位置 + * @memberOf module:zrender/core/curve + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} p3 + * @param {Array.} extrema + * @return {number} 有效数目 + */ +function cubicExtrema(p0, p1, p2, p3, extrema) { + var b = 6 * p2 - 12 * p1 + 6 * p0; + var a = 9 * p1 + 3 * p3 - 3 * p0 - 9 * p2; + var c = 3 * p1 - 3 * p0; + + var n = 0; + if (isAroundZero(a)) { + if (isNotAroundZero$1(b)) { + var t1 = -c / b; + if (t1 >= 0 && t1 <= 1) { + extrema[n++] = t1; + } + } + } + else { + var disc = b * b - 4 * a * c; + if (isAroundZero(disc)) { + extrema[0] = -b / (2 * a); + } + else if (disc > 0) { + var discSqrt = mathSqrt$2(disc); + var t1 = (-b + discSqrt) / (2 * a); + var t2 = (-b - discSqrt) / (2 * a); + if (t1 >= 0 && t1 <= 1) { + extrema[n++] = t1; + } + if (t2 >= 0 && t2 <= 1) { + extrema[n++] = t2; + } + } + } + return n; +} + +/** + * 细分三次贝塞尔曲线 + * @memberOf module:zrender/core/curve + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} p3 + * @param {number} t + * @param {Array.} out + */ +function cubicSubdivide(p0, p1, p2, p3, t, out) { + var p01 = (p1 - p0) * t + p0; + var p12 = (p2 - p1) * t + p1; + var p23 = (p3 - p2) * t + p2; + + var p012 = (p12 - p01) * t + p01; + var p123 = (p23 - p12) * t + p12; + + var p0123 = (p123 - p012) * t + p012; + // Seg0 + out[0] = p0; + out[1] = p01; + out[2] = p012; + out[3] = p0123; + // Seg1 + out[4] = p0123; + out[5] = p123; + out[6] = p23; + out[7] = p3; +} + +/** + * 投射点到三次贝塞尔曲线上,返回投射距离。 + * 投射点有可能会有一个或者多个,这里只返回其中距离最短的一个。 + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @param {number} x + * @param {number} y + * @param {Array.} [out] 投射点 + * @return {number} + */ +function cubicProjectPoint( + x0, y0, x1, y1, x2, y2, x3, y3, + x, y, out +) { + // http://pomax.github.io/bezierinfo/#projections + var t; + var interval = 0.005; + var d = Infinity; + var prev; + var next; + var d1; + var d2; + + _v0[0] = x; + _v0[1] = y; + + // 先粗略估计一下可能的最小距离的 t 值 + // PENDING + for (var _t = 0; _t < 1; _t += 0.05) { + _v1[0] = cubicAt(x0, x1, x2, x3, _t); + _v1[1] = cubicAt(y0, y1, y2, y3, _t); + d1 = distSquare(_v0, _v1); + if (d1 < d) { + t = _t; + d = d1; + } + } + d = Infinity; + + // At most 32 iteration + for (var i = 0; i < 32; i++) { + if (interval < EPSILON_NUMERIC) { + break; + } + prev = t - interval; + next = t + interval; + // t - interval + _v1[0] = cubicAt(x0, x1, x2, x3, prev); + _v1[1] = cubicAt(y0, y1, y2, y3, prev); + + d1 = distSquare(_v1, _v0); + + if (prev >= 0 && d1 < d) { + t = prev; + d = d1; + } + else { + // t + interval + _v2[0] = cubicAt(x0, x1, x2, x3, next); + _v2[1] = cubicAt(y0, y1, y2, y3, next); + d2 = distSquare(_v2, _v0); + + if (next <= 1 && d2 < d) { + t = next; + d = d2; + } + else { + interval *= 0.5; + } + } + } + // t + if (out) { + out[0] = cubicAt(x0, x1, x2, x3, t); + out[1] = cubicAt(y0, y1, y2, y3, t); + } + // console.log(interval, i); + return mathSqrt$2(d); +} + +/** + * 计算二次方贝塞尔值 + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} t + * @return {number} + */ +function quadraticAt(p0, p1, p2, t) { + var onet = 1 - t; + return onet * (onet * p0 + 2 * t * p1) + t * t * p2; +} + +/** + * 计算二次方贝塞尔导数值 + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} t + * @return {number} + */ +function quadraticDerivativeAt(p0, p1, p2, t) { + return 2 * ((1 - t) * (p1 - p0) + t * (p2 - p1)); +} + +/** + * 计算二次方贝塞尔方程根 + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} t + * @param {Array.} roots + * @return {number} 有效根数目 + */ +function quadraticRootAt(p0, p1, p2, val, roots) { + var a = p0 - 2 * p1 + p2; + var b = 2 * (p1 - p0); + var c = p0 - val; + + var n = 0; + if (isAroundZero(a)) { + if (isNotAroundZero$1(b)) { + var t1 = -c / b; + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + } + } + else { + var disc = b * b - 4 * a * c; + if (isAroundZero(disc)) { + var t1 = -b / (2 * a); + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + } + else if (disc > 0) { + var discSqrt = mathSqrt$2(disc); + var t1 = (-b + discSqrt) / (2 * a); + var t2 = (-b - discSqrt) / (2 * a); + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + if (t2 >= 0 && t2 <= 1) { + roots[n++] = t2; + } + } + } + return n; +} + +/** + * 计算二次贝塞尔方程极限值 + * @memberOf module:zrender/core/curve + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @return {number} + */ +function quadraticExtremum(p0, p1, p2) { + var divider = p0 + p2 - 2 * p1; + if (divider === 0) { + // p1 is center of p0 and p2 + return 0.5; + } + else { + return (p0 - p1) / divider; + } +} + +/** + * 细分二次贝塞尔曲线 + * @memberOf module:zrender/core/curve + * @param {number} p0 + * @param {number} p1 + * @param {number} p2 + * @param {number} t + * @param {Array.} out + */ +function quadraticSubdivide(p0, p1, p2, t, out) { + var p01 = (p1 - p0) * t + p0; + var p12 = (p2 - p1) * t + p1; + var p012 = (p12 - p01) * t + p01; + + // Seg0 + out[0] = p0; + out[1] = p01; + out[2] = p012; + + // Seg1 + out[3] = p012; + out[4] = p12; + out[5] = p2; +} + +/** + * 投射点到二次贝塞尔曲线上,返回投射距离。 + * 投射点有可能会有一个或者多个,这里只返回其中距离最短的一个。 + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x + * @param {number} y + * @param {Array.} out 投射点 + * @return {number} + */ +function quadraticProjectPoint( + x0, y0, x1, y1, x2, y2, + x, y, out +) { + // http://pomax.github.io/bezierinfo/#projections + var t; + var interval = 0.005; + var d = Infinity; + + _v0[0] = x; + _v0[1] = y; + + // 先粗略估计一下可能的最小距离的 t 值 + // PENDING + for (var _t = 0; _t < 1; _t += 0.05) { + _v1[0] = quadraticAt(x0, x1, x2, _t); + _v1[1] = quadraticAt(y0, y1, y2, _t); + var d1 = distSquare(_v0, _v1); + if (d1 < d) { + t = _t; + d = d1; + } + } + d = Infinity; + + // At most 32 iteration + for (var i = 0; i < 32; i++) { + if (interval < EPSILON_NUMERIC) { + break; + } + var prev = t - interval; + var next = t + interval; + // t - interval + _v1[0] = quadraticAt(x0, x1, x2, prev); + _v1[1] = quadraticAt(y0, y1, y2, prev); + + var d1 = distSquare(_v1, _v0); + + if (prev >= 0 && d1 < d) { + t = prev; + d = d1; + } + else { + // t + interval + _v2[0] = quadraticAt(x0, x1, x2, next); + _v2[1] = quadraticAt(y0, y1, y2, next); + var d2 = distSquare(_v2, _v0); + if (next <= 1 && d2 < d) { + t = next; + d = d2; + } + else { + interval *= 0.5; + } + } + } + // t + if (out) { + out[0] = quadraticAt(x0, x1, x2, t); + out[1] = quadraticAt(y0, y1, y2, t); + } + // console.log(interval, i); + return mathSqrt$2(d); +} + +/** + * @author Yi Shen(https://github.com/pissang) + */ + +var mathMin$3 = Math.min; +var mathMax$3 = Math.max; +var mathSin$2 = Math.sin; +var mathCos$2 = Math.cos; +var PI2 = Math.PI * 2; + +var start = create(); +var end = create(); +var extremity = create(); + +/** + * 从顶点数组中计算出最小包围盒,写入`min`和`max`中 + * @module zrender/core/bbox + * @param {Array} points 顶点数组 + * @param {number} min + * @param {number} max + */ +function fromPoints(points, min$$1, max$$1) { + if (points.length === 0) { + return; + } + var p = points[0]; + var left = p[0]; + var right = p[0]; + var top = p[1]; + var bottom = p[1]; + var i; + + for (i = 1; i < points.length; i++) { + p = points[i]; + left = mathMin$3(left, p[0]); + right = mathMax$3(right, p[0]); + top = mathMin$3(top, p[1]); + bottom = mathMax$3(bottom, p[1]); + } + + min$$1[0] = left; + min$$1[1] = top; + max$$1[0] = right; + max$$1[1] = bottom; +} + +/** + * @memberOf module:zrender/core/bbox + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {Array.} min + * @param {Array.} max + */ +function fromLine(x0, y0, x1, y1, min$$1, max$$1) { + min$$1[0] = mathMin$3(x0, x1); + min$$1[1] = mathMin$3(y0, y1); + max$$1[0] = mathMax$3(x0, x1); + max$$1[1] = mathMax$3(y0, y1); +} + +var xDim = []; +var yDim = []; +/** + * 从三阶贝塞尔曲线(p0, p1, p2, p3)中计算出最小包围盒,写入`min`和`max`中 + * @memberOf module:zrender/core/bbox + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @param {Array.} min + * @param {Array.} max + */ +function fromCubic( + x0, y0, x1, y1, x2, y2, x3, y3, min$$1, max$$1 +) { + var cubicExtrema$$1 = cubicExtrema; + var cubicAt$$1 = cubicAt; + var i; + var n = cubicExtrema$$1(x0, x1, x2, x3, xDim); + min$$1[0] = Infinity; + min$$1[1] = Infinity; + max$$1[0] = -Infinity; + max$$1[1] = -Infinity; + + for (i = 0; i < n; i++) { + var x = cubicAt$$1(x0, x1, x2, x3, xDim[i]); + min$$1[0] = mathMin$3(x, min$$1[0]); + max$$1[0] = mathMax$3(x, max$$1[0]); + } + n = cubicExtrema$$1(y0, y1, y2, y3, yDim); + for (i = 0; i < n; i++) { + var y = cubicAt$$1(y0, y1, y2, y3, yDim[i]); + min$$1[1] = mathMin$3(y, min$$1[1]); + max$$1[1] = mathMax$3(y, max$$1[1]); + } + + min$$1[0] = mathMin$3(x0, min$$1[0]); + max$$1[0] = mathMax$3(x0, max$$1[0]); + min$$1[0] = mathMin$3(x3, min$$1[0]); + max$$1[0] = mathMax$3(x3, max$$1[0]); + + min$$1[1] = mathMin$3(y0, min$$1[1]); + max$$1[1] = mathMax$3(y0, max$$1[1]); + min$$1[1] = mathMin$3(y3, min$$1[1]); + max$$1[1] = mathMax$3(y3, max$$1[1]); +} + +/** + * 从二阶贝塞尔曲线(p0, p1, p2)中计算出最小包围盒,写入`min`和`max`中 + * @memberOf module:zrender/core/bbox + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {Array.} min + * @param {Array.} max + */ +function fromQuadratic(x0, y0, x1, y1, x2, y2, min$$1, max$$1) { + var quadraticExtremum$$1 = quadraticExtremum; + var quadraticAt$$1 = quadraticAt; + // Find extremities, where derivative in x dim or y dim is zero + var tx = + mathMax$3( + mathMin$3(quadraticExtremum$$1(x0, x1, x2), 1), 0 + ); + var ty = + mathMax$3( + mathMin$3(quadraticExtremum$$1(y0, y1, y2), 1), 0 + ); + + var x = quadraticAt$$1(x0, x1, x2, tx); + var y = quadraticAt$$1(y0, y1, y2, ty); + + min$$1[0] = mathMin$3(x0, x2, x); + min$$1[1] = mathMin$3(y0, y2, y); + max$$1[0] = mathMax$3(x0, x2, x); + max$$1[1] = mathMax$3(y0, y2, y); +} + +/** + * 从圆弧中计算出最小包围盒,写入`min`和`max`中 + * @method + * @memberOf module:zrender/core/bbox + * @param {number} x + * @param {number} y + * @param {number} rx + * @param {number} ry + * @param {number} startAngle + * @param {number} endAngle + * @param {number} anticlockwise + * @param {Array.} min + * @param {Array.} max + */ +function fromArc( + x, y, rx, ry, startAngle, endAngle, anticlockwise, min$$1, max$$1 +) { + var vec2Min = min; + var vec2Max = max; + + var diff = Math.abs(startAngle - endAngle); + + + if (diff % PI2 < 1e-4 && diff > 1e-4) { + // Is a circle + min$$1[0] = x - rx; + min$$1[1] = y - ry; + max$$1[0] = x + rx; + max$$1[1] = y + ry; + return; + } + + start[0] = mathCos$2(startAngle) * rx + x; + start[1] = mathSin$2(startAngle) * ry + y; + + end[0] = mathCos$2(endAngle) * rx + x; + end[1] = mathSin$2(endAngle) * ry + y; + + vec2Min(min$$1, start, end); + vec2Max(max$$1, start, end); + + // Thresh to [0, Math.PI * 2] + startAngle = startAngle % (PI2); + if (startAngle < 0) { + startAngle = startAngle + PI2; + } + endAngle = endAngle % (PI2); + if (endAngle < 0) { + endAngle = endAngle + PI2; + } + + if (startAngle > endAngle && !anticlockwise) { + endAngle += PI2; + } + else if (startAngle < endAngle && anticlockwise) { + startAngle += PI2; + } + if (anticlockwise) { + var tmp = endAngle; + endAngle = startAngle; + startAngle = tmp; + } + + // var number = 0; + // var step = (anticlockwise ? -Math.PI : Math.PI) / 2; + for (var angle = 0; angle < endAngle; angle += Math.PI / 2) { + if (angle > startAngle) { + extremity[0] = mathCos$2(angle) * rx + x; + extremity[1] = mathSin$2(angle) * ry + y; + + vec2Min(min$$1, extremity, min$$1); + vec2Max(max$$1, extremity, max$$1); + } + } +} + +/** + * Path 代理,可以在`buildPath`中用于替代`ctx`, 会保存每个path操作的命令到pathCommands属性中 + * 可以用于 isInsidePath 判断以及获取boundingRect + * + * @module zrender/core/PathProxy + * @author Yi Shen (http://www.github.com/pissang) + */ + +// TODO getTotalLength, getPointAtLength + +/* global Float32Array */ + +var CMD = { + M: 1, + L: 2, + C: 3, + Q: 4, + A: 5, + Z: 6, + // Rect + R: 7 +}; + +// var CMD_MEM_SIZE = { +// M: 3, +// L: 3, +// C: 7, +// Q: 5, +// A: 9, +// R: 5, +// Z: 1 +// }; + +var min$1 = []; +var max$1 = []; +var min2 = []; +var max2 = []; +var mathMin$2 = Math.min; +var mathMax$2 = Math.max; +var mathCos$1 = Math.cos; +var mathSin$1 = Math.sin; +var mathSqrt$1 = Math.sqrt; +var mathAbs = Math.abs; + +var hasTypedArray = typeof Float32Array !== 'undefined'; + +/** + * @alias module:zrender/core/PathProxy + * @constructor + */ +var PathProxy = function (notSaveData) { + + this._saveData = !(notSaveData || false); + + if (this._saveData) { + /** + * Path data. Stored as flat array + * @type {Array.} + */ + this.data = []; + } + + this._ctx = null; +}; + +/** + * 快速计算Path包围盒(并不是最小包围盒) + * @return {Object} + */ +PathProxy.prototype = { + + constructor: PathProxy, + + _xi: 0, + _yi: 0, + + _x0: 0, + _y0: 0, + // Unit x, Unit y. Provide for avoiding drawing that too short line segment + _ux: 0, + _uy: 0, + + _len: 0, + + _lineDash: null, + + _dashOffset: 0, + + _dashIdx: 0, + + _dashSum: 0, + + /** + * @readOnly + */ + setScale: function (sx, sy, segmentIgnoreThreshold) { + // Compat. Previously there is no segmentIgnoreThreshold. + segmentIgnoreThreshold = segmentIgnoreThreshold || 0; + this._ux = mathAbs(segmentIgnoreThreshold / devicePixelRatio / sx) || 0; + this._uy = mathAbs(segmentIgnoreThreshold / devicePixelRatio / sy) || 0; + }, + + getContext: function () { + return this._ctx; + }, + + /** + * @param {CanvasRenderingContext2D} ctx + * @return {module:zrender/core/PathProxy} + */ + beginPath: function (ctx) { + + this._ctx = ctx; + + ctx && ctx.beginPath(); + + ctx && (this.dpr = ctx.dpr); + + // Reset + if (this._saveData) { + this._len = 0; + } + + if (this._lineDash) { + this._lineDash = null; + + this._dashOffset = 0; + } + + return this; + }, + + /** + * @param {number} x + * @param {number} y + * @return {module:zrender/core/PathProxy} + */ + moveTo: function (x, y) { + this.addData(CMD.M, x, y); + this._ctx && this._ctx.moveTo(x, y); + + // x0, y0, xi, yi 是记录在 _dashedXXXXTo 方法中使用 + // xi, yi 记录当前点, x0, y0 在 closePath 的时候回到起始点。 + // 有可能在 beginPath 之后直接调用 lineTo,这时候 x0, y0 需要 + // 在 lineTo 方法中记录,这里先不考虑这种情况,dashed line 也只在 IE10- 中不支持 + this._x0 = x; + this._y0 = y; + + this._xi = x; + this._yi = y; + + return this; + }, + + /** + * @param {number} x + * @param {number} y + * @return {module:zrender/core/PathProxy} + */ + lineTo: function (x, y) { + var exceedUnit = mathAbs(x - this._xi) > this._ux + || mathAbs(y - this._yi) > this._uy + // Force draw the first segment + || this._len < 5; + + this.addData(CMD.L, x, y); + + if (this._ctx && exceedUnit) { + this._needsDash() ? this._dashedLineTo(x, y) + : this._ctx.lineTo(x, y); + } + if (exceedUnit) { + this._xi = x; + this._yi = y; + } + + return this; + }, + + /** + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @return {module:zrender/core/PathProxy} + */ + bezierCurveTo: function (x1, y1, x2, y2, x3, y3) { + this.addData(CMD.C, x1, y1, x2, y2, x3, y3); + if (this._ctx) { + this._needsDash() ? this._dashedBezierTo(x1, y1, x2, y2, x3, y3) + : this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + } + this._xi = x3; + this._yi = y3; + return this; + }, + + /** + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @return {module:zrender/core/PathProxy} + */ + quadraticCurveTo: function (x1, y1, x2, y2) { + this.addData(CMD.Q, x1, y1, x2, y2); + if (this._ctx) { + this._needsDash() ? this._dashedQuadraticTo(x1, y1, x2, y2) + : this._ctx.quadraticCurveTo(x1, y1, x2, y2); + } + this._xi = x2; + this._yi = y2; + return this; + }, + + /** + * @param {number} cx + * @param {number} cy + * @param {number} r + * @param {number} startAngle + * @param {number} endAngle + * @param {boolean} anticlockwise + * @return {module:zrender/core/PathProxy} + */ + arc: function (cx, cy, r, startAngle, endAngle, anticlockwise) { + this.addData( + CMD.A, cx, cy, r, r, startAngle, endAngle - startAngle, 0, anticlockwise ? 0 : 1 + ); + this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise); + + this._xi = mathCos$1(endAngle) * r + cx; + this._yi = mathSin$1(endAngle) * r + cy; + return this; + }, + + // TODO + arcTo: function (x1, y1, x2, y2, radius) { + if (this._ctx) { + this._ctx.arcTo(x1, y1, x2, y2, radius); + } + return this; + }, + + // TODO + rect: function (x, y, w, h) { + this._ctx && this._ctx.rect(x, y, w, h); + this.addData(CMD.R, x, y, w, h); + return this; + }, + + /** + * @return {module:zrender/core/PathProxy} + */ + closePath: function () { + this.addData(CMD.Z); + + var ctx = this._ctx; + var x0 = this._x0; + var y0 = this._y0; + if (ctx) { + this._needsDash() && this._dashedLineTo(x0, y0); + ctx.closePath(); + } + + this._xi = x0; + this._yi = y0; + return this; + }, + + /** + * Context 从外部传入,因为有可能是 rebuildPath 完之后再 fill。 + * stroke 同样 + * @param {CanvasRenderingContext2D} ctx + * @return {module:zrender/core/PathProxy} + */ + fill: function (ctx) { + ctx && ctx.fill(); + this.toStatic(); + }, + + /** + * @param {CanvasRenderingContext2D} ctx + * @return {module:zrender/core/PathProxy} + */ + stroke: function (ctx) { + ctx && ctx.stroke(); + this.toStatic(); + }, + + /** + * 必须在其它绘制命令前调用 + * Must be invoked before all other path drawing methods + * @return {module:zrender/core/PathProxy} + */ + setLineDash: function (lineDash) { + if (lineDash instanceof Array) { + this._lineDash = lineDash; + + this._dashIdx = 0; + + var lineDashSum = 0; + for (var i = 0; i < lineDash.length; i++) { + lineDashSum += lineDash[i]; + } + this._dashSum = lineDashSum; + } + return this; + }, + + /** + * 必须在其它绘制命令前调用 + * Must be invoked before all other path drawing methods + * @return {module:zrender/core/PathProxy} + */ + setLineDashOffset: function (offset) { + this._dashOffset = offset; + return this; + }, + + /** + * + * @return {boolean} + */ + len: function () { + return this._len; + }, + + /** + * 直接设置 Path 数据 + */ + setData: function (data) { + + var len$$1 = data.length; + + if (!(this.data && this.data.length === len$$1) && hasTypedArray) { + this.data = new Float32Array(len$$1); + } + + for (var i = 0; i < len$$1; i++) { + this.data[i] = data[i]; + } + + this._len = len$$1; + }, + + /** + * 添加子路径 + * @param {module:zrender/core/PathProxy|Array.} path + */ + appendPath: function (path) { + if (!(path instanceof Array)) { + path = [path]; + } + var len$$1 = path.length; + var appendSize = 0; + var offset = this._len; + for (var i = 0; i < len$$1; i++) { + appendSize += path[i].len(); + } + if (hasTypedArray && (this.data instanceof Float32Array)) { + this.data = new Float32Array(offset + appendSize); + } + for (var i = 0; i < len$$1; i++) { + var appendPathData = path[i].data; + for (var k = 0; k < appendPathData.length; k++) { + this.data[offset++] = appendPathData[k]; + } + } + this._len = offset; + }, + + /** + * 填充 Path 数据。 + * 尽量复用而不申明新的数组。大部分图形重绘的指令数据长度都是不变的。 + */ + addData: function (cmd) { + if (!this._saveData) { + return; + } + + var data = this.data; + if (this._len + arguments.length > data.length) { + // 因为之前的数组已经转换成静态的 Float32Array + // 所以不够用时需要扩展一个新的动态数组 + this._expandData(); + data = this.data; + } + for (var i = 0; i < arguments.length; i++) { + data[this._len++] = arguments[i]; + } + + this._prevCmd = cmd; + }, + + _expandData: function () { + // Only if data is Float32Array + if (!(this.data instanceof Array)) { + var newData = []; + for (var i = 0; i < this._len; i++) { + newData[i] = this.data[i]; + } + this.data = newData; + } + }, + + /** + * If needs js implemented dashed line + * @return {boolean} + * @private + */ + _needsDash: function () { + return this._lineDash; + }, + + _dashedLineTo: function (x1, y1) { + var dashSum = this._dashSum; + var offset = this._dashOffset; + var lineDash = this._lineDash; + var ctx = this._ctx; + + var x0 = this._xi; + var y0 = this._yi; + var dx = x1 - x0; + var dy = y1 - y0; + var dist$$1 = mathSqrt$1(dx * dx + dy * dy); + var x = x0; + var y = y0; + var dash; + var nDash = lineDash.length; + var idx; + dx /= dist$$1; + dy /= dist$$1; + + if (offset < 0) { + // Convert to positive offset + offset = dashSum + offset; + } + offset %= dashSum; + x -= offset * dx; + y -= offset * dy; + + while ((dx > 0 && x <= x1) || (dx < 0 && x >= x1) + || (dx === 0 && ((dy > 0 && y <= y1) || (dy < 0 && y >= y1)))) { + idx = this._dashIdx; + dash = lineDash[idx]; + x += dx * dash; + y += dy * dash; + this._dashIdx = (idx + 1) % nDash; + // Skip positive offset + if ((dx > 0 && x < x0) || (dx < 0 && x > x0) || (dy > 0 && y < y0) || (dy < 0 && y > y0)) { + continue; + } + ctx[idx % 2 ? 'moveTo' : 'lineTo']( + dx >= 0 ? mathMin$2(x, x1) : mathMax$2(x, x1), + dy >= 0 ? mathMin$2(y, y1) : mathMax$2(y, y1) + ); + } + // Offset for next lineTo + dx = x - x1; + dy = y - y1; + this._dashOffset = -mathSqrt$1(dx * dx + dy * dy); + }, + + // Not accurate dashed line to + _dashedBezierTo: function (x1, y1, x2, y2, x3, y3) { + var dashSum = this._dashSum; + var offset = this._dashOffset; + var lineDash = this._lineDash; + var ctx = this._ctx; + + var x0 = this._xi; + var y0 = this._yi; + var t; + var dx; + var dy; + var cubicAt$$1 = cubicAt; + var bezierLen = 0; + var idx = this._dashIdx; + var nDash = lineDash.length; + + var x; + var y; + + var tmpLen = 0; + + if (offset < 0) { + // Convert to positive offset + offset = dashSum + offset; + } + offset %= dashSum; + // Bezier approx length + for (t = 0; t < 1; t += 0.1) { + dx = cubicAt$$1(x0, x1, x2, x3, t + 0.1) + - cubicAt$$1(x0, x1, x2, x3, t); + dy = cubicAt$$1(y0, y1, y2, y3, t + 0.1) + - cubicAt$$1(y0, y1, y2, y3, t); + bezierLen += mathSqrt$1(dx * dx + dy * dy); + } + + // Find idx after add offset + for (; idx < nDash; idx++) { + tmpLen += lineDash[idx]; + if (tmpLen > offset) { + break; + } + } + t = (tmpLen - offset) / bezierLen; + + while (t <= 1) { + + x = cubicAt$$1(x0, x1, x2, x3, t); + y = cubicAt$$1(y0, y1, y2, y3, t); + + // Use line to approximate dashed bezier + // Bad result if dash is long + idx % 2 ? ctx.moveTo(x, y) + : ctx.lineTo(x, y); + + t += lineDash[idx] / bezierLen; + + idx = (idx + 1) % nDash; + } + + // Finish the last segment and calculate the new offset + (idx % 2 !== 0) && ctx.lineTo(x3, y3); + dx = x3 - x; + dy = y3 - y; + this._dashOffset = -mathSqrt$1(dx * dx + dy * dy); + }, + + _dashedQuadraticTo: function (x1, y1, x2, y2) { + // Convert quadratic to cubic using degree elevation + var x3 = x2; + var y3 = y2; + x2 = (x2 + 2 * x1) / 3; + y2 = (y2 + 2 * y1) / 3; + x1 = (this._xi + 2 * x1) / 3; + y1 = (this._yi + 2 * y1) / 3; + + this._dashedBezierTo(x1, y1, x2, y2, x3, y3); + }, + + /** + * 转成静态的 Float32Array 减少堆内存占用 + * Convert dynamic array to static Float32Array + */ + toStatic: function () { + var data = this.data; + if (data instanceof Array) { + data.length = this._len; + if (hasTypedArray) { + this.data = new Float32Array(data); + } + } + }, + + /** + * @return {module:zrender/core/BoundingRect} + */ + getBoundingRect: function () { + min$1[0] = min$1[1] = min2[0] = min2[1] = Number.MAX_VALUE; + max$1[0] = max$1[1] = max2[0] = max2[1] = -Number.MAX_VALUE; + + var data = this.data; + var xi = 0; + var yi = 0; + var x0 = 0; + var y0 = 0; + + for (var i = 0; i < data.length;) { + var cmd = data[i++]; + + if (i === 1) { + // 如果第一个命令是 L, C, Q + // 则 previous point 同绘制命令的第一个 point + // + // 第一个命令为 Arc 的情况下会在后面特殊处理 + xi = data[i]; + yi = data[i + 1]; + + x0 = xi; + y0 = yi; + } + + switch (cmd) { + case CMD.M: + // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点 + // 在 closePath 的时候使用 + x0 = data[i++]; + y0 = data[i++]; + xi = x0; + yi = y0; + min2[0] = x0; + min2[1] = y0; + max2[0] = x0; + max2[1] = y0; + break; + case CMD.L: + fromLine(xi, yi, data[i], data[i + 1], min2, max2); + xi = data[i++]; + yi = data[i++]; + break; + case CMD.C: + fromCubic( + xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], + min2, max2 + ); + xi = data[i++]; + yi = data[i++]; + break; + case CMD.Q: + fromQuadratic( + xi, yi, data[i++], data[i++], data[i], data[i + 1], + min2, max2 + ); + xi = data[i++]; + yi = data[i++]; + break; + case CMD.A: + // TODO Arc 判断的开销比较大 + var cx = data[i++]; + var cy = data[i++]; + var rx = data[i++]; + var ry = data[i++]; + var startAngle = data[i++]; + var endAngle = data[i++] + startAngle; + // TODO Arc 旋转 + i += 1; + var anticlockwise = 1 - data[i++]; + + if (i === 1) { + // 直接使用 arc 命令 + // 第一个命令起点还未定义 + x0 = mathCos$1(startAngle) * rx + cx; + y0 = mathSin$1(startAngle) * ry + cy; + } + + fromArc( + cx, cy, rx, ry, startAngle, endAngle, + anticlockwise, min2, max2 + ); + + xi = mathCos$1(endAngle) * rx + cx; + yi = mathSin$1(endAngle) * ry + cy; + break; + case CMD.R: + x0 = xi = data[i++]; + y0 = yi = data[i++]; + var width = data[i++]; + var height = data[i++]; + // Use fromLine + fromLine(x0, y0, x0 + width, y0 + height, min2, max2); + break; + case CMD.Z: + xi = x0; + yi = y0; + break; + } + + // Union + min(min$1, min$1, min2); + max(max$1, max$1, max2); + } + + // No data + if (i === 0) { + min$1[0] = min$1[1] = max$1[0] = max$1[1] = 0; + } + + return new BoundingRect( + min$1[0], min$1[1], max$1[0] - min$1[0], max$1[1] - min$1[1] + ); + }, + + /** + * Rebuild path from current data + * Rebuild path will not consider javascript implemented line dash. + * @param {CanvasRenderingContext2D} ctx + */ + rebuildPath: function (ctx) { + var d = this.data; + var x0; + var y0; + var xi; + var yi; + var x; + var y; + var ux = this._ux; + var uy = this._uy; + var len$$1 = this._len; + for (var i = 0; i < len$$1;) { + var cmd = d[i++]; + + if (i === 1) { + // 如果第一个命令是 L, C, Q + // 则 previous point 同绘制命令的第一个 point + // + // 第一个命令为 Arc 的情况下会在后面特殊处理 + xi = d[i]; + yi = d[i + 1]; + + x0 = xi; + y0 = yi; + } + switch (cmd) { + case CMD.M: + x0 = xi = d[i++]; + y0 = yi = d[i++]; + ctx.moveTo(xi, yi); + break; + case CMD.L: + x = d[i++]; + y = d[i++]; + // Not draw too small seg between + if (mathAbs(x - xi) > ux || mathAbs(y - yi) > uy || i === len$$1 - 1) { + ctx.lineTo(x, y); + xi = x; + yi = y; + } + break; + case CMD.C: + ctx.bezierCurveTo( + d[i++], d[i++], d[i++], d[i++], d[i++], d[i++] + ); + xi = d[i - 2]; + yi = d[i - 1]; + break; + case CMD.Q: + ctx.quadraticCurveTo(d[i++], d[i++], d[i++], d[i++]); + xi = d[i - 2]; + yi = d[i - 1]; + break; + case CMD.A: + var cx = d[i++]; + var cy = d[i++]; + var rx = d[i++]; + var ry = d[i++]; + var theta = d[i++]; + var dTheta = d[i++]; + var psi = d[i++]; + var fs = d[i++]; + var r = (rx > ry) ? rx : ry; + var scaleX = (rx > ry) ? 1 : rx / ry; + var scaleY = (rx > ry) ? ry / rx : 1; + var isEllipse = Math.abs(rx - ry) > 1e-3; + var endAngle = theta + dTheta; + if (isEllipse) { + ctx.translate(cx, cy); + ctx.rotate(psi); + ctx.scale(scaleX, scaleY); + ctx.arc(0, 0, r, theta, endAngle, 1 - fs); + ctx.scale(1 / scaleX, 1 / scaleY); + ctx.rotate(-psi); + ctx.translate(-cx, -cy); + } + else { + ctx.arc(cx, cy, r, theta, endAngle, 1 - fs); + } + + if (i === 1) { + // 直接使用 arc 命令 + // 第一个命令起点还未定义 + x0 = mathCos$1(theta) * rx + cx; + y0 = mathSin$1(theta) * ry + cy; + } + xi = mathCos$1(endAngle) * rx + cx; + yi = mathSin$1(endAngle) * ry + cy; + break; + case CMD.R: + x0 = xi = d[i]; + y0 = yi = d[i + 1]; + ctx.rect(d[i++], d[i++], d[i++], d[i++]); + break; + case CMD.Z: + ctx.closePath(); + xi = x0; + yi = y0; + } + } + } +}; + +PathProxy.CMD = CMD; + +/** + * 线段包含判断 + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {number} lineWidth + * @param {number} x + * @param {number} y + * @return {boolean} + */ +function containStroke$1(x0, y0, x1, y1, lineWidth, x, y) { + if (lineWidth === 0) { + return false; + } + var _l = lineWidth; + var _a = 0; + var _b = x0; + // Quick reject + if ( + (y > y0 + _l && y > y1 + _l) + || (y < y0 - _l && y < y1 - _l) + || (x > x0 + _l && x > x1 + _l) + || (x < x0 - _l && x < x1 - _l) + ) { + return false; + } + + if (x0 !== x1) { + _a = (y0 - y1) / (x0 - x1); + _b = (x0 * y1 - x1 * y0) / (x0 - x1); + } + else { + return Math.abs(x - x0) <= _l / 2; + } + var tmp = _a * x - y + _b; + var _s = tmp * tmp / (_a * _a + 1); + return _s <= _l / 2 * _l / 2; +} + +/** + * 三次贝塞尔曲线描边包含判断 + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @param {number} lineWidth + * @param {number} x + * @param {number} y + * @return {boolean} + */ +function containStroke$2(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) { + if (lineWidth === 0) { + return false; + } + var _l = lineWidth; + // Quick reject + if ( + (y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l) + || (y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l) + || (x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l) + || (x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l) + ) { + return false; + } + var d = cubicProjectPoint( + x0, y0, x1, y1, x2, y2, x3, y3, + x, y, null + ); + return d <= _l / 2; +} + +/** + * 二次贝塞尔曲线描边包含判断 + * @param {number} x0 + * @param {number} y0 + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} lineWidth + * @param {number} x + * @param {number} y + * @return {boolean} + */ +function containStroke$3(x0, y0, x1, y1, x2, y2, lineWidth, x, y) { + if (lineWidth === 0) { + return false; + } + var _l = lineWidth; + // Quick reject + if ( + (y > y0 + _l && y > y1 + _l && y > y2 + _l) + || (y < y0 - _l && y < y1 - _l && y < y2 - _l) + || (x > x0 + _l && x > x1 + _l && x > x2 + _l) + || (x < x0 - _l && x < x1 - _l && x < x2 - _l) + ) { + return false; + } + var d = quadraticProjectPoint( + x0, y0, x1, y1, x2, y2, + x, y, null + ); + return d <= _l / 2; +} + +var PI2$3 = Math.PI * 2; + +function normalizeRadian(angle) { + angle %= PI2$3; + if (angle < 0) { + angle += PI2$3; + } + return angle; +} + +var PI2$2 = Math.PI * 2; + +/** + * 圆弧描边包含判断 + * @param {number} cx + * @param {number} cy + * @param {number} r + * @param {number} startAngle + * @param {number} endAngle + * @param {boolean} anticlockwise + * @param {number} lineWidth + * @param {number} x + * @param {number} y + * @return {Boolean} + */ +function containStroke$4( + cx, cy, r, startAngle, endAngle, anticlockwise, + lineWidth, x, y +) { + + if (lineWidth === 0) { + return false; + } + var _l = lineWidth; + + x -= cx; + y -= cy; + var d = Math.sqrt(x * x + y * y); + + if ((d - _l > r) || (d + _l < r)) { + return false; + } + if (Math.abs(startAngle - endAngle) % PI2$2 < 1e-4) { + // Is a circle + return true; + } + if (anticlockwise) { + var tmp = startAngle; + startAngle = normalizeRadian(endAngle); + endAngle = normalizeRadian(tmp); + } + else { + startAngle = normalizeRadian(startAngle); + endAngle = normalizeRadian(endAngle); + } + if (startAngle > endAngle) { + endAngle += PI2$2; + } + + var angle = Math.atan2(y, x); + if (angle < 0) { + angle += PI2$2; + } + return (angle >= startAngle && angle <= endAngle) + || (angle + PI2$2 >= startAngle && angle + PI2$2 <= endAngle); +} + +function windingLine(x0, y0, x1, y1, x, y) { + if ((y > y0 && y > y1) || (y < y0 && y < y1)) { + return 0; + } + // Ignore horizontal line + if (y1 === y0) { + return 0; + } + var dir = y1 < y0 ? 1 : -1; + var t = (y - y0) / (y1 - y0); + + // Avoid winding error when intersection point is the connect point of two line of polygon + if (t === 1 || t === 0) { + dir = y1 < y0 ? 0.5 : -0.5; + } + + var x_ = t * (x1 - x0) + x0; + + // If (x, y) on the line, considered as "contain". + return x_ === x ? Infinity : x_ > x ? dir : 0; +} + +var CMD$1 = PathProxy.CMD; +var PI2$1 = Math.PI * 2; + +var EPSILON$2 = 1e-4; + +function isAroundEqual(a, b) { + return Math.abs(a - b) < EPSILON$2; +} + +// 临时数组 +var roots = [-1, -1, -1]; +var extrema = [-1, -1]; + +function swapExtrema() { + var tmp = extrema[0]; + extrema[0] = extrema[1]; + extrema[1] = tmp; +} + +function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) { + // Quick reject + if ( + (y > y0 && y > y1 && y > y2 && y > y3) + || (y < y0 && y < y1 && y < y2 && y < y3) + ) { + return 0; + } + var nRoots = cubicRootAt(y0, y1, y2, y3, y, roots); + if (nRoots === 0) { + return 0; + } + else { + var w = 0; + var nExtrema = -1; + var y0_; + var y1_; + for (var i = 0; i < nRoots; i++) { + var t = roots[i]; + + // Avoid winding error when intersection point is the connect point of two line of polygon + var unit = (t === 0 || t === 1) ? 0.5 : 1; + + var x_ = cubicAt(x0, x1, x2, x3, t); + if (x_ < x) { // Quick reject + continue; + } + if (nExtrema < 0) { + nExtrema = cubicExtrema(y0, y1, y2, y3, extrema); + if (extrema[1] < extrema[0] && nExtrema > 1) { + swapExtrema(); + } + y0_ = cubicAt(y0, y1, y2, y3, extrema[0]); + if (nExtrema > 1) { + y1_ = cubicAt(y0, y1, y2, y3, extrema[1]); + } + } + if (nExtrema === 2) { + // 分成三段单调函数 + if (t < extrema[0]) { + w += y0_ < y0 ? unit : -unit; + } + else if (t < extrema[1]) { + w += y1_ < y0_ ? unit : -unit; + } + else { + w += y3 < y1_ ? unit : -unit; + } + } + else { + // 分成两段单调函数 + if (t < extrema[0]) { + w += y0_ < y0 ? unit : -unit; + } + else { + w += y3 < y0_ ? unit : -unit; + } + } + } + return w; + } +} + +function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) { + // Quick reject + if ( + (y > y0 && y > y1 && y > y2) + || (y < y0 && y < y1 && y < y2) + ) { + return 0; + } + var nRoots = quadraticRootAt(y0, y1, y2, y, roots); + if (nRoots === 0) { + return 0; + } + else { + var t = quadraticExtremum(y0, y1, y2); + if (t >= 0 && t <= 1) { + var w = 0; + var y_ = quadraticAt(y0, y1, y2, t); + for (var i = 0; i < nRoots; i++) { + // Remove one endpoint. + var unit = (roots[i] === 0 || roots[i] === 1) ? 0.5 : 1; + + var x_ = quadraticAt(x0, x1, x2, roots[i]); + if (x_ < x) { // Quick reject + continue; + } + if (roots[i] < t) { + w += y_ < y0 ? unit : -unit; + } + else { + w += y2 < y_ ? unit : -unit; + } + } + return w; + } + else { + // Remove one endpoint. + var unit = (roots[0] === 0 || roots[0] === 1) ? 0.5 : 1; + + var x_ = quadraticAt(x0, x1, x2, roots[0]); + if (x_ < x) { // Quick reject + return 0; + } + return y2 < y0 ? unit : -unit; + } + } +} + +// TODO +// Arc 旋转 +function windingArc( + cx, cy, r, startAngle, endAngle, anticlockwise, x, y +) { + y -= cy; + if (y > r || y < -r) { + return 0; + } + var tmp = Math.sqrt(r * r - y * y); + roots[0] = -tmp; + roots[1] = tmp; + + var diff = Math.abs(startAngle - endAngle); + if (diff < 1e-4) { + return 0; + } + if (diff % PI2$1 < 1e-4) { + // Is a circle + startAngle = 0; + endAngle = PI2$1; + var dir = anticlockwise ? 1 : -1; + if (x >= roots[0] + cx && x <= roots[1] + cx) { + return dir; + } + else { + return 0; + } + } + + if (anticlockwise) { + var tmp = startAngle; + startAngle = normalizeRadian(endAngle); + endAngle = normalizeRadian(tmp); + } + else { + startAngle = normalizeRadian(startAngle); + endAngle = normalizeRadian(endAngle); + } + if (startAngle > endAngle) { + endAngle += PI2$1; + } + + var w = 0; + for (var i = 0; i < 2; i++) { + var x_ = roots[i]; + if (x_ + cx > x) { + var angle = Math.atan2(y, x_); + var dir = anticlockwise ? 1 : -1; + if (angle < 0) { + angle = PI2$1 + angle; + } + if ( + (angle >= startAngle && angle <= endAngle) + || (angle + PI2$1 >= startAngle && angle + PI2$1 <= endAngle) + ) { + if (angle > Math.PI / 2 && angle < Math.PI * 1.5) { + dir = -dir; + } + w += dir; + } + } + } + return w; +} + +function containPath(data, lineWidth, isStroke, x, y) { + var w = 0; + var xi = 0; + var yi = 0; + var x0 = 0; + var y0 = 0; + + for (var i = 0; i < data.length;) { + var cmd = data[i++]; + // Begin a new subpath + if (cmd === CMD$1.M && i > 1) { + // Close previous subpath + if (!isStroke) { + w += windingLine(xi, yi, x0, y0, x, y); + } + // 如果被任何一个 subpath 包含 + // if (w !== 0) { + // return true; + // } + } + + if (i === 1) { + // 如果第一个命令是 L, C, Q + // 则 previous point 同绘制命令的第一个 point + // + // 第一个命令为 Arc 的情况下会在后面特殊处理 + xi = data[i]; + yi = data[i + 1]; + + x0 = xi; + y0 = yi; + } + + switch (cmd) { + case CMD$1.M: + // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点 + // 在 closePath 的时候使用 + x0 = data[i++]; + y0 = data[i++]; + xi = x0; + yi = y0; + break; + case CMD$1.L: + if (isStroke) { + if (containStroke$1(xi, yi, data[i], data[i + 1], lineWidth, x, y)) { + return true; + } + } + else { + // NOTE 在第一个命令为 L, C, Q 的时候会计算出 NaN + w += windingLine(xi, yi, data[i], data[i + 1], x, y) || 0; + } + xi = data[i++]; + yi = data[i++]; + break; + case CMD$1.C: + if (isStroke) { + if (containStroke$2(xi, yi, + data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], + lineWidth, x, y + )) { + return true; + } + } + else { + w += windingCubic( + xi, yi, + data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], + x, y + ) || 0; + } + xi = data[i++]; + yi = data[i++]; + break; + case CMD$1.Q: + if (isStroke) { + if (containStroke$3(xi, yi, + data[i++], data[i++], data[i], data[i + 1], + lineWidth, x, y + )) { + return true; + } + } + else { + w += windingQuadratic( + xi, yi, + data[i++], data[i++], data[i], data[i + 1], + x, y + ) || 0; + } + xi = data[i++]; + yi = data[i++]; + break; + case CMD$1.A: + // TODO Arc 判断的开销比较大 + var cx = data[i++]; + var cy = data[i++]; + var rx = data[i++]; + var ry = data[i++]; + var theta = data[i++]; + var dTheta = data[i++]; + // TODO Arc 旋转 + i += 1; + var anticlockwise = 1 - data[i++]; + var x1 = Math.cos(theta) * rx + cx; + var y1 = Math.sin(theta) * ry + cy; + // 不是直接使用 arc 命令 + if (i > 1) { + w += windingLine(xi, yi, x1, y1, x, y); + } + else { + // 第一个命令起点还未定义 + x0 = x1; + y0 = y1; + } + // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放 + var _x = (x - cx) * ry / rx + cx; + if (isStroke) { + if (containStroke$4( + cx, cy, ry, theta, theta + dTheta, anticlockwise, + lineWidth, _x, y + )) { + return true; + } + } + else { + w += windingArc( + cx, cy, ry, theta, theta + dTheta, anticlockwise, + _x, y + ); + } + xi = Math.cos(theta + dTheta) * rx + cx; + yi = Math.sin(theta + dTheta) * ry + cy; + break; + case CMD$1.R: + x0 = xi = data[i++]; + y0 = yi = data[i++]; + var width = data[i++]; + var height = data[i++]; + var x1 = x0 + width; + var y1 = y0 + height; + if (isStroke) { + if (containStroke$1(x0, y0, x1, y0, lineWidth, x, y) + || containStroke$1(x1, y0, x1, y1, lineWidth, x, y) + || containStroke$1(x1, y1, x0, y1, lineWidth, x, y) + || containStroke$1(x0, y1, x0, y0, lineWidth, x, y) + ) { + return true; + } + } + else { + // FIXME Clockwise ? + w += windingLine(x1, y0, x1, y1, x, y); + w += windingLine(x0, y1, x0, y0, x, y); + } + break; + case CMD$1.Z: + if (isStroke) { + if (containStroke$1( + xi, yi, x0, y0, lineWidth, x, y + )) { + return true; + } + } + else { + // Close a subpath + w += windingLine(xi, yi, x0, y0, x, y); + // 如果被任何一个 subpath 包含 + // FIXME subpaths may overlap + // if (w !== 0) { + // return true; + // } + } + xi = x0; + yi = y0; + break; + } + } + if (!isStroke && !isAroundEqual(yi, y0)) { + w += windingLine(xi, yi, x0, y0, x, y) || 0; + } + return w !== 0; +} + +function contain(pathData, x, y) { + return containPath(pathData, 0, false, x, y); +} + +function containStroke(pathData, lineWidth, x, y) { + return containPath(pathData, lineWidth, true, x, y); +} + +var getCanvasPattern = Pattern.prototype.getCanvasPattern; + +var abs = Math.abs; + +var pathProxyForDraw = new PathProxy(true); +/** + * @alias module:zrender/graphic/Path + * @extends module:zrender/graphic/Displayable + * @constructor + * @param {Object} opts + */ +function Path(opts) { + Displayable.call(this, opts); + + /** + * @type {module:zrender/core/PathProxy} + * @readOnly + */ + this.path = null; +} + +Path.prototype = { + + constructor: Path, + + type: 'path', + + __dirtyPath: true, + + strokeContainThreshold: 5, + + // This item default to be false. But in map series in echarts, + // in order to improve performance, it should be set to true, + // so the shorty segment won't draw. + segmentIgnoreThreshold: 0, + + /** + * See `module:zrender/src/graphic/helper/subPixelOptimize`. + * @type {boolean} + */ + subPixelOptimize: false, + + brush: function (ctx, prevEl) { + var style = this.style; + var path = this.path || pathProxyForDraw; + var hasStroke = style.hasStroke(); + var hasFill = style.hasFill(); + var fill = style.fill; + var stroke = style.stroke; + var hasFillGradient = hasFill && !!(fill.colorStops); + var hasStrokeGradient = hasStroke && !!(stroke.colorStops); + var hasFillPattern = hasFill && !!(fill.image); + var hasStrokePattern = hasStroke && !!(stroke.image); + + style.bind(ctx, this, prevEl); + this.setTransform(ctx); + + if (this.__dirty) { + var rect; + // Update gradient because bounding rect may changed + if (hasFillGradient) { + rect = rect || this.getBoundingRect(); + this._fillGradient = style.getGradient(ctx, fill, rect); + } + if (hasStrokeGradient) { + rect = rect || this.getBoundingRect(); + this._strokeGradient = style.getGradient(ctx, stroke, rect); + } + } + // Use the gradient or pattern + if (hasFillGradient) { + // PENDING If may have affect the state + ctx.fillStyle = this._fillGradient; + } + else if (hasFillPattern) { + ctx.fillStyle = getCanvasPattern.call(fill, ctx); + } + if (hasStrokeGradient) { + ctx.strokeStyle = this._strokeGradient; + } + else if (hasStrokePattern) { + ctx.strokeStyle = getCanvasPattern.call(stroke, ctx); + } + + var lineDash = style.lineDash; + var lineDashOffset = style.lineDashOffset; + + var ctxLineDash = !!ctx.setLineDash; + + // Update path sx, sy + var scale = this.getGlobalScale(); + path.setScale(scale[0], scale[1], this.segmentIgnoreThreshold); + + // Proxy context + // Rebuild path in following 2 cases + // 1. Path is dirty + // 2. Path needs javascript implemented lineDash stroking. + // In this case, lineDash information will not be saved in PathProxy + if (this.__dirtyPath + || (lineDash && !ctxLineDash && hasStroke) + ) { + path.beginPath(ctx); + + // Setting line dash before build path + if (lineDash && !ctxLineDash) { + path.setLineDash(lineDash); + path.setLineDashOffset(lineDashOffset); + } + + this.buildPath(path, this.shape, false); + + // Clear path dirty flag + if (this.path) { + this.__dirtyPath = false; + } + } + else { + // Replay path building + ctx.beginPath(); + this.path.rebuildPath(ctx); + } + + if (hasFill) { + if (style.fillOpacity != null) { + var originalGlobalAlpha = ctx.globalAlpha; + ctx.globalAlpha = style.fillOpacity * style.opacity; + path.fill(ctx); + ctx.globalAlpha = originalGlobalAlpha; + } + else { + path.fill(ctx); + } + } + + if (lineDash && ctxLineDash) { + ctx.setLineDash(lineDash); + ctx.lineDashOffset = lineDashOffset; + } + + if (hasStroke) { + if (style.strokeOpacity != null) { + var originalGlobalAlpha = ctx.globalAlpha; + ctx.globalAlpha = style.strokeOpacity * style.opacity; + path.stroke(ctx); + ctx.globalAlpha = originalGlobalAlpha; + } + else { + path.stroke(ctx); + } + } + + if (lineDash && ctxLineDash) { + // PENDING + // Remove lineDash + ctx.setLineDash([]); + } + + // Draw rect text + if (style.text != null) { + // Only restore transform when needs draw text. + this.restoreTransform(ctx); + this.drawRectText(ctx, this.getBoundingRect()); + } + }, + + // When bundling path, some shape may decide if use moveTo to begin a new subpath or closePath + // Like in circle + buildPath: function (ctx, shapeCfg, inBundle) {}, + + createPathProxy: function () { + this.path = new PathProxy(); + }, + + getBoundingRect: function () { + var rect = this._rect; + var style = this.style; + var needsUpdateRect = !rect; + if (needsUpdateRect) { + var path = this.path; + if (!path) { + // Create path on demand. + path = this.path = new PathProxy(); + } + if (this.__dirtyPath) { + path.beginPath(); + this.buildPath(path, this.shape, false); + } + rect = path.getBoundingRect(); + } + this._rect = rect; + + if (style.hasStroke()) { + // Needs update rect with stroke lineWidth when + // 1. Element changes scale or lineWidth + // 2. Shape is changed + var rectWithStroke = this._rectWithStroke || (this._rectWithStroke = rect.clone()); + if (this.__dirty || needsUpdateRect) { + rectWithStroke.copy(rect); + // FIXME Must after updateTransform + var w = style.lineWidth; + // PENDING, Min line width is needed when line is horizontal or vertical + var lineScale = style.strokeNoScale ? this.getLineScale() : 1; + + // Only add extra hover lineWidth when there are no fill + if (!style.hasFill()) { + w = Math.max(w, this.strokeContainThreshold || 4); + } + // Consider line width + // Line scale can't be 0; + if (lineScale > 1e-10) { + rectWithStroke.width += w / lineScale; + rectWithStroke.height += w / lineScale; + rectWithStroke.x -= w / lineScale / 2; + rectWithStroke.y -= w / lineScale / 2; + } + } + + // Return rect with stroke + return rectWithStroke; + } + + return rect; + }, + + contain: function (x, y) { + var localPos = this.transformCoordToLocal(x, y); + var rect = this.getBoundingRect(); + var style = this.style; + x = localPos[0]; + y = localPos[1]; + + if (rect.contain(x, y)) { + var pathData = this.path.data; + if (style.hasStroke()) { + var lineWidth = style.lineWidth; + var lineScale = style.strokeNoScale ? this.getLineScale() : 1; + // Line scale can't be 0; + if (lineScale > 1e-10) { + // Only add extra hover lineWidth when there are no fill + if (!style.hasFill()) { + lineWidth = Math.max(lineWidth, this.strokeContainThreshold); + } + if (containStroke( + pathData, lineWidth / lineScale, x, y + )) { + return true; + } + } + } + if (style.hasFill()) { + return contain(pathData, x, y); + } + } + return false; + }, + + /** + * @param {boolean} dirtyPath + */ + dirty: function (dirtyPath) { + if (dirtyPath == null) { + dirtyPath = true; + } + // Only mark dirty, not mark clean + if (dirtyPath) { + this.__dirtyPath = dirtyPath; + this._rect = null; + } + + this.__dirty = this.__dirtyText = true; + + this.__zr && this.__zr.refresh(); + + // Used as a clipping path + if (this.__clipTarget) { + this.__clipTarget.dirty(); + } + }, + + /** + * Alias for animate('shape') + * @param {boolean} loop + */ + animateShape: function (loop) { + return this.animate('shape', loop); + }, + + // Overwrite attrKV + attrKV: function (key, value) { + // FIXME + if (key === 'shape') { + this.setShape(value); + this.__dirtyPath = true; + this._rect = null; + } + else { + Displayable.prototype.attrKV.call(this, key, value); + } + }, + + /** + * @param {Object|string} key + * @param {*} value + */ + setShape: function (key, value) { + var shape = this.shape; + // Path from string may not have shape + if (shape) { + if (isObject$1(key)) { + for (var name in key) { + if (key.hasOwnProperty(name)) { + shape[name] = key[name]; + } + } + } + else { + shape[key] = value; + } + this.dirty(true); + } + return this; + }, + + getLineScale: function () { + var m = this.transform; + // Get the line scale. + // Determinant of `m` means how much the area is enlarged by the + // transformation. So its square root can be used as a scale factor + // for width. + return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10 + ? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1])) + : 1; + } +}; + +/** + * 扩展一个 Path element, 比如星形,圆等。 + * Extend a path element + * @param {Object} props + * @param {string} props.type Path type + * @param {Function} props.init Initialize + * @param {Function} props.buildPath Overwrite buildPath method + * @param {Object} [props.style] Extended default style config + * @param {Object} [props.shape] Extended default shape config + */ +Path.extend = function (defaults$$1) { + var Sub = function (opts) { + Path.call(this, opts); + + if (defaults$$1.style) { + // Extend default style + this.style.extendFrom(defaults$$1.style, false); + } + + // Extend default shape + var defaultShape = defaults$$1.shape; + if (defaultShape) { + this.shape = this.shape || {}; + var thisShape = this.shape; + for (var name in defaultShape) { + if ( + !thisShape.hasOwnProperty(name) + && defaultShape.hasOwnProperty(name) + ) { + thisShape[name] = defaultShape[name]; + } + } + } + + defaults$$1.init && defaults$$1.init.call(this, opts); + }; + + inherits(Sub, Path); + + // FIXME 不能 extend position, rotation 等引用对象 + for (var name in defaults$$1) { + // Extending prototype values and methods + if (name !== 'style' && name !== 'shape') { + Sub.prototype[name] = defaults$$1[name]; + } + } + + return Sub; +}; + +inherits(Path, Displayable); + +var CMD$2 = PathProxy.CMD; + +var points = [[], [], []]; +var mathSqrt$3 = Math.sqrt; +var mathAtan2 = Math.atan2; + +var transformPath = function (path, m) { + var data = path.data; + var cmd; + var nPoint; + var i; + var j; + var k; + var p; + + var M = CMD$2.M; + var C = CMD$2.C; + var L = CMD$2.L; + var R = CMD$2.R; + var A = CMD$2.A; + var Q = CMD$2.Q; + + for (i = 0, j = 0; i < data.length;) { + cmd = data[i++]; + j = i; + nPoint = 0; + + switch (cmd) { + case M: + nPoint = 1; + break; + case L: + nPoint = 1; + break; + case C: + nPoint = 3; + break; + case Q: + nPoint = 2; + break; + case A: + var x = m[4]; + var y = m[5]; + var sx = mathSqrt$3(m[0] * m[0] + m[1] * m[1]); + var sy = mathSqrt$3(m[2] * m[2] + m[3] * m[3]); + var angle = mathAtan2(-m[1] / sy, m[0] / sx); + // cx + data[i] *= sx; + data[i++] += x; + // cy + data[i] *= sy; + data[i++] += y; + // Scale rx and ry + // FIXME Assume psi is 0 here + data[i++] *= sx; + data[i++] *= sy; + + // Start angle + data[i++] += angle; + // end angle + data[i++] += angle; + // FIXME psi + i += 2; + j = i; + break; + case R: + // x0, y0 + p[0] = data[i++]; + p[1] = data[i++]; + applyTransform(p, p, m); + data[j++] = p[0]; + data[j++] = p[1]; + // x1, y1 + p[0] += data[i++]; + p[1] += data[i++]; + applyTransform(p, p, m); + data[j++] = p[0]; + data[j++] = p[1]; + } + + for (k = 0; k < nPoint; k++) { + var p = points[k]; + p[0] = data[i++]; + p[1] = data[i++]; + + applyTransform(p, p, m); + // Write back + data[j++] = p[0]; + data[j++] = p[1]; + } + } +}; + +// command chars +// var cc = [ +// 'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', +// 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A' +// ]; + +var mathSqrt = Math.sqrt; +var mathSin = Math.sin; +var mathCos = Math.cos; +var PI = Math.PI; + +var vMag = function (v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1]); +}; +var vRatio = function (u, v) { + return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); +}; +var vAngle = function (u, v) { + return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) + * Math.acos(vRatio(u, v)); +}; + +function processArc(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg, cmd, path) { + var psi = psiDeg * (PI / 180.0); + var xp = mathCos(psi) * (x1 - x2) / 2.0 + + mathSin(psi) * (y1 - y2) / 2.0; + var yp = -1 * mathSin(psi) * (x1 - x2) / 2.0 + + mathCos(psi) * (y1 - y2) / 2.0; + + var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); + + if (lambda > 1) { + rx *= mathSqrt(lambda); + ry *= mathSqrt(lambda); + } + + var f = (fa === fs ? -1 : 1) + * mathSqrt((((rx * rx) * (ry * ry)) + - ((rx * rx) * (yp * yp)) + - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp) + + (ry * ry) * (xp * xp)) + ) || 0; + + var cxp = f * rx * yp / ry; + var cyp = f * -ry * xp / rx; + + var cx = (x1 + x2) / 2.0 + + mathCos(psi) * cxp + - mathSin(psi) * cyp; + var cy = (y1 + y2) / 2.0 + + mathSin(psi) * cxp + + mathCos(psi) * cyp; + + var theta = vAngle([ 1, 0 ], [ (xp - cxp) / rx, (yp - cyp) / ry ]); + var u = [ (xp - cxp) / rx, (yp - cyp) / ry ]; + var v = [ (-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry ]; + var dTheta = vAngle(u, v); + + if (vRatio(u, v) <= -1) { + dTheta = PI; + } + if (vRatio(u, v) >= 1) { + dTheta = 0; + } + if (fs === 0 && dTheta > 0) { + dTheta = dTheta - 2 * PI; + } + if (fs === 1 && dTheta < 0) { + dTheta = dTheta + 2 * PI; + } + + path.addData(cmd, cx, cy, rx, ry, theta, dTheta, psi, fs); +} + + +var commandReg = /([mlvhzcqtsa])([^mlvhzcqtsa]*)/ig; +// Consider case: +// (1) delimiter can be comma or space, where continuous commas +// or spaces should be seen as one comma. +// (2) value can be like: +// '2e-4', 'l.5.9' (ignore 0), 'M-10-10', 'l-2.43e-1,34.9983', +// 'l-.5E1,54', '121-23-44-11' (no delimiter) +var numberReg = /-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g; +// var valueSplitReg = /[\s,]+/; + +function createPathProxyFromString(data) { + if (!data) { + return new PathProxy(); + } + + // var data = data.replace(/-/g, ' -') + // .replace(/ /g, ' ') + // .replace(/ /g, ',') + // .replace(/,,/g, ','); + + // var n; + // create pipes so that we can split the data + // for (n = 0; n < cc.length; n++) { + // cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); + // } + + // data = data.replace(/-/g, ',-'); + + // create array + // var arr = cs.split('|'); + // init context point + var cpx = 0; + var cpy = 0; + var subpathX = cpx; + var subpathY = cpy; + var prevCmd; + + var path = new PathProxy(); + var CMD = PathProxy.CMD; + + // commandReg.lastIndex = 0; + // var cmdResult; + // while ((cmdResult = commandReg.exec(data)) != null) { + // var cmdStr = cmdResult[1]; + // var cmdContent = cmdResult[2]; + + var cmdList = data.match(commandReg); + for (var l = 0; l < cmdList.length; l++) { + var cmdText = cmdList[l]; + var cmdStr = cmdText.charAt(0); + + var cmd; + + // String#split is faster a little bit than String#replace or RegExp#exec. + // var p = cmdContent.split(valueSplitReg); + // var pLen = 0; + // for (var i = 0; i < p.length; i++) { + // // '' and other invalid str => NaN + // var val = parseFloat(p[i]); + // !isNaN(val) && (p[pLen++] = val); + // } + + var p = cmdText.match(numberReg) || []; + var pLen = p.length; + for (var i = 0; i < pLen; i++) { + p[i] = parseFloat(p[i]); + } + + var off = 0; + while (off < pLen) { + var ctlPtx; + var ctlPty; + + var rx; + var ry; + var psi; + var fa; + var fs; + + var x1 = cpx; + var y1 = cpy; + + // convert l, H, h, V, and v to L + switch (cmdStr) { + case 'l': + cpx += p[off++]; + cpy += p[off++]; + cmd = CMD.L; + path.addData(cmd, cpx, cpy); + break; + case 'L': + cpx = p[off++]; + cpy = p[off++]; + cmd = CMD.L; + path.addData(cmd, cpx, cpy); + break; + case 'm': + cpx += p[off++]; + cpy += p[off++]; + cmd = CMD.M; + path.addData(cmd, cpx, cpy); + subpathX = cpx; + subpathY = cpy; + cmdStr = 'l'; + break; + case 'M': + cpx = p[off++]; + cpy = p[off++]; + cmd = CMD.M; + path.addData(cmd, cpx, cpy); + subpathX = cpx; + subpathY = cpy; + cmdStr = 'L'; + break; + case 'h': + cpx += p[off++]; + cmd = CMD.L; + path.addData(cmd, cpx, cpy); + break; + case 'H': + cpx = p[off++]; + cmd = CMD.L; + path.addData(cmd, cpx, cpy); + break; + case 'v': + cpy += p[off++]; + cmd = CMD.L; + path.addData(cmd, cpx, cpy); + break; + case 'V': + cpy = p[off++]; + cmd = CMD.L; + path.addData(cmd, cpx, cpy); + break; + case 'C': + cmd = CMD.C; + path.addData( + cmd, p[off++], p[off++], p[off++], p[off++], p[off++], p[off++] + ); + cpx = p[off - 2]; + cpy = p[off - 1]; + break; + case 'c': + cmd = CMD.C; + path.addData( + cmd, + p[off++] + cpx, p[off++] + cpy, + p[off++] + cpx, p[off++] + cpy, + p[off++] + cpx, p[off++] + cpy + ); + cpx += p[off - 2]; + cpy += p[off - 1]; + break; + case 'S': + ctlPtx = cpx; + ctlPty = cpy; + var len = path.len(); + var pathData = path.data; + if (prevCmd === CMD.C) { + ctlPtx += cpx - pathData[len - 4]; + ctlPty += cpy - pathData[len - 3]; + } + cmd = CMD.C; + x1 = p[off++]; + y1 = p[off++]; + cpx = p[off++]; + cpy = p[off++]; + path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy); + break; + case 's': + ctlPtx = cpx; + ctlPty = cpy; + var len = path.len(); + var pathData = path.data; + if (prevCmd === CMD.C) { + ctlPtx += cpx - pathData[len - 4]; + ctlPty += cpy - pathData[len - 3]; + } + cmd = CMD.C; + x1 = cpx + p[off++]; + y1 = cpy + p[off++]; + cpx += p[off++]; + cpy += p[off++]; + path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy); + break; + case 'Q': + x1 = p[off++]; + y1 = p[off++]; + cpx = p[off++]; + cpy = p[off++]; + cmd = CMD.Q; + path.addData(cmd, x1, y1, cpx, cpy); + break; + case 'q': + x1 = p[off++] + cpx; + y1 = p[off++] + cpy; + cpx += p[off++]; + cpy += p[off++]; + cmd = CMD.Q; + path.addData(cmd, x1, y1, cpx, cpy); + break; + case 'T': + ctlPtx = cpx; + ctlPty = cpy; + var len = path.len(); + var pathData = path.data; + if (prevCmd === CMD.Q) { + ctlPtx += cpx - pathData[len - 4]; + ctlPty += cpy - pathData[len - 3]; + } + cpx = p[off++]; + cpy = p[off++]; + cmd = CMD.Q; + path.addData(cmd, ctlPtx, ctlPty, cpx, cpy); + break; + case 't': + ctlPtx = cpx; + ctlPty = cpy; + var len = path.len(); + var pathData = path.data; + if (prevCmd === CMD.Q) { + ctlPtx += cpx - pathData[len - 4]; + ctlPty += cpy - pathData[len - 3]; + } + cpx += p[off++]; + cpy += p[off++]; + cmd = CMD.Q; + path.addData(cmd, ctlPtx, ctlPty, cpx, cpy); + break; + case 'A': + rx = p[off++]; + ry = p[off++]; + psi = p[off++]; + fa = p[off++]; + fs = p[off++]; + + x1 = cpx, y1 = cpy; + cpx = p[off++]; + cpy = p[off++]; + cmd = CMD.A; + processArc( + x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path + ); + break; + case 'a': + rx = p[off++]; + ry = p[off++]; + psi = p[off++]; + fa = p[off++]; + fs = p[off++]; + + x1 = cpx, y1 = cpy; + cpx += p[off++]; + cpy += p[off++]; + cmd = CMD.A; + processArc( + x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path + ); + break; + } + } + + if (cmdStr === 'z' || cmdStr === 'Z') { + cmd = CMD.Z; + path.addData(cmd); + // z may be in the middle of the path. + cpx = subpathX; + cpy = subpathY; + } + + prevCmd = cmd; + } + + path.toStatic(); + + return path; +} + +// TODO Optimize double memory cost problem +function createPathOptions(str, opts) { + var pathProxy = createPathProxyFromString(str); + opts = opts || {}; + opts.buildPath = function (path) { + if (path.setData) { + path.setData(pathProxy.data); + // Svg and vml renderer don't have context + var ctx = path.getContext(); + if (ctx) { + path.rebuildPath(ctx); + } + } + else { + var ctx = path; + pathProxy.rebuildPath(ctx); + } + }; + + opts.applyTransform = function (m) { + transformPath(pathProxy, m); + this.dirty(true); + }; + + return opts; +} + +/** + * Create a Path object from path string data + * http://www.w3.org/TR/SVG/paths.html#PathData + * @param {Object} opts Other options + */ +function createFromString(str, opts) { + return new Path(createPathOptions(str, opts)); +} + +/** + * Create a Path class from path string data + * @param {string} str + * @param {Object} opts Other options + */ +function extendFromString(str, opts) { + return Path.extend(createPathOptions(str, opts)); +} + +/** + * Merge multiple paths + */ +// TODO Apply transform +// TODO stroke dash +// TODO Optimize double memory cost problem +function mergePath$1(pathEls, opts) { + var pathList = []; + var len = pathEls.length; + for (var i = 0; i < len; i++) { + var pathEl = pathEls[i]; + if (!pathEl.path) { + pathEl.createPathProxy(); + } + if (pathEl.__dirtyPath) { + pathEl.buildPath(pathEl.path, pathEl.shape, true); + } + pathList.push(pathEl.path); + } + + var pathBundle = new Path(opts); + // Need path proxy. + pathBundle.createPathProxy(); + pathBundle.buildPath = function (path) { + path.appendPath(pathList); + // Svg and vml renderer don't have context + var ctx = path.getContext(); + if (ctx) { + path.rebuildPath(ctx); + } + }; + + return pathBundle; +} + +/** + * @alias zrender/graphic/Text + * @extends module:zrender/graphic/Displayable + * @constructor + * @param {Object} opts + */ +var Text = function (opts) { // jshint ignore:line + Displayable.call(this, opts); +}; + +Text.prototype = { + + constructor: Text, + + type: 'text', + + brush: function (ctx, prevEl) { + var style = this.style; + + // Optimize, avoid normalize every time. + this.__dirty && normalizeTextStyle(style, true); + + // Use props with prefix 'text'. + style.fill = style.stroke = style.shadowBlur = style.shadowColor = + style.shadowOffsetX = style.shadowOffsetY = null; + + var text = style.text; + // Convert to string + text != null && (text += ''); + + // Do not apply style.bind in Text node. Because the real bind job + // is in textHelper.renderText, and performance of text render should + // be considered. + // style.bind(ctx, this, prevEl); + + if (!needDrawText(text, style)) { + // The current el.style is not applied + // and should not be used as cache. + ctx.__attrCachedBy = ContextCachedBy.NONE; + return; + } + + this.setTransform(ctx); + + renderText(this, ctx, text, style, null, prevEl); + + this.restoreTransform(ctx); + }, + + getBoundingRect: function () { + var style = this.style; + + // Optimize, avoid normalize every time. + this.__dirty && normalizeTextStyle(style, true); + + if (!this._rect) { + var text = style.text; + text != null ? (text += '') : (text = ''); + + var rect = getBoundingRect( + style.text + '', + style.font, + style.textAlign, + style.textVerticalAlign, + style.textPadding, + style.textLineHeight, + style.rich + ); + + rect.x += style.x || 0; + rect.y += style.y || 0; + + if (getStroke(style.textStroke, style.textStrokeWidth)) { + var w = style.textStrokeWidth; + rect.x -= w / 2; + rect.y -= w / 2; + rect.width += w; + rect.height += w; + } + + this._rect = rect; + } + + return this._rect; + } +}; + +inherits(Text, Displayable); + +/** + * 圆形 + * @module zrender/shape/Circle + */ + +var Circle = Path.extend({ + + type: 'circle', + + shape: { + cx: 0, + cy: 0, + r: 0 + }, + + + buildPath: function (ctx, shape, inBundle) { + // Better stroking in ShapeBundle + // Always do it may have performence issue ( fill may be 2x more cost) + if (inBundle) { + ctx.moveTo(shape.cx + shape.r, shape.cy); + } + // else { + // if (ctx.allocate && !ctx.data.length) { + // ctx.allocate(ctx.CMD_MEM_SIZE.A); + // } + // } + // Better stroking in ShapeBundle + // ctx.moveTo(shape.cx + shape.r, shape.cy); + ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2, true); + } +}); + +// Fix weird bug in some version of IE11 (like 11.0.9600.178**), +// where exception "unexpected call to method or property access" +// might be thrown when calling ctx.fill or ctx.stroke after a path +// whose area size is zero is drawn and ctx.clip() is called and +// shadowBlur is set. See #4572, #3112, #5777. +// (e.g., +// ctx.moveTo(10, 10); +// ctx.lineTo(20, 10); +// ctx.closePath(); +// ctx.clip(); +// ctx.shadowBlur = 10; +// ... +// ctx.fill(); +// ) + +var shadowTemp = [ + ['shadowBlur', 0], + ['shadowColor', '#000'], + ['shadowOffsetX', 0], + ['shadowOffsetY', 0] +]; + +var fixClipWithShadow = function (orignalBrush) { + + // version string can be: '11.0' + return (env$1.browser.ie && env$1.browser.version >= 11) + + ? function () { + var clipPaths = this.__clipPaths; + var style = this.style; + var modified; + + if (clipPaths) { + for (var i = 0; i < clipPaths.length; i++) { + var clipPath = clipPaths[i]; + var shape = clipPath && clipPath.shape; + var type = clipPath && clipPath.type; + + if (shape && ( + (type === 'sector' && shape.startAngle === shape.endAngle) + || (type === 'rect' && (!shape.width || !shape.height)) + )) { + for (var j = 0; j < shadowTemp.length; j++) { + // It is save to put shadowTemp static, because shadowTemp + // will be all modified each item brush called. + shadowTemp[j][2] = style[shadowTemp[j][0]]; + style[shadowTemp[j][0]] = shadowTemp[j][1]; + } + modified = true; + break; + } + } + } + + orignalBrush.apply(this, arguments); + + if (modified) { + for (var j = 0; j < shadowTemp.length; j++) { + style[shadowTemp[j][0]] = shadowTemp[j][2]; + } + } + } + + : orignalBrush; +}; + +/** + * 扇形 + * @module zrender/graphic/shape/Sector + */ + +var Sector = Path.extend({ + + type: 'sector', + + shape: { + + cx: 0, + + cy: 0, + + r0: 0, + + r: 0, + + startAngle: 0, + + endAngle: Math.PI * 2, + + clockwise: true + }, + + brush: fixClipWithShadow(Path.prototype.brush), + + buildPath: function (ctx, shape) { + + var x = shape.cx; + var y = shape.cy; + var r0 = Math.max(shape.r0 || 0, 0); + var r = Math.max(shape.r, 0); + var startAngle = shape.startAngle; + var endAngle = shape.endAngle; + var clockwise = shape.clockwise; + + var unitX = Math.cos(startAngle); + var unitY = Math.sin(startAngle); + + ctx.moveTo(unitX * r0 + x, unitY * r0 + y); + + ctx.lineTo(unitX * r + x, unitY * r + y); + + ctx.arc(x, y, r, startAngle, endAngle, !clockwise); + + ctx.lineTo( + Math.cos(endAngle) * r0 + x, + Math.sin(endAngle) * r0 + y + ); + + if (r0 !== 0) { + ctx.arc(x, y, r0, endAngle, startAngle, clockwise); + } + + ctx.closePath(); + } +}); + +/** + * 圆环 + * @module zrender/graphic/shape/Ring + */ + +var Ring = Path.extend({ + + type: 'ring', + + shape: { + cx: 0, + cy: 0, + r: 0, + r0: 0 + }, + + buildPath: function (ctx, shape) { + var x = shape.cx; + var y = shape.cy; + var PI2 = Math.PI * 2; + ctx.moveTo(x + shape.r, y); + ctx.arc(x, y, shape.r, 0, PI2, false); + ctx.moveTo(x + shape.r0, y); + ctx.arc(x, y, shape.r0, 0, PI2, true); + } +}); + +/** + * Catmull-Rom spline 插值折线 + * @module zrender/shape/util/smoothSpline + * @author pissang (https://www.github.com/pissang) + * Kener (@Kener-林峰, kener.linfeng@gmail.com) + * errorrik (errorrik@gmail.com) + */ + +/** + * @inner + */ +function interpolate(p0, p1, p2, p3, t, t2, t3) { + var v0 = (p2 - p0) * 0.5; + var v1 = (p3 - p1) * 0.5; + return (2 * (p1 - p2) + v0 + v1) * t3 + + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + + v0 * t + p1; +} + +/** + * @alias module:zrender/shape/util/smoothSpline + * @param {Array} points 线段顶点数组 + * @param {boolean} isLoop + * @return {Array} + */ +var smoothSpline = function (points, isLoop) { + var len$$1 = points.length; + var ret = []; + + var distance$$1 = 0; + for (var i = 1; i < len$$1; i++) { + distance$$1 += distance(points[i - 1], points[i]); + } + + var segs = distance$$1 / 2; + segs = segs < len$$1 ? len$$1 : segs; + for (var i = 0; i < segs; i++) { + var pos = i / (segs - 1) * (isLoop ? len$$1 : len$$1 - 1); + var idx = Math.floor(pos); + + var w = pos - idx; + + var p0; + var p1 = points[idx % len$$1]; + var p2; + var p3; + if (!isLoop) { + p0 = points[idx === 0 ? idx : idx - 1]; + p2 = points[idx > len$$1 - 2 ? len$$1 - 1 : idx + 1]; + p3 = points[idx > len$$1 - 3 ? len$$1 - 1 : idx + 2]; + } + else { + p0 = points[(idx - 1 + len$$1) % len$$1]; + p2 = points[(idx + 1) % len$$1]; + p3 = points[(idx + 2) % len$$1]; + } + + var w2 = w * w; + var w3 = w * w2; + + ret.push([ + interpolate(p0[0], p1[0], p2[0], p3[0], w, w2, w3), + interpolate(p0[1], p1[1], p2[1], p3[1], w, w2, w3) + ]); + } + return ret; +}; + +/** + * 贝塞尔平滑曲线 + * @module zrender/shape/util/smoothBezier + * @author pissang (https://www.github.com/pissang) + * Kener (@Kener-林峰, kener.linfeng@gmail.com) + * errorrik (errorrik@gmail.com) + */ + +/** + * 贝塞尔平滑曲线 + * @alias module:zrender/shape/util/smoothBezier + * @param {Array} points 线段顶点数组 + * @param {number} smooth 平滑等级, 0-1 + * @param {boolean} isLoop + * @param {Array} constraint 将计算出来的控制点约束在一个包围盒内 + * 比如 [[0, 0], [100, 100]], 这个包围盒会与 + * 整个折线的包围盒做一个并集用来约束控制点。 + * @param {Array} 计算出来的控制点数组 + */ +var smoothBezier = function (points, smooth, isLoop, constraint) { + var cps = []; + + var v = []; + var v1 = []; + var v2 = []; + var prevPoint; + var nextPoint; + + var min$$1; + var max$$1; + if (constraint) { + min$$1 = [Infinity, Infinity]; + max$$1 = [-Infinity, -Infinity]; + for (var i = 0, len$$1 = points.length; i < len$$1; i++) { + min(min$$1, min$$1, points[i]); + max(max$$1, max$$1, points[i]); + } + // 与指定的包围盒做并集 + min(min$$1, min$$1, constraint[0]); + max(max$$1, max$$1, constraint[1]); + } + + for (var i = 0, len$$1 = points.length; i < len$$1; i++) { + var point = points[i]; + + if (isLoop) { + prevPoint = points[i ? i - 1 : len$$1 - 1]; + nextPoint = points[(i + 1) % len$$1]; + } + else { + if (i === 0 || i === len$$1 - 1) { + cps.push(clone$1(points[i])); + continue; + } + else { + prevPoint = points[i - 1]; + nextPoint = points[i + 1]; + } + } + + sub(v, nextPoint, prevPoint); + + // use degree to scale the handle length + scale(v, v, smooth); + + var d0 = distance(point, prevPoint); + var d1 = distance(point, nextPoint); + var sum = d0 + d1; + if (sum !== 0) { + d0 /= sum; + d1 /= sum; + } + + scale(v1, v, -d0); + scale(v2, v, d1); + var cp0 = add([], point, v1); + var cp1 = add([], point, v2); + if (constraint) { + max(cp0, cp0, min$$1); + min(cp0, cp0, max$$1); + max(cp1, cp1, min$$1); + min(cp1, cp1, max$$1); + } + cps.push(cp0); + cps.push(cp1); + } + + if (isLoop) { + cps.push(cps.shift()); + } + + return cps; +}; + +function buildPath$1(ctx, shape, closePath) { + var points = shape.points; + var smooth = shape.smooth; + if (points && points.length >= 2) { + if (smooth && smooth !== 'spline') { + var controlPoints = smoothBezier( + points, smooth, closePath, shape.smoothConstraint + ); + + ctx.moveTo(points[0][0], points[0][1]); + var len = points.length; + for (var i = 0; i < (closePath ? len : len - 1); i++) { + var cp1 = controlPoints[i * 2]; + var cp2 = controlPoints[i * 2 + 1]; + var p = points[(i + 1) % len]; + ctx.bezierCurveTo( + cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1] + ); + } + } + else { + if (smooth === 'spline') { + points = smoothSpline(points, closePath); + } + + ctx.moveTo(points[0][0], points[0][1]); + for (var i = 1, l = points.length; i < l; i++) { + ctx.lineTo(points[i][0], points[i][1]); + } + } + + closePath && ctx.closePath(); + } +} + +/** + * 多边形 + * @module zrender/shape/Polygon + */ + +var Polygon = Path.extend({ + + type: 'polygon', + + shape: { + points: null, + + smooth: false, + + smoothConstraint: null + }, + + buildPath: function (ctx, shape) { + buildPath$1(ctx, shape, true); + } +}); + +/** + * @module zrender/graphic/shape/Polyline + */ + +var Polyline = Path.extend({ + + type: 'polyline', + + shape: { + points: null, + + smooth: false, + + smoothConstraint: null + }, + + style: { + stroke: '#000', + + fill: null + }, + + buildPath: function (ctx, shape) { + buildPath$1(ctx, shape, false); + } +}); + +/** + * Sub-pixel optimize for canvas rendering, prevent from blur + * when rendering a thin vertical/horizontal line. + */ + +var round = Math.round; + +/** + * Sub pixel optimize line for canvas + * + * @param {Object} outputShape The modification will be performed on `outputShape`. + * `outputShape` and `inputShape` can be the same object. + * `outputShape` object can be used repeatly, because all of + * the `x1`, `x2`, `y1`, `y2` will be assigned in this method. + * @param {Object} [inputShape] + * @param {number} [inputShape.x1] + * @param {number} [inputShape.y1] + * @param {number} [inputShape.x2] + * @param {number} [inputShape.y2] + * @param {Object} [style] + * @param {number} [style.lineWidth] + */ +function subPixelOptimizeLine$1(outputShape, inputShape, style) { + var lineWidth = style && style.lineWidth; + + if (!inputShape || !lineWidth) { + return; + } + + var x1 = inputShape.x1; + var x2 = inputShape.x2; + var y1 = inputShape.y1; + var y2 = inputShape.y2; + + if (round(x1 * 2) === round(x2 * 2)) { + outputShape.x1 = outputShape.x2 = subPixelOptimize$1(x1, lineWidth, true); + } + else { + outputShape.x1 = x1; + outputShape.x2 = x2; + } + if (round(y1 * 2) === round(y2 * 2)) { + outputShape.y1 = outputShape.y2 = subPixelOptimize$1(y1, lineWidth, true); + } + else { + outputShape.y1 = y1; + outputShape.y2 = y2; + } +} + +/** + * Sub pixel optimize rect for canvas + * + * @param {Object} outputShape The modification will be performed on `outputShape`. + * `outputShape` and `inputShape` can be the same object. + * `outputShape` object can be used repeatly, because all of + * the `x`, `y`, `width`, `height` will be assigned in this method. + * @param {Object} [inputShape] + * @param {number} [inputShape.x] + * @param {number} [inputShape.y] + * @param {number} [inputShape.width] + * @param {number} [inputShape.height] + * @param {Object} [style] + * @param {number} [style.lineWidth] + */ +function subPixelOptimizeRect$1(outputShape, inputShape, style) { + var lineWidth = style && style.lineWidth; + + if (!inputShape || !lineWidth) { + return; + } + + var originX = inputShape.x; + var originY = inputShape.y; + var originWidth = inputShape.width; + var originHeight = inputShape.height; + + outputShape.x = subPixelOptimize$1(originX, lineWidth, true); + outputShape.y = subPixelOptimize$1(originY, lineWidth, true); + outputShape.width = Math.max( + subPixelOptimize$1(originX + originWidth, lineWidth, false) - outputShape.x, + originWidth === 0 ? 0 : 1 + ); + outputShape.height = Math.max( + subPixelOptimize$1(originY + originHeight, lineWidth, false) - outputShape.y, + originHeight === 0 ? 0 : 1 + ); +} + +/** + * Sub pixel optimize for canvas + * + * @param {number} position Coordinate, such as x, y + * @param {number} lineWidth Should be nonnegative integer. + * @param {boolean=} positiveOrNegative Default false (negative). + * @return {number} Optimized position. + */ +function subPixelOptimize$1(position, lineWidth, positiveOrNegative) { + // Assure that (position + lineWidth / 2) is near integer edge, + // otherwise line will be fuzzy in canvas. + var doubledPosition = round(position * 2); + return (doubledPosition + round(lineWidth)) % 2 === 0 + ? doubledPosition / 2 + : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2; +} + +/** + * 矩形 + * @module zrender/graphic/shape/Rect + */ + +// Avoid create repeatly. +var subPixelOptimizeOutputShape = {}; + +var Rect = Path.extend({ + + type: 'rect', + + shape: { + // 左上、右上、右下、左下角的半径依次为r1、r2、r3、r4 + // r缩写为1 相当于 [1, 1, 1, 1] + // r缩写为[1] 相当于 [1, 1, 1, 1] + // r缩写为[1, 2] 相当于 [1, 2, 1, 2] + // r缩写为[1, 2, 3] 相当于 [1, 2, 3, 2] + r: 0, + + x: 0, + y: 0, + width: 0, + height: 0 + }, + + buildPath: function (ctx, shape) { + var x; + var y; + var width; + var height; + + if (this.subPixelOptimize) { + subPixelOptimizeRect$1(subPixelOptimizeOutputShape, shape, this.style); + x = subPixelOptimizeOutputShape.x; + y = subPixelOptimizeOutputShape.y; + width = subPixelOptimizeOutputShape.width; + height = subPixelOptimizeOutputShape.height; + subPixelOptimizeOutputShape.r = shape.r; + shape = subPixelOptimizeOutputShape; + } + else { + x = shape.x; + y = shape.y; + width = shape.width; + height = shape.height; + } + + if (!shape.r) { + ctx.rect(x, y, width, height); + } + else { + buildPath(ctx, shape); + } + ctx.closePath(); + return; + } +}); + +/** + * 直线 + * @module zrender/graphic/shape/Line + */ + +// Avoid create repeatly. +var subPixelOptimizeOutputShape$1 = {}; + +var Line = Path.extend({ + + type: 'line', + + shape: { + // Start point + x1: 0, + y1: 0, + // End point + x2: 0, + y2: 0, + + percent: 1 + }, + + style: { + stroke: '#000', + fill: null + }, + + buildPath: function (ctx, shape) { + var x1; + var y1; + var x2; + var y2; + + if (this.subPixelOptimize) { + subPixelOptimizeLine$1(subPixelOptimizeOutputShape$1, shape, this.style); + x1 = subPixelOptimizeOutputShape$1.x1; + y1 = subPixelOptimizeOutputShape$1.y1; + x2 = subPixelOptimizeOutputShape$1.x2; + y2 = subPixelOptimizeOutputShape$1.y2; + } + else { + x1 = shape.x1; + y1 = shape.y1; + x2 = shape.x2; + y2 = shape.y2; + } + + var percent = shape.percent; + + if (percent === 0) { + return; + } + + ctx.moveTo(x1, y1); + + if (percent < 1) { + x2 = x1 * (1 - percent) + x2 * percent; + y2 = y1 * (1 - percent) + y2 * percent; + } + ctx.lineTo(x2, y2); + }, + + /** + * Get point at percent + * @param {number} percent + * @return {Array.} + */ + pointAt: function (p) { + var shape = this.shape; + return [ + shape.x1 * (1 - p) + shape.x2 * p, + shape.y1 * (1 - p) + shape.y2 * p + ]; + } +}); + +/** + * 贝塞尔曲线 + * @module zrender/shape/BezierCurve + */ + +var out = []; + +function someVectorAt(shape, t, isTangent) { + var cpx2 = shape.cpx2; + var cpy2 = shape.cpy2; + if (cpx2 === null || cpy2 === null) { + return [ + (isTangent ? cubicDerivativeAt : cubicAt)(shape.x1, shape.cpx1, shape.cpx2, shape.x2, t), + (isTangent ? cubicDerivativeAt : cubicAt)(shape.y1, shape.cpy1, shape.cpy2, shape.y2, t) + ]; + } + else { + return [ + (isTangent ? quadraticDerivativeAt : quadraticAt)(shape.x1, shape.cpx1, shape.x2, t), + (isTangent ? quadraticDerivativeAt : quadraticAt)(shape.y1, shape.cpy1, shape.y2, t) + ]; + } +} + +var BezierCurve = Path.extend({ + + type: 'bezier-curve', + + shape: { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + cpx1: 0, + cpy1: 0, + // cpx2: 0, + // cpy2: 0 + + // Curve show percent, for animating + percent: 1 + }, + + style: { + stroke: '#000', + fill: null + }, + + buildPath: function (ctx, shape) { + var x1 = shape.x1; + var y1 = shape.y1; + var x2 = shape.x2; + var y2 = shape.y2; + var cpx1 = shape.cpx1; + var cpy1 = shape.cpy1; + var cpx2 = shape.cpx2; + var cpy2 = shape.cpy2; + var percent = shape.percent; + if (percent === 0) { + return; + } + + ctx.moveTo(x1, y1); + + if (cpx2 == null || cpy2 == null) { + if (percent < 1) { + quadraticSubdivide( + x1, cpx1, x2, percent, out + ); + cpx1 = out[1]; + x2 = out[2]; + quadraticSubdivide( + y1, cpy1, y2, percent, out + ); + cpy1 = out[1]; + y2 = out[2]; + } + + ctx.quadraticCurveTo( + cpx1, cpy1, + x2, y2 + ); + } + else { + if (percent < 1) { + cubicSubdivide( + x1, cpx1, cpx2, x2, percent, out + ); + cpx1 = out[1]; + cpx2 = out[2]; + x2 = out[3]; + cubicSubdivide( + y1, cpy1, cpy2, y2, percent, out + ); + cpy1 = out[1]; + cpy2 = out[2]; + y2 = out[3]; + } + ctx.bezierCurveTo( + cpx1, cpy1, + cpx2, cpy2, + x2, y2 + ); + } + }, + + /** + * Get point at percent + * @param {number} t + * @return {Array.} + */ + pointAt: function (t) { + return someVectorAt(this.shape, t, false); + }, + + /** + * Get tangent at percent + * @param {number} t + * @return {Array.} + */ + tangentAt: function (t) { + var p = someVectorAt(this.shape, t, true); + return normalize(p, p); + } +}); + +/** + * 圆弧 + * @module zrender/graphic/shape/Arc + */ + +var Arc = Path.extend({ + + type: 'arc', + + shape: { + + cx: 0, + + cy: 0, + + r: 0, + + startAngle: 0, + + endAngle: Math.PI * 2, + + clockwise: true + }, + + style: { + + stroke: '#000', + + fill: null + }, + + buildPath: function (ctx, shape) { + + var x = shape.cx; + var y = shape.cy; + var r = Math.max(shape.r, 0); + var startAngle = shape.startAngle; + var endAngle = shape.endAngle; + var clockwise = shape.clockwise; + + var unitX = Math.cos(startAngle); + var unitY = Math.sin(startAngle); + + ctx.moveTo(unitX * r + x, unitY * r + y); + ctx.arc(x, y, r, startAngle, endAngle, !clockwise); + } +}); + +// CompoundPath to improve performance + +var CompoundPath = Path.extend({ + + type: 'compound', + + shape: { + + paths: null + }, + + _updatePathDirty: function () { + var dirtyPath = this.__dirtyPath; + var paths = this.shape.paths; + for (var i = 0; i < paths.length; i++) { + // Mark as dirty if any subpath is dirty + dirtyPath = dirtyPath || paths[i].__dirtyPath; + } + this.__dirtyPath = dirtyPath; + this.__dirty = this.__dirty || dirtyPath; + }, + + beforeBrush: function () { + this._updatePathDirty(); + var paths = this.shape.paths || []; + var scale = this.getGlobalScale(); + // Update path scale + for (var i = 0; i < paths.length; i++) { + if (!paths[i].path) { + paths[i].createPathProxy(); + } + paths[i].path.setScale(scale[0], scale[1], paths[i].segmentIgnoreThreshold); + } + }, + + buildPath: function (ctx, shape) { + var paths = shape.paths || []; + for (var i = 0; i < paths.length; i++) { + paths[i].buildPath(ctx, paths[i].shape, true); + } + }, + + afterBrush: function () { + var paths = this.shape.paths || []; + for (var i = 0; i < paths.length; i++) { + paths[i].__dirtyPath = false; + } + }, + + getBoundingRect: function () { + this._updatePathDirty(); + return Path.prototype.getBoundingRect.call(this); + } +}); + +/** + * @param {Array.} colorStops + */ +var Gradient = function (colorStops) { + + this.colorStops = colorStops || []; + +}; + +Gradient.prototype = { + + constructor: Gradient, + + addColorStop: function (offset, color) { + this.colorStops.push({ + + offset: offset, + + color: color + }); + } + +}; + +/** + * x, y, x2, y2 are all percent from 0 to 1 + * @param {number} [x=0] + * @param {number} [y=0] + * @param {number} [x2=1] + * @param {number} [y2=0] + * @param {Array.} colorStops + * @param {boolean} [globalCoord=false] + */ +var LinearGradient = function (x, y, x2, y2, colorStops, globalCoord) { + // Should do nothing more in this constructor. Because gradient can be + // declard by `color: {type: 'linear', colorStops: ...}`, where + // this constructor will not be called. + + this.x = x == null ? 0 : x; + + this.y = y == null ? 0 : y; + + this.x2 = x2 == null ? 1 : x2; + + this.y2 = y2 == null ? 0 : y2; + + // Can be cloned + this.type = 'linear'; + + // If use global coord + this.global = globalCoord || false; + + Gradient.call(this, colorStops); +}; + +LinearGradient.prototype = { + + constructor: LinearGradient +}; + +inherits(LinearGradient, Gradient); + +/** + * x, y, r are all percent from 0 to 1 + * @param {number} [x=0.5] + * @param {number} [y=0.5] + * @param {number} [r=0.5] + * @param {Array.} [colorStops] + * @param {boolean} [globalCoord=false] + */ +var RadialGradient = function (x, y, r, colorStops, globalCoord) { + // Should do nothing more in this constructor. Because gradient can be + // declard by `color: {type: 'radial', colorStops: ...}`, where + // this constructor will not be called. + + this.x = x == null ? 0.5 : x; + + this.y = y == null ? 0.5 : y; + + this.r = r == null ? 0.5 : r; + + // Can be cloned + this.type = 'radial'; + + // If use global coord + this.global = globalCoord || false; + + Gradient.call(this, colorStops); +}; + +RadialGradient.prototype = { + + constructor: RadialGradient +}; + +inherits(RadialGradient, Gradient); + +/** + * Displayable for incremental rendering. It will be rendered in a separate layer + * IncrementalDisplay have two main methods. `clearDisplayables` and `addDisplayables` + * addDisplayables will render the added displayables incremetally. + * + * It use a not clearFlag to tell the painter don't clear the layer if it's the first element. + */ +// TODO Style override ? +function IncrementalDisplayble(opts) { + + Displayable.call(this, opts); + + this._displayables = []; + + this._temporaryDisplayables = []; + + this._cursor = 0; + + this.notClear = true; +} + +IncrementalDisplayble.prototype.incremental = true; + +IncrementalDisplayble.prototype.clearDisplaybles = function () { + this._displayables = []; + this._temporaryDisplayables = []; + this._cursor = 0; + this.dirty(); + + this.notClear = false; +}; + +IncrementalDisplayble.prototype.addDisplayable = function (displayable, notPersistent) { + if (notPersistent) { + this._temporaryDisplayables.push(displayable); + } + else { + this._displayables.push(displayable); + } + this.dirty(); +}; + +IncrementalDisplayble.prototype.addDisplayables = function (displayables, notPersistent) { + notPersistent = notPersistent || false; + for (var i = 0; i < displayables.length; i++) { + this.addDisplayable(displayables[i], notPersistent); + } +}; + +IncrementalDisplayble.prototype.eachPendingDisplayable = function (cb) { + for (var i = this._cursor; i < this._displayables.length; i++) { + cb && cb(this._displayables[i]); + } + for (var i = 0; i < this._temporaryDisplayables.length; i++) { + cb && cb(this._temporaryDisplayables[i]); + } +}; + +IncrementalDisplayble.prototype.update = function () { + this.updateTransform(); + for (var i = this._cursor; i < this._displayables.length; i++) { + var displayable = this._displayables[i]; + // PENDING + displayable.parent = this; + displayable.update(); + displayable.parent = null; + } + for (var i = 0; i < this._temporaryDisplayables.length; i++) { + var displayable = this._temporaryDisplayables[i]; + // PENDING + displayable.parent = this; + displayable.update(); + displayable.parent = null; + } +}; + +IncrementalDisplayble.prototype.brush = function (ctx, prevEl) { + // Render persistant displayables. + for (var i = this._cursor; i < this._displayables.length; i++) { + var displayable = this._displayables[i]; + displayable.beforeBrush && displayable.beforeBrush(ctx); + displayable.brush(ctx, i === this._cursor ? null : this._displayables[i - 1]); + displayable.afterBrush && displayable.afterBrush(ctx); + } + this._cursor = i; + // Render temporary displayables. + for (var i = 0; i < this._temporaryDisplayables.length; i++) { + var displayable = this._temporaryDisplayables[i]; + displayable.beforeBrush && displayable.beforeBrush(ctx); + displayable.brush(ctx, i === 0 ? null : this._temporaryDisplayables[i - 1]); + displayable.afterBrush && displayable.afterBrush(ctx); + } + + this._temporaryDisplayables = []; + + this.notClear = true; +}; + +var m = []; +IncrementalDisplayble.prototype.getBoundingRect = function () { + if (!this._rect) { + var rect = new BoundingRect(Infinity, Infinity, -Infinity, -Infinity); + for (var i = 0; i < this._displayables.length; i++) { + var displayable = this._displayables[i]; + var childRect = displayable.getBoundingRect().clone(); + if (displayable.needLocalTransform()) { + childRect.applyTransform(displayable.getLocalTransform(m)); + } + rect.union(childRect); + } + this._rect = rect; + } + return this._rect; +}; + +IncrementalDisplayble.prototype.contain = function (x, y) { + var localPos = this.transformCoordToLocal(x, y); + var rect = this.getBoundingRect(); + + if (rect.contain(localPos[0], localPos[1])) { + for (var i = 0; i < this._displayables.length; i++) { + var displayable = this._displayables[i]; + if (displayable.contain(x, y)) { + return true; + } + } + } + return false; +}; + +inherits(IncrementalDisplayble, Displayable); + +/* +* 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. +*/ + +var mathMax$1 = Math.max; +var mathMin$1 = Math.min; + +var EMPTY_OBJ = {}; + +var Z2_EMPHASIS_LIFT = 1; + +// key: label model property nane, value: style property name. +var CACHED_LABEL_STYLE_PROPERTIES = { + color: 'textFill', + textBorderColor: 'textStroke', + textBorderWidth: 'textStrokeWidth' +}; + +var EMPHASIS = 'emphasis'; +var NORMAL = 'normal'; + +// Reserve 0 as default. +var _highlightNextDigit = 1; +var _highlightKeyMap = {}; + +var _customShapeMap = {}; + + +/** + * Extend shape with parameters + */ +function extendShape(opts) { + return Path.extend(opts); +} + +/** + * Extend path + */ +function extendPath(pathData, opts) { + return extendFromString(pathData, opts); +} + +/** + * Register a user defined shape. + * The shape class can be fetched by `getShapeClass` + * This method will overwrite the registered shapes, including + * the registered built-in shapes, if using the same `name`. + * The shape can be used in `custom series` and + * `graphic component` by declaring `{type: name}`. + * + * @param {string} name + * @param {Object} ShapeClass Can be generated by `extendShape`. + */ +function registerShape(name, ShapeClass) { + _customShapeMap[name] = ShapeClass; +} + +/** + * Find shape class registered by `registerShape`. Usually used in + * fetching user defined shape. + * + * [Caution]: + * (1) This method **MUST NOT be used inside echarts !!!**, unless it is prepared + * to use user registered shapes. + * Because the built-in shape (see `getBuiltInShape`) will be registered by + * `registerShape` by default. That enables users to get both built-in + * shapes as well as the shapes belonging to themsleves. But users can overwrite + * the built-in shapes by using names like 'circle', 'rect' via calling + * `registerShape`. So the echarts inner featrues should not fetch shapes from here + * in case that it is overwritten by users, except that some features, like + * `custom series`, `graphic component`, do it deliberately. + * + * (2) In the features like `custom series`, `graphic component`, the user input + * `{tpye: 'xxx'}` does not only specify shapes but also specify other graphic + * elements like `'group'`, `'text'`, `'image'` or event `'path'`. Those names + * are reserved names, that is, if some user register a shape named `'image'`, + * the shape will not be used. If we intending to add some more reserved names + * in feature, that might bring break changes (disable some existing user shape + * names). But that case probably rearly happen. So we dont make more mechanism + * to resolve this issue here. + * + * @param {string} name + * @return {Object} The shape class. If not found, return nothing. + */ +function getShapeClass(name) { + if (_customShapeMap.hasOwnProperty(name)) { + return _customShapeMap[name]; + } +} + +/** + * Create a path element from path data string + * @param {string} pathData + * @param {Object} opts + * @param {module:zrender/core/BoundingRect} rect + * @param {string} [layout=cover] 'center' or 'cover' + */ +function makePath(pathData, opts, rect, layout) { + var path = createFromString(pathData, opts); + if (rect) { + if (layout === 'center') { + rect = centerGraphic(rect, path.getBoundingRect()); + } + resizePath(path, rect); + } + return path; +} + +/** + * Create a image element from image url + * @param {string} imageUrl image url + * @param {Object} opts options + * @param {module:zrender/core/BoundingRect} rect constrain rect + * @param {string} [layout=cover] 'center' or 'cover' + */ +function makeImage(imageUrl, rect, layout) { + var path = new ZImage({ + style: { + image: imageUrl, + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + }, + onload: function (img) { + if (layout === 'center') { + var boundingRect = { + width: img.width, + height: img.height + }; + path.setStyle(centerGraphic(rect, boundingRect)); + } + } + }); + return path; +} + +/** + * Get position of centered element in bounding box. + * + * @param {Object} rect element local bounding box + * @param {Object} boundingRect constraint bounding box + * @return {Object} element position containing x, y, width, and height + */ +function centerGraphic(rect, boundingRect) { + // Set rect to center, keep width / height ratio. + var aspect = boundingRect.width / boundingRect.height; + var width = rect.height * aspect; + var height; + if (width <= rect.width) { + height = rect.height; + } + else { + width = rect.width; + height = width / aspect; + } + var cx = rect.x + rect.width / 2; + var cy = rect.y + rect.height / 2; + + return { + x: cx - width / 2, + y: cy - height / 2, + width: width, + height: height + }; +} + +var mergePath = mergePath$1; + +/** + * Resize a path to fit the rect + * @param {module:zrender/graphic/Path} path + * @param {Object} rect + */ +function resizePath(path, rect) { + if (!path.applyTransform) { + return; + } + + var pathRect = path.getBoundingRect(); + + var m = pathRect.calculateTransform(rect); + + path.applyTransform(m); +} + +/** + * Sub pixel optimize line for canvas + * + * @param {Object} param + * @param {Object} [param.shape] + * @param {number} [param.shape.x1] + * @param {number} [param.shape.y1] + * @param {number} [param.shape.x2] + * @param {number} [param.shape.y2] + * @param {Object} [param.style] + * @param {number} [param.style.lineWidth] + * @return {Object} Modified param + */ +function subPixelOptimizeLine(param) { + subPixelOptimizeLine$1(param.shape, param.shape, param.style); + return param; +} + +/** + * Sub pixel optimize rect for canvas + * + * @param {Object} param + * @param {Object} [param.shape] + * @param {number} [param.shape.x] + * @param {number} [param.shape.y] + * @param {number} [param.shape.width] + * @param {number} [param.shape.height] + * @param {Object} [param.style] + * @param {number} [param.style.lineWidth] + * @return {Object} Modified param + */ +function subPixelOptimizeRect(param) { + subPixelOptimizeRect$1(param.shape, param.shape, param.style); + return param; +} + +/** + * Sub pixel optimize for canvas + * + * @param {number} position Coordinate, such as x, y + * @param {number} lineWidth Should be nonnegative integer. + * @param {boolean=} positiveOrNegative Default false (negative). + * @return {number} Optimized position. + */ +var subPixelOptimize = subPixelOptimize$1; + + +function hasFillOrStroke(fillOrStroke) { + return fillOrStroke != null && fillOrStroke !== 'none'; +} + +// Most lifted color are duplicated. +var liftedColorMap = createHashMap(); +var liftedColorCount = 0; + +function liftColor(color) { + if (typeof color !== 'string') { + return color; + } + var liftedColor = liftedColorMap.get(color); + if (!liftedColor) { + liftedColor = lift(color, -0.1); + if (liftedColorCount < 10000) { + liftedColorMap.set(color, liftedColor); + liftedColorCount++; + } + } + return liftedColor; +} + +function cacheElementStl(el) { + if (!el.__hoverStlDirty) { + return; + } + el.__hoverStlDirty = false; + + var hoverStyle = el.__hoverStl; + if (!hoverStyle) { + el.__cachedNormalStl = el.__cachedNormalZ2 = null; + return; + } + + var normalStyle = el.__cachedNormalStl = {}; + el.__cachedNormalZ2 = el.z2; + var elStyle = el.style; + + for (var name in hoverStyle) { + // See comment in `singleEnterEmphasis`. + if (hoverStyle[name] != null) { + normalStyle[name] = elStyle[name]; + } + } + + // Always cache fill and stroke to normalStyle for lifting color. + normalStyle.fill = elStyle.fill; + normalStyle.stroke = elStyle.stroke; +} + +function singleEnterEmphasis(el) { + var hoverStl = el.__hoverStl; + + if (!hoverStl || el.__highlighted) { + return; + } + + var zr = el.__zr; + + var useHoverLayer = el.useHoverLayer && zr && zr.painter.type === 'canvas'; + el.__highlighted = useHoverLayer ? 'layer' : 'plain'; + + if (el.isGroup || (!zr && el.useHoverLayer)) { + return; + } + + var elTarget = el; + var targetStyle = el.style; + + if (useHoverLayer) { + elTarget = zr.addHover(el); + targetStyle = elTarget.style; + } + + rollbackDefaultTextStyle(targetStyle); + + if (!useHoverLayer) { + cacheElementStl(elTarget); + } + + // styles can be: + // { + // label: { + // show: false, + // position: 'outside', + // fontSize: 18 + // }, + // emphasis: { + // label: { + // show: true + // } + // } + // }, + // where properties of `emphasis` may not appear in `normal`. We previously use + // module:echarts/util/model#defaultEmphasis to merge `normal` to `emphasis`. + // But consider rich text and setOption in merge mode, it is impossible to cover + // all properties in merge. So we use merge mode when setting style here. + // But we choose the merge strategy that only properties that is not `null/undefined`. + // Because when making a textStyle (espacially rich text), it is not easy to distinguish + // `hasOwnProperty` and `null/undefined` in code, so we trade them as the same for simplicity. + // But this strategy brings a trouble that `null/undefined` can not be used to remove + // style any more in `emphasis`. Users can both set properties directly on normal and + // emphasis to avoid this issue, or we might support `'none'` for this case if required. + targetStyle.extendFrom(hoverStl); + + setDefaultHoverFillStroke(targetStyle, hoverStl, 'fill'); + setDefaultHoverFillStroke(targetStyle, hoverStl, 'stroke'); + + applyDefaultTextStyle(targetStyle); + + if (!useHoverLayer) { + el.dirty(false); + el.z2 += Z2_EMPHASIS_LIFT; + } +} + +function setDefaultHoverFillStroke(targetStyle, hoverStyle, prop) { + if (!hasFillOrStroke(hoverStyle[prop]) && hasFillOrStroke(targetStyle[prop])) { + targetStyle[prop] = liftColor(targetStyle[prop]); + } +} + +function singleEnterNormal(el) { + var highlighted = el.__highlighted; + + if (!highlighted) { + return; + } + + el.__highlighted = false; + + if (el.isGroup) { + return; + } + + if (highlighted === 'layer') { + el.__zr && el.__zr.removeHover(el); + } + else { + var style = el.style; + + var normalStl = el.__cachedNormalStl; + if (normalStl) { + rollbackDefaultTextStyle(style); + el.setStyle(normalStl); + applyDefaultTextStyle(style); + } + // `__cachedNormalZ2` will not be reset if calling `setElementHoverStyle` + // when `el` is on emphasis state. So here by comparing with 1, we try + // hard to make the bug case rare. + var normalZ2 = el.__cachedNormalZ2; + if (normalZ2 != null && el.z2 - normalZ2 === Z2_EMPHASIS_LIFT) { + el.z2 = normalZ2; + } + } +} + +function traverseUpdate(el, updater, commonParam) { + // If root is group, also enter updater for `highDownOnUpdate`. + var fromState = NORMAL; + var toState = NORMAL; + var trigger; + // See the rule of `highDownOnUpdate` on `graphic.setAsHighDownDispatcher`. + el.__highlighted && (fromState = EMPHASIS, trigger = true); + updater(el, commonParam); + el.__highlighted && (toState = EMPHASIS, trigger = true); + + el.isGroup && el.traverse(function (child) { + !child.isGroup && updater(child, commonParam); + }); + + trigger && el.__highDownOnUpdate && el.__highDownOnUpdate(fromState, toState); +} + +/** + * Set hover style (namely "emphasis style") of element, based on the current + * style of the given `el`. + * This method should be called after all of the normal styles have been adopted + * to the `el`. See the reason on `setHoverStyle`. + * + * @param {module:zrender/Element} el Should not be `zrender/container/Group`. + * @param {Object} [el.hoverStyle] Can be set on el or its descendants, + * e.g., `el.hoverStyle = ...; graphic.setHoverStyle(el); `. + * Often used when item group has a label element and it's hoverStyle is different. + * @param {Object|boolean} [hoverStl] The specified hover style. + * If set as `false`, disable the hover style. + * Similarly, The `el.hoverStyle` can alse be set + * as `false` to disable the hover style. + * Otherwise, use the default hover style if not provided. + */ +function setElementHoverStyle(el, hoverStl) { + // For performance consideration, it might be better to make the "hover style" only the + // difference properties from the "normal style", but not a entire copy of all styles. + hoverStl = el.__hoverStl = hoverStl !== false && (el.hoverStyle || hoverStl || {}); + el.__hoverStlDirty = true; + + // FIXME + // It is not completely right to save "normal"/"emphasis" flag on elements. + // It probably should be saved on `data` of series. Consider the cases: + // (1) A highlighted elements are moved out of the view port and re-enter + // again by dataZoom. + // (2) call `setOption` and replace elements totally when they are highlighted. + if (el.__highlighted) { + // Consider the case: + // The styles of a highlighted `el` is being updated. The new "emphasis style" + // should be adapted to the `el`. Notice here new "normal styles" should have + // been set outside and the cached "normal style" is out of date. + el.__cachedNormalStl = null; + // Do not clear `__cachedNormalZ2` here, because setting `z2` is not a constraint + // of this method. In most cases, `z2` is not set and hover style should be able + // to rollback. Of course, that would bring bug, but only in a rare case, see + // `doSingleLeaveHover` for details. + singleEnterNormal(el); + + singleEnterEmphasis(el); + } +} + +function onElementMouseOver(e) { + !shouldSilent(this, e) + // "emphasis" event highlight has higher priority than mouse highlight. + && !this.__highByOuter + && traverseUpdate(this, singleEnterEmphasis); +} + +function onElementMouseOut(e) { + !shouldSilent(this, e) + // "emphasis" event highlight has higher priority than mouse highlight. + && !this.__highByOuter + && traverseUpdate(this, singleEnterNormal); +} + +function onElementEmphasisEvent(highlightDigit) { + this.__highByOuter |= 1 << (highlightDigit || 0); + traverseUpdate(this, singleEnterEmphasis); +} + +function onElementNormalEvent(highlightDigit) { + !(this.__highByOuter &= ~(1 << (highlightDigit || 0))) + && traverseUpdate(this, singleEnterNormal); +} + +function shouldSilent(el, e) { + return el.__highDownSilentOnTouch && e.zrByTouch; +} + +/** + * Set hover style (namely "emphasis style") of element, + * based on the current style of the given `el`. + * + * (1) + * **CONSTRAINTS** for this method: + * This method MUST be called after all of the normal styles having been adopted + * to the `el`. + * The input `hoverStyle` (that is, "emphasis style") MUST be the subset of the + * "normal style" having been set to the el. + * `color` MUST be one of the "normal styles" (because color might be lifted as + * a default hover style). + * + * The reason: this method treat the current style of the `el` as the "normal style" + * and cache them when enter/update the "emphasis style". Consider the case: the `el` + * is in "emphasis" state and `setOption`/`dispatchAction` trigger the style updating + * logic, where the el should shift from the original emphasis style to the new + * "emphasis style" and should be able to "downplay" back to the new "normal style". + * + * Indeed, it is error-prone to make a interface has so many constraints, but I have + * not found a better solution yet to fit the backward compatibility, performance and + * the current programming style. + * + * (2) + * Call the method for a "root" element once. Do not call it for each descendants. + * If the descendants elemenets of a group has itself hover style different from the + * root group, we can simply mount the style on `el.hoverStyle` for them, but should + * not call this method for them. + * + * (3) These input parameters can be set directly on `el`: + * + * @param {module:zrender/Element} el + * @param {Object} [el.hoverStyle] See `graphic.setElementHoverStyle`. + * @param {boolean} [el.highDownSilentOnTouch=false] See `graphic.setAsHighDownDispatcher`. + * @param {Function} [el.highDownOnUpdate] See `graphic.setAsHighDownDispatcher`. + * @param {Object|boolean} [hoverStyle] See `graphic.setElementHoverStyle`. + */ +function setHoverStyle(el, hoverStyle) { + setAsHighDownDispatcher(el, true); + traverseUpdate(el, setElementHoverStyle, hoverStyle); +} + +/** + * @param {module:zrender/Element} el + * @param {Function} [el.highDownOnUpdate] Called when state updated. + * Since `setHoverStyle` has the constraint that it must be called after + * all of the normal style updated, `highDownOnUpdate` is not needed to + * trigger if both `fromState` and `toState` is 'normal', and needed to + * trigger if both `fromState` and `toState` is 'emphasis', which enables + * to sync outside style settings to "emphasis" state. + * @this {string} This dispatcher `el`. + * @param {string} fromState Can be "normal" or "emphasis". + * `fromState` might equal to `toState`, + * for example, when this method is called when `el` is + * on "emphasis" state. + * @param {string} toState Can be "normal" or "emphasis". + * + * FIXME + * CAUTION: Do not expose `highDownOnUpdate` outside echarts. + * Because it is not a complete solution. The update + * listener should not have been mount in element, + * and the normal/emphasis state should not have + * mantained on elements. + * + * @param {boolean} [el.highDownSilentOnTouch=false] + * In touch device, mouseover event will be trigger on touchstart event + * (see module:zrender/dom/HandlerProxy). By this mechanism, we can + * conveniently use hoverStyle when tap on touch screen without additional + * code for compatibility. + * But if the chart/component has select feature, which usually also use + * hoverStyle, there might be conflict between 'select-highlight' and + * 'hover-highlight' especially when roam is enabled (see geo for example). + * In this case, `highDownSilentOnTouch` should be used to disable + * hover-highlight on touch device. + * @param {boolean} [asDispatcher=true] If `false`, do not set as "highDownDispatcher". + */ +function setAsHighDownDispatcher(el, asDispatcher) { + var disable = asDispatcher === false; + // Make `highDownSilentOnTouch` and `highDownOnUpdate` only work after + // `setAsHighDownDispatcher` called. Avoid it is modified by user unexpectedly. + el.__highDownSilentOnTouch = el.highDownSilentOnTouch; + el.__highDownOnUpdate = el.highDownOnUpdate; + + // Simple optimize, since this method might be + // called for each elements of a group in some cases. + if (!disable || el.__highDownDispatcher) { + var method = disable ? 'off' : 'on'; + + // Duplicated function will be auto-ignored, see Eventful.js. + el[method]('mouseover', onElementMouseOver)[method]('mouseout', onElementMouseOut); + // Emphasis, normal can be triggered manually by API or other components like hover link. + el[method]('emphasis', onElementEmphasisEvent)[method]('normal', onElementNormalEvent); + // Also keep previous record. + el.__highByOuter = el.__highByOuter || 0; + + el.__highDownDispatcher = !disable; + } +} + +/** + * @param {module:zrender/src/Element} el + * @return {boolean} + */ +function isHighDownDispatcher(el) { + return !!(el && el.__highDownDispatcher); +} + +/** + * Support hightlight/downplay record on each elements. + * For the case: hover highlight/downplay (legend, visualMap, ...) and + * user triggerred hightlight/downplay should not conflict. + * Only all of the highlightDigit cleared, return to normal. + * @param {string} highlightKey + * @return {number} highlightDigit + */ +function getHighlightDigit(highlightKey) { + var highlightDigit = _highlightKeyMap[highlightKey]; + if (highlightDigit == null && _highlightNextDigit <= 32) { + highlightDigit = _highlightKeyMap[highlightKey] = _highlightNextDigit++; + } + return highlightDigit; +} + +/** + * See more info in `setTextStyleCommon`. + * @param {Object|module:zrender/graphic/Style} normalStyle + * @param {Object} emphasisStyle + * @param {module:echarts/model/Model} normalModel + * @param {module:echarts/model/Model} emphasisModel + * @param {Object} opt Check `opt` of `setTextStyleCommon` to find other props. + * @param {string|Function} [opt.defaultText] + * @param {module:echarts/model/Model} [opt.labelFetcher] Fetch text by + * `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)` + * @param {module:echarts/model/Model} [opt.labelDataIndex] Fetch text by + * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)` + * @param {module:echarts/model/Model} [opt.labelDimIndex] Fetch text by + * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)` + * @param {Object} [normalSpecified] + * @param {Object} [emphasisSpecified] + */ +function setLabelStyle( + normalStyle, emphasisStyle, + normalModel, emphasisModel, + opt, + normalSpecified, emphasisSpecified +) { + opt = opt || EMPTY_OBJ; + var labelFetcher = opt.labelFetcher; + var labelDataIndex = opt.labelDataIndex; + var labelDimIndex = opt.labelDimIndex; + + // This scenario, `label.normal.show = true; label.emphasis.show = false`, + // is not supported util someone requests. + + var showNormal = normalModel.getShallow('show'); + var showEmphasis = emphasisModel.getShallow('show'); + + // Consider performance, only fetch label when necessary. + // If `normal.show` is `false` and `emphasis.show` is `true` and `emphasis.formatter` is not set, + // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`. + var baseText; + if (showNormal || showEmphasis) { + if (labelFetcher) { + baseText = labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex); + } + if (baseText == null) { + baseText = isFunction$1(opt.defaultText) ? opt.defaultText(labelDataIndex, opt) : opt.defaultText; + } + } + var normalStyleText = showNormal ? baseText : null; + var emphasisStyleText = showEmphasis + ? retrieve2( + labelFetcher + ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex) + : null, + baseText + ) + : null; + + // Optimize: If style.text is null, text will not be drawn. + if (normalStyleText != null || emphasisStyleText != null) { + // Always set `textStyle` even if `normalStyle.text` is null, because default + // values have to be set on `normalStyle`. + // If we set default values on `emphasisStyle`, consider case: + // Firstly, `setOption(... label: {normal: {text: null}, emphasis: {show: true}} ...);` + // Secondly, `setOption(... label: {noraml: {show: true, text: 'abc', color: 'red'} ...);` + // Then the 'red' will not work on emphasis. + setTextStyle(normalStyle, normalModel, normalSpecified, opt); + setTextStyle(emphasisStyle, emphasisModel, emphasisSpecified, opt, true); + } + + normalStyle.text = normalStyleText; + emphasisStyle.text = emphasisStyleText; +} + +/** + * Modify label style manually. + * Only works after `setLabelStyle` and `setElementHoverStyle` called. + * + * @param {module:zrender/src/Element} el + * @param {Object} [normalStyleProps] optional + * @param {Object} [emphasisStyleProps] optional + */ +function modifyLabelStyle(el, normalStyleProps, emphasisStyleProps) { + var elStyle = el.style; + if (normalStyleProps) { + rollbackDefaultTextStyle(elStyle); + el.setStyle(normalStyleProps); + applyDefaultTextStyle(elStyle); + } + elStyle = el.__hoverStl; + if (emphasisStyleProps && elStyle) { + rollbackDefaultTextStyle(elStyle); + extend(elStyle, emphasisStyleProps); + applyDefaultTextStyle(elStyle); + } +} + +/** + * Set basic textStyle properties. + * See more info in `setTextStyleCommon`. + * @param {Object|module:zrender/graphic/Style} textStyle + * @param {module:echarts/model/Model} model + * @param {Object} [specifiedTextStyle] Can be overrided by settings in model. + * @param {Object} [opt] See `opt` of `setTextStyleCommon`. + * @param {boolean} [isEmphasis] + */ +function setTextStyle( + textStyle, textStyleModel, specifiedTextStyle, opt, isEmphasis +) { + setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis); + specifiedTextStyle && extend(textStyle, specifiedTextStyle); + // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false); + + return textStyle; +} + +/** + * Set text option in the style. + * See more info in `setTextStyleCommon`. + * @deprecated + * @param {Object} textStyle + * @param {module:echarts/model/Model} labelModel + * @param {string|boolean} defaultColor Default text color. + * If set as false, it will be processed as a emphasis style. + */ +function setText(textStyle, labelModel, defaultColor) { + var opt = {isRectText: true}; + var isEmphasis; + + if (defaultColor === false) { + isEmphasis = true; + } + else { + // Support setting color as 'auto' to get visual color. + opt.autoColor = defaultColor; + } + setTextStyleCommon(textStyle, labelModel, opt, isEmphasis); + // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false); +} + +/** + * The uniform entry of set text style, that is, retrieve style definitions + * from `model` and set to `textStyle` object. + * + * Never in merge mode, but in overwrite mode, that is, all of the text style + * properties will be set. (Consider the states of normal and emphasis and + * default value can be adopted, merge would make the logic too complicated + * to manage.) + * + * The `textStyle` object can either be a plain object or an instance of + * `zrender/src/graphic/Style`, and either be the style of normal or emphasis. + * After this mothod called, the `textStyle` object can then be used in + * `el.setStyle(textStyle)` or `el.hoverStyle = textStyle`. + * + * Default value will be adopted and `insideRollbackOpt` will be created. + * See `applyDefaultTextStyle` `rollbackDefaultTextStyle` for more details. + * + * opt: { + * disableBox: boolean, Whether diable drawing box of block (outer most). + * isRectText: boolean, + * autoColor: string, specify a color when color is 'auto', + * for textFill, textStroke, textBackgroundColor, and textBorderColor. + * If autoColor specified, it is used as default textFill. + * useInsideStyle: + * `true`: Use inside style (textFill, textStroke, textStrokeWidth) + * if `textFill` is not specified. + * `false`: Do not use inside style. + * `null/undefined`: use inside style if `isRectText` is true and + * `textFill` is not specified and textPosition contains `'inside'`. + * forceRich: boolean + * } + */ +function setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis) { + // Consider there will be abnormal when merge hover style to normal style if given default value. + opt = opt || EMPTY_OBJ; + + if (opt.isRectText) { + var textPosition; + if (opt.getTextPosition) { + textPosition = opt.getTextPosition(textStyleModel, isEmphasis); + } + else { + textPosition = textStyleModel.getShallow('position') + || (isEmphasis ? null : 'inside'); + // 'outside' is not a valid zr textPostion value, but used + // in bar series, and magric type should be considered. + textPosition === 'outside' && (textPosition = 'top'); + } + + textStyle.textPosition = textPosition; + textStyle.textOffset = textStyleModel.getShallow('offset'); + var labelRotate = textStyleModel.getShallow('rotate'); + labelRotate != null && (labelRotate *= Math.PI / 180); + textStyle.textRotation = labelRotate; + textStyle.textDistance = retrieve2( + textStyleModel.getShallow('distance'), isEmphasis ? null : 5 + ); + } + + var ecModel = textStyleModel.ecModel; + var globalTextStyle = ecModel && ecModel.option.textStyle; + + // Consider case: + // { + // data: [{ + // value: 12, + // label: { + // rich: { + // // no 'a' here but using parent 'a'. + // } + // } + // }], + // rich: { + // a: { ... } + // } + // } + var richItemNames = getRichItemNames(textStyleModel); + var richResult; + if (richItemNames) { + richResult = {}; + for (var name in richItemNames) { + if (richItemNames.hasOwnProperty(name)) { + // Cascade is supported in rich. + var richTextStyle = textStyleModel.getModel(['rich', name]); + // In rich, never `disableBox`. + // FIXME: consider `label: {formatter: '{a|xx}', color: 'blue', rich: {a: {}}}`, + // the default color `'blue'` will not be adopted if no color declared in `rich`. + // That might confuses users. So probably we should put `textStyleModel` as the + // root ancestor of the `richTextStyle`. But that would be a break change. + setTokenTextStyle(richResult[name] = {}, richTextStyle, globalTextStyle, opt, isEmphasis); + } + } + } + textStyle.rich = richResult; + + setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, true); + + if (opt.forceRich && !opt.textStyle) { + opt.textStyle = {}; + } + + return textStyle; +} + +// Consider case: +// { +// data: [{ +// value: 12, +// label: { +// rich: { +// // no 'a' here but using parent 'a'. +// } +// } +// }], +// rich: { +// a: { ... } +// } +// } +function getRichItemNames(textStyleModel) { + // Use object to remove duplicated names. + var richItemNameMap; + while (textStyleModel && textStyleModel !== textStyleModel.ecModel) { + var rich = (textStyleModel.option || EMPTY_OBJ).rich; + if (rich) { + richItemNameMap = richItemNameMap || {}; + for (var name in rich) { + if (rich.hasOwnProperty(name)) { + richItemNameMap[name] = 1; + } + } + } + textStyleModel = textStyleModel.parentModel; + } + return richItemNameMap; +} + +function setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, isBlock) { + // In merge mode, default value should not be given. + globalTextStyle = !isEmphasis && globalTextStyle || EMPTY_OBJ; + + textStyle.textFill = getAutoColor(textStyleModel.getShallow('color'), opt) + || globalTextStyle.color; + textStyle.textStroke = getAutoColor(textStyleModel.getShallow('textBorderColor'), opt) + || globalTextStyle.textBorderColor; + textStyle.textStrokeWidth = retrieve2( + textStyleModel.getShallow('textBorderWidth'), + globalTextStyle.textBorderWidth + ); + + if (!isEmphasis) { + if (isBlock) { + textStyle.insideRollbackOpt = opt; + applyDefaultTextStyle(textStyle); + } + + // Set default finally. + if (textStyle.textFill == null) { + textStyle.textFill = opt.autoColor; + } + } + + // Do not use `getFont` here, because merge should be supported, where + // part of these properties may be changed in emphasis style, and the + // others should remain their original value got from normal style. + textStyle.fontStyle = textStyleModel.getShallow('fontStyle') || globalTextStyle.fontStyle; + textStyle.fontWeight = textStyleModel.getShallow('fontWeight') || globalTextStyle.fontWeight; + textStyle.fontSize = textStyleModel.getShallow('fontSize') || globalTextStyle.fontSize; + textStyle.fontFamily = textStyleModel.getShallow('fontFamily') || globalTextStyle.fontFamily; + + textStyle.textAlign = textStyleModel.getShallow('align'); + textStyle.textVerticalAlign = textStyleModel.getShallow('verticalAlign') + || textStyleModel.getShallow('baseline'); + + textStyle.textLineHeight = textStyleModel.getShallow('lineHeight'); + textStyle.textWidth = textStyleModel.getShallow('width'); + textStyle.textHeight = textStyleModel.getShallow('height'); + textStyle.textTag = textStyleModel.getShallow('tag'); + + if (!isBlock || !opt.disableBox) { + textStyle.textBackgroundColor = getAutoColor(textStyleModel.getShallow('backgroundColor'), opt); + textStyle.textPadding = textStyleModel.getShallow('padding'); + textStyle.textBorderColor = getAutoColor(textStyleModel.getShallow('borderColor'), opt); + textStyle.textBorderWidth = textStyleModel.getShallow('borderWidth'); + textStyle.textBorderRadius = textStyleModel.getShallow('borderRadius'); + + textStyle.textBoxShadowColor = textStyleModel.getShallow('shadowColor'); + textStyle.textBoxShadowBlur = textStyleModel.getShallow('shadowBlur'); + textStyle.textBoxShadowOffsetX = textStyleModel.getShallow('shadowOffsetX'); + textStyle.textBoxShadowOffsetY = textStyleModel.getShallow('shadowOffsetY'); + } + + textStyle.textShadowColor = textStyleModel.getShallow('textShadowColor') + || globalTextStyle.textShadowColor; + textStyle.textShadowBlur = textStyleModel.getShallow('textShadowBlur') + || globalTextStyle.textShadowBlur; + textStyle.textShadowOffsetX = textStyleModel.getShallow('textShadowOffsetX') + || globalTextStyle.textShadowOffsetX; + textStyle.textShadowOffsetY = textStyleModel.getShallow('textShadowOffsetY') + || globalTextStyle.textShadowOffsetY; +} + +function getAutoColor(color, opt) { + return color !== 'auto' ? color : (opt && opt.autoColor) ? opt.autoColor : null; +} + +/** + * Give some default value to the input `textStyle` object, based on the current settings + * in this `textStyle` object. + * + * The Scenario: + * when text position is `inside` and `textFill` is not specified, we show + * text border by default for better view. But it should be considered that text position + * might be changed when hovering or being emphasis, where the `insideRollback` is used to + * restore the style. + * + * Usage (& NOTICE): + * When a style object (eithor plain object or instance of `zrender/src/graphic/Style`) is + * about to be modified on its text related properties, `rollbackDefaultTextStyle` should + * be called before the modification and `applyDefaultTextStyle` should be called after that. + * (For the case that all of the text related properties is reset, like `setTextStyleCommon` + * does, `rollbackDefaultTextStyle` is not needed to be called). + */ +function applyDefaultTextStyle(textStyle) { + var textPosition = textStyle.textPosition; + var opt = textStyle.insideRollbackOpt; + var insideRollback; + + if (opt && textStyle.textFill == null) { + var autoColor = opt.autoColor; + var isRectText = opt.isRectText; + var useInsideStyle = opt.useInsideStyle; + + var useInsideStyleCache = useInsideStyle !== false + && (useInsideStyle === true + || (isRectText + && textPosition + // textPosition can be [10, 30] + && typeof textPosition === 'string' + && textPosition.indexOf('inside') >= 0 + ) + ); + var useAutoColorCache = !useInsideStyleCache && autoColor != null; + + // All of the props declared in `CACHED_LABEL_STYLE_PROPERTIES` are to be cached. + if (useInsideStyleCache || useAutoColorCache) { + insideRollback = { + textFill: textStyle.textFill, + textStroke: textStyle.textStroke, + textStrokeWidth: textStyle.textStrokeWidth + }; + } + if (useInsideStyleCache) { + textStyle.textFill = '#fff'; + // Consider text with #fff overflow its container. + if (textStyle.textStroke == null) { + textStyle.textStroke = autoColor; + textStyle.textStrokeWidth == null && (textStyle.textStrokeWidth = 2); + } + } + if (useAutoColorCache) { + textStyle.textFill = autoColor; + } + } + + // Always set `insideRollback`, so that the previous one can be cleared. + textStyle.insideRollback = insideRollback; +} + +/** + * Consider the case: in a scatter, + * label: { + * normal: {position: 'inside'}, + * emphasis: {position: 'top'} + * } + * In the normal state, the `textFill` will be set as '#fff' for pretty view (see + * `applyDefaultTextStyle`), but when switching to emphasis state, the `textFill` + * should be retured to 'autoColor', but not keep '#fff'. + */ +function rollbackDefaultTextStyle(style) { + var insideRollback = style.insideRollback; + if (insideRollback) { + // Reset all of the props in `CACHED_LABEL_STYLE_PROPERTIES`. + style.textFill = insideRollback.textFill; + style.textStroke = insideRollback.textStroke; + style.textStrokeWidth = insideRollback.textStrokeWidth; + style.insideRollback = null; + } +} + +function getFont(opt, ecModel) { + var gTextStyleModel = ecModel && ecModel.getModel('textStyle'); + return trim([ + // FIXME in node-canvas fontWeight is before fontStyle + opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '', + opt.fontWeight || gTextStyleModel && gTextStyleModel.getShallow('fontWeight') || '', + (opt.fontSize || gTextStyleModel && gTextStyleModel.getShallow('fontSize') || 12) + 'px', + opt.fontFamily || gTextStyleModel && gTextStyleModel.getShallow('fontFamily') || 'sans-serif' + ].join(' ')); +} + +function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) { + if (typeof dataIndex === 'function') { + cb = dataIndex; + dataIndex = null; + } + // Do not check 'animation' property directly here. Consider this case: + // animation model is an `itemModel`, whose does not have `isAnimationEnabled` + // but its parent model (`seriesModel`) does. + var animationEnabled = animatableModel && animatableModel.isAnimationEnabled(); + + if (animationEnabled) { + var postfix = isUpdate ? 'Update' : ''; + var duration = animatableModel.getShallow('animationDuration' + postfix); + var animationEasing = animatableModel.getShallow('animationEasing' + postfix); + var animationDelay = animatableModel.getShallow('animationDelay' + postfix); + if (typeof animationDelay === 'function') { + animationDelay = animationDelay( + dataIndex, + animatableModel.getAnimationDelayParams + ? animatableModel.getAnimationDelayParams(el, dataIndex) + : null + ); + } + if (typeof duration === 'function') { + duration = duration(dataIndex); + } + + duration > 0 + ? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb, !!cb) + : (el.stopAnimation(), el.attr(props), cb && cb()); + } + else { + el.stopAnimation(); + el.attr(props); + cb && cb(); + } +} + +/** + * Update graphic element properties with or without animation according to the + * configuration in series. + * + * Caution: this method will stop previous animation. + * So if do not use this method to one element twice before + * animation starts, unless you know what you are doing. + * + * @param {module:zrender/Element} el + * @param {Object} props + * @param {module:echarts/model/Model} [animatableModel] + * @param {number} [dataIndex] + * @param {Function} [cb] + * @example + * graphic.updateProps(el, { + * position: [100, 100] + * }, seriesModel, dataIndex, function () { console.log('Animation done!'); }); + * // Or + * graphic.updateProps(el, { + * position: [100, 100] + * }, seriesModel, function () { console.log('Animation done!'); }); + */ +function updateProps(el, props, animatableModel, dataIndex, cb) { + animateOrSetProps(true, el, props, animatableModel, dataIndex, cb); +} + +/** + * Init graphic element properties with or without animation according to the + * configuration in series. + * + * Caution: this method will stop previous animation. + * So if do not use this method to one element twice before + * animation starts, unless you know what you are doing. + * + * @param {module:zrender/Element} el + * @param {Object} props + * @param {module:echarts/model/Model} [animatableModel] + * @param {number} [dataIndex] + * @param {Function} cb + */ +function initProps(el, props, animatableModel, dataIndex, cb) { + animateOrSetProps(false, el, props, animatableModel, dataIndex, cb); +} + +/** + * Get transform matrix of target (param target), + * in coordinate of its ancestor (param ancestor) + * + * @param {module:zrender/mixin/Transformable} target + * @param {module:zrender/mixin/Transformable} [ancestor] + */ +function getTransform(target, ancestor) { + var mat = identity([]); + + while (target && target !== ancestor) { + mul$1(mat, target.getLocalTransform(), mat); + target = target.parent; + } + + return mat; +} + +/** + * Apply transform to an vertex. + * @param {Array.} target [x, y] + * @param {Array.|TypedArray.|Object} transform Can be: + * + Transform matrix: like [1, 0, 0, 1, 0, 0] + * + {position, rotation, scale}, the same as `zrender/Transformable`. + * @param {boolean=} invert Whether use invert matrix. + * @return {Array.} [x, y] + */ +function applyTransform$1(target, transform, invert$$1) { + if (transform && !isArrayLike(transform)) { + transform = Transformable.getLocalTransform(transform); + } + + if (invert$$1) { + transform = invert([], transform); + } + return applyTransform([], target, transform); +} + +/** + * @param {string} direction 'left' 'right' 'top' 'bottom' + * @param {Array.} transform Transform matrix: like [1, 0, 0, 1, 0, 0] + * @param {boolean=} invert Whether use invert matrix. + * @return {string} Transformed direction. 'left' 'right' 'top' 'bottom' + */ +function transformDirection(direction, transform, invert$$1) { + + // Pick a base, ensure that transform result will not be (0, 0). + var hBase = (transform[4] === 0 || transform[5] === 0 || transform[0] === 0) + ? 1 : Math.abs(2 * transform[4] / transform[0]); + var vBase = (transform[4] === 0 || transform[5] === 0 || transform[2] === 0) + ? 1 : Math.abs(2 * transform[4] / transform[2]); + + var vertex = [ + direction === 'left' ? -hBase : direction === 'right' ? hBase : 0, + direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0 + ]; + + vertex = applyTransform$1(vertex, transform, invert$$1); + + return Math.abs(vertex[0]) > Math.abs(vertex[1]) + ? (vertex[0] > 0 ? 'right' : 'left') + : (vertex[1] > 0 ? 'bottom' : 'top'); +} + +/** + * Apply group transition animation from g1 to g2. + * If no animatableModel, no animation. + */ +function groupTransition(g1, g2, animatableModel, cb) { + if (!g1 || !g2) { + return; + } + + function getElMap(g) { + var elMap = {}; + g.traverse(function (el) { + if (!el.isGroup && el.anid) { + elMap[el.anid] = el; + } + }); + return elMap; + } + function getAnimatableProps(el) { + var obj = { + position: clone$1(el.position), + rotation: el.rotation + }; + if (el.shape) { + obj.shape = extend({}, el.shape); + } + return obj; + } + var elMap1 = getElMap(g1); + + g2.traverse(function (el) { + if (!el.isGroup && el.anid) { + var oldEl = elMap1[el.anid]; + if (oldEl) { + var newProp = getAnimatableProps(el); + el.attr(getAnimatableProps(oldEl)); + updateProps(el, newProp, animatableModel, el.dataIndex); + } + // else { + // if (el.previousProps) { + // graphic.updateProps + // } + // } + } + }); +} + +/** + * @param {Array.>} points Like: [[23, 44], [53, 66], ...] + * @param {Object} rect {x, y, width, height} + * @return {Array.>} A new clipped points. + */ +function clipPointsByRect(points, rect) { + // FIXME: this way migth be incorrect when grpahic clipped by a corner. + // and when element have border. + return map(points, function (point) { + var x = point[0]; + x = mathMax$1(x, rect.x); + x = mathMin$1(x, rect.x + rect.width); + var y = point[1]; + y = mathMax$1(y, rect.y); + y = mathMin$1(y, rect.y + rect.height); + return [x, y]; + }); +} + +/** + * @param {Object} targetRect {x, y, width, height} + * @param {Object} rect {x, y, width, height} + * @return {Object} A new clipped rect. If rect size are negative, return undefined. + */ +function clipRectByRect(targetRect, rect) { + var x = mathMax$1(targetRect.x, rect.x); + var x2 = mathMin$1(targetRect.x + targetRect.width, rect.x + rect.width); + var y = mathMax$1(targetRect.y, rect.y); + var y2 = mathMin$1(targetRect.y + targetRect.height, rect.y + rect.height); + + // If the total rect is cliped, nothing, including the border, + // should be painted. So return undefined. + if (x2 >= x && y2 >= y) { + return { + x: x, + y: y, + width: x2 - x, + height: y2 - y + }; + } +} + +/** + * @param {string} iconStr Support 'image://' or 'path://' or direct svg path. + * @param {Object} [opt] Properties of `module:zrender/Element`, except `style`. + * @param {Object} [rect] {x, y, width, height} + * @return {module:zrender/Element} Icon path or image element. + */ +function createIcon(iconStr, opt, rect) { + opt = extend({rectHover: true}, opt); + var style = opt.style = {strokeNoScale: true}; + rect = rect || {x: -1, y: -1, width: 2, height: 2}; + + if (iconStr) { + return iconStr.indexOf('image://') === 0 + ? ( + style.image = iconStr.slice(8), + defaults(style, rect), + new ZImage(opt) + ) + : ( + makePath( + iconStr.replace('path://', ''), + opt, + rect, + 'center' + ) + ); + } +} + +/** + * Return `true` if the given line (line `a`) and the given polygon + * are intersect. + * Note that we do not count colinear as intersect here because no + * requirement for that. We could do that if required in future. + * + * @param {number} a1x + * @param {number} a1y + * @param {number} a2x + * @param {number} a2y + * @param {Array.>} points Points of the polygon. + * @return {boolean} + */ +function linePolygonIntersect(a1x, a1y, a2x, a2y, points) { + for (var i = 0, p2 = points[points.length - 1]; i < points.length; i++) { + var p = points[i]; + if (lineLineIntersect(a1x, a1y, a2x, a2y, p[0], p[1], p2[0], p2[1])) { + return true; + } + p2 = p; + } +} + +/** + * Return `true` if the given two lines (line `a` and line `b`) + * are intersect. + * Note that we do not count colinear as intersect here because no + * requirement for that. We could do that if required in future. + * + * @param {number} a1x + * @param {number} a1y + * @param {number} a2x + * @param {number} a2y + * @param {number} b1x + * @param {number} b1y + * @param {number} b2x + * @param {number} b2y + * @return {boolean} + */ +function lineLineIntersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y) { + // let `vec_m` to be `vec_a2 - vec_a1` and `vec_n` to be `vec_b2 - vec_b1`. + var mx = a2x - a1x; + var my = a2y - a1y; + var nx = b2x - b1x; + var ny = b2y - b1y; + + // `vec_m` and `vec_n` are parallel iff + // exising `k` such that `vec_m = k · vec_n`, equivalent to `vec_m X vec_n = 0`. + var nmCrossProduct = crossProduct2d(nx, ny, mx, my); + if (nearZero(nmCrossProduct)) { + return false; + } + + // `vec_m` and `vec_n` are intersect iff + // existing `p` and `q` in [0, 1] such that `vec_a1 + p * vec_m = vec_b1 + q * vec_n`, + // such that `q = ((vec_a1 - vec_b1) X vec_m) / (vec_n X vec_m)` + // and `p = ((vec_a1 - vec_b1) X vec_n) / (vec_n X vec_m)`. + var b1a1x = a1x - b1x; + var b1a1y = a1y - b1y; + var q = crossProduct2d(b1a1x, b1a1y, mx, my) / nmCrossProduct; + if (q < 0 || q > 1) { + return false; + } + var p = crossProduct2d(b1a1x, b1a1y, nx, ny) / nmCrossProduct; + if (p < 0 || p > 1) { + return false; + } + + return true; +} + +/** + * Cross product of 2-dimension vector. + */ +function crossProduct2d(x1, y1, x2, y2) { + return x1 * y2 - x2 * y1; +} + +function nearZero(val) { + return val <= (1e-6) && val >= -(1e-6); +} + +// Register built-in shapes. These shapes might be overwirtten +// by users, although we do not recommend that. +registerShape('circle', Circle); +registerShape('sector', Sector); +registerShape('ring', Ring); +registerShape('polygon', Polygon); +registerShape('polyline', Polyline); +registerShape('rect', Rect); +registerShape('line', Line); +registerShape('bezierCurve', BezierCurve); +registerShape('arc', Arc); + + + + +var graphic = (Object.freeze || Object)({ + Z2_EMPHASIS_LIFT: Z2_EMPHASIS_LIFT, + CACHED_LABEL_STYLE_PROPERTIES: CACHED_LABEL_STYLE_PROPERTIES, + extendShape: extendShape, + extendPath: extendPath, + registerShape: registerShape, + getShapeClass: getShapeClass, + makePath: makePath, + makeImage: makeImage, + mergePath: mergePath, + resizePath: resizePath, + subPixelOptimizeLine: subPixelOptimizeLine, + subPixelOptimizeRect: subPixelOptimizeRect, + subPixelOptimize: subPixelOptimize, + setElementHoverStyle: setElementHoverStyle, + setHoverStyle: setHoverStyle, + setAsHighDownDispatcher: setAsHighDownDispatcher, + isHighDownDispatcher: isHighDownDispatcher, + getHighlightDigit: getHighlightDigit, + setLabelStyle: setLabelStyle, + modifyLabelStyle: modifyLabelStyle, + setTextStyle: setTextStyle, + setText: setText, + getFont: getFont, + updateProps: updateProps, + initProps: initProps, + getTransform: getTransform, + applyTransform: applyTransform$1, + transformDirection: transformDirection, + groupTransition: groupTransition, + clipPointsByRect: clipPointsByRect, + clipRectByRect: clipRectByRect, + createIcon: createIcon, + linePolygonIntersect: linePolygonIntersect, + lineLineIntersect: lineLineIntersect, + Group: Group, + Image: ZImage, + Text: Text, + Circle: Circle, + Sector: Sector, + Ring: Ring, + Polygon: Polygon, + Polyline: Polyline, + Rect: Rect, + Line: Line, + BezierCurve: BezierCurve, + Arc: Arc, + IncrementalDisplayable: IncrementalDisplayble, + CompoundPath: CompoundPath, + LinearGradient: LinearGradient, + RadialGradient: RadialGradient, + BoundingRect: BoundingRect +}); + +/* +* 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. +*/ + +var PATH_COLOR = ['textStyle', 'color']; + +var textStyleMixin = { + /** + * Get color property or get color from option.textStyle.color + * @param {boolean} [isEmphasis] + * @return {string} + */ + getTextColor: function (isEmphasis) { + var ecModel = this.ecModel; + return this.getShallow('color') + || ( + (!isEmphasis && ecModel) ? ecModel.get(PATH_COLOR) : null + ); + }, + + /** + * Create font string from fontStyle, fontWeight, fontSize, fontFamily + * @return {string} + */ + getFont: function () { + return getFont({ + fontStyle: this.getShallow('fontStyle'), + fontWeight: this.getShallow('fontWeight'), + fontSize: this.getShallow('fontSize'), + fontFamily: this.getShallow('fontFamily') + }, this.ecModel); + }, + + getTextRect: function (text) { + return getBoundingRect( + text, + this.getFont(), + this.getShallow('align'), + this.getShallow('verticalAlign') || this.getShallow('baseline'), + this.getShallow('padding'), + this.getShallow('lineHeight'), + this.getShallow('rich'), + this.getShallow('truncateText') + ); + } +}; + +/* +* 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. +*/ + +var getItemStyle = makeStyleMapper( + [ + ['fill', 'color'], + ['stroke', 'borderColor'], + ['lineWidth', 'borderWidth'], + ['opacity'], + ['shadowBlur'], + ['shadowOffsetX'], + ['shadowOffsetY'], + ['shadowColor'], + ['textPosition'], + ['textAlign'] + ] +); + +var itemStyleMixin = { + getItemStyle: function (excludes, includes) { + var style = getItemStyle(this, excludes, includes); + var lineDash = this.getBorderLineDash(); + lineDash && (style.lineDash = lineDash); + return style; + }, + + getBorderLineDash: function () { + var lineType = this.get('borderType'); + return (lineType === 'solid' || lineType == null) ? null + : (lineType === 'dashed' ? [5, 5] : [1, 1]); + } +}; + +/* +* 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. +*/ + +/** + * @module echarts/model/Model + */ + +var mixin$1 = mixin; +var inner = makeInner(); + +/** + * @alias module:echarts/model/Model + * @constructor + * @param {Object} [option] + * @param {module:echarts/model/Model} [parentModel] + * @param {module:echarts/model/Global} [ecModel] + */ +function Model(option, parentModel, ecModel) { + /** + * @type {module:echarts/model/Model} + * @readOnly + */ + this.parentModel = parentModel; + + /** + * @type {module:echarts/model/Global} + * @readOnly + */ + this.ecModel = ecModel; + + /** + * @type {Object} + * @protected + */ + this.option = option; + + // Simple optimization + // if (this.init) { + // if (arguments.length <= 4) { + // this.init(option, parentModel, ecModel, extraOpt); + // } + // else { + // this.init.apply(this, arguments); + // } + // } +} + +Model.prototype = { + + constructor: Model, + + /** + * Model 的初始化函数 + * @param {Object} option + */ + init: null, + + /** + * 从新的 Option merge + */ + mergeOption: function (option) { + merge(this.option, option, true); + }, + + /** + * @param {string|Array.} path + * @param {boolean} [ignoreParent=false] + * @return {*} + */ + get: function (path, ignoreParent) { + if (path == null) { + return this.option; + } + + return doGet( + this.option, + this.parsePath(path), + !ignoreParent && getParent(this, path) + ); + }, + + /** + * @param {string} key + * @param {boolean} [ignoreParent=false] + * @return {*} + */ + getShallow: function (key, ignoreParent) { + var option = this.option; + + var val = option == null ? option : option[key]; + var parentModel = !ignoreParent && getParent(this, key); + if (val == null && parentModel) { + val = parentModel.getShallow(key); + } + return val; + }, + + /** + * @param {string|Array.} [path] + * @param {module:echarts/model/Model} [parentModel] + * @return {module:echarts/model/Model} + */ + getModel: function (path, parentModel) { + var obj = path == null + ? this.option + : doGet(this.option, path = this.parsePath(path)); + + var thisParentModel; + parentModel = parentModel || ( + (thisParentModel = getParent(this, path)) + && thisParentModel.getModel(path) + ); + + return new Model(obj, parentModel, this.ecModel); + }, + + /** + * If model has option + */ + isEmpty: function () { + return this.option == null; + }, + + restoreData: function () {}, + + // Pending + clone: function () { + var Ctor = this.constructor; + return new Ctor(clone(this.option)); + }, + + setReadOnly: function (properties) { + // clazzUtil.setReadOnly(this, properties); + }, + + // If path is null/undefined, return null/undefined. + parsePath: function (path) { + if (typeof path === 'string') { + path = path.split('.'); + } + return path; + }, + + /** + * @param {Function} getParentMethod + * param {Array.|string} path + * return {module:echarts/model/Model} + */ + customizeGetParent: function (getParentMethod) { + inner(this).getParent = getParentMethod; + }, + + isAnimationEnabled: function () { + if (!env$1.node) { + if (this.option.animation != null) { + return !!this.option.animation; + } + else if (this.parentModel) { + return this.parentModel.isAnimationEnabled(); + } + } + } + +}; + +function doGet(obj, pathArr, parentModel) { + for (var i = 0; i < pathArr.length; i++) { + // Ignore empty + if (!pathArr[i]) { + continue; + } + // obj could be number/string/... (like 0) + obj = (obj && typeof obj === 'object') ? obj[pathArr[i]] : null; + if (obj == null) { + break; + } + } + if (obj == null && parentModel) { + obj = parentModel.get(pathArr); + } + return obj; +} + +// `path` can be null/undefined +function getParent(model, path) { + var getParentMethod = inner(model).getParent; + return getParentMethod ? getParentMethod.call(model, path) : model.parentModel; +} + +// Enable Model.extend. +enableClassExtend(Model); +enableClassCheck(Model); + +mixin$1(Model, lineStyleMixin); +mixin$1(Model, areaStyleMixin); +mixin$1(Model, textStyleMixin); +mixin$1(Model, itemStyleMixin); + +/* +* 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. +*/ + +var base = 0; + +/** + * @public + * @param {string} type + * @return {string} + */ +function getUID(type) { + // Considering the case of crossing js context, + // use Math.random to make id as unique as possible. + return [(type || ''), base++, Math.random().toFixed(5)].join('_'); +} + +/** + * @inner + */ +function enableSubTypeDefaulter(entity) { + + var subTypeDefaulters = {}; + + entity.registerSubTypeDefaulter = function (componentType, defaulter) { + componentType = parseClassType$1(componentType); + subTypeDefaulters[componentType.main] = defaulter; + }; + + entity.determineSubType = function (componentType, option) { + var type = option.type; + if (!type) { + var componentTypeMain = parseClassType$1(componentType).main; + if (entity.hasSubTypes(componentType) && subTypeDefaulters[componentTypeMain]) { + type = subTypeDefaulters[componentTypeMain](option); + } + } + return type; + }; + + return entity; +} + +/** + * Topological travel on Activity Network (Activity On Vertices). + * Dependencies is defined in Model.prototype.dependencies, like ['xAxis', 'yAxis']. + * + * If 'xAxis' or 'yAxis' is absent in componentTypeList, just ignore it in topology. + * + * If there is circle dependencey, Error will be thrown. + * + */ +function enableTopologicalTravel(entity, dependencyGetter) { + + /** + * @public + * @param {Array.} targetNameList Target Component type list. + * Can be ['aa', 'bb', 'aa.xx'] + * @param {Array.} fullNameList By which we can build dependency graph. + * @param {Function} callback Params: componentType, dependencies. + * @param {Object} context Scope of callback. + */ + entity.topologicalTravel = function (targetNameList, fullNameList, callback, context) { + if (!targetNameList.length) { + return; + } + + var result = makeDepndencyGraph(fullNameList); + var graph = result.graph; + var stack = result.noEntryList; + + var targetNameSet = {}; + each$1(targetNameList, function (name) { + targetNameSet[name] = true; + }); + + while (stack.length) { + var currComponentType = stack.pop(); + var currVertex = graph[currComponentType]; + var isInTargetNameSet = !!targetNameSet[currComponentType]; + if (isInTargetNameSet) { + callback.call(context, currComponentType, currVertex.originalDeps.slice()); + delete targetNameSet[currComponentType]; + } + each$1( + currVertex.successor, + isInTargetNameSet ? removeEdgeAndAdd : removeEdge + ); + } + + each$1(targetNameSet, function () { + throw new Error('Circle dependency may exists'); + }); + + function removeEdge(succComponentType) { + graph[succComponentType].entryCount--; + if (graph[succComponentType].entryCount === 0) { + stack.push(succComponentType); + } + } + + // Consider this case: legend depends on series, and we call + // chart.setOption({series: [...]}), where only series is in option. + // If we do not have 'removeEdgeAndAdd', legendModel.mergeOption will + // not be called, but only sereis.mergeOption is called. Thus legend + // have no chance to update its local record about series (like which + // name of series is available in legend). + function removeEdgeAndAdd(succComponentType) { + targetNameSet[succComponentType] = true; + removeEdge(succComponentType); + } + }; + + /** + * DepndencyGraph: {Object} + * key: conponentType, + * value: { + * successor: [conponentTypes...], + * originalDeps: [conponentTypes...], + * entryCount: {number} + * } + */ + function makeDepndencyGraph(fullNameList) { + var graph = {}; + var noEntryList = []; + + each$1(fullNameList, function (name) { + + var thisItem = createDependencyGraphItem(graph, name); + var originalDeps = thisItem.originalDeps = dependencyGetter(name); + + var availableDeps = getAvailableDependencies(originalDeps, fullNameList); + thisItem.entryCount = availableDeps.length; + if (thisItem.entryCount === 0) { + noEntryList.push(name); + } + + each$1(availableDeps, function (dependentName) { + if (indexOf(thisItem.predecessor, dependentName) < 0) { + thisItem.predecessor.push(dependentName); + } + var thatItem = createDependencyGraphItem(graph, dependentName); + if (indexOf(thatItem.successor, dependentName) < 0) { + thatItem.successor.push(name); + } + }); + }); + + return {graph: graph, noEntryList: noEntryList}; + } + + function createDependencyGraphItem(graph, name) { + if (!graph[name]) { + graph[name] = {predecessor: [], successor: []}; + } + return graph[name]; + } + + function getAvailableDependencies(originalDeps, fullNameList) { + var availableDeps = []; + each$1(originalDeps, function (dep) { + indexOf(fullNameList, dep) >= 0 && availableDeps.push(dep); + }); + return availableDeps; + } +} + +/* +* 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. +*/ + +/* +* A third-party license is embeded for some of the code in this file: +* The method "quantile" was copied from "d3.js". +* (See more details in the comment of the method below.) +* The use of the source code of this file is also subject to the terms +* and consitions of the license of "d3.js" (BSD-3Clause, see +* ). +*/ + +var RADIAN_EPSILON = 1e-4; + +function _trim(str) { + return str.replace(/^\s+|\s+$/g, ''); +} + +/** + * Linear mapping a value from domain to range + * @memberOf module:echarts/util/number + * @param {(number|Array.)} val + * @param {Array.} domain Domain extent domain[0] can be bigger than domain[1] + * @param {Array.} range Range extent range[0] can be bigger than range[1] + * @param {boolean} clamp + * @return {(number|Array.} + */ +function linearMap(val, domain, range, clamp) { + var subDomain = domain[1] - domain[0]; + var subRange = range[1] - range[0]; + + if (subDomain === 0) { + return subRange === 0 + ? range[0] + : (range[0] + range[1]) / 2; + } + + // Avoid accuracy problem in edge, such as + // 146.39 - 62.83 === 83.55999999999999. + // See echarts/test/ut/spec/util/number.js#linearMap#accuracyError + // It is a little verbose for efficiency considering this method + // is a hotspot. + if (clamp) { + if (subDomain > 0) { + if (val <= domain[0]) { + return range[0]; + } + else if (val >= domain[1]) { + return range[1]; + } + } + else { + if (val >= domain[0]) { + return range[0]; + } + else if (val <= domain[1]) { + return range[1]; + } + } + } + else { + if (val === domain[0]) { + return range[0]; + } + if (val === domain[1]) { + return range[1]; + } + } + + return (val - domain[0]) / subDomain * subRange + range[0]; +} + +/** + * Convert a percent string to absolute number. + * Returns NaN if percent is not a valid string or number + * @memberOf module:echarts/util/number + * @param {string|number} percent + * @param {number} all + * @return {number} + */ +function parsePercent$1(percent, all) { + switch (percent) { + case 'center': + case 'middle': + percent = '50%'; + break; + case 'left': + case 'top': + percent = '0%'; + break; + case 'right': + case 'bottom': + percent = '100%'; + break; + } + if (typeof percent === 'string') { + if (_trim(percent).match(/%$/)) { + return parseFloat(percent) / 100 * all; + } + + return parseFloat(percent); + } + + return percent == null ? NaN : +percent; +} + +/** + * (1) Fix rounding error of float numbers. + * (2) Support return string to avoid scientific notation like '3.5e-7'. + * + * @param {number} x + * @param {number} [precision] + * @param {boolean} [returnStr] + * @return {number|string} + */ +function round$1(x, precision, returnStr) { + if (precision == null) { + precision = 10; + } + // Avoid range error + precision = Math.min(Math.max(0, precision), 20); + x = (+x).toFixed(precision); + return returnStr ? x : +x; +} + +/** + * asc sort arr. + * The input arr will be modified. + * + * @param {Array} arr + * @return {Array} The input arr. + */ +function asc(arr) { + arr.sort(function (a, b) { + return a - b; + }); + return arr; +} + +/** + * Get precision + * @param {number} val + */ +function getPrecision(val) { + val = +val; + if (isNaN(val)) { + return 0; + } + // It is much faster than methods converting number to string as follows + // var tmp = val.toString(); + // return tmp.length - 1 - tmp.indexOf('.'); + // especially when precision is low + var e = 1; + var count = 0; + while (Math.round(val * e) / e !== val) { + e *= 10; + count++; + } + return count; +} + +/** + * @param {string|number} val + * @return {number} + */ +function getPrecisionSafe(val) { + var str = val.toString(); + + // Consider scientific notation: '3.4e-12' '3.4e+12' + var eIndex = str.indexOf('e'); + if (eIndex > 0) { + var precision = +str.slice(eIndex + 1); + return precision < 0 ? -precision : 0; + } + else { + var dotIndex = str.indexOf('.'); + return dotIndex < 0 ? 0 : str.length - 1 - dotIndex; + } +} + +/** + * Minimal dicernible data precisioin according to a single pixel. + * + * @param {Array.} dataExtent + * @param {Array.} pixelExtent + * @return {number} precision + */ +function getPixelPrecision(dataExtent, pixelExtent) { + var log = Math.log; + var LN10 = Math.LN10; + var dataQuantity = Math.floor(log(dataExtent[1] - dataExtent[0]) / LN10); + var sizeQuantity = Math.round(log(Math.abs(pixelExtent[1] - pixelExtent[0])) / LN10); + // toFixed() digits argument must be between 0 and 20. + var precision = Math.min(Math.max(-dataQuantity + sizeQuantity, 0), 20); + return !isFinite(precision) ? 20 : precision; +} + +/** + * Get a data of given precision, assuring the sum of percentages + * in valueList is 1. + * The largest remainer method is used. + * https://en.wikipedia.org/wiki/Largest_remainder_method + * + * @param {Array.} valueList a list of all data + * @param {number} idx index of the data to be processed in valueList + * @param {number} precision integer number showing digits of precision + * @return {number} percent ranging from 0 to 100 + */ +function getPercentWithPrecision(valueList, idx, precision) { + if (!valueList[idx]) { + return 0; + } + + var sum = reduce(valueList, function (acc, val) { + return acc + (isNaN(val) ? 0 : val); + }, 0); + if (sum === 0) { + return 0; + } + + var digits = Math.pow(10, precision); + var votesPerQuota = map(valueList, function (val) { + return (isNaN(val) ? 0 : val) / sum * digits * 100; + }); + var targetSeats = digits * 100; + + var seats = map(votesPerQuota, function (votes) { + // Assign automatic seats. + return Math.floor(votes); + }); + var currentSum = reduce(seats, function (acc, val) { + return acc + val; + }, 0); + + var remainder = map(votesPerQuota, function (votes, idx) { + return votes - seats[idx]; + }); + + // Has remainding votes. + while (currentSum < targetSeats) { + // Find next largest remainder. + var max = Number.NEGATIVE_INFINITY; + var maxId = null; + for (var i = 0, len = remainder.length; i < len; ++i) { + if (remainder[i] > max) { + max = remainder[i]; + maxId = i; + } + } + + // Add a vote to max remainder. + ++seats[maxId]; + remainder[maxId] = 0; + ++currentSum; + } + + return seats[idx] / digits; +} + +// Number.MAX_SAFE_INTEGER, ie do not support. +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * To 0 - 2 * PI, considering negative radian. + * @param {number} radian + * @return {number} + */ +function remRadian(radian) { + var pi2 = Math.PI * 2; + return (radian % pi2 + pi2) % pi2; +} + +/** + * @param {type} radian + * @return {boolean} + */ +function isRadianAroundZero(val) { + return val > -RADIAN_EPSILON && val < RADIAN_EPSILON; +} + +/* eslint-disable */ +var TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d\d)(?::(\d\d)(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // jshint ignore:line +/* eslint-enable */ + +/** + * @param {string|Date|number} value These values can be accepted: + * + An instance of Date, represent a time in its own time zone. + * + Or string in a subset of ISO 8601, only including: + * + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-03-01 05:06', + * + separated with T or space: '2012-03-01T12:22:33.123', '2012-03-01 12:22:33.123', + * + time zone: '2012-03-01T12:22:33Z', '2012-03-01T12:22:33+8000', '2012-03-01T12:22:33-05:00', + * all of which will be treated as local time if time zone is not specified + * (see ). + * + Or other string format, including (all of which will be treated as loacal time): + * '2012', '2012-3-1', '2012/3/1', '2012/03/01', + * '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123' + * + a timestamp, which represent a time in UTC. + * @return {Date} date + */ +function parseDate(value) { + if (value instanceof Date) { + return value; + } + else if (typeof value === 'string') { + // Different browsers parse date in different way, so we parse it manually. + // Some other issues: + // new Date('1970-01-01') is UTC, + // new Date('1970/01/01') and new Date('1970-1-01') is local. + // See issue #3623 + var match = TIME_REG.exec(value); + + if (!match) { + // return Invalid Date. + return new Date(NaN); + } + + // Use local time when no timezone offset specifed. + if (!match[8]) { + // match[n] can only be string or undefined. + // But take care of '12' + 1 => '121'. + return new Date( + +match[1], + +(match[2] || 1) - 1, + +match[3] || 1, + +match[4] || 0, + +(match[5] || 0), + +match[6] || 0, + +match[7] || 0 + ); + } + // Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time, + // https://tc39.github.io/ecma262/#sec-daylight-saving-time-adjustment). + // For example, system timezone is set as "Time Zone: America/Toronto", + // then these code will get different result: + // `new Date(1478411999999).getTimezoneOffset(); // get 240` + // `new Date(1478412000000).getTimezoneOffset(); // get 300` + // So we should not use `new Date`, but use `Date.UTC`. + else { + var hour = +match[4] || 0; + if (match[8].toUpperCase() !== 'Z') { + hour -= match[8].slice(0, 3); + } + return new Date(Date.UTC( + +match[1], + +(match[2] || 1) - 1, + +match[3] || 1, + hour, + +(match[5] || 0), + +match[6] || 0, + +match[7] || 0 + )); + } + } + else if (value == null) { + return new Date(NaN); + } + + return new Date(Math.round(value)); +} + +/** + * Quantity of a number. e.g. 0.1, 1, 10, 100 + * + * @param {number} val + * @return {number} + */ +function quantity(val) { + return Math.pow(10, quantityExponent(val)); +} + +/** + * Exponent of the quantity of a number + * e.g., 1234 equals to 1.234*10^3, so quantityExponent(1234) is 3 + * + * @param {number} val non-negative value + * @return {number} + */ +function quantityExponent(val) { + if (val === 0) { + return 0; + } + + var exp = Math.floor(Math.log(val) / Math.LN10); + /** + * exp is expected to be the rounded-down result of the base-10 log of val. + * But due to the precision loss with Math.log(val), we need to restore it + * using 10^exp to make sure we can get val back from exp. #11249 + */ + if (val / Math.pow(10, exp) >= 10) { + exp++; + } + return exp; +} + +/** + * find a “nice” number approximately equal to x. Round the number if round = true, + * take ceiling if round = false. The primary observation is that the “nicest” + * numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers. + * + * See "Nice Numbers for Graph Labels" of Graphic Gems. + * + * @param {number} val Non-negative value. + * @param {boolean} round + * @return {number} + */ +function nice(val, round) { + var exponent = quantityExponent(val); + var exp10 = Math.pow(10, exponent); + var f = val / exp10; // 1 <= f < 10 + var nf; + if (round) { + if (f < 1.5) { + nf = 1; + } + else if (f < 2.5) { + nf = 2; + } + else if (f < 4) { + nf = 3; + } + else if (f < 7) { + nf = 5; + } + else { + nf = 10; + } + } + else { + if (f < 1) { + nf = 1; + } + else if (f < 2) { + nf = 2; + } + else if (f < 3) { + nf = 3; + } + else if (f < 5) { + nf = 5; + } + else { + nf = 10; + } + } + val = nf * exp10; + + // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754). + // 20 is the uppper bound of toFixed. + return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val; +} + +/** + * This code was copied from "d3.js" + * . + * See the license statement at the head of this file. + * @param {Array.} ascArr + */ +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([ + * {interval: [18, 62], close: [1, 1]}, + * {interval: [-Infinity, -70], close: [0, 0]}, + * {interval: [-70, -26], close: [1, 1]}, + * {interval: [-26, 18], close: [1, 1]}, + * {interval: [62, 150], close: [1, 1]}, + * {interval: [106, 150], close: [1, 1]}, + * {interval: [150, Infinity], close: [0, 0]} + * ])).toEqual([ + * {interval: [-Infinity, -70], close: [0, 0]}, + * {interval: [-70, -26], close: [1, 1]}, + * {interval: [-26, 18], close: [0, 1]}, + * {interval: [18, 62], close: [0, 1]}, + * {interval: [62, 150], close: [0, 1]}, + * {interval: [150, Infinity], close: [0, 0]} + * ]); + * @param {Array.} list, where `close` mean open or close + * of the interval, and Infinity can be used. + * @return {Array.} The origin list, which has been reformed. + */ +function reformIntervals(list) { + list.sort(function (a, b) { + return littleThan(a, b, 0) ? -1 : 1; + }); + + var curr = -Infinity; + var currClose = 1; + for (var i = 0; i < list.length;) { + var interval = list[i].interval; + var close = list[i].close; + + for (var lg = 0; lg < 2; lg++) { + if (interval[lg] <= curr) { + interval[lg] = curr; + close[lg] = !lg ? 1 - currClose : 1; + } + curr = interval[lg]; + currClose = close[lg]; + } + + if (interval[0] === interval[1] && close[0] * close[1] !== 1) { + list.splice(i, 1); + } + else { + i++; + } + } + + return list; + + function littleThan(a, b, lg) { + return a.interval[lg] < b.interval[lg] + || ( + a.interval[lg] === b.interval[lg] + && ( + (a.close[lg] - b.close[lg] === (!lg ? 1 : -1)) + || (!lg && littleThan(a, b, 1)) + ) + ); + } +} + +/** + * parseFloat NaNs numeric-cast false positives (null|true|false|"") + * ...but misinterprets leading-number strings, particularly hex literals ("0x...") + * subtraction forces infinities to NaN + * + * @param {*} v + * @return {boolean} + */ +function isNumeric(v) { + return v - parseFloat(v) >= 0; +} + + +var number = (Object.freeze || Object)({ + linearMap: linearMap, + parsePercent: parsePercent$1, + round: round$1, + asc: asc, + getPrecision: getPrecision, + getPrecisionSafe: getPrecisionSafe, + getPixelPrecision: getPixelPrecision, + getPercentWithPrecision: getPercentWithPrecision, + MAX_SAFE_INTEGER: MAX_SAFE_INTEGER, + remRadian: remRadian, + isRadianAroundZero: isRadianAroundZero, + parseDate: parseDate, + quantity: quantity, + quantityExponent: quantityExponent, + nice: nice, + quantile: quantile, + reformIntervals: reformIntervals, + isNumeric: isNumeric +}); + +/* +* 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 Text from 'zrender/src/graphic/Text'; + +/** + * 每三位默认加,格式化 + * @param {string|number} x + * @return {string} + */ +function addCommas(x) { + if (isNaN(x)) { + return '-'; + } + x = (x + '').split('.'); + return x[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, '$1,') + + (x.length > 1 ? ('.' + x[1]) : ''); +} + +/** + * @param {string} str + * @param {boolean} [upperCaseFirst=false] + * @return {string} str + */ +function toCamelCase(str, upperCaseFirst) { + str = (str || '').toLowerCase().replace(/-(.)/g, function (match, group1) { + return group1.toUpperCase(); + }); + + if (upperCaseFirst && str) { + str = str.charAt(0).toUpperCase() + str.slice(1); + } + + return str; +} + +var normalizeCssArray$1 = normalizeCssArray; + + +var replaceReg = /([&<>"'])/g; +var replaceMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''' +}; + +function encodeHTML(source) { + return source == null + ? '' + : (source + '').replace(replaceReg, function (str, c) { + return replaceMap[c]; + }); +} + +var TPL_VAR_ALIAS = ['a', 'b', 'c', 'd', 'e', 'f', 'g']; + +var wrapVar = function (varName, seriesIdx) { + return '{' + varName + (seriesIdx == null ? '' : seriesIdx) + '}'; +}; + +/** + * Template formatter + * @param {string} tpl + * @param {Array.|Object} paramsList + * @param {boolean} [encode=false] + * @return {string} + */ +function formatTpl(tpl, paramsList, encode) { + if (!isArray(paramsList)) { + paramsList = [paramsList]; + } + var seriesLen = paramsList.length; + if (!seriesLen) { + return ''; + } + + var $vars = paramsList[0].$vars || []; + for (var i = 0; i < $vars.length; i++) { + var alias = TPL_VAR_ALIAS[i]; + tpl = tpl.replace(wrapVar(alias), wrapVar(alias, 0)); + } + for (var seriesIdx = 0; seriesIdx < seriesLen; seriesIdx++) { + for (var k = 0; k < $vars.length; k++) { + var val = paramsList[seriesIdx][$vars[k]]; + tpl = tpl.replace( + wrapVar(TPL_VAR_ALIAS[k], seriesIdx), + encode ? encodeHTML(val) : val + ); + } + } + + return tpl; +} + +/** + * simple Template formatter + * + * @param {string} tpl + * @param {Object} param + * @param {boolean} [encode=false] + * @return {string} + */ +function formatTplSimple(tpl, param, encode) { + each$1(param, function (value, key) { + tpl = tpl.replace( + '{' + key + '}', + encode ? encodeHTML(value) : value + ); + }); + return tpl; +} + +/** + * @param {Object|string} [opt] If string, means color. + * @param {string} [opt.color] + * @param {string} [opt.extraCssText] + * @param {string} [opt.type='item'] 'item' or 'subItem' + * @param {string} [opt.renderMode='html'] render mode of tooltip, 'html' or 'richText' + * @param {string} [opt.markerId='X'] id name for marker. If only one marker is in a rich text, this can be omitted. + * @return {string} + */ +function getTooltipMarker(opt, extraCssText) { + opt = isString(opt) ? {color: opt, extraCssText: extraCssText} : (opt || {}); + var color = opt.color; + var type = opt.type; + var extraCssText = opt.extraCssText; + var renderMode = opt.renderMode || 'html'; + var markerId = opt.markerId || 'X'; + + if (!color) { + return ''; + } + + if (renderMode === 'html') { + return type === 'subItem' + ? '' + : ''; + } + else { + // Space for rich element marker + return { + renderMode: renderMode, + content: '{marker' + markerId + '|} ', + style: { + color: color + } + }; + } +} + +function pad(str, len) { + str += ''; + return '0000'.substr(0, len - str.length) + str; +} + + +/** + * ISO Date format + * @param {string} tpl + * @param {number} value + * @param {boolean} [isUTC=false] Default in local time. + * see `module:echarts/scale/Time` + * and `module:echarts/util/number#parseDate`. + * @inner + */ +function formatTime(tpl, value, isUTC) { + if (tpl === 'week' + || tpl === 'month' + || tpl === 'quarter' + || tpl === 'half-year' + || tpl === 'year' + ) { + tpl = 'MM-dd\nyyyy'; + } + + var date = parseDate(value); + var utc = isUTC ? 'UTC' : ''; + var y = date['get' + utc + 'FullYear'](); + var M = date['get' + utc + 'Month']() + 1; + var d = date['get' + utc + 'Date'](); + var h = date['get' + utc + 'Hours'](); + var m = date['get' + utc + 'Minutes'](); + var s = date['get' + utc + 'Seconds'](); + var S = date['get' + utc + 'Milliseconds'](); + + tpl = tpl.replace('MM', pad(M, 2)) + .replace('M', M) + .replace('yyyy', y) + .replace('yy', y % 100) + .replace('dd', pad(d, 2)) + .replace('d', d) + .replace('hh', pad(h, 2)) + .replace('h', h) + .replace('mm', pad(m, 2)) + .replace('m', m) + .replace('ss', pad(s, 2)) + .replace('s', s) + .replace('SSS', pad(S, 3)); + + return tpl; +} + +/** + * Capital first + * @param {string} str + * @return {string} + */ +function capitalFirst(str) { + return str ? str.charAt(0).toUpperCase() + str.substr(1) : str; +} + +var truncateText$1 = truncateText; + +/** + * @public + * @param {Object} opt + * @param {string} opt.text + * @param {string} opt.font + * @param {string} [opt.textAlign='left'] + * @param {string} [opt.textVerticalAlign='top'] + * @param {Array.} [opt.textPadding] + * @param {number} [opt.textLineHeight] + * @param {Object} [opt.rich] + * @param {Object} [opt.truncate] + * @return {Object} {x, y, width, height, lineHeight} + */ +function getTextBoundingRect(opt) { + return getBoundingRect( + opt.text, + opt.font, + opt.textAlign, + opt.textVerticalAlign, + opt.textPadding, + opt.textLineHeight, + opt.rich, + opt.truncate + ); +} + +/** + * @deprecated + * the `textLineHeight` was added later. + * For backward compatiblility, put it as the last parameter. + * But deprecated this interface. Please use `getTextBoundingRect` instead. + */ +function getTextRect( + text, font, textAlign, textVerticalAlign, textPadding, rich, truncate, textLineHeight +) { + return getBoundingRect( + text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate + ); +} + + +var format = (Object.freeze || Object)({ + addCommas: addCommas, + toCamelCase: toCamelCase, + normalizeCssArray: normalizeCssArray$1, + encodeHTML: encodeHTML, + formatTpl: formatTpl, + formatTplSimple: formatTplSimple, + getTooltipMarker: getTooltipMarker, + formatTime: formatTime, + capitalFirst: capitalFirst, + truncateText: truncateText$1, + getTextBoundingRect: getTextBoundingRect, + getTextRect: getTextRect +}); + +/* +* 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. +*/ + +// Layout helpers for each component positioning + +var each$3 = each$1; + +/** + * @public + */ +var LOCATION_PARAMS = [ + 'left', 'right', 'top', 'bottom', 'width', 'height' +]; + +/** + * @public + */ +var HV_NAMES = [ + ['width', 'left', 'right'], + ['height', 'top', 'bottom'] +]; + +function boxLayout(orient, group, gap, maxWidth, maxHeight) { + var x = 0; + var y = 0; + + if (maxWidth == null) { + maxWidth = Infinity; + } + if (maxHeight == null) { + maxHeight = Infinity; + } + var currentLineMaxSize = 0; + + group.eachChild(function (child, idx) { + var position = child.position; + var rect = child.getBoundingRect(); + var nextChild = group.childAt(idx + 1); + var nextChildRect = nextChild && nextChild.getBoundingRect(); + var nextX; + var nextY; + + if (orient === 'horizontal') { + var moveX = rect.width + (nextChildRect ? (-nextChildRect.x + rect.x) : 0); + nextX = x + moveX; + // Wrap when width exceeds maxWidth or meet a `newline` group + // FIXME compare before adding gap? + if (nextX > maxWidth || child.newline) { + x = 0; + nextX = moveX; + y += currentLineMaxSize + gap; + currentLineMaxSize = rect.height; + } + else { + // FIXME: consider rect.y is not `0`? + currentLineMaxSize = Math.max(currentLineMaxSize, rect.height); + } + } + else { + var moveY = rect.height + (nextChildRect ? (-nextChildRect.y + rect.y) : 0); + nextY = y + moveY; + // Wrap when width exceeds maxHeight or meet a `newline` group + if (nextY > maxHeight || child.newline) { + x += currentLineMaxSize + gap; + y = 0; + nextY = moveY; + currentLineMaxSize = rect.width; + } + else { + currentLineMaxSize = Math.max(currentLineMaxSize, rect.width); + } + } + + if (child.newline) { + return; + } + + position[0] = x; + position[1] = y; + + orient === 'horizontal' + ? (x = nextX + gap) + : (y = nextY + gap); + }); +} + +/** + * VBox or HBox layouting + * @param {string} orient + * @param {module:zrender/container/Group} group + * @param {number} gap + * @param {number} [width=Infinity] + * @param {number} [height=Infinity] + */ +var box = boxLayout; + +/** + * VBox layouting + * @param {module:zrender/container/Group} group + * @param {number} gap + * @param {number} [width=Infinity] + * @param {number} [height=Infinity] + */ +var vbox = curry(boxLayout, 'vertical'); + +/** + * HBox layouting + * @param {module:zrender/container/Group} group + * @param {number} gap + * @param {number} [width=Infinity] + * @param {number} [height=Infinity] + */ +var hbox = curry(boxLayout, 'horizontal'); + +/** + * If x or x2 is not specified or 'center' 'left' 'right', + * the width would be as long as possible. + * If y or y2 is not specified or 'middle' 'top' 'bottom', + * the height would be as long as possible. + * + * @param {Object} positionInfo + * @param {number|string} [positionInfo.x] + * @param {number|string} [positionInfo.y] + * @param {number|string} [positionInfo.x2] + * @param {number|string} [positionInfo.y2] + * @param {Object} containerRect {width, height} + * @param {string|number} margin + * @return {Object} {width, height} + */ +function getAvailableSize(positionInfo, containerRect, margin) { + var containerWidth = containerRect.width; + var containerHeight = containerRect.height; + + var x = parsePercent$1(positionInfo.x, containerWidth); + var y = parsePercent$1(positionInfo.y, containerHeight); + var x2 = parsePercent$1(positionInfo.x2, containerWidth); + var y2 = parsePercent$1(positionInfo.y2, containerHeight); + + (isNaN(x) || isNaN(parseFloat(positionInfo.x))) && (x = 0); + (isNaN(x2) || isNaN(parseFloat(positionInfo.x2))) && (x2 = containerWidth); + (isNaN(y) || isNaN(parseFloat(positionInfo.y))) && (y = 0); + (isNaN(y2) || isNaN(parseFloat(positionInfo.y2))) && (y2 = containerHeight); + + margin = normalizeCssArray$1(margin || 0); + + return { + width: Math.max(x2 - x - margin[1] - margin[3], 0), + height: Math.max(y2 - y - margin[0] - margin[2], 0) + }; +} + +/** + * Parse position info. + * + * @param {Object} positionInfo + * @param {number|string} [positionInfo.left] + * @param {number|string} [positionInfo.top] + * @param {number|string} [positionInfo.right] + * @param {number|string} [positionInfo.bottom] + * @param {number|string} [positionInfo.width] + * @param {number|string} [positionInfo.height] + * @param {number|string} [positionInfo.aspect] Aspect is width / height + * @param {Object} containerRect + * @param {string|number} [margin] + * + * @return {module:zrender/core/BoundingRect} + */ +function getLayoutRect( + positionInfo, containerRect, margin +) { + margin = normalizeCssArray$1(margin || 0); + + var containerWidth = containerRect.width; + var containerHeight = containerRect.height; + + var left = parsePercent$1(positionInfo.left, containerWidth); + var top = parsePercent$1(positionInfo.top, containerHeight); + var right = parsePercent$1(positionInfo.right, containerWidth); + var bottom = parsePercent$1(positionInfo.bottom, containerHeight); + var width = parsePercent$1(positionInfo.width, containerWidth); + var height = parsePercent$1(positionInfo.height, containerHeight); + + var verticalMargin = margin[2] + margin[0]; + var horizontalMargin = margin[1] + margin[3]; + var aspect = positionInfo.aspect; + + // If width is not specified, calculate width from left and right + if (isNaN(width)) { + width = containerWidth - right - horizontalMargin - left; + } + if (isNaN(height)) { + height = containerHeight - bottom - verticalMargin - top; + } + + if (aspect != null) { + // If width and height are not given + // 1. Graph should not exceeds the container + // 2. Aspect must be keeped + // 3. Graph should take the space as more as possible + // FIXME + // Margin is not considered, because there is no case that both + // using margin and aspect so far. + if (isNaN(width) && isNaN(height)) { + if (aspect > containerWidth / containerHeight) { + width = containerWidth * 0.8; + } + else { + height = containerHeight * 0.8; + } + } + + // Calculate width or height with given aspect + if (isNaN(width)) { + width = aspect * height; + } + if (isNaN(height)) { + height = width / aspect; + } + } + + // If left is not specified, calculate left from right and width + if (isNaN(left)) { + left = containerWidth - right - width - horizontalMargin; + } + if (isNaN(top)) { + top = containerHeight - bottom - height - verticalMargin; + } + + // Align left and top + switch (positionInfo.left || positionInfo.right) { + case 'center': + left = containerWidth / 2 - width / 2 - margin[3]; + break; + case 'right': + left = containerWidth - width - horizontalMargin; + break; + } + switch (positionInfo.top || positionInfo.bottom) { + case 'middle': + case 'center': + top = containerHeight / 2 - height / 2 - margin[0]; + break; + case 'bottom': + top = containerHeight - height - verticalMargin; + break; + } + // If something is wrong and left, top, width, height are calculated as NaN + left = left || 0; + top = top || 0; + if (isNaN(width)) { + // Width may be NaN if only one value is given except width + width = containerWidth - horizontalMargin - left - (right || 0); + } + if (isNaN(height)) { + // Height may be NaN if only one value is given except height + height = containerHeight - verticalMargin - top - (bottom || 0); + } + + var rect = new BoundingRect(left + margin[3], top + margin[0], width, height); + rect.margin = margin; + return rect; +} + + +/** + * Position a zr element in viewport + * Group position is specified by either + * {left, top}, {right, bottom} + * If all properties exists, right and bottom will be igonred. + * + * Logic: + * 1. Scale (against origin point in parent coord) + * 2. Rotate (against origin point in parent coord) + * 3. Traslate (with el.position by this method) + * So this method only fixes the last step 'Traslate', which does not affect + * scaling and rotating. + * + * If be called repeatly with the same input el, the same result will be gotten. + * + * @param {module:zrender/Element} el Should have `getBoundingRect` method. + * @param {Object} positionInfo + * @param {number|string} [positionInfo.left] + * @param {number|string} [positionInfo.top] + * @param {number|string} [positionInfo.right] + * @param {number|string} [positionInfo.bottom] + * @param {number|string} [positionInfo.width] Only for opt.boundingModel: 'raw' + * @param {number|string} [positionInfo.height] Only for opt.boundingModel: 'raw' + * @param {Object} containerRect + * @param {string|number} margin + * @param {Object} [opt] + * @param {Array.} [opt.hv=[1,1]] Only horizontal or only vertical. + * @param {Array.} [opt.boundingMode='all'] + * Specify how to calculate boundingRect when locating. + * 'all': Position the boundingRect that is transformed and uioned + * both itself and its descendants. + * This mode simplies confine the elements in the bounding + * of their container (e.g., using 'right: 0'). + * 'raw': Position the boundingRect that is not transformed and only itself. + * This mode is useful when you want a element can overflow its + * container. (Consider a rotated circle needs to be located in a corner.) + * In this mode positionInfo.width/height can only be number. + */ +function positionElement(el, positionInfo, containerRect, margin, opt) { + var h = !opt || !opt.hv || opt.hv[0]; + var v = !opt || !opt.hv || opt.hv[1]; + var boundingMode = opt && opt.boundingMode || 'all'; + + if (!h && !v) { + return; + } + + var rect; + if (boundingMode === 'raw') { + rect = el.type === 'group' + ? new BoundingRect(0, 0, +positionInfo.width || 0, +positionInfo.height || 0) + : el.getBoundingRect(); + } + else { + rect = el.getBoundingRect(); + if (el.needLocalTransform()) { + var transform = el.getLocalTransform(); + // Notice: raw rect may be inner object of el, + // which should not be modified. + rect = rect.clone(); + rect.applyTransform(transform); + } + } + + // The real width and height can not be specified but calculated by the given el. + positionInfo = getLayoutRect( + defaults( + {width: rect.width, height: rect.height}, + positionInfo + ), + containerRect, + margin + ); + + // Because 'tranlate' is the last step in transform + // (see zrender/core/Transformable#getLocalTransform), + // we can just only modify el.position to get final result. + var elPos = el.position; + var dx = h ? positionInfo.x - rect.x : 0; + var dy = v ? positionInfo.y - rect.y : 0; + + el.attr('position', boundingMode === 'raw' ? [dx, dy] : [elPos[0] + dx, elPos[1] + dy]); +} + +/** + * @param {Object} option Contains some of the properties in HV_NAMES. + * @param {number} hvIdx 0: horizontal; 1: vertical. + */ +function sizeCalculable(option, hvIdx) { + return option[HV_NAMES[hvIdx][0]] != null + || (option[HV_NAMES[hvIdx][1]] != null && option[HV_NAMES[hvIdx][2]] != null); +} + +/** + * Consider Case: + * When defulat option has {left: 0, width: 100}, and we set {right: 0} + * through setOption or media query, using normal zrUtil.merge will cause + * {right: 0} does not take effect. + * + * @example + * ComponentModel.extend({ + * init: function () { + * ... + * var inputPositionParams = layout.getLayoutParams(option); + * this.mergeOption(inputPositionParams); + * }, + * mergeOption: function (newOption) { + * newOption && zrUtil.merge(thisOption, newOption, true); + * layout.mergeLayoutParam(thisOption, newOption); + * } + * }); + * + * @param {Object} targetOption + * @param {Object} newOption + * @param {Object|string} [opt] + * @param {boolean|Array.} [opt.ignoreSize=false] Used for the components + * that width (or height) should not be calculated by left and right (or top and bottom). + */ +function mergeLayoutParam(targetOption, newOption, opt) { + !isObject$1(opt) && (opt = {}); + + var ignoreSize = opt.ignoreSize; + !isArray(ignoreSize) && (ignoreSize = [ignoreSize, ignoreSize]); + + var hResult = merge$$1(HV_NAMES[0], 0); + var vResult = merge$$1(HV_NAMES[1], 1); + + copy(HV_NAMES[0], targetOption, hResult); + copy(HV_NAMES[1], targetOption, vResult); + + function merge$$1(names, hvIdx) { + var newParams = {}; + var newValueCount = 0; + var merged = {}; + var mergedValueCount = 0; + var enoughParamNumber = 2; + + each$3(names, function (name) { + merged[name] = targetOption[name]; + }); + each$3(names, function (name) { + // Consider case: newOption.width is null, which is + // set by user for removing width setting. + hasProp(newOption, name) && (newParams[name] = merged[name] = newOption[name]); + hasValue(newParams, name) && newValueCount++; + hasValue(merged, name) && mergedValueCount++; + }); + + if (ignoreSize[hvIdx]) { + // Only one of left/right is premitted to exist. + if (hasValue(newOption, names[1])) { + merged[names[2]] = null; + } + else if (hasValue(newOption, names[2])) { + merged[names[1]] = null; + } + return merged; + } + + // Case: newOption: {width: ..., right: ...}, + // or targetOption: {right: ...} and newOption: {width: ...}, + // There is no conflict when merged only has params count + // little than enoughParamNumber. + if (mergedValueCount === enoughParamNumber || !newValueCount) { + return merged; + } + // Case: newOption: {width: ..., right: ...}, + // Than we can make sure user only want those two, and ignore + // all origin params in targetOption. + else if (newValueCount >= enoughParamNumber) { + return newParams; + } + else { + // Chose another param from targetOption by priority. + for (var i = 0; i < names.length; i++) { + var name = names[i]; + if (!hasProp(newParams, name) && hasProp(targetOption, name)) { + newParams[name] = targetOption[name]; + break; + } + } + return newParams; + } + } + + function hasProp(obj, name) { + return obj.hasOwnProperty(name); + } + + function hasValue(obj, name) { + return obj[name] != null && obj[name] !== 'auto'; + } + + function copy(names, target, source) { + each$3(names, function (name) { + target[name] = source[name]; + }); + } +} + +/** + * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object. + * @param {Object} source + * @return {Object} Result contains those props. + */ +function getLayoutParams(source) { + return copyLayoutParams({}, source); +} + +/** + * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object. + * @param {Object} source + * @return {Object} Result contains those props. + */ +function copyLayoutParams(target, source) { + source && target && each$3(LOCATION_PARAMS, function (name) { + source.hasOwnProperty(name) && (target[name] = source[name]); + }); + return target; +} + +/* +* 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. +*/ + + +var boxLayoutMixin = { + getBoxLayoutParams: function () { + return { + left: this.get('left'), + top: this.get('top'), + right: this.get('right'), + bottom: this.get('bottom'), + width: this.get('width'), + height: this.get('height') + }; + } +}; + +/* +* 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. +*/ + +/** + * Component model + * + * @module echarts/model/Component + */ + +var inner$1 = makeInner(); + +/** + * @alias module:echarts/model/Component + * @constructor + * @param {Object} option + * @param {module:echarts/model/Model} parentModel + * @param {module:echarts/model/Model} ecModel + */ +var ComponentModel = Model.extend({ + + type: 'component', + + /** + * @readOnly + * @type {string} + */ + id: '', + + /** + * Because simplified concept is probably better, series.name (or component.name) + * has been having too many resposibilities: + * (1) Generating id (which requires name in option should not be modified). + * (2) As an index to mapping series when merging option or calling API (a name + * can refer to more then one components, which is convinient is some case). + * (3) Display. + * @readOnly + */ + name: '', + + /** + * @readOnly + * @type {string} + */ + mainType: '', + + /** + * @readOnly + * @type {string} + */ + subType: '', + + /** + * @readOnly + * @type {number} + */ + componentIndex: 0, + + /** + * @type {Object} + * @protected + */ + defaultOption: null, + + /** + * @type {module:echarts/model/Global} + * @readOnly + */ + ecModel: null, + + /** + * key: componentType + * value: Component model list, can not be null. + * @type {Object.>} + * @readOnly + */ + dependentModels: [], + + /** + * @type {string} + * @readOnly + */ + uid: null, + + /** + * Support merge layout params. + * Only support 'box' now (left/right/top/bottom/width/height). + * @type {string|Object} Object can be {ignoreSize: true} + * @readOnly + */ + layoutMode: null, + + $constructor: function (option, parentModel, ecModel, extraOpt) { + Model.call(this, option, parentModel, ecModel, extraOpt); + + this.uid = getUID('ec_cpt_model'); + }, + + init: function (option, parentModel, ecModel, extraOpt) { + this.mergeDefaultAndTheme(option, ecModel); + }, + + mergeDefaultAndTheme: function (option, ecModel) { + var layoutMode = this.layoutMode; + var inputPositionParams = layoutMode + ? getLayoutParams(option) : {}; + + var themeModel = ecModel.getTheme(); + merge(option, themeModel.get(this.mainType)); + merge(option, this.getDefaultOption()); + + if (layoutMode) { + mergeLayoutParam(option, inputPositionParams, layoutMode); + } + }, + + mergeOption: function (option, extraOpt) { + merge(this.option, option, true); + + var layoutMode = this.layoutMode; + if (layoutMode) { + mergeLayoutParam(this.option, option, layoutMode); + } + }, + + // Hooker after init or mergeOption + optionUpdated: function (newCptOption, isInit) {}, + + getDefaultOption: function () { + var fields = inner$1(this); + if (!fields.defaultOption) { + var optList = []; + var Class = this.constructor; + while (Class) { + var opt = Class.prototype.defaultOption; + opt && optList.push(opt); + Class = Class.superClass; + } + + var defaultOption = {}; + for (var i = optList.length - 1; i >= 0; i--) { + defaultOption = merge(defaultOption, optList[i], true); + } + fields.defaultOption = defaultOption; + } + return fields.defaultOption; + }, + + getReferringComponents: function (mainType) { + return this.ecModel.queryComponents({ + mainType: mainType, + index: this.get(mainType + 'Index', true), + id: this.get(mainType + 'Id', true) + }); + } + +}); + +// Reset ComponentModel.extend, add preConstruct. +// clazzUtil.enableClassExtend( +// ComponentModel, +// function (option, parentModel, ecModel, extraOpt) { +// // Set dependentModels, componentIndex, name, id, mainType, subType. +// zrUtil.extend(this, extraOpt); + +// this.uid = componentUtil.getUID('componentModel'); + +// // this.setReadOnly([ +// // 'type', 'id', 'uid', 'name', 'mainType', 'subType', +// // 'dependentModels', 'componentIndex' +// // ]); +// } +// ); + +// Add capability of registerClass, getClass, hasClass, registerSubTypeDefaulter and so on. +enableClassManagement( + ComponentModel, {registerWhenExtend: true} +); +enableSubTypeDefaulter(ComponentModel); + +// Add capability of ComponentModel.topologicalTravel. +enableTopologicalTravel(ComponentModel, getDependencies); + +function getDependencies(componentType) { + var deps = []; + each$1(ComponentModel.getClassesByMainType(componentType), function (Clazz) { + deps = deps.concat(Clazz.prototype.dependencies || []); + }); + + // Ensure main type. + deps = map(deps, function (type) { + return parseClassType$1(type).main; + }); + + // Hack dataset for convenience. + if (componentType !== 'dataset' && indexOf(deps, 'dataset') <= 0) { + deps.unshift('dataset'); + } + + return deps; +} + +mixin(ComponentModel, boxLayoutMixin); + +/* +* 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. +*/ + + +var platform = ''; +// Navigator not exists in node +if (typeof navigator !== 'undefined') { + platform = navigator.platform || ''; +} + +var globalDefault = { + // backgroundColor: 'rgba(0,0,0,0)', + + // https://dribbble.com/shots/1065960-Infographic-Pie-chart-visualization + // color: ['#5793f3', '#d14a61', '#fd9c35', '#675bba', '#fec42c', '#dd4444', '#d4df5a', '#cd4870'], + // Light colors: + // color: ['#bcd3bb', '#e88f70', '#edc1a5', '#9dc5c8', '#e1e8c8', '#7b7c68', '#e5b5b5', '#f0b489', '#928ea8', '#bda29a'], + // color: ['#cc5664', '#9bd6ec', '#ea946e', '#8acaaa', '#f1ec64', '#ee8686', '#a48dc1', '#5da6bc', '#b9dcae'], + // Dark colors: + color: [ + '#c23531', '#2f4554', '#61a0a8', '#d48265', '#91c7ae', '#749f83', + '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3' + ], + + gradientColor: ['#f6efa6', '#d88273', '#bf444c'], + + // If xAxis and yAxis declared, grid is created by default. + // grid: {}, + + textStyle: { + // color: '#000', + // decoration: 'none', + // PENDING + fontFamily: platform.match(/^Win/) ? 'Microsoft YaHei' : 'sans-serif', + // fontFamily: 'Arial, Verdana, sans-serif', + fontSize: 12, + fontStyle: 'normal', + fontWeight: 'normal' + }, + + // http://blogs.adobe.com/webplatform/2014/02/24/using-blend-modes-in-html-canvas/ + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation + // Default is source-over + blendMode: null, + + animation: 'auto', + animationDuration: 1000, + animationDurationUpdate: 300, + animationEasing: 'exponentialOut', + animationEasingUpdate: 'cubicOut', + + animationThreshold: 2000, + // Configuration for progressive/incremental rendering + progressiveThreshold: 3000, + progressive: 400, + + // Threshold of if use single hover layer to optimize. + // It is recommended that `hoverLayerThreshold` is equivalent to or less than + // `progressiveThreshold`, otherwise hover will cause restart of progressive, + // which is unexpected. + // see example . + hoverLayerThreshold: 3000, + + // See: module:echarts/scale/Time + useUTC: false +}; + +/* +* 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. +*/ + +var inner$2 = makeInner(); + +function getNearestColorPalette(colors, requestColorNum) { + var paletteNum = colors.length; + // TODO colors must be in order + for (var i = 0; i < paletteNum; i++) { + if (colors[i].length > requestColorNum) { + return colors[i]; + } + } + return colors[paletteNum - 1]; +} + +var colorPaletteMixin = { + clearColorPalette: function () { + inner$2(this).colorIdx = 0; + inner$2(this).colorNameMap = {}; + }, + + /** + * @param {string} name MUST NOT be null/undefined. Otherwise call this function + * twise with the same parameters will get different result. + * @param {Object} [scope=this] + * @param {Object} [requestColorNum] + * @return {string} color string. + */ + getColorFromPalette: function (name, scope, requestColorNum) { + scope = scope || this; + var scopeFields = inner$2(scope); + var colorIdx = scopeFields.colorIdx || 0; + var colorNameMap = scopeFields.colorNameMap = scopeFields.colorNameMap || {}; + // Use `hasOwnProperty` to avoid conflict with Object.prototype. + if (colorNameMap.hasOwnProperty(name)) { + return colorNameMap[name]; + } + var defaultColorPalette = normalizeToArray(this.get('color', true)); + var layeredColorPalette = this.get('colorLayer', true); + var colorPalette = ((requestColorNum == null || !layeredColorPalette) + ? defaultColorPalette : getNearestColorPalette(layeredColorPalette, requestColorNum)); + + // In case can't find in layered color palette. + colorPalette = colorPalette || defaultColorPalette; + + if (!colorPalette || !colorPalette.length) { + return; + } + + var color = colorPalette[colorIdx]; + if (name) { + colorNameMap[name] = color; + } + scopeFields.colorIdx = (colorIdx + 1) % colorPalette.length; + + return color; + } +}; + +/* +* 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. +*/ + +// Avoid typo. +var SOURCE_FORMAT_ORIGINAL = 'original'; +var SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows'; +var SOURCE_FORMAT_OBJECT_ROWS = 'objectRows'; +var SOURCE_FORMAT_KEYED_COLUMNS = 'keyedColumns'; +var SOURCE_FORMAT_UNKNOWN = 'unknown'; +// ??? CHANGE A NAME +var SOURCE_FORMAT_TYPED_ARRAY = 'typedArray'; + +var SERIES_LAYOUT_BY_COLUMN = 'column'; +var SERIES_LAYOUT_BY_ROW = 'row'; + +/* +* 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. +*/ + +/** + * [sourceFormat] + * + * + "original": + * This format is only used in series.data, where + * itemStyle can be specified in data item. + * + * + "arrayRows": + * [ + * ['product', 'score', 'amount'], + * ['Matcha Latte', 89.3, 95.8], + * ['Milk Tea', 92.1, 89.4], + * ['Cheese Cocoa', 94.4, 91.2], + * ['Walnut Brownie', 85.4, 76.9] + * ] + * + * + "objectRows": + * [ + * {product: 'Matcha Latte', score: 89.3, amount: 95.8}, + * {product: 'Milk Tea', score: 92.1, amount: 89.4}, + * {product: 'Cheese Cocoa', score: 94.4, amount: 91.2}, + * {product: 'Walnut Brownie', score: 85.4, amount: 76.9} + * ] + * + * + "keyedColumns": + * { + * 'product': ['Matcha Latte', 'Milk Tea', 'Cheese Cocoa', 'Walnut Brownie'], + * 'count': [823, 235, 1042, 988], + * 'score': [95.8, 81.4, 91.2, 76.9] + * } + * + * + "typedArray" + * + * + "unknown" + */ + +/** + * @constructor + * @param {Object} fields + * @param {string} fields.sourceFormat + * @param {Array|Object} fields.fromDataset + * @param {Array|Object} [fields.data] + * @param {string} [seriesLayoutBy='column'] + * @param {Array.} [dimensionsDefine] + * @param {Objet|HashMap} [encodeDefine] + * @param {number} [startIndex=0] + * @param {number} [dimensionsDetectCount] + */ +function Source(fields) { + + /** + * @type {boolean} + */ + this.fromDataset = fields.fromDataset; + + /** + * Not null/undefined. + * @type {Array|Object} + */ + this.data = fields.data || ( + fields.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS ? {} : [] + ); + + /** + * See also "detectSourceFormat". + * Not null/undefined. + * @type {string} + */ + this.sourceFormat = fields.sourceFormat || SOURCE_FORMAT_UNKNOWN; + + /** + * 'row' or 'column' + * Not null/undefined. + * @type {string} seriesLayoutBy + */ + this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN; + + /** + * dimensions definition in option. + * can be null/undefined. + * @type {Array.} + */ + this.dimensionsDefine = fields.dimensionsDefine; + + /** + * encode definition in option. + * can be null/undefined. + * @type {Objet|HashMap} + */ + this.encodeDefine = fields.encodeDefine && createHashMap(fields.encodeDefine); + + /** + * Not null/undefined, uint. + * @type {number} + */ + this.startIndex = fields.startIndex || 0; + + /** + * Can be null/undefined (when unknown), uint. + * @type {number} + */ + this.dimensionsDetectCount = fields.dimensionsDetectCount; +} + +/** + * Wrap original series data for some compatibility cases. + */ +Source.seriesDataToSource = function (data) { + return new Source({ + data: data, + sourceFormat: isTypedArray(data) + ? SOURCE_FORMAT_TYPED_ARRAY + : SOURCE_FORMAT_ORIGINAL, + fromDataset: false + }); +}; + +enableClassCheck(Source); + +/* +* 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. +*/ + +// The result of `guessOrdinal`. +var BE_ORDINAL = { + Must: 1, // Encounter string but not '-' and not number-like. + Might: 2, // Encounter string but number-like. + Not: 3 // Other cases +}; + +var inner$3 = makeInner(); + +/** + * @see {module:echarts/data/Source} + * @param {module:echarts/component/dataset/DatasetModel} datasetModel + * @return {string} sourceFormat + */ +function detectSourceFormat(datasetModel) { + var data = datasetModel.option.source; + var sourceFormat = SOURCE_FORMAT_UNKNOWN; + + if (isTypedArray(data)) { + sourceFormat = SOURCE_FORMAT_TYPED_ARRAY; + } + else if (isArray(data)) { + // FIXME Whether tolerate null in top level array? + if (data.length === 0) { + sourceFormat = SOURCE_FORMAT_ARRAY_ROWS; + } + + for (var i = 0, len = data.length; i < len; i++) { + var item = data[i]; + + if (item == null) { + continue; + } + else if (isArray(item)) { + sourceFormat = SOURCE_FORMAT_ARRAY_ROWS; + break; + } + else if (isObject$1(item)) { + sourceFormat = SOURCE_FORMAT_OBJECT_ROWS; + break; + } + } + } + else if (isObject$1(data)) { + for (var key in data) { + if (data.hasOwnProperty(key) && isArrayLike(data[key])) { + sourceFormat = SOURCE_FORMAT_KEYED_COLUMNS; + break; + } + } + } + else if (data != null) { + throw new Error('Invalid data'); + } + + inner$3(datasetModel).sourceFormat = sourceFormat; +} + +/** + * [Scenarios]: + * (1) Provide source data directly: + * series: { + * encode: {...}, + * dimensions: [...] + * seriesLayoutBy: 'row', + * data: [[...]] + * } + * (2) Refer to datasetModel. + * series: [{ + * encode: {...} + * // Ignore datasetIndex means `datasetIndex: 0` + * // and the dimensions defination in dataset is used + * }, { + * encode: {...}, + * seriesLayoutBy: 'column', + * datasetIndex: 1 + * }] + * + * Get data from series itself or datset. + * @return {module:echarts/data/Source} source + */ +function getSource(seriesModel) { + return inner$3(seriesModel).source; +} + +/** + * MUST be called before mergeOption of all series. + * @param {module:echarts/model/Global} ecModel + */ +function resetSourceDefaulter(ecModel) { + // `datasetMap` is used to make default encode. + inner$3(ecModel).datasetMap = createHashMap(); +} + +/** + * [Caution]: + * MUST be called after series option merged and + * before "series.getInitailData()" called. + * + * [The rule of making default encode]: + * Category axis (if exists) alway map to the first dimension. + * Each other axis occupies a subsequent dimension. + * + * [Why make default encode]: + * Simplify the typing of encode in option, avoiding the case like that: + * series: [{encode: {x: 0, y: 1}}, {encode: {x: 0, y: 2}}, {encode: {x: 0, y: 3}}], + * where the "y" have to be manually typed as "1, 2, 3, ...". + * + * @param {module:echarts/model/Series} seriesModel + */ +function prepareSource(seriesModel) { + var seriesOption = seriesModel.option; + + var data = seriesOption.data; + var sourceFormat = isTypedArray(data) + ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL; + var fromDataset = false; + + var seriesLayoutBy = seriesOption.seriesLayoutBy; + var sourceHeader = seriesOption.sourceHeader; + var dimensionsDefine = seriesOption.dimensions; + + var datasetModel = getDatasetModel(seriesModel); + if (datasetModel) { + var datasetOption = datasetModel.option; + + data = datasetOption.source; + sourceFormat = inner$3(datasetModel).sourceFormat; + fromDataset = true; + + // These settings from series has higher priority. + seriesLayoutBy = seriesLayoutBy || datasetOption.seriesLayoutBy; + sourceHeader == null && (sourceHeader = datasetOption.sourceHeader); + dimensionsDefine = dimensionsDefine || datasetOption.dimensions; + } + + var completeResult = completeBySourceData( + data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine + ); + + inner$3(seriesModel).source = new Source({ + data: data, + fromDataset: fromDataset, + seriesLayoutBy: seriesLayoutBy, + sourceFormat: sourceFormat, + dimensionsDefine: completeResult.dimensionsDefine, + startIndex: completeResult.startIndex, + dimensionsDetectCount: completeResult.dimensionsDetectCount, + // Note: dataset option does not have `encode`. + encodeDefine: seriesOption.encode + }); +} + +// return {startIndex, dimensionsDefine, dimensionsCount} +function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine) { + if (!data) { + return {dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine)}; + } + + var dimensionsDetectCount; + var startIndex; + + if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) { + // Rule: Most of the first line are string: it is header. + // Caution: consider a line with 5 string and 1 number, + // it still can not be sure it is a head, because the + // 5 string may be 5 values of category columns. + if (sourceHeader === 'auto' || sourceHeader == null) { + arrayRowsTravelFirst(function (val) { + // '-' is regarded as null/undefined. + if (val != null && val !== '-') { + if (isString(val)) { + startIndex == null && (startIndex = 1); + } + else { + startIndex = 0; + } + } + // 10 is an experience number, avoid long loop. + }, seriesLayoutBy, data, 10); + } + else { + startIndex = sourceHeader ? 1 : 0; + } + + if (!dimensionsDefine && startIndex === 1) { + dimensionsDefine = []; + arrayRowsTravelFirst(function (val, index) { + dimensionsDefine[index] = val != null ? val : ''; + }, seriesLayoutBy, data); + } + + dimensionsDetectCount = dimensionsDefine + ? dimensionsDefine.length + : seriesLayoutBy === SERIES_LAYOUT_BY_ROW + ? data.length + : data[0] + ? data[0].length + : null; + } + else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) { + if (!dimensionsDefine) { + dimensionsDefine = objectRowsCollectDimensions(data); + } + } + else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) { + if (!dimensionsDefine) { + dimensionsDefine = []; + each$1(data, function (colArr, key) { + dimensionsDefine.push(key); + }); + } + } + else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) { + var value0 = getDataItemValue(data[0]); + dimensionsDetectCount = isArray(value0) && value0.length || 1; + } + else if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { + if (__DEV__) { + assert$1(!!dimensionsDefine, 'dimensions must be given if data is TypedArray.'); + } + } + + return { + startIndex: startIndex, + dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine), + dimensionsDetectCount: dimensionsDetectCount + }; +} + +// Consider dimensions defined like ['A', 'price', 'B', 'price', 'C', 'price'], +// which is reasonable. But dimension name is duplicated. +// Returns undefined or an array contains only object without null/undefiend or string. +function normalizeDimensionsDefine(dimensionsDefine) { + if (!dimensionsDefine) { + // The meaning of null/undefined is different from empty array. + return; + } + var nameMap = createHashMap(); + return map(dimensionsDefine, function (item, index) { + item = extend({}, isObject$1(item) ? item : {name: item}); + + // User can set null in dimensions. + // We dont auto specify name, othewise a given name may + // cause it be refered unexpectedly. + if (item.name == null) { + return item; + } + + // Also consider number form like 2012. + item.name += ''; + // User may also specify displayName. + // displayName will always exists except user not + // specified or dim name is not specified or detected. + // (A auto generated dim name will not be used as + // displayName). + if (item.displayName == null) { + item.displayName = item.name; + } + + var exist = nameMap.get(item.name); + if (!exist) { + nameMap.set(item.name, {count: 1}); + } + else { + item.name += '-' + exist.count++; + } + + return item; + }); +} + +function arrayRowsTravelFirst(cb, seriesLayoutBy, data, maxLoop) { + maxLoop == null && (maxLoop = Infinity); + if (seriesLayoutBy === SERIES_LAYOUT_BY_ROW) { + for (var i = 0; i < data.length && i < maxLoop; i++) { + cb(data[i] ? data[i][0] : null, i); + } + } + else { + var value0 = data[0] || []; + for (var i = 0; i < value0.length && i < maxLoop; i++) { + cb(value0[i], i); + } + } +} + +function objectRowsCollectDimensions(data) { + var firstIndex = 0; + var obj; + while (firstIndex < data.length && !(obj = data[firstIndex++])) {} // jshint ignore: line + if (obj) { + var dimensions = []; + each$1(obj, function (value, key) { + dimensions.push(key); + }); + return dimensions; + } +} + +/** + * [The strategy of the arrengment of data dimensions for dataset]: + * "value way": all axes are non-category axes. So series one by one take + * several (the number is coordSysDims.length) dimensions from dataset. + * The result of data arrengment of data dimensions like: + * | ser0_x | ser0_y | ser1_x | ser1_y | ser2_x | ser2_y | + * "category way": at least one axis is category axis. So the the first data + * dimension is always mapped to the first category axis and shared by + * all of the series. The other data dimensions are taken by series like + * "value way" does. + * The result of data arrengment of data dimensions like: + * | ser_shared_x | ser0_y | ser1_y | ser2_y | + * + * @param {Array.} coordDimensions [{name: , type: , dimsDef: }, ...] + * @param {module:model/Series} seriesModel + * @param {module:data/Source} source + * @return {Object} encode Never be `null/undefined`. + */ +function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, source) { + var encode = {}; + + var datasetModel = getDatasetModel(seriesModel); + // Currently only make default when using dataset, util more reqirements occur. + if (!datasetModel || !coordDimensions) { + return encode; + } + + var encodeItemName = []; + var encodeSeriesName = []; + + var ecModel = seriesModel.ecModel; + var datasetMap = inner$3(ecModel).datasetMap; + var key = datasetModel.uid + '_' + source.seriesLayoutBy; + + var baseCategoryDimIndex; + var categoryWayValueDimStart; + coordDimensions = coordDimensions.slice(); + each$1(coordDimensions, function (coordDimInfo, coordDimIdx) { + !isObject$1(coordDimInfo) && (coordDimensions[coordDimIdx] = {name: coordDimInfo}); + if (coordDimInfo.type === 'ordinal' && baseCategoryDimIndex == null) { + baseCategoryDimIndex = coordDimIdx; + categoryWayValueDimStart = getDataDimCountOnCoordDim(coordDimensions[coordDimIdx]); + } + encode[coordDimInfo.name] = []; + }); + + var datasetRecord = datasetMap.get(key) + || datasetMap.set(key, {categoryWayDim: categoryWayValueDimStart, valueWayDim: 0}); + + // TODO + // Auto detect first time axis and do arrangement. + each$1(coordDimensions, function (coordDimInfo, coordDimIdx) { + var coordDimName = coordDimInfo.name; + var count = getDataDimCountOnCoordDim(coordDimInfo); + + // In value way. + if (baseCategoryDimIndex == null) { + var start = datasetRecord.valueWayDim; + pushDim(encode[coordDimName], start, count); + pushDim(encodeSeriesName, start, count); + datasetRecord.valueWayDim += count; + + // ??? TODO give a better default series name rule? + // especially when encode x y specified. + // consider: when mutiple series share one dimension + // category axis, series name should better use + // the other dimsion name. On the other hand, use + // both dimensions name. + } + // In category way, the first category axis. + else if (baseCategoryDimIndex === coordDimIdx) { + pushDim(encode[coordDimName], 0, count); + pushDim(encodeItemName, 0, count); + } + // In category way, the other axis. + else { + var start = datasetRecord.categoryWayDim; + pushDim(encode[coordDimName], start, count); + pushDim(encodeSeriesName, start, count); + datasetRecord.categoryWayDim += count; + } + }); + + function pushDim(dimIdxArr, idxFrom, idxCount) { + for (var i = 0; i < idxCount; i++) { + dimIdxArr.push(idxFrom + i); + } + } + + function getDataDimCountOnCoordDim(coordDimInfo) { + var dimsDef = coordDimInfo.dimsDef; + return dimsDef ? dimsDef.length : 1; + } + + encodeItemName.length && (encode.itemName = encodeItemName); + encodeSeriesName.length && (encode.seriesName = encodeSeriesName); + + return encode; +} + +/** + * Work for data like [{name: ..., value: ...}, ...]. + * + * @param {module:model/Series} seriesModel + * @param {module:data/Source} source + * @return {Object} encode Never be `null/undefined`. + */ +function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) { + var encode = {}; + + var datasetModel = getDatasetModel(seriesModel); + // Currently only make default when using dataset, util more reqirements occur. + if (!datasetModel) { + return encode; + } + + var sourceFormat = source.sourceFormat; + var dimensionsDefine = source.dimensionsDefine; + + var potentialNameDimIndex; + if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS || sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) { + each$1(dimensionsDefine, function (dim, idx) { + if ((isObject$1(dim) ? dim.name : dim) === 'name') { + potentialNameDimIndex = idx; + } + }); + } + + // idxResult: {v, n}. + var idxResult = (function () { + + var idxRes0 = {}; + var idxRes1 = {}; + var guessRecords = []; + + // 5 is an experience value. + for (var i = 0, len = Math.min(5, dimCount); i < len; i++) { + var guessResult = doGuessOrdinal( + source.data, sourceFormat, source.seriesLayoutBy, + dimensionsDefine, source.startIndex, i + ); + guessRecords.push(guessResult); + var isPureNumber = guessResult === BE_ORDINAL.Not; + + // [Strategy of idxRes0]: find the first BE_ORDINAL.Not as the value dim, + // and then find a name dim with the priority: + // "BE_ORDINAL.Might|BE_ORDINAL.Must" > "other dim" > "the value dim itself". + if (isPureNumber && idxRes0.v == null && i !== potentialNameDimIndex) { + idxRes0.v = i; + } + if (idxRes0.n == null + || (idxRes0.n === idxRes0.v) + || (!isPureNumber && guessRecords[idxRes0.n] === BE_ORDINAL.Not) + ) { + idxRes0.n = i; + } + if (fulfilled(idxRes0) && guessRecords[idxRes0.n] !== BE_ORDINAL.Not) { + return idxRes0; + } + + // [Strategy of idxRes1]: if idxRes0 not satisfied (that is, no BE_ORDINAL.Not), + // find the first BE_ORDINAL.Might as the value dim, + // and then find a name dim with the priority: + // "other dim" > "the value dim itself". + // That is for backward compat: number-like (e.g., `'3'`, `'55'`) can be + // treated as number. + if (!isPureNumber) { + if (guessResult === BE_ORDINAL.Might && idxRes1.v == null && i !== potentialNameDimIndex) { + idxRes1.v = i; + } + if (idxRes1.n == null || (idxRes1.n === idxRes1.v)) { + idxRes1.n = i; + } + } + } + + function fulfilled(idxResult) { + return idxResult.v != null && idxResult.n != null; + } + + return fulfilled(idxRes0) ? idxRes0 : fulfilled(idxRes1) ? idxRes1 : null; + })(); + + if (idxResult) { + encode.value = idxResult.v; + // `potentialNameDimIndex` has highest priority. + var nameDimIndex = potentialNameDimIndex != null ? potentialNameDimIndex : idxResult.n; + // By default, label use itemName in charts. + // So we dont set encodeLabel here. + encode.itemName = [nameDimIndex]; + encode.seriesName = [nameDimIndex]; + } + + return encode; +} + +/** + * If return null/undefined, indicate that should not use datasetModel. + */ +function getDatasetModel(seriesModel) { + var option = seriesModel.option; + // Caution: consider the scenario: + // A dataset is declared and a series is not expected to use the dataset, + // and at the beginning `setOption({series: { noData })` (just prepare other + // option but no data), then `setOption({series: {data: [...]}); In this case, + // the user should set an empty array to avoid that dataset is used by default. + var thisData = option.data; + if (!thisData) { + return seriesModel.ecModel.getComponent('dataset', option.datasetIndex || 0); + } +} + +/** + * The rule should not be complex, otherwise user might not + * be able to known where the data is wrong. + * The code is ugly, but how to make it neat? + * + * @param {module:echars/data/Source} source + * @param {number} dimIndex + * @return {BE_ORDINAL} guess result. + */ +function guessOrdinal(source, dimIndex) { + return doGuessOrdinal( + source.data, + source.sourceFormat, + source.seriesLayoutBy, + source.dimensionsDefine, + source.startIndex, + dimIndex + ); +} + +// dimIndex may be overflow source data. +// return {BE_ORDINAL} +function doGuessOrdinal( + data, sourceFormat, seriesLayoutBy, dimensionsDefine, startIndex, dimIndex +) { + var result; + // Experience value. + var maxLoop = 5; + + if (isTypedArray(data)) { + return BE_ORDINAL.Not; + } + + // When sourceType is 'objectRows' or 'keyedColumns', dimensionsDefine + // always exists in source. + var dimName; + var dimType; + if (dimensionsDefine) { + var dimDefItem = dimensionsDefine[dimIndex]; + if (isObject$1(dimDefItem)) { + dimName = dimDefItem.name; + dimType = dimDefItem.type; + } + else if (isString(dimDefItem)) { + dimName = dimDefItem; + } + } + + if (dimType != null) { + return dimType === 'ordinal' ? BE_ORDINAL.Must : BE_ORDINAL.Not; + } + + if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) { + if (seriesLayoutBy === SERIES_LAYOUT_BY_ROW) { + var sample = data[dimIndex]; + for (var i = 0; i < (sample || []).length && i < maxLoop; i++) { + if ((result = detectValue(sample[startIndex + i])) != null) { + return result; + } + } + } + else { + for (var i = 0; i < data.length && i < maxLoop; i++) { + var row = data[startIndex + i]; + if (row && (result = detectValue(row[dimIndex])) != null) { + return result; + } + } + } + } + else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) { + if (!dimName) { + return BE_ORDINAL.Not; + } + for (var i = 0; i < data.length && i < maxLoop; i++) { + var item = data[i]; + if (item && (result = detectValue(item[dimName])) != null) { + return result; + } + } + } + else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) { + if (!dimName) { + return BE_ORDINAL.Not; + } + var sample = data[dimName]; + if (!sample || isTypedArray(sample)) { + return BE_ORDINAL.Not; + } + for (var i = 0; i < sample.length && i < maxLoop; i++) { + if ((result = detectValue(sample[i])) != null) { + return result; + } + } + } + else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) { + for (var i = 0; i < data.length && i < maxLoop; i++) { + var item = data[i]; + var val = getDataItemValue(item); + if (!isArray(val)) { + return BE_ORDINAL.Not; + } + if ((result = detectValue(val[dimIndex])) != null) { + return result; + } + } + } + + function detectValue(val) { + var beStr = isString(val); + // Consider usage convenience, '1', '2' will be treated as "number". + // `isFinit('')` get `true`. + if (val != null && isFinite(val) && val !== '') { + return beStr ? BE_ORDINAL.Might : BE_ORDINAL.Not; + } + else if (beStr && val !== '-') { + return BE_ORDINAL.Must; + } + } + + return BE_ORDINAL.Not; +} + +/* +* 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. +*/ + +/** + * ECharts global model + * + * @module {echarts/model/Global} + */ + + +/** + * Caution: If the mechanism should be changed some day, these cases + * should be considered: + * + * (1) In `merge option` mode, if using the same option to call `setOption` + * many times, the result should be the same (try our best to ensure that). + * (2) In `merge option` mode, if a component has no id/name specified, it + * will be merged by index, and the result sequence of the components is + * consistent to the original sequence. + * (3) `reset` feature (in toolbox). Find detailed info in comments about + * `mergeOption` in module:echarts/model/OptionManager. + */ + +var OPTION_INNER_KEY = '\0_ec_inner'; + +/** + * @alias module:echarts/model/Global + * + * @param {Object} option + * @param {module:echarts/model/Model} parentModel + * @param {Object} theme + */ +var GlobalModel = Model.extend({ + + init: function (option, parentModel, theme, optionManager) { + theme = theme || {}; + + this.option = null; // Mark as not initialized. + + /** + * @type {module:echarts/model/Model} + * @private + */ + this._theme = new Model(theme); + + /** + * @type {module:echarts/model/OptionManager} + */ + this._optionManager = optionManager; + }, + + setOption: function (option, optionPreprocessorFuncs) { + assert$1( + !(OPTION_INNER_KEY in option), + 'please use chart.getOption()' + ); + + this._optionManager.setOption(option, optionPreprocessorFuncs); + + this.resetOption(null); + }, + + /** + * @param {string} type null/undefined: reset all. + * 'recreate': force recreate all. + * 'timeline': only reset timeline option + * 'media': only reset media query option + * @return {boolean} Whether option changed. + */ + resetOption: function (type) { + var optionChanged = false; + var optionManager = this._optionManager; + + if (!type || type === 'recreate') { + var baseOption = optionManager.mountOption(type === 'recreate'); + + if (!this.option || type === 'recreate') { + initBase.call(this, baseOption); + } + else { + this.restoreData(); + this.mergeOption(baseOption); + } + optionChanged = true; + } + + if (type === 'timeline' || type === 'media') { + this.restoreData(); + } + + if (!type || type === 'recreate' || type === 'timeline') { + var timelineOption = optionManager.getTimelineOption(this); + timelineOption && (this.mergeOption(timelineOption), optionChanged = true); + } + + if (!type || type === 'recreate' || type === 'media') { + var mediaOptions = optionManager.getMediaOption(this, this._api); + if (mediaOptions.length) { + each$1(mediaOptions, function (mediaOption) { + this.mergeOption(mediaOption, optionChanged = true); + }, this); + } + } + + return optionChanged; + }, + + /** + * @protected + */ + mergeOption: function (newOption) { + var option = this.option; + var componentsMap = this._componentsMap; + var newCptTypes = []; + + resetSourceDefaulter(this); + + // If no component class, merge directly. + // For example: color, animaiton options, etc. + each$1(newOption, function (componentOption, mainType) { + if (componentOption == null) { + return; + } + + if (!ComponentModel.hasClass(mainType)) { + // globalSettingTask.dirty(); + option[mainType] = option[mainType] == null + ? clone(componentOption) + : merge(option[mainType], componentOption, true); + } + else if (mainType) { + newCptTypes.push(mainType); + } + }); + + ComponentModel.topologicalTravel( + newCptTypes, ComponentModel.getAllClassMainTypes(), visitComponent, this + ); + + function visitComponent(mainType, dependencies) { + + var newCptOptionList = normalizeToArray(newOption[mainType]); + + var mapResult = mappingToExists( + componentsMap.get(mainType), newCptOptionList + ); + + makeIdAndName(mapResult); + + // Set mainType and complete subType. + each$1(mapResult, function (item, index) { + var opt = item.option; + if (isObject$1(opt)) { + item.keyInfo.mainType = mainType; + item.keyInfo.subType = determineSubType(mainType, opt, item.exist); + } + }); + + var dependentModels = getComponentsByTypes( + componentsMap, dependencies + ); + + option[mainType] = []; + componentsMap.set(mainType, []); + + each$1(mapResult, function (resultItem, index) { + var componentModel = resultItem.exist; + var newCptOption = resultItem.option; + + assert$1( + isObject$1(newCptOption) || componentModel, + 'Empty component definition' + ); + + // Consider where is no new option and should be merged using {}, + // see removeEdgeAndAdd in topologicalTravel and + // ComponentModel.getAllClassMainTypes. + if (!newCptOption) { + componentModel.mergeOption({}, this); + componentModel.optionUpdated({}, false); + } + else { + var ComponentModelClass = ComponentModel.getClass( + mainType, resultItem.keyInfo.subType, true + ); + + if (componentModel && componentModel.constructor === ComponentModelClass) { + componentModel.name = resultItem.keyInfo.name; + // componentModel.settingTask && componentModel.settingTask.dirty(); + componentModel.mergeOption(newCptOption, this); + componentModel.optionUpdated(newCptOption, false); + } + else { + // PENDING Global as parent ? + var extraOpt = extend( + { + dependentModels: dependentModels, + componentIndex: index + }, + resultItem.keyInfo + ); + componentModel = new ComponentModelClass( + newCptOption, this, this, extraOpt + ); + extend(componentModel, extraOpt); + componentModel.init(newCptOption, this, this, extraOpt); + + // Call optionUpdated after init. + // newCptOption has been used as componentModel.option + // and may be merged with theme and default, so pass null + // to avoid confusion. + componentModel.optionUpdated(null, true); + } + } + + componentsMap.get(mainType)[index] = componentModel; + option[mainType][index] = componentModel.option; + }, this); + + // Backup series for filtering. + if (mainType === 'series') { + createSeriesIndices(this, componentsMap.get('series')); + } + } + + this._seriesIndicesMap = createHashMap( + this._seriesIndices = this._seriesIndices || [] + ); + }, + + /** + * Get option for output (cloned option and inner info removed) + * @public + * @return {Object} + */ + getOption: function () { + var option = clone(this.option); + + each$1(option, function (opts, mainType) { + if (ComponentModel.hasClass(mainType)) { + var opts = normalizeToArray(opts); + for (var i = opts.length - 1; i >= 0; i--) { + // Remove options with inner id. + if (isIdInner(opts[i])) { + opts.splice(i, 1); + } + } + option[mainType] = opts; + } + }); + + delete option[OPTION_INNER_KEY]; + + return option; + }, + + /** + * @return {module:echarts/model/Model} + */ + getTheme: function () { + return this._theme; + }, + + /** + * @param {string} mainType + * @param {number} [idx=0] + * @return {module:echarts/model/Component} + */ + getComponent: function (mainType, idx) { + var list = this._componentsMap.get(mainType); + if (list) { + return list[idx || 0]; + } + }, + + /** + * If none of index and id and name used, return all components with mainType. + * @param {Object} condition + * @param {string} condition.mainType + * @param {string} [condition.subType] If ignore, only query by mainType + * @param {number|Array.} [condition.index] Either input index or id or name. + * @param {string|Array.} [condition.id] Either input index or id or name. + * @param {string|Array.} [condition.name] Either input index or id or name. + * @return {Array.} + */ + queryComponents: function (condition) { + var mainType = condition.mainType; + if (!mainType) { + return []; + } + + var index = condition.index; + var id = condition.id; + var name = condition.name; + + var cpts = this._componentsMap.get(mainType); + + if (!cpts || !cpts.length) { + return []; + } + + var result; + + if (index != null) { + if (!isArray(index)) { + index = [index]; + } + result = filter(map(index, function (idx) { + return cpts[idx]; + }), function (val) { + return !!val; + }); + } + else if (id != null) { + var isIdArray = isArray(id); + result = filter(cpts, function (cpt) { + return (isIdArray && indexOf(id, cpt.id) >= 0) + || (!isIdArray && cpt.id === id); + }); + } + else if (name != null) { + var isNameArray = isArray(name); + result = filter(cpts, function (cpt) { + return (isNameArray && indexOf(name, cpt.name) >= 0) + || (!isNameArray && cpt.name === name); + }); + } + else { + // Return all components with mainType + result = cpts.slice(); + } + + return filterBySubType(result, condition); + }, + + /** + * The interface is different from queryComponents, + * which is convenient for inner usage. + * + * @usage + * var result = findComponents( + * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}} + * ); + * var result = findComponents( + * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}} + * ); + * var result = findComponents( + * {mainType: 'series', + * filter: function (model, index) {...}} + * ); + * // result like [component0, componnet1, ...] + * + * @param {Object} condition + * @param {string} condition.mainType Mandatory. + * @param {string} [condition.subType] Optional. + * @param {Object} [condition.query] like {xxxIndex, xxxId, xxxName}, + * where xxx is mainType. + * If query attribute is null/undefined or has no index/id/name, + * do not filtering by query conditions, which is convenient for + * no-payload situations or when target of action is global. + * @param {Function} [condition.filter] parameter: component, return boolean. + * @return {Array.} + */ + findComponents: function (condition) { + var query = condition.query; + var mainType = condition.mainType; + + var queryCond = getQueryCond(query); + var result = queryCond + ? this.queryComponents(queryCond) + : this._componentsMap.get(mainType); + + return doFilter(filterBySubType(result, condition)); + + function getQueryCond(q) { + var indexAttr = mainType + 'Index'; + var idAttr = mainType + 'Id'; + var nameAttr = mainType + 'Name'; + return q && ( + q[indexAttr] != null + || q[idAttr] != null + || q[nameAttr] != null + ) + ? { + mainType: mainType, + // subType will be filtered finally. + index: q[indexAttr], + id: q[idAttr], + name: q[nameAttr] + } + : null; + } + + function doFilter(res) { + return condition.filter + ? filter(res, condition.filter) + : res; + } + }, + + /** + * @usage + * eachComponent('legend', function (legendModel, index) { + * ... + * }); + * eachComponent(function (componentType, model, index) { + * // componentType does not include subType + * // (componentType is 'xxx' but not 'xxx.aa') + * }); + * eachComponent( + * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}}, + * function (model, index) {...} + * ); + * eachComponent( + * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}}, + * function (model, index) {...} + * ); + * + * @param {string|Object=} mainType When mainType is object, the definition + * is the same as the method 'findComponents'. + * @param {Function} cb + * @param {*} context + */ + eachComponent: function (mainType, cb, context) { + var componentsMap = this._componentsMap; + + if (typeof mainType === 'function') { + context = cb; + cb = mainType; + componentsMap.each(function (components, componentType) { + each$1(components, function (component, index) { + cb.call(context, componentType, component, index); + }); + }); + } + else if (isString(mainType)) { + each$1(componentsMap.get(mainType), cb, context); + } + else if (isObject$1(mainType)) { + var queryResult = this.findComponents(mainType); + each$1(queryResult, cb, context); + } + }, + + /** + * @param {string} name + * @return {Array.} + */ + getSeriesByName: function (name) { + var series = this._componentsMap.get('series'); + return filter(series, function (oneSeries) { + return oneSeries.name === name; + }); + }, + + /** + * @param {number} seriesIndex + * @return {module:echarts/model/Series} + */ + getSeriesByIndex: function (seriesIndex) { + return this._componentsMap.get('series')[seriesIndex]; + }, + + /** + * Get series list before filtered by type. + * FIXME: rename to getRawSeriesByType? + * + * @param {string} subType + * @return {Array.} + */ + getSeriesByType: function (subType) { + var series = this._componentsMap.get('series'); + return filter(series, function (oneSeries) { + return oneSeries.subType === subType; + }); + }, + + /** + * @return {Array.} + */ + getSeries: function () { + return this._componentsMap.get('series').slice(); + }, + + /** + * @return {number} + */ + getSeriesCount: function () { + return this._componentsMap.get('series').length; + }, + + /** + * After filtering, series may be different + * frome raw series. + * + * @param {Function} cb + * @param {*} context + */ + eachSeries: function (cb, context) { + assertSeriesInitialized(this); + each$1(this._seriesIndices, function (rawSeriesIndex) { + var series = this._componentsMap.get('series')[rawSeriesIndex]; + cb.call(context, series, rawSeriesIndex); + }, this); + }, + + /** + * Iterate raw series before filtered. + * + * @param {Function} cb + * @param {*} context + */ + eachRawSeries: function (cb, context) { + each$1(this._componentsMap.get('series'), cb, context); + }, + + /** + * After filtering, series may be different. + * frome raw series. + * + * @param {string} subType. + * @param {Function} cb + * @param {*} context + */ + eachSeriesByType: function (subType, cb, context) { + assertSeriesInitialized(this); + each$1(this._seriesIndices, function (rawSeriesIndex) { + var series = this._componentsMap.get('series')[rawSeriesIndex]; + if (series.subType === subType) { + cb.call(context, series, rawSeriesIndex); + } + }, this); + }, + + /** + * Iterate raw series before filtered of given type. + * + * @parma {string} subType + * @param {Function} cb + * @param {*} context + */ + eachRawSeriesByType: function (subType, cb, context) { + return each$1(this.getSeriesByType(subType), cb, context); + }, + + /** + * @param {module:echarts/model/Series} seriesModel + */ + isSeriesFiltered: function (seriesModel) { + assertSeriesInitialized(this); + return this._seriesIndicesMap.get(seriesModel.componentIndex) == null; + }, + + /** + * @return {Array.} + */ + getCurrentSeriesIndices: function () { + return (this._seriesIndices || []).slice(); + }, + + /** + * @param {Function} cb + * @param {*} context + */ + filterSeries: function (cb, context) { + assertSeriesInitialized(this); + var filteredSeries = filter( + this._componentsMap.get('series'), cb, context + ); + createSeriesIndices(this, filteredSeries); + }, + + restoreData: function (payload) { + var componentsMap = this._componentsMap; + + createSeriesIndices(this, componentsMap.get('series')); + + var componentTypes = []; + componentsMap.each(function (components, componentType) { + componentTypes.push(componentType); + }); + + ComponentModel.topologicalTravel( + componentTypes, + ComponentModel.getAllClassMainTypes(), + function (componentType, dependencies) { + each$1(componentsMap.get(componentType), function (component) { + (componentType !== 'series' || !isNotTargetSeries(component, payload)) + && component.restoreData(); + }); + } + ); + } + +}); + +function isNotTargetSeries(seriesModel, payload) { + if (payload) { + var index = payload.seiresIndex; + var id = payload.seriesId; + var name = payload.seriesName; + return (index != null && seriesModel.componentIndex !== index) + || (id != null && seriesModel.id !== id) + || (name != null && seriesModel.name !== name); + } +} + +/** + * @inner + */ +function mergeTheme(option, theme) { + // PENDING + // NOT use `colorLayer` in theme if option has `color` + var notMergeColorLayer = option.color && !option.colorLayer; + + each$1(theme, function (themeItem, name) { + if (name === 'colorLayer' && notMergeColorLayer) { + return; + } + // 如果有 component model 则把具体的 merge 逻辑交给该 model 处理 + if (!ComponentModel.hasClass(name)) { + if (typeof themeItem === 'object') { + option[name] = !option[name] + ? clone(themeItem) + : merge(option[name], themeItem, false); + } + else { + if (option[name] == null) { + option[name] = themeItem; + } + } + } + }); +} + +function initBase(baseOption) { + baseOption = baseOption; + + // Using OPTION_INNER_KEY to mark that this option can not be used outside, + // i.e. `chart.setOption(chart.getModel().option);` is forbiden. + this.option = {}; + this.option[OPTION_INNER_KEY] = 1; + + /** + * Init with series: [], in case of calling findSeries method + * before series initialized. + * @type {Object.>} + * @private + */ + this._componentsMap = createHashMap({series: []}); + + /** + * Mapping between filtered series list and raw series list. + * key: filtered series indices, value: raw series indices. + * @type {Array.} + * @private + */ + this._seriesIndices; + + this._seriesIndicesMap; + + mergeTheme(baseOption, this._theme.option); + + // TODO Needs clone when merging to the unexisted property + merge(baseOption, globalDefault, false); + + this.mergeOption(baseOption); +} + +/** + * @inner + * @param {Array.|string} types model types + * @return {Object} key: {string} type, value: {Array.} models + */ +function getComponentsByTypes(componentsMap, types) { + if (!isArray(types)) { + types = types ? [types] : []; + } + + var ret = {}; + each$1(types, function (type) { + ret[type] = (componentsMap.get(type) || []).slice(); + }); + + return ret; +} + +/** + * @inner + */ +function determineSubType(mainType, newCptOption, existComponent) { + var subType = newCptOption.type + ? newCptOption.type + : existComponent + ? existComponent.subType + // Use determineSubType only when there is no existComponent. + : ComponentModel.determineSubType(mainType, newCptOption); + + // tooltip, markline, markpoint may always has no subType + return subType; +} + +/** + * @inner + */ +function createSeriesIndices(ecModel, seriesModels) { + ecModel._seriesIndicesMap = createHashMap( + ecModel._seriesIndices = map(seriesModels, function (series) { + return series.componentIndex; + }) || [] + ); +} + +/** + * @inner + */ +function filterBySubType(components, condition) { + // Using hasOwnProperty for restrict. Consider + // subType is undefined in user payload. + return condition.hasOwnProperty('subType') + ? filter(components, function (cpt) { + return cpt.subType === condition.subType; + }) + : components; +} + +/** + * @inner + */ +function assertSeriesInitialized(ecModel) { + // Components that use _seriesIndices should depends on series component, + // which make sure that their initialization is after series. + if (__DEV__) { + if (!ecModel._seriesIndices) { + throw new Error('Option should contains series.'); + } + } +} + +mixin(GlobalModel, colorPaletteMixin); + +/* +* 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. +*/ + +var echartsAPIList = [ + 'getDom', 'getZr', 'getWidth', 'getHeight', 'getDevicePixelRatio', 'dispatchAction', 'isDisposed', + 'on', 'off', 'getDataURL', 'getConnectedDataURL', 'getModel', 'getOption', + 'getViewOfComponentModel', 'getViewOfSeriesModel' +]; +// And `getCoordinateSystems` and `getComponentByElement` will be injected in echarts.js + +function ExtensionAPI(chartInstance) { + each$1(echartsAPIList, function (name) { + this[name] = bind(chartInstance[name], chartInstance); + }, this); +} + +/* +* 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. +*/ + +var coordinateSystemCreators = {}; + +function CoordinateSystemManager() { + + this._coordinateSystems = []; +} + +CoordinateSystemManager.prototype = { + + constructor: CoordinateSystemManager, + + create: function (ecModel, api) { + var coordinateSystems = []; + each$1(coordinateSystemCreators, function (creater, type) { + var list = creater.create(ecModel, api); + coordinateSystems = coordinateSystems.concat(list || []); + }); + + this._coordinateSystems = coordinateSystems; + }, + + update: function (ecModel, api) { + each$1(this._coordinateSystems, function (coordSys) { + coordSys.update && coordSys.update(ecModel, api); + }); + }, + + getCoordinateSystems: function () { + return this._coordinateSystems.slice(); + } +}; + +CoordinateSystemManager.register = function (type, coordinateSystemCreator) { + coordinateSystemCreators[type] = coordinateSystemCreator; +}; + +CoordinateSystemManager.get = function (type) { + return coordinateSystemCreators[type]; +}; + +/* +* 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. +*/ + +/** + * ECharts option manager + * + * @module {echarts/model/OptionManager} + */ + + +var each$4 = each$1; +var clone$3 = clone; +var map$1 = map; +var merge$1 = merge; + +var QUERY_REG = /^(min|max)?(.+)$/; + +/** + * TERM EXPLANATIONS: + * + * [option]: + * + * An object that contains definitions of components. For example: + * var option = { + * title: {...}, + * legend: {...}, + * visualMap: {...}, + * series: [ + * {data: [...]}, + * {data: [...]}, + * ... + * ] + * }; + * + * [rawOption]: + * + * An object input to echarts.setOption. 'rawOption' may be an + * 'option', or may be an object contains multi-options. For example: + * var option = { + * baseOption: { + * title: {...}, + * legend: {...}, + * series: [ + * {data: [...]}, + * {data: [...]}, + * ... + * ] + * }, + * timeline: {...}, + * options: [ + * {title: {...}, series: {data: [...]}}, + * {title: {...}, series: {data: [...]}}, + * ... + * ], + * media: [ + * { + * query: {maxWidth: 320}, + * option: {series: {x: 20}, visualMap: {show: false}} + * }, + * { + * query: {minWidth: 320, maxWidth: 720}, + * option: {series: {x: 500}, visualMap: {show: true}} + * }, + * { + * option: {series: {x: 1200}, visualMap: {show: true}} + * } + * ] + * }; + * + * @alias module:echarts/model/OptionManager + * @param {module:echarts/ExtensionAPI} api + */ +function OptionManager(api) { + + /** + * @private + * @type {module:echarts/ExtensionAPI} + */ + this._api = api; + + /** + * @private + * @type {Array.} + */ + this._timelineOptions = []; + + /** + * @private + * @type {Array.} + */ + this._mediaList = []; + + /** + * @private + * @type {Object} + */ + this._mediaDefault; + + /** + * -1, means default. + * empty means no media. + * @private + * @type {Array.} + */ + this._currentMediaIndices = []; + + /** + * @private + * @type {Object} + */ + this._optionBackup; + + /** + * @private + * @type {Object} + */ + this._newBaseOption; +} + +// timeline.notMerge is not supported in ec3. Firstly there is rearly +// case that notMerge is needed. Secondly supporting 'notMerge' requires +// rawOption cloned and backuped when timeline changed, which does no +// good to performance. What's more, that both timeline and setOption +// method supply 'notMerge' brings complex and some problems. +// Consider this case: +// (step1) chart.setOption({timeline: {notMerge: false}, ...}, false); +// (step2) chart.setOption({timeline: {notMerge: true}, ...}, false); + +OptionManager.prototype = { + + constructor: OptionManager, + + /** + * @public + * @param {Object} rawOption Raw option. + * @param {module:echarts/model/Global} ecModel + * @param {Array.} optionPreprocessorFuncs + * @return {Object} Init option + */ + setOption: function (rawOption, optionPreprocessorFuncs) { + if (rawOption) { + // That set dat primitive is dangerous if user reuse the data when setOption again. + each$1(normalizeToArray(rawOption.series), function (series) { + series && series.data && isTypedArray(series.data) && setAsPrimitive(series.data); + }); + } + + // Caution: some series modify option data, if do not clone, + // it should ensure that the repeat modify correctly + // (create a new object when modify itself). + rawOption = clone$3(rawOption); + + // FIXME + // 如果 timeline options 或者 media 中设置了某个属性,而baseOption中没有设置,则进行警告。 + + var oldOptionBackup = this._optionBackup; + var newParsedOption = parseRawOption.call( + this, rawOption, optionPreprocessorFuncs, !oldOptionBackup + ); + this._newBaseOption = newParsedOption.baseOption; + + // For setOption at second time (using merge mode); + if (oldOptionBackup) { + // Only baseOption can be merged. + mergeOption(oldOptionBackup.baseOption, newParsedOption.baseOption); + + // For simplicity, timeline options and media options do not support merge, + // that is, if you `setOption` twice and both has timeline options, the latter + // timeline opitons will not be merged to the formers, but just substitude them. + if (newParsedOption.timelineOptions.length) { + oldOptionBackup.timelineOptions = newParsedOption.timelineOptions; + } + if (newParsedOption.mediaList.length) { + oldOptionBackup.mediaList = newParsedOption.mediaList; + } + if (newParsedOption.mediaDefault) { + oldOptionBackup.mediaDefault = newParsedOption.mediaDefault; + } + } + else { + this._optionBackup = newParsedOption; + } + }, + + /** + * @param {boolean} isRecreate + * @return {Object} + */ + mountOption: function (isRecreate) { + var optionBackup = this._optionBackup; + + // TODO + // 如果没有reset功能则不clone。 + + this._timelineOptions = map$1(optionBackup.timelineOptions, clone$3); + this._mediaList = map$1(optionBackup.mediaList, clone$3); + this._mediaDefault = clone$3(optionBackup.mediaDefault); + this._currentMediaIndices = []; + + return clone$3(isRecreate + // this._optionBackup.baseOption, which is created at the first `setOption` + // called, and is merged into every new option by inner method `mergeOption` + // each time `setOption` called, can be only used in `isRecreate`, because + // its reliability is under suspicion. In other cases option merge is + // performed by `model.mergeOption`. + ? optionBackup.baseOption : this._newBaseOption + ); + }, + + /** + * @param {module:echarts/model/Global} ecModel + * @return {Object} + */ + getTimelineOption: function (ecModel) { + var option; + var timelineOptions = this._timelineOptions; + + if (timelineOptions.length) { + // getTimelineOption can only be called after ecModel inited, + // so we can get currentIndex from timelineModel. + var timelineModel = ecModel.getComponent('timeline'); + if (timelineModel) { + option = clone$3( + timelineOptions[timelineModel.getCurrentIndex()], + true + ); + } + } + + return option; + }, + + /** + * @param {module:echarts/model/Global} ecModel + * @return {Array.} + */ + getMediaOption: function (ecModel) { + var ecWidth = this._api.getWidth(); + var ecHeight = this._api.getHeight(); + var mediaList = this._mediaList; + var mediaDefault = this._mediaDefault; + var indices = []; + var result = []; + + // No media defined. + if (!mediaList.length && !mediaDefault) { + return result; + } + + // Multi media may be applied, the latter defined media has higher priority. + for (var i = 0, len = mediaList.length; i < len; i++) { + if (applyMediaQuery(mediaList[i].query, ecWidth, ecHeight)) { + indices.push(i); + } + } + + // FIXME + // 是否mediaDefault应该强制用户设置,否则可能修改不能回归。 + if (!indices.length && mediaDefault) { + indices = [-1]; + } + + if (indices.length && !indicesEquals(indices, this._currentMediaIndices)) { + result = map$1(indices, function (index) { + return clone$3( + index === -1 ? mediaDefault.option : mediaList[index].option + ); + }); + } + // Otherwise return nothing. + + this._currentMediaIndices = indices; + + return result; + } +}; + +function parseRawOption(rawOption, optionPreprocessorFuncs, isNew) { + var timelineOptions = []; + var mediaList = []; + var mediaDefault; + var baseOption; + + // Compatible with ec2. + var timelineOpt = rawOption.timeline; + + if (rawOption.baseOption) { + baseOption = rawOption.baseOption; + } + + // For timeline + if (timelineOpt || rawOption.options) { + baseOption = baseOption || {}; + timelineOptions = (rawOption.options || []).slice(); + } + + // For media query + if (rawOption.media) { + baseOption = baseOption || {}; + var media = rawOption.media; + each$4(media, function (singleMedia) { + if (singleMedia && singleMedia.option) { + if (singleMedia.query) { + mediaList.push(singleMedia); + } + else if (!mediaDefault) { + // Use the first media default. + mediaDefault = singleMedia; + } + } + }); + } + + // For normal option + if (!baseOption) { + baseOption = rawOption; + } + + // Set timelineOpt to baseOption in ec3, + // which is convenient for merge option. + if (!baseOption.timeline) { + baseOption.timeline = timelineOpt; + } + + // Preprocess. + each$4([baseOption].concat(timelineOptions) + .concat(map(mediaList, function (media) { + return media.option; + })), + function (option) { + each$4(optionPreprocessorFuncs, function (preProcess) { + preProcess(option, isNew); + }); + } + ); + + return { + baseOption: baseOption, + timelineOptions: timelineOptions, + mediaDefault: mediaDefault, + mediaList: mediaList + }; +} + +/** + * @see + * Support: width, height, aspectRatio + * Can use max or min as prefix. + */ +function applyMediaQuery(query, ecWidth, ecHeight) { + var realMap = { + width: ecWidth, + height: ecHeight, + aspectratio: ecWidth / ecHeight // lowser case for convenientce. + }; + + var applicatable = true; + + each$1(query, function (value, attr) { + var matched = attr.match(QUERY_REG); + + if (!matched || !matched[1] || !matched[2]) { + return; + } + + var operator = matched[1]; + var realAttr = matched[2].toLowerCase(); + + if (!compare(realMap[realAttr], value, operator)) { + applicatable = false; + } + }); + + return applicatable; +} + +function compare(real, expect, operator) { + if (operator === 'min') { + return real >= expect; + } + else if (operator === 'max') { + return real <= expect; + } + else { // Equals + return real === expect; + } +} + +function indicesEquals(indices1, indices2) { + // indices is always order by asc and has only finite number. + return indices1.join(',') === indices2.join(','); +} + +/** + * Consider case: + * `chart.setOption(opt1);` + * Then user do some interaction like dataZoom, dataView changing. + * `chart.setOption(opt2);` + * Then user press 'reset button' in toolbox. + * + * After doing that all of the interaction effects should be reset, the + * chart should be the same as the result of invoke + * `chart.setOption(opt1); chart.setOption(opt2);`. + * + * Although it is not able ensure that + * `chart.setOption(opt1); chart.setOption(opt2);` is equivalents to + * `chart.setOption(merge(opt1, opt2));` exactly, + * this might be the only simple way to implement that feature. + * + * MEMO: We've considered some other approaches: + * 1. Each model handle its self restoration but not uniform treatment. + * (Too complex in logic and error-prone) + * 2. Use a shadow ecModel. (Performace expensive) + */ +function mergeOption(oldOption, newOption) { + newOption = newOption || {}; + + each$4(newOption, function (newCptOpt, mainType) { + if (newCptOpt == null) { + return; + } + + var oldCptOpt = oldOption[mainType]; + + if (!ComponentModel.hasClass(mainType)) { + oldOption[mainType] = merge$1(oldCptOpt, newCptOpt, true); + } + else { + newCptOpt = normalizeToArray(newCptOpt); + oldCptOpt = normalizeToArray(oldCptOpt); + + var mapResult = mappingToExists(oldCptOpt, newCptOpt); + + oldOption[mainType] = map$1(mapResult, function (item) { + return (item.option && item.exist) + ? merge$1(item.exist, item.option, true) + : (item.exist || item.option); + }); + } + }); +} + +/* +* 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. +*/ + +var each$5 = each$1; +var isObject$3 = isObject$1; + +var POSSIBLE_STYLES = [ + 'areaStyle', 'lineStyle', 'nodeStyle', 'linkStyle', + 'chordStyle', 'label', 'labelLine' +]; + +function compatEC2ItemStyle(opt) { + var itemStyleOpt = opt && opt.itemStyle; + if (!itemStyleOpt) { + return; + } + for (var i = 0, len = POSSIBLE_STYLES.length; i < len; i++) { + var styleName = POSSIBLE_STYLES[i]; + var normalItemStyleOpt = itemStyleOpt.normal; + var emphasisItemStyleOpt = itemStyleOpt.emphasis; + if (normalItemStyleOpt && normalItemStyleOpt[styleName]) { + opt[styleName] = opt[styleName] || {}; + if (!opt[styleName].normal) { + opt[styleName].normal = normalItemStyleOpt[styleName]; + } + else { + merge(opt[styleName].normal, normalItemStyleOpt[styleName]); + } + normalItemStyleOpt[styleName] = null; + } + if (emphasisItemStyleOpt && emphasisItemStyleOpt[styleName]) { + opt[styleName] = opt[styleName] || {}; + if (!opt[styleName].emphasis) { + opt[styleName].emphasis = emphasisItemStyleOpt[styleName]; + } + else { + merge(opt[styleName].emphasis, emphasisItemStyleOpt[styleName]); + } + emphasisItemStyleOpt[styleName] = null; + } + } +} + +function convertNormalEmphasis(opt, optType, useExtend) { + if (opt && opt[optType] && (opt[optType].normal || opt[optType].emphasis)) { + var normalOpt = opt[optType].normal; + var emphasisOpt = opt[optType].emphasis; + + if (normalOpt) { + // Timeline controlStyle has other properties besides normal and emphasis + if (useExtend) { + opt[optType].normal = opt[optType].emphasis = null; + defaults(opt[optType], normalOpt); + } + else { + opt[optType] = normalOpt; + } + } + if (emphasisOpt) { + opt.emphasis = opt.emphasis || {}; + opt.emphasis[optType] = emphasisOpt; + } + } +} + +function removeEC3NormalStatus(opt) { + convertNormalEmphasis(opt, 'itemStyle'); + convertNormalEmphasis(opt, 'lineStyle'); + convertNormalEmphasis(opt, 'areaStyle'); + convertNormalEmphasis(opt, 'label'); + convertNormalEmphasis(opt, 'labelLine'); + // treemap + convertNormalEmphasis(opt, 'upperLabel'); + // graph + convertNormalEmphasis(opt, 'edgeLabel'); +} + +function compatTextStyle(opt, propName) { + // Check whether is not object (string\null\undefined ...) + var labelOptSingle = isObject$3(opt) && opt[propName]; + var textStyle = isObject$3(labelOptSingle) && labelOptSingle.textStyle; + if (textStyle) { + for (var i = 0, len = TEXT_STYLE_OPTIONS.length; i < len; i++) { + var propName = TEXT_STYLE_OPTIONS[i]; + if (textStyle.hasOwnProperty(propName)) { + labelOptSingle[propName] = textStyle[propName]; + } + } + } +} + +function compatEC3CommonStyles(opt) { + if (opt) { + removeEC3NormalStatus(opt); + compatTextStyle(opt, 'label'); + opt.emphasis && compatTextStyle(opt.emphasis, 'label'); + } +} + +function processSeries(seriesOpt) { + if (!isObject$3(seriesOpt)) { + return; + } + + compatEC2ItemStyle(seriesOpt); + removeEC3NormalStatus(seriesOpt); + + compatTextStyle(seriesOpt, 'label'); + // treemap + compatTextStyle(seriesOpt, 'upperLabel'); + // graph + compatTextStyle(seriesOpt, 'edgeLabel'); + if (seriesOpt.emphasis) { + compatTextStyle(seriesOpt.emphasis, 'label'); + // treemap + compatTextStyle(seriesOpt.emphasis, 'upperLabel'); + // graph + compatTextStyle(seriesOpt.emphasis, 'edgeLabel'); + } + + var markPoint = seriesOpt.markPoint; + if (markPoint) { + compatEC2ItemStyle(markPoint); + compatEC3CommonStyles(markPoint); + } + + var markLine = seriesOpt.markLine; + if (markLine) { + compatEC2ItemStyle(markLine); + compatEC3CommonStyles(markLine); + } + + var markArea = seriesOpt.markArea; + if (markArea) { + compatEC3CommonStyles(markArea); + } + + var data = seriesOpt.data; + + // Break with ec3: if `setOption` again, there may be no `type` in option, + // then the backward compat based on option type will not be performed. + + if (seriesOpt.type === 'graph') { + data = data || seriesOpt.nodes; + var edgeData = seriesOpt.links || seriesOpt.edges; + if (edgeData && !isTypedArray(edgeData)) { + for (var i = 0; i < edgeData.length; i++) { + compatEC3CommonStyles(edgeData[i]); + } + } + each$1(seriesOpt.categories, function (opt) { + removeEC3NormalStatus(opt); + }); + } + + if (data && !isTypedArray(data)) { + for (var i = 0; i < data.length; i++) { + compatEC3CommonStyles(data[i]); + } + } + + // mark point data + var markPoint = seriesOpt.markPoint; + if (markPoint && markPoint.data) { + var mpData = markPoint.data; + for (var i = 0; i < mpData.length; i++) { + compatEC3CommonStyles(mpData[i]); + } + } + // mark line data + var markLine = seriesOpt.markLine; + if (markLine && markLine.data) { + var mlData = markLine.data; + for (var i = 0; i < mlData.length; i++) { + if (isArray(mlData[i])) { + compatEC3CommonStyles(mlData[i][0]); + compatEC3CommonStyles(mlData[i][1]); + } + else { + compatEC3CommonStyles(mlData[i]); + } + } + } + + // Series + if (seriesOpt.type === 'gauge') { + compatTextStyle(seriesOpt, 'axisLabel'); + compatTextStyle(seriesOpt, 'title'); + compatTextStyle(seriesOpt, 'detail'); + } + else if (seriesOpt.type === 'treemap') { + convertNormalEmphasis(seriesOpt.breadcrumb, 'itemStyle'); + each$1(seriesOpt.levels, function (opt) { + removeEC3NormalStatus(opt); + }); + } + else if (seriesOpt.type === 'tree') { + removeEC3NormalStatus(seriesOpt.leaves); + } + // sunburst starts from ec4, so it does not need to compat levels. +} + +function toArr(o) { + return isArray(o) ? o : o ? [o] : []; +} + +function toObj(o) { + return (isArray(o) ? o[0] : o) || {}; +} + +var compatStyle = function (option, isTheme) { + each$5(toArr(option.series), function (seriesOpt) { + isObject$3(seriesOpt) && processSeries(seriesOpt); + }); + + var axes = ['xAxis', 'yAxis', 'radiusAxis', 'angleAxis', 'singleAxis', 'parallelAxis', 'radar']; + isTheme && axes.push('valueAxis', 'categoryAxis', 'logAxis', 'timeAxis'); + + each$5( + axes, + function (axisName) { + each$5(toArr(option[axisName]), function (axisOpt) { + if (axisOpt) { + compatTextStyle(axisOpt, 'axisLabel'); + compatTextStyle(axisOpt.axisPointer, 'label'); + } + }); + } + ); + + each$5(toArr(option.parallel), function (parallelOpt) { + var parallelAxisDefault = parallelOpt && parallelOpt.parallelAxisDefault; + compatTextStyle(parallelAxisDefault, 'axisLabel'); + compatTextStyle(parallelAxisDefault && parallelAxisDefault.axisPointer, 'label'); + }); + + each$5(toArr(option.calendar), function (calendarOpt) { + convertNormalEmphasis(calendarOpt, 'itemStyle'); + compatTextStyle(calendarOpt, 'dayLabel'); + compatTextStyle(calendarOpt, 'monthLabel'); + compatTextStyle(calendarOpt, 'yearLabel'); + }); + + // radar.name.textStyle + each$5(toArr(option.radar), function (radarOpt) { + compatTextStyle(radarOpt, 'name'); + }); + + each$5(toArr(option.geo), function (geoOpt) { + if (isObject$3(geoOpt)) { + compatEC3CommonStyles(geoOpt); + each$5(toArr(geoOpt.regions), function (regionObj) { + compatEC3CommonStyles(regionObj); + }); + } + }); + + each$5(toArr(option.timeline), function (timelineOpt) { + compatEC3CommonStyles(timelineOpt); + convertNormalEmphasis(timelineOpt, 'label'); + convertNormalEmphasis(timelineOpt, 'itemStyle'); + convertNormalEmphasis(timelineOpt, 'controlStyle', true); + + var data = timelineOpt.data; + isArray(data) && each$1(data, function (item) { + if (isObject$1(item)) { + convertNormalEmphasis(item, 'label'); + convertNormalEmphasis(item, 'itemStyle'); + } + }); + }); + + each$5(toArr(option.toolbox), function (toolboxOpt) { + convertNormalEmphasis(toolboxOpt, 'iconStyle'); + each$5(toolboxOpt.feature, function (featureOpt) { + convertNormalEmphasis(featureOpt, 'iconStyle'); + }); + }); + + compatTextStyle(toObj(option.axisPointer), 'label'); + compatTextStyle(toObj(option.tooltip).axisPointer, 'label'); +}; + +/* +* 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. +*/ + +// Compatitable with 2.0 + +function get(opt, path) { + path = path.split(','); + var obj = opt; + for (var i = 0; i < path.length; i++) { + obj = obj && obj[path[i]]; + if (obj == null) { + break; + } + } + return obj; +} + +function set$1(opt, path, val, overwrite) { + path = path.split(','); + var obj = opt; + var key; + for (var i = 0; i < path.length - 1; i++) { + key = path[i]; + if (obj[key] == null) { + obj[key] = {}; + } + obj = obj[key]; + } + if (overwrite || obj[path[i]] == null) { + obj[path[i]] = val; + } +} + +function compatLayoutProperties(option) { + each$1(LAYOUT_PROPERTIES, function (prop) { + if (prop[0] in option && !(prop[1] in option)) { + option[prop[1]] = option[prop[0]]; + } + }); +} + +var LAYOUT_PROPERTIES = [ + ['x', 'left'], ['y', 'top'], ['x2', 'right'], ['y2', 'bottom'] +]; + +var COMPATITABLE_COMPONENTS = [ + 'grid', 'geo', 'parallel', 'legend', 'toolbox', 'title', 'visualMap', 'dataZoom', 'timeline' +]; + +var backwardCompat = function (option, isTheme) { + compatStyle(option, isTheme); + + // Make sure series array for model initialization. + option.series = normalizeToArray(option.series); + + each$1(option.series, function (seriesOpt) { + if (!isObject$1(seriesOpt)) { + return; + } + + var seriesType = seriesOpt.type; + + if (seriesType === 'line') { + if (seriesOpt.clipOverflow != null) { + seriesOpt.clip = seriesOpt.clipOverflow; + } + } + else if (seriesType === 'pie' || seriesType === 'gauge') { + if (seriesOpt.clockWise != null) { + seriesOpt.clockwise = seriesOpt.clockWise; + } + } + else if (seriesType === 'gauge') { + var pointerColor = get(seriesOpt, 'pointer.color'); + pointerColor != null + && set$1(seriesOpt, 'itemStyle.color', pointerColor); + } + + compatLayoutProperties(seriesOpt); + }); + + // dataRange has changed to visualMap + if (option.dataRange) { + option.visualMap = option.dataRange; + } + + each$1(COMPATITABLE_COMPONENTS, function (componentName) { + var options = option[componentName]; + if (options) { + if (!isArray(options)) { + options = [options]; + } + each$1(options, function (option) { + compatLayoutProperties(option); + }); + } + }); +}; + +/* +* 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. +*/ + +// (1) [Caution]: the logic is correct based on the premises: +// data processing stage is blocked in stream. +// See +// (2) Only register once when import repeatly. +// Should be executed after series filtered and before stack calculation. +var dataStack = function (ecModel) { + var stackInfoMap = createHashMap(); + ecModel.eachSeries(function (seriesModel) { + var stack = seriesModel.get('stack'); + // Compatibal: when `stack` is set as '', do not stack. + if (stack) { + var stackInfoList = stackInfoMap.get(stack) || stackInfoMap.set(stack, []); + var data = seriesModel.getData(); + + var stackInfo = { + // Used for calculate axis extent automatically. + stackResultDimension: data.getCalculationInfo('stackResultDimension'), + stackedOverDimension: data.getCalculationInfo('stackedOverDimension'), + stackedDimension: data.getCalculationInfo('stackedDimension'), + stackedByDimension: data.getCalculationInfo('stackedByDimension'), + isStackedByIndex: data.getCalculationInfo('isStackedByIndex'), + data: data, + seriesModel: seriesModel + }; + + // If stacked on axis that do not support data stack. + if (!stackInfo.stackedDimension + || !(stackInfo.isStackedByIndex || stackInfo.stackedByDimension) + ) { + return; + } + + stackInfoList.length && data.setCalculationInfo( + 'stackedOnSeries', stackInfoList[stackInfoList.length - 1].seriesModel + ); + + stackInfoList.push(stackInfo); + } + }); + + stackInfoMap.each(calculateStack); +}; + +function calculateStack(stackInfoList) { + each$1(stackInfoList, function (targetStackInfo, idxInStack) { + var resultVal = []; + var resultNaN = [NaN, NaN]; + var dims = [targetStackInfo.stackResultDimension, targetStackInfo.stackedOverDimension]; + var targetData = targetStackInfo.data; + var isStackedByIndex = targetStackInfo.isStackedByIndex; + + // Should not write on raw data, because stack series model list changes + // depending on legend selection. + var newData = targetData.map(dims, function (v0, v1, dataIndex) { + var sum = targetData.get(targetStackInfo.stackedDimension, dataIndex); + + // Consider `connectNulls` of line area, if value is NaN, stackedOver + // should also be NaN, to draw a appropriate belt area. + if (isNaN(sum)) { + return resultNaN; + } + + var byValue; + var stackedDataRawIndex; + + if (isStackedByIndex) { + stackedDataRawIndex = targetData.getRawIndex(dataIndex); + } + else { + byValue = targetData.get(targetStackInfo.stackedByDimension, dataIndex); + } + + // If stackOver is NaN, chart view will render point on value start. + var stackedOver = NaN; + + for (var j = idxInStack - 1; j >= 0; j--) { + var stackInfo = stackInfoList[j]; + + // Has been optimized by inverted indices on `stackedByDimension`. + if (!isStackedByIndex) { + stackedDataRawIndex = stackInfo.data.rawIndexOf(stackInfo.stackedByDimension, byValue); + } + + if (stackedDataRawIndex >= 0) { + var val = stackInfo.data.getByRawIndex(stackInfo.stackResultDimension, stackedDataRawIndex); + + // Considering positive stack, negative stack and empty data + if ((sum >= 0 && val > 0) // Positive stack + || (sum <= 0 && val < 0) // Negative stack + ) { + sum += val; + stackedOver = val; + break; + } + } + } + + resultVal[0] = sum; + resultVal[1] = stackedOver; + + return resultVal; + }); + + targetData.hostModel.setData(newData); + // Update for consequent calculation + targetStackInfo.data = newData; + }); +} + +/* +* 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. +*/ + +// TODO +// ??? refactor? check the outer usage of data provider. +// merge with defaultDimValueGetter? + +/** + * If normal array used, mutable chunk size is supported. + * If typed array used, chunk size must be fixed. + */ +function DefaultDataProvider(source, dimSize) { + if (!Source.isInstance(source)) { + source = Source.seriesDataToSource(source); + } + this._source = source; + + var data = this._data = source.data; + var sourceFormat = source.sourceFormat; + + // Typed array. TODO IE10+? + if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { + if (__DEV__) { + if (dimSize == null) { + throw new Error('Typed array data must specify dimension size'); + } + } + this._offset = 0; + this._dimSize = dimSize; + this._data = data; + } + + var methods = providerMethods[ + sourceFormat === SOURCE_FORMAT_ARRAY_ROWS + ? sourceFormat + '_' + source.seriesLayoutBy + : sourceFormat + ]; + + if (__DEV__) { + assert$1(methods, 'Invalide sourceFormat: ' + sourceFormat); + } + + extend(this, methods); +} + +var providerProto = DefaultDataProvider.prototype; +// If data is pure without style configuration +providerProto.pure = false; +// If data is persistent and will not be released after use. +providerProto.persistent = true; + +// ???! FIXME legacy data provider do not has method getSource +providerProto.getSource = function () { + return this._source; +}; + +var providerMethods = { + + 'arrayRows_column': { + pure: true, + count: function () { + return Math.max(0, this._data.length - this._source.startIndex); + }, + getItem: function (idx) { + return this._data[idx + this._source.startIndex]; + }, + appendData: appendDataSimply + }, + + 'arrayRows_row': { + pure: true, + count: function () { + var row = this._data[0]; + return row ? Math.max(0, row.length - this._source.startIndex) : 0; + }, + getItem: function (idx) { + idx += this._source.startIndex; + var item = []; + var data = this._data; + for (var i = 0; i < data.length; i++) { + var row = data[i]; + item.push(row ? row[idx] : null); + } + return item; + }, + appendData: function () { + throw new Error('Do not support appendData when set seriesLayoutBy: "row".'); + } + }, + + 'objectRows': { + pure: true, + count: countSimply, + getItem: getItemSimply, + appendData: appendDataSimply + }, + + 'keyedColumns': { + pure: true, + count: function () { + var dimName = this._source.dimensionsDefine[0].name; + var col = this._data[dimName]; + return col ? col.length : 0; + }, + getItem: function (idx) { + var item = []; + var dims = this._source.dimensionsDefine; + for (var i = 0; i < dims.length; i++) { + var col = this._data[dims[i].name]; + item.push(col ? col[idx] : null); + } + return item; + }, + appendData: function (newData) { + var data = this._data; + each$1(newData, function (newCol, key) { + var oldCol = data[key] || (data[key] = []); + for (var i = 0; i < (newCol || []).length; i++) { + oldCol.push(newCol[i]); + } + }); + } + }, + + 'original': { + count: countSimply, + getItem: getItemSimply, + appendData: appendDataSimply + }, + + 'typedArray': { + persistent: false, + pure: true, + count: function () { + return this._data ? (this._data.length / this._dimSize) : 0; + }, + getItem: function (idx, out) { + idx = idx - this._offset; + out = out || []; + var offset = this._dimSize * idx; + for (var i = 0; i < this._dimSize; i++) { + out[i] = this._data[offset + i]; + } + return out; + }, + appendData: function (newData) { + if (__DEV__) { + assert$1( + isTypedArray(newData), + 'Added data must be TypedArray if data in initialization is TypedArray' + ); + } + + this._data = newData; + }, + + // Clean self if data is already used. + clean: function () { + // PENDING + this._offset += this.count(); + this._data = null; + } + } +}; + +function countSimply() { + return this._data.length; +} +function getItemSimply(idx) { + return this._data[idx]; +} +function appendDataSimply(newData) { + for (var i = 0; i < newData.length; i++) { + this._data.push(newData[i]); + } +} + + + +var rawValueGetters = { + + arrayRows: getRawValueSimply, + + objectRows: function (dataItem, dataIndex, dimIndex, dimName) { + return dimIndex != null ? dataItem[dimName] : dataItem; + }, + + keyedColumns: getRawValueSimply, + + original: function (dataItem, dataIndex, dimIndex, dimName) { + // FIXME + // In some case (markpoint in geo (geo-map.html)), dataItem + // is {coord: [...]} + var value = getDataItemValue(dataItem); + return (dimIndex == null || !(value instanceof Array)) + ? value + : value[dimIndex]; + }, + + typedArray: getRawValueSimply +}; + +function getRawValueSimply(dataItem, dataIndex, dimIndex, dimName) { + return dimIndex != null ? dataItem[dimIndex] : dataItem; +} + + +var defaultDimValueGetters = { + + arrayRows: getDimValueSimply, + + objectRows: function (dataItem, dimName, dataIndex, dimIndex) { + return converDataValue(dataItem[dimName], this._dimensionInfos[dimName]); + }, + + keyedColumns: getDimValueSimply, + + original: function (dataItem, dimName, dataIndex, dimIndex) { + // Performance sensitive, do not use modelUtil.getDataItemValue. + // If dataItem is an plain object with no value field, the var `value` + // will be assigned with the object, but it will be tread correctly + // in the `convertDataValue`. + var value = dataItem && (dataItem.value == null ? dataItem : dataItem.value); + + // If any dataItem is like { value: 10 } + if (!this._rawData.pure && isDataItemOption(dataItem)) { + this.hasItemOption = true; + } + return converDataValue( + (value instanceof Array) + ? value[dimIndex] + // If value is a single number or something else not array. + : value, + this._dimensionInfos[dimName] + ); + }, + + typedArray: function (dataItem, dimName, dataIndex, dimIndex) { + return dataItem[dimIndex]; + } + +}; + +function getDimValueSimply(dataItem, dimName, dataIndex, dimIndex) { + return converDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]); +} + +/** + * This helper method convert value in data. + * @param {string|number|Date} value + * @param {Object|string} [dimInfo] If string (like 'x'), dimType defaults 'number'. + * If "dimInfo.ordinalParseAndSave", ordinal value can be parsed. + */ +function converDataValue(value, dimInfo) { + // Performance sensitive. + var dimType = dimInfo && dimInfo.type; + if (dimType === 'ordinal') { + // If given value is a category string + var ordinalMeta = dimInfo && dimInfo.ordinalMeta; + return ordinalMeta + ? ordinalMeta.parseAndCollect(value) + : value; + } + + if (dimType === 'time' + // spead up when using timestamp + && typeof value !== 'number' + && value != null + && value !== '-' + ) { + value = +parseDate(value); + } + + // dimType defaults 'number'. + // If dimType is not ordinal and value is null or undefined or NaN or '-', + // parse to NaN. + return (value == null || value === '') + ? NaN + // If string (like '-'), using '+' parse to NaN + // If object, also parse to NaN + : +value; +} + +// ??? FIXME can these logic be more neat: getRawValue, getRawDataItem, +// Consider persistent. +// Caution: why use raw value to display on label or tooltip? +// A reason is to avoid format. For example time value we do not know +// how to format is expected. More over, if stack is used, calculated +// value may be 0.91000000001, which have brings trouble to display. +// TODO: consider how to treat null/undefined/NaN when display? +/** + * @param {module:echarts/data/List} data + * @param {number} dataIndex + * @param {string|number} [dim] dimName or dimIndex + * @return {Array.|string|number} can be null/undefined. + */ +function retrieveRawValue(data, dataIndex, dim) { + if (!data) { + return; + } + + // Consider data may be not persistent. + var dataItem = data.getRawDataItem(dataIndex); + + if (dataItem == null) { + return; + } + + var sourceFormat = data.getProvider().getSource().sourceFormat; + var dimName; + var dimIndex; + + var dimInfo = data.getDimensionInfo(dim); + if (dimInfo) { + dimName = dimInfo.name; + dimIndex = dimInfo.index; + } + + return rawValueGetters[sourceFormat](dataItem, dataIndex, dimIndex, dimName); +} + +/** + * Compatible with some cases (in pie, map) like: + * data: [{name: 'xx', value: 5, selected: true}, ...] + * where only sourceFormat is 'original' and 'objectRows' supported. + * + * ??? TODO + * Supported detail options in data item when using 'arrayRows'. + * + * @param {module:echarts/data/List} data + * @param {number} dataIndex + * @param {string} attr like 'selected' + */ +function retrieveRawAttr(data, dataIndex, attr) { + if (!data) { + return; + } + + var sourceFormat = data.getProvider().getSource().sourceFormat; + + if (sourceFormat !== SOURCE_FORMAT_ORIGINAL + && sourceFormat !== SOURCE_FORMAT_OBJECT_ROWS + ) { + return; + } + + var dataItem = data.getRawDataItem(dataIndex); + if (sourceFormat === SOURCE_FORMAT_ORIGINAL && !isObject$1(dataItem)) { + dataItem = null; + } + if (dataItem) { + return dataItem[attr]; + } +} + +/* +* 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. +*/ + +var DIMENSION_LABEL_REG = /\{@(.+?)\}/g; + +// PENDING A little ugly +var dataFormatMixin = { + /** + * Get params for formatter + * @param {number} dataIndex + * @param {string} [dataType] + * @return {Object} + */ + getDataParams: function (dataIndex, dataType) { + var data = this.getData(dataType); + var rawValue = this.getRawValue(dataIndex, dataType); + var rawDataIndex = data.getRawIndex(dataIndex); + var name = data.getName(dataIndex); + var itemOpt = data.getRawDataItem(dataIndex); + var color = data.getItemVisual(dataIndex, 'color'); + var borderColor = data.getItemVisual(dataIndex, 'borderColor'); + var tooltipModel = this.ecModel.getComponent('tooltip'); + var renderModeOption = tooltipModel && tooltipModel.get('renderMode'); + var renderMode = getTooltipRenderMode(renderModeOption); + var mainType = this.mainType; + var isSeries = mainType === 'series'; + var userOutput = data.userOutput; + + return { + componentType: mainType, + componentSubType: this.subType, + componentIndex: this.componentIndex, + seriesType: isSeries ? this.subType : null, + seriesIndex: this.seriesIndex, + seriesId: isSeries ? this.id : null, + seriesName: isSeries ? this.name : null, + name: name, + dataIndex: rawDataIndex, + data: itemOpt, + dataType: dataType, + value: rawValue, + color: color, + borderColor: borderColor, + dimensionNames: userOutput ? userOutput.dimensionNames : null, + encode: userOutput ? userOutput.encode : null, + marker: getTooltipMarker({ + color: color, + renderMode: renderMode + }), + + // Param name list for mapping `a`, `b`, `c`, `d`, `e` + $vars: ['seriesName', 'name', 'value'] + }; + }, + + /** + * Format label + * @param {number} dataIndex + * @param {string} [status='normal'] 'normal' or 'emphasis' + * @param {string} [dataType] + * @param {number} [dimIndex] Only used in some chart that + * use formatter in different dimensions, like radar. + * @param {string} [labelProp='label'] + * @return {string} If not formatter, return null/undefined + */ + getFormattedLabel: function (dataIndex, status, dataType, dimIndex, labelProp) { + status = status || 'normal'; + var data = this.getData(dataType); + var itemModel = data.getItemModel(dataIndex); + + var params = this.getDataParams(dataIndex, dataType); + if (dimIndex != null && (params.value instanceof Array)) { + params.value = params.value[dimIndex]; + } + + var formatter = itemModel.get( + status === 'normal' + ? [labelProp || 'label', 'formatter'] + : [status, labelProp || 'label', 'formatter'] + ); + + if (typeof formatter === 'function') { + params.status = status; + params.dimensionIndex = dimIndex; + return formatter(params); + } + else if (typeof formatter === 'string') { + var str = formatTpl(formatter, params); + + // Support 'aaa{@[3]}bbb{@product}ccc'. + // Do not support '}' in dim name util have to. + return str.replace(DIMENSION_LABEL_REG, function (origin, dim) { + var len = dim.length; + if (dim.charAt(0) === '[' && dim.charAt(len - 1) === ']') { + dim = +dim.slice(1, len - 1); // Also: '[]' => 0 + } + return retrieveRawValue(data, dataIndex, dim); + }); + } + }, + + /** + * Get raw value in option + * @param {number} idx + * @param {string} [dataType] + * @return {Array|number|string} + */ + getRawValue: function (idx, dataType) { + return retrieveRawValue(this.getData(dataType), idx); + }, + + /** + * Should be implemented. + * @param {number} dataIndex + * @param {boolean} [multipleSeries=false] + * @param {number} [dataType] + * @return {string} tooltip string + */ + formatTooltip: function () { + // Empty function + } +}; + +/* +* 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. +*/ + +/** + * @param {Object} define + * @return See the return of `createTask`. + */ +function createTask(define) { + return new Task(define); +} + +/** + * @constructor + * @param {Object} define + * @param {Function} define.reset Custom reset + * @param {Function} [define.plan] Returns 'reset' indicate reset immediately. + * @param {Function} [define.count] count is used to determin data task. + * @param {Function} [define.onDirty] count is used to determin data task. + */ +function Task(define) { + define = define || {}; + + this._reset = define.reset; + this._plan = define.plan; + this._count = define.count; + this._onDirty = define.onDirty; + + this._dirty = true; + + // Context must be specified implicitly, to + // avoid miss update context when model changed. + this.context; +} + +var taskProto = Task.prototype; + +/** + * @param {Object} performArgs + * @param {number} [performArgs.step] Specified step. + * @param {number} [performArgs.skip] Skip customer perform call. + * @param {number} [performArgs.modBy] Sampling window size. + * @param {number} [performArgs.modDataCount] Sampling count. + */ +taskProto.perform = function (performArgs) { + var upTask = this._upstream; + var skip = performArgs && performArgs.skip; + + // TODO some refactor. + // Pull data. Must pull data each time, because context.data + // may be updated by Series.setData. + if (this._dirty && upTask) { + var context = this.context; + context.data = context.outputData = upTask.context.outputData; + } + + if (this.__pipeline) { + this.__pipeline.currentTask = this; + } + + var planResult; + if (this._plan && !skip) { + planResult = this._plan(this.context); + } + + // Support sharding by mod, which changes the render sequence and makes the rendered graphic + // elements uniformed distributed when progress, especially when moving or zooming. + var lastModBy = normalizeModBy(this._modBy); + var lastModDataCount = this._modDataCount || 0; + var modBy = normalizeModBy(performArgs && performArgs.modBy); + var modDataCount = performArgs && performArgs.modDataCount || 0; + if (lastModBy !== modBy || lastModDataCount !== modDataCount) { + planResult = 'reset'; + } + + function normalizeModBy(val) { + !(val >= 1) && (val = 1); // jshint ignore:line + return val; + } + + var forceFirstProgress; + if (this._dirty || planResult === 'reset') { + this._dirty = false; + forceFirstProgress = reset(this, skip); + } + + this._modBy = modBy; + this._modDataCount = modDataCount; + + var step = performArgs && performArgs.step; + + if (upTask) { + + if (__DEV__) { + assert$1(upTask._outputDueEnd != null); + } + this._dueEnd = upTask._outputDueEnd; + } + // DataTask or overallTask + else { + if (__DEV__) { + assert$1(!this._progress || this._count); + } + this._dueEnd = this._count ? this._count(this.context) : Infinity; + } + + // Note: Stubs, that its host overall task let it has progress, has progress. + // If no progress, pass index from upstream to downstream each time plan called. + if (this._progress) { + var start = this._dueIndex; + var end = Math.min( + step != null ? this._dueIndex + step : Infinity, + this._dueEnd + ); + + if (!skip && (forceFirstProgress || start < end)) { + var progress = this._progress; + if (isArray(progress)) { + for (var i = 0; i < progress.length; i++) { + doProgress(this, progress[i], start, end, modBy, modDataCount); + } + } + else { + doProgress(this, progress, start, end, modBy, modDataCount); + } + } + + this._dueIndex = end; + // If no `outputDueEnd`, assume that output data and + // input data is the same, so use `dueIndex` as `outputDueEnd`. + var outputDueEnd = this._settedOutputEnd != null + ? this._settedOutputEnd : end; + + if (__DEV__) { + // ??? Can not rollback. + assert$1(outputDueEnd >= this._outputDueEnd); + } + + this._outputDueEnd = outputDueEnd; + } + else { + // (1) Some overall task has no progress. + // (2) Stubs, that its host overall task do not let it has progress, has no progress. + // This should always be performed so it can be passed to downstream. + this._dueIndex = this._outputDueEnd = this._settedOutputEnd != null + ? this._settedOutputEnd : this._dueEnd; + } + + return this.unfinished(); +}; + +var iterator = (function () { + + var end; + var current; + var modBy; + var modDataCount; + var winCount; + + var it = { + reset: function (s, e, sStep, sCount) { + current = s; + end = e; + + modBy = sStep; + modDataCount = sCount; + winCount = Math.ceil(modDataCount / modBy); + + it.next = (modBy > 1 && modDataCount > 0) ? modNext : sequentialNext; + } + }; + + return it; + + function sequentialNext() { + return current < end ? current++ : null; + } + + function modNext() { + var dataIndex = (current % winCount) * modBy + Math.ceil(current / winCount); + var result = current >= end + ? null + : dataIndex < modDataCount + ? dataIndex + // If modDataCount is smaller than data.count() (consider `appendData` case), + // Use normal linear rendering mode. + : current; + current++; + return result; + } +})(); + +taskProto.dirty = function () { + this._dirty = true; + this._onDirty && this._onDirty(this.context); +}; + +function doProgress(taskIns, progress, start, end, modBy, modDataCount) { + iterator.reset(start, end, modBy, modDataCount); + taskIns._callingProgress = progress; + taskIns._callingProgress({ + start: start, end: end, count: end - start, next: iterator.next + }, taskIns.context); +} + +function reset(taskIns, skip) { + taskIns._dueIndex = taskIns._outputDueEnd = taskIns._dueEnd = 0; + taskIns._settedOutputEnd = null; + + var progress; + var forceFirstProgress; + + if (!skip && taskIns._reset) { + progress = taskIns._reset(taskIns.context); + if (progress && progress.progress) { + forceFirstProgress = progress.forceFirstProgress; + progress = progress.progress; + } + // To simplify no progress checking, array must has item. + if (isArray(progress) && !progress.length) { + progress = null; + } + } + + taskIns._progress = progress; + taskIns._modBy = taskIns._modDataCount = null; + + var downstream = taskIns._downstream; + downstream && downstream.dirty(); + + return forceFirstProgress; +} + +/** + * @return {boolean} + */ +taskProto.unfinished = function () { + return this._progress && this._dueIndex < this._dueEnd; +}; + +/** + * @param {Object} downTask The downstream task. + * @return {Object} The downstream task. + */ +taskProto.pipe = function (downTask) { + if (__DEV__) { + assert$1(downTask && !downTask._disposed && downTask !== this); + } + + // If already downstream, do not dirty downTask. + if (this._downstream !== downTask || this._dirty) { + this._downstream = downTask; + downTask._upstream = this; + downTask.dirty(); + } +}; + +taskProto.dispose = function () { + if (this._disposed) { + return; + } + + this._upstream && (this._upstream._downstream = null); + this._downstream && (this._downstream._upstream = null); + + this._dirty = false; + this._disposed = true; +}; + +taskProto.getUpstream = function () { + return this._upstream; +}; + +taskProto.getDownstream = function () { + return this._downstream; +}; + +taskProto.setOutputEnd = function (end) { + // This only happend in dataTask, dataZoom, map, currently. + // where dataZoom do not set end each time, but only set + // when reset. So we should record the setted end, in case + // that the stub of dataZoom perform again and earse the + // setted end by upstream. + this._outputDueEnd = this._settedOutputEnd = end; +}; + + +/////////////////////////////////////////////////////////// +// For stream debug (Should be commented out after used!) +// Usage: printTask(this, 'begin'); +// Usage: printTask(this, null, {someExtraProp}); +// function printTask(task, prefix, extra) { +// window.ecTaskUID == null && (window.ecTaskUID = 0); +// task.uidDebug == null && (task.uidDebug = `task_${window.ecTaskUID++}`); +// task.agent && task.agent.uidDebug == null && (task.agent.uidDebug = `task_${window.ecTaskUID++}`); +// var props = []; +// if (task.__pipeline) { +// var val = `${task.__idxInPipeline}/${task.__pipeline.tail.__idxInPipeline} ${task.agent ? '(stub)' : ''}`; +// props.push({text: 'idx', value: val}); +// } else { +// var stubCount = 0; +// task.agentStubMap.each(() => stubCount++); +// props.push({text: 'idx', value: `overall (stubs: ${stubCount})`}); +// } +// props.push({text: 'uid', value: task.uidDebug}); +// if (task.__pipeline) { +// props.push({text: 'pid', value: task.__pipeline.id}); +// task.agent && props.push( +// {text: 'stubFor', value: task.agent.uidDebug} +// ); +// } +// props.push( +// {text: 'dirty', value: task._dirty}, +// {text: 'dueIndex', value: task._dueIndex}, +// {text: 'dueEnd', value: task._dueEnd}, +// {text: 'outputDueEnd', value: task._outputDueEnd} +// ); +// if (extra) { +// Object.keys(extra).forEach(key => { +// props.push({text: key, value: extra[key]}); +// }); +// } +// var args = ['color: blue']; +// var msg = `%c[${prefix || 'T'}] %c` + props.map(item => ( +// args.push('color: black', 'color: red'), +// `${item.text}: %c${item.value}` +// )).join('%c, '); +// console.log.apply(console, [msg].concat(args)); +// // console.log(this); +// } + +/* +* 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. +*/ + +var inner$4 = makeInner(); + +var SeriesModel = ComponentModel.extend({ + + type: 'series.__base__', + + /** + * @readOnly + */ + seriesIndex: 0, + + // coodinateSystem will be injected in the echarts/CoordinateSystem + coordinateSystem: null, + + /** + * @type {Object} + * @protected + */ + defaultOption: null, + + /** + * legend visual provider to the legend component + * @type {Object} + */ + // PENDING + legendVisualProvider: null, + + /** + * Access path of color for visual + */ + visualColorAccessPath: 'itemStyle.color', + + /** + * Access path of borderColor for visual + */ + visualBorderColorAccessPath: 'itemStyle.borderColor', + + /** + * Support merge layout params. + * Only support 'box' now (left/right/top/bottom/width/height). + * @type {string|Object} Object can be {ignoreSize: true} + * @readOnly + */ + layoutMode: null, + + init: function (option, parentModel, ecModel, extraOpt) { + + /** + * @type {number} + * @readOnly + */ + this.seriesIndex = this.componentIndex; + + this.dataTask = createTask({ + count: dataTaskCount, + reset: dataTaskReset + }); + this.dataTask.context = {model: this}; + + this.mergeDefaultAndTheme(option, ecModel); + + prepareSource(this); + + + var data = this.getInitialData(option, ecModel); + wrapData(data, this); + this.dataTask.context.data = data; + + if (__DEV__) { + assert$1(data, 'getInitialData returned invalid data.'); + } + + /** + * @type {module:echarts/data/List|module:echarts/data/Tree|module:echarts/data/Graph} + * @private + */ + inner$4(this).dataBeforeProcessed = data; + + // If we reverse the order (make data firstly, and then make + // dataBeforeProcessed by cloneShallow), cloneShallow will + // cause data.graph.data !== data when using + // module:echarts/data/Graph or module:echarts/data/Tree. + // See module:echarts/data/helper/linkList + + // Theoretically, it is unreasonable to call `seriesModel.getData()` in the model + // init or merge stage, because the data can be restored. So we do not `restoreData` + // and `setData` here, which forbids calling `seriesModel.getData()` in this stage. + // Call `seriesModel.getRawData()` instead. + // this.restoreData(); + + autoSeriesName(this); + }, + + /** + * Util for merge default and theme to option + * @param {Object} option + * @param {module:echarts/model/Global} ecModel + */ + mergeDefaultAndTheme: function (option, ecModel) { + var layoutMode = this.layoutMode; + var inputPositionParams = layoutMode + ? getLayoutParams(option) : {}; + + // Backward compat: using subType on theme. + // But if name duplicate between series subType + // (for example: parallel) add component mainType, + // add suffix 'Series'. + var themeSubType = this.subType; + if (ComponentModel.hasClass(themeSubType)) { + themeSubType += 'Series'; + } + merge( + option, + ecModel.getTheme().get(this.subType) + ); + merge(option, this.getDefaultOption()); + + // Default label emphasis `show` + defaultEmphasis(option, 'label', ['show']); + + this.fillDataTextStyle(option.data); + + if (layoutMode) { + mergeLayoutParam(option, inputPositionParams, layoutMode); + } + }, + + mergeOption: function (newSeriesOption, ecModel) { + // this.settingTask.dirty(); + + newSeriesOption = merge(this.option, newSeriesOption, true); + this.fillDataTextStyle(newSeriesOption.data); + + var layoutMode = this.layoutMode; + if (layoutMode) { + mergeLayoutParam(this.option, newSeriesOption, layoutMode); + } + + prepareSource(this); + + var data = this.getInitialData(newSeriesOption, ecModel); + wrapData(data, this); + this.dataTask.dirty(); + this.dataTask.context.data = data; + + inner$4(this).dataBeforeProcessed = data; + + autoSeriesName(this); + }, + + fillDataTextStyle: function (data) { + // Default data label emphasis `show` + // FIXME Tree structure data ? + // FIXME Performance ? + if (data && !isTypedArray(data)) { + var props = ['show']; + for (var i = 0; i < data.length; i++) { + if (data[i] && data[i].label) { + defaultEmphasis(data[i], 'label', props); + } + } + } + }, + + /** + * Init a data structure from data related option in series + * Must be overwritten + */ + getInitialData: function () {}, + + /** + * Append data to list + * @param {Object} params + * @param {Array|TypedArray} params.data + */ + appendData: function (params) { + // FIXME ??? + // (1) If data from dataset, forbidden append. + // (2) support append data of dataset. + var data = this.getRawData(); + data.appendData(params.data); + }, + + /** + * Consider some method like `filter`, `map` need make new data, + * We should make sure that `seriesModel.getData()` get correct + * data in the stream procedure. So we fetch data from upstream + * each time `task.perform` called. + * @param {string} [dataType] + * @return {module:echarts/data/List} + */ + getData: function (dataType) { + var task = getCurrentTask(this); + if (task) { + var data = task.context.data; + return dataType == null ? data : data.getLinkedData(dataType); + } + else { + // When series is not alive (that may happen when click toolbox + // restore or setOption with not merge mode), series data may + // be still need to judge animation or something when graphic + // elements want to know whether fade out. + return inner$4(this).data; + } + }, + + /** + * @param {module:echarts/data/List} data + */ + setData: function (data) { + var task = getCurrentTask(this); + if (task) { + var context = task.context; + // Consider case: filter, data sample. + if (context.data !== data && task.modifyOutputEnd) { + task.setOutputEnd(data.count()); + } + context.outputData = data; + // Caution: setData should update context.data, + // Because getData may be called multiply in a + // single stage and expect to get the data just + // set. (For example, AxisProxy, x y both call + // getData and setDate sequentially). + // So the context.data should be fetched from + // upstream each time when a stage starts to be + // performed. + if (task !== this.dataTask) { + context.data = data; + } + } + inner$4(this).data = data; + }, + + /** + * @see {module:echarts/data/helper/sourceHelper#getSource} + * @return {module:echarts/data/Source} source + */ + getSource: function () { + return getSource(this); + }, + + /** + * Get data before processed + * @return {module:echarts/data/List} + */ + getRawData: function () { + return inner$4(this).dataBeforeProcessed; + }, + + /** + * Get base axis if has coordinate system and has axis. + * By default use coordSys.getBaseAxis(); + * Can be overrided for some chart. + * @return {type} description + */ + getBaseAxis: function () { + var coordSys = this.coordinateSystem; + return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis(); + }, + + // FIXME + /** + * Default tooltip formatter + * + * @param {number} dataIndex + * @param {boolean} [multipleSeries=false] + * @param {number} [dataType] + * @param {string} [renderMode='html'] valid values: 'html' and 'richText'. + * 'html' is used for rendering tooltip in extra DOM form, and the result + * string is used as DOM HTML content. + * 'richText' is used for rendering tooltip in rich text form, for those where + * DOM operation is not supported. + * @return {Object} formatted tooltip with `html` and `markers` + */ + formatTooltip: function (dataIndex, multipleSeries, dataType, renderMode) { + + var series = this; + renderMode = renderMode || 'html'; + var newLine = renderMode === 'html' ? '
    ' : '\n'; + var isRichText = renderMode === 'richText'; + var markers = {}; + var markerId = 0; + + function formatArrayValue(value) { + // ??? TODO refactor these logic. + // check: category-no-encode-has-axis-data in dataset.html + var vertially = reduce(value, function (vertially, val, idx) { + var dimItem = data.getDimensionInfo(idx); + return vertially |= dimItem && dimItem.tooltip !== false && dimItem.displayName != null; + }, 0); + + var result = []; + + tooltipDims.length + ? each$1(tooltipDims, function (dim) { + setEachItem(retrieveRawValue(data, dataIndex, dim), dim); + }) + // By default, all dims is used on tooltip. + : each$1(value, setEachItem); + + function setEachItem(val, dim) { + var dimInfo = data.getDimensionInfo(dim); + // If `dimInfo.tooltip` is not set, show tooltip. + if (!dimInfo || dimInfo.otherDims.tooltip === false) { + return; + } + var dimType = dimInfo.type; + var markName = 'sub' + series.seriesIndex + 'at' + markerId; + var dimHead = getTooltipMarker({ + color: color, + type: 'subItem', + renderMode: renderMode, + markerId: markName + }); + + var dimHeadStr = typeof dimHead === 'string' ? dimHead : dimHead.content; + var valStr = (vertially + ? dimHeadStr + encodeHTML(dimInfo.displayName || '-') + ': ' + : '' + ) + // FIXME should not format time for raw data? + + encodeHTML(dimType === 'ordinal' + ? val + '' + : dimType === 'time' + ? (multipleSeries ? '' : formatTime('yyyy/MM/dd hh:mm:ss', val)) + : addCommas(val) + ); + valStr && result.push(valStr); + + if (isRichText) { + markers[markName] = color; + ++markerId; + } + } + + var newLine = vertially ? (isRichText ? '\n' : '
    ') : ''; + var content = newLine + result.join(newLine || ', '); + return { + renderMode: renderMode, + content: content, + style: markers + }; + } + + function formatSingleValue(val) { + // return encodeHTML(addCommas(val)); + return { + renderMode: renderMode, + content: encodeHTML(addCommas(val)), + style: markers + }; + } + + var data = this.getData(); + var tooltipDims = data.mapDimension('defaultedTooltip', true); + var tooltipDimLen = tooltipDims.length; + var value = this.getRawValue(dataIndex); + var isValueArr = isArray(value); + + var color = data.getItemVisual(dataIndex, 'color'); + if (isObject$1(color) && color.colorStops) { + color = (color.colorStops[0] || {}).color; + } + color = color || 'transparent'; + + // Complicated rule for pretty tooltip. + var formattedValue = (tooltipDimLen > 1 || (isValueArr && !tooltipDimLen)) + ? formatArrayValue(value) + : tooltipDimLen + ? formatSingleValue(retrieveRawValue(data, dataIndex, tooltipDims[0])) + : formatSingleValue(isValueArr ? value[0] : value); + var content = formattedValue.content; + + var markName = series.seriesIndex + 'at' + markerId; + var colorEl = getTooltipMarker({ + color: color, + type: 'item', + renderMode: renderMode, + markerId: markName + }); + markers[markName] = color; + ++markerId; + + var name = data.getName(dataIndex); + + var seriesName = this.name; + if (!isNameSpecified(this)) { + seriesName = ''; + } + seriesName = seriesName + ? encodeHTML(seriesName) + (!multipleSeries ? newLine : ': ') + : ''; + + var colorStr = typeof colorEl === 'string' ? colorEl : colorEl.content; + var html = !multipleSeries + ? seriesName + colorStr + + (name + ? encodeHTML(name) + ': ' + content + : content + ) + : colorStr + seriesName + content; + + return { + html: html, + markers: markers + }; + }, + + /** + * @return {boolean} + */ + isAnimationEnabled: function () { + if (env$1.node) { + return false; + } + + var animationEnabled = this.getShallow('animation'); + if (animationEnabled) { + if (this.getData().count() > this.getShallow('animationThreshold')) { + animationEnabled = false; + } + } + return animationEnabled; + }, + + restoreData: function () { + this.dataTask.dirty(); + }, + + getColorFromPalette: function (name, scope, requestColorNum) { + var ecModel = this.ecModel; + // PENDING + var color = colorPaletteMixin.getColorFromPalette.call(this, name, scope, requestColorNum); + if (!color) { + color = ecModel.getColorFromPalette(name, scope, requestColorNum); + } + return color; + }, + + /** + * Use `data.mapDimension(coordDim, true)` instead. + * @deprecated + */ + coordDimToDataDim: function (coordDim) { + return this.getRawData().mapDimension(coordDim, true); + }, + + /** + * Get progressive rendering count each step + * @return {number} + */ + getProgressive: function () { + return this.get('progressive'); + }, + + /** + * Get progressive rendering count each step + * @return {number} + */ + getProgressiveThreshold: function () { + return this.get('progressiveThreshold'); + }, + + /** + * Get data indices for show tooltip content. See tooltip. + * @abstract + * @param {Array.|string} dim + * @param {Array.} value + * @param {module:echarts/coord/single/SingleAxis} baseAxis + * @return {Object} {dataIndices, nestestValue}. + */ + getAxisTooltipData: null, + + /** + * See tooltip. + * @abstract + * @param {number} dataIndex + * @return {Array.} Point of tooltip. null/undefined can be returned. + */ + getTooltipPosition: null, + + /** + * @see {module:echarts/stream/Scheduler} + */ + pipeTask: null, + + /** + * Convinient for override in extended class. + * @protected + * @type {Function} + */ + preventIncremental: null, + + /** + * @public + * @readOnly + * @type {Object} + */ + pipelineContext: null + +}); + + +mixin(SeriesModel, dataFormatMixin); +mixin(SeriesModel, colorPaletteMixin); + +/** + * MUST be called after `prepareSource` called + * Here we need to make auto series, especially for auto legend. But we + * do not modify series.name in option to avoid side effects. + */ +function autoSeriesName(seriesModel) { + // User specified name has higher priority, otherwise it may cause + // series can not be queried unexpectedly. + var name = seriesModel.name; + if (!isNameSpecified(seriesModel)) { + seriesModel.name = getSeriesAutoName(seriesModel) || name; + } +} + +function getSeriesAutoName(seriesModel) { + var data = seriesModel.getRawData(); + var dataDims = data.mapDimension('seriesName', true); + var nameArr = []; + each$1(dataDims, function (dataDim) { + var dimInfo = data.getDimensionInfo(dataDim); + dimInfo.displayName && nameArr.push(dimInfo.displayName); + }); + return nameArr.join(' '); +} + +function dataTaskCount(context) { + return context.model.getRawData().count(); +} + +function dataTaskReset(context) { + var seriesModel = context.model; + seriesModel.setData(seriesModel.getRawData().cloneShallow()); + return dataTaskProgress; +} + +function dataTaskProgress(param, context) { + // Avoid repead cloneShallow when data just created in reset. + if (param.end > context.outputData.count()) { + context.model.getRawData().cloneShallow(context.outputData); + } +} + +// TODO refactor +function wrapData(data, seriesModel) { + each$1(data.CHANGABLE_METHODS, function (methodName) { + data.wrapMethod(methodName, curry(onDataSelfChange, seriesModel)); + }); +} + +function onDataSelfChange(seriesModel) { + var task = getCurrentTask(seriesModel); + if (task) { + // Consider case: filter, selectRange + task.setOutputEnd(this.count()); + } +} + +function getCurrentTask(seriesModel) { + var scheduler = (seriesModel.ecModel || {}).scheduler; + var pipeline = scheduler && scheduler.getPipeline(seriesModel.uid); + + if (pipeline) { + // When pipline finished, the currrentTask keep the last + // task (renderTask). + var task = pipeline.currentTask; + if (task) { + var agentStubMap = task.agentStubMap; + if (agentStubMap) { + task = agentStubMap.get(seriesModel.uid); + } + } + return task; + } +} + +/* +* 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. +*/ + +var Component$1 = function () { + /** + * @type {module:zrender/container/Group} + * @readOnly + */ + this.group = new Group(); + + /** + * @type {string} + * @readOnly + */ + this.uid = getUID('viewComponent'); +}; + +Component$1.prototype = { + + constructor: Component$1, + + init: function (ecModel, api) {}, + + render: function (componentModel, ecModel, api, payload) {}, + + dispose: function () {}, + + /** + * @param {string} eventType + * @param {Object} query + * @param {module:zrender/Element} targetEl + * @param {Object} packedEvent + * @return {boolen} Pass only when return `true`. + */ + filterForExposedEvent: null + +}; + +var componentProto = Component$1.prototype; +componentProto.updateView = + componentProto.updateLayout = + componentProto.updateVisual = + function (seriesModel, ecModel, api, payload) { + // Do nothing; + }; +// Enable Component.extend. +enableClassExtend(Component$1); + +// Enable capability of registerClass, getClass, hasClass, registerSubTypeDefaulter and so on. +enableClassManagement(Component$1, {registerWhenExtend: true}); + +/* +* 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. +*/ + +/** + * @return {string} If large mode changed, return string 'reset'; + */ +var createRenderPlanner = function () { + var inner = makeInner(); + + return function (seriesModel) { + var fields = inner(seriesModel); + var pipelineContext = seriesModel.pipelineContext; + + var originalLarge = fields.large; + var originalProgressive = fields.progressiveRender; + + var large = fields.large = pipelineContext.large; + var progressive = fields.progressiveRender = pipelineContext.progressiveRender; + + return !!((originalLarge ^ large) || (originalProgressive ^ progressive)) && 'reset'; + }; +}; + +/* +* 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. +*/ + +var inner$5 = makeInner(); +var renderPlanner = createRenderPlanner(); + +function Chart() { + + /** + * @type {module:zrender/container/Group} + * @readOnly + */ + this.group = new Group(); + + /** + * @type {string} + * @readOnly + */ + this.uid = getUID('viewChart'); + + this.renderTask = createTask({ + plan: renderTaskPlan, + reset: renderTaskReset + }); + this.renderTask.context = {view: this}; +} + +Chart.prototype = { + + type: 'chart', + + /** + * Init the chart. + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ + init: function (ecModel, api) {}, + + /** + * Render the chart. + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @param {Object} payload + */ + render: function (seriesModel, ecModel, api, payload) {}, + + /** + * Highlight series or specified data item. + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @param {Object} payload + */ + highlight: function (seriesModel, ecModel, api, payload) { + toggleHighlight(seriesModel.getData(), payload, 'emphasis'); + }, + + /** + * Downplay series or specified data item. + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @param {Object} payload + */ + downplay: function (seriesModel, ecModel, api, payload) { + toggleHighlight(seriesModel.getData(), payload, 'normal'); + }, + + /** + * Remove self. + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ + remove: function (ecModel, api) { + this.group.removeAll(); + }, + + /** + * Dispose self. + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ + dispose: function () {}, + + /** + * Rendering preparation in progressive mode. + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @param {Object} payload + */ + incrementalPrepareRender: null, + + /** + * 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 + * @param {Object} payload + */ + incrementalRender: null, + + /** + * Update transform directly. + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @param {Object} payload + * @return {Object} {update: true} + */ + updateTransform: null, + + /** + * The view contains the given point. + * @interface + * @param {Array.} point + * @return {boolean} + */ + // containPoint: function () {} + + /** + * @param {string} eventType + * @param {Object} query + * @param {module:zrender/Element} targetEl + * @param {Object} packedEvent + * @return {boolen} Pass only when return `true`. + */ + filterForExposedEvent: null + +}; + +var chartProto = Chart.prototype; +chartProto.updateView = +chartProto.updateLayout = +chartProto.updateVisual = + function (seriesModel, ecModel, api, payload) { + this.render(seriesModel, ecModel, api, payload); + }; + +/** + * Set state of single element + * @param {module:zrender/Element} el + * @param {string} state 'normal'|'emphasis' + * @param {number} highlightDigit + */ +function elSetState(el, state, highlightDigit) { + if (el) { + el.trigger(state, highlightDigit); + if (el.isGroup + // Simple optimize. + && !isHighDownDispatcher(el) + ) { + for (var i = 0, len = el.childCount(); i < len; i++) { + elSetState(el.childAt(i), state, highlightDigit); + } + } + } +} + +/** + * @param {module:echarts/data/List} data + * @param {Object} payload + * @param {string} state 'normal'|'emphasis' + */ +function toggleHighlight(data, payload, state) { + var dataIndex = queryDataIndex(data, payload); + + var highlightDigit = (payload && payload.highlightKey != null) + ? getHighlightDigit(payload.highlightKey) + : null; + + if (dataIndex != null) { + each$1(normalizeToArray(dataIndex), function (dataIdx) { + elSetState(data.getItemGraphicEl(dataIdx), state, highlightDigit); + }); + } + else { + data.eachItemGraphicEl(function (el) { + elSetState(el, state, highlightDigit); + }); + } +} + +// Enable Chart.extend. +enableClassExtend(Chart, ['dispose']); + +// Add capability of registerClass, getClass, hasClass, registerSubTypeDefaulter and so on. +enableClassManagement(Chart, {registerWhenExtend: true}); + +Chart.markUpdateMethod = function (payload, methodName) { + inner$5(payload).updateMethod = methodName; +}; + +function renderTaskPlan(context) { + return renderPlanner(context.model); +} + +function renderTaskReset(context) { + var seriesModel = context.model; + var ecModel = context.ecModel; + var api = context.api; + var payload = context.payload; + // ???! remove updateView updateVisual + var progressiveRender = seriesModel.pipelineContext.progressiveRender; + var view = context.view; + + var updateMethod = payload && inner$5(payload).updateMethod; + var methodName = progressiveRender + ? 'incrementalPrepareRender' + : (updateMethod && view[updateMethod]) + ? updateMethod + // `appendData` is also supported when data amount + // is less than progressive threshold. + : 'render'; + + if (methodName !== 'render') { + view[methodName](seriesModel, ecModel, api, payload); + } + + return progressMethodMap[methodName]; +} + +var progressMethodMap = { + incrementalPrepareRender: { + progress: function (params, context) { + context.view.incrementalRender( + params, context.model, context.ecModel, context.api, context.payload + ); + } + }, + render: { + // Put view.render in `progress` to support appendData. But in this case + // view.render should not be called in reset, otherwise it will be called + // twise. Use `forceFirstProgress` to make sure that view.render is called + // in any cases. + forceFirstProgress: true, + progress: function (params, context) { + context.view.render( + context.model, context.ecModel, context.api, context.payload + ); + } + } +}; + +/* +* 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. +*/ + + +var ORIGIN_METHOD = '\0__throttleOriginMethod'; +var RATE = '\0__throttleRate'; +var THROTTLE_TYPE = '\0__throttleType'; + +/** + * @public + * @param {(Function)} fn + * @param {number} [delay=0] Unit: ms. + * @param {boolean} [debounce=false] + * true: If call interval less than `delay`, only the last call works. + * false: If call interval less than `delay, call works on fixed rate. + * @return {(Function)} throttled fn. + */ +function throttle(fn, delay, debounce) { + + var currCall; + var lastCall = 0; + var lastExec = 0; + var timer = null; + var diff; + var scope; + var args; + var debounceNextCall; + + delay = delay || 0; + + function exec() { + lastExec = (new Date()).getTime(); + timer = null; + fn.apply(scope, args || []); + } + + var cb = function () { + currCall = (new Date()).getTime(); + scope = this; + args = arguments; + var thisDelay = debounceNextCall || delay; + var thisDebounce = debounceNextCall || debounce; + debounceNextCall = null; + diff = currCall - (thisDebounce ? lastCall : lastExec) - thisDelay; + + clearTimeout(timer); + + // Here we should make sure that: the `exec` SHOULD NOT be called later + // than a new call of `cb`, that is, preserving the command order. Consider + // calculating "scale rate" when roaming as an example. When a call of `cb` + // happens, either the `exec` is called dierectly, or the call is delayed. + // But the delayed call should never be later than next call of `cb`. Under + // this assurance, we can simply update view state each time `dispatchAction` + // triggered by user roaming, but not need to add extra code to avoid the + // state being "rolled-back". + if (thisDebounce) { + timer = setTimeout(exec, thisDelay); + } + else { + if (diff >= 0) { + exec(); + } + else { + timer = setTimeout(exec, -diff); + } + } + + lastCall = currCall; + }; + + /** + * Clear throttle. + * @public + */ + cb.clear = function () { + if (timer) { + clearTimeout(timer); + timer = null; + } + }; + + /** + * Enable debounce once. + */ + cb.debounceNextCall = function (debounceDelay) { + debounceNextCall = debounceDelay; + }; + + return cb; +} + +/** + * Create throttle method or update throttle rate. + * + * @example + * ComponentView.prototype.render = function () { + * ... + * throttle.createOrUpdate( + * this, + * '_dispatchAction', + * this.model.get('throttle'), + * 'fixRate' + * ); + * }; + * ComponentView.prototype.remove = function () { + * throttle.clear(this, '_dispatchAction'); + * }; + * ComponentView.prototype.dispose = function () { + * throttle.clear(this, '_dispatchAction'); + * }; + * + * @public + * @param {Object} obj + * @param {string} fnAttr + * @param {number} [rate] + * @param {string} [throttleType='fixRate'] 'fixRate' or 'debounce' + * @return {Function} throttled function. + */ +function createOrUpdate(obj, fnAttr, rate, throttleType) { + var fn = obj[fnAttr]; + + if (!fn) { + return; + } + + var originFn = fn[ORIGIN_METHOD] || fn; + var lastThrottleType = fn[THROTTLE_TYPE]; + var lastRate = fn[RATE]; + + if (lastRate !== rate || lastThrottleType !== throttleType) { + if (rate == null || !throttleType) { + return (obj[fnAttr] = originFn); + } + + fn = obj[fnAttr] = throttle( + originFn, rate, throttleType === 'debounce' + ); + fn[ORIGIN_METHOD] = originFn; + fn[THROTTLE_TYPE] = throttleType; + fn[RATE] = rate; + } + + return fn; +} + +/** + * Clear throttle. Example see throttle.createOrUpdate. + * + * @public + * @param {Object} obj + * @param {string} fnAttr + */ +function clear(obj, fnAttr) { + var fn = obj[fnAttr]; + if (fn && fn[ORIGIN_METHOD]) { + obj[fnAttr] = fn[ORIGIN_METHOD]; + } +} + +/* +* 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. +*/ + +var seriesColor = { + createOnAllSeries: true, + performRawSeries: true, + reset: function (seriesModel, ecModel) { + var data = seriesModel.getData(); + var colorAccessPath = (seriesModel.visualColorAccessPath || 'itemStyle.color').split('.'); + // Set in itemStyle + var color = seriesModel.get(colorAccessPath); + var colorCallback = (isFunction$1(color) && !(color instanceof Gradient)) + ? color : null; + // Default color + if (!color || colorCallback) { + color = seriesModel.getColorFromPalette( + // TODO series count changed. + seriesModel.name, null, ecModel.getSeriesCount() + ); + } + + data.setVisual('color', color); + + var borderColorAccessPath = (seriesModel.visualBorderColorAccessPath || 'itemStyle.borderColor').split('.'); + var borderColor = seriesModel.get(borderColorAccessPath); + data.setVisual('borderColor', borderColor); + + // Only visible series has each data be visual encoded + if (!ecModel.isSeriesFiltered(seriesModel)) { + if (colorCallback) { + data.each(function (idx) { + data.setItemVisual( + idx, 'color', colorCallback(seriesModel.getDataParams(idx)) + ); + }); + } + + // itemStyle in each data item + var dataEach = function (data, idx) { + var itemModel = data.getItemModel(idx); + var color = itemModel.get(colorAccessPath, true); + var borderColor = itemModel.get(borderColorAccessPath, true); + if (color != null) { + data.setItemVisual(idx, 'color', color); + } + if (borderColor != null) { + data.setItemVisual(idx, 'borderColor', borderColor); + } + }; + + return { dataEach: data.hasItemOption ? dataEach : null }; + } + } +}; + +/* +* 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. +*/ + +/** + * Language: (Simplified) Chinese. + */ + +var lang = { + legend: { + selector: { + all: '全选', + inverse: '反选' + } + }, + toolbox: { + brush: { + title: { + rect: '矩形选择', + polygon: '圈选', + lineX: '横向选择', + lineY: '纵向选择', + keep: '保持选择', + clear: '清除选择' + } + }, + dataView: { + title: '数据视图', + lang: ['数据视图', '关闭', '刷新'] + }, + dataZoom: { + title: { + zoom: '区域缩放', + back: '区域缩放还原' + } + }, + magicType: { + title: { + line: '切换为折线图', + bar: '切换为柱状图', + stack: '切换为堆叠', + tiled: '切换为平铺' + } + }, + restore: { + title: '还原' + }, + saveAsImage: { + title: '保存为图片', + lang: ['右键另存为图片'] + } + }, + series: { + typeNames: { + pie: '饼图', + bar: '柱状图', + line: '折线图', + scatter: '散点图', + effectScatter: '涟漪散点图', + radar: '雷达图', + tree: '树图', + treemap: '矩形树图', + boxplot: '箱型图', + candlestick: 'K线图', + k: 'K线图', + heatmap: '热力图', + map: '地图', + parallel: '平行坐标图', + lines: '线图', + graph: '关系图', + sankey: '桑基图', + funnel: '漏斗图', + gauge: '仪表盘图', + pictorialBar: '象形柱图', + themeRiver: '主题河流图', + sunburst: '旭日图' + } + }, + aria: { + general: { + withTitle: '这是一个关于“{title}”的图表。', + withoutTitle: '这是一个图表,' + }, + series: { + single: { + prefix: '', + withName: '图表类型是{seriesType},表示{seriesName}。', + withoutName: '图表类型是{seriesType}。' + }, + multiple: { + prefix: '它由{seriesCount}个图表系列组成。', + withName: '第{seriesId}个系列是一个表示{seriesName}的{seriesType},', + withoutName: '第{seriesId}个系列是一个{seriesType},', + separator: { + middle: ';', + end: '。' + } + } + }, + data: { + allData: '其数据是——', + partialData: '其中,前{displayCnt}项是——', + withName: '{name}的数据是{value}', + withoutName: '{value}', + separator: { + middle: ',', + end: '' + } + } + } +}; + +/* +* 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. +*/ + +var aria = function (dom, ecModel) { + var ariaModel = ecModel.getModel('aria'); + if (!ariaModel.get('show')) { + return; + } + else if (ariaModel.get('description')) { + dom.setAttribute('aria-label', ariaModel.get('description')); + return; + } + + var seriesCnt = 0; + ecModel.eachSeries(function (seriesModel, idx) { + ++seriesCnt; + }, this); + + var maxDataCnt = ariaModel.get('data.maxCount') || 10; + var maxSeriesCnt = ariaModel.get('series.maxCount') || 10; + var displaySeriesCnt = Math.min(seriesCnt, maxSeriesCnt); + + var ariaLabel; + if (seriesCnt < 1) { + // No series, no aria label + return; + } + else { + var title = getTitle(); + if (title) { + ariaLabel = replace(getConfig('general.withTitle'), { + title: title + }); + } + else { + ariaLabel = getConfig('general.withoutTitle'); + } + + var seriesLabels = []; + var prefix = seriesCnt > 1 + ? 'series.multiple.prefix' + : 'series.single.prefix'; + ariaLabel += replace(getConfig(prefix), { seriesCount: seriesCnt }); + + ecModel.eachSeries(function (seriesModel, idx) { + if (idx < displaySeriesCnt) { + var seriesLabel; + + var seriesName = seriesModel.get('name'); + var seriesTpl = 'series.' + + (seriesCnt > 1 ? 'multiple' : 'single') + '.'; + seriesLabel = getConfig(seriesName + ? seriesTpl + 'withName' + : seriesTpl + 'withoutName'); + + seriesLabel = replace(seriesLabel, { + seriesId: seriesModel.seriesIndex, + seriesName: seriesModel.get('name'), + seriesType: getSeriesTypeName(seriesModel.subType) + }); + + var data = seriesModel.getData(); + window.data = data; + if (data.count() > maxDataCnt) { + // Show part of data + seriesLabel += replace(getConfig('data.partialData'), { + displayCnt: maxDataCnt + }); + } + else { + seriesLabel += getConfig('data.allData'); + } + + var dataLabels = []; + for (var i = 0; i < data.count(); i++) { + if (i < maxDataCnt) { + var name = data.getName(i); + var value = retrieveRawValue(data, i); + dataLabels.push( + replace( + name + ? getConfig('data.withName') + : getConfig('data.withoutName'), + { + name: name, + value: value + } + ) + ); + } + } + seriesLabel += dataLabels + .join(getConfig('data.separator.middle')) + + getConfig('data.separator.end'); + + seriesLabels.push(seriesLabel); + } + }); + + ariaLabel += seriesLabels + .join(getConfig('series.multiple.separator.middle')) + + getConfig('series.multiple.separator.end'); + + dom.setAttribute('aria-label', ariaLabel); + } + + function replace(str, keyValues) { + if (typeof str !== 'string') { + return str; + } + + var result = str; + each$1(keyValues, function (value, key) { + result = result.replace( + new RegExp('\\{\\s*' + key + '\\s*\\}', 'g'), + value + ); + }); + return result; + } + + function getConfig(path) { + var userConfig = ariaModel.get(path); + if (userConfig == null) { + var pathArr = path.split('.'); + var result = lang.aria; + for (var i = 0; i < pathArr.length; ++i) { + result = result[pathArr[i]]; + } + return result; + } + else { + return userConfig; + } + } + + function getTitle() { + var title = ecModel.getModel('title').option; + if (title && title.length) { + title = title[0]; + } + return title && title.text; + } + + function getSeriesTypeName(type) { + return lang.series.typeNames[type] || '自定义图'; + } +}; + +/* +* 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. +*/ + +var PI$1 = Math.PI; + +/** + * @param {module:echarts/ExtensionAPI} api + * @param {Object} [opts] + * @param {string} [opts.text] + * @param {string} [opts.color] + * @param {string} [opts.textColor] + * @return {module:zrender/Element} + */ +var loadingDefault = function (api, opts) { + opts = opts || {}; + defaults(opts, { + text: 'loading', + color: '#c23531', + textColor: '#000', + maskColor: 'rgba(255, 255, 255, 0.8)', + zlevel: 0 + }); + var mask = new Rect({ + style: { + fill: opts.maskColor + }, + zlevel: opts.zlevel, + z: 10000 + }); + var arc = new Arc({ + shape: { + startAngle: -PI$1 / 2, + endAngle: -PI$1 / 2 + 0.1, + r: 10 + }, + style: { + stroke: opts.color, + lineCap: 'round', + lineWidth: 5 + }, + zlevel: opts.zlevel, + z: 10001 + }); + var labelRect = new Rect({ + style: { + fill: 'none', + text: opts.text, + textPosition: 'right', + textDistance: 10, + textFill: opts.textColor + }, + zlevel: opts.zlevel, + z: 10001 + }); + + arc.animateShape(true) + .when(1000, { + endAngle: PI$1 * 3 / 2 + }) + .start('circularInOut'); + arc.animateShape(true) + .when(1000, { + startAngle: PI$1 * 3 / 2 + }) + .delay(300) + .start('circularInOut'); + + var group = new Group(); + group.add(arc); + group.add(labelRect); + group.add(mask); + // Inject resize + group.resize = function () { + var cx = api.getWidth() / 2; + var cy = api.getHeight() / 2; + arc.setShape({ + cx: cx, + cy: cy + }); + var r = arc.shape.r; + labelRect.setShape({ + x: cx - r, + y: cy - r, + width: r * 2, + height: r * 2 + }); + + mask.setShape({ + x: 0, + y: 0, + width: api.getWidth(), + height: api.getHeight() + }); + }; + group.resize(); + return group; +}; + +/* +* 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. +*/ + +/** + * @module echarts/stream/Scheduler + */ + +/** + * @constructor + */ +function Scheduler(ecInstance, api, dataProcessorHandlers, visualHandlers) { + this.ecInstance = ecInstance; + this.api = api; + this.unfinished; + + // Fix current processors in case that in some rear cases that + // processors might be registered after echarts instance created. + // Register processors incrementally for a echarts instance is + // not supported by this stream architecture. + var dataProcessorHandlers = this._dataProcessorHandlers = dataProcessorHandlers.slice(); + var visualHandlers = this._visualHandlers = visualHandlers.slice(); + this._allHandlers = dataProcessorHandlers.concat(visualHandlers); + + /** + * @private + * @type { + * [handlerUID: string]: { + * seriesTaskMap?: { + * [seriesUID: string]: Task + * }, + * overallTask?: Task + * } + * } + */ + this._stageTaskMap = createHashMap(); +} + +var proto = Scheduler.prototype; + +/** + * @param {module:echarts/model/Global} ecModel + * @param {Object} payload + */ +proto.restoreData = function (ecModel, payload) { + // TODO: Only restroe needed series and components, but not all components. + // Currently `restoreData` of all of the series and component will be called. + // But some independent components like `title`, `legend`, `graphic`, `toolbox`, + // `tooltip`, `axisPointer`, etc, do not need series refresh when `setOption`, + // and some components like coordinate system, axes, dataZoom, visualMap only + // need their target series refresh. + // (1) If we are implementing this feature some day, we should consider these cases: + // if a data processor depends on a component (e.g., dataZoomProcessor depends + // on the settings of `dataZoom`), it should be re-performed if the component + // is modified by `setOption`. + // (2) If a processor depends on sevral series, speicified by its `getTargetSeries`, + // it should be re-performed when the result array of `getTargetSeries` changed. + // We use `dependencies` to cover these issues. + // (3) How to update target series when coordinate system related components modified. + + // TODO: simply the dirty mechanism? Check whether only the case here can set tasks dirty, + // and this case all of the tasks will be set as dirty. + + ecModel.restoreData(payload); + + // Theoretically an overall task not only depends on each of its target series, but also + // depends on all of the series. + // The overall task is not in pipeline, and `ecModel.restoreData` only set pipeline tasks + // dirty. If `getTargetSeries` of an overall task returns nothing, we should also ensure + // that the overall task is set as dirty and to be performed, otherwise it probably cause + // state chaos. So we have to set dirty of all of the overall tasks manually, otherwise it + // probably cause state chaos (consider `dataZoomProcessor`). + this._stageTaskMap.each(function (taskRecord) { + var overallTask = taskRecord.overallTask; + overallTask && overallTask.dirty(); + }); +}; + +// If seriesModel provided, incremental threshold is check by series data. +proto.getPerformArgs = function (task, isBlock) { + // For overall task + if (!task.__pipeline) { + return; + } + + var pipeline = this._pipelineMap.get(task.__pipeline.id); + var pCtx = pipeline.context; + var incremental = !isBlock + && pipeline.progressiveEnabled + && (!pCtx || pCtx.progressiveRender) + && task.__idxInPipeline > pipeline.blockIndex; + + var step = incremental ? pipeline.step : null; + var modDataCount = pCtx && pCtx.modDataCount; + var modBy = modDataCount != null ? Math.ceil(modDataCount / step) : null; + + return {step: step, modBy: modBy, modDataCount: modDataCount}; +}; + +proto.getPipeline = function (pipelineId) { + return this._pipelineMap.get(pipelineId); +}; + +/** + * Current, progressive rendering starts from visual and layout. + * Always detect render mode in the same stage, avoiding that incorrect + * detection caused by data filtering. + * Caution: + * `updateStreamModes` use `seriesModel.getData()`. + */ +proto.updateStreamModes = function (seriesModel, view) { + var pipeline = this._pipelineMap.get(seriesModel.uid); + var data = seriesModel.getData(); + var dataLen = data.count(); + + // `progressiveRender` means that can render progressively in each + // animation frame. Note that some types of series do not provide + // `view.incrementalPrepareRender` but support `chart.appendData`. We + // use the term `incremental` but not `progressive` to describe the + // case that `chart.appendData`. + var progressiveRender = pipeline.progressiveEnabled + && view.incrementalPrepareRender + && dataLen >= pipeline.threshold; + + var large = seriesModel.get('large') && dataLen >= seriesModel.get('largeThreshold'); + + // TODO: modDataCount should not updated if `appendData`, otherwise cause whole repaint. + // see `test/candlestick-large3.html` + var modDataCount = seriesModel.get('progressiveChunkMode') === 'mod' ? dataLen : null; + + seriesModel.pipelineContext = pipeline.context = { + progressiveRender: progressiveRender, + modDataCount: modDataCount, + large: large + }; +}; + +proto.restorePipelines = function (ecModel) { + var scheduler = this; + var pipelineMap = scheduler._pipelineMap = createHashMap(); + + ecModel.eachSeries(function (seriesModel) { + var progressive = seriesModel.getProgressive(); + var pipelineId = seriesModel.uid; + + pipelineMap.set(pipelineId, { + id: pipelineId, + head: null, + tail: null, + threshold: seriesModel.getProgressiveThreshold(), + progressiveEnabled: progressive + && !(seriesModel.preventIncremental && seriesModel.preventIncremental()), + blockIndex: -1, + step: Math.round(progressive || 700), + count: 0 + }); + + pipe(scheduler, seriesModel, seriesModel.dataTask); + }); +}; + +proto.prepareStageTasks = function () { + var stageTaskMap = this._stageTaskMap; + var ecModel = this.ecInstance.getModel(); + var api = this.api; + + each$1(this._allHandlers, function (handler) { + var record = stageTaskMap.get(handler.uid) || stageTaskMap.set(handler.uid, []); + + handler.reset && createSeriesStageTask(this, handler, record, ecModel, api); + handler.overallReset && createOverallStageTask(this, handler, record, ecModel, api); + }, this); +}; + +proto.prepareView = function (view, model, ecModel, api) { + var renderTask = view.renderTask; + var context = renderTask.context; + + context.model = model; + context.ecModel = ecModel; + context.api = api; + + renderTask.__block = !view.incrementalPrepareRender; + + pipe(this, model, renderTask); +}; + + +proto.performDataProcessorTasks = function (ecModel, payload) { + // If we do not use `block` here, it should be considered when to update modes. + performStageTasks(this, this._dataProcessorHandlers, ecModel, payload, {block: true}); +}; + +// opt +// opt.visualType: 'visual' or 'layout' +// opt.setDirty +proto.performVisualTasks = function (ecModel, payload, opt) { + performStageTasks(this, this._visualHandlers, ecModel, payload, opt); +}; + +function performStageTasks(scheduler, stageHandlers, ecModel, payload, opt) { + opt = opt || {}; + var unfinished; + + each$1(stageHandlers, function (stageHandler, idx) { + if (opt.visualType && opt.visualType !== stageHandler.visualType) { + return; + } + + var stageHandlerRecord = scheduler._stageTaskMap.get(stageHandler.uid); + var seriesTaskMap = stageHandlerRecord.seriesTaskMap; + var overallTask = stageHandlerRecord.overallTask; + + if (overallTask) { + var overallNeedDirty; + var agentStubMap = overallTask.agentStubMap; + agentStubMap.each(function (stub) { + if (needSetDirty(opt, stub)) { + stub.dirty(); + overallNeedDirty = true; + } + }); + overallNeedDirty && overallTask.dirty(); + updatePayload(overallTask, payload); + var performArgs = scheduler.getPerformArgs(overallTask, opt.block); + // Execute stubs firstly, which may set the overall task dirty, + // then execute the overall task. And stub will call seriesModel.setData, + // which ensures that in the overallTask seriesModel.getData() will not + // return incorrect data. + agentStubMap.each(function (stub) { + stub.perform(performArgs); + }); + unfinished |= overallTask.perform(performArgs); + } + else if (seriesTaskMap) { + seriesTaskMap.each(function (task, pipelineId) { + if (needSetDirty(opt, task)) { + task.dirty(); + } + var performArgs = scheduler.getPerformArgs(task, opt.block); + performArgs.skip = !stageHandler.performRawSeries + && ecModel.isSeriesFiltered(task.context.model); + updatePayload(task, payload); + unfinished |= task.perform(performArgs); + }); + } + }); + + function needSetDirty(opt, task) { + return opt.setDirty && (!opt.dirtyMap || opt.dirtyMap.get(task.__pipeline.id)); + } + + scheduler.unfinished |= unfinished; +} + +proto.performSeriesTasks = function (ecModel) { + var unfinished; + + ecModel.eachSeries(function (seriesModel) { + // Progress to the end for dataInit and dataRestore. + unfinished |= seriesModel.dataTask.perform(); + }); + + this.unfinished |= unfinished; +}; + +proto.plan = function () { + // Travel pipelines, check block. + this._pipelineMap.each(function (pipeline) { + var task = pipeline.tail; + do { + if (task.__block) { + pipeline.blockIndex = task.__idxInPipeline; + break; + } + task = task.getUpstream(); + } + while (task); + }); +}; + +var updatePayload = proto.updatePayload = function (task, payload) { + payload !== 'remain' && (task.context.payload = payload); +}; + +function createSeriesStageTask(scheduler, stageHandler, stageHandlerRecord, ecModel, api) { + var seriesTaskMap = stageHandlerRecord.seriesTaskMap + || (stageHandlerRecord.seriesTaskMap = createHashMap()); + var seriesType = stageHandler.seriesType; + var getTargetSeries = stageHandler.getTargetSeries; + + // If a stageHandler should cover all series, `createOnAllSeries` should be declared mandatorily, + // to avoid some typo or abuse. Otherwise if an extension do not specify a `seriesType`, + // it works but it may cause other irrelevant charts blocked. + if (stageHandler.createOnAllSeries) { + ecModel.eachRawSeries(create); + } + else if (seriesType) { + ecModel.eachRawSeriesByType(seriesType, create); + } + else if (getTargetSeries) { + getTargetSeries(ecModel, api).each(create); + } + + function create(seriesModel) { + var pipelineId = seriesModel.uid; + + // Init tasks for each seriesModel only once. + // Reuse original task instance. + var task = seriesTaskMap.get(pipelineId) + || seriesTaskMap.set(pipelineId, createTask({ + plan: seriesTaskPlan, + reset: seriesTaskReset, + count: seriesTaskCount + })); + task.context = { + model: seriesModel, + ecModel: ecModel, + api: api, + useClearVisual: stageHandler.isVisual && !stageHandler.isLayout, + plan: stageHandler.plan, + reset: stageHandler.reset, + scheduler: scheduler + }; + pipe(scheduler, seriesModel, task); + } + + // Clear unused series tasks. + var pipelineMap = scheduler._pipelineMap; + seriesTaskMap.each(function (task, pipelineId) { + if (!pipelineMap.get(pipelineId)) { + task.dispose(); + seriesTaskMap.removeKey(pipelineId); + } + }); +} + +function createOverallStageTask(scheduler, stageHandler, stageHandlerRecord, ecModel, api) { + var overallTask = stageHandlerRecord.overallTask = stageHandlerRecord.overallTask + // For overall task, the function only be called on reset stage. + || createTask({reset: overallTaskReset}); + + overallTask.context = { + ecModel: ecModel, + api: api, + overallReset: stageHandler.overallReset, + scheduler: scheduler + }; + + // Reuse orignal stubs. + var agentStubMap = overallTask.agentStubMap = overallTask.agentStubMap || createHashMap(); + + var seriesType = stageHandler.seriesType; + var getTargetSeries = stageHandler.getTargetSeries; + var overallProgress = true; + var modifyOutputEnd = stageHandler.modifyOutputEnd; + + // An overall task with seriesType detected or has `getTargetSeries`, we add + // stub in each pipelines, it will set the overall task dirty when the pipeline + // progress. Moreover, to avoid call the overall task each frame (too frequent), + // we set the pipeline block. + if (seriesType) { + ecModel.eachRawSeriesByType(seriesType, createStub); + } + else if (getTargetSeries) { + getTargetSeries(ecModel, api).each(createStub); + } + // Otherwise, (usually it is legancy case), the overall task will only be + // executed when upstream dirty. Otherwise the progressive rendering of all + // pipelines will be disabled unexpectedly. But it still needs stubs to receive + // dirty info from upsteam. + else { + overallProgress = false; + each$1(ecModel.getSeries(), createStub); + } + + function createStub(seriesModel) { + var pipelineId = seriesModel.uid; + var stub = agentStubMap.get(pipelineId); + if (!stub) { + stub = agentStubMap.set(pipelineId, createTask( + {reset: stubReset, onDirty: stubOnDirty} + )); + // When the result of `getTargetSeries` changed, the overallTask + // should be set as dirty and re-performed. + overallTask.dirty(); + } + stub.context = { + model: seriesModel, + overallProgress: overallProgress, + modifyOutputEnd: modifyOutputEnd + }; + stub.agent = overallTask; + stub.__block = overallProgress; + + pipe(scheduler, seriesModel, stub); + } + + // Clear unused stubs. + var pipelineMap = scheduler._pipelineMap; + agentStubMap.each(function (stub, pipelineId) { + if (!pipelineMap.get(pipelineId)) { + stub.dispose(); + // When the result of `getTargetSeries` changed, the overallTask + // should be set as dirty and re-performed. + overallTask.dirty(); + agentStubMap.removeKey(pipelineId); + } + }); +} + +function overallTaskReset(context) { + context.overallReset( + context.ecModel, context.api, context.payload + ); +} + +function stubReset(context, upstreamContext) { + return context.overallProgress && stubProgress; +} + +function stubProgress() { + this.agent.dirty(); + this.getDownstream().dirty(); +} + +function stubOnDirty() { + this.agent && this.agent.dirty(); +} + +function seriesTaskPlan(context) { + return context.plan && context.plan( + context.model, context.ecModel, context.api, context.payload + ); +} + +function seriesTaskReset(context) { + if (context.useClearVisual) { + context.data.clearAllVisual(); + } + var resetDefines = context.resetDefines = normalizeToArray(context.reset( + context.model, context.ecModel, context.api, context.payload + )); + return resetDefines.length > 1 + ? map(resetDefines, function (v, idx) { + return makeSeriesTaskProgress(idx); + }) + : singleSeriesTaskProgress; +} + +var singleSeriesTaskProgress = makeSeriesTaskProgress(0); + +function makeSeriesTaskProgress(resetDefineIdx) { + return function (params, context) { + var data = context.data; + var resetDefine = context.resetDefines[resetDefineIdx]; + + if (resetDefine && resetDefine.dataEach) { + for (var i = params.start; i < params.end; i++) { + resetDefine.dataEach(data, i); + } + } + else if (resetDefine && resetDefine.progress) { + resetDefine.progress(params, data); + } + }; +} + +function seriesTaskCount(context) { + return context.data.count(); +} + +function pipe(scheduler, seriesModel, task) { + var pipelineId = seriesModel.uid; + var pipeline = scheduler._pipelineMap.get(pipelineId); + !pipeline.head && (pipeline.head = task); + pipeline.tail && pipeline.tail.pipe(task); + pipeline.tail = task; + task.__idxInPipeline = pipeline.count++; + task.__pipeline = pipeline; +} + +Scheduler.wrapStageHandler = function (stageHandler, visualType) { + if (isFunction$1(stageHandler)) { + stageHandler = { + overallReset: stageHandler, + seriesType: detectSeriseType(stageHandler) + }; + } + + stageHandler.uid = getUID('stageHandler'); + visualType && (stageHandler.visualType = visualType); + + return stageHandler; +}; + + + +/** + * Only some legacy stage handlers (usually in echarts extensions) are pure function. + * To ensure that they can work normally, they should work in block mode, that is, + * they should not be started util the previous tasks finished. So they cause the + * progressive rendering disabled. We try to detect the series type, to narrow down + * the block range to only the series type they concern, but not all series. + */ +function detectSeriseType(legacyFunc) { + seriesType = null; + try { + // Assume there is no async when calling `eachSeriesByType`. + legacyFunc(ecModelMock, apiMock); + } + catch (e) { + } + return seriesType; +} + +var ecModelMock = {}; +var apiMock = {}; +var seriesType; + +mockMethods(ecModelMock, GlobalModel); +mockMethods(apiMock, ExtensionAPI); +ecModelMock.eachSeriesByType = ecModelMock.eachRawSeriesByType = function (type) { + seriesType = type; +}; +ecModelMock.eachComponent = function (cond) { + if (cond.mainType === 'series' && cond.subType) { + seriesType = cond.subType; + } +}; + +function mockMethods(target, Clz) { + /* eslint-disable */ + for (var name in Clz.prototype) { + // Do not use hasOwnProperty + target[name] = noop; + } + /* eslint-enable */ +} + +/* +* 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. +*/ + +var colorAll = [ + '#37A2DA', '#32C5E9', '#67E0E3', '#9FE6B8', '#FFDB5C', '#ff9f7f', + '#fb7293', '#E062AE', '#E690D1', '#e7bcf3', '#9d96f5', '#8378EA', '#96BFFF' +]; + +var lightTheme = { + + color: colorAll, + + colorLayer: [ + ['#37A2DA', '#ffd85c', '#fd7b5f'], + ['#37A2DA', '#67E0E3', '#FFDB5C', '#ff9f7f', '#E062AE', '#9d96f5'], + ['#37A2DA', '#32C5E9', '#9FE6B8', '#FFDB5C', '#ff9f7f', '#fb7293', '#e7bcf3', '#8378EA', '#96BFFF'], + colorAll + ] +}; + +/* +* 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. +*/ + +var contrastColor = '#eee'; +var axisCommon = function () { + return { + axisLine: { + lineStyle: { + color: contrastColor + } + }, + axisTick: { + lineStyle: { + color: contrastColor + } + }, + axisLabel: { + textStyle: { + color: contrastColor + } + }, + splitLine: { + lineStyle: { + type: 'dashed', + color: '#aaa' + } + }, + splitArea: { + areaStyle: { + color: contrastColor + } + } + }; +}; + +var colorPalette = [ + '#dd6b66', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53', + '#eedd78', '#73a373', '#73b9bc', '#7289ab', '#91ca8c', '#f49f42' +]; +var theme = { + color: colorPalette, + backgroundColor: '#333', + tooltip: { + axisPointer: { + lineStyle: { + color: contrastColor + }, + crossStyle: { + color: contrastColor + }, + label: { + color: '#000' + } + } + }, + legend: { + textStyle: { + color: contrastColor + } + }, + textStyle: { + color: contrastColor + }, + title: { + textStyle: { + color: contrastColor + } + }, + toolbox: { + iconStyle: { + normal: { + borderColor: contrastColor + } + } + }, + dataZoom: { + textStyle: { + color: contrastColor + } + }, + visualMap: { + textStyle: { + color: contrastColor + } + }, + timeline: { + lineStyle: { + color: contrastColor + }, + itemStyle: { + normal: { + color: colorPalette[1] + } + }, + label: { + normal: { + textStyle: { + color: contrastColor + } + } + }, + controlStyle: { + normal: { + color: contrastColor, + borderColor: contrastColor + } + } + }, + timeAxis: axisCommon(), + logAxis: axisCommon(), + valueAxis: axisCommon(), + categoryAxis: axisCommon(), + + line: { + symbol: 'circle' + }, + graph: { + color: colorPalette + }, + gauge: { + title: { + textStyle: { + color: contrastColor + } + } + }, + candlestick: { + itemStyle: { + normal: { + color: '#FD1050', + color0: '#0CF49B', + borderColor: '#FD1050', + borderColor0: '#0CF49B' + } + } + } +}; +theme.categoryAxis.splitLine.show = false; + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/** + * This module is imported by echarts directly. + * + * Notice: + * Always keep this file exists for backward compatibility. + * Because before 4.1.0, dataset is an optional component, + * some users may import this module manually. + */ + +ComponentModel.extend({ + + type: 'dataset', + + /** + * @protected + */ + defaultOption: { + + // 'row', 'column' + seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN, + + // null/'auto': auto detect header, see "module:echarts/data/helper/sourceHelper" + sourceHeader: null, + + dimensions: null, + + source: null + }, + + optionUpdated: function () { + detectSourceFormat(this); + } + +}); + +Component$1.extend({ + + type: 'dataset' + +}); + +/** + * 椭圆形状 + * @module zrender/graphic/shape/Ellipse + */ + +var Ellipse = Path.extend({ + + type: 'ellipse', + + shape: { + cx: 0, cy: 0, + rx: 0, ry: 0 + }, + + buildPath: function (ctx, shape) { + var k = 0.5522848; + var x = shape.cx; + var y = shape.cy; + var a = shape.rx; + var b = shape.ry; + var ox = a * k; // 水平控制点偏移量 + var oy = b * k; // 垂直控制点偏移量 + // 从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线 + ctx.moveTo(x - a, y); + ctx.bezierCurveTo(x - a, y - oy, x - ox, y - b, x, y - b); + ctx.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y); + ctx.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b); + ctx.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y); + ctx.closePath(); + } +}); + +// import RadialGradient from '../graphic/RadialGradient'; +// import Pattern from '../graphic/Pattern'; +// import * as vector from '../core/vector'; +// Most of the values can be separated by comma and/or white space. +var DILIMITER_REG = /[\s,]+/; + +/** + * For big svg string, this method might be time consuming. + * + * @param {string} svg xml string + * @return {Object} xml root. + */ +function parseXML(svg) { + if (isString(svg)) { + var parser = new DOMParser(); + svg = parser.parseFromString(svg, 'text/xml'); + } + + // Document node. If using $.get, doc node may be input. + if (svg.nodeType === 9) { + svg = svg.firstChild; + } + // nodeName of is also 'svg'. + while (svg.nodeName.toLowerCase() !== 'svg' || svg.nodeType !== 1) { + svg = svg.nextSibling; + } + + return svg; +} + +function SVGParser() { + this._defs = {}; + this._root = null; + + this._isDefine = false; + this._isText = false; +} + +SVGParser.prototype.parse = function (xml, opt) { + opt = opt || {}; + + var svg = parseXML(xml); + + if (!svg) { + throw new Error('Illegal svg'); + } + + var root = new Group(); + this._root = root; + // parse view port + var viewBox = svg.getAttribute('viewBox') || ''; + + // If width/height not specified, means "100%" of `opt.width/height`. + // TODO: Other percent value not supported yet. + var width = parseFloat(svg.getAttribute('width') || opt.width); + var height = parseFloat(svg.getAttribute('height') || opt.height); + // If width/height not specified, set as null for output. + isNaN(width) && (width = null); + isNaN(height) && (height = null); + + // Apply inline style on svg element. + parseAttributes(svg, root, null, true); + + var child = svg.firstChild; + while (child) { + this._parseNode(child, root); + child = child.nextSibling; + } + + var viewBoxRect; + var viewBoxTransform; + + if (viewBox) { + var viewBoxArr = trim(viewBox).split(DILIMITER_REG); + // Some invalid case like viewBox: 'none'. + if (viewBoxArr.length >= 4) { + viewBoxRect = { + x: parseFloat(viewBoxArr[0] || 0), + y: parseFloat(viewBoxArr[1] || 0), + width: parseFloat(viewBoxArr[2]), + height: parseFloat(viewBoxArr[3]) + }; + } + } + + if (viewBoxRect && width != null && height != null) { + viewBoxTransform = makeViewBoxTransform(viewBoxRect, width, height); + + if (!opt.ignoreViewBox) { + // If set transform on the output group, it probably bring trouble when + // some users only intend to show the clipped content inside the viewBox, + // but not intend to transform the output group. So we keep the output + // group no transform. If the user intend to use the viewBox as a + // camera, just set `opt.ignoreViewBox` as `true` and set transfrom + // manually according to the viewBox info in the output of this method. + var elRoot = root; + root = new Group(); + root.add(elRoot); + elRoot.scale = viewBoxTransform.scale.slice(); + elRoot.position = viewBoxTransform.position.slice(); + } + } + + // Some shapes might be overflow the viewport, which should be + // clipped despite whether the viewBox is used, as the SVG does. + if (!opt.ignoreRootClip && width != null && height != null) { + root.setClipPath(new Rect({ + shape: {x: 0, y: 0, width: width, height: height} + })); + } + + // Set width/height on group just for output the viewport size. + return { + root: root, + width: width, + height: height, + viewBoxRect: viewBoxRect, + viewBoxTransform: viewBoxTransform + }; +}; + +SVGParser.prototype._parseNode = function (xmlNode, parentGroup) { + + var nodeName = xmlNode.nodeName.toLowerCase(); + + // TODO + // support in svg, where nodeName is 'style', + // CSS classes is defined globally wherever the style tags are declared. + + if (nodeName === 'defs') { + // define flag + this._isDefine = true; + } + else if (nodeName === 'text') { + this._isText = true; + } + + var el; + if (this._isDefine) { + var parser = defineParsers[nodeName]; + if (parser) { + var def = parser.call(this, xmlNode); + var id = xmlNode.getAttribute('id'); + if (id) { + this._defs[id] = def; + } + } + } + else { + var parser = nodeParsers[nodeName]; + if (parser) { + el = parser.call(this, xmlNode, parentGroup); + parentGroup.add(el); + } + } + + var child = xmlNode.firstChild; + while (child) { + if (child.nodeType === 1) { + this._parseNode(child, el); + } + // Is text + if (child.nodeType === 3 && this._isText) { + this._parseText(child, el); + } + child = child.nextSibling; + } + + // Quit define + if (nodeName === 'defs') { + this._isDefine = false; + } + else if (nodeName === 'text') { + this._isText = false; + } +}; + +SVGParser.prototype._parseText = function (xmlNode, parentGroup) { + if (xmlNode.nodeType === 1) { + var dx = xmlNode.getAttribute('dx') || 0; + var dy = xmlNode.getAttribute('dy') || 0; + this._textX += parseFloat(dx); + this._textY += parseFloat(dy); + } + + var text = new Text({ + style: { + text: xmlNode.textContent, + transformText: true + }, + position: [this._textX || 0, this._textY || 0] + }); + + inheritStyle(parentGroup, text); + parseAttributes(xmlNode, text, this._defs); + + var fontSize = text.style.fontSize; + if (fontSize && fontSize < 9) { + // PENDING + text.style.fontSize = 9; + text.scale = text.scale || [1, 1]; + text.scale[0] *= fontSize / 9; + text.scale[1] *= fontSize / 9; + } + + var rect = text.getBoundingRect(); + this._textX += rect.width; + + parentGroup.add(text); + + return text; +}; + +var nodeParsers = { + 'g': function (xmlNode, parentGroup) { + var g = new Group(); + inheritStyle(parentGroup, g); + parseAttributes(xmlNode, g, this._defs); + + return g; + }, + 'rect': function (xmlNode, parentGroup) { + var rect = new Rect(); + inheritStyle(parentGroup, rect); + parseAttributes(xmlNode, rect, this._defs); + + rect.setShape({ + x: parseFloat(xmlNode.getAttribute('x') || 0), + y: parseFloat(xmlNode.getAttribute('y') || 0), + width: parseFloat(xmlNode.getAttribute('width') || 0), + height: parseFloat(xmlNode.getAttribute('height') || 0) + }); + + // console.log(xmlNode.getAttribute('transform')); + // console.log(rect.transform); + + return rect; + }, + 'circle': function (xmlNode, parentGroup) { + var circle = new Circle(); + inheritStyle(parentGroup, circle); + parseAttributes(xmlNode, circle, this._defs); + + circle.setShape({ + cx: parseFloat(xmlNode.getAttribute('cx') || 0), + cy: parseFloat(xmlNode.getAttribute('cy') || 0), + r: parseFloat(xmlNode.getAttribute('r') || 0) + }); + + return circle; + }, + 'line': function (xmlNode, parentGroup) { + var line = new Line(); + inheritStyle(parentGroup, line); + parseAttributes(xmlNode, line, this._defs); + + line.setShape({ + x1: parseFloat(xmlNode.getAttribute('x1') || 0), + y1: parseFloat(xmlNode.getAttribute('y1') || 0), + x2: parseFloat(xmlNode.getAttribute('x2') || 0), + y2: parseFloat(xmlNode.getAttribute('y2') || 0) + }); + + return line; + }, + 'ellipse': function (xmlNode, parentGroup) { + var ellipse = new Ellipse(); + inheritStyle(parentGroup, ellipse); + parseAttributes(xmlNode, ellipse, this._defs); + + ellipse.setShape({ + cx: parseFloat(xmlNode.getAttribute('cx') || 0), + cy: parseFloat(xmlNode.getAttribute('cy') || 0), + rx: parseFloat(xmlNode.getAttribute('rx') || 0), + ry: parseFloat(xmlNode.getAttribute('ry') || 0) + }); + return ellipse; + }, + 'polygon': function (xmlNode, parentGroup) { + var points = xmlNode.getAttribute('points'); + if (points) { + points = parsePoints(points); + } + var polygon = new Polygon({ + shape: { + points: points || [] + } + }); + + inheritStyle(parentGroup, polygon); + parseAttributes(xmlNode, polygon, this._defs); + + return polygon; + }, + 'polyline': function (xmlNode, parentGroup) { + var path = new Path(); + inheritStyle(parentGroup, path); + parseAttributes(xmlNode, path, this._defs); + + var points = xmlNode.getAttribute('points'); + if (points) { + points = parsePoints(points); + } + var polyline = new Polyline({ + shape: { + points: points || [] + } + }); + + return polyline; + }, + 'image': function (xmlNode, parentGroup) { + var img = new ZImage(); + inheritStyle(parentGroup, img); + parseAttributes(xmlNode, img, this._defs); + + img.setStyle({ + image: xmlNode.getAttribute('xlink:href'), + x: xmlNode.getAttribute('x'), + y: xmlNode.getAttribute('y'), + width: xmlNode.getAttribute('width'), + height: xmlNode.getAttribute('height') + }); + + return img; + }, + 'text': function (xmlNode, parentGroup) { + var x = xmlNode.getAttribute('x') || 0; + var y = xmlNode.getAttribute('y') || 0; + var dx = xmlNode.getAttribute('dx') || 0; + var dy = xmlNode.getAttribute('dy') || 0; + + this._textX = parseFloat(x) + parseFloat(dx); + this._textY = parseFloat(y) + parseFloat(dy); + + var g = new Group(); + inheritStyle(parentGroup, g); + parseAttributes(xmlNode, g, this._defs); + + return g; + }, + 'tspan': function (xmlNode, parentGroup) { + var x = xmlNode.getAttribute('x'); + var y = xmlNode.getAttribute('y'); + if (x != null) { + // new offset x + this._textX = parseFloat(x); + } + if (y != null) { + // new offset y + this._textY = parseFloat(y); + } + var dx = xmlNode.getAttribute('dx') || 0; + var dy = xmlNode.getAttribute('dy') || 0; + + var g = new Group(); + + inheritStyle(parentGroup, g); + parseAttributes(xmlNode, g, this._defs); + + + this._textX += dx; + this._textY += dy; + + return g; + }, + 'path': function (xmlNode, parentGroup) { + // TODO svg fill rule + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule + // path.style.globalCompositeOperation = 'xor'; + var d = xmlNode.getAttribute('d') || ''; + + // Performance sensitive. + + var path = createFromString(d); + + inheritStyle(parentGroup, path); + parseAttributes(xmlNode, path, this._defs); + + return path; + } +}; + +var defineParsers = { + + 'lineargradient': function (xmlNode) { + var x1 = parseInt(xmlNode.getAttribute('x1') || 0, 10); + var y1 = parseInt(xmlNode.getAttribute('y1') || 0, 10); + var x2 = parseInt(xmlNode.getAttribute('x2') || 10, 10); + var y2 = parseInt(xmlNode.getAttribute('y2') || 0, 10); + + var gradient = new LinearGradient(x1, y1, x2, y2); + + _parseGradientColorStops(xmlNode, gradient); + + return gradient; + }, + + 'radialgradient': function (xmlNode) { + + } +}; + +function _parseGradientColorStops(xmlNode, gradient) { + + var stop = xmlNode.firstChild; + + while (stop) { + if (stop.nodeType === 1) { + var offset = stop.getAttribute('offset'); + if (offset.indexOf('%') > 0) { // percentage + offset = parseInt(offset, 10) / 100; + } + else if (offset) { // number from 0 to 1 + offset = parseFloat(offset); + } + else { + offset = 0; + } + + var stopColor = stop.getAttribute('stop-color') || '#000000'; + + gradient.addColorStop(offset, stopColor); + } + stop = stop.nextSibling; + } +} + +function inheritStyle(parent, child) { + if (parent && parent.__inheritedStyle) { + if (!child.__inheritedStyle) { + child.__inheritedStyle = {}; + } + defaults(child.__inheritedStyle, parent.__inheritedStyle); + } +} + +function parsePoints(pointsString) { + var list = trim(pointsString).split(DILIMITER_REG); + var points = []; + + for (var i = 0; i < list.length; i += 2) { + var x = parseFloat(list[i]); + var y = parseFloat(list[i + 1]); + points.push([x, y]); + } + return points; +} + +var attributesMap = { + 'fill': 'fill', + 'stroke': 'stroke', + 'stroke-width': 'lineWidth', + 'opacity': 'opacity', + 'fill-opacity': 'fillOpacity', + 'stroke-opacity': 'strokeOpacity', + 'stroke-dasharray': 'lineDash', + 'stroke-dashoffset': 'lineDashOffset', + 'stroke-linecap': 'lineCap', + 'stroke-linejoin': 'lineJoin', + 'stroke-miterlimit': 'miterLimit', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', + + 'text-align': 'textAlign', + 'alignment-baseline': 'textBaseline' +}; + +function parseAttributes(xmlNode, el, defs, onlyInlineStyle) { + var zrStyle = el.__inheritedStyle || {}; + var isTextEl = el.type === 'text'; + + // TODO Shadow + if (xmlNode.nodeType === 1) { + parseTransformAttribute(xmlNode, el); + + extend(zrStyle, parseStyleAttribute(xmlNode)); + + if (!onlyInlineStyle) { + for (var svgAttrName in attributesMap) { + if (attributesMap.hasOwnProperty(svgAttrName)) { + var attrValue = xmlNode.getAttribute(svgAttrName); + if (attrValue != null) { + zrStyle[attributesMap[svgAttrName]] = attrValue; + } + } + } + } + } + + var elFillProp = isTextEl ? 'textFill' : 'fill'; + var elStrokeProp = isTextEl ? 'textStroke' : 'stroke'; + + el.style = el.style || new Style(); + var elStyle = el.style; + + zrStyle.fill != null && elStyle.set(elFillProp, getPaint(zrStyle.fill, defs)); + zrStyle.stroke != null && elStyle.set(elStrokeProp, getPaint(zrStyle.stroke, defs)); + + each$1([ + 'lineWidth', 'opacity', 'fillOpacity', 'strokeOpacity', 'miterLimit', 'fontSize' + ], function (propName) { + var elPropName = (propName === 'lineWidth' && isTextEl) ? 'textStrokeWidth' : propName; + zrStyle[propName] != null && elStyle.set(elPropName, parseFloat(zrStyle[propName])); + }); + + if (!zrStyle.textBaseline || zrStyle.textBaseline === 'auto') { + zrStyle.textBaseline = 'alphabetic'; + } + if (zrStyle.textBaseline === 'alphabetic') { + zrStyle.textBaseline = 'bottom'; + } + if (zrStyle.textAlign === 'start') { + zrStyle.textAlign = 'left'; + } + if (zrStyle.textAlign === 'end') { + zrStyle.textAlign = 'right'; + } + + each$1(['lineDashOffset', 'lineCap', 'lineJoin', + 'fontWeight', 'fontFamily', 'fontStyle', 'textAlign', 'textBaseline' + ], function (propName) { + zrStyle[propName] != null && elStyle.set(propName, zrStyle[propName]); + }); + + if (zrStyle.lineDash) { + el.style.lineDash = trim(zrStyle.lineDash).split(DILIMITER_REG); + } + + if (elStyle[elStrokeProp] && elStyle[elStrokeProp] !== 'none') { + // enable stroke + el[elStrokeProp] = true; + } + + el.__inheritedStyle = zrStyle; +} + + +var urlRegex = /url\(\s*#(.*?)\)/; +function getPaint(str, defs) { + // if (str === 'none') { + // return; + // } + var urlMatch = defs && str && str.match(urlRegex); + if (urlMatch) { + var url = trim(urlMatch[1]); + var def = defs[url]; + return def; + } + return str; +} + +var transformRegex = /(translate|scale|rotate|skewX|skewY|matrix)\(([\-\s0-9\.e,]*)\)/g; + +function parseTransformAttribute(xmlNode, node) { + var transform = xmlNode.getAttribute('transform'); + if (transform) { + transform = transform.replace(/,/g, ' '); + var m = null; + var transformOps = []; + transform.replace(transformRegex, function (str, type, value) { + transformOps.push(type, value); + }); + for (var i = transformOps.length - 1; i > 0; i -= 2) { + var value = transformOps[i]; + var type = transformOps[i - 1]; + m = m || create$1(); + switch (type) { + case 'translate': + value = trim(value).split(DILIMITER_REG); + translate(m, m, [parseFloat(value[0]), parseFloat(value[1] || 0)]); + break; + case 'scale': + value = trim(value).split(DILIMITER_REG); + scale$1(m, m, [parseFloat(value[0]), parseFloat(value[1] || value[0])]); + break; + case 'rotate': + value = trim(value).split(DILIMITER_REG); + rotate(m, m, parseFloat(value[0])); + break; + case 'skew': + value = trim(value).split(DILIMITER_REG); + console.warn('Skew transform is not supported yet'); + break; + case 'matrix': + var value = trim(value).split(DILIMITER_REG); + m[0] = parseFloat(value[0]); + m[1] = parseFloat(value[1]); + m[2] = parseFloat(value[2]); + m[3] = parseFloat(value[3]); + m[4] = parseFloat(value[4]); + m[5] = parseFloat(value[5]); + break; + } + } + node.setLocalTransform(m); + } +} + +// Value may contain space. +var styleRegex = /([^\s:;]+)\s*:\s*([^:;]+)/g; +function parseStyleAttribute(xmlNode) { + var style = xmlNode.getAttribute('style'); + var result = {}; + + if (!style) { + return result; + } + + var styleList = {}; + styleRegex.lastIndex = 0; + var styleRegResult; + while ((styleRegResult = styleRegex.exec(style)) != null) { + styleList[styleRegResult[1]] = styleRegResult[2]; + } + + for (var svgAttrName in attributesMap) { + if (attributesMap.hasOwnProperty(svgAttrName) && styleList[svgAttrName] != null) { + result[attributesMap[svgAttrName]] = styleList[svgAttrName]; + } + } + + return result; +} + +/** + * @param {Array.} viewBoxRect + * @param {number} width + * @param {number} height + * @return {Object} {scale, position} + */ +function makeViewBoxTransform(viewBoxRect, width, height) { + var scaleX = width / viewBoxRect.width; + var scaleY = height / viewBoxRect.height; + var scale = Math.min(scaleX, scaleY); + // preserveAspectRatio 'xMidYMid' + var viewBoxScale = [scale, scale]; + var viewBoxPosition = [ + -(viewBoxRect.x + viewBoxRect.width / 2) * scale + width / 2, + -(viewBoxRect.y + viewBoxRect.height / 2) * scale + height / 2 + ]; + + return { + scale: viewBoxScale, + position: viewBoxPosition + }; +} + +/** + * @param {string|XMLElement} xml + * @param {Object} [opt] + * @param {number} [opt.width] Default width if svg width not specified or is a percent value. + * @param {number} [opt.height] Default height if svg height not specified or is a percent value. + * @param {boolean} [opt.ignoreViewBox] + * @param {boolean} [opt.ignoreRootClip] + * @return {Object} result: + * { + * root: Group, The root of the the result tree of zrender shapes, + * width: number, the viewport width of the SVG, + * height: number, the viewport height of the SVG, + * viewBoxRect: {x, y, width, height}, the declared viewBox rect of the SVG, if exists, + * viewBoxTransform: the {scale, position} calculated by viewBox and viewport, is exists. + * } + */ +function parseSVG(xml, opt) { + var parser = new SVGParser(); + return parser.parse(xml, opt); +} + +/* +* 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. +*/ + +var storage = createHashMap(); + +// For minimize the code size of common echarts package, +// do not put too much logic in this module. + +var mapDataStorage = { + + // 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$1(records, function (record) { + var type = record.type; + type === 'geoJson' && (type = record.type = 'geoJSON'); + + var parse = parsers[type]; + + if (__DEV__) { + assert$1(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); + } + +}; + +/* +* 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. +*/ +var assert = assert$1; +var each = each$1; +var isFunction = isFunction$1; +var isObject = isObject$1; +var parseClassType = ComponentModel.parseClassType; + +var version = '4.6.0'; + +var dependencies = { + zrender: '4.2.0' +}; + +var TEST_FRAME_REMAIN_TIME = 1; + +var PRIORITY_PROCESSOR_FILTER = 1000; +var PRIORITY_PROCESSOR_SERIES_FILTER = 800; +var PRIORITY_PROCESSOR_DATASTACK = 900; +var PRIORITY_PROCESSOR_STATISTIC = 5000; + +var PRIORITY_VISUAL_LAYOUT = 1000; +var PRIORITY_VISUAL_PROGRESSIVE_LAYOUT = 1100; +var PRIORITY_VISUAL_GLOBAL = 2000; +var PRIORITY_VISUAL_CHART = 3000; +var PRIORITY_VISUAL_POST_CHART_LAYOUT = 3500; +var PRIORITY_VISUAL_COMPONENT = 4000; +// FIXME +// necessary? +var PRIORITY_VISUAL_BRUSH = 5000; + +var PRIORITY = { + PROCESSOR: { + FILTER: PRIORITY_PROCESSOR_FILTER, + SERIES_FILTER: PRIORITY_PROCESSOR_SERIES_FILTER, + STATISTIC: PRIORITY_PROCESSOR_STATISTIC + }, + VISUAL: { + LAYOUT: PRIORITY_VISUAL_LAYOUT, + PROGRESSIVE_LAYOUT: PRIORITY_VISUAL_PROGRESSIVE_LAYOUT, + GLOBAL: PRIORITY_VISUAL_GLOBAL, + CHART: PRIORITY_VISUAL_CHART, + POST_CHART_LAYOUT: PRIORITY_VISUAL_POST_CHART_LAYOUT, + COMPONENT: PRIORITY_VISUAL_COMPONENT, + BRUSH: PRIORITY_VISUAL_BRUSH + } +}; + +// Main process have three entries: `setOption`, `dispatchAction` and `resize`, +// where they must not be invoked nestedly, except the only case: invoke +// dispatchAction with updateMethod "none" in main process. +// This flag is used to carry out this rule. +// All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]). +var IN_MAIN_PROCESS = '__flagInMainProcess'; +var OPTION_UPDATED = '__optionUpdated'; +var ACTION_REG = /^[a-zA-Z0-9_]+$/; + + +function createRegisterEventWithLowercaseName(method, ignoreDisposed) { + return function (eventName, handler, context) { + if (!ignoreDisposed && this._disposed) { + disposedWarning(this.id); + return; + } + + // Event name is all lowercase + eventName = eventName && eventName.toLowerCase(); + Eventful.prototype[method].call(this, eventName, handler, context); + }; +} + +/** + * @module echarts~MessageCenter + */ +function MessageCenter() { + Eventful.call(this); +} +MessageCenter.prototype.on = createRegisterEventWithLowercaseName('on', true); +MessageCenter.prototype.off = createRegisterEventWithLowercaseName('off', true); +MessageCenter.prototype.one = createRegisterEventWithLowercaseName('one', true); +mixin(MessageCenter, Eventful); + +/** + * @module echarts~ECharts + */ +function ECharts(dom, theme$$1, opts) { + opts = opts || {}; + + // Get theme by name + if (typeof theme$$1 === 'string') { + theme$$1 = themeStorage[theme$$1]; + } + + /** + * @type {string} + */ + this.id; + + /** + * Group id + * @type {string} + */ + this.group; + + /** + * @type {HTMLElement} + * @private + */ + this._dom = dom; + + var defaultRenderer = 'canvas'; + if (__DEV__) { + defaultRenderer = ( + typeof window === 'undefined' ? global : window + ).__ECHARTS__DEFAULT__RENDERER__ || defaultRenderer; + } + + /** + * @type {module:zrender/ZRender} + * @private + */ + var zr = this._zr = init$1(dom, { + renderer: opts.renderer || defaultRenderer, + devicePixelRatio: opts.devicePixelRatio, + width: opts.width, + height: opts.height + }); + + /** + * Expect 60 fps. + * @type {Function} + * @private + */ + this._throttledZrFlush = throttle(bind(zr.flush, zr), 17); + + var theme$$1 = clone(theme$$1); + theme$$1 && backwardCompat(theme$$1, true); + /** + * @type {Object} + * @private + */ + this._theme = theme$$1; + + /** + * @type {Array.} + * @private + */ + this._chartsViews = []; + + /** + * @type {Object.} + * @private + */ + this._chartsMap = {}; + + /** + * @type {Array.} + * @private + */ + this._componentsViews = []; + + /** + * @type {Object.} + * @private + */ + this._componentsMap = {}; + + /** + * @type {module:echarts/CoordinateSystem} + * @private + */ + this._coordSysMgr = new CoordinateSystemManager(); + + /** + * @type {module:echarts/ExtensionAPI} + * @private + */ + var api = this._api = createExtensionAPI(this); + + // Sort on demand + function prioritySortFunc(a, b) { + return a.__prio - b.__prio; + } + sort(visualFuncs, prioritySortFunc); + sort(dataProcessorFuncs, prioritySortFunc); + + /** + * @type {module:echarts/stream/Scheduler} + */ + this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs); + + Eventful.call(this, this._ecEventProcessor = new EventProcessor()); + + /** + * @type {module:echarts~MessageCenter} + * @private + */ + this._messageCenter = new MessageCenter(); + + // Init mouse events + this._initEvents(); + + // In case some people write `window.onresize = chart.resize` + this.resize = bind(this.resize, this); + + // Can't dispatch action during rendering procedure + this._pendingActions = []; + + zr.animation.on('frame', this._onframe, this); + + bindRenderedEvent(zr, this); + + // ECharts instance can be used as value. + setAsPrimitive(this); +} + +var echartsProto = ECharts.prototype; + +echartsProto._onframe = function () { + if (this._disposed) { + return; + } + + var scheduler = this._scheduler; + + // Lazy update + if (this[OPTION_UPDATED]) { + var silent = this[OPTION_UPDATED].silent; + + this[IN_MAIN_PROCESS] = true; + + prepare(this); + updateMethods.update.call(this); + + this[IN_MAIN_PROCESS] = false; + + this[OPTION_UPDATED] = false; + + flushPendingActions.call(this, silent); + + triggerUpdatedEvent.call(this, silent); + } + // Avoid do both lazy update and progress in one frame. + else if (scheduler.unfinished) { + // Stream progress. + var remainTime = TEST_FRAME_REMAIN_TIME; + var ecModel = this._model; + var api = this._api; + scheduler.unfinished = false; + do { + var startTime = +new Date(); + + scheduler.performSeriesTasks(ecModel); + + // Currently dataProcessorFuncs do not check threshold. + scheduler.performDataProcessorTasks(ecModel); + + updateStreamModes(this, ecModel); + + // Do not update coordinate system here. Because that coord system update in + // each frame is not a good user experience. So we follow the rule that + // the extent of the coordinate system is determin in the first frame (the + // frame is executed immedietely after task reset. + // this._coordSysMgr.update(ecModel, api); + + // console.log('--- ec frame visual ---', remainTime); + scheduler.performVisualTasks(ecModel); + + renderSeries(this, this._model, api, 'remain'); + + remainTime -= (+new Date() - startTime); + } + while (remainTime > 0 && scheduler.unfinished); + + // Call flush explicitly for trigger finished event. + if (!scheduler.unfinished) { + this._zr.flush(); + } + // Else, zr flushing be ensue within the same frame, + // because zr flushing is after onframe event. + } +}; + +/** + * @return {HTMLElement} + */ +echartsProto.getDom = function () { + return this._dom; +}; + +/** + * @return {module:zrender~ZRender} + */ +echartsProto.getZr = function () { + return this._zr; +}; + +/** + * Usage: + * chart.setOption(option, notMerge, lazyUpdate); + * chart.setOption(option, { + * notMerge: ..., + * lazyUpdate: ..., + * silent: ... + * }); + * + * @param {Object} option + * @param {Object|boolean} [opts] opts or notMerge. + * @param {boolean} [opts.notMerge=false] + * @param {boolean} [opts.lazyUpdate=false] Useful when setOption frequently. + */ +echartsProto.setOption = function (option, notMerge, lazyUpdate) { + if (__DEV__) { + assert(!this[IN_MAIN_PROCESS], '`setOption` should not be called during main process.'); + } + if (this._disposed) { + disposedWarning(this.id); + return; + } + + var silent; + if (isObject(notMerge)) { + lazyUpdate = notMerge.lazyUpdate; + silent = notMerge.silent; + notMerge = notMerge.notMerge; + } + + this[IN_MAIN_PROCESS] = true; + + if (!this._model || notMerge) { + var optionManager = new OptionManager(this._api); + var theme$$1 = this._theme; + var ecModel = this._model = new GlobalModel(); + ecModel.scheduler = this._scheduler; + ecModel.init(null, null, theme$$1, optionManager); + } + + this._model.setOption(option, optionPreprocessorFuncs); + + if (lazyUpdate) { + this[OPTION_UPDATED] = {silent: silent}; + this[IN_MAIN_PROCESS] = false; + } + else { + prepare(this); + + updateMethods.update.call(this); + + // Ensure zr refresh sychronously, and then pixel in canvas can be + // fetched after `setOption`. + this._zr.flush(); + + this[OPTION_UPDATED] = false; + this[IN_MAIN_PROCESS] = false; + + flushPendingActions.call(this, silent); + triggerUpdatedEvent.call(this, silent); + } +}; + +/** + * @DEPRECATED + */ +echartsProto.setTheme = function () { + console.error('ECharts#setTheme() is DEPRECATED in ECharts 3.0'); +}; + +/** + * @return {module:echarts/model/Global} + */ +echartsProto.getModel = function () { + return this._model; +}; + +/** + * @return {Object} + */ +echartsProto.getOption = function () { + return this._model && this._model.getOption(); +}; + +/** + * @return {number} + */ +echartsProto.getWidth = function () { + return this._zr.getWidth(); +}; + +/** + * @return {number} + */ +echartsProto.getHeight = function () { + return this._zr.getHeight(); +}; + +/** + * @return {number} + */ +echartsProto.getDevicePixelRatio = function () { + return this._zr.painter.dpr || window.devicePixelRatio || 1; +}; + +/** + * Get canvas which has all thing rendered + * @param {Object} opts + * @param {string} [opts.backgroundColor] + * @return {string} + */ +echartsProto.getRenderedCanvas = function (opts) { + if (!env$1.canvasSupported) { + return; + } + opts = opts || {}; + opts.pixelRatio = opts.pixelRatio || 1; + opts.backgroundColor = opts.backgroundColor + || this._model.get('backgroundColor'); + var zr = this._zr; + // var list = zr.storage.getDisplayList(); + // Stop animations + // Never works before in init animation, so remove it. + // zrUtil.each(list, function (el) { + // el.stopAnimation(true); + // }); + return zr.painter.getRenderedCanvas(opts); +}; + +/** + * Get svg data url + * @return {string} + */ +echartsProto.getSvgDataUrl = function () { + if (!env$1.svgSupported) { + return; + } + + var zr = this._zr; + var list = zr.storage.getDisplayList(); + // Stop animations + each$1(list, function (el) { + el.stopAnimation(true); + }); + + return zr.painter.pathToDataUrl(); +}; + +/** + * @return {string} + * @param {Object} opts + * @param {string} [opts.type='png'] + * @param {string} [opts.pixelRatio=1] + * @param {string} [opts.backgroundColor] + * @param {string} [opts.excludeComponents] + */ +echartsProto.getDataURL = function (opts) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + opts = opts || {}; + var excludeComponents = opts.excludeComponents; + var ecModel = this._model; + var excludesComponentViews = []; + var self = this; + + each(excludeComponents, function (componentType) { + ecModel.eachComponent({ + mainType: componentType + }, function (component) { + var view = self._componentsMap[component.__viewId]; + if (!view.group.ignore) { + excludesComponentViews.push(view); + view.group.ignore = true; + } + }); + }); + + var url = this._zr.painter.getType() === 'svg' + ? this.getSvgDataUrl() + : this.getRenderedCanvas(opts).toDataURL( + 'image/' + (opts && opts.type || 'png') + ); + + each(excludesComponentViews, function (view) { + view.group.ignore = false; + }); + + return url; +}; + + +/** + * @return {string} + * @param {Object} opts + * @param {string} [opts.type='png'] + * @param {string} [opts.pixelRatio=1] + * @param {string} [opts.backgroundColor] + */ +echartsProto.getConnectedDataURL = function (opts) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + if (!env$1.canvasSupported) { + return; + } + var groupId = this.group; + var mathMin = Math.min; + var mathMax = Math.max; + var MAX_NUMBER = Infinity; + if (connectedGroups[groupId]) { + var left = MAX_NUMBER; + var top = MAX_NUMBER; + var right = -MAX_NUMBER; + var bottom = -MAX_NUMBER; + var canvasList = []; + var dpr = (opts && opts.pixelRatio) || 1; + + each$1(instances, function (chart, id) { + if (chart.group === groupId) { + var canvas = chart.getRenderedCanvas( + clone(opts) + ); + var boundingRect = chart.getDom().getBoundingClientRect(); + left = mathMin(boundingRect.left, left); + top = mathMin(boundingRect.top, top); + right = mathMax(boundingRect.right, right); + bottom = mathMax(boundingRect.bottom, bottom); + canvasList.push({ + dom: canvas, + left: boundingRect.left, + top: boundingRect.top + }); + } + }); + + left *= dpr; + top *= dpr; + right *= dpr; + bottom *= dpr; + var width = right - left; + var height = bottom - top; + var targetCanvas = createCanvas(); + targetCanvas.width = width; + targetCanvas.height = height; + var zr = init$1(targetCanvas); + + // Background between the charts + if (opts.connectedBackgroundColor) { + zr.add(new Rect({ + shape: { + x: 0, + y: 0, + width: width, + height: height + }, + style: { + fill: opts.connectedBackgroundColor + } + })); + } + + each(canvasList, function (item) { + var img = new ZImage({ + style: { + x: item.left * dpr - left, + y: item.top * dpr - top, + image: item.dom + } + }); + zr.add(img); + }); + zr.refreshImmediately(); + + return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png')); + } + else { + return this.getDataURL(opts); + } +}; + +/** + * Convert from logical coordinate system to pixel coordinate system. + * See CoordinateSystem#convertToPixel. + * @param {string|Object} finder + * If string, e.g., 'geo', means {geoIndex: 0}. + * If Object, could contain some of these properties below: + * { + * seriesIndex / seriesId / seriesName, + * geoIndex / geoId, geoName, + * bmapIndex / bmapId / bmapName, + * xAxisIndex / xAxisId / xAxisName, + * yAxisIndex / yAxisId / yAxisName, + * gridIndex / gridId / gridName, + * ... (can be extended) + * } + * @param {Array|number} value + * @return {Array|number} result + */ +echartsProto.convertToPixel = curry(doConvertPixel, 'convertToPixel'); + +/** + * Convert from pixel coordinate system to logical coordinate system. + * See CoordinateSystem#convertFromPixel. + * @param {string|Object} finder + * If string, e.g., 'geo', means {geoIndex: 0}. + * If Object, could contain some of these properties below: + * { + * seriesIndex / seriesId / seriesName, + * geoIndex / geoId / geoName, + * bmapIndex / bmapId / bmapName, + * xAxisIndex / xAxisId / xAxisName, + * yAxisIndex / yAxisId / yAxisName + * gridIndex / gridId / gridName, + * ... (can be extended) + * } + * @param {Array|number} value + * @return {Array|number} result + */ +echartsProto.convertFromPixel = curry(doConvertPixel, 'convertFromPixel'); + +function doConvertPixel(methodName, finder, value) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + var ecModel = this._model; + var coordSysList = this._coordSysMgr.getCoordinateSystems(); + var result; + + finder = parseFinder(ecModel, finder); + + for (var i = 0; i < coordSysList.length; i++) { + var coordSys = coordSysList[i]; + if (coordSys[methodName] + && (result = coordSys[methodName](ecModel, finder, value)) != null + ) { + return result; + } + } + + if (__DEV__) { + console.warn( + 'No coordinate system that supports ' + methodName + ' found by the given finder.' + ); + } +} + +/** + * Is the specified coordinate systems or components contain the given pixel point. + * @param {string|Object} finder + * If string, e.g., 'geo', means {geoIndex: 0}. + * If Object, could contain some of these properties below: + * { + * seriesIndex / seriesId / seriesName, + * geoIndex / geoId / geoName, + * bmapIndex / bmapId / bmapName, + * xAxisIndex / xAxisId / xAxisName, + * yAxisIndex / yAxisId / yAxisName, + * gridIndex / gridId / gridName, + * ... (can be extended) + * } + * @param {Array|number} value + * @return {boolean} result + */ +echartsProto.containPixel = function (finder, value) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + var ecModel = this._model; + var result; + + finder = parseFinder(ecModel, finder); + + each$1(finder, function (models, key) { + key.indexOf('Models') >= 0 && each$1(models, function (model) { + var coordSys = model.coordinateSystem; + if (coordSys && coordSys.containPoint) { + result |= !!coordSys.containPoint(value); + } + else if (key === 'seriesModels') { + var view = this._chartsMap[model.__viewId]; + if (view && view.containPoint) { + result |= view.containPoint(value, model); + } + else { + if (__DEV__) { + console.warn(key + ': ' + (view + ? 'The found component do not support containPoint.' + : 'No view mapping to the found component.' + )); + } + } + } + else { + if (__DEV__) { + console.warn(key + ': containPoint is not supported'); + } + } + }, this); + }, this); + + return !!result; +}; + +/** + * Get visual from series or data. + * @param {string|Object} finder + * If string, e.g., 'series', means {seriesIndex: 0}. + * If Object, could contain some of these properties below: + * { + * seriesIndex / seriesId / seriesName, + * dataIndex / dataIndexInside + * } + * If dataIndex is not specified, series visual will be fetched, + * but not data item visual. + * If all of seriesIndex, seriesId, seriesName are not specified, + * visual will be fetched from first series. + * @param {string} visualType 'color', 'symbol', 'symbolSize' + */ +echartsProto.getVisual = function (finder, visualType) { + var ecModel = this._model; + + finder = parseFinder(ecModel, finder, {defaultMainType: 'series'}); + + var seriesModel = finder.seriesModel; + + if (__DEV__) { + if (!seriesModel) { + console.warn('There is no specified seires model'); + } + } + + var data = seriesModel.getData(); + + var dataIndexInside = finder.hasOwnProperty('dataIndexInside') + ? finder.dataIndexInside + : finder.hasOwnProperty('dataIndex') + ? data.indexOfRawIndex(finder.dataIndex) + : null; + + return dataIndexInside != null + ? data.getItemVisual(dataIndexInside, visualType) + : data.getVisual(visualType); +}; + +/** + * Get view of corresponding component model + * @param {module:echarts/model/Component} componentModel + * @return {module:echarts/view/Component} + */ +echartsProto.getViewOfComponentModel = function (componentModel) { + return this._componentsMap[componentModel.__viewId]; +}; + +/** + * Get view of corresponding series model + * @param {module:echarts/model/Series} seriesModel + * @return {module:echarts/view/Chart} + */ +echartsProto.getViewOfSeriesModel = function (seriesModel) { + return this._chartsMap[seriesModel.__viewId]; +}; + +var updateMethods = { + + prepareAndUpdate: function (payload) { + prepare(this); + updateMethods.update.call(this, payload); + }, + + /** + * @param {Object} payload + * @private + */ + update: function (payload) { + // console.profile && console.profile('update'); + + var ecModel = this._model; + var api = this._api; + var zr = this._zr; + var coordSysMgr = this._coordSysMgr; + var scheduler = this._scheduler; + + // update before setOption + if (!ecModel) { + return; + } + + scheduler.restoreData(ecModel, payload); + + scheduler.performSeriesTasks(ecModel); + + // TODO + // Save total ecModel here for undo/redo (after restoring data and before processing data). + // Undo (restoration of total ecModel) can be carried out in 'action' or outside API call. + + // Create new coordinate system each update + // In LineView may save the old coordinate system and use it to get the orignal point + coordSysMgr.create(ecModel, api); + + scheduler.performDataProcessorTasks(ecModel, payload); + + // Current stream render is not supported in data process. So we can update + // stream modes after data processing, where the filtered data is used to + // deteming whether use progressive rendering. + updateStreamModes(this, ecModel); + + // We update stream modes before coordinate system updated, then the modes info + // can be fetched when coord sys updating (consider the barGrid extent fix). But + // the drawback is the full coord info can not be fetched. Fortunately this full + // coord is not requied in stream mode updater currently. + coordSysMgr.update(ecModel, api); + + clearColorPalette(ecModel); + scheduler.performVisualTasks(ecModel, payload); + + render(this, ecModel, api, payload); + + // Set background + var backgroundColor = ecModel.get('backgroundColor') || 'transparent'; + + // In IE8 + if (!env$1.canvasSupported) { + var colorArr = parse(backgroundColor); + backgroundColor = stringify(colorArr, 'rgb'); + if (colorArr[3] === 0) { + backgroundColor = 'transparent'; + } + } + else { + zr.setBackgroundColor(backgroundColor); + } + + performPostUpdateFuncs(ecModel, api); + + // console.profile && console.profileEnd('update'); + }, + + /** + * @param {Object} payload + * @private + */ + updateTransform: function (payload) { + var ecModel = this._model; + var ecIns = this; + var api = this._api; + + // update before setOption + if (!ecModel) { + return; + } + + // ChartView.markUpdateMethod(payload, 'updateTransform'); + + var componentDirtyList = []; + ecModel.eachComponent(function (componentType, componentModel) { + var componentView = ecIns.getViewOfComponentModel(componentModel); + if (componentView && componentView.__alive) { + if (componentView.updateTransform) { + var result = componentView.updateTransform(componentModel, ecModel, api, payload); + result && result.update && componentDirtyList.push(componentView); + } + else { + componentDirtyList.push(componentView); + } + } + }); + + var seriesDirtyMap = createHashMap(); + ecModel.eachSeries(function (seriesModel) { + var chartView = ecIns._chartsMap[seriesModel.__viewId]; + if (chartView.updateTransform) { + var result = chartView.updateTransform(seriesModel, ecModel, api, payload); + result && result.update && seriesDirtyMap.set(seriesModel.uid, 1); + } + else { + seriesDirtyMap.set(seriesModel.uid, 1); + } + }); + + clearColorPalette(ecModel); + // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. + // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true); + this._scheduler.performVisualTasks( + ecModel, payload, {setDirty: true, dirtyMap: seriesDirtyMap} + ); + + // Currently, not call render of components. Geo render cost a lot. + // renderComponents(ecIns, ecModel, api, payload, componentDirtyList); + renderSeries(ecIns, ecModel, api, payload, seriesDirtyMap); + + performPostUpdateFuncs(ecModel, this._api); + }, + + /** + * @param {Object} payload + * @private + */ + updateView: function (payload) { + var ecModel = this._model; + + // update before setOption + if (!ecModel) { + return; + } + + Chart.markUpdateMethod(payload, 'updateView'); + + clearColorPalette(ecModel); + + // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. + this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true}); + + render(this, this._model, this._api, payload); + + performPostUpdateFuncs(ecModel, this._api); + }, + + /** + * @param {Object} payload + * @private + */ + updateVisual: function (payload) { + updateMethods.update.call(this, payload); + + // var ecModel = this._model; + + // // update before setOption + // if (!ecModel) { + // return; + // } + + // ChartView.markUpdateMethod(payload, 'updateVisual'); + + // clearColorPalette(ecModel); + + // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. + // this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true}); + + // render(this, this._model, this._api, payload); + + // performPostUpdateFuncs(ecModel, this._api); + }, + + /** + * @param {Object} payload + * @private + */ + updateLayout: function (payload) { + updateMethods.update.call(this, payload); + + // var ecModel = this._model; + + // // update before setOption + // if (!ecModel) { + // return; + // } + + // ChartView.markUpdateMethod(payload, 'updateLayout'); + + // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. + // // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true); + // this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true}); + + // render(this, this._model, this._api, payload); + + // performPostUpdateFuncs(ecModel, this._api); + } +}; + +function prepare(ecIns) { + var ecModel = ecIns._model; + var scheduler = ecIns._scheduler; + + scheduler.restorePipelines(ecModel); + + scheduler.prepareStageTasks(); + + prepareView(ecIns, 'component', ecModel, scheduler); + + prepareView(ecIns, 'chart', ecModel, scheduler); + + scheduler.plan(); +} + +/** + * @private + */ +function updateDirectly(ecIns, method, payload, mainType, subType) { + var ecModel = ecIns._model; + + // broadcast + if (!mainType) { + // FIXME + // Chart will not be update directly here, except set dirty. + // But there is no such scenario now. + each(ecIns._componentsViews.concat(ecIns._chartsViews), callView); + return; + } + + var query = {}; + query[mainType + 'Id'] = payload[mainType + 'Id']; + query[mainType + 'Index'] = payload[mainType + 'Index']; + query[mainType + 'Name'] = payload[mainType + 'Name']; + + var condition = {mainType: mainType, query: query}; + subType && (condition.subType = subType); // subType may be '' by parseClassType; + + var excludeSeriesId = payload.excludeSeriesId; + if (excludeSeriesId != null) { + excludeSeriesId = createHashMap(normalizeToArray(excludeSeriesId)); + } + + // If dispatchAction before setOption, do nothing. + ecModel && ecModel.eachComponent(condition, function (model) { + if (!excludeSeriesId || excludeSeriesId.get(model.id) == null) { + callView(ecIns[ + mainType === 'series' ? '_chartsMap' : '_componentsMap' + ][model.__viewId]); + } + }, ecIns); + + function callView(view) { + view && view.__alive && view[method] && view[method]( + view.__model, ecModel, ecIns._api, payload + ); + } +} + +/** + * Resize the chart + * @param {Object} opts + * @param {number} [opts.width] Can be 'auto' (the same as null/undefined) + * @param {number} [opts.height] Can be 'auto' (the same as null/undefined) + * @param {boolean} [opts.silent=false] + */ +echartsProto.resize = function (opts) { + if (__DEV__) { + assert(!this[IN_MAIN_PROCESS], '`resize` should not be called during main process.'); + } + if (this._disposed) { + disposedWarning(this.id); + return; + } + + this._zr.resize(opts); + + var ecModel = this._model; + + // Resize loading effect + this._loadingFX && this._loadingFX.resize(); + + if (!ecModel) { + return; + } + + var optionChanged = ecModel.resetOption('media'); + + var silent = opts && opts.silent; + + this[IN_MAIN_PROCESS] = true; + + optionChanged && prepare(this); + updateMethods.update.call(this); + + this[IN_MAIN_PROCESS] = false; + + flushPendingActions.call(this, silent); + + triggerUpdatedEvent.call(this, silent); +}; + +function updateStreamModes(ecIns, ecModel) { + var chartsMap = ecIns._chartsMap; + var scheduler = ecIns._scheduler; + ecModel.eachSeries(function (seriesModel) { + scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]); + }); +} + +/** + * Show loading effect + * @param {string} [name='default'] + * @param {Object} [cfg] + */ +echartsProto.showLoading = function (name, cfg) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + if (isObject(name)) { + cfg = name; + name = ''; + } + name = name || 'default'; + + this.hideLoading(); + if (!loadingEffects[name]) { + if (__DEV__) { + console.warn('Loading effects ' + name + ' not exists.'); + } + return; + } + var el = loadingEffects[name](this._api, cfg); + var zr = this._zr; + this._loadingFX = el; + + zr.add(el); +}; + +/** + * Hide loading effect + */ +echartsProto.hideLoading = function () { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + this._loadingFX && this._zr.remove(this._loadingFX); + this._loadingFX = null; +}; + +/** + * @param {Object} eventObj + * @return {Object} + */ +echartsProto.makeActionFromEvent = function (eventObj) { + var payload = extend({}, eventObj); + payload.type = eventActionMap[eventObj.type]; + return payload; +}; + +/** + * @pubilc + * @param {Object} payload + * @param {string} [payload.type] Action type + * @param {Object|boolean} [opt] If pass boolean, means opt.silent + * @param {boolean} [opt.silent=false] Whether trigger events. + * @param {boolean} [opt.flush=undefined] + * true: Flush immediately, and then pixel in canvas can be fetched + * immediately. Caution: it might affect performance. + * false: Not flush. + * undefined: Auto decide whether perform flush. + */ +echartsProto.dispatchAction = function (payload, opt) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + if (!isObject(opt)) { + opt = {silent: !!opt}; + } + + if (!actions[payload.type]) { + return; + } + + // Avoid dispatch action before setOption. Especially in `connect`. + if (!this._model) { + return; + } + + // May dispatchAction in rendering procedure + if (this[IN_MAIN_PROCESS]) { + this._pendingActions.push(payload); + return; + } + + doDispatchAction.call(this, payload, opt.silent); + + if (opt.flush) { + this._zr.flush(true); + } + else if (opt.flush !== false && env$1.browser.weChat) { + // In WeChat embeded browser, `requestAnimationFrame` and `setInterval` + // hang when sliding page (on touch event), which cause that zr does not + // refresh util user interaction finished, which is not expected. + // But `dispatchAction` may be called too frequently when pan on touch + // screen, which impacts performance if do not throttle them. + this._throttledZrFlush(); + } + + flushPendingActions.call(this, opt.silent); + + triggerUpdatedEvent.call(this, opt.silent); +}; + +function doDispatchAction(payload, silent) { + var payloadType = payload.type; + var escapeConnect = payload.escapeConnect; + var actionWrap = actions[payloadType]; + var actionInfo = actionWrap.actionInfo; + + var cptType = (actionInfo.update || 'update').split(':'); + var updateMethod = cptType.pop(); + cptType = cptType[0] != null && parseClassType(cptType[0]); + + this[IN_MAIN_PROCESS] = true; + + var payloads = [payload]; + var batched = false; + // Batch action + if (payload.batch) { + batched = true; + payloads = map(payload.batch, function (item) { + item = defaults(extend({}, item), payload); + item.batch = null; + return item; + }); + } + + var eventObjBatch = []; + var eventObj; + var isHighDown = payloadType === 'highlight' || payloadType === 'downplay'; + + each(payloads, function (batchItem) { + // Action can specify the event by return it. + eventObj = actionWrap.action(batchItem, this._model, this._api); + // Emit event outside + eventObj = eventObj || extend({}, batchItem); + // Convert type to eventType + eventObj.type = actionInfo.event || eventObj.type; + eventObjBatch.push(eventObj); + + // light update does not perform data process, layout and visual. + if (isHighDown) { + // method, payload, mainType, subType + updateDirectly(this, updateMethod, batchItem, 'series'); + } + else if (cptType) { + updateDirectly(this, updateMethod, batchItem, cptType.main, cptType.sub); + } + }, this); + + if (updateMethod !== 'none' && !isHighDown && !cptType) { + // Still dirty + if (this[OPTION_UPDATED]) { + // FIXME Pass payload ? + prepare(this); + updateMethods.update.call(this, payload); + this[OPTION_UPDATED] = false; + } + else { + updateMethods[updateMethod].call(this, payload); + } + } + + // Follow the rule of action batch + if (batched) { + eventObj = { + type: actionInfo.event || payloadType, + escapeConnect: escapeConnect, + batch: eventObjBatch + }; + } + else { + eventObj = eventObjBatch[0]; + } + + this[IN_MAIN_PROCESS] = false; + + !silent && this._messageCenter.trigger(eventObj.type, eventObj); +} + +function flushPendingActions(silent) { + var pendingActions = this._pendingActions; + while (pendingActions.length) { + var payload = pendingActions.shift(); + doDispatchAction.call(this, payload, silent); + } +} + +function triggerUpdatedEvent(silent) { + !silent && this.trigger('updated'); +} + +/** + * Event `rendered` is triggered when zr + * rendered. It is useful for realtime + * snapshot (reflect animation). + * + * Event `finished` is triggered when: + * (1) zrender rendering finished. + * (2) initial animation finished. + * (3) progressive rendering finished. + * (4) no pending action. + * (5) no delayed setOption needs to be processed. + */ +function bindRenderedEvent(zr, ecIns) { + zr.on('rendered', function () { + + ecIns.trigger('rendered'); + + // The `finished` event should not be triggered repeatly, + // so it should only be triggered when rendering indeed happend + // in zrender. (Consider the case that dipatchAction is keep + // triggering when mouse move). + if ( + // Although zr is dirty if initial animation is not finished + // and this checking is called on frame, we also check + // animation finished for robustness. + zr.animation.isFinished() + && !ecIns[OPTION_UPDATED] + && !ecIns._scheduler.unfinished + && !ecIns._pendingActions.length + ) { + ecIns.trigger('finished'); + } + }); +} + +/** + * @param {Object} params + * @param {number} params.seriesIndex + * @param {Array|TypedArray} params.data + */ +echartsProto.appendData = function (params) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + + var seriesIndex = params.seriesIndex; + var ecModel = this.getModel(); + var seriesModel = ecModel.getSeriesByIndex(seriesIndex); + + if (__DEV__) { + assert(params.data && seriesModel); + } + + seriesModel.appendData(params); + + // Note: `appendData` does not support that update extent of coordinate + // system, util some scenario require that. In the expected usage of + // `appendData`, the initial extent of coordinate system should better + // be fixed by axis `min`/`max` setting or initial data, otherwise if + // the extent changed while `appendData`, the location of the painted + // graphic elements have to be changed, which make the usage of + // `appendData` meaningless. + + this._scheduler.unfinished = true; +}; + +/** + * Register event + * @method + */ +echartsProto.on = createRegisterEventWithLowercaseName('on', false); +echartsProto.off = createRegisterEventWithLowercaseName('off', false); +echartsProto.one = createRegisterEventWithLowercaseName('one', false); + +/** + * Prepare view instances of charts and components + * @param {module:echarts/model/Global} ecModel + * @private + */ +function prepareView(ecIns, type, ecModel, scheduler) { + var isComponent = type === 'component'; + var viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews; + var viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap; + var zr = ecIns._zr; + var api = ecIns._api; + + for (var i = 0; i < viewList.length; i++) { + viewList[i].__alive = false; + } + + isComponent + ? ecModel.eachComponent(function (componentType, model) { + componentType !== 'series' && doPrepare(model); + }) + : ecModel.eachSeries(doPrepare); + + function doPrepare(model) { + // Consider: id same and type changed. + var viewId = '_ec_' + model.id + '_' + model.type; + var view = viewMap[viewId]; + if (!view) { + var classType = parseClassType(model.type); + var Clazz = isComponent + ? Component$1.getClass(classType.main, classType.sub) + : Chart.getClass(classType.sub); + + if (__DEV__) { + assert(Clazz, classType.sub + ' does not exist.'); + } + + view = new Clazz(); + view.init(ecModel, api); + viewMap[viewId] = view; + viewList.push(view); + zr.add(view.group); + } + + model.__viewId = view.__id = viewId; + view.__alive = true; + view.__model = model; + view.group.__ecComponentInfo = { + mainType: model.mainType, + index: model.componentIndex + }; + !isComponent && scheduler.prepareView(view, model, ecModel, api); + } + + for (var i = 0; i < viewList.length;) { + var view = viewList[i]; + if (!view.__alive) { + !isComponent && view.renderTask.dispose(); + zr.remove(view.group); + view.dispose(ecModel, api); + viewList.splice(i, 1); + delete viewMap[view.__id]; + view.__id = view.group.__ecComponentInfo = null; + } + else { + i++; + } + } +} + +// /** +// * Encode visual infomation from data after data processing +// * +// * @param {module:echarts/model/Global} ecModel +// * @param {object} layout +// * @param {boolean} [layoutFilter] `true`: only layout, +// * `false`: only not layout, +// * `null`/`undefined`: all. +// * @param {string} taskBaseTag +// * @private +// */ +// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) { +// each(visualFuncs, function (visual, index) { +// var isLayout = visual.isLayout; +// if (layoutFilter == null +// || (layoutFilter === false && !isLayout) +// || (layoutFilter === true && isLayout) +// ) { +// visual.func(ecModel, api, payload); +// } +// }); +// } + +function clearColorPalette(ecModel) { + ecModel.clearColorPalette(); + ecModel.eachSeries(function (seriesModel) { + seriesModel.clearColorPalette(); + }); +} + +function render(ecIns, ecModel, api, payload) { + + renderComponents(ecIns, ecModel, api, payload); + + each(ecIns._chartsViews, function (chart) { + chart.__alive = false; + }); + + renderSeries(ecIns, ecModel, api, payload); + + // Remove groups of unrendered charts + each(ecIns._chartsViews, function (chart) { + if (!chart.__alive) { + chart.remove(ecModel, api); + } + }); +} + +function renderComponents(ecIns, ecModel, api, payload, dirtyList) { + each(dirtyList || ecIns._componentsViews, function (componentView) { + var componentModel = componentView.__model; + componentView.render(componentModel, ecModel, api, payload); + + updateZ(componentModel, componentView); + }); +} + +/** + * Render each chart and component + * @private + */ +function renderSeries(ecIns, ecModel, api, payload, dirtyMap) { + // Render all charts + var scheduler = ecIns._scheduler; + var unfinished; + ecModel.eachSeries(function (seriesModel) { + var chartView = ecIns._chartsMap[seriesModel.__viewId]; + chartView.__alive = true; + + var renderTask = chartView.renderTask; + scheduler.updatePayload(renderTask, payload); + + if (dirtyMap && dirtyMap.get(seriesModel.uid)) { + renderTask.dirty(); + } + + unfinished |= renderTask.perform(scheduler.getPerformArgs(renderTask)); + + chartView.group.silent = !!seriesModel.get('silent'); + + updateZ(seriesModel, chartView); + + updateBlend(seriesModel, chartView); + }); + scheduler.unfinished |= unfinished; + + // If use hover layer + updateHoverLayerStatus(ecIns, ecModel); + + // Add aria + aria(ecIns._zr.dom, ecModel); +} + +function performPostUpdateFuncs(ecModel, api) { + each(postUpdateFuncs, function (func) { + func(ecModel, api); + }); +} + + +var MOUSE_EVENT_NAMES = [ + 'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove', + 'mousedown', 'mouseup', 'globalout', 'contextmenu' +]; + +/** + * @private + */ +echartsProto._initEvents = function () { + each(MOUSE_EVENT_NAMES, function (eveName) { + var handler = function (e) { + var ecModel = this.getModel(); + var el = e.target; + var params; + var isGlobalOut = eveName === 'globalout'; + + // no e.target when 'globalout'. + if (isGlobalOut) { + params = {}; + } + else if (el && el.dataIndex != null) { + var dataModel = el.dataModel || ecModel.getSeriesByIndex(el.seriesIndex); + params = dataModel && dataModel.getDataParams(el.dataIndex, el.dataType, el) || {}; + } + // If element has custom eventData of components + else if (el && el.eventData) { + params = extend({}, el.eventData); + } + + // Contract: if params prepared in mouse event, + // these properties must be specified: + // { + // componentType: string (component main type) + // componentIndex: number + // } + // Otherwise event query can not work. + + if (params) { + var componentType = params.componentType; + var componentIndex = params.componentIndex; + // Special handling for historic reason: when trigger by + // markLine/markPoint/markArea, the componentType is + // 'markLine'/'markPoint'/'markArea', but we should better + // enable them to be queried by seriesIndex, since their + // option is set in each series. + if (componentType === 'markLine' + || componentType === 'markPoint' + || componentType === 'markArea' + ) { + componentType = 'series'; + componentIndex = params.seriesIndex; + } + var model = componentType && componentIndex != null + && ecModel.getComponent(componentType, componentIndex); + var view = model && this[ + model.mainType === 'series' ? '_chartsMap' : '_componentsMap' + ][model.__viewId]; + + if (__DEV__) { + // `event.componentType` and `event[componentTpype + 'Index']` must not + // be missed, otherwise there is no way to distinguish source component. + // See `dataFormat.getDataParams`. + if (!isGlobalOut && !(model && view)) { + console.warn('model or view can not be found by params'); + } + } + + params.event = e; + params.type = eveName; + + this._ecEventProcessor.eventInfo = { + targetEl: el, + packedEvent: params, + model: model, + view: view + }; + + this.trigger(eveName, params); + } + }; + // Consider that some component (like tooltip, brush, ...) + // register zr event handler, but user event handler might + // do anything, such as call `setOption` or `dispatchAction`, + // which probably update any of the content and probably + // cause problem if it is called previous other inner handlers. + handler.zrEventfulCallAtLast = true; + this._zr.on(eveName, handler, this); + }, this); + + each(eventActionMap, function (actionType, eventType) { + this._messageCenter.on(eventType, function (event) { + this.trigger(eventType, event); + }, this); + }, this); +}; + +/** + * @return {boolean} + */ +echartsProto.isDisposed = function () { + return this._disposed; +}; + +/** + * Clear + */ +echartsProto.clear = function () { + if (this._disposed) { + disposedWarning(this.id); + return; + } + this.setOption({ series: [] }, true); +}; + +/** + * Dispose instance + */ +echartsProto.dispose = function () { + if (this._disposed) { + disposedWarning(this.id); + return; + } + this._disposed = true; + + setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, ''); + + var api = this._api; + var ecModel = this._model; + + each(this._componentsViews, function (component) { + component.dispose(ecModel, api); + }); + each(this._chartsViews, function (chart) { + chart.dispose(ecModel, api); + }); + + // Dispose after all views disposed + this._zr.dispose(); + + delete instances[this.id]; +}; + +mixin(ECharts, Eventful); + +function disposedWarning(id) { + if (__DEV__) { + console.warn('Instance ' + id + ' has been disposed'); + } +} + +function updateHoverLayerStatus(ecIns, ecModel) { + var zr = ecIns._zr; + var storage = zr.storage; + var elCount = 0; + + storage.traverse(function (el) { + elCount++; + }); + + if (elCount > ecModel.get('hoverLayerThreshold') && !env$1.node) { + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.preventUsingHoverLayer) { + return; + } + var chartView = ecIns._chartsMap[seriesModel.__viewId]; + if (chartView.__alive) { + chartView.group.traverse(function (el) { + // Don't switch back. + el.useHoverLayer = true; + }); + } + }); + } +} + +/** + * Update chart progressive and blend. + * @param {module:echarts/model/Series|module:echarts/model/Component} model + * @param {module:echarts/view/Component|module:echarts/view/Chart} view + */ +function updateBlend(seriesModel, chartView) { + var blendMode = seriesModel.get('blendMode') || null; + if (__DEV__) { + if (!env$1.canvasSupported && blendMode && blendMode !== 'source-over') { + console.warn('Only canvas support blendMode'); + } + } + chartView.group.traverse(function (el) { + // FIXME marker and other components + if (!el.isGroup) { + // Only set if blendMode is changed. In case element is incremental and don't wan't to rerender. + if (el.style.blend !== blendMode) { + el.setStyle('blend', blendMode); + } + } + if (el.eachPendingDisplayable) { + el.eachPendingDisplayable(function (displayable) { + displayable.setStyle('blend', blendMode); + }); + } + }); +} + +/** + * @param {module:echarts/model/Series|module:echarts/model/Component} model + * @param {module:echarts/view/Component|module:echarts/view/Chart} view + */ +function updateZ(model, view) { + var z = model.get('z'); + var zlevel = model.get('zlevel'); + // Set z and zlevel + view.group.traverse(function (el) { + if (el.type !== 'group') { + z != null && (el.z = z); + zlevel != null && (el.zlevel = zlevel); + } + }); +} + +function createExtensionAPI(ecInstance) { + var coordSysMgr = ecInstance._coordSysMgr; + return extend(new ExtensionAPI(ecInstance), { + // Inject methods + getCoordinateSystems: bind( + coordSysMgr.getCoordinateSystems, coordSysMgr + ), + getComponentByElement: function (el) { + while (el) { + var modelInfo = el.__ecComponentInfo; + if (modelInfo != null) { + return ecInstance._model.getComponent(modelInfo.mainType, modelInfo.index); + } + el = el.parent; + } + } + }); +} + + +/** + * @class + * Usage of query: + * `chart.on('click', query, handler);` + * The `query` can be: + * + The component type query string, only `mainType` or `mainType.subType`, + * like: 'xAxis', 'series', 'xAxis.category' or 'series.line'. + * + The component query object, like: + * `{seriesIndex: 2}`, `{seriesName: 'xx'}`, `{seriesId: 'some'}`, + * `{xAxisIndex: 2}`, `{xAxisName: 'xx'}`, `{xAxisId: 'some'}`. + * + The data query object, like: + * `{dataIndex: 123}`, `{dataType: 'link'}`, `{name: 'some'}`. + * + The other query object (cmponent customized query), like: + * `{element: 'some'}` (only available in custom series). + * + * Caveat: If a prop in the `query` object is `null/undefined`, it is the + * same as there is no such prop in the `query` object. + */ +function EventProcessor() { + // These info required: targetEl, packedEvent, model, view + this.eventInfo; +} +EventProcessor.prototype = { + constructor: EventProcessor, + + normalizeQuery: function (query) { + var cptQuery = {}; + var dataQuery = {}; + var otherQuery = {}; + + // `query` is `mainType` or `mainType.subType` of component. + if (isString(query)) { + var condCptType = parseClassType(query); + // `.main` and `.sub` may be ''. + cptQuery.mainType = condCptType.main || null; + cptQuery.subType = condCptType.sub || null; + } + // `query` is an object, convert to {mainType, index, name, id}. + else { + // `xxxIndex`, `xxxName`, `xxxId`, `name`, `dataIndex`, `dataType` is reserved, + // can not be used in `compomentModel.filterForExposedEvent`. + var suffixes = ['Index', 'Name', 'Id']; + var dataKeys = {name: 1, dataIndex: 1, dataType: 1}; + each$1(query, function (val, key) { + var reserved = false; + for (var i = 0; i < suffixes.length; i++) { + var propSuffix = suffixes[i]; + var suffixPos = key.lastIndexOf(propSuffix); + if (suffixPos > 0 && suffixPos === key.length - propSuffix.length) { + var mainType = key.slice(0, suffixPos); + // Consider `dataIndex`. + if (mainType !== 'data') { + cptQuery.mainType = mainType; + cptQuery[propSuffix.toLowerCase()] = val; + reserved = true; + } + } + } + if (dataKeys.hasOwnProperty(key)) { + dataQuery[key] = val; + reserved = true; + } + if (!reserved) { + otherQuery[key] = val; + } + }); + } + + return { + cptQuery: cptQuery, + dataQuery: dataQuery, + otherQuery: otherQuery + }; + }, + + filter: function (eventType, query, args) { + // They should be assigned before each trigger call. + var eventInfo = this.eventInfo; + + if (!eventInfo) { + return true; + } + + var targetEl = eventInfo.targetEl; + var packedEvent = eventInfo.packedEvent; + var model = eventInfo.model; + var view = eventInfo.view; + + // For event like 'globalout'. + if (!model || !view) { + return true; + } + + var cptQuery = query.cptQuery; + var dataQuery = query.dataQuery; + + return check(cptQuery, model, 'mainType') + && check(cptQuery, model, 'subType') + && check(cptQuery, model, 'index', 'componentIndex') + && check(cptQuery, model, 'name') + && check(cptQuery, model, 'id') + && check(dataQuery, packedEvent, 'name') + && check(dataQuery, packedEvent, 'dataIndex') + && check(dataQuery, packedEvent, 'dataType') + && (!view.filterForExposedEvent || view.filterForExposedEvent( + eventType, query.otherQuery, targetEl, packedEvent + )); + + function check(query, host, prop, propOnHost) { + return query[prop] == null || host[propOnHost || prop] === query[prop]; + } + }, + + afterTrigger: function () { + // Make sure the eventInfo wont be used in next trigger. + this.eventInfo = null; + } +}; + + +/** + * @type {Object} key: actionType. + * @inner + */ +var actions = {}; + +/** + * Map eventType to actionType + * @type {Object} + */ +var eventActionMap = {}; + +/** + * Data processor functions of each stage + * @type {Array.>} + * @inner + */ +var dataProcessorFuncs = []; + +/** + * @type {Array.} + * @inner + */ +var optionPreprocessorFuncs = []; + +/** + * @type {Array.} + * @inner + */ +var postUpdateFuncs = []; + +/** + * Visual encoding functions of each stage + * @type {Array.>} + */ +var visualFuncs = []; + +/** + * Theme storage + * @type {Object.} + */ +var themeStorage = {}; +/** + * Loading effects + */ +var loadingEffects = {}; + +var instances = {}; +var connectedGroups = {}; + +var idBase = new Date() - 0; +var groupIdBase = new Date() - 0; +var DOM_ATTRIBUTE_KEY = '_echarts_instance_'; + +function enableConnect(chart) { + var STATUS_PENDING = 0; + var STATUS_UPDATING = 1; + var STATUS_UPDATED = 2; + var STATUS_KEY = '__connectUpdateStatus'; + + function updateConnectedChartsStatus(charts, status) { + for (var i = 0; i < charts.length; i++) { + var otherChart = charts[i]; + otherChart[STATUS_KEY] = status; + } + } + + each(eventActionMap, function (actionType, eventType) { + chart._messageCenter.on(eventType, function (event) { + if (connectedGroups[chart.group] && chart[STATUS_KEY] !== STATUS_PENDING) { + if (event && event.escapeConnect) { + return; + } + + var action = chart.makeActionFromEvent(event); + var otherCharts = []; + + each(instances, function (otherChart) { + if (otherChart !== chart && otherChart.group === chart.group) { + otherCharts.push(otherChart); + } + }); + + updateConnectedChartsStatus(otherCharts, STATUS_PENDING); + each(otherCharts, function (otherChart) { + if (otherChart[STATUS_KEY] !== STATUS_UPDATING) { + otherChart.dispatchAction(action); + } + }); + updateConnectedChartsStatus(otherCharts, STATUS_UPDATED); + } + }); + }); +} + +/** + * @param {HTMLElement} dom + * @param {Object} [theme] + * @param {Object} opts + * @param {number} [opts.devicePixelRatio] Use window.devicePixelRatio by default + * @param {string} [opts.renderer] Can choose 'canvas' or 'svg' to render the chart. + * @param {number} [opts.width] Use clientWidth of the input `dom` by default. + * Can be 'auto' (the same as null/undefined) + * @param {number} [opts.height] Use clientHeight of the input `dom` by default. + * Can be 'auto' (the same as null/undefined) + */ +function init(dom, theme$$1, opts) { + if (__DEV__) { + // Check version + if ((version$1.replace('.', '') - 0) < (dependencies.zrender.replace('.', '') - 0)) { + throw new Error( + 'zrender/src ' + version$1 + + ' is too old for ECharts ' + version + + '. Current version need ZRender ' + + dependencies.zrender + '+' + ); + } + + if (!dom) { + throw new Error('Initialize failed: invalid dom.'); + } + } + + var existInstance = getInstanceByDom(dom); + if (existInstance) { + if (__DEV__) { + console.warn('There is a chart instance already initialized on the dom.'); + } + return existInstance; + } + + if (__DEV__) { + if (isDom(dom) + && dom.nodeName.toUpperCase() !== 'CANVAS' + && ( + (!dom.clientWidth && (!opts || opts.width == null)) + || (!dom.clientHeight && (!opts || opts.height == null)) + ) + ) { + console.warn('Can\'t get DOM width or height. Please check ' + + 'dom.clientWidth and dom.clientHeight. They should not be 0.' + + 'For example, you may need to call this in the callback ' + + 'of window.onload.'); + } + } + + var chart = new ECharts(dom, theme$$1, opts); + chart.id = 'ec_' + idBase++; + instances[chart.id] = chart; + + setAttribute(dom, DOM_ATTRIBUTE_KEY, chart.id); + + enableConnect(chart); + + return chart; +} + +/** + * @return {string|Array.} groupId + */ +function connect(groupId) { + // Is array of charts + if (isArray(groupId)) { + var charts = groupId; + groupId = null; + // If any chart has group + each(charts, function (chart) { + if (chart.group != null) { + groupId = chart.group; + } + }); + groupId = groupId || ('g_' + groupIdBase++); + each(charts, function (chart) { + chart.group = groupId; + }); + } + connectedGroups[groupId] = true; + return groupId; +} + +/** + * @DEPRECATED + * @return {string} groupId + */ +function disConnect(groupId) { + connectedGroups[groupId] = false; +} + +/** + * @return {string} groupId + */ +var disconnect = disConnect; + +/** + * Dispose a chart instance + * @param {module:echarts~ECharts|HTMLDomElement|string} chart + */ +function dispose(chart) { + if (typeof chart === 'string') { + chart = instances[chart]; + } + else if (!(chart instanceof ECharts)) { + // Try to treat as dom + chart = getInstanceByDom(chart); + } + if ((chart instanceof ECharts) && !chart.isDisposed()) { + chart.dispose(); + } +} + +/** + * @param {HTMLElement} dom + * @return {echarts~ECharts} + */ +function getInstanceByDom(dom) { + return instances[getAttribute(dom, DOM_ATTRIBUTE_KEY)]; +} + +/** + * @param {string} key + * @return {echarts~ECharts} + */ +function getInstanceById(key) { + return instances[key]; +} + +/** + * Register theme + */ +function registerTheme(name, theme$$1) { + themeStorage[name] = theme$$1; +} + +/** + * Register option preprocessor + * @param {Function} preprocessorFunc + */ +function registerPreprocessor(preprocessorFunc) { + optionPreprocessorFuncs.push(preprocessorFunc); +} + +/** + * @param {number} [priority=1000] + * @param {Object|Function} processor + */ +function registerProcessor(priority, processor) { + normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_FILTER); +} + +/** + * Register postUpdater + * @param {Function} postUpdateFunc + */ +function registerPostUpdate(postUpdateFunc) { + postUpdateFuncs.push(postUpdateFunc); +} + +/** + * Usage: + * registerAction('someAction', 'someEvent', function () { ... }); + * registerAction('someAction', function () { ... }); + * registerAction( + * {type: 'someAction', event: 'someEvent', update: 'updateView'}, + * function () { ... } + * ); + * + * @param {(string|Object)} actionInfo + * @param {string} actionInfo.type + * @param {string} [actionInfo.event] + * @param {string} [actionInfo.update] + * @param {string} [eventName] + * @param {Function} action + */ +function registerAction(actionInfo, eventName, action) { + if (typeof eventName === 'function') { + action = eventName; + eventName = ''; + } + var actionType = isObject(actionInfo) + ? actionInfo.type + : ([actionInfo, actionInfo = { + event: eventName + }][0]); + + // Event name is all lowercase + actionInfo.event = (actionInfo.event || actionType).toLowerCase(); + eventName = actionInfo.event; + + // Validate action type and event name. + assert(ACTION_REG.test(actionType) && ACTION_REG.test(eventName)); + + if (!actions[actionType]) { + actions[actionType] = {action: action, actionInfo: actionInfo}; + } + eventActionMap[eventName] = actionType; +} + +/** + * @param {string} type + * @param {*} CoordinateSystem + */ +function registerCoordinateSystem(type, CoordinateSystem$$1) { + CoordinateSystemManager.register(type, CoordinateSystem$$1); +} + +/** + * Get dimensions of specified coordinate system. + * @param {string} type + * @return {Array.} + */ +function getCoordinateSystemDimensions(type) { + var coordSysCreator = CoordinateSystemManager.get(type); + if (coordSysCreator) { + return coordSysCreator.getDimensionsInfo + ? coordSysCreator.getDimensionsInfo() + : coordSysCreator.dimensions.slice(); + } +} + +/** + * Layout is a special stage of visual encoding + * Most visual encoding like color are common for different chart + * But each chart has it's own layout algorithm + * + * @param {number} [priority=1000] + * @param {Function} layoutTask + */ +function registerLayout(priority, layoutTask) { + normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, 'layout'); +} + +/** + * @param {number} [priority=3000] + * @param {module:echarts/stream/Task} visualTask + */ +function registerVisual(priority, visualTask) { + normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, 'visual'); +} + +/** + * @param {Object|Function} fn: {seriesType, createOnAllSeries, performRawSeries, reset} + */ +function normalizeRegister(targetList, priority, fn, defaultPriority, visualType) { + if (isFunction(priority) || isObject(priority)) { + fn = priority; + priority = defaultPriority; + } + + if (__DEV__) { + if (isNaN(priority) || priority == null) { + throw new Error('Illegal priority'); + } + // Check duplicate + each(targetList, function (wrap) { + assert(wrap.__raw !== fn); + }); + } + + var stageHandler = Scheduler.wrapStageHandler(fn, visualType); + + stageHandler.__prio = priority; + stageHandler.__raw = fn; + targetList.push(stageHandler); + + return stageHandler; +} + +/** + * @param {string} name + */ +function registerLoading(name, loadingFx) { + loadingEffects[name] = loadingFx; +} + +/** + * @param {Object} opts + * @param {string} [superClass] + */ +function extendComponentModel(opts/*, superClass*/) { + // var Clazz = ComponentModel; + // if (superClass) { + // var classType = parseClassType(superClass); + // Clazz = ComponentModel.getClass(classType.main, classType.sub, true); + // } + return ComponentModel.extend(opts); +} + +/** + * @param {Object} opts + * @param {string} [superClass] + */ +function extendComponentView(opts/*, superClass*/) { + // var Clazz = ComponentView; + // if (superClass) { + // var classType = parseClassType(superClass); + // Clazz = ComponentView.getClass(classType.main, classType.sub, true); + // } + return Component$1.extend(opts); +} + +/** + * @param {Object} opts + * @param {string} [superClass] + */ +function extendSeriesModel(opts/*, superClass*/) { + // var Clazz = SeriesModel; + // if (superClass) { + // superClass = 'series.' + superClass.replace('series.', ''); + // var classType = parseClassType(superClass); + // Clazz = ComponentModel.getClass(classType.main, classType.sub, true); + // } + return SeriesModel.extend(opts); +} + +/** + * @param {Object} opts + * @param {string} [superClass] + */ +function extendChartView(opts/*, superClass*/) { + // var Clazz = ChartView; + // if (superClass) { + // superClass = superClass.replace('series.', ''); + // var classType = parseClassType(superClass); + // Clazz = ChartView.getClass(classType.main, true); + // } + return Chart.extend(opts); +} + +/** + * ZRender need a canvas context to do measureText. + * But in node environment canvas may be created by node-canvas. + * So we need to specify how to create a canvas instead of using document.createElement('canvas') + * + * Be careful of using it in the browser. + * + * @param {Function} creator + * @example + * var Canvas = require('canvas'); + * var echarts = require('echarts'); + * echarts.setCanvasCreator(function () { + * // Small size is enough. + * return new Canvas(32, 32); + * }); + */ +function setCanvasCreator(creator) { + $override('createCanvas', creator); +} + +/** + * @param {string} mapName + * @param {Array.|Object|string} geoJson + * @param {Object} [specialAreas] + * + * @example GeoJSON + * $.get('USA.json', function (geoJson) { + * echarts.registerMap('USA', geoJson); + * // Or + * echarts.registerMap('USA', { + * geoJson: geoJson, + * specialAreas: {} + * }) + * }); + * + * $.get('airport.svg', function (svg) { + * echarts.registerMap('airport', { + * svg: svg + * } + * }); + * + * echarts.registerMap('eu', [ + * {svg: eu-topographic.svg}, + * {geoJSON: eu.json} + * ]) + */ +function registerMap(mapName, geoJson, specialAreas) { + mapDataStorage.registerMap(mapName, geoJson, specialAreas); +} + +/** + * @param {string} mapName + * @return {Object} + */ +function getMap(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); +registerPreprocessor(backwardCompat); +registerProcessor(PRIORITY_PROCESSOR_DATASTACK, dataStack); +registerLoading('default', loadingDefault); + +// Default actions + +registerAction({ + type: 'highlight', + event: 'highlight', + update: 'highlight' +}, noop); + +registerAction({ + type: 'downplay', + event: 'downplay', + update: 'downplay' +}, noop); + +// Default theme +registerTheme('light', lightTheme); +registerTheme('dark', theme); + +// For backward compatibility, where the namespace `dataTool` will +// be mounted on `echarts` is the extension `dataTool` is imported. +var dataTool = {}; + +/* +* 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. +*/ + + +function defaultKeyGetter(item) { + return item; +} + +/** + * @param {Array} oldArr + * @param {Array} newArr + * @param {Function} oldKeyGetter + * @param {Function} newKeyGetter + * @param {Object} [context] Can be visited by this.context in callback. + */ +function DataDiffer(oldArr, newArr, oldKeyGetter, newKeyGetter, context) { + this._old = oldArr; + this._new = newArr; + + this._oldKeyGetter = oldKeyGetter || defaultKeyGetter; + this._newKeyGetter = newKeyGetter || defaultKeyGetter; + + this.context = context; +} + +DataDiffer.prototype = { + + constructor: DataDiffer, + + /** + * Callback function when add a data + */ + add: function (func) { + this._add = func; + return this; + }, + + /** + * Callback function when update a data + */ + update: function (func) { + this._update = func; + return this; + }, + + /** + * Callback function when remove a data + */ + remove: function (func) { + this._remove = func; + return this; + }, + + execute: function () { + var oldArr = this._old; + var newArr = this._new; + + var oldDataIndexMap = {}; + var newDataIndexMap = {}; + var oldDataKeyArr = []; + var newDataKeyArr = []; + var i; + + initIndexMap(oldArr, oldDataIndexMap, oldDataKeyArr, '_oldKeyGetter', this); + initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter', this); + + for (i = 0; i < oldArr.length; i++) { + var key = oldDataKeyArr[i]; + var idx = newDataIndexMap[key]; + + // idx can never be empty array here. see 'set null' logic below. + if (idx != null) { + // Consider there is duplicate key (for example, use dataItem.name as key). + // We should make sure every item in newArr and oldArr can be visited. + var len = idx.length; + if (len) { + len === 1 && (newDataIndexMap[key] = null); + idx = idx.shift(); + } + else { + newDataIndexMap[key] = null; + } + this._update && this._update(idx, i); + } + else { + this._remove && this._remove(i); + } + } + + for (var i = 0; i < newDataKeyArr.length; i++) { + var key = newDataKeyArr[i]; + if (newDataIndexMap.hasOwnProperty(key)) { + var idx = newDataIndexMap[key]; + if (idx == null) { + continue; + } + // idx can never be empty array here. see 'set null' logic above. + if (!idx.length) { + this._add && this._add(idx); + } + else { + for (var j = 0, len = idx.length; j < len; j++) { + this._add && this._add(idx[j]); + } + } + } + } + } +}; + +function initIndexMap(arr, map, keyArr, keyGetterName, dataDiffer) { + for (var i = 0; i < arr.length; i++) { + // Add prefix to avoid conflict with Object.prototype. + var key = '_ec_' + dataDiffer[keyGetterName](arr[i], i); + var existence = map[key]; + if (existence == null) { + keyArr.push(key); + map[key] = i; + } + else { + if (!existence.length) { + map[key] = existence = [existence]; + } + existence.push(i); + } + } +} + +/* +* 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. +*/ + +var OTHER_DIMENSIONS = createHashMap([ + 'tooltip', 'label', 'itemName', 'itemId', 'seriesName' +]); + +function summarizeDimensions(data) { + var summary = {}; + var encode = summary.encode = {}; + var notExtraCoordDimMap = createHashMap(); + var defaultedLabel = []; + var defaultedTooltip = []; + + // See the comment of `List.js#userOutput`. + var userOutput = summary.userOutput = { + dimensionNames: data.dimensions.slice(), + encode: {} + }; + + each$1(data.dimensions, function (dimName) { + var dimItem = data.getDimensionInfo(dimName); + + var coordDim = dimItem.coordDim; + if (coordDim) { + if (__DEV__) { + assert$1(OTHER_DIMENSIONS.get(coordDim) == null); + } + + var coordDimIndex = dimItem.coordDimIndex; + getOrCreateEncodeArr(encode, coordDim)[coordDimIndex] = dimName; + + if (!dimItem.isExtraCoord) { + notExtraCoordDimMap.set(coordDim, 1); + + // Use the last coord dim (and label friendly) as default label, + // because when dataset is used, it is hard to guess which dimension + // can be value dimension. If both show x, y on label is not look good, + // and conventionally y axis is focused more. + if (mayLabelDimType(dimItem.type)) { + defaultedLabel[0] = dimName; + } + + // User output encode do not contain generated coords. + // And it only has index. User can use index to retrieve value from the raw item array. + getOrCreateEncodeArr(userOutput.encode, coordDim)[coordDimIndex] = dimItem.index; + } + if (dimItem.defaultTooltip) { + defaultedTooltip.push(dimName); + } + } + + OTHER_DIMENSIONS.each(function (v, otherDim) { + var encodeArr = getOrCreateEncodeArr(encode, otherDim); + + var dimIndex = dimItem.otherDims[otherDim]; + if (dimIndex != null && dimIndex !== false) { + encodeArr[dimIndex] = dimItem.name; + } + }); + }); + + var dataDimsOnCoord = []; + var encodeFirstDimNotExtra = {}; + + notExtraCoordDimMap.each(function (v, coordDim) { + var dimArr = encode[coordDim]; + // ??? FIXME extra coord should not be set in dataDimsOnCoord. + // But should fix the case that radar axes: simplify the logic + // of `completeDimension`, remove `extraPrefix`. + encodeFirstDimNotExtra[coordDim] = dimArr[0]; + // Not necessary to remove duplicate, because a data + // dim canot on more than one coordDim. + dataDimsOnCoord = dataDimsOnCoord.concat(dimArr); + }); + + summary.dataDimsOnCoord = dataDimsOnCoord; + summary.encodeFirstDimNotExtra = encodeFirstDimNotExtra; + + var encodeLabel = encode.label; + // FIXME `encode.label` is not recommanded, because formatter can not be set + // in this way. Use label.formatter instead. May be remove this approach someday. + if (encodeLabel && encodeLabel.length) { + defaultedLabel = encodeLabel.slice(); + } + + var encodeTooltip = encode.tooltip; + if (encodeTooltip && encodeTooltip.length) { + defaultedTooltip = encodeTooltip.slice(); + } + else if (!defaultedTooltip.length) { + defaultedTooltip = defaultedLabel.slice(); + } + + encode.defaultedLabel = defaultedLabel; + encode.defaultedTooltip = defaultedTooltip; + + return summary; +} + +function getOrCreateEncodeArr(encode, dim) { + if (!encode.hasOwnProperty(dim)) { + encode[dim] = []; + } + return encode[dim]; +} + +function getDimensionTypeByAxis(axisType) { + return axisType === 'category' + ? 'ordinal' + : axisType === 'time' + ? 'time' + : 'float'; +} + +function mayLabelDimType(dimType) { + // In most cases, ordinal and time do not suitable for label. + // Ordinal info can be displayed on axis. Time is too long. + return !(dimType === 'ordinal' || dimType === 'time'); +} + +// function findTheLastDimMayLabel(data) { +// // Get last value dim +// var dimensions = data.dimensions.slice(); +// var valueType; +// var valueDim; +// while (dimensions.length && ( +// valueDim = dimensions.pop(), +// valueType = data.getDimensionInfo(valueDim).type, +// valueType === 'ordinal' || valueType === 'time' +// )) {} // jshint ignore:line +// return valueDim; +// } + +/* +* 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. +*/ + +/** + * @class + * @param {Object|DataDimensionInfo} [opt] All of the fields will be shallow copied. + */ +function DataDimensionInfo(opt) { + if (opt != null) { + extend(this, opt); + } + + /** + * Dimension name. + * Mandatory. + * @type {string} + */ + // this.name; + + /** + * The origin name in dimsDef, see source helper. + * If displayName given, the tooltip will displayed vertically. + * Optional. + * @type {string} + */ + // this.displayName; + + /** + * Which coordSys dimension this dimension mapped to. + * A `coordDim` can be a "coordSysDim" that the coordSys required + * (for example, an item in `coordSysDims` of `model/referHelper#CoordSysInfo`), + * or an generated "extra coord name" if does not mapped to any "coordSysDim" + * (That is determined by whether `isExtraCoord` is `true`). + * Mandatory. + * @type {string} + */ + // this.coordDim; + + /** + * The index of this dimension in `series.encode[coordDim]`. + * Mandatory. + * @type {number} + */ + // this.coordDimIndex; + + /** + * Dimension type. The enumerable values are the key of + * `dataCtors` of `data/List`. + * Optional. + * @type {string} + */ + // this.type; + + /** + * This index of this dimension info in `data/List#_dimensionInfos`. + * Mandatory after added to `data/List`. + * @type {number} + */ + // this.index; + + /** + * The format of `otherDims` is: + * ```js + * { + * tooltip: number optional, + * label: number optional, + * itemName: number optional, + * seriesName: number optional, + * } + * ``` + * + * A `series.encode` can specified these fields: + * ```js + * encode: { + * // "3, 1, 5" is the index of data dimension. + * tooltip: [3, 1, 5], + * label: [0, 3], + * ... + * } + * ``` + * `otherDims` is the parse result of the `series.encode` above, like: + * ```js + * // Suppose the index of this data dimension is `3`. + * this.otherDims = { + * // `3` is at the index `0` of the `encode.tooltip` + * tooltip: 0, + * // `3` is at the index `1` of the `encode.tooltip` + * label: 1 + * }; + * ``` + * + * This prop should never be `null`/`undefined` after initialized. + * @type {Object} + */ + this.otherDims = {}; + + /** + * Be `true` if this dimension is not mapped to any "coordSysDim" that the + * "coordSys" required. + * Mandatory. + * @type {boolean} + */ + // this.isExtraCoord; + + /** + * @type {module:data/OrdinalMeta} + */ + // this.ordinalMeta; + + /** + * Whether to create inverted indices. + * @type {boolean} + */ + // this.createInvertedIndices; +} + +/* +* 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. +*/ + +/* global Float64Array, Int32Array, Uint32Array, Uint16Array */ + +/** + * List for data storage + * @module echarts/data/List + */ + +var isObject$4 = isObject$1; + +var UNDEFINED = 'undefined'; +var INDEX_NOT_FOUND = -1; + +// Use prefix to avoid index to be the same as otherIdList[idx], +// which will cause weird udpate animation. +var ID_PREFIX = 'e\0\0'; + +var dataCtors = { + 'float': typeof Float64Array === UNDEFINED + ? Array : Float64Array, + 'int': typeof Int32Array === UNDEFINED + ? Array : Int32Array, + // Ordinal data type can be string or int + 'ordinal': Array, + 'number': Array, + 'time': Array +}; + +// Caution: MUST not use `new CtorUint32Array(arr, 0, len)`, because the Ctor of array is +// different from the Ctor of typed array. +var CtorUint32Array = typeof Uint32Array === UNDEFINED ? Array : Uint32Array; +var CtorInt32Array = typeof Int32Array === UNDEFINED ? Array : Int32Array; +var CtorUint16Array = typeof Uint16Array === UNDEFINED ? Array : Uint16Array; + +function getIndicesCtor(list) { + // The possible max value in this._indicies is always this._rawCount despite of filtering. + return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array; +} + +function cloneChunk(originalChunk) { + var Ctor = originalChunk.constructor; + // Only shallow clone is enough when Array. + return Ctor === Array ? originalChunk.slice() : new Ctor(originalChunk); +} + +var TRANSFERABLE_PROPERTIES = [ + 'hasItemOption', '_nameList', '_idList', '_invertedIndicesMap', + '_rawData', '_chunkSize', '_chunkCount', '_dimValueGetter', + '_count', '_rawCount', '_nameDimIdx', '_idDimIdx' +]; +var CLONE_PROPERTIES = [ + '_extent', '_approximateExtent', '_rawExtent' +]; + +function transferProperties(target, source) { + each$1(TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []), function (propName) { + if (source.hasOwnProperty(propName)) { + target[propName] = source[propName]; + } + }); + + target.__wrappedMethods = source.__wrappedMethods; + + each$1(CLONE_PROPERTIES, function (propName) { + target[propName] = clone(source[propName]); + }); + + target._calculationInfo = extend(source._calculationInfo); +} + + + + + +/** + * @constructor + * @alias module:echarts/data/List + * + * @param {Array.} dimensions + * For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...]. + * Dimensions should be concrete names like x, y, z, lng, lat, angle, radius + * @param {module:echarts/model/Model} hostModel + */ +var List = function (dimensions, hostModel) { + + dimensions = dimensions || ['x', 'y']; + + var dimensionInfos = {}; + var dimensionNames = []; + var invertedIndicesMap = {}; + + for (var i = 0; i < dimensions.length; i++) { + // Use the original dimensions[i], where other flag props may exists. + var dimensionInfo = dimensions[i]; + + if (isString(dimensionInfo)) { + dimensionInfo = new DataDimensionInfo({name: dimensionInfo}); + } + else if (!(dimensionInfo instanceof DataDimensionInfo)) { + dimensionInfo = new DataDimensionInfo(dimensionInfo); + } + + var dimensionName = dimensionInfo.name; + dimensionInfo.type = dimensionInfo.type || 'float'; + if (!dimensionInfo.coordDim) { + dimensionInfo.coordDim = dimensionName; + dimensionInfo.coordDimIndex = 0; + } + + dimensionInfo.otherDims = dimensionInfo.otherDims || {}; + dimensionNames.push(dimensionName); + dimensionInfos[dimensionName] = dimensionInfo; + + dimensionInfo.index = i; + + if (dimensionInfo.createInvertedIndices) { + invertedIndicesMap[dimensionName] = []; + } + } + + /** + * @readOnly + * @type {Array.} + */ + this.dimensions = dimensionNames; + + /** + * Infomation of each data dimension, like data type. + * @type {Object} + */ + this._dimensionInfos = dimensionInfos; + + /** + * @type {module:echarts/model/Model} + */ + this.hostModel = hostModel; + + /** + * @type {module:echarts/model/Model} + */ + this.dataType; + + /** + * Indices stores the indices of data subset after filtered. + * This data subset will be used in chart. + * @type {Array.} + * @readOnly + */ + this._indices = null; + + this._count = 0; + this._rawCount = 0; + + /** + * Data storage + * @type {Object.>} + * @private + */ + this._storage = {}; + + /** + * @type {Array.} + */ + this._nameList = []; + /** + * @type {Array.} + */ + this._idList = []; + + /** + * Models of data option is stored sparse for optimizing memory cost + * @type {Array.} + * @private + */ + this._optionModels = []; + + /** + * Global visual properties after visual coding + * @type {Object} + * @private + */ + this._visual = {}; + + /** + * Globel layout properties. + * @type {Object} + * @private + */ + this._layout = {}; + + /** + * Item visual properties after visual coding + * @type {Array.} + * @private + */ + this._itemVisuals = []; + + /** + * Key: visual type, Value: boolean + * @type {Object} + * @readOnly + */ + this.hasItemVisual = {}; + + /** + * Item layout properties after layout + * @type {Array.} + * @private + */ + this._itemLayouts = []; + + /** + * Graphic elemnents + * @type {Array.} + * @private + */ + this._graphicEls = []; + + /** + * Max size of each chunk. + * @type {number} + * @private + */ + this._chunkSize = 1e5; + + /** + * @type {number} + * @private + */ + this._chunkCount = 0; + + /** + * @type {Array.} + * @private + */ + this._rawData; + + /** + * Raw extent will not be cloned, but only transfered. + * It will not be calculated util needed. + * key: dim, + * value: {end: number, extent: Array.} + * @type {Object} + * @private + */ + this._rawExtent = {}; + + /** + * @type {Object} + * @private + */ + this._extent = {}; + + /** + * key: dim + * value: extent + * @type {Object} + * @private + */ + this._approximateExtent = {}; + + /** + * Cache summary info for fast visit. See "dimensionHelper". + * @type {Object} + * @private + */ + this._dimensionsSummary = summarizeDimensions(this); + + /** + * @type {Object.} + * @private + */ + this._invertedIndicesMap = invertedIndicesMap; + + /** + * @type {Object} + * @private + */ + this._calculationInfo = {}; + + /** + * User output info of this data. + * DO NOT use it in other places! + * + * When preparing user params for user callbacks, we have + * to clone these inner data structures to prevent users + * from modifying them to effect built-in logic. And for + * performance consideration we make this `userOutput` to + * avoid clone them too many times. + * + * @type {Object} + * @readOnly + */ + this.userOutput = this._dimensionsSummary.userOutput; +}; + +var listProto = List.prototype; + +listProto.type = 'list'; + +/** + * If each data item has it's own option + * @type {boolean} + */ +listProto.hasItemOption = true; + +/** + * The meanings of the input parameter `dim`: + * + * + If dim is a number (e.g., `1`), it means the index of the dimension. + * For example, `getDimension(0)` will return 'x' or 'lng' or 'radius'. + * + If dim is a number-like string (e.g., `"1"`): + * + If there is the same concrete dim name defined in `this.dimensions`, it means that concrete name. + * + If not, it will be converted to a number, which means the index of the dimension. + * (why? because of the backward compatbility. We have been tolerating number-like string in + * dimension setting, although now it seems that it is not a good idea.) + * For example, `visualMap[i].dimension: "1"` is the same meaning as `visualMap[i].dimension: 1`, + * if no dimension name is defined as `"1"`. + * + If dim is a not-number-like string, it means the concrete dim name. + * For example, it can be be default name `"x"`, `"y"`, `"z"`, `"lng"`, `"lat"`, `"angle"`, `"radius"`, + * or customized in `dimensions` property of option like `"age"`. + * + * Get dimension name + * @param {string|number} dim See above. + * @return {string} Concrete dim name. + */ +listProto.getDimension = function (dim) { + if (typeof dim === 'number' + // If being a number-like string but not being defined a dimension name. + || (!isNaN(dim) && !this._dimensionInfos.hasOwnProperty(dim)) + ) { + dim = this.dimensions[dim]; + } + return dim; +}; + +/** + * Get type and calculation info of particular dimension + * @param {string|number} dim + * Dimension can be concrete names like x, y, z, lng, lat, angle, radius + * Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius' + */ +listProto.getDimensionInfo = function (dim) { + // Do not clone, because there may be categories in dimInfo. + return this._dimensionInfos[this.getDimension(dim)]; +}; + +/** + * @return {Array.} concrete dimension name list on coord. + */ +listProto.getDimensionsOnCoord = function () { + return this._dimensionsSummary.dataDimsOnCoord.slice(); +}; + +/** + * @param {string} coordDim + * @param {number} [idx] A coordDim may map to more than one data dim. + * If idx is `true`, return a array of all mapped dims. + * If idx is not specified, return the first dim not extra. + * @return {string|Array.} concrete data dim. + * If idx is number, and not found, return null/undefined. + * If idx is `true`, and not found, return empty array (always return array). + */ +listProto.mapDimension = function (coordDim, idx) { + var dimensionsSummary = this._dimensionsSummary; + + if (idx == null) { + return dimensionsSummary.encodeFirstDimNotExtra[coordDim]; + } + + var dims = dimensionsSummary.encode[coordDim]; + return idx === true + // always return array if idx is `true` + ? (dims || []).slice() + : (dims && dims[idx]); +}; + +/** + * Initialize from data + * @param {Array.} data source or data or data provider. + * @param {Array.} [nameLIst] The name of a datum is used on data diff and + * defualt label/tooltip. + * A name can be specified in encode.itemName, + * or dataItem.name (only for series option data), + * or provided in nameList from outside. + * @param {Function} [dimValueGetter] (dataItem, dimName, dataIndex, dimIndex) => number + */ +listProto.initData = function (data, nameList, dimValueGetter) { + + var notProvider = Source.isInstance(data) || isArrayLike(data); + if (notProvider) { + data = new DefaultDataProvider(data, this.dimensions.length); + } + + if (__DEV__) { + if (!notProvider && (typeof data.getItem !== 'function' || typeof data.count !== 'function')) { + throw new Error('Inavlid data provider.'); + } + } + + this._rawData = data; + + // Clear + this._storage = {}; + this._indices = null; + + this._nameList = nameList || []; + + this._idList = []; + + this._nameRepeatCount = {}; + + if (!dimValueGetter) { + this.hasItemOption = false; + } + + /** + * @readOnly + */ + this.defaultDimValueGetter = defaultDimValueGetters[ + this._rawData.getSource().sourceFormat + ]; + // Default dim value getter + this._dimValueGetter = dimValueGetter = dimValueGetter + || this.defaultDimValueGetter; + this._dimValueGetterArrayRows = defaultDimValueGetters.arrayRows; + + // Reset raw extent. + this._rawExtent = {}; + + this._initDataFromProvider(0, data.count()); + + // If data has no item option. + if (data.pure) { + this.hasItemOption = false; + } +}; + +listProto.getProvider = function () { + return this._rawData; +}; + +/** + * Caution: Can be only called on raw data (before `this._indices` created). + */ +listProto.appendData = function (data) { + if (__DEV__) { + assert$1(!this._indices, 'appendData can only be called on raw data.'); + } + + var rawData = this._rawData; + var start = this.count(); + rawData.appendData(data); + var end = rawData.count(); + if (!rawData.persistent) { + end += start; + } + this._initDataFromProvider(start, end); +}; + +/** + * Caution: Can be only called on raw data (before `this._indices` created). + * This method does not modify `rawData` (`dataProvider`), but only + * add values to storage. + * + * The final count will be increased by `Math.max(values.length, names.length)`. + * + * @param {Array.>} values That is the SourceType: 'arrayRows', like + * [ + * [12, 33, 44], + * [NaN, 43, 1], + * ['-', 'asdf', 0] + * ] + * Each item is exaclty cooresponding to a dimension. + * @param {Array.} [names] + */ +listProto.appendValues = function (values, names) { + var chunkSize = this._chunkSize; + var storage = this._storage; + var dimensions = this.dimensions; + var dimLen = dimensions.length; + var rawExtent = this._rawExtent; + + var start = this.count(); + var end = start + Math.max(values.length, names ? names.length : 0); + var originalChunkCount = this._chunkCount; + + for (var i = 0; i < dimLen; i++) { + var dim = dimensions[i]; + if (!rawExtent[dim]) { + rawExtent[dim] = getInitialExtent(); + } + if (!storage[dim]) { + storage[dim] = []; + } + prepareChunks(storage, this._dimensionInfos[dim], chunkSize, originalChunkCount, end); + this._chunkCount = storage[dim].length; + } + + var emptyDataItem = new Array(dimLen); + for (var idx = start; idx < end; idx++) { + var sourceIdx = idx - start; + var chunkIndex = Math.floor(idx / chunkSize); + var chunkOffset = idx % chunkSize; + + // Store the data by dimensions + for (var k = 0; k < dimLen; k++) { + var dim = dimensions[k]; + var val = this._dimValueGetterArrayRows( + values[sourceIdx] || emptyDataItem, dim, sourceIdx, k + ); + storage[dim][chunkIndex][chunkOffset] = val; + + var dimRawExtent = rawExtent[dim]; + val < dimRawExtent[0] && (dimRawExtent[0] = val); + val > dimRawExtent[1] && (dimRawExtent[1] = val); + } + + if (names) { + this._nameList[idx] = names[sourceIdx]; + } + } + + this._rawCount = this._count = end; + + // Reset data extent + this._extent = {}; + + prepareInvertedIndex(this); +}; + +listProto._initDataFromProvider = function (start, end) { + // Optimize. + if (start >= end) { + return; + } + + var chunkSize = this._chunkSize; + var rawData = this._rawData; + var storage = this._storage; + var dimensions = this.dimensions; + var dimLen = dimensions.length; + var dimensionInfoMap = this._dimensionInfos; + var nameList = this._nameList; + var idList = this._idList; + var rawExtent = this._rawExtent; + var nameRepeatCount = this._nameRepeatCount = {}; + var nameDimIdx; + + var originalChunkCount = this._chunkCount; + for (var i = 0; i < dimLen; i++) { + var dim = dimensions[i]; + if (!rawExtent[dim]) { + rawExtent[dim] = getInitialExtent(); + } + + var dimInfo = dimensionInfoMap[dim]; + if (dimInfo.otherDims.itemName === 0) { + nameDimIdx = this._nameDimIdx = i; + } + if (dimInfo.otherDims.itemId === 0) { + this._idDimIdx = i; + } + + if (!storage[dim]) { + storage[dim] = []; + } + + prepareChunks(storage, dimInfo, chunkSize, originalChunkCount, end); + + this._chunkCount = storage[dim].length; + } + + var dataItem = new Array(dimLen); + for (var idx = start; idx < end; idx++) { + // NOTICE: Try not to write things into dataItem + dataItem = rawData.getItem(idx, dataItem); + // Each data item is value + // [1, 2] + // 2 + // Bar chart, line chart which uses category axis + // only gives the 'y' value. 'x' value is the indices of category + // Use a tempValue to normalize the value to be a (x, y) value + var chunkIndex = Math.floor(idx / chunkSize); + var chunkOffset = idx % chunkSize; + + // Store the data by dimensions + for (var k = 0; k < dimLen; k++) { + var dim = dimensions[k]; + var dimStorage = storage[dim][chunkIndex]; + // PENDING NULL is empty or zero + var val = this._dimValueGetter(dataItem, dim, idx, k); + dimStorage[chunkOffset] = val; + + var dimRawExtent = rawExtent[dim]; + val < dimRawExtent[0] && (dimRawExtent[0] = val); + val > dimRawExtent[1] && (dimRawExtent[1] = val); + } + + // ??? FIXME not check by pure but sourceFormat? + // TODO refactor these logic. + if (!rawData.pure) { + var name = nameList[idx]; + + if (dataItem && name == null) { + // If dataItem is {name: ...}, it has highest priority. + // That is appropriate for many common cases. + if (dataItem.name != null) { + // There is no other place to persistent dataItem.name, + // so save it to nameList. + nameList[idx] = name = dataItem.name; + } + else if (nameDimIdx != null) { + var nameDim = dimensions[nameDimIdx]; + var nameDimChunk = storage[nameDim][chunkIndex]; + if (nameDimChunk) { + name = nameDimChunk[chunkOffset]; + var ordinalMeta = dimensionInfoMap[nameDim].ordinalMeta; + if (ordinalMeta && ordinalMeta.categories.length) { + name = ordinalMeta.categories[name]; + } + } + } + } + + // Try using the id in option + // id or name is used on dynamical data, mapping old and new items. + var id = dataItem == null ? null : dataItem.id; + + if (id == null && name != null) { + // Use name as id and add counter to avoid same name + nameRepeatCount[name] = nameRepeatCount[name] || 0; + id = name; + if (nameRepeatCount[name] > 0) { + id += '__ec__' + nameRepeatCount[name]; + } + nameRepeatCount[name]++; + } + id != null && (idList[idx] = id); + } + } + + if (!rawData.persistent && rawData.clean) { + // Clean unused data if data source is typed array. + rawData.clean(); + } + + this._rawCount = this._count = end; + + // Reset data extent + this._extent = {}; + + prepareInvertedIndex(this); +}; + +function prepareChunks(storage, dimInfo, chunkSize, chunkCount, end) { + var DataCtor = dataCtors[dimInfo.type]; + var lastChunkIndex = chunkCount - 1; + var dim = dimInfo.name; + var resizeChunkArray = storage[dim][lastChunkIndex]; + if (resizeChunkArray && resizeChunkArray.length < chunkSize) { + var newStore = new DataCtor(Math.min(end - lastChunkIndex * chunkSize, chunkSize)); + // The cost of the copy is probably inconsiderable + // within the initial chunkSize. + for (var j = 0; j < resizeChunkArray.length; j++) { + newStore[j] = resizeChunkArray[j]; + } + storage[dim][lastChunkIndex] = newStore; + } + + // Create new chunks. + for (var k = chunkCount * chunkSize; k < end; k += chunkSize) { + storage[dim].push(new DataCtor(Math.min(end - k, chunkSize))); + } +} + +function prepareInvertedIndex(list) { + var invertedIndicesMap = list._invertedIndicesMap; + each$1(invertedIndicesMap, function (invertedIndices, dim) { + var dimInfo = list._dimensionInfos[dim]; + + // Currently, only dimensions that has ordinalMeta can create inverted indices. + var ordinalMeta = dimInfo.ordinalMeta; + if (ordinalMeta) { + invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array( + ordinalMeta.categories.length + ); + // The default value of TypedArray is 0. To avoid miss + // mapping to 0, we should set it as INDEX_NOT_FOUND. + for (var i = 0; i < invertedIndices.length; i++) { + invertedIndices[i] = INDEX_NOT_FOUND; + } + for (var i = 0; i < list._count; i++) { + // Only support the case that all values are distinct. + invertedIndices[list.get(dim, i)] = i; + } + } + }); +} + +function getRawValueFromStore(list, dimIndex, rawIndex) { + var val; + if (dimIndex != null) { + var chunkSize = list._chunkSize; + var chunkIndex = Math.floor(rawIndex / chunkSize); + var chunkOffset = rawIndex % chunkSize; + var dim = list.dimensions[dimIndex]; + var chunk = list._storage[dim][chunkIndex]; + if (chunk) { + val = chunk[chunkOffset]; + var ordinalMeta = list._dimensionInfos[dim].ordinalMeta; + if (ordinalMeta && ordinalMeta.categories.length) { + val = ordinalMeta.categories[val]; + } + } + } + return val; +} + +/** + * @return {number} + */ +listProto.count = function () { + return this._count; +}; + +listProto.getIndices = function () { + var newIndices; + + var indices = this._indices; + if (indices) { + var Ctor = indices.constructor; + var thisCount = this._count; + // `new Array(a, b, c)` is different from `new Uint32Array(a, b, c)`. + if (Ctor === Array) { + newIndices = new Ctor(thisCount); + for (var i = 0; i < thisCount; i++) { + newIndices[i] = indices[i]; + } + } + else { + newIndices = new Ctor(indices.buffer, 0, thisCount); + } + } + else { + var Ctor = getIndicesCtor(this); + var newIndices = new Ctor(this.count()); + for (var i = 0; i < newIndices.length; i++) { + newIndices[i] = i; + } + } + + return newIndices; +}; + +/** + * Get value. Return NaN if idx is out of range. + * @param {string} dim Dim must be concrete name. + * @param {number} idx + * @param {boolean} stack + * @return {number} + */ +listProto.get = function (dim, idx /*, stack */) { + if (!(idx >= 0 && idx < this._count)) { + return NaN; + } + var storage = this._storage; + if (!storage[dim]) { + // TODO Warn ? + return NaN; + } + + idx = this.getRawIndex(idx); + + var chunkIndex = Math.floor(idx / this._chunkSize); + var chunkOffset = idx % this._chunkSize; + + var chunkStore = storage[dim][chunkIndex]; + var value = chunkStore[chunkOffset]; + // FIXME ordinal data type is not stackable + // if (stack) { + // var dimensionInfo = this._dimensionInfos[dim]; + // if (dimensionInfo && dimensionInfo.stackable) { + // var stackedOn = this.stackedOn; + // while (stackedOn) { + // // Get no stacked data of stacked on + // var stackedValue = stackedOn.get(dim, idx); + // // Considering positive stack, negative stack and empty data + // if ((value >= 0 && stackedValue > 0) // Positive stack + // || (value <= 0 && stackedValue < 0) // Negative stack + // ) { + // value += stackedValue; + // } + // stackedOn = stackedOn.stackedOn; + // } + // } + // } + + return value; +}; + +/** + * @param {string} dim concrete dim + * @param {number} rawIndex + * @return {number|string} + */ +listProto.getByRawIndex = function (dim, rawIdx) { + if (!(rawIdx >= 0 && rawIdx < this._rawCount)) { + return NaN; + } + var dimStore = this._storage[dim]; + if (!dimStore) { + // TODO Warn ? + return NaN; + } + + var chunkIndex = Math.floor(rawIdx / this._chunkSize); + var chunkOffset = rawIdx % this._chunkSize; + var chunkStore = dimStore[chunkIndex]; + return chunkStore[chunkOffset]; +}; + +/** + * FIXME Use `get` on chrome maybe slow(in filterSelf and selectRange). + * Hack a much simpler _getFast + * @private + */ +listProto._getFast = function (dim, rawIdx) { + var chunkIndex = Math.floor(rawIdx / this._chunkSize); + var chunkOffset = rawIdx % this._chunkSize; + var chunkStore = this._storage[dim][chunkIndex]; + return chunkStore[chunkOffset]; +}; + +/** + * Get value for multi dimensions. + * @param {Array.} [dimensions] If ignored, using all dimensions. + * @param {number} idx + * @return {number} + */ +listProto.getValues = function (dimensions, idx /*, stack */) { + var values = []; + + if (!isArray(dimensions)) { + // stack = idx; + idx = dimensions; + dimensions = this.dimensions; + } + + for (var i = 0, len = dimensions.length; i < len; i++) { + values.push(this.get(dimensions[i], idx /*, stack */)); + } + + return values; +}; + +/** + * If value is NaN. Inlcuding '-' + * Only check the coord dimensions. + * @param {string} dim + * @param {number} idx + * @return {number} + */ +listProto.hasValue = function (idx) { + var dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord; + for (var i = 0, len = dataDimsOnCoord.length; i < len; i++) { + // Ordinal type originally can be string or number. + // But when an ordinal type is used on coord, it can + // not be string but only number. So we can also use isNaN. + if (isNaN(this.get(dataDimsOnCoord[i], idx))) { + return false; + } + } + return true; +}; + +/** + * Get extent of data in one dimension + * @param {string} dim + * @param {boolean} stack + */ +listProto.getDataExtent = function (dim /*, stack */) { + // Make sure use concrete dim as cache name. + dim = this.getDimension(dim); + var dimData = this._storage[dim]; + var initialExtent = getInitialExtent(); + + // stack = !!((stack || false) && this.getCalculationInfo(dim)); + + if (!dimData) { + return initialExtent; + } + + // Make more strict checkings to ensure hitting cache. + var currEnd = this.count(); + // var cacheName = [dim, !!stack].join('_'); + // var cacheName = dim; + + // Consider the most cases when using data zoom, `getDataExtent` + // happened before filtering. We cache raw extent, which is not + // necessary to be cleared and recalculated when restore data. + var useRaw = !this._indices; // && !stack; + var dimExtent; + + if (useRaw) { + return this._rawExtent[dim].slice(); + } + dimExtent = this._extent[dim]; + if (dimExtent) { + return dimExtent.slice(); + } + dimExtent = initialExtent; + + var min = dimExtent[0]; + var max = dimExtent[1]; + + for (var i = 0; i < currEnd; i++) { + // var value = stack ? this.get(dim, i, true) : this._getFast(dim, this.getRawIndex(i)); + var value = this._getFast(dim, this.getRawIndex(i)); + value < min && (min = value); + value > max && (max = value); + } + + dimExtent = [min, max]; + + this._extent[dim] = dimExtent; + + return dimExtent; +}; + +/** + * Optimize for the scenario that data is filtered by a given extent. + * Consider that if data amount is more than hundreds of thousand, + * extent calculation will cost more than 10ms and the cache will + * be erased because of the filtering. + */ +listProto.getApproximateExtent = function (dim /*, stack */) { + dim = this.getDimension(dim); + return this._approximateExtent[dim] || this.getDataExtent(dim /*, stack */); +}; + +listProto.setApproximateExtent = function (extent, dim /*, stack */) { + dim = this.getDimension(dim); + this._approximateExtent[dim] = extent.slice(); +}; + +/** + * @param {string} key + * @return {*} + */ +listProto.getCalculationInfo = function (key) { + return this._calculationInfo[key]; +}; + +/** + * @param {string|Object} key or k-v object + * @param {*} [value] + */ +listProto.setCalculationInfo = function (key, value) { + isObject$4(key) + ? extend(this._calculationInfo, key) + : (this._calculationInfo[key] = value); +}; + +/** + * Get sum of data in one dimension + * @param {string} dim + */ +listProto.getSum = function (dim /*, stack */) { + var dimData = this._storage[dim]; + var sum = 0; + if (dimData) { + for (var i = 0, len = this.count(); i < len; i++) { + var value = this.get(dim, i /*, stack */); + if (!isNaN(value)) { + sum += value; + } + } + } + return sum; +}; + +/** + * Get median of data in one dimension + * @param {string} dim + */ +listProto.getMedian = function (dim /*, stack */) { + var dimDataArray = []; + // map all data of one dimension + this.each(dim, function (val, idx) { + if (!isNaN(val)) { + dimDataArray.push(val); + } + }); + + // TODO + // Use quick select? + + // immutability & sort + var sortedDimDataArray = [].concat(dimDataArray).sort(function (a, b) { + return a - b; + }); + var len = this.count(); + // calculate median + return len === 0 + ? 0 + : len % 2 === 1 + ? sortedDimDataArray[(len - 1) / 2] + : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2; +}; + +// /** +// * Retreive the index with given value +// * @param {string} dim Concrete dimension. +// * @param {number} value +// * @return {number} +// */ +// Currently incorrect: should return dataIndex but not rawIndex. +// Do not fix it until this method is to be used somewhere. +// FIXME Precision of float value +// listProto.indexOf = function (dim, value) { +// var storage = this._storage; +// var dimData = storage[dim]; +// var chunkSize = this._chunkSize; +// if (dimData) { +// for (var i = 0, len = this.count(); i < len; i++) { +// var chunkIndex = Math.floor(i / chunkSize); +// var chunkOffset = i % chunkSize; +// if (dimData[chunkIndex][chunkOffset] === value) { +// return i; +// } +// } +// } +// return -1; +// }; + +/** + * Only support the dimension which inverted index created. + * Do not support other cases until required. + * @param {string} concrete dim + * @param {number|string} value + * @return {number} rawIndex + */ +listProto.rawIndexOf = function (dim, value) { + var invertedIndices = dim && this._invertedIndicesMap[dim]; + if (__DEV__) { + if (!invertedIndices) { + throw new Error('Do not supported yet'); + } + } + var rawIndex = invertedIndices[value]; + if (rawIndex == null || isNaN(rawIndex)) { + return INDEX_NOT_FOUND; + } + return rawIndex; +}; + +/** + * Retreive the index with given name + * @param {number} idx + * @param {number} name + * @return {number} + */ +listProto.indexOfName = function (name) { + for (var i = 0, len = this.count(); i < len; i++) { + if (this.getName(i) === name) { + return i; + } + } + + return -1; +}; + +/** + * Retreive the index with given raw data index + * @param {number} idx + * @param {number} name + * @return {number} + */ +listProto.indexOfRawIndex = function (rawIndex) { + if (rawIndex >= this._rawCount || rawIndex < 0) { + return -1; + } + + if (!this._indices) { + return rawIndex; + } + + // Indices are ascending + var indices = this._indices; + + // If rawIndex === dataIndex + var rawDataIndex = indices[rawIndex]; + if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) { + return rawIndex; + } + + var left = 0; + var right = this._count - 1; + while (left <= right) { + var mid = (left + right) / 2 | 0; + if (indices[mid] < rawIndex) { + left = mid + 1; + } + else if (indices[mid] > rawIndex) { + right = mid - 1; + } + else { + return mid; + } + } + return -1; +}; + +/** + * Retreive the index of nearest value + * @param {string} dim + * @param {number} value + * @param {number} [maxDistance=Infinity] + * @return {Array.} If and only if multiple indices has + * the same value, they are put to the result. + */ +listProto.indicesOfNearest = function (dim, value, maxDistance) { + var storage = this._storage; + var dimData = storage[dim]; + var nearestIndices = []; + + if (!dimData) { + return nearestIndices; + } + + if (maxDistance == null) { + maxDistance = Infinity; + } + + var minDist = Infinity; + var minDiff = -1; + var nearestIndicesLen = 0; + + // Check the test case of `test/ut/spec/data/List.js`. + for (var i = 0, len = this.count(); i < len; i++) { + var diff = value - this.get(dim, i); + var dist = Math.abs(diff); + if (dist <= maxDistance) { + // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`, + // we'd better not push both of them to `nearestIndices`, otherwise it is easy to + // get more than one item in `nearestIndices` (more specifically, in `tooltip`). + // So we chose the one that `diff >= 0` in this csae. + // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them + // should be push to `nearestIndices`. + if (dist < minDist + || (dist === minDist && diff >= 0 && minDiff < 0) + ) { + minDist = dist; + minDiff = diff; + nearestIndicesLen = 0; + } + if (diff === minDiff) { + nearestIndices[nearestIndicesLen++] = i; + } + } + } + nearestIndices.length = nearestIndicesLen; + + return nearestIndices; +}; + +/** + * Get raw data index + * @param {number} idx + * @return {number} + */ +listProto.getRawIndex = getRawIndexWithoutIndices; + +function getRawIndexWithoutIndices(idx) { + return idx; +} + +function getRawIndexWithIndices(idx) { + if (idx < this._count && idx >= 0) { + return this._indices[idx]; + } + return -1; +} + +/** + * Get raw data item + * @param {number} idx + * @return {number} + */ +listProto.getRawDataItem = function (idx) { + if (!this._rawData.persistent) { + var val = []; + for (var i = 0; i < this.dimensions.length; i++) { + var dim = this.dimensions[i]; + val.push(this.get(dim, idx)); + } + return val; + } + else { + return this._rawData.getItem(this.getRawIndex(idx)); + } +}; + +/** + * @param {number} idx + * @param {boolean} [notDefaultIdx=false] + * @return {string} + */ +listProto.getName = function (idx) { + var rawIndex = this.getRawIndex(idx); + return this._nameList[rawIndex] + || getRawValueFromStore(this, this._nameDimIdx, rawIndex) + || ''; +}; + +/** + * @param {number} idx + * @param {boolean} [notDefaultIdx=false] + * @return {string} + */ +listProto.getId = function (idx) { + return getId(this, this.getRawIndex(idx)); +}; + +function getId(list, rawIndex) { + var id = list._idList[rawIndex]; + if (id == null) { + id = getRawValueFromStore(list, list._idDimIdx, rawIndex); + } + if (id == null) { + // FIXME Check the usage in graph, should not use prefix. + id = ID_PREFIX + rawIndex; + } + return id; +} + +function normalizeDimensions(dimensions) { + if (!isArray(dimensions)) { + dimensions = [dimensions]; + } + return dimensions; +} + +function validateDimensions(list, dims) { + for (var i = 0; i < dims.length; i++) { + // stroage may be empty when no data, so use + // dimensionInfos to check. + if (!list._dimensionInfos[dims[i]]) { + console.error('Unkown dimension ' + dims[i]); + } + } +} + +/** + * Data iteration + * @param {string|Array.} + * @param {Function} cb + * @param {*} [context=this] + * + * @example + * list.each('x', function (x, idx) {}); + * list.each(['x', 'y'], function (x, y, idx) {}); + * list.each(function (idx) {}) + */ +listProto.each = function (dims, cb, context, contextCompat) { + 'use strict'; + + if (!this._count) { + return; + } + + if (typeof dims === 'function') { + contextCompat = context; + context = cb; + cb = dims; + dims = []; + } + + // contextCompat just for compat echarts3 + context = context || contextCompat || this; + + dims = map(normalizeDimensions(dims), this.getDimension, this); + + if (__DEV__) { + validateDimensions(this, dims); + } + + var dimSize = dims.length; + + for (var i = 0; i < this.count(); i++) { + // Simple optimization + switch (dimSize) { + case 0: + cb.call(context, i); + break; + case 1: + cb.call(context, this.get(dims[0], i), i); + break; + case 2: + cb.call(context, this.get(dims[0], i), this.get(dims[1], i), i); + break; + default: + var k = 0; + var value = []; + for (; k < dimSize; k++) { + value[k] = this.get(dims[k], i); + } + // Index + value[k] = i; + cb.apply(context, value); + } + } +}; + +/** + * Data filter + * @param {string|Array.} + * @param {Function} cb + * @param {*} [context=this] + */ +listProto.filterSelf = function (dimensions, cb, context, contextCompat) { + 'use strict'; + + if (!this._count) { + return; + } + + if (typeof dimensions === 'function') { + contextCompat = context; + context = cb; + cb = dimensions; + dimensions = []; + } + + // contextCompat just for compat echarts3 + context = context || contextCompat || this; + + dimensions = map( + normalizeDimensions(dimensions), this.getDimension, this + ); + + if (__DEV__) { + validateDimensions(this, dimensions); + } + + + var count = this.count(); + var Ctor = getIndicesCtor(this); + var newIndices = new Ctor(count); + var value = []; + var dimSize = dimensions.length; + + var offset = 0; + var dim0 = dimensions[0]; + + for (var i = 0; i < count; i++) { + var keep; + var rawIdx = this.getRawIndex(i); + // Simple optimization + if (dimSize === 0) { + keep = cb.call(context, i); + } + else if (dimSize === 1) { + var val = this._getFast(dim0, rawIdx); + keep = cb.call(context, val, i); + } + else { + for (var k = 0; k < dimSize; k++) { + value[k] = this._getFast(dim0, rawIdx); + } + value[k] = i; + keep = cb.apply(context, value); + } + if (keep) { + newIndices[offset++] = rawIdx; + } + } + + // Set indices after filtered. + if (offset < count) { + this._indices = newIndices; + } + this._count = offset; + // Reset data extent + this._extent = {}; + + this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; + + return this; +}; + +/** + * Select data in range. (For optimization of filter) + * (Manually inline code, support 5 million data filtering in data zoom.) + */ +listProto.selectRange = function (range) { + 'use strict'; + + if (!this._count) { + return; + } + + var dimensions = []; + for (var dim in range) { + if (range.hasOwnProperty(dim)) { + dimensions.push(dim); + } + } + + if (__DEV__) { + validateDimensions(this, dimensions); + } + + var dimSize = dimensions.length; + if (!dimSize) { + return; + } + + var originalCount = this.count(); + var Ctor = getIndicesCtor(this); + var newIndices = new Ctor(originalCount); + + var offset = 0; + var dim0 = dimensions[0]; + + var min = range[dim0][0]; + var max = range[dim0][1]; + + var quickFinished = false; + if (!this._indices) { + // Extreme optimization for common case. About 2x faster in chrome. + var idx = 0; + if (dimSize === 1) { + var dimStorage = this._storage[dimensions[0]]; + for (var k = 0; k < this._chunkCount; k++) { + var chunkStorage = dimStorage[k]; + var len = Math.min(this._count - k * this._chunkSize, this._chunkSize); + for (var i = 0; i < len; i++) { + var val = chunkStorage[i]; + // NaN will not be filtered. Consider the case, in line chart, empty + // value indicates the line should be broken. But for the case like + // scatter plot, a data item with empty value will not be rendered, + // but the axis extent may be effected if some other dim of the data + // item has value. Fortunately it is not a significant negative effect. + if ( + (val >= min && val <= max) || isNaN(val) + ) { + newIndices[offset++] = idx; + } + idx++; + } + } + quickFinished = true; + } + else if (dimSize === 2) { + var dimStorage = this._storage[dim0]; + var dimStorage2 = this._storage[dimensions[1]]; + var min2 = range[dimensions[1]][0]; + var max2 = range[dimensions[1]][1]; + for (var k = 0; k < this._chunkCount; k++) { + var chunkStorage = dimStorage[k]; + var chunkStorage2 = dimStorage2[k]; + var len = Math.min(this._count - k * this._chunkSize, this._chunkSize); + for (var i = 0; i < len; i++) { + var val = chunkStorage[i]; + var val2 = chunkStorage2[i]; + // Do not filter NaN, see comment above. + if (( + (val >= min && val <= max) || isNaN(val) + ) + && ( + (val2 >= min2 && val2 <= max2) || isNaN(val2) + ) + ) { + newIndices[offset++] = idx; + } + idx++; + } + } + quickFinished = true; + } + } + if (!quickFinished) { + if (dimSize === 1) { + for (var i = 0; i < originalCount; i++) { + var rawIndex = this.getRawIndex(i); + var val = this._getFast(dim0, rawIndex); + // Do not filter NaN, see comment above. + if ( + (val >= min && val <= max) || isNaN(val) + ) { + newIndices[offset++] = rawIndex; + } + } + } + else { + for (var i = 0; i < originalCount; i++) { + var keep = true; + var rawIndex = this.getRawIndex(i); + for (var k = 0; k < dimSize; k++) { + var dimk = dimensions[k]; + var val = this._getFast(dim, rawIndex); + // Do not filter NaN, see comment above. + if (val < range[dimk][0] || val > range[dimk][1]) { + keep = false; + } + } + if (keep) { + newIndices[offset++] = this.getRawIndex(i); + } + } + } + } + + // Set indices after filtered. + if (offset < originalCount) { + this._indices = newIndices; + } + this._count = offset; + // Reset data extent + this._extent = {}; + + this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; + + return this; +}; + +/** + * Data mapping to a plain array + * @param {string|Array.} [dimensions] + * @param {Function} cb + * @param {*} [context=this] + * @return {Array} + */ +listProto.mapArray = function (dimensions, cb, context, contextCompat) { + 'use strict'; + + if (typeof dimensions === 'function') { + contextCompat = context; + context = cb; + cb = dimensions; + dimensions = []; + } + + // contextCompat just for compat echarts3 + context = context || contextCompat || this; + + var result = []; + this.each(dimensions, function () { + result.push(cb && cb.apply(this, arguments)); + }, context); + return result; +}; + +// Data in excludeDimensions is copied, otherwise transfered. +function cloneListForMapAndSample(original, excludeDimensions) { + var allDimensions = original.dimensions; + var list = new List( + map(allDimensions, original.getDimensionInfo, original), + original.hostModel + ); + // FIXME If needs stackedOn, value may already been stacked + transferProperties(list, original); + + var storage = list._storage = {}; + var originalStorage = original._storage; + + // Init storage + for (var i = 0; i < allDimensions.length; i++) { + var dim = allDimensions[i]; + if (originalStorage[dim]) { + // Notice that we do not reset invertedIndicesMap here, becuase + // there is no scenario of mapping or sampling ordinal dimension. + if (indexOf(excludeDimensions, dim) >= 0) { + storage[dim] = cloneDimStore(originalStorage[dim]); + list._rawExtent[dim] = getInitialExtent(); + list._extent[dim] = null; + } + else { + // Direct reference for other dimensions + storage[dim] = originalStorage[dim]; + } + } + } + return list; +} + +function cloneDimStore(originalDimStore) { + var newDimStore = new Array(originalDimStore.length); + for (var j = 0; j < originalDimStore.length; j++) { + newDimStore[j] = cloneChunk(originalDimStore[j]); + } + return newDimStore; +} + +function getInitialExtent() { + return [Infinity, -Infinity]; +} + +/** + * Data mapping to a new List with given dimensions + * @param {string|Array.} dimensions + * @param {Function} cb + * @param {*} [context=this] + * @return {Array} + */ +listProto.map = function (dimensions, cb, context, contextCompat) { + 'use strict'; + + // contextCompat just for compat echarts3 + context = context || contextCompat || this; + + dimensions = map( + normalizeDimensions(dimensions), this.getDimension, this + ); + + if (__DEV__) { + validateDimensions(this, dimensions); + } + + var list = cloneListForMapAndSample(this, dimensions); + + // Following properties are all immutable. + // So we can reference to the same value + list._indices = this._indices; + list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; + + var storage = list._storage; + + var tmpRetValue = []; + var chunkSize = this._chunkSize; + var dimSize = dimensions.length; + var dataCount = this.count(); + var values = []; + var rawExtent = list._rawExtent; + + for (var dataIndex = 0; dataIndex < dataCount; dataIndex++) { + for (var dimIndex = 0; dimIndex < dimSize; dimIndex++) { + values[dimIndex] = this.get(dimensions[dimIndex], dataIndex /*, stack */); + } + values[dimSize] = dataIndex; + + var retValue = cb && cb.apply(context, values); + if (retValue != null) { + // a number or string (in oridinal dimension)? + if (typeof retValue !== 'object') { + tmpRetValue[0] = retValue; + retValue = tmpRetValue; + } + + var rawIndex = this.getRawIndex(dataIndex); + var chunkIndex = Math.floor(rawIndex / chunkSize); + var chunkOffset = rawIndex % chunkSize; + + for (var i = 0; i < retValue.length; i++) { + var dim = dimensions[i]; + var val = retValue[i]; + var rawExtentOnDim = rawExtent[dim]; + + var dimStore = storage[dim]; + if (dimStore) { + dimStore[chunkIndex][chunkOffset] = val; + } + + if (val < rawExtentOnDim[0]) { + rawExtentOnDim[0] = val; + } + if (val > rawExtentOnDim[1]) { + rawExtentOnDim[1] = val; + } + } + } + } + + return list; +}; + +/** + * Large data down sampling on given dimension + * @param {string} dimension + * @param {number} rate + * @param {Function} sampleValue + * @param {Function} sampleIndex Sample index for name and id + */ +listProto.downSample = function (dimension, rate, sampleValue, sampleIndex) { + var list = cloneListForMapAndSample(this, [dimension]); + var targetStorage = list._storage; + + var frameValues = []; + var frameSize = Math.floor(1 / rate); + + var dimStore = targetStorage[dimension]; + var len = this.count(); + var chunkSize = this._chunkSize; + var rawExtentOnDim = list._rawExtent[dimension]; + + var newIndices = new (getIndicesCtor(this))(len); + + var offset = 0; + for (var i = 0; i < len; i += frameSize) { + // Last frame + if (frameSize > len - i) { + frameSize = len - i; + frameValues.length = frameSize; + } + for (var k = 0; k < frameSize; k++) { + var dataIdx = this.getRawIndex(i + k); + var originalChunkIndex = Math.floor(dataIdx / chunkSize); + var originalChunkOffset = dataIdx % chunkSize; + frameValues[k] = dimStore[originalChunkIndex][originalChunkOffset]; + } + var value = sampleValue(frameValues); + var sampleFrameIdx = this.getRawIndex( + Math.min(i + sampleIndex(frameValues, value) || 0, len - 1) + ); + var sampleChunkIndex = Math.floor(sampleFrameIdx / chunkSize); + var sampleChunkOffset = sampleFrameIdx % chunkSize; + // Only write value on the filtered data + dimStore[sampleChunkIndex][sampleChunkOffset] = value; + + if (value < rawExtentOnDim[0]) { + rawExtentOnDim[0] = value; + } + if (value > rawExtentOnDim[1]) { + rawExtentOnDim[1] = value; + } + + newIndices[offset++] = sampleFrameIdx; + } + + list._count = offset; + list._indices = newIndices; + + list.getRawIndex = getRawIndexWithIndices; + + return list; +}; + +/** + * Get model of one data item. + * + * @param {number} idx + */ +// FIXME Model proxy ? +listProto.getItemModel = function (idx) { + var hostModel = this.hostModel; + return new Model(this.getRawDataItem(idx), hostModel, hostModel && hostModel.ecModel); +}; + +/** + * Create a data differ + * @param {module:echarts/data/List} otherList + * @return {module:echarts/data/DataDiffer} + */ +listProto.diff = function (otherList) { + var thisList = this; + + return new DataDiffer( + otherList ? otherList.getIndices() : [], + this.getIndices(), + function (idx) { + return getId(otherList, idx); + }, + function (idx) { + return getId(thisList, idx); + } + ); +}; +/** + * Get visual property. + * @param {string} key + */ +listProto.getVisual = function (key) { + var visual = this._visual; + return visual && visual[key]; +}; + +/** + * Set visual property + * @param {string|Object} key + * @param {*} [value] + * + * @example + * setVisual('color', color); + * setVisual({ + * 'color': color + * }); + */ +listProto.setVisual = function (key, val) { + if (isObject$4(key)) { + for (var name in key) { + if (key.hasOwnProperty(name)) { + this.setVisual(name, key[name]); + } + } + return; + } + this._visual = this._visual || {}; + this._visual[key] = val; +}; + +/** + * Set layout property. + * @param {string|Object} key + * @param {*} [val] + */ +listProto.setLayout = function (key, val) { + if (isObject$4(key)) { + for (var name in key) { + if (key.hasOwnProperty(name)) { + this.setLayout(name, key[name]); + } + } + return; + } + this._layout[key] = val; +}; + +/** + * Get layout property. + * @param {string} key. + * @return {*} + */ +listProto.getLayout = function (key) { + return this._layout[key]; +}; + +/** + * Get layout of single data item + * @param {number} idx + */ +listProto.getItemLayout = function (idx) { + return this._itemLayouts[idx]; +}; + +/** + * Set layout of single data item + * @param {number} idx + * @param {Object} layout + * @param {boolean=} [merge=false] + */ +listProto.setItemLayout = function (idx, layout, merge$$1) { + this._itemLayouts[idx] = merge$$1 + ? extend(this._itemLayouts[idx] || {}, layout) + : layout; +}; + +/** + * Clear all layout of single data item + */ +listProto.clearItemLayouts = function () { + this._itemLayouts.length = 0; +}; + +/** + * Get visual property of single data item + * @param {number} idx + * @param {string} key + * @param {boolean} [ignoreParent=false] + */ +listProto.getItemVisual = function (idx, key, ignoreParent) { + var itemVisual = this._itemVisuals[idx]; + var val = itemVisual && itemVisual[key]; + if (val == null && !ignoreParent) { + // Use global visual property + return this.getVisual(key); + } + return val; +}; + +/** + * Set visual property of single data item + * + * @param {number} idx + * @param {string|Object} key + * @param {*} [value] + * + * @example + * setItemVisual(0, 'color', color); + * setItemVisual(0, { + * 'color': color + * }); + */ +listProto.setItemVisual = function (idx, key, value) { + var itemVisual = this._itemVisuals[idx] || {}; + var hasItemVisual = this.hasItemVisual; + this._itemVisuals[idx] = itemVisual; + + if (isObject$4(key)) { + for (var name in key) { + if (key.hasOwnProperty(name)) { + itemVisual[name] = key[name]; + hasItemVisual[name] = true; + } + } + return; + } + itemVisual[key] = value; + hasItemVisual[key] = true; +}; + +/** + * Clear itemVisuals and list visual. + */ +listProto.clearAllVisual = function () { + this._visual = {}; + this._itemVisuals = []; + this.hasItemVisual = {}; +}; + +var setItemDataAndSeriesIndex = function (child) { + child.seriesIndex = this.seriesIndex; + child.dataIndex = this.dataIndex; + child.dataType = this.dataType; +}; +/** + * Set graphic element relative to data. It can be set as null + * @param {number} idx + * @param {module:zrender/Element} [el] + */ +listProto.setItemGraphicEl = function (idx, el) { + var hostModel = this.hostModel; + + if (el) { + // Add data index and series index for indexing the data by element + // Useful in tooltip + el.dataIndex = idx; + el.dataType = this.dataType; + el.seriesIndex = hostModel && hostModel.seriesIndex; + if (el.type === 'group') { + el.traverse(setItemDataAndSeriesIndex, el); + } + } + + this._graphicEls[idx] = el; +}; + +/** + * @param {number} idx + * @return {module:zrender/Element} + */ +listProto.getItemGraphicEl = function (idx) { + return this._graphicEls[idx]; +}; + +/** + * @param {Function} cb + * @param {*} context + */ +listProto.eachItemGraphicEl = function (cb, context) { + each$1(this._graphicEls, function (el, idx) { + if (el) { + cb && cb.call(context, el, idx); + } + }); +}; + +/** + * Shallow clone a new list except visual and layout properties, and graph elements. + * New list only change the indices. + */ +listProto.cloneShallow = function (list) { + if (!list) { + var dimensionInfoList = map(this.dimensions, this.getDimensionInfo, this); + list = new List(dimensionInfoList, this.hostModel); + } + + // FIXME + list._storage = this._storage; + + transferProperties(list, this); + + // Clone will not change the data extent and indices + if (this._indices) { + var Ctor = this._indices.constructor; + list._indices = new Ctor(this._indices); + } + else { + list._indices = null; + } + list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; + + return list; +}; + +/** + * Wrap some method to add more feature + * @param {string} methodName + * @param {Function} injectFunction + */ +listProto.wrapMethod = function (methodName, injectFunction) { + var originalMethod = this[methodName]; + if (typeof originalMethod !== 'function') { + return; + } + this.__wrappedMethods = this.__wrappedMethods || []; + this.__wrappedMethods.push(methodName); + this[methodName] = function () { + var res = originalMethod.apply(this, arguments); + return injectFunction.apply(this, [res].concat(slice(arguments))); + }; +}; + +// Methods that create a new list based on this list should be listed here. +// Notice that those method should `RETURN` the new list. +listProto.TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'map']; +// Methods that change indices of this list should be listed here. +listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange']; + +/* +* 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. +*/ + +/** + * @deprecated + * Use `echarts/data/helper/createDimensions` instead. + */ + +/** + * @see {module:echarts/test/ut/spec/data/completeDimensions} + * + * This method builds the relationship between: + * + "what the coord sys or series requires (see `sysDims`)", + * + "what the user defines (in `encode` and `dimensions`, see `opt.dimsDef` and `opt.encodeDef`)" + * + "what the data source provids (see `source`)". + * + * Some guess strategy will be adapted if user does not define something. + * If no 'value' dimension specified, the first no-named dimension will be + * named as 'value'. + * + * @param {Array.} sysDims Necessary dimensions, like ['x', 'y'], which + * provides not only dim template, but also default order. + * properties: 'name', 'type', 'displayName'. + * `name` of each item provides default coord name. + * [{dimsDef: [string|Object, ...]}, ...] dimsDef of sysDim item provides default dim name, and + * provide dims count that the sysDim required. + * [{ordinalMeta}] can be specified. + * @param {module:echarts/data/Source|Array|Object} source or data (for compatibal with pervious) + * @param {Object} [opt] + * @param {Array.} [opt.dimsDef] option.series.dimensions User defined dimensions + * For example: ['asdf', {name, type}, ...]. + * @param {Object|HashMap} [opt.encodeDef] option.series.encode {x: 2, y: [3, 1], tooltip: [1, 2], label: 3} + * @param {Function} [opt.encodeDefaulter] Called if no `opt.encodeDef` exists. + * If not specified, auto find the next available data dim. + * param source {module:data/Source} + * param dimCount {number} + * return {Object} encode Never be `null/undefined`. + * @param {string} [opt.generateCoord] Generate coord dim with the given name. + * If not specified, extra dim names will be: + * 'value', 'value0', 'value1', ... + * @param {number} [opt.generateCoordCount] By default, the generated dim name is `generateCoord`. + * If `generateCoordCount` specified, the generated dim names will be: + * `generateCoord` + 0, `generateCoord` + 1, ... + * can be Infinity, indicate that use all of the remain columns. + * @param {number} [opt.dimCount] If not specified, guess by the first data item. + * @return {Array.} + */ +function completeDimensions(sysDims, source, opt) { + if (!Source.isInstance(source)) { + source = Source.seriesDataToSource(source); + } + + opt = opt || {}; + sysDims = (sysDims || []).slice(); + var dimsDef = (opt.dimsDef || []).slice(); + var dataDimNameMap = createHashMap(); + var coordDimNameMap = createHashMap(); + // var valueCandidate; + var result = []; + + var dimCount = getDimCount(source, sysDims, dimsDef, opt.dimCount); + + // Apply user defined dims (`name` and `type`) and init result. + for (var i = 0; i < dimCount; i++) { + var dimDefItem = dimsDef[i] = extend( + {}, isObject$1(dimsDef[i]) ? dimsDef[i] : {name: dimsDef[i]} + ); + var userDimName = dimDefItem.name; + var resultItem = result[i] = new DataDimensionInfo(); + // Name will be applied later for avoiding duplication. + if (userDimName != null && dataDimNameMap.get(userDimName) == null) { + // Only if `series.dimensions` is defined in option + // displayName, will be set, and dimension will be diplayed vertically in + // tooltip by default. + resultItem.name = resultItem.displayName = userDimName; + dataDimNameMap.set(userDimName, i); + } + dimDefItem.type != null && (resultItem.type = dimDefItem.type); + dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName); + } + + var encodeDef = opt.encodeDef; + if (!encodeDef && opt.encodeDefaulter) { + encodeDef = opt.encodeDefaulter(source, dimCount); + } + encodeDef = createHashMap(encodeDef); + + // Set `coordDim` and `coordDimIndex` by `encodeDef` and normalize `encodeDef`. + encodeDef.each(function (dataDims, coordDim) { + dataDims = normalizeToArray(dataDims).slice(); + + // Note: It is allowed that `dataDims.length` is `0`, e.g., options is + // `{encode: {x: -1, y: 1}}`. Should not filter anything in + // this case. + if (dataDims.length === 1 && !isString(dataDims[0]) && dataDims[0] < 0) { + encodeDef.set(coordDim, false); + return; + } + + var validDataDims = encodeDef.set(coordDim, []); + each$1(dataDims, function (resultDimIdx, idx) { + // The input resultDimIdx can be dim name or index. + isString(resultDimIdx) && (resultDimIdx = dataDimNameMap.get(resultDimIdx)); + if (resultDimIdx != null && resultDimIdx < dimCount) { + validDataDims[idx] = resultDimIdx; + applyDim(result[resultDimIdx], coordDim, idx); + } + }); + }); + + // Apply templetes and default order from `sysDims`. + var availDimIdx = 0; + each$1(sysDims, function (sysDimItem, sysDimIndex) { + var coordDim; + var sysDimItem; + var sysDimItemDimsDef; + var sysDimItemOtherDims; + if (isString(sysDimItem)) { + coordDim = sysDimItem; + sysDimItem = {}; + } + else { + coordDim = sysDimItem.name; + var ordinalMeta = sysDimItem.ordinalMeta; + sysDimItem.ordinalMeta = null; + sysDimItem = clone(sysDimItem); + sysDimItem.ordinalMeta = ordinalMeta; + // `coordDimIndex` should not be set directly. + sysDimItemDimsDef = sysDimItem.dimsDef; + sysDimItemOtherDims = sysDimItem.otherDims; + sysDimItem.name = sysDimItem.coordDim = sysDimItem.coordDimIndex = + sysDimItem.dimsDef = sysDimItem.otherDims = null; + } + + var dataDims = encodeDef.get(coordDim); + + // negative resultDimIdx means no need to mapping. + if (dataDims === false) { + return; + } + + var dataDims = normalizeToArray(dataDims); + + // dimensions provides default dim sequences. + if (!dataDims.length) { + for (var i = 0; i < (sysDimItemDimsDef && sysDimItemDimsDef.length || 1); i++) { + while (availDimIdx < result.length && result[availDimIdx].coordDim != null) { + availDimIdx++; + } + availDimIdx < result.length && dataDims.push(availDimIdx++); + } + } + + // Apply templates. + each$1(dataDims, function (resultDimIdx, coordDimIndex) { + var resultItem = result[resultDimIdx]; + applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex); + if (resultItem.name == null && sysDimItemDimsDef) { + var sysDimItemDimsDefItem = sysDimItemDimsDef[coordDimIndex]; + !isObject$1(sysDimItemDimsDefItem) && (sysDimItemDimsDefItem = {name: sysDimItemDimsDefItem}); + resultItem.name = resultItem.displayName = sysDimItemDimsDefItem.name; + resultItem.defaultTooltip = sysDimItemDimsDefItem.defaultTooltip; + } + // FIXME refactor, currently only used in case: {otherDims: {tooltip: false}} + sysDimItemOtherDims && defaults(resultItem.otherDims, sysDimItemOtherDims); + }); + }); + + function applyDim(resultItem, coordDim, coordDimIndex) { + if (OTHER_DIMENSIONS.get(coordDim) != null) { + resultItem.otherDims[coordDim] = coordDimIndex; + } + else { + resultItem.coordDim = coordDim; + resultItem.coordDimIndex = coordDimIndex; + coordDimNameMap.set(coordDim, true); + } + } + + // Make sure the first extra dim is 'value'. + var generateCoord = opt.generateCoord; + var generateCoordCount = opt.generateCoordCount; + var fromZero = generateCoordCount != null; + generateCoordCount = generateCoord ? (generateCoordCount || 1) : 0; + var extra = generateCoord || 'value'; + + // Set dim `name` and other `coordDim` and other props. + for (var resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) { + var resultItem = result[resultDimIdx] = result[resultDimIdx] || new DataDimensionInfo(); + var coordDim = resultItem.coordDim; + + if (coordDim == null) { + resultItem.coordDim = genName( + extra, coordDimNameMap, fromZero + ); + resultItem.coordDimIndex = 0; + if (!generateCoord || generateCoordCount <= 0) { + resultItem.isExtraCoord = true; + } + generateCoordCount--; + } + + resultItem.name == null && (resultItem.name = genName( + resultItem.coordDim, + dataDimNameMap + )); + + if (resultItem.type == null + && ( + guessOrdinal(source, resultDimIdx, resultItem.name) === BE_ORDINAL.Must + // Consider the case: + // { + // dataset: {source: [ + // ['2001', 123], + // ['2002', 456], + // ... + // ['The others', 987], + // ]}, + // series: {type: 'pie'} + // } + // The first colum should better be treated as a "ordinal" although it + // might not able to be detected as an "ordinal" by `guessOrdinal`. + || (resultItem.isExtraCoord + && (resultItem.otherDims.itemName != null + || resultItem.otherDims.seriesName != null + ) + ) + ) + ) { + resultItem.type = 'ordinal'; + } + } + + return result; +} + +// ??? TODO +// Originally detect dimCount by data[0]. Should we +// optimize it to only by sysDims and dimensions and encode. +// So only necessary dims will be initialized. +// But +// (1) custom series should be considered. where other dims +// may be visited. +// (2) sometimes user need to calcualte bubble size or use visualMap +// on other dimensions besides coordSys needed. +// So, dims that is not used by system, should be shared in storage? +function getDimCount(source, sysDims, dimsDef, optDimCount) { + // Note that the result dimCount should not small than columns count + // of data, otherwise `dataDimNameMap` checking will be incorrect. + var dimCount = Math.max( + source.dimensionsDetectCount || 1, + sysDims.length, + dimsDef.length, + optDimCount || 0 + ); + each$1(sysDims, function (sysDimItem) { + var sysDimItemDimsDef = sysDimItem.dimsDef; + sysDimItemDimsDef && (dimCount = Math.max(dimCount, sysDimItemDimsDef.length)); + }); + return dimCount; +} + +function genName(name, map$$1, fromZero) { + if (fromZero || map$$1.get(name) != null) { + var i = 0; + while (map$$1.get(name + i) != null) { + i++; + } + name += i; + } + map$$1.set(name, true); + return name; +} + +/* +* 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. +*/ + +/** + * Substitute `completeDimensions`. + * `completeDimensions` is to be deprecated. + */ +/** + * @param {module:echarts/data/Source|module:echarts/data/List} source or data. + * @param {Object|Array} [opt] + * @param {Array.} [opt.coordDimensions=[]] + * @param {number} [opt.dimensionsCount] + * @param {string} [opt.generateCoord] + * @param {string} [opt.generateCoordCount] + * @param {Array.} [opt.dimensionsDefine=source.dimensionsDefine] Overwrite source define. + * @param {Object|HashMap} [opt.encodeDefine=source.encodeDefine] Overwrite source define. + * @param {Function} [opt.encodeDefaulter] Make default encode if user not specified. + * @return {Array.} dimensionsInfo + */ +var createDimensions = function (source, opt) { + opt = opt || {}; + return completeDimensions(opt.coordDimensions || [], source, { + dimsDef: opt.dimensionsDefine || source.dimensionsDefine, + encodeDef: opt.encodeDefine || source.encodeDefine, + dimCount: opt.dimensionsCount, + encodeDefaulter: opt.encodeDefaulter, + generateCoord: opt.generateCoord, + generateCoordCount: opt.generateCoordCount + }); +}; + +/* +* 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. +*/ + +/** + * Helper for model references. + * There are many manners to refer axis/coordSys. + */ + +// TODO +// merge relevant logic to this file? +// check: "modelHelper" of tooltip and "BrushTargetManager". + +/** + * @class + * For example: + * { + * coordSysName: 'cartesian2d', + * coordSysDims: ['x', 'y', ...], + * axisMap: HashMap({ + * x: xAxisModel, + * y: yAxisModel + * }), + * categoryAxisMap: HashMap({ + * x: xAxisModel, + * y: undefined + * }), + * // The index of the first category axis in `coordSysDims`. + * // `null/undefined` means no category axis exists. + * firstCategoryDimIndex: 1, + * // To replace user specified encode. + * } + */ +function CoordSysInfo(coordSysName) { + /** + * @type {string} + */ + this.coordSysName = coordSysName; + /** + * @type {Array.} + */ + this.coordSysDims = []; + /** + * @type {module:zrender/core/util#HashMap} + */ + this.axisMap = createHashMap(); + /** + * @type {module:zrender/core/util#HashMap} + */ + this.categoryAxisMap = createHashMap(); + /** + * @type {number} + */ + this.firstCategoryDimIndex = null; +} + +/** + * @return {module:model/referHelper#CoordSysInfo} + */ +function getCoordSysInfoBySeries(seriesModel) { + var coordSysName = seriesModel.get('coordinateSystem'); + var result = new CoordSysInfo(coordSysName); + var fetch = fetchers[coordSysName]; + if (fetch) { + fetch(seriesModel, result, result.axisMap, result.categoryAxisMap); + return result; + } +} + +var fetchers = { + + cartesian2d: function (seriesModel, result, axisMap, categoryAxisMap) { + var xAxisModel = seriesModel.getReferringComponents('xAxis')[0]; + var yAxisModel = seriesModel.getReferringComponents('yAxis')[0]; + + if (__DEV__) { + if (!xAxisModel) { + throw new Error('xAxis "' + retrieve( + seriesModel.get('xAxisIndex'), + seriesModel.get('xAxisId'), + 0 + ) + '" not found'); + } + if (!yAxisModel) { + throw new Error('yAxis "' + retrieve( + seriesModel.get('xAxisIndex'), + seriesModel.get('yAxisId'), + 0 + ) + '" not found'); + } + } + + result.coordSysDims = ['x', 'y']; + axisMap.set('x', xAxisModel); + axisMap.set('y', yAxisModel); + + if (isCategory(xAxisModel)) { + categoryAxisMap.set('x', xAxisModel); + result.firstCategoryDimIndex = 0; + } + if (isCategory(yAxisModel)) { + categoryAxisMap.set('y', yAxisModel); + result.firstCategoryDimIndex == null & (result.firstCategoryDimIndex = 1); + } + }, + + singleAxis: function (seriesModel, result, axisMap, categoryAxisMap) { + var singleAxisModel = seriesModel.getReferringComponents('singleAxis')[0]; + + if (__DEV__) { + if (!singleAxisModel) { + throw new Error('singleAxis should be specified.'); + } + } + + result.coordSysDims = ['single']; + axisMap.set('single', singleAxisModel); + + if (isCategory(singleAxisModel)) { + categoryAxisMap.set('single', singleAxisModel); + result.firstCategoryDimIndex = 0; + } + }, + + polar: function (seriesModel, result, axisMap, categoryAxisMap) { + var polarModel = seriesModel.getReferringComponents('polar')[0]; + var radiusAxisModel = polarModel.findAxisModel('radiusAxis'); + var angleAxisModel = polarModel.findAxisModel('angleAxis'); + + if (__DEV__) { + if (!angleAxisModel) { + throw new Error('angleAxis option not found'); + } + if (!radiusAxisModel) { + throw new Error('radiusAxis option not found'); + } + } + + result.coordSysDims = ['radius', 'angle']; + axisMap.set('radius', radiusAxisModel); + axisMap.set('angle', angleAxisModel); + + if (isCategory(radiusAxisModel)) { + categoryAxisMap.set('radius', radiusAxisModel); + result.firstCategoryDimIndex = 0; + } + if (isCategory(angleAxisModel)) { + categoryAxisMap.set('angle', angleAxisModel); + result.firstCategoryDimIndex == null && (result.firstCategoryDimIndex = 1); + } + }, + + geo: function (seriesModel, result, axisMap, categoryAxisMap) { + result.coordSysDims = ['lng', 'lat']; + }, + + parallel: function (seriesModel, result, axisMap, categoryAxisMap) { + var ecModel = seriesModel.ecModel; + var parallelModel = ecModel.getComponent( + 'parallel', seriesModel.get('parallelIndex') + ); + var coordSysDims = result.coordSysDims = parallelModel.dimensions.slice(); + + each$1(parallelModel.parallelAxisIndex, function (axisIndex, index) { + var axisModel = ecModel.getComponent('parallelAxis', axisIndex); + var axisDim = coordSysDims[index]; + axisMap.set(axisDim, axisModel); + + if (isCategory(axisModel) && result.firstCategoryDimIndex == null) { + categoryAxisMap.set(axisDim, axisModel); + result.firstCategoryDimIndex = index; + } + }); + } +}; + +function isCategory(axisModel) { + return axisModel.get('type') === 'category'; +} + +/* +* 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. +*/ + +/** + * Note that it is too complicated to support 3d stack by value + * (have to create two-dimension inverted index), so in 3d case + * we just support that stacked by index. + * + * @param {module:echarts/model/Series} seriesModel + * @param {Array.} dimensionInfoList The same as the input of . + * The input dimensionInfoList will be modified. + * @param {Object} [opt] + * @param {boolean} [opt.stackedCoordDimension=''] Specify a coord dimension if needed. + * @param {boolean} [opt.byIndex=false] + * @return {Object} calculationInfo + * { + * stackedDimension: string + * stackedByDimension: string + * isStackedByIndex: boolean + * stackedOverDimension: string + * stackResultDimension: string + * } + */ +function enableDataStack(seriesModel, dimensionInfoList, opt) { + opt = opt || {}; + var byIndex = opt.byIndex; + var stackedCoordDimension = opt.stackedCoordDimension; + + // Compatibal: when `stack` is set as '', do not stack. + var mayStack = !!(seriesModel && seriesModel.get('stack')); + var stackedByDimInfo; + var stackedDimInfo; + var stackResultDimension; + var stackedOverDimension; + + each$1(dimensionInfoList, function (dimensionInfo, index) { + if (isString(dimensionInfo)) { + dimensionInfoList[index] = dimensionInfo = {name: dimensionInfo}; + } + + if (mayStack && !dimensionInfo.isExtraCoord) { + // Find the first ordinal dimension as the stackedByDimInfo. + if (!byIndex && !stackedByDimInfo && dimensionInfo.ordinalMeta) { + stackedByDimInfo = dimensionInfo; + } + // Find the first stackable dimension as the stackedDimInfo. + if (!stackedDimInfo + && dimensionInfo.type !== 'ordinal' + && dimensionInfo.type !== 'time' + && (!stackedCoordDimension || stackedCoordDimension === dimensionInfo.coordDim) + ) { + stackedDimInfo = dimensionInfo; + } + } + }); + + if (stackedDimInfo && !byIndex && !stackedByDimInfo) { + // Compatible with previous design, value axis (time axis) only stack by index. + // It may make sense if the user provides elaborately constructed data. + byIndex = true; + } + + // Add stack dimension, they can be both calculated by coordinate system in `unionExtent`. + // That put stack logic in List is for using conveniently in echarts extensions, but it + // might not be a good way. + if (stackedDimInfo) { + // Use a weird name that not duplicated with other names. + stackResultDimension = '__\0ecstackresult'; + stackedOverDimension = '__\0ecstackedover'; + + // Create inverted index to fast query index by value. + if (stackedByDimInfo) { + stackedByDimInfo.createInvertedIndices = true; + } + + var stackedDimCoordDim = stackedDimInfo.coordDim; + var stackedDimType = stackedDimInfo.type; + var stackedDimCoordIndex = 0; + + each$1(dimensionInfoList, function (dimensionInfo) { + if (dimensionInfo.coordDim === stackedDimCoordDim) { + stackedDimCoordIndex++; + } + }); + + dimensionInfoList.push({ + name: stackResultDimension, + coordDim: stackedDimCoordDim, + coordDimIndex: stackedDimCoordIndex, + type: stackedDimType, + isExtraCoord: true, + isCalculationCoord: true + }); + + stackedDimCoordIndex++; + + dimensionInfoList.push({ + name: stackedOverDimension, + // This dimension contains stack base (generally, 0), so do not set it as + // `stackedDimCoordDim` to avoid extent calculation, consider log scale. + coordDim: stackedOverDimension, + coordDimIndex: stackedDimCoordIndex, + type: stackedDimType, + isExtraCoord: true, + isCalculationCoord: true + }); + } + + return { + stackedDimension: stackedDimInfo && stackedDimInfo.name, + stackedByDimension: stackedByDimInfo && stackedByDimInfo.name, + isStackedByIndex: byIndex, + stackedOverDimension: stackedOverDimension, + stackResultDimension: stackResultDimension + }; +} + +/** + * @param {module:echarts/data/List} data + * @param {string} stackedDim + */ +function isDimensionStacked(data, stackedDim /*, stackedByDim*/) { + // Each single series only maps to one pair of axis. So we do not need to + // check stackByDim, whatever stacked by a dimension or stacked by index. + return !!stackedDim && stackedDim === data.getCalculationInfo('stackedDimension'); + // && ( + // stackedByDim != null + // ? stackedByDim === data.getCalculationInfo('stackedByDimension') + // : data.getCalculationInfo('isStackedByIndex') + // ); +} + +/** + * @param {module:echarts/data/List} data + * @param {string} targetDim + * @param {string} [stackedByDim] If not input this parameter, check whether + * stacked by index. + * @return {string} dimension + */ +function getStackedDimension(data, targetDim) { + return isDimensionStacked(data, targetDim) + ? data.getCalculationInfo('stackResultDimension') + : targetDim; +} + +/* +* 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. +*/ + +/** + * @param {module:echarts/data/Source|Array} source Or raw data. + * @param {module:echarts/model/Series} seriesModel + * @param {Object} [opt] + * @param {string} [opt.generateCoord] + * @param {boolean} [opt.useEncodeDefaulter] + */ +function createListFromArray(source, seriesModel, opt) { + opt = opt || {}; + + if (!Source.isInstance(source)) { + source = Source.seriesDataToSource(source); + } + + var coordSysName = seriesModel.get('coordinateSystem'); + var registeredCoordSys = CoordinateSystemManager.get(coordSysName); + + var coordSysInfo = getCoordSysInfoBySeries(seriesModel); + + var coordSysDimDefs; + + if (coordSysInfo) { + coordSysDimDefs = map(coordSysInfo.coordSysDims, function (dim) { + var dimInfo = {name: dim}; + var axisModel = coordSysInfo.axisMap.get(dim); + if (axisModel) { + var axisType = axisModel.get('type'); + dimInfo.type = getDimensionTypeByAxis(axisType); + // dimInfo.stackable = isStackable(axisType); + } + return dimInfo; + }); + } + + if (!coordSysDimDefs) { + // Get dimensions from registered coordinate system + coordSysDimDefs = (registeredCoordSys && ( + registeredCoordSys.getDimensionsInfo + ? registeredCoordSys.getDimensionsInfo() + : registeredCoordSys.dimensions.slice() + )) || ['x', 'y']; + } + + var dimInfoList = createDimensions(source, { + coordDimensions: coordSysDimDefs, + generateCoord: opt.generateCoord, + encodeDefaulter: opt.useEncodeDefaulter + ? curry(makeSeriesEncodeForAxisCoordSys, coordSysDimDefs, seriesModel) + : null + }); + + var firstCategoryDimIndex; + var hasNameEncode; + coordSysInfo && each$1(dimInfoList, function (dimInfo, dimIndex) { + var coordDim = dimInfo.coordDim; + var categoryAxisModel = coordSysInfo.categoryAxisMap.get(coordDim); + if (categoryAxisModel) { + if (firstCategoryDimIndex == null) { + firstCategoryDimIndex = dimIndex; + } + dimInfo.ordinalMeta = categoryAxisModel.getOrdinalMeta(); + } + if (dimInfo.otherDims.itemName != null) { + hasNameEncode = true; + } + }); + if (!hasNameEncode && firstCategoryDimIndex != null) { + dimInfoList[firstCategoryDimIndex].otherDims.itemName = 0; + } + + var stackCalculationInfo = enableDataStack(seriesModel, dimInfoList); + + var list = new List(dimInfoList, seriesModel); + + list.setCalculationInfo(stackCalculationInfo); + + var dimValueGetter = (firstCategoryDimIndex != null && isNeedCompleteOrdinalData(source)) + ? function (itemOpt, dimName, dataIndex, dimIndex) { + // Use dataIndex as ordinal value in categoryAxis + return dimIndex === firstCategoryDimIndex + ? dataIndex + : this.defaultDimValueGetter(itemOpt, dimName, dataIndex, dimIndex); + } + : null; + + list.hasItemOption = false; + list.initData(source, null, dimValueGetter); + + return list; +} + +function isNeedCompleteOrdinalData(source) { + if (source.sourceFormat === SOURCE_FORMAT_ORIGINAL) { + var sampleItem = firstDataNotNull(source.data || []); + return sampleItem != null + && !isArray(getDataItemValue(sampleItem)); + } +} + +function firstDataNotNull(data) { + var i = 0; + while (i < data.length && data[i] == null) { + i++; + } + return data[i]; +} + +/* +* 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. +*/ + +/** + * // Scale class management + * @module echarts/scale/Scale + */ + +/** + * @param {Object} [setting] + */ +function Scale(setting) { + this._setting = setting || {}; + + /** + * Extent + * @type {Array.} + * @protected + */ + this._extent = [Infinity, -Infinity]; + + /** + * Step is calculated in adjustExtent + * @type {Array.} + * @protected + */ + this._interval = 0; + + this.init && this.init.apply(this, arguments); +} + +/** + * Parse input val to valid inner number. + * @param {*} val + * @return {number} + */ +Scale.prototype.parse = function (val) { + // Notice: This would be a trap here, If the implementation + // of this method depends on extent, and this method is used + // before extent set (like in dataZoom), it would be wrong. + // Nevertheless, parse does not depend on extent generally. + return val; +}; + +Scale.prototype.getSetting = function (name) { + return this._setting[name]; +}; + +Scale.prototype.contain = function (val) { + var extent = this._extent; + return val >= extent[0] && val <= extent[1]; +}; + +/** + * Normalize value to linear [0, 1], return 0.5 if extent span is 0 + * @param {number} val + * @return {number} + */ +Scale.prototype.normalize = function (val) { + var extent = this._extent; + if (extent[1] === extent[0]) { + return 0.5; + } + return (val - extent[0]) / (extent[1] - extent[0]); +}; + +/** + * Scale normalized value + * @param {number} val + * @return {number} + */ +Scale.prototype.scale = function (val) { + var extent = this._extent; + return val * (extent[1] - extent[0]) + extent[0]; +}; + +/** + * Set extent from data + * @param {Array.} other + */ +Scale.prototype.unionExtent = function (other) { + var extent = this._extent; + other[0] < extent[0] && (extent[0] = other[0]); + other[1] > extent[1] && (extent[1] = other[1]); + // not setExtent because in log axis it may transformed to power + // this.setExtent(extent[0], extent[1]); +}; + +/** + * Set extent from data + * @param {module:echarts/data/List} data + * @param {string} dim + */ +Scale.prototype.unionExtentFromData = function (data, dim) { + this.unionExtent(data.getApproximateExtent(dim)); +}; + +/** + * Get extent + * @return {Array.} + */ +Scale.prototype.getExtent = function () { + return this._extent.slice(); +}; + +/** + * Set extent + * @param {number} start + * @param {number} end + */ +Scale.prototype.setExtent = function (start, end) { + var thisExtent = this._extent; + if (!isNaN(start)) { + thisExtent[0] = start; + } + if (!isNaN(end)) { + thisExtent[1] = end; + } +}; + +/** + * When axis extent depends on data and no data exists, + * axis ticks should not be drawn, which is named 'blank'. + */ +Scale.prototype.isBlank = function () { + return this._isBlank; +}, + +/** + * When axis extent depends on data and no data exists, + * axis ticks should not be drawn, which is named 'blank'. + */ +Scale.prototype.setBlank = function (isBlank) { + this._isBlank = isBlank; +}; + +/** + * @abstract + * @param {*} tick + * @return {string} label of the tick. + */ +Scale.prototype.getLabel = null; + + +enableClassExtend(Scale); +enableClassManagement(Scale, { + registerWhenExtend: true +}); + +/* +* 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. +*/ + +/** + * @constructor + * @param {Object} [opt] + * @param {Object} [opt.categories=[]] + * @param {Object} [opt.needCollect=false] + * @param {Object} [opt.deduplication=false] + */ +function OrdinalMeta(opt) { + + /** + * @readOnly + * @type {Array.} + */ + this.categories = opt.categories || []; + + /** + * @private + * @type {boolean} + */ + this._needCollect = opt.needCollect; + + /** + * @private + * @type {boolean} + */ + this._deduplication = opt.deduplication; + + /** + * @private + * @type {boolean} + */ + this._map; +} + +/** + * @param {module:echarts/model/Model} axisModel + * @return {module:echarts/data/OrdinalMeta} + */ +OrdinalMeta.createByAxisModel = function (axisModel) { + var option = axisModel.option; + var data = option.data; + var categories = data && map(data, getName); + + return new OrdinalMeta({ + categories: categories, + needCollect: !categories, + // deduplication is default in axis. + deduplication: option.dedplication !== false + }); +}; + +var proto$1 = OrdinalMeta.prototype; + +/** + * @param {string} category + * @return {number} ordinal + */ +proto$1.getOrdinal = function (category) { + return getOrCreateMap(this).get(category); +}; + +/** + * @param {*} category + * @return {number} The ordinal. If not found, return NaN. + */ +proto$1.parseAndCollect = function (category) { + var index; + var needCollect = this._needCollect; + + // The value of category dim can be the index of the given category set. + // This feature is only supported when !needCollect, because we should + // consider a common case: a value is 2017, which is a number but is + // expected to be tread as a category. This case usually happen in dataset, + // where it happent to be no need of the index feature. + if (typeof category !== 'string' && !needCollect) { + return category; + } + + // Optimize for the scenario: + // category is ['2012-01-01', '2012-01-02', ...], where the input + // data has been ensured not duplicate and is large data. + // Notice, if a dataset dimension provide categroies, usually echarts + // should remove duplication except user tell echarts dont do that + // (set axis.deduplication = false), because echarts do not know whether + // the values in the category dimension has duplication (consider the + // parallel-aqi example) + if (needCollect && !this._deduplication) { + index = this.categories.length; + this.categories[index] = category; + return index; + } + + var map$$1 = getOrCreateMap(this); + index = map$$1.get(category); + + if (index == null) { + if (needCollect) { + index = this.categories.length; + this.categories[index] = category; + map$$1.set(category, index); + } + else { + index = NaN; + } + } + + return index; +}; + +// Consider big data, do not create map until needed. +function getOrCreateMap(ordinalMeta) { + return ordinalMeta._map || ( + ordinalMeta._map = createHashMap(ordinalMeta.categories) + ); +} + +function getName(obj) { + if (isObject$1(obj) && obj.value != null) { + return obj.value; + } + else { + return obj + ''; + } +} + +/* +* 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. +*/ + +/** + * Linear continuous scale + * @module echarts/coord/scale/Ordinal + * + * http://en.wikipedia.org/wiki/Level_of_measurement + */ + +// FIXME only one data + +var scaleProto = Scale.prototype; + +var OrdinalScale = Scale.extend({ + + type: 'ordinal', + + /** + * @param {module:echarts/data/OrdianlMeta|Array.} ordinalMeta + */ + init: function (ordinalMeta, extent) { + // Caution: Should not use instanceof, consider ec-extensions using + // import approach to get OrdinalMeta class. + if (!ordinalMeta || isArray(ordinalMeta)) { + ordinalMeta = new OrdinalMeta({categories: ordinalMeta}); + } + this._ordinalMeta = ordinalMeta; + this._extent = extent || [0, ordinalMeta.categories.length - 1]; + }, + + parse: function (val) { + return typeof val === 'string' + ? this._ordinalMeta.getOrdinal(val) + // val might be float. + : Math.round(val); + }, + + contain: function (rank) { + rank = this.parse(rank); + return scaleProto.contain.call(this, rank) + && this._ordinalMeta.categories[rank] != null; + }, + + /** + * Normalize given rank or name to linear [0, 1] + * @param {number|string} [val] + * @return {number} + */ + normalize: function (val) { + return scaleProto.normalize.call(this, this.parse(val)); + }, + + scale: function (val) { + return Math.round(scaleProto.scale.call(this, val)); + }, + + /** + * @return {Array} + */ + getTicks: function () { + var ticks = []; + var extent = this._extent; + var rank = extent[0]; + + while (rank <= extent[1]) { + ticks.push(rank); + rank++; + } + + return ticks; + }, + + /** + * Get item on rank n + * @param {number} n + * @return {string} + */ + getLabel: function (n) { + if (!this.isBlank()) { + // Note that if no data, ordinalMeta.categories is an empty array. + return this._ordinalMeta.categories[n]; + } + }, + + /** + * @return {number} + */ + count: function () { + return this._extent[1] - this._extent[0] + 1; + }, + + /** + * @override + */ + unionExtentFromData: function (data, dim) { + this.unionExtent(data.getApproximateExtent(dim)); + }, + + getOrdinalMeta: function () { + return this._ordinalMeta; + }, + + niceTicks: noop, + niceExtent: noop +}); + +/** + * @return {module:echarts/scale/Time} + */ +OrdinalScale.create = function () { + return new OrdinalScale(); +}; + +/* +* 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. +*/ + +/** + * For testable. + */ + +var roundNumber$1 = round$1; + +/** + * @param {Array.} extent Both extent[0] and extent[1] should be valid number. + * Should be extent[0] < extent[1]. + * @param {number} splitNumber splitNumber should be >= 1. + * @param {number} [minInterval] + * @param {number} [maxInterval] + * @return {Object} {interval, intervalPrecision, niceTickExtent} + */ +function intervalScaleNiceTicks(extent, splitNumber, minInterval, maxInterval) { + var result = {}; + var span = extent[1] - extent[0]; + + var interval = result.interval = nice(span / splitNumber, true); + if (minInterval != null && interval < minInterval) { + interval = result.interval = minInterval; + } + if (maxInterval != null && interval > maxInterval) { + interval = result.interval = maxInterval; + } + // Tow more digital for tick. + var precision = result.intervalPrecision = getIntervalPrecision(interval); + // Niced extent inside original extent + var niceTickExtent = result.niceTickExtent = [ + roundNumber$1(Math.ceil(extent[0] / interval) * interval, precision), + roundNumber$1(Math.floor(extent[1] / interval) * interval, precision) + ]; + + fixExtent(niceTickExtent, extent); + + return result; +} + +/** + * @param {number} interval + * @return {number} interval precision + */ +function getIntervalPrecision(interval) { + // Tow more digital for tick. + return getPrecisionSafe(interval) + 2; +} + +function clamp(niceTickExtent, idx, extent) { + niceTickExtent[idx] = Math.max(Math.min(niceTickExtent[idx], extent[1]), extent[0]); +} + +// In some cases (e.g., splitNumber is 1), niceTickExtent may be out of extent. +function fixExtent(niceTickExtent, extent) { + !isFinite(niceTickExtent[0]) && (niceTickExtent[0] = extent[0]); + !isFinite(niceTickExtent[1]) && (niceTickExtent[1] = extent[1]); + clamp(niceTickExtent, 0, extent); + clamp(niceTickExtent, 1, extent); + if (niceTickExtent[0] > niceTickExtent[1]) { + niceTickExtent[0] = niceTickExtent[1]; + } +} + +/* +* 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. +*/ + +/** + * Interval scale + * @module echarts/scale/Interval + */ + + +var roundNumber = round$1; + +/** + * @alias module:echarts/coord/scale/Interval + * @constructor + */ +var IntervalScale = Scale.extend({ + + type: 'interval', + + _interval: 0, + + _intervalPrecision: 2, + + setExtent: function (start, end) { + var thisExtent = this._extent; + //start,end may be a Number like '25',so... + if (!isNaN(start)) { + thisExtent[0] = parseFloat(start); + } + if (!isNaN(end)) { + thisExtent[1] = parseFloat(end); + } + }, + + unionExtent: function (other) { + var extent = this._extent; + other[0] < extent[0] && (extent[0] = other[0]); + other[1] > extent[1] && (extent[1] = other[1]); + + // unionExtent may called by it's sub classes + IntervalScale.prototype.setExtent.call(this, extent[0], extent[1]); + }, + /** + * Get interval + */ + getInterval: function () { + return this._interval; + }, + + /** + * Set interval + */ + setInterval: function (interval) { + this._interval = interval; + // Dropped auto calculated niceExtent and use user setted extent + // We assume user wan't to set both interval, min, max to get a better result + this._niceExtent = this._extent.slice(); + + this._intervalPrecision = getIntervalPrecision(interval); + }, + + /** + * @param {boolean} [expandToNicedExtent=false] If expand the ticks to niced extent. + * @return {Array.} + */ + getTicks: function (expandToNicedExtent) { + var interval = this._interval; + var extent = this._extent; + var niceTickExtent = this._niceExtent; + var intervalPrecision = this._intervalPrecision; + + var ticks = []; + // If interval is 0, return []; + if (!interval) { + return ticks; + } + + // Consider this case: using dataZoom toolbox, zoom and zoom. + var safeLimit = 10000; + + if (extent[0] < niceTickExtent[0]) { + if (expandToNicedExtent) { + ticks.push(roundNumber(niceTickExtent[0] - interval)); + } + else { + ticks.push(extent[0]); + } + } + var tick = niceTickExtent[0]; + + while (tick <= niceTickExtent[1]) { + ticks.push(tick); + // Avoid rounding error + tick = roundNumber(tick + interval, intervalPrecision); + if (tick === ticks[ticks.length - 1]) { + // Consider out of safe float point, e.g., + // -3711126.9907707 + 2e-10 === -3711126.9907707 + break; + } + if (ticks.length > safeLimit) { + return []; + } + } + // Consider this case: the last item of ticks is smaller + // than niceTickExtent[1] and niceTickExtent[1] === extent[1]. + var lastNiceTick = ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1]; + if (extent[1] > lastNiceTick) { + if (expandToNicedExtent) { + ticks.push(lastNiceTick + interval); + } + else { + ticks.push(extent[1]); + } + } + + return ticks; + }, + + /** + * @param {number} [splitNumber=5] + * @return {Array.>} + */ + getMinorTicks: function (splitNumber) { + var ticks = this.getTicks(true); + var minorTicks = []; + var extent = this.getExtent(); + + for (var i = 1; i < ticks.length; i++) { + var nextTick = ticks[i]; + var prevTick = ticks[i - 1]; + var count = 0; + var minorTicksGroup = []; + var interval = nextTick - prevTick; + var minorInterval = interval / splitNumber; + + while (count < splitNumber - 1) { + var minorTick = round$1(prevTick + (count + 1) * minorInterval); + + // For the first and last interval. The count may be less than splitNumber. + if (minorTick > extent[0] && minorTick < extent[1]) { + minorTicksGroup.push(minorTick); + } + count++; + } + minorTicks.push(minorTicksGroup); + } + + return minorTicks; + }, + + /** + * @param {number} data + * @param {Object} [opt] + * @param {number|string} [opt.precision] If 'auto', use nice presision. + * @param {boolean} [opt.pad] returns 1.50 but not 1.5 if precision is 2. + * @return {string} + */ + getLabel: function (data, opt) { + if (data == null) { + return ''; + } + + var precision = opt && opt.precision; + + if (precision == null) { + precision = getPrecisionSafe(data) || 0; + } + else if (precision === 'auto') { + // Should be more precise then tick. + precision = this._intervalPrecision; + } + + // (1) If `precision` is set, 12.005 should be display as '12.00500'. + // (2) Use roundNumber (toFixed) to avoid scientific notation like '3.5e-7'. + data = roundNumber(data, precision, true); + + return addCommas(data); + }, + + /** + * Update interval and extent of intervals for nice ticks + * + * @param {number} [splitNumber = 5] Desired number of ticks + * @param {number} [minInterval] + * @param {number} [maxInterval] + */ + niceTicks: function (splitNumber, minInterval, maxInterval) { + splitNumber = splitNumber || 5; + var extent = this._extent; + var span = extent[1] - extent[0]; + if (!isFinite(span)) { + return; + } + // User may set axis min 0 and data are all negative + // FIXME If it needs to reverse ? + if (span < 0) { + span = -span; + extent.reverse(); + } + + var result = intervalScaleNiceTicks( + extent, splitNumber, minInterval, maxInterval + ); + + this._intervalPrecision = result.intervalPrecision; + this._interval = result.interval; + this._niceExtent = result.niceTickExtent; + }, + + /** + * Nice extent. + * @param {Object} opt + * @param {number} [opt.splitNumber = 5] Given approx tick number + * @param {boolean} [opt.fixMin=false] + * @param {boolean} [opt.fixMax=false] + * @param {boolean} [opt.minInterval] + * @param {boolean} [opt.maxInterval] + */ + niceExtent: function (opt) { + var extent = this._extent; + // If extent start and end are same, expand them + if (extent[0] === extent[1]) { + if (extent[0] !== 0) { + // Expand extent + var expandSize = extent[0]; + // In the fowllowing case + // Axis has been fixed max 100 + // Plus data are all 100 and axis extent are [100, 100]. + // Extend to the both side will cause expanded max is larger than fixed max. + // So only expand to the smaller side. + if (!opt.fixMax) { + extent[1] += expandSize / 2; + extent[0] -= expandSize / 2; + } + else { + extent[0] -= expandSize / 2; + } + } + else { + extent[1] = 1; + } + } + var span = extent[1] - extent[0]; + // If there are no data and extent are [Infinity, -Infinity] + if (!isFinite(span)) { + extent[0] = 0; + extent[1] = 1; + } + + this.niceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval); + + // var extent = this._extent; + var interval = this._interval; + + if (!opt.fixMin) { + extent[0] = roundNumber(Math.floor(extent[0] / interval) * interval); + } + if (!opt.fixMax) { + extent[1] = roundNumber(Math.ceil(extent[1] / interval) * interval); + } + } +}); + +/** + * @return {module:echarts/scale/Time} + */ +IntervalScale.create = function () { + return new IntervalScale(); +}; + +/* +* 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. +*/ + +/* global Float32Array */ + +var STACK_PREFIX = '__ec_stack_'; +var LARGE_BAR_MIN_WIDTH = 0.5; + +var LargeArr = typeof Float32Array !== 'undefined' ? Float32Array : Array; + +function getSeriesStackId(seriesModel) { + return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex; +} + +function getAxisKey(axis) { + return axis.dim + axis.index; +} + +/** + * @param {Object} opt + * @param {module:echarts/coord/Axis} opt.axis Only support category axis currently. + * @param {number} opt.count Positive interger. + * @param {number} [opt.barWidth] + * @param {number} [opt.barMaxWidth] + * @param {number} [opt.barMinWidth] + * @param {number} [opt.barGap] + * @param {number} [opt.barCategoryGap] + * @return {Object} {width, offset, offsetCenter} If axis.type is not 'category', return undefined. + */ +function getLayoutOnAxis(opt) { + var params = []; + var baseAxis = opt.axis; + var axisKey = 'axis0'; + + if (baseAxis.type !== 'category') { + return; + } + var bandWidth = baseAxis.getBandWidth(); + + for (var i = 0; i < opt.count || 0; i++) { + params.push(defaults({ + bandWidth: bandWidth, + axisKey: axisKey, + stackId: STACK_PREFIX + i + }, opt)); + } + var widthAndOffsets = doCalBarWidthAndOffset(params); + + var result = []; + for (var i = 0; i < opt.count; i++) { + var item = widthAndOffsets[axisKey][STACK_PREFIX + i]; + item.offsetCenter = item.offset + item.width / 2; + result.push(item); + } + + return result; +} + +function prepareLayoutBarSeries(seriesType, ecModel) { + var seriesModels = []; + ecModel.eachSeriesByType(seriesType, function (seriesModel) { + // Check series coordinate, do layout for cartesian2d only + if (isOnCartesian(seriesModel) && !isInLargeMode(seriesModel)) { + seriesModels.push(seriesModel); + } + }); + return seriesModels; +} + + +/** + * Map from (baseAxis.dim + '_' + baseAxis.index) to min gap of two adjacent + * values. + * This works for time axes, value axes, and log axes. + * For a single time axis, return value is in the form like + * {'x_0': [1000000]}. + * The value of 1000000 is in milliseconds. + */ +function getValueAxesMinGaps(barSeries) { + /** + * Map from axis.index to values. + * For a single time axis, axisValues is in the form like + * {'x_0': [1495555200000, 1495641600000, 1495728000000]}. + * Items in axisValues[x], e.g. 1495555200000, are time values of all + * series. + */ + var axisValues = {}; + each$1(barSeries, function (seriesModel) { + var cartesian = seriesModel.coordinateSystem; + var baseAxis = cartesian.getBaseAxis(); + if (baseAxis.type !== 'time' && baseAxis.type !== 'value') { + return; + } + + var data = seriesModel.getData(); + var key = baseAxis.dim + '_' + baseAxis.index; + var dim = data.mapDimension(baseAxis.dim); + for (var i = 0, cnt = data.count(); i < cnt; ++i) { + var value = data.get(dim, i); + if (!axisValues[key]) { + // No previous data for the axis + axisValues[key] = [value]; + } + else { + // No value in previous series + axisValues[key].push(value); + } + // Ignore duplicated time values in the same axis + } + }); + + var axisMinGaps = []; + for (var key in axisValues) { + if (axisValues.hasOwnProperty(key)) { + var valuesInAxis = axisValues[key]; + if (valuesInAxis) { + // Sort axis values into ascending order to calculate gaps + valuesInAxis.sort(function (a, b) { + return a - b; + }); + + var min = null; + for (var j = 1; j < valuesInAxis.length; ++j) { + var delta = valuesInAxis[j] - valuesInAxis[j - 1]; + if (delta > 0) { + // Ignore 0 delta because they are of the same axis value + min = min === null ? delta : Math.min(min, delta); + } + } + // Set to null if only have one data + axisMinGaps[key] = min; + } + } + } + return axisMinGaps; +} + +function makeColumnLayout(barSeries) { + var axisMinGaps = getValueAxesMinGaps(barSeries); + + var seriesInfoList = []; + each$1(barSeries, function (seriesModel) { + var cartesian = seriesModel.coordinateSystem; + var baseAxis = cartesian.getBaseAxis(); + var axisExtent = baseAxis.getExtent(); + + var bandWidth; + if (baseAxis.type === 'category') { + bandWidth = baseAxis.getBandWidth(); + } + else if (baseAxis.type === 'value' || baseAxis.type === 'time') { + var key = baseAxis.dim + '_' + baseAxis.index; + var minGap = axisMinGaps[key]; + var extentSpan = Math.abs(axisExtent[1] - axisExtent[0]); + var scale = baseAxis.scale.getExtent(); + var scaleSpan = Math.abs(scale[1] - scale[0]); + bandWidth = minGap + ? extentSpan / scaleSpan * minGap + : extentSpan; // When there is only one data value + } + else { + var data = seriesModel.getData(); + bandWidth = Math.abs(axisExtent[1] - axisExtent[0]) / data.count(); + } + + var barWidth = parsePercent$1( + seriesModel.get('barWidth'), bandWidth + ); + var barMaxWidth = parsePercent$1( + seriesModel.get('barMaxWidth'), bandWidth + ); + var barMinWidth = parsePercent$1( + // barMinWidth by default is 1 in cartesian. Because in value axis, + // the auto-calculated bar width might be less than 1. + seriesModel.get('barMinWidth') || 1, bandWidth + ); + var barGap = seriesModel.get('barGap'); + var barCategoryGap = seriesModel.get('barCategoryGap'); + + seriesInfoList.push({ + bandWidth: bandWidth, + barWidth: barWidth, + barMaxWidth: barMaxWidth, + barMinWidth: barMinWidth, + barGap: barGap, + barCategoryGap: barCategoryGap, + axisKey: getAxisKey(baseAxis), + stackId: getSeriesStackId(seriesModel) + }); + }); + + return doCalBarWidthAndOffset(seriesInfoList); +} + +function doCalBarWidthAndOffset(seriesInfoList) { + // Columns info on each category axis. Key is cartesian name + var columnsMap = {}; + + each$1(seriesInfoList, function (seriesInfo, idx) { + var axisKey = seriesInfo.axisKey; + var bandWidth = seriesInfo.bandWidth; + var columnsOnAxis = columnsMap[axisKey] || { + bandWidth: bandWidth, + remainedWidth: bandWidth, + autoWidthCount: 0, + categoryGap: '20%', + gap: '30%', + stacks: {} + }; + var stacks = columnsOnAxis.stacks; + columnsMap[axisKey] = columnsOnAxis; + + var stackId = seriesInfo.stackId; + + if (!stacks[stackId]) { + columnsOnAxis.autoWidthCount++; + } + stacks[stackId] = stacks[stackId] || { + width: 0, + maxWidth: 0 + }; + + // Caution: In a single coordinate system, these barGrid attributes + // will be shared by series. Consider that they have default values, + // only the attributes set on the last series will work. + // Do not change this fact unless there will be a break change. + + var barWidth = seriesInfo.barWidth; + if (barWidth && !stacks[stackId].width) { + // See #6312, do not restrict width. + stacks[stackId].width = barWidth; + barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth); + columnsOnAxis.remainedWidth -= barWidth; + } + + var barMaxWidth = seriesInfo.barMaxWidth; + barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth); + var barMinWidth = seriesInfo.barMinWidth; + barMinWidth && (stacks[stackId].minWidth = barMinWidth); + var barGap = seriesInfo.barGap; + (barGap != null) && (columnsOnAxis.gap = barGap); + var barCategoryGap = seriesInfo.barCategoryGap; + (barCategoryGap != null) && (columnsOnAxis.categoryGap = barCategoryGap); + }); + + var result = {}; + + each$1(columnsMap, function (columnsOnAxis, coordSysName) { + + result[coordSysName] = {}; + + var stacks = columnsOnAxis.stacks; + var bandWidth = columnsOnAxis.bandWidth; + var categoryGap = parsePercent$1(columnsOnAxis.categoryGap, bandWidth); + var barGapPercent = parsePercent$1(columnsOnAxis.gap, 1); + + var remainedWidth = columnsOnAxis.remainedWidth; + var autoWidthCount = columnsOnAxis.autoWidthCount; + var autoWidth = (remainedWidth - categoryGap) + / (autoWidthCount + (autoWidthCount - 1) * barGapPercent); + autoWidth = Math.max(autoWidth, 0); + + // Find if any auto calculated bar exceeded maxBarWidth + each$1(stacks, function (column) { + var maxWidth = column.maxWidth; + var minWidth = column.minWidth; + + if (!column.width) { + var finalWidth = autoWidth; + if (maxWidth && maxWidth < finalWidth) { + finalWidth = Math.min(maxWidth, remainedWidth); + } + // `minWidth` has higher priority. `minWidth` decide that wheter the + // bar is able to be visible. So `minWidth` should not be restricted + // by `maxWidth` or `remainedWidth` (which is from `bandWidth`). In + // the extreme cases for `value` axis, bars are allowed to overlap + // with each other if `minWidth` specified. + if (minWidth && minWidth > finalWidth) { + finalWidth = minWidth; + } + if (finalWidth !== autoWidth) { + column.width = finalWidth; + remainedWidth -= finalWidth + barGapPercent * finalWidth; + autoWidthCount--; + } + } + else { + // `barMinWidth/barMaxWidth` has higher priority than `barWidth`, as + // CSS does. Becuase barWidth can be a percent value, where + // `barMaxWidth` can be used to restrict the final width. + var finalWidth = column.width; + if (maxWidth) { + finalWidth = Math.min(finalWidth, maxWidth); + } + // `minWidth` has higher priority, as described above + if (minWidth) { + finalWidth = Math.max(finalWidth, minWidth); + } + column.width = finalWidth; + remainedWidth -= finalWidth + barGapPercent * finalWidth; + autoWidthCount--; + } + }); + + // Recalculate width again + autoWidth = (remainedWidth - categoryGap) + / (autoWidthCount + (autoWidthCount - 1) * barGapPercent); + + autoWidth = Math.max(autoWidth, 0); + + + var widthSum = 0; + var lastColumn; + each$1(stacks, function (column, idx) { + if (!column.width) { + column.width = autoWidth; + } + lastColumn = column; + widthSum += column.width * (1 + barGapPercent); + }); + if (lastColumn) { + widthSum -= lastColumn.width * barGapPercent; + } + + var offset = -widthSum / 2; + each$1(stacks, function (column, stackId) { + result[coordSysName][stackId] = result[coordSysName][stackId] || { + bandWidth: bandWidth, + offset: offset, + width: column.width + }; + + offset += column.width * (1 + barGapPercent); + }); + }); + + return result; +} + +/** + * @param {Object} barWidthAndOffset The result of makeColumnLayout + * @param {module:echarts/coord/Axis} axis + * @param {module:echarts/model/Series} [seriesModel] If not provided, return all. + * @return {Object} {stackId: {offset, width}} or {offset, width} if seriesModel provided. + */ +function retrieveColumnLayout(barWidthAndOffset, axis, seriesModel) { + if (barWidthAndOffset && axis) { + var result = barWidthAndOffset[getAxisKey(axis)]; + if (result != null && seriesModel != null) { + result = result[getSeriesStackId(seriesModel)]; + } + return result; + } +} + +/** + * @param {string} seriesType + * @param {module:echarts/model/Global} ecModel + */ +function layout(seriesType, ecModel) { + + var seriesModels = prepareLayoutBarSeries(seriesType, ecModel); + var barWidthAndOffset = makeColumnLayout(seriesModels); + + var lastStackCoords = {}; + each$1(seriesModels, function (seriesModel) { + + var data = seriesModel.getData(); + var cartesian = seriesModel.coordinateSystem; + var baseAxis = cartesian.getBaseAxis(); + + var stackId = getSeriesStackId(seriesModel); + var columnLayoutInfo = barWidthAndOffset[getAxisKey(baseAxis)][stackId]; + var columnOffset = columnLayoutInfo.offset; + var columnWidth = columnLayoutInfo.width; + var valueAxis = cartesian.getOtherAxis(baseAxis); + + var barMinHeight = seriesModel.get('barMinHeight') || 0; + + lastStackCoords[stackId] = lastStackCoords[stackId] || []; + data.setLayout({ + bandWidth: columnLayoutInfo.bandWidth, + offset: columnOffset, + size: columnWidth + }); + + var valueDim = data.mapDimension(valueAxis.dim); + var baseDim = data.mapDimension(baseAxis.dim); + var stacked = isDimensionStacked(data, valueDim /*, baseDim*/); + var isValueAxisH = valueAxis.isHorizontal(); + + var valueAxisStart = getValueAxisStart(baseAxis, valueAxis, stacked); + + for (var idx = 0, len = data.count(); idx < len; idx++) { + var value = data.get(valueDim, idx); + var baseValue = data.get(baseDim, idx); + + // If dataZoom in filteMode: 'empty', the baseValue can be set as NaN in "axisProxy". + if (isNaN(value) || isNaN(baseValue)) { + continue; + } + + var sign = value >= 0 ? 'p' : 'n'; + var baseCoord = valueAxisStart; + + // Because of the barMinHeight, we can not use the value in + // stackResultDimension directly. + if (stacked) { + // Only ordinal axis can be stacked. + if (!lastStackCoords[stackId][baseValue]) { + lastStackCoords[stackId][baseValue] = { + p: valueAxisStart, // Positive stack + n: valueAxisStart // Negative stack + }; + } + // Should also consider #4243 + baseCoord = lastStackCoords[stackId][baseValue][sign]; + } + + var x; + var y; + var width; + var height; + + if (isValueAxisH) { + var coord = cartesian.dataToPoint([value, baseValue]); + x = baseCoord; + y = coord[1] + columnOffset; + width = coord[0] - valueAxisStart; + height = columnWidth; + + if (Math.abs(width) < barMinHeight) { + width = (width < 0 ? -1 : 1) * barMinHeight; + } + stacked && (lastStackCoords[stackId][baseValue][sign] += width); + } + else { + var coord = cartesian.dataToPoint([baseValue, value]); + x = coord[0] + columnOffset; + y = baseCoord; + width = columnWidth; + height = coord[1] - valueAxisStart; + + if (Math.abs(height) < barMinHeight) { + // Include zero to has a positive bar + height = (height <= 0 ? -1 : 1) * barMinHeight; + } + stacked && (lastStackCoords[stackId][baseValue][sign] += height); + } + + data.setItemLayout(idx, { + x: x, + y: y, + width: width, + height: height + }); + } + + }, this); +} + +// TODO: Do not support stack in large mode yet. +var largeLayout = { + + seriesType: 'bar', + + plan: createRenderPlanner(), + + reset: function (seriesModel) { + if (!isOnCartesian(seriesModel) || !isInLargeMode(seriesModel)) { + return; + } + + var data = seriesModel.getData(); + var cartesian = seriesModel.coordinateSystem; + var baseAxis = cartesian.getBaseAxis(); + var valueAxis = cartesian.getOtherAxis(baseAxis); + var valueDim = data.mapDimension(valueAxis.dim); + var baseDim = data.mapDimension(baseAxis.dim); + var valueAxisHorizontal = valueAxis.isHorizontal(); + var valueDimIdx = valueAxisHorizontal ? 0 : 1; + + var barWidth = retrieveColumnLayout( + makeColumnLayout([seriesModel]), baseAxis, seriesModel + ).width; + if (!(barWidth > LARGE_BAR_MIN_WIDTH)) { // jshint ignore:line + barWidth = LARGE_BAR_MIN_WIDTH; + } + + return {progress: progress}; + + function progress(params, data) { + var count = params.count; + var largePoints = new LargeArr(count * 2); + var largeDataIndices = new LargeArr(count); + var dataIndex; + var coord = []; + var valuePair = []; + var pointsOffset = 0; + var idxOffset = 0; + + while ((dataIndex = params.next()) != null) { + valuePair[valueDimIdx] = data.get(valueDim, dataIndex); + valuePair[1 - valueDimIdx] = data.get(baseDim, dataIndex); + + coord = cartesian.dataToPoint(valuePair, null, coord); + // Data index might not be in order, depends on `progressiveChunkMode`. + largePoints[pointsOffset++] = coord[0]; + largePoints[pointsOffset++] = coord[1]; + largeDataIndices[idxOffset++] = dataIndex; + } + + data.setLayout({ + largePoints: largePoints, + largeDataIndices: largeDataIndices, + barWidth: barWidth, + valueAxisStart: getValueAxisStart(baseAxis, valueAxis, false), + valueAxisHorizontal: valueAxisHorizontal + }); + } + } +}; + +function isOnCartesian(seriesModel) { + return seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d'; +} + +function isInLargeMode(seriesModel) { + return seriesModel.pipelineContext && seriesModel.pipelineContext.large; +} + +// See cases in `test/bar-start.html` and `#7412`, `#8747`. +function getValueAxisStart(baseAxis, valueAxis, stacked) { + return valueAxis.toGlobalCoord(valueAxis.dataToCoord(valueAxis.type === 'log' ? 1 : 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. +*/ + +/* +* A third-party license is embeded for some of the code in this file: +* The "scaleLevels" was originally copied from "d3.js" with some +* modifications made for this project. +* (See more details in the comment on the definition of "scaleLevels" below.) +* The use of the source code of this file is also subject to the terms +* and consitions of the license of "d3.js" (BSD-3Clause, see +* ). +*/ + + +// [About UTC and local time zone]: +// In most cases, `number.parseDate` will treat input data string as local time +// (except time zone is specified in time string). And `format.formateTime` returns +// local time by default. option.useUTC is false by default. This design have +// concidered these common case: +// (1) Time that is persistent in server is in UTC, but it is needed to be diplayed +// in local time by default. +// (2) By default, the input data string (e.g., '2011-01-02') should be displayed +// as its original time, without any time difference. + +var intervalScaleProto = IntervalScale.prototype; + +var mathCeil = Math.ceil; +var mathFloor = Math.floor; +var ONE_SECOND = 1000; +var ONE_MINUTE = ONE_SECOND * 60; +var ONE_HOUR = ONE_MINUTE * 60; +var ONE_DAY = ONE_HOUR * 24; + +// FIXME 公用? +var bisect = function (a, x, lo, hi) { + while (lo < hi) { + var mid = lo + hi >>> 1; + if (a[mid][1] < x) { + lo = mid + 1; + } + else { + hi = mid; + } + } + return lo; +}; + +/** + * @alias module:echarts/coord/scale/Time + * @constructor + */ +var TimeScale = IntervalScale.extend({ + type: 'time', + + /** + * @override + */ + getLabel: function (val) { + var stepLvl = this._stepLvl; + + var date = new Date(val); + + return formatTime(stepLvl[0], date, this.getSetting('useUTC')); + }, + + /** + * @override + */ + niceExtent: function (opt) { + var extent = this._extent; + // If extent start and end are same, expand them + if (extent[0] === extent[1]) { + // Expand extent + extent[0] -= ONE_DAY; + extent[1] += ONE_DAY; + } + // If there are no data and extent are [Infinity, -Infinity] + if (extent[1] === -Infinity && extent[0] === Infinity) { + var d = new Date(); + extent[1] = +new Date(d.getFullYear(), d.getMonth(), d.getDate()); + extent[0] = extent[1] - ONE_DAY; + } + + this.niceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval); + + // var extent = this._extent; + var interval = this._interval; + + if (!opt.fixMin) { + extent[0] = round$1(mathFloor(extent[0] / interval) * interval); + } + if (!opt.fixMax) { + extent[1] = round$1(mathCeil(extent[1] / interval) * interval); + } + }, + + /** + * @override + */ + niceTicks: function (approxTickNum, minInterval, maxInterval) { + approxTickNum = approxTickNum || 10; + + var extent = this._extent; + var span = extent[1] - extent[0]; + var approxInterval = span / approxTickNum; + + if (minInterval != null && approxInterval < minInterval) { + approxInterval = minInterval; + } + if (maxInterval != null && approxInterval > maxInterval) { + approxInterval = maxInterval; + } + + var scaleLevelsLen = scaleLevels.length; + var idx = bisect(scaleLevels, approxInterval, 0, scaleLevelsLen); + + var level = scaleLevels[Math.min(idx, scaleLevelsLen - 1)]; + var interval = level[1]; + // Same with interval scale if span is much larger than 1 year + if (level[0] === 'year') { + var yearSpan = span / interval; + + // From "Nice Numbers for Graph Labels" of Graphic Gems + // var niceYearSpan = numberUtil.nice(yearSpan, false); + var yearStep = nice(yearSpan / approxTickNum, true); + + interval *= yearStep; + } + + var timezoneOffset = this.getSetting('useUTC') + ? 0 : (new Date(+extent[0] || +extent[1])).getTimezoneOffset() * 60 * 1000; + var niceExtent = [ + Math.round(mathCeil((extent[0] - timezoneOffset) / interval) * interval + timezoneOffset), + Math.round(mathFloor((extent[1] - timezoneOffset) / interval) * interval + timezoneOffset) + ]; + + fixExtent(niceExtent, extent); + + this._stepLvl = level; + // Interval will be used in getTicks + this._interval = interval; + this._niceExtent = niceExtent; + }, + + parse: function (val) { + // val might be float. + return +parseDate(val); + } +}); + +each$1(['contain', 'normalize'], function (methodName) { + TimeScale.prototype[methodName] = function (val) { + return intervalScaleProto[methodName].call(this, this.parse(val)); + }; +}); + +/** + * This implementation was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + */ +var scaleLevels = [ + // Format interval + ['hh:mm:ss', ONE_SECOND], // 1s + ['hh:mm:ss', ONE_SECOND * 5], // 5s + ['hh:mm:ss', ONE_SECOND * 10], // 10s + ['hh:mm:ss', ONE_SECOND * 15], // 15s + ['hh:mm:ss', ONE_SECOND * 30], // 30s + ['hh:mm\nMM-dd', ONE_MINUTE], // 1m + ['hh:mm\nMM-dd', ONE_MINUTE * 5], // 5m + ['hh:mm\nMM-dd', ONE_MINUTE * 10], // 10m + ['hh:mm\nMM-dd', ONE_MINUTE * 15], // 15m + ['hh:mm\nMM-dd', ONE_MINUTE * 30], // 30m + ['hh:mm\nMM-dd', ONE_HOUR], // 1h + ['hh:mm\nMM-dd', ONE_HOUR * 2], // 2h + ['hh:mm\nMM-dd', ONE_HOUR * 6], // 6h + ['hh:mm\nMM-dd', ONE_HOUR * 12], // 12h + ['MM-dd\nyyyy', ONE_DAY], // 1d + ['MM-dd\nyyyy', ONE_DAY * 2], // 2d + ['MM-dd\nyyyy', ONE_DAY * 3], // 3d + ['MM-dd\nyyyy', ONE_DAY * 4], // 4d + ['MM-dd\nyyyy', ONE_DAY * 5], // 5d + ['MM-dd\nyyyy', ONE_DAY * 6], // 6d + ['week', ONE_DAY * 7], // 7d + ['MM-dd\nyyyy', ONE_DAY * 10], // 10d + ['week', ONE_DAY * 14], // 2w + ['week', ONE_DAY * 21], // 3w + ['month', ONE_DAY * 31], // 1M + ['week', ONE_DAY * 42], // 6w + ['month', ONE_DAY * 62], // 2M + ['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 + ['month', ONE_DAY * 31 * 8], // 8M + ['month', ONE_DAY * 31 * 10], // 10M + ['year', ONE_DAY * 380] // 1Y +]; + +/** + * @param {module:echarts/model/Model} + * @return {module:echarts/scale/Time} + */ +TimeScale.create = function (model) { + return new TimeScale({useUTC: model.ecModel.get('useUTC')}); +}; + +/* +* 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. +*/ + +/** + * Log scale + * @module echarts/scale/Log + */ + +// Use some method of IntervalScale +var scaleProto$1 = Scale.prototype; +var intervalScaleProto$1 = IntervalScale.prototype; + +var getPrecisionSafe$1 = getPrecisionSafe; +var roundingErrorFix = round$1; + +var mathFloor$1 = Math.floor; +var mathCeil$1 = Math.ceil; +var mathPow$1 = Math.pow; + +var mathLog = Math.log; + +var LogScale = Scale.extend({ + + type: 'log', + + base: 10, + + $constructor: function () { + Scale.apply(this, arguments); + this._originalScale = new IntervalScale(); + }, + + /** + * @param {boolean} [expandToNicedExtent=false] If expand the ticks to niced extent. + * @return {Array.} + */ + getTicks: function (expandToNicedExtent) { + var originalScale = this._originalScale; + var extent = this._extent; + var originalExtent = originalScale.getExtent(); + + return map(intervalScaleProto$1.getTicks.call(this, expandToNicedExtent), function (val) { + var powVal = round$1(mathPow$1(this.base, val)); + + // Fix #4158 + powVal = (val === extent[0] && originalScale.__fixMin) + ? fixRoundingError(powVal, originalExtent[0]) + : powVal; + powVal = (val === extent[1] && originalScale.__fixMax) + ? fixRoundingError(powVal, originalExtent[1]) + : powVal; + + return powVal; + }, this); + }, + + /** + * @param {number} splitNumber + * @return {Array.>} + */ + getMinorTicks: intervalScaleProto$1.getMinorTicks, + + /** + * @param {number} val + * @return {string} + */ + getLabel: intervalScaleProto$1.getLabel, + + /** + * @param {number} val + * @return {number} + */ + scale: function (val) { + val = scaleProto$1.scale.call(this, val); + return mathPow$1(this.base, val); + }, + + /** + * @param {number} start + * @param {number} end + */ + setExtent: function (start, end) { + var base = this.base; + start = mathLog(start) / mathLog(base); + end = mathLog(end) / mathLog(base); + intervalScaleProto$1.setExtent.call(this, start, end); + }, + + /** + * @return {number} end + */ + getExtent: function () { + var base = this.base; + var extent = scaleProto$1.getExtent.call(this); + extent[0] = mathPow$1(base, extent[0]); + extent[1] = mathPow$1(base, extent[1]); + + // Fix #4158 + var originalScale = this._originalScale; + var originalExtent = originalScale.getExtent(); + originalScale.__fixMin && (extent[0] = fixRoundingError(extent[0], originalExtent[0])); + originalScale.__fixMax && (extent[1] = fixRoundingError(extent[1], originalExtent[1])); + + return extent; + }, + + /** + * @param {Array.} extent + */ + unionExtent: function (extent) { + this._originalScale.unionExtent(extent); + + var base = this.base; + extent[0] = mathLog(extent[0]) / mathLog(base); + extent[1] = mathLog(extent[1]) / mathLog(base); + scaleProto$1.unionExtent.call(this, extent); + }, + + /** + * @override + */ + unionExtentFromData: function (data, dim) { + // TODO + // filter value that <= 0 + this.unionExtent(data.getApproximateExtent(dim)); + }, + + /** + * Update interval and extent of intervals for nice ticks + * @param {number} [approxTickNum = 10] Given approx tick number + */ + niceTicks: function (approxTickNum) { + approxTickNum = approxTickNum || 10; + var extent = this._extent; + var span = extent[1] - extent[0]; + if (span === Infinity || span <= 0) { + return; + } + + var interval = quantity(span); + var err = approxTickNum / span * interval; + + // Filter ticks to get closer to the desired count. + if (err <= 0.5) { + interval *= 10; + } + + // Interval should be integer + while (!isNaN(interval) && Math.abs(interval) < 1 && Math.abs(interval) > 0) { + interval *= 10; + } + + var niceExtent = [ + round$1(mathCeil$1(extent[0] / interval) * interval), + round$1(mathFloor$1(extent[1] / interval) * interval) + ]; + + this._interval = interval; + this._niceExtent = niceExtent; + }, + + /** + * Nice extent. + * @override + */ + niceExtent: function (opt) { + intervalScaleProto$1.niceExtent.call(this, opt); + + var originalScale = this._originalScale; + originalScale.__fixMin = opt.fixMin; + originalScale.__fixMax = opt.fixMax; + } + +}); + +each$1(['contain', 'normalize'], function (methodName) { + LogScale.prototype[methodName] = function (val) { + val = mathLog(val) / mathLog(this.base); + return scaleProto$1[methodName].call(this, val); + }; +}); + +LogScale.create = function () { + return new LogScale(); +}; + +function fixRoundingError(val, originalVal) { + return roundingErrorFix(val, getPrecisionSafe$1(originalVal)); +} + +/* +* 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. +*/ + +/** + * Get axis scale extent before niced. + * Item of returned array can only be number (including Infinity and NaN). + */ +function getScaleExtent(scale, model) { + var scaleType = scale.type; + + var min = model.getMin(); + var max = model.getMax(); + var fixMin = min != null; + var fixMax = max != null; + var originalExtent = scale.getExtent(); + + var axisDataLen; + var boundaryGap; + var span; + if (scaleType === 'ordinal') { + axisDataLen = model.getCategories().length; + } + else { + boundaryGap = model.get('boundaryGap'); + if (!isArray(boundaryGap)) { + boundaryGap = [boundaryGap || 0, boundaryGap || 0]; + } + if (typeof boundaryGap[0] === 'boolean') { + if (__DEV__) { + console.warn('Boolean type for boundaryGap is only ' + + 'allowed for ordinal axis. Please use string in ' + + 'percentage instead, e.g., "20%". Currently, ' + + 'boundaryGap is set to be 0.'); + } + boundaryGap = [0, 0]; + } + boundaryGap[0] = parsePercent$1(boundaryGap[0], 1); + boundaryGap[1] = parsePercent$1(boundaryGap[1], 1); + span = (originalExtent[1] - originalExtent[0]) + || Math.abs(originalExtent[0]); + } + + // Notice: When min/max is not set (that is, when there are null/undefined, + // which is the most common case), these cases should be ensured: + // (1) For 'ordinal', show all axis.data. + // (2) For others: + // + `boundaryGap` is applied (if min/max set, boundaryGap is + // disabled). + // + If `needCrossZero`, min/max should be zero, otherwise, min/max should + // be the result that originalExtent enlarged by boundaryGap. + // (3) If no data, it should be ensured that `scale.setBlank` is set. + + // FIXME + // (1) When min/max is 'dataMin' or 'dataMax', should boundaryGap be able to used? + // (2) When `needCrossZero` and all data is positive/negative, should it be ensured + // that the results processed by boundaryGap are positive/negative? + + if (min == null) { + min = scaleType === 'ordinal' + ? (axisDataLen ? 0 : NaN) + : originalExtent[0] - boundaryGap[0] * span; + } + if (max == null) { + max = scaleType === 'ordinal' + ? (axisDataLen ? axisDataLen - 1 : NaN) + : originalExtent[1] + boundaryGap[1] * span; + } + + if (min === 'dataMin') { + min = originalExtent[0]; + } + else if (typeof min === 'function') { + min = min({ + min: originalExtent[0], + max: originalExtent[1] + }); + } + + if (max === 'dataMax') { + max = originalExtent[1]; + } + else if (typeof max === 'function') { + max = max({ + min: originalExtent[0], + max: originalExtent[1] + }); + } + + (min == null || !isFinite(min)) && (min = NaN); + (max == null || !isFinite(max)) && (max = NaN); + + scale.setBlank( + eqNaN(min) + || eqNaN(max) + || (scaleType === 'ordinal' && !scale.getOrdinalMeta().categories.length) + ); + + // Evaluate if axis needs cross zero + if (model.getNeedCrossZero()) { + // Axis is over zero and min is not set + if (min > 0 && max > 0 && !fixMin) { + min = 0; + } + // Axis is under zero and max is not set + if (min < 0 && max < 0 && !fixMax) { + max = 0; + } + } + + // If bars are placed on a base axis of type time or interval account for axis boundary overflow and current axis + // is base axis + // FIXME + // (1) Consider support value axis, where below zero and axis `onZero` should be handled properly. + // (2) Refactor the logic with `barGrid`. Is it not need to `makeBarWidthAndOffsetInfo` twice with different extent? + // Should not depend on series type `bar`? + // (3) Fix that might overlap when using dataZoom. + // (4) Consider other chart types using `barGrid`? + // See #6728, #4862, `test/bar-overflow-time-plot.html` + var ecModel = model.ecModel; + if (ecModel && (scaleType === 'time' /*|| scaleType === 'interval' */)) { + var barSeriesModels = prepareLayoutBarSeries('bar', ecModel); + var isBaseAxisAndHasBarSeries; + + each$1(barSeriesModels, function (seriesModel) { + isBaseAxisAndHasBarSeries |= seriesModel.getBaseAxis() === model.axis; + }); + + if (isBaseAxisAndHasBarSeries) { + // Calculate placement of bars on axis + var barWidthAndOffset = makeColumnLayout(barSeriesModels); + + // Adjust axis min and max to account for overflow + var adjustedScale = adjustScaleForOverflow(min, max, model, barWidthAndOffset); + min = adjustedScale.min; + max = adjustedScale.max; + } + } + + return [min, max]; +} + +function adjustScaleForOverflow(min, max, model, barWidthAndOffset) { + + // Get Axis Length + var axisExtent = model.axis.getExtent(); + var axisLength = axisExtent[1] - axisExtent[0]; + + // Get bars on current base axis and calculate min and max overflow + var barsOnCurrentAxis = retrieveColumnLayout(barWidthAndOffset, model.axis); + if (barsOnCurrentAxis === undefined) { + return {min: min, max: max}; + } + + var minOverflow = Infinity; + each$1(barsOnCurrentAxis, function (item) { + minOverflow = Math.min(item.offset, minOverflow); + }); + var maxOverflow = -Infinity; + each$1(barsOnCurrentAxis, function (item) { + maxOverflow = Math.max(item.offset + item.width, maxOverflow); + }); + minOverflow = Math.abs(minOverflow); + maxOverflow = Math.abs(maxOverflow); + var totalOverFlow = minOverflow + maxOverflow; + + // Calulate required buffer based on old range and overflow + var oldRange = max - min; + var oldRangePercentOfNew = (1 - (minOverflow + maxOverflow) / axisLength); + var overflowBuffer = ((oldRange / oldRangePercentOfNew) - oldRange); + + max += overflowBuffer * (maxOverflow / totalOverFlow); + min -= overflowBuffer * (minOverflow / totalOverFlow); + + return {min: min, max: max}; +} + +function niceScaleExtent(scale, model) { + var extent = getScaleExtent(scale, model); + var fixMin = model.getMin() != null; + var fixMax = model.getMax() != null; + var splitNumber = model.get('splitNumber'); + + if (scale.type === 'log') { + scale.base = model.get('logBase'); + } + + var scaleType = scale.type; + scale.setExtent(extent[0], extent[1]); + scale.niceExtent({ + splitNumber: splitNumber, + fixMin: fixMin, + fixMax: fixMax, + minInterval: (scaleType === 'interval' || scaleType === 'time') + ? model.get('minInterval') : null, + maxInterval: (scaleType === 'interval' || scaleType === 'time') + ? model.get('maxInterval') : null + }); + + // If some one specified the min, max. And the default calculated interval + // is not good enough. He can specify the interval. It is often appeared + // in angle axis with angle 0 - 360. Interval calculated in interval scale is hard + // to be 60. + // FIXME + var interval = model.get('interval'); + if (interval != null) { + scale.setInterval && scale.setInterval(interval); + } +} + +/** + * @param {module:echarts/model/Model} model + * @param {string} [axisType] Default retrieve from model.type + * @return {module:echarts/scale/*} + */ +function createScaleByModel(model, axisType) { + axisType = axisType || model.get('type'); + if (axisType) { + switch (axisType) { + // Buildin scale + case 'category': + return new OrdinalScale( + model.getOrdinalMeta + ? model.getOrdinalMeta() + : model.getCategories(), + [Infinity, -Infinity] + ); + case 'value': + return new IntervalScale(); + // Extended scale, like time and log + default: + return (Scale.getClass(axisType) || IntervalScale).create(model); + } + } +} + +/** + * Check if the axis corss 0 + */ +function ifAxisCrossZero(axis) { + var dataExtent = axis.scale.getExtent(); + var min = dataExtent[0]; + var max = dataExtent[1]; + return !((min > 0 && max > 0) || (min < 0 && max < 0)); +} + +/** + * @param {module:echarts/coord/Axis} axis + * @return {Function} Label formatter function. + * param: {number} tickValue, + * param: {number} idx, the index in all ticks. + * If category axis, this param is not requied. + * return: {string} label string. + */ +function makeLabelFormatter(axis) { + var labelFormatter = axis.getLabelModel().get('formatter'); + var categoryTickStart = axis.type === 'category' ? axis.scale.getExtent()[0] : null; + + 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); + // Consider empty array + return labelFormatter; + } + else if (typeof labelFormatter === 'function') { + return function (tickValue, idx) { + // The original intention of `idx` is "the index of the tick in all ticks". + // But the previous implementation of category axis do not consider the + // `axisLabel.interval`, which cause that, for example, the `interval` is + // `1`, then the ticks "name5", "name7", "name9" are displayed, where the + // corresponding `idx` are `0`, `2`, `4`, but not `0`, `1`, `2`. So we keep + // the definition here for back compatibility. + if (categoryTickStart != null) { + idx = tickValue - categoryTickStart; + } + return labelFormatter(getAxisRawValue(axis, tickValue), idx); + }; + } + else { + return function (tick) { + return axis.scale.getLabel(tick); + }; + } +} + +function getAxisRawValue(axis, value) { + // In category axis with data zoom, tick is not the original + // index of axis.data. So tick should not be exposed to user + // in category axis. + return axis.type === 'category' ? axis.scale.getLabel(value) : value; +} + +/** + * @param {module:echarts/coord/Axis} axis + * @return {module:zrender/core/BoundingRect} Be null/undefined if no labels. + */ +function estimateLabelUnionRect(axis) { + var axisModel = axis.model; + var scale = axis.scale; + + if (!axisModel.get('axisLabel.show') || scale.isBlank()) { + return; + } + + var isCategory = axis.type === 'category'; + + var realNumberScaleTicks; + var tickCount; + var categoryScaleExtent = scale.getExtent(); + + // Optimize for large category data, avoid call `getTicks()`. + if (isCategory) { + tickCount = scale.count(); + } + else { + realNumberScaleTicks = scale.getTicks(); + tickCount = realNumberScaleTicks.length; + } + + var axisLabelModel = axis.getLabelModel(); + var labelFormatter = makeLabelFormatter(axis); + + var rect; + var step = 1; + // Simple optimization for large amount of labels + if (tickCount > 40) { + step = Math.ceil(tickCount / 40); + } + for (var i = 0; i < tickCount; i += step) { + var tickValue = realNumberScaleTicks ? realNumberScaleTicks[i] : categoryScaleExtent[0] + i; + var label = labelFormatter(tickValue); + var unrotatedSingleRect = axisLabelModel.getTextRect(label); + var singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0); + + rect ? rect.union(singleRect) : (rect = singleRect); + } + + return rect; +} + +function rotateTextRect(textRect, rotate) { + var rotateRadians = rotate * Math.PI / 180; + var boundingBox = textRect.plain(); + var beforeWidth = boundingBox.width; + var beforeHeight = boundingBox.height; + var afterWidth = beforeWidth * Math.cos(rotateRadians) + beforeHeight * Math.sin(rotateRadians); + var afterHeight = beforeWidth * Math.sin(rotateRadians) + beforeHeight * Math.cos(rotateRadians); + var rotatedRect = new BoundingRect(boundingBox.x, boundingBox.y, afterWidth, afterHeight); + + return rotatedRect; +} + +/** + * @param {module:echarts/src/model/Model} model axisLabelModel or axisTickModel + * @return {number|String} Can be null|'auto'|number|function + */ +function getOptionCategoryInterval(model) { + var interval = model.get('interval'); + return interval == null ? 'auto' : interval; +} + +/** + * Set `categoryInterval` as 0 implicitly indicates that + * show all labels reguardless of overlap. + * @param {Object} axis axisModel.axis + * @return {boolean} + */ +function shouldShowAllLabels(axis) { + return axis.type === 'category' + && getOptionCategoryInterval(axis.getLabelModel()) === 0; +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// import * as axisHelper from './axisHelper'; + +var axisModelCommonMixin = { + + /** + * @param {boolean} origin + * @return {number|string} min value or 'dataMin' or null/undefined (means auto) or NaN + */ + getMin: function (origin) { + var option = this.option; + var min = (!origin && option.rangeStart != null) + ? option.rangeStart : option.min; + + if (this.axis + && min != null + && min !== 'dataMin' + && typeof min !== 'function' + && !eqNaN(min) + ) { + min = this.axis.scale.parse(min); + } + return min; + }, + + /** + * @param {boolean} origin + * @return {number|string} max value or 'dataMax' or null/undefined (means auto) or NaN + */ + getMax: function (origin) { + var option = this.option; + var max = (!origin && option.rangeEnd != null) + ? option.rangeEnd : option.max; + + if (this.axis + && max != null + && max !== 'dataMax' + && typeof max !== 'function' + && !eqNaN(max) + ) { + max = this.axis.scale.parse(max); + } + return max; + }, + + /** + * @return {boolean} + */ + getNeedCrossZero: function () { + var option = this.option; + return (option.rangeStart != null || option.rangeEnd != null) + ? false : !option.scale; + }, + + /** + * Should be implemented by each axis model if necessary. + * @return {module:echarts/model/Component} coordinate system model + */ + getCoordSysModel: noop, + + /** + * @param {number} rangeStart Can only be finite number or null/undefined or NaN. + * @param {number} rangeEnd Can only be finite number or null/undefined or NaN. + */ + setRange: function (rangeStart, rangeEnd) { + this.option.rangeStart = rangeStart; + this.option.rangeEnd = rangeEnd; + }, + + /** + * Reset range + */ + resetRange: function () { + // rangeStart and rangeEnd is readonly. + this.option.rangeStart = this.option.rangeEnd = null; + } +}; + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// Symbol factory + +/** + * Triangle shape + * @inner + */ +var Triangle = extendShape({ + type: 'triangle', + shape: { + cx: 0, + cy: 0, + width: 0, + height: 0 + }, + buildPath: function (path, shape) { + var cx = shape.cx; + var cy = shape.cy; + var width = shape.width / 2; + var height = shape.height / 2; + path.moveTo(cx, cy - height); + path.lineTo(cx + width, cy + height); + path.lineTo(cx - width, cy + height); + path.closePath(); + } +}); + +/** + * Diamond shape + * @inner + */ +var Diamond = extendShape({ + type: 'diamond', + shape: { + cx: 0, + cy: 0, + width: 0, + height: 0 + }, + buildPath: function (path, shape) { + var cx = shape.cx; + var cy = shape.cy; + var width = shape.width / 2; + var height = shape.height / 2; + path.moveTo(cx, cy - height); + path.lineTo(cx + width, cy); + path.lineTo(cx, cy + height); + path.lineTo(cx - width, cy); + path.closePath(); + } +}); + +/** + * Pin shape + * @inner + */ +var Pin = extendShape({ + type: 'pin', + shape: { + // x, y on the cusp + x: 0, + y: 0, + width: 0, + height: 0 + }, + + buildPath: function (path, shape) { + var x = shape.x; + var y = shape.y; + var w = shape.width / 5 * 3; + // Height must be larger than width + var h = Math.max(w, shape.height); + var r = w / 2; + + // Dist on y with tangent point and circle center + var dy = r * r / (h - r); + var cy = y - h + r + dy; + var angle = Math.asin(dy / r); + // Dist on x with tangent point and circle center + var dx = Math.cos(angle) * r; + + var tanX = Math.sin(angle); + var tanY = Math.cos(angle); + + var cpLen = r * 0.6; + var cpLen2 = r * 0.7; + + path.moveTo(x - dx, cy + dy); + + path.arc( + x, cy, r, + Math.PI - angle, + Math.PI * 2 + angle + ); + path.bezierCurveTo( + x + dx - tanX * cpLen, cy + dy + tanY * cpLen, + x, y - cpLen2, + x, y + ); + path.bezierCurveTo( + x, y - cpLen2, + x - dx + tanX * cpLen, cy + dy + tanY * cpLen, + x - dx, cy + dy + ); + path.closePath(); + } +}); + +/** + * Arrow shape + * @inner + */ +var Arrow = extendShape({ + + type: 'arrow', + + shape: { + x: 0, + y: 0, + width: 0, + height: 0 + }, + + buildPath: function (ctx, shape) { + var height = shape.height; + var width = shape.width; + var x = shape.x; + var y = shape.y; + var dx = width / 3 * 2; + ctx.moveTo(x, y); + ctx.lineTo(x + dx, y + height); + ctx.lineTo(x, y + height / 4 * 3); + ctx.lineTo(x - dx, y + height); + ctx.lineTo(x, y); + ctx.closePath(); + } +}); + +/** + * Map of path contructors + * @type {Object.} + */ +var symbolCtors = { + + line: Line, + + rect: Rect, + + roundRect: Rect, + + square: Rect, + + circle: Circle, + + diamond: Diamond, + + pin: Pin, + + arrow: Arrow, + + triangle: Triangle +}; + +var symbolShapeMakers = { + + line: function (x, y, w, h, shape) { + // FIXME + shape.x1 = x; + shape.y1 = y + h / 2; + shape.x2 = x + w; + shape.y2 = y + h / 2; + }, + + rect: function (x, y, w, h, shape) { + shape.x = x; + shape.y = y; + shape.width = w; + shape.height = h; + }, + + roundRect: function (x, y, w, h, shape) { + shape.x = x; + shape.y = y; + shape.width = w; + shape.height = h; + shape.r = Math.min(w, h) / 4; + }, + + square: function (x, y, w, h, shape) { + var size = Math.min(w, h); + shape.x = x; + shape.y = y; + shape.width = size; + shape.height = size; + }, + + circle: function (x, y, w, h, shape) { + // Put circle in the center of square + shape.cx = x + w / 2; + shape.cy = y + h / 2; + shape.r = Math.min(w, h) / 2; + }, + + diamond: function (x, y, w, h, shape) { + shape.cx = x + w / 2; + shape.cy = y + h / 2; + shape.width = w; + shape.height = h; + }, + + pin: function (x, y, w, h, shape) { + shape.x = x + w / 2; + shape.y = y + h / 2; + shape.width = w; + shape.height = h; + }, + + arrow: function (x, y, w, h, shape) { + shape.x = x + w / 2; + shape.y = y + h / 2; + shape.width = w; + shape.height = h; + }, + + triangle: function (x, y, w, h, shape) { + shape.cx = x + w / 2; + shape.cy = y + h / 2; + shape.width = w; + shape.height = h; + } +}; + +var symbolBuildProxies = {}; +each$1(symbolCtors, function (Ctor, name) { + symbolBuildProxies[name] = new Ctor(); +}); + +var SymbolClz = extendShape({ + + type: 'symbol', + + shape: { + symbolType: '', + x: 0, + y: 0, + width: 0, + height: 0 + }, + + calculateTextPosition: function (out, style, rect) { + var res = calculateTextPosition(out, style, rect); + var shape = this.shape; + if (shape && shape.symbolType === 'pin' && style.textPosition === 'inside') { + res.y = rect.y + rect.height * 0.4; + } + return res; + }, + + buildPath: function (ctx, shape, inBundle) { + var symbolType = shape.symbolType; + if (symbolType !== 'none') { + var proxySymbol = symbolBuildProxies[symbolType]; + if (!proxySymbol) { + // Default rect + symbolType = 'rect'; + proxySymbol = symbolBuildProxies[symbolType]; + } + symbolShapeMakers[symbolType]( + shape.x, shape.y, shape.width, shape.height, proxySymbol.shape + ); + proxySymbol.buildPath(ctx, proxySymbol.shape, inBundle); + } + } +}); + +// Provide setColor helper method to avoid determine if set the fill or stroke outside +function symbolPathSetColor(color, innerColor) { + if (this.type !== 'image') { + var symbolStyle = this.style; + var symbolShape = this.shape; + if (symbolShape && symbolShape.symbolType === 'line') { + symbolStyle.stroke = color; + } + else if (this.__isEmptyBrush) { + symbolStyle.stroke = color; + symbolStyle.fill = innerColor || '#fff'; + } + else { + // FIXME 判断图形默认是填充还是描边,使用 onlyStroke ? + symbolStyle.fill && (symbolStyle.fill = color); + symbolStyle.stroke && (symbolStyle.stroke = color); + } + this.dirty(false); + } +} + +/** + * Create a symbol element with given symbol configuration: shape, x, y, width, height, color + * @param {string} symbolType + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + * @param {string} color + * @param {boolean} [keepAspect=false] whether to keep the ratio of w/h, + * for path and image only. + */ +function createSymbol(symbolType, x, y, w, h, color, keepAspect) { + // TODO Support image object, DynamicImage. + + var isEmpty = symbolType.indexOf('empty') === 0; + if (isEmpty) { + symbolType = symbolType.substr(5, 1).toLowerCase() + symbolType.substr(6); + } + var symbolPath; + + if (symbolType.indexOf('image://') === 0) { + symbolPath = makeImage( + symbolType.slice(8), + new BoundingRect(x, y, w, h), + keepAspect ? 'center' : 'cover' + ); + } + else if (symbolType.indexOf('path://') === 0) { + symbolPath = makePath( + symbolType.slice(7), + {}, + new BoundingRect(x, y, w, h), + keepAspect ? 'center' : 'cover' + ); + } + else { + symbolPath = new SymbolClz({ + shape: { + symbolType: symbolType, + x: x, + y: y, + width: w, + height: h + } + }); + } + + symbolPath.__isEmptyBrush = isEmpty; + + symbolPath.setColor = symbolPathSetColor; + + symbolPath.setColor(color); + + return symbolPath; +} + +/* +* 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 createGraphFromNodeEdge from './chart/helper/createGraphFromNodeEdge'; +/** + * Create a muti dimension List structure from seriesModel. + * @param {module:echarts/model/Model} seriesModel + * @return {module:echarts/data/List} list + */ +function createList(seriesModel) { + return createListFromArray(seriesModel.getSource(), seriesModel); +} + +var dataStack$1 = { + isDimensionStacked: isDimensionStacked, + enableDataStack: enableDataStack, + getStackedDimension: getStackedDimension +}; + +/** + * Create scale + * @param {Array.} dataExtent + * @param {Object|module:echarts/Model} option + */ +function createScale(dataExtent, option) { + var axisModel = option; + if (!Model.isInstance(option)) { + axisModel = new Model(option); + mixin(axisModel, axisModelCommonMixin); + } + + var scale = createScaleByModel(axisModel); + scale.setExtent(dataExtent[0], dataExtent[1]); + + niceScaleExtent(scale, axisModel); + return scale; +} + +/** + * Mixin common methods to axis model, + * + * Inlcude methods + * `getFormattedLabels() => Array.` + * `getCategories() => Array.` + * `getMin(origin: boolean) => number` + * `getMax(origin: boolean) => number` + * `getNeedCrossZero() => boolean` + * `setRange(start: number, end: number)` + * `resetRange()` + */ +function mixinAxisModelCommonMethods(Model$$1) { + mixin(Model$$1, axisModelCommonMixin); +} + +var helper = (Object.freeze || Object)({ + createList: createList, + getLayoutRect: getLayoutRect, + dataStack: dataStack$1, + createScale: createScale, + mixinAxisModelCommonMethods: mixinAxisModelCommonMethods, + completeDimensions: completeDimensions, + createDimensions: createDimensions, + createSymbol: createSymbol +}); + +var EPSILON$3 = 1e-8; + +function isAroundEqual$1(a, b) { + return Math.abs(a - b) < EPSILON$3; +} + +function contain$1(points, x, y) { + var w = 0; + var p = points[0]; + + if (!p) { + return false; + } + + for (var i = 1; i < points.length; i++) { + var p2 = points[i]; + w += windingLine(p[0], p[1], p2[0], p2[1], x, y); + p = p2; + } + + // Close polygon + var p0 = points[0]; + if (!isAroundEqual$1(p[0], p0[0]) || !isAroundEqual$1(p[1], p0[1])) { + w += windingLine(p[0], p[1], p0[0], p0[1], x, y); + } + + return w !== 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. +*/ + +/** + * @module echarts/coord/geo/Region + */ + +/** + * @param {string|Region} name + * @param {Array} geometries + * @param {Array.} cp + */ +function Region(name, geometries, cp) { + + /** + * @type {string} + * @readOnly + */ + this.name = name; + + /** + * @type {Array.} + * @readOnly + */ + this.geometries = geometries; + + if (!cp) { + var rect = this.getBoundingRect(); + cp = [ + rect.x + rect.width / 2, + rect.y + rect.height / 2 + ]; + } + else { + cp = [cp[0], cp[1]]; + } + /** + * @type {Array.} + */ + this.center = cp; +} + +Region.prototype = { + + constructor: Region, + + properties: null, + + /** + * @return {module:zrender/core/BoundingRect} + */ + getBoundingRect: function () { + var rect = this._rect; + if (rect) { + return rect; + } + + var MAX_NUMBER = Number.MAX_VALUE; + var min$$1 = [MAX_NUMBER, MAX_NUMBER]; + var max$$1 = [-MAX_NUMBER, -MAX_NUMBER]; + var min2 = []; + var max2 = []; + var geometries = this.geometries; + for (var i = 0; i < geometries.length; i++) { + // Only support polygon + if (geometries[i].type !== 'polygon') { + continue; + } + // Doesn't consider hole + var exterior = geometries[i].exterior; + fromPoints(exterior, min2, max2); + min(min$$1, min$$1, min2); + max(max$$1, max$$1, max2); + } + // No data + if (i === 0) { + min$$1[0] = min$$1[1] = max$$1[0] = max$$1[1] = 0; + } + + return (this._rect = new BoundingRect( + min$$1[0], min$$1[1], max$$1[0] - min$$1[0], max$$1[1] - min$$1[1] + )); + }, + + /** + * @param {} coord + * @return {boolean} + */ + contain: function (coord) { + var rect = this.getBoundingRect(); + var geometries = this.geometries; + if (!rect.contain(coord[0], coord[1])) { + return false; + } + loopGeo: for (var i = 0, len$$1 = geometries.length; i < len$$1; i++) { + // Only support polygon. + if (geometries[i].type !== 'polygon') { + continue; + } + var exterior = geometries[i].exterior; + var interiors = geometries[i].interiors; + if (contain$1(exterior, coord[0], coord[1])) { + // Not in the region if point is in the hole. + for (var k = 0; k < (interiors ? interiors.length : 0); k++) { + if (contain$1(interiors[k])) { + continue loopGeo; + } + } + return true; + } + } + return false; + }, + + transformTo: function (x, y, width, height) { + var rect = this.getBoundingRect(); + var aspect = rect.width / rect.height; + if (!width) { + width = aspect * height; + } + else if (!height) { + height = width / aspect; + } + var target = new BoundingRect(x, y, width, height); + var transform = rect.calculateTransform(target); + var geometries = this.geometries; + for (var i = 0; i < geometries.length; i++) { + // Only support polygon. + if (geometries[i].type !== 'polygon') { + continue; + } + var exterior = geometries[i].exterior; + var interiors = geometries[i].interiors; + for (var p = 0; p < exterior.length; p++) { + applyTransform(exterior[p], exterior[p], transform); + } + for (var h = 0; h < (interiors ? interiors.length : 0); h++) { + for (var p = 0; p < interiors[h].length; p++) { + applyTransform(interiors[h][p], interiors[h][p], transform); + } + } + } + rect = this._rect; + rect.copy(target); + // Update center + this.center = [ + 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; + } +}; + +/* +* 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. +*/ + +/** + * Parse and decode geo json + * @module echarts/coord/geo/parseGeoJson + */ + +function decode(json) { + if (!json.UTF8Encoding) { + return json; + } + var encodeScale = json.UTF8Scale; + if (encodeScale == null) { + encodeScale = 1024; + } + + var features = json.features; + + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + var geometry = feature.geometry; + var coordinates = geometry.coordinates; + var encodeOffsets = geometry.encodeOffsets; + + for (var c = 0; c < coordinates.length; c++) { + var coordinate = coordinates[c]; + + if (geometry.type === 'Polygon') { + coordinates[c] = decodePolygon( + coordinate, + encodeOffsets[c], + encodeScale + ); + } + else if (geometry.type === 'MultiPolygon') { + for (var c2 = 0; c2 < coordinate.length; c2++) { + var polygon = coordinate[c2]; + coordinate[c2] = decodePolygon( + polygon, + encodeOffsets[c][c2], + encodeScale + ); + } + } + } + } + // Has been decoded + json.UTF8Encoding = false; + return json; +} + +function decodePolygon(coordinate, encodeOffsets, encodeScale) { + var result = []; + var prevX = encodeOffsets[0]; + var prevY = encodeOffsets[1]; + + for (var i = 0; i < coordinate.length; i += 2) { + var x = coordinate.charCodeAt(i) - 64; + var y = coordinate.charCodeAt(i + 1) - 64; + // ZigZag decoding + x = (x >> 1) ^ (-(x & 1)); + y = (y >> 1) ^ (-(y & 1)); + // Delta deocding + x += prevX; + y += prevY; + + prevX = x; + prevY = y; + // Dequantize + result.push([x / encodeScale, y / encodeScale]); + } + + return result; +} + +/** + * @alias module:echarts/coord/geo/parseGeoJson + * @param {Object} geoJson + * @return {module:zrender/container/Group} + */ +var parseGeoJson$1 = function (geoJson) { + + decode(geoJson); + + return map(filter(geoJson.features, function (featureObj) { + // Output of mapshaper may have geometry null + return featureObj.geometry + && featureObj.properties + && featureObj.geometry.coordinates.length > 0; + }), function (featureObj) { + var properties = featureObj.properties; + var geo = featureObj.geometry; + + var coordinates = geo.coordinates; + + var geometries = []; + if (geo.type === 'Polygon') { + geometries.push({ + type: 'polygon', + // According to the GeoJSON specification. + // First must be exterior, and the rest are all interior(holes). + exterior: coordinates[0], + interiors: coordinates.slice(1) + }); + } + if (geo.type === 'MultiPolygon') { + each$1(coordinates, function (item) { + if (item[0]) { + geometries.push({ + type: 'polygon', + exterior: item[0], + interiors: item.slice(1) + }); + } + }); + } + + var region = new Region( + properties.name, + geometries, + properties.cp + ); + region.properties = properties; + return region; + }); +}; + +/* +* 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. +*/ + +var inner$6 = makeInner(); + +/** + * @param {module:echats/coord/Axis} axis + * @return {Object} { + * labels: [{ + * formattedLabel: string, + * rawLabel: string, + * tickValue: number + * }, ...], + * labelCategoryInterval: number + * } + */ +function createAxisLabels(axis) { + // Only ordinal scale support tick interval + return axis.type === 'category' + ? makeCategoryLabels(axis) + : makeRealNumberLabels(axis); +} + +/** + * @param {module:echats/coord/Axis} axis + * @param {module:echarts/model/Model} tickModel For example, can be axisTick, splitLine, splitArea. + * @return {Object} { + * ticks: Array. + * tickCategoryInterval: number + * } + */ +function createAxisTicks(axis, tickModel) { + // Only ordinal scale support tick interval + return axis.type === 'category' + ? makeCategoryTicks(axis, tickModel) + : {ticks: axis.scale.getTicks()}; +} + +function makeCategoryLabels(axis) { + var labelModel = axis.getLabelModel(); + var result = makeCategoryLabelsActually(axis, labelModel); + + return (!labelModel.get('show') || axis.scale.isBlank()) + ? {labels: [], labelCategoryInterval: result.labelCategoryInterval} + : result; +} + +function makeCategoryLabelsActually(axis, labelModel) { + var labelsCache = getListCache(axis, 'labels'); + var optionLabelInterval = getOptionCategoryInterval(labelModel); + var result = listCacheGet(labelsCache, optionLabelInterval); + + if (result) { + return result; + } + + var labels; + var numericLabelInterval; + + if (isFunction$1(optionLabelInterval)) { + labels = makeLabelsByCustomizedCategoryInterval(axis, optionLabelInterval); + } + else { + numericLabelInterval = optionLabelInterval === 'auto' + ? makeAutoCategoryInterval(axis) : optionLabelInterval; + labels = makeLabelsByNumericCategoryInterval(axis, numericLabelInterval); + } + + // Cache to avoid calling interval function repeatly. + return listCacheSet(labelsCache, optionLabelInterval, { + labels: labels, labelCategoryInterval: numericLabelInterval + }); +} + +function makeCategoryTicks(axis, tickModel) { + var ticksCache = getListCache(axis, 'ticks'); + var optionTickInterval = getOptionCategoryInterval(tickModel); + var result = listCacheGet(ticksCache, optionTickInterval); + + if (result) { + return result; + } + + var ticks; + var tickCategoryInterval; + + // Optimize for the case that large category data and no label displayed, + // we should not return all ticks. + if (!tickModel.get('show') || axis.scale.isBlank()) { + ticks = []; + } + + if (isFunction$1(optionTickInterval)) { + ticks = makeLabelsByCustomizedCategoryInterval(axis, optionTickInterval, true); + } + // Always use label interval by default despite label show. Consider this + // scenario, Use multiple grid with the xAxis sync, and only one xAxis shows + // labels. `splitLine` and `axisTick` should be consistent in this case. + else if (optionTickInterval === 'auto') { + var labelsResult = makeCategoryLabelsActually(axis, axis.getLabelModel()); + tickCategoryInterval = labelsResult.labelCategoryInterval; + ticks = map(labelsResult.labels, function (labelItem) { + return labelItem.tickValue; + }); + } + else { + tickCategoryInterval = optionTickInterval; + ticks = makeLabelsByNumericCategoryInterval(axis, tickCategoryInterval, true); + } + + // Cache to avoid calling interval function repeatly. + return listCacheSet(ticksCache, optionTickInterval, { + ticks: ticks, tickCategoryInterval: tickCategoryInterval + }); +} + +function makeRealNumberLabels(axis) { + var ticks = axis.scale.getTicks(); + var labelFormatter = makeLabelFormatter(axis); + return { + labels: map(ticks, function (tickValue, idx) { + return { + formattedLabel: labelFormatter(tickValue, idx), + rawLabel: axis.scale.getLabel(tickValue), + tickValue: tickValue + }; + }) + }; +} + +// Large category data calculation is performence sensitive, and ticks and label +// probably be fetched by multiple times. So we cache the result. +// axis is created each time during a ec process, so we do not need to clear cache. +function getListCache(axis, prop) { + // Because key can be funciton, and cache size always be small, we use array cache. + return inner$6(axis)[prop] || (inner$6(axis)[prop] = []); +} + +function listCacheGet(cache, key) { + for (var i = 0; i < cache.length; i++) { + if (cache[i].key === key) { + return cache[i].value; + } + } +} + +function listCacheSet(cache, key, value) { + cache.push({key: key, value: value}); + return value; +} + +function makeAutoCategoryInterval(axis) { + var result = inner$6(axis).autoInterval; + return result != null + ? result + : (inner$6(axis).autoInterval = axis.calculateCategoryInterval()); +} + +/** + * Calculate interval for category axis ticks and labels. + * To get precise result, at least one of `getRotate` and `isHorizontal` + * should be implemented in axis. + */ +function calculateCategoryInterval(axis) { + var params = fetchAutoCategoryIntervalCalculationParams(axis); + var labelFormatter = makeLabelFormatter(axis); + var rotation = (params.axisRotate - params.labelRotate) / 180 * Math.PI; + + var ordinalScale = axis.scale; + var ordinalExtent = ordinalScale.getExtent(); + // Providing this method is for optimization: + // avoid generating a long array by `getTicks` + // in large category data case. + var tickCount = ordinalScale.count(); + + if (ordinalExtent[1] - ordinalExtent[0] < 1) { + return 0; + } + + var step = 1; + // Simple optimization. Empirical value: tick count should less than 40. + if (tickCount > 40) { + step = Math.max(1, Math.floor(tickCount / 40)); + } + var tickValue = ordinalExtent[0]; + var unitSpan = axis.dataToCoord(tickValue + 1) - axis.dataToCoord(tickValue); + var unitW = Math.abs(unitSpan * Math.cos(rotation)); + var unitH = Math.abs(unitSpan * Math.sin(rotation)); + + var maxW = 0; + var maxH = 0; + + // Caution: Performance sensitive for large category data. + // Consider dataZoom, we should make appropriate step to avoid O(n) loop. + for (; tickValue <= ordinalExtent[1]; tickValue += step) { + var width = 0; + var height = 0; + + // Not precise, do not consider align and vertical align + // and each distance from axis line yet. + var rect = getBoundingRect( + labelFormatter(tickValue), params.font, 'center', 'top' + ); + // Magic number + width = rect.width * 1.3; + height = rect.height * 1.3; + + // Min size, void long loop. + maxW = Math.max(maxW, width, 7); + maxH = Math.max(maxH, height, 7); + } + + var dw = maxW / unitW; + var dh = maxH / unitH; + // 0/0 is NaN, 1/0 is Infinity. + isNaN(dw) && (dw = Infinity); + isNaN(dh) && (dh = Infinity); + var interval = Math.max(0, Math.floor(Math.min(dw, dh))); + + var cache = inner$6(axis.model); + var axisExtent = axis.getExtent(); + var lastAutoInterval = cache.lastAutoInterval; + var lastTickCount = cache.lastTickCount; + + // Use cache to keep interval stable while moving zoom window, + // otherwise the calculated interval might jitter when the zoom + // window size is close to the interval-changing size. + // For example, if all of the axis labels are `a, b, c, d, e, f, g`. + // The jitter will cause that sometimes the displayed labels are + // `a, d, g` (interval: 2) sometimes `a, c, e`(interval: 1). + if (lastAutoInterval != null + && lastTickCount != null + && Math.abs(lastAutoInterval - interval) <= 1 + && Math.abs(lastTickCount - tickCount) <= 1 + // Always choose the bigger one, otherwise the critical + // point is not the same when zooming in or zooming out. + && lastAutoInterval > interval + // If the axis change is caused by chart resize, the cache should not + // be used. Otherwise some hiden labels might not be shown again. + && cache.axisExtend0 === axisExtent[0] + && cache.axisExtend1 === axisExtent[1] + ) { + interval = lastAutoInterval; + } + // Only update cache if cache not used, otherwise the + // changing of interval is too insensitive. + else { + cache.lastTickCount = tickCount; + cache.lastAutoInterval = interval; + cache.axisExtend0 = axisExtent[0]; + cache.axisExtend1 = axisExtent[1]; + } + + return interval; +} + +function fetchAutoCategoryIntervalCalculationParams(axis) { + var labelModel = axis.getLabelModel(); + return { + axisRotate: axis.getRotate + ? axis.getRotate() + : (axis.isHorizontal && !axis.isHorizontal()) + ? 90 + : 0, + labelRotate: labelModel.get('rotate') || 0, + font: labelModel.getFont() + }; +} + +function makeLabelsByNumericCategoryInterval(axis, categoryInterval, onlyTick) { + var labelFormatter = makeLabelFormatter(axis); + var ordinalScale = axis.scale; + var ordinalExtent = ordinalScale.getExtent(); + var labelModel = axis.getLabelModel(); + var result = []; + + // TODO: axisType: ordinalTime, pick the tick from each month/day/year/... + + var step = Math.max((categoryInterval || 0) + 1, 1); + var startTick = ordinalExtent[0]; + var tickCount = ordinalScale.count(); + + // Calculate start tick based on zero if possible to keep label consistent + // while zooming and moving while interval > 0. Otherwise the selection + // of displayable ticks and symbols probably keep changing. + // 3 is empirical value. + if (startTick !== 0 && step > 1 && tickCount / step > 2) { + startTick = Math.round(Math.ceil(startTick / step) * step); + } + + // (1) Only add min max label here but leave overlap checking + // to render stage, which also ensure the returned list + // suitable for splitLine and splitArea rendering. + // (2) Scales except category always contain min max label so + // do not need to perform this process. + var showAllLabel = shouldShowAllLabels(axis); + var includeMinLabel = labelModel.get('showMinLabel') || showAllLabel; + var includeMaxLabel = labelModel.get('showMaxLabel') || showAllLabel; + + if (includeMinLabel && startTick !== ordinalExtent[0]) { + addItem(ordinalExtent[0]); + } + + // Optimize: avoid generating large array by `ordinalScale.getTicks()`. + var tickValue = startTick; + for (; tickValue <= ordinalExtent[1]; tickValue += step) { + addItem(tickValue); + } + + if (includeMaxLabel && tickValue - step !== ordinalExtent[1]) { + addItem(ordinalExtent[1]); + } + + function addItem(tVal) { + result.push(onlyTick + ? tVal + : { + formattedLabel: labelFormatter(tVal), + rawLabel: ordinalScale.getLabel(tVal), + tickValue: tVal + } + ); + } + + return result; +} + +// When interval is function, the result `false` means ignore the tick. +// It is time consuming for large category data. +function makeLabelsByCustomizedCategoryInterval(axis, categoryInterval, onlyTick) { + var ordinalScale = axis.scale; + var labelFormatter = makeLabelFormatter(axis); + var result = []; + + each$1(ordinalScale.getTicks(), function (tickValue) { + var rawLabel = ordinalScale.getLabel(tickValue); + if (categoryInterval(tickValue, rawLabel)) { + result.push(onlyTick + ? tickValue + : { + formattedLabel: labelFormatter(tickValue), + rawLabel: rawLabel, + tickValue: tickValue + } + ); + } + }); + + return result; +} + +/* +* 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. +*/ + +var NORMALIZED_EXTENT = [0, 1]; + +/** + * Base class of Axis. + * @constructor + */ +var Axis = function (dim, scale, extent) { + + /** + * Axis dimension. Such as 'x', 'y', 'z', 'angle', 'radius'. + * @type {string} + */ + this.dim = dim; + + /** + * Axis scale + * @type {module:echarts/coord/scale/*} + */ + this.scale = scale; + + /** + * @type {Array.} + * @private + */ + this._extent = extent || [0, 0]; + + /** + * @type {boolean} + */ + this.inverse = false; + + /** + * Usually true when axis has a ordinal scale + * @type {boolean} + */ + this.onBand = false; +}; + +Axis.prototype = { + + constructor: Axis, + + /** + * If axis extent contain given coord + * @param {number} coord + * @return {boolean} + */ + contain: function (coord) { + var extent = this._extent; + var min = Math.min(extent[0], extent[1]); + var max = Math.max(extent[0], extent[1]); + return coord >= min && coord <= max; + }, + + /** + * If axis extent contain given data + * @param {number} data + * @return {boolean} + */ + containData: function (data) { + return this.scale.contain(data); + }, + + /** + * Get coord extent. + * @return {Array.} + */ + getExtent: function () { + return this._extent.slice(); + }, + + /** + * Get precision used for formatting + * @param {Array.} [dataExtent] + * @return {number} + */ + getPixelPrecision: function (dataExtent) { + return getPixelPrecision( + dataExtent || this.scale.getExtent(), + this._extent + ); + }, + + /** + * Set coord extent + * @param {number} start + * @param {number} end + */ + setExtent: function (start, end) { + var extent = this._extent; + extent[0] = start; + extent[1] = end; + }, + + /** + * Convert data to coord. Data is the rank if it has an ordinal scale + * @param {number} data + * @param {boolean} clamp + * @return {number} + */ + dataToCoord: function (data, clamp) { + var extent = this._extent; + var scale = this.scale; + data = scale.normalize(data); + + if (this.onBand && scale.type === 'ordinal') { + extent = extent.slice(); + fixExtentWithBands(extent, scale.count()); + } + + return linearMap(data, NORMALIZED_EXTENT, extent, clamp); + }, + + /** + * Convert coord to data. Data is the rank if it has an ordinal scale + * @param {number} coord + * @param {boolean} clamp + * @return {number} + */ + coordToData: function (coord, clamp) { + var extent = this._extent; + var scale = this.scale; + + if (this.onBand && scale.type === 'ordinal') { + extent = extent.slice(); + fixExtentWithBands(extent, scale.count()); + } + + var t = linearMap(coord, extent, NORMALIZED_EXTENT, clamp); + + return this.scale.scale(t); + }, + + /** + * Convert pixel point to data in axis + * @param {Array.} point + * @param {boolean} clamp + * @return {number} data + */ + pointToData: function (point, clamp) { + // Should be implemented in derived class if necessary. + }, + + /** + * Different from `zrUtil.map(axis.getTicks(), axis.dataToCoord, axis)`, + * `axis.getTicksCoords` considers `onBand`, which is used by + * `boundaryGap:true` of category axis and splitLine and splitArea. + * @param {Object} [opt] + * @param {Model} [opt.tickModel=axis.model.getModel('axisTick')] + * @param {boolean} [opt.clamp] If `true`, the first and the last + * tick must be at the axis end points. Otherwise, clip ticks + * that outside the axis extent. + * @return {Array.} [{ + * coord: ..., + * tickValue: ... + * }, ...] + */ + getTicksCoords: function (opt) { + opt = opt || {}; + + var tickModel = opt.tickModel || this.getTickModel(); + var result = createAxisTicks(this, tickModel); + var ticks = result.ticks; + + var ticksCoords = map(ticks, function (tickValue) { + return { + coord: this.dataToCoord(tickValue), + tickValue: tickValue + }; + }, this); + + var alignWithLabel = tickModel.get('alignWithLabel'); + + fixOnBandTicksCoords( + this, ticksCoords, alignWithLabel, opt.clamp + ); + + return ticksCoords; + }, + + /** + * @return {Array.>} [{ coord: ..., tickValue: ...}] + */ + getMinorTicksCoords: function () { + if (this.scale.type === 'ordinal') { + // Category axis doesn't support minor ticks + return []; + } + + var minorTickModel = this.model.getModel('minorTick'); + var splitNumber = minorTickModel.get('splitNumber'); + // Protection. + if (!(splitNumber > 0 && splitNumber < 100)) { + splitNumber = 5; + } + var minorTicks = this.scale.getMinorTicks(splitNumber); + var minorTicksCoords = map(minorTicks, function (minorTicksGroup) { + return map(minorTicksGroup, function (minorTick) { + return { + coord: this.dataToCoord(minorTick), + tickValue: minorTick + }; + }, this); + }, this); + return minorTicksCoords; + }, + + /** + * @return {Array.} [{ + * formattedLabel: string, + * rawLabel: axis.scale.getLabel(tickValue) + * tickValue: number + * }, ...] + */ + getViewLabels: function () { + return createAxisLabels(this).labels; + }, + + /** + * @return {module:echarts/coord/model/Model} + */ + getLabelModel: function () { + return this.model.getModel('axisLabel'); + }, + + /** + * Notice here we only get the default tick model. For splitLine + * or splitArea, we should pass the splitLineModel or splitAreaModel + * manually when calling `getTicksCoords`. + * In GL, this method may be overrided to: + * `axisModel.getModel('axisTick', grid3DModel.getModel('axisTick'));` + * @return {module:echarts/coord/model/Model} + */ + getTickModel: function () { + return this.model.getModel('axisTick'); + }, + + /** + * Get width of band + * @return {number} + */ + getBandWidth: function () { + var axisExtent = this._extent; + var dataExtent = this.scale.getExtent(); + + var len = dataExtent[1] - dataExtent[0] + (this.onBand ? 1 : 0); + // Fix #2728, avoid NaN when only one data. + len === 0 && (len = 1); + + var size = Math.abs(axisExtent[1] - axisExtent[0]); + + return Math.abs(size) / len; + }, + + /** + * @abstract + * @return {boolean} Is horizontal + */ + isHorizontal: null, + + /** + * @abstract + * @return {number} Get axis rotate, by degree. + */ + getRotate: null, + + /** + * Only be called in category axis. + * Can be overrided, consider other axes like in 3D. + * @return {number} Auto interval for cateogry axis tick and label + */ + calculateCategoryInterval: function () { + return calculateCategoryInterval(this); + } + +}; + +function fixExtentWithBands(extent, nTick) { + var size = extent[1] - extent[0]; + var len = nTick; + var margin = size / len / 2; + extent[0] += margin; + extent[1] -= margin; +} + +// If axis has labels [1, 2, 3, 4]. Bands on the axis are +// |---1---|---2---|---3---|---4---|. +// So the displayed ticks and splitLine/splitArea should between +// each data item, otherwise cause misleading (e.g., split tow bars +// of a single data item when there are two bar series). +// Also consider if tickCategoryInterval > 0 and onBand, ticks and +// splitLine/spliteArea should layout appropriately corresponding +// to displayed labels. (So we should not use `getBandWidth` in this +// case). +function fixOnBandTicksCoords(axis, ticksCoords, alignWithLabel, clamp) { + var ticksLen = ticksCoords.length; + + if (!axis.onBand || alignWithLabel || !ticksLen) { + return; + } + + var axisExtent = axis.getExtent(); + var last; + var diffSize; + if (ticksLen === 1) { + ticksCoords[0].coord = axisExtent[0]; + last = ticksCoords[1] = {coord: axisExtent[0]}; + } + else { + var crossLen = ticksCoords[ticksLen - 1].tickValue - ticksCoords[0].tickValue; + var shift = (ticksCoords[ticksLen - 1].coord - ticksCoords[0].coord) / crossLen; + + each$1(ticksCoords, function (ticksItem) { + ticksItem.coord -= shift / 2; + }); + + var dataExtent = axis.scale.getExtent(); + diffSize = 1 + dataExtent[1] - ticksCoords[ticksLen - 1].tickValue; + + last = {coord: ticksCoords[ticksLen - 1].coord + shift * diffSize}; + + ticksCoords.push(last); + } + + var inverse = axisExtent[0] > axisExtent[1]; + + // Handling clamp. + if (littleThan(ticksCoords[0].coord, axisExtent[0])) { + clamp ? (ticksCoords[0].coord = axisExtent[0]) : ticksCoords.shift(); + } + if (clamp && littleThan(axisExtent[0], ticksCoords[0].coord)) { + ticksCoords.unshift({coord: axisExtent[0]}); + } + if (littleThan(axisExtent[1], last.coord)) { + clamp ? (last.coord = axisExtent[1]) : ticksCoords.pop(); + } + if (clamp && littleThan(last.coord, axisExtent[1])) { + ticksCoords.push({coord: axisExtent[1]}); + } + + function littleThan(a, b) { + // Avoid rounding error cause calculated tick coord different with extent. + // It may cause an extra unecessary tick added. + a = round$1(a); + b = round$1(b); + return inverse ? a > b : a < b; + } +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/** + * Do not mount those modules on 'src/echarts' for better tree shaking. + */ + +var parseGeoJson = parseGeoJson$1; + +var ecUtil = {}; +each$1( + [ + 'map', 'each', 'filter', 'indexOf', 'inherits', 'reduce', 'filter', + 'bind', 'curry', 'isArray', 'isString', 'isObject', 'isFunction', + 'extend', 'defaults', 'clone', 'merge' + ], + function (name) { + ecUtil[name] = zrUtil[name]; + } +); +var graphic$1 = {}; +each$1( + [ + 'extendShape', 'extendPath', 'makePath', 'makeImage', + 'mergePath', 'resizePath', 'createIcon', + 'setHoverStyle', 'setLabelStyle', 'setTextStyle', 'setText', + 'getFont', 'updateProps', 'initProps', 'getTransform', + 'clipPointsByRect', 'clipRectByRect', + 'registerShape', 'getShapeClass', + 'Group', + 'Image', + 'Text', + 'Circle', + 'Sector', + 'Ring', + 'Polygon', + 'Polyline', + 'Rect', + 'Line', + 'BezierCurve', + 'Arc', + 'IncrementalDisplayable', + 'CompoundPath', + 'LinearGradient', + 'RadialGradient', + 'BoundingRect' + ], + function (name) { + graphic$1[name] = graphic[name]; + } +); + +/* +* 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. +*/ + +SeriesModel.extend({ + + type: 'series.line', + + dependencies: ['grid', 'polar'], + + getInitialData: function (option, ecModel) { + if (__DEV__) { + var coordSys = option.coordinateSystem; + if (coordSys !== 'polar' && coordSys !== 'cartesian2d') { + throw new Error('Line not support coordinateSystem besides cartesian and polar'); + } + } + return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true}); + }, + + defaultOption: { + zlevel: 0, + z: 2, + coordinateSystem: 'cartesian2d', + legendHoverLink: true, + + hoverAnimation: true, + // stack: null + // xAxisIndex: 0, + // yAxisIndex: 0, + + // polarIndex: 0, + + // If clip the overflow value + clip: true, + // cursor: null, + + label: { + position: 'top' + }, + // itemStyle: { + // }, + + lineStyle: { + width: 2, + type: 'solid' + }, + // areaStyle: { + // origin of areaStyle. Valid values: + // `'auto'/null/undefined`: from axisLine to data + // `'start'`: from min to data + // `'end'`: from data to max + // origin: 'auto' + // }, + // false, 'start', 'end', 'middle' + step: false, + + // Disabled if step is true + smooth: false, + smoothMonotone: null, + symbol: 'emptyCircle', + symbolSize: 4, + symbolRotate: null, + + showSymbol: true, + // `false`: follow the label interval strategy. + // `true`: show all symbols. + // `'auto'`: If possible, show all symbols, otherwise + // follow the label interval strategy. + showAllSymbol: 'auto', + + // Whether to connect break point. + connectNulls: false, + + // Sampling for large data. Can be: 'average', 'max', 'min', 'sum'. + sampling: 'none', + + animationEasing: 'linear', + + // Disable progressive + progressive: 0, + hoverLayerThreshold: Infinity + } +}); + +/* +* 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. +*/ + +/** + * @param {module:echarts/data/List} data + * @param {number} dataIndex + * @return {string} label string. Not null/undefined + */ +function getDefaultLabel(data, dataIndex) { + var labelDims = data.mapDimension('defaultedLabel', true); + var len = labelDims.length; + + // Simple optimization (in lots of cases, label dims length is 1) + if (len === 1) { + return retrieveRawValue(data, dataIndex, labelDims[0]); + } + else if (len) { + var vals = []; + for (var i = 0; i < labelDims.length; i++) { + var val = retrieveRawValue(data, dataIndex, labelDims[i]); + vals.push(val); + } + return vals.join(' '); + } +} + +/* +* 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. +*/ + +/** + * @module echarts/chart/helper/Symbol + */ + +/** + * @constructor + * @alias {module:echarts/chart/helper/Symbol} + * @param {module:echarts/data/List} data + * @param {number} idx + * @extends {module:zrender/graphic/Group} + */ +function SymbolClz$1(data, idx, seriesScope) { + Group.call(this); + this.updateData(data, idx, seriesScope); +} + +var symbolProto = SymbolClz$1.prototype; + +/** + * @public + * @static + * @param {module:echarts/data/List} data + * @param {number} dataIndex + * @return {Array.} [width, height] + */ +var getSymbolSize = SymbolClz$1.getSymbolSize = function (data, idx) { + var symbolSize = data.getItemVisual(idx, 'symbolSize'); + return symbolSize instanceof Array + ? symbolSize.slice() + : [+symbolSize, +symbolSize]; +}; + +function getScale(symbolSize) { + return [symbolSize[0] / 2, symbolSize[1] / 2]; +} + +function driftSymbol(dx, dy) { + this.parent.drift(dx, dy); +} + +symbolProto._createSymbol = function ( + symbolType, + data, + idx, + symbolSize, + keepAspect +) { + // Remove paths created before + this.removeAll(); + + var color = data.getItemVisual(idx, 'color'); + + // var symbolPath = createSymbol( + // symbolType, -0.5, -0.5, 1, 1, color + // ); + // If width/height are set too small (e.g., set to 1) on ios10 + // and macOS Sierra, a circle stroke become a rect, no matter what + // the scale is set. So we set width/height as 2. See #4150. + var symbolPath = createSymbol( + symbolType, -1, -1, 2, 2, color, keepAspect + ); + + symbolPath.attr({ + z2: 100, + culling: true, + scale: getScale(symbolSize) + }); + // Rewrite drift method + symbolPath.drift = driftSymbol; + + this._symbolType = symbolType; + + this.add(symbolPath); +}; + +/** + * Stop animation + * @param {boolean} toLastFrame + */ +symbolProto.stopSymbolAnimation = function (toLastFrame) { + this.childAt(0).stopAnimation(toLastFrame); +}; + +/** + * FIXME: + * Caution: This method breaks the encapsulation of this module, + * but it indeed brings convenience. So do not use the method + * unless you detailedly know all the implements of `Symbol`, + * especially animation. + * + * Get symbol path element. + */ +symbolProto.getSymbolPath = function () { + return this.childAt(0); +}; + +/** + * Get scale(aka, current symbol size). + * Including the change caused by animation + */ +symbolProto.getScale = function () { + return this.childAt(0).scale; +}; + +/** + * Highlight symbol + */ +symbolProto.highlight = function () { + this.childAt(0).trigger('emphasis'); +}; + +/** + * Downplay symbol + */ +symbolProto.downplay = function () { + this.childAt(0).trigger('normal'); +}; + +/** + * @param {number} zlevel + * @param {number} z + */ +symbolProto.setZ = function (zlevel, z) { + var symbolPath = this.childAt(0); + symbolPath.zlevel = zlevel; + symbolPath.z = z; +}; + +symbolProto.setDraggable = function (draggable) { + var symbolPath = this.childAt(0); + symbolPath.draggable = draggable; + symbolPath.cursor = draggable ? 'move' : symbolPath.cursor; +}; + +/** + * Update symbol properties + * @param {module:echarts/data/List} data + * @param {number} idx + * @param {Object} [seriesScope] + * @param {Object} [seriesScope.itemStyle] + * @param {Object} [seriesScope.hoverItemStyle] + * @param {Object} [seriesScope.symbolRotate] + * @param {Object} [seriesScope.symbolOffset] + * @param {module:echarts/model/Model} [seriesScope.labelModel] + * @param {module:echarts/model/Model} [seriesScope.hoverLabelModel] + * @param {boolean} [seriesScope.hoverAnimation] + * @param {Object} [seriesScope.cursorStyle] + * @param {module:echarts/model/Model} [seriesScope.itemModel] + * @param {string} [seriesScope.symbolInnerColor] + * @param {Object} [seriesScope.fadeIn=false] + */ +symbolProto.updateData = function (data, idx, seriesScope) { + this.silent = false; + + var symbolType = data.getItemVisual(idx, 'symbol') || 'circle'; + var seriesModel = data.hostModel; + var symbolSize = getSymbolSize(data, idx); + var isInit = symbolType !== this._symbolType; + + if (isInit) { + var keepAspect = data.getItemVisual(idx, 'symbolKeepAspect'); + this._createSymbol(symbolType, data, idx, symbolSize, keepAspect); + } + else { + var symbolPath = this.childAt(0); + symbolPath.silent = false; + updateProps(symbolPath, { + scale: getScale(symbolSize) + }, seriesModel, idx); + } + + this._updateCommon(data, idx, symbolSize, seriesScope); + + if (isInit) { + var symbolPath = this.childAt(0); + var fadeIn = seriesScope && seriesScope.fadeIn; + + var target = {scale: symbolPath.scale.slice()}; + fadeIn && (target.style = {opacity: symbolPath.style.opacity}); + + symbolPath.scale = [0, 0]; + fadeIn && (symbolPath.style.opacity = 0); + + initProps(symbolPath, target, seriesModel, idx); + } + + this._seriesModel = seriesModel; +}; + +// Update common properties +var normalStyleAccessPath = ['itemStyle']; +var emphasisStyleAccessPath = ['emphasis', 'itemStyle']; +var normalLabelAccessPath = ['label']; +var emphasisLabelAccessPath = ['emphasis', 'label']; + +/** + * @param {module:echarts/data/List} data + * @param {number} idx + * @param {Array.} symbolSize + * @param {Object} [seriesScope] + */ +symbolProto._updateCommon = function (data, idx, symbolSize, seriesScope) { + var symbolPath = this.childAt(0); + var seriesModel = data.hostModel; + var color = data.getItemVisual(idx, 'color'); + + // Reset style + if (symbolPath.type !== 'image') { + symbolPath.useStyle({ + strokeNoScale: true + }); + } + else { + symbolPath.setStyle({ + opacity: null, + shadowBlur: null, + shadowOffsetX: null, + shadowOffsetY: null, + shadowColor: null + }); + } + + var itemStyle = seriesScope && seriesScope.itemStyle; + var hoverItemStyle = seriesScope && seriesScope.hoverItemStyle; + var symbolRotate = seriesScope && seriesScope.symbolRotate; + var symbolOffset = seriesScope && seriesScope.symbolOffset; + var labelModel = seriesScope && seriesScope.labelModel; + var hoverLabelModel = seriesScope && seriesScope.hoverLabelModel; + var hoverAnimation = seriesScope && seriesScope.hoverAnimation; + var cursorStyle = seriesScope && seriesScope.cursorStyle; + + if (!seriesScope || data.hasItemOption) { + var itemModel = (seriesScope && seriesScope.itemModel) + ? seriesScope.itemModel : data.getItemModel(idx); + + // Color must be excluded. + // Because symbol provide setColor individually to set fill and stroke + itemStyle = itemModel.getModel(normalStyleAccessPath).getItemStyle(['color']); + hoverItemStyle = itemModel.getModel(emphasisStyleAccessPath).getItemStyle(); + + symbolRotate = itemModel.getShallow('symbolRotate'); + symbolOffset = itemModel.getShallow('symbolOffset'); + + labelModel = itemModel.getModel(normalLabelAccessPath); + hoverLabelModel = itemModel.getModel(emphasisLabelAccessPath); + hoverAnimation = itemModel.getShallow('hoverAnimation'); + cursorStyle = itemModel.getShallow('cursor'); + } + else { + hoverItemStyle = extend({}, hoverItemStyle); + } + + var elStyle = symbolPath.style; + + symbolPath.attr('rotation', (symbolRotate || 0) * Math.PI / 180 || 0); + + if (symbolOffset) { + symbolPath.attr('position', [ + parsePercent$1(symbolOffset[0], symbolSize[0]), + parsePercent$1(symbolOffset[1], symbolSize[1]) + ]); + } + + cursorStyle && symbolPath.attr('cursor', cursorStyle); + + // PENDING setColor before setStyle!!! + symbolPath.setColor(color, seriesScope && seriesScope.symbolInnerColor); + + symbolPath.setStyle(itemStyle); + + var opacity = data.getItemVisual(idx, 'opacity'); + if (opacity != null) { + elStyle.opacity = opacity; + } + + var liftZ = data.getItemVisual(idx, 'liftZ'); + var z2Origin = symbolPath.__z2Origin; + if (liftZ != null) { + if (z2Origin == null) { + symbolPath.__z2Origin = symbolPath.z2; + symbolPath.z2 += liftZ; + } + } + else if (z2Origin != null) { + symbolPath.z2 = z2Origin; + symbolPath.__z2Origin = null; + } + + var useNameLabel = seriesScope && seriesScope.useNameLabel; + + setLabelStyle( + elStyle, hoverItemStyle, labelModel, hoverLabelModel, + { + labelFetcher: seriesModel, + labelDataIndex: idx, + defaultText: getLabelDefaultText, + isRectText: true, + autoColor: color + } + ); + + // Do not execute util needed. + function getLabelDefaultText(idx, opt) { + return useNameLabel ? data.getName(idx) : getDefaultLabel(data, idx); + } + + symbolPath.__symbolOriginalScale = getScale(symbolSize); + symbolPath.hoverStyle = hoverItemStyle; + symbolPath.highDownOnUpdate = ( + hoverAnimation && seriesModel.isAnimationEnabled() + ) ? highDownOnUpdate : null; + + setHoverStyle(symbolPath); +}; + +function highDownOnUpdate(fromState, toState) { + // Do not support this hover animation util some scenario required. + // Animation can only be supported in hover layer when using `el.incremetal`. + if (this.incremental || this.useHoverLayer) { + return; + } + + if (toState === 'emphasis') { + var scale = this.__symbolOriginalScale; + var ratio = scale[1] / scale[0]; + var emphasisOpt = { + scale: [ + Math.max(scale[0] * 1.1, scale[0] + 3), + Math.max(scale[1] * 1.1, scale[1] + 3 * ratio) + ] + }; + // FIXME + // modify it after support stop specified animation. + // toState === fromState + // ? (this.stopAnimation(), this.attr(emphasisOpt)) + this.animateTo(emphasisOpt, 400, 'elasticOut'); + } + else if (toState === 'normal') { + this.animateTo({ + scale: this.__symbolOriginalScale + }, 400, 'elasticOut'); + } +} + +/** + * @param {Function} cb + * @param {Object} [opt] + * @param {Object} [opt.keepLabel=true] + */ +symbolProto.fadeOut = function (cb, opt) { + var symbolPath = this.childAt(0); + // Avoid mistaken hover when fading out + this.silent = symbolPath.silent = true; + // Not show text when animating + !(opt && opt.keepLabel) && (symbolPath.style.text = null); + + updateProps( + symbolPath, + { + style: {opacity: 0}, + scale: [0, 0] + }, + this._seriesModel, + this.dataIndex, + cb + ); +}; + +inherits(SymbolClz$1, Group); + +/* +* 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. +*/ + +/** + * @module echarts/chart/helper/SymbolDraw + */ + +/** + * @constructor + * @alias module:echarts/chart/helper/SymbolDraw + * @param {module:zrender/graphic/Group} [symbolCtor] + */ +function SymbolDraw(symbolCtor) { + this.group = new Group(); + + this._symbolCtor = symbolCtor || SymbolClz$1; +} + +var symbolDrawProto = SymbolDraw.prototype; + +function symbolNeedsDraw(data, point, idx, opt) { + return point && !isNaN(point[0]) && !isNaN(point[1]) + && !(opt.isIgnore && opt.isIgnore(idx)) + // We do not set clipShape on group, because it will cut part of + // the symbol element shape. We use the same clip shape here as + // the line clip. + && !(opt.clipShape && !opt.clipShape.contain(point[0], point[1])) + && data.getItemVisual(idx, 'symbol') !== 'none'; +} + +/** + * Update symbols draw by new data + * @param {module:echarts/data/List} data + * @param {Object} [opt] Or isIgnore + * @param {Function} [opt.isIgnore] + * @param {Object} [opt.clipShape] + */ +symbolDrawProto.updateData = function (data, opt) { + opt = normalizeUpdateOpt(opt); + + var group = this.group; + var seriesModel = data.hostModel; + var oldData = this._data; + var SymbolCtor = this._symbolCtor; + + var seriesScope = makeSeriesScope(data); + + // There is no oldLineData only when first rendering or switching from + // stream mode to normal mode, where previous elements should be removed. + if (!oldData) { + group.removeAll(); + } + + data.diff(oldData) + .add(function (newIdx) { + var point = data.getItemLayout(newIdx); + if (symbolNeedsDraw(data, point, newIdx, opt)) { + var symbolEl = new SymbolCtor(data, newIdx, seriesScope); + symbolEl.attr('position', point); + data.setItemGraphicEl(newIdx, symbolEl); + group.add(symbolEl); + } + }) + .update(function (newIdx, oldIdx) { + var symbolEl = oldData.getItemGraphicEl(oldIdx); + var point = data.getItemLayout(newIdx); + if (!symbolNeedsDraw(data, point, newIdx, opt)) { + group.remove(symbolEl); + return; + } + if (!symbolEl) { + symbolEl = new SymbolCtor(data, newIdx); + symbolEl.attr('position', point); + } + else { + symbolEl.updateData(data, newIdx, seriesScope); + updateProps(symbolEl, { + position: point + }, seriesModel); + } + + // Add back + group.add(symbolEl); + + data.setItemGraphicEl(newIdx, symbolEl); + }) + .remove(function (oldIdx) { + var el = oldData.getItemGraphicEl(oldIdx); + el && el.fadeOut(function () { + group.remove(el); + }); + }) + .execute(); + + this._data = data; +}; + +symbolDrawProto.isPersistent = function () { + return true; +}; + +symbolDrawProto.updateLayout = function () { + var data = this._data; + if (data) { + // Not use animation + data.eachItemGraphicEl(function (el, idx) { + var point = data.getItemLayout(idx); + el.attr('position', point); + }); + } +}; + +symbolDrawProto.incrementalPrepareUpdate = function (data) { + this._seriesScope = makeSeriesScope(data); + this._data = null; + this.group.removeAll(); +}; + +/** + * Update symbols draw by new data + * @param {module:echarts/data/List} data + * @param {Object} [opt] Or isIgnore + * @param {Function} [opt.isIgnore] + * @param {Object} [opt.clipShape] + */ +symbolDrawProto.incrementalUpdate = function (taskParams, data, opt) { + opt = normalizeUpdateOpt(opt); + + function updateIncrementalAndHover(el) { + if (!el.isGroup) { + el.incremental = el.useHoverLayer = true; + } + } + for (var idx = taskParams.start; idx < taskParams.end; idx++) { + var point = data.getItemLayout(idx); + if (symbolNeedsDraw(data, point, idx, opt)) { + var el = new this._symbolCtor(data, idx, this._seriesScope); + el.traverse(updateIncrementalAndHover); + el.attr('position', point); + this.group.add(el); + data.setItemGraphicEl(idx, el); + } + } +}; + +function normalizeUpdateOpt(opt) { + if (opt != null && !isObject$1(opt)) { + opt = {isIgnore: opt}; + } + return opt || {}; +} + +symbolDrawProto.remove = function (enableAnimation) { + var group = this.group; + var data = this._data; + // Incremental model do not have this._data. + if (data && enableAnimation) { + data.eachItemGraphicEl(function (el) { + el.fadeOut(function () { + group.remove(el); + }); + }); + } + else { + group.removeAll(); + } +}; + +function makeSeriesScope(data) { + var seriesModel = data.hostModel; + return { + itemStyle: seriesModel.getModel('itemStyle').getItemStyle(['color']), + hoverItemStyle: seriesModel.getModel('emphasis.itemStyle').getItemStyle(), + symbolRotate: seriesModel.get('symbolRotate'), + symbolOffset: seriesModel.get('symbolOffset'), + hoverAnimation: seriesModel.get('hoverAnimation'), + labelModel: seriesModel.getModel('label'), + hoverLabelModel: seriesModel.getModel('emphasis.label'), + cursorStyle: seriesModel.get('cursor') + }; +} + +/* +* 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. +*/ + +/** + * @param {Object} coordSys + * @param {module:echarts/data/List} data + * @param {string} valueOrigin lineSeries.option.areaStyle.origin + */ +function prepareDataCoordInfo(coordSys, data, valueOrigin) { + var baseAxis = coordSys.getBaseAxis(); + var valueAxis = coordSys.getOtherAxis(baseAxis); + var valueStart = getValueStart(valueAxis, valueOrigin); + + var baseAxisDim = baseAxis.dim; + var valueAxisDim = valueAxis.dim; + var valueDim = data.mapDimension(valueAxisDim); + var baseDim = data.mapDimension(baseAxisDim); + var baseDataOffset = valueAxisDim === 'x' || valueAxisDim === 'radius' ? 1 : 0; + + var dims = map(coordSys.dimensions, function (coordDim) { + return data.mapDimension(coordDim); + }); + + var stacked; + var stackResultDim = data.getCalculationInfo('stackResultDimension'); + if (stacked |= isDimensionStacked(data, dims[0] /*, dims[1]*/)) { // jshint ignore:line + dims[0] = stackResultDim; + } + if (stacked |= isDimensionStacked(data, dims[1] /*, dims[0]*/)) { // jshint ignore:line + dims[1] = stackResultDim; + } + + return { + dataDimsForPoint: dims, + valueStart: valueStart, + valueAxisDim: valueAxisDim, + baseAxisDim: baseAxisDim, + stacked: !!stacked, + valueDim: valueDim, + baseDim: baseDim, + baseDataOffset: baseDataOffset, + stackedOverDimension: data.getCalculationInfo('stackedOverDimension') + }; +} + +function getValueStart(valueAxis, valueOrigin) { + var valueStart = 0; + var extent = valueAxis.scale.getExtent(); + + if (valueOrigin === 'start') { + valueStart = extent[0]; + } + else if (valueOrigin === 'end') { + valueStart = extent[1]; + } + // auto + else { + // Both positive + if (extent[0] > 0) { + valueStart = extent[0]; + } + // Both negative + else if (extent[1] < 0) { + valueStart = extent[1]; + } + // If is one positive, and one negative, onZero shall be true + } + + return valueStart; +} + +function getStackedOnPoint(dataCoordInfo, coordSys, data, idx) { + var value = NaN; + if (dataCoordInfo.stacked) { + value = data.get(data.getCalculationInfo('stackedOverDimension'), idx); + } + if (isNaN(value)) { + value = dataCoordInfo.valueStart; + } + + var baseDataOffset = dataCoordInfo.baseDataOffset; + var stackedData = []; + stackedData[baseDataOffset] = data.get(dataCoordInfo.baseDim, idx); + stackedData[1 - baseDataOffset] = value; + + return coordSys.dataToPoint(stackedData); +} + +/* +* 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. +*/ + +// var arrayDiff = require('zrender/src/core/arrayDiff'); +// 'zrender/src/core/arrayDiff' has been used before, but it did +// not do well in performance when roam with fixed dataZoom window. + +// function convertToIntId(newIdList, oldIdList) { +// // Generate int id instead of string id. +// // Compare string maybe slow in score function of arrDiff + +// // Assume id in idList are all unique +// var idIndicesMap = {}; +// var idx = 0; +// for (var i = 0; i < newIdList.length; i++) { +// idIndicesMap[newIdList[i]] = idx; +// newIdList[i] = idx++; +// } +// for (var i = 0; i < oldIdList.length; i++) { +// var oldId = oldIdList[i]; +// // Same with newIdList +// if (idIndicesMap[oldId]) { +// oldIdList[i] = idIndicesMap[oldId]; +// } +// else { +// oldIdList[i] = idx++; +// } +// } +// } + +function diffData(oldData, newData) { + var diffResult = []; + + newData.diff(oldData) + .add(function (idx) { + diffResult.push({cmd: '+', idx: idx}); + }) + .update(function (newIdx, oldIdx) { + diffResult.push({cmd: '=', idx: oldIdx, idx1: newIdx}); + }) + .remove(function (idx) { + diffResult.push({cmd: '-', idx: idx}); + }) + .execute(); + + return diffResult; +} + +var lineAnimationDiff = function ( + oldData, newData, + oldStackedOnPoints, newStackedOnPoints, + oldCoordSys, newCoordSys, + oldValueOrigin, newValueOrigin +) { + var diff = diffData(oldData, newData); + + // var newIdList = newData.mapArray(newData.getId); + // var oldIdList = oldData.mapArray(oldData.getId); + + // convertToIntId(newIdList, oldIdList); + + // // FIXME One data ? + // diff = arrayDiff(oldIdList, newIdList); + + var currPoints = []; + var nextPoints = []; + // Points for stacking base line + var currStackedPoints = []; + var nextStackedPoints = []; + + var status = []; + var sortedIndices = []; + var rawIndices = []; + + var newDataOldCoordInfo = prepareDataCoordInfo(oldCoordSys, newData, oldValueOrigin); + var oldDataNewCoordInfo = prepareDataCoordInfo(newCoordSys, oldData, newValueOrigin); + + for (var i = 0; i < diff.length; i++) { + var diffItem = diff[i]; + var pointAdded = true; + + // FIXME, animation is not so perfect when dataZoom window moves fast + // Which is in case remvoing or add more than one data in the tail or head + switch (diffItem.cmd) { + case '=': + var currentPt = oldData.getItemLayout(diffItem.idx); + var nextPt = newData.getItemLayout(diffItem.idx1); + // If previous data is NaN, use next point directly + if (isNaN(currentPt[0]) || isNaN(currentPt[1])) { + currentPt = nextPt.slice(); + } + currPoints.push(currentPt); + nextPoints.push(nextPt); + + currStackedPoints.push(oldStackedOnPoints[diffItem.idx]); + nextStackedPoints.push(newStackedOnPoints[diffItem.idx1]); + + rawIndices.push(newData.getRawIndex(diffItem.idx1)); + break; + case '+': + var idx = diffItem.idx; + currPoints.push( + oldCoordSys.dataToPoint([ + newData.get(newDataOldCoordInfo.dataDimsForPoint[0], idx), + newData.get(newDataOldCoordInfo.dataDimsForPoint[1], idx) + ]) + ); + + nextPoints.push(newData.getItemLayout(idx).slice()); + + currStackedPoints.push( + getStackedOnPoint(newDataOldCoordInfo, oldCoordSys, newData, idx) + ); + nextStackedPoints.push(newStackedOnPoints[idx]); + + rawIndices.push(newData.getRawIndex(idx)); + break; + case '-': + var idx = diffItem.idx; + var rawIndex = oldData.getRawIndex(idx); + // Data is replaced. In the case of dynamic data queue + // FIXME FIXME FIXME + if (rawIndex !== idx) { + currPoints.push(oldData.getItemLayout(idx)); + nextPoints.push(newCoordSys.dataToPoint([ + oldData.get(oldDataNewCoordInfo.dataDimsForPoint[0], idx), + oldData.get(oldDataNewCoordInfo.dataDimsForPoint[1], idx) + ])); + + currStackedPoints.push(oldStackedOnPoints[idx]); + nextStackedPoints.push( + getStackedOnPoint(oldDataNewCoordInfo, newCoordSys, oldData, idx) + ); + + rawIndices.push(rawIndex); + } + else { + pointAdded = false; + } + } + + // Original indices + if (pointAdded) { + status.push(diffItem); + sortedIndices.push(sortedIndices.length); + } + } + + // Diff result may be crossed if all items are changed + // Sort by data index + sortedIndices.sort(function (a, b) { + return rawIndices[a] - rawIndices[b]; + }); + + var sortedCurrPoints = []; + var sortedNextPoints = []; + + var sortedCurrStackedPoints = []; + var sortedNextStackedPoints = []; + + var sortedStatus = []; + for (var i = 0; i < sortedIndices.length; i++) { + var idx = sortedIndices[i]; + sortedCurrPoints[i] = currPoints[idx]; + sortedNextPoints[i] = nextPoints[idx]; + + sortedCurrStackedPoints[i] = currStackedPoints[idx]; + sortedNextStackedPoints[i] = nextStackedPoints[idx]; + + sortedStatus[i] = status[idx]; + } + + return { + current: sortedCurrPoints, + next: sortedNextPoints, + + stackedOnCurrent: sortedCurrStackedPoints, + stackedOnNext: sortedNextStackedPoints, + + status: sortedStatus + }; +}; + +/* +* 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. +*/ + +// Poly path support NaN point + +var vec2Min = min; +var vec2Max = max; + +var scaleAndAdd$1 = scaleAndAdd; +var v2Copy = copy; + +// Temporary variable +var v = []; +var cp0 = []; +var cp1 = []; + +function isPointNull(p) { + return isNaN(p[0]) || isNaN(p[1]); +} + +function drawSegment( + ctx, points, start, segLen, allLen, + dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls +) { + // if (smoothMonotone == null) { + // if (isMono(points, 'x')) { + // return drawMono(ctx, points, start, segLen, allLen, + // dir, smoothMin, smoothMax, smooth, 'x', connectNulls); + // } + // else if (isMono(points, 'y')) { + // return drawMono(ctx, points, start, segLen, allLen, + // dir, smoothMin, smoothMax, smooth, 'y', connectNulls); + // } + // else { + // return drawNonMono.apply(this, arguments); + // } + // } + // else if (smoothMonotone !== 'none' && isMono(points, smoothMonotone)) { + // return drawMono.apply(this, arguments); + // } + // else { + // return drawNonMono.apply(this, arguments); + // } + if (smoothMonotone === 'none' || !smoothMonotone) { + return drawNonMono.apply(this, arguments); + } + else { + return drawMono.apply(this, arguments); + } +} + +/** + * Check if points is in monotone. + * + * @param {number[][]} points Array of points which is in [x, y] form + * @param {string} smoothMonotone 'x', 'y', or 'none', stating for which + * dimension that is checking. + * If is 'none', `drawNonMono` should be + * called. + * If is undefined, either being monotone + * in 'x' or 'y' will call `drawMono`. + */ +// function isMono(points, smoothMonotone) { +// if (points.length <= 1) { +// return true; +// } + +// var dim = smoothMonotone === 'x' ? 0 : 1; +// var last = points[0][dim]; +// var lastDiff = 0; +// for (var i = 1; i < points.length; ++i) { +// var diff = points[i][dim] - last; +// if (!isNaN(diff) && !isNaN(lastDiff) +// && diff !== 0 && lastDiff !== 0 +// && ((diff >= 0) !== (lastDiff >= 0)) +// ) { +// return false; +// } +// if (!isNaN(diff) && diff !== 0) { +// lastDiff = diff; +// last = points[i][dim]; +// } +// } +// return true; +// } + +/** + * Draw smoothed line in monotone, in which only vertical or horizontal bezier + * control points will be used. This should be used when points are monotone + * either in x or y dimension. + */ +function drawMono( + ctx, points, start, segLen, allLen, + dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls +) { + var prevIdx = 0; + var idx = start; + for (var k = 0; k < segLen; k++) { + var p = points[idx]; + if (idx >= allLen || idx < 0) { + break; + } + if (isPointNull(p)) { + if (connectNulls) { + idx += dir; + continue; + } + break; + } + + if (idx === start) { + ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]); + } + else { + if (smooth > 0) { + var prevP = points[prevIdx]; + var dim = smoothMonotone === 'y' ? 1 : 0; + + // Length of control point to p, either in x or y, but not both + var ctrlLen = (p[dim] - prevP[dim]) * smooth; + + v2Copy(cp0, prevP); + cp0[dim] = prevP[dim] + ctrlLen; + + v2Copy(cp1, p); + cp1[dim] = p[dim] - ctrlLen; + + ctx.bezierCurveTo( + cp0[0], cp0[1], + cp1[0], cp1[1], + p[0], p[1] + ); + } + else { + ctx.lineTo(p[0], p[1]); + } + } + + prevIdx = idx; + idx += dir; + } + + return k; +} + +/** + * Draw smoothed line in non-monotone, in may cause undesired curve in extreme + * situations. This should be used when points are non-monotone neither in x or + * y dimension. + */ +function drawNonMono( + ctx, points, start, segLen, allLen, + dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls +) { + var prevIdx = 0; + var idx = start; + for (var k = 0; k < segLen; k++) { + var p = points[idx]; + if (idx >= allLen || idx < 0) { + break; + } + if (isPointNull(p)) { + if (connectNulls) { + idx += dir; + continue; + } + break; + } + + if (idx === start) { + ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]); + v2Copy(cp0, p); + } + else { + if (smooth > 0) { + var nextIdx = idx + dir; + var nextP = points[nextIdx]; + if (connectNulls) { + // Find next point not null + while (nextP && isPointNull(points[nextIdx])) { + nextIdx += dir; + nextP = points[nextIdx]; + } + } + + var ratioNextSeg = 0.5; + var prevP = points[prevIdx]; + var nextP = points[nextIdx]; + // Last point + if (!nextP || isPointNull(nextP)) { + v2Copy(cp1, p); + } + else { + // If next data is null in not connect case + if (isPointNull(nextP) && !connectNulls) { + nextP = p; + } + + sub(v, nextP, prevP); + + var lenPrevSeg; + var lenNextSeg; + if (smoothMonotone === 'x' || smoothMonotone === 'y') { + var dim = smoothMonotone === 'x' ? 0 : 1; + lenPrevSeg = Math.abs(p[dim] - prevP[dim]); + lenNextSeg = Math.abs(p[dim] - nextP[dim]); + } + else { + lenPrevSeg = dist(p, prevP); + lenNextSeg = dist(p, nextP); + } + + // Use ratio of seg length + ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg); + + scaleAndAdd$1(cp1, p, v, -smooth * (1 - ratioNextSeg)); + } + // Smooth constraint + vec2Min(cp0, cp0, smoothMax); + vec2Max(cp0, cp0, smoothMin); + vec2Min(cp1, cp1, smoothMax); + vec2Max(cp1, cp1, smoothMin); + + ctx.bezierCurveTo( + cp0[0], cp0[1], + cp1[0], cp1[1], + p[0], p[1] + ); + // cp0 of next segment + scaleAndAdd$1(cp0, p, v, smooth * ratioNextSeg); + } + else { + ctx.lineTo(p[0], p[1]); + } + } + + prevIdx = idx; + idx += dir; + } + + return k; +} + +function getBoundingBox(points, smoothConstraint) { + var ptMin = [Infinity, Infinity]; + var ptMax = [-Infinity, -Infinity]; + if (smoothConstraint) { + for (var i = 0; i < points.length; i++) { + var pt = points[i]; + if (pt[0] < ptMin[0]) { + ptMin[0] = pt[0]; + } + if (pt[1] < ptMin[1]) { + ptMin[1] = pt[1]; + } + if (pt[0] > ptMax[0]) { + ptMax[0] = pt[0]; + } + if (pt[1] > ptMax[1]) { + ptMax[1] = pt[1]; + } + } + } + return { + min: smoothConstraint ? ptMin : ptMax, + max: smoothConstraint ? ptMax : ptMin + }; +} + +var Polyline$1 = Path.extend({ + + type: 'ec-polyline', + + shape: { + points: [], + + smooth: 0, + + smoothConstraint: true, + + smoothMonotone: null, + + connectNulls: false + }, + + style: { + fill: null, + + stroke: '#000' + }, + + brush: fixClipWithShadow(Path.prototype.brush), + + buildPath: function (ctx, shape) { + var points = shape.points; + + var i = 0; + var len$$1 = points.length; + + var result = getBoundingBox(points, shape.smoothConstraint); + + if (shape.connectNulls) { + // Must remove first and last null values avoid draw error in polygon + for (; len$$1 > 0; len$$1--) { + if (!isPointNull(points[len$$1 - 1])) { + break; + } + } + for (; i < len$$1; i++) { + if (!isPointNull(points[i])) { + break; + } + } + } + while (i < len$$1) { + i += drawSegment( + ctx, points, i, len$$1, len$$1, + 1, result.min, result.max, shape.smooth, + shape.smoothMonotone, shape.connectNulls + ) + 1; + } + } +}); + +var Polygon$1 = Path.extend({ + + type: 'ec-polygon', + + shape: { + points: [], + + // Offset between stacked base points and points + stackedOnPoints: [], + + smooth: 0, + + stackedOnSmooth: 0, + + smoothConstraint: true, + + smoothMonotone: null, + + connectNulls: false + }, + + brush: fixClipWithShadow(Path.prototype.brush), + + buildPath: function (ctx, shape) { + var points = shape.points; + var stackedOnPoints = shape.stackedOnPoints; + + var i = 0; + var len$$1 = points.length; + var smoothMonotone = shape.smoothMonotone; + var bbox = getBoundingBox(points, shape.smoothConstraint); + var stackedOnBBox = getBoundingBox(stackedOnPoints, shape.smoothConstraint); + + if (shape.connectNulls) { + // Must remove first and last null values avoid draw error in polygon + for (; len$$1 > 0; len$$1--) { + if (!isPointNull(points[len$$1 - 1])) { + break; + } + } + for (; i < len$$1; i++) { + if (!isPointNull(points[i])) { + break; + } + } + } + while (i < len$$1) { + var k = drawSegment( + ctx, points, i, len$$1, len$$1, + 1, bbox.min, bbox.max, shape.smooth, + smoothMonotone, shape.connectNulls + ); + drawSegment( + ctx, stackedOnPoints, i + k - 1, k, len$$1, + -1, stackedOnBBox.min, stackedOnBBox.max, shape.stackedOnSmooth, + smoothMonotone, shape.connectNulls + ); + i += k + 1; + + ctx.closePath(); + } + } +}); + +/* +* 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. +*/ +function createGridClipPath(cartesian, hasAnimation, seriesModel) { + var rect = cartesian.getArea(); + var isHorizontal = cartesian.getBaseAxis().isHorizontal(); + + var x = rect.x; + var y = rect.y; + var width = rect.width; + var height = rect.height; + + var lineWidth = seriesModel.get('lineStyle.width') || 2; + // Expand the clip path a bit to avoid the border is clipped and looks thinner + x -= lineWidth / 2; + y -= lineWidth / 2; + width += lineWidth; + height += lineWidth; + + var clipPath = new Rect({ + shape: { + x: x, + y: y, + width: width, + height: height + } + }); + + if (hasAnimation) { + clipPath.shape[isHorizontal ? 'width' : 'height'] = 0; + initProps(clipPath, { + shape: { + width: width, + height: height + } + }, seriesModel); + } + + return clipPath; +} + +function createPolarClipPath(polar, hasAnimation, seriesModel) { + var sectorArea = polar.getArea(); + // Avoid float number rounding error for symbol on the edge of axis extent. + + var clipPath = new Sector({ + shape: { + cx: round$1(polar.cx, 1), + cy: round$1(polar.cy, 1), + r0: round$1(sectorArea.r0, 1), + r: round$1(sectorArea.r, 1), + startAngle: sectorArea.startAngle, + endAngle: sectorArea.endAngle, + clockwise: sectorArea.clockwise + } + }); + + if (hasAnimation) { + clipPath.shape.endAngle = sectorArea.startAngle; + initProps(clipPath, { + shape: { + endAngle: sectorArea.endAngle + } + }, seriesModel); + } + return clipPath; +} + +function createClipPath(coordSys, hasAnimation, seriesModel) { + if (!coordSys) { + return null; + } + else if (coordSys.type === 'polar') { + return createPolarClipPath(coordSys, hasAnimation, seriesModel); + } + else if (coordSys.type === 'cartesian2d') { + return createGridClipPath(coordSys, hasAnimation, seriesModel); + } + return null; +} + +/* +* 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. +*/ + +// FIXME step not support polar + +function isPointsSame(points1, points2) { + if (points1.length !== points2.length) { + return; + } + for (var i = 0; i < points1.length; i++) { + var p1 = points1[i]; + var p2 = points2[i]; + if (p1[0] !== p2[0] || p1[1] !== p2[1]) { + return; + } + } + return true; +} + +function getSmooth(smooth) { + return typeof (smooth) === 'number' ? smooth : (smooth ? 0.5 : 0); +} + +/** + * @param {module:echarts/coord/cartesian/Cartesian2D|module:echarts/coord/polar/Polar} coordSys + * @param {module:echarts/data/List} data + * @param {Object} dataCoordInfo + * @param {Array.>} points + */ +function getStackedOnPoints(coordSys, data, dataCoordInfo) { + if (!dataCoordInfo.valueDim) { + return []; + } + + var points = []; + for (var idx = 0, len = data.count(); idx < len; idx++) { + points.push(getStackedOnPoint(dataCoordInfo, coordSys, data, idx)); + } + + return points; +} + +function turnPointsIntoStep(points, coordSys, stepTurnAt) { + var baseAxis = coordSys.getBaseAxis(); + var baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1; + + var stepPoints = []; + for (var i = 0; i < points.length - 1; i++) { + var nextPt = points[i + 1]; + var pt = points[i]; + stepPoints.push(pt); + + var stepPt = []; + switch (stepTurnAt) { + case 'end': + stepPt[baseIndex] = nextPt[baseIndex]; + stepPt[1 - baseIndex] = pt[1 - baseIndex]; + // default is start + stepPoints.push(stepPt); + break; + case 'middle': + // default is start + var middle = (pt[baseIndex] + nextPt[baseIndex]) / 2; + var stepPt2 = []; + stepPt[baseIndex] = stepPt2[baseIndex] = middle; + stepPt[1 - baseIndex] = pt[1 - baseIndex]; + stepPt2[1 - baseIndex] = nextPt[1 - baseIndex]; + stepPoints.push(stepPt); + stepPoints.push(stepPt2); + break; + default: + stepPt[baseIndex] = pt[baseIndex]; + stepPt[1 - baseIndex] = nextPt[1 - baseIndex]; + // default is start + stepPoints.push(stepPt); + } + } + // Last points + points[i] && stepPoints.push(points[i]); + return stepPoints; +} + +function getVisualGradient(data, coordSys) { + var visualMetaList = data.getVisual('visualMeta'); + if (!visualMetaList || !visualMetaList.length || !data.count()) { + // When data.count() is 0, gradient range can not be calculated. + return; + } + + if (coordSys.type !== 'cartesian2d') { + if (__DEV__) { + console.warn('Visual map on line style is only supported on cartesian2d.'); + } + return; + } + + var coordDim; + var visualMeta; + + for (var i = visualMetaList.length - 1; i >= 0; i--) { + var dimIndex = visualMetaList[i].dimension; + var dimName = data.dimensions[dimIndex]; + var dimInfo = data.getDimensionInfo(dimName); + coordDim = dimInfo && dimInfo.coordDim; + // Can only be x or y + if (coordDim === 'x' || coordDim === 'y') { + visualMeta = visualMetaList[i]; + break; + } + } + + if (!visualMeta) { + if (__DEV__) { + console.warn('Visual map on line style only support x or y dimension.'); + } + return; + } + + // If the area to be rendered is bigger than area defined by LinearGradient, + // the canvas spec prescribes that the color of the first stop and the last + // stop should be used. But if two stops are added at offset 0, in effect + // browsers use the color of the second stop to render area outside + // LinearGradient. So we can only infinitesimally extend area defined in + // LinearGradient to render `outerColors`. + + var axis = coordSys.getAxis(coordDim); + + // dataToCoor mapping may not be linear, but must be monotonic. + var colorStops = map(visualMeta.stops, function (stop) { + return { + coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)), + color: stop.color + }; + }); + var stopLen = colorStops.length; + var outerColors = visualMeta.outerColors.slice(); + + if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) { + colorStops.reverse(); + outerColors.reverse(); + } + + var tinyExtent = 10; // Arbitrary value: 10px + var minCoord = colorStops[0].coord - tinyExtent; + var maxCoord = colorStops[stopLen - 1].coord + tinyExtent; + var coordSpan = maxCoord - minCoord; + + if (coordSpan < 1e-3) { + return 'transparent'; + } + + each$1(colorStops, function (stop) { + stop.offset = (stop.coord - minCoord) / coordSpan; + }); + colorStops.push({ + offset: stopLen ? colorStops[stopLen - 1].offset : 0.5, + color: outerColors[1] || 'transparent' + }); + colorStops.unshift({ // notice colorStops.length have been changed. + offset: stopLen ? colorStops[0].offset : 0.5, + color: outerColors[0] || 'transparent' + }); + + // zrUtil.each(colorStops, function (colorStop) { + // // Make sure each offset has rounded px to avoid not sharp edge + // colorStop.offset = (Math.round(colorStop.offset * (end - start) + start) - start) / (end - start); + // }); + + var gradient = new LinearGradient(0, 0, 0, 0, colorStops, true); + gradient[coordDim] = minCoord; + gradient[coordDim + '2'] = maxCoord; + + return gradient; +} + +function getIsIgnoreFunc(seriesModel, data, coordSys) { + var showAllSymbol = seriesModel.get('showAllSymbol'); + var isAuto = showAllSymbol === 'auto'; + + if (showAllSymbol && !isAuto) { + return; + } + + var categoryAxis = coordSys.getAxesByScale('ordinal')[0]; + if (!categoryAxis) { + return; + } + + // Note that category label interval strategy might bring some weird effect + // in some scenario: users may wonder why some of the symbols are not + // displayed. So we show all symbols as possible as we can. + if (isAuto + // Simplify the logic, do not determine label overlap here. + && canShowAllSymbolForCategory(categoryAxis, data) + ) { + return; + } + + // Otherwise follow the label interval strategy on category axis. + var categoryDataDim = data.mapDimension(categoryAxis.dim); + var labelMap = {}; + + each$1(categoryAxis.getViewLabels(), function (labelItem) { + labelMap[labelItem.tickValue] = 1; + }); + + return function (dataIndex) { + return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex)); + }; +} + +function canShowAllSymbolForCategory(categoryAxis, data) { + // In mose cases, line is monotonous on category axis, and the label size + // is close with each other. So we check the symbol size and some of the + // label size alone with the category axis to estimate whether all symbol + // can be shown without overlap. + var axisExtent = categoryAxis.getExtent(); + var availSize = Math.abs(axisExtent[1] - axisExtent[0]) / categoryAxis.scale.count(); + isNaN(availSize) && (availSize = 0); // 0/0 is NaN. + + // Sampling some points, max 5. + var dataLen = data.count(); + var step = Math.max(1, Math.round(dataLen / 5)); + for (var dataIndex = 0; dataIndex < dataLen; dataIndex += step) { + if (SymbolClz$1.getSymbolSize( + data, dataIndex + // Only for cartesian, where `isHorizontal` exists. + )[categoryAxis.isHorizontal() ? 1 : 0] + // Empirical number + * 1.5 > availSize + ) { + return false; + } + } + + return true; +} + +function createLineClipPath(coordSys, hasAnimation, seriesModel) { + if (coordSys.type === 'cartesian2d') { + var isHorizontal = coordSys.getBaseAxis().isHorizontal(); + var clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel); + // Expand clip shape to avoid clipping when line value exceeds axis + if (!seriesModel.get('clip', true)) { + var rectShape = clipPath.shape; + var expandSize = Math.max(rectShape.width, rectShape.height); + if (isHorizontal) { + rectShape.y -= expandSize; + rectShape.height += expandSize * 2; + } + else { + rectShape.x -= expandSize; + rectShape.width += expandSize * 2; + } + } + return clipPath; + } + else { + return createPolarClipPath(coordSys, hasAnimation, seriesModel); + } + +} + +Chart.extend({ + + type: 'line', + + init: function () { + var lineGroup = new Group(); + + var symbolDraw = new SymbolDraw(); + this.group.add(symbolDraw.group); + + this._symbolDraw = symbolDraw; + this._lineGroup = lineGroup; + }, + + render: function (seriesModel, ecModel, api) { + var coordSys = seriesModel.coordinateSystem; + var group = this.group; + var data = seriesModel.getData(); + var lineStyleModel = seriesModel.getModel('lineStyle'); + var areaStyleModel = seriesModel.getModel('areaStyle'); + + var points = data.mapArray(data.getItemLayout); + + var isCoordSysPolar = coordSys.type === 'polar'; + var prevCoordSys = this._coordSys; + + var symbolDraw = this._symbolDraw; + var polyline = this._polyline; + var polygon = this._polygon; + + var lineGroup = this._lineGroup; + + var hasAnimation = seriesModel.get('animation'); + + var isAreaChart = !areaStyleModel.isEmpty(); + + var valueOrigin = areaStyleModel.get('origin'); + var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin); + + var stackedOnPoints = getStackedOnPoints(coordSys, data, dataCoordInfo); + + var showSymbol = seriesModel.get('showSymbol'); + + var isIgnoreFunc = showSymbol && !isCoordSysPolar + && getIsIgnoreFunc(seriesModel, data, coordSys); + + // Remove temporary symbols + var oldData = this._data; + oldData && oldData.eachItemGraphicEl(function (el, idx) { + if (el.__temp) { + group.remove(el); + oldData.setItemGraphicEl(idx, null); + } + }); + + // Remove previous created symbols if showSymbol changed to false + if (!showSymbol) { + symbolDraw.remove(); + } + + group.add(lineGroup); + + // FIXME step not support polar + var step = !isCoordSysPolar && seriesModel.get('step'); + var clipShapeForSymbol; + if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) { + clipShapeForSymbol = coordSys.getArea(); + // Avoid float number rounding error for symbol on the edge of axis extent. + // See #7913 and `test/dataZoom-clip.html`. + if (clipShapeForSymbol.width != null) { + clipShapeForSymbol.x -= 0.1; + clipShapeForSymbol.y -= 0.1; + clipShapeForSymbol.width += 0.2; + clipShapeForSymbol.height += 0.2; + } + else if (clipShapeForSymbol.r0) { + clipShapeForSymbol.r0 -= 0.5; + clipShapeForSymbol.r1 += 0.5; + } + } + this._clipShapeForSymbol = clipShapeForSymbol; + // Initialization animation or coordinate system changed + if ( + !(polyline && prevCoordSys.type === coordSys.type && step === this._step) + ) { + showSymbol && symbolDraw.updateData(data, { + isIgnore: isIgnoreFunc, + clipShape: clipShapeForSymbol + }); + + if (step) { + // TODO If stacked series is not step + points = turnPointsIntoStep(points, coordSys, step); + stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step); + } + + polyline = this._newPolyline(points, coordSys, hasAnimation); + if (isAreaChart) { + polygon = this._newPolygon( + points, stackedOnPoints, + coordSys, hasAnimation + ); + } + lineGroup.setClipPath(createLineClipPath(coordSys, true, seriesModel)); + } + else { + if (isAreaChart && !polygon) { + // If areaStyle is added + polygon = this._newPolygon( + points, stackedOnPoints, + coordSys, hasAnimation + ); + } + else if (polygon && !isAreaChart) { + // If areaStyle is removed + lineGroup.remove(polygon); + polygon = this._polygon = null; + } + + // Update clipPath + lineGroup.setClipPath(createLineClipPath(coordSys, false, seriesModel)); + + // Always update, or it is wrong in the case turning on legend + // because points are not changed + showSymbol && symbolDraw.updateData(data, { + isIgnore: isIgnoreFunc, + clipShape: clipShapeForSymbol + }); + + // Stop symbol animation and sync with line points + // FIXME performance? + data.eachItemGraphicEl(function (el) { + el.stopAnimation(true); + }); + + // In the case data zoom triggerred refreshing frequently + // Data may not change if line has a category axis. So it should animate nothing + if (!isPointsSame(this._stackedOnPoints, stackedOnPoints) + || !isPointsSame(this._points, points) + ) { + if (hasAnimation) { + this._updateAnimation( + data, stackedOnPoints, coordSys, api, step, valueOrigin + ); + } + else { + // Not do it in update with animation + if (step) { + // TODO If stacked series is not step + points = turnPointsIntoStep(points, coordSys, step); + stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step); + } + + polyline.setShape({ + points: points + }); + polygon && polygon.setShape({ + points: points, + stackedOnPoints: stackedOnPoints + }); + } + } + } + + var visualColor = getVisualGradient(data, coordSys) || data.getVisual('color'); + + polyline.useStyle(defaults( + // Use color in lineStyle first + lineStyleModel.getLineStyle(), + { + fill: 'none', + stroke: visualColor, + lineJoin: 'bevel' + } + )); + + var smooth = seriesModel.get('smooth'); + smooth = getSmooth(seriesModel.get('smooth')); + polyline.setShape({ + smooth: smooth, + smoothMonotone: seriesModel.get('smoothMonotone'), + connectNulls: seriesModel.get('connectNulls') + }); + + if (polygon) { + var stackedOnSeries = data.getCalculationInfo('stackedOnSeries'); + var stackedOnSmooth = 0; + + polygon.useStyle(defaults( + areaStyleModel.getAreaStyle(), + { + fill: visualColor, + opacity: 0.7, + lineJoin: 'bevel' + } + )); + + if (stackedOnSeries) { + stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth')); + } + + polygon.setShape({ + smooth: smooth, + stackedOnSmooth: stackedOnSmooth, + smoothMonotone: seriesModel.get('smoothMonotone'), + connectNulls: seriesModel.get('connectNulls') + }); + } + + this._data = data; + // Save the coordinate system for transition animation when data changed + this._coordSys = coordSys; + this._stackedOnPoints = stackedOnPoints; + this._points = points; + this._step = step; + this._valueOrigin = valueOrigin; + }, + + dispose: function () {}, + + highlight: function (seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(); + var dataIndex = queryDataIndex(data, payload); + + if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) { + var symbol = data.getItemGraphicEl(dataIndex); + if (!symbol) { + // Create a temporary symbol if it is not exists + var pt = data.getItemLayout(dataIndex); + if (!pt) { + // Null data + return; + } + // fix #11360: should't draw symbol outside clipShapeForSymbol + if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(pt[0], pt[1])) { + return; + } + symbol = new SymbolClz$1(data, dataIndex); + symbol.position = pt; + symbol.setZ( + seriesModel.get('zlevel'), + seriesModel.get('z') + ); + symbol.ignore = isNaN(pt[0]) || isNaN(pt[1]); + symbol.__temp = true; + data.setItemGraphicEl(dataIndex, symbol); + + // Stop scale animation + symbol.stopSymbolAnimation(true); + + this.group.add(symbol); + } + symbol.highlight(); + } + else { + // Highlight whole series + Chart.prototype.highlight.call( + this, seriesModel, ecModel, api, payload + ); + } + }, + + downplay: function (seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(); + var dataIndex = queryDataIndex(data, payload); + if (dataIndex != null && dataIndex >= 0) { + var symbol = data.getItemGraphicEl(dataIndex); + if (symbol) { + if (symbol.__temp) { + data.setItemGraphicEl(dataIndex, null); + this.group.remove(symbol); + } + else { + symbol.downplay(); + } + } + } + else { + // FIXME + // can not downplay completely. + // Downplay whole series + Chart.prototype.downplay.call( + this, seriesModel, ecModel, api, payload + ); + } + }, + + /** + * @param {module:zrender/container/Group} group + * @param {Array.>} points + * @private + */ + _newPolyline: function (points) { + var polyline = this._polyline; + // Remove previous created polyline + if (polyline) { + this._lineGroup.remove(polyline); + } + + polyline = new Polyline$1({ + shape: { + points: points + }, + silent: true, + z2: 10 + }); + + this._lineGroup.add(polyline); + + this._polyline = polyline; + + return polyline; + }, + + /** + * @param {module:zrender/container/Group} group + * @param {Array.>} stackedOnPoints + * @param {Array.>} points + * @private + */ + _newPolygon: function (points, stackedOnPoints) { + var polygon = this._polygon; + // Remove previous created polygon + if (polygon) { + this._lineGroup.remove(polygon); + } + + polygon = new Polygon$1({ + shape: { + points: points, + stackedOnPoints: stackedOnPoints + }, + silent: true + }); + + this._lineGroup.add(polygon); + + this._polygon = polygon; + return polygon; + }, + + /** + * @private + */ + // FIXME Two value axis + _updateAnimation: function (data, stackedOnPoints, coordSys, api, step, valueOrigin) { + var polyline = this._polyline; + var polygon = this._polygon; + var seriesModel = data.hostModel; + + var diff = lineAnimationDiff( + this._data, data, + this._stackedOnPoints, stackedOnPoints, + this._coordSys, coordSys, + this._valueOrigin, valueOrigin + ); + + var current = diff.current; + var stackedOnCurrent = diff.stackedOnCurrent; + var next = diff.next; + var stackedOnNext = diff.stackedOnNext; + if (step) { + // TODO If stacked series is not step + current = turnPointsIntoStep(diff.current, coordSys, step); + stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step); + next = turnPointsIntoStep(diff.next, coordSys, step); + stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step); + } + // `diff.current` is subset of `current` (which should be ensured by + // turnPointsIntoStep), so points in `__points` can be updated when + // points in `current` are update during animation. + polyline.shape.__points = diff.current; + polyline.shape.points = current; + + updateProps(polyline, { + shape: { + points: next + } + }, seriesModel); + + if (polygon) { + polygon.setShape({ + points: current, + stackedOnPoints: stackedOnCurrent + }); + updateProps(polygon, { + shape: { + points: next, + stackedOnPoints: stackedOnNext + } + }, seriesModel); + } + + var updatedDataInfo = []; + var diffStatus = diff.status; + + for (var i = 0; i < diffStatus.length; i++) { + var cmd = diffStatus[i].cmd; + if (cmd === '=') { + var el = data.getItemGraphicEl(diffStatus[i].idx1); + if (el) { + updatedDataInfo.push({ + el: el, + ptIdx: i // Index of points + }); + } + } + } + + if (polyline.animators && polyline.animators.length) { + polyline.animators[0].during(function () { + for (var i = 0; i < updatedDataInfo.length; i++) { + var el = updatedDataInfo[i].el; + el.attr('position', polyline.shape.__points[updatedDataInfo[i].ptIdx]); + } + }); + } + }, + + remove: function (ecModel) { + var group = this.group; + var oldData = this._data; + this._lineGroup.removeAll(); + this._symbolDraw.remove(true); + // Remove temporary created elements when highlighting + oldData && oldData.eachItemGraphicEl(function (el, idx) { + if (el.__temp) { + group.remove(el); + oldData.setItemGraphicEl(idx, null); + } + }); + + this._polyline = + this._polygon = + this._coordSys = + this._points = + this._stackedOnPoints = + this._data = null; + } +}); + +/* +* 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. +*/ + +var visualSymbol = function (seriesType, defaultSymbolType, legendSymbol) { + // Encoding visual for all series include which is filtered for legend drawing + return { + seriesType: seriesType, + + // For legend. + performRawSeries: true, + + reset: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + + var symbolType = seriesModel.get('symbol'); + var symbolSize = seriesModel.get('symbolSize'); + var keepAspect = seriesModel.get('symbolKeepAspect'); + + var hasSymbolTypeCallback = isFunction$1(symbolType); + var hasSymbolSizeCallback = isFunction$1(symbolSize); + var hasCallback = hasSymbolTypeCallback || hasSymbolSizeCallback; + var seriesSymbol = (!hasSymbolTypeCallback && symbolType) ? symbolType : defaultSymbolType; + var seriesSymbolSize = !hasSymbolSizeCallback ? symbolSize : null; + + data.setVisual({ + legendSymbol: legendSymbol || seriesSymbol, + // If seting callback functions on `symbol` or `symbolSize`, for simplicity and avoiding + // to bring trouble, we do not pick a reuslt from one of its calling on data item here, + // but just use the default value. Callback on `symbol` or `symbolSize` is convenient in + // some cases but generally it is not recommanded. + symbol: seriesSymbol, + symbolSize: seriesSymbolSize, + symbolKeepAspect: keepAspect + }); + + // Only visible series has each data be visual encoded + if (ecModel.isSeriesFiltered(seriesModel)) { + return; + } + + function dataEach(data, idx) { + if (hasCallback) { + var rawValue = seriesModel.getRawValue(idx); + var params = seriesModel.getDataParams(idx); + hasSymbolTypeCallback && data.setItemVisual(idx, 'symbol', symbolType(rawValue, params)); + hasSymbolSizeCallback && data.setItemVisual(idx, 'symbolSize', symbolSize(rawValue, params)); + } + + if (data.hasItemOption) { + var itemModel = data.getItemModel(idx); + var itemSymbolType = itemModel.getShallow('symbol', true); + var itemSymbolSize = itemModel.getShallow('symbolSize', true); + var itemSymbolKeepAspect = itemModel.getShallow('symbolKeepAspect', true); + + // If has item symbol + if (itemSymbolType != null) { + data.setItemVisual(idx, 'symbol', itemSymbolType); + } + if (itemSymbolSize != null) { + // PENDING Transform symbolSize ? + data.setItemVisual(idx, 'symbolSize', itemSymbolSize); + } + if (itemSymbolKeepAspect != null) { + data.setItemVisual(idx, 'symbolKeepAspect', itemSymbolKeepAspect); + } + } + } + + return { dataEach: (data.hasItemOption || hasCallback) ? dataEach : null }; + } + }; +}; + +/* +* 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. +*/ + +/* global Float32Array */ + +var pointsLayout = function (seriesType) { + return { + seriesType: seriesType, + + plan: createRenderPlanner(), + + reset: function (seriesModel) { + var data = seriesModel.getData(); + var coordSys = seriesModel.coordinateSystem; + var pipelineContext = seriesModel.pipelineContext; + var isLargeRender = pipelineContext.large; + + if (!coordSys) { + return; + } + + var dims = map(coordSys.dimensions, function (dim) { + return data.mapDimension(dim); + }).slice(0, 2); + var dimLen = dims.length; + + var stackResultDim = data.getCalculationInfo('stackResultDimension'); + if (isDimensionStacked(data, dims[0] /*, dims[1]*/)) { + dims[0] = stackResultDim; + } + if (isDimensionStacked(data, dims[1] /*, dims[0]*/)) { + dims[1] = stackResultDim; + } + + function progress(params, data) { + var segCount = params.end - params.start; + var points = isLargeRender && new Float32Array(segCount * dimLen); + + for (var i = params.start, offset = 0, tmpIn = [], tmpOut = []; i < params.end; i++) { + var point; + + if (dimLen === 1) { + var x = data.get(dims[0], i); + point = !isNaN(x) && coordSys.dataToPoint(x, null, tmpOut); + } + else { + var x = tmpIn[0] = data.get(dims[0], i); + var y = tmpIn[1] = data.get(dims[1], i); + // Also {Array.}, not undefined to avoid if...else... statement + point = !isNaN(x) && !isNaN(y) && coordSys.dataToPoint(tmpIn, null, tmpOut); + } + + if (isLargeRender) { + points[offset++] = point ? point[0] : NaN; + points[offset++] = point ? point[1] : NaN; + } + else { + data.setItemLayout(i, (point && point.slice()) || [NaN, NaN]); + } + } + + isLargeRender && data.setLayout('symbolPoints', points); + } + + return dimLen && {progress: progress}; + } + }; +}; + +/* +* 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. +*/ + + +var samplers = { + average: function (frame) { + var sum = 0; + var count = 0; + for (var i = 0; i < frame.length; i++) { + if (!isNaN(frame[i])) { + sum += frame[i]; + count++; + } + } + // Return NaN if count is 0 + return count === 0 ? NaN : sum / count; + }, + sum: function (frame) { + var sum = 0; + for (var i = 0; i < frame.length; i++) { + // Ignore NaN + sum += frame[i] || 0; + } + return sum; + }, + max: function (frame) { + var max = -Infinity; + for (var i = 0; i < frame.length; i++) { + frame[i] > max && (max = frame[i]); + } + // NaN will cause illegal axis extent. + return isFinite(max) ? max : NaN; + }, + min: function (frame) { + var min = Infinity; + for (var i = 0; i < frame.length; i++) { + frame[i] < min && (min = frame[i]); + } + // NaN will cause illegal axis extent. + return isFinite(min) ? min : NaN; + }, + // TODO + // Median + nearest: function (frame) { + return frame[0]; + } +}; + +var indexSampler = function (frame, value) { + return Math.round(frame.length / 2); +}; + +var dataSample = function (seriesType) { + return { + + seriesType: seriesType, + + modifyOutputEnd: true, + + reset: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + var sampling = seriesModel.get('sampling'); + var coordSys = seriesModel.coordinateSystem; + // Only cartesian2d support down sampling + if (coordSys.type === 'cartesian2d' && sampling) { + var baseAxis = coordSys.getBaseAxis(); + var valueAxis = coordSys.getOtherAxis(baseAxis); + var extent = baseAxis.getExtent(); + // Coordinste system has been resized + var size = extent[1] - extent[0]; + var rate = Math.round(data.count() / size); + if (rate > 1) { + var sampler; + if (typeof sampling === 'string') { + sampler = samplers[sampling]; + } + else if (typeof sampling === 'function') { + sampler = sampling; + } + if (sampler) { + // Only support sample the first dim mapped from value axis. + seriesModel.setData(data.downSample( + data.mapDimension(valueAxis.dim), 1 / rate, sampler, indexSampler + )); + } + } + } + } + }; +}; + +/* +* 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. +*/ + +/** + * Cartesian coordinate system + * @module echarts/coord/Cartesian + * + */ + +function dimAxisMapper(dim) { + return this._axes[dim]; +} + +/** + * @alias module:echarts/coord/Cartesian + * @constructor + */ +var Cartesian = function (name) { + this._axes = {}; + + this._dimList = []; + + /** + * @type {string} + */ + this.name = name || ''; +}; + +Cartesian.prototype = { + + constructor: Cartesian, + + type: 'cartesian', + + /** + * Get axis + * @param {number|string} dim + * @return {module:echarts/coord/Cartesian~Axis} + */ + getAxis: function (dim) { + return this._axes[dim]; + }, + + /** + * Get axes list + * @return {Array.} + */ + getAxes: function () { + return map(this._dimList, dimAxisMapper, this); + }, + + /** + * Get axes list by given scale type + */ + getAxesByScale: function (scaleType) { + scaleType = scaleType.toLowerCase(); + return filter( + this.getAxes(), + function (axis) { + return axis.scale.type === scaleType; + } + ); + }, + + /** + * Add axis + * @param {module:echarts/coord/Cartesian.Axis} + */ + addAxis: function (axis) { + var dim = axis.dim; + + this._axes[dim] = axis; + + this._dimList.push(dim); + }, + + /** + * Convert data to coord in nd space + * @param {Array.|Object.} val + * @return {Array.|Object.} + */ + dataToCoord: function (val) { + return this._dataCoordConvert(val, 'dataToCoord'); + }, + + /** + * Convert coord in nd space to data + * @param {Array.|Object.} val + * @return {Array.|Object.} + */ + coordToData: function (val) { + return this._dataCoordConvert(val, 'coordToData'); + }, + + _dataCoordConvert: function (input, method) { + var dimList = this._dimList; + + var output = input instanceof Array ? [] : {}; + + for (var i = 0; i < dimList.length; i++) { + var dim = dimList[i]; + var axis = this._axes[dim]; + + output[dim] = axis[method](input[dim]); + } + + return output; + } +}; + +/* +* 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. +*/ + + +function Cartesian2D(name) { + + Cartesian.call(this, name); +} + +Cartesian2D.prototype = { + + constructor: Cartesian2D, + + type: 'cartesian2d', + + /** + * @type {Array.} + * @readOnly + */ + dimensions: ['x', 'y'], + + /** + * Base axis will be used on stacking. + * + * @return {module:echarts/coord/cartesian/Axis2D} + */ + getBaseAxis: function () { + return this.getAxesByScale('ordinal')[0] + || this.getAxesByScale('time')[0] + || this.getAxis('x'); + }, + + /** + * If contain point + * @param {Array.} point + * @return {boolean} + */ + containPoint: function (point) { + var axisX = this.getAxis('x'); + var axisY = this.getAxis('y'); + return axisX.contain(axisX.toLocalCoord(point[0])) + && axisY.contain(axisY.toLocalCoord(point[1])); + }, + + /** + * If contain data + * @param {Array.} data + * @return {boolean} + */ + containData: function (data) { + return this.getAxis('x').containData(data[0]) + && this.getAxis('y').containData(data[1]); + }, + + /** + * @param {Array.} data + * @param {Array.} out + * @return {Array.} + */ + dataToPoint: function (data, reserved, out) { + var xAxis = this.getAxis('x'); + var yAxis = this.getAxis('y'); + out = out || []; + out[0] = xAxis.toGlobalCoord(xAxis.dataToCoord(data[0])); + out[1] = yAxis.toGlobalCoord(yAxis.dataToCoord(data[1])); + return out; + }, + + /** + * @param {Array.} data + * @param {Array.} out + * @return {Array.} + */ + clampData: function (data, out) { + var xScale = this.getAxis('x').scale; + var yScale = this.getAxis('y').scale; + var xAxisExtent = xScale.getExtent(); + var yAxisExtent = yScale.getExtent(); + var x = xScale.parse(data[0]); + var y = yScale.parse(data[1]); + out = out || []; + out[0] = Math.min( + Math.max(Math.min(xAxisExtent[0], xAxisExtent[1]), x), + Math.max(xAxisExtent[0], xAxisExtent[1]) + ); + out[1] = Math.min( + Math.max(Math.min(yAxisExtent[0], yAxisExtent[1]), y), + Math.max(yAxisExtent[0], yAxisExtent[1]) + ); + + return out; + }, + + /** + * @param {Array.} point + * @param {Array.} out + * @return {Array.} + */ + pointToData: function (point, out) { + var xAxis = this.getAxis('x'); + var yAxis = this.getAxis('y'); + out = out || []; + out[0] = xAxis.coordToData(xAxis.toLocalCoord(point[0])); + out[1] = yAxis.coordToData(yAxis.toLocalCoord(point[1])); + return out; + }, + + /** + * Get other axis + * @param {module:echarts/coord/cartesian/Axis2D} axis + */ + getOtherAxis: function (axis) { + return this.getAxis(axis.dim === 'x' ? 'y' : 'x'); + }, + + /** + * Get rect area of cartesian. + * Area will have a contain function to determine if a point is in the coordinate system. + * @return {BoundingRect} + */ + getArea: function () { + var xExtent = this.getAxis('x').getGlobalExtent(); + var yExtent = this.getAxis('y').getGlobalExtent(); + var x = Math.min(xExtent[0], xExtent[1]); + var y = Math.min(yExtent[0], yExtent[1]); + var width = Math.max(xExtent[0], xExtent[1]) - x; + var height = Math.max(yExtent[0], yExtent[1]) - y; + + var rect = new BoundingRect(x, y, width, height); + return rect; + } + +}; + +inherits(Cartesian2D, Cartesian); + +/* +* 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. +*/ + +/** + * Extend axis 2d + * @constructor module:echarts/coord/cartesian/Axis2D + * @extends {module:echarts/coord/cartesian/Axis} + * @param {string} dim + * @param {*} scale + * @param {Array.} coordExtent + * @param {string} axisType + * @param {string} position + */ +var Axis2D = function (dim, scale, coordExtent, axisType, position) { + Axis.call(this, dim, scale, coordExtent); + /** + * Axis type + * - 'category' + * - 'value' + * - 'time' + * - 'log' + * @type {string} + */ + this.type = axisType || 'value'; + + /** + * Axis position + * - 'top' + * - 'bottom' + * - 'left' + * - 'right' + */ + this.position = position || 'bottom'; +}; + +Axis2D.prototype = { + + constructor: Axis2D, + + /** + * Index of axis, can be used as key + */ + index: 0, + + /** + * Implemented in . + * @return {Array.} + * If not on zero of other axis, return null/undefined. + * If no axes, return an empty array. + */ + getAxesOnZeroOf: null, + + /** + * Axis model + * @param {module:echarts/coord/cartesian/AxisModel} + */ + model: null, + + isHorizontal: function () { + var position = this.position; + return position === 'top' || position === 'bottom'; + }, + + /** + * Each item cooresponds to this.getExtent(), which + * means globalExtent[0] may greater than globalExtent[1], + * unless `asc` is input. + * + * @param {boolean} [asc] + * @return {Array.} + */ + getGlobalExtent: function (asc) { + var ret = this.getExtent(); + ret[0] = this.toGlobalCoord(ret[0]); + ret[1] = this.toGlobalCoord(ret[1]); + asc && ret[0] > ret[1] && ret.reverse(); + return ret; + }, + + getOtherAxis: function () { + this.grid.getOtherAxis(); + }, + + /** + * @override + */ + pointToData: function (point, clamp) { + return this.coordToData(this.toLocalCoord(point[this.dim === 'x' ? 0 : 1]), clamp); + }, + + /** + * Transform global coord to local coord, + * i.e. var localCoord = axis.toLocalCoord(80); + * designate by module:echarts/coord/cartesian/Grid. + * @type {Function} + */ + toLocalCoord: null, + + /** + * Transform global coord to local coord, + * i.e. var globalCoord = axis.toLocalCoord(40); + * designate by module:echarts/coord/cartesian/Grid. + * @type {Function} + */ + toGlobalCoord: null + +}; + +inherits(Axis2D, Axis); + +/* +* 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. +*/ + +var defaultOption = { + show: true, + zlevel: 0, + z: 0, + // Inverse the axis. + inverse: false, + + // Axis name displayed. + name: '', + // 'start' | 'middle' | 'end' + nameLocation: 'end', + // By degree. By defualt auto rotate by nameLocation. + nameRotate: null, + nameTruncate: { + maxWidth: null, + ellipsis: '...', + placeholder: '.' + }, + // Use global text style by default. + nameTextStyle: {}, + // The gap between axisName and axisLine. + nameGap: 15, + + // Default `false` to support tooltip. + silent: false, + // Default `false` to avoid legacy user event listener fail. + triggerEvent: false, + + tooltip: { + show: false + }, + + axisPointer: {}, + + axisLine: { + show: true, + onZero: true, + onZeroAxisIndex: null, + lineStyle: { + color: '#333', + width: 1, + type: 'solid' + }, + // The arrow at both ends the the axis. + symbol: ['none', 'none'], + symbolSize: [10, 15] + }, + axisTick: { + show: true, + // Whether axisTick is inside the grid or outside the grid. + inside: false, + // The length of axisTick. + length: 5, + lineStyle: { + width: 1 + } + }, + axisLabel: { + show: true, + // Whether axisLabel is inside the grid or outside the grid. + inside: false, + rotate: 0, + // true | false | null/undefined (auto) + showMinLabel: null, + // true | false | null/undefined (auto) + showMaxLabel: null, + margin: 8, + // formatter: null, + fontSize: 12 + }, + splitLine: { + show: true, + lineStyle: { + color: ['#ccc'], + width: 1, + type: 'solid' + } + }, + splitArea: { + show: false, + areaStyle: { + color: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'] + } + } +}; + +var axisDefault = {}; + +axisDefault.categoryAxis = merge({ + // The gap at both ends of the axis. For categoryAxis, boolean. + boundaryGap: true, + // Set false to faster category collection. + // Only usefull in the case like: category is + // ['2012-01-01', '2012-01-02', ...], where the input + // data has been ensured not duplicate and is large data. + // null means "auto": + // if axis.data provided, do not deduplication, + // else do deduplication. + deduplication: null, + // splitArea: { + // show: false + // }, + splitLine: { + show: false + }, + axisTick: { + // If tick is align with label when boundaryGap is true + alignWithLabel: false, + interval: 'auto' + }, + axisLabel: { + interval: 'auto' + } +}, defaultOption); + +axisDefault.valueAxis = merge({ + // The gap at both ends of the axis. For value axis, [GAP, GAP], where + // `GAP` can be an absolute pixel number (like `35`), or percent (like `'30%'`) + boundaryGap: [0, 0], + + // TODO + // min/max: [30, datamin, 60] or [20, datamin] or [datamin, 60] + + // Min value of the axis. can be: + // + a number + // + 'dataMin': use the min value in data. + // + null/undefined: auto decide min value (consider pretty look and boundaryGap). + // min: null, + + // Max value of the axis. can be: + // + a number + // + 'dataMax': use the max value in data. + // + null/undefined: auto decide max value (consider pretty look and boundaryGap). + // max: null, + + // Readonly prop, specifies start value of the range when using data zoom. + // rangeStart: null + + // Readonly prop, specifies end value of the range when using data zoom. + // rangeEnd: null + + // Optional value can be: + // + `false`: always include value 0. + // + `true`: the extent do not consider value 0. + // scale: false, + + // AxisTick and axisLabel and splitLine are caculated based on splitNumber. + splitNumber: 5, + + // Interval specifies the span of the ticks is mandatorily. + // interval: null + + // Specify min interval when auto calculate tick interval. + // minInterval: null + + // Specify max interval when auto calculate tick interval. + // maxInterval: null + + minorTick: { + // Minor tick, not available for cateogry axis. + show: false, + // Split number of minor ticks. The value should be in range of (0, 100) + splitNumber: 5, + // Lenght of minor tick + length: 3, + + // Same inside with axisTick + + // Line style + lineStyle: { + // Default to be same with axisTick + } + }, + + minorSplitLine: { + show: false, + + lineStyle: { + color: '#eee', + width: 1 + } + } +}, defaultOption); + +axisDefault.timeAxis = defaults({ + scale: true, + min: 'dataMin', + max: 'dataMax' +}, axisDefault.valueAxis); + +axisDefault.logAxis = defaults({ + scale: true, + logBase: 10 +}, axisDefault.valueAxis); + +/* +* 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. +*/ + +// FIXME axisType is fixed ? +var AXIS_TYPES = ['value', 'category', 'time', 'log']; + +/** + * Generate sub axis model class + * @param {string} axisName 'x' 'y' 'radius' 'angle' 'parallel' + * @param {module:echarts/model/Component} BaseAxisModelClass + * @param {Function} axisTypeDefaulter + * @param {Object} [extraDefaultOption] + */ +var axisModelCreator = function (axisName, BaseAxisModelClass, axisTypeDefaulter, extraDefaultOption) { + + each$1(AXIS_TYPES, function (axisType) { + + BaseAxisModelClass.extend({ + + /** + * @readOnly + */ + type: axisName + 'Axis.' + axisType, + + mergeDefaultAndTheme: function (option, ecModel) { + var layoutMode = this.layoutMode; + var inputPositionParams = layoutMode + ? getLayoutParams(option) : {}; + + var themeModel = ecModel.getTheme(); + merge(option, themeModel.get(axisType + 'Axis')); + merge(option, this.getDefaultOption()); + + option.type = axisTypeDefaulter(axisName, option); + + if (layoutMode) { + mergeLayoutParam(option, inputPositionParams, layoutMode); + } + }, + + /** + * @override + */ + optionUpdated: function () { + var thisOption = this.option; + if (thisOption.type === 'category') { + this.__ordinalMeta = OrdinalMeta.createByAxisModel(this); + } + }, + + /** + * Should not be called before all of 'getInitailData' finished. + * Because categories are collected during initializing data. + */ + getCategories: function (rawData) { + var option = this.option; + // FIXME + // warning if called before all of 'getInitailData' finished. + if (option.type === 'category') { + if (rawData) { + return option.data; + } + return this.__ordinalMeta.categories; + } + }, + + getOrdinalMeta: function () { + return this.__ordinalMeta; + }, + + defaultOption: mergeAll( + [ + {}, + axisDefault[axisType + 'Axis'], + extraDefaultOption + ], + true + ) + }); + }); + + ComponentModel.registerSubTypeDefaulter( + axisName + 'Axis', + curry(axisTypeDefaulter, axisName) + ); +}; + +/* +* 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. +*/ + +var AxisModel = ComponentModel.extend({ + + type: 'cartesian2dAxis', + + /** + * @type {module:echarts/coord/cartesian/Axis2D} + */ + axis: null, + + /** + * @override + */ + init: function () { + AxisModel.superApply(this, 'init', arguments); + this.resetRange(); + }, + + /** + * @override + */ + mergeOption: function () { + AxisModel.superApply(this, 'mergeOption', arguments); + this.resetRange(); + }, + + /** + * @override + */ + restoreData: function () { + AxisModel.superApply(this, 'restoreData', arguments); + this.resetRange(); + }, + + /** + * @override + * @return {module:echarts/model/Component} + */ + getCoordSysModel: function () { + return this.ecModel.queryComponents({ + mainType: 'grid', + index: this.option.gridIndex, + id: this.option.gridId + })[0]; + } + +}); + +function getAxisType(axisDim, option) { + // Default axis with data is category axis + return option.type || (option.data ? 'category' : 'value'); +} + +merge(AxisModel.prototype, axisModelCommonMixin); + +var extraOption = { + // gridIndex: 0, + // gridId: '', + + // Offset is for multiple axis on the same position + offset: 0 +}; + +axisModelCreator('x', AxisModel, getAxisType, extraOption); +axisModelCreator('y', AxisModel, getAxisType, extraOption); + +/* +* 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. +*/ + +// Grid 是在有直角坐标系的时候必须要存在的 +// 所以这里也要被 Cartesian2D 依赖 + +ComponentModel.extend({ + + type: 'grid', + + dependencies: ['xAxis', 'yAxis'], + + layoutMode: 'box', + + /** + * @type {module:echarts/coord/cartesian/Grid} + */ + coordinateSystem: null, + + defaultOption: { + show: false, + zlevel: 0, + z: 0, + left: '10%', + top: 60, + right: '10%', + bottom: 60, + // If grid size contain label + containLabel: false, + // width: {totalWidth} - left - right, + // height: {totalHeight} - top - bottom, + backgroundColor: 'rgba(0,0,0,0)', + borderWidth: 1, + borderColor: '#ccc' + } +}); + +/* +* 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. +*/ + +/** + * Grid is a region which contains at most 4 cartesian systems + * + * TODO Default cartesian + */ + +// Depends on GridModel, AxisModel, which performs preprocess. +/** + * Check if the axis is used in the specified grid + * @inner + */ +function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) { + return axisModel.getCoordSysModel() === gridModel; +} + +function Grid(gridModel, ecModel, api) { + /** + * @type {Object.} + * @private + */ + this._coordsMap = {}; + + /** + * @type {Array.} + * @private + */ + this._coordsList = []; + + /** + * @type {Object.>} + * @private + */ + this._axesMap = {}; + + /** + * @type {Array.} + * @private + */ + this._axesList = []; + + this._initCartesian(gridModel, ecModel, api); + + this.model = gridModel; +} + +var gridProto = Grid.prototype; + +gridProto.type = 'grid'; + +gridProto.axisPointerEnabled = true; + +gridProto.getRect = function () { + return this._rect; +}; + +gridProto.update = function (ecModel, api) { + + var axesMap = this._axesMap; + + this._updateScale(ecModel, this.model); + + each$1(axesMap.x, function (xAxis) { + niceScaleExtent(xAxis.scale, xAxis.model); + }); + each$1(axesMap.y, function (yAxis) { + niceScaleExtent(yAxis.scale, yAxis.model); + }); + + // Key: axisDim_axisIndex, value: boolean, whether onZero target. + var onZeroRecords = {}; + + each$1(axesMap.x, function (xAxis) { + fixAxisOnZero(axesMap, 'y', xAxis, onZeroRecords); + }); + each$1(axesMap.y, function (yAxis) { + fixAxisOnZero(axesMap, 'x', yAxis, onZeroRecords); + }); + + // Resize again if containLabel is enabled + // FIXME It may cause getting wrong grid size in data processing stage + this.resize(this.model, api); +}; + +function fixAxisOnZero(axesMap, otherAxisDim, axis, onZeroRecords) { + + axis.getAxesOnZeroOf = function () { + // TODO: onZero of multiple axes. + return otherAxisOnZeroOf ? [otherAxisOnZeroOf] : []; + }; + + // onZero can not be enabled in these two situations: + // 1. When any other axis is a category axis. + // 2. When no axis is cross 0 point. + var otherAxes = axesMap[otherAxisDim]; + + var otherAxisOnZeroOf; + var axisModel = axis.model; + var onZero = axisModel.get('axisLine.onZero'); + var onZeroAxisIndex = axisModel.get('axisLine.onZeroAxisIndex'); + + if (!onZero) { + return; + } + + // If target axis is specified. + if (onZeroAxisIndex != null) { + if (canOnZeroToAxis(otherAxes[onZeroAxisIndex])) { + otherAxisOnZeroOf = otherAxes[onZeroAxisIndex]; + } + } + else { + // Find the first available other axis. + for (var idx in otherAxes) { + if (otherAxes.hasOwnProperty(idx) + && canOnZeroToAxis(otherAxes[idx]) + // Consider that two Y axes on one value axis, + // if both onZero, the two Y axes overlap. + && !onZeroRecords[getOnZeroRecordKey(otherAxes[idx])] + ) { + otherAxisOnZeroOf = otherAxes[idx]; + break; + } + } + } + + if (otherAxisOnZeroOf) { + onZeroRecords[getOnZeroRecordKey(otherAxisOnZeroOf)] = true; + } + + function getOnZeroRecordKey(axis) { + return axis.dim + '_' + axis.index; + } +} + +function canOnZeroToAxis(axis) { + return axis && axis.type !== 'category' && axis.type !== 'time' && ifAxisCrossZero(axis); +} + +/** + * Resize the grid + * @param {module:echarts/coord/cartesian/GridModel} gridModel + * @param {module:echarts/ExtensionAPI} api + */ +gridProto.resize = function (gridModel, api, ignoreContainLabel) { + + var gridRect = getLayoutRect( + gridModel.getBoxLayoutParams(), { + width: api.getWidth(), + height: api.getHeight() + }); + + this._rect = gridRect; + + var axesList = this._axesList; + + adjustAxes(); + + // Minus label size + if (!ignoreContainLabel && gridModel.get('containLabel')) { + each$1(axesList, function (axis) { + if (!axis.model.get('axisLabel.inside')) { + var labelUnionRect = estimateLabelUnionRect(axis); + if (labelUnionRect) { + var dim = axis.isHorizontal() ? 'height' : 'width'; + var margin = axis.model.get('axisLabel.margin'); + gridRect[dim] -= labelUnionRect[dim] + margin; + if (axis.position === 'top') { + gridRect.y += labelUnionRect.height + margin; + } + else if (axis.position === 'left') { + gridRect.x += labelUnionRect.width + margin; + } + } + } + }); + + adjustAxes(); + } + + function adjustAxes() { + each$1(axesList, function (axis) { + var isHorizontal = axis.isHorizontal(); + var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height]; + var idx = axis.inverse ? 1 : 0; + axis.setExtent(extent[idx], extent[1 - idx]); + updateAxisTransform(axis, isHorizontal ? gridRect.x : gridRect.y); + }); + } +}; + +/** + * @param {string} axisType + * @param {number} [axisIndex] + */ +gridProto.getAxis = function (axisType, axisIndex) { + var axesMapOnDim = this._axesMap[axisType]; + if (axesMapOnDim != null) { + if (axisIndex == null) { + // Find first axis + for (var name in axesMapOnDim) { + if (axesMapOnDim.hasOwnProperty(name)) { + return axesMapOnDim[name]; + } + } + } + return axesMapOnDim[axisIndex]; + } +}; + +/** + * @return {Array.} + */ +gridProto.getAxes = function () { + return this._axesList.slice(); +}; + +/** + * Usage: + * grid.getCartesian(xAxisIndex, yAxisIndex); + * grid.getCartesian(xAxisIndex); + * grid.getCartesian(null, yAxisIndex); + * grid.getCartesian({xAxisIndex: ..., yAxisIndex: ...}); + * + * @param {number|Object} [xAxisIndex] + * @param {number} [yAxisIndex] + */ +gridProto.getCartesian = function (xAxisIndex, yAxisIndex) { + if (xAxisIndex != null && yAxisIndex != null) { + var key = 'x' + xAxisIndex + 'y' + yAxisIndex; + return this._coordsMap[key]; + } + + if (isObject$1(xAxisIndex)) { + yAxisIndex = xAxisIndex.yAxisIndex; + xAxisIndex = xAxisIndex.xAxisIndex; + } + // When only xAxisIndex or yAxisIndex given, find its first cartesian. + for (var i = 0, coordList = this._coordsList; i < coordList.length; i++) { + if (coordList[i].getAxis('x').index === xAxisIndex + || coordList[i].getAxis('y').index === yAxisIndex + ) { + return coordList[i]; + } + } +}; + +gridProto.getCartesians = function () { + return this._coordsList.slice(); +}; + +/** + * @implements + * see {module:echarts/CoodinateSystem} + */ +gridProto.convertToPixel = function (ecModel, finder, value) { + var target = this._findConvertTarget(ecModel, finder); + + return target.cartesian + ? target.cartesian.dataToPoint(value) + : target.axis + ? target.axis.toGlobalCoord(target.axis.dataToCoord(value)) + : null; +}; + +/** + * @implements + * see {module:echarts/CoodinateSystem} + */ +gridProto.convertFromPixel = function (ecModel, finder, value) { + var target = this._findConvertTarget(ecModel, finder); + + return target.cartesian + ? target.cartesian.pointToData(value) + : target.axis + ? target.axis.coordToData(target.axis.toLocalCoord(value)) + : null; +}; + +/** + * @inner + */ +gridProto._findConvertTarget = function (ecModel, finder) { + var seriesModel = finder.seriesModel; + var xAxisModel = finder.xAxisModel + || (seriesModel && seriesModel.getReferringComponents('xAxis')[0]); + var yAxisModel = finder.yAxisModel + || (seriesModel && seriesModel.getReferringComponents('yAxis')[0]); + var gridModel = finder.gridModel; + var coordsList = this._coordsList; + var cartesian; + var axis; + + if (seriesModel) { + cartesian = seriesModel.coordinateSystem; + indexOf(coordsList, cartesian) < 0 && (cartesian = null); + } + else if (xAxisModel && yAxisModel) { + cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex); + } + else if (xAxisModel) { + axis = this.getAxis('x', xAxisModel.componentIndex); + } + else if (yAxisModel) { + axis = this.getAxis('y', yAxisModel.componentIndex); + } + // Lowest priority. + else if (gridModel) { + var grid = gridModel.coordinateSystem; + if (grid === this) { + cartesian = this._coordsList[0]; + } + } + + return {cartesian: cartesian, axis: axis}; +}; + +/** + * @implements + * see {module:echarts/CoodinateSystem} + */ +gridProto.containPoint = function (point) { + var coord = this._coordsList[0]; + if (coord) { + return coord.containPoint(point); + } +}; + +/** + * Initialize cartesian coordinate systems + * @private + */ +gridProto._initCartesian = function (gridModel, ecModel, api) { + var axisPositionUsed = { + left: false, + right: false, + top: false, + bottom: false + }; + + var axesMap = { + x: {}, + y: {} + }; + var axesCount = { + x: 0, + y: 0 + }; + + /// Create axis + ecModel.eachComponent('xAxis', createAxisCreator('x'), this); + ecModel.eachComponent('yAxis', createAxisCreator('y'), this); + + if (!axesCount.x || !axesCount.y) { + // Roll back when there no either x or y axis + this._axesMap = {}; + this._axesList = []; + return; + } + + this._axesMap = axesMap; + + /// Create cartesian2d + each$1(axesMap.x, function (xAxis, xAxisIndex) { + each$1(axesMap.y, function (yAxis, yAxisIndex) { + var key = 'x' + xAxisIndex + 'y' + yAxisIndex; + var cartesian = new Cartesian2D(key); + + cartesian.grid = this; + cartesian.model = gridModel; + + this._coordsMap[key] = cartesian; + this._coordsList.push(cartesian); + + cartesian.addAxis(xAxis); + cartesian.addAxis(yAxis); + }, this); + }, this); + + function createAxisCreator(axisType) { + return function (axisModel, idx) { + if (!isAxisUsedInTheGrid(axisModel, gridModel, ecModel)) { + return; + } + + var axisPosition = axisModel.get('position'); + if (axisType === 'x') { + // Fix position + if (axisPosition !== 'top' && axisPosition !== 'bottom') { + // Default bottom of X + axisPosition = axisPositionUsed.bottom ? 'top' : 'bottom'; + } + } + else { + // Fix position + if (axisPosition !== 'left' && axisPosition !== 'right') { + // Default left of Y + axisPosition = axisPositionUsed.left ? 'right' : 'left'; + } + } + axisPositionUsed[axisPosition] = true; + + var axis = new Axis2D( + axisType, createScaleByModel(axisModel), + [0, 0], + axisModel.get('type'), + axisPosition + ); + + var isCategory = axis.type === 'category'; + axis.onBand = isCategory && axisModel.get('boundaryGap'); + axis.inverse = axisModel.get('inverse'); + + // Inject axis into axisModel + axisModel.axis = axis; + + // Inject axisModel into axis + axis.model = axisModel; + + // Inject grid info axis + axis.grid = this; + + // Index of axis, can be used as key + axis.index = idx; + + this._axesList.push(axis); + + axesMap[axisType][idx] = axis; + axesCount[axisType]++; + }; + } +}; + +/** + * Update cartesian properties from series + * @param {module:echarts/model/Option} option + * @private + */ +gridProto._updateScale = function (ecModel, gridModel) { + // Reset scale + each$1(this._axesList, function (axis) { + axis.scale.setExtent(Infinity, -Infinity); + }); + ecModel.eachSeries(function (seriesModel) { + if (isCartesian2D(seriesModel)) { + var axesModels = findAxesModels(seriesModel, ecModel); + var xAxisModel = axesModels[0]; + var yAxisModel = axesModels[1]; + + if (!isAxisUsedInTheGrid(xAxisModel, gridModel, ecModel) + || !isAxisUsedInTheGrid(yAxisModel, gridModel, ecModel) + ) { + return; + } + + var cartesian = this.getCartesian( + xAxisModel.componentIndex, yAxisModel.componentIndex + ); + var data = seriesModel.getData(); + var xAxis = cartesian.getAxis('x'); + var yAxis = cartesian.getAxis('y'); + + if (data.type === 'list') { + unionExtent(data, xAxis, seriesModel); + unionExtent(data, yAxis, seriesModel); + } + } + }, this); + + function unionExtent(data, axis, seriesModel) { + each$1(data.mapDimension(axis.dim, true), function (dim) { + axis.scale.unionExtentFromData( + // For example, the extent of the orginal dimension + // is [0.1, 0.5], the extent of the `stackResultDimension` + // is [7, 9], the final extent should not include [0.1, 0.5]. + data, getStackedDimension(data, dim) + ); + }); + } +}; + +/** + * @param {string} [dim] 'x' or 'y' or 'auto' or null/undefined + * @return {Object} {baseAxes: [], otherAxes: []} + */ +gridProto.getTooltipAxes = function (dim) { + var baseAxes = []; + var otherAxes = []; + + each$1(this.getCartesians(), function (cartesian) { + var baseAxis = (dim != null && dim !== 'auto') + ? cartesian.getAxis(dim) : cartesian.getBaseAxis(); + var otherAxis = cartesian.getOtherAxis(baseAxis); + indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis); + indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis); + }); + + return {baseAxes: baseAxes, otherAxes: otherAxes}; +}; + +/** + * @inner + */ +function updateAxisTransform(axis, coordBase) { + var axisExtent = axis.getExtent(); + var axisExtentSum = axisExtent[0] + axisExtent[1]; + + // Fast transform + axis.toGlobalCoord = axis.dim === 'x' + ? function (coord) { + return coord + coordBase; + } + : function (coord) { + return axisExtentSum - coord + coordBase; + }; + axis.toLocalCoord = axis.dim === 'x' + ? function (coord) { + return coord - coordBase; + } + : function (coord) { + return axisExtentSum - coord + coordBase; + }; +} + +var axesTypes = ['xAxis', 'yAxis']; +/** + * @inner + */ +function findAxesModels(seriesModel, ecModel) { + return map(axesTypes, function (axisType) { + var axisModel = seriesModel.getReferringComponents(axisType)[0]; + + if (__DEV__) { + if (!axisModel) { + throw new Error(axisType + ' "' + retrieve( + seriesModel.get(axisType + 'Index'), + seriesModel.get(axisType + 'Id'), + 0 + ) + '" not found'); + } + } + return axisModel; + }); +} + +/** + * @inner + */ +function isCartesian2D(seriesModel) { + return seriesModel.get('coordinateSystem') === 'cartesian2d'; +} + +Grid.create = function (ecModel, api) { + var grids = []; + ecModel.eachComponent('grid', function (gridModel, idx) { + var grid = new Grid(gridModel, ecModel, api); + grid.name = 'grid_' + idx; + // dataSampling requires axis extent, so resize + // should be performed in create stage. + grid.resize(gridModel, api, true); + + gridModel.coordinateSystem = grid; + + grids.push(grid); + }); + + // Inject the coordinateSystems into seriesModel + ecModel.eachSeries(function (seriesModel) { + if (!isCartesian2D(seriesModel)) { + return; + } + + var axesModels = findAxesModels(seriesModel, ecModel); + var xAxisModel = axesModels[0]; + var yAxisModel = axesModels[1]; + + var gridModel = xAxisModel.getCoordSysModel(); + + if (__DEV__) { + if (!gridModel) { + throw new Error( + 'Grid "' + retrieve( + xAxisModel.get('gridIndex'), + xAxisModel.get('gridId'), + 0 + ) + '" not found' + ); + } + if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) { + throw new Error('xAxis and yAxis must use the same grid'); + } + } + + var grid = gridModel.coordinateSystem; + + seriesModel.coordinateSystem = grid.getCartesian( + xAxisModel.componentIndex, yAxisModel.componentIndex + ); + }); + + return grids; +}; + +// For deciding which dimensions to use when creating list data +Grid.dimensions = Grid.prototype.dimensions = Cartesian2D.prototype.dimensions; + +CoordinateSystemManager.register('cartesian2d', Grid); + +/* +* 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. +*/ + +var PI$2 = Math.PI; + +/** + * A final axis is translated and rotated from a "standard axis". + * So opt.position and opt.rotation is required. + * + * A standard axis is and axis from [0, 0] to [0, axisExtent[1]], + * for example: (0, 0) ------------> (0, 50) + * + * nameDirection or tickDirection or labelDirection is 1 means tick + * or label is below the standard axis, whereas is -1 means above + * the standard axis. labelOffset means offset between label and axis, + * which is useful when 'onZero', where axisLabel is in the grid and + * label in outside grid. + * + * Tips: like always, + * positive rotation represents anticlockwise, and negative rotation + * represents clockwise. + * The direction of position coordinate is the same as the direction + * of screen coordinate. + * + * Do not need to consider axis 'inverse', which is auto processed by + * axis extent. + * + * @param {module:zrender/container/Group} group + * @param {Object} axisModel + * @param {Object} opt Standard axis parameters. + * @param {Array.} opt.position [x, y] + * @param {number} opt.rotation by radian + * @param {number} [opt.nameDirection=1] 1 or -1 Used when nameLocation is 'middle' or 'center'. + * @param {number} [opt.tickDirection=1] 1 or -1 + * @param {number} [opt.labelDirection=1] 1 or -1 + * @param {number} [opt.labelOffset=0] Usefull when onZero. + * @param {string} [opt.axisLabelShow] default get from axisModel. + * @param {string} [opt.axisName] default get from axisModel. + * @param {number} [opt.axisNameAvailableWidth] + * @param {number} [opt.labelRotate] by degree, default get from axisModel. + * @param {number} [opt.strokeContainThreshold] Default label interval when label + * @param {number} [opt.nameTruncateMaxWidth] + */ +var AxisBuilder = function (axisModel, opt) { + + /** + * @readOnly + */ + this.opt = opt; + + /** + * @readOnly + */ + this.axisModel = axisModel; + + // Default value + defaults( + opt, + { + labelOffset: 0, + nameDirection: 1, + tickDirection: 1, + labelDirection: 1, + silent: true + } + ); + + /** + * @readOnly + */ + this.group = new Group(); + + // FIXME Not use a seperate text group? + var dumbGroup = new Group({ + position: opt.position.slice(), + rotation: opt.rotation + }); + + // this.group.add(dumbGroup); + // this._dumbGroup = dumbGroup; + + dumbGroup.updateTransform(); + this._transform = dumbGroup.transform; + + this._dumbGroup = dumbGroup; +}; + +AxisBuilder.prototype = { + + constructor: AxisBuilder, + + hasBuilder: function (name) { + return !!builders[name]; + }, + + add: function (name) { + builders[name].call(this); + }, + + getGroup: function () { + return this.group; + } + +}; + +var builders = { + + /** + * @private + */ + axisLine: function () { + var opt = this.opt; + var axisModel = this.axisModel; + + if (!axisModel.get('axisLine.show')) { + return; + } + + var extent = this.axisModel.axis.getExtent(); + + var matrix = this._transform; + var pt1 = [extent[0], 0]; + var pt2 = [extent[1], 0]; + if (matrix) { + applyTransform(pt1, pt1, matrix); + applyTransform(pt2, pt2, matrix); + } + + var lineStyle = extend( + { + lineCap: 'round' + }, + axisModel.getModel('axisLine.lineStyle').getLineStyle() + ); + + this.group.add(new Line({ + // Id for animation + anid: 'line', + subPixelOptimize: true, + shape: { + x1: pt1[0], + y1: pt1[1], + x2: pt2[0], + y2: pt2[1] + }, + style: lineStyle, + strokeContainThreshold: opt.strokeContainThreshold || 5, + silent: true, + z2: 1 + })); + + var arrows = axisModel.get('axisLine.symbol'); + var arrowSize = axisModel.get('axisLine.symbolSize'); + + var arrowOffset = axisModel.get('axisLine.symbolOffset') || 0; + if (typeof arrowOffset === 'number') { + arrowOffset = [arrowOffset, arrowOffset]; + } + + if (arrows != null) { + if (typeof arrows === 'string') { + // Use the same arrow for start and end point + arrows = [arrows, arrows]; + } + if (typeof arrowSize === 'string' + || typeof arrowSize === 'number' + ) { + // Use the same size for width and height + arrowSize = [arrowSize, arrowSize]; + } + + var symbolWidth = arrowSize[0]; + var symbolHeight = arrowSize[1]; + + each$1([{ + rotate: opt.rotation + Math.PI / 2, + offset: arrowOffset[0], + r: 0 + }, { + rotate: opt.rotation - Math.PI / 2, + offset: arrowOffset[1], + r: Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) + }], function (point, index) { + if (arrows[index] !== 'none' && arrows[index] != null) { + var symbol = createSymbol( + arrows[index], + -symbolWidth / 2, + -symbolHeight / 2, + symbolWidth, + symbolHeight, + lineStyle.stroke, + true + ); + + // Calculate arrow position with offset + var r = point.r + point.offset; + var pos = [ + pt1[0] + r * Math.cos(opt.rotation), + pt1[1] - r * Math.sin(opt.rotation) + ]; + + symbol.attr({ + rotation: point.rotate, + position: pos, + silent: true, + z2: 11 + }); + this.group.add(symbol); + } + }, this); + } + }, + + /** + * @private + */ + axisTickLabel: function () { + var axisModel = this.axisModel; + var opt = this.opt; + + var ticksEls = buildAxisMajorTicks(this, axisModel, opt); + var labelEls = buildAxisLabel(this, axisModel, opt); + + fixMinMaxLabelShow(axisModel, labelEls, ticksEls); + + buildAxisMinorTicks(this, axisModel, opt); + }, + + /** + * @private + */ + axisName: function () { + var opt = this.opt; + var axisModel = this.axisModel; + var name = retrieve(opt.axisName, axisModel.get('name')); + + if (!name) { + return; + } + + var nameLocation = axisModel.get('nameLocation'); + var nameDirection = opt.nameDirection; + var textStyleModel = axisModel.getModel('nameTextStyle'); + var gap = axisModel.get('nameGap') || 0; + + var extent = this.axisModel.axis.getExtent(); + var gapSignal = extent[0] > extent[1] ? -1 : 1; + var pos = [ + nameLocation === 'start' + ? extent[0] - gapSignal * gap + : nameLocation === 'end' + ? extent[1] + gapSignal * gap + : (extent[0] + extent[1]) / 2, // 'middle' + // Reuse labelOffset. + isNameLocationCenter(nameLocation) ? opt.labelOffset + nameDirection * gap : 0 + ]; + + var labelLayout; + + var nameRotation = axisModel.get('nameRotate'); + if (nameRotation != null) { + nameRotation = nameRotation * PI$2 / 180; // To radian. + } + + var axisNameAvailableWidth; + + if (isNameLocationCenter(nameLocation)) { + labelLayout = innerTextLayout( + opt.rotation, + nameRotation != null ? nameRotation : opt.rotation, // Adapt to axis. + nameDirection + ); + } + else { + labelLayout = endTextLayout( + opt, nameLocation, nameRotation || 0, extent + ); + + axisNameAvailableWidth = opt.axisNameAvailableWidth; + if (axisNameAvailableWidth != null) { + axisNameAvailableWidth = Math.abs( + axisNameAvailableWidth / Math.sin(labelLayout.rotation) + ); + !isFinite(axisNameAvailableWidth) && (axisNameAvailableWidth = null); + } + } + + var textFont = textStyleModel.getFont(); + + var truncateOpt = axisModel.get('nameTruncate', true) || {}; + var ellipsis = truncateOpt.ellipsis; + var maxWidth = retrieve( + opt.nameTruncateMaxWidth, truncateOpt.maxWidth, axisNameAvailableWidth + ); + // FIXME + // truncate rich text? (consider performance) + var truncatedText = (ellipsis != null && maxWidth != null) + ? truncateText$1( + name, maxWidth, textFont, ellipsis, + {minChar: 2, placeholder: truncateOpt.placeholder} + ) + : name; + + var tooltipOpt = axisModel.get('tooltip', true); + + var mainType = axisModel.mainType; + var formatterParams = { + componentType: mainType, + name: name, + $vars: ['name'] + }; + formatterParams[mainType + 'Index'] = axisModel.componentIndex; + + var textEl = new Text({ + // Id for animation + anid: 'name', + + __fullText: name, + __truncatedText: truncatedText, + + position: pos, + rotation: labelLayout.rotation, + silent: isLabelSilent(axisModel), + z2: 1, + tooltip: (tooltipOpt && tooltipOpt.show) + ? extend({ + content: name, + formatter: function () { + return name; + }, + formatterParams: formatterParams + }, tooltipOpt) + : null + }); + + setTextStyle(textEl.style, textStyleModel, { + text: truncatedText, + textFont: textFont, + textFill: textStyleModel.getTextColor() + || axisModel.get('axisLine.lineStyle.color'), + textAlign: textStyleModel.get('align') + || labelLayout.textAlign, + textVerticalAlign: textStyleModel.get('verticalAlign') + || labelLayout.textVerticalAlign + }); + + if (axisModel.get('triggerEvent')) { + textEl.eventData = makeAxisEventDataBase(axisModel); + textEl.eventData.targetType = 'axisName'; + textEl.eventData.name = name; + } + + // FIXME + this._dumbGroup.add(textEl); + textEl.updateTransform(); + + this.group.add(textEl); + + textEl.decomposeTransform(); + } + +}; + +var makeAxisEventDataBase = AxisBuilder.makeAxisEventDataBase = function (axisModel) { + var eventData = { + componentType: axisModel.mainType, + componentIndex: axisModel.componentIndex + }; + eventData[axisModel.mainType + 'Index'] = axisModel.componentIndex; + return eventData; +}; + +/** + * @public + * @static + * @param {Object} opt + * @param {number} axisRotation in radian + * @param {number} textRotation in radian + * @param {number} direction + * @return {Object} { + * rotation, // according to axis + * textAlign, + * textVerticalAlign + * } + */ +var innerTextLayout = AxisBuilder.innerTextLayout = function (axisRotation, textRotation, direction) { + var rotationDiff = remRadian(textRotation - axisRotation); + var textAlign; + var textVerticalAlign; + + if (isRadianAroundZero(rotationDiff)) { // Label is parallel with axis line. + textVerticalAlign = direction > 0 ? 'top' : 'bottom'; + textAlign = 'center'; + } + else if (isRadianAroundZero(rotationDiff - PI$2)) { // Label is inverse parallel with axis line. + textVerticalAlign = direction > 0 ? 'bottom' : 'top'; + textAlign = 'center'; + } + else { + textVerticalAlign = 'middle'; + + if (rotationDiff > 0 && rotationDiff < PI$2) { + textAlign = direction > 0 ? 'right' : 'left'; + } + else { + textAlign = direction > 0 ? 'left' : 'right'; + } + } + + return { + rotation: rotationDiff, + textAlign: textAlign, + textVerticalAlign: textVerticalAlign + }; +}; + +function endTextLayout(opt, textPosition, textRotate, extent) { + var rotationDiff = remRadian(textRotate - opt.rotation); + var textAlign; + var textVerticalAlign; + var inverse = extent[0] > extent[1]; + var onLeft = (textPosition === 'start' && !inverse) + || (textPosition !== 'start' && inverse); + + if (isRadianAroundZero(rotationDiff - PI$2 / 2)) { + textVerticalAlign = onLeft ? 'bottom' : 'top'; + textAlign = 'center'; + } + else if (isRadianAroundZero(rotationDiff - PI$2 * 1.5)) { + textVerticalAlign = onLeft ? 'top' : 'bottom'; + textAlign = 'center'; + } + else { + textVerticalAlign = 'middle'; + if (rotationDiff < PI$2 * 1.5 && rotationDiff > PI$2 / 2) { + textAlign = onLeft ? 'left' : 'right'; + } + else { + textAlign = onLeft ? 'right' : 'left'; + } + } + + return { + rotation: rotationDiff, + textAlign: textAlign, + textVerticalAlign: textVerticalAlign + }; +} + +var isLabelSilent = AxisBuilder.isLabelSilent = function (axisModel) { + var tooltipOpt = axisModel.get('tooltip'); + return axisModel.get('silent') + // Consider mouse cursor, add these restrictions. + || !( + axisModel.get('triggerEvent') || (tooltipOpt && tooltipOpt.show) + ); +}; + +function fixMinMaxLabelShow(axisModel, labelEls, tickEls) { + if (shouldShowAllLabels(axisModel.axis)) { + return; + } + + // If min or max are user set, we need to check + // If the tick on min(max) are overlap on their neighbour tick + // If they are overlapped, we need to hide the min(max) tick label + var showMinLabel = axisModel.get('axisLabel.showMinLabel'); + var showMaxLabel = axisModel.get('axisLabel.showMaxLabel'); + + // FIXME + // Have not consider onBand yet, where tick els is more than label els. + + labelEls = labelEls || []; + tickEls = tickEls || []; + + var firstLabel = labelEls[0]; + var nextLabel = labelEls[1]; + var lastLabel = labelEls[labelEls.length - 1]; + var prevLabel = labelEls[labelEls.length - 2]; + + var firstTick = tickEls[0]; + var nextTick = tickEls[1]; + var lastTick = tickEls[tickEls.length - 1]; + var prevTick = tickEls[tickEls.length - 2]; + + if (showMinLabel === false) { + ignoreEl(firstLabel); + ignoreEl(firstTick); + } + else if (isTwoLabelOverlapped(firstLabel, nextLabel)) { + if (showMinLabel) { + ignoreEl(nextLabel); + ignoreEl(nextTick); + } + else { + ignoreEl(firstLabel); + ignoreEl(firstTick); + } + } + + if (showMaxLabel === false) { + ignoreEl(lastLabel); + ignoreEl(lastTick); + } + else if (isTwoLabelOverlapped(prevLabel, lastLabel)) { + if (showMaxLabel) { + ignoreEl(prevLabel); + ignoreEl(prevTick); + } + else { + ignoreEl(lastLabel); + ignoreEl(lastTick); + } + } +} + +function ignoreEl(el) { + el && (el.ignore = true); +} + +function isTwoLabelOverlapped(current, next, labelLayout) { + // current and next has the same rotation. + var firstRect = current && current.getBoundingRect().clone(); + var nextRect = next && next.getBoundingRect().clone(); + + if (!firstRect || !nextRect) { + return; + } + + // When checking intersect of two rotated labels, we use mRotationBack + // to avoid that boundingRect is enlarge when using `boundingRect.applyTransform`. + var mRotationBack = identity([]); + rotate(mRotationBack, mRotationBack, -current.rotation); + + firstRect.applyTransform(mul$1([], mRotationBack, current.getLocalTransform())); + nextRect.applyTransform(mul$1([], mRotationBack, next.getLocalTransform())); + + return firstRect.intersect(nextRect); +} + +function isNameLocationCenter(nameLocation) { + return nameLocation === 'middle' || nameLocation === 'center'; +} + + +function createTicks(ticksCoords, tickTransform, tickEndCoord, tickLineStyle, aniid) { + var tickEls = []; + var pt1 = []; + var pt2 = []; + for (var i = 0; i < ticksCoords.length; i++) { + var tickCoord = ticksCoords[i].coord; + + pt1[0] = tickCoord; + pt1[1] = 0; + pt2[0] = tickCoord; + pt2[1] = tickEndCoord; + + if (tickTransform) { + applyTransform(pt1, pt1, tickTransform); + applyTransform(pt2, pt2, tickTransform); + } + // Tick line, Not use group transform to have better line draw + var tickEl = new Line({ + // Id for animation + anid: aniid + '_' + ticksCoords[i].tickValue, + subPixelOptimize: true, + shape: { + x1: pt1[0], + y1: pt1[1], + x2: pt2[0], + y2: pt2[1] + }, + style: tickLineStyle, + z2: 2, + silent: true + }); + tickEls.push(tickEl); + } + return tickEls; +} + +function buildAxisMajorTicks(axisBuilder, axisModel, opt) { + var axis = axisModel.axis; + + var tickModel = axisModel.getModel('axisTick'); + + if (!tickModel.get('show') || axis.scale.isBlank()) { + return; + } + + var lineStyleModel = tickModel.getModel('lineStyle'); + var tickEndCoord = opt.tickDirection * tickModel.get('length'); + + var ticksCoords = axis.getTicksCoords(); + + var ticksEls = createTicks(ticksCoords, axisBuilder._transform, tickEndCoord, defaults( + lineStyleModel.getLineStyle(), + { + stroke: axisModel.get('axisLine.lineStyle.color') + } + ), 'ticks'); + + for (var i = 0; i < ticksEls.length; i++) { + axisBuilder.group.add(ticksEls[i]); + } + + return ticksEls; +} + +function buildAxisMinorTicks(axisBuilder, axisModel, opt) { + var axis = axisModel.axis; + + var minorTickModel = axisModel.getModel('minorTick'); + + if (!minorTickModel.get('show') || axis.scale.isBlank()) { + return; + } + + var minorTicksCoords = axis.getMinorTicksCoords(); + if (!minorTicksCoords.length) { + return; + } + + var lineStyleModel = minorTickModel.getModel('lineStyle'); + var tickEndCoord = opt.tickDirection * minorTickModel.get('length'); + + var minorTickLineStyle = defaults( + lineStyleModel.getLineStyle(), + defaults( + axisModel.getModel('axisTick').getLineStyle(), + { + stroke: axisModel.get('axisLine.lineStyle.color') + } + ) + ); + + for (var i = 0; i < minorTicksCoords.length; i++) { + var minorTicksEls = createTicks( + minorTicksCoords[i], axisBuilder._transform, tickEndCoord, minorTickLineStyle, 'minorticks_' + i + ); + for (var k = 0; k < minorTicksEls.length; k++) { + axisBuilder.group.add(minorTicksEls[k]); + } + } +} + +function buildAxisLabel(axisBuilder, axisModel, opt) { + var axis = axisModel.axis; + var show = retrieve(opt.axisLabelShow, axisModel.get('axisLabel.show')); + + if (!show || axis.scale.isBlank()) { + return; + } + + var labelModel = axisModel.getModel('axisLabel'); + var labelMargin = labelModel.get('margin'); + var labels = axis.getViewLabels(); + + // Special label rotate. + var labelRotation = ( + retrieve(opt.labelRotate, labelModel.get('rotate')) || 0 + ) * PI$2 / 180; + + var labelLayout = innerTextLayout(opt.rotation, labelRotation, opt.labelDirection); + var rawCategoryData = axisModel.getCategories && axisModel.getCategories(true); + + var labelEls = []; + var silent = isLabelSilent(axisModel); + var triggerEvent = axisModel.get('triggerEvent'); + + each$1(labels, function (labelItem, index) { + var tickValue = labelItem.tickValue; + var formattedLabel = labelItem.formattedLabel; + var rawLabel = labelItem.rawLabel; + + var itemLabelModel = labelModel; + if (rawCategoryData && rawCategoryData[tickValue] && rawCategoryData[tickValue].textStyle) { + itemLabelModel = new Model( + rawCategoryData[tickValue].textStyle, labelModel, axisModel.ecModel + ); + } + + var textColor = itemLabelModel.getTextColor() + || axisModel.get('axisLine.lineStyle.color'); + + var tickCoord = axis.dataToCoord(tickValue); + var pos = [ + tickCoord, + opt.labelOffset + opt.labelDirection * labelMargin + ]; + + var textEl = new Text({ + // Id for animation + anid: 'label_' + tickValue, + position: pos, + rotation: labelLayout.rotation, + silent: silent, + z2: 10 + }); + + setTextStyle(textEl.style, itemLabelModel, { + text: formattedLabel, + textAlign: itemLabelModel.getShallow('align', true) + || labelLayout.textAlign, + textVerticalAlign: itemLabelModel.getShallow('verticalAlign', true) + || itemLabelModel.getShallow('baseline', true) + || labelLayout.textVerticalAlign, + textFill: typeof textColor === 'function' + ? textColor( + // (1) In category axis with data zoom, tick is not the original + // index of axis.data. So tick should not be exposed to user + // in category axis. + // (2) Compatible with previous version, which always use formatted label as + // input. But in interval scale the formatted label is like '223,445', which + // maked user repalce ','. So we modify it to return original val but remain + // it as 'string' to avoid error in replacing. + axis.type === 'category' + ? rawLabel + : axis.type === 'value' + ? tickValue + '' + : tickValue, + index + ) + : textColor + }); + + // Pack data for mouse event + if (triggerEvent) { + textEl.eventData = makeAxisEventDataBase(axisModel); + textEl.eventData.targetType = 'axisLabel'; + textEl.eventData.value = rawLabel; + } + + // FIXME + axisBuilder._dumbGroup.add(textEl); + textEl.updateTransform(); + + labelEls.push(textEl); + axisBuilder.group.add(textEl); + + textEl.decomposeTransform(); + + }); + + return labelEls; +} + +/* +* 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. +*/ + +var each$6 = each$1; +var curry$1 = curry; + +// Build axisPointerModel, mergin tooltip.axisPointer model for each axis. +// allAxesInfo should be updated when setOption performed. +function collect(ecModel, api) { + var result = { + /** + * key: makeKey(axis.model) + * value: { + * axis, + * coordSys, + * axisPointerModel, + * triggerTooltip, + * involveSeries, + * snap, + * seriesModels, + * seriesDataCount + * } + */ + axesInfo: {}, + seriesInvolved: false, + /** + * key: makeKey(coordSys.model) + * value: Object: key makeKey(axis.model), value: axisInfo + */ + coordSysAxesInfo: {}, + coordSysMap: {} + }; + + collectAxesInfo(result, ecModel, api); + + // Check seriesInvolved for performance, in case too many series in some chart. + result.seriesInvolved && collectSeriesInfo(result, ecModel); + + return result; +} + +function collectAxesInfo(result, ecModel, api) { + var globalTooltipModel = ecModel.getComponent('tooltip'); + var globalAxisPointerModel = ecModel.getComponent('axisPointer'); + // links can only be set on global. + var linksOption = globalAxisPointerModel.get('link', true) || []; + var linkGroups = []; + + // Collect axes info. + each$6(api.getCoordinateSystems(), function (coordSys) { + // Some coordinate system do not support axes, like geo. + if (!coordSys.axisPointerEnabled) { + return; + } + + var coordSysKey = makeKey(coordSys.model); + var axesInfoInCoordSys = result.coordSysAxesInfo[coordSysKey] = {}; + result.coordSysMap[coordSysKey] = coordSys; + + // Set tooltip (like 'cross') is a convienent way to show axisPointer + // for user. So we enable seting tooltip on coordSys model. + var coordSysModel = coordSys.model; + var baseTooltipModel = coordSysModel.getModel('tooltip', globalTooltipModel); + + each$6(coordSys.getAxes(), curry$1(saveTooltipAxisInfo, false, null)); + + // If axis tooltip used, choose tooltip axis for each coordSys. + // Notice this case: coordSys is `grid` but not `cartesian2D` here. + if (coordSys.getTooltipAxes + && globalTooltipModel + // If tooltip.showContent is set as false, tooltip will not + // show but axisPointer will show as normal. + && baseTooltipModel.get('show') + ) { + // Compatible with previous logic. But series.tooltip.trigger: 'axis' + // or series.data[n].tooltip.trigger: 'axis' are not support any more. + var triggerAxis = baseTooltipModel.get('trigger') === 'axis'; + var cross = baseTooltipModel.get('axisPointer.type') === 'cross'; + var tooltipAxes = coordSys.getTooltipAxes(baseTooltipModel.get('axisPointer.axis')); + if (triggerAxis || cross) { + each$6(tooltipAxes.baseAxes, curry$1( + saveTooltipAxisInfo, cross ? 'cross' : true, triggerAxis + )); + } + if (cross) { + each$6(tooltipAxes.otherAxes, curry$1(saveTooltipAxisInfo, 'cross', false)); + } + } + + // fromTooltip: true | false | 'cross' + // triggerTooltip: true | false | null + function saveTooltipAxisInfo(fromTooltip, triggerTooltip, axis) { + var axisPointerModel = axis.model.getModel('axisPointer', globalAxisPointerModel); + + var axisPointerShow = axisPointerModel.get('show'); + if (!axisPointerShow || ( + axisPointerShow === 'auto' + && !fromTooltip + && !isHandleTrigger(axisPointerModel) + )) { + return; + } + + if (triggerTooltip == null) { + triggerTooltip = axisPointerModel.get('triggerTooltip'); + } + + axisPointerModel = fromTooltip + ? makeAxisPointerModel( + axis, baseTooltipModel, globalAxisPointerModel, ecModel, + fromTooltip, triggerTooltip + ) + : axisPointerModel; + + var snap = axisPointerModel.get('snap'); + var key = makeKey(axis.model); + var involveSeries = triggerTooltip || snap || axis.type === 'category'; + + // If result.axesInfo[key] exist, override it (tooltip has higher priority). + var axisInfo = result.axesInfo[key] = { + key: key, + axis: axis, + coordSys: coordSys, + axisPointerModel: axisPointerModel, + triggerTooltip: triggerTooltip, + involveSeries: involveSeries, + snap: snap, + useHandle: isHandleTrigger(axisPointerModel), + seriesModels: [] + }; + axesInfoInCoordSys[key] = axisInfo; + result.seriesInvolved |= involveSeries; + + var groupIndex = getLinkGroupIndex(linksOption, axis); + if (groupIndex != null) { + var linkGroup = linkGroups[groupIndex] || (linkGroups[groupIndex] = {axesInfo: {}}); + linkGroup.axesInfo[key] = axisInfo; + linkGroup.mapper = linksOption[groupIndex].mapper; + axisInfo.linkGroup = linkGroup; + } + } + }); +} + +function makeAxisPointerModel( + axis, baseTooltipModel, globalAxisPointerModel, ecModel, fromTooltip, triggerTooltip +) { + var tooltipAxisPointerModel = baseTooltipModel.getModel('axisPointer'); + var volatileOption = {}; + + each$6( + [ + 'type', 'snap', 'lineStyle', 'shadowStyle', 'label', + 'animation', 'animationDurationUpdate', 'animationEasingUpdate', 'z' + ], + function (field) { + volatileOption[field] = clone(tooltipAxisPointerModel.get(field)); + } + ); + + // category axis do not auto snap, otherwise some tick that do not + // has value can not be hovered. value/time/log axis default snap if + // triggered from tooltip and trigger tooltip. + volatileOption.snap = axis.type !== 'category' && !!triggerTooltip; + + // Compatibel with previous behavior, tooltip axis do not show label by default. + // Only these properties can be overrided from tooltip to axisPointer. + if (tooltipAxisPointerModel.get('type') === 'cross') { + volatileOption.type = 'line'; + } + var labelOption = volatileOption.label || (volatileOption.label = {}); + // Follow the convention, do not show label when triggered by tooltip by default. + labelOption.show == null && (labelOption.show = false); + + if (fromTooltip === 'cross') { + // When 'cross', both axes show labels. + var tooltipAxisPointerLabelShow = tooltipAxisPointerModel.get('label.show'); + labelOption.show = tooltipAxisPointerLabelShow != null ? tooltipAxisPointerLabelShow : true; + // If triggerTooltip, this is a base axis, which should better not use cross style + // (cross style is dashed by default) + if (!triggerTooltip) { + var crossStyle = volatileOption.lineStyle = tooltipAxisPointerModel.get('crossStyle'); + crossStyle && defaults(labelOption, crossStyle.textStyle); + } + } + + return axis.model.getModel( + 'axisPointer', + new Model(volatileOption, globalAxisPointerModel, ecModel) + ); +} + +function collectSeriesInfo(result, ecModel) { + // Prepare data for axis trigger + ecModel.eachSeries(function (seriesModel) { + + // Notice this case: this coordSys is `cartesian2D` but not `grid`. + var coordSys = seriesModel.coordinateSystem; + var seriesTooltipTrigger = seriesModel.get('tooltip.trigger', true); + var seriesTooltipShow = seriesModel.get('tooltip.show', true); + if (!coordSys + || seriesTooltipTrigger === 'none' + || seriesTooltipTrigger === false + || seriesTooltipTrigger === 'item' + || seriesTooltipShow === false + || seriesModel.get('axisPointer.show', true) === false + ) { + return; + } + + each$6(result.coordSysAxesInfo[makeKey(coordSys.model)], function (axisInfo) { + var axis = axisInfo.axis; + if (coordSys.getAxis(axis.dim) === axis) { + axisInfo.seriesModels.push(seriesModel); + axisInfo.seriesDataCount == null && (axisInfo.seriesDataCount = 0); + axisInfo.seriesDataCount += seriesModel.getData().count(); + } + }); + + }, this); +} + +/** + * For example: + * { + * axisPointer: { + * links: [{ + * xAxisIndex: [2, 4], + * yAxisIndex: 'all' + * }, { + * xAxisId: ['a5', 'a7'], + * xAxisName: 'xxx' + * }] + * } + * } + */ +function getLinkGroupIndex(linksOption, axis) { + var axisModel = axis.model; + var dim = axis.dim; + for (var i = 0; i < linksOption.length; i++) { + var linkOption = linksOption[i] || {}; + if (checkPropInLink(linkOption[dim + 'AxisId'], axisModel.id) + || checkPropInLink(linkOption[dim + 'AxisIndex'], axisModel.componentIndex) + || checkPropInLink(linkOption[dim + 'AxisName'], axisModel.name) + ) { + return i; + } + } +} + +function checkPropInLink(linkPropValue, axisPropValue) { + return linkPropValue === 'all' + || (isArray(linkPropValue) && indexOf(linkPropValue, axisPropValue) >= 0) + || linkPropValue === axisPropValue; +} + +function fixValue(axisModel) { + var axisInfo = getAxisInfo(axisModel); + if (!axisInfo) { + return; + } + + var axisPointerModel = axisInfo.axisPointerModel; + var scale = axisInfo.axis.scale; + var option = axisPointerModel.option; + var status = axisPointerModel.get('status'); + var value = axisPointerModel.get('value'); + + // Parse init value for category and time axis. + if (value != null) { + value = scale.parse(value); + } + + var useHandle = isHandleTrigger(axisPointerModel); + // If `handle` used, `axisPointer` will always be displayed, so value + // and status should be initialized. + if (status == null) { + option.status = useHandle ? 'show' : 'hide'; + } + + var extent = scale.getExtent().slice(); + extent[0] > extent[1] && extent.reverse(); + + if (// Pick a value on axis when initializing. + value == null + // If both `handle` and `dataZoom` are used, value may be out of axis extent, + // where we should re-pick a value to keep `handle` displaying normally. + || value > extent[1] + ) { + // Make handle displayed on the end of the axis when init, which looks better. + value = extent[1]; + } + if (value < extent[0]) { + value = extent[0]; + } + + option.value = value; + + if (useHandle) { + option.status = axisInfo.axis.scale.isBlank() ? 'hide' : 'show'; + } +} + +function getAxisInfo(axisModel) { + var coordSysAxesInfo = (axisModel.ecModel.getComponent('axisPointer') || {}).coordSysAxesInfo; + return coordSysAxesInfo && coordSysAxesInfo.axesInfo[makeKey(axisModel)]; +} + +function getAxisPointerModel(axisModel) { + var axisInfo = getAxisInfo(axisModel); + return axisInfo && axisInfo.axisPointerModel; +} + +function isHandleTrigger(axisPointerModel) { + return !!axisPointerModel.get('handle.show'); +} + +/** + * @param {module:echarts/model/Model} model + * @return {string} unique key + */ +function makeKey(model) { + return model.type + '||' + model.id; +} + +/* +* 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. +*/ + +/** + * Base class of AxisView. + */ +var AxisView = extendComponentView({ + + type: 'axis', + + /** + * @private + */ + _axisPointer: null, + + /** + * @protected + * @type {string} + */ + axisPointerClass: null, + + /** + * @override + */ + render: function (axisModel, ecModel, api, payload) { + // FIXME + // This process should proformed after coordinate systems updated + // (axis scale updated), and should be performed each time update. + // So put it here temporarily, although it is not appropriate to + // put a model-writing procedure in `view`. + this.axisPointerClass && fixValue(axisModel); + + AxisView.superApply(this, 'render', arguments); + + updateAxisPointer(this, axisModel, ecModel, api, payload, true); + }, + + /** + * Action handler. + * @public + * @param {module:echarts/coord/cartesian/AxisModel} axisModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @param {Object} payload + */ + updateAxisPointer: function (axisModel, ecModel, api, payload, force) { + updateAxisPointer(this, axisModel, ecModel, api, payload, false); + }, + + /** + * @override + */ + remove: function (ecModel, api) { + var axisPointer = this._axisPointer; + axisPointer && axisPointer.remove(api); + AxisView.superApply(this, 'remove', arguments); + }, + + /** + * @override + */ + dispose: function (ecModel, api) { + disposeAxisPointer(this, api); + AxisView.superApply(this, 'dispose', arguments); + } + +}); + +function updateAxisPointer(axisView, axisModel, ecModel, api, payload, forceRender) { + var Clazz = AxisView.getAxisPointerClass(axisView.axisPointerClass); + if (!Clazz) { + return; + } + var axisPointerModel = getAxisPointerModel(axisModel); + axisPointerModel + ? (axisView._axisPointer || (axisView._axisPointer = new Clazz())) + .render(axisModel, axisPointerModel, api, forceRender) + : disposeAxisPointer(axisView, api); +} + +function disposeAxisPointer(axisView, ecModel, api) { + var axisPointer = axisView._axisPointer; + axisPointer && axisPointer.dispose(ecModel, api); + axisView._axisPointer = null; +} + +var axisPointerClazz = []; + +AxisView.registerAxisPointerClass = function (type, clazz) { + if (__DEV__) { + if (axisPointerClazz[type]) { + throw new Error('axisPointer ' + type + ' exists'); + } + } + axisPointerClazz[type] = clazz; +}; + +AxisView.getAxisPointerClass = function (type) { + return type && axisPointerClazz[type]; +}; + +/* +* 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. +*/ + +/** + * Can only be called after coordinate system creation stage. + * (Can be called before coordinate system update stage). + * + * @param {Object} opt {labelInside} + * @return {Object} { + * position, rotation, labelDirection, labelOffset, + * tickDirection, labelRotate, z2 + * } + */ +function layout$1(gridModel, axisModel, opt) { + opt = opt || {}; + var grid = gridModel.coordinateSystem; + var axis = axisModel.axis; + var layout = {}; + var otherAxisOnZeroOf = axis.getAxesOnZeroOf()[0]; + + var rawAxisPosition = axis.position; + var axisPosition = otherAxisOnZeroOf ? 'onZero' : rawAxisPosition; + var axisDim = axis.dim; + + var rect = grid.getRect(); + var rectBound = [rect.x, rect.x + rect.width, rect.y, rect.y + rect.height]; + var idx = {left: 0, right: 1, top: 0, bottom: 1, onZero: 2}; + var axisOffset = axisModel.get('offset') || 0; + + var posBound = axisDim === 'x' + ? [rectBound[2] - axisOffset, rectBound[3] + axisOffset] + : [rectBound[0] - axisOffset, rectBound[1] + axisOffset]; + + if (otherAxisOnZeroOf) { + var onZeroCoord = otherAxisOnZeroOf.toGlobalCoord(otherAxisOnZeroOf.dataToCoord(0)); + posBound[idx.onZero] = Math.max(Math.min(onZeroCoord, posBound[1]), posBound[0]); + } + + // Axis position + layout.position = [ + axisDim === 'y' ? posBound[idx[axisPosition]] : rectBound[0], + axisDim === 'x' ? posBound[idx[axisPosition]] : rectBound[3] + ]; + + // Axis rotation + layout.rotation = Math.PI / 2 * (axisDim === 'x' ? 0 : 1); + + // Tick and label direction, x y is axisDim + var dirMap = {top: -1, bottom: 1, left: -1, right: 1}; + + layout.labelDirection = layout.tickDirection = layout.nameDirection = dirMap[rawAxisPosition]; + layout.labelOffset = otherAxisOnZeroOf ? posBound[idx[rawAxisPosition]] - posBound[idx.onZero] : 0; + + if (axisModel.get('axisTick.inside')) { + layout.tickDirection = -layout.tickDirection; + } + if (retrieve(opt.labelInside, axisModel.get('axisLabel.inside'))) { + layout.labelDirection = -layout.labelDirection; + } + + // Special label rotation + var labelRotate = axisModel.get('axisLabel.rotate'); + layout.labelRotate = axisPosition === 'top' ? -labelRotate : labelRotate; + + // Over splitLine and splitArea + layout.z2 = 1; + + return layout; +} + +/* +* 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. +*/ + +var axisBuilderAttrs = [ + 'axisLine', 'axisTickLabel', 'axisName' +]; +var selfBuilderAttrs = [ + 'splitArea', 'splitLine', 'minorSplitLine' +]; + +var CartesianAxisView = AxisView.extend({ + + type: 'cartesianAxis', + + axisPointerClass: 'CartesianAxisPointer', + + /** + * @override + */ + render: function (axisModel, ecModel, api, payload) { + + this.group.removeAll(); + + var oldAxisGroup = this._axisGroup; + this._axisGroup = new Group(); + + this.group.add(this._axisGroup); + + if (!axisModel.get('show')) { + return; + } + + var gridModel = axisModel.getCoordSysModel(); + + var layout = layout$1(gridModel, axisModel); + + var axisBuilder = new AxisBuilder(axisModel, layout); + + each$1(axisBuilderAttrs, axisBuilder.add, axisBuilder); + + this._axisGroup.add(axisBuilder.getGroup()); + + each$1(selfBuilderAttrs, function (name) { + if (axisModel.get(name + '.show')) { + this['_' + name](axisModel, gridModel); + } + }, this); + + groupTransition(oldAxisGroup, this._axisGroup, axisModel); + + CartesianAxisView.superCall(this, 'render', axisModel, ecModel, api, payload); + }, + + remove: function () { + this._splitAreaColors = null; + }, + + /** + * @param {module:echarts/coord/cartesian/AxisModel} axisModel + * @param {module:echarts/coord/cartesian/GridModel} gridModel + * @private + */ + _splitLine: function (axisModel, gridModel) { + var axis = axisModel.axis; + + if (axis.scale.isBlank()) { + return; + } + + var splitLineModel = axisModel.getModel('splitLine'); + var lineStyleModel = splitLineModel.getModel('lineStyle'); + var lineColors = lineStyleModel.get('color'); + + lineColors = isArray(lineColors) ? lineColors : [lineColors]; + + var gridRect = gridModel.coordinateSystem.getRect(); + var isHorizontal = axis.isHorizontal(); + + var lineCount = 0; + + var ticksCoords = axis.getTicksCoords({ + tickModel: splitLineModel + }); + + var p1 = []; + var p2 = []; + + var lineStyle = lineStyleModel.getLineStyle(); + for (var i = 0; i < ticksCoords.length; i++) { + var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord); + + if (isHorizontal) { + p1[0] = tickCoord; + p1[1] = gridRect.y; + p2[0] = tickCoord; + p2[1] = gridRect.y + gridRect.height; + } + else { + p1[0] = gridRect.x; + p1[1] = tickCoord; + p2[0] = gridRect.x + gridRect.width; + p2[1] = tickCoord; + } + + var colorIndex = (lineCount++) % lineColors.length; + var tickValue = ticksCoords[i].tickValue; + this._axisGroup.add(new Line({ + anid: tickValue != null ? 'line_' + ticksCoords[i].tickValue : null, + subPixelOptimize: true, + shape: { + x1: p1[0], + y1: p1[1], + x2: p2[0], + y2: p2[1] + }, + style: defaults({ + stroke: lineColors[colorIndex] + }, lineStyle), + silent: true + })); + } + }, + + /** + * @param {module:echarts/coord/cartesian/AxisModel} axisModel + * @param {module:echarts/coord/cartesian/GridModel} gridModel + * @private + */ + _minorSplitLine: function (axisModel, gridModel) { + var axis = axisModel.axis; + + var minorSplitLineModel = axisModel.getModel('minorSplitLine'); + var lineStyleModel = minorSplitLineModel.getModel('lineStyle'); + + var gridRect = gridModel.coordinateSystem.getRect(); + var isHorizontal = axis.isHorizontal(); + + var minorTicksCoords = axis.getMinorTicksCoords(); + if (!minorTicksCoords.length) { + return; + } + var p1 = []; + var p2 = []; + + var lineStyle = lineStyleModel.getLineStyle(); + + + for (var i = 0; i < minorTicksCoords.length; i++) { + for (var k = 0; k < minorTicksCoords[i].length; k++) { + var tickCoord = axis.toGlobalCoord(minorTicksCoords[i][k].coord); + + if (isHorizontal) { + p1[0] = tickCoord; + p1[1] = gridRect.y; + p2[0] = tickCoord; + p2[1] = gridRect.y + gridRect.height; + } + else { + p1[0] = gridRect.x; + p1[1] = tickCoord; + p2[0] = gridRect.x + gridRect.width; + p2[1] = tickCoord; + } + + this._axisGroup.add(new Line({ + anid: 'minor_line_' + minorTicksCoords[i][k].tickValue, + subPixelOptimize: true, + shape: { + x1: p1[0], + y1: p1[1], + x2: p2[0], + y2: p2[1] + }, + style: lineStyle, + silent: true + })); + } + } + }, + + /** + * @param {module:echarts/coord/cartesian/AxisModel} axisModel + * @param {module:echarts/coord/cartesian/GridModel} gridModel + * @private + */ + _splitArea: function (axisModel, gridModel) { + var axis = axisModel.axis; + + if (axis.scale.isBlank()) { + return; + } + + var splitAreaModel = axisModel.getModel('splitArea'); + var areaStyleModel = splitAreaModel.getModel('areaStyle'); + var areaColors = areaStyleModel.get('color'); + + var gridRect = gridModel.coordinateSystem.getRect(); + + var ticksCoords = axis.getTicksCoords({ + tickModel: splitAreaModel, + clamp: true + }); + + if (!ticksCoords.length) { + return; + } + + // For Making appropriate splitArea animation, the color and anid + // should be corresponding to previous one if possible. + var areaColorsLen = areaColors.length; + var lastSplitAreaColors = this._splitAreaColors; + var newSplitAreaColors = createHashMap(); + var colorIndex = 0; + if (lastSplitAreaColors) { + for (var i = 0; i < ticksCoords.length; i++) { + var cIndex = lastSplitAreaColors.get(ticksCoords[i].tickValue); + if (cIndex != null) { + colorIndex = (cIndex + (areaColorsLen - 1) * i) % areaColorsLen; + break; + } + } + } + + var prev = axis.toGlobalCoord(ticksCoords[0].coord); + + var areaStyle = areaStyleModel.getAreaStyle(); + areaColors = isArray(areaColors) ? areaColors : [areaColors]; + + for (var i = 1; i < ticksCoords.length; i++) { + var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord); + + var x; + var y; + var width; + var height; + if (axis.isHorizontal()) { + x = prev; + y = gridRect.y; + width = tickCoord - x; + height = gridRect.height; + prev = x + width; + } + else { + x = gridRect.x; + y = prev; + width = gridRect.width; + height = tickCoord - y; + prev = y + height; + } + + var tickValue = ticksCoords[i - 1].tickValue; + tickValue != null && newSplitAreaColors.set(tickValue, colorIndex); + + this._axisGroup.add(new Rect({ + anid: tickValue != null ? 'area_' + tickValue : null, + shape: { + x: x, + y: y, + width: width, + height: height + }, + style: defaults({ + fill: areaColors[colorIndex] + }, areaStyle), + silent: true + })); + + colorIndex = (colorIndex + 1) % areaColorsLen; + } + + this._splitAreaColors = newSplitAreaColors; + } +}); + +CartesianAxisView.extend({ + type: 'xAxis' +}); +CartesianAxisView.extend({ + type: 'yAxis' +}); + +/* +* 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. +*/ + +/* +* 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. +*/ + +// Grid view +extendComponentView({ + + type: 'grid', + + render: function (gridModel, ecModel) { + this.group.removeAll(); + if (gridModel.get('show')) { + this.group.add(new Rect({ + shape: gridModel.coordinateSystem.getRect(), + style: defaults({ + fill: gridModel.get('backgroundColor') + }, gridModel.getItemStyle()), + silent: true, + z2: -1 + })); + } + } + +}); + +registerPreprocessor(function (option) { + // Only create grid when need + if (option.xAxis && option.yAxis && !option.grid) { + option.grid = {}; + } +}); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// In case developer forget to include grid component +registerVisual(visualSymbol('line', 'circle', 'line')); +registerLayout(pointsLayout('line')); + +// Down sample after filter +registerProcessor( + PRIORITY.PROCESSOR.STATISTIC, + dataSample('line') +); + +/* +* 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. +*/ + +var BaseBarSeries = SeriesModel.extend({ + + type: 'series.__base_bar__', + + getInitialData: function (option, ecModel) { + return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true}); + }, + + getMarkerPosition: function (value) { + var coordSys = this.coordinateSystem; + if (coordSys) { + // PENDING if clamp ? + var pt = coordSys.dataToPoint(coordSys.clampData(value)); + var data = this.getData(); + var offset = data.getLayout('offset'); + var size = data.getLayout('size'); + var offsetIndex = coordSys.getBaseAxis().isHorizontal() ? 0 : 1; + pt[offsetIndex] += offset + size / 2; + return pt; + } + return [NaN, NaN]; + }, + + defaultOption: { + zlevel: 0, // 一级层叠 + z: 2, // 二级层叠 + coordinateSystem: 'cartesian2d', + legendHoverLink: true, + // stack: null + + // Cartesian coordinate system + // xAxisIndex: 0, + // yAxisIndex: 0, + + // 最小高度改为0 + barMinHeight: 0, + // 最小角度为0,仅对极坐标系下的柱状图有效 + barMinAngle: 0, + // cursor: null, + + large: false, + largeThreshold: 400, + progressive: 3e3, + progressiveChunkMode: 'mod', + + // barMaxWidth: null, + + // In cartesian, the default value is 1. Otherwise null. + // barMinWidth: null, + + // 默认自适应 + // barWidth: null, + // 柱间距离,默认为柱形宽度的30%,可设固定值 + // barGap: '30%', + // 类目间柱形距离,默认为类目间距的20%,可设固定值 + // barCategoryGap: '20%', + // label: { + // show: false + // }, + itemStyle: {}, + emphasis: {} + } +}); + +/* +* 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. +*/ + +BaseBarSeries.extend({ + + type: 'series.bar', + + dependencies: ['grid', 'polar'], + + brushSelector: 'rect', + + /** + * @override + */ + getProgressive: function () { + // Do not support progressive in normal mode. + return this.get('large') + ? this.get('progressive') + : false; + }, + + /** + * @override + */ + getProgressiveThreshold: function () { + // Do not support progressive in normal mode. + var progressiveThreshold = this.get('progressiveThreshold'); + var largeThreshold = this.get('largeThreshold'); + if (largeThreshold > progressiveThreshold) { + progressiveThreshold = largeThreshold; + } + return progressiveThreshold; + }, + + defaultOption: { + // If clipped + // Only available on cartesian2d + clip: true, + + // If use caps on two sides of bars + // Only available on tangential polar bar + roundCap: false + } +}); + +/* +* 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. +*/ + +function setLabel( + normalStyle, hoverStyle, itemModel, color, seriesModel, dataIndex, labelPositionOutside +) { + var labelModel = itemModel.getModel('label'); + var hoverLabelModel = itemModel.getModel('emphasis.label'); + + setLabelStyle( + normalStyle, hoverStyle, labelModel, hoverLabelModel, + { + labelFetcher: seriesModel, + labelDataIndex: dataIndex, + defaultText: getDefaultLabel(seriesModel.getData(), dataIndex), + isRectText: true, + autoColor: color + } + ); + + fixPosition(normalStyle); + fixPosition(hoverStyle); +} + +function fixPosition(style, labelPositionOutside) { + if (style.textPosition === 'outside') { + style.textPosition = labelPositionOutside; + } +} + +/* +* 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. +*/ + +var getBarItemStyle = makeStyleMapper( + [ + ['fill', 'color'], + ['stroke', 'borderColor'], + ['lineWidth', 'borderWidth'], + // Compatitable with 2 + ['stroke', 'barBorderColor'], + ['lineWidth', 'barBorderWidth'], + ['opacity'], + ['shadowBlur'], + ['shadowOffsetX'], + ['shadowOffsetY'], + ['shadowColor'] + ] +); + +var barItemStyle = { + getBarItemStyle: function (excludes) { + var style = getBarItemStyle(this, excludes); + if (this.getBorderLineDash) { + var lineDash = this.getBorderLineDash(); + lineDash && (style.lineDash = lineDash); + } + return style; + } +}; + +/* +* 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. +*/ + +/** + * Sausage: similar to sector, but have half circle on both sides + * @public + */ +var Sausage = extendShape({ + + type: 'sausage', + + shape: { + + cx: 0, + + cy: 0, + + r0: 0, + + r: 0, + + startAngle: 0, + + endAngle: Math.PI * 2, + + clockwise: true + }, + + buildPath: function (ctx, shape) { + var x = shape.cx; + var y = shape.cy; + var r0 = Math.max(shape.r0 || 0, 0); + var r = Math.max(shape.r, 0); + var dr = (r - r0) * 0.5; + var rCenter = r0 + dr; + var startAngle = shape.startAngle; + var endAngle = shape.endAngle; + var clockwise = shape.clockwise; + + var unitStartX = Math.cos(startAngle); + var unitStartY = Math.sin(startAngle); + var unitEndX = Math.cos(endAngle); + var unitEndY = Math.sin(endAngle); + + var lessThanCircle = clockwise + ? endAngle - startAngle < Math.PI * 2 + : startAngle - endAngle < Math.PI * 2; + + if (lessThanCircle) { + ctx.moveTo(unitStartX * r0 + x, unitStartY * r0 + y); + + ctx.arc( + unitStartX * rCenter + x, unitStartY * rCenter + y, dr, + -Math.PI + startAngle, startAngle, !clockwise + ); + } + + ctx.arc(x, y, r, startAngle, endAngle, !clockwise); + + ctx.moveTo(unitEndX * r + x, unitEndY * r + y); + + ctx.arc( + unitEndX * rCenter + x, unitEndY * rCenter + y, dr, + endAngle - Math.PI * 2, endAngle - Math.PI, !clockwise + ); + + if (r0 !== 0) { + ctx.arc(x, y, r0, endAngle, startAngle, clockwise); + + ctx.moveTo(unitStartX * r0 + x, unitEndY * r0 + y); + } + + ctx.closePath(); + } +}); + +/* +* 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. +*/ + +var BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'barBorderWidth']; +var _eventPos = [0, 0]; + +// FIXME +// Just for compatible with ec2. +extend(Model.prototype, barItemStyle); + +function getClipArea(coord, data) { + var coordSysClipArea = coord.getArea && coord.getArea(); + if (coord.type === 'cartesian2d') { + var baseAxis = coord.getBaseAxis(); + // When boundaryGap is false or using time axis. bar may exceed the grid. + // We should not clip this part. + // See test/bar2.html + if (baseAxis.type !== 'category' || !baseAxis.onBand) { + var expandWidth = data.getLayout('bandWidth'); + if (baseAxis.isHorizontal()) { + coordSysClipArea.x -= expandWidth; + coordSysClipArea.width += expandWidth * 2; + } + else { + coordSysClipArea.y -= expandWidth; + coordSysClipArea.height += expandWidth * 2; + } + } + } + + return coordSysClipArea; +} + +extendChartView({ + + type: 'bar', + + render: function (seriesModel, ecModel, api) { + this._updateDrawMode(seriesModel); + + var coordinateSystemType = seriesModel.get('coordinateSystem'); + + if (coordinateSystemType === 'cartesian2d' + || coordinateSystemType === 'polar' + ) { + this._isLargeDraw + ? this._renderLarge(seriesModel, ecModel, api) + : this._renderNormal(seriesModel, ecModel, api); + } + else if (__DEV__) { + console.warn('Only cartesian2d and polar supported for bar.'); + } + + return this.group; + }, + + incrementalPrepareRender: function (seriesModel, ecModel, api) { + this._clear(); + this._updateDrawMode(seriesModel); + }, + + incrementalRender: function (params, seriesModel, ecModel, api) { + // Do not support progressive in normal mode. + this._incrementalRenderLarge(params, seriesModel); + }, + + _updateDrawMode: function (seriesModel) { + var isLargeDraw = seriesModel.pipelineContext.large; + if (this._isLargeDraw == null || isLargeDraw ^ this._isLargeDraw) { + this._isLargeDraw = isLargeDraw; + this._clear(); + } + }, + + _renderNormal: function (seriesModel, ecModel, api) { + var group = this.group; + var data = seriesModel.getData(); + var oldData = this._data; + + var coord = seriesModel.coordinateSystem; + var baseAxis = coord.getBaseAxis(); + var isHorizontalOrRadial; + + if (coord.type === 'cartesian2d') { + isHorizontalOrRadial = baseAxis.isHorizontal(); + } + else if (coord.type === 'polar') { + isHorizontalOrRadial = baseAxis.dim === 'angle'; + } + + var animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null; + + var needsClip = seriesModel.get('clip', true); + var coordSysClipArea = getClipArea(coord, data); + // If there is clipPath created in large mode. Remove it. + group.removeClipPath(); + // We don't use clipPath in normal mode because we needs a perfect animation + // And don't want the label are clipped. + + var roundCap = seriesModel.get('roundCap', true); + + data.diff(oldData) + .add(function (dataIndex) { + if (!data.hasValue(dataIndex)) { + return; + } + + var itemModel = data.getItemModel(dataIndex); + var layout = getLayout[coord.type](data, dataIndex, itemModel); + + if (needsClip) { + // Clip will modify the layout params. + // And return a boolean to determine if the shape are fully clipped. + var isClipped = clip[coord.type](coordSysClipArea, layout); + if (isClipped) { + group.remove(el); + return; + } + } + + var el = elementCreator[coord.type]( + dataIndex, layout, isHorizontalOrRadial, animationModel, false, roundCap + ); + data.setItemGraphicEl(dataIndex, el); + group.add(el); + + updateStyle( + el, data, dataIndex, itemModel, layout, + seriesModel, isHorizontalOrRadial, coord.type === 'polar' + ); + }) + .update(function (newIndex, oldIndex) { + var el = oldData.getItemGraphicEl(oldIndex); + + if (!data.hasValue(newIndex)) { + group.remove(el); + return; + } + + var itemModel = data.getItemModel(newIndex); + var layout = getLayout[coord.type](data, newIndex, itemModel); + + if (needsClip) { + var isClipped = clip[coord.type](coordSysClipArea, layout); + if (isClipped) { + group.remove(el); + return; + } + } + + if (el) { + updateProps(el, {shape: layout}, animationModel, newIndex); + } + else { + el = elementCreator[coord.type]( + newIndex, layout, isHorizontalOrRadial, animationModel, true, roundCap + ); + } + + data.setItemGraphicEl(newIndex, el); + // Add back + group.add(el); + + updateStyle( + el, data, newIndex, itemModel, layout, + seriesModel, isHorizontalOrRadial, coord.type === 'polar' + ); + }) + .remove(function (dataIndex) { + var el = oldData.getItemGraphicEl(dataIndex); + if (coord.type === 'cartesian2d') { + el && removeRect(dataIndex, animationModel, el); + } + else { + el && removeSector(dataIndex, animationModel, el); + } + }) + .execute(); + + this._data = data; + }, + + _renderLarge: function (seriesModel, ecModel, api) { + this._clear(); + createLarge(seriesModel, this.group); + + // Use clipPath in large mode. + var clipPath = seriesModel.get('clip', true) + ? createClipPath(seriesModel.coordinateSystem, false, seriesModel) + : null; + if (clipPath) { + this.group.setClipPath(clipPath); + } + else { + this.group.removeClipPath(); + } + }, + + _incrementalRenderLarge: function (params, seriesModel) { + createLarge(seriesModel, this.group, true); + }, + + dispose: noop, + + remove: function (ecModel) { + this._clear(ecModel); + }, + + _clear: function (ecModel) { + var group = this.group; + var data = this._data; + if (ecModel && ecModel.get('animation') && data && !this._isLargeDraw) { + data.eachItemGraphicEl(function (el) { + if (el.type === 'sector') { + removeSector(el.dataIndex, ecModel, el); + } + else { + removeRect(el.dataIndex, ecModel, el); + } + }); + } + else { + group.removeAll(); + } + this._data = null; + } + +}); + +var mathMax$4 = Math.max; +var mathMin$4 = Math.min; + +var clip = { + cartesian2d: function (coordSysBoundingRect, layout) { + var signWidth = layout.width < 0 ? -1 : 1; + var signHeight = layout.height < 0 ? -1 : 1; + // Needs positive width and height + if (signWidth < 0) { + layout.x += layout.width; + layout.width = -layout.width; + } + if (signHeight < 0) { + layout.y += layout.height; + layout.height = -layout.height; + } + + var x = mathMax$4(layout.x, coordSysBoundingRect.x); + var x2 = mathMin$4(layout.x + layout.width, coordSysBoundingRect.x + coordSysBoundingRect.width); + var y = mathMax$4(layout.y, coordSysBoundingRect.y); + var y2 = mathMin$4(layout.y + layout.height, coordSysBoundingRect.y + coordSysBoundingRect.height); + + layout.x = x; + layout.y = y; + layout.width = x2 - x; + layout.height = y2 - y; + + var clipped = layout.width < 0 || layout.height < 0; + + // Reverse back + if (signWidth < 0) { + layout.x += layout.width; + layout.width = -layout.width; + } + if (signHeight < 0) { + layout.y += layout.height; + layout.height = -layout.height; + } + + return clipped; + }, + + polar: function (coordSysClipArea) { + return false; + } +}; + +var elementCreator = { + + cartesian2d: function ( + dataIndex, layout, isHorizontal, + animationModel, isUpdate + ) { + var rect = new Rect({shape: extend({}, layout)}); + + // Animation + if (animationModel) { + var rectShape = rect.shape; + var animateProperty = isHorizontal ? 'height' : 'width'; + var animateTarget = {}; + rectShape[animateProperty] = 0; + animateTarget[animateProperty] = layout[animateProperty]; + graphic[isUpdate ? 'updateProps' : 'initProps'](rect, { + shape: animateTarget + }, animationModel, dataIndex); + } + + return rect; + }, + + polar: function ( + dataIndex, layout, isRadial, + animationModel, isUpdate, roundCap + ) { + // Keep the same logic with bar in catesion: use end value to control + // direction. Notice that if clockwise is true (by default), the sector + // will always draw clockwisely, no matter whether endAngle is greater + // or less than startAngle. + var clockwise = layout.startAngle < layout.endAngle; + + var ShapeClass = (!isRadial && roundCap) ? Sausage : Sector; + + var sector = new ShapeClass({ + shape: defaults({clockwise: clockwise}, layout) + }); + + // Animation + if (animationModel) { + var sectorShape = sector.shape; + var animateProperty = isRadial ? 'r' : 'endAngle'; + var animateTarget = {}; + sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle; + animateTarget[animateProperty] = layout[animateProperty]; + graphic[isUpdate ? 'updateProps' : 'initProps'](sector, { + shape: animateTarget + }, animationModel, dataIndex); + } + + return sector; + } +}; + +function removeRect(dataIndex, animationModel, el) { + // Not show text when animating + el.style.text = null; + updateProps(el, { + shape: { + width: 0 + } + }, animationModel, dataIndex, function () { + el.parent && el.parent.remove(el); + }); +} + +function removeSector(dataIndex, animationModel, el) { + // Not show text when animating + el.style.text = null; + updateProps(el, { + shape: { + r: el.shape.r0 + } + }, animationModel, dataIndex, function () { + el.parent && el.parent.remove(el); + }); +} + +var getLayout = { + cartesian2d: function (data, dataIndex, itemModel) { + var layout = data.getItemLayout(dataIndex); + var fixedLineWidth = getLineWidth(itemModel, layout); + + // fix layout with lineWidth + var signX = layout.width > 0 ? 1 : -1; + var signY = layout.height > 0 ? 1 : -1; + return { + x: layout.x + signX * fixedLineWidth / 2, + y: layout.y + signY * fixedLineWidth / 2, + width: layout.width - signX * fixedLineWidth, + height: layout.height - signY * fixedLineWidth + }; + }, + + polar: function (data, dataIndex, itemModel) { + var layout = data.getItemLayout(dataIndex); + return { + cx: layout.cx, + cy: layout.cy, + r0: layout.r0, + r: layout.r, + startAngle: layout.startAngle, + endAngle: layout.endAngle + }; + } +}; + +function isZeroOnPolar(layout) { + return layout.startAngle != null + && layout.endAngle != null + && layout.startAngle === layout.endAngle; +} + +function updateStyle( + el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal, isPolar +) { + var color = data.getItemVisual(dataIndex, 'color'); + var opacity = data.getItemVisual(dataIndex, 'opacity'); + var stroke = data.getVisual('borderColor'); + var itemStyleModel = itemModel.getModel('itemStyle'); + var hoverStyle = itemModel.getModel('emphasis.itemStyle').getBarItemStyle(); + + if (!isPolar) { + el.setShape('r', itemStyleModel.get('barBorderRadius') || 0); + } + + el.useStyle(defaults( + { + stroke: isZeroOnPolar(layout) ? 'none' : stroke, + fill: isZeroOnPolar(layout) ? 'none' : color, + opacity: opacity + }, + itemStyleModel.getBarItemStyle() + )); + + var cursorStyle = itemModel.getShallow('cursor'); + cursorStyle && el.attr('cursor', cursorStyle); + + var labelPositionOutside = isHorizontal + ? (layout.height > 0 ? 'bottom' : 'top') + : (layout.width > 0 ? 'left' : 'right'); + + if (!isPolar) { + setLabel( + el.style, hoverStyle, itemModel, color, + seriesModel, dataIndex, labelPositionOutside + ); + } + if (isZeroOnPolar(layout)) { + hoverStyle.fill = hoverStyle.stroke = 'none'; + } + setHoverStyle(el, hoverStyle); +} + +// In case width or height are too small. +function getLineWidth(itemModel, rawLayout) { + var lineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0; + return Math.min(lineWidth, Math.abs(rawLayout.width), Math.abs(rawLayout.height)); +} + + +var LargePath = Path.extend({ + + type: 'largeBar', + + shape: {points: []}, + + buildPath: function (ctx, shape) { + // Drawing lines is more efficient than drawing + // a whole line or drawing rects. + var points = shape.points; + var startPoint = this.__startPoint; + var baseDimIdx = this.__baseDimIdx; + + for (var i = 0; i < points.length; i += 2) { + startPoint[baseDimIdx] = points[i + baseDimIdx]; + ctx.moveTo(startPoint[0], startPoint[1]); + ctx.lineTo(points[i], points[i + 1]); + } + } +}); + +function createLarge(seriesModel, group, incremental) { + // TODO support polar + var data = seriesModel.getData(); + var startPoint = []; + var baseDimIdx = data.getLayout('valueAxisHorizontal') ? 1 : 0; + startPoint[1 - baseDimIdx] = data.getLayout('valueAxisStart'); + + var el = new LargePath({ + shape: {points: data.getLayout('largePoints')}, + incremental: !!incremental, + __startPoint: startPoint, + __baseDimIdx: baseDimIdx, + __largeDataIndices: data.getLayout('largeDataIndices'), + __barWidth: data.getLayout('barWidth') + }); + group.add(el); + setLargeStyle(el, seriesModel, data); + + // Enable tooltip and user mouse/touch event handlers. + el.seriesIndex = seriesModel.seriesIndex; + + if (!seriesModel.get('silent')) { + el.on('mousedown', largePathUpdateDataIndex); + el.on('mousemove', largePathUpdateDataIndex); + } +} + +// Use throttle to avoid frequently traverse to find dataIndex. +var largePathUpdateDataIndex = throttle(function (event) { + var largePath = this; + var dataIndex = largePathFindDataIndex(largePath, event.offsetX, event.offsetY); + largePath.dataIndex = dataIndex >= 0 ? dataIndex : null; +}, 30, false); + +function largePathFindDataIndex(largePath, x, y) { + var baseDimIdx = largePath.__baseDimIdx; + var valueDimIdx = 1 - baseDimIdx; + var points = largePath.shape.points; + var largeDataIndices = largePath.__largeDataIndices; + var barWidthHalf = Math.abs(largePath.__barWidth / 2); + var startValueVal = largePath.__startPoint[valueDimIdx]; + + _eventPos[0] = x; + _eventPos[1] = y; + var pointerBaseVal = _eventPos[baseDimIdx]; + var pointerValueVal = _eventPos[1 - baseDimIdx]; + var baseLowerBound = pointerBaseVal - barWidthHalf; + var baseUpperBound = pointerBaseVal + barWidthHalf; + + for (var i = 0, len = points.length / 2; i < len; i++) { + var ii = i * 2; + var barBaseVal = points[ii + baseDimIdx]; + var barValueVal = points[ii + valueDimIdx]; + if ( + barBaseVal >= baseLowerBound && barBaseVal <= baseUpperBound + && ( + startValueVal <= barValueVal + ? (pointerValueVal >= startValueVal && pointerValueVal <= barValueVal) + : (pointerValueVal >= barValueVal && pointerValueVal <= startValueVal) + ) + ) { + return largeDataIndices[i]; + } + } + + return -1; +} + +function setLargeStyle(el, seriesModel, data) { + var borderColor = data.getVisual('borderColor') || data.getVisual('color'); + var itemStyle = seriesModel.getModel('itemStyle').getItemStyle(['color', 'borderColor']); + + el.useStyle(itemStyle); + el.style.fill = null; + el.style.stroke = borderColor; + el.style.lineWidth = data.getLayout('barWidth'); +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// In case developer forget to include grid component +registerLayout(PRIORITY.VISUAL.LAYOUT, curry(layout, 'bar')); +// Use higher prority to avoid to be blocked by other overall layout, which do not +// only exist in this module, but probably also exist in other modules, like `barPolar`. +registerLayout(PRIORITY.VISUAL.PROGRESSIVE_LAYOUT, largeLayout); + +registerVisual({ + seriesType: 'bar', + reset: function (seriesModel) { + // Visual coding for legend + seriesModel.getData().setVisual('legendSymbol', 'roundRect'); + } +}); + +/* +* 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. +*/ + + +/** + * [Usage]: + * (1) + * createListSimply(seriesModel, ['value']); + * (2) + * createListSimply(seriesModel, { + * coordDimensions: ['value'], + * dimensionsCount: 5 + * }); + * + * @param {module:echarts/model/Series} seriesModel + * @param {Object|Array.} opt opt or coordDimensions + * The options in opt, see `echarts/data/helper/createDimensions` + * @param {Array.} [nameList] + * @return {module:echarts/data/List} + */ +var createListSimply = function (seriesModel, opt, nameList) { + opt = isArray(opt) && {coordDimensions: opt} || extend({}, opt); + + var source = seriesModel.getSource(); + + var dimensionsInfo = createDimensions(source, opt); + + var list = new List(dimensionsInfo, seriesModel); + list.initData(source, nameList); + + return list; +}; + +/* +* 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. +*/ + +/** + * Data selectable mixin for chart series. + * To eanble data select, option of series must have `selectedMode`. + * And each data item will use `selected` to toggle itself selected status + */ + +var selectableMixin = { + + /** + * @param {Array.} targetList [{name, value, selected}, ...] + * If targetList is an array, it should like [{name: ..., value: ...}, ...]. + * If targetList is a "List", it must have coordDim: 'value' dimension and name. + */ + updateSelectedMap: function (targetList) { + this._targetList = isArray(targetList) ? targetList.slice() : []; + + this._selectTargetMap = reduce(targetList || [], function (targetMap, target) { + targetMap.set(target.name, target); + return targetMap; + }, createHashMap()); + }, + + /** + * Either name or id should be passed as input here. + * If both of them are defined, id is used. + * + * @param {string|undefined} name name of data + * @param {number|undefined} id dataIndex of data + */ + // PENGING If selectedMode is null ? + select: function (name, id) { + var target = id != null + ? this._targetList[id] + : this._selectTargetMap.get(name); + var selectedMode = this.get('selectedMode'); + if (selectedMode === 'single') { + this._selectTargetMap.each(function (target) { + target.selected = false; + }); + } + target && (target.selected = true); + }, + + /** + * Either name or id should be passed as input here. + * If both of them are defined, id is used. + * + * @param {string|undefined} name name of data + * @param {number|undefined} id dataIndex of data + */ + unSelect: function (name, id) { + var target = id != null + ? this._targetList[id] + : this._selectTargetMap.get(name); + // var selectedMode = this.get('selectedMode'); + // selectedMode !== 'single' && target && (target.selected = false); + target && (target.selected = false); + }, + + /** + * Either name or id should be passed as input here. + * If both of them are defined, id is used. + * + * @param {string|undefined} name name of data + * @param {number|undefined} id dataIndex of data + */ + toggleSelected: function (name, id) { + var target = id != null + ? this._targetList[id] + : this._selectTargetMap.get(name); + if (target != null) { + this[target.selected ? 'unSelect' : 'select'](name, id); + return target.selected; + } + }, + + /** + * Either name or id should be passed as input here. + * If both of them are defined, id is used. + * + * @param {string|undefined} name name of data + * @param {number|undefined} id dataIndex of data + */ + isSelected: function (name, id) { + var target = id != null + ? this._targetList[id] + : this._selectTargetMap.get(name); + return target && target.selected; + } +}; + +/* +* 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. +*/ + + +/** + * LegendVisualProvider is an bridge that pick encoded color from data and + * provide to the legend component. + * @param {Function} getDataWithEncodedVisual Function to get data after filtered. It stores all the encoding info + * @param {Function} getRawData Function to get raw data before filtered. + */ +function LegendVisualProvider(getDataWithEncodedVisual, getRawData) { + this.getAllNames = function () { + var rawData = getRawData(); + // We find the name from the raw data. In case it's filtered by the legend component. + // Normally, the name can be found in rawData, but can't be found in filtered data will display as gray. + return rawData.mapArray(rawData.getName); + }; + + this.containName = function (name) { + var rawData = getRawData(); + return rawData.indexOfName(name) >= 0; + }; + + this.indexOfName = function (name) { + // Only get data when necessary. + // Because LegendVisualProvider constructor may be new in the stage that data is not prepared yet. + // Invoking Series#getData immediately will throw an error. + var dataWithEncodedVisual = getDataWithEncodedVisual(); + return dataWithEncodedVisual.indexOfName(name); + }; + + this.getItemVisual = function (dataIndex, key) { + // Get encoded visual properties from final filtered data. + var dataWithEncodedVisual = getDataWithEncodedVisual(); + return dataWithEncodedVisual.getItemVisual(dataIndex, key); + }; +} + +/* +* 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. +*/ + +var PieSeries = extendSeriesModel({ + + type: 'series.pie', + + // Overwrite + init: function (option) { + PieSeries.superApply(this, 'init', arguments); + + // Enable legend selection for each data item + // Use a function instead of direct access because data reference may changed + this.legendVisualProvider = new LegendVisualProvider( + bind(this.getData, this), bind(this.getRawData, this) + ); + + this.updateSelectedMap(this._createSelectableList()); + + this._defaultLabelLine(option); + }, + + // Overwrite + mergeOption: function (newOption) { + PieSeries.superCall(this, 'mergeOption', newOption); + + this.updateSelectedMap(this._createSelectableList()); + }, + + getInitialData: function (option, ecModel) { + return createListSimply(this, { + coordDimensions: ['value'], + encodeDefaulter: curry(makeSeriesEncodeForNameBased, this) + }); + }, + + _createSelectableList: function () { + var data = this.getRawData(); + var valueDim = data.mapDimension('value'); + var targetList = []; + for (var i = 0, len = data.count(); i < len; i++) { + targetList.push({ + name: data.getName(i), + value: data.get(valueDim, i), + selected: retrieveRawAttr(data, i, 'selected') + }); + } + return targetList; + }, + + // Overwrite + getDataParams: function (dataIndex) { + var data = this.getData(); + var params = PieSeries.superCall(this, 'getDataParams', dataIndex); + // FIXME toFixed? + + var valueList = []; + data.each(data.mapDimension('value'), function (value) { + valueList.push(value); + }); + + params.percent = getPercentWithPrecision( + valueList, + dataIndex, + data.hostModel.get('percentPrecision') + ); + + params.$vars.push('percent'); + return params; + }, + + _defaultLabelLine: function (option) { + // Extend labelLine emphasis + defaultEmphasis(option, 'labelLine', ['show']); + + var labelLineNormalOpt = option.labelLine; + var labelLineEmphasisOpt = option.emphasis.labelLine; + // Not show label line if `label.normal.show = false` + labelLineNormalOpt.show = labelLineNormalOpt.show + && option.label.show; + labelLineEmphasisOpt.show = labelLineEmphasisOpt.show + && option.emphasis.label.show; + }, + + defaultOption: { + zlevel: 0, + z: 2, + legendHoverLink: true, + + hoverAnimation: true, + // 默认全局居中 + center: ['50%', '50%'], + radius: [0, '75%'], + // 默认顺时针 + clockwise: true, + startAngle: 90, + // 最小角度改为0 + minAngle: 0, + + // If the angle of a sector less than `minShowLabelAngle`, + // the label will not be displayed. + minShowLabelAngle: 0, + + // 选中时扇区偏移量 + selectedOffset: 10, + // 高亮扇区偏移量 + hoverOffset: 10, + + // If use strategy to avoid label overlapping + avoidLabelOverlap: true, + // 选择模式,默认关闭,可选single,multiple + // selectedMode: false, + // 南丁格尔玫瑰图模式,'radius'(半径) | 'area'(面积) + // roseType: null, + + percentPrecision: 2, + + // If still show when all data zero. + stillShowZeroSum: true, + + // cursor: null, + + left: 0, + top: 0, + right: 0, + bottom: 0, + width: null, + height: null, + + label: { + // If rotate around circle + rotate: false, + show: true, + // 'outer', 'inside', 'center' + position: 'outer', + // 'none', 'labelLine', 'edge'. Works only when position is 'outer' + alignTo: 'none', + // Closest distance between label and chart edge. + // Works only position is 'outer' and alignTo is 'edge'. + margin: '25%', + // Works only position is 'outer' and alignTo is not 'edge'. + bleedMargin: 10, + // Distance between text and label line. + distanceToLabelLine: 5 + // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调 + // 默认使用全局文本样式,详见TEXTSTYLE + // distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数 + }, + // Enabled when label.normal.position is 'outer' + labelLine: { + show: true, + // 引导线两段中的第一段长度 + length: 15, + // 引导线两段中的第二段长度 + length2: 15, + smooth: false, + lineStyle: { + // color: 各异, + width: 1, + type: 'solid' + } + }, + itemStyle: { + borderWidth: 1 + }, + + // Animation type. Valid values: expansion, scale + animationType: 'expansion', + + // Animation type when update. Valid values: transition, expansion + animationTypeUpdate: 'transition', + + animationEasing: 'cubicOut' + } +}); + +mixin(PieSeries, selectableMixin); + +/* +* 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. +*/ + +/** + * @param {module:echarts/model/Series} seriesModel + * @param {boolean} hasAnimation + * @inner + */ +function updateDataSelected(uid, seriesModel, hasAnimation, api) { + var data = seriesModel.getData(); + var dataIndex = this.dataIndex; + var name = data.getName(dataIndex); + var selectedOffset = seriesModel.get('selectedOffset'); + + api.dispatchAction({ + type: 'pieToggleSelect', + from: uid, + name: name, + seriesId: seriesModel.id + }); + + data.each(function (idx) { + toggleItemSelected( + data.getItemGraphicEl(idx), + data.getItemLayout(idx), + seriesModel.isSelected(data.getName(idx)), + selectedOffset, + hasAnimation + ); + }); +} + +/** + * @param {module:zrender/graphic/Sector} el + * @param {Object} layout + * @param {boolean} isSelected + * @param {number} selectedOffset + * @param {boolean} hasAnimation + * @inner + */ +function toggleItemSelected(el, layout, isSelected, selectedOffset, hasAnimation) { + var midAngle = (layout.startAngle + layout.endAngle) / 2; + + var dx = Math.cos(midAngle); + var dy = Math.sin(midAngle); + + var offset = isSelected ? selectedOffset : 0; + var position = [dx * offset, dy * offset]; + + hasAnimation + // animateTo will stop revious animation like update transition + ? el.animate() + .when(200, { + position: position + }) + .start('bounceOut') + : el.attr('position', position); +} + +/** + * Piece of pie including Sector, Label, LabelLine + * @constructor + * @extends {module:zrender/graphic/Group} + */ +function PiePiece(data, idx) { + + Group.call(this); + + var sector = new Sector({ + z2: 2 + }); + var polyline = new Polyline(); + var text = new Text(); + this.add(sector); + this.add(polyline); + this.add(text); + + this.updateData(data, idx, true); +} + +var piePieceProto = PiePiece.prototype; + +piePieceProto.updateData = function (data, idx, firstCreate) { + + var sector = this.childAt(0); + var labelLine = this.childAt(1); + var labelText = this.childAt(2); + + var seriesModel = data.hostModel; + var itemModel = data.getItemModel(idx); + var layout = data.getItemLayout(idx); + var sectorShape = extend({}, layout); + sectorShape.label = null; + + var animationTypeUpdate = seriesModel.getShallow('animationTypeUpdate'); + + if (firstCreate) { + sector.setShape(sectorShape); + + var animationType = seriesModel.getShallow('animationType'); + if (animationType === 'scale') { + sector.shape.r = layout.r0; + initProps(sector, { + shape: { + r: layout.r + } + }, seriesModel, idx); + } + // Expansion + else { + sector.shape.endAngle = layout.startAngle; + updateProps(sector, { + shape: { + endAngle: layout.endAngle + } + }, seriesModel, idx); + } + + } + else { + if (animationTypeUpdate === 'expansion') { + // Sectors are set to be target shape and an overlaying clipPath is used for animation + sector.setShape(sectorShape); + } + else { + // Transition animation from the old shape + updateProps(sector, { + shape: sectorShape + }, seriesModel, idx); + } + } + + // Update common style + var visualColor = data.getItemVisual(idx, 'color'); + + sector.useStyle( + defaults( + { + lineJoin: 'bevel', + fill: visualColor + }, + itemModel.getModel('itemStyle').getItemStyle() + ) + ); + sector.hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle(); + + var cursorStyle = itemModel.getShallow('cursor'); + cursorStyle && sector.attr('cursor', cursorStyle); + + // Toggle selected + toggleItemSelected( + this, + data.getItemLayout(idx), + seriesModel.isSelected(null, idx), + seriesModel.get('selectedOffset'), + seriesModel.get('animation') + ); + + // Label and text animation should be applied only for transition type animation when update + var withAnimation = !firstCreate && animationTypeUpdate === 'transition'; + this._updateLabel(data, idx, withAnimation); + + this.highDownOnUpdate = (itemModel.get('hoverAnimation') && seriesModel.isAnimationEnabled()) + ? function (fromState, toState) { + if (toState === 'emphasis') { + labelLine.ignore = labelLine.hoverIgnore; + labelText.ignore = labelText.hoverIgnore; + + // Sector may has animation of updating data. Force to move to the last frame + // Or it may stopped on the wrong shape + sector.stopAnimation(true); + sector.animateTo({ + shape: { + r: layout.r + seriesModel.get('hoverOffset') + } + }, 300, 'elasticOut'); + } + else { + labelLine.ignore = labelLine.normalIgnore; + labelText.ignore = labelText.normalIgnore; + + sector.stopAnimation(true); + sector.animateTo({ + shape: { + r: layout.r + } + }, 300, 'elasticOut'); + } + } + : null; + + setHoverStyle(this); +}; + +piePieceProto._updateLabel = function (data, idx, withAnimation) { + + var labelLine = this.childAt(1); + var labelText = this.childAt(2); + + var seriesModel = data.hostModel; + var itemModel = data.getItemModel(idx); + var layout = data.getItemLayout(idx); + var labelLayout = layout.label; + var visualColor = data.getItemVisual(idx, 'color'); + + if (!labelLayout || isNaN(labelLayout.x) || isNaN(labelLayout.y)) { + labelText.ignore = labelText.normalIgnore = labelText.hoverIgnore = + labelLine.ignore = labelLine.normalIgnore = labelLine.hoverIgnore = true; + return; + } + + var targetLineShape = { + points: labelLayout.linePoints || [ + [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y] + ] + }; + var targetTextStyle = { + x: labelLayout.x, + y: labelLayout.y + }; + if (withAnimation) { + updateProps(labelLine, { + shape: targetLineShape + }, seriesModel, idx); + + updateProps(labelText, { + style: targetTextStyle + }, seriesModel, idx); + } + else { + labelLine.attr({ + shape: targetLineShape + }); + labelText.attr({ + style: targetTextStyle + }); + } + + labelText.attr({ + rotation: labelLayout.rotation, + origin: [labelLayout.x, labelLayout.y], + z2: 10 + }); + + var labelModel = itemModel.getModel('label'); + var labelHoverModel = itemModel.getModel('emphasis.label'); + var labelLineModel = itemModel.getModel('labelLine'); + var labelLineHoverModel = itemModel.getModel('emphasis.labelLine'); + var visualColor = data.getItemVisual(idx, 'color'); + + setLabelStyle( + labelText.style, labelText.hoverStyle = {}, labelModel, labelHoverModel, + { + labelFetcher: data.hostModel, + labelDataIndex: idx, + defaultText: labelLayout.text, + autoColor: visualColor, + useInsideStyle: !!labelLayout.inside + }, + { + textAlign: labelLayout.textAlign, + textVerticalAlign: labelLayout.verticalAlign, + opacity: data.getItemVisual(idx, 'opacity') + } + ); + + labelText.ignore = labelText.normalIgnore = !labelModel.get('show'); + labelText.hoverIgnore = !labelHoverModel.get('show'); + + labelLine.ignore = labelLine.normalIgnore = !labelLineModel.get('show'); + labelLine.hoverIgnore = !labelLineHoverModel.get('show'); + + // Default use item visual color + labelLine.setStyle({ + stroke: visualColor, + opacity: data.getItemVisual(idx, 'opacity') + }); + labelLine.setStyle(labelLineModel.getModel('lineStyle').getLineStyle()); + + labelLine.hoverStyle = labelLineHoverModel.getModel('lineStyle').getLineStyle(); + + var smooth = labelLineModel.get('smooth'); + if (smooth && smooth === true) { + smooth = 0.4; + } + labelLine.setShape({ + smooth: smooth + }); +}; + +inherits(PiePiece, Group); + + +// Pie view +var PieView = Chart.extend({ + + type: 'pie', + + init: function () { + var sectorGroup = new Group(); + this._sectorGroup = sectorGroup; + }, + + render: function (seriesModel, ecModel, api, payload) { + if (payload && (payload.from === this.uid)) { + return; + } + + var data = seriesModel.getData(); + var oldData = this._data; + var group = this.group; + + var hasAnimation = ecModel.get('animation'); + var isFirstRender = !oldData; + var animationType = seriesModel.get('animationType'); + var animationTypeUpdate = seriesModel.get('animationTypeUpdate'); + + var onSectorClick = curry( + updateDataSelected, this.uid, seriesModel, hasAnimation, api + ); + + var selectedMode = seriesModel.get('selectedMode'); + data.diff(oldData) + .add(function (idx) { + var piePiece = new PiePiece(data, idx); + // Default expansion animation + if (isFirstRender && animationType !== 'scale') { + piePiece.eachChild(function (child) { + child.stopAnimation(true); + }); + } + + selectedMode && piePiece.on('click', onSectorClick); + + data.setItemGraphicEl(idx, piePiece); + + group.add(piePiece); + }) + .update(function (newIdx, oldIdx) { + var piePiece = oldData.getItemGraphicEl(oldIdx); + + if (!isFirstRender && animationTypeUpdate !== 'transition') { + piePiece.eachChild(function (child) { + child.stopAnimation(true); + }); + } + + piePiece.updateData(data, newIdx); + + piePiece.off('click'); + selectedMode && piePiece.on('click', onSectorClick); + group.add(piePiece); + data.setItemGraphicEl(newIdx, piePiece); + }) + .remove(function (idx) { + var piePiece = oldData.getItemGraphicEl(idx); + group.remove(piePiece); + }) + .execute(); + + if ( + hasAnimation && data.count() > 0 + && (isFirstRender ? animationType !== 'scale' : animationTypeUpdate !== 'transition') + ) { + var shape = data.getItemLayout(0); + for (var s = 1; isNaN(shape.startAngle) && s < data.count(); ++s) { + shape = data.getItemLayout(s); + } + + var r = Math.max(api.getWidth(), api.getHeight()) / 2; + + var removeClipPath = bind(group.removeClipPath, group); + group.setClipPath(this._createClipPath( + shape.cx, shape.cy, r, shape.startAngle, shape.clockwise, removeClipPath, seriesModel, isFirstRender + )); + } + else { + // clipPath is used in first-time animation, so remove it when otherwise. See: #8994 + group.removeClipPath(); + } + + this._data = data; + }, + + dispose: function () {}, + + _createClipPath: function ( + cx, cy, r, startAngle, clockwise, cb, seriesModel, isFirstRender + ) { + var clipPath = new Sector({ + shape: { + cx: cx, + cy: cy, + r0: 0, + r: r, + startAngle: startAngle, + endAngle: startAngle, + clockwise: clockwise + } + }); + + var initOrUpdate = isFirstRender ? initProps : updateProps; + initOrUpdate(clipPath, { + shape: { + endAngle: startAngle + (clockwise ? 1 : -1) * Math.PI * 2 + } + }, seriesModel, cb); + + return clipPath; + }, + + /** + * @implement + */ + containPoint: function (point, seriesModel) { + var data = seriesModel.getData(); + var itemLayout = data.getItemLayout(0); + if (itemLayout) { + var dx = point[0] - itemLayout.cx; + var dy = point[1] - itemLayout.cy; + var radius = Math.sqrt(dx * dx + dy * dy); + return radius <= itemLayout.r && radius >= itemLayout.r0; + } + } + +}); + +/* +* 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. +*/ + +var createDataSelectAction = function (seriesType, actionInfos) { + each$1(actionInfos, function (actionInfo) { + actionInfo.update = 'updateView'; + /** + * @payload + * @property {string} seriesName + * @property {string} name + */ + registerAction(actionInfo, function (payload, ecModel) { + var selected = {}; + ecModel.eachComponent( + {mainType: 'series', subType: seriesType, query: payload}, + function (seriesModel) { + if (seriesModel[actionInfo.method]) { + seriesModel[actionInfo.method]( + payload.name, + payload.dataIndex + ); + } + var data = seriesModel.getData(); + // Create selected map + data.each(function (idx) { + var name = data.getName(idx); + selected[name] = seriesModel.isSelected(name) + || false; + }); + } + ); + return { + name: payload.name, + selected: selected, + seriesId: payload.seriesId + }; + }); + }); +}; + +/* +* 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. +*/ + +// Pick color from palette for each data item. +// Applicable for charts that require applying color palette +// in data level (like pie, funnel, chord). +var dataColor = function (seriesType) { + return { + getTargetSeries: function (ecModel) { + // Pie and funnel may use diferrent scope + var paletteScope = {}; + var seiresModelMap = createHashMap(); + + ecModel.eachSeriesByType(seriesType, function (seriesModel) { + seriesModel.__paletteScope = paletteScope; + seiresModelMap.set(seriesModel.uid, seriesModel); + }); + + return seiresModelMap; + }, + reset: function (seriesModel, ecModel) { + var dataAll = seriesModel.getRawData(); + var idxMap = {}; + var data = seriesModel.getData(); + + data.each(function (idx) { + var rawIdx = data.getRawIndex(idx); + idxMap[rawIdx] = idx; + }); + + dataAll.each(function (rawIdx) { + var filteredIdx = idxMap[rawIdx]; + + // If series.itemStyle.normal.color is a function. itemVisual may be encoded + var singleDataColor = filteredIdx != null + && data.getItemVisual(filteredIdx, 'color', true); + + var singleDataBorderColor = filteredIdx != null + && data.getItemVisual(filteredIdx, 'borderColor', true); + + var itemModel; + if (!singleDataColor || !singleDataBorderColor) { + // FIXME Performance + itemModel = dataAll.getItemModel(rawIdx); + } + + if (!singleDataColor) { + var color = itemModel.get('itemStyle.color') + || seriesModel.getColorFromPalette( + dataAll.getName(rawIdx) || (rawIdx + ''), seriesModel.__paletteScope, + dataAll.count() + ); + // Data is not filtered + if (filteredIdx != null) { + data.setItemVisual(filteredIdx, 'color', color); + } + } + + if (!singleDataBorderColor) { + var borderColor = itemModel.get('itemStyle.borderColor'); + + // Data is not filtered + if (filteredIdx != null) { + data.setItemVisual(filteredIdx, 'borderColor', borderColor); + } + } + }); + } + }; +}; + +/* +* 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. +*/ + +// FIXME emphasis label position is not same with normal label position + +var RADIAN$1 = Math.PI / 180; + +function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) { + list.sort(function (a, b) { + return a.y - b.y; + }); + + function shiftDown(start, end, delta, dir) { + for (var j = start; j < end; j++) { + if (list[j].y + delta > viewTop + viewHeight) { + break; + } + + list[j].y += delta; + if (j > start + && j + 1 < end + && list[j + 1].y > list[j].y + list[j].height + ) { + shiftUp(j, delta / 2); + return; + } + } + + shiftUp(end - 1, delta / 2); + } + + function shiftUp(end, delta) { + for (var j = end; j >= 0; j--) { + if (list[j].y - delta < viewTop) { + break; + } + + list[j].y -= delta; + if (j > 0 + && list[j].y > list[j - 1].y + list[j - 1].height + ) { + break; + } + } + } + + function changeX(list, isDownList, cx, cy, r, dir) { + var lastDeltaX = dir > 0 + ? isDownList // right-side + ? Number.MAX_VALUE // down + : 0 // up + : isDownList // left-side + ? Number.MAX_VALUE // down + : 0; // up + + for (var i = 0, l = list.length; i < l; i++) { + if (list[i].labelAlignTo !== 'none') { + continue; + } + + var deltaY = Math.abs(list[i].y - cy); + var length = list[i].len; + var length2 = list[i].len2; + var deltaX = (deltaY < r + length) + ? Math.sqrt( + (r + length + length2) * (r + length + length2) + - deltaY * deltaY + ) + : Math.abs(list[i].x - cx); + if (isDownList && deltaX >= lastDeltaX) { + // right-down, left-down + deltaX = lastDeltaX - 10; + } + if (!isDownList && deltaX <= lastDeltaX) { + // right-up, left-up + deltaX = lastDeltaX + 10; + } + + list[i].x = cx + deltaX * dir; + lastDeltaX = deltaX; + } + } + + var lastY = 0; + var delta; + var len = list.length; + var upList = []; + var downList = []; + for (var i = 0; i < len; i++) { + if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') { + var dx = list[i].x - farthestX; + list[i].linePoints[1][0] += dx; + list[i].x = farthestX; + } + + delta = list[i].y - lastY; + if (delta < 0) { + shiftDown(i, len, -delta, dir); + } + lastY = list[i].y + list[i].height; + } + if (viewHeight - lastY < 0) { + shiftUp(len - 1, lastY - viewHeight); + } + for (var i = 0; i < len; i++) { + if (list[i].y >= cy) { + downList.push(list[i]); + } + else { + upList.push(list[i]); + } + } + changeX(upList, false, cx, cy, r, dir); + changeX(downList, true, cx, cy, r, dir); +} + +function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop) { + var leftList = []; + var rightList = []; + var leftmostX = Number.MAX_VALUE; + var rightmostX = -Number.MAX_VALUE; + for (var i = 0; i < labelLayoutList.length; i++) { + if (isPositionCenter(labelLayoutList[i])) { + continue; + } + if (labelLayoutList[i].x < cx) { + leftmostX = Math.min(leftmostX, labelLayoutList[i].x); + leftList.push(labelLayoutList[i]); + } + else { + rightmostX = Math.max(rightmostX, labelLayoutList[i].x); + rightList.push(labelLayoutList[i]); + } + } + + adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight, viewLeft, viewTop, rightmostX); + adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight, viewLeft, viewTop, leftmostX); + + for (var i = 0; i < labelLayoutList.length; i++) { + var layout = labelLayoutList[i]; + if (isPositionCenter(layout)) { + continue; + } + + var linePoints = layout.linePoints; + if (linePoints) { + var isAlignToEdge = layout.labelAlignTo === 'edge'; + + var realTextWidth = layout.textRect.width; + var targetTextWidth; + if (isAlignToEdge) { + if (layout.x < cx) { + targetTextWidth = linePoints[2][0] - layout.labelDistance + - viewLeft - layout.labelMargin; + } + else { + targetTextWidth = viewLeft + viewWidth - layout.labelMargin + - linePoints[2][0] - layout.labelDistance; + } + } + else { + if (layout.x < cx) { + targetTextWidth = layout.x - viewLeft - layout.bleedMargin; + } + else { + targetTextWidth = viewLeft + viewWidth - layout.x - layout.bleedMargin; + } + } + if (targetTextWidth < layout.textRect.width) { + layout.text = truncateText(layout.text, targetTextWidth, layout.font); + if (layout.labelAlignTo === 'edge') { + realTextWidth = getWidth(layout.text, layout.font); + } + } + + var dist = linePoints[1][0] - linePoints[2][0]; + if (isAlignToEdge) { + if (layout.x < cx) { + linePoints[2][0] = viewLeft + layout.labelMargin + realTextWidth + layout.labelDistance; + } + else { + linePoints[2][0] = viewLeft + viewWidth - layout.labelMargin + - realTextWidth - layout.labelDistance; + } + } + else { + if (layout.x < cx) { + linePoints[2][0] = layout.x + layout.labelDistance; + } + else { + linePoints[2][0] = layout.x - layout.labelDistance; + } + linePoints[1][0] = linePoints[2][0] + dist; + } + linePoints[1][1] = linePoints[2][1] = layout.y; + } + } +} + +function isPositionCenter(layout) { + // Not change x for center label + return layout.position === 'center'; +} + +var labelLayout = function (seriesModel, r, viewWidth, viewHeight, viewLeft, viewTop) { + var data = seriesModel.getData(); + var labelLayoutList = []; + var cx; + var cy; + var hasLabelRotate = false; + var minShowLabelRadian = (seriesModel.get('minShowLabelAngle') || 0) * RADIAN$1; + + data.each(function (idx) { + var layout = data.getItemLayout(idx); + + var itemModel = data.getItemModel(idx); + var labelModel = itemModel.getModel('label'); + // Use position in normal or emphasis + var labelPosition = labelModel.get('position') || itemModel.get('emphasis.label.position'); + var labelDistance = labelModel.get('distanceToLabelLine'); + var labelAlignTo = labelModel.get('alignTo'); + var labelMargin = parsePercent$1(labelModel.get('margin'), viewWidth); + var bleedMargin = labelModel.get('bleedMargin'); + var font = labelModel.getFont(); + + var labelLineModel = itemModel.getModel('labelLine'); + var labelLineLen = labelLineModel.get('length'); + labelLineLen = parsePercent$1(labelLineLen, viewWidth); + var labelLineLen2 = labelLineModel.get('length2'); + labelLineLen2 = parsePercent$1(labelLineLen2, viewWidth); + + if (layout.angle < minShowLabelRadian) { + return; + } + + var midAngle = (layout.startAngle + layout.endAngle) / 2; + var dx = Math.cos(midAngle); + var dy = Math.sin(midAngle); + + var textX; + var textY; + var linePoints; + var textAlign; + + cx = layout.cx; + cy = layout.cy; + + var text = seriesModel.getFormattedLabel(idx, 'normal') + || data.getName(idx); + var textRect = getBoundingRect( + text, font, textAlign, 'top' + ); + + var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner'; + if (labelPosition === 'center') { + textX = layout.cx; + textY = layout.cy; + textAlign = 'center'; + } + else { + var x1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dx : layout.r * dx) + cx; + var y1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dy : layout.r * dy) + cy; + + textX = x1 + dx * 3; + textY = y1 + dy * 3; + + if (!isLabelInside) { + // For roseType + var x2 = x1 + dx * (labelLineLen + r - layout.r); + var y2 = y1 + dy * (labelLineLen + r - layout.r); + var x3 = x2 + ((dx < 0 ? -1 : 1) * labelLineLen2); + var y3 = y2; + + if (labelAlignTo === 'edge') { + // Adjust textX because text align of edge is opposite + textX = dx < 0 + ? viewLeft + labelMargin + : viewLeft + viewWidth - labelMargin; + } + else { + textX = x3 + (dx < 0 ? -labelDistance : labelDistance); + } + textY = y3; + linePoints = [[x1, y1], [x2, y2], [x3, y3]]; + } + + textAlign = isLabelInside + ? 'center' + : (labelAlignTo === 'edge' + ? (dx > 0 ? 'right' : 'left') + : (dx > 0 ? 'left' : 'right')); + } + + var labelRotate; + var rotate = labelModel.get('rotate'); + if (typeof rotate === 'number') { + labelRotate = rotate * (Math.PI / 180); + } + else { + labelRotate = rotate + ? (dx < 0 ? -midAngle + Math.PI : -midAngle) + : 0; + } + + hasLabelRotate = !!labelRotate; + layout.label = { + x: textX, + y: textY, + position: labelPosition, + height: textRect.height, + len: labelLineLen, + len2: labelLineLen2, + linePoints: linePoints, + textAlign: textAlign, + verticalAlign: 'middle', + rotation: labelRotate, + inside: isLabelInside, + labelDistance: labelDistance, + labelAlignTo: labelAlignTo, + labelMargin:labelMargin, + bleedMargin: bleedMargin, + textRect: textRect, + text: text, + font: font + }; + + // Not layout the inside label + if (!isLabelInside) { + labelLayoutList.push(layout.label); + } + }); + if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) { + avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop); + } +}; + +/* +* 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. +*/ + + +var PI2$4 = Math.PI * 2; +var RADIAN = Math.PI / 180; + +function getViewRect(seriesModel, api) { + return getLayoutRect( + seriesModel.getBoxLayoutParams(), { + width: api.getWidth(), + height: api.getHeight() + } + ); +} + +var pieLayout = function (seriesType, ecModel, api, payload) { + ecModel.eachSeriesByType(seriesType, function (seriesModel) { + var data = seriesModel.getData(); + var valueDim = data.mapDimension('value'); + var viewRect = getViewRect(seriesModel, api); + + var center = seriesModel.get('center'); + var radius = seriesModel.get('radius'); + + if (!isArray(radius)) { + radius = [0, radius]; + } + if (!isArray(center)) { + center = [center, center]; + } + + var width = parsePercent$1(viewRect.width, api.getWidth()); + var height = parsePercent$1(viewRect.height, api.getHeight()); + var size = Math.min(width, height); + var cx = parsePercent$1(center[0], width) + viewRect.x; + var cy = parsePercent$1(center[1], height) + viewRect.y; + var r0 = parsePercent$1(radius[0], size / 2); + var r = parsePercent$1(radius[1], size / 2); + + var startAngle = -seriesModel.get('startAngle') * RADIAN; + + var minAngle = seriesModel.get('minAngle') * RADIAN; + + var validDataCount = 0; + data.each(valueDim, function (value) { + !isNaN(value) && validDataCount++; + }); + + var sum = data.getSum(valueDim); + // Sum may be 0 + var unitRadian = Math.PI / (sum || validDataCount) * 2; + + var clockwise = seriesModel.get('clockwise'); + + var roseType = seriesModel.get('roseType'); + var stillShowZeroSum = seriesModel.get('stillShowZeroSum'); + + // [0...max] + var extent = data.getDataExtent(valueDim); + extent[0] = 0; + + // In the case some sector angle is smaller than minAngle + var restAngle = PI2$4; + var valueSumLargerThanMinAngle = 0; + + var currentAngle = startAngle; + var dir = clockwise ? 1 : -1; + + data.each(valueDim, function (value, idx) { + var angle; + if (isNaN(value)) { + data.setItemLayout(idx, { + angle: NaN, + startAngle: NaN, + endAngle: NaN, + clockwise: clockwise, + cx: cx, + cy: cy, + r0: r0, + r: roseType + ? NaN + : r, + viewRect: viewRect + }); + return; + } + + // FIXME 兼容 2.0 但是 roseType 是 area 的时候才是这样? + if (roseType !== 'area') { + angle = (sum === 0 && stillShowZeroSum) + ? unitRadian : (value * unitRadian); + } + else { + angle = PI2$4 / validDataCount; + } + + if (angle < minAngle) { + angle = minAngle; + restAngle -= minAngle; + } + else { + valueSumLargerThanMinAngle += value; + } + + var endAngle = currentAngle + dir * angle; + data.setItemLayout(idx, { + angle: angle, + startAngle: currentAngle, + endAngle: endAngle, + clockwise: clockwise, + cx: cx, + cy: cy, + r0: r0, + r: roseType + ? linearMap(value, extent, [r0, r]) + : r, + viewRect: viewRect + }); + + currentAngle = endAngle; + }); + + // Some sector is constrained by minAngle + // Rest sectors needs recalculate angle + if (restAngle < PI2$4 && validDataCount) { + // Average the angle if rest angle is not enough after all angles is + // Constrained by minAngle + if (restAngle <= 1e-3) { + var angle = PI2$4 / validDataCount; + data.each(valueDim, function (value, idx) { + if (!isNaN(value)) { + var layout = data.getItemLayout(idx); + layout.angle = angle; + layout.startAngle = startAngle + dir * idx * angle; + layout.endAngle = startAngle + dir * (idx + 1) * angle; + } + }); + } + else { + unitRadian = restAngle / valueSumLargerThanMinAngle; + currentAngle = startAngle; + data.each(valueDim, function (value, idx) { + if (!isNaN(value)) { + var layout = data.getItemLayout(idx); + var angle = layout.angle === minAngle + ? minAngle : value * unitRadian; + layout.startAngle = currentAngle; + layout.endAngle = currentAngle + dir * angle; + currentAngle += dir * angle; + } + }); + } + } + + labelLayout(seriesModel, r, viewRect.width, viewRect.height, viewRect.x, viewRect.y); + }); +}; + +/* +* 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. +*/ + +var dataFilter = function (seriesType) { + return { + seriesType: seriesType, + reset: function (seriesModel, ecModel) { + var legendModels = ecModel.findComponents({ + mainType: 'legend' + }); + if (!legendModels || !legendModels.length) { + return; + } + var data = seriesModel.getData(); + data.filterSelf(function (idx) { + var name = data.getName(idx); + // If in any legend component the status is not selected. + for (var i = 0; i < legendModels.length; i++) { + if (!legendModels[i].isSelected(name)) { + return false; + } + } + return true; + }); + } + }; +}; + +/* +* 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. +*/ + +createDataSelectAction('pie', [{ + type: 'pieToggleSelect', + event: 'pieselectchanged', + method: 'toggleSelected' +}, { + type: 'pieSelect', + event: 'pieselected', + method: 'select' +}, { + type: 'pieUnSelect', + event: 'pieunselected', + method: 'unSelect' +}]); + +registerVisual(dataColor('pie')); +registerLayout(curry(pieLayout, 'pie')); +registerProcessor(dataFilter('pie')); + +/* +* 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. +*/ + +SeriesModel.extend({ + + type: 'series.scatter', + + dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'], + + getInitialData: function (option, ecModel) { + return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true}); + }, + + brushSelector: 'point', + + getProgressive: function () { + var progressive = this.option.progressive; + if (progressive == null) { + // PENDING + return this.option.large ? 5e3 : this.get('progressive'); + } + return progressive; + }, + + getProgressiveThreshold: function () { + var progressiveThreshold = this.option.progressiveThreshold; + if (progressiveThreshold == null) { + // PENDING + return this.option.large ? 1e4 : this.get('progressiveThreshold'); + } + return progressiveThreshold; + }, + + defaultOption: { + coordinateSystem: 'cartesian2d', + zlevel: 0, + z: 2, + legendHoverLink: true, + + hoverAnimation: true, + // Cartesian coordinate system + // xAxisIndex: 0, + // yAxisIndex: 0, + + // Polar coordinate system + // polarIndex: 0, + + // Geo coordinate system + // geoIndex: 0, + + // symbol: null, // 图形类型 + symbolSize: 10, // 图形大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2 + // symbolRotate: null, // 图形旋转控制 + + large: false, + // Available when large is true + largeThreshold: 2000, + // cursor: null, + + // label: { + // show: false + // distance: 5, + // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调 + // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 + // 'inside'|'left'|'right'|'top'|'bottom' + // 默认使用全局文本样式,详见TEXTSTYLE + // }, + itemStyle: { + opacity: 0.8 + // color: 各异 + }, + + // If clip the overflow graphics + // Works on cartesian / polar series + clip: true + + // progressive: null + } + +}); + +/* +* 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. +*/ + +/* global Float32Array */ + +// TODO Batch by color + +var BOOST_SIZE_THRESHOLD = 4; + +var LargeSymbolPath = extendShape({ + + shape: { + points: null + }, + + symbolProxy: null, + + softClipShape: null, + + buildPath: function (path, shape) { + var points = shape.points; + var size = shape.size; + + var symbolProxy = this.symbolProxy; + var symbolProxyShape = symbolProxy.shape; + var ctx = path.getContext ? path.getContext() : path; + var canBoost = ctx && size[0] < BOOST_SIZE_THRESHOLD; + + // Do draw in afterBrush. + if (canBoost) { + return; + } + + for (var i = 0; i < points.length;) { + var x = points[i++]; + var y = points[i++]; + + if (isNaN(x) || isNaN(y)) { + continue; + } + if (this.softClipShape && !this.softClipShape.contain(x, y)) { + continue; + } + + symbolProxyShape.x = x - size[0] / 2; + symbolProxyShape.y = y - size[1] / 2; + symbolProxyShape.width = size[0]; + symbolProxyShape.height = size[1]; + + symbolProxy.buildPath(path, symbolProxyShape, true); + } + }, + + afterBrush: function (ctx) { + var shape = this.shape; + var points = shape.points; + var size = shape.size; + var canBoost = size[0] < BOOST_SIZE_THRESHOLD; + + if (!canBoost) { + return; + } + + this.setTransform(ctx); + // PENDING If style or other canvas status changed? + for (var i = 0; i < points.length;) { + var x = points[i++]; + var y = points[i++]; + if (isNaN(x) || isNaN(y)) { + continue; + } + if (this.softClipShape && !this.softClipShape.contain(x, y)) { + continue; + } + // fillRect is faster than building a rect path and draw. + // And it support light globalCompositeOperation. + ctx.fillRect( + x - size[0] / 2, y - size[1] / 2, + size[0], size[1] + ); + } + + this.restoreTransform(ctx); + }, + + findDataIndex: function (x, y) { + // TODO ??? + // Consider transform + + var shape = this.shape; + var points = shape.points; + var size = shape.size; + + var w = Math.max(size[0], 4); + var h = Math.max(size[1], 4); + + // Not consider transform + // Treat each element as a rect + // top down traverse + for (var idx = points.length / 2 - 1; idx >= 0; idx--) { + var i = idx * 2; + var x0 = points[i] - w / 2; + var y0 = points[i + 1] - h / 2; + if (x >= x0 && y >= y0 && x <= x0 + w && y <= y0 + h) { + return idx; + } + } + + return -1; + } +}); + +function LargeSymbolDraw() { + this.group = new Group(); +} + +var largeSymbolProto = LargeSymbolDraw.prototype; + +largeSymbolProto.isPersistent = function () { + return !this._incremental; +}; + +/** + * Update symbols draw by new data + * @param {module:echarts/data/List} data + * @param {Object} opt + * @param {Object} [opt.clipShape] + */ +largeSymbolProto.updateData = function (data, opt) { + this.group.removeAll(); + var symbolEl = new LargeSymbolPath({ + rectHover: true, + cursor: 'default' + }); + + symbolEl.setShape({ + points: data.getLayout('symbolPoints') + }); + this._setCommon(symbolEl, data, false, opt); + this.group.add(symbolEl); + + this._incremental = null; +}; + +largeSymbolProto.updateLayout = function (data) { + if (this._incremental) { + return; + } + + var points = data.getLayout('symbolPoints'); + this.group.eachChild(function (child) { + if (child.startIndex != null) { + var len = (child.endIndex - child.startIndex) * 2; + var byteOffset = child.startIndex * 4 * 2; + points = new Float32Array(points.buffer, byteOffset, len); + } + child.setShape('points', points); + }); +}; + +largeSymbolProto.incrementalPrepareUpdate = function (data) { + this.group.removeAll(); + + this._clearIncremental(); + // Only use incremental displayables when data amount is larger than 2 million. + // PENDING Incremental data? + if (data.count() > 2e6) { + if (!this._incremental) { + this._incremental = new IncrementalDisplayble({ + silent: true + }); + } + this.group.add(this._incremental); + } + else { + this._incremental = null; + } +}; + +largeSymbolProto.incrementalUpdate = function (taskParams, data, opt) { + var symbolEl; + if (this._incremental) { + symbolEl = new LargeSymbolPath(); + this._incremental.addDisplayable(symbolEl, true); + } + else { + symbolEl = new LargeSymbolPath({ + rectHover: true, + cursor: 'default', + startIndex: taskParams.start, + endIndex: taskParams.end + }); + symbolEl.incremental = true; + this.group.add(symbolEl); + } + + symbolEl.setShape({ + points: data.getLayout('symbolPoints') + }); + this._setCommon(symbolEl, data, !!this._incremental, opt); +}; + +largeSymbolProto._setCommon = function (symbolEl, data, isIncremental, opt) { + var hostModel = data.hostModel; + + opt = opt || {}; + // TODO + // if (data.hasItemVisual.symbolSize) { + // // TODO typed array? + // symbolEl.setShape('sizes', data.mapArray( + // function (idx) { + // var size = data.getItemVisual(idx, 'symbolSize'); + // return (size instanceof Array) ? size : [size, size]; + // } + // )); + // } + // else { + var size = data.getVisual('symbolSize'); + symbolEl.setShape('size', (size instanceof Array) ? size : [size, size]); + // } + + symbolEl.softClipShape = opt.clipShape || null; + // Create symbolProxy to build path for each data + symbolEl.symbolProxy = createSymbol( + data.getVisual('symbol'), 0, 0, 0, 0 + ); + // Use symbolProxy setColor method + symbolEl.setColor = symbolEl.symbolProxy.setColor; + + var extrudeShadow = symbolEl.shape.size[0] < BOOST_SIZE_THRESHOLD; + symbolEl.useStyle( + // Draw shadow when doing fillRect is extremely slow. + hostModel.getModel('itemStyle').getItemStyle(extrudeShadow ? ['color', 'shadowBlur', 'shadowColor'] : ['color']) + ); + + var visualColor = data.getVisual('color'); + if (visualColor) { + symbolEl.setColor(visualColor); + } + + if (!isIncremental) { + // Enable tooltip + // PENDING May have performance issue when path is extremely large + symbolEl.seriesIndex = hostModel.seriesIndex; + symbolEl.on('mousemove', function (e) { + symbolEl.dataIndex = null; + var dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY); + if (dataIndex >= 0) { + // Provide dataIndex for tooltip + symbolEl.dataIndex = dataIndex + (symbolEl.startIndex || 0); + } + }); + } +}; + +largeSymbolProto.remove = function () { + this._clearIncremental(); + this._incremental = null; + this.group.removeAll(); +}; + +largeSymbolProto._clearIncremental = function () { + var incremental = this._incremental; + if (incremental) { + incremental.clearDisplaybles(); + } +}; + +/* +* 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. +*/ + +extendChartView({ + + type: 'scatter', + + render: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + + var symbolDraw = this._updateSymbolDraw(data, seriesModel); + + symbolDraw.updateData(data, { + // TODO + // If this parameter should be a shape or a bounding volume + // shape will be more general. + // But bounding volume like bounding rect will be much faster in the contain calculation + clipShape: this._getClipShape(seriesModel) + }); + + this._finished = true; + }, + + incrementalPrepareRender: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + var symbolDraw = this._updateSymbolDraw(data, seriesModel); + + symbolDraw.incrementalPrepareUpdate(data); + + this._finished = false; + }, + + incrementalRender: function (taskParams, seriesModel, ecModel) { + this._symbolDraw.incrementalUpdate(taskParams, seriesModel.getData(), { + clipShape: this._getClipShape(seriesModel) + }); + + this._finished = taskParams.end === seriesModel.getData().count(); + }, + + updateTransform: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + // Must mark group dirty and make sure the incremental layer will be cleared + // PENDING + this.group.dirty(); + + if (!this._finished || data.count() > 1e4 || !this._symbolDraw.isPersistent()) { + return { + update: true + }; + } + else { + var res = pointsLayout().reset(seriesModel); + if (res.progress) { + res.progress({ start: 0, end: data.count() }, data); + } + + this._symbolDraw.updateLayout(data); + } + }, + + _getClipShape: function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + var clipArea = coordSys && coordSys.getArea && coordSys.getArea(); + return seriesModel.get('clip', true) ? clipArea : null; + }, + + _updateSymbolDraw: function (data, seriesModel) { + var symbolDraw = this._symbolDraw; + var pipelineContext = seriesModel.pipelineContext; + var isLargeDraw = pipelineContext.large; + + if (!symbolDraw || isLargeDraw !== this._isLargeDraw) { + symbolDraw && symbolDraw.remove(); + symbolDraw = this._symbolDraw = isLargeDraw + ? new LargeSymbolDraw() + : new SymbolDraw(); + this._isLargeDraw = isLargeDraw; + this.group.removeAll(); + } + + this.group.add(symbolDraw.group); + + return symbolDraw; + }, + + remove: function (ecModel, api) { + this._symbolDraw && this._symbolDraw.remove(true); + this._symbolDraw = null; + }, + + dispose: function () {} +}); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// import * as zrUtil from 'zrender/src/core/util'; + +// In case developer forget to include grid component +registerVisual(visualSymbol('scatter', 'circle')); +registerLayout(pointsLayout('scatter')); + +// echarts.registerProcessor(function (ecModel, api) { +// ecModel.eachSeriesByType('scatter', function (seriesModel) { +// var data = seriesModel.getData(); +// var coordSys = seriesModel.coordinateSystem; +// if (coordSys.type !== 'geo') { +// return; +// } +// var startPt = coordSys.pointToData([0, 0]); +// var endPt = coordSys.pointToData([api.getWidth(), api.getHeight()]); + +// var dims = zrUtil.map(coordSys.dimensions, function (dim) { +// return data.mapDimension(dim); +// }); +// var range = {}; +// range[dims[0]] = [Math.min(startPt[0], endPt[0]), Math.max(startPt[0], endPt[0])]; +// range[dims[1]] = [Math.min(startPt[1], endPt[1]), Math.max(startPt[1], endPt[1])]; + +// data.selectRange(range); +// }); +// }); + +/* +* 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. +*/ + +function IndicatorAxis(dim, scale, radiusExtent) { + Axis.call(this, dim, scale, radiusExtent); + + /** + * Axis type + * - 'category' + * - 'value' + * - 'time' + * - 'log' + * @type {string} + */ + this.type = 'value'; + + this.angle = 0; + + /** + * Indicator name + * @type {string} + */ + this.name = ''; + /** + * @type {module:echarts/model/Model} + */ + this.model; +} + +inherits(IndicatorAxis, Axis); + +/* +* 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. +*/ + +// TODO clockwise + +function Radar(radarModel, ecModel, api) { + + this._model = radarModel; + /** + * Radar dimensions + * @type {Array.} + */ + this.dimensions = []; + + this._indicatorAxes = map(radarModel.getIndicatorModels(), function (indicatorModel, idx) { + var dim = 'indicator_' + idx; + var indicatorAxis = new IndicatorAxis(dim, + (indicatorModel.get('axisType') === 'log') ? new LogScale() : new IntervalScale()); + indicatorAxis.name = indicatorModel.get('name'); + // Inject model and axis + indicatorAxis.model = indicatorModel; + indicatorModel.axis = indicatorAxis; + this.dimensions.push(dim); + return indicatorAxis; + }, this); + + this.resize(radarModel, api); + + /** + * @type {number} + * @readOnly + */ + this.cx; + /** + * @type {number} + * @readOnly + */ + this.cy; + /** + * @type {number} + * @readOnly + */ + this.r; + /** + * @type {number} + * @readOnly + */ + this.r0; + /** + * @type {number} + * @readOnly + */ + this.startAngle; +} + +Radar.prototype.getIndicatorAxes = function () { + return this._indicatorAxes; +}; + +Radar.prototype.dataToPoint = function (value, indicatorIndex) { + var indicatorAxis = this._indicatorAxes[indicatorIndex]; + + return this.coordToPoint(indicatorAxis.dataToCoord(value), indicatorIndex); +}; + +Radar.prototype.coordToPoint = function (coord, indicatorIndex) { + var indicatorAxis = this._indicatorAxes[indicatorIndex]; + var angle = indicatorAxis.angle; + var x = this.cx + coord * Math.cos(angle); + var y = this.cy - coord * Math.sin(angle); + return [x, y]; +}; + +Radar.prototype.pointToData = function (pt) { + var dx = pt[0] - this.cx; + var dy = pt[1] - this.cy; + var radius = Math.sqrt(dx * dx + dy * dy); + dx /= radius; + dy /= radius; + + var radian = Math.atan2(-dy, dx); + + // Find the closest angle + // FIXME index can calculated directly + var minRadianDiff = Infinity; + var closestAxis; + var closestAxisIdx = -1; + for (var i = 0; i < this._indicatorAxes.length; i++) { + var indicatorAxis = this._indicatorAxes[i]; + var diff = Math.abs(radian - indicatorAxis.angle); + if (diff < minRadianDiff) { + closestAxis = indicatorAxis; + closestAxisIdx = i; + minRadianDiff = diff; + } + } + + return [closestAxisIdx, +(closestAxis && closestAxis.coordToData(radius))]; +}; + +Radar.prototype.resize = function (radarModel, api) { + var center = radarModel.get('center'); + var viewWidth = api.getWidth(); + var viewHeight = api.getHeight(); + var viewSize = Math.min(viewWidth, viewHeight) / 2; + this.cx = parsePercent$1(center[0], viewWidth); + this.cy = parsePercent$1(center[1], viewHeight); + + this.startAngle = radarModel.get('startAngle') * Math.PI / 180; + + // radius may be single value like `20`, `'80%'`, or array like `[10, '80%']` + var radius = radarModel.get('radius'); + if (typeof radius === 'string' || typeof radius === 'number') { + radius = [0, radius]; + } + this.r0 = parsePercent$1(radius[0], viewSize); + this.r = parsePercent$1(radius[1], viewSize); + + each$1(this._indicatorAxes, function (indicatorAxis, idx) { + indicatorAxis.setExtent(this.r0, this.r); + var angle = (this.startAngle + idx * Math.PI * 2 / this._indicatorAxes.length); + // Normalize to [-PI, PI] + angle = Math.atan2(Math.sin(angle), Math.cos(angle)); + indicatorAxis.angle = angle; + }, this); +}; + +Radar.prototype.update = function (ecModel, api) { + var indicatorAxes = this._indicatorAxes; + var radarModel = this._model; + each$1(indicatorAxes, function (indicatorAxis) { + indicatorAxis.scale.setExtent(Infinity, -Infinity); + }); + ecModel.eachSeriesByType('radar', function (radarSeries, idx) { + if (radarSeries.get('coordinateSystem') !== 'radar' + || ecModel.getComponent('radar', radarSeries.get('radarIndex')) !== radarModel + ) { + return; + } + var data = radarSeries.getData(); + each$1(indicatorAxes, function (indicatorAxis) { + indicatorAxis.scale.unionExtentFromData(data, data.mapDimension(indicatorAxis.dim)); + }); + }, this); + + var splitNumber = radarModel.get('splitNumber'); + + function increaseInterval(interval) { + var exp10 = Math.pow(10, Math.floor(Math.log(interval) / Math.LN10)); + // Increase interval + var f = interval / exp10; + if (f === 2) { + f = 5; + } + else { // f is 2 or 5 + f *= 2; + } + return f * exp10; + } + // Force all the axis fixing the maxSplitNumber. + each$1(indicatorAxes, function (indicatorAxis, idx) { + var rawExtent = getScaleExtent(indicatorAxis.scale, indicatorAxis.model); + niceScaleExtent(indicatorAxis.scale, indicatorAxis.model); + + var axisModel = indicatorAxis.model; + var scale = indicatorAxis.scale; + var fixedMin = axisModel.getMin(); + var fixedMax = axisModel.getMax(); + var interval = scale.getInterval(); + + if (fixedMin != null && fixedMax != null) { + // User set min, max, divide to get new interval + scale.setExtent(+fixedMin, +fixedMax); + scale.setInterval( + (fixedMax - fixedMin) / splitNumber + ); + } + else if (fixedMin != null) { + var max; + // User set min, expand extent on the other side + do { + max = fixedMin + interval * splitNumber; + scale.setExtent(+fixedMin, max); + // Interval must been set after extent + // FIXME + scale.setInterval(interval); + + interval = increaseInterval(interval); + } while (max < rawExtent[1] && isFinite(max) && isFinite(rawExtent[1])); + } + else if (fixedMax != null) { + var min; + // User set min, expand extent on the other side + do { + min = fixedMax - interval * splitNumber; + scale.setExtent(min, +fixedMax); + scale.setInterval(interval); + interval = increaseInterval(interval); + } while (min > rawExtent[0] && isFinite(min) && isFinite(rawExtent[0])); + } + else { + var nicedSplitNumber = scale.getTicks().length - 1; + if (nicedSplitNumber > splitNumber) { + interval = increaseInterval(interval); + } + // PENDING + var center = Math.round((rawExtent[0] + rawExtent[1]) / 2 / interval) * interval; + var halfSplitNumber = Math.round(splitNumber / 2); + scale.setExtent( + round$1(center - halfSplitNumber * interval), + round$1(center + (splitNumber - halfSplitNumber) * interval) + ); + scale.setInterval(interval); + } + }); +}; + +/** + * Radar dimensions is based on the data + * @type {Array} + */ +Radar.dimensions = []; + +Radar.create = function (ecModel, api) { + var radarList = []; + ecModel.eachComponent('radar', function (radarModel) { + var radar = new Radar(radarModel, ecModel, api); + radarList.push(radar); + radarModel.coordinateSystem = radar; + }); + ecModel.eachSeriesByType('radar', function (radarSeries) { + if (radarSeries.get('coordinateSystem') === 'radar') { + // Inject coordinate system + radarSeries.coordinateSystem = radarList[radarSeries.get('radarIndex') || 0]; + } + }); + return radarList; +}; + +CoordinateSystemManager.register('radar', Radar); + +/* +* 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. +*/ + +var valueAxisDefault = axisDefault.valueAxis; + +function defaultsShow(opt, show) { + return defaults({ + show: show + }, opt); +} + +var RadarModel = extendComponentModel({ + + type: 'radar', + + optionUpdated: function () { + var boundaryGap = this.get('boundaryGap'); + var splitNumber = this.get('splitNumber'); + var scale = this.get('scale'); + var axisLine = this.get('axisLine'); + var axisTick = this.get('axisTick'); + var axisType = this.get('axisType'); + var axisLabel = this.get('axisLabel'); + var nameTextStyle = this.get('name'); + var showName = this.get('name.show'); + var nameFormatter = this.get('name.formatter'); + var nameGap = this.get('nameGap'); + var triggerEvent = this.get('triggerEvent'); + + var indicatorModels = map(this.get('indicator') || [], function (indicatorOpt) { + // PENDING + if (indicatorOpt.max != null && indicatorOpt.max > 0 && !indicatorOpt.min) { + indicatorOpt.min = 0; + } + else if (indicatorOpt.min != null && indicatorOpt.min < 0 && !indicatorOpt.max) { + indicatorOpt.max = 0; + } + var iNameTextStyle = nameTextStyle; + if (indicatorOpt.color != null) { + iNameTextStyle = defaults({color: indicatorOpt.color}, nameTextStyle); + } + // Use same configuration + indicatorOpt = merge(clone(indicatorOpt), { + boundaryGap: boundaryGap, + splitNumber: splitNumber, + scale: scale, + axisLine: axisLine, + axisTick: axisTick, + axisType: axisType, + axisLabel: axisLabel, + // Compatible with 2 and use text + name: indicatorOpt.text, + nameLocation: 'end', + nameGap: nameGap, + // min: 0, + nameTextStyle: iNameTextStyle, + triggerEvent: triggerEvent + }, false); + if (!showName) { + indicatorOpt.name = ''; + } + if (typeof nameFormatter === 'string') { + var indName = indicatorOpt.name; + indicatorOpt.name = nameFormatter.replace('{value}', indName != null ? indName : ''); + } + else if (typeof nameFormatter === 'function') { + indicatorOpt.name = nameFormatter( + indicatorOpt.name, indicatorOpt + ); + } + var model = extend( + new Model(indicatorOpt, null, this.ecModel), + axisModelCommonMixin + ); + + // For triggerEvent. + model.mainType = 'radar'; + model.componentIndex = this.componentIndex; + + return model; + }, this); + + this.getIndicatorModels = function () { + return indicatorModels; + }; + }, + + defaultOption: { + + zlevel: 0, + + z: 0, + + center: ['50%', '50%'], + + radius: '75%', + + startAngle: 90, + + name: { + show: true + // formatter: null + // textStyle: {} + }, + + boundaryGap: [0, 0], + + splitNumber: 5, + + nameGap: 15, + + scale: false, + + // Polygon or circle + shape: 'polygon', + + axisLine: merge( + { + lineStyle: { + color: '#bbb' + } + }, + valueAxisDefault.axisLine + ), + axisLabel: defaultsShow(valueAxisDefault.axisLabel, false), + axisTick: defaultsShow(valueAxisDefault.axisTick, false), + axisType: 'interval', + splitLine: defaultsShow(valueAxisDefault.splitLine, true), + splitArea: defaultsShow(valueAxisDefault.splitArea, true), + + // {text, min, max} + indicator: [] + } +}); + +/* +* 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. +*/ + +var axisBuilderAttrs$1 = [ + 'axisLine', 'axisTickLabel', 'axisName' +]; + +extendComponentView({ + + type: 'radar', + + render: function (radarModel, ecModel, api) { + var group = this.group; + group.removeAll(); + + this._buildAxes(radarModel); + this._buildSplitLineAndArea(radarModel); + }, + + _buildAxes: function (radarModel) { + var radar = radarModel.coordinateSystem; + var indicatorAxes = radar.getIndicatorAxes(); + var axisBuilders = map(indicatorAxes, function (indicatorAxis) { + var axisBuilder = new AxisBuilder(indicatorAxis.model, { + position: [radar.cx, radar.cy], + rotation: indicatorAxis.angle, + labelDirection: -1, + tickDirection: -1, + nameDirection: 1 + }); + return axisBuilder; + }); + + each$1(axisBuilders, function (axisBuilder) { + each$1(axisBuilderAttrs$1, axisBuilder.add, axisBuilder); + this.group.add(axisBuilder.getGroup()); + }, this); + }, + + _buildSplitLineAndArea: function (radarModel) { + var radar = radarModel.coordinateSystem; + var indicatorAxes = radar.getIndicatorAxes(); + if (!indicatorAxes.length) { + return; + } + var shape = radarModel.get('shape'); + var splitLineModel = radarModel.getModel('splitLine'); + var splitAreaModel = radarModel.getModel('splitArea'); + var lineStyleModel = splitLineModel.getModel('lineStyle'); + var areaStyleModel = splitAreaModel.getModel('areaStyle'); + + var showSplitLine = splitLineModel.get('show'); + var showSplitArea = splitAreaModel.get('show'); + var splitLineColors = lineStyleModel.get('color'); + var splitAreaColors = areaStyleModel.get('color'); + + splitLineColors = isArray(splitLineColors) ? splitLineColors : [splitLineColors]; + splitAreaColors = isArray(splitAreaColors) ? splitAreaColors : [splitAreaColors]; + + var splitLines = []; + var splitAreas = []; + + function getColorIndex(areaOrLine, areaOrLineColorList, idx) { + var colorIndex = idx % areaOrLineColorList.length; + areaOrLine[colorIndex] = areaOrLine[colorIndex] || []; + return colorIndex; + } + + if (shape === 'circle') { + var ticksRadius = indicatorAxes[0].getTicksCoords(); + var cx = radar.cx; + var cy = radar.cy; + for (var i = 0; i < ticksRadius.length; i++) { + if (showSplitLine) { + var colorIndex = getColorIndex(splitLines, splitLineColors, i); + splitLines[colorIndex].push(new Circle({ + shape: { + cx: cx, + cy: cy, + r: ticksRadius[i].coord + } + })); + } + if (showSplitArea && i < ticksRadius.length - 1) { + var colorIndex = getColorIndex(splitAreas, splitAreaColors, i); + splitAreas[colorIndex].push(new Ring({ + shape: { + cx: cx, + cy: cy, + r0: ticksRadius[i].coord, + r: ticksRadius[i + 1].coord + } + })); + } + } + } + // Polyyon + else { + var realSplitNumber; + var axesTicksPoints = map(indicatorAxes, function (indicatorAxis, idx) { + var ticksCoords = indicatorAxis.getTicksCoords(); + realSplitNumber = realSplitNumber == null + ? ticksCoords.length - 1 + : Math.min(ticksCoords.length - 1, realSplitNumber); + return map(ticksCoords, function (tickCoord) { + return radar.coordToPoint(tickCoord.coord, idx); + }); + }); + + var prevPoints = []; + for (var i = 0; i <= realSplitNumber; i++) { + var points = []; + for (var j = 0; j < indicatorAxes.length; j++) { + points.push(axesTicksPoints[j][i]); + } + // Close + if (points[0]) { + points.push(points[0].slice()); + } + else { + if (__DEV__) { + console.error('Can\'t draw value axis ' + i); + } + } + + if (showSplitLine) { + var colorIndex = getColorIndex(splitLines, splitLineColors, i); + splitLines[colorIndex].push(new Polyline({ + shape: { + points: points + } + })); + } + if (showSplitArea && prevPoints) { + var colorIndex = getColorIndex(splitAreas, splitAreaColors, i - 1); + splitAreas[colorIndex].push(new Polygon({ + shape: { + points: points.concat(prevPoints) + } + })); + } + prevPoints = points.slice().reverse(); + } + } + + var lineStyle = lineStyleModel.getLineStyle(); + var areaStyle = areaStyleModel.getAreaStyle(); + // Add splitArea before splitLine + each$1(splitAreas, function (splitAreas, idx) { + this.group.add(mergePath( + splitAreas, { + style: defaults({ + stroke: 'none', + fill: splitAreaColors[idx % splitAreaColors.length] + }, areaStyle), + silent: true + } + )); + }, this); + + each$1(splitLines, function (splitLines, idx) { + this.group.add(mergePath( + splitLines, { + style: defaults({ + fill: 'none', + stroke: splitLineColors[idx % splitLineColors.length] + }, lineStyle), + silent: true + } + )); + }, this); + + } +}); + +/* +* 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. +*/ + +/* +* 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. +*/ + +var RadarSeries = SeriesModel.extend({ + + type: 'series.radar', + + dependencies: ['radar'], + + + // Overwrite + init: function (option) { + RadarSeries.superApply(this, 'init', arguments); + + // Enable legend selection for each data item + // Use a function instead of direct access because data reference may changed + this.legendVisualProvider = new LegendVisualProvider( + bind(this.getData, this), bind(this.getRawData, this) + ); + + }, + + getInitialData: function (option, ecModel) { + return createListSimply(this, { + generateCoord: 'indicator_', + generateCoordCount: Infinity + }); + }, + + formatTooltip: function (dataIndex) { + var data = this.getData(); + var coordSys = this.coordinateSystem; + var indicatorAxes = coordSys.getIndicatorAxes(); + var name = this.getData().getName(dataIndex); + return encodeHTML(name === '' ? this.name : name) + '
    ' + + map(indicatorAxes, function (axis, idx) { + var val = data.get(data.mapDimension(axis.dim), dataIndex); + return encodeHTML(axis.name + ' : ' + val); + }).join('
    '); + }, + + defaultOption: { + zlevel: 0, + z: 2, + coordinateSystem: 'radar', + legendHoverLink: true, + radarIndex: 0, + lineStyle: { + width: 2, + type: 'solid' + }, + label: { + position: 'top' + }, + // areaStyle: { + // }, + // itemStyle: {} + symbol: 'emptyCircle', + symbolSize: 4 + // symbolRotate: null + } +}); + +/* +* 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. +*/ + +function normalizeSymbolSize(symbolSize) { + if (!isArray(symbolSize)) { + symbolSize = [+symbolSize, +symbolSize]; + } + return symbolSize; +} + +extendChartView({ + + type: 'radar', + + render: function (seriesModel, ecModel, api) { + var polar = seriesModel.coordinateSystem; + var group = this.group; + + var data = seriesModel.getData(); + var oldData = this._data; + + function createSymbol$$1(data, idx) { + var symbolType = data.getItemVisual(idx, 'symbol') || 'circle'; + var color = data.getItemVisual(idx, 'color'); + if (symbolType === 'none') { + return; + } + var symbolSize = normalizeSymbolSize( + data.getItemVisual(idx, 'symbolSize') + ); + var symbolPath = createSymbol( + symbolType, -1, -1, 2, 2, color + ); + symbolPath.attr({ + style: { + strokeNoScale: true + }, + z2: 100, + scale: [symbolSize[0] / 2, symbolSize[1] / 2] + }); + return symbolPath; + } + + function updateSymbols(oldPoints, newPoints, symbolGroup, data, idx, isInit) { + // Simply rerender all + symbolGroup.removeAll(); + for (var i = 0; i < newPoints.length - 1; i++) { + var symbolPath = createSymbol$$1(data, idx); + if (symbolPath) { + symbolPath.__dimIdx = i; + if (oldPoints[i]) { + symbolPath.attr('position', oldPoints[i]); + graphic[isInit ? 'initProps' : 'updateProps']( + symbolPath, { + position: newPoints[i] + }, seriesModel, idx + ); + } + else { + symbolPath.attr('position', newPoints[i]); + } + symbolGroup.add(symbolPath); + } + } + } + + function getInitialPoints(points) { + return map(points, function (pt) { + return [polar.cx, polar.cy]; + }); + } + data.diff(oldData) + .add(function (idx) { + var points = data.getItemLayout(idx); + if (!points) { + return; + } + var polygon = new Polygon(); + var polyline = new Polyline(); + var target = { + shape: { + points: points + } + }; + + polygon.shape.points = getInitialPoints(points); + polyline.shape.points = getInitialPoints(points); + initProps(polygon, target, seriesModel, idx); + initProps(polyline, target, seriesModel, idx); + + var itemGroup = new Group(); + var symbolGroup = new Group(); + itemGroup.add(polyline); + itemGroup.add(polygon); + itemGroup.add(symbolGroup); + + updateSymbols( + polyline.shape.points, points, symbolGroup, data, idx, true + ); + + data.setItemGraphicEl(idx, itemGroup); + }) + .update(function (newIdx, oldIdx) { + var itemGroup = oldData.getItemGraphicEl(oldIdx); + var polyline = itemGroup.childAt(0); + var polygon = itemGroup.childAt(1); + var symbolGroup = itemGroup.childAt(2); + var target = { + shape: { + points: data.getItemLayout(newIdx) + } + }; + + if (!target.shape.points) { + return; + } + updateSymbols( + polyline.shape.points, target.shape.points, symbolGroup, data, newIdx, false + ); + + updateProps(polyline, target, seriesModel); + updateProps(polygon, target, seriesModel); + + data.setItemGraphicEl(newIdx, itemGroup); + }) + .remove(function (idx) { + group.remove(oldData.getItemGraphicEl(idx)); + }) + .execute(); + + data.eachItemGraphicEl(function (itemGroup, idx) { + var itemModel = data.getItemModel(idx); + var polyline = itemGroup.childAt(0); + var polygon = itemGroup.childAt(1); + var symbolGroup = itemGroup.childAt(2); + var color = data.getItemVisual(idx, 'color'); + + group.add(itemGroup); + + polyline.useStyle( + defaults( + itemModel.getModel('lineStyle').getLineStyle(), + { + fill: 'none', + stroke: color + } + ) + ); + polyline.hoverStyle = itemModel.getModel('emphasis.lineStyle').getLineStyle(); + + var areaStyleModel = itemModel.getModel('areaStyle'); + var hoverAreaStyleModel = itemModel.getModel('emphasis.areaStyle'); + var polygonIgnore = areaStyleModel.isEmpty() && areaStyleModel.parentModel.isEmpty(); + var hoverPolygonIgnore = hoverAreaStyleModel.isEmpty() && hoverAreaStyleModel.parentModel.isEmpty(); + + hoverPolygonIgnore = hoverPolygonIgnore && polygonIgnore; + polygon.ignore = polygonIgnore; + + polygon.useStyle( + defaults( + areaStyleModel.getAreaStyle(), + { + fill: color, + opacity: 0.7 + } + ) + ); + polygon.hoverStyle = hoverAreaStyleModel.getAreaStyle(); + + var itemStyle = itemModel.getModel('itemStyle').getItemStyle(['color']); + var itemHoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle(); + var labelModel = itemModel.getModel('label'); + var labelHoverModel = itemModel.getModel('emphasis.label'); + symbolGroup.eachChild(function (symbolPath) { + symbolPath.setStyle(itemStyle); + symbolPath.hoverStyle = clone(itemHoverStyle); + var defaultText = data.get(data.dimensions[symbolPath.__dimIdx], idx); + (defaultText == null || isNaN(defaultText)) && (defaultText = ''); + + setLabelStyle( + symbolPath.style, symbolPath.hoverStyle, labelModel, labelHoverModel, + { + labelFetcher: data.hostModel, + labelDataIndex: idx, + labelDimIndex: symbolPath.__dimIdx, + defaultText: defaultText, + autoColor: color, + isRectText: true + } + ); + }); + + itemGroup.highDownOnUpdate = function (fromState, toState) { + polygon.attr('ignore', toState === 'emphasis' ? hoverPolygonIgnore : polygonIgnore); + }; + setHoverStyle(itemGroup); + }); + + this._data = data; + }, + + remove: function () { + this.group.removeAll(); + this._data = null; + }, + + dispose: function () {} +}); + +/* +* 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. +*/ + +var radarLayout = function (ecModel) { + ecModel.eachSeriesByType('radar', function (seriesModel) { + var data = seriesModel.getData(); + var points = []; + var coordSys = seriesModel.coordinateSystem; + if (!coordSys) { + return; + } + + var axes = coordSys.getIndicatorAxes(); + + each$1(axes, function (axis, axisIndex) { + data.each(data.mapDimension(axes[axisIndex].dim), function (val, dataIndex) { + points[dataIndex] = points[dataIndex] || []; + var point = coordSys.dataToPoint(val, axisIndex); + points[dataIndex][axisIndex] = isValidPoint(point) + ? point : getValueMissingPoint(coordSys); + }); + }); + + // Close polygon + data.each(function (idx) { + // TODO + // Is it appropriate to connect to the next data when some data is missing? + // Or, should trade it like `connectNull` in line chart? + var firstPoint = find(points[idx], function (point) { + return isValidPoint(point); + }) || getValueMissingPoint(coordSys); + + // Copy the first actual point to the end of the array + points[idx].push(firstPoint.slice()); + data.setItemLayout(idx, points[idx]); + }); + }); +}; + +function isValidPoint(point) { + return !isNaN(point[0]) && !isNaN(point[1]); +} + +function getValueMissingPoint(coordSys) { + // It is error-prone to input [NaN, NaN] into polygon, polygon. + // (probably cause problem when refreshing or animating) + return [coordSys.cx, coordSys.cy]; +} + +/* +* 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. +*/ + +// Backward compat for radar chart in 2 +var backwardCompat$1 = function (option) { + var polarOptArr = option.polar; + if (polarOptArr) { + if (!isArray(polarOptArr)) { + polarOptArr = [polarOptArr]; + } + var polarNotRadar = []; + each$1(polarOptArr, function (polarOpt, idx) { + if (polarOpt.indicator) { + if (polarOpt.type && !polarOpt.shape) { + polarOpt.shape = polarOpt.type; + } + option.radar = option.radar || []; + if (!isArray(option.radar)) { + option.radar = [option.radar]; + } + option.radar.push(polarOpt); + } + else { + polarNotRadar.push(polarOpt); + } + }); + option.polar = polarNotRadar; + } + each$1(option.series, function (seriesOpt) { + if (seriesOpt && seriesOpt.type === 'radar' && seriesOpt.polarIndex) { + seriesOpt.radarIndex = seriesOpt.polarIndex; + } + }); +}; + +/* +* 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. +*/ + + +// Must use radar component +registerVisual(dataColor('radar')); +registerVisual(visualSymbol('radar', 'circle')); +registerLayout(radarLayout); +registerProcessor(dataFilter('radar')); +registerPreprocessor(backwardCompat$1); + +/* +* 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. +*/ + +// Fix for 南海诸岛 + +var geoCoord = [126, 25]; + +var points$1 = [ + [[0, 3.5], [7, 11.2], [15, 11.9], [30, 7], [42, 0.7], [52, 0.7], + [56, 7.7], [59, 0.7], [64, 0.7], [64, 0], [5, 0], [0, 3.5]], + [[13, 16.1], [19, 14.7], [16, 21.7], [11, 23.1], [13, 16.1]], + [[12, 32.2], [14, 38.5], [15, 38.5], [13, 32.2], [12, 32.2]], + [[16, 47.6], [12, 53.2], [13, 53.2], [18, 47.6], [16, 47.6]], + [[6, 64.4], [8, 70], [9, 70], [8, 64.4], [6, 64.4]], + [[23, 82.6], [29, 79.8], [30, 79.8], [25, 82.6], [23, 82.6]], + [[37, 70.7], [43, 62.3], [44, 62.3], [39, 70.7], [37, 70.7]], + [[48, 51.1], [51, 45.5], [53, 45.5], [50, 51.1], [48, 51.1]], + [[51, 35], [51, 28.7], [53, 28.7], [53, 35], [51, 35]], + [[52, 22.4], [55, 17.5], [56, 17.5], [53, 22.4], [52, 22.4]], + [[58, 12.6], [62, 7], [63, 7], [60, 12.6], [58, 12.6]], + [[0, 3.5], [0, 93.1], [64, 93.1], [64, 0], [63, 0], [63, 92.4], + [1, 92.4], [1, 3.5], [0, 3.5]] +]; + +for (var i$1 = 0; i$1 < points$1.length; i$1++) { + for (var k = 0; k < points$1[i$1].length; k++) { + points$1[i$1][k][0] /= 10.5; + points$1[i$1][k][1] /= -10.5 / 0.75; + + points$1[i$1][k][0] += geoCoord[0]; + points$1[i$1][k][1] += geoCoord[1]; + } +} + +var fixNanhai = function (mapType, regions) { + if (mapType === 'china') { + regions.push(new Region( + '南海诸岛', + map(points$1, function (exterior) { + return { + type: 'polygon', + exterior: exterior + }; + }), geoCoord + )); + } +}; + +/* +* 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. +*/ + +var coordsOffsetMap = { + '南海诸岛': [32, 80], + // 全国 + '广东': [0, -10], + '香港': [10, 5], + '澳门': [-10, 10], + //'北京': [-10, 0], + '天津': [5, 5] +}; + +var fixTextCoord = 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); + } + } +}; + +/* +* 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. +*/ + +var geoCoordMap = { + 'Russia': [100, 60], + 'United States': [-99, 38], + 'United States of America': [-99, 38] +}; + +var fixGeoCoord = 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]; + } + } +}; + +/* +* 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. +*/ + +// Fix for 钓鱼岛 + +// var Region = require('../Region'); +// var zrUtil = require('zrender/src/core/util'); + +// var geoCoord = [126, 25]; + +var points$2 = [ + [ + [123.45165252685547, 25.73527164402261], + [123.49731445312499, 25.73527164402261], + [123.49731445312499, 25.750734064600884], + [123.45165252685547, 25.750734064600884], + [123.45165252685547, 25.73527164402261] + ] +]; + +var fixDiaoyuIsland = function (mapType, region) { + if (mapType === 'china' && region.name === '台湾') { + region.geometries.push({ + type: 'polygon', + exterior: points$2[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. +*/ + +// Built-in GEO fixer. +var inner$7 = makeInner(); + +var geoJSONLoader = { + + /** + * @param {string} mapName + * @param {Object} mapRecord {specialAreas, geoJSON} + * @return {Object} {regions, boundingRect} + */ + load: function (mapName, mapRecord) { + + var parsed = inner$7(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$1(geoJSON) : []; + } + catch (e) { + throw new Error('Invalid geoJson format\n' + e.message); + } + + fixNanhai(mapName, regions); + + each$1(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 + ); + } + }); + + return (inner$7(mapRecord).parsed = { + regions: regions, + boundingRect: getBoundingRect$1(regions) + }); + } +}; + +function getBoundingRect$1(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; +} + +/* +* 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. +*/ + +var inner$8 = makeInner(); + +var geoSVGLoader = { + + /** + * @param {string} mapName + * @param {Object} mapRecord {specialAreas, geoJSON} + * @return {Object} {root, boundingRect} + */ + load: function (mapName, mapRecord) { + var originRoot = inner$8(mapRecord).originRoot; + if (originRoot) { + return { + root: originRoot, + boundingRect: inner$8(mapRecord).boundingRect + }; + } + + var graphic = buildGraphic(mapRecord); + + inner$8(mapRecord).originRoot = graphic.root; + inner$8(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$8(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$8(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$1(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 + }; +} + +/* +* 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. +*/ + +var loaders = { + geoJSON: geoJSONLoader, + svg: geoSVGLoader +}; + +var geoSourceManager = { + + /** + * @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$1(mapRecords, function (record) { + var singleSource = loaders[record.type].load(mapName, record); + + each$1(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$1(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. The GeoJSON of the map must be provided.' + ); + } +} + +function retrieveMap(mapName) { + var mapRecords = mapDataStorage.retrieveMap(mapName) || []; + + if (__DEV__) { + if (!mapRecords.length) { + mapNotExistsError(mapName); + } + } + + return mapRecords; +} + +/* +* 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. +*/ + +var MapSeries = SeriesModel.extend({ + + type: 'series.map', + + dependencies: ['geo'], + + layoutMode: 'box', + + /** + * Only first map series of same mapType will drawMap + * @type {boolean} + */ + needsDrawMap: false, + + /** + * Group of all map series with same mapType + * @type {boolean} + */ + seriesGroup: [], + + getInitialData: function (option) { + var data = createListSimply(this, { + coordDimensions: ['value'], + encodeDefaulter: curry(makeSeriesEncodeForNameBased, this) + }); + var valueDim = data.mapDimension('value'); + var dataNameMap = createHashMap(); + var selectTargetList = []; + var toAppendNames = []; + + for (var i = 0, len = data.count(); i < len; i++) { + var name = data.getName(i); + dataNameMap.set(name, true); + selectTargetList.push({ + name: name, + value: data.get(valueDim, i), + selected: retrieveRawAttr(data, i, 'selected') + }); + } + + var geoSource = geoSourceManager.load(this.getMapType(), this.option.nameMap); + each$1(geoSource.regions, function (region) { + var name = region.name; + if (!dataNameMap.get(name)) { + selectTargetList.push({name: name}); + toAppendNames.push(name); + } + }); + + this.updateSelectedMap(selectTargetList); + + // Complete data with missing regions. The consequent processes (like visual + // map and render) can not be performed without a "full data". For example, + // find `dataIndex` by name. + data.appendValues([], toAppendNames); + + return data; + }, + + /** + * If no host geo model, return null, which means using a + * inner exclusive geo model. + */ + getHostGeoModel: function () { + var geoIndex = this.option.geoIndex; + return geoIndex != null + ? this.dependentModels.geo[geoIndex] + : null; + }, + + getMapType: function () { + return (this.getHostGeoModel() || this).option.map; + }, + + // _fillOption: function (option, mapName) { + // Shallow clone + // option = zrUtil.extend({}, option); + + // option.data = geoCreator.getFilledRegions(option.data, mapName, option.nameMap); + + // return option; + // }, + + getRawValue: function (dataIndex) { + // Use value stored in data instead because it is calculated from multiple series + // FIXME Provide all value of multiple series ? + var data = this.getData(); + return data.get(data.mapDimension('value'), dataIndex); + }, + + /** + * Get model of region + * @param {string} name + * @return {module:echarts/model/Model} + */ + getRegionModel: function (regionName) { + var data = this.getData(); + return data.getItemModel(data.indexOfName(regionName)); + }, + + /** + * Map tooltip formatter + * + * @param {number} dataIndex + */ + formatTooltip: function (dataIndex) { + // FIXME orignalData and data is a bit confusing + var data = this.getData(); + var formattedValue = addCommas(this.getRawValue(dataIndex)); + var name = data.getName(dataIndex); + + var seriesGroup = this.seriesGroup; + var seriesNames = []; + for (var i = 0; i < seriesGroup.length; i++) { + var otherIndex = seriesGroup[i].originalData.indexOfName(name); + var valueDim = data.mapDimension('value'); + if (!isNaN(seriesGroup[i].originalData.get(valueDim, otherIndex))) { + seriesNames.push( + encodeHTML(seriesGroup[i].name) + ); + } + } + + return seriesNames.join(', ') + '
    ' + + encodeHTML(name + ' : ' + formattedValue); + }, + + /** + * @implement + */ + getTooltipPosition: function (dataIndex) { + if (dataIndex != null) { + var name = this.getData().getName(dataIndex); + var geo = this.coordinateSystem; + var region = geo.getRegion(name); + + return region && geo.dataToPoint(region.center); + } + }, + + setZoom: function (zoom) { + this.option.zoom = zoom; + }, + + setCenter: function (center) { + this.option.center = center; + }, + + defaultOption: { + // 一级层叠 + zlevel: 0, + // 二级层叠 + z: 2, + + coordinateSystem: 'geo', + + // map should be explicitly specified since ec3. + map: '', + + // If `geoIndex` is not specified, a exclusive geo will be + // created. Otherwise use the specified geo component, and + // `map` and `mapType` are ignored. + // geoIndex: 0, + + // 'center' | 'left' | 'right' | 'x%' | {number} + left: 'center', + // 'center' | 'top' | 'bottom' | 'x%' | {number} + top: 'center', + // right + // bottom + // width: + // height + + // Aspect is width / height. Inited to be geoJson bbox aspect + // This parameter is used for scale this aspect + aspectScale: 0.75, + + ///// Layout with center and size + // If you wan't to put map in a fixed size box with right aspect ratio + // This two properties may more conveninet + // layoutCenter: [50%, 50%] + // layoutSize: 100 + + + // 数值合并方式,默认加和,可选为: + // 'sum' | 'average' | 'max' | 'min' + // mapValueCalculation: 'sum', + // 地图数值计算结果小数精度 + // mapValuePrecision: 0, + + + // 显示图例颜色标识(系列标识的小圆点),图例开启时有效 + showLegendSymbol: true, + // 选择模式,默认关闭,可选single,multiple + // selectedMode: false, + dataRangeHoverLink: true, + // 是否开启缩放及漫游模式 + // roam: false, + + // Define left-top, right-bottom coords to control view + // For example, [ [180, 90], [-180, -90] ], + // higher priority than center and zoom + boundingCoords: null, + + // Default on center of map + center: null, + + zoom: 1, + + scaleLimit: null, + + label: { + show: false, + color: '#000' + }, + // scaleLimit: null, + itemStyle: { + borderWidth: 0.5, + borderColor: '#444', + areaColor: '#eee' + }, + + emphasis: { + label: { + show: true, + color: 'rgb(100,0,0)' + }, + itemStyle: { + areaColor: 'rgba(255,215,0,0.8)' + } + } + } + +}); + +mixin(MapSeries, selectableMixin); + +/* +* 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. +*/ + +var ATTR = '\0_ec_interaction_mutex'; + +function take(zr, resourceKey, userKey) { + var store = getStore(zr); + store[resourceKey] = userKey; +} + +function release(zr, resourceKey, userKey) { + var store = getStore(zr); + var uKey = store[resourceKey]; + + if (uKey === userKey) { + store[resourceKey] = null; + } +} + +function isTaken(zr, resourceKey) { + return !!getStore(zr)[resourceKey]; +} + +function getStore(zr) { + return zr[ATTR] || (zr[ATTR] = {}); +} + +/** + * payload: { + * type: 'takeGlobalCursor', + * key: 'dataZoomSelect', or 'brush', or ..., + * If no userKey, release global cursor. + * } + */ +registerAction( + {type: 'takeGlobalCursor', event: 'globalCursorTaken', update: 'update'}, + function () {} +); + +/* +* 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. +*/ + +/** + * @alias module:echarts/component/helper/RoamController + * @constructor + * @mixin {module:zrender/mixin/Eventful} + * + * @param {module:zrender/zrender~ZRender} zr + */ +function RoamController(zr) { + + /** + * @type {Function} + */ + this.pointerChecker; + + /** + * @type {module:zrender} + */ + this._zr = zr; + + /** + * @type {Object} + */ + this._opt = {}; + + // Avoid two roamController bind the same handler + var bind$$1 = bind; + var mousedownHandler = bind$$1(mousedown, this); + var mousemoveHandler = bind$$1(mousemove, this); + var mouseupHandler = bind$$1(mouseup, this); + var mousewheelHandler = bind$$1(mousewheel, this); + var pinchHandler = bind$$1(pinch, this); + + Eventful.call(this); + + /** + * @param {Function} pointerChecker + * input: x, y + * output: boolean + */ + this.setPointerChecker = function (pointerChecker) { + this.pointerChecker = pointerChecker; + }; + + /** + * Notice: only enable needed types. For example, if 'zoom' + * is not needed, 'zoom' should not be enabled, otherwise + * default mousewheel behaviour (scroll page) will be disabled. + * + * @param {boolean|string} [controlType=true] Specify the control type, + * which can be null/undefined or true/false + * or 'pan/move' or 'zoom'/'scale' + * @param {Object} [opt] + * @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) { + + // Disable previous first + this.disable(); + + this._opt = defaults(clone(opt) || {}, { + zoomOnMouseWheel: true, + moveOnMouseMove: true, + // By default, wheel do not trigger move. + moveOnMouseWheel: false, + preventDefaultMouseMove: true + }); + + if (controlType == null) { + controlType = true; + } + + if (controlType === true || (controlType === 'move' || controlType === 'pan')) { + zr.on('mousedown', mousedownHandler); + zr.on('mousemove', mousemoveHandler); + zr.on('mouseup', mouseupHandler); + } + if (controlType === true || (controlType === 'scale' || controlType === 'zoom')) { + zr.on('mousewheel', mousewheelHandler); + zr.on('pinch', pinchHandler); + } + }; + + this.disable = function () { + zr.off('mousedown', mousedownHandler); + zr.off('mousemove', mousemoveHandler); + zr.off('mouseup', mouseupHandler); + zr.off('mousewheel', mousewheelHandler); + zr.off('pinch', pinchHandler); + }; + + this.dispose = this.disable; + + this.isDragging = function () { + return this._dragging; + }; + + this.isPinching = function () { + return this._pinching; + }; +} + +mixin(RoamController, Eventful); + + +function mousedown(e) { + if (isMiddleOrRightButtonOnMouseUpDown(e) + || (e.target && e.target.draggable) + ) { + return; + } + + var x = e.offsetX; + var y = e.offsetY; + + // Only check on mosedown, but not mousemove. + // Mouse can be out of target when mouse moving. + if (this.pointerChecker && this.pointerChecker(e, x, y)) { + this._x = x; + this._y = y; + this._dragging = true; + } +} + +function mousemove(e) { + if (!this._dragging + || !isAvailableBehavior('moveOnMouseMove', e, this._opt) + || e.gestureEvent === 'pinch' + || isTaken(this._zr, 'globalPan') + ) { + return; + } + + var x = e.offsetX; + var y = e.offsetY; + + var oldX = this._x; + var oldY = this._y; + + var dx = x - oldX; + var dy = y - oldY; + + this._x = x; + this._y = y; + + this._opt.preventDefaultMouseMove && stop(e.event); + + trigger(this, 'pan', 'moveOnMouseMove', e, { + dx: dx, dy: dy, oldX: oldX, oldY: oldY, newX: x, newY: y + }); +} + +function mouseup(e) { + if (!isMiddleOrRightButtonOnMouseUpDown(e)) { + this._dragging = false; + } +} + +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 (wheelDelta === 0 || (!shouldZoom && !shouldMove)) { + return; + } + + // If both `shouldZoom` and `shouldMove` is true, trigger + // their event both, and the final behavior is determined + // by event listener themselves. + + 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 + }); + } + + 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 (isTaken(this._zr, 'globalPan')) { + return; + } + var scale = e.pinchScale > 1 ? 1.1 : 1 / 1.1; + checkPointerAndTrigger(this, 'zoom', null, e, { + scale: scale, originX: e.pinchX, originY: e.pinchY + }); +} + +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. + stop(e.event); + + trigger(controller, eventName, behaviorToCheck, e, contollerEvent); + } +} + +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 = 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 && (!isString(setting) || e.event[setting + 'Key']) + ); +} + +/* +* 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. +*/ + + +/** + * For geo and graph. + * + * @param {Object} controllerHost + * @param {module:zrender/Element} controllerHost.target + */ +function updateViewOnPan(controllerHost, dx, dy) { + var target = controllerHost.target; + var pos = target.position; + pos[0] += dx; + pos[1] += dy; + target.dirty(); +} + +/** + * For geo and graph. + * + * @param {Object} controllerHost + * @param {module:zrender/Element} controllerHost.target + * @param {number} controllerHost.zoom + * @param {number} controllerHost.zoomLimit like: {min: 1, max: 2} + */ +function updateViewOnZoom(controllerHost, zoomDelta, zoomX, zoomY) { + var target = controllerHost.target; + var zoomLimit = controllerHost.zoomLimit; + var pos = target.position; + var scale = target.scale; + + var newZoom = controllerHost.zoom = controllerHost.zoom || 1; + newZoom *= zoomDelta; + if (zoomLimit) { + var zoomMin = zoomLimit.min || 0; + var zoomMax = zoomLimit.max || Infinity; + newZoom = Math.max( + Math.min(zoomMax, newZoom), + zoomMin + ); + } + var zoomScale = newZoom / controllerHost.zoom; + controllerHost.zoom = newZoom; + // Keep the mouse center when scaling + pos[0] -= (zoomX - pos[0]) * (zoomScale - 1); + pos[1] -= (zoomY - pos[1]) * (zoomScale - 1); + scale[0] *= zoomScale; + scale[1] *= zoomScale; + + target.dirty(); +} + +/* +* 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. +*/ + + +var IRRELEVANT_EXCLUDES = {'axisPointer': 1, 'tooltip': 1, 'brush': 1}; + +/** + * Avoid that: mouse click on a elements that is over geo or graph, + * but roam is triggered. + */ +function onIrrelevantElement(e, api, targetCoordSysModel) { + var model = api.getComponentByElement(e.topTarget); + // If model is axisModel, it works only if it is injected with coordinateSystem. + var coordSys = model && model.coordinateSystem; + return model + && model !== targetCoordSysModel + && !IRRELEVANT_EXCLUDES[model.mainType] + && (coordSys && coordSys.model !== targetCoordSysModel); +} + +/* +* 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. +*/ + +function getFixedItemStyle(model) { + var itemStyle = model.getItemStyle(); + var areaColor = model.get('areaColor'); + + // If user want the color not to be changed when hover, + // they should both set areaColor and color to be null. + if (areaColor != null) { + itemStyle.fill = areaColor; + } + + return itemStyle; +} + +function updateMapSelectHandler(mapDraw, mapOrGeoModel, regionsGroup, api, fromView) { + regionsGroup.off('click'); + regionsGroup.off('mousedown'); + + if (mapOrGeoModel.get('selectedMode')) { + + regionsGroup.on('mousedown', function () { + mapDraw._mouseDownFlag = true; + }); + + regionsGroup.on('click', function (e) { + if (!mapDraw._mouseDownFlag) { + return; + } + mapDraw._mouseDownFlag = false; + + var el = e.target; + while (!el.__regions) { + el = el.parent; + } + if (!el) { + return; + } + + var action = { + type: (mapOrGeoModel.mainType === 'geo' ? 'geo' : 'map') + 'ToggleSelect', + batch: map(el.__regions, function (region) { + return { + name: region.name, + from: fromView.uid + }; + }) + }; + action[mapOrGeoModel.mainType + 'Id'] = mapOrGeoModel.id; + + api.dispatchAction(action); + + updateMapSelected(mapOrGeoModel, regionsGroup); + }); + } +} + +function updateMapSelected(mapOrGeoModel, regionsGroup) { + // FIXME + regionsGroup.eachChild(function (otherRegionEl) { + each$1(otherRegionEl.__regions, function (region) { + otherRegionEl.trigger(mapOrGeoModel.isSelected(region.name) ? 'emphasis' : 'normal'); + }); + }); +} + +/** + * @alias module:echarts/component/helper/MapDraw + * @param {module:echarts/ExtensionAPI} api + * @param {boolean} updateGroup + */ +function MapDraw(api, updateGroup) { + + var group = new Group(); + + /** + * @type {string} + * @private + */ + this.uid = getUID('ec_map_draw'); + + /** + * @type {module:echarts/component/helper/RoamController} + * @private + */ + this._controller = new RoamController(api.getZr()); + + /** + * @type {Object} {target, zoom, zoomLimit} + * @private + */ + this._controllerHost = {target: updateGroup ? group : null}; + + /** + * @type {module:zrender/container/Group} + * @readOnly + */ + this.group = group; + + /** + * @type {boolean} + * @private + */ + this._updateGroup = updateGroup; + + /** + * This flag is used to make sure that only one among + * `pan`, `zoom`, `click` can occurs, otherwise 'selected' + * action may be triggered when `pan`, which is unexpected. + * @type {booelan} + */ + this._mouseDownFlag; + + /** + * @type {string} + */ + this._mapName; + + /** + * @type {boolean} + */ + this._initialized; + + /** + * @type {module:zrender/container/Group} + */ + group.add(this._regionsGroup = new Group()); + + /** + * @type {module:zrender/container/Group} + */ + group.add(this._backgroundGroup = new Group()); +} + +MapDraw.prototype = { + + constructor: MapDraw, + + draw: function (mapOrGeoModel, ecModel, api, fromView, payload) { + + var isGeo = mapOrGeoModel.mainType === 'geo'; + + // Map series has data. GEO model that controlled by map series + // will be assigned with map data. Other GEO model has no data. + var data = mapOrGeoModel.getData && mapOrGeoModel.getData(); + isGeo && ecModel.eachComponent({mainType: 'series', subType: 'map'}, function (mapSeries) { + if (!data && mapSeries.getHostGeoModel() === mapOrGeoModel) { + data = mapSeries.getData(); + } + }); + + var geo = mapOrGeoModel.coordinateSystem; + + this._updateBackground(geo); + + var regionsGroup = this._regionsGroup; + var group = this.group; + + if (geo._roamTransformable.transform) { + group.transform = geo._roamTransformable.transform.slice(); + group.decomposeTransform(); + } + + var scale = geo._rawTransformable.scale; + var position = geo._rawTransformable.position; + + regionsGroup.removeAll(); + + var itemStyleAccessPath = ['itemStyle']; + var hoverItemStyleAccessPath = ['emphasis', 'itemStyle']; + var labelAccessPath = ['label']; + var hoverLabelAccessPath = ['emphasis', 'label']; + var nameMap = createHashMap(); + + each$1(geo.regions, function (region) { + // Consider in GeoJson properties.name may be duplicated, for example, + // there is multiple region named "United Kindom" or "France" (so many + // colonies). And it is not appropriate to merge them in geo, which + // will make them share the same label and bring trouble in label + // location calculation. + var regionGroup = nameMap.get(region.name) + || nameMap.set(region.name, new Group()); + + var compoundPath = new CompoundPath({ + segmentIgnoreThreshold: 1, + shape: { + paths: [] + } + }); + regionGroup.add(compoundPath); + + var regionModel = mapOrGeoModel.getRegionModel(region.name) || mapOrGeoModel; + + var itemStyleModel = regionModel.getModel(itemStyleAccessPath); + var hoverItemStyleModel = regionModel.getModel(hoverItemStyleAccessPath); + var itemStyle = getFixedItemStyle(itemStyleModel); + var hoverItemStyle = getFixedItemStyle(hoverItemStyleModel); + + var labelModel = regionModel.getModel(labelAccessPath); + var hoverLabelModel = regionModel.getModel(hoverLabelAccessPath); + + var dataIdx; + // Use the itemStyle in data if has data + if (data) { + dataIdx = data.indexOfName(region.name); + // Only visual color of each item will be used. It can be encoded by dataRange + // But visual color of series is used in symbol drawing + // + // Visual color for each series is for the symbol draw + var visualColor = data.getItemVisual(dataIdx, 'color', true); + if (visualColor) { + itemStyle.fill = visualColor; + } + } + + var transformPoint = function (point) { + return [ + point[0] * scale[0] + position[0], + point[1] * scale[1] + position[1] + ]; + }; + + each$1(region.geometries, function (geometry) { + if (geometry.type !== 'polygon') { + return; + } + var points = []; + for (var i = 0; i < geometry.exterior.length; ++i) { + points.push(transformPoint(geometry.exterior[i])); + } + compoundPath.shape.paths.push(new Polygon({ + segmentIgnoreThreshold: 1, + shape: { + points: points + } + })); + + for (var i = 0; i < (geometry.interiors ? geometry.interiors.length : 0); ++i) { + var interior = geometry.interiors[i]; + var points = []; + for (var j = 0; j < interior.length; ++j) { + points.push(transformPoint(interior[j])); + } + compoundPath.shape.paths.push(new Polygon({ + segmentIgnoreThreshold: 1, + shape: { + points: points + } + })); + } + }); + + compoundPath.setStyle(itemStyle); + compoundPath.style.strokeNoScale = true; + compoundPath.culling = true; + + // Label + var showLabel = labelModel.get('show'); + var hoverShowLabel = hoverLabelModel.get('show'); + + var isDataNaN = data && isNaN(data.get(data.mapDimension('value'), dataIdx)); + var itemLayout = data && data.getItemLayout(dataIdx); + // In the following cases label will be drawn + // 1. In map series and data value is NaN + // 2. In geo component + // 4. Region has no series legendSymbol, which will be add a showLabel flag in mapSymbolLayout + if ( + (isGeo || isDataNaN && (showLabel || hoverShowLabel)) + || (itemLayout && itemLayout.showLabel) + ) { + var query = !isGeo ? dataIdx : region.name; + var labelFetcher; + + // Consider dataIdx not found. + if (!data || dataIdx >= 0) { + labelFetcher = mapOrGeoModel; + } + + var textEl = new Text({ + position: transformPoint(region.center.slice()), + // FIXME + // label rotation is not support yet in geo or regions of series-map + // that has no data. The rotation will be effected by this `scale`. + // So needed to change to RectText? + scale: [1 / group.scale[0], 1 / group.scale[1]], + z2: 10, + silent: true + }); + + setLabelStyle( + textEl.style, textEl.hoverStyle = {}, labelModel, hoverLabelModel, + { + labelFetcher: labelFetcher, + labelDataIndex: query, + defaultText: region.name, + useInsideStyle: false + }, + { + textAlign: 'center', + textVerticalAlign: 'middle' + } + ); + + regionGroup.add(textEl); + } + + // setItemGraphicEl, setHoverStyle after all polygons and labels + // are added to the rigionGroup + if (data) { + data.setItemGraphicEl(dataIdx, regionGroup); + } + else { + var regionModel = mapOrGeoModel.getRegionModel(region.name); + // Package custom mouse event for geo component + compoundPath.eventData = { + componentType: 'geo', + componentIndex: mapOrGeoModel.componentIndex, + geoIndex: mapOrGeoModel.componentIndex, + name: region.name, + region: (regionModel && regionModel.option) || {} + }; + } + + var groupRegions = regionGroup.__regions || (regionGroup.__regions = []); + groupRegions.push(region); + + regionGroup.highDownSilentOnTouch = !!mapOrGeoModel.get('selectedMode'); + setHoverStyle(regionGroup, hoverItemStyle); + + regionsGroup.add(regionGroup); + }); + + this._updateController(mapOrGeoModel, ecModel, api); + + updateMapSelectHandler(this, mapOrGeoModel, regionsGroup, api, fromView); + + updateMapSelected(mapOrGeoModel, regionsGroup); + }, + + remove: function () { + 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) { + each$1(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; + var controllerHost = this._controllerHost; + + controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit'); + controllerHost.zoom = geo.getZoom(); + + // roamType is will be set default true if it is null + controller.enable(mapOrGeoModel.get('roam') || false); + var mainType = mapOrGeoModel.mainType; + + function makeActionBase() { + var action = { + type: 'geoRoam', + componentType: mainType + }; + action[mainType + 'Id'] = mapOrGeoModel.id; + return action; + } + + controller.off('pan').on('pan', function (e) { + this._mouseDownFlag = false; + + updateViewOnPan(controllerHost, e.dx, e.dy); + + api.dispatchAction(extend(makeActionBase(), { + dx: e.dx, + dy: e.dy + })); + }, this); + + controller.off('zoom').on('zoom', function (e) { + this._mouseDownFlag = false; + + updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); + + api.dispatchAction(extend(makeActionBase(), { + zoom: e.scale, + originX: e.originX, + originY: e.originY + })); + + if (this._updateGroup) { + var scale = this.group.scale; + this._regionsGroup.traverse(function (el) { + if (el.type === 'text') { + el.attr('scale', [1 / scale[0], 1 / scale[1]]); + } + }); + } + }, this); + + controller.setPointerChecker(function (e, x, y) { + return geo.getViewRectAfterRoam().contain(x, y) + && !onIrrelevantElement(e, api, mapOrGeoModel); + }); + } +}; + +/* +* 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. +*/ + +var HIGH_DOWN_PROP = '__seriesMapHighDown'; +var RECORD_VERSION_PROP = '__seriesMapCallKey'; + +extendChartView({ + + type: 'map', + + render: function (mapModel, ecModel, api, payload) { + // Not render if it is an toggleSelect action from self + if (payload && payload.type === 'mapToggleSelect' + && payload.from === this.uid + ) { + return; + } + + var group = this.group; + group.removeAll(); + + if (mapModel.getHostGeoModel()) { + return; + } + + // Not update map if it is an roam action from self + if (!(payload && payload.type === 'geoRoam' + && payload.componentType === 'series' + && payload.seriesId === mapModel.id + ) + ) { + if (mapModel.needsDrawMap) { + var mapDraw = this._mapDraw || new MapDraw(api, true); + group.add(mapDraw.group); + + mapDraw.draw(mapModel, ecModel, api, this, payload); + + this._mapDraw = mapDraw; + } + else { + // Remove drawed map + this._mapDraw && this._mapDraw.remove(); + this._mapDraw = null; + } + } + else { + var mapDraw = this._mapDraw; + mapDraw && group.add(mapDraw.group); + } + + mapModel.get('showLegendSymbol') && ecModel.getComponent('legend') + && this._renderSymbols(mapModel, ecModel, api); + }, + + remove: function () { + this._mapDraw && this._mapDraw.remove(); + this._mapDraw = null; + this.group.removeAll(); + }, + + dispose: function () { + this._mapDraw && this._mapDraw.remove(); + this._mapDraw = null; + }, + + _renderSymbols: function (mapModel, ecModel, api) { + var originalData = mapModel.originalData; + var group = this.group; + + originalData.each(originalData.mapDimension('value'), function (value, originalDataIndex) { + if (isNaN(value)) { + return; + } + + var layout = originalData.getItemLayout(originalDataIndex); + + if (!layout || !layout.point) { + // Not exists in map + return; + } + + var point = layout.point; + var offset = layout.offset; + + var circle = new Circle({ + style: { + // Because the special of map draw. + // Which needs statistic of multiple series and draw on one map. + // And each series also need a symbol with legend color + // + // Layout and visual are put one the different data + fill: mapModel.getData().getVisual('color') + }, + shape: { + cx: point[0] + offset * 9, + cy: point[1], + r: 3 + }, + silent: true, + // Do not overlap the first series, on which labels are displayed. + z2: 8 + (!offset ? Z2_EMPHASIS_LIFT + 1 : 0) + }); + + // Only the series that has the first value on the same region is in charge of rendering the label. + // But consider the case: + // series: [ + // {id: 'X', type: 'map', map: 'm', {data: [{name: 'A', value: 11}, {name: 'B', {value: 22}]}, + // {id: 'Y', type: 'map', map: 'm', {data: [{name: 'A', value: 21}, {name: 'C', {value: 33}]} + // ] + // The offset `0` of item `A` is at series `X`, but of item `C` is at series `Y`. + // For backward compatibility, we follow the rule that render label `A` by the + // settings on series `X` but render label `C` by the settings on series `Y`. + if (!offset) { + + var fullData = mapModel.mainSeries.getData(); + var name = originalData.getName(originalDataIndex); + + var fullIndex = fullData.indexOfName(name); + + var itemModel = originalData.getItemModel(originalDataIndex); + var labelModel = itemModel.getModel('label'); + var hoverLabelModel = itemModel.getModel('emphasis.label'); + + var regionGroup = fullData.getItemGraphicEl(fullIndex); + + // `getFormattedLabel` needs to use `getData` inside. Here + // `mapModel.getData()` is shallow cloned from `mainSeries.getData()`. + // FIXME + // If this is not the `mainSeries`, the item model (like label formatter) + // set on original data item will never get. But it has been working + // like that from the begining, and this scenario is rarely encountered. + // So it won't be fixed until have to. + var normalText = retrieve2( + mapModel.getFormattedLabel(fullIndex, 'normal'), + name + ); + var emphasisText = retrieve2( + mapModel.getFormattedLabel(fullIndex, 'emphasis'), + normalText + ); + + var highDownRecord = regionGroup[HIGH_DOWN_PROP]; + var recordVersion = Math.random(); + + // Prevent from register listeners duplicatedly when roaming. + if (!highDownRecord) { + highDownRecord = regionGroup[HIGH_DOWN_PROP] = {}; + var onEmphasis = curry(onRegionHighDown, true); + var onNormal = curry(onRegionHighDown, false); + regionGroup.on('mouseover', onEmphasis) + .on('mouseout', onNormal) + .on('emphasis', onEmphasis) + .on('normal', onNormal); + } + + // Prevent removed regions effect current grapics. + regionGroup[RECORD_VERSION_PROP] = recordVersion; + extend(highDownRecord, { + recordVersion: recordVersion, + circle: circle, + labelModel: labelModel, + hoverLabelModel: hoverLabelModel, + emphasisText: emphasisText, + normalText: normalText + }); + + // FIXME + // Consider set option when emphasis. + enterRegionHighDown(highDownRecord, false); + } + + group.add(circle); + }); + } +}); + +function onRegionHighDown(toHighOrDown) { + var highDownRecord = this[HIGH_DOWN_PROP]; + if (highDownRecord && highDownRecord.recordVersion === this[RECORD_VERSION_PROP]) { + enterRegionHighDown(highDownRecord, toHighOrDown); + } +} + +function enterRegionHighDown(highDownRecord, toHighOrDown) { + var circle = highDownRecord.circle; + var labelModel = highDownRecord.labelModel; + var hoverLabelModel = highDownRecord.hoverLabelModel; + var emphasisText = highDownRecord.emphasisText; + var normalText = highDownRecord.normalText; + + if (toHighOrDown) { + circle.style.extendFrom( + setTextStyle({}, hoverLabelModel, { + text: hoverLabelModel.get('show') ? emphasisText : null + }, {isRectText: true, useInsideStyle: false}, true) + ); + // Make label upper than others if overlaps. + circle.__mapOriginalZ2 = circle.z2; + circle.z2 += Z2_EMPHASIS_LIFT; + } + else { + setTextStyle(circle.style, labelModel, { + text: labelModel.get('show') ? normalText : null, + textPosition: labelModel.getShallow('position') || 'bottom' + }, {isRectText: true, useInsideStyle: false}); + // Trigger normalize style like padding. + circle.dirty(false); + + if (circle.__mapOriginalZ2 != null) { + circle.z2 = circle.__mapOriginalZ2; + circle.__mapOriginalZ2 = null; + } + } +} + +/* +* 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. +*/ + +/** + * @param {module:echarts/coord/View} view + * @param {Object} payload + * @param {Object} [zoomLimit] + */ +function updateCenterAndZoom( + view, payload, zoomLimit +) { + var previousZoom = view.getZoom(); + var center = view.getCenter(); + var zoom = payload.zoom; + + var point = view.dataToPoint(center); + + if (payload.dx != null && payload.dy != null) { + point[0] -= payload.dx; + point[1] -= payload.dy; + + var center = view.pointToData(point); + view.setCenter(center); + } + if (zoom != null) { + if (zoomLimit) { + var zoomMin = zoomLimit.min || 0; + var zoomMax = zoomLimit.max || Infinity; + zoom = Math.max( + Math.min(previousZoom * zoom, zoomMax), + zoomMin + ) / previousZoom; + } + + // Zoom on given point(originX, originY) + view.scale[0] *= zoom; + view.scale[1] *= zoom; + var position = view.position; + var fixX = (payload.originX - position[0]) * (zoom - 1); + var fixY = (payload.originY - position[1]) * (zoom - 1); + + position[0] -= fixX; + position[1] -= fixY; + + view.updateTransform(); + // Get the new center + var center = view.pointToData(point); + view.setCenter(center); + view.setZoom(zoom * previousZoom); + } + + return { + center: view.getCenter(), + zoom: view.getZoom() + }; +} + +/* +* 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. +*/ + +/** + * @payload + * @property {string} [componentType=series] + * @property {number} [dx] + * @property {number} [dy] + * @property {number} [zoom] + * @property {number} [originX] + * @property {number} [originY] + */ +registerAction({ + type: 'geoRoam', + event: 'geoRoam', + update: 'updateTransform' +}, function (payload, ecModel) { + var componentType = payload.componentType || 'series'; + + ecModel.eachComponent( + { mainType: componentType, query: payload }, + function (componentModel) { + var geo = componentModel.coordinateSystem; + if (geo.type !== 'geo') { + return; + } + + var res = updateCenterAndZoom( + geo, payload, componentModel.get('scaleLimit') + ); + + componentModel.setCenter + && componentModel.setCenter(res.center); + + componentModel.setZoom + && componentModel.setZoom(res.zoom); + + // All map series with same `map` use the same geo coordinate system + // So the center and zoom must be in sync. Include the series not selected by legend + if (componentType === 'series') { + each$1(componentModel.seriesGroup, function (seriesModel) { + seriesModel.setCenter(res.center); + seriesModel.setZoom(res.zoom); + }); + } + } + ); +}); + +/* +* 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. +*/ + +/** + * Simple view coordinate system + * Mapping given x, y to transformd view x, y + */ + +var v2ApplyTransform$1 = applyTransform; + +// Dummy transform node +function TransformDummy() { + Transformable.call(this); +} +mixin(TransformDummy, Transformable); + +function View(name) { + /** + * @type {string} + */ + this.name = name; + + /** + * @type {Object} + */ + this.zoomLimit; + + Transformable.call(this); + + this._roamTransformable = new TransformDummy(); + + this._rawTransformable = new TransformDummy(); + + this._center; + this._zoom; +} + +View.prototype = { + + constructor: View, + + type: 'view', + + /** + * @param {Array.} + * @readOnly + */ + dimensions: ['x', 'y'], + + /** + * Set bounding rect + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + + // PENDING to getRect + setBoundingRect: function (x, y, width, height) { + this._rect = new BoundingRect(x, y, width, height); + return this._rect; + }, + + /** + * @return {module:zrender/core/BoundingRect} + */ + // PENDING to getRect + getBoundingRect: function () { + return this._rect; + }, + + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + setViewRect: function (x, y, width, height) { + this.transformTo(x, y, width, height); + this._viewRect = new BoundingRect(x, y, width, height); + }, + + /** + * Transformed to particular position and size + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + transformTo: function (x, y, width, height) { + var rect = this.getBoundingRect(); + var rawTransform = this._rawTransformable; + + rawTransform.transform = rect.calculateTransform( + new BoundingRect(x, y, width, height) + ); + + rawTransform.decomposeTransform(); + + this._updateTransform(); + }, + + /** + * Set center of view + * @param {Array.} [centerCoord] + */ + setCenter: function (centerCoord) { + if (!centerCoord) { + return; + } + this._center = centerCoord; + + this._updateCenterAndZoom(); + }, + + /** + * @param {number} zoom + */ + setZoom: function (zoom) { + zoom = zoom || 1; + + var zoomLimit = this.zoomLimit; + if (zoomLimit) { + if (zoomLimit.max != null) { + zoom = Math.min(zoomLimit.max, zoom); + } + if (zoomLimit.min != null) { + zoom = Math.max(zoomLimit.min, zoom); + } + } + this._zoom = zoom; + + this._updateCenterAndZoom(); + }, + + /** + * Get default center without roam + */ + getDefaultCenter: function () { + // Rect before any transform + var rawRect = this.getBoundingRect(); + var cx = rawRect.x + rawRect.width / 2; + var cy = rawRect.y + rawRect.height / 2; + + return [cx, cy]; + }, + + getCenter: function () { + return this._center || this.getDefaultCenter(); + }, + + getZoom: function () { + return this._zoom || 1; + }, + + /** + * @return {Array.} data + * @param {boolean} noRoam + * @param {Array.} [out] + * @return {Array.} + */ + dataToPoint: function (data, noRoam, out) { + var transform = noRoam ? this._rawTransform : this.transform; + out = out || []; + return transform + ? v2ApplyTransform$1(out, data, transform) + : copy(out, data); + }, + + /** + * Convert a (x, y) point to (lon, lat) data + * @param {Array.} point + * @return {Array.} + */ + pointToData: function (point) { + var invTransform = this.invTransform; + return invTransform + ? v2ApplyTransform$1([], point, invTransform) + : [point[0], point[1]]; + }, + + /** + * @implements + * see {module:echarts/CoodinateSystem} + */ + convertToPixel: curry(doConvert$1, 'dataToPoint'), + + /** + * @implements + * see {module:echarts/CoodinateSystem} + */ + convertFromPixel: curry(doConvert$1, 'pointToData'), + + /** + * @implements + * see {module:echarts/CoodinateSystem} + */ + containPoint: function (point) { + return this.getViewRectAfterRoam().contain(point[0], point[1]); + } + + /** + * @return {number} + */ + // getScalarScale: function () { + // // Use determinant square root of transform to mutiply scalar + // var m = this.transform; + // var det = Math.sqrt(Math.abs(m[0] * m[3] - m[2] * m[1])); + // return det; + // } +}; + +mixin(View, Transformable); + +function doConvert$1(methodName, ecModel, finder, value) { + var seriesModel = finder.seriesModel; + var coordSys = seriesModel ? seriesModel.coordinateSystem : null; // e.g., graph. + return coordSys === this ? coordSys[methodName](value) : null; +} + +/* +* 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. +*/ + +/** + * [Geo description] + * For backward compatibility, the orginal interface: + * `name, map, geoJson, specialAreas, nameMap` is kept. + * + * @param {string|Object} name + * @param {string} map Map type + * Specify the positioned areas by left, top, width, height + * @param {Object.} [nameMap] + * Specify name alias + * @param {boolean} [invertLongitute=true] + */ +function Geo(name, map$$1, nameMap, invertLongitute) { + + View.call(this, name); + + /** + * Map type + * @type {string} + */ + this.map = map$$1; + + var source = geoSourceManager.load(map$$1, nameMap); + + this._nameCoordMap = source.nameCoordMap; + this._regionsMap = source.regionsMap; + this._invertLongitute = invertLongitute == null ? true : invertLongitute; + + /** + * @readOnly + */ + this.regions = source.regions; + + /** + * @type {module:zrender/src/core/BoundingRect} + */ + this._rect = source.boundingRect; +} + +Geo.prototype = { + + constructor: Geo, + + type: 'geo', + + /** + * @param {Array.} + * @readOnly + */ + dimensions: ['lng', 'lat'], + + /** + * If contain given lng,lat coord + * @param {Array.} + * @readOnly + */ + containCoord: function (coord) { + var regions = this.regions; + for (var i = 0; i < regions.length; i++) { + if (regions[i].contain(coord)) { + return true; + } + } + return false; + }, + + /** + * @override + */ + transformTo: function (x, y, width, height) { + var rect = this.getBoundingRect(); + var invertLongitute = this._invertLongitute; + + rect = rect.clone(); + + if (invertLongitute) { + // Longitute is inverted + rect.y = -rect.y - rect.height; + } + + var rawTransformable = this._rawTransformable; + + rawTransformable.transform = rect.calculateTransform( + new BoundingRect(x, y, width, height) + ); + + rawTransformable.decomposeTransform(); + + if (invertLongitute) { + var scale = rawTransformable.scale; + scale[1] = -scale[1]; + } + + rawTransformable.updateTransform(); + + this._updateTransform(); + }, + + /** + * @param {string} name + * @return {module:echarts/coord/geo/Region} + */ + getRegion: function (name) { + return this._regionsMap.get(name); + }, + + getRegionByCoord: function (coord) { + var regions = this.regions; + for (var i = 0; i < regions.length; i++) { + if (regions[i].contain(coord)) { + return regions[i]; + } + } + }, + + /** + * Add geoCoord for indexing by name + * @param {string} name + * @param {Array.} geoCoord + */ + addGeoCoord: function (name, geoCoord) { + this._nameCoordMap.set(name, geoCoord); + }, + + /** + * Get geoCoord by name + * @param {string} name + * @return {Array.} + */ + getGeoCoord: function (name) { + return this._nameCoordMap.get(name); + }, + + /** + * @override + */ + getBoundingRect: function () { + return this._rect; + }, + + /** + * @param {string|Array.} data + * @param {boolean} noRoam + * @param {Array.} [out] + * @return {Array.} + */ + dataToPoint: function (data, noRoam, out) { + if (typeof data === 'string') { + // Map area name to geoCoord + data = this.getGeoCoord(data); + } + if (data) { + return View.prototype.dataToPoint.call(this, data, noRoam, out); + } + }, + + /** + * @override + */ + convertToPixel: curry(doConvert, 'dataToPoint'), + + /** + * @override + */ + convertFromPixel: curry(doConvert, 'pointToData') + +}; + +mixin(Geo, View); + +function doConvert(methodName, ecModel, finder, value) { + var geoModel = finder.geoModel; + var seriesModel = finder.seriesModel; + + var coordSys = geoModel + ? geoModel.coordinateSystem + : seriesModel + ? ( + seriesModel.coordinateSystem // For map. + || (seriesModel.getReferringComponents('geo')[0] || {}).coordinateSystem + ) + : null; + + return coordSys === this ? coordSys[methodName](value) : null; +} + +/* +* 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. +*/ + +/** + * Resize method bound to the geo + * @param {module:echarts/coord/geo/GeoModel|module:echarts/chart/map/MapModel} geoModel + * @param {module:echarts/ExtensionAPI} api + */ +function resizeGeo(geoModel, api) { + + var boundingCoords = geoModel.get('boundingCoords'); + if (boundingCoords != null) { + var leftTop = boundingCoords[0]; + var rightBottom = boundingCoords[1]; + if (isNaN(leftTop[0]) || isNaN(leftTop[1]) || isNaN(rightBottom[0]) || isNaN(rightBottom[1])) { + if (__DEV__) { + console.error('Invalid boundingCoords'); + } + } + else { + this.setBoundingRect(leftTop[0], leftTop[1], rightBottom[0] - leftTop[0], rightBottom[1] - leftTop[1]); + } + } + + var rect = this.getBoundingRect(); + + var boxLayoutOption; + + var center = geoModel.get('layoutCenter'); + var size = geoModel.get('layoutSize'); + + var viewWidth = api.getWidth(); + var viewHeight = api.getHeight(); + + var aspect = rect.width / rect.height * this.aspectScale; + + var useCenterAndSize = false; + + if (center && size) { + center = [ + parsePercent$1(center[0], viewWidth), + parsePercent$1(center[1], viewHeight) + ]; + size = parsePercent$1(size, Math.min(viewWidth, viewHeight)); + + if (!isNaN(center[0]) && !isNaN(center[1]) && !isNaN(size)) { + useCenterAndSize = true; + } + else { + if (__DEV__) { + console.warn('Given layoutCenter or layoutSize data are invalid. Use left/top/width/height instead.'); + } + } + } + + var viewRect; + if (useCenterAndSize) { + var viewRect = {}; + if (aspect > 1) { + // Width is same with size + viewRect.width = size; + viewRect.height = size / aspect; + } + else { + viewRect.height = size; + viewRect.width = size * aspect; + } + viewRect.y = center[1] - viewRect.height / 2; + viewRect.x = center[0] - viewRect.width / 2; + } + else { + // Use left/top/width/height + boxLayoutOption = geoModel.getBoxLayoutParams(); + + // 0.75 rate + boxLayoutOption.aspect = aspect; + + viewRect = getLayoutRect(boxLayoutOption, { + width: viewWidth, + height: viewHeight + }); + } + + this.setViewRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height); + + this.setCenter(geoModel.get('center')); + this.setZoom(geoModel.get('zoom')); +} + +/** + * @param {module:echarts/coord/Geo} geo + * @param {module:echarts/model/Model} model + * @inner + */ +function setGeoCoords(geo, model) { + each$1(model.get('geoCoord'), function (geoCoord, name) { + geo.addGeoCoord(name, geoCoord); + }); +} + +var geoCreator = { + + // For deciding which dimensions to use when creating list data + dimensions: Geo.prototype.dimensions, + + create: function (ecModel, api) { + var geoList = []; + + // FIXME Create each time may be slow + ecModel.eachComponent('geo', function (geoModel, idx) { + var name = geoModel.get('map'); + + var aspectScale = geoModel.get('aspectScale'); + var invertLongitute = true; + var mapRecords = mapDataStorage.retrieveMap(name); + if (mapRecords && mapRecords[0] && mapRecords[0].type === 'svg') { + aspectScale == null && (aspectScale = 1); + invertLongitute = false; + } + else { + aspectScale == null && (aspectScale = 0.75); + } + + var geo = new Geo(name + idx, name, geoModel.get('nameMap'), invertLongitute); + + geo.aspectScale = aspectScale; + geo.zoomLimit = geoModel.get('scaleLimit'); + geoList.push(geo); + + setGeoCoords(geo, geoModel); + + geoModel.coordinateSystem = geo; + geo.model = geoModel; + + // Inject resize method + geo.resize = resizeGeo; + + geo.resize(geoModel, api); + }); + + ecModel.eachSeries(function (seriesModel) { + var coordSys = seriesModel.get('coordinateSystem'); + if (coordSys === 'geo') { + var geoIndex = seriesModel.get('geoIndex') || 0; + seriesModel.coordinateSystem = geoList[geoIndex]; + } + }); + + // If has map series + var mapModelGroupBySeries = {}; + + ecModel.eachSeriesByType('map', function (seriesModel) { + if (!seriesModel.getHostGeoModel()) { + var mapType = seriesModel.getMapType(); + mapModelGroupBySeries[mapType] = mapModelGroupBySeries[mapType] || []; + mapModelGroupBySeries[mapType].push(seriesModel); + } + }); + + each$1(mapModelGroupBySeries, function (mapSeries, mapType) { + var nameMapList = map(mapSeries, function (singleMapSeries) { + return singleMapSeries.get('nameMap'); + }); + var geo = new Geo(mapType, mapType, mergeAll(nameMapList)); + + geo.zoomLimit = retrieve.apply(null, map(mapSeries, function (singleMapSeries) { + return singleMapSeries.get('scaleLimit'); + })); + geoList.push(geo); + + // Inject resize method + geo.resize = resizeGeo; + geo.aspectScale = mapSeries[0].get('aspectScale'); + + geo.resize(mapSeries[0], api); + + each$1(mapSeries, function (singleMapSeries) { + singleMapSeries.coordinateSystem = geo; + + setGeoCoords(geo, singleMapSeries); + }); + }); + + return geoList; + }, + + /** + * Fill given regions array + * @param {Array.} originRegionArr + * @param {string} mapName + * @param {Object} [nameMap] + * @return {Array} + */ + getFilledRegions: function (originRegionArr, mapName, nameMap) { + // Not use the original + var regionsArr = (originRegionArr || []).slice(); + + var dataNameMap = createHashMap(); + for (var i = 0; i < regionsArr.length; i++) { + dataNameMap.set(regionsArr[i].name, regionsArr[i]); + } + + var source = geoSourceManager.load(mapName, nameMap); + each$1(source.regions, function (region) { + var name = region.name; + !dataNameMap.get(name) && regionsArr.push({name: name}); + }); + + return regionsArr; + } +}; + +registerCoordinateSystem('geo', geoCreator); + +/* +* 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. +*/ + +var mapSymbolLayout = function (ecModel) { + + var processedMapType = {}; + + ecModel.eachSeriesByType('map', function (mapSeries) { + var mapType = mapSeries.getMapType(); + if (mapSeries.getHostGeoModel() || processedMapType[mapType]) { + return; + } + + var mapSymbolOffsets = {}; + + each$1(mapSeries.seriesGroup, function (subMapSeries) { + var geo = subMapSeries.coordinateSystem; + var data = subMapSeries.originalData; + if (subMapSeries.get('showLegendSymbol') && ecModel.getComponent('legend')) { + data.each(data.mapDimension('value'), function (value, idx) { + var name = data.getName(idx); + var region = geo.getRegion(name); + + // If input series.data is [11, 22, '-'/null/undefined, 44], + // it will be filled with NaN: [11, 22, NaN, 44] and NaN will + // not be drawn. So here must validate if value is NaN. + if (!region || isNaN(value)) { + return; + } + + var offset = mapSymbolOffsets[name] || 0; + + var point = geo.dataToPoint(region.center); + + mapSymbolOffsets[name] = offset + 1; + + data.setItemLayout(idx, { + point: point, + offset: offset + }); + }); + } + }); + + // Show label of those region not has legendSymbol(which is offset 0) + var data = mapSeries.getData(); + data.each(function (idx) { + var name = data.getName(idx); + var layout = data.getItemLayout(idx) || {}; + layout.showLabel = !mapSymbolOffsets[name]; + data.setItemLayout(idx, layout); + }); + + processedMapType[mapType] = true; + }); +}; + +/* +* 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. +*/ + + +var mapVisual = function (ecModel) { + ecModel.eachSeriesByType('map', function (seriesModel) { + var colorList = seriesModel.get('color'); + var itemStyleModel = seriesModel.getModel('itemStyle'); + + var areaColor = itemStyleModel.get('areaColor'); + var color = itemStyleModel.get('color') + || colorList[seriesModel.seriesIndex % colorList.length]; + + seriesModel.getData().setVisual({ + 'areaColor': areaColor, + 'color': color + }); + }); +}; + +/* +* 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. +*/ + +// FIXME 公用? +/** + * @param {Array.} datas + * @param {string} statisticType 'average' 'sum' + * @inner + */ +function dataStatistics(datas, statisticType) { + var dataNameMap = {}; + + each$1(datas, function (data) { + data.each(data.mapDimension('value'), function (value, idx) { + // Add prefix to avoid conflict with Object.prototype. + var mapKey = 'ec-' + data.getName(idx); + dataNameMap[mapKey] = dataNameMap[mapKey] || []; + if (!isNaN(value)) { + dataNameMap[mapKey].push(value); + } + }); + }); + + return datas[0].map(datas[0].mapDimension('value'), function (value, idx) { + var mapKey = 'ec-' + datas[0].getName(idx); + var sum = 0; + var min = Infinity; + var max = -Infinity; + var len = dataNameMap[mapKey].length; + for (var i = 0; i < len; i++) { + min = Math.min(min, dataNameMap[mapKey][i]); + max = Math.max(max, dataNameMap[mapKey][i]); + sum += dataNameMap[mapKey][i]; + } + var result; + if (statisticType === 'min') { + result = min; + } + else if (statisticType === 'max') { + result = max; + } + else if (statisticType === 'average') { + result = sum / len; + } + else { + result = sum; + } + return len === 0 ? NaN : result; + }); +} + +var mapDataStatistic = function (ecModel) { + var seriesGroups = {}; + ecModel.eachSeriesByType('map', function (seriesModel) { + var hostGeoModel = seriesModel.getHostGeoModel(); + var key = hostGeoModel ? 'o' + hostGeoModel.id : 'i' + seriesModel.getMapType(); + (seriesGroups[key] = seriesGroups[key] || []).push(seriesModel); + }); + + each$1(seriesGroups, function (seriesList, key) { + var data = dataStatistics( + map(seriesList, function (seriesModel) { + return seriesModel.getData(); + }), + seriesList[0].get('mapValueCalculation') + ); + + for (var i = 0; i < seriesList.length; i++) { + seriesList[i].originalData = seriesList[i].getData(); + } + + // FIXME Put where? + for (var i = 0; i < seriesList.length; i++) { + seriesList[i].seriesGroup = seriesList; + seriesList[i].needsDrawMap = i === 0 && !seriesList[i].getHostGeoModel(); + + seriesList[i].setData(data.cloneShallow()); + seriesList[i].mainSeries = seriesList[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. +*/ + +var backwardCompat$2 = function (option) { + // Save geoCoord + var mapSeries = []; + each$1(option.series, function (seriesOpt) { + if (seriesOpt && seriesOpt.type === 'map') { + mapSeries.push(seriesOpt); + seriesOpt.map = seriesOpt.map || seriesOpt.mapType; + // Put x, y, width, height, x2, y2 in the top level + defaults(seriesOpt, seriesOpt.mapLocation); + } + }); +}; + +/* +* 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. +*/ + +registerLayout(mapSymbolLayout); +registerVisual(mapVisual); +registerProcessor(PRIORITY.PROCESSOR.STATISTIC, mapDataStatistic); +registerPreprocessor(backwardCompat$2); + +createDataSelectAction('map', [{ + type: 'mapToggleSelect', + event: 'mapselectchanged', + method: 'toggleSelected' +}, { + type: 'mapSelect', + event: 'mapselected', + method: 'select' +}, { + type: 'mapUnSelect', + event: 'mapunselected', + method: 'unSelect' +}]); + +/* +* 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. +*/ + +/** + * Link lists and struct (graph or tree) + */ + +var each$7 = each$1; + +var DATAS = '\0__link_datas'; +var MAIN_DATA = '\0__link_mainData'; + +// Caution: +// In most case, either list or its shallow clones (see list.cloneShallow) +// is active in echarts process. So considering heap memory consumption, +// we do not clone tree or graph, but share them among list and its shallow clones. +// But in some rare case, we have to keep old list (like do animation in chart). So +// please take care that both the old list and the new list share the same tree/graph. + +/** + * @param {Object} opt + * @param {module:echarts/data/List} opt.mainData + * @param {Object} [opt.struct] For example, instance of Graph or Tree. + * @param {string} [opt.structAttr] designation: list[structAttr] = struct; + * @param {Object} [opt.datas] {dataType: data}, + * like: {node: nodeList, edge: edgeList}. + * Should contain mainData. + * @param {Object} [opt.datasAttr] {dataType: attr}, + * designation: struct[datasAttr[dataType]] = list; + */ +function linkList(opt) { + var mainData = opt.mainData; + var datas = opt.datas; + + if (!datas) { + datas = {main: mainData}; + opt.datasAttr = {main: 'data'}; + } + opt.datas = opt.mainData = null; + + linkAll(mainData, datas, opt); + + // Porxy data original methods. + each$7(datas, function (data) { + each$7(mainData.TRANSFERABLE_METHODS, function (methodName) { + data.wrapMethod(methodName, curry(transferInjection, opt)); + }); + + }); + + // Beyond transfer, additional features should be added to `cloneShallow`. + mainData.wrapMethod('cloneShallow', curry(cloneShallowInjection, opt)); + + // Only mainData trigger change, because struct.update may trigger + // another changable methods, which may bring about dead lock. + each$7(mainData.CHANGABLE_METHODS, function (methodName) { + mainData.wrapMethod(methodName, curry(changeInjection, opt)); + }); + + // Make sure datas contains mainData. + assert$1(datas[mainData.dataType] === mainData); +} + +function transferInjection(opt, res) { + if (isMainData(this)) { + // Transfer datas to new main data. + var datas = extend({}, this[DATAS]); + datas[this.dataType] = res; + linkAll(res, datas, opt); + } + else { + // Modify the reference in main data to point newData. + linkSingle(res, this.dataType, this[MAIN_DATA], opt); + } + return res; +} + +function changeInjection(opt, res) { + opt.struct && opt.struct.update(this); + return res; +} + +function cloneShallowInjection(opt, res) { + // cloneShallow, which brings about some fragilities, may be inappropriate + // to be exposed as an API. So for implementation simplicity we can make + // the restriction that cloneShallow of not-mainData should not be invoked + // outside, but only be invoked here. + each$7(res[DATAS], function (data, dataType) { + data !== res && linkSingle(data.cloneShallow(), dataType, res, opt); + }); + return res; +} + +/** + * Supplement method to List. + * + * @public + * @param {string} [dataType] If not specified, return mainData. + * @return {module:echarts/data/List} + */ +function getLinkedData(dataType) { + var mainData = this[MAIN_DATA]; + return (dataType == null || mainData == null) + ? mainData + : mainData[DATAS][dataType]; +} + +function isMainData(data) { + return data[MAIN_DATA] === data; +} + +function linkAll(mainData, datas, opt) { + mainData[DATAS] = {}; + each$7(datas, function (data, dataType) { + linkSingle(data, dataType, mainData, opt); + }); +} + +function linkSingle(data, dataType, mainData, opt) { + mainData[DATAS][dataType] = data; + data[MAIN_DATA] = mainData; + data.dataType = dataType; + + if (opt.struct) { + data[opt.structAttr] = opt.struct; + opt.struct[opt.datasAttr[dataType]] = data; + } + + // Supplement method. + data.getLinkedData = getLinkedData; +} + +/* +* 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. +*/ + +/** + * Tree data structure + * + * @module echarts/data/Tree + */ + +/** + * @constructor module:echarts/data/Tree~TreeNode + * @param {string} name + * @param {module:echarts/data/Tree} hostTree + */ +var TreeNode = function (name, hostTree) { + /** + * @type {string} + */ + this.name = name || ''; + + /** + * Depth of node + * + * @type {number} + * @readOnly + */ + this.depth = 0; + + /** + * Height of the subtree rooted at this node. + * @type {number} + * @readOnly + */ + this.height = 0; + + /** + * @type {module:echarts/data/Tree~TreeNode} + * @readOnly + */ + this.parentNode = null; + + /** + * Reference to list item. + * Do not persistent dataIndex outside, + * besause it may be changed by list. + * If dataIndex -1, + * this node is logical deleted (filtered) in list. + * + * @type {Object} + * @readOnly + */ + this.dataIndex = -1; + + /** + * @type {Array.} + * @readOnly + */ + this.children = []; + + /** + * @type {Array.} + * @pubilc + */ + this.viewChildren = []; + + /** + * @type {moduel:echarts/data/Tree} + * @readOnly + */ + this.hostTree = hostTree; +}; + +TreeNode.prototype = { + + constructor: TreeNode, + + /** + * The node is removed. + * @return {boolean} is removed. + */ + isRemoved: function () { + return this.dataIndex < 0; + }, + + /** + * Travel this subtree (include this node). + * Usage: + * node.eachNode(function () { ... }); // preorder + * node.eachNode('preorder', function () { ... }); // preorder + * node.eachNode('postorder', function () { ... }); // postorder + * node.eachNode( + * {order: 'postorder', attr: 'viewChildren'}, + * function () { ... } + * ); // postorder + * + * @param {(Object|string)} options If string, means order. + * @param {string=} options.order 'preorder' or 'postorder' + * @param {string=} options.attr 'children' or 'viewChildren' + * @param {Function} cb If in preorder and return false, + * its subtree will not be visited. + * @param {Object} [context] + */ + eachNode: function (options, cb, context) { + if (typeof options === 'function') { + context = cb; + cb = options; + options = null; + } + + options = options || {}; + if (isString(options)) { + options = {order: options}; + } + + var order = options.order || 'preorder'; + var children = this[options.attr || 'children']; + + var suppressVisitSub; + order === 'preorder' && (suppressVisitSub = cb.call(context, this)); + + for (var i = 0; !suppressVisitSub && i < children.length; i++) { + children[i].eachNode(options, cb, context); + } + + order === 'postorder' && cb.call(context, this); + }, + + /** + * Update depth and height of this subtree. + * + * @param {number} depth + */ + updateDepthAndHeight: function (depth) { + var height = 0; + this.depth = depth; + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.updateDepthAndHeight(depth + 1); + if (child.height > height) { + height = child.height; + } + } + this.height = height + 1; + }, + + /** + * @param {string} id + * @return {module:echarts/data/Tree~TreeNode} + */ + getNodeById: function (id) { + if (this.getId() === id) { + return this; + } + for (var i = 0, children = this.children, len = children.length; i < len; i++) { + var res = children[i].getNodeById(id); + if (res) { + return res; + } + } + }, + + /** + * @param {module:echarts/data/Tree~TreeNode} node + * @return {boolean} + */ + contains: function (node) { + if (node === this) { + return true; + } + for (var i = 0, children = this.children, len = children.length; i < len; i++) { + var res = children[i].contains(node); + if (res) { + return res; + } + } + }, + + /** + * @param {boolean} includeSelf Default false. + * @return {Array.} order: [root, child, grandchild, ...] + */ + getAncestors: function (includeSelf) { + var ancestors = []; + var node = includeSelf ? this : this.parentNode; + while (node) { + ancestors.push(node); + node = node.parentNode; + } + ancestors.reverse(); + return ancestors; + }, + + /** + * @param {string|Array=} [dimension='value'] Default 'value'. can be 0, 1, 2, 3 + * @return {number} Value. + */ + getValue: function (dimension) { + var data = this.hostTree.data; + return data.get(data.getDimension(dimension || 'value'), this.dataIndex); + }, + + /** + * @param {Object} layout + * @param {boolean=} [merge=false] + */ + setLayout: function (layout, merge$$1) { + this.dataIndex >= 0 + && this.hostTree.data.setItemLayout(this.dataIndex, layout, merge$$1); + }, + + /** + * @return {Object} layout + */ + getLayout: function () { + return this.hostTree.data.getItemLayout(this.dataIndex); + }, + + /** + * @param {string} [path] + * @return {module:echarts/model/Model} + */ + getModel: function (path) { + if (this.dataIndex < 0) { + return; + } + var hostTree = this.hostTree; + var itemModel = hostTree.data.getItemModel(this.dataIndex); + var levelModel = this.getLevelModel(); + var leavesModel; + if (!levelModel && (this.children.length === 0 || (this.children.length !== 0 && this.isExpand === false))) { + leavesModel = this.getLeavesModel(); + } + return itemModel.getModel(path, (levelModel || leavesModel || hostTree.hostModel).getModel(path)); + }, + + /** + * @return {module:echarts/model/Model} + */ + getLevelModel: function () { + return (this.hostTree.levelModels || [])[this.depth]; + }, + + /** + * @return {module:echarts/model/Model} + */ + getLeavesModel: function () { + return this.hostTree.leavesModel; + }, + + /** + * @example + * setItemVisual('color', color); + * setItemVisual({ + * 'color': color + * }); + */ + setVisual: function (key, value) { + this.dataIndex >= 0 + && this.hostTree.data.setItemVisual(this.dataIndex, key, value); + }, + + /** + * Get item visual + */ + getVisual: function (key, ignoreParent) { + return this.hostTree.data.getItemVisual(this.dataIndex, key, ignoreParent); + }, + + /** + * @public + * @return {number} + */ + getRawIndex: function () { + return this.hostTree.data.getRawIndex(this.dataIndex); + }, + + /** + * @public + * @return {string} + */ + getId: function () { + return this.hostTree.data.getId(this.dataIndex); + }, + + /** + * if this is an ancestor of another node + * + * @public + * @param {TreeNode} node another node + * @return {boolean} if is ancestor + */ + isAncestorOf: function (node) { + var parent = node.parentNode; + while (parent) { + if (parent === this) { + return true; + } + parent = parent.parentNode; + } + return false; + }, + + /** + * if this is an descendant of another node + * + * @public + * @param {TreeNode} node another node + * @return {boolean} if is descendant + */ + isDescendantOf: function (node) { + return node !== this && node.isAncestorOf(this); + } +}; + +/** + * @constructor + * @alias module:echarts/data/Tree + * @param {module:echarts/model/Model} hostModel + * @param {Array.} levelOptions + * @param {Object} leavesOption + */ +function Tree(hostModel, levelOptions, leavesOption) { + /** + * @type {module:echarts/data/Tree~TreeNode} + * @readOnly + */ + this.root; + + /** + * @type {module:echarts/data/List} + * @readOnly + */ + this.data; + + /** + * Index of each item is the same as the raw index of coresponding list item. + * @private + * @type {Array.} treeOptions.levels + * @param {Array.} treeOptions.leaves + * @return module:echarts/data/Tree + */ +Tree.createTree = function (dataRoot, hostModel, treeOptions, beforeLink) { + + var tree = new Tree(hostModel, treeOptions.levels, treeOptions.leaves); + var listData = []; + var dimMax = 1; + + buildHierarchy(dataRoot); + + function buildHierarchy(dataNode, parentNode) { + var value = dataNode.value; + dimMax = Math.max(dimMax, isArray(value) ? value.length : 1); + + listData.push(dataNode); + + var node = new TreeNode(dataNode.name, tree); + parentNode + ? addChild(node, parentNode) + : (tree.root = node); + + tree._nodes.push(node); + + var children = dataNode.children; + if (children) { + for (var i = 0; i < children.length; i++) { + buildHierarchy(children[i], node); + } + } + } + + tree.root.updateDepthAndHeight(0); + + var dimensionsInfo = createDimensions(listData, { + coordDimensions: ['value'], + dimensionsCount: dimMax + }); + + var list = new List(dimensionsInfo, hostModel); + list.initData(listData); + + linkList({ + mainData: list, + struct: tree, + structAttr: 'tree' + }); + + tree.update(); + + beforeLink && beforeLink(list); + + return tree; +}; + +/** + * It is needed to consider the mess of 'list', 'hostModel' when creating a TreeNote, + * so this function is not ready and not necessary to be public. + * + * @param {(module:echarts/data/Tree~TreeNode|Object)} child + */ +function addChild(child, node) { + var children = node.children; + if (child.parentNode === node) { + return; + } + + children.push(child); + child.parentNode = node; +} + +/* +* 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. +*/ + +SeriesModel.extend({ + + type: 'series.tree', + + layoutInfo: null, + + // can support the position parameters 'left', 'top','right','bottom', 'width', + // 'height' in the setOption() with 'merge' mode normal. + layoutMode: 'box', + + /** + * Init a tree data structure from data in option series + * @param {Object} option the object used to config echarts view + * @return {module:echarts/data/List} storage initial data + */ + getInitialData: function (option) { + + //create an virtual root + var root = {name: option.name, children: option.data}; + + var leaves = option.leaves || {}; + + var treeOption = {}; + + treeOption.leaves = leaves; + + var tree = Tree.createTree(root, this, treeOption, beforeLink); + + function beforeLink(nodeData) { + nodeData.wrapMethod('getItemModel', function (model, idx) { + var node = tree.getNodeByDataIndex(idx); + var leavesModel = node.getLeavesModel(); + if (!node.children.length || !node.isExpand) { + model.parentModel = leavesModel; + } + return model; + }); + } + + var treeDepth = 0; + + tree.eachNode('preorder', function (node) { + if (node.depth > treeDepth) { + treeDepth = node.depth; + } + }); + + var expandAndCollapse = option.expandAndCollapse; + var expandTreeDepth = (expandAndCollapse && option.initialTreeDepth >= 0) + ? option.initialTreeDepth : treeDepth; + + tree.root.eachNode('preorder', function (node) { + var item = node.hostTree.data.getRawDataItem(node.dataIndex); + // Add item.collapsed != null, because users can collapse node original in the series.data. + node.isExpand = (item && item.collapsed != null) + ? !item.collapsed + : node.depth <= expandTreeDepth; + }); + + return tree.data; + }, + + /** + * Make the configuration 'orient' backward compatibly, with 'horizontal = LR', 'vertical = TB'. + * @returns {string} orient + */ + getOrient: function () { + var orient = this.get('orient'); + if (orient === 'horizontal') { + orient = 'LR'; + } + else if (orient === 'vertical') { + orient = 'TB'; + } + return orient; + }, + + setZoom: function (zoom) { + this.option.zoom = zoom; + }, + + setCenter: function (center) { + this.option.center = center; + }, + + /** + * @override + * @param {number} dataIndex + */ + formatTooltip: function (dataIndex) { + var tree = this.getData().tree; + var realRoot = tree.root.children[0]; + var node = tree.getNodeByDataIndex(dataIndex); + var value = node.getValue(); + var name = node.name; + while (node && (node !== realRoot)) { + name = node.parentNode.name + '.' + name; + node = node.parentNode; + } + return encodeHTML(name + ( + (isNaN(value) || value == null) ? '' : ' : ' + value + )); + }, + + defaultOption: { + zlevel: 0, + z: 2, + coordinateSystem: 'view', + + // the position of the whole view + left: '12%', + top: '12%', + right: '12%', + bottom: '12%', + + // the layout of the tree, two value can be selected, 'orthogonal' or 'radial' + layout: 'orthogonal', + + // true | false | 'move' | 'scale', see module:component/helper/RoamController. + 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', + + symbol: 'emptyCircle', + + symbolSize: 7, + + expandAndCollapse: true, + + initialTreeDepth: 2, + + lineStyle: { + color: '#ccc', + width: 1.5, + curveness: 0.5 + }, + + itemStyle: { + color: 'lightsteelblue', + borderColor: '#c23531', + borderWidth: 1.5 + }, + + label: { + show: true, + color: '#555' + }, + + leaves: { + label: { + show: true + } + }, + + animationEasing: 'linear', + + animationDuration: 700, + + animationDurationUpdate: 1000 + } +}); + +/* +* 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. +*/ + +/* +* A third-party license is embeded for some of the code in this file: +* The tree layoutHelper implementation was originally copied from +* "d3.js"(https://github.com/d3/d3-hierarchy) with +* some modifications made for this project. +* (see more details in the comment of the specific method below.) +* The use of the source code of this file is also subject to the terms +* and consitions of the licence of "d3.js" (BSD-3Clause, see +* ). +*/ + +/** + * @file The layout algorithm of node-link tree diagrams. Here we using Reingold-Tilford algorithm to drawing + * the tree. + */ + +/** + * Initialize all computational message for following algorithm. + * + * @param {module:echarts/data/Tree~TreeNode} root The virtual root of the tree. + */ +function init$2(root) { + root.hierNode = { + defaultAncestor: null, + ancestor: root, + prelim: 0, + modifier: 0, + change: 0, + shift: 0, + i: 0, + thread: null + }; + + var nodes = [root]; + var node; + var children; + + while (node = nodes.pop()) { // jshint ignore:line + children = node.children; + if (node.isExpand && children.length) { + var n = children.length; + for (var i = n - 1; i >= 0; i--) { + var child = children[i]; + child.hierNode = { + defaultAncestor: null, + ancestor: child, + prelim: 0, + modifier: 0, + change: 0, + shift: 0, + i: i, + thread: null + }; + nodes.push(child); + } + } + } +} + +/** + * The implementation of this function was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + * + * Computes a preliminary x coordinate for node. Before that, this function is + * applied recursively to the children of node, as well as the function + * apportion(). After spacing out the children by calling executeShifts(), the + * node is placed to the midpoint of its outermost children. + * + * @param {module:echarts/data/Tree~TreeNode} node + * @param {Function} separation + */ +function firstWalk(node, separation) { + var children = node.isExpand ? node.children : []; + var siblings = node.parentNode.children; + var subtreeW = node.hierNode.i ? siblings[node.hierNode.i - 1] : null; + if (children.length) { + executeShifts(node); + var midPoint = (children[0].hierNode.prelim + children[children.length - 1].hierNode.prelim) / 2; + if (subtreeW) { + node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW); + node.hierNode.modifier = node.hierNode.prelim - midPoint; + } + else { + node.hierNode.prelim = midPoint; + } + } + else if (subtreeW) { + node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW); + } + node.parentNode.hierNode.defaultAncestor = apportion( + node, + subtreeW, + node.parentNode.hierNode.defaultAncestor || siblings[0], + separation + ); +} + + +/** + * The implementation of this function was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + * + * Computes all real x-coordinates by summing up the modifiers recursively. + * + * @param {module:echarts/data/Tree~TreeNode} node + */ +function secondWalk(node) { + var nodeX = node.hierNode.prelim + node.parentNode.hierNode.modifier; + node.setLayout({x: nodeX}, true); + node.hierNode.modifier += node.parentNode.hierNode.modifier; +} + + +function separation(cb) { + return arguments.length ? cb : defaultSeparation; +} + +/** + * Transform the common coordinate to radial coordinate. + * + * @param {number} x + * @param {number} y + * @return {Object} + */ +function radialCoordinate(x, y) { + var radialCoor = {}; + x -= Math.PI / 2; + radialCoor.x = y * Math.cos(x); + radialCoor.y = y * Math.sin(x); + return radialCoor; +} + +/** + * Get the layout position of the whole view. + * + * @param {module:echarts/model/Series} seriesModel the model object of sankey series + * @param {module:echarts/ExtensionAPI} api provide the API list that the developer can call + * @return {module:zrender/core/BoundingRect} size of rect to draw the sankey view + */ +function getViewRect$1(seriesModel, api) { + return getLayoutRect( + seriesModel.getBoxLayoutParams(), { + width: api.getWidth(), + height: api.getHeight() + } + ); +} + +/** + * All other shifts, applied to the smaller subtrees between w- and w+, are + * performed by this function. + * + * The implementation of this function was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + * + * @param {module:echarts/data/Tree~TreeNode} node + */ +function executeShifts(node) { + var children = node.children; + var n = children.length; + var shift = 0; + var change = 0; + while (--n >= 0) { + var child = children[n]; + child.hierNode.prelim += shift; + child.hierNode.modifier += shift; + change += child.hierNode.change; + shift += child.hierNode.shift + change; + } +} + +/** + * The implementation of this function was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + * + * The core of the algorithm. Here, a new subtree is combined with the + * previous subtrees. Threads are used to traverse the inside and outside + * contours of the left and right subtree up to the highest common level. + * Whenever two nodes of the inside contours conflict, we compute the left + * one of the greatest uncommon ancestors using the function nextAncestor() + * and call moveSubtree() to shift the subtree and prepare the shifts of + * smaller subtrees. Finally, we add a new thread (if necessary). + * + * @param {module:echarts/data/Tree~TreeNode} subtreeV + * @param {module:echarts/data/Tree~TreeNode} subtreeW + * @param {module:echarts/data/Tree~TreeNode} ancestor + * @param {Function} separation + * @return {module:echarts/data/Tree~TreeNode} + */ +function apportion(subtreeV, subtreeW, ancestor, separation) { + + if (subtreeW) { + var nodeOutRight = subtreeV; + var nodeInRight = subtreeV; + var nodeOutLeft = nodeInRight.parentNode.children[0]; + var nodeInLeft = subtreeW; + + var sumOutRight = nodeOutRight.hierNode.modifier; + var sumInRight = nodeInRight.hierNode.modifier; + var sumOutLeft = nodeOutLeft.hierNode.modifier; + var sumInLeft = nodeInLeft.hierNode.modifier; + + while (nodeInLeft = nextRight(nodeInLeft), nodeInRight = nextLeft(nodeInRight), nodeInLeft && nodeInRight) { + nodeOutRight = nextRight(nodeOutRight); + nodeOutLeft = nextLeft(nodeOutLeft); + nodeOutRight.hierNode.ancestor = subtreeV; + var shift = nodeInLeft.hierNode.prelim + sumInLeft - nodeInRight.hierNode.prelim + - sumInRight + separation(nodeInLeft, nodeInRight); + if (shift > 0) { + moveSubtree(nextAncestor(nodeInLeft, subtreeV, ancestor), subtreeV, shift); + sumInRight += shift; + sumOutRight += shift; + } + sumInLeft += nodeInLeft.hierNode.modifier; + sumInRight += nodeInRight.hierNode.modifier; + sumOutRight += nodeOutRight.hierNode.modifier; + sumOutLeft += nodeOutLeft.hierNode.modifier; + } + if (nodeInLeft && !nextRight(nodeOutRight)) { + nodeOutRight.hierNode.thread = nodeInLeft; + nodeOutRight.hierNode.modifier += sumInLeft - sumOutRight; + + } + if (nodeInRight && !nextLeft(nodeOutLeft)) { + nodeOutLeft.hierNode.thread = nodeInRight; + nodeOutLeft.hierNode.modifier += sumInRight - sumOutLeft; + ancestor = subtreeV; + } + } + return ancestor; +} + +/** + * This function is used to traverse the right contour of a subtree. + * It returns the rightmost child of node or the thread of node. The function + * returns null if and only if node is on the highest depth of its subtree. + * + * @param {module:echarts/data/Tree~TreeNode} node + * @return {module:echarts/data/Tree~TreeNode} + */ +function nextRight(node) { + var children = node.children; + return children.length && node.isExpand ? children[children.length - 1] : node.hierNode.thread; +} + +/** + * This function is used to traverse the left contour of a subtree (or a subforest). + * It returns the leftmost child of node or the thread of node. The function + * returns null if and only if node is on the highest depth of its subtree. + * + * @param {module:echarts/data/Tree~TreeNode} node + * @return {module:echarts/data/Tree~TreeNode} + */ +function nextLeft(node) { + var children = node.children; + return children.length && node.isExpand ? children[0] : node.hierNode.thread; +} + +/** + * If nodeInLeft’s ancestor is a sibling of node, returns nodeInLeft’s ancestor. + * Otherwise, returns the specified ancestor. + * + * @param {module:echarts/data/Tree~TreeNode} nodeInLeft + * @param {module:echarts/data/Tree~TreeNode} node + * @param {module:echarts/data/Tree~TreeNode} ancestor + * @return {module:echarts/data/Tree~TreeNode} + */ +function nextAncestor(nodeInLeft, node, ancestor) { + return nodeInLeft.hierNode.ancestor.parentNode === node.parentNode + ? nodeInLeft.hierNode.ancestor : ancestor; +} + +/** + * The implementation of this function was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + * + * Shifts the current subtree rooted at wr. + * This is done by increasing prelim(w+) and modifier(w+) by shift. + * + * @param {module:echarts/data/Tree~TreeNode} wl + * @param {module:echarts/data/Tree~TreeNode} wr + * @param {number} shift [description] + */ +function moveSubtree(wl, wr, shift) { + var change = shift / (wr.hierNode.i - wl.hierNode.i); + wr.hierNode.change -= change; + wr.hierNode.shift += shift; + wr.hierNode.modifier += shift; + wr.hierNode.prelim += shift; + wl.hierNode.change += change; +} + +/** + * The implementation of this function was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + */ +function defaultSeparation(node1, node2) { + return node1.parentNode === node2.parentNode ? 1 : 2; +} + +/* +* 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. +*/ + +extendChartView({ + + type: 'tree', + + /** + * Init the chart + * @override + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ + init: function (ecModel, api) { + + /** + * @private + * @type {module:echarts/data/Tree} + */ + this._oldTree; + + /** + * @private + * @type {module:zrender/container/Group} + */ + this._mainGroup = new Group(); + + /** + * @private + * @type {module:echarts/componet/helper/RoamController} + */ + this._controller = new RoamController(api.getZr()); + + this._controllerHost = {target: this.group}; + + this.group.add(this._mainGroup); + }, + + render: function (seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(); + + var layoutInfo = seriesModel.layoutInfo; + + var group = this._mainGroup; + + var layout = seriesModel.get('layout'); + + if (layout === 'radial') { + group.attr('position', [layoutInfo.x + layoutInfo.width / 2, layoutInfo.y + layoutInfo.height / 2]); + } + else { + group.attr('position', [layoutInfo.x, layoutInfo.y]); + } + + this._updateViewCoordSys(seriesModel, layoutInfo, layout); + this._updateController(seriesModel, ecModel, api); + + var oldData = this._data; + + var seriesScope = { + expandAndCollapse: seriesModel.get('expandAndCollapse'), + layout: layout, + orient: seriesModel.getOrient(), + curvature: seriesModel.get('lineStyle.curveness'), + symbolRotate: seriesModel.get('symbolRotate'), + symbolOffset: seriesModel.get('symbolOffset'), + hoverAnimation: seriesModel.get('hoverAnimation'), + useNameLabel: true, + fadeIn: true + }; + + data.diff(oldData) + .add(function (newIdx) { + if (symbolNeedsDraw$1(data, newIdx)) { + // Create node and edge + updateNode(data, newIdx, null, group, seriesModel, seriesScope); + } + }) + .update(function (newIdx, oldIdx) { + var symbolEl = oldData.getItemGraphicEl(oldIdx); + if (!symbolNeedsDraw$1(data, newIdx)) { + symbolEl && removeNode(oldData, oldIdx, symbolEl, group, seriesModel, seriesScope); + return; + } + // Update node and edge + updateNode(data, newIdx, symbolEl, group, seriesModel, seriesScope); + }) + .remove(function (oldIdx) { + var symbolEl = oldData.getItemGraphicEl(oldIdx); + // When remove a collapsed node of subtree, since the collapsed + // node haven't been initialized with a symbol element, + // you can't found it's symbol element through index. + // so if we want to remove the symbol element we should insure + // that the symbol element is not null. + if (symbolEl) { + removeNode(oldData, oldIdx, symbolEl, group, seriesModel, seriesScope); + } + }) + .execute(); + + this._nodeScaleRatio = seriesModel.get('nodeScaleRatio'); + + this._updateNodeAndLinkScale(seriesModel); + + if (seriesScope.expandAndCollapse === true) { + data.eachItemGraphicEl(function (el, dataIndex) { + el.off('click').on('click', function () { + api.dispatchAction({ + type: 'treeExpandAndCollapse', + seriesId: seriesModel.id, + dataIndex: dataIndex + }); + }); + }); + } + this._data = data; + }, + + _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 = []; + fromPoints(points, min, max); + + // If don't Store min max when collapse the root node after roam, + // the root node will disappear. + var oldMin = this._min; + var oldMax = this._max; + + // If width or height is 0 + if (max[0] - min[0] === 0) { + min[0] = oldMin ? oldMin[0] : min[0] - 1; + max[0] = oldMax ? oldMax[0] : max[0] + 1; + } + if (max[1] - min[1] === 0) { + min[1] = oldMin ? oldMin[1] : min[1] - 1; + max[1] = oldMax ? oldMax[1] : max[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')); + + // Here we use viewCoordSys just for computing the 'position' and 'scale' of the group + this.group.attr({ + position: viewCoordSys.position, + scale: viewCoordSys.scale + }); + + this._viewCoordSys = viewCoordSys; + this._min = min; + this._max = max; + }, + + _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) { + updateViewOnPan(controllerHost, e.dx, e.dy); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'treeRoam', + dx: e.dx, + dy: e.dy + }); + }, this) + .on('zoom', function (e) { + 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(); + this._data = null; + } + +}); + +function symbolNeedsDraw$1(data, dataIndex) { + var layout = data.getItemLayout(dataIndex); + + return layout + && !isNaN(layout.x) && !isNaN(layout.y) + && data.getItemVisual(dataIndex, 'symbol') !== 'none'; +} + +function getTreeNodeStyle(node, itemModel, seriesScope) { + seriesScope.itemModel = itemModel; + seriesScope.itemStyle = itemModel.getModel('itemStyle').getItemStyle(); + seriesScope.hoverItemStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle(); + seriesScope.lineStyle = itemModel.getModel('lineStyle').getLineStyle(); + seriesScope.labelModel = itemModel.getModel('label'); + seriesScope.hoverLabelModel = itemModel.getModel('emphasis.label'); + + if (node.isExpand === false && node.children.length !== 0) { + seriesScope.symbolInnerColor = seriesScope.itemStyle.fill; + } + else { + seriesScope.symbolInnerColor = '#fff'; + } + + return seriesScope; +} + +function updateNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope) { + var isInit = !symbolEl; + var node = data.tree.getNodeByDataIndex(dataIndex); + var itemModel = node.getModel(); + var seriesScope = getTreeNodeStyle(node, itemModel, seriesScope); + var virtualRoot = data.tree.root; + + var source = node.parentNode === virtualRoot ? node : node.parentNode || node; + var sourceSymbolEl = data.getItemGraphicEl(source.dataIndex); + var sourceLayout = source.getLayout(); + var sourceOldLayout = sourceSymbolEl + ? { + x: sourceSymbolEl.position[0], + y: sourceSymbolEl.position[1], + rawX: sourceSymbolEl.__radialOldRawX, + rawY: sourceSymbolEl.__radialOldRawY + } + : sourceLayout; + var targetLayout = node.getLayout(); + + if (isInit) { + symbolEl = new SymbolClz$1(data, dataIndex, seriesScope); + symbolEl.attr('position', [sourceOldLayout.x, sourceOldLayout.y]); + } + else { + symbolEl.updateData(data, dataIndex, seriesScope); + } + + symbolEl.__radialOldRawX = symbolEl.__radialRawX; + symbolEl.__radialOldRawY = symbolEl.__radialRawY; + symbolEl.__radialRawX = targetLayout.rawX; + symbolEl.__radialRawY = targetLayout.rawY; + + group.add(symbolEl); + data.setItemGraphicEl(dataIndex, symbolEl); + updateProps(symbolEl, { + position: [targetLayout.x, targetLayout.y] + }, seriesModel); + + var symbolPath = symbolEl.getSymbolPath(); + + if (seriesScope.layout === 'radial') { + var realRoot = virtualRoot.children[0]; + var rootLayout = realRoot.getLayout(); + var length = realRoot.children.length; + var rad; + var isLeft; + + if (targetLayout.x === rootLayout.x && node.isExpand === true) { + var center = {}; + center.x = (realRoot.children[0].getLayout().x + realRoot.children[length - 1].getLayout().x) / 2; + center.y = (realRoot.children[0].getLayout().y + realRoot.children[length - 1].getLayout().y) / 2; + rad = Math.atan2(center.y - rootLayout.y, center.x - rootLayout.x); + if (rad < 0) { + rad = Math.PI * 2 + rad; + } + isLeft = center.x < rootLayout.x; + if (isLeft) { + rad = rad - Math.PI; + } + } + else { + rad = Math.atan2(targetLayout.y - rootLayout.y, targetLayout.x - rootLayout.x); + if (rad < 0) { + rad = Math.PI * 2 + rad; + } + if (node.children.length === 0 || (node.children.length !== 0 && node.isExpand === false)) { + isLeft = targetLayout.x < rootLayout.x; + if (isLeft) { + rad = rad - Math.PI; + } + } + else { + isLeft = targetLayout.x > rootLayout.x; + if (!isLeft) { + rad = rad - Math.PI; + } + } + } + + var textPosition = isLeft ? 'left' : 'right'; + var rotate = seriesScope.labelModel.get('rotate'); + var labelRotateRadian = rotate * (Math.PI / 180); + + symbolPath.setStyle({ + textPosition: seriesScope.labelModel.get('position') || textPosition, + textRotation: rotate == null ? -rad : labelRotateRadian, + textOrigin: 'center', + verticalAlign: 'middle' + }); + } + + if (node.parentNode && node.parentNode !== virtualRoot) { + var edge = symbolEl.__edge; + if (!edge) { + edge = symbolEl.__edge = new BezierCurve({ + shape: getEdgeShape(seriesScope, sourceOldLayout, sourceOldLayout), + style: defaults({opacity: 0, strokeNoScale: true}, seriesScope.lineStyle) + }); + } + + updateProps(edge, { + shape: getEdgeShape(seriesScope, sourceLayout, targetLayout), + style: {opacity: 1} + }, seriesModel); + + group.add(edge); + } +} + +function removeNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope) { + var node = data.tree.getNodeByDataIndex(dataIndex); + var virtualRoot = data.tree.root; + var itemModel = node.getModel(); + var seriesScope = getTreeNodeStyle(node, itemModel, seriesScope); + + var source = node.parentNode === virtualRoot ? node : node.parentNode || node; + var sourceLayout; + while (sourceLayout = source.getLayout(), sourceLayout == null) { + source = source.parentNode === virtualRoot ? source : source.parentNode || source; + } + + updateProps(symbolEl, { + position: [sourceLayout.x + 1, sourceLayout.y + 1] + }, seriesModel, function () { + group.remove(symbolEl); + data.setItemGraphicEl(dataIndex, null); + }); + + symbolEl.fadeOut(null, {keepLabel: true}); + + var edge = symbolEl.__edge; + if (edge) { + updateProps(edge, { + shape: getEdgeShape(seriesScope, sourceLayout, sourceLayout), + style: { + opacity: 0 + } + }, seriesModel, function () { + group.remove(edge); + }); + } +} + +function getEdgeShape(seriesScope, sourceLayout, targetLayout) { + var cpx1; + var cpy1; + var cpx2; + var cpy2; + var orient = seriesScope.orient; + var x1; + var x2; + var y1; + var y2; + + if (seriesScope.layout === 'radial') { + x1 = sourceLayout.rawX; + y1 = sourceLayout.rawY; + x2 = targetLayout.rawX; + y2 = targetLayout.rawY; + + var radialCoor1 = radialCoordinate(x1, y1); + var radialCoor2 = radialCoordinate(x1, y1 + (y2 - y1) * seriesScope.curvature); + var radialCoor3 = radialCoordinate(x2, y2 + (y1 - y2) * seriesScope.curvature); + var radialCoor4 = radialCoordinate(x2, y2); + + return { + x1: radialCoor1.x, + y1: radialCoor1.y, + x2: radialCoor4.x, + y2: radialCoor4.y, + cpx1: radialCoor2.x, + cpy1: radialCoor2.y, + cpx2: radialCoor3.x, + cpy2: radialCoor3.y + }; + } + else { + x1 = sourceLayout.x; + y1 = sourceLayout.y; + x2 = targetLayout.x; + y2 = targetLayout.y; + + if (orient === 'LR' || orient === 'RL') { + cpx1 = x1 + (x2 - x1) * seriesScope.curvature; + cpy1 = y1; + cpx2 = x2 + (x1 - x2) * seriesScope.curvature; + cpy2 = y2; + } + if (orient === 'TB' || orient === 'BT') { + cpx1 = x1; + cpy1 = y1 + (y2 - y1) * seriesScope.curvature; + cpx2 = x2; + cpy2 = y2 + (y1 - y2) * seriesScope.curvature; + } + } + + return { + x1: x1, + y1: y1, + x2: x2, + y2: y2, + cpx1: cpx1, + cpy1: cpy1, + cpx2: cpx2, + cpy2: cpy2 + }; + +} + +/* +* 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. +*/ + +registerAction({ + type: 'treeExpandAndCollapse', + event: 'treeExpandAndCollapse', + update: 'update' +}, function (payload, ecModel) { + ecModel.eachComponent({mainType: 'series', subType: 'tree', query: payload}, function (seriesModel) { + var dataIndex = payload.dataIndex; + var tree = seriesModel.getData().tree; + var node = tree.getNodeByDataIndex(dataIndex); + node.isExpand = !node.isExpand; + }); +}); + +registerAction({ + type: 'treeRoam', + event: 'treeRoam', + // Here we set 'none' instead of 'update', because roam action + // just need to update the transform matrix without having to recalculate + // the layout. So don't need to go through the whole update process, such + // as 'dataPrcocess', 'coordSystemUpdate', 'layout' and so on. + update: 'none' +}, function (payload, ecModel) { + ecModel.eachComponent({mainType: 'series', subType: 'tree', 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); + }); +}); + +/* +* 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. +*/ + + +/** + * Traverse the tree from bottom to top and do something + * @param {module:echarts/data/Tree~TreeNode} root The real root of the tree + * @param {Function} callback + */ +function eachAfter(root, callback, separation) { + var nodes = [root]; + var next = []; + var node; + + while (node = nodes.pop()) { // jshint ignore:line + next.push(node); + if (node.isExpand) { + var children = node.children; + if (children.length) { + for (var i = 0; i < children.length; i++) { + nodes.push(children[i]); + } + } + } + } + + while (node = next.pop()) { // jshint ignore:line + callback(node, separation); + } +} + +/** + * Traverse the tree from top to bottom and do something + * @param {module:echarts/data/Tree~TreeNode} root The real root of the tree + * @param {Function} callback + */ +function eachBefore(root, callback) { + var nodes = [root]; + var node; + while (node = nodes.pop()) { // jshint ignore:line + callback(node); + if (node.isExpand) { + var children = node.children; + if (children.length) { + for (var i = children.length - 1; i >= 0; i--) { + nodes.push(children[i]); + } + } + } + } +} + +/* +* 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. +*/ + +var treeLayout = function (ecModel, api) { + ecModel.eachSeriesByType('tree', function (seriesModel) { + commonLayout(seriesModel, api); + }); +}; + +function commonLayout(seriesModel, api) { + var layoutInfo = getViewRect$1(seriesModel, api); + seriesModel.layoutInfo = layoutInfo; + var layout = seriesModel.get('layout'); + var width = 0; + var height = 0; + var separation$$1 = null; + + if (layout === 'radial') { + width = 2 * Math.PI; + height = Math.min(layoutInfo.height, layoutInfo.width) / 2; + separation$$1 = separation(function (node1, node2) { + return (node1.parentNode === node2.parentNode ? 1 : 2) / node1.depth; + }); + } + else { + width = layoutInfo.width; + height = layoutInfo.height; + separation$$1 = separation(); + } + + var virtualRoot = seriesModel.getData().tree.root; + var realRoot = virtualRoot.children[0]; + + if (realRoot) { + init$2(virtualRoot); + eachAfter(realRoot, firstWalk, separation$$1); + virtualRoot.hierNode.modifier = -realRoot.hierNode.prelim; + eachBefore(realRoot, secondWalk); + + var left = realRoot; + var right = realRoot; + var bottom = realRoot; + eachBefore(realRoot, function (node) { + var x = node.getLayout().x; + if (x < left.getLayout().x) { + left = node; + } + if (x > right.getLayout().x) { + right = node; + } + if (node.depth > bottom.depth) { + bottom = node; + } + }); + + var delta = left === right ? 1 : separation$$1(left, right) / 2; + var tx = delta - left.getLayout().x; + var kx = 0; + var ky = 0; + var coorX = 0; + var coorY = 0; + if (layout === 'radial') { + kx = width / (right.getLayout().x + delta + tx); + // here we use (node.depth - 1), bucause the real root's depth is 1 + ky = height / ((bottom.depth - 1) || 1); + eachBefore(realRoot, function (node) { + coorX = (node.getLayout().x + tx) * kx; + coorY = (node.depth - 1) * ky; + var finalCoor = radialCoordinate(coorX, coorY); + node.setLayout({x: finalCoor.x, y: finalCoor.y, rawX: coorX, rawY: coorY}, true); + }); + } + else { + var orient = seriesModel.getOrient(); + if (orient === 'RL' || orient === 'LR') { + ky = height / (right.getLayout().x + delta + tx); + kx = width / ((bottom.depth - 1) || 1); + eachBefore(realRoot, function (node) { + coorY = (node.getLayout().x + tx) * ky; + coorX = orient === 'LR' + ? (node.depth - 1) * kx + : width - (node.depth - 1) * kx; + node.setLayout({x: coorX, y: coorY}, true); + }); + } + else if (orient === 'TB' || orient === 'BT') { + kx = width / (right.getLayout().x + delta + tx); + ky = height / ((bottom.depth - 1) || 1); + eachBefore(realRoot, function (node) { + coorX = (node.getLayout().x + tx) * kx; + coorY = orient === 'TB' + ? (node.depth - 1) * ky + : height - (node.depth - 1) * ky; + node.setLayout({x: coorX, y: coorY}, true); + }); + } + } + } +} + +/* +* 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. +*/ + +registerVisual(visualSymbol('tree', 'circle')); +registerLayout(treeLayout); + +/* +* 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. +*/ + +function retrieveTargetInfo(payload, validPayloadTypes, seriesModel) { + if (payload && indexOf(validPayloadTypes, payload.type) >= 0) { + var root = seriesModel.getData().tree.root; + var targetNode = payload.targetNode; + + if (typeof targetNode === 'string') { + targetNode = root.getNodeById(targetNode); + } + + if (targetNode && root.contains(targetNode)) { + return {node: targetNode}; + } + + var targetNodeId = payload.targetNodeId; + if (targetNodeId != null && (targetNode = root.getNodeById(targetNodeId))) { + return {node: targetNode}; + } + } +} + +// Not includes the given node at the last item. +function getPathToRoot(node) { + var path = []; + while (node) { + node = node.parentNode; + node && path.push(node); + } + return path.reverse(); +} + +function aboveViewRoot(viewRoot, node) { + var viewPath = getPathToRoot(viewRoot); + return indexOf(viewPath, node) >= 0; +} + +// From root to the input node (the input node will be included). +function wrapTreePathInfo(node, seriesModel) { + var treePathInfo = []; + + while (node) { + var nodeDataIndex = node.dataIndex; + treePathInfo.push({ + name: node.name, + dataIndex: nodeDataIndex, + value: seriesModel.getRawValue(nodeDataIndex) + }); + node = node.parentNode; + } + + treePathInfo.reverse(); + + return treePathInfo; +} + +/* +* 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. +*/ + +SeriesModel.extend({ + + type: 'series.treemap', + + layoutMode: 'box', + + dependencies: ['grid', 'polar'], + + preventUsingHoverLayer: true, + + /** + * @type {module:echarts/data/Tree~Node} + */ + _viewRoot: null, + + defaultOption: { + // Disable progressive rendering + progressive: 0, + // center: ['50%', '50%'], // not supported in ec3. + // size: ['80%', '80%'], // deprecated, compatible with ec2. + left: 'center', + top: 'middle', + right: null, + bottom: null, + width: '80%', + height: '80%', + sort: true, // Can be null or false or true + // (order by desc default, asc not supported yet (strange effect)) + clipWindow: 'origin', // Size of clipped window when zooming. 'origin' or 'fullscreen' + squareRatio: 0.5 * (1 + Math.sqrt(5)), // golden ratio + leafDepth: null, // Nodes on depth from root are regarded as leaves. + // Count from zero (zero represents only view root). + drillDownIcon: '▶', // Use html character temporarily because it is complicated + // to align specialized icon. ▷▶❒❐▼✚ + + zoomToNodeRatio: 0.32 * 0.32, // Be effective when using zoomToNode. Specify the proportion of the + // target node area in the view area. + roam: true, // true, false, 'scale' or 'zoom', 'move'. + nodeClick: 'zoomToNode', // Leaf node click behaviour: 'zoomToNode', 'link', false. + // If leafDepth is set and clicking a node which has children but + // be on left depth, the behaviour would be changing root. Otherwise + // use behavious defined above. + animation: true, + animationDurationUpdate: 900, + animationEasing: 'quinticInOut', + breadcrumb: { + show: true, + height: 22, + left: 'center', + top: 'bottom', + // right + // bottom + emptyItemWidth: 25, // Width of empty node. + itemStyle: { + color: 'rgba(0,0,0,0.7)', //'#5793f3', + borderColor: 'rgba(255,255,255,0.7)', + borderWidth: 1, + shadowColor: 'rgba(150,150,150,1)', + shadowBlur: 3, + shadowOffsetX: 0, + shadowOffsetY: 0, + textStyle: { + color: '#fff' + } + }, + emphasis: { + textStyle: {} + } + }, + label: { + show: true, + // Do not use textDistance, for ellipsis rect just the same as treemap node rect. + distance: 0, + padding: 5, + position: 'inside', // Can be [5, '5%'] or position stirng like 'insideTopLeft', ... + // formatter: null, + color: '#fff', + ellipsis: true + // align + // verticalAlign + }, + upperLabel: { // Label when node is parent. + show: false, + position: [0, '50%'], + height: 20, + // formatter: null, + color: '#fff', + ellipsis: true, + // align: null, + verticalAlign: 'middle' + }, + itemStyle: { + color: null, // Can be 'none' if not necessary. + colorAlpha: null, // Can be 'none' if not necessary. + colorSaturation: null, // Can be 'none' if not necessary. + borderWidth: 0, + gapWidth: 0, + borderColor: '#fff', + borderColorSaturation: null // If specified, borderColor will be ineffective, and the + // border color is evaluated by color of current node and + // borderColorSaturation. + }, + emphasis: { + upperLabel: { + show: true, + position: [0, '50%'], + color: '#fff', + ellipsis: true, + verticalAlign: 'middle' + } + }, + + visualDimension: 0, // Can be 0, 1, 2, 3. + visualMin: null, + visualMax: null, + + color: [], // + treemapSeries.color should not be modified. Please only modified + // level[n].color (if necessary). + // + Specify color list of each level. level[0].color would be global + // color list if not specified. (see method `setDefault`). + // + But set as a empty array to forbid fetch color from global palette + // when using nodeModel.get('color'), otherwise nodes on deep level + // will always has color palette set and are not able to inherit color + // from parent node. + // + TreemapSeries.color can not be set as 'none', otherwise effect + // legend color fetching (see seriesColor.js). + colorAlpha: null, // Array. Specify color alpha range of each level, like [0.2, 0.8] + colorSaturation: null, // Array. Specify color saturation of each level, like [0.2, 0.5] + colorMappingBy: 'index', // 'value' or 'index' or 'id'. + visibleMin: 10, // If area less than this threshold (unit: pixel^2), node will not + // be rendered. Only works when sort is 'asc' or 'desc'. + childrenVisibleMin: null, // If area of a node less than this threshold (unit: pixel^2), + // grandchildren will not show. + // Why grandchildren? If not grandchildren but children, + // some siblings show children and some not, + // the appearance may be mess and not consistent, + levels: [] // Each item: { + // visibleMin, itemStyle, visualDimension, label + // } + // data: { + // value: [], + // children: [], + // link: 'http://xxx.xxx.xxx', + // target: 'blank' or 'self' + // } + }, + + /** + * @override + */ + getInitialData: function (option, ecModel) { + // Create a virtual root. + var root = {name: option.name, children: option.data}; + + completeTreeValue(root); + + var levels = option.levels || []; + + levels = option.levels = setDefault(levels, ecModel); + + var treeOption = {}; + + treeOption.levels = levels; + + // Make sure always a new tree is created when setOption, + // in TreemapView, we check whether oldTree === newTree + // to choose mappings approach among old shapes and new shapes. + return Tree.createTree(root, this, treeOption).data; + }, + + optionUpdated: function () { + this.resetViewRoot(); + }, + + /** + * @override + * @param {number} dataIndex + * @param {boolean} [mutipleSeries=false] + */ + formatTooltip: function (dataIndex) { + var data = this.getData(); + var value = this.getRawValue(dataIndex); + var formattedValue = isArray(value) + ? addCommas(value[0]) : addCommas(value); + var name = data.getName(dataIndex); + + return encodeHTML(name + ': ' + formattedValue); + }, + + /** + * Add tree path to tooltip param + * + * @override + * @param {number} dataIndex + * @return {Object} + */ + getDataParams: function (dataIndex) { + var params = SeriesModel.prototype.getDataParams.apply(this, arguments); + + var node = this.getData().tree.getNodeByDataIndex(dataIndex); + params.treePathInfo = wrapTreePathInfo(node, this); + + return params; + }, + + /** + * @public + * @param {Object} layoutInfo { + * x: containerGroup x + * y: containerGroup y + * width: containerGroup width + * height: containerGroup height + * } + */ + setLayoutInfo: function (layoutInfo) { + /** + * @readOnly + * @type {Object} + */ + this.layoutInfo = this.layoutInfo || {}; + extend(this.layoutInfo, layoutInfo); + }, + + /** + * @param {string} id + * @return {number} index + */ + mapIdToIndex: function (id) { + // A feature is implemented: + // index is monotone increasing with the sequence of + // input id at the first time. + // This feature can make sure that each data item and its + // mapped color have the same index between data list and + // color list at the beginning, which is useful for user + // to adjust data-color mapping. + + /** + * @private + * @type {Object} + */ + var idIndexMap = this._idIndexMap; + + if (!idIndexMap) { + idIndexMap = this._idIndexMap = createHashMap(); + /** + * @private + * @type {number} + */ + this._idIndexMapCount = 0; + } + + var index = idIndexMap.get(id); + if (index == null) { + idIndexMap.set(id, index = this._idIndexMapCount++); + } + + return index; + }, + + getViewRoot: function () { + return this._viewRoot; + }, + + /** + * @param {module:echarts/data/Tree~Node} [viewRoot] + */ + resetViewRoot: function (viewRoot) { + viewRoot + ? (this._viewRoot = viewRoot) + : (viewRoot = this._viewRoot); + + var root = this.getRawData().tree.root; + + if (!viewRoot + || (viewRoot !== root && !root.contains(viewRoot)) + ) { + this._viewRoot = root; + } + } +}); + +/** + * @param {Object} dataNode + */ +function completeTreeValue(dataNode) { + // Postorder travel tree. + // If value of none-leaf node is not set, + // calculate it by suming up the value of all children. + var sum = 0; + + each$1(dataNode.children, function (child) { + + completeTreeValue(child); + + var childValue = child.value; + isArray(childValue) && (childValue = childValue[0]); + + sum += childValue; + }); + + var thisValue = dataNode.value; + if (isArray(thisValue)) { + thisValue = thisValue[0]; + } + + if (thisValue == null || isNaN(thisValue)) { + thisValue = sum; + } + // Value should not less than 0. + if (thisValue < 0) { + thisValue = 0; + } + + isArray(dataNode.value) + ? (dataNode.value[0] = thisValue) + : (dataNode.value = thisValue); +} + +/** + * set default to level configuration + */ +function setDefault(levels, ecModel) { + var globalColorList = ecModel.get('color'); + + if (!globalColorList) { + return; + } + + levels = levels || []; + var hasColorDefine; + each$1(levels, function (levelDefine) { + var model = new Model(levelDefine); + var modelColor = model.get('color'); + + if (model.get('itemStyle.color') + || (modelColor && modelColor !== 'none') + ) { + hasColorDefine = true; + } + }); + + if (!hasColorDefine) { + var level0 = levels[0] || (levels[0] = {}); + level0.color = globalColorList.slice(); + } + + return levels; +} + +/* +* 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. +*/ + +var TEXT_PADDING = 8; +var ITEM_GAP = 8; +var ARRAY_LENGTH = 5; + +function Breadcrumb(containerGroup) { + /** + * @private + * @type {module:zrender/container/Group} + */ + this.group = new Group(); + + containerGroup.add(this.group); +} + +Breadcrumb.prototype = { + + constructor: Breadcrumb, + + render: function (seriesModel, api, targetNode, onSelect) { + var model = seriesModel.getModel('breadcrumb'); + var thisGroup = this.group; + + thisGroup.removeAll(); + + if (!model.get('show') || !targetNode) { + return; + } + + var normalStyleModel = model.getModel('itemStyle'); + // var emphasisStyleModel = model.getModel('emphasis.itemStyle'); + var textStyleModel = normalStyleModel.getModel('textStyle'); + + var layoutParam = { + pos: { + left: model.get('left'), + right: model.get('right'), + top: model.get('top'), + bottom: model.get('bottom') + }, + box: { + width: api.getWidth(), + height: api.getHeight() + }, + emptyItemWidth: model.get('emptyItemWidth'), + totalWidth: 0, + renderList: [] + }; + + this._prepare(targetNode, layoutParam, textStyleModel); + this._renderContent(seriesModel, layoutParam, normalStyleModel, textStyleModel, onSelect); + + positionElement(thisGroup, layoutParam.pos, layoutParam.box); + }, + + /** + * Prepare render list and total width + * @private + */ + _prepare: function (targetNode, layoutParam, textStyleModel) { + for (var node = targetNode; node; node = node.parentNode) { + var text = node.getModel().get('name'); + var textRect = textStyleModel.getTextRect(text); + var itemWidth = Math.max( + textRect.width + TEXT_PADDING * 2, + layoutParam.emptyItemWidth + ); + layoutParam.totalWidth += itemWidth + ITEM_GAP; + layoutParam.renderList.push({node: node, text: text, width: itemWidth}); + } + }, + + /** + * @private + */ + _renderContent: function ( + seriesModel, layoutParam, normalStyleModel, textStyleModel, onSelect + ) { + // Start rendering. + var lastX = 0; + var emptyItemWidth = layoutParam.emptyItemWidth; + var height = seriesModel.get('breadcrumb.height'); + var availableSize = getAvailableSize(layoutParam.pos, layoutParam.box); + var totalWidth = layoutParam.totalWidth; + var renderList = layoutParam.renderList; + + for (var i = renderList.length - 1; i >= 0; i--) { + var item = renderList[i]; + var itemNode = item.node; + var itemWidth = item.width; + var text = item.text; + + // Hdie text and shorten width if necessary. + if (totalWidth > availableSize.width) { + totalWidth -= itemWidth - emptyItemWidth; + itemWidth = emptyItemWidth; + text = null; + } + + var el = new Polygon({ + shape: { + points: makeItemPoints( + lastX, 0, itemWidth, height, + i === renderList.length - 1, i === 0 + ) + }, + style: defaults( + normalStyleModel.getItemStyle(), + { + lineJoin: 'bevel', + text: text, + textFill: textStyleModel.getTextColor(), + textFont: textStyleModel.getFont() + } + ), + z: 10, + onclick: curry(onSelect, itemNode) + }); + this.group.add(el); + + packEventData(el, seriesModel, itemNode); + + lastX += itemWidth + ITEM_GAP; + } + }, + + /** + * @override + */ + remove: function () { + this.group.removeAll(); + } +}; + +function makeItemPoints(x, y, itemWidth, itemHeight, head, tail) { + var points = [ + [head ? x : x - ARRAY_LENGTH, y], + [x + itemWidth, y], + [x + itemWidth, y + itemHeight], + [head ? x : x - ARRAY_LENGTH, y + itemHeight] + ]; + !tail && points.splice(2, 0, [x + itemWidth + ARRAY_LENGTH, y + itemHeight / 2]); + !head && points.push([x, y + itemHeight / 2]); + return points; +} + +// Package custom mouse event. +function packEventData(el, seriesModel, itemNode) { + el.eventData = { + componentType: 'series', + componentSubType: 'treemap', + componentIndex: seriesModel.componentIndex, + seriesIndex: seriesModel.componentIndex, + seriesName: seriesModel.name, + seriesType: 'treemap', + selfType: 'breadcrumb', // Distinguish with click event on treemap node. + nodeData: { + dataIndex: itemNode && itemNode.dataIndex, + name: itemNode && itemNode.name + }, + treePathInfo: itemNode && wrapTreePathInfo(itemNode, seriesModel) + }; +} + +/* +* 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. +*/ + +/** + * @param {number} [time=500] Time in ms + * @param {string} [easing='linear'] + * @param {number} [delay=0] + * @param {Function} [callback] + * + * @example + * // Animate position + * animation + * .createWrap() + * .add(el1, {position: [10, 10]}) + * .add(el2, {shape: {width: 500}, style: {fill: 'red'}}, 400) + * .done(function () { // done }) + * .start('cubicOut'); + */ +function createWrap() { + + var storage = []; + var elExistsMap = {}; + var doneCallback; + + return { + + /** + * Caution: a el can only be added once, otherwise 'done' + * might not be called. This method checks this (by el.id), + * suppresses adding and returns false when existing el found. + * + * @param {modele:zrender/Element} el + * @param {Object} target + * @param {number} [time=500] + * @param {number} [delay=0] + * @param {string} [easing='linear'] + * @return {boolean} Whether adding succeeded. + * + * @example + * add(el, target, time, delay, easing); + * add(el, target, time, easing); + * add(el, target, time); + * add(el, target); + */ + add: function (el, target, time, delay, easing) { + if (isString(delay)) { + easing = delay; + delay = 0; + } + + if (elExistsMap[el.id]) { + return false; + } + elExistsMap[el.id] = 1; + + storage.push( + {el: el, target: target, time: time, delay: delay, easing: easing} + ); + + return true; + }, + + /** + * Only execute when animation finished. Will not execute when any + * of 'stop' or 'stopAnimation' called. + * + * @param {Function} callback + */ + done: function (callback) { + doneCallback = callback; + return this; + }, + + /** + * Will stop exist animation firstly. + */ + start: function () { + var count = storage.length; + + for (var i = 0, len = storage.length; i < len; i++) { + var item = storage[i]; + item.el.animateTo(item.target, item.time, item.delay, item.easing, done); + } + + return this; + + function done() { + count--; + if (!count) { + storage.length = 0; + elExistsMap = {}; + doneCallback && doneCallback(); + } + } + } + }; +} + +/* +* 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. +*/ + +var bind$1 = bind; +var Group$2 = Group; +var Rect$1 = Rect; +var each$8 = each$1; + +var DRAG_THRESHOLD = 3; +var PATH_LABEL_NOAMAL = ['label']; +var PATH_LABEL_EMPHASIS = ['emphasis', 'label']; +var PATH_UPPERLABEL_NORMAL = ['upperLabel']; +var PATH_UPPERLABEL_EMPHASIS = ['emphasis', 'upperLabel']; +var Z_BASE = 10; // Should bigger than every z. +var Z_BG = 1; +var Z_CONTENT = 2; + +var getItemStyleEmphasis = makeStyleMapper([ + ['fill', 'color'], + // `borderColor` and `borderWidth` has been occupied, + // so use `stroke` to indicate the stroke of the rect. + ['stroke', 'strokeColor'], + ['lineWidth', 'strokeWidth'], + ['shadowBlur'], + ['shadowOffsetX'], + ['shadowOffsetY'], + ['shadowColor'] +]); +var getItemStyleNormal = function (model) { + // Normal style props should include emphasis style props. + var itemStyle = getItemStyleEmphasis(model); + // Clear styles set by emphasis. + itemStyle.stroke = itemStyle.fill = itemStyle.lineWidth = null; + return itemStyle; +}; + +extendChartView({ + + type: 'treemap', + + /** + * @override + */ + init: function (o, api) { + + /** + * @private + * @type {module:zrender/container/Group} + */ + this._containerGroup; + + /** + * @private + * @type {Object.>} + */ + this._storage = createStorage(); + + /** + * @private + * @type {module:echarts/data/Tree} + */ + this._oldTree; + + /** + * @private + * @type {module:echarts/chart/treemap/Breadcrumb} + */ + this._breadcrumb; + + /** + * @private + * @type {module:echarts/component/helper/RoamController} + */ + this._controller; + + /** + * 'ready', 'animating' + * @private + */ + this._state = 'ready'; + }, + + /** + * @override + */ + render: function (seriesModel, ecModel, api, payload) { + + var models = ecModel.findComponents({ + mainType: 'series', subType: 'treemap', query: payload + }); + if (indexOf(models, seriesModel) < 0) { + return; + } + + this.seriesModel = seriesModel; + this.api = api; + this.ecModel = ecModel; + + var types = ['treemapZoomToNode', 'treemapRootToNode']; + var targetInfo = retrieveTargetInfo(payload, types, seriesModel); + var payloadType = payload && payload.type; + var layoutInfo = seriesModel.layoutInfo; + var isInit = !this._oldTree; + var thisStorage = this._storage; + + // Mark new root when action is treemapRootToNode. + var reRoot = (payloadType === 'treemapRootToNode' && targetInfo && thisStorage) + ? { + rootNodeGroup: thisStorage.nodeGroup[targetInfo.node.getRawIndex()], + direction: payload.direction + } + : null; + + var containerGroup = this._giveContainerGroup(layoutInfo); + + var renderResult = this._doRender(containerGroup, seriesModel, reRoot); + ( + !isInit && ( + !payloadType + || payloadType === 'treemapZoomToNode' + || payloadType === 'treemapRootToNode' + ) + ) + ? this._doAnimation(containerGroup, renderResult, seriesModel, reRoot) + : renderResult.renderFinally(); + + this._resetController(api); + + this._renderBreadcrumb(seriesModel, api, targetInfo); + }, + + /** + * @private + */ + _giveContainerGroup: function (layoutInfo) { + var containerGroup = this._containerGroup; + if (!containerGroup) { + // FIXME + // 加一层containerGroup是为了clip,但是现在clip功能并没有实现。 + containerGroup = this._containerGroup = new Group$2(); + this._initEvents(containerGroup); + this.group.add(containerGroup); + } + containerGroup.attr('position', [layoutInfo.x, layoutInfo.y]); + + return containerGroup; + }, + + /** + * @private + */ + _doRender: function (containerGroup, seriesModel, reRoot) { + var thisTree = seriesModel.getData().tree; + var oldTree = this._oldTree; + + // Clear last shape records. + var lastsForAnimation = createStorage(); + var thisStorage = createStorage(); + var oldStorage = this._storage; + var willInvisibleEls = []; + var doRenderNode = curry( + renderNode, seriesModel, + thisStorage, oldStorage, reRoot, + lastsForAnimation, willInvisibleEls + ); + + // Notice: when thisTree and oldTree are the same tree (see list.cloneShallow), + // the oldTree is actually losted, so we can not find all of the old graphic + // elements from tree. So we use this stragegy: make element storage, move + // from old storage to new storage, clear old storage. + + dualTravel( + thisTree.root ? [thisTree.root] : [], + (oldTree && oldTree.root) ? [oldTree.root] : [], + containerGroup, + thisTree === oldTree || !oldTree, + 0 + ); + + // Process all removing. + var willDeleteEls = clearStorage(oldStorage); + + this._oldTree = thisTree; + this._storage = thisStorage; + + return { + lastsForAnimation: lastsForAnimation, + willDeleteEls: willDeleteEls, + renderFinally: renderFinally + }; + + function dualTravel(thisViewChildren, oldViewChildren, parentGroup, sameTree, depth) { + // When 'render' is triggered by action, + // 'this' and 'old' may be the same tree, + // we use rawIndex in that case. + if (sameTree) { + oldViewChildren = thisViewChildren; + each$8(thisViewChildren, function (child, index) { + !child.isRemoved() && processNode(index, index); + }); + } + // Diff hierarchically (diff only in each subtree, but not whole). + // because, consistency of view is important. + else { + (new DataDiffer(oldViewChildren, thisViewChildren, getKey, getKey)) + .add(processNode) + .update(processNode) + .remove(curry(processNode, null)) + .execute(); + } + + function getKey(node) { + // Identify by name or raw index. + return node.getId(); + } + + function processNode(newIndex, oldIndex) { + var thisNode = newIndex != null ? thisViewChildren[newIndex] : null; + var oldNode = oldIndex != null ? oldViewChildren[oldIndex] : null; + + var group = doRenderNode(thisNode, oldNode, parentGroup, depth); + + group && dualTravel( + thisNode && thisNode.viewChildren || [], + oldNode && oldNode.viewChildren || [], + group, + sameTree, + depth + 1 + ); + } + } + + function clearStorage(storage) { + var willDeleteEls = createStorage(); + storage && each$8(storage, function (store, storageName) { + var delEls = willDeleteEls[storageName]; + each$8(store, function (el) { + el && (delEls.push(el), el.__tmWillDelete = 1); + }); + }); + return willDeleteEls; + } + + function renderFinally() { + each$8(willDeleteEls, function (els) { + each$8(els, function (el) { + el.parent && el.parent.remove(el); + }); + }); + each$8(willInvisibleEls, function (el) { + el.invisible = true; + // Setting invisible is for optimizing, so no need to set dirty, + // just mark as invisible. + el.dirty(); + }); + } + }, + + /** + * @private + */ + _doAnimation: function (containerGroup, renderResult, seriesModel, reRoot) { + if (!seriesModel.get('animation')) { + return; + } + + var duration = seriesModel.get('animationDurationUpdate'); + var easing = seriesModel.get('animationEasing'); + var animationWrap = createWrap(); + + // Make delete animations. + each$8(renderResult.willDeleteEls, function (store, storageName) { + each$8(store, function (el, rawIndex) { + if (el.invisible) { + return; + } + + var parent = el.parent; // Always has parent, and parent is nodeGroup. + var target; + + if (reRoot && reRoot.direction === 'drillDown') { + target = parent === reRoot.rootNodeGroup + // This is the content element of view root. + // Only `content` will enter this branch, because + // `background` and `nodeGroup` will not be deleted. + ? { + shape: { + x: 0, + y: 0, + width: parent.__tmNodeWidth, + height: parent.__tmNodeHeight + }, + style: { + opacity: 0 + } + } + // Others. + : {style: {opacity: 0}}; + } + else { + var targetX = 0; + var targetY = 0; + + if (!parent.__tmWillDelete) { + // Let node animate to right-bottom corner, cooperating with fadeout, + // which is appropriate for user understanding. + // Divided by 2 for reRoot rolling up effect. + targetX = parent.__tmNodeWidth / 2; + targetY = parent.__tmNodeHeight / 2; + } + + target = storageName === 'nodeGroup' + ? {position: [targetX, targetY], style: {opacity: 0}} + : { + shape: {x: targetX, y: targetY, width: 0, height: 0}, + style: {opacity: 0} + }; + } + + target && animationWrap.add(el, target, duration, easing); + }); + }); + + // Make other animations + each$8(this._storage, function (store, storageName) { + each$8(store, function (el, rawIndex) { + var last = renderResult.lastsForAnimation[storageName][rawIndex]; + var target = {}; + + if (!last) { + return; + } + + if (storageName === 'nodeGroup') { + if (last.old) { + target.position = el.position.slice(); + el.attr('position', last.old); + } + } + else { + if (last.old) { + target.shape = extend({}, el.shape); + el.setShape(last.old); + } + + if (last.fadein) { + el.setStyle('opacity', 0); + target.style = {opacity: 1}; + } + // When animation is stopped for succedent animation starting, + // el.style.opacity might not be 1 + else if (el.style.opacity !== 1) { + target.style = {opacity: 1}; + } + } + + animationWrap.add(el, target, duration, easing); + }); + }, this); + + this._state = 'animating'; + + animationWrap + .done(bind$1(function () { + this._state = 'ready'; + renderResult.renderFinally(); + }, this)) + .start(); + }, + + /** + * @private + */ + _resetController: function (api) { + var controller = this._controller; + + // Init controller. + if (!controller) { + controller = this._controller = new RoamController(api.getZr()); + controller.enable(this.seriesModel.get('roam')); + controller.on('pan', bind$1(this._onPan, this)); + controller.on('zoom', bind$1(this._onZoom, this)); + } + + var rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight()); + controller.setPointerChecker(function (e, x, y) { + return rect.contain(x, y); + }); + }, + + /** + * @private + */ + _clearController: function () { + var controller = this._controller; + if (controller) { + controller.dispose(); + controller = null; + } + }, + + /** + * @private + */ + _onPan: function (e) { + if (this._state !== 'animating' + && (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; + + if (!root) { + return; + } + + var rootLayout = root.getLayout(); + + if (!rootLayout) { + return; + } + + this.api.dispatchAction({ + type: 'treemapMove', + from: this.uid, + seriesId: this.seriesModel.id, + rootRect: { + x: rootLayout.x + e.dx, y: rootLayout.y + e.dy, + width: rootLayout.width, height: rootLayout.height + } + }); + } + }, + + /** + * @private + */ + _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; + + if (!root) { + return; + } + + var rootLayout = root.getLayout(); + + if (!rootLayout) { + return; + } + + var rect = new BoundingRect( + rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height + ); + var layoutInfo = this.seriesModel.layoutInfo; + + // Transform mouse coord from global to containerGroup. + mouseX -= layoutInfo.x; + mouseY -= layoutInfo.y; + + // Scale root bounding rect. + var m = create$1(); + translate(m, m, [-mouseX, -mouseY]); + scale$1(m, m, [e.scale, e.scale]); + translate(m, m, [mouseX, mouseY]); + + rect.applyTransform(m); + + this.api.dispatchAction({ + type: 'treemapRender', + from: this.uid, + seriesId: this.seriesModel.id, + rootRect: { + x: rect.x, y: rect.y, + width: rect.width, height: rect.height + } + }); + } + }, + + /** + * @private + */ + _initEvents: function (containerGroup) { + containerGroup.on('click', function (e) { + if (this._state !== 'ready') { + return; + } + + var nodeClick = this.seriesModel.get('nodeClick', true); + + if (!nodeClick) { + return; + } + + var targetInfo = this.findTarget(e.offsetX, e.offsetY); + + if (!targetInfo) { + return; + } + + var node = targetInfo.node; + if (node.getLayout().isLeafRoot) { + this._rootToNode(targetInfo); + } + else { + if (nodeClick === 'zoomToNode') { + this._zoomToNode(targetInfo); + } + else if (nodeClick === 'link') { + var itemModel = node.hostTree.data.getItemModel(node.dataIndex); + var link = itemModel.get('link', true); + var linkTarget = itemModel.get('target', true) || 'blank'; + link && window.open(link, linkTarget); + } + } + + }, this); + }, + + /** + * @private + */ + _renderBreadcrumb: function (seriesModel, api, targetInfo) { + if (!targetInfo) { + targetInfo = seriesModel.get('leafDepth', true) != null + ? {node: seriesModel.getViewRoot()} + // FIXME + // better way? + // Find breadcrumb tail on center of containerGroup. + : this.findTarget(api.getWidth() / 2, api.getHeight() / 2); + + if (!targetInfo) { + targetInfo = {node: seriesModel.getData().tree.root}; + } + } + + (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group))) + .render(seriesModel, api, targetInfo.node, bind$1(onSelect, this)); + + function onSelect(node) { + if (this._state !== 'animating') { + aboveViewRoot(seriesModel.getViewRoot(), node) + ? this._rootToNode({node: node}) + : this._zoomToNode({node: node}); + } + } + }, + + /** + * @override + */ + remove: function () { + this._clearController(); + this._containerGroup && this._containerGroup.removeAll(); + this._storage = createStorage(); + this._state = 'ready'; + this._breadcrumb && this._breadcrumb.remove(); + }, + + dispose: function () { + this._clearController(); + }, + + /** + * @private + */ + _zoomToNode: function (targetInfo) { + this.api.dispatchAction({ + type: 'treemapZoomToNode', + from: this.uid, + seriesId: this.seriesModel.id, + targetNode: targetInfo.node + }); + }, + + /** + * @private + */ + _rootToNode: function (targetInfo) { + this.api.dispatchAction({ + type: 'treemapRootToNode', + from: this.uid, + seriesId: this.seriesModel.id, + targetNode: targetInfo.node + }); + }, + + /** + * @public + * @param {number} x Global coord x. + * @param {number} y Global coord y. + * @return {Object} info If not found, return undefined; + * @return {number} info.node Target node. + * @return {number} info.offsetX x refer to target node. + * @return {number} info.offsetY y refer to target node. + */ + findTarget: function (x, y) { + var targetInfo; + var viewRoot = this.seriesModel.getViewRoot(); + + viewRoot.eachNode({attr: 'viewChildren', order: 'preorder'}, function (node) { + var bgEl = this._storage.background[node.getRawIndex()]; + // If invisible, there might be no element. + if (bgEl) { + var point = bgEl.transformCoordToLocal(x, y); + var shape = bgEl.shape; + + // For performance consideration, dont use 'getBoundingRect'. + if (shape.x <= point[0] + && point[0] <= shape.x + shape.width + && shape.y <= point[1] + && point[1] <= shape.y + shape.height + ) { + targetInfo = {node: node, offsetX: point[0], offsetY: point[1]}; + } + else { + return false; // Suppress visit subtree. + } + } + }, this); + + return targetInfo; + } + +}); + +/** + * @inner + */ +function createStorage() { + return {nodeGroup: [], background: [], content: []}; +} + +/** + * @inner + * @return Return undefined means do not travel further. + */ +function renderNode( + seriesModel, thisStorage, oldStorage, reRoot, + lastsForAnimation, willInvisibleEls, + thisNode, oldNode, parentGroup, depth +) { + // Whether under viewRoot. + if (!thisNode) { + // Deleting nodes will be performed finally. This method just find + // element from old storage, or create new element, set them to new + // storage, and set styles. + return; + } + + // ------------------------------------------------------------------- + // Start of closure variables available in "Procedures in renderNode". + + var thisLayout = thisNode.getLayout(); + + if (!thisLayout || !thisLayout.isInView) { + return; + } + + var thisWidth = thisLayout.width; + var thisHeight = thisLayout.height; + var borderWidth = thisLayout.borderWidth; + var thisInvisible = thisLayout.invisible; + + var thisRawIndex = thisNode.getRawIndex(); + var oldRawIndex = oldNode && oldNode.getRawIndex(); + + var thisViewChildren = thisNode.viewChildren; + var upperHeight = thisLayout.upperHeight; + var isParent = thisViewChildren && thisViewChildren.length; + var itemStyleNormalModel = thisNode.getModel('itemStyle'); + var itemStyleEmphasisModel = thisNode.getModel('emphasis.itemStyle'); + + // End of closure ariables available in "Procedures in renderNode". + // ----------------------------------------------------------------- + + // Node group + var group = giveGraphic('nodeGroup', Group$2); + + if (!group) { + return; + } + + parentGroup.add(group); + // x,y are not set when el is above view root. + group.attr('position', [thisLayout.x || 0, thisLayout.y || 0]); + group.__tmNodeWidth = thisWidth; + group.__tmNodeHeight = thisHeight; + + if (thisLayout.isAboveViewRoot) { + return group; + } + + // Background + var bg = giveGraphic('background', Rect$1, depth, Z_BG); + bg && renderBackground(group, bg, isParent && thisLayout.upperHeight); + + // No children, render content. + if (!isParent) { + var content = giveGraphic('content', Rect$1, depth, Z_CONTENT); + content && renderContent(group, content); + } + + return group; + + // ---------------------------- + // | Procedures in renderNode | + // ---------------------------- + + function renderBackground(group, bg, useUpperLabel) { + // For tooltip. + bg.dataIndex = thisNode.dataIndex; + bg.seriesIndex = seriesModel.seriesIndex; + + bg.setShape({x: 0, y: 0, width: thisWidth, height: thisHeight}); + var visualBorderColor = thisNode.getVisual('borderColor', true); + var emphasisBorderColor = itemStyleEmphasisModel.get('borderColor'); + + updateStyle(bg, function () { + var normalStyle = getItemStyleNormal(itemStyleNormalModel); + normalStyle.fill = visualBorderColor; + var emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel); + emphasisStyle.fill = emphasisBorderColor; + + if (useUpperLabel) { + var upperLabelWidth = thisWidth - 2 * borderWidth; + + prepareText( + normalStyle, emphasisStyle, visualBorderColor, upperLabelWidth, upperHeight, + {x: borderWidth, y: 0, width: upperLabelWidth, height: upperHeight} + ); + } + // For old bg. + else { + normalStyle.text = emphasisStyle.text = null; + } + + bg.setStyle(normalStyle); + setHoverStyle(bg, emphasisStyle); + }); + + group.add(bg); + } + + function renderContent(group, content) { + // For tooltip. + content.dataIndex = thisNode.dataIndex; + content.seriesIndex = seriesModel.seriesIndex; + + var contentWidth = Math.max(thisWidth - 2 * borderWidth, 0); + var contentHeight = Math.max(thisHeight - 2 * borderWidth, 0); + + content.culling = true; + content.setShape({ + x: borderWidth, + y: borderWidth, + width: contentWidth, + height: contentHeight + }); + + var visualColor = thisNode.getVisual('color', true); + updateStyle(content, function () { + var normalStyle = getItemStyleNormal(itemStyleNormalModel); + normalStyle.fill = visualColor; + var emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel); + + prepareText(normalStyle, emphasisStyle, visualColor, contentWidth, contentHeight); + + content.setStyle(normalStyle); + setHoverStyle(content, emphasisStyle); + }); + + group.add(content); + } + + function updateStyle(element, cb) { + if (!thisInvisible) { + // If invisible, do not set visual, otherwise the element will + // change immediately before animation. We think it is OK to + // remain its origin color when moving out of the view window. + cb(); + + if (!element.__tmWillVisible) { + element.invisible = false; + } + } + else { + // Delay invisible setting utill animation finished, + // avoid element vanish suddenly before animation. + !element.invisible && willInvisibleEls.push(element); + } + } + + function prepareText(normalStyle, emphasisStyle, visualColor, width, height, upperLabelRect) { + var nodeModel = thisNode.getModel(); + var text = retrieve( + seriesModel.getFormattedLabel( + thisNode.dataIndex, 'normal', null, null, upperLabelRect ? 'upperLabel' : 'label' + ), + nodeModel.get('name') + ); + if (!upperLabelRect && thisLayout.isLeafRoot) { + var iconChar = seriesModel.get('drillDownIcon', true); + text = iconChar ? iconChar + ' ' + text : text; + } + + var normalLabelModel = nodeModel.getModel( + upperLabelRect ? PATH_UPPERLABEL_NORMAL : PATH_LABEL_NOAMAL + ); + var emphasisLabelModel = nodeModel.getModel( + upperLabelRect ? PATH_UPPERLABEL_EMPHASIS : PATH_LABEL_EMPHASIS + ); + + var isShow = normalLabelModel.getShallow('show'); + + setLabelStyle( + normalStyle, emphasisStyle, normalLabelModel, emphasisLabelModel, + { + defaultText: isShow ? text : null, + autoColor: visualColor, + isRectText: true + } + ); + + upperLabelRect && (normalStyle.textRect = clone(upperLabelRect)); + + normalStyle.truncate = (isShow && normalLabelModel.get('ellipsis')) + ? { + outerWidth: width, + outerHeight: height, + minChar: 2 + } + : null; + } + + function giveGraphic(storageName, Ctor, depth, z) { + var element = oldRawIndex != null && oldStorage[storageName][oldRawIndex]; + var lasts = lastsForAnimation[storageName]; + + if (element) { + // Remove from oldStorage + oldStorage[storageName][oldRawIndex] = null; + prepareAnimationWhenHasOld(lasts, element, storageName); + } + // If invisible and no old element, do not create new element (for optimizing). + else if (!thisInvisible) { + element = new Ctor({z: calculateZ(depth, z)}); + element.__tmDepth = depth; + element.__tmStorageName = storageName; + prepareAnimationWhenNoOld(lasts, element, storageName); + } + + // Set to thisStorage + return (thisStorage[storageName][thisRawIndex] = element); + } + + function prepareAnimationWhenHasOld(lasts, element, storageName) { + var lastCfg = lasts[thisRawIndex] = {}; + lastCfg.old = storageName === 'nodeGroup' + ? element.position.slice() + : extend({}, element.shape); + } + + // If a element is new, we need to find the animation start point carefully, + // otherwise it will looks strange when 'zoomToNode'. + function prepareAnimationWhenNoOld(lasts, element, storageName) { + var lastCfg = lasts[thisRawIndex] = {}; + var parentNode = thisNode.parentNode; + + if (parentNode && (!reRoot || reRoot.direction === 'drillDown')) { + var parentOldX = 0; + var parentOldY = 0; + + // New nodes appear from right-bottom corner in 'zoomToNode' animation. + // For convenience, get old bounding rect from background. + var parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()]; + if (!reRoot && parentOldBg && parentOldBg.old) { + parentOldX = parentOldBg.old.width; + parentOldY = parentOldBg.old.height; + } + + // When no parent old shape found, its parent is new too, + // so we can just use {x:0, y:0}. + lastCfg.old = storageName === 'nodeGroup' + ? [0, parentOldY] + : {x: parentOldX, y: parentOldY, width: 0, height: 0}; + } + + // Fade in, user can be aware that these nodes are new. + lastCfg.fadein = storageName !== 'nodeGroup'; + } +} + +// We can not set all backgroud with the same z, Because the behaviour of +// drill down and roll up differ background creation sequence from tree +// hierarchy sequence, which cause that lowser background element overlap +// upper ones. So we calculate z based on depth. +// Moreover, we try to shrink down z interval to [0, 1] to avoid that +// treemap with large z overlaps other components. +function calculateZ(depth, zInLevel) { + var zb = depth * Z_BASE + zInLevel; + return (zb - 1) / zb; +} + +/* +* 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. +*/ + +/** + * @file Treemap action + */ + +var noop$1 = function () {}; + +var actionTypes = [ + 'treemapZoomToNode', + 'treemapRender', + 'treemapMove' +]; + +for (var i$2 = 0; i$2 < actionTypes.length; i$2++) { + registerAction({type: actionTypes[i$2], update: 'updateView'}, noop$1); +} + +registerAction( + {type: 'treemapRootToNode', update: 'updateView'}, + function (payload, ecModel) { + + ecModel.eachComponent( + {mainType: 'series', subType: 'treemap', query: payload}, + handleRootToNode + ); + + function handleRootToNode(model, index) { + var types = ['treemapZoomToNode', 'treemapRootToNode']; + var targetInfo = retrieveTargetInfo(payload, types, model); + + if (targetInfo) { + var originViewRoot = model.getViewRoot(); + if (originViewRoot) { + payload.direction = aboveViewRoot(originViewRoot, targetInfo.node) + ? 'rollUp' : 'drillDown'; + } + model.resetViewRoot(targetInfo.node); + } + } + } +); + +/* +* 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. +*/ + +var each$9 = each$1; +var isObject$5 = isObject$1; + +var CATEGORY_DEFAULT_VISUAL_INDEX = -1; + +/** + * @param {Object} option + * @param {string} [option.type] See visualHandlers. + * @param {string} [option.mappingMethod] 'linear' or 'piecewise' or 'category' or 'fixed' + * @param {Array.=} [option.dataExtent] [minExtent, maxExtent], + * required when mappingMethod is 'linear' + * @param {Array.=} [option.pieceList] [ + * {value: someValue}, + * {interval: [min1, max1], visual: {...}}, + * {interval: [min2, max2]} + * ], + * required when mappingMethod is 'piecewise'. + * Visual for only each piece can be specified. + * @param {Array.=} [option.categories] ['cate1', 'cate2'] + * required when mappingMethod is 'category'. + * If no option.categories, categories is set + * as [0, 1, 2, ...]. + * @param {boolean} [option.loop=false] Whether loop mapping when mappingMethod is 'category'. + * @param {(Array|Object|*)} [option.visual] Visual data. + * when mappingMethod is 'category', + * visual data can be array or object + * (like: {cate1: '#222', none: '#fff'}) + * or primary types (which represents + * defualt category visual), otherwise visual + * can be array or primary (which will be + * normalized to array). + * + */ +var VisualMapping = function (option) { + var mappingMethod = option.mappingMethod; + var visualType = option.type; + + /** + * @readOnly + * @type {Object} + */ + var thisOption = this.option = clone(option); + + /** + * @readOnly + * @type {string} + */ + this.type = visualType; + + /** + * @readOnly + * @type {string} + */ + this.mappingMethod = mappingMethod; + + /** + * @private + * @type {Function} + */ + this._normalizeData = normalizers[mappingMethod]; + + var visualHandler = visualHandlers[visualType]; + + /** + * @public + * @type {Function} + */ + this.applyVisual = visualHandler.applyVisual; + + /** + * @public + * @type {Function} + */ + this.getColorMapper = visualHandler.getColorMapper; + + /** + * @private + * @type {Function} + */ + this._doMap = visualHandler._doMap[mappingMethod]; + + if (mappingMethod === 'piecewise') { + normalizeVisualRange(thisOption); + preprocessForPiecewise(thisOption); + } + else if (mappingMethod === 'category') { + thisOption.categories + ? preprocessForSpecifiedCategory(thisOption) + // categories is ordinal when thisOption.categories not specified, + // which need no more preprocess except normalize visual. + : normalizeVisualRange(thisOption, true); + } + else { // mappingMethod === 'linear' or 'fixed' + assert$1(mappingMethod !== 'linear' || thisOption.dataExtent); + normalizeVisualRange(thisOption); + } +}; + +VisualMapping.prototype = { + + constructor: VisualMapping, + + mapValueToVisual: function (value) { + var normalized = this._normalizeData(value); + return this._doMap(normalized, value); + }, + + getNormalizer: function () { + return bind(this._normalizeData, this); + } +}; + +var visualHandlers = VisualMapping.visualHandlers = { + + color: { + + applyVisual: makeApplyVisual('color'), + + /** + * Create a mapper function + * @return {Function} + */ + getColorMapper: function () { + var thisOption = this.option; + + return bind( + thisOption.mappingMethod === 'category' + ? function (value, isNormalized) { + !isNormalized && (value = this._normalizeData(value)); + return doMapCategory.call(this, value); + } + : function (value, isNormalized, out) { + // If output rgb array + // which will be much faster and useful in pixel manipulation + var returnRGBArray = !!out; + !isNormalized && (value = this._normalizeData(value)); + out = fastLerp(value, thisOption.parsedVisual, out); + return returnRGBArray ? out : stringify(out, 'rgba'); + }, + this + ); + }, + + _doMap: { + linear: function (normalized) { + return stringify( + fastLerp(normalized, this.option.parsedVisual), + 'rgba' + ); + }, + category: doMapCategory, + piecewise: function (normalized, value) { + var result = getSpecifiedVisual.call(this, value); + if (result == null) { + result = stringify( + fastLerp(normalized, this.option.parsedVisual), + 'rgba' + ); + } + return result; + }, + fixed: doMapFixed + } + }, + + colorHue: makePartialColorVisualHandler(function (color, value) { + return modifyHSL(color, value); + }), + + colorSaturation: makePartialColorVisualHandler(function (color, value) { + return modifyHSL(color, null, value); + }), + + colorLightness: makePartialColorVisualHandler(function (color, value) { + return modifyHSL(color, null, null, value); + }), + + colorAlpha: makePartialColorVisualHandler(function (color, value) { + return modifyAlpha(color, value); + }), + + opacity: { + applyVisual: makeApplyVisual('opacity'), + _doMap: makeDoMap([0, 1]) + }, + + liftZ: { + applyVisual: makeApplyVisual('liftZ'), + _doMap: { + linear: doMapFixed, + category: doMapFixed, + piecewise: doMapFixed, + fixed: doMapFixed + } + }, + + symbol: { + applyVisual: function (value, getter, setter) { + var symbolCfg = this.mapValueToVisual(value); + if (isString(symbolCfg)) { + setter('symbol', symbolCfg); + } + else if (isObject$5(symbolCfg)) { + for (var name in symbolCfg) { + if (symbolCfg.hasOwnProperty(name)) { + setter(name, symbolCfg[name]); + } + } + } + }, + _doMap: { + linear: doMapToArray, + category: doMapCategory, + piecewise: function (normalized, value) { + var result = getSpecifiedVisual.call(this, value); + if (result == null) { + result = doMapToArray.call(this, normalized); + } + return result; + }, + fixed: doMapFixed + } + }, + + symbolSize: { + applyVisual: makeApplyVisual('symbolSize'), + _doMap: makeDoMap([0, 1]) + } +}; + + +function preprocessForPiecewise(thisOption) { + var pieceList = thisOption.pieceList; + thisOption.hasSpecialVisual = false; + + each$1(pieceList, function (piece, index) { + piece.originIndex = index; + // piece.visual is "result visual value" but not + // a visual range, so it does not need to be normalized. + if (piece.visual != null) { + thisOption.hasSpecialVisual = true; + } + }); +} + +function preprocessForSpecifiedCategory(thisOption) { + // Hash categories. + var categories = thisOption.categories; + var visual = thisOption.visual; + + var categoryMap = thisOption.categoryMap = {}; + each$9(categories, function (cate, index) { + categoryMap[cate] = index; + }); + + // Process visual map input. + if (!isArray(visual)) { + var visualArr = []; + + if (isObject$1(visual)) { + each$9(visual, function (v, cate) { + var index = categoryMap[cate]; + visualArr[index != null ? index : CATEGORY_DEFAULT_VISUAL_INDEX] = v; + }); + } + else { // Is primary type, represents default visual. + visualArr[CATEGORY_DEFAULT_VISUAL_INDEX] = visual; + } + + visual = setVisualToOption(thisOption, visualArr); + } + + // Remove categories that has no visual, + // then we can mapping them to CATEGORY_DEFAULT_VISUAL_INDEX. + for (var i = categories.length - 1; i >= 0; i--) { + if (visual[i] == null) { + delete categoryMap[categories[i]]; + categories.pop(); + } + } +} + +function normalizeVisualRange(thisOption, isCategory) { + var visual = thisOption.visual; + var visualArr = []; + + if (isObject$1(visual)) { + each$9(visual, function (v) { + visualArr.push(v); + }); + } + else if (visual != null) { + visualArr.push(visual); + } + + var doNotNeedPair = {color: 1, symbol: 1}; + + if (!isCategory + && visualArr.length === 1 + && !doNotNeedPair.hasOwnProperty(thisOption.type) + ) { + // Do not care visualArr.length === 0, which is illegal. + visualArr[1] = visualArr[0]; + } + + setVisualToOption(thisOption, visualArr); +} + +function makePartialColorVisualHandler(applyValue) { + return { + applyVisual: function (value, getter, setter) { + value = this.mapValueToVisual(value); + // Must not be array value + setter('color', applyValue(getter('color'), value)); + }, + _doMap: makeDoMap([0, 1]) + }; +} + +function doMapToArray(normalized) { + var visual = this.option.visual; + return visual[ + Math.round(linearMap(normalized, [0, 1], [0, visual.length - 1], true)) + ] || {}; +} + +function makeApplyVisual(visualType) { + return function (value, getter, setter) { + setter(visualType, this.mapValueToVisual(value)); + }; +} + +function doMapCategory(normalized) { + var visual = this.option.visual; + return visual[ + (this.option.loop && normalized !== CATEGORY_DEFAULT_VISUAL_INDEX) + ? normalized % visual.length + : normalized + ]; +} + +function doMapFixed() { + return this.option.visual[0]; +} + +function makeDoMap(sourceExtent) { + return { + linear: function (normalized) { + return linearMap(normalized, sourceExtent, this.option.visual, true); + }, + category: doMapCategory, + piecewise: function (normalized, value) { + var result = getSpecifiedVisual.call(this, value); + if (result == null) { + result = linearMap(normalized, sourceExtent, this.option.visual, true); + } + return result; + }, + fixed: doMapFixed + }; +} + +function getSpecifiedVisual(value) { + var thisOption = this.option; + var pieceList = thisOption.pieceList; + if (thisOption.hasSpecialVisual) { + var pieceIndex = VisualMapping.findPieceIndex(value, pieceList); + var piece = pieceList[pieceIndex]; + if (piece && piece.visual) { + return piece.visual[this.type]; + } + } +} + +function setVisualToOption(thisOption, visualArr) { + thisOption.visual = visualArr; + if (thisOption.type === 'color') { + thisOption.parsedVisual = map(visualArr, function (item) { + return parse(item); + }); + } + return visualArr; +} + + +/** + * Normalizers by mapping methods. + */ +var normalizers = { + + linear: function (value) { + return linearMap(value, this.option.dataExtent, [0, 1], true); + }, + + piecewise: function (value) { + var pieceList = this.option.pieceList; + var pieceIndex = VisualMapping.findPieceIndex(value, pieceList, true); + if (pieceIndex != null) { + return linearMap(pieceIndex, [0, pieceList.length - 1], [0, 1], true); + } + }, + + category: function (value) { + var index = this.option.categories + ? this.option.categoryMap[value] + : value; // ordinal + return index == null ? CATEGORY_DEFAULT_VISUAL_INDEX : index; + }, + + fixed: noop +}; + + + +/** + * List available visual types. + * + * @public + * @return {Array.} + */ +VisualMapping.listVisualTypes = function () { + var visualTypes = []; + each$1(visualHandlers, function (handler, key) { + visualTypes.push(key); + }); + return visualTypes; +}; + +/** + * @public + */ +VisualMapping.addVisualHandler = function (name, handler) { + visualHandlers[name] = handler; +}; + +/** + * @public + */ +VisualMapping.isValidType = function (visualType) { + return visualHandlers.hasOwnProperty(visualType); +}; + +/** + * Convinent method. + * Visual can be Object or Array or primary type. + * + * @public + */ +VisualMapping.eachVisual = function (visual, callback, context) { + if (isObject$1(visual)) { + each$1(visual, callback, context); + } + else { + callback.call(context, visual); + } +}; + +VisualMapping.mapVisual = function (visual, callback, context) { + var isPrimary; + var newVisual = isArray(visual) + ? [] + : isObject$1(visual) + ? {} + : (isPrimary = true, null); + + VisualMapping.eachVisual(visual, function (v, key) { + var newVal = callback.call(context, v, key); + isPrimary ? (newVisual = newVal) : (newVisual[key] = newVal); + }); + return newVisual; +}; + +/** + * @public + * @param {Object} obj + * @return {Object} new object containers visual values. + * If no visuals, return null. + */ +VisualMapping.retrieveVisuals = function (obj) { + var ret = {}; + var hasVisual; + + obj && each$9(visualHandlers, function (h, visualType) { + if (obj.hasOwnProperty(visualType)) { + ret[visualType] = obj[visualType]; + hasVisual = true; + } + }); + + return hasVisual ? ret : null; +}; + +/** + * Give order to visual types, considering colorSaturation, colorAlpha depends on color. + * + * @public + * @param {(Object|Array)} visualTypes If Object, like: {color: ..., colorSaturation: ...} + * IF Array, like: ['color', 'symbol', 'colorSaturation'] + * @return {Array.} Sorted visual types. + */ +VisualMapping.prepareVisualTypes = function (visualTypes) { + if (isObject$5(visualTypes)) { + var types = []; + each$9(visualTypes, function (item, type) { + types.push(type); + }); + visualTypes = types; + } + else if (isArray(visualTypes)) { + visualTypes = visualTypes.slice(); + } + else { + return []; + } + + visualTypes.sort(function (type1, type2) { + // color should be front of colorSaturation, colorAlpha, ... + // symbol and symbolSize do not matter. + return (type2 === 'color' && type1 !== 'color' && type1.indexOf('color') === 0) + ? 1 : -1; + }); + + return visualTypes; +}; + +/** + * 'color', 'colorSaturation', 'colorAlpha', ... are depends on 'color'. + * Other visuals are only depends on themself. + * + * @public + * @param {string} visualType1 + * @param {string} visualType2 + * @return {boolean} + */ +VisualMapping.dependsOn = function (visualType1, visualType2) { + return visualType2 === 'color' + ? !!(visualType1 && visualType1.indexOf(visualType2) === 0) + : visualType1 === visualType2; +}; + +/** + * @param {number} value + * @param {Array.} pieceList [{value: ..., interval: [min, max]}, ...] + * Always from small to big. + * @param {boolean} [findClosestWhenOutside=false] + * @return {number} index + */ +VisualMapping.findPieceIndex = function (value, pieceList, findClosestWhenOutside) { + var possibleI; + var abs = Infinity; + + // value has the higher priority. + for (var i = 0, len = pieceList.length; i < len; i++) { + var pieceValue = pieceList[i].value; + if (pieceValue != null) { + if (pieceValue === value + // FIXME + // It is supposed to compare value according to value type of dimension, + // but currently value type can exactly be string or number. + // Compromise for numeric-like string (like '12'), especially + // in the case that visualMap.categories is ['22', '33']. + || (typeof pieceValue === 'string' && pieceValue === value + '') + ) { + return i; + } + findClosestWhenOutside && updatePossible(pieceValue, i); + } + } + + for (var i = 0, len = pieceList.length; i < len; i++) { + var piece = pieceList[i]; + var interval = piece.interval; + var close = piece.close; + + if (interval) { + if (interval[0] === -Infinity) { + if (littleThan(close[1], value, interval[1])) { + return i; + } + } + else if (interval[1] === Infinity) { + if (littleThan(close[0], interval[0], value)) { + return i; + } + } + else if ( + littleThan(close[0], interval[0], value) + && littleThan(close[1], value, interval[1]) + ) { + return i; + } + findClosestWhenOutside && updatePossible(interval[0], i); + findClosestWhenOutside && updatePossible(interval[1], i); + } + } + + if (findClosestWhenOutside) { + return value === Infinity + ? pieceList.length - 1 + : value === -Infinity + ? 0 + : possibleI; + } + + function updatePossible(val, index) { + var newAbs = Math.abs(val - value); + if (newAbs < abs) { + abs = newAbs; + possibleI = index; + } + } + +}; + +function littleThan(close, a, b) { + return close ? a <= b : a < b; +} + +/* +* 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. +*/ + +var isArray$2 = isArray; + +var ITEM_STYLE_NORMAL = 'itemStyle'; + +var treemapVisual = { + seriesType: 'treemap', + reset: function (seriesModel, ecModel, api, payload) { + var tree = seriesModel.getData().tree; + var root = tree.root; + var seriesItemStyleModel = seriesModel.getModel(ITEM_STYLE_NORMAL); + + if (root.isRemoved()) { + return; + } + + var levelItemStyles = map(tree.levelModels, function (levelModel) { + return levelModel ? levelModel.get(ITEM_STYLE_NORMAL) : null; + }); + + travelTree( + root, // Visual should calculate from tree root but not view root. + {}, + levelItemStyles, + seriesItemStyleModel, + seriesModel.getViewRoot().getAncestors(), + seriesModel + ); + } +}; + +function travelTree( + node, designatedVisual, levelItemStyles, seriesItemStyleModel, + viewRootAncestors, seriesModel +) { + var nodeModel = node.getModel(); + var nodeLayout = node.getLayout(); + + // Optimize + if (!nodeLayout || nodeLayout.invisible || !nodeLayout.isInView) { + return; + } + + var nodeItemStyleModel = node.getModel(ITEM_STYLE_NORMAL); + var levelItemStyle = levelItemStyles[node.depth]; + var visuals = buildVisuals( + nodeItemStyleModel, designatedVisual, levelItemStyle, seriesItemStyleModel + ); + + // calculate border color + var borderColor = nodeItemStyleModel.get('borderColor'); + var borderColorSaturation = nodeItemStyleModel.get('borderColorSaturation'); + var thisNodeColor; + if (borderColorSaturation != null) { + // For performance, do not always execute 'calculateColor'. + thisNodeColor = calculateColor(visuals, node); + borderColor = calculateBorderColor(borderColorSaturation, thisNodeColor); + } + node.setVisual('borderColor', borderColor); + + var viewChildren = node.viewChildren; + if (!viewChildren || !viewChildren.length) { + thisNodeColor = calculateColor(visuals, node); + // Apply visual to this node. + node.setVisual('color', thisNodeColor); + } + else { + var mapping = buildVisualMapping( + node, nodeModel, nodeLayout, nodeItemStyleModel, visuals, viewChildren + ); + + // Designate visual to children. + each$1(viewChildren, function (child, index) { + // If higher than viewRoot, only ancestors of viewRoot is needed to visit. + if (child.depth >= viewRootAncestors.length + || child === viewRootAncestors[child.depth] + ) { + var childVisual = mapVisual$1( + nodeModel, visuals, child, index, mapping, seriesModel + ); + travelTree( + child, childVisual, levelItemStyles, seriesItemStyleModel, + viewRootAncestors, seriesModel + ); + } + }); + } +} + +function buildVisuals( + nodeItemStyleModel, designatedVisual, levelItemStyle, seriesItemStyleModel +) { + var visuals = extend({}, designatedVisual); + + each$1(['color', 'colorAlpha', 'colorSaturation'], function (visualName) { + // Priority: thisNode > thisLevel > parentNodeDesignated > seriesModel + var val = nodeItemStyleModel.get(visualName, true); // Ignore parent + val == null && levelItemStyle && (val = levelItemStyle[visualName]); + val == null && (val = designatedVisual[visualName]); + val == null && (val = seriesItemStyleModel.get(visualName)); + + val != null && (visuals[visualName] = val); + }); + + return visuals; +} + +function calculateColor(visuals) { + var color = getValueVisualDefine(visuals, 'color'); + + if (color) { + var colorAlpha = getValueVisualDefine(visuals, 'colorAlpha'); + var colorSaturation = getValueVisualDefine(visuals, 'colorSaturation'); + if (colorSaturation) { + color = modifyHSL(color, null, null, colorSaturation); + } + if (colorAlpha) { + color = modifyAlpha(color, colorAlpha); + } + + return color; + } +} + +function calculateBorderColor(borderColorSaturation, thisNodeColor) { + return thisNodeColor != null + ? modifyHSL(thisNodeColor, null, null, borderColorSaturation) + : null; +} + +function getValueVisualDefine(visuals, name) { + var value = visuals[name]; + if (value != null && value !== 'none') { + return value; + } +} + +function buildVisualMapping( + node, nodeModel, nodeLayout, nodeItemStyleModel, visuals, viewChildren +) { + if (!viewChildren || !viewChildren.length) { + return; + } + + var rangeVisual = getRangeVisual(nodeModel, 'color') + || ( + visuals.color != null + && visuals.color !== 'none' + && ( + getRangeVisual(nodeModel, 'colorAlpha') + || getRangeVisual(nodeModel, 'colorSaturation') + ) + ); + + if (!rangeVisual) { + return; + } + + var visualMin = nodeModel.get('visualMin'); + var visualMax = nodeModel.get('visualMax'); + var dataExtent = nodeLayout.dataExtent.slice(); + visualMin != null && visualMin < dataExtent[0] && (dataExtent[0] = visualMin); + visualMax != null && visualMax > dataExtent[1] && (dataExtent[1] = visualMax); + + var colorMappingBy = nodeModel.get('colorMappingBy'); + var opt = { + type: rangeVisual.name, + dataExtent: dataExtent, + visual: rangeVisual.range + }; + if (opt.type === 'color' + && (colorMappingBy === 'index' || colorMappingBy === 'id') + ) { + opt.mappingMethod = 'category'; + opt.loop = true; + // categories is ordinal, so do not set opt.categories. + } + else { + opt.mappingMethod = 'linear'; + } + + var mapping = new VisualMapping(opt); + mapping.__drColorMappingBy = colorMappingBy; + + return mapping; +} + +// Notice: If we dont have the attribute 'colorRange', but only use +// attribute 'color' to represent both concepts of 'colorRange' and 'color', +// (It means 'colorRange' when 'color' is Array, means 'color' when not array), +// this problem will be encountered: +// If a level-1 node dont have children, and its siblings has children, +// and colorRange is set on level-1, then the node can not be colored. +// So we separate 'colorRange' and 'color' to different attributes. +function getRangeVisual(nodeModel, name) { + // 'colorRange', 'colorARange', 'colorSRange'. + // If not exsits on this node, fetch from levels and series. + var range = nodeModel.get(name); + return (isArray$2(range) && range.length) ? {name: name, range: range} : null; +} + +function mapVisual$1(nodeModel, visuals, child, index, mapping, seriesModel) { + var childVisuals = extend({}, visuals); + + if (mapping) { + var mappingType = mapping.type; + var colorMappingBy = mappingType === 'color' && mapping.__drColorMappingBy; + var value = colorMappingBy === 'index' + ? index + : colorMappingBy === 'id' + ? seriesModel.mapIdToIndex(child.getId()) + : child.getValue(nodeModel.get('visualDimension')); + + childVisuals[mappingType] = mapping.mapValueToVisual(value); + } + + return childVisuals; +} + +/* +* 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. +*/ + +/* +* A third-party license is embeded for some of the code in this file: +* The treemap layout implementation was originally copied from +* "d3.js" with some modifications made for this project. +* (See more details in the comment of the method "squarify" below.) +* The use of the source code of this file is also subject to the terms +* and consitions of the license of "d3.js" (BSD-3Clause, see +* ). +*/ + +var mathMax$5 = Math.max; +var mathMin$5 = Math.min; +var retrieveValue = retrieve; +var each$10 = each$1; + +var PATH_BORDER_WIDTH = ['itemStyle', 'borderWidth']; +var PATH_GAP_WIDTH = ['itemStyle', 'gapWidth']; +var PATH_UPPER_LABEL_SHOW = ['upperLabel', 'show']; +var PATH_UPPER_LABEL_HEIGHT = ['upperLabel', 'height']; + +/** + * @public + */ +var treemapLayout = { + seriesType: 'treemap', + reset: function (seriesModel, ecModel, api, payload) { + // Layout result in each node: + // {x, y, width, height, area, borderWidth} + var ecWidth = api.getWidth(); + var ecHeight = api.getHeight(); + var seriesOption = seriesModel.option; + + var layoutInfo = getLayoutRect( + seriesModel.getBoxLayoutParams(), + { + width: api.getWidth(), + height: api.getHeight() + } + ); + + var size = seriesOption.size || []; // Compatible with ec2. + var containerWidth = parsePercent$1( + retrieveValue(layoutInfo.width, size[0]), + ecWidth + ); + var containerHeight = parsePercent$1( + retrieveValue(layoutInfo.height, size[1]), + ecHeight + ); + + // Fetch payload info. + var payloadType = payload && payload.type; + var types = ['treemapZoomToNode', 'treemapRootToNode']; + var targetInfo = retrieveTargetInfo(payload, types, seriesModel); + var rootRect = (payloadType === 'treemapRender' || payloadType === 'treemapMove') + ? payload.rootRect : null; + var viewRoot = seriesModel.getViewRoot(); + var viewAbovePath = getPathToRoot(viewRoot); + + if (payloadType !== 'treemapMove') { + var rootSize = payloadType === 'treemapZoomToNode' + ? estimateRootSize( + seriesModel, targetInfo, viewRoot, containerWidth, containerHeight + ) + : rootRect + ? [rootRect.width, rootRect.height] + : [containerWidth, containerHeight]; + + var sort = seriesOption.sort; + if (sort && sort !== 'asc' && sort !== 'desc') { + sort = 'desc'; + } + var options = { + squareRatio: seriesOption.squareRatio, + sort: sort, + leafDepth: seriesOption.leafDepth + }; + + // layout should be cleared because using updateView but not update. + viewRoot.hostTree.clearLayouts(); + + // TODO + // optimize: if out of view clip, do not layout. + // But take care that if do not render node out of view clip, + // how to calculate start po + + var viewRootLayout = { + x: 0, y: 0, + width: rootSize[0], height: rootSize[1], + area: rootSize[0] * rootSize[1] + }; + viewRoot.setLayout(viewRootLayout); + + squarify(viewRoot, options, false, 0); + // Supplement layout. + var viewRootLayout = viewRoot.getLayout(); + each$10(viewAbovePath, function (node, index) { + var childValue = (viewAbovePath[index + 1] || viewRoot).getValue(); + node.setLayout(extend( + {dataExtent: [childValue, childValue], borderWidth: 0, upperHeight: 0}, + viewRootLayout + )); + }); + } + + var treeRoot = seriesModel.getData().tree.root; + + treeRoot.setLayout( + calculateRootPosition(layoutInfo, rootRect, targetInfo), + true + ); + + seriesModel.setLayoutInfo(layoutInfo); + + // FIXME + // 现在没有clip功能,暂时取ec高宽。 + prunning( + treeRoot, + // Transform to base element coordinate system. + new BoundingRect(-layoutInfo.x, -layoutInfo.y, ecWidth, ecHeight), + viewAbovePath, + viewRoot, + 0 + ); + } +}; + +/** + * Layout treemap with squarify algorithm. + * The original presentation of this algorithm + * was made by Mark Bruls, Kees Huizing, and Jarke J. van Wijk + * . + * The implementation of this algorithm was originally copied from "d3.js" + * + * with some modifications made for this program. + * See the license statement at the head of this file. + * + * @protected + * @param {module:echarts/data/Tree~TreeNode} node + * @param {Object} options + * @param {string} options.sort 'asc' or 'desc' + * @param {number} options.squareRatio + * @param {boolean} hideChildren + * @param {number} depth + */ +function squarify(node, options, hideChildren, depth) { + var width; + var height; + + if (node.isRemoved()) { + return; + } + + var thisLayout = node.getLayout(); + width = thisLayout.width; + height = thisLayout.height; + + // Considering border and gap + var nodeModel = node.getModel(); + var borderWidth = nodeModel.get(PATH_BORDER_WIDTH); + var halfGapWidth = nodeModel.get(PATH_GAP_WIDTH) / 2; + var upperLabelHeight = getUpperLabelHeight(nodeModel); + var upperHeight = Math.max(borderWidth, upperLabelHeight); + var layoutOffset = borderWidth - halfGapWidth; + var layoutOffsetUpper = upperHeight - halfGapWidth; + var nodeModel = node.getModel(); + + node.setLayout({ + borderWidth: borderWidth, + upperHeight: upperHeight, + upperLabelHeight: upperLabelHeight + }, true); + + width = mathMax$5(width - 2 * layoutOffset, 0); + height = mathMax$5(height - layoutOffset - layoutOffsetUpper, 0); + + var totalArea = width * height; + var viewChildren = initChildren( + node, nodeModel, totalArea, options, hideChildren, depth + ); + + if (!viewChildren.length) { + return; + } + + var rect = {x: layoutOffset, y: layoutOffsetUpper, width: width, height: height}; + var rowFixedLength = mathMin$5(width, height); + var best = Infinity; // the best row score so far + var row = []; + row.area = 0; + + for (var i = 0, len = viewChildren.length; i < len;) { + var child = viewChildren[i]; + + row.push(child); + row.area += child.getLayout().area; + var score = worst(row, rowFixedLength, options.squareRatio); + + // continue with this orientation + if (score <= best) { + i++; + best = score; + } + // abort, and try a different orientation + else { + row.area -= row.pop().getLayout().area; + position(row, rowFixedLength, rect, halfGapWidth, false); + rowFixedLength = mathMin$5(rect.width, rect.height); + row.length = row.area = 0; + best = Infinity; + } + } + + if (row.length) { + position(row, rowFixedLength, rect, halfGapWidth, true); + } + + if (!hideChildren) { + var childrenVisibleMin = nodeModel.get('childrenVisibleMin'); + if (childrenVisibleMin != null && totalArea < childrenVisibleMin) { + hideChildren = true; + } + } + + for (var i = 0, len = viewChildren.length; i < len; i++) { + squarify(viewChildren[i], options, hideChildren, depth + 1); + } +} + +/** + * Set area to each child, and calculate data extent for visual coding. + */ +function initChildren(node, nodeModel, totalArea, options, hideChildren, depth) { + var viewChildren = node.children || []; + var orderBy = options.sort; + orderBy !== 'asc' && orderBy !== 'desc' && (orderBy = null); + + var overLeafDepth = options.leafDepth != null && options.leafDepth <= depth; + + // leafDepth has higher priority. + if (hideChildren && !overLeafDepth) { + return (node.viewChildren = []); + } + + // Sort children, order by desc. + viewChildren = filter(viewChildren, function (child) { + return !child.isRemoved(); + }); + + sort$1(viewChildren, orderBy); + + var info = statistic(nodeModel, viewChildren, orderBy); + + if (info.sum === 0) { + return (node.viewChildren = []); + } + + info.sum = filterByThreshold(nodeModel, totalArea, info.sum, orderBy, viewChildren); + + if (info.sum === 0) { + return (node.viewChildren = []); + } + + // Set area to each child. + for (var i = 0, len = viewChildren.length; i < len; i++) { + var area = viewChildren[i].getValue() / info.sum * totalArea; + // Do not use setLayout({...}, true), because it is needed to clear last layout. + viewChildren[i].setLayout({area: area}); + } + + if (overLeafDepth) { + viewChildren.length && node.setLayout({isLeafRoot: true}, true); + viewChildren.length = 0; + } + + node.viewChildren = viewChildren; + node.setLayout({dataExtent: info.dataExtent}, true); + + return viewChildren; +} + +/** + * Consider 'visibleMin'. Modify viewChildren and get new sum. + */ +function filterByThreshold(nodeModel, totalArea, sum, orderBy, orderedChildren) { + + // visibleMin is not supported yet when no option.sort. + if (!orderBy) { + return sum; + } + + var visibleMin = nodeModel.get('visibleMin'); + var len = orderedChildren.length; + var deletePoint = len; + + // Always travel from little value to big value. + for (var i = len - 1; i >= 0; i--) { + var value = orderedChildren[ + orderBy === 'asc' ? len - i - 1 : i + ].getValue(); + + if (value / sum * totalArea < visibleMin) { + deletePoint = i; + sum -= value; + } + } + + orderBy === 'asc' + ? orderedChildren.splice(0, len - deletePoint) + : orderedChildren.splice(deletePoint, len - deletePoint); + + return sum; +} + +/** + * Sort + */ +function sort$1(viewChildren, orderBy) { + if (orderBy) { + viewChildren.sort(function (a, b) { + var diff = orderBy === 'asc' + ? a.getValue() - b.getValue() : b.getValue() - a.getValue(); + return diff === 0 + ? (orderBy === 'asc' + ? a.dataIndex - b.dataIndex : b.dataIndex - a.dataIndex + ) + : diff; + }); + } + return viewChildren; +} + +/** + * Statistic + */ +function statistic(nodeModel, children, orderBy) { + // Calculate sum. + var sum = 0; + for (var i = 0, len = children.length; i < len; i++) { + sum += children[i].getValue(); + } + + // Statistic data extent for latter visual coding. + // Notice: data extent should be calculate based on raw children + // but not filtered view children, otherwise visual mapping will not + // be stable when zoom (where children is filtered by visibleMin). + + var dimension = nodeModel.get('visualDimension'); + var dataExtent; + + // The same as area dimension. + if (!children || !children.length) { + dataExtent = [NaN, NaN]; + } + else if (dimension === 'value' && orderBy) { + dataExtent = [ + children[children.length - 1].getValue(), + children[0].getValue() + ]; + orderBy === 'asc' && dataExtent.reverse(); + } + // Other dimension. + else { + var dataExtent = [Infinity, -Infinity]; + each$10(children, function (child) { + var value = child.getValue(dimension); + value < dataExtent[0] && (dataExtent[0] = value); + value > dataExtent[1] && (dataExtent[1] = value); + }); + } + + return {sum: sum, dataExtent: dataExtent}; +} + +/** + * Computes the score for the specified row, + * as the worst aspect ratio. + */ +function worst(row, rowFixedLength, ratio) { + var areaMax = 0; + var areaMin = Infinity; + + for (var i = 0, area, len = row.length; i < len; i++) { + area = row[i].getLayout().area; + if (area) { + area < areaMin && (areaMin = area); + area > areaMax && (areaMax = area); + } + } + + var squareArea = row.area * row.area; + var f = rowFixedLength * rowFixedLength * ratio; + + return squareArea + ? mathMax$5( + (f * areaMax) / squareArea, + squareArea / (f * areaMin) + ) + : Infinity; +} + +/** + * Positions the specified row of nodes. Modifies `rect`. + */ +function position(row, rowFixedLength, rect, halfGapWidth, flush) { + // When rowFixedLength === rect.width, + // it is horizontal subdivision, + // rowFixedLength is the width of the subdivision, + // rowOtherLength is the height of the subdivision, + // and nodes will be positioned from left to right. + + // wh[idx0WhenH] means: when horizontal, + // wh[idx0WhenH] => wh[0] => 'width'. + // xy[idx1WhenH] => xy[1] => 'y'. + var idx0WhenH = rowFixedLength === rect.width ? 0 : 1; + var idx1WhenH = 1 - idx0WhenH; + var xy = ['x', 'y']; + var wh = ['width', 'height']; + + var last = rect[xy[idx0WhenH]]; + var rowOtherLength = rowFixedLength + ? row.area / rowFixedLength : 0; + + if (flush || rowOtherLength > rect[wh[idx1WhenH]]) { + rowOtherLength = rect[wh[idx1WhenH]]; // over+underflow + } + for (var i = 0, rowLen = row.length; i < rowLen; i++) { + var node = row[i]; + var nodeLayout = {}; + var step = rowOtherLength + ? node.getLayout().area / rowOtherLength : 0; + + var wh1 = nodeLayout[wh[idx1WhenH]] = mathMax$5(rowOtherLength - 2 * halfGapWidth, 0); + + // We use Math.max/min to avoid negative width/height when considering gap width. + var remain = rect[xy[idx0WhenH]] + rect[wh[idx0WhenH]] - last; + var modWH = (i === rowLen - 1 || remain < step) ? remain : step; + var wh0 = nodeLayout[wh[idx0WhenH]] = mathMax$5(modWH - 2 * halfGapWidth, 0); + + nodeLayout[xy[idx1WhenH]] = rect[xy[idx1WhenH]] + mathMin$5(halfGapWidth, wh1 / 2); + nodeLayout[xy[idx0WhenH]] = last + mathMin$5(halfGapWidth, wh0 / 2); + + last += modWH; + node.setLayout(nodeLayout, true); + } + + rect[xy[idx1WhenH]] += rowOtherLength; + rect[wh[idx1WhenH]] -= rowOtherLength; +} + +// Return [containerWidth, containerHeight] as defualt. +function estimateRootSize(seriesModel, targetInfo, viewRoot, containerWidth, containerHeight) { + // If targetInfo.node exists, we zoom to the node, + // so estimate whold width and heigth by target node. + var currNode = (targetInfo || {}).node; + var defaultSize = [containerWidth, containerHeight]; + + if (!currNode || currNode === viewRoot) { + return defaultSize; + } + + var parent; + var viewArea = containerWidth * containerHeight; + var area = viewArea * seriesModel.option.zoomToNodeRatio; + + while (parent = currNode.parentNode) { // jshint ignore:line + var sum = 0; + var siblings = parent.children; + + for (var i = 0, len = siblings.length; i < len; i++) { + sum += siblings[i].getValue(); + } + var currNodeValue = currNode.getValue(); + if (currNodeValue === 0) { + return defaultSize; + } + area *= sum / currNodeValue; + + // Considering border, suppose aspect ratio is 1. + var parentModel = parent.getModel(); + var borderWidth = parentModel.get(PATH_BORDER_WIDTH); + var upperHeight = Math.max(borderWidth, getUpperLabelHeight(parentModel, borderWidth)); + area += 4 * borderWidth * borderWidth + + (3 * borderWidth + upperHeight) * Math.pow(area, 0.5); + + area > MAX_SAFE_INTEGER && (area = MAX_SAFE_INTEGER); + + currNode = parent; + } + + area < viewArea && (area = viewArea); + var scale = Math.pow(area / viewArea, 0.5); + + return [containerWidth * scale, containerHeight * scale]; +} + +// Root postion base on coord of containerGroup +function calculateRootPosition(layoutInfo, rootRect, targetInfo) { + if (rootRect) { + return {x: rootRect.x, y: rootRect.y}; + } + + var defaultPosition = {x: 0, y: 0}; + if (!targetInfo) { + return defaultPosition; + } + + // If targetInfo is fetched by 'retrieveTargetInfo', + // old tree and new tree are the same tree, + // so the node still exists and we can visit it. + + var targetNode = targetInfo.node; + var layout = targetNode.getLayout(); + + if (!layout) { + return defaultPosition; + } + + // Transform coord from local to container. + var targetCenter = [layout.width / 2, layout.height / 2]; + var node = targetNode; + while (node) { + var nodeLayout = node.getLayout(); + targetCenter[0] += nodeLayout.x; + targetCenter[1] += nodeLayout.y; + node = node.parentNode; + } + + return { + x: layoutInfo.width / 2 - targetCenter[0], + y: layoutInfo.height / 2 - targetCenter[1] + }; +} + +// Mark nodes visible for prunning when visual coding and rendering. +// Prunning depends on layout and root position, so we have to do it after layout. +function prunning(node, clipRect, viewAbovePath, viewRoot, depth) { + var nodeLayout = node.getLayout(); + var nodeInViewAbovePath = viewAbovePath[depth]; + var isAboveViewRoot = nodeInViewAbovePath && nodeInViewAbovePath === node; + + if ( + (nodeInViewAbovePath && !isAboveViewRoot) + || (depth === viewAbovePath.length && node !== viewRoot) + ) { + return; + } + + node.setLayout({ + // isInView means: viewRoot sub tree + viewAbovePath + isInView: true, + // invisible only means: outside view clip so that the node can not + // see but still layout for animation preparation but not render. + invisible: !isAboveViewRoot && !clipRect.intersect(nodeLayout), + isAboveViewRoot: isAboveViewRoot + }, true); + + // Transform to child coordinate. + var childClipRect = new BoundingRect( + clipRect.x - nodeLayout.x, + clipRect.y - nodeLayout.y, + clipRect.width, + clipRect.height + ); + + each$10(node.viewChildren || [], function (child) { + prunning(child, childClipRect, viewAbovePath, viewRoot, depth + 1); + }); +} + +function getUpperLabelHeight(model) { + return model.get(PATH_UPPER_LABEL_SHOW) ? model.get(PATH_UPPER_LABEL_HEIGHT) : 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. +*/ + +registerVisual(treemapVisual); +registerLayout(treemapLayout); + +/* +* 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. +*/ + +// id may be function name of Object, add a prefix to avoid this problem. +function generateNodeKey(id) { + return '_EC_' + id; +} +/** + * @alias module:echarts/data/Graph + * @constructor + * @param {boolean} directed + */ +var Graph = function (directed) { + /** + * 是否是有向图 + * @type {boolean} + * @private + */ + this._directed = directed || false; + + /** + * @type {Array.} + * @readOnly + */ + this.nodes = []; + + /** + * @type {Array.} + * @readOnly + */ + this.edges = []; + + /** + * @type {Object.} + * @private + */ + this._nodesMap = {}; + /** + * @type {Object.} + * @private + */ + this._edgesMap = {}; + + /** + * @type {module:echarts/data/List} + * @readOnly + */ + this.data; + + /** + * @type {module:echarts/data/List} + * @readOnly + */ + this.edgeData; +}; + +var graphProto = Graph.prototype; +/** + * @type {string} + */ +graphProto.type = 'graph'; + +/** + * If is directed graph + * @return {boolean} + */ +graphProto.isDirected = function () { + return this._directed; +}; + +/** + * Add a new node + * @param {string} id + * @param {number} [dataIndex] + */ +graphProto.addNode = function (id, dataIndex) { + id = id == null ? ('' + dataIndex) : ('' + id); + + var nodesMap = this._nodesMap; + + if (nodesMap[generateNodeKey(id)]) { + if (__DEV__) { + console.error('Graph nodes have duplicate name or id'); + } + return; + } + + var node = new Node(id, dataIndex); + node.hostGraph = this; + + this.nodes.push(node); + + nodesMap[generateNodeKey(id)] = node; + return node; +}; + +/** + * Get node by data index + * @param {number} dataIndex + * @return {module:echarts/data/Graph~Node} + */ +graphProto.getNodeByIndex = function (dataIndex) { + var rawIdx = this.data.getRawIndex(dataIndex); + return this.nodes[rawIdx]; +}; +/** + * Get node by id + * @param {string} id + * @return {module:echarts/data/Graph.Node} + */ +graphProto.getNodeById = function (id) { + return this._nodesMap[generateNodeKey(id)]; +}; + +/** + * Add a new edge + * @param {number|string|module:echarts/data/Graph.Node} n1 + * @param {number|string|module:echarts/data/Graph.Node} n2 + * @param {number} [dataIndex=-1] + * @return {module:echarts/data/Graph.Edge} + */ +graphProto.addEdge = function (n1, n2, dataIndex) { + var nodesMap = this._nodesMap; + var edgesMap = this._edgesMap; + + // PNEDING + if (typeof n1 === 'number') { + n1 = this.nodes[n1]; + } + if (typeof n2 === 'number') { + n2 = this.nodes[n2]; + } + + if (!Node.isInstance(n1)) { + n1 = nodesMap[generateNodeKey(n1)]; + } + if (!Node.isInstance(n2)) { + n2 = nodesMap[generateNodeKey(n2)]; + } + if (!n1 || !n2) { + return; + } + + var key = n1.id + '-' + n2.id; + // PENDING + if (edgesMap[key]) { + return; + } + + var edge = new Edge(n1, n2, dataIndex); + edge.hostGraph = this; + + if (this._directed) { + n1.outEdges.push(edge); + n2.inEdges.push(edge); + } + n1.edges.push(edge); + if (n1 !== n2) { + n2.edges.push(edge); + } + + this.edges.push(edge); + edgesMap[key] = edge; + + return edge; +}; + +/** + * Get edge by data index + * @param {number} dataIndex + * @return {module:echarts/data/Graph~Node} + */ +graphProto.getEdgeByIndex = function (dataIndex) { + var rawIdx = this.edgeData.getRawIndex(dataIndex); + return this.edges[rawIdx]; +}; +/** + * Get edge by two linked nodes + * @param {module:echarts/data/Graph.Node|string} n1 + * @param {module:echarts/data/Graph.Node|string} n2 + * @return {module:echarts/data/Graph.Edge} + */ +graphProto.getEdge = function (n1, n2) { + if (Node.isInstance(n1)) { + n1 = n1.id; + } + if (Node.isInstance(n2)) { + n2 = n2.id; + } + + var edgesMap = this._edgesMap; + + if (this._directed) { + return edgesMap[n1 + '-' + n2]; + } + else { + return edgesMap[n1 + '-' + n2] + || edgesMap[n2 + '-' + n1]; + } +}; + +/** + * Iterate all nodes + * @param {Function} cb + * @param {*} [context] + */ +graphProto.eachNode = function (cb, context) { + var nodes = this.nodes; + var len = nodes.length; + for (var i = 0; i < len; i++) { + if (nodes[i].dataIndex >= 0) { + cb.call(context, nodes[i], i); + } + } +}; + +/** + * Iterate all edges + * @param {Function} cb + * @param {*} [context] + */ +graphProto.eachEdge = function (cb, context) { + var edges = this.edges; + var len = edges.length; + for (var i = 0; i < len; i++) { + if (edges[i].dataIndex >= 0 + && edges[i].node1.dataIndex >= 0 + && edges[i].node2.dataIndex >= 0 + ) { + cb.call(context, edges[i], i); + } + } +}; + +/** + * Breadth first traverse + * @param {Function} cb + * @param {module:echarts/data/Graph.Node} startNode + * @param {string} [direction='none'] 'none'|'in'|'out' + * @param {*} [context] + */ +graphProto.breadthFirstTraverse = function ( + cb, startNode, direction, context +) { + if (!Node.isInstance(startNode)) { + startNode = this._nodesMap[generateNodeKey(startNode)]; + } + if (!startNode) { + return; + } + + var edgeType = direction === 'out' + ? 'outEdges' : (direction === 'in' ? 'inEdges' : 'edges'); + + for (var i = 0; i < this.nodes.length; i++) { + this.nodes[i].__visited = false; + } + + if (cb.call(context, startNode, null)) { + return; + } + + var queue = [startNode]; + while (queue.length) { + var currentNode = queue.shift(); + var edges = currentNode[edgeType]; + + for (var i = 0; i < edges.length; i++) { + var e = edges[i]; + var otherNode = e.node1 === currentNode + ? e.node2 : e.node1; + if (!otherNode.__visited) { + if (cb.call(context, otherNode, currentNode)) { + // Stop traversing + return; + } + queue.push(otherNode); + otherNode.__visited = true; + } + } + } +}; + +// TODO +// graphProto.depthFirstTraverse = function ( +// cb, startNode, direction, context +// ) { + +// }; + +// Filter update +graphProto.update = function () { + var data = this.data; + var edgeData = this.edgeData; + var nodes = this.nodes; + var edges = this.edges; + + for (var i = 0, len = nodes.length; i < len; i++) { + nodes[i].dataIndex = -1; + } + for (var i = 0, len = data.count(); i < len; i++) { + nodes[data.getRawIndex(i)].dataIndex = i; + } + + edgeData.filterSelf(function (idx) { + var edge = edges[edgeData.getRawIndex(idx)]; + return edge.node1.dataIndex >= 0 && edge.node2.dataIndex >= 0; + }); + + // Update edge + for (var i = 0, len = edges.length; i < len; i++) { + edges[i].dataIndex = -1; + } + for (var i = 0, len = edgeData.count(); i < len; i++) { + edges[edgeData.getRawIndex(i)].dataIndex = i; + } +}; + +/** + * @return {module:echarts/data/Graph} + */ +graphProto.clone = function () { + var graph = new Graph(this._directed); + var nodes = this.nodes; + var edges = this.edges; + for (var i = 0; i < nodes.length; i++) { + graph.addNode(nodes[i].id, nodes[i].dataIndex); + } + for (var i = 0; i < edges.length; i++) { + var e = edges[i]; + graph.addEdge(e.node1.id, e.node2.id, e.dataIndex); + } + return graph; +}; + + +/** + * @alias module:echarts/data/Graph.Node + */ +function Node(id, dataIndex) { + /** + * @type {string} + */ + this.id = id == null ? '' : id; + + /** + * @type {Array.} + */ + this.inEdges = []; + /** + * @type {Array.} + */ + this.outEdges = []; + /** + * @type {Array.} + */ + this.edges = []; + /** + * @type {module:echarts/data/Graph} + */ + this.hostGraph; + + /** + * @type {number} + */ + this.dataIndex = dataIndex == null ? -1 : dataIndex; +} + +Node.prototype = { + + constructor: Node, + + /** + * @return {number} + */ + degree: function () { + return this.edges.length; + }, + + /** + * @return {number} + */ + inDegree: function () { + return this.inEdges.length; + }, + + /** + * @return {number} + */ + outDegree: function () { + return this.outEdges.length; + }, + + /** + * @param {string} [path] + * @return {module:echarts/model/Model} + */ + getModel: function (path) { + if (this.dataIndex < 0) { + return; + } + var graph = this.hostGraph; + var itemModel = graph.data.getItemModel(this.dataIndex); + + return itemModel.getModel(path); + } +}; + +/** + * 图边 + * @alias module:echarts/data/Graph.Edge + * @param {module:echarts/data/Graph.Node} n1 + * @param {module:echarts/data/Graph.Node} n2 + * @param {number} [dataIndex=-1] + */ +function Edge(n1, n2, dataIndex) { + + /** + * 节点1,如果是有向图则为源节点 + * @type {module:echarts/data/Graph.Node} + */ + this.node1 = n1; + + /** + * 节点2,如果是有向图则为目标节点 + * @type {module:echarts/data/Graph.Node} + */ + this.node2 = n2; + + this.dataIndex = dataIndex == null ? -1 : dataIndex; +} + +/** + * @param {string} [path] + * @return {module:echarts/model/Model} + */ +Edge.prototype.getModel = function (path) { + if (this.dataIndex < 0) { + return; + } + var graph = this.hostGraph; + var itemModel = graph.edgeData.getItemModel(this.dataIndex); + + return itemModel.getModel(path); +}; + +var createGraphDataProxyMixin = function (hostName, dataName) { + return { + /** + * @param {string=} [dimension='value'] Default 'value'. can be 'a', 'b', 'c', 'd', 'e'. + * @return {number} + */ + getValue: function (dimension) { + var data = this[hostName][dataName]; + return data.get(data.getDimension(dimension || 'value'), this.dataIndex); + }, + + /** + * @param {Object|string} key + * @param {*} [value] + */ + setVisual: function (key, value) { + this.dataIndex >= 0 + && this[hostName][dataName].setItemVisual(this.dataIndex, key, value); + }, + + /** + * @param {string} key + * @return {boolean} + */ + getVisual: function (key, ignoreParent) { + return this[hostName][dataName].getItemVisual(this.dataIndex, key, ignoreParent); + }, + + /** + * @param {Object} layout + * @return {boolean} [merge=false] + */ + setLayout: function (layout, merge$$1) { + this.dataIndex >= 0 + && this[hostName][dataName].setItemLayout(this.dataIndex, layout, merge$$1); + }, + + /** + * @return {Object} + */ + getLayout: function () { + return this[hostName][dataName].getItemLayout(this.dataIndex); + }, + + /** + * @return {module:zrender/Element} + */ + getGraphicEl: function () { + return this[hostName][dataName].getItemGraphicEl(this.dataIndex); + }, + + /** + * @return {number} + */ + getRawIndex: function () { + return this[hostName][dataName].getRawIndex(this.dataIndex); + } + }; +}; + +mixin(Node, createGraphDataProxyMixin('hostGraph', 'data')); +mixin(Edge, createGraphDataProxyMixin('hostGraph', 'edgeData')); + +Graph.Node = Node; +Graph.Edge = Edge; + +enableClassCheck(Node); +enableClassCheck(Edge); + +/* +* 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. +*/ + +var createGraphFromNodeEdge = function (nodes, edges, seriesModel, directed, beforeLink) { + // ??? TODO + // support dataset? + var graph = new Graph(directed); + for (var i = 0; i < nodes.length; i++) { + graph.addNode(retrieve( + // Id, name, dataIndex + nodes[i].id, nodes[i].name, i + ), i); + } + + var linkNameList = []; + var validEdges = []; + var linkCount = 0; + for (var i = 0; i < edges.length; i++) { + var link = edges[i]; + var source = link.source; + var target = link.target; + // addEdge may fail when source or target not exists + if (graph.addEdge(source, target, linkCount)) { + validEdges.push(link); + linkNameList.push(retrieve(link.id, source + ' > ' + target)); + linkCount++; + } + } + + var coordSys = seriesModel.get('coordinateSystem'); + var nodeData; + if (coordSys === 'cartesian2d' || coordSys === 'polar') { + nodeData = createListFromArray(nodes, seriesModel); + } + else { + var coordSysCtor = CoordinateSystemManager.get(coordSys); + var coordDimensions = (coordSysCtor && coordSysCtor.type !== 'view') + ? (coordSysCtor.dimensions || []) : []; + // FIXME: Some geo do not need `value` dimenson, whereas `calendar` needs + // `value` dimension, but graph need `value` dimension. It's better to + // uniform this behavior. + if (indexOf(coordDimensions, 'value') < 0) { + coordDimensions.concat(['value']); + } + + var dimensionNames = createDimensions(nodes, { + coordDimensions: coordDimensions + }); + nodeData = new List(dimensionNames, seriesModel); + nodeData.initData(nodes); + } + + var edgeData = new List(['value'], seriesModel); + edgeData.initData(validEdges, linkNameList); + + beforeLink && beforeLink(nodeData, edgeData); + + linkList({ + mainData: nodeData, + struct: graph, + structAttr: 'graph', + datas: {node: nodeData, edge: edgeData}, + datasAttr: {node: 'data', edge: 'edgeData'} + }); + + // Update dataIndex of nodes and edges because invalid edge may be removed + graph.update(); + + return graph; +}; + +/* +* 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. +*/ + +var GraphSeries = extendSeriesModel({ + + type: 'series.graph', + + init: function (option) { + GraphSeries.superApply(this, 'init', arguments); + + var self = this; + function getCategoriesData() { + return self._categoriesData; + } + // Provide data for legend select + this.legendVisualProvider = new LegendVisualProvider( + getCategoriesData, getCategoriesData + ); + + this.fillDataTextStyle(option.edges || option.links); + + this._updateCategoriesData(); + }, + + mergeOption: function (option) { + GraphSeries.superApply(this, 'mergeOption', arguments); + + this.fillDataTextStyle(option.edges || option.links); + + this._updateCategoriesData(); + }, + + mergeDefaultAndTheme: function (option) { + GraphSeries.superApply(this, 'mergeDefaultAndTheme', arguments); + defaultEmphasis(option, ['edgeLabel'], ['show']); + }, + + getInitialData: function (option, ecModel) { + var edges = option.edges || option.links || []; + var nodes = option.data || option.nodes || []; + var self = this; + + if (nodes && edges) { + return createGraphFromNodeEdge(nodes, edges, this, true, beforeLink).data; + } + + function beforeLink(nodeData, edgeData) { + // Overwrite nodeData.getItemModel to + nodeData.wrapMethod('getItemModel', function (model) { + var categoriesModels = self._categoriesModels; + var categoryIdx = model.getShallow('category'); + var categoryModel = categoriesModels[categoryIdx]; + if (categoryModel) { + categoryModel.parentModel = model.parentModel; + model.parentModel = categoryModel; + } + return model; + }); + + var edgeLabelModel = self.getModel('edgeLabel'); + // For option `edgeLabel` can be found by label.xxx.xxx on item mode. + var fakeSeriesModel = new Model( + {label: edgeLabelModel.option}, + edgeLabelModel.parentModel, + ecModel + ); + var emphasisEdgeLabelModel = self.getModel('emphasis.edgeLabel'); + var emphasisFakeSeriesModel = new Model( + {emphasis: {label: emphasisEdgeLabelModel.option}}, + emphasisEdgeLabelModel.parentModel, + ecModel + ); + + edgeData.wrapMethod('getItemModel', function (model) { + model.customizeGetParent(edgeGetParent); + return model; + }); + + function edgeGetParent(path) { + path = this.parsePath(path); + return (path && path[0] === 'label') + ? fakeSeriesModel + : (path && path[0] === 'emphasis' && path[1] === 'label') + ? emphasisFakeSeriesModel + : this.parentModel; + } + } + }, + + /** + * @return {module:echarts/data/Graph} + */ + getGraph: function () { + return this.getData().graph; + }, + + /** + * @return {module:echarts/data/List} + */ + getEdgeData: function () { + return this.getGraph().edgeData; + }, + + /** + * @return {module:echarts/data/List} + */ + getCategoriesData: function () { + return this._categoriesData; + }, + + /** + * @override + */ + formatTooltip: function (dataIndex, multipleSeries, dataType) { + if (dataType === 'edge') { + var nodeData = this.getData(); + var params = this.getDataParams(dataIndex, dataType); + var edge = nodeData.graph.getEdgeByIndex(dataIndex); + var sourceName = nodeData.getName(edge.node1.dataIndex); + var targetName = nodeData.getName(edge.node2.dataIndex); + + var html = []; + sourceName != null && html.push(sourceName); + targetName != null && html.push(targetName); + html = encodeHTML(html.join(' > ')); + + if (params.value) { + html += ' : ' + encodeHTML(params.value); + } + return html; + } + else { // dataType === 'node' or empty + return GraphSeries.superApply(this, 'formatTooltip', arguments); + } + }, + + _updateCategoriesData: function () { + var categories = map(this.option.categories || [], function (category) { + // Data must has value + return category.value != null ? category : extend({ + value: 0 + }, category); + }); + var categoriesData = new List(['value'], this); + categoriesData.initData(categories); + + this._categoriesData = categoriesData; + + this._categoriesModels = categoriesData.mapArray(function (idx) { + return categoriesData.getItemModel(idx, true); + }); + }, + + setZoom: function (zoom) { + this.option.zoom = zoom; + }, + + setCenter: function (center) { + this.option.center = center; + }, + + isAnimationEnabled: function () { + return GraphSeries.superCall(this, 'isAnimationEnabled') + // Not enable animation when do force layout + && !(this.get('layout') === 'force' && this.get('force.layoutAnimation')); + }, + + defaultOption: { + zlevel: 0, + z: 2, + + coordinateSystem: 'view', + + // Default option for all coordinate systems + // xAxisIndex: 0, + // yAxisIndex: 0, + // polarIndex: 0, + // geoIndex: 0, + + legendHoverLink: true, + + hoverAnimation: true, + + layout: null, + + focusNodeAdjacency: false, + + // Configuration of circular layout + circular: { + rotateLabel: false + }, + // Configuration of force directed layout + force: { + initLayout: null, + // Node repulsion. Can be an array to represent range. + repulsion: [0, 50], + gravity: 0.1, + // Initial friction + friction: 0.6, + + // Edge length. Can be an array to represent range. + edgeLength: 30, + + layoutAnimation: true + }, + + left: 'center', + top: 'center', + // right: null, + // bottom: null, + // width: '80%', + // height: '80%', + + symbol: 'circle', + symbolSize: 10, + + edgeSymbol: ['none', 'none'], + edgeSymbolSize: 10, + edgeLabel: { + position: 'middle' + }, + + draggable: false, + + roam: false, + + // Default on center of graph + center: null, + + zoom: 1, + // Symbol size scale ratio in roam + nodeScaleRatio: 0.6, + // cursor: null, + + // categories: [], + + // data: [] + // Or + // nodes: [] + // + // links: [] + // Or + // edges: [] + + label: { + show: false, + formatter: '{b}' + }, + + itemStyle: {}, + + lineStyle: { + color: '#aaa', + width: 1, + curveness: 0, + opacity: 0.5 + }, + emphasis: { + label: { + show: true + } + } + } +}); + +/* +* 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. +*/ + +/** + * Line path for bezier and straight line draw + */ + +var straightLineProto = Line.prototype; +var bezierCurveProto = BezierCurve.prototype; + +function isLine(shape) { + return isNaN(+shape.cpx1) || isNaN(+shape.cpy1); +} + +var LinePath = extendShape({ + + type: 'ec-line', + + style: { + stroke: '#000', + fill: null + }, + + shape: { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + percent: 1, + cpx1: null, + cpy1: null + }, + + buildPath: function (ctx, shape) { + this[isLine(shape) ? '_buildPathLine' : '_buildPathCurve'](ctx, shape); + }, + _buildPathLine: straightLineProto.buildPath, + _buildPathCurve: bezierCurveProto.buildPath, + + pointAt: function (t) { + return this[isLine(this.shape) ? '_pointAtLine' : '_pointAtCurve'](t); + }, + _pointAtLine: straightLineProto.pointAt, + _pointAtCurve: bezierCurveProto.pointAt, + + tangentAt: function (t) { + var shape = this.shape; + var p = isLine(shape) + ? [shape.x2 - shape.x1, shape.y2 - shape.y1] + : this._tangentAtCurve(t); + return normalize(p, p); + }, + _tangentAtCurve: bezierCurveProto.tangentAt + +}); + +/* +* 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. +*/ + +/** + * @module echarts/chart/helper/Line + */ + +var SYMBOL_CATEGORIES = ['fromSymbol', 'toSymbol']; + +function makeSymbolTypeKey(symbolCategory) { + return '_' + symbolCategory + 'Type'; +} +/** + * @inner + */ +function createSymbol$1(name, lineData, idx) { + var color = lineData.getItemVisual(idx, 'color'); + var symbolType = lineData.getItemVisual(idx, name); + var symbolSize = lineData.getItemVisual(idx, name + 'Size'); + + if (!symbolType || symbolType === 'none') { + return; + } + + if (!isArray(symbolSize)) { + symbolSize = [symbolSize, symbolSize]; + } + var symbolPath = createSymbol( + symbolType, -symbolSize[0] / 2, -symbolSize[1] / 2, + symbolSize[0], symbolSize[1], color + ); + + symbolPath.name = name; + + return symbolPath; +} + +function createLine(points) { + var line = new LinePath({ + name: 'line', + subPixelOptimize: true + }); + setLinePoints(line.shape, points); + return line; +} + +function setLinePoints(targetShape, points) { + targetShape.x1 = points[0][0]; + targetShape.y1 = points[0][1]; + targetShape.x2 = points[1][0]; + targetShape.y2 = points[1][1]; + targetShape.percent = 1; + + var cp1 = points[2]; + if (cp1) { + targetShape.cpx1 = cp1[0]; + targetShape.cpy1 = cp1[1]; + } + else { + targetShape.cpx1 = NaN; + targetShape.cpy1 = NaN; + } +} + +function updateSymbolAndLabelBeforeLineUpdate() { + var lineGroup = this; + var symbolFrom = lineGroup.childOfName('fromSymbol'); + var symbolTo = lineGroup.childOfName('toSymbol'); + var label = lineGroup.childOfName('label'); + // Quick reject + if (!symbolFrom && !symbolTo && label.ignore) { + return; + } + + var invScale = 1; + var parentNode = this.parent; + while (parentNode) { + if (parentNode.scale) { + invScale /= parentNode.scale[0]; + } + parentNode = parentNode.parent; + } + + var line = lineGroup.childOfName('line'); + // If line not changed + // FIXME Parent scale changed + if (!this.__dirty && !line.__dirty) { + return; + } + + var percent = line.shape.percent; + var fromPos = line.pointAt(0); + var toPos = line.pointAt(percent); + + var d = sub([], toPos, fromPos); + normalize(d, d); + + if (symbolFrom) { + symbolFrom.attr('position', fromPos); + var tangent = line.tangentAt(0); + symbolFrom.attr('rotation', Math.PI / 2 - Math.atan2( + tangent[1], tangent[0] + )); + symbolFrom.attr('scale', [invScale * percent, invScale * percent]); + } + if (symbolTo) { + symbolTo.attr('position', toPos); + var tangent = line.tangentAt(1); + symbolTo.attr('rotation', -Math.PI / 2 - Math.atan2( + tangent[1], tangent[0] + )); + symbolTo.attr('scale', [invScale * percent, invScale * percent]); + } + + if (!label.ignore) { + label.attr('position', toPos); + + var textPosition; + var textAlign; + var textVerticalAlign; + + var distance$$1 = 5 * invScale; + // End + if (label.__position === 'end') { + textPosition = [d[0] * distance$$1 + toPos[0], d[1] * distance$$1 + toPos[1]]; + textAlign = d[0] > 0.8 ? 'left' : (d[0] < -0.8 ? 'right' : 'center'); + textVerticalAlign = d[1] > 0.8 ? 'top' : (d[1] < -0.8 ? 'bottom' : 'middle'); + } + // Middle + else if (label.__position === 'middle') { + var halfPercent = percent / 2; + var tangent = line.tangentAt(halfPercent); + var n = [tangent[1], -tangent[0]]; + var cp = line.pointAt(halfPercent); + if (n[1] > 0) { + n[0] = -n[0]; + n[1] = -n[1]; + } + textPosition = [cp[0] + n[0] * distance$$1, cp[1] + n[1] * distance$$1]; + textAlign = 'center'; + textVerticalAlign = 'bottom'; + var rotation = -Math.atan2(tangent[1], tangent[0]); + if (toPos[0] < fromPos[0]) { + rotation = Math.PI + rotation; + } + label.attr('rotation', rotation); + } + // Start + else { + textPosition = [-d[0] * distance$$1 + fromPos[0], -d[1] * distance$$1 + fromPos[1]]; + textAlign = d[0] > 0.8 ? 'right' : (d[0] < -0.8 ? 'left' : 'center'); + textVerticalAlign = d[1] > 0.8 ? 'bottom' : (d[1] < -0.8 ? 'top' : 'middle'); + } + label.attr({ + style: { + // Use the user specified text align and baseline first + textVerticalAlign: label.__verticalAlign || textVerticalAlign, + textAlign: label.__textAlign || textAlign + }, + position: textPosition, + scale: [invScale, invScale] + }); + } +} + +/** + * @constructor + * @extends {module:zrender/graphic/Group} + * @alias {module:echarts/chart/helper/Line} + */ +function Line$1(lineData, idx, seriesScope) { + Group.call(this); + + this._createLine(lineData, idx, seriesScope); +} + +var lineProto = Line$1.prototype; + +// Update symbol position and rotation +lineProto.beforeUpdate = updateSymbolAndLabelBeforeLineUpdate; + +lineProto._createLine = function (lineData, idx, seriesScope) { + var seriesModel = lineData.hostModel; + var linePoints = lineData.getItemLayout(idx); + var line = createLine(linePoints); + line.shape.percent = 0; + initProps(line, { + shape: { + percent: 1 + } + }, seriesModel, idx); + + this.add(line); + + var label = new Text({ + name: 'label', + // FIXME + // Temporary solution for `focusNodeAdjacency`. + // line label do not use the opacity of lineStyle. + lineLabelOriginalOpacity: 1 + }); + this.add(label); + + each$1(SYMBOL_CATEGORIES, function (symbolCategory) { + var symbol = createSymbol$1(symbolCategory, lineData, idx); + // symbols must added after line to make sure + // it will be updated after line#update. + // Or symbol position and rotation update in line#beforeUpdate will be one frame slow + this.add(symbol); + this[makeSymbolTypeKey(symbolCategory)] = lineData.getItemVisual(idx, symbolCategory); + }, this); + + this._updateCommonStl(lineData, idx, seriesScope); +}; + +lineProto.updateData = function (lineData, idx, seriesScope) { + var seriesModel = lineData.hostModel; + + var line = this.childOfName('line'); + var linePoints = lineData.getItemLayout(idx); + var target = { + shape: {} + }; + + setLinePoints(target.shape, linePoints); + updateProps(line, target, seriesModel, idx); + + each$1(SYMBOL_CATEGORIES, function (symbolCategory) { + var symbolType = lineData.getItemVisual(idx, symbolCategory); + var key = makeSymbolTypeKey(symbolCategory); + // Symbol changed + if (this[key] !== symbolType) { + this.remove(this.childOfName(symbolCategory)); + var symbol = createSymbol$1(symbolCategory, lineData, idx); + this.add(symbol); + } + this[key] = symbolType; + }, this); + + this._updateCommonStl(lineData, idx, seriesScope); +}; + +lineProto._updateCommonStl = function (lineData, idx, seriesScope) { + var seriesModel = lineData.hostModel; + + var line = this.childOfName('line'); + + var lineStyle = seriesScope && seriesScope.lineStyle; + var hoverLineStyle = seriesScope && seriesScope.hoverLineStyle; + var labelModel = seriesScope && seriesScope.labelModel; + var hoverLabelModel = seriesScope && seriesScope.hoverLabelModel; + + // Optimization for large dataset + if (!seriesScope || lineData.hasItemOption) { + var itemModel = lineData.getItemModel(idx); + + lineStyle = itemModel.getModel('lineStyle').getLineStyle(); + hoverLineStyle = itemModel.getModel('emphasis.lineStyle').getLineStyle(); + + labelModel = itemModel.getModel('label'); + hoverLabelModel = itemModel.getModel('emphasis.label'); + } + + var visualColor = lineData.getItemVisual(idx, 'color'); + var visualOpacity = retrieve3( + lineData.getItemVisual(idx, 'opacity'), + lineStyle.opacity, + 1 + ); + + line.useStyle(defaults( + { + strokeNoScale: true, + fill: 'none', + stroke: visualColor, + opacity: visualOpacity + }, + lineStyle + )); + line.hoverStyle = hoverLineStyle; + + // Update symbol + each$1(SYMBOL_CATEGORIES, function (symbolCategory) { + var symbol = this.childOfName(symbolCategory); + if (symbol) { + symbol.setColor(visualColor); + symbol.setStyle({ + opacity: visualOpacity + }); + } + }, this); + + var showLabel = labelModel.getShallow('show'); + var hoverShowLabel = hoverLabelModel.getShallow('show'); + + var label = this.childOfName('label'); + var defaultLabelColor; + var baseText; + + // FIXME: the logic below probably should be merged to `graphic.setLabelStyle`. + if (showLabel || hoverShowLabel) { + defaultLabelColor = visualColor || '#000'; + + baseText = seriesModel.getFormattedLabel(idx, 'normal', lineData.dataType); + if (baseText == null) { + var rawVal = seriesModel.getRawValue(idx); + baseText = rawVal == null + ? lineData.getName(idx) + : isFinite(rawVal) + ? round$1(rawVal) + : rawVal; + } + } + var normalText = showLabel ? baseText : null; + var emphasisText = hoverShowLabel + ? retrieve2( + seriesModel.getFormattedLabel(idx, 'emphasis', lineData.dataType), + baseText + ) + : null; + + var labelStyle = label.style; + + // Always set `textStyle` even if `normalStyle.text` is null, because default + // values have to be set on `normalStyle`. + if (normalText != null || emphasisText != null) { + setTextStyle(label.style, labelModel, { + text: normalText + }, { + autoColor: defaultLabelColor + }); + + label.__textAlign = labelStyle.textAlign; + label.__verticalAlign = labelStyle.textVerticalAlign; + // 'start', 'middle', 'end' + label.__position = labelModel.get('position') || 'middle'; + } + + if (emphasisText != null) { + // Only these properties supported in this emphasis style here. + label.hoverStyle = { + text: emphasisText, + textFill: hoverLabelModel.getTextColor(true), + // For merging hover style to normal style, do not use + // `hoverLabelModel.getFont()` here. + fontStyle: hoverLabelModel.getShallow('fontStyle'), + fontWeight: hoverLabelModel.getShallow('fontWeight'), + fontSize: hoverLabelModel.getShallow('fontSize'), + fontFamily: hoverLabelModel.getShallow('fontFamily') + }; + } + else { + label.hoverStyle = { + text: null + }; + } + + label.ignore = !showLabel && !hoverShowLabel; + + setHoverStyle(this); +}; + +lineProto.highlight = function () { + this.trigger('emphasis'); +}; + +lineProto.downplay = function () { + this.trigger('normal'); +}; + +lineProto.updateLayout = function (lineData, idx) { + this.setLinePoints(lineData.getItemLayout(idx)); +}; + +lineProto.setLinePoints = function (points) { + var linePath = this.childOfName('line'); + setLinePoints(linePath.shape, points); + linePath.dirty(); +}; + +inherits(Line$1, Group); + +/* +* 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. +*/ + +/** + * @module echarts/chart/helper/LineDraw + */ + +// import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable'; + +/** + * @alias module:echarts/component/marker/LineDraw + * @constructor + */ +function LineDraw(ctor) { + this._ctor = ctor || Line$1; + + this.group = new Group(); +} + +var lineDrawProto = LineDraw.prototype; + +lineDrawProto.isPersistent = function () { + return true; +}; + +/** + * @param {module:echarts/data/List} lineData + */ +lineDrawProto.updateData = function (lineData) { + var lineDraw = this; + var group = lineDraw.group; + + var oldLineData = lineDraw._lineData; + lineDraw._lineData = lineData; + + // There is no oldLineData only when first rendering or switching from + // stream mode to normal mode, where previous elements should be removed. + if (!oldLineData) { + group.removeAll(); + } + + var seriesScope = makeSeriesScope$1(lineData); + + lineData.diff(oldLineData) + .add(function (idx) { + doAdd(lineDraw, lineData, idx, seriesScope); + }) + .update(function (newIdx, oldIdx) { + doUpdate(lineDraw, oldLineData, lineData, oldIdx, newIdx, seriesScope); + }) + .remove(function (idx) { + group.remove(oldLineData.getItemGraphicEl(idx)); + }) + .execute(); +}; + +function doAdd(lineDraw, lineData, idx, seriesScope) { + var itemLayout = lineData.getItemLayout(idx); + + if (!lineNeedsDraw(itemLayout)) { + return; + } + + var el = new lineDraw._ctor(lineData, idx, seriesScope); + lineData.setItemGraphicEl(idx, el); + lineDraw.group.add(el); +} + +function doUpdate(lineDraw, oldLineData, newLineData, oldIdx, newIdx, seriesScope) { + var itemEl = oldLineData.getItemGraphicEl(oldIdx); + + if (!lineNeedsDraw(newLineData.getItemLayout(newIdx))) { + lineDraw.group.remove(itemEl); + return; + } + + if (!itemEl) { + itemEl = new lineDraw._ctor(newLineData, newIdx, seriesScope); + } + else { + itemEl.updateData(newLineData, newIdx, seriesScope); + } + + newLineData.setItemGraphicEl(newIdx, itemEl); + + lineDraw.group.add(itemEl); +} + +lineDrawProto.updateLayout = function () { + var lineData = this._lineData; + + // Do not support update layout in incremental mode. + if (!lineData) { + return; + } + + lineData.eachItemGraphicEl(function (el, idx) { + el.updateLayout(lineData, idx); + }, this); +}; + +lineDrawProto.incrementalPrepareUpdate = function (lineData) { + this._seriesScope = makeSeriesScope$1(lineData); + this._lineData = null; + this.group.removeAll(); +}; + +lineDrawProto.incrementalUpdate = function (taskParams, lineData) { + function updateIncrementalAndHover(el) { + if (!el.isGroup) { + el.incremental = el.useHoverLayer = true; + } + } + + for (var idx = taskParams.start; idx < taskParams.end; idx++) { + var itemLayout = lineData.getItemLayout(idx); + + if (lineNeedsDraw(itemLayout)) { + var el = new this._ctor(lineData, idx, this._seriesScope); + el.traverse(updateIncrementalAndHover); + + this.group.add(el); + lineData.setItemGraphicEl(idx, el); + } + } +}; + +function makeSeriesScope$1(lineData) { + var hostModel = lineData.hostModel; + return { + lineStyle: hostModel.getModel('lineStyle').getLineStyle(), + hoverLineStyle: hostModel.getModel('emphasis.lineStyle').getLineStyle(), + labelModel: hostModel.getModel('label'), + hoverLabelModel: hostModel.getModel('emphasis.label') + }; +} + +lineDrawProto.remove = function () { + this._clearIncremental(); + this._incremental = null; + this.group.removeAll(); +}; + +lineDrawProto._clearIncremental = function () { + var incremental = this._incremental; + if (incremental) { + incremental.clearDisplaybles(); + } +}; + +function isPointNaN(pt) { + return isNaN(pt[0]) || isNaN(pt[1]); +} + +function lineNeedsDraw(pts) { + return !isPointNaN(pts[0]) && !isPointNaN(pts[1]); +} + +/* +* 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. +*/ + +function getNodeGlobalScale(seriesModel) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys.type !== 'view') { + return 1; + } + + var nodeScaleRatio = seriesModel.option.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; +} + +function getSymbolSize$1(node) { + var symbolSize = node.getVisual('symbolSize'); + if (symbolSize instanceof Array) { + symbolSize = (symbolSize[0] + symbolSize[1]) / 2; + } + return +symbolSize; +} + +/* +* 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. +*/ + +var v1 = []; +var v2 = []; +var v3 = []; +var quadraticAt$1 = quadraticAt; +var v2DistSquare = distSquare; +var mathAbs$1 = Math.abs; +function intersectCurveCircle(curvePoints, center, radius) { + var p0 = curvePoints[0]; + var p1 = curvePoints[1]; + var p2 = curvePoints[2]; + + var d = Infinity; + var t; + var radiusSquare = radius * radius; + var interval = 0.1; + + for (var _t = 0.1; _t <= 0.9; _t += 0.1) { + v1[0] = quadraticAt$1(p0[0], p1[0], p2[0], _t); + v1[1] = quadraticAt$1(p0[1], p1[1], p2[1], _t); + var diff = mathAbs$1(v2DistSquare(v1, center) - radiusSquare); + if (diff < d) { + d = diff; + t = _t; + } + } + + // Assume the segment is monotone,Find root through Bisection method + // At most 32 iteration + for (var i = 0; i < 32; i++) { + // var prev = t - interval; + var next = t + interval; + // v1[0] = quadraticAt(p0[0], p1[0], p2[0], prev); + // v1[1] = quadraticAt(p0[1], p1[1], p2[1], prev); + v2[0] = quadraticAt$1(p0[0], p1[0], p2[0], t); + v2[1] = quadraticAt$1(p0[1], p1[1], p2[1], t); + v3[0] = quadraticAt$1(p0[0], p1[0], p2[0], next); + v3[1] = quadraticAt$1(p0[1], p1[1], p2[1], next); + + var diff = v2DistSquare(v2, center) - radiusSquare; + if (mathAbs$1(diff) < 1e-2) { + break; + } + + // var prevDiff = v2DistSquare(v1, center) - radiusSquare; + var nextDiff = v2DistSquare(v3, center) - radiusSquare; + + interval /= 2; + if (diff < 0) { + if (nextDiff >= 0) { + t = t + interval; + } + else { + t = t - interval; + } + } + else { + if (nextDiff >= 0) { + t = t - interval; + } + else { + t = t + interval; + } + } + } + + return t; +} + +// Adjust edge to avoid +var adjustEdge = function (graph, scale$$1) { + var tmp0 = []; + var quadraticSubdivide$$1 = quadraticSubdivide; + var pts = [[], [], []]; + var pts2 = [[], []]; + var v = []; + scale$$1 /= 2; + + graph.eachEdge(function (edge, idx) { + var linePoints = edge.getLayout(); + var fromSymbol = edge.getVisual('fromSymbol'); + var toSymbol = edge.getVisual('toSymbol'); + + if (!linePoints.__original) { + linePoints.__original = [ + clone$1(linePoints[0]), + clone$1(linePoints[1]) + ]; + if (linePoints[2]) { + linePoints.__original.push(clone$1(linePoints[2])); + } + } + var originalPoints = linePoints.__original; + // Quadratic curve + if (linePoints[2] != null) { + copy(pts[0], originalPoints[0]); + copy(pts[1], originalPoints[2]); + copy(pts[2], originalPoints[1]); + if (fromSymbol && fromSymbol !== 'none') { + var symbolSize = getSymbolSize$1(edge.node1); + + var t = intersectCurveCircle(pts, originalPoints[0], symbolSize * scale$$1); + // Subdivide and get the second + quadraticSubdivide$$1(pts[0][0], pts[1][0], pts[2][0], t, tmp0); + pts[0][0] = tmp0[3]; + pts[1][0] = tmp0[4]; + quadraticSubdivide$$1(pts[0][1], pts[1][1], pts[2][1], t, tmp0); + pts[0][1] = tmp0[3]; + pts[1][1] = tmp0[4]; + } + if (toSymbol && toSymbol !== 'none') { + var symbolSize = getSymbolSize$1(edge.node2); + + var t = intersectCurveCircle(pts, originalPoints[1], symbolSize * scale$$1); + // Subdivide and get the first + quadraticSubdivide$$1(pts[0][0], pts[1][0], pts[2][0], t, tmp0); + pts[1][0] = tmp0[1]; + pts[2][0] = tmp0[2]; + quadraticSubdivide$$1(pts[0][1], pts[1][1], pts[2][1], t, tmp0); + pts[1][1] = tmp0[1]; + pts[2][1] = tmp0[2]; + } + // Copy back to layout + copy(linePoints[0], pts[0]); + copy(linePoints[1], pts[2]); + copy(linePoints[2], pts[1]); + } + // Line + else { + copy(pts2[0], originalPoints[0]); + copy(pts2[1], originalPoints[1]); + + sub(v, pts2[1], pts2[0]); + normalize(v, v); + if (fromSymbol && fromSymbol !== 'none') { + + var symbolSize = getSymbolSize$1(edge.node1); + + scaleAndAdd(pts2[0], pts2[0], v, symbolSize * scale$$1); + } + if (toSymbol && toSymbol !== 'none') { + var symbolSize = getSymbolSize$1(edge.node2); + + scaleAndAdd(pts2[1], pts2[1], v, -symbolSize * scale$$1); + } + copy(linePoints[0], pts2[0]); + copy(linePoints[1], pts2[1]); + } + }); +}; + +/* +* 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. +*/ + +var FOCUS_ADJACENCY = '__focusNodeAdjacency'; +var UNFOCUS_ADJACENCY = '__unfocusNodeAdjacency'; + +var nodeOpacityPath = ['itemStyle', 'opacity']; +var lineOpacityPath = ['lineStyle', 'opacity']; + +function getItemOpacity(item, opacityPath) { + var opacity = item.getVisual('opacity'); + return opacity != null ? 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.isGroup) { + var opct = child.lineLabelOriginalOpacity; + if (opct == null || opacityRatio != null) { + opct = opacity; + } + child.setStyle('opacity', opct); + } + }); +} + +function fadeInItem(item, opacityPath) { + var opacity = getItemOpacity(item, opacityPath); + var el = item.getGraphicEl(); + // Should go back to normal opacity first, consider hoverLayer, + // where current state is copied to elMirror, and support + // emphasis opacity here. + el.traverse(function (child) { + !child.isGroup && child.setStyle('opacity', opacity); + }); + el.highlight && el.highlight(); +} + +extendChartView({ + + type: 'graph', + + init: function (ecModel, api) { + var symbolDraw = new SymbolDraw(); + var lineDraw = new LineDraw(); + var group = this.group; + + this._controller = new RoamController(api.getZr()); + this._controllerHost = {target: group}; + + group.add(symbolDraw.group); + group.add(lineDraw.group); + + this._symbolDraw = symbolDraw; + this._lineDraw = lineDraw; + + this._firstRender = true; + }, + + render: function (seriesModel, ecModel, api) { + var graphView = this; + var coordSys = seriesModel.coordinateSystem; + + this._model = seriesModel; + + var symbolDraw = this._symbolDraw; + var lineDraw = this._lineDraw; + + var group = this.group; + + if (coordSys.type === 'view') { + var groupNewProp = { + position: coordSys.position, + scale: coordSys.scale + }; + if (this._firstRender) { + group.attr(groupNewProp); + } + else { + updateProps(group, groupNewProp, seriesModel); + } + } + // Fix edge contact point with node + adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); + + var data = seriesModel.getData(); + symbolDraw.updateData(data); + + var edgeData = seriesModel.getEdgeData(); + lineDraw.updateData(edgeData); + + this._updateNodeAndLinkScale(); + + this._updateController(seriesModel, ecModel, api); + + clearTimeout(this._layoutTimeout); + var forceLayout = seriesModel.forceLayout; + var layoutAnimation = seriesModel.get('force.layoutAnimation'); + if (forceLayout) { + this._startForceLayoutIteration(forceLayout, layoutAnimation); + } + + data.eachItemGraphicEl(function (el, idx) { + var itemModel = data.getItemModel(idx); + // Update draggable + el.off('drag').off('dragend'); + var draggable = itemModel.get('draggable'); + if (draggable) { + el.on('drag', function () { + if (forceLayout) { + forceLayout.warmUp(); + !this._layouting + && this._startForceLayoutIteration(forceLayout, layoutAnimation); + forceLayout.setFixed(idx); + // Write position back to layout + data.setItemLayout(idx, el.position); + } + }, this).on('dragend', function () { + if (forceLayout) { + forceLayout.setUnfixed(idx); + } + }, this); + } + el.setDraggable(draggable && forceLayout); + + el[FOCUS_ADJACENCY] && el.off('mouseover', el[FOCUS_ADJACENCY]); + el[UNFOCUS_ADJACENCY] && el.off('mouseout', el[UNFOCUS_ADJACENCY]); + + if (itemModel.get('focusNodeAdjacency')) { + el.on('mouseover', el[FOCUS_ADJACENCY] = function () { + graphView._clearTimer(); + api.dispatchAction({ + type: 'focusNodeAdjacency', + seriesId: seriesModel.id, + dataIndex: el.dataIndex + }); + }); + el.on('mouseout', el[UNFOCUS_ADJACENCY] = function () { + graphView._dispatchUnfocus(api); + }); + } + + }, this); + + data.graph.eachEdge(function (edge) { + var el = edge.getGraphicEl(); + + el[FOCUS_ADJACENCY] && el.off('mouseover', el[FOCUS_ADJACENCY]); + el[UNFOCUS_ADJACENCY] && el.off('mouseout', el[UNFOCUS_ADJACENCY]); + + if (edge.getModel().get('focusNodeAdjacency')) { + el.on('mouseover', el[FOCUS_ADJACENCY] = function () { + graphView._clearTimer(); + api.dispatchAction({ + type: 'focusNodeAdjacency', + seriesId: seriesModel.id, + edgeDataIndex: edge.dataIndex + }); + }); + el.on('mouseout', el[UNFOCUS_ADJACENCY] = function () { + graphView._dispatchUnfocus(api); + }); + } + }); + + var circularRotateLabel = seriesModel.get('layout') === 'circular' + && seriesModel.get('circular.rotateLabel'); + var cx = data.getLayout('cx'); + var cy = data.getLayout('cy'); + data.eachItemGraphicEl(function (el, idx) { + var itemModel = data.getItemModel(idx); + var labelRotate = itemModel.get('label.rotate') || 0; + var symbolPath = el.getSymbolPath(); + if (circularRotateLabel) { + var pos = data.getItemLayout(idx); + var rad = Math.atan2(pos[1] - cy, pos[0] - cx); + if (rad < 0) { + rad = Math.PI * 2 + rad; + } + var isLeft = pos[0] < cx; + if (isLeft) { + rad = rad - Math.PI; + } + var textPosition = isLeft ? 'left' : 'right'; + modifyLabelStyle( + symbolPath, + { + textRotation: -rad, + textPosition: textPosition, + textOrigin: 'center' + }, + { + textPosition: textPosition + } + ); + } + else { + modifyLabelStyle( + symbolPath, + { + textRotation: labelRotate *= Math.PI / 180 + } + ); + } + }); + + this._firstRender = false; + }, + + dispose: function () { + this._controller && this._controller.dispose(); + this._controllerHost = {}; + this._clearTimer(); + }, + + _dispatchUnfocus: function (api, opt) { + var self = this; + this._clearTimer(); + this._unfocusDelayTimer = setTimeout(function () { + self._unfocusDelayTimer = null; + api.dispatchAction({ + type: 'unfocusNodeAdjacency', + seriesId: self._model.id + }); + }, 500); + + }, + + _clearTimer: function () { + if (this._unfocusDelayTimer) { + clearTimeout(this._unfocusDelayTimer); + this._unfocusDelayTimer = null; + } + }, + + focusNodeAdjacency: function (seriesModel, ecModel, api, payload) { + var data = this._model.getData(); + var graph = data.graph; + var dataIndex = payload.dataIndex; + var edgeDataIndex = payload.edgeDataIndex; + + var node = graph.getNodeByIndex(dataIndex); + var edge = graph.getEdgeByIndex(edgeDataIndex); + + if (!node && !edge) { + return; + } + + graph.eachNode(function (node) { + fadeOutItem(node, nodeOpacityPath, 0.1); + }); + graph.eachEdge(function (edge) { + fadeOutItem(edge, lineOpacityPath, 0.1); + }); + + if (node) { + fadeInItem(node, nodeOpacityPath); + each$1(node.edges, function (adjacentEdge) { + if (adjacentEdge.dataIndex < 0) { + return; + } + fadeInItem(adjacentEdge, lineOpacityPath); + fadeInItem(adjacentEdge.node1, nodeOpacityPath); + fadeInItem(adjacentEdge.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.getData().graph; + + graph.eachNode(function (node) { + fadeOutItem(node, nodeOpacityPath); + }); + graph.eachEdge(function (edge) { + fadeOutItem(edge, lineOpacityPath); + }); + }, + + _startForceLayoutIteration: function (forceLayout, layoutAnimation) { + var self = this; + (function step() { + forceLayout.step(function (stopped) { + self.updateLayout(self._model); + (self._layouting = !stopped) && ( + layoutAnimation + ? (self._layoutTimeout = setTimeout(step, 16)) + : step() + ); + }); + })(); + }, + + _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); + }); + + if (seriesModel.coordinateSystem.type !== 'view') { + controller.disable(); + return; + } + controller.enable(seriesModel.get('roam')); + controllerHost.zoomLimit = seriesModel.get('scaleLimit'); + controllerHost.zoom = seriesModel.coordinateSystem.getZoom(); + + controller + .off('pan') + .off('zoom') + .on('pan', function (e) { + updateViewOnPan(controllerHost, e.dx, e.dy); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'graphRoam', + dx: e.dx, + dy: e.dy + }); + }) + .on('zoom', function (e) { + updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'graphRoam', + zoom: e.scale, + originX: e.originX, + originY: e.originY + }); + this._updateNodeAndLinkScale(); + adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); + this._lineDraw.updateLayout(); + }, this); + }, + + _updateNodeAndLinkScale: function () { + var seriesModel = this._model; + var data = seriesModel.getData(); + + var nodeScale = getNodeGlobalScale(seriesModel); + var invScale = [nodeScale, nodeScale]; + + data.eachItemGraphicEl(function (el, idx) { + el.attr('scale', invScale); + }); + }, + + updateLayout: function (seriesModel) { + adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); + + this._symbolDraw.updateLayout(); + this._lineDraw.updateLayout(); + }, + + remove: function (ecModel, api) { + this._symbolDraw && this._symbolDraw.remove(); + this._lineDraw && this._lineDraw.remove(); + } +}); + +/* +* 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. +*/ + +/** + * @payload + * @property {number} [seriesIndex] + * @property {string} [seriesId] + * @property {string} [seriesName] + * @property {number} [dataIndex] + */ +registerAction({ + type: 'focusNodeAdjacency', + event: 'focusNodeAdjacency', + update: 'series:focusNodeAdjacency' +}, function () {}); + +/** + * @payload + * @property {number} [seriesIndex] + * @property {string} [seriesId] + * @property {string} [seriesName] + */ +registerAction({ + type: 'unfocusNodeAdjacency', + event: 'unfocusNodeAdjacency', + update: 'series:unfocusNodeAdjacency' +}, function () {}); + +/* +* 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. +*/ + +var actionInfo = { + type: 'graphRoam', + event: 'graphRoam', + update: 'none' +}; + +/** + * @payload + * @property {string} name Series name + * @property {number} [dx] + * @property {number} [dy] + * @property {number} [zoom] + * @property {number} [originX] + * @property {number} [originY] + */ +registerAction(actionInfo, 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); + }); +}); + +/* +* 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. +*/ + + +var categoryFilter = function (ecModel) { + var legendModels = ecModel.findComponents({ + mainType: 'legend' + }); + if (!legendModels || !legendModels.length) { + return; + } + ecModel.eachSeriesByType('graph', function (graphSeries) { + var categoriesData = graphSeries.getCategoriesData(); + var graph = graphSeries.getGraph(); + var data = graph.data; + + var categoryNames = categoriesData.mapArray(categoriesData.getName); + + data.filterSelf(function (idx) { + var model = data.getItemModel(idx); + var category = model.getShallow('category'); + if (category != null) { + if (typeof category === 'number') { + category = categoryNames[category]; + } + // If in any legend component the status is not selected. + for (var i = 0; i < legendModels.length; i++) { + if (!legendModels[i].isSelected(category)) { + return false; + } + } + } + return true; + }); + }, this); +}; + +/* +* 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. +*/ + + +var categoryVisual = function (ecModel) { + + var paletteScope = {}; + ecModel.eachSeriesByType('graph', function (seriesModel) { + var categoriesData = seriesModel.getCategoriesData(); + var data = seriesModel.getData(); + + var categoryNameIdxMap = {}; + + categoriesData.each(function (idx) { + var name = categoriesData.getName(idx); + // Add prefix to avoid conflict with Object.prototype. + categoryNameIdxMap['ec-' + name] = idx; + var itemModel = categoriesData.getItemModel(idx); + + var color = itemModel.get('itemStyle.color') + || seriesModel.getColorFromPalette(name, paletteScope); + categoriesData.setItemVisual(idx, 'color', color); + + var itemStyleList = ['opacity', 'symbol', 'symbolSize', 'symbolKeepAspect']; + for (var i = 0; i < itemStyleList.length; i++) { + var itemStyle = itemModel.getShallow(itemStyleList[i], true); + if (itemStyle != null) { + categoriesData.setItemVisual(idx, itemStyleList[i], itemStyle); + } + } + }); + + // Assign category color to visual + if (categoriesData.count()) { + data.each(function (idx) { + var model = data.getItemModel(idx); + var category = model.getShallow('category'); + if (category != null) { + if (typeof category === 'string') { + category = categoryNameIdxMap['ec-' + category]; + } + + var itemStyleList = ['color', 'opacity', 'symbol', 'symbolSize', 'symbolKeepAspect']; + + for (var i = 0; i < itemStyleList.length; i++) { + if (data.getItemVisual(idx, itemStyleList[i], true) == null) { + data.setItemVisual( + idx, itemStyleList[i], + categoriesData.getItemVisual(category, itemStyleList[i]) + ); + } + } + } + }); + } + }); +}; + +/* +* 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. +*/ + + +function normalize$1(a) { + if (!(a instanceof Array)) { + a = [a, a]; + } + return a; +} + +var edgeVisual = function (ecModel) { + ecModel.eachSeriesByType('graph', function (seriesModel) { + var graph = seriesModel.getGraph(); + var edgeData = seriesModel.getEdgeData(); + var symbolType = normalize$1(seriesModel.get('edgeSymbol')); + var symbolSize = normalize$1(seriesModel.get('edgeSymbolSize')); + + var colorQuery = 'lineStyle.color'.split('.'); + var opacityQuery = 'lineStyle.opacity'.split('.'); + + edgeData.setVisual('fromSymbol', symbolType && symbolType[0]); + edgeData.setVisual('toSymbol', symbolType && symbolType[1]); + edgeData.setVisual('fromSymbolSize', symbolSize && symbolSize[0]); + edgeData.setVisual('toSymbolSize', symbolSize && symbolSize[1]); + edgeData.setVisual('color', seriesModel.get(colorQuery)); + edgeData.setVisual('opacity', seriesModel.get(opacityQuery)); + + edgeData.each(function (idx) { + var itemModel = edgeData.getItemModel(idx); + var edge = graph.getEdgeByIndex(idx); + var symbolType = normalize$1(itemModel.getShallow('symbol', true)); + var symbolSize = normalize$1(itemModel.getShallow('symbolSize', true)); + // Edge visual must after node visual + var color = itemModel.get(colorQuery); + var opacity = itemModel.get(opacityQuery); + switch (color) { + case 'source': + color = edge.node1.getVisual('color'); + break; + case 'target': + color = edge.node2.getVisual('color'); + break; + } + + symbolType[0] && edge.setVisual('fromSymbol', symbolType[0]); + symbolType[1] && edge.setVisual('toSymbol', symbolType[1]); + symbolSize[0] && edge.setVisual('fromSymbolSize', symbolSize[0]); + symbolSize[1] && edge.setVisual('toSymbolSize', symbolSize[1]); + + edge.setVisual('color', color); + edge.setVisual('opacity', opacity); + }); + }); +}; + +/* +* 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. +*/ + +function simpleLayout$1(seriesModel) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.type !== 'view') { + return; + } + var graph = seriesModel.getGraph(); + + graph.eachNode(function (node) { + var model = node.getModel(); + node.setLayout([+model.get('x'), +model.get('y')]); + }); + + simpleLayoutEdge(graph); +} + +function simpleLayoutEdge(graph) { + graph.eachEdge(function (edge) { + var curveness = edge.getModel().get('lineStyle.curveness') || 0; + var p1 = clone$1(edge.node1.getLayout()); + var p2 = clone$1(edge.node2.getLayout()); + var points = [p1, p2]; + if (+curveness) { + points.push([ + (p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * curveness, + (p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * curveness + ]); + } + edge.setLayout(points); + }); +} + +/* +* 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. +*/ + +var simpleLayout = function (ecModel, api) { + ecModel.eachSeriesByType('graph', function (seriesModel) { + var layout = seriesModel.get('layout'); + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.type !== 'view') { + var data = seriesModel.getData(); + + var dimensions = []; + each$1(coordSys.dimensions, function (coordDim) { + dimensions = dimensions.concat(data.mapDimension(coordDim, true)); + }); + + for (var dataIndex = 0; dataIndex < data.count(); dataIndex++) { + var value = []; + var hasValue = false; + for (var i = 0; i < dimensions.length; i++) { + var val = data.get(dimensions[i], dataIndex); + if (!isNaN(val)) { + hasValue = true; + } + value.push(val); + } + if (hasValue) { + data.setItemLayout(dataIndex, coordSys.dataToPoint(value)); + } + else { + // Also {Array.}, not undefined to avoid if...else... statement + data.setItemLayout(dataIndex, [NaN, NaN]); + } + } + + simpleLayoutEdge(data.graph); + } + else if (!layout || layout === 'none') { + simpleLayout$1(seriesModel); + } + }); +}; + +/* +* 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. +*/ + +var PI$3 = Math.PI; + +var _symbolRadiansHalf = []; + +/** + * `basedOn` can be: + * 'value': + * This layout is not accurate and have same bad case. For example, + * if the min value is very smaller than the max value, the nodes + * with the min value probably overlap even though there is enough + * space to layout them. So we only use this approach in the as the + * init layout of the force layout. + * FIXME + * Probably we do not need this method any more but use + * `basedOn: 'symbolSize'` in force layout if + * delay its init operations to GraphView. + * 'symbolSize': + * This approach work only if all of the symbol size calculated. + * That is, the progressive rendering is not applied to graph. + * FIXME + * If progressive rendering is applied to graph some day, + * probably we have to use `basedOn: 'value'`. + * + * @param {module:echarts/src/model/Series} seriesModel + * @param {string} basedOn 'value' or 'symbolSize' + */ +function circularLayout$1(seriesModel, basedOn) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.type !== 'view') { + return; + } + + var rect = coordSys.getBoundingRect(); + + var nodeData = seriesModel.getData(); + var graph = nodeData.graph; + + var cx = rect.width / 2 + rect.x; + var cy = rect.height / 2 + rect.y; + var r = Math.min(rect.width, rect.height) / 2; + var count = nodeData.count(); + + nodeData.setLayout({ + cx: cx, + cy: cy + }); + + if (!count) { + return; + } + + _layoutNodesBasedOn[basedOn](seriesModel, coordSys, graph, nodeData, r, cx, cy, count); + + graph.eachEdge(function (edge) { + var curveness = edge.getModel().get('lineStyle.curveness') || 0; + var p1 = clone$1(edge.node1.getLayout()); + var p2 = clone$1(edge.node2.getLayout()); + var cp1; + var x12 = (p1[0] + p2[0]) / 2; + var y12 = (p1[1] + p2[1]) / 2; + if (+curveness) { + curveness *= 3; + cp1 = [ + cx * curveness + x12 * (1 - curveness), + cy * curveness + y12 * (1 - curveness) + ]; + } + edge.setLayout([p1, p2, cp1]); + }); +} + +var _layoutNodesBasedOn = { + + value: function (seriesModel, coordSys, graph, nodeData, r, cx, cy, count) { + var angle = 0; + var sum = nodeData.getSum('value'); + var unitAngle = Math.PI * 2 / (sum || count); + + graph.eachNode(function (node) { + var value = node.getValue('value'); + var radianHalf = unitAngle * (sum ? value : 1) / 2; + + angle += radianHalf; + node.setLayout([ + r * Math.cos(angle) + cx, + r * Math.sin(angle) + cy + ]); + angle += radianHalf; + }); + }, + + symbolSize: function (seriesModel, coordSys, graph, nodeData, r, cx, cy, count) { + var sumRadian = 0; + _symbolRadiansHalf.length = count; + + var nodeScale = getNodeGlobalScale(seriesModel); + + graph.eachNode(function (node) { + var symbolSize = getSymbolSize$1(node); + + // Normally this case will not happen, but we still add + // some the defensive code (2px is an arbitrary value). + isNaN(symbolSize) && (symbolSize = 2); + symbolSize < 0 && (symbolSize = 0); + + symbolSize *= nodeScale; + + var symbolRadianHalf = Math.asin(symbolSize / 2 / r); + // when `symbolSize / 2` is bigger than `r`. + isNaN(symbolRadianHalf) && (symbolRadianHalf = PI$3 / 2); + _symbolRadiansHalf[node.dataIndex] = symbolRadianHalf; + sumRadian += symbolRadianHalf * 2; + }); + + var halfRemainRadian = (2 * PI$3 - sumRadian) / count / 2; + + var angle = 0; + graph.eachNode(function (node) { + var radianHalf = halfRemainRadian + _symbolRadiansHalf[node.dataIndex]; + + angle += radianHalf; + node.setLayout([ + r * Math.cos(angle) + cx, + r * Math.sin(angle) + cy + ]); + angle += radianHalf; + }); + } +}; + +/* +* 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. +*/ + +var circularLayout = function (ecModel) { + ecModel.eachSeriesByType('graph', function (seriesModel) { + if (seriesModel.get('layout') === 'circular') { + circularLayout$1(seriesModel, 'symbolSize'); + } + }); +}; + +/* +* 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. +*/ + +/* +* A third-party license is embeded for some of the code in this file: +* Some formulas were originally copied from "d3.js" with some +* modifications made for this project. +* (See more details in the comment of the method "step" below.) +* The use of the source code of this file is also subject to the terms +* and consitions of the license of "d3.js" (BSD-3Clause, see +* ). +*/ + +var scaleAndAdd$2 = scaleAndAdd; + +// function adjacentNode(n, e) { +// return e.n1 === n ? e.n2 : e.n1; +// } + +function forceLayout$1(nodes, edges, opts) { + var rect = opts.rect; + var width = rect.width; + var height = rect.height; + var center = [rect.x + width / 2, rect.y + height / 2]; + // var scale = opts.scale || 1; + var gravity = opts.gravity == null ? 0.1 : opts.gravity; + + // for (var i = 0; i < edges.length; i++) { + // var e = edges[i]; + // var n1 = e.n1; + // var n2 = e.n2; + // n1.edges = n1.edges || []; + // n2.edges = n2.edges || []; + // n1.edges.push(e); + // n2.edges.push(e); + // } + // Init position + for (var i = 0; i < nodes.length; i++) { + var n = nodes[i]; + if (!n.p) { + n.p = create( + width * (Math.random() - 0.5) + center[0], + height * (Math.random() - 0.5) + center[1] + ); + } + n.pp = clone$1(n.p); + n.edges = null; + } + + // Formula in 'Graph Drawing by Force-directed Placement' + // var k = scale * Math.sqrt(width * height / nodes.length); + // var k2 = k * k; + + var initialFriction = opts.friction == null ? 0.6 : opts.friction; + var friction = initialFriction; + + return { + warmUp: function () { + friction = initialFriction * 0.8; + }, + + setFixed: function (idx) { + nodes[idx].fixed = true; + }, + + setUnfixed: function (idx) { + nodes[idx].fixed = false; + }, + + /** + * Some formulas were originally copied from "d3.js" + * https://github.com/d3/d3/blob/b516d77fb8566b576088e73410437494717ada26/src/layout/force.js + * with some modifications made for this project. + * See the license statement at the head of this file. + */ + step: function (cb) { + var v12 = []; + var nLen = nodes.length; + for (var i = 0; i < edges.length; i++) { + var e = edges[i]; + if (e.ignoreForceLayout) { + continue; + } + var n1 = e.n1; + var n2 = e.n2; + + sub(v12, n2.p, n1.p); + var d = len(v12) - e.d; + var w = n2.w / (n1.w + n2.w); + + if (isNaN(w)) { + w = 0; + } + + normalize(v12, v12); + + !n1.fixed && scaleAndAdd$2(n1.p, n1.p, v12, w * d * friction); + !n2.fixed && scaleAndAdd$2(n2.p, n2.p, v12, -(1 - w) * d * friction); + } + // Gravity + for (var i = 0; i < nLen; i++) { + var n = nodes[i]; + if (!n.fixed) { + sub(v12, center, n.p); + // var d = vec2.len(v12); + // vec2.scale(v12, v12, 1 / d); + // var gravityFactor = gravity; + scaleAndAdd$2(n.p, n.p, v12, gravity * friction); + } + } + + // Repulsive + // PENDING + for (var i = 0; i < nLen; i++) { + var n1 = nodes[i]; + for (var j = i + 1; j < nLen; j++) { + var n2 = nodes[j]; + sub(v12, n2.p, n1.p); + var d = len(v12); + if (d === 0) { + // Random repulse + set(v12, Math.random() - 0.5, Math.random() - 0.5); + d = 1; + } + var repFact = (n1.rep + n2.rep) / d / d; + !n1.fixed && scaleAndAdd$2(n1.pp, n1.pp, v12, repFact); + !n2.fixed && scaleAndAdd$2(n2.pp, n2.pp, v12, -repFact); + } + } + var v = []; + for (var i = 0; i < nLen; i++) { + var n = nodes[i]; + if (!n.fixed) { + sub(v, n.p, n.pp); + scaleAndAdd$2(n.p, n.p, v, friction); + copy(n.pp, n.p); + } + } + + friction = friction * 0.992; + + cb && cb(nodes, edges, friction < 0.01); + } + }; +} + +/* +* 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. +*/ + +var forceLayout = function (ecModel) { + ecModel.eachSeriesByType('graph', function (graphSeries) { + var coordSys = graphSeries.coordinateSystem; + if (coordSys && coordSys.type !== 'view') { + return; + } + if (graphSeries.get('layout') === 'force') { + var preservedPoints = graphSeries.preservedPoints || {}; + var graph = graphSeries.getGraph(); + var nodeData = graph.data; + var edgeData = graph.edgeData; + var forceModel = graphSeries.getModel('force'); + var initLayout = forceModel.get('initLayout'); + if (graphSeries.preservedPoints) { + nodeData.each(function (idx) { + var id = nodeData.getId(idx); + nodeData.setItemLayout(idx, preservedPoints[id] || [NaN, NaN]); + }); + } + else if (!initLayout || initLayout === 'none') { + simpleLayout$1(graphSeries); + } + else if (initLayout === 'circular') { + circularLayout$1(graphSeries, 'value'); + } + + var nodeDataExtent = nodeData.getDataExtent('value'); + var edgeDataExtent = edgeData.getDataExtent('value'); + // var edgeDataExtent = edgeData.getDataExtent('value'); + var repulsion = forceModel.get('repulsion'); + var edgeLength = forceModel.get('edgeLength'); + if (!isArray(repulsion)) { + repulsion = [repulsion, repulsion]; + } + if (!isArray(edgeLength)) { + edgeLength = [edgeLength, edgeLength]; + } + // Larger value has smaller length + edgeLength = [edgeLength[1], edgeLength[0]]; + + var nodes = nodeData.mapArray('value', function (value, idx) { + var point = nodeData.getItemLayout(idx); + var rep = linearMap(value, nodeDataExtent, repulsion); + if (isNaN(rep)) { + rep = (repulsion[0] + repulsion[1]) / 2; + } + return { + w: rep, + rep: rep, + fixed: nodeData.getItemModel(idx).get('fixed'), + p: (!point || isNaN(point[0]) || isNaN(point[1])) ? null : point + }; + }); + var edges = edgeData.mapArray('value', function (value, idx) { + var edge = graph.getEdgeByIndex(idx); + var d = linearMap(value, edgeDataExtent, edgeLength); + if (isNaN(d)) { + d = (edgeLength[0] + edgeLength[1]) / 2; + } + var edgeModel = edge.getModel(); + return { + n1: nodes[edge.node1.dataIndex], + n2: nodes[edge.node2.dataIndex], + d: d, + curveness: edgeModel.get('lineStyle.curveness') || 0, + ignoreForceLayout: edgeModel.get('ignoreForceLayout') + }; + }); + + var coordSys = graphSeries.coordinateSystem; + var rect = coordSys.getBoundingRect(); + var forceInstance = forceLayout$1(nodes, edges, { + rect: rect, + gravity: forceModel.get('gravity'), + friction: forceModel.get('friction') + }); + var oldStep = forceInstance.step; + forceInstance.step = function (cb) { + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i].fixed) { + // Write back to layout instance + copy(nodes[i].p, graph.getNodeByIndex(i).getLayout()); + } + } + oldStep(function (nodes, edges, stopped) { + for (var i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].fixed) { + graph.getNodeByIndex(i).setLayout(nodes[i].p); + } + preservedPoints[nodeData.getId(i)] = nodes[i].p; + } + for (var i = 0, l = edges.length; i < l; i++) { + var e = edges[i]; + var edge = graph.getEdgeByIndex(i); + var p1 = e.n1.p; + var p2 = e.n2.p; + var points = edge.getLayout(); + points = points ? points.slice() : []; + points[0] = points[0] || []; + points[1] = points[1] || []; + copy(points[0], p1); + copy(points[1], p2); + if (+e.curveness) { + points[2] = [ + (p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * e.curveness, + (p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * e.curveness + ]; + } + edge.setLayout(points); + } + // Update layout + + cb && cb(stopped); + }); + }; + graphSeries.forceLayout = forceInstance; + graphSeries.preservedPoints = preservedPoints; + + // Step to get the layout + forceInstance.step(); + } + else { + // Remove prev injected forceLayout instance + graphSeries.forceLayout = null; + } + }); +}; + +/* +* 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. +*/ + +// FIXME Where to create the simple view coordinate system +function getViewRect$2(seriesModel, api, aspect) { + var option = seriesModel.getBoxLayoutParams(); + option.aspect = aspect; + return getLayoutRect(option, { + width: api.getWidth(), + height: api.getHeight() + }); +} + +var createView = function (ecModel, api) { + var viewList = []; + ecModel.eachSeriesByType('graph', function (seriesModel) { + var coordSysType = seriesModel.get('coordinateSystem'); + if (!coordSysType || coordSysType === 'view') { + + var data = seriesModel.getData(); + var positions = data.mapArray(function (idx) { + var itemModel = data.getItemModel(idx); + return [+itemModel.get('x'), +itemModel.get('y')]; + }); + + var min = []; + var max = []; + + fromPoints(positions, 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 aspect = (max[0] - min[0]) / (max[1] - min[1]); + // FIXME If get view rect after data processed? + var viewRect = getViewRect$2(seriesModel, api, aspect); + // Position may be NaN, use view rect instead + if (isNaN(aspect)) { + min = [viewRect.x, viewRect.y]; + max = [viewRect.x + viewRect.width, viewRect.y + viewRect.height]; + } + + var bbWidth = max[0] - min[0]; + var bbHeight = max[1] - min[1]; + + var viewWidth = viewRect.width; + var viewHeight = viewRect.height; + + var viewCoordSys = seriesModel.coordinateSystem = new View(); + viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); + + viewCoordSys.setBoundingRect( + min[0], min[1], bbWidth, bbHeight + ); + viewCoordSys.setViewRect( + viewRect.x, viewRect.y, viewWidth, viewHeight + ); + + // Update roam info + viewCoordSys.setCenter(seriesModel.get('center')); + viewCoordSys.setZoom(seriesModel.get('zoom')); + + viewList.push(viewCoordSys); + } + }); + + return viewList; +}; + +/* +* 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. +*/ + +registerProcessor(categoryFilter); + +registerVisual(visualSymbol('graph', 'circle', null)); +registerVisual(categoryVisual); +registerVisual(edgeVisual); + +registerLayout(simpleLayout); +registerLayout(PRIORITY.VISUAL.POST_CHART_LAYOUT, circularLayout); +registerLayout(forceLayout); + +// Graph view coordinate system +registerCoordinateSystem('graphView', { + create: createView +}); + +/* +* 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. +*/ + +var GaugeSeries = SeriesModel.extend({ + + type: 'series.gauge', + + getInitialData: function (option, ecModel) { + return createListSimply(this, ['value']); + }, + + defaultOption: { + zlevel: 0, + z: 2, + // 默认全局居中 + center: ['50%', '50%'], + legendHoverLink: true, + radius: '75%', + startAngle: 225, + endAngle: -45, + clockwise: true, + // 最小值 + min: 0, + // 最大值 + max: 100, + // 分割段数,默认为10 + splitNumber: 10, + // 坐标轴线 + axisLine: { + // 默认显示,属性show控制显示与否 + show: true, + lineStyle: { // 属性lineStyle控制线条样式 + color: [[0.2, '#91c7ae'], [0.8, '#63869e'], [1, '#c23531']], + width: 30 + } + }, + // 分隔线 + splitLine: { + // 默认显示,属性show控制显示与否 + show: true, + // 属性length控制线长 + length: 30, + // 属性lineStyle(详见lineStyle)控制线条样式 + lineStyle: { + color: '#eee', + width: 2, + type: 'solid' + } + }, + // 坐标轴小标记 + axisTick: { + // 属性show控制显示与否,默认不显示 + show: true, + // 每份split细分多少段 + splitNumber: 5, + // 属性length控制线长 + length: 8, + // 属性lineStyle控制线条样式 + lineStyle: { + color: '#eee', + width: 1, + type: 'solid' + } + }, + axisLabel: { + show: true, + distance: 5, + // formatter: null, + color: 'auto' + }, + pointer: { + show: true, + length: '80%', + width: 8 + }, + itemStyle: { + color: 'auto' + }, + title: { + show: true, + // x, y,单位px + offsetCenter: [0, '-40%'], + // 其余属性默认使用全局文本样式,详见TEXTSTYLE + color: '#333', + fontSize: 15 + }, + detail: { + show: true, + backgroundColor: 'rgba(0,0,0,0)', + borderWidth: 0, + borderColor: '#ccc', + width: 100, + height: null, // self-adaption + padding: [5, 10], + // x, y,单位px + offsetCenter: [0, '40%'], + // formatter: null, + // 其余属性默认使用全局文本样式,详见TEXTSTYLE + color: 'auto', + fontSize: 30 + } + } +}); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +var PointerPath = Path.extend({ + + type: 'echartsGaugePointer', + + shape: { + angle: 0, + + width: 10, + + r: 10, + + x: 0, + + y: 0 + }, + + buildPath: function (ctx, shape) { + var mathCos = Math.cos; + var mathSin = Math.sin; + + var r = shape.r; + var width = shape.width; + var angle = shape.angle; + var x = shape.x - mathCos(angle) * width * (width >= r / 3 ? 1 : 2); + var y = shape.y - mathSin(angle) * width * (width >= r / 3 ? 1 : 2); + + angle = shape.angle - Math.PI / 2; + ctx.moveTo(x, y); + ctx.lineTo( + shape.x + mathCos(angle) * width, + shape.y + mathSin(angle) * width + ); + ctx.lineTo( + shape.x + mathCos(shape.angle) * r, + shape.y + mathSin(shape.angle) * r + ); + ctx.lineTo( + shape.x - mathCos(angle) * width, + shape.y - mathSin(angle) * width + ); + ctx.lineTo(x, y); + return; + } +}); + +/* +* 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. +*/ + +function parsePosition(seriesModel, api) { + var center = seriesModel.get('center'); + var width = api.getWidth(); + var height = api.getHeight(); + var size = Math.min(width, height); + var cx = parsePercent$1(center[0], api.getWidth()); + var cy = parsePercent$1(center[1], api.getHeight()); + var r = parsePercent$1(seriesModel.get('radius'), size / 2); + + return { + cx: cx, + cy: cy, + r: r + }; +} + +function formatLabel(label, labelFormatter) { + if (labelFormatter) { + if (typeof labelFormatter === 'string') { + label = labelFormatter.replace('{value}', label != null ? label : ''); + } + else if (typeof labelFormatter === 'function') { + label = labelFormatter(label); + } + } + + return label; +} + +var PI2$5 = Math.PI * 2; + +var GaugeView = Chart.extend({ + + type: 'gauge', + + render: function (seriesModel, ecModel, api) { + + this.group.removeAll(); + + var colorList = seriesModel.get('axisLine.lineStyle.color'); + var posInfo = parsePosition(seriesModel, api); + + this._renderMain( + seriesModel, ecModel, api, colorList, posInfo + ); + }, + + dispose: function () {}, + + _renderMain: function (seriesModel, ecModel, api, colorList, posInfo) { + var group = this.group; + + var axisLineModel = seriesModel.getModel('axisLine'); + var lineStyleModel = axisLineModel.getModel('lineStyle'); + + var clockwise = seriesModel.get('clockwise'); + var startAngle = -seriesModel.get('startAngle') / 180 * Math.PI; + var endAngle = -seriesModel.get('endAngle') / 180 * Math.PI; + + var angleRangeSpan = (endAngle - startAngle) % PI2$5; + + var prevEndAngle = startAngle; + var axisLineWidth = lineStyleModel.get('width'); + var showAxis = axisLineModel.get('show'); + + for (var i = 0; showAxis && i < colorList.length; i++) { + // Clamp + var percent = Math.min(Math.max(colorList[i][0], 0), 1); + var endAngle = startAngle + angleRangeSpan * percent; + var sector = new Sector({ + shape: { + startAngle: prevEndAngle, + endAngle: endAngle, + cx: posInfo.cx, + cy: posInfo.cy, + clockwise: clockwise, + r0: posInfo.r - axisLineWidth, + r: posInfo.r + }, + silent: true + }); + + sector.setStyle({ + fill: colorList[i][1] + }); + + sector.setStyle(lineStyleModel.getLineStyle( + // Because we use sector to simulate arc + // so the properties for stroking are useless + ['color', 'borderWidth', 'borderColor'] + )); + + group.add(sector); + + prevEndAngle = endAngle; + } + + var getColor = function (percent) { + // Less than 0 + if (percent <= 0) { + return colorList[0][1]; + } + for (var i = 0; i < colorList.length; i++) { + if (colorList[i][0] >= percent + && (i === 0 ? 0 : colorList[i - 1][0]) < percent + ) { + return colorList[i][1]; + } + } + // More than 1 + return colorList[i - 1][1]; + }; + + if (!clockwise) { + var tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + + this._renderTicks( + seriesModel, ecModel, api, getColor, posInfo, + startAngle, endAngle, clockwise + ); + + this._renderPointer( + seriesModel, ecModel, api, getColor, posInfo, + startAngle, endAngle, clockwise + ); + + this._renderTitle( + seriesModel, ecModel, api, getColor, posInfo + ); + this._renderDetail( + seriesModel, ecModel, api, getColor, posInfo + ); + }, + + _renderTicks: function ( + seriesModel, ecModel, api, getColor, posInfo, + startAngle, endAngle, clockwise + ) { + var group = this.group; + var cx = posInfo.cx; + var cy = posInfo.cy; + var r = posInfo.r; + + var minVal = +seriesModel.get('min'); + var maxVal = +seriesModel.get('max'); + + var splitLineModel = seriesModel.getModel('splitLine'); + var tickModel = seriesModel.getModel('axisTick'); + var labelModel = seriesModel.getModel('axisLabel'); + + var splitNumber = seriesModel.get('splitNumber'); + var subSplitNumber = tickModel.get('splitNumber'); + + var splitLineLen = parsePercent$1( + splitLineModel.get('length'), r + ); + var tickLen = parsePercent$1( + tickModel.get('length'), r + ); + + var angle = startAngle; + var step = (endAngle - startAngle) / splitNumber; + var subStep = step / subSplitNumber; + + var splitLineStyle = splitLineModel.getModel('lineStyle').getLineStyle(); + var tickLineStyle = tickModel.getModel('lineStyle').getLineStyle(); + + for (var i = 0; i <= splitNumber; i++) { + var unitX = Math.cos(angle); + var unitY = Math.sin(angle); + // Split line + if (splitLineModel.get('show')) { + var splitLine = new Line({ + shape: { + x1: unitX * r + cx, + y1: unitY * r + cy, + x2: unitX * (r - splitLineLen) + cx, + y2: unitY * (r - splitLineLen) + cy + }, + style: splitLineStyle, + silent: true + }); + if (splitLineStyle.stroke === 'auto') { + splitLine.setStyle({ + stroke: getColor(i / splitNumber) + }); + } + + group.add(splitLine); + } + + // Label + if (labelModel.get('show')) { + var label = formatLabel( + round$1(i / splitNumber * (maxVal - minVal) + minVal), + labelModel.get('formatter') + ); + var distance = labelModel.get('distance'); + var autoColor = getColor(i / splitNumber); + + group.add(new Text({ + style: setTextStyle({}, labelModel, { + text: label, + x: unitX * (r - splitLineLen - distance) + cx, + y: unitY * (r - splitLineLen - distance) + cy, + textVerticalAlign: unitY < -0.4 ? 'top' : (unitY > 0.4 ? 'bottom' : 'middle'), + textAlign: unitX < -0.4 ? 'left' : (unitX > 0.4 ? 'right' : 'center') + }, {autoColor: autoColor}), + silent: true + })); + } + + // Axis tick + if (tickModel.get('show') && i !== splitNumber) { + for (var j = 0; j <= subSplitNumber; j++) { + var unitX = Math.cos(angle); + var unitY = Math.sin(angle); + var tickLine = new Line({ + shape: { + x1: unitX * r + cx, + y1: unitY * r + cy, + x2: unitX * (r - tickLen) + cx, + y2: unitY * (r - tickLen) + cy + }, + silent: true, + style: tickLineStyle + }); + + if (tickLineStyle.stroke === 'auto') { + tickLine.setStyle({ + stroke: getColor((i + j / subSplitNumber) / splitNumber) + }); + } + + group.add(tickLine); + angle += subStep; + } + angle -= subStep; + } + else { + angle += step; + } + } + }, + + _renderPointer: function ( + seriesModel, ecModel, api, getColor, posInfo, + startAngle, endAngle, clockwise + ) { + + var group = this.group; + var oldData = this._data; + + if (!seriesModel.get('pointer.show')) { + // Remove old element + oldData && oldData.eachItemGraphicEl(function (el) { + group.remove(el); + }); + return; + } + + var valueExtent = [+seriesModel.get('min'), +seriesModel.get('max')]; + var angleExtent = [startAngle, endAngle]; + + var data = seriesModel.getData(); + var valueDim = data.mapDimension('value'); + + data.diff(oldData) + .add(function (idx) { + var pointer = new PointerPath({ + shape: { + angle: startAngle + } + }); + + initProps(pointer, { + shape: { + angle: linearMap(data.get(valueDim, idx), valueExtent, angleExtent, true) + } + }, seriesModel); + + group.add(pointer); + data.setItemGraphicEl(idx, pointer); + }) + .update(function (newIdx, oldIdx) { + var pointer = oldData.getItemGraphicEl(oldIdx); + + updateProps(pointer, { + shape: { + angle: linearMap(data.get(valueDim, newIdx), valueExtent, angleExtent, true) + } + }, seriesModel); + + group.add(pointer); + data.setItemGraphicEl(newIdx, pointer); + }) + .remove(function (idx) { + var pointer = oldData.getItemGraphicEl(idx); + group.remove(pointer); + }) + .execute(); + + data.eachItemGraphicEl(function (pointer, idx) { + var itemModel = data.getItemModel(idx); + var pointerModel = itemModel.getModel('pointer'); + + pointer.setShape({ + x: posInfo.cx, + y: posInfo.cy, + width: parsePercent$1( + pointerModel.get('width'), posInfo.r + ), + r: parsePercent$1(pointerModel.get('length'), posInfo.r) + }); + + pointer.useStyle(itemModel.getModel('itemStyle').getItemStyle()); + + if (pointer.style.fill === 'auto') { + pointer.setStyle('fill', getColor( + linearMap(data.get(valueDim, idx), valueExtent, [0, 1], true) + )); + } + + setHoverStyle( + pointer, itemModel.getModel('emphasis.itemStyle').getItemStyle() + ); + }); + + this._data = data; + }, + + _renderTitle: function ( + seriesModel, ecModel, api, getColor, posInfo + ) { + var data = seriesModel.getData(); + var valueDim = data.mapDimension('value'); + var titleModel = seriesModel.getModel('title'); + if (titleModel.get('show')) { + var offsetCenter = titleModel.get('offsetCenter'); + var x = posInfo.cx + parsePercent$1(offsetCenter[0], posInfo.r); + var y = posInfo.cy + parsePercent$1(offsetCenter[1], posInfo.r); + + var minVal = +seriesModel.get('min'); + var maxVal = +seriesModel.get('max'); + var value = seriesModel.getData().get(valueDim, 0); + var autoColor = getColor( + linearMap(value, [minVal, maxVal], [0, 1], true) + ); + + this.group.add(new Text({ + silent: true, + style: setTextStyle({}, titleModel, { + x: x, + y: y, + // FIXME First data name ? + text: data.getName(0), + textAlign: 'center', + textVerticalAlign: 'middle' + }, {autoColor: autoColor, forceRich: true}) + })); + } + }, + + _renderDetail: function ( + seriesModel, ecModel, api, getColor, posInfo + ) { + var detailModel = seriesModel.getModel('detail'); + var minVal = +seriesModel.get('min'); + var maxVal = +seriesModel.get('max'); + if (detailModel.get('show')) { + var offsetCenter = detailModel.get('offsetCenter'); + var x = posInfo.cx + parsePercent$1(offsetCenter[0], posInfo.r); + var y = posInfo.cy + parsePercent$1(offsetCenter[1], posInfo.r); + var width = parsePercent$1(detailModel.get('width'), posInfo.r); + var height = parsePercent$1(detailModel.get('height'), posInfo.r); + var data = seriesModel.getData(); + var value = data.get(data.mapDimension('value'), 0); + var autoColor = getColor( + linearMap(value, [minVal, maxVal], [0, 1], true) + ); + + this.group.add(new Text({ + silent: true, + style: setTextStyle({}, detailModel, { + x: x, + y: y, + text: formatLabel( + // FIXME First data name ? + value, detailModel.get('formatter') + ), + textWidth: isNaN(width) ? null : width, + textHeight: isNaN(height) ? null : height, + textAlign: 'center', + textVerticalAlign: 'middle' + }, {autoColor: autoColor, forceRich: true}) + })); + } + } +}); + +/* +* 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. +*/ + +/* +* 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. +*/ + +var FunnelSeries = extendSeriesModel({ + + type: 'series.funnel', + + init: function (option) { + FunnelSeries.superApply(this, 'init', arguments); + + // Enable legend selection for each data item + // Use a function instead of direct access because data reference may changed + this.legendVisualProvider = new LegendVisualProvider( + bind(this.getData, this), bind(this.getRawData, this) + ); + // Extend labelLine emphasis + this._defaultLabelLine(option); + }, + + getInitialData: function (option, ecModel) { + return createListSimply(this, { + coordDimensions: ['value'], + encodeDefaulter: curry(makeSeriesEncodeForNameBased, this) + }); + }, + + _defaultLabelLine: function (option) { + // Extend labelLine emphasis + defaultEmphasis(option, 'labelLine', ['show']); + + var labelLineNormalOpt = option.labelLine; + var labelLineEmphasisOpt = option.emphasis.labelLine; + // Not show label line if `label.normal.show = false` + labelLineNormalOpt.show = labelLineNormalOpt.show + && option.label.show; + labelLineEmphasisOpt.show = labelLineEmphasisOpt.show + && option.emphasis.label.show; + }, + + // Overwrite + getDataParams: function (dataIndex) { + var data = this.getData(); + var params = FunnelSeries.superCall(this, 'getDataParams', dataIndex); + var valueDim = data.mapDimension('value'); + var sum = data.getSum(valueDim); + // Percent is 0 if sum is 0 + params.percent = !sum ? 0 : +(data.get(valueDim, dataIndex) / sum * 100).toFixed(2); + + params.$vars.push('percent'); + return params; + }, + + defaultOption: { + zlevel: 0, // 一级层叠 + z: 2, // 二级层叠 + legendHoverLink: true, + left: 80, + top: 60, + right: 80, + bottom: 60, + // width: {totalWidth} - left - right, + // height: {totalHeight} - top - bottom, + + // 默认取数据最小最大值 + // min: 0, + // max: 100, + minSize: '0%', + maxSize: '100%', + sort: 'descending', // 'ascending', 'descending' + gap: 0, + funnelAlign: 'center', + label: { + show: true, + position: 'outer' + // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调 + }, + labelLine: { + show: true, + length: 20, + lineStyle: { + // color: 各异, + width: 1, + type: 'solid' + } + }, + itemStyle: { + // color: 各异, + borderColor: '#fff', + borderWidth: 1 + }, + emphasis: { + label: { + show: true + } + } + } +}); + +/* +* 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. +*/ + +/** + * Piece of pie including Sector, Label, LabelLine + * @constructor + * @extends {module:zrender/graphic/Group} + */ +function FunnelPiece(data, idx) { + + Group.call(this); + + var polygon = new Polygon(); + var labelLine = new Polyline(); + var text = new Text(); + this.add(polygon); + this.add(labelLine); + this.add(text); + + this.highDownOnUpdate = function (fromState, toState) { + if (toState === 'emphasis') { + labelLine.ignore = labelLine.hoverIgnore; + text.ignore = text.hoverIgnore; + } + else { + labelLine.ignore = labelLine.normalIgnore; + text.ignore = text.normalIgnore; + } + }; + + this.updateData(data, idx, true); +} + +var funnelPieceProto = FunnelPiece.prototype; + +var opacityAccessPath = ['itemStyle', 'opacity']; +funnelPieceProto.updateData = function (data, idx, firstCreate) { + + var polygon = this.childAt(0); + + var seriesModel = data.hostModel; + var itemModel = data.getItemModel(idx); + var layout = data.getItemLayout(idx); + var opacity = data.getItemModel(idx).get(opacityAccessPath); + opacity = opacity == null ? 1 : opacity; + + // Reset style + polygon.useStyle({}); + + if (firstCreate) { + polygon.setShape({ + points: layout.points + }); + polygon.setStyle({opacity: 0}); + initProps(polygon, { + style: { + opacity: opacity + } + }, seriesModel, idx); + } + else { + updateProps(polygon, { + style: { + opacity: opacity + }, + shape: { + points: layout.points + } + }, seriesModel, idx); + } + + // Update common style + var itemStyleModel = itemModel.getModel('itemStyle'); + var visualColor = data.getItemVisual(idx, 'color'); + + polygon.setStyle( + defaults( + { + lineJoin: 'round', + fill: visualColor + }, + itemStyleModel.getItemStyle(['opacity']) + ) + ); + polygon.hoverStyle = itemStyleModel.getModel('emphasis').getItemStyle(); + + this._updateLabel(data, idx); + + setHoverStyle(this); +}; + +funnelPieceProto._updateLabel = function (data, idx) { + + var labelLine = this.childAt(1); + var labelText = this.childAt(2); + + var seriesModel = data.hostModel; + var itemModel = data.getItemModel(idx); + var layout = data.getItemLayout(idx); + var labelLayout = layout.label; + var visualColor = data.getItemVisual(idx, 'color'); + + updateProps(labelLine, { + shape: { + points: labelLayout.linePoints || labelLayout.linePoints + } + }, seriesModel, idx); + + updateProps(labelText, { + style: { + x: labelLayout.x, + y: labelLayout.y + } + }, seriesModel, idx); + labelText.attr({ + rotation: labelLayout.rotation, + origin: [labelLayout.x, labelLayout.y], + z2: 10 + }); + + var labelModel = itemModel.getModel('label'); + var labelHoverModel = itemModel.getModel('emphasis.label'); + var labelLineModel = itemModel.getModel('labelLine'); + var labelLineHoverModel = itemModel.getModel('emphasis.labelLine'); + var visualColor = data.getItemVisual(idx, 'color'); + + setLabelStyle( + labelText.style, labelText.hoverStyle = {}, labelModel, labelHoverModel, + { + labelFetcher: data.hostModel, + labelDataIndex: idx, + defaultText: data.getName(idx), + autoColor: visualColor, + useInsideStyle: !!labelLayout.inside + }, + { + textAlign: labelLayout.textAlign, + textVerticalAlign: labelLayout.verticalAlign + } + ); + + labelText.ignore = labelText.normalIgnore = !labelModel.get('show'); + labelText.hoverIgnore = !labelHoverModel.get('show'); + + labelLine.ignore = labelLine.normalIgnore = !labelLineModel.get('show'); + labelLine.hoverIgnore = !labelLineHoverModel.get('show'); + + // Default use item visual color + labelLine.setStyle({ + stroke: visualColor + }); + labelLine.setStyle(labelLineModel.getModel('lineStyle').getLineStyle()); + + labelLine.hoverStyle = labelLineHoverModel.getModel('lineStyle').getLineStyle(); +}; + +inherits(FunnelPiece, Group); + + +var FunnelView = Chart.extend({ + + type: 'funnel', + + render: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + var oldData = this._data; + + var group = this.group; + + data.diff(oldData) + .add(function (idx) { + var funnelPiece = new FunnelPiece(data, idx); + + data.setItemGraphicEl(idx, funnelPiece); + + group.add(funnelPiece); + }) + .update(function (newIdx, oldIdx) { + var piePiece = oldData.getItemGraphicEl(oldIdx); + + piePiece.updateData(data, newIdx); + + group.add(piePiece); + data.setItemGraphicEl(newIdx, piePiece); + }) + .remove(function (idx) { + var piePiece = oldData.getItemGraphicEl(idx); + group.remove(piePiece); + }) + .execute(); + + this._data = data; + }, + + remove: function () { + this.group.removeAll(); + this._data = null; + }, + + dispose: function () {} +}); + +/* +* 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. +*/ + +function getViewRect$3(seriesModel, api) { + return getLayoutRect( + seriesModel.getBoxLayoutParams(), { + width: api.getWidth(), + height: api.getHeight() + } + ); +} + +function getSortedIndices(data, sort) { + var valueDim = data.mapDimension('value'); + var valueArr = data.mapArray(valueDim, function (val) { + return val; + }); + var indices = []; + var isAscending = sort === 'ascending'; + for (var i = 0, len = data.count(); i < len; i++) { + indices[i] = i; + } + + // Add custom sortable function & none sortable opetion by "options.sort" + if (typeof sort === 'function') { + indices.sort(sort); + } + else if (sort !== 'none') { + indices.sort(function (a, b) { + return isAscending ? valueArr[a] - valueArr[b] : valueArr[b] - valueArr[a]; + }); + } + return indices; +} + +function labelLayout$1(data) { + data.each(function (idx) { + var itemModel = data.getItemModel(idx); + var labelModel = itemModel.getModel('label'); + var labelPosition = labelModel.get('position'); + + var labelLineModel = itemModel.getModel('labelLine'); + + var layout = data.getItemLayout(idx); + var points = layout.points; + + var isLabelInside = labelPosition === 'inner' + || labelPosition === 'inside' || labelPosition === 'center' + || labelPosition === 'insideLeft' || labelPosition === 'insideRight'; + + var textAlign; + var textX; + var textY; + var linePoints; + + if (isLabelInside) { + if (labelPosition === 'insideLeft') { + textX = (points[0][0] + points[3][0]) / 2 + 5; + textY = (points[0][1] + points[3][1]) / 2; + textAlign = 'left'; + } + else if (labelPosition === 'insideRight') { + textX = (points[1][0] + points[2][0]) / 2 - 5; + textY = (points[1][1] + points[2][1]) / 2; + textAlign = 'right'; + } + else { + textX = (points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4; + textY = (points[0][1] + points[1][1] + points[2][1] + points[3][1]) / 4; + textAlign = 'center'; + } + linePoints = [ + [textX, textY], [textX, textY] + ]; + } + else { + var x1; + var y1; + var x2; + var labelLineLen = labelLineModel.get('length'); + if (labelPosition === 'left') { + // Left side + x1 = (points[3][0] + points[0][0]) / 2; + y1 = (points[3][1] + points[0][1]) / 2; + x2 = x1 - labelLineLen; + textX = x2 - 5; + textAlign = 'right'; + } + else if (labelPosition === 'right') { + // Right side + x1 = (points[1][0] + points[2][0]) / 2; + y1 = (points[1][1] + points[2][1]) / 2; + x2 = x1 + labelLineLen; + textX = x2 + 5; + textAlign = 'left'; + } + else if (labelPosition === 'rightTop') { + // RightTop side + x1 = points[1][0]; + y1 = points[1][1]; + x2 = x1 + labelLineLen; + textX = x2 + 5; + textAlign = 'top'; + } + else if (labelPosition === 'rightBottom') { + // RightBottom side + x1 = points[2][0]; + y1 = points[2][1]; + x2 = x1 + labelLineLen; + textX = x2 + 5; + textAlign = 'bottom'; + } + else if (labelPosition === 'leftTop') { + // LeftTop side + x1 = points[0][0]; + y1 = points[1][1]; + x2 = x1 - labelLineLen; + textX = x2 - 5; + textAlign = 'right'; + } + else if (labelPosition === 'leftBottom') { + // LeftBottom side + x1 = points[3][0]; + y1 = points[2][1]; + x2 = x1 - labelLineLen; + textX = x2 - 5; + textAlign = 'right'; + } + else { + // Right side + x1 = (points[1][0] + points[2][0]) / 2; + y1 = (points[1][1] + points[2][1]) / 2; + x2 = x1 + labelLineLen; + textX = x2 + 5; + textAlign = 'left'; + } + var y2 = y1; + + linePoints = [[x1, y1], [x2, y2]]; + textY = y2; + } + + layout.label = { + linePoints: linePoints, + x: textX, + y: textY, + verticalAlign: 'middle', + textAlign: textAlign, + inside: isLabelInside + }; + }); +} + +var funnelLayout = function (ecModel, api, payload) { + ecModel.eachSeriesByType('funnel', function (seriesModel) { + var data = seriesModel.getData(); + var valueDim = data.mapDimension('value'); + var sort = seriesModel.get('sort'); + var viewRect = getViewRect$3(seriesModel, api); + var indices = getSortedIndices(data, sort); + + var sizeExtent = [ + parsePercent$1(seriesModel.get('minSize'), viewRect.width), + parsePercent$1(seriesModel.get('maxSize'), viewRect.width) + ]; + var dataExtent = data.getDataExtent(valueDim); + var min = seriesModel.get('min'); + var max = seriesModel.get('max'); + if (min == null) { + min = Math.min(dataExtent[0], 0); + } + if (max == null) { + max = dataExtent[1]; + } + + var funnelAlign = seriesModel.get('funnelAlign'); + var gap = seriesModel.get('gap'); + var itemHeight = (viewRect.height - gap * (data.count() - 1)) / data.count(); + + var y = viewRect.y; + + var getLinePoints = function (idx, offY) { + // End point index is data.count() and we assign it 0 + var val = data.get(valueDim, idx) || 0; + var itemWidth = linearMap(val, [min, max], sizeExtent, true); + var x0; + switch (funnelAlign) { + case 'left': + x0 = viewRect.x; + break; + case 'center': + x0 = viewRect.x + (viewRect.width - itemWidth) / 2; + break; + case 'right': + x0 = viewRect.x + viewRect.width - itemWidth; + break; + } + return [ + [x0, offY], + [x0 + itemWidth, offY] + ]; + }; + + if (sort === 'ascending') { + // From bottom to top + itemHeight = -itemHeight; + gap = -gap; + y += viewRect.height; + indices = indices.reverse(); + } + + for (var i = 0; i < indices.length; i++) { + var idx = indices[i]; + var nextIdx = indices[i + 1]; + + var itemModel = data.getItemModel(idx); + var height = itemModel.get('itemStyle.height'); + if (height == null) { + height = itemHeight; + } + else { + height = parsePercent$1(height, viewRect.height); + if (sort === 'ascending') { + height = -height; + } + } + + var start = getLinePoints(idx, y); + var end = getLinePoints(nextIdx, y + height); + + y += height + gap; + + data.setItemLayout(idx, { + points: start.concat(end.slice().reverse()) + }); + } + + labelLayout$1(data); + }); +}; + +/* +* 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. +*/ + +registerVisual(dataColor('funnel')); +registerLayout(funnelLayout); +registerProcessor(dataFilter('funnel')); + +/* +* 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. +*/ + +var parallelPreprocessor = function (option) { + createParallelIfNeeded(option); + mergeAxisOptionFromParallel(option); +}; + +/** + * Create a parallel coordinate if not exists. + * @inner + */ +function createParallelIfNeeded(option) { + if (option.parallel) { + return; + } + + var hasParallelSeries = false; + + each$1(option.series, function (seriesOpt) { + if (seriesOpt && seriesOpt.type === 'parallel') { + hasParallelSeries = true; + } + }); + + if (hasParallelSeries) { + option.parallel = [{}]; + } +} + +/** + * Merge aixs definition from parallel option (if exists) to axis option. + * @inner + */ +function mergeAxisOptionFromParallel(option) { + var axes = normalizeToArray(option.parallelAxis); + + each$1(axes, function (axisOption) { + if (!isObject$1(axisOption)) { + return; + } + + var parallelIndex = axisOption.parallelIndex || 0; + var parallelOption = normalizeToArray(option.parallel)[parallelIndex]; + + if (parallelOption && parallelOption.parallelAxisDefault) { + merge(axisOption, parallelOption.parallelAxisDefault, false); + } + }); +} + +/* +* 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. +*/ + +/** + * @constructor module:echarts/coord/parallel/ParallelAxis + * @extends {module:echarts/coord/Axis} + * @param {string} dim + * @param {*} scale + * @param {Array.} coordExtent + * @param {string} axisType + */ +var ParallelAxis = function (dim, scale, coordExtent, axisType, axisIndex) { + + Axis.call(this, dim, scale, coordExtent); + + /** + * Axis type + * - 'category' + * - 'value' + * - 'time' + * - 'log' + * @type {string} + */ + this.type = axisType || 'value'; + + /** + * @type {number} + * @readOnly + */ + this.axisIndex = axisIndex; +}; + +ParallelAxis.prototype = { + + constructor: ParallelAxis, + + /** + * Axis model + * @param {module:echarts/coord/parallel/AxisModel} + */ + model: null, + + /** + * @override + */ + isHorizontal: function () { + return this.coordinateSystem.getModel().get('layout') !== 'horizontal'; + } + +}; + +inherits(ParallelAxis, Axis); + +/* +* 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. +*/ + +/** + * Calculate slider move result. + * Usage: + * (1) If both handle0 and handle1 are needed to be moved, set minSpan the same as + * maxSpan and the same as `Math.abs(handleEnd[1] - handleEnds[0])`. + * (2) If handle0 is forbidden to cross handle1, set minSpan as `0`. + * + * @param {number} delta Move length. + * @param {Array.} handleEnds handleEnds[0] can be bigger then handleEnds[1]. + * handleEnds will be modified in this method. + * @param {Array.} extent handleEnds is restricted by extent. + * extent[0] should less or equals than extent[1]. + * @param {number|string} handleIndex Can be 'all', means that both move the two handleEnds. + * @param {number} [minSpan] The range of dataZoom can not be smaller than that. + * If not set, handle0 and cross handle1. If set as a non-negative + * number (including `0`), handles will push each other when reaching + * the minSpan. + * @param {number} [maxSpan] The range of dataZoom can not be larger than that. + * @return {Array.} The input handleEnds. + */ +var sliderMove = function (delta, handleEnds, extent, handleIndex, minSpan, maxSpan) { + + delta = delta || 0; + + var extentSpan = extent[1] - extent[0]; + + // Notice maxSpan and minSpan can be null/undefined. + if (minSpan != null) { + minSpan = restrict$1(minSpan, [0, extentSpan]); + } + if (maxSpan != null) { + maxSpan = Math.max(maxSpan, minSpan != null ? minSpan : 0); + } + if (handleIndex === 'all') { + var handleSpan = Math.abs(handleEnds[1] - handleEnds[0]); + handleSpan = restrict$1(handleSpan, [0, extentSpan]); + minSpan = maxSpan = restrict$1(handleSpan, [minSpan, maxSpan]); + handleIndex = 0; + } + + handleEnds[0] = restrict$1(handleEnds[0], extent); + handleEnds[1] = restrict$1(handleEnds[1], extent); + + var originalDistSign = getSpanSign(handleEnds, handleIndex); + + handleEnds[handleIndex] += delta; + + // Restrict in extent. + var extentMinSpan = minSpan || 0; + var realExtent = extent.slice(); + originalDistSign.sign < 0 ? (realExtent[0] += extentMinSpan) : (realExtent[1] -= extentMinSpan); + handleEnds[handleIndex] = restrict$1(handleEnds[handleIndex], realExtent); + + // Expand span. + var currDistSign = getSpanSign(handleEnds, handleIndex); + if (minSpan != null && ( + currDistSign.sign !== originalDistSign.sign || currDistSign.span < minSpan + )) { + // If minSpan exists, 'cross' is forbidden. + handleEnds[1 - handleIndex] = handleEnds[handleIndex] + originalDistSign.sign * minSpan; + } + + // Shrink span. + var currDistSign = getSpanSign(handleEnds, handleIndex); + if (maxSpan != null && currDistSign.span > maxSpan) { + handleEnds[1 - handleIndex] = handleEnds[handleIndex] + currDistSign.sign * maxSpan; + } + + return handleEnds; +}; + +function getSpanSign(handleEnds, handleIndex) { + var dist = handleEnds[handleIndex] - handleEnds[1 - handleIndex]; + // If `handleEnds[0] === handleEnds[1]`, always believe that handleEnd[0] + // is at left of handleEnds[1] for non-cross case. + return {span: Math.abs(dist), sign: dist > 0 ? -1 : dist < 0 ? 1 : handleIndex ? -1 : 1}; +} + +function restrict$1(value, extend) { + return Math.min( + extend[1] != null ? extend[1] : Infinity, + Math.max(extend[0] != null ? extend[0] : -Infinity, value) + ); +} + +/* +* 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. +*/ + +/** + * Parallel Coordinates + * + */ + +var each$11 = each$1; +var mathMin$6 = Math.min; +var mathMax$6 = Math.max; +var mathFloor$2 = Math.floor; +var mathCeil$2 = Math.ceil; +var round$2 = round$1; + +var PI$4 = Math.PI; + +function Parallel(parallelModel, ecModel, api) { + + /** + * key: dimension + * @type {Object.} + * @private + */ + this._axesMap = createHashMap(); + + /** + * key: dimension + * value: {position: [], rotation, } + * @type {Object.} + * @private + */ + this._axesLayout = {}; + + /** + * Always follow axis order. + * @type {Array.} + * @readOnly + */ + this.dimensions = parallelModel.dimensions; + + /** + * @type {module:zrender/core/BoundingRect} + */ + this._rect; + + /** + * @type {module:echarts/coord/parallel/ParallelModel} + */ + this._model = parallelModel; + + this._init(parallelModel, ecModel, api); +} + +Parallel.prototype = { + + type: 'parallel', + + constructor: Parallel, + + /** + * Initialize cartesian coordinate systems + * @private + */ + _init: function (parallelModel, ecModel, api) { + + var dimensions = parallelModel.dimensions; + var parallelAxisIndex = parallelModel.parallelAxisIndex; + + each$11(dimensions, function (dim, idx) { + + var axisIndex = parallelAxisIndex[idx]; + var axisModel = ecModel.getComponent('parallelAxis', axisIndex); + + var axis = this._axesMap.set(dim, new ParallelAxis( + dim, + createScaleByModel(axisModel), + [0, 0], + axisModel.get('type'), + axisIndex + )); + + var isCategory = axis.type === 'category'; + axis.onBand = isCategory && axisModel.get('boundaryGap'); + axis.inverse = axisModel.get('inverse'); + + // Injection + axisModel.axis = axis; + axis.model = axisModel; + axis.coordinateSystem = axisModel.coordinateSystem = this; + + }, this); + }, + + /** + * Update axis scale after data processed + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ + update: function (ecModel, api) { + this._updateAxesFromSeries(this._model, ecModel); + }, + + /** + * @override + */ + containPoint: function (point) { + var layoutInfo = this._makeLayoutInfo(); + var axisBase = layoutInfo.axisBase; + var layoutBase = layoutInfo.layoutBase; + var pixelDimIndex = layoutInfo.pixelDimIndex; + var pAxis = point[1 - pixelDimIndex]; + var pLayout = point[pixelDimIndex]; + + return pAxis >= axisBase + && pAxis <= axisBase + layoutInfo.axisLength + && pLayout >= layoutBase + && pLayout <= layoutBase + layoutInfo.layoutLength; + }, + + getModel: function () { + return this._model; + }, + + /** + * Update properties from series + * @private + */ + _updateAxesFromSeries: function (parallelModel, ecModel) { + ecModel.eachSeries(function (seriesModel) { + + if (!parallelModel.contains(seriesModel, ecModel)) { + return; + } + + var data = seriesModel.getData(); + + each$11(this.dimensions, function (dim) { + var axis = this._axesMap.get(dim); + axis.scale.unionExtentFromData(data, data.mapDimension(dim)); + niceScaleExtent(axis.scale, axis.model); + }, this); + }, this); + }, + + /** + * Resize the parallel coordinate system. + * @param {module:echarts/coord/parallel/ParallelModel} parallelModel + * @param {module:echarts/ExtensionAPI} api + */ + resize: function (parallelModel, api) { + this._rect = getLayoutRect( + parallelModel.getBoxLayoutParams(), + { + width: api.getWidth(), + height: api.getHeight() + } + ); + + this._layoutAxes(); + }, + + /** + * @return {module:zrender/core/BoundingRect} + */ + getRect: function () { + return this._rect; + }, + + /** + * @private + */ + _makeLayoutInfo: function () { + var parallelModel = this._model; + var rect = this._rect; + var xy = ['x', 'y']; + var wh = ['width', 'height']; + var layout = parallelModel.get('layout'); + var pixelDimIndex = layout === 'horizontal' ? 0 : 1; + var layoutLength = rect[wh[pixelDimIndex]]; + var layoutExtent = [0, layoutLength]; + var axisCount = this.dimensions.length; + + var axisExpandWidth = restrict(parallelModel.get('axisExpandWidth'), layoutExtent); + var axisExpandCount = restrict(parallelModel.get('axisExpandCount') || 0, [0, axisCount]); + var axisExpandable = parallelModel.get('axisExpandable') + && axisCount > 3 + && axisCount > axisExpandCount + && axisExpandCount > 1 + && axisExpandWidth > 0 + && layoutLength > 0; + + // `axisExpandWindow` is According to the coordinates of [0, axisExpandLength], + // for sake of consider the case that axisCollapseWidth is 0 (when screen is narrow), + // where collapsed axes should be overlapped. + var axisExpandWindow = parallelModel.get('axisExpandWindow'); + var winSize; + if (!axisExpandWindow) { + winSize = restrict(axisExpandWidth * (axisExpandCount - 1), layoutExtent); + var axisExpandCenter = parallelModel.get('axisExpandCenter') || mathFloor$2(axisCount / 2); + axisExpandWindow = [axisExpandWidth * axisExpandCenter - winSize / 2]; + axisExpandWindow[1] = axisExpandWindow[0] + winSize; + } + else { + winSize = restrict(axisExpandWindow[1] - axisExpandWindow[0], layoutExtent); + axisExpandWindow[1] = axisExpandWindow[0] + winSize; + } + + var axisCollapseWidth = (layoutLength - winSize) / (axisCount - axisExpandCount); + // Avoid axisCollapseWidth is too small. + axisCollapseWidth < 3 && (axisCollapseWidth = 0); + + // Find the first and last indices > ewin[0] and < ewin[1]. + var winInnerIndices = [ + mathFloor$2(round$2(axisExpandWindow[0] / axisExpandWidth, 1)) + 1, + mathCeil$2(round$2(axisExpandWindow[1] / axisExpandWidth, 1)) - 1 + ]; + + // Pos in ec coordinates. + var axisExpandWindow0Pos = axisCollapseWidth / axisExpandWidth * axisExpandWindow[0]; + + return { + layout: layout, + pixelDimIndex: pixelDimIndex, + layoutBase: rect[xy[pixelDimIndex]], + layoutLength: layoutLength, + axisBase: rect[xy[1 - pixelDimIndex]], + axisLength: rect[wh[1 - pixelDimIndex]], + axisExpandable: axisExpandable, + axisExpandWidth: axisExpandWidth, + axisCollapseWidth: axisCollapseWidth, + axisExpandWindow: axisExpandWindow, + axisCount: axisCount, + winInnerIndices: winInnerIndices, + axisExpandWindow0Pos: axisExpandWindow0Pos + }; + }, + + /** + * @private + */ + _layoutAxes: function () { + var rect = this._rect; + var axes = this._axesMap; + var dimensions = this.dimensions; + var layoutInfo = this._makeLayoutInfo(); + var layout = layoutInfo.layout; + + axes.each(function (axis) { + var axisExtent = [0, layoutInfo.axisLength]; + var idx = axis.inverse ? 1 : 0; + axis.setExtent(axisExtent[idx], axisExtent[1 - idx]); + }); + + each$11(dimensions, function (dim, idx) { + var posInfo = (layoutInfo.axisExpandable + ? layoutAxisWithExpand : layoutAxisWithoutExpand + )(idx, layoutInfo); + + var positionTable = { + horizontal: { + x: posInfo.position, + y: layoutInfo.axisLength + }, + vertical: { + x: 0, + y: posInfo.position + } + }; + var rotationTable = { + horizontal: PI$4 / 2, + vertical: 0 + }; + + var position = [ + positionTable[layout].x + rect.x, + positionTable[layout].y + rect.y + ]; + + var rotation = rotationTable[layout]; + var transform = create$1(); + rotate(transform, transform, rotation); + translate(transform, transform, position); + + // TODO + // tick等排布信息。 + + // TODO + // 根据axis order 更新 dimensions顺序。 + + this._axesLayout[dim] = { + position: position, + rotation: rotation, + transform: transform, + axisNameAvailableWidth: posInfo.axisNameAvailableWidth, + axisLabelShow: posInfo.axisLabelShow, + nameTruncateMaxWidth: posInfo.nameTruncateMaxWidth, + tickDirection: 1, + labelDirection: 1 + }; + }, this); + }, + + /** + * Get axis by dim. + * @param {string} dim + * @return {module:echarts/coord/parallel/ParallelAxis} [description] + */ + getAxis: function (dim) { + return this._axesMap.get(dim); + }, + + /** + * Convert a dim value of a single item of series data to Point. + * @param {*} value + * @param {string} dim + * @return {Array} + */ + dataToPoint: function (value, dim) { + return this.axisCoordToPoint( + this._axesMap.get(dim).dataToCoord(value), + dim + ); + }, + + /** + * Travel data for one time, get activeState of each data item. + * @param {module:echarts/data/List} data + * @param {Functio} cb param: {string} activeState 'active' or 'inactive' or 'normal' + * {number} dataIndex + * @param {number} [start=0] the start dataIndex that travel from. + * @param {number} [end=data.count()] the next dataIndex of the last dataIndex will be travel. + */ + eachActiveState: function (data, callback, start, end) { + start == null && (start = 0); + end == null && (end = data.count()); + + var axesMap = this._axesMap; + var dimensions = this.dimensions; + var dataDimensions = []; + var axisModels = []; + + each$1(dimensions, function (axisDim) { + dataDimensions.push(data.mapDimension(axisDim)); + axisModels.push(axesMap.get(axisDim).model); + }); + + var hasActiveSet = this.hasAxisBrushed(); + + for (var dataIndex = start; dataIndex < end; dataIndex++) { + var activeState; + + if (!hasActiveSet) { + activeState = 'normal'; + } + else { + activeState = 'active'; + var values = data.getValues(dataDimensions, dataIndex); + for (var j = 0, lenj = dimensions.length; j < lenj; j++) { + var state = axisModels[j].getActiveState(values[j]); + + if (state === 'inactive') { + activeState = 'inactive'; + break; + } + } + } + + callback(activeState, dataIndex); + } + }, + + /** + * Whether has any activeSet. + * @return {boolean} + */ + hasAxisBrushed: function () { + var dimensions = this.dimensions; + var axesMap = this._axesMap; + var hasActiveSet = false; + + for (var j = 0, lenj = dimensions.length; j < lenj; j++) { + if (axesMap.get(dimensions[j]).model.getActiveState() !== 'normal') { + hasActiveSet = true; + } + } + + return hasActiveSet; + }, + + /** + * Convert coords of each axis to Point. + * Return point. For example: [10, 20] + * @param {Array.} coords + * @param {string} dim + * @return {Array.} + */ + axisCoordToPoint: function (coord, dim) { + var axisLayout = this._axesLayout[dim]; + return applyTransform$1([coord, 0], axisLayout.transform); + }, + + /** + * Get axis layout. + */ + getAxisLayout: function (dim) { + return clone(this._axesLayout[dim]); + }, + + /** + * @param {Array.} point + * @return {Object} {axisExpandWindow, delta, behavior: 'jump' | 'slide' | 'none'}. + */ + getSlidedAxisExpandWindow: function (point) { + var layoutInfo = this._makeLayoutInfo(); + var pixelDimIndex = layoutInfo.pixelDimIndex; + var axisExpandWindow = layoutInfo.axisExpandWindow.slice(); + var winSize = axisExpandWindow[1] - axisExpandWindow[0]; + var extent = [0, layoutInfo.axisExpandWidth * (layoutInfo.axisCount - 1)]; + + // Out of the area of coordinate system. + if (!this.containPoint(point)) { + return {behavior: 'none', axisExpandWindow: axisExpandWindow}; + } + + // Conver the point from global to expand coordinates. + var pointCoord = point[pixelDimIndex] - layoutInfo.layoutBase - layoutInfo.axisExpandWindow0Pos; + + // For dragging operation convenience, the window should not be + // slided when mouse is the center area of the window. + var delta; + var behavior = 'slide'; + var axisCollapseWidth = layoutInfo.axisCollapseWidth; + var triggerArea = this._model.get('axisExpandSlideTriggerArea'); + // But consider touch device, jump is necessary. + var useJump = triggerArea[0] != null; + + if (axisCollapseWidth) { + if (useJump && axisCollapseWidth && pointCoord < winSize * triggerArea[0]) { + behavior = 'jump'; + delta = pointCoord - winSize * triggerArea[2]; + } + else if (useJump && axisCollapseWidth && pointCoord > winSize * (1 - triggerArea[0])) { + behavior = 'jump'; + delta = pointCoord - winSize * (1 - triggerArea[2]); + } + else { + (delta = pointCoord - winSize * triggerArea[1]) >= 0 + && (delta = pointCoord - winSize * (1 - triggerArea[1])) <= 0 + && (delta = 0); + } + delta *= layoutInfo.axisExpandWidth / axisCollapseWidth; + delta + ? sliderMove(delta, axisExpandWindow, extent, 'all') + // Avoid nonsense triger on mousemove. + : (behavior = 'none'); + } + // When screen is too narrow, make it visible and slidable, although it is hard to interact. + else { + var winSize = axisExpandWindow[1] - axisExpandWindow[0]; + var pos = extent[1] * pointCoord / winSize; + axisExpandWindow = [mathMax$6(0, pos - winSize / 2)]; + axisExpandWindow[1] = mathMin$6(extent[1], axisExpandWindow[0] + winSize); + axisExpandWindow[0] = axisExpandWindow[1] - winSize; + } + + return { + axisExpandWindow: axisExpandWindow, + behavior: behavior + }; + } +}; + +function restrict(len, extent) { + return mathMin$6(mathMax$6(len, extent[0]), extent[1]); +} + +function layoutAxisWithoutExpand(axisIndex, layoutInfo) { + var step = layoutInfo.layoutLength / (layoutInfo.axisCount - 1); + return { + position: step * axisIndex, + axisNameAvailableWidth: step, + axisLabelShow: true + }; +} + +function layoutAxisWithExpand(axisIndex, layoutInfo) { + var layoutLength = layoutInfo.layoutLength; + var axisExpandWidth = layoutInfo.axisExpandWidth; + var axisCount = layoutInfo.axisCount; + var axisCollapseWidth = layoutInfo.axisCollapseWidth; + var winInnerIndices = layoutInfo.winInnerIndices; + + var position; + var axisNameAvailableWidth = axisCollapseWidth; + var axisLabelShow = false; + var nameTruncateMaxWidth; + + if (axisIndex < winInnerIndices[0]) { + position = axisIndex * axisCollapseWidth; + nameTruncateMaxWidth = axisCollapseWidth; + } + else if (axisIndex <= winInnerIndices[1]) { + position = layoutInfo.axisExpandWindow0Pos + + axisIndex * axisExpandWidth - layoutInfo.axisExpandWindow[0]; + axisNameAvailableWidth = axisExpandWidth; + axisLabelShow = true; + } + else { + position = layoutLength - (axisCount - 1 - axisIndex) * axisCollapseWidth; + nameTruncateMaxWidth = axisCollapseWidth; + } + + return { + position: position, + axisNameAvailableWidth: axisNameAvailableWidth, + axisLabelShow: axisLabelShow, + nameTruncateMaxWidth: nameTruncateMaxWidth + }; +} + +/* +* 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. +*/ + +/** + * Parallel coordinate system creater. + */ + +function create$2(ecModel, api) { + var coordSysList = []; + + ecModel.eachComponent('parallel', function (parallelModel, idx) { + var coordSys = new Parallel(parallelModel, ecModel, api); + + coordSys.name = 'parallel_' + idx; + coordSys.resize(parallelModel, api); + + parallelModel.coordinateSystem = coordSys; + coordSys.model = parallelModel; + + coordSysList.push(coordSys); + }); + + // Inject the coordinateSystems into seriesModel + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.get('coordinateSystem') === 'parallel') { + var parallelModel = ecModel.queryComponents({ + mainType: 'parallel', + index: seriesModel.get('parallelIndex'), + id: seriesModel.get('parallelId') + })[0]; + seriesModel.coordinateSystem = parallelModel.coordinateSystem; + } + }); + + return coordSysList; +} + +CoordinateSystemManager.register('parallel', {create: create$2}); + +/* +* 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. +*/ + +var AxisModel$2 = ComponentModel.extend({ + + type: 'baseParallelAxis', + + /** + * @type {module:echarts/coord/parallel/Axis} + */ + axis: null, + + /** + * @type {Array.} + * @readOnly + */ + activeIntervals: [], + + /** + * @return {Object} + */ + getAreaSelectStyle: function () { + return makeStyleMapper( + [ + ['fill', 'color'], + ['lineWidth', 'borderWidth'], + ['stroke', 'borderColor'], + ['width', 'width'], + ['opacity', 'opacity'] + ] + )(this.getModel('areaSelectStyle')); + }, + + /** + * The code of this feature is put on AxisModel but not ParallelAxis, + * because axisModel can be alive after echarts updating but instance of + * ParallelAxis having been disposed. this._activeInterval should be kept + * when action dispatched (i.e. legend click). + * + * @param {Array.>} intervals interval.length === 0 + * means set all active. + * @public + */ + setActiveIntervals: function (intervals) { + var activeIntervals = this.activeIntervals = clone(intervals); + + // Normalize + if (activeIntervals) { + for (var i = activeIntervals.length - 1; i >= 0; i--) { + asc(activeIntervals[i]); + } + } + }, + + /** + * @param {number|string} [value] When attempting to detect 'no activeIntervals set', + * value can not be input. + * @return {string} 'normal': no activeIntervals set, + * 'active', + * 'inactive'. + * @public + */ + getActiveState: function (value) { + var activeIntervals = this.activeIntervals; + + if (!activeIntervals.length) { + return 'normal'; + } + + if (value == null || isNaN(value)) { + return 'inactive'; + } + + // Simple optimization + if (activeIntervals.length === 1) { + var interval = activeIntervals[0]; + if (interval[0] <= value && value <= interval[1]) { + return 'active'; + } + } + else { + for (var i = 0, len = activeIntervals.length; i < len; i++) { + if (activeIntervals[i][0] <= value && value <= activeIntervals[i][1]) { + return 'active'; + } + } + } + + return 'inactive'; + } + +}); + +var defaultOption$1 = { + + type: 'value', + + /** + * @type {Array.} + */ + dim: null, // 0, 1, 2, ... + + // parallelIndex: null, + + areaSelectStyle: { + width: 20, + borderWidth: 1, + borderColor: 'rgba(160,197,232)', + color: 'rgba(160,197,232)', + opacity: 0.3 + }, + + realtime: true, // Whether realtime update view when select. + + z: 10 +}; + +merge(AxisModel$2.prototype, axisModelCommonMixin); + +function getAxisType$1(axisName, option) { + return option.type || (option.data ? 'category' : 'value'); +} + +axisModelCreator('parallel', AxisModel$2, getAxisType$1, defaultOption$1); + +/* +* 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. +*/ + +ComponentModel.extend({ + + type: 'parallel', + + dependencies: ['parallelAxis'], + + /** + * @type {module:echarts/coord/parallel/Parallel} + */ + coordinateSystem: null, + + /** + * Each item like: 'dim0', 'dim1', 'dim2', ... + * @type {Array.} + * @readOnly + */ + dimensions: null, + + /** + * Coresponding to dimensions. + * @type {Array.} + * @readOnly + */ + parallelAxisIndex: null, + + layoutMode: 'box', + + defaultOption: { + zlevel: 0, + z: 0, + left: 80, + top: 60, + right: 80, + bottom: 60, + // width: {totalWidth} - left - right, + // height: {totalHeight} - top - bottom, + + layout: 'horizontal', // 'horizontal' or 'vertical' + + // FIXME + // naming? + axisExpandable: false, + axisExpandCenter: null, + axisExpandCount: 0, + axisExpandWidth: 50, // FIXME '10%' ? + axisExpandRate: 17, + axisExpandDebounce: 50, + // [out, in, jumpTarget]. In percentage. If use [null, 0.05], null means full. + // Do not doc to user until necessary. + axisExpandSlideTriggerArea: [-0.15, 0.05, 0.4], + axisExpandTriggerOn: 'click', // 'mousemove' or 'click' + + parallelAxisDefault: null + }, + + /** + * @override + */ + init: function () { + ComponentModel.prototype.init.apply(this, arguments); + + this.mergeOption({}); + }, + + /** + * @override + */ + mergeOption: function (newOption) { + var thisOption = this.option; + + newOption && merge(thisOption, newOption, true); + + this._initDimensions(); + }, + + /** + * Whether series or axis is in this coordinate system. + * @param {module:echarts/model/Series|module:echarts/coord/parallel/AxisModel} model + * @param {module:echarts/model/Global} ecModel + */ + contains: function (model, ecModel) { + var parallelIndex = model.get('parallelIndex'); + return parallelIndex != null + && ecModel.getComponent('parallel', parallelIndex) === this; + }, + + setAxisExpand: function (opt) { + each$1( + ['axisExpandable', 'axisExpandCenter', 'axisExpandCount', 'axisExpandWidth', 'axisExpandWindow'], + function (name) { + if (opt.hasOwnProperty(name)) { + this.option[name] = opt[name]; + } + }, + this + ); + }, + + /** + * @private + */ + _initDimensions: function () { + var dimensions = this.dimensions = []; + var parallelAxisIndex = this.parallelAxisIndex = []; + + var axisModels = filter(this.dependentModels.parallelAxis, function (axisModel) { + // Can not use this.contains here, because + // initialization has not been completed yet. + return (axisModel.get('parallelIndex') || 0) === this.componentIndex; + }, this); + + each$1(axisModels, function (axisModel) { + dimensions.push('dim' + axisModel.get('dim')); + parallelAxisIndex.push(axisModel.componentIndex); + }); + } + +}); + +/* +* 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. +*/ + +/** + * @payload + * @property {string} parallelAxisId + * @property {Array.>} intervals + */ +var actionInfo$1 = { + type: 'axisAreaSelect', + event: 'axisAreaSelected' + // update: 'updateVisual' +}; + +registerAction(actionInfo$1, function (payload, ecModel) { + ecModel.eachComponent( + {mainType: 'parallelAxis', query: payload}, + function (parallelAxisModel) { + parallelAxisModel.axis.model.setActiveIntervals(payload.intervals); + } + ); +}); + +/** + * @payload + */ +registerAction('parallelAxisExpand', function (payload, ecModel) { + ecModel.eachComponent( + {mainType: 'parallel', query: payload}, + function (parallelModel) { + parallelModel.setAxisExpand(payload); + } + ); + +}); + +/* +* 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. +*/ + +var curry$2 = curry; +var each$12 = each$1; +var map$2 = map; +var mathMin$7 = Math.min; +var mathMax$7 = Math.max; +var mathPow$2 = Math.pow; + +var COVER_Z = 10000; +var UNSELECT_THRESHOLD = 6; +var MIN_RESIZE_LINE_WIDTH = 6; +var MUTEX_RESOURCE_KEY = 'globalPan'; + +var DIRECTION_MAP = { + w: [0, 0], + e: [0, 1], + n: [1, 0], + s: [1, 1] +}; +var CURSOR_MAP = { + w: 'ew', + e: 'ew', + n: 'ns', + s: 'ns', + ne: 'nesw', + sw: 'nesw', + nw: 'nwse', + se: 'nwse' +}; +var DEFAULT_BRUSH_OPT = { + brushStyle: { + lineWidth: 2, + stroke: 'rgba(0,0,0,0.3)', + fill: 'rgba(0,0,0,0.1)' + }, + transformable: true, + brushMode: 'single', + removeOnClick: false +}; + +var baseUID = 0; + +/** + * @alias module:echarts/component/helper/BrushController + * @constructor + * @mixin {module:zrender/mixin/Eventful} + * @event module:echarts/component/helper/BrushController#brush + * params: + * areas: Array., coord relates to container group, + * If no container specified, to global. + * opt { + * isEnd: boolean, + * removeOnClick: boolean + * } + * + * @param {module:zrender/zrender~ZRender} zr + */ +function BrushController(zr) { + + if (__DEV__) { + assert$1(zr); + } + + Eventful.call(this); + + /** + * @type {module:zrender/zrender~ZRender} + * @private + */ + this._zr = zr; + + /** + * @type {module:zrender/container/Group} + * @readOnly + */ + this.group = new Group(); + + /** + * Only for drawing (after enabledBrush). + * 'line', 'rect', 'polygon' or false + * If passing false/null/undefined, disable brush. + * If passing 'auto', determined by panel.defaultBrushType + * @private + * @type {string} + */ + this._brushType; + + /** + * Only for drawing (after enabledBrush). + * + * @private + * @type {Object} + */ + this._brushOption; + + /** + * @private + * @type {Object} + */ + this._panels; + + /** + * @private + * @type {Array.} + */ + this._track = []; + + /** + * @private + * @type {boolean} + */ + this._dragging; + + /** + * @private + * @type {Array} + */ + this._covers = []; + + /** + * @private + * @type {moudule:zrender/container/Group} + */ + this._creatingCover; + + /** + * `true` means global panel + * @private + * @type {module:zrender/container/Group|boolean} + */ + this._creatingPanel; + + /** + * @private + * @type {boolean} + */ + this._enableGlobalPan; + + /** + * @private + * @type {boolean} + */ + if (__DEV__) { + this._mounted; + } + + /** + * @private + * @type {string} + */ + this._uid = 'brushController_' + baseUID++; + + /** + * @private + * @type {Object} + */ + this._handlers = {}; + + each$12(pointerHandlers, function (handler, eventName) { + this._handlers[eventName] = bind(handler, this); + }, this); +} + +BrushController.prototype = { + + constructor: BrushController, + + /** + * If set to null/undefined/false, select disabled. + * @param {Object} brushOption + * @param {string|boolean} brushOption.brushType 'line', 'rect', 'polygon' or false + * If passing false/null/undefined, disable brush. + * If passing 'auto', determined by panel.defaultBrushType. + * ('auto' can not be used in global panel) + * @param {number} [brushOption.brushMode='single'] 'single' or 'multiple' + * @param {boolean} [brushOption.transformable=true] + * @param {boolean} [brushOption.removeOnClick=false] + * @param {Object} [brushOption.brushStyle] + * @param {number} [brushOption.brushStyle.width] + * @param {number} [brushOption.brushStyle.lineWidth] + * @param {string} [brushOption.brushStyle.stroke] + * @param {string} [brushOption.brushStyle.fill] + * @param {number} [brushOption.z] + */ + enableBrush: function (brushOption) { + if (__DEV__) { + assert$1(this._mounted); + } + + this._brushType && doDisableBrush(this); + brushOption.brushType && doEnableBrush(this, brushOption); + + return this; + }, + + /** + * @param {Array.} panelOpts If not pass, it is global brush. + * Each items: { + * panelId, // mandatory. + * clipPath, // mandatory. function. + * isTargetByCursor, // mandatory. function. + * defaultBrushType, // optional, only used when brushType is 'auto'. + * getLinearBrushOtherExtent, // optional. function. + * } + */ + setPanels: function (panelOpts) { + if (panelOpts && panelOpts.length) { + var panels = this._panels = {}; + each$1(panelOpts, function (panelOpts) { + panels[panelOpts.panelId] = clone(panelOpts); + }); + } + else { + this._panels = null; + } + return this; + }, + + /** + * @param {Object} [opt] + * @return {boolean} [opt.enableGlobalPan=false] + */ + mount: function (opt) { + opt = opt || {}; + + if (__DEV__) { + this._mounted = true; // should be at first. + } + + this._enableGlobalPan = opt.enableGlobalPan; + + var thisGroup = this.group; + this._zr.add(thisGroup); + + thisGroup.attr({ + position: opt.position || [0, 0], + rotation: opt.rotation || 0, + scale: opt.scale || [1, 1] + }); + this._transform = thisGroup.getLocalTransform(); + + return this; + }, + + eachCover: function (cb, context) { + each$12(this._covers, cb, context); + }, + + /** + * Update covers. + * @param {Array.} brushOptionList Like: + * [ + * {id: 'xx', brushType: 'line', range: [23, 44], brushStyle, transformable}, + * {id: 'yy', brushType: 'rect', range: [[23, 44], [23, 54]]}, + * ... + * ] + * `brushType` is required in each cover info. (can not be 'auto') + * `id` is not mandatory. + * `brushStyle`, `transformable` is not mandatory, use DEFAULT_BRUSH_OPT by default. + * If brushOptionList is null/undefined, all covers removed. + */ + updateCovers: function (brushOptionList) { + if (__DEV__) { + assert$1(this._mounted); + } + + brushOptionList = map(brushOptionList, function (brushOption) { + return merge(clone(DEFAULT_BRUSH_OPT), brushOption, true); + }); + + var tmpIdPrefix = '\0-brush-index-'; + var oldCovers = this._covers; + var newCovers = this._covers = []; + var controller = this; + var creatingCover = this._creatingCover; + + (new DataDiffer(oldCovers, brushOptionList, oldGetKey, getKey)) + .add(addOrUpdate) + .update(addOrUpdate) + .remove(remove) + .execute(); + + return this; + + function getKey(brushOption, index) { + return (brushOption.id != null ? brushOption.id : tmpIdPrefix + index) + + '-' + brushOption.brushType; + } + + function oldGetKey(cover, index) { + return getKey(cover.__brushOption, index); + } + + function addOrUpdate(newIndex, oldIndex) { + var newBrushOption = brushOptionList[newIndex]; + // Consider setOption in event listener of brushSelect, + // where updating cover when creating should be forbiden. + if (oldIndex != null && oldCovers[oldIndex] === creatingCover) { + newCovers[newIndex] = oldCovers[oldIndex]; + } + else { + var cover = newCovers[newIndex] = oldIndex != null + ? ( + oldCovers[oldIndex].__brushOption = newBrushOption, + oldCovers[oldIndex] + ) + : endCreating(controller, createCover(controller, newBrushOption)); + updateCoverAfterCreation(controller, cover); + } + } + + function remove(oldIndex) { + if (oldCovers[oldIndex] !== creatingCover) { + controller.group.remove(oldCovers[oldIndex]); + } + } + }, + + unmount: function () { + if (__DEV__) { + if (!this._mounted) { + return; + } + } + + this.enableBrush(false); + + // container may 'removeAll' outside. + clearCovers(this); + this._zr.remove(this.group); + + if (__DEV__) { + this._mounted = false; // should be at last. + } + + return this; + }, + + dispose: function () { + this.unmount(); + this.off(); + } +}; + +mixin(BrushController, Eventful); + +function doEnableBrush(controller, brushOption) { + var zr = controller._zr; + + // Consider roam, which takes globalPan too. + if (!controller._enableGlobalPan) { + take(zr, MUTEX_RESOURCE_KEY, controller._uid); + } + + mountHandlers(zr, controller._handlers); + + controller._brushType = brushOption.brushType; + controller._brushOption = merge(clone(DEFAULT_BRUSH_OPT), brushOption, true); +} + +function doDisableBrush(controller) { + var zr = controller._zr; + + release(zr, MUTEX_RESOURCE_KEY, controller._uid); + + unmountHandlers(zr, controller._handlers); + + controller._brushType = controller._brushOption = null; +} + +function mountHandlers(zr, handlers) { + each$12(handlers, function (handler, eventName) { + zr.on(eventName, handler); + }); +} + +function unmountHandlers(zr, handlers) { + each$12(handlers, function (handler, eventName) { + zr.off(eventName, handler); + }); +} + +function createCover(controller, brushOption) { + var cover = coverRenderers[brushOption.brushType].createCover(controller, brushOption); + cover.__brushOption = brushOption; + updateZ$1(cover, brushOption); + controller.group.add(cover); + return cover; +} + +function endCreating(controller, creatingCover) { + var coverRenderer = getCoverRenderer(creatingCover); + if (coverRenderer.endCreating) { + coverRenderer.endCreating(controller, creatingCover); + updateZ$1(creatingCover, creatingCover.__brushOption); + } + return creatingCover; +} + +function updateCoverShape(controller, cover) { + var brushOption = cover.__brushOption; + getCoverRenderer(cover).updateCoverShape( + controller, cover, brushOption.range, brushOption + ); +} + +function updateZ$1(cover, brushOption) { + var z = brushOption.z; + z == null && (z = COVER_Z); + cover.traverse(function (el) { + el.z = z; + el.z2 = z; // Consider in given container. + }); +} + +function updateCoverAfterCreation(controller, cover) { + getCoverRenderer(cover).updateCommon(controller, cover); + updateCoverShape(controller, cover); +} + +function getCoverRenderer(cover) { + return coverRenderers[cover.__brushOption.brushType]; +} + +// return target panel or `true` (means global panel) +function getPanelByPoint(controller, e, localCursorPoint) { + var panels = controller._panels; + if (!panels) { + return true; // Global panel + } + var panel; + var transform = controller._transform; + each$12(panels, function (pn) { + pn.isTargetByCursor(e, localCursorPoint, transform) && (panel = pn); + }); + return panel; +} + +// Return a panel or true +function getPanelByCover(controller, cover) { + var panels = controller._panels; + if (!panels) { + return true; // Global panel + } + var panelId = cover.__brushOption.panelId; + // User may give cover without coord sys info, + // which is then treated as global panel. + return panelId != null ? panels[panelId] : true; +} + +function clearCovers(controller) { + var covers = controller._covers; + var originalLength = covers.length; + each$12(covers, function (cover) { + controller.group.remove(cover); + }, controller); + covers.length = 0; + + return !!originalLength; +} + +function trigger$1(controller, opt) { + var areas = map$2(controller._covers, function (cover) { + var brushOption = cover.__brushOption; + var range = clone(brushOption.range); + return { + brushType: brushOption.brushType, + panelId: brushOption.panelId, + range: range + }; + }); + + controller.trigger('brush', areas, { + isEnd: !!opt.isEnd, + removeOnClick: !!opt.removeOnClick + }); +} + +function shouldShowCover(controller) { + var track = controller._track; + + if (!track.length) { + return false; + } + + var p2 = track[track.length - 1]; + var p1 = track[0]; + var dx = p2[0] - p1[0]; + var dy = p2[1] - p1[1]; + var dist = mathPow$2(dx * dx + dy * dy, 0.5); + + return dist > UNSELECT_THRESHOLD; +} + +function getTrackEnds(track) { + var tail = track.length - 1; + tail < 0 && (tail = 0); + return [track[0], track[tail]]; +} + +function createBaseRectCover(doDrift, controller, brushOption, edgeNames) { + var cover = new Group(); + + cover.add(new Rect({ + name: 'main', + style: makeStyle(brushOption), + silent: true, + draggable: true, + cursor: 'move', + drift: curry$2(doDrift, controller, cover, 'nswe'), + ondragend: curry$2(trigger$1, controller, {isEnd: true}) + })); + + each$12( + edgeNames, + function (name) { + cover.add(new Rect({ + name: name, + style: {opacity: 0}, + draggable: true, + silent: true, + invisible: true, + drift: curry$2(doDrift, controller, cover, name), + ondragend: curry$2(trigger$1, controller, {isEnd: true}) + })); + } + ); + + return cover; +} + +function updateBaseRect(controller, cover, localRange, brushOption) { + var lineWidth = brushOption.brushStyle.lineWidth || 0; + var handleSize = mathMax$7(lineWidth, MIN_RESIZE_LINE_WIDTH); + var x = localRange[0][0]; + var y = localRange[1][0]; + var xa = x - lineWidth / 2; + var ya = y - lineWidth / 2; + var x2 = localRange[0][1]; + var y2 = localRange[1][1]; + var x2a = x2 - handleSize + lineWidth / 2; + var y2a = y2 - handleSize + lineWidth / 2; + var width = x2 - x; + var height = y2 - y; + var widtha = width + lineWidth; + var heighta = height + lineWidth; + + updateRectShape(controller, cover, 'main', x, y, width, height); + + if (brushOption.transformable) { + updateRectShape(controller, cover, 'w', xa, ya, handleSize, heighta); + updateRectShape(controller, cover, 'e', x2a, ya, handleSize, heighta); + updateRectShape(controller, cover, 'n', xa, ya, widtha, handleSize); + updateRectShape(controller, cover, 's', xa, y2a, widtha, handleSize); + + updateRectShape(controller, cover, 'nw', xa, ya, handleSize, handleSize); + updateRectShape(controller, cover, 'ne', x2a, ya, handleSize, handleSize); + updateRectShape(controller, cover, 'sw', xa, y2a, handleSize, handleSize); + updateRectShape(controller, cover, 'se', x2a, y2a, handleSize, handleSize); + } +} + +function updateCommon(controller, cover) { + var brushOption = cover.__brushOption; + var transformable = brushOption.transformable; + + var mainEl = cover.childAt(0); + mainEl.useStyle(makeStyle(brushOption)); + mainEl.attr({ + silent: !transformable, + cursor: transformable ? 'move' : 'default' + }); + + each$12( + ['w', 'e', 'n', 's', 'se', 'sw', 'ne', 'nw'], + function (name) { + var el = cover.childOfName(name); + var globalDir = getGlobalDirection(controller, name); + + el && el.attr({ + silent: !transformable, + invisible: !transformable, + cursor: transformable ? CURSOR_MAP[globalDir] + '-resize' : null + }); + } + ); +} + +function updateRectShape(controller, cover, name, x, y, w, h) { + var el = cover.childOfName(name); + el && el.setShape(pointsToRect( + clipByPanel(controller, cover, [[x, y], [x + w, y + h]]) + )); +} + +function makeStyle(brushOption) { + return defaults({strokeNoScale: true}, brushOption.brushStyle); +} + +function formatRectRange(x, y, x2, y2) { + var min = [mathMin$7(x, x2), mathMin$7(y, y2)]; + var max = [mathMax$7(x, x2), mathMax$7(y, y2)]; + + return [ + [min[0], max[0]], // x range + [min[1], max[1]] // y range + ]; +} + +function getTransform$1(controller) { + return getTransform(controller.group); +} + +function getGlobalDirection(controller, localDirection) { + if (localDirection.length > 1) { + localDirection = localDirection.split(''); + var globalDir = [ + getGlobalDirection(controller, localDirection[0]), + getGlobalDirection(controller, localDirection[1]) + ]; + (globalDir[0] === 'e' || globalDir[0] === 'w') && globalDir.reverse(); + return globalDir.join(''); + } + else { + var map$$1 = {w: 'left', e: 'right', n: 'top', s: 'bottom'}; + var inverseMap = {left: 'w', right: 'e', top: 'n', bottom: 's'}; + var globalDir = transformDirection( + map$$1[localDirection], getTransform$1(controller) + ); + return inverseMap[globalDir]; + } +} + +function driftRect(toRectRange, fromRectRange, controller, cover, name, dx, dy, e) { + var brushOption = cover.__brushOption; + var rectRange = toRectRange(brushOption.range); + var localDelta = toLocalDelta(controller, dx, dy); + + each$12(name.split(''), function (namePart) { + var ind = DIRECTION_MAP[namePart]; + rectRange[ind[0]][ind[1]] += localDelta[ind[0]]; + }); + + brushOption.range = fromRectRange(formatRectRange( + rectRange[0][0], rectRange[1][0], rectRange[0][1], rectRange[1][1] + )); + + updateCoverAfterCreation(controller, cover); + trigger$1(controller, {isEnd: false}); +} + +function driftPolygon(controller, cover, dx, dy, e) { + var range = cover.__brushOption.range; + var localDelta = toLocalDelta(controller, dx, dy); + + each$12(range, function (point) { + point[0] += localDelta[0]; + point[1] += localDelta[1]; + }); + + updateCoverAfterCreation(controller, cover); + trigger$1(controller, {isEnd: false}); +} + +function toLocalDelta(controller, dx, dy) { + var thisGroup = controller.group; + var localD = thisGroup.transformCoordToLocal(dx, dy); + var localZero = thisGroup.transformCoordToLocal(0, 0); + + return [localD[0] - localZero[0], localD[1] - localZero[1]]; +} + +function clipByPanel(controller, cover, data) { + var panel = getPanelByCover(controller, cover); + + return (panel && panel !== true) + ? panel.clipPath(data, controller._transform) + : clone(data); +} + +function pointsToRect(points) { + var xmin = mathMin$7(points[0][0], points[1][0]); + var ymin = mathMin$7(points[0][1], points[1][1]); + var xmax = mathMax$7(points[0][0], points[1][0]); + var ymax = mathMax$7(points[0][1], points[1][1]); + + return { + x: xmin, + y: ymin, + width: xmax - xmin, + height: ymax - ymin + }; +} + +function resetCursor(controller, e, localCursorPoint) { + if ( + // Check active + !controller._brushType + // resetCursor should be always called when mouse is in zr area, + // but not called when mouse is out of zr area to avoid bad influence + // if `mousemove`, `mouseup` are triggered from `document` event. + || isOutsideZrArea(controller, e) + ) { + return; + } + + var zr = controller._zr; + var covers = controller._covers; + var currPanel = getPanelByPoint(controller, e, localCursorPoint); + + // Check whether in covers. + if (!controller._dragging) { + for (var i = 0; i < covers.length; i++) { + var brushOption = covers[i].__brushOption; + if (currPanel + && (currPanel === true || brushOption.panelId === currPanel.panelId) + && coverRenderers[brushOption.brushType].contain( + covers[i], localCursorPoint[0], localCursorPoint[1] + ) + ) { + // Use cursor style set on cover. + return; + } + } + } + + currPanel && zr.setCursorStyle('crosshair'); +} + +function preventDefault(e) { + var rawE = e.event; + rawE.preventDefault && rawE.preventDefault(); +} + +function mainShapeContain(cover, x, y) { + return cover.childOfName('main').contain(x, y); +} + +function updateCoverByMouse(controller, e, localCursorPoint, isEnd) { + var creatingCover = controller._creatingCover; + var panel = controller._creatingPanel; + var thisBrushOption = controller._brushOption; + var eventParams; + + controller._track.push(localCursorPoint.slice()); + + if (shouldShowCover(controller) || creatingCover) { + + if (panel && !creatingCover) { + thisBrushOption.brushMode === 'single' && clearCovers(controller); + var brushOption = clone(thisBrushOption); + brushOption.brushType = determineBrushType(brushOption.brushType, panel); + brushOption.panelId = panel === true ? null : panel.panelId; + creatingCover = controller._creatingCover = createCover(controller, brushOption); + controller._covers.push(creatingCover); + } + + if (creatingCover) { + var coverRenderer = coverRenderers[determineBrushType(controller._brushType, panel)]; + var coverBrushOption = creatingCover.__brushOption; + + coverBrushOption.range = coverRenderer.getCreatingRange( + clipByPanel(controller, creatingCover, controller._track) + ); + + if (isEnd) { + endCreating(controller, creatingCover); + coverRenderer.updateCommon(controller, creatingCover); + } + + updateCoverShape(controller, creatingCover); + + eventParams = {isEnd: isEnd}; + } + } + else if ( + isEnd + && thisBrushOption.brushMode === 'single' + && thisBrushOption.removeOnClick + ) { + // Help user to remove covers easily, only by a tiny drag, in 'single' mode. + // But a single click do not clear covers, because user may have casual + // clicks (for example, click on other component and do not expect covers + // disappear). + // Only some cover removed, trigger action, but not every click trigger action. + if (getPanelByPoint(controller, e, localCursorPoint) && clearCovers(controller)) { + eventParams = {isEnd: isEnd, removeOnClick: true}; + } + } + + return eventParams; +} + +function determineBrushType(brushType, panel) { + if (brushType === 'auto') { + if (__DEV__) { + assert$1( + panel && panel.defaultBrushType, + 'MUST have defaultBrushType when brushType is "atuo"' + ); + } + return panel.defaultBrushType; + } + return brushType; +} + +var pointerHandlers = { + + mousedown: function (e) { + if (this._dragging) { + // In case some browser do not support globalOut, + // and release mose out side the browser. + handleDragEnd(this, e); + } + else if (!e.target || !e.target.draggable) { + + preventDefault(e); + + var localCursorPoint = this.group.transformCoordToLocal(e.offsetX, e.offsetY); + + this._creatingCover = null; + var panel = this._creatingPanel = getPanelByPoint(this, e, localCursorPoint); + + if (panel) { + this._dragging = true; + this._track = [localCursorPoint.slice()]; + } + } + }, + + mousemove: function (e) { + var x = e.offsetX; + var y = e.offsetY; + + var localCursorPoint = this.group.transformCoordToLocal(x, y); + + resetCursor(this, e, localCursorPoint); + + if (this._dragging) { + preventDefault(e); + var eventParams = updateCoverByMouse(this, e, localCursorPoint, false); + eventParams && trigger$1(this, eventParams); + } + }, + + mouseup: function (e) { + handleDragEnd(this, e); + } +}; + + +function handleDragEnd(controller, e) { + if (controller._dragging) { + preventDefault(e); + + var x = e.offsetX; + var y = e.offsetY; + + var localCursorPoint = controller.group.transformCoordToLocal(x, y); + var eventParams = updateCoverByMouse(controller, e, localCursorPoint, true); + + controller._dragging = false; + controller._track = []; + controller._creatingCover = null; + + // trigger event shoule be at final, after procedure will be nested. + eventParams && trigger$1(controller, eventParams); + } +} + +function isOutsideZrArea(controller, x, y) { + var zr = controller._zr; + return x < 0 || x > zr.getWidth() || y < 0 || y > zr.getHeight(); +} + + +/** + * key: brushType + * @type {Object} + */ +var coverRenderers = { + + lineX: getLineRenderer(0), + + lineY: getLineRenderer(1), + + rect: { + createCover: function (controller, brushOption) { + return createBaseRectCover( + curry$2( + driftRect, + function (range) { + return range; + }, + function (range) { + return range; + } + ), + controller, + brushOption, + ['w', 'e', 'n', 's', 'se', 'sw', 'ne', 'nw'] + ); + }, + getCreatingRange: function (localTrack) { + var ends = getTrackEnds(localTrack); + return formatRectRange(ends[1][0], ends[1][1], ends[0][0], ends[0][1]); + }, + updateCoverShape: function (controller, cover, localRange, brushOption) { + updateBaseRect(controller, cover, localRange, brushOption); + }, + updateCommon: updateCommon, + contain: mainShapeContain + }, + + polygon: { + createCover: function (controller, brushOption) { + var cover = new Group(); + + // Do not use graphic.Polygon because graphic.Polyline do not close the + // border of the shape when drawing, which is a better experience for user. + cover.add(new Polyline({ + name: 'main', + style: makeStyle(brushOption), + silent: true + })); + + return cover; + }, + getCreatingRange: function (localTrack) { + return localTrack; + }, + endCreating: function (controller, cover) { + cover.remove(cover.childAt(0)); + // Use graphic.Polygon close the shape. + cover.add(new Polygon({ + name: 'main', + draggable: true, + drift: curry$2(driftPolygon, controller, cover), + ondragend: curry$2(trigger$1, controller, {isEnd: true}) + })); + }, + updateCoverShape: function (controller, cover, localRange, brushOption) { + cover.childAt(0).setShape({ + points: clipByPanel(controller, cover, localRange) + }); + }, + updateCommon: updateCommon, + contain: mainShapeContain + } +}; + +function getLineRenderer(xyIndex) { + return { + createCover: function (controller, brushOption) { + return createBaseRectCover( + curry$2( + driftRect, + function (range) { + var rectRange = [range, [0, 100]]; + xyIndex && rectRange.reverse(); + return rectRange; + }, + function (rectRange) { + return rectRange[xyIndex]; + } + ), + controller, + brushOption, + [['w', 'e'], ['n', 's']][xyIndex] + ); + }, + getCreatingRange: function (localTrack) { + var ends = getTrackEnds(localTrack); + var min = mathMin$7(ends[0][xyIndex], ends[1][xyIndex]); + var max = mathMax$7(ends[0][xyIndex], ends[1][xyIndex]); + + return [min, max]; + }, + updateCoverShape: function (controller, cover, localRange, brushOption) { + var otherExtent; + // If brushWidth not specified, fit the panel. + var panel = getPanelByCover(controller, cover); + if (panel !== true && panel.getLinearBrushOtherExtent) { + otherExtent = panel.getLinearBrushOtherExtent( + xyIndex, controller._transform + ); + } + else { + var zr = controller._zr; + otherExtent = [0, [zr.getWidth(), zr.getHeight()][1 - xyIndex]]; + } + var rectRange = [localRange, otherExtent]; + xyIndex && rectRange.reverse(); + + updateBaseRect(controller, cover, rectRange, brushOption); + }, + updateCommon: updateCommon, + contain: mainShapeContain + }; +} + +/* +* 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. +*/ + +function makeRectPanelClipPath(rect) { + rect = normalizeRect(rect); + return function (localPoints, transform) { + return clipPointsByRect(localPoints, rect); + }; +} + +function makeLinearBrushOtherExtent(rect, specifiedXYIndex) { + rect = normalizeRect(rect); + return function (xyIndex) { + var idx = specifiedXYIndex != null ? specifiedXYIndex : xyIndex; + var brushWidth = idx ? rect.width : rect.height; + var base = idx ? rect.x : rect.y; + return [base, base + (brushWidth || 0)]; + }; +} + +function makeRectIsTargetByCursor(rect, api, targetModel) { + rect = normalizeRect(rect); + return function (e, localCursorPoint, transform) { + return rect.contain(localCursorPoint[0], localCursorPoint[1]) + && !onIrrelevantElement(e, api, targetModel); + }; +} + +// Consider width/height is negative. +function normalizeRect(rect) { + return BoundingRect.create(rect); +} + +/* +* 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. +*/ + +var elementList = ['axisLine', 'axisTickLabel', 'axisName']; + +var AxisView$2 = extendComponentView({ + + type: 'parallelAxis', + + /** + * @override + */ + init: function (ecModel, api) { + AxisView$2.superApply(this, 'init', arguments); + + /** + * @type {module:echarts/component/helper/BrushController} + */ + (this._brushController = new BrushController(api.getZr())) + .on('brush', bind(this._onBrush, this)); + }, + + /** + * @override + */ + render: function (axisModel, ecModel, api, payload) { + if (fromAxisAreaSelect(axisModel, ecModel, payload)) { + return; + } + + this.axisModel = axisModel; + this.api = api; + + this.group.removeAll(); + + var oldAxisGroup = this._axisGroup; + this._axisGroup = new Group(); + this.group.add(this._axisGroup); + + if (!axisModel.get('show')) { + return; + } + + var coordSysModel = getCoordSysModel(axisModel, ecModel); + var coordSys = coordSysModel.coordinateSystem; + + var areaSelectStyle = axisModel.getAreaSelectStyle(); + var areaWidth = areaSelectStyle.width; + + var dim = axisModel.axis.dim; + var axisLayout = coordSys.getAxisLayout(dim); + + var builderOpt = extend( + {strokeContainThreshold: areaWidth}, + axisLayout + ); + + var axisBuilder = new AxisBuilder(axisModel, builderOpt); + + each$1(elementList, axisBuilder.add, axisBuilder); + + this._axisGroup.add(axisBuilder.getGroup()); + + this._refreshBrushController( + builderOpt, areaSelectStyle, axisModel, coordSysModel, areaWidth, api + ); + + var animationModel = (payload && payload.animation === false) ? null : axisModel; + groupTransition(oldAxisGroup, this._axisGroup, animationModel); + }, + + // /** + // * @override + // */ + // updateVisual: function (axisModel, ecModel, api, payload) { + // this._brushController && this._brushController + // .updateCovers(getCoverInfoList(axisModel)); + // }, + + _refreshBrushController: function ( + builderOpt, areaSelectStyle, axisModel, coordSysModel, areaWidth, api + ) { + // After filtering, axis may change, select area needs to be update. + var extent = axisModel.axis.getExtent(); + var extentLen = extent[1] - extent[0]; + var extra = Math.min(30, Math.abs(extentLen) * 0.1); // Arbitrary value. + + // width/height might be negative, which will be + // normalized in BoundingRect. + var rect = BoundingRect.create({ + x: extent[0], + y: -areaWidth / 2, + width: extentLen, + height: areaWidth + }); + rect.x -= extra; + rect.width += 2 * extra; + + this._brushController + .mount({ + enableGlobalPan: true, + rotation: builderOpt.rotation, + position: builderOpt.position + }) + .setPanels([{ + panelId: 'pl', + clipPath: makeRectPanelClipPath(rect), + isTargetByCursor: makeRectIsTargetByCursor(rect, api, coordSysModel), + getLinearBrushOtherExtent: makeLinearBrushOtherExtent(rect, 0) + }]) + .enableBrush({ + brushType: 'lineX', + brushStyle: areaSelectStyle, + removeOnClick: true + }) + .updateCovers(getCoverInfoList(axisModel)); + }, + + _onBrush: function (coverInfoList, opt) { + // Do not cache these object, because the mey be changed. + var axisModel = this.axisModel; + var axis = axisModel.axis; + var intervals = map(coverInfoList, function (coverInfo) { + return [ + axis.coordToData(coverInfo.range[0], true), + axis.coordToData(coverInfo.range[1], true) + ]; + }); + + // If realtime is true, action is not dispatched on drag end, because + // the drag end emits the same params with the last drag move event, + // and may have some delay when using touch pad. + if (!axisModel.option.realtime === opt.isEnd || opt.removeOnClick) { // jshint ignore:line + this.api.dispatchAction({ + type: 'axisAreaSelect', + parallelAxisId: axisModel.id, + intervals: intervals + }); + } + }, + + /** + * @override + */ + dispose: function () { + this._brushController.dispose(); + } +}); + +function fromAxisAreaSelect(axisModel, ecModel, payload) { + return payload + && payload.type === 'axisAreaSelect' + && ecModel.findComponents( + {mainType: 'parallelAxis', query: payload} + )[0] === axisModel; +} + +function getCoverInfoList(axisModel) { + var axis = axisModel.axis; + return map(axisModel.activeIntervals, function (interval) { + return { + brushType: 'lineX', + panelId: 'pl', + range: [ + axis.dataToCoord(interval[0], true), + axis.dataToCoord(interval[1], true) + ] + }; + }); +} + +function getCoordSysModel(axisModel, ecModel) { + return ecModel.getComponent( + 'parallel', axisModel.get('parallelIndex') + ); +} + +/* +* 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. +*/ + +/* +* 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. +*/ + +var CLICK_THRESHOLD = 5; // > 4 + +// Parallel view +extendComponentView({ + type: 'parallel', + + render: function (parallelModel, ecModel, api) { + this._model = parallelModel; + this._api = api; + + if (!this._handlers) { + this._handlers = {}; + each$1(handlers, function (handler, eventName) { + api.getZr().on(eventName, this._handlers[eventName] = bind(handler, this)); + }, this); + } + + createOrUpdate( + this, + '_throttledDispatchExpand', + parallelModel.get('axisExpandRate'), + 'fixRate' + ); + }, + + dispose: function (ecModel, api) { + each$1(this._handlers, function (handler, eventName) { + api.getZr().off(eventName, handler); + }); + this._handlers = null; + }, + + /** + * @param {Object} [opt] If null, cancle the last action triggering for debounce. + */ + _throttledDispatchExpand: function (opt) { + this._dispatchExpand(opt); + }, + + _dispatchExpand: function (opt) { + opt && this._api.dispatchAction( + extend({type: 'parallelAxisExpand'}, opt) + ); + } + +}); + +var handlers = { + + mousedown: function (e) { + if (checkTrigger(this, 'click')) { + this._mouseDownPoint = [e.offsetX, e.offsetY]; + } + }, + + mouseup: function (e) { + var mouseDownPoint = this._mouseDownPoint; + + if (checkTrigger(this, 'click') && mouseDownPoint) { + var point = [e.offsetX, e.offsetY]; + var dist = Math.pow(mouseDownPoint[0] - point[0], 2) + + Math.pow(mouseDownPoint[1] - point[1], 2); + + if (dist > CLICK_THRESHOLD) { + return; + } + + var result = this._model.coordinateSystem.getSlidedAxisExpandWindow( + [e.offsetX, e.offsetY] + ); + + result.behavior !== 'none' && this._dispatchExpand({ + axisExpandWindow: result.axisExpandWindow + }); + } + + this._mouseDownPoint = null; + }, + + mousemove: function (e) { + // Should do nothing when brushing. + if (this._mouseDownPoint || !checkTrigger(this, 'mousemove')) { + return; + } + var model = this._model; + var result = model.coordinateSystem.getSlidedAxisExpandWindow( + [e.offsetX, e.offsetY] + ); + + var behavior = result.behavior; + behavior === 'jump' && this._throttledDispatchExpand.debounceNextCall(model.get('axisExpandDebounce')); + this._throttledDispatchExpand( + behavior === 'none' + ? null // Cancle the last trigger, in case that mouse slide out of the area quickly. + : { + axisExpandWindow: result.axisExpandWindow, + // Jumping uses animation, and sliding suppresses animation. + animation: behavior === 'jump' ? null : false + } + ); + } +}; + +function checkTrigger(view, triggerOn) { + var model = view._model; + return model.get('axisExpandable') && model.get('axisExpandTriggerOn') === triggerOn; +} + +registerPreprocessor(parallelPreprocessor); + +/* +* 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. +*/ + +SeriesModel.extend({ + + type: 'series.parallel', + + dependencies: ['parallel'], + + visualColorAccessPath: 'lineStyle.color', + + getInitialData: function (option, ecModel) { + var source = this.getSource(); + + setEncodeAndDimensions(source, this); + + return createListFromArray(source, this); + }, + + /** + * User can get data raw indices on 'axisAreaSelected' event received. + * + * @public + * @param {string} activeState 'active' or 'inactive' or 'normal' + * @return {Array.} Raw indices + */ + getRawIndicesByActiveState: function (activeState) { + var coordSys = this.coordinateSystem; + var data = this.getData(); + var indices = []; + + coordSys.eachActiveState(data, function (theActiveState, dataIndex) { + if (activeState === theActiveState) { + indices.push(data.getRawIndex(dataIndex)); + } + }); + + return indices; + }, + + defaultOption: { + zlevel: 0, // 一级层叠 + z: 2, // 二级层叠 + + coordinateSystem: 'parallel', + parallelIndex: 0, + + label: { + show: false + }, + + inactiveOpacity: 0.05, + activeOpacity: 1, + + lineStyle: { + width: 1, + opacity: 0.45, + type: 'solid' + }, + emphasis: { + label: { + show: false + } + }, + + progressive: 500, + smooth: false, // true | false | number + + animationEasing: 'linear' + } +}); + +function setEncodeAndDimensions(source, seriesModel) { + // The mapping of parallelAxis dimension to data dimension can + // be specified in parallelAxis.option.dim. For example, if + // parallelAxis.option.dim is 'dim3', it mapping to the third + // dimension of data. But `data.encode` has higher priority. + // Moreover, parallelModel.dimension should not be regarded as data + // dimensions. Consider dimensions = ['dim4', 'dim2', 'dim6']; + + if (source.encodeDefine) { + return; + } + + var parallelModel = seriesModel.ecModel.getComponent( + 'parallel', seriesModel.get('parallelIndex') + ); + if (!parallelModel) { + return; + } + + var encodeDefine = source.encodeDefine = createHashMap(); + each$1(parallelModel.dimensions, function (axisDim) { + var dataDimIndex = convertDimNameToNumber(axisDim); + encodeDefine.set(axisDim, dataDimIndex); + }); +} + +function convertDimNameToNumber(dimName) { + return +dimName.replace('dim', ''); +} + +/* +* 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. +*/ + +var DEFAULT_SMOOTH = 0.3; + +var ParallelView = Chart.extend({ + + type: 'parallel', + + init: function () { + + /** + * @type {module:zrender/container/Group} + * @private + */ + this._dataGroup = new Group(); + + this.group.add(this._dataGroup); + + /** + * @type {module:echarts/data/List} + */ + this._data; + + /** + * @type {boolean} + */ + this._initialized; + }, + + /** + * @override + */ + render: function (seriesModel, ecModel, api, payload) { + var dataGroup = this._dataGroup; + var data = seriesModel.getData(); + var oldData = this._data; + var coordSys = seriesModel.coordinateSystem; + var dimensions = coordSys.dimensions; + var seriesScope = makeSeriesScope$2(seriesModel); + + data.diff(oldData) + .add(add) + .update(update) + .remove(remove) + .execute(); + + function add(newDataIndex) { + var line = addEl(data, dataGroup, newDataIndex, dimensions, coordSys); + updateElCommon(line, data, newDataIndex, seriesScope); + } + + function update(newDataIndex, oldDataIndex) { + var line = oldData.getItemGraphicEl(oldDataIndex); + var points = createLinePoints(data, newDataIndex, dimensions, coordSys); + data.setItemGraphicEl(newDataIndex, line); + var animationModel = (payload && payload.animation === false) ? null : seriesModel; + updateProps(line, {shape: {points: points}}, animationModel, newDataIndex); + + updateElCommon(line, data, newDataIndex, seriesScope); + } + + function remove(oldDataIndex) { + var line = oldData.getItemGraphicEl(oldDataIndex); + dataGroup.remove(line); + } + + // First create + if (!this._initialized) { + this._initialized = true; + var clipPath = createGridClipShape( + coordSys, seriesModel, function () { + // Callback will be invoked immediately if there is no animation + setTimeout(function () { + dataGroup.removeClipPath(); + }); + } + ); + dataGroup.setClipPath(clipPath); + } + + this._data = data; + }, + + incrementalPrepareRender: function (seriesModel, ecModel, api) { + this._initialized = true; + this._data = null; + this._dataGroup.removeAll(); + }, + + incrementalRender: function (taskParams, seriesModel, ecModel) { + var data = seriesModel.getData(); + var coordSys = seriesModel.coordinateSystem; + var dimensions = coordSys.dimensions; + var seriesScope = makeSeriesScope$2(seriesModel); + + for (var dataIndex = taskParams.start; dataIndex < taskParams.end; dataIndex++) { + var line = addEl(data, this._dataGroup, dataIndex, dimensions, coordSys); + line.incremental = true; + updateElCommon(line, data, dataIndex, seriesScope); + } + }, + + dispose: function () {}, + + // _renderForProgressive: function (seriesModel) { + // var dataGroup = this._dataGroup; + // var data = seriesModel.getData(); + // var oldData = this._data; + // var coordSys = seriesModel.coordinateSystem; + // var dimensions = coordSys.dimensions; + // var option = seriesModel.option; + // var progressive = option.progressive; + // var smooth = option.smooth ? SMOOTH : null; + + // // In progressive animation is disabled, so use simple data diff, + // // which effects performance less. + // // (Typically performance for data with length 7000+ like: + // // simpleDiff: 60ms, addEl: 184ms, + // // in RMBP 2.4GHz intel i7, OSX 10.9 chrome 50.0.2661.102 (64-bit)) + // if (simpleDiff(oldData, data, dimensions)) { + // dataGroup.removeAll(); + // data.each(function (dataIndex) { + // addEl(data, dataGroup, dataIndex, dimensions, coordSys); + // }); + // } + + // updateElCommon(data, progressive, smooth); + + // // Consider switch between progressive and not. + // data.__plProgressive = true; + // this._data = data; + // }, + + /** + * @override + */ + remove: function () { + this._dataGroup && this._dataGroup.removeAll(); + this._data = null; + } +}); + +function createGridClipShape(coordSys, seriesModel, cb) { + var parallelModel = coordSys.model; + var rect = coordSys.getRect(); + var rectEl = new Rect({ + shape: { + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + } + }); + + var dim = parallelModel.get('layout') === 'horizontal' ? 'width' : 'height'; + rectEl.setShape(dim, 0); + initProps(rectEl, { + shape: { + width: rect.width, + height: rect.height + } + }, seriesModel, cb); + return rectEl; +} + +function createLinePoints(data, dataIndex, dimensions, coordSys) { + var points = []; + for (var i = 0; i < dimensions.length; i++) { + var dimName = dimensions[i]; + var value = data.get(data.mapDimension(dimName), dataIndex); + if (!isEmptyValue(value, coordSys.getAxis(dimName).type)) { + points.push(coordSys.dataToPoint(value, dimName)); + } + } + return points; +} + +function addEl(data, dataGroup, dataIndex, dimensions, coordSys) { + var points = createLinePoints(data, dataIndex, dimensions, coordSys); + var line = new Polyline({ + shape: {points: points}, + silent: true, + z2: 10 + }); + dataGroup.add(line); + data.setItemGraphicEl(dataIndex, line); + return line; +} + +function makeSeriesScope$2(seriesModel) { + var smooth = seriesModel.get('smooth', true); + smooth === true && (smooth = DEFAULT_SMOOTH); + return { + lineStyle: seriesModel.getModel('lineStyle').getLineStyle(), + smooth: smooth != null ? smooth : DEFAULT_SMOOTH + }; +} + +function updateElCommon(el, data, dataIndex, seriesScope) { + var lineStyle = seriesScope.lineStyle; + + if (data.hasItemOption) { + var lineStyleModel = data.getItemModel(dataIndex).getModel('lineStyle'); + lineStyle = lineStyleModel.getLineStyle(); + } + + el.useStyle(lineStyle); + + var elStyle = el.style; + elStyle.fill = null; + // lineStyle.color have been set to itemVisual in module:echarts/visual/seriesColor. + elStyle.stroke = data.getItemVisual(dataIndex, 'color'); + // lineStyle.opacity have been set to itemVisual in parallelVisual. + elStyle.opacity = data.getItemVisual(dataIndex, 'opacity'); + + seriesScope.smooth && (el.shape.smooth = seriesScope.smooth); +} + +// function simpleDiff(oldData, newData, dimensions) { +// var oldLen; +// if (!oldData +// || !oldData.__plProgressive +// || (oldLen = oldData.count()) !== newData.count() +// ) { +// return true; +// } + +// var dimLen = dimensions.length; +// for (var i = 0; i < oldLen; i++) { +// for (var j = 0; j < dimLen; j++) { +// if (oldData.get(dimensions[j], i) !== newData.get(dimensions[j], i)) { +// return true; +// } +// } +// } + +// return false; +// } + +// FIXME +// 公用方法? +function isEmptyValue(val, axisType) { + return axisType === 'category' + ? val == null + : (val == null || isNaN(val)); // axisType === 'value' +} + +/* +* 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. +*/ + + +var opacityAccessPath$1 = ['lineStyle', 'normal', 'opacity']; + +var parallelVisual = { + + seriesType: 'parallel', + + reset: function (seriesModel, ecModel, api) { + + var itemStyleModel = seriesModel.getModel('itemStyle'); + var lineStyleModel = seriesModel.getModel('lineStyle'); + var globalColors = ecModel.get('color'); + + var color = lineStyleModel.get('color') + || itemStyleModel.get('color') + || globalColors[seriesModel.seriesIndex % globalColors.length]; + var inactiveOpacity = seriesModel.get('inactiveOpacity'); + var activeOpacity = seriesModel.get('activeOpacity'); + var lineStyle = seriesModel.getModel('lineStyle').getLineStyle(); + + var coordSys = seriesModel.coordinateSystem; + var data = seriesModel.getData(); + + var opacityMap = { + normal: lineStyle.opacity, + active: activeOpacity, + inactive: inactiveOpacity + }; + + data.setVisual('color', color); + + function progress(params, data) { + coordSys.eachActiveState(data, function (activeState, dataIndex) { + var opacity = opacityMap[activeState]; + if (activeState === 'normal' && data.hasItemOption) { + var itemOpacity = data.getItemModel(dataIndex).get(opacityAccessPath$1, true); + itemOpacity != null && (opacity = itemOpacity); + } + data.setItemVisual(dataIndex, 'opacity', opacity); + }, params.start, params.end); + } + + return {progress: progress}; + } +}; + +/* +* 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. +*/ + +registerVisual(parallelVisual); + +/* +* 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. +*/ + +var SankeySeries = SeriesModel.extend({ + + type: 'series.sankey', + + layoutInfo: null, + + levelModels: null, + + /** + * Init a graph data structure from data in option series + * + * @param {Object} option the object used to config echarts view + * @return {module:echarts/data/List} storage initial data + */ + getInitialData: function (option, ecModel) { + var links = option.edges || option.links; + var nodes = option.data || option.nodes; + var levels = option.levels; + var levelModels = this.levelModels = {}; + + for (var i = 0; i < levels.length; i++) { + if (levels[i].depth != null && levels[i].depth >= 0) { + levelModels[levels[i].depth] = new Model(levels[i], this, ecModel); + } + else { + if (__DEV__) { + throw new Error('levels[i].depth is mandatory and should be natural number'); + } + } + } + if (nodes && links) { + var graph = createGraphFromNodeEdge(nodes, links, this, true, beforeLink); + return graph.data; + } + function beforeLink(nodeData, edgeData) { + nodeData.wrapMethod('getItemModel', function (model, idx) { + model.customizeGetParent(function (path) { + var parentModel = this.parentModel; + var nodeDepth = parentModel.getData().getItemLayout(idx).depth; + var levelModel = parentModel.levelModels[nodeDepth]; + return levelModel || this.parentModel; + }); + return model; + }); + + edgeData.wrapMethod('getItemModel', function (model, idx) { + model.customizeGetParent(function (path) { + var parentModel = this.parentModel; + var edge = parentModel.getGraph().getEdgeByIndex(idx); + var depth = edge.node1.getLayout().depth; + var levelModel = parentModel.levelModels[depth]; + return levelModel || this.parentModel; + }); + return model; + }); + } + }, + + setNodePosition: function (dataIndex, localPosition) { + var dataItem = this.option.data[dataIndex]; + dataItem.localX = localPosition[0]; + dataItem.localY = localPosition[1]; + }, + + /** + * Return the graphic data structure + * + * @return {module:echarts/data/Graph} graphic data structure + */ + getGraph: function () { + return this.getData().graph; + }, + + /** + * Get edge data of graphic data structure + * + * @return {module:echarts/data/List} data structure of list + */ + getEdgeData: function () { + return this.getGraph().edgeData; + }, + + /** + * @override + */ + formatTooltip: function (dataIndex, multipleSeries, dataType) { + // dataType === 'node' or empty do not show tooltip by default + if (dataType === 'edge') { + var params = this.getDataParams(dataIndex, dataType); + var rawDataOpt = params.data; + var html = rawDataOpt.source + ' -- ' + rawDataOpt.target; + if (params.value) { + html += ' : ' + params.value; + } + return encodeHTML(html); + } + else if (dataType === 'node') { + var node = this.getGraph().getNodeByIndex(dataIndex); + var value = node.getLayout().value; + var name = this.getDataParams(dataIndex, dataType).data.name; + if (value) { + var html = name + ' : ' + value; + } + return encodeHTML(html); + } + return SankeySeries.superCall(this, 'formatTooltip', dataIndex, multipleSeries); + }, + + optionUpdated: function () { + var option = this.option; + if (option.focusNodeAdjacency === true) { + option.focusNodeAdjacency = 'allEdges'; + } + }, + + defaultOption: { + zlevel: 0, + z: 2, + + coordinateSystem: 'view', + + layout: null, + + // The position of the whole view + left: '5%', + top: '5%', + right: '20%', + bottom: '5%', + + // Value can be 'vertical' + orient: 'horizontal', + + // The dx of the node + nodeWidth: 20, + + // The vertical distance between two nodes + nodeGap: 8, + + // Control if the node can move or not + draggable: true, + + // Value can be 'inEdges', 'outEdges', 'allEdges', true (the same as 'allEdges'). + focusNodeAdjacency: false, + + // The number of iterations to change the position of the node + layoutIterations: 32, + + label: { + show: true, + position: 'right', + color: '#000', + fontSize: 12 + }, + + levels: [], + + // Value can be 'left' or 'right' + nodeAlign: 'justify', + + itemStyle: { + borderWidth: 1, + borderColor: '#333' + }, + + lineStyle: { + color: '#314656', + opacity: 0.2, + curveness: 0.5 + }, + + emphasis: { + label: { + show: true + }, + lineStyle: { + opacity: 0.6 + } + }, + + animationEasing: 'linear', + + animationDuration: 1000 + } + +}); + +/* +* 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. +*/ + +var nodeOpacityPath$1 = ['itemStyle', 'opacity']; +var lineOpacityPath$1 = ['lineStyle', 'opacity']; + +function getItemOpacity$1(item, opacityPath) { + return item.getVisual('opacity') || item.getModel().get(opacityPath); +} + +function fadeOutItem$1(item, opacityPath, opacityRatio) { + var el = item.getGraphicEl(); + + var opacity = getItemOpacity$1(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$1(item, opacityPath) { + var opacity = getItemOpacity$1(item, opacityPath); + var el = item.getGraphicEl(); + + el.highlight && el.highlight(); + el.traverse(function (child) { + if (child.type !== 'group') { + child.setStyle('opacity', opacity); + } + }); +} + +var SankeyShape = extendShape({ + shape: { + x1: 0, y1: 0, + x2: 0, y2: 0, + cpx1: 0, cpy1: 0, + cpx2: 0, cpy2: 0, + extent: 0, + orient: '' + }, + + buildPath: function (ctx, shape) { + var extent = shape.extent; + ctx.moveTo(shape.x1, shape.y1); + ctx.bezierCurveTo( + shape.cpx1, shape.cpy1, + shape.cpx2, shape.cpy2, + shape.x2, shape.y2 + ); + if (shape.orient === 'vertical') { + ctx.lineTo(shape.x2 + extent, shape.y2); + ctx.bezierCurveTo( + shape.cpx2 + extent, shape.cpy2, + shape.cpx1 + extent, shape.cpy1, + shape.x1 + extent, shape.y1 + ); + } + else { + ctx.lineTo(shape.x2, shape.y2 + extent); + ctx.bezierCurveTo( + shape.cpx2, shape.cpy2 + extent, + shape.cpx1, shape.cpy1 + extent, + shape.x1, shape.y1 + extent + ); + } + ctx.closePath(); + } +}); + +extendChartView({ + + type: 'sankey', + + /** + * @private + * @type {module:echarts/chart/sankey/SankeySeries} + */ + _model: null, + + /** + * @private + * @type {boolean} + */ + _focusAdjacencyDisabled: false, + + render: function (seriesModel, ecModel, api) { + var sankeyView = this; + var graph = seriesModel.getGraph(); + var group = this.group; + var layoutInfo = seriesModel.layoutInfo; + // view width + var width = layoutInfo.width; + // view height + var height = layoutInfo.height; + var nodeData = seriesModel.getData(); + var edgeData = seriesModel.getData('edge'); + var orient = seriesModel.get('orient'); + + this._model = seriesModel; + + group.removeAll(); + + group.attr('position', [layoutInfo.x, layoutInfo.y]); + + // generate a bezire Curve for each edge + graph.eachEdge(function (edge) { + var curve = new SankeyShape(); + curve.dataIndex = edge.dataIndex; + curve.seriesIndex = seriesModel.seriesIndex; + curve.dataType = 'edge'; + var lineStyleModel = edge.getModel('lineStyle'); + var curvature = lineStyleModel.get('curveness'); + var n1Layout = edge.node1.getLayout(); + var node1Model = edge.node1.getModel(); + var dragX1 = node1Model.get('localX'); + var dragY1 = node1Model.get('localY'); + var n2Layout = edge.node2.getLayout(); + var node2Model = edge.node2.getModel(); + var dragX2 = node2Model.get('localX'); + var dragY2 = node2Model.get('localY'); + var edgeLayout = edge.getLayout(); + var x1; + var y1; + var x2; + var y2; + var cpx1; + var cpy1; + var cpx2; + var cpy2; + + curve.shape.extent = Math.max(1, edgeLayout.dy); + curve.shape.orient = orient; + + if (orient === 'vertical') { + x1 = (dragX1 != null ? dragX1 * width : n1Layout.x) + edgeLayout.sy; + y1 = (dragY1 != null ? dragY1 * height : n1Layout.y) + n1Layout.dy; + x2 = (dragX2 != null ? dragX2 * width : n2Layout.x) + edgeLayout.ty; + y2 = dragY2 != null ? dragY2 * height : n2Layout.y; + cpx1 = x1; + cpy1 = y1 * (1 - curvature) + y2 * curvature; + cpx2 = x2; + cpy2 = y1 * curvature + y2 * (1 - curvature); + } + else { + x1 = (dragX1 != null ? dragX1 * width : n1Layout.x) + n1Layout.dx; + y1 = (dragY1 != null ? dragY1 * height : n1Layout.y) + edgeLayout.sy; + x2 = dragX2 != null ? dragX2 * width : n2Layout.x; + y2 = (dragY2 != null ? dragY2 * height : n2Layout.y) + edgeLayout.ty; + cpx1 = x1 * (1 - curvature) + x2 * curvature; + cpy1 = y1; + cpx2 = x1 * curvature + x2 * (1 - curvature); + cpy2 = y2; + } + + curve.setShape({ + x1: x1, + y1: y1, + x2: x2, + y2: y2, + cpx1: cpx1, + cpy1: cpy1, + cpx2: cpx2, + cpy2: cpy2 + }); + + curve.setStyle(lineStyleModel.getItemStyle()); + // Special color, use source node color or target node color + switch (curve.style.fill) { + case 'source': + curve.style.fill = edge.node1.getVisual('color'); + break; + case 'target': + curve.style.fill = edge.node2.getVisual('color'); + break; + } + + setHoverStyle(curve, edge.getModel('emphasis.lineStyle').getItemStyle()); + + group.add(curve); + + edgeData.setItemGraphicEl(edge.dataIndex, curve); + }); + + // Generate a rect for each node + graph.eachNode(function (node) { + var layout = node.getLayout(); + var itemModel = node.getModel(); + var dragX = itemModel.get('localX'); + var dragY = itemModel.get('localY'); + var labelModel = itemModel.getModel('label'); + var labelHoverModel = itemModel.getModel('emphasis.label'); + + var rect = new Rect({ + shape: { + x: dragX != null ? dragX * width : layout.x, + y: dragY != null ? dragY * height : layout.y, + width: layout.dx, + height: layout.dy + }, + style: itemModel.getModel('itemStyle').getItemStyle() + }); + + var hoverStyle = node.getModel('emphasis.itemStyle').getItemStyle(); + + setLabelStyle( + rect.style, hoverStyle, labelModel, labelHoverModel, + { + labelFetcher: seriesModel, + labelDataIndex: node.dataIndex, + defaultText: node.id, + isRectText: true + } + ); + + rect.setStyle('fill', node.getVisual('color')); + + setHoverStyle(rect, hoverStyle); + + group.add(rect); + + nodeData.setItemGraphicEl(node.dataIndex, rect); + + rect.dataType = 'node'; + }); + + nodeData.eachItemGraphicEl(function (el, dataIndex) { + var itemModel = nodeData.getItemModel(dataIndex); + if (itemModel.get('draggable')) { + el.drift = function (dx, dy) { + sankeyView._focusAdjacencyDisabled = true; + this.shape.x += dx; + this.shape.y += dy; + this.dirty(); + api.dispatchAction({ + type: 'dragNode', + seriesId: seriesModel.id, + dataIndex: nodeData.getRawIndex(dataIndex), + localX: this.shape.x / width, + localY: this.shape.y / height + }); + }; + el.ondragend = function () { + sankeyView._focusAdjacencyDisabled = false; + }; + el.draggable = true; + el.cursor = 'move'; + } + + if (itemModel.get('focusNodeAdjacency')) { + el.off('mouseover').on('mouseover', function () { + if (!sankeyView._focusAdjacencyDisabled) { + sankeyView._clearTimer(); + api.dispatchAction({ + type: 'focusNodeAdjacency', + seriesId: seriesModel.id, + dataIndex: el.dataIndex + }); + } + }); + el.off('mouseout').on('mouseout', function () { + if (!sankeyView._focusAdjacencyDisabled) { + sankeyView._dispatchUnfocus(api); + } + }); + } + }); + + edgeData.eachItemGraphicEl(function (el, dataIndex) { + var edgeModel = edgeData.getItemModel(dataIndex); + if (edgeModel.get('focusNodeAdjacency')) { + el.off('mouseover').on('mouseover', function () { + if (!sankeyView._focusAdjacencyDisabled) { + sankeyView._clearTimer(); + api.dispatchAction({ + type: 'focusNodeAdjacency', + seriesId: seriesModel.id, + edgeDataIndex: el.dataIndex + }); + } + }); + el.off('mouseout').on('mouseout', function () { + if (!sankeyView._focusAdjacencyDisabled) { + sankeyView._dispatchUnfocus(api); + } + }); + } + }); + + if (!this._data && seriesModel.get('animation')) { + group.setClipPath(createGridClipShape$1(group.getBoundingRect(), seriesModel, function () { + group.removeClipPath(); + })); + } + + this._data = seriesModel.getData(); + }, + + dispose: function () { + this._clearTimer(); + }, + + _dispatchUnfocus: function (api) { + var self = this; + this._clearTimer(); + this._unfocusDelayTimer = setTimeout(function () { + self._unfocusDelayTimer = null; + api.dispatchAction({ + type: 'unfocusNodeAdjacency', + seriesId: self._model.id + }); + }, 500); + }, + + _clearTimer: function () { + if (this._unfocusDelayTimer) { + clearTimeout(this._unfocusDelayTimer); + this._unfocusDelayTimer = null; + } + }, + + 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$1(node, nodeOpacityPath$1, 0.1); + }); + graph.eachEdge(function (edge) { + fadeOutItem$1(edge, lineOpacityPath$1, 0.1); + }); + + if (node) { + fadeInItem$1(node, nodeOpacityPath$1); + var focusNodeAdj = itemModel.get('focusNodeAdjacency'); + if (focusNodeAdj === 'outEdges') { + each$1(node.outEdges, function (edge) { + if (edge.dataIndex < 0) { + return; + } + fadeInItem$1(edge, lineOpacityPath$1); + fadeInItem$1(edge.node2, nodeOpacityPath$1); + }); + } + else if (focusNodeAdj === 'inEdges') { + each$1(node.inEdges, function (edge) { + if (edge.dataIndex < 0) { + return; + } + fadeInItem$1(edge, lineOpacityPath$1); + fadeInItem$1(edge.node1, nodeOpacityPath$1); + }); + } + else if (focusNodeAdj === 'allEdges') { + each$1(node.edges, function (edge) { + if (edge.dataIndex < 0) { + return; + } + fadeInItem$1(edge, lineOpacityPath$1); + fadeInItem$1(edge.node1, nodeOpacityPath$1); + fadeInItem$1(edge.node2, nodeOpacityPath$1); + }); + } + } + if (edge) { + fadeInItem$1(edge, lineOpacityPath$1); + fadeInItem$1(edge.node1, nodeOpacityPath$1); + fadeInItem$1(edge.node2, nodeOpacityPath$1); + } + }, + + unfocusNodeAdjacency: function (seriesModel, ecModel, api, payload) { + var graph = this._model.getGraph(); + + graph.eachNode(function (node) { + fadeOutItem$1(node, nodeOpacityPath$1); + }); + graph.eachEdge(function (edge) { + fadeOutItem$1(edge, lineOpacityPath$1); + }); + } +}); + +// Add animation to the view +function createGridClipShape$1(rect, seriesModel, cb) { + var rectEl = new Rect({ + shape: { + x: rect.x - 10, + y: rect.y - 10, + width: 0, + height: rect.height + 20 + } + }); + initProps(rectEl, { + shape: { + width: rect.width + 20, + height: rect.height + 20 + } + }, seriesModel, cb); + + return rectEl; +} + +/* +* 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. +*/ + +registerAction({ + type: 'dragNode', + event: 'dragnode', + // here can only use 'update' now, other value is not support in echarts. + update: 'update' +}, function (payload, ecModel) { + ecModel.eachComponent({mainType: 'series', subType: 'sankey', query: payload}, function (seriesModel) { + seriesModel.setNodePosition(payload.dataIndex, [payload.localX, payload.localY]); + }); +}); + +/* +* 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. +*/ + +var sankeyLayout = function (ecModel, api, payload) { + + ecModel.eachSeriesByType('sankey', function (seriesModel) { + + var nodeWidth = seriesModel.get('nodeWidth'); + var nodeGap = seriesModel.get('nodeGap'); + + var layoutInfo = getViewRect$4(seriesModel, api); + + seriesModel.layoutInfo = layoutInfo; + + var width = layoutInfo.width; + var height = layoutInfo.height; + + var graph = seriesModel.getGraph(); + + var nodes = graph.nodes; + var edges = graph.edges; + + computeNodeValues(nodes); + + var filteredNodes = filter(nodes, function (node) { + return node.getLayout().value === 0; + }); + + var iterations = filteredNodes.length !== 0 ? 0 : seriesModel.get('layoutIterations'); + + var orient = seriesModel.get('orient'); + + var nodeAlign = seriesModel.get('nodeAlign'); + + layoutSankey(nodes, edges, nodeWidth, nodeGap, width, height, iterations, orient, nodeAlign); + }); +}; + +/** + * Get the layout position of the whole view + * + * @param {module:echarts/model/Series} seriesModel the model object of sankey series + * @param {module:echarts/ExtensionAPI} api provide the API list that the developer can call + * @return {module:zrender/core/BoundingRect} size of rect to draw the sankey view + */ +function getViewRect$4(seriesModel, api) { + return getLayoutRect( + seriesModel.getBoxLayoutParams(), { + width: api.getWidth(), + height: api.getHeight() + } + ); +} + +function layoutSankey(nodes, edges, nodeWidth, nodeGap, width, height, iterations, orient, nodeAlign) { + computeNodeBreadths(nodes, edges, nodeWidth, width, height, orient, nodeAlign); + computeNodeDepths(nodes, edges, height, width, nodeGap, iterations, orient); + computeEdgeDepths(nodes, orient); +} + +/** + * Compute the value of each node by summing the associated edge's value + * + * @param {module:echarts/data/Graph~Node} nodes node of sankey view + */ +function computeNodeValues(nodes) { + each$1(nodes, function (node) { + var value1 = sum(node.outEdges, getEdgeValue); + var value2 = sum(node.inEdges, getEdgeValue); + var value = Math.max(value1, value2); + node.setLayout({value: value}, true); + }); +} + +/** + * Compute the x-position for each node. + * + * Here we use Kahn algorithm to detect cycle when we traverse + * the node to computer the initial x position. + * + * @param {module:echarts/data/Graph~Node} nodes node of sankey view + * @param {number} nodeWidth the dx of the node + * @param {number} width the whole width of the area to draw the view + */ +function computeNodeBreadths(nodes, edges, nodeWidth, width, height, orient, nodeAlign) { + // Used to mark whether the edge is deleted. if it is deleted, + // the value is 0, otherwise it is 1. + var remainEdges = []; + // Storage each node's indegree. + var indegreeArr = []; + //Used to storage the node with indegree is equal to 0. + var zeroIndegrees = []; + var nextTargetNode = []; + var x = 0; + var kx = 0; + + for (var i = 0; i < edges.length; i++) { + remainEdges[i] = 1; + } + for (i = 0; i < nodes.length; i++) { + indegreeArr[i] = nodes[i].inEdges.length; + if (indegreeArr[i] === 0) { + zeroIndegrees.push(nodes[i]); + } + } + var maxNodeDepth = -1; + // Traversing nodes using topological sorting to calculate the + // horizontal(if orient === 'horizontal') or vertical(if orient === 'vertical') + // position of the nodes. + while (zeroIndegrees.length) { + for (var idx = 0; idx < zeroIndegrees.length; idx++) { + var node = zeroIndegrees[idx]; + var item = node.hostGraph.data.getRawDataItem(node.dataIndex); + var isItemDepth = item.depth != null && item.depth >= 0; + if (isItemDepth && item.depth > maxNodeDepth) { + maxNodeDepth = item.depth; + } + node.setLayout({depth: isItemDepth ? item.depth : x}, true); + orient === 'vertical' + ? node.setLayout({dy: nodeWidth}, true) + : node.setLayout({dx: nodeWidth}, true); + + for (var edgeIdx = 0; edgeIdx < node.outEdges.length; edgeIdx++) { + var edge = node.outEdges[edgeIdx]; + var indexEdge = edges.indexOf(edge); + remainEdges[indexEdge] = 0; + var targetNode = edge.node2; + var nodeIndex = nodes.indexOf(targetNode); + if (--indegreeArr[nodeIndex] === 0 && nextTargetNode.indexOf(targetNode) < 0) { + nextTargetNode.push(targetNode); + } + } + } + ++x; + zeroIndegrees = nextTargetNode; + nextTargetNode = []; + } + + for (i = 0; i < remainEdges.length; i++) { + if (remainEdges[i] === 1) { + throw new Error('Sankey is a DAG, the original data has cycle!'); + } + } + + var maxDepth = maxNodeDepth > x - 1 ? maxNodeDepth : x - 1; + if (nodeAlign && nodeAlign !== 'left') { + adjustNodeWithNodeAlign(nodes, nodeAlign, orient, maxDepth); + } + var kx = orient === 'vertical' + ? (height - nodeWidth) / maxDepth + : (width - nodeWidth) / maxDepth; + + scaleNodeBreadths(nodes, kx, orient); +} + +function isNodeDepth(node) { + var item = node.hostGraph.data.getRawDataItem(node.dataIndex); + return item.depth != null && item.depth >= 0; +} + +function adjustNodeWithNodeAlign(nodes, nodeAlign, orient, maxDepth) { + if (nodeAlign === 'right') { + var nextSourceNode = []; + var remainNodes = nodes; + var nodeHeight = 0; + while (remainNodes.length) { + for (var i = 0; i < remainNodes.length; i++) { + var node = remainNodes[i]; + node.setLayout({skNodeHeight: nodeHeight}, true); + for (var j = 0; j < node.inEdges.length; j++) { + var edge = node.inEdges[j]; + if (nextSourceNode.indexOf(edge.node1) < 0) { + nextSourceNode.push(edge.node1); + } + } + } + remainNodes = nextSourceNode; + nextSourceNode = []; + ++nodeHeight; + } + + each$1(nodes, function (node) { + if (!isNodeDepth(node)) { + node.setLayout({depth: Math.max(0, maxDepth - node.getLayout().skNodeHeight)}, true); + } + }); + } + else if (nodeAlign === 'justify') { + moveSinksRight(nodes, maxDepth); + } +} + +/** + * All the node without outEgdes are assigned maximum x-position and + * be aligned in the last column. + * + * @param {module:echarts/data/Graph~Node} nodes. node of sankey view. + * @param {number} maxDepth. use to assign to node without outEdges as x-position. + */ +function moveSinksRight(nodes, maxDepth) { + each$1(nodes, function (node) { + if (!isNodeDepth(node) && !node.outEdges.length) { + node.setLayout({depth: maxDepth}, true); + } + }); +} + +/** + * Scale node x-position to the width + * + * @param {module:echarts/data/Graph~Node} nodes node of sankey view + * @param {number} kx multiple used to scale nodes + */ +function scaleNodeBreadths(nodes, kx, orient) { + each$1(nodes, function (node) { + var nodeDepth = node.getLayout().depth * kx; + orient === 'vertical' + ? node.setLayout({y: nodeDepth}, true) + : node.setLayout({x: nodeDepth}, true); + }); +} + +/** + * Using Gauss-Seidel iterations method to compute the node depth(y-position) + * + * @param {module:echarts/data/Graph~Node} nodes node of sankey view + * @param {module:echarts/data/Graph~Edge} edges edge of sankey view + * @param {number} height the whole height of the area to draw the view + * @param {number} nodeGap the vertical distance between two nodes + * in the same column. + * @param {number} iterations the number of iterations for the algorithm + */ +function computeNodeDepths(nodes, edges, height, width, nodeGap, iterations, orient) { + var nodesByBreadth = prepareNodesByBreadth(nodes, orient); + + initializeNodeDepth(nodesByBreadth, edges, height, width, nodeGap, orient); + resolveCollisions(nodesByBreadth, nodeGap, height, width, orient); + + for (var alpha = 1; iterations > 0; iterations--) { + // 0.99 is a experience parameter, ensure that each iterations of + // changes as small as possible. + alpha *= 0.99; + relaxRightToLeft(nodesByBreadth, alpha, orient); + resolveCollisions(nodesByBreadth, nodeGap, height, width, orient); + relaxLeftToRight(nodesByBreadth, alpha, orient); + resolveCollisions(nodesByBreadth, nodeGap, height, width, orient); + } +} + +function prepareNodesByBreadth(nodes, orient) { + var nodesByBreadth = []; + var keyAttr = orient === 'vertical' ? 'y' : 'x'; + + var groupResult = groupData(nodes, function (node) { + return node.getLayout()[keyAttr]; + }); + groupResult.keys.sort(function (a, b) { + return a - b; + }); + each$1(groupResult.keys, function (key) { + nodesByBreadth.push(groupResult.buckets.get(key)); + }); + + return nodesByBreadth; +} + +/** + * Compute the original y-position for each node + * + * @param {module:echarts/data/Graph~Node} nodes node of sankey view + * @param {Array.>} nodesByBreadth + * group by the array of all sankey nodes based on the nodes x-position. + * @param {module:echarts/data/Graph~Edge} edges edge of sankey view + * @param {number} height the whole height of the area to draw the view + * @param {number} nodeGap the vertical distance between two nodes + */ +function initializeNodeDepth(nodesByBreadth, edges, height, width, nodeGap, orient) { + var minKy = Infinity; + each$1(nodesByBreadth, function (nodes) { + var n = nodes.length; + var sum = 0; + each$1(nodes, function (node) { + sum += node.getLayout().value; + }); + var ky = orient === 'vertical' + ? (width - (n - 1) * nodeGap) / sum + : (height - (n - 1) * nodeGap) / sum; + + if (ky < minKy) { + minKy = ky; + } + }); + + each$1(nodesByBreadth, function (nodes) { + each$1(nodes, function (node, i) { + var nodeDy = node.getLayout().value * minKy; + if (orient === 'vertical') { + node.setLayout({x: i}, true); + node.setLayout({dx: nodeDy}, true); + } + else { + node.setLayout({y: i}, true); + node.setLayout({dy: nodeDy}, true); + } + }); + }); + + each$1(edges, function (edge) { + var edgeDy = +edge.getValue() * minKy; + edge.setLayout({dy: edgeDy}, true); + }); +} + +/** + * Resolve the collision of initialized depth (y-position) + * + * @param {Array.>} nodesByBreadth + * group by the array of all sankey nodes based on the nodes x-position. + * @param {number} nodeGap the vertical distance between two nodes + * @param {number} height the whole height of the area to draw the view + */ +function resolveCollisions(nodesByBreadth, nodeGap, height, width, orient) { + var keyAttr = orient === 'vertical' ? 'x' : 'y'; + each$1(nodesByBreadth, function (nodes) { + nodes.sort(function (a, b) { + return a.getLayout()[keyAttr] - b.getLayout()[keyAttr]; + }); + var nodeX; + var node; + var dy; + var y0 = 0; + var n = nodes.length; + var nodeDyAttr = orient === 'vertical' ? 'dx' : 'dy'; + for (var i = 0; i < n; i++) { + node = nodes[i]; + dy = y0 - node.getLayout()[keyAttr]; + if (dy > 0) { + nodeX = node.getLayout()[keyAttr] + dy; + orient === 'vertical' + ? node.setLayout({x: nodeX}, true) + : node.setLayout({y: nodeX}, true); + } + y0 = node.getLayout()[keyAttr] + node.getLayout()[nodeDyAttr] + nodeGap; + } + var viewWidth = orient === 'vertical' ? width : height; + // If the bottommost node goes outside the bounds, push it back up + dy = y0 - nodeGap - viewWidth; + if (dy > 0) { + nodeX = node.getLayout()[keyAttr] - dy; + orient === 'vertical' + ? node.setLayout({x: nodeX}, true) + : node.setLayout({y: nodeX}, true); + + y0 = nodeX; + for (i = n - 2; i >= 0; --i) { + node = nodes[i]; + dy = node.getLayout()[keyAttr] + node.getLayout()[nodeDyAttr] + nodeGap - y0; + if (dy > 0) { + nodeX = node.getLayout()[keyAttr] - dy; + orient === 'vertical' + ? node.setLayout({x: nodeX}, true) + : node.setLayout({y: nodeX}, true); + } + y0 = node.getLayout()[keyAttr]; + } + } + }); +} + +/** + * Change the y-position of the nodes, except most the right side nodes + * + * @param {Array.>} nodesByBreadth + * group by the array of all sankey nodes based on the node x-position. + * @param {number} alpha parameter used to adjust the nodes y-position + */ +function relaxRightToLeft(nodesByBreadth, alpha, orient) { + each$1(nodesByBreadth.slice().reverse(), function (nodes) { + each$1(nodes, function (node) { + if (node.outEdges.length) { + var y = sum(node.outEdges, weightedTarget, orient) + / sum(node.outEdges, getEdgeValue, orient); + if (orient === 'vertical') { + var nodeX = node.getLayout().x + (y - center$1(node, orient)) * alpha; + node.setLayout({x: nodeX}, true); + } + else { + var nodeY = node.getLayout().y + (y - center$1(node, orient)) * alpha; + node.setLayout({y: nodeY}, true); + } + } + }); + }); +} + +function weightedTarget(edge, orient) { + return center$1(edge.node2, orient) * edge.getValue(); +} + +function weightedSource(edge, orient) { + return center$1(edge.node1, orient) * edge.getValue(); +} + +function center$1(node, orient) { + return orient === 'vertical' + ? node.getLayout().x + node.getLayout().dx / 2 + : node.getLayout().y + node.getLayout().dy / 2; +} + +function getEdgeValue(edge) { + return edge.getValue(); +} + +function sum(array, f, orient) { + var sum = 0; + var len = array.length; + var i = -1; + while (++i < len) { + var value = +f.call(array, array[i], orient); + if (!isNaN(value)) { + sum += value; + } + } + return sum; +} + +/** + * Change the y-position of the nodes, except most the left side nodes + * + * @param {Array.>} nodesByBreadth + * group by the array of all sankey nodes based on the node x-position. + * @param {number} alpha parameter used to adjust the nodes y-position + */ +function relaxLeftToRight(nodesByBreadth, alpha, orient) { + each$1(nodesByBreadth, function (nodes) { + each$1(nodes, function (node) { + if (node.inEdges.length) { + var y = sum(node.inEdges, weightedSource, orient) + / sum(node.inEdges, getEdgeValue, orient); + if (orient === 'vertical') { + var nodeX = node.getLayout().x + (y - center$1(node, orient)) * alpha; + node.setLayout({x: nodeX}, true); + } + else { + var nodeY = node.getLayout().y + (y - center$1(node, orient)) * alpha; + node.setLayout({y: nodeY}, true); + } + } + }); + }); +} + +/** + * Compute the depth(y-position) of each edge + * + * @param {module:echarts/data/Graph~Node} nodes node of sankey view + */ +function computeEdgeDepths(nodes, orient) { + var keyAttr = orient === 'vertical' ? 'x' : 'y'; + each$1(nodes, function (node) { + node.outEdges.sort(function (a, b) { + return a.node2.getLayout()[keyAttr] - b.node2.getLayout()[keyAttr]; + }); + node.inEdges.sort(function (a, b) { + return a.node1.getLayout()[keyAttr] - b.node1.getLayout()[keyAttr]; + }); + }); + each$1(nodes, function (node) { + var sy = 0; + var ty = 0; + each$1(node.outEdges, function (edge) { + edge.setLayout({sy: sy}, true); + sy += edge.getLayout().dy; + }); + each$1(node.inEdges, function (edge) { + edge.setLayout({ty: ty}, true); + ty += edge.getLayout().dy; + }); + }); +} + +/* +* 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. +*/ + +var sankeyVisual = function (ecModel, payload) { + ecModel.eachSeriesByType('sankey', function (seriesModel) { + var graph = seriesModel.getGraph(); + var nodes = graph.nodes; + if (nodes.length) { + var minValue = Infinity; + var maxValue = -Infinity; + each$1(nodes, function (node) { + var nodeValue = node.getLayout().value; + if (nodeValue < minValue) { + minValue = nodeValue; + } + if (nodeValue > maxValue) { + maxValue = nodeValue; + } + }); + + each$1(nodes, function (node) { + var mapping = new VisualMapping({ + type: 'color', + mappingMethod: 'linear', + dataExtent: [minValue, maxValue], + visual: seriesModel.get('color') + }); + + var mapValueToColor = mapping.mapValueToVisual(node.getLayout().value); + var customColor = node.getModel().get('itemStyle.color'); + customColor != null + ? node.setVisual('color', customColor) + : node.setVisual('color', mapValueToColor); + }); + } + }); +}; + +/* +* 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. +*/ + +registerLayout(sankeyLayout); +registerVisual(sankeyVisual); + +/* +* 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. +*/ + + +var seriesModelMixin = { + + /** + * @private + * @type {string} + */ + _baseAxisDim: null, + + /** + * @override + */ + getInitialData: function (option, ecModel) { + // When both types of xAxis and yAxis are 'value', layout is + // needed to be specified by user. Otherwise, layout can be + // judged by which axis is category. + + var ordinalMeta; + + var xAxisModel = ecModel.getComponent('xAxis', this.get('xAxisIndex')); + var yAxisModel = ecModel.getComponent('yAxis', this.get('yAxisIndex')); + var xAxisType = xAxisModel.get('type'); + var yAxisType = yAxisModel.get('type'); + var addOrdinal; + + // FIXME + // Consider time axis. + + if (xAxisType === 'category') { + option.layout = 'horizontal'; + ordinalMeta = xAxisModel.getOrdinalMeta(); + addOrdinal = true; + } + else if (yAxisType === 'category') { + option.layout = 'vertical'; + ordinalMeta = yAxisModel.getOrdinalMeta(); + addOrdinal = true; + } + else { + option.layout = option.layout || 'horizontal'; + } + + var coordDims = ['x', 'y']; + var baseAxisDimIndex = option.layout === 'horizontal' ? 0 : 1; + var baseAxisDim = this._baseAxisDim = coordDims[baseAxisDimIndex]; + var otherAxisDim = coordDims[1 - baseAxisDimIndex]; + var axisModels = [xAxisModel, yAxisModel]; + var baseAxisType = axisModels[baseAxisDimIndex].get('type'); + var otherAxisType = axisModels[1 - baseAxisDimIndex].get('type'); + var data = option.data; + + // ??? FIXME make a stage to perform data transfrom. + // MUST create a new data, consider setOption({}) again. + if (data && addOrdinal) { + var newOptionData = []; + each$1(data, function (item, index) { + var newItem; + if (item.value && isArray(item.value)) { + newItem = item.value.slice(); + item.value.unshift(index); + } + else if (isArray(item)) { + newItem = item.slice(); + item.unshift(index); + } + else { + newItem = item; + } + newOptionData.push(newItem); + }); + option.data = newOptionData; + } + + var defaultValueDimensions = this.defaultValueDimensions; + var coordDimensions = [{ + name: baseAxisDim, + type: getDimensionTypeByAxis(baseAxisType), + ordinalMeta: ordinalMeta, + otherDims: { + tooltip: false, + itemName: 0 + }, + dimsDef: ['base'] + }, { + name: otherAxisDim, + type: getDimensionTypeByAxis(otherAxisType), + dimsDef: defaultValueDimensions.slice() + }]; + + return createListSimply( + this, + { + coordDimensions: coordDimensions, + dimensionsCount: defaultValueDimensions.length + 1, + encodeDefaulter: curry( + makeSeriesEncodeForAxisCoordSys, coordDimensions, this + ) + } + ); + }, + + /** + * If horizontal, base axis is x, otherwise y. + * @override + */ + getBaseAxis: function () { + var dim = this._baseAxisDim; + return this.ecModel.getComponent(dim + 'Axis', this.get(dim + 'AxisIndex')).axis; + } + +}; + +/* +* 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. +*/ + +var BoxplotSeries = SeriesModel.extend({ + + type: 'series.boxplot', + + dependencies: ['xAxis', 'yAxis', 'grid'], + + // TODO + // box width represents group size, so dimension should have 'size'. + + /** + * @see + * The meanings of 'min' and 'max' depend on user, + * and echarts do not need to know it. + * @readOnly + */ + defaultValueDimensions: [ + {name: 'min', defaultTooltip: true}, + {name: 'Q1', defaultTooltip: true}, + {name: 'median', defaultTooltip: true}, + {name: 'Q3', defaultTooltip: true}, + {name: 'max', defaultTooltip: true} + ], + + /** + * @type {Array.} + * @readOnly + */ + dimensions: null, + + /** + * @override + */ + defaultOption: { + zlevel: 0, // 一级层叠 + z: 2, // 二级层叠 + coordinateSystem: 'cartesian2d', + legendHoverLink: true, + + hoverAnimation: true, + + // xAxisIndex: 0, + // yAxisIndex: 0, + + layout: null, // 'horizontal' or 'vertical' + boxWidth: [7, 50], // [min, max] can be percent of band width. + + itemStyle: { + color: '#fff', + borderWidth: 1 + }, + + emphasis: { + itemStyle: { + borderWidth: 2, + shadowBlur: 5, + shadowOffsetX: 2, + shadowOffsetY: 2, + shadowColor: 'rgba(0,0,0,0.4)' + } + }, + + animationEasing: 'elasticOut', + animationDuration: 800 + } +}); + +mixin(BoxplotSeries, seriesModelMixin, true); + +/* +* 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. +*/ + +// Update common properties +var NORMAL_ITEM_STYLE_PATH = ['itemStyle']; +var EMPHASIS_ITEM_STYLE_PATH = ['emphasis', 'itemStyle']; + +var BoxplotView = Chart.extend({ + + type: 'boxplot', + + render: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + var group = this.group; + var oldData = this._data; + + // There is no old data only when first rendering or switching from + // stream mode to normal mode, where previous elements should be removed. + if (!this._data) { + group.removeAll(); + } + + var constDim = seriesModel.get('layout') === 'horizontal' ? 1 : 0; + + data.diff(oldData) + .add(function (newIdx) { + if (data.hasValue(newIdx)) { + var itemLayout = data.getItemLayout(newIdx); + var symbolEl = createNormalBox(itemLayout, data, newIdx, constDim, true); + data.setItemGraphicEl(newIdx, symbolEl); + group.add(symbolEl); + } + }) + .update(function (newIdx, oldIdx) { + var symbolEl = oldData.getItemGraphicEl(oldIdx); + + // Empty data + if (!data.hasValue(newIdx)) { + group.remove(symbolEl); + return; + } + + var itemLayout = data.getItemLayout(newIdx); + if (!symbolEl) { + symbolEl = createNormalBox(itemLayout, data, newIdx, constDim); + } + else { + updateNormalBoxData(itemLayout, symbolEl, data, newIdx); + } + + group.add(symbolEl); + + data.setItemGraphicEl(newIdx, symbolEl); + }) + .remove(function (oldIdx) { + var el = oldData.getItemGraphicEl(oldIdx); + el && group.remove(el); + }) + .execute(); + + this._data = data; + }, + + remove: function (ecModel) { + var group = this.group; + var data = this._data; + this._data = null; + data && data.eachItemGraphicEl(function (el) { + el && group.remove(el); + }); + }, + + dispose: noop + +}); + + +var BoxPath = Path.extend({ + + type: 'boxplotBoxPath', + + shape: {}, + + buildPath: function (ctx, shape) { + var ends = shape.points; + + var i = 0; + ctx.moveTo(ends[i][0], ends[i][1]); + i++; + for (; i < 4; i++) { + ctx.lineTo(ends[i][0], ends[i][1]); + } + ctx.closePath(); + + for (; i < ends.length; i++) { + ctx.moveTo(ends[i][0], ends[i][1]); + i++; + ctx.lineTo(ends[i][0], ends[i][1]); + } + } +}); + + +function createNormalBox(itemLayout, data, dataIndex, constDim, isInit) { + var ends = itemLayout.ends; + + var el = new BoxPath({ + shape: { + points: isInit + ? transInit(ends, constDim, itemLayout) + : ends + } + }); + + updateNormalBoxData(itemLayout, el, data, dataIndex, isInit); + + return el; +} + +function updateNormalBoxData(itemLayout, el, data, dataIndex, isInit) { + var seriesModel = data.hostModel; + var updateMethod = graphic[isInit ? 'initProps' : 'updateProps']; + + updateMethod( + el, + {shape: {points: itemLayout.ends}}, + seriesModel, + dataIndex + ); + + var itemModel = data.getItemModel(dataIndex); + var normalItemStyleModel = itemModel.getModel(NORMAL_ITEM_STYLE_PATH); + var borderColor = data.getItemVisual(dataIndex, 'color'); + + // Exclude borderColor. + var itemStyle = normalItemStyleModel.getItemStyle(['borderColor']); + itemStyle.stroke = borderColor; + itemStyle.strokeNoScale = true; + el.useStyle(itemStyle); + + el.z2 = 100; + + var hoverStyle = itemModel.getModel(EMPHASIS_ITEM_STYLE_PATH).getItemStyle(); + setHoverStyle(el, hoverStyle); +} + +function transInit(points, dim, itemLayout) { + return map(points, function (point) { + point = point.slice(); + point[dim] = itemLayout.initBaseline; + return point; + }); +} + +/* +* 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. +*/ + + +var borderColorQuery = ['itemStyle', 'borderColor']; + +var boxplotVisual = function (ecModel, api) { + + var globalColors = ecModel.get('color'); + + ecModel.eachRawSeriesByType('boxplot', function (seriesModel) { + + var defaulColor = globalColors[seriesModel.seriesIndex % globalColors.length]; + var data = seriesModel.getData(); + + data.setVisual({ + legendSymbol: 'roundRect', + // Use name 'color' but not 'borderColor' for legend usage and + // visual coding from other component like dataRange. + color: seriesModel.get(borderColorQuery) || defaulColor + }); + + // Only visible series has each data be visual encoded + if (!ecModel.isSeriesFiltered(seriesModel)) { + data.each(function (idx) { + var itemModel = data.getItemModel(idx); + data.setItemVisual( + idx, + {color: itemModel.get(borderColorQuery, true)} + ); + }); + } + }); + +}; + +/* +* 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. +*/ + +var each$13 = each$1; + +var boxplotLayout = function (ecModel) { + + var groupResult = groupSeriesByAxis(ecModel); + + each$13(groupResult, function (groupItem) { + var seriesModels = groupItem.seriesModels; + + if (!seriesModels.length) { + return; + } + + calculateBase(groupItem); + + each$13(seriesModels, function (seriesModel, idx) { + layoutSingleSeries( + seriesModel, + groupItem.boxOffsetList[idx], + groupItem.boxWidthList[idx] + ); + }); + }); +}; + +/** + * Group series by axis. + */ +function groupSeriesByAxis(ecModel) { + var result = []; + var axisList = []; + + ecModel.eachSeriesByType('boxplot', function (seriesModel) { + var baseAxis = seriesModel.getBaseAxis(); + var idx = indexOf(axisList, baseAxis); + + if (idx < 0) { + idx = axisList.length; + axisList[idx] = baseAxis; + result[idx] = {axis: baseAxis, seriesModels: []}; + } + + result[idx].seriesModels.push(seriesModel); + }); + + return result; +} + +/** + * Calculate offset and box width for each series. + */ +function calculateBase(groupItem) { + var extent; + var baseAxis = groupItem.axis; + var seriesModels = groupItem.seriesModels; + var seriesCount = seriesModels.length; + + var boxWidthList = groupItem.boxWidthList = []; + var boxOffsetList = groupItem.boxOffsetList = []; + var boundList = []; + + var bandWidth; + if (baseAxis.type === 'category') { + bandWidth = baseAxis.getBandWidth(); + } + else { + var maxDataCount = 0; + each$13(seriesModels, function (seriesModel) { + maxDataCount = Math.max(maxDataCount, seriesModel.getData().count()); + }); + extent = baseAxis.getExtent(), + Math.abs(extent[1] - extent[0]) / maxDataCount; + } + + each$13(seriesModels, function (seriesModel) { + var boxWidthBound = seriesModel.get('boxWidth'); + if (!isArray(boxWidthBound)) { + boxWidthBound = [boxWidthBound, boxWidthBound]; + } + boundList.push([ + parsePercent$1(boxWidthBound[0], bandWidth) || 0, + parsePercent$1(boxWidthBound[1], bandWidth) || 0 + ]); + }); + + var availableWidth = bandWidth * 0.8 - 2; + var boxGap = availableWidth / seriesCount * 0.3; + var boxWidth = (availableWidth - boxGap * (seriesCount - 1)) / seriesCount; + var base = boxWidth / 2 - availableWidth / 2; + + each$13(seriesModels, function (seriesModel, idx) { + boxOffsetList.push(base); + base += boxGap + boxWidth; + + boxWidthList.push( + Math.min(Math.max(boxWidth, boundList[idx][0]), boundList[idx][1]) + ); + }); +} + +/** + * Calculate points location for each series. + */ +function layoutSingleSeries(seriesModel, offset, boxWidth) { + var coordSys = seriesModel.coordinateSystem; + var data = seriesModel.getData(); + var halfWidth = boxWidth / 2; + var cDimIdx = seriesModel.get('layout') === 'horizontal' ? 0 : 1; + var vDimIdx = 1 - cDimIdx; + var coordDims = ['x', 'y']; + var cDim = data.mapDimension(coordDims[cDimIdx]); + var vDims = data.mapDimension(coordDims[vDimIdx], true); + + if (cDim == null || vDims.length < 5) { + return; + } + + for (var dataIndex = 0; dataIndex < data.count(); dataIndex++) { + var axisDimVal = data.get(cDim, dataIndex); + + var median = getPoint(axisDimVal, vDims[2], dataIndex); + var end1 = getPoint(axisDimVal, vDims[0], dataIndex); + var end2 = getPoint(axisDimVal, vDims[1], dataIndex); + var end4 = getPoint(axisDimVal, vDims[3], dataIndex); + var end5 = getPoint(axisDimVal, vDims[4], dataIndex); + + var ends = []; + addBodyEnd(ends, end2, 0); + addBodyEnd(ends, end4, 1); + + ends.push(end1, end2, end5, end4); + layEndLine(ends, end1); + layEndLine(ends, end5); + layEndLine(ends, median); + + data.setItemLayout(dataIndex, { + initBaseline: median[vDimIdx], + ends: ends + }); + } + + function getPoint(axisDimVal, dimIdx, dataIndex) { + var val = data.get(dimIdx, dataIndex); + var p = []; + p[cDimIdx] = axisDimVal; + p[vDimIdx] = val; + var point; + if (isNaN(axisDimVal) || isNaN(val)) { + point = [NaN, NaN]; + } + else { + point = coordSys.dataToPoint(p); + point[cDimIdx] += offset; + } + return point; + } + + function addBodyEnd(ends, point, start) { + var point1 = point.slice(); + var point2 = point.slice(); + point1[cDimIdx] += halfWidth; + point2[cDimIdx] -= halfWidth; + start + ? ends.push(point1, point2) + : ends.push(point2, point1); + } + + function layEndLine(ends, endCenter) { + var from = endCenter.slice(); + var to = endCenter.slice(); + from[cDimIdx] -= halfWidth; + to[cDimIdx] += halfWidth; + ends.push(from, to); + } +} + +/* +* 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. +*/ + +registerVisual(boxplotVisual); +registerLayout(boxplotLayout); + +/* +* 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. +*/ + +var CandlestickSeries = SeriesModel.extend({ + + type: 'series.candlestick', + + dependencies: ['xAxis', 'yAxis', 'grid'], + + /** + * @readOnly + */ + defaultValueDimensions: [ + {name: 'open', defaultTooltip: true}, + {name: 'close', defaultTooltip: true}, + {name: 'lowest', defaultTooltip: true}, + {name: 'highest', defaultTooltip: true} + ], + + /** + * @type {Array.} + * @readOnly + */ + dimensions: null, + + /** + * @override + */ + defaultOption: { + zlevel: 0, + z: 2, + coordinateSystem: 'cartesian2d', + legendHoverLink: true, + + hoverAnimation: true, + + // xAxisIndex: 0, + // yAxisIndex: 0, + + layout: null, // 'horizontal' or 'vertical' + + clip: true, + + itemStyle: { + color: '#c23531', // 阳线 positive + color0: '#314656', // 阴线 negative '#c23531', '#314656' + borderWidth: 1, + // FIXME + // ec2中使用的是lineStyle.color 和 lineStyle.color0 + borderColor: '#c23531', + borderColor0: '#314656' + }, + + emphasis: { + itemStyle: { + borderWidth: 2 + } + }, + + barMaxWidth: null, + barMinWidth: null, + barWidth: null, + + large: true, + largeThreshold: 600, + + progressive: 3e3, + progressiveThreshold: 1e4, + progressiveChunkMode: 'mod', + + animationUpdate: false, + animationEasing: 'linear', + animationDuration: 300 + }, + + /** + * Get dimension for shadow in dataZoom + * @return {string} dimension name + */ + getShadowDim: function () { + return 'open'; + }, + + brushSelector: function (dataIndex, data, selectors) { + var itemLayout = data.getItemLayout(dataIndex); + return itemLayout && selectors.rect(itemLayout.brushRect); + } + +}); + +mixin(CandlestickSeries, seriesModelMixin, true); + +/* +* 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. +*/ + +var NORMAL_ITEM_STYLE_PATH$1 = ['itemStyle']; +var EMPHASIS_ITEM_STYLE_PATH$1 = ['emphasis', 'itemStyle']; +var SKIP_PROPS = ['color', 'color0', 'borderColor', 'borderColor0']; + +var CandlestickView = Chart.extend({ + + type: 'candlestick', + + render: function (seriesModel, ecModel, api) { + // If there is clipPath created in large mode. Remove it. + this.group.removeClipPath(); + + this._updateDrawMode(seriesModel); + + this._isLargeDraw + ? this._renderLarge(seriesModel) + : this._renderNormal(seriesModel); + }, + + incrementalPrepareRender: function (seriesModel, ecModel, api) { + this._clear(); + this._updateDrawMode(seriesModel); + }, + + incrementalRender: function (params, seriesModel, ecModel, api) { + this._isLargeDraw + ? this._incrementalRenderLarge(params, seriesModel) + : this._incrementalRenderNormal(params, seriesModel); + }, + + _updateDrawMode: function (seriesModel) { + var isLargeDraw = seriesModel.pipelineContext.large; + if (this._isLargeDraw == null || isLargeDraw ^ this._isLargeDraw) { + this._isLargeDraw = isLargeDraw; + this._clear(); + } + }, + + _renderNormal: function (seriesModel) { + var data = seriesModel.getData(); + var oldData = this._data; + var group = this.group; + var isSimpleBox = data.getLayout('isSimpleBox'); + + var needsClip = seriesModel.get('clip', true); + var coord = seriesModel.coordinateSystem; + var clipArea = coord.getArea && coord.getArea(); + + // There is no old data only when first rendering or switching from + // stream mode to normal mode, where previous elements should be removed. + if (!this._data) { + group.removeAll(); + } + + data.diff(oldData) + .add(function (newIdx) { + if (data.hasValue(newIdx)) { + var el; + + var itemLayout = data.getItemLayout(newIdx); + + if (needsClip && isNormalBoxClipped(clipArea, itemLayout)) { + return; + } + + el = createNormalBox$1(itemLayout, newIdx, true); + initProps(el, {shape: {points: itemLayout.ends}}, seriesModel, newIdx); + + setBoxCommon(el, data, newIdx, isSimpleBox); + + group.add(el); + + data.setItemGraphicEl(newIdx, el); + } + }) + .update(function (newIdx, oldIdx) { + var el = oldData.getItemGraphicEl(oldIdx); + + // Empty data + if (!data.hasValue(newIdx)) { + group.remove(el); + return; + } + + var itemLayout = data.getItemLayout(newIdx); + if (needsClip && isNormalBoxClipped(clipArea, itemLayout)) { + group.remove(el); + return; + } + + if (!el) { + el = createNormalBox$1(itemLayout, newIdx); + } + else { + updateProps(el, {shape: {points: itemLayout.ends}}, seriesModel, newIdx); + } + + setBoxCommon(el, data, newIdx, isSimpleBox); + + group.add(el); + data.setItemGraphicEl(newIdx, el); + }) + .remove(function (oldIdx) { + var el = oldData.getItemGraphicEl(oldIdx); + el && group.remove(el); + }) + .execute(); + + this._data = data; + }, + + _renderLarge: function (seriesModel) { + this._clear(); + + createLarge$1(seriesModel, this.group); + + var clipPath = seriesModel.get('clip', true) + ? createClipPath(seriesModel.coordinateSystem, false, seriesModel) + : null; + if (clipPath) { + this.group.setClipPath(clipPath); + } + else { + this.group.removeClipPath(); + } + + }, + + _incrementalRenderNormal: function (params, seriesModel) { + var data = seriesModel.getData(); + var isSimpleBox = data.getLayout('isSimpleBox'); + + var dataIndex; + while ((dataIndex = params.next()) != null) { + var el; + + var itemLayout = data.getItemLayout(dataIndex); + el = createNormalBox$1(itemLayout, dataIndex); + setBoxCommon(el, data, dataIndex, isSimpleBox); + + el.incremental = true; + this.group.add(el); + } + }, + + _incrementalRenderLarge: function (params, seriesModel) { + createLarge$1(seriesModel, this.group, true); + }, + + remove: function (ecModel) { + this._clear(); + }, + + _clear: function () { + this.group.removeAll(); + this._data = null; + }, + + dispose: noop + +}); + + +var NormalBoxPath = Path.extend({ + + type: 'normalCandlestickBox', + + shape: {}, + + buildPath: function (ctx, shape) { + var ends = shape.points; + + if (this.__simpleBox) { + ctx.moveTo(ends[4][0], ends[4][1]); + ctx.lineTo(ends[6][0], ends[6][1]); + } + else { + ctx.moveTo(ends[0][0], ends[0][1]); + ctx.lineTo(ends[1][0], ends[1][1]); + ctx.lineTo(ends[2][0], ends[2][1]); + ctx.lineTo(ends[3][0], ends[3][1]); + ctx.closePath(); + + ctx.moveTo(ends[4][0], ends[4][1]); + ctx.lineTo(ends[5][0], ends[5][1]); + ctx.moveTo(ends[6][0], ends[6][1]); + ctx.lineTo(ends[7][0], ends[7][1]); + } + } +}); + + +function createNormalBox$1(itemLayout, dataIndex, isInit) { + var ends = itemLayout.ends; + return new NormalBoxPath({ + shape: { + points: isInit + ? transInit$1(ends, itemLayout) + : ends + }, + z2: 100 + }); +} + +function isNormalBoxClipped(clipArea, itemLayout) { + var clipped = true; + for (var i = 0; i < itemLayout.ends.length; i++) { + // If any point are in the region. + if (clipArea.contain(itemLayout.ends[i][0], itemLayout.ends[i][1])) { + clipped = false; + break; + } + } + return clipped; +} + +function setBoxCommon(el, data, dataIndex, isSimpleBox) { + var itemModel = data.getItemModel(dataIndex); + var normalItemStyleModel = itemModel.getModel(NORMAL_ITEM_STYLE_PATH$1); + var color = data.getItemVisual(dataIndex, 'color'); + var borderColor = data.getItemVisual(dataIndex, 'borderColor') || color; + + // Color must be excluded. + // Because symbol provide setColor individually to set fill and stroke + var itemStyle = normalItemStyleModel.getItemStyle(SKIP_PROPS); + + el.useStyle(itemStyle); + el.style.strokeNoScale = true; + el.style.fill = color; + el.style.stroke = borderColor; + + el.__simpleBox = isSimpleBox; + + var hoverStyle = itemModel.getModel(EMPHASIS_ITEM_STYLE_PATH$1).getItemStyle(); + setHoverStyle(el, hoverStyle); +} + +function transInit$1(points, itemLayout) { + return map(points, function (point) { + point = point.slice(); + point[1] = itemLayout.initBaseline; + return point; + }); +} + + + +var LargeBoxPath = Path.extend({ + + type: 'largeCandlestickBox', + + shape: {}, + + buildPath: function (ctx, shape) { + // Drawing lines is more efficient than drawing + // a whole line or drawing rects. + var points = shape.points; + for (var i = 0; i < points.length;) { + if (this.__sign === points[i++]) { + var x = points[i++]; + ctx.moveTo(x, points[i++]); + ctx.lineTo(x, points[i++]); + } + else { + i += 3; + } + } + } +}); + +function createLarge$1(seriesModel, group, incremental) { + var data = seriesModel.getData(); + var largePoints = data.getLayout('largePoints'); + + var elP = new LargeBoxPath({ + shape: {points: largePoints}, + __sign: 1 + }); + group.add(elP); + var elN = new LargeBoxPath({ + shape: {points: largePoints}, + __sign: -1 + }); + group.add(elN); + + setLargeStyle$1(1, elP, seriesModel, data); + setLargeStyle$1(-1, elN, seriesModel, data); + + if (incremental) { + elP.incremental = true; + elN.incremental = true; + } +} + +function setLargeStyle$1(sign, el, seriesModel, data) { + var suffix = sign > 0 ? 'P' : 'N'; + var borderColor = data.getVisual('borderColor' + suffix) + || data.getVisual('color' + suffix); + + // Color must be excluded. + // Because symbol provide setColor individually to set fill and stroke + var itemStyle = seriesModel.getModel(NORMAL_ITEM_STYLE_PATH$1).getItemStyle(SKIP_PROPS); + + el.useStyle(itemStyle); + el.style.fill = null; + el.style.stroke = borderColor; + // No different + // el.style.lineWidth = .5; +} + +/* +* 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. +*/ + +var preprocessor = function (option) { + if (!option || !isArray(option.series)) { + return; + } + + // Translate 'k' to 'candlestick'. + each$1(option.series, function (seriesItem) { + if (isObject$1(seriesItem) && seriesItem.type === 'k') { + seriesItem.type = 'candlestick'; + } + }); +}; + +/* +* 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. +*/ + +var positiveBorderColorQuery = ['itemStyle', 'borderColor']; +var negativeBorderColorQuery = ['itemStyle', 'borderColor0']; +var positiveColorQuery = ['itemStyle', 'color']; +var negativeColorQuery = ['itemStyle', 'color0']; + +var candlestickVisual = { + + seriesType: 'candlestick', + + plan: createRenderPlanner(), + + // For legend. + performRawSeries: true, + + reset: function (seriesModel, ecModel) { + + var data = seriesModel.getData(); + var isLargeRender = seriesModel.pipelineContext.large; + + data.setVisual({ + legendSymbol: 'roundRect', + colorP: getColor(1, seriesModel), + colorN: getColor(-1, seriesModel), + borderColorP: getBorderColor(1, seriesModel), + borderColorN: getBorderColor(-1, seriesModel) + }); + + // Only visible series has each data be visual encoded + if (ecModel.isSeriesFiltered(seriesModel)) { + return; + } + + return !isLargeRender && {progress: progress}; + + + function progress(params, data) { + var dataIndex; + while ((dataIndex = params.next()) != null) { + var itemModel = data.getItemModel(dataIndex); + var sign = data.getItemLayout(dataIndex).sign; + + data.setItemVisual( + dataIndex, + { + color: getColor(sign, itemModel), + borderColor: getBorderColor(sign, itemModel) + } + ); + } + } + + function getColor(sign, model) { + return model.get( + sign > 0 ? positiveColorQuery : negativeColorQuery + ); + } + + function getBorderColor(sign, model) { + return model.get( + sign > 0 ? positiveBorderColorQuery : negativeBorderColorQuery + ); + } + + } + +}; + +/* +* 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. +*/ + +/* global Float32Array */ + +var LargeArr$1 = typeof Float32Array !== 'undefined' ? Float32Array : Array; + +var candlestickLayout = { + + seriesType: 'candlestick', + + plan: createRenderPlanner(), + + reset: function (seriesModel) { + + var coordSys = seriesModel.coordinateSystem; + var data = seriesModel.getData(); + var candleWidth = calculateCandleWidth(seriesModel, data); + var cDimIdx = 0; + var vDimIdx = 1; + var coordDims = ['x', 'y']; + var cDim = data.mapDimension(coordDims[cDimIdx]); + var vDims = data.mapDimension(coordDims[vDimIdx], true); + var openDim = vDims[0]; + var closeDim = vDims[1]; + var lowestDim = vDims[2]; + var highestDim = vDims[3]; + + data.setLayout({ + candleWidth: candleWidth, + // The value is experimented visually. + isSimpleBox: candleWidth <= 1.3 + }); + + if (cDim == null || vDims.length < 4) { + return; + } + + return { + progress: seriesModel.pipelineContext.large + ? largeProgress : normalProgress + }; + + function normalProgress(params, data) { + var dataIndex; + while ((dataIndex = params.next()) != null) { + + var axisDimVal = data.get(cDim, dataIndex); + var openVal = data.get(openDim, dataIndex); + var closeVal = data.get(closeDim, dataIndex); + var lowestVal = data.get(lowestDim, dataIndex); + var highestVal = data.get(highestDim, dataIndex); + + var ocLow = Math.min(openVal, closeVal); + var ocHigh = Math.max(openVal, closeVal); + + var ocLowPoint = getPoint(ocLow, axisDimVal); + var ocHighPoint = getPoint(ocHigh, axisDimVal); + var lowestPoint = getPoint(lowestVal, axisDimVal); + var highestPoint = getPoint(highestVal, axisDimVal); + + var ends = []; + addBodyEnd(ends, ocHighPoint, 0); + addBodyEnd(ends, ocLowPoint, 1); + + ends.push( + subPixelOptimizePoint(highestPoint), + subPixelOptimizePoint(ocHighPoint), + subPixelOptimizePoint(lowestPoint), + subPixelOptimizePoint(ocLowPoint) + ); + + data.setItemLayout(dataIndex, { + sign: getSign(data, dataIndex, openVal, closeVal, closeDim), + initBaseline: openVal > closeVal + ? ocHighPoint[vDimIdx] : ocLowPoint[vDimIdx], // open point. + ends: ends, + brushRect: makeBrushRect(lowestVal, highestVal, axisDimVal) + }); + } + + function getPoint(val, axisDimVal) { + var p = []; + p[cDimIdx] = axisDimVal; + p[vDimIdx] = val; + return (isNaN(axisDimVal) || isNaN(val)) + ? [NaN, NaN] + : coordSys.dataToPoint(p); + } + + function addBodyEnd(ends, point, start) { + var point1 = point.slice(); + var point2 = point.slice(); + + point1[cDimIdx] = subPixelOptimize( + point1[cDimIdx] + candleWidth / 2, 1, false + ); + point2[cDimIdx] = subPixelOptimize( + point2[cDimIdx] - candleWidth / 2, 1, true + ); + + start + ? ends.push(point1, point2) + : ends.push(point2, point1); + } + + function makeBrushRect(lowestVal, highestVal, axisDimVal) { + var pmin = getPoint(lowestVal, axisDimVal); + var pmax = getPoint(highestVal, axisDimVal); + + pmin[cDimIdx] -= candleWidth / 2; + pmax[cDimIdx] -= candleWidth / 2; + + return { + x: pmin[0], + y: pmin[1], + width: vDimIdx ? candleWidth : pmax[0] - pmin[0], + height: vDimIdx ? pmax[1] - pmin[1] : candleWidth + }; + } + + function subPixelOptimizePoint(point) { + point[cDimIdx] = subPixelOptimize(point[cDimIdx], 1); + return point; + } + } + + function largeProgress(params, data) { + // Structure: [sign, x, yhigh, ylow, sign, x, yhigh, ylow, ...] + var points = new LargeArr$1(params.count * 4); + var offset = 0; + var point; + var tmpIn = []; + var tmpOut = []; + var dataIndex; + + while ((dataIndex = params.next()) != null) { + var axisDimVal = data.get(cDim, dataIndex); + var openVal = data.get(openDim, dataIndex); + var closeVal = data.get(closeDim, dataIndex); + var lowestVal = data.get(lowestDim, dataIndex); + var highestVal = data.get(highestDim, dataIndex); + + if (isNaN(axisDimVal) || isNaN(lowestVal) || isNaN(highestVal)) { + points[offset++] = NaN; + offset += 3; + continue; + } + + points[offset++] = getSign(data, dataIndex, openVal, closeVal, closeDim); + + tmpIn[cDimIdx] = axisDimVal; + + tmpIn[vDimIdx] = lowestVal; + point = coordSys.dataToPoint(tmpIn, null, tmpOut); + points[offset++] = point ? point[0] : NaN; + points[offset++] = point ? point[1] : NaN; + tmpIn[vDimIdx] = highestVal; + point = coordSys.dataToPoint(tmpIn, null, tmpOut); + points[offset++] = point ? point[1] : NaN; + } + + data.setLayout('largePoints', points); + } + } +}; + +function getSign(data, dataIndex, openVal, closeVal, closeDim) { + var sign; + if (openVal > closeVal) { + sign = -1; + } + else if (openVal < closeVal) { + sign = 1; + } + else { + sign = dataIndex > 0 + // If close === open, compare with close of last record + ? (data.get(closeDim, dataIndex - 1) <= closeVal ? 1 : -1) + // No record of previous, set to be positive + : 1; + } + + return sign; +} + +function calculateCandleWidth(seriesModel, data) { + var baseAxis = seriesModel.getBaseAxis(); + var extent; + + var bandWidth = baseAxis.type === 'category' + ? baseAxis.getBandWidth() + : ( + extent = baseAxis.getExtent(), + Math.abs(extent[1] - extent[0]) / data.count() + ); + + var barMaxWidth = parsePercent$1( + retrieve2(seriesModel.get('barMaxWidth'), bandWidth), + bandWidth + ); + var barMinWidth = parsePercent$1( + retrieve2(seriesModel.get('barMinWidth'), 1), + bandWidth + ); + var barWidth = seriesModel.get('barWidth'); + + return barWidth != null + ? parsePercent$1(barWidth, bandWidth) + // Put max outer to ensure bar visible in spite of overlap. + : Math.max(Math.min(bandWidth / 2, barMaxWidth), barMinWidth); +} + +/* +* 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. +*/ + +registerPreprocessor(preprocessor); +registerVisual(candlestickVisual); +registerLayout(candlestickLayout); + +/* +* 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. +*/ + +SeriesModel.extend({ + + type: 'series.effectScatter', + + dependencies: ['grid', 'polar'], + + getInitialData: function (option, ecModel) { + return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true}); + }, + + brushSelector: 'point', + + defaultOption: { + coordinateSystem: 'cartesian2d', + zlevel: 0, + z: 2, + legendHoverLink: true, + + effectType: 'ripple', + + progressive: 0, + + // When to show the effect, option: 'render'|'emphasis' + showEffectOn: 'render', + + // Ripple effect config + rippleEffect: { + period: 4, + // Scale of ripple + scale: 2.5, + // Brush type can be fill or stroke + brushType: 'fill' + }, + + // Cartesian coordinate system + // xAxisIndex: 0, + // yAxisIndex: 0, + + // Polar coordinate system + // polarIndex: 0, + + // Geo coordinate system + // geoIndex: 0, + + // symbol: null, // 图形类型 + symbolSize: 10 // 图形大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2 + // symbolRotate: null, // 图形旋转控制 + + // large: false, + // Available when large is true + // largeThreshold: 2000, + + // itemStyle: { + // opacity: 1 + // } + } + +}); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/** + * Symbol with ripple effect + * @module echarts/chart/helper/EffectSymbol + */ + +var EFFECT_RIPPLE_NUMBER = 3; + +function normalizeSymbolSize$1(symbolSize) { + if (!isArray(symbolSize)) { + symbolSize = [+symbolSize, +symbolSize]; + } + return symbolSize; +} + +function updateRipplePath(rippleGroup, effectCfg) { + var color = effectCfg.rippleEffectColor || effectCfg.color; + rippleGroup.eachChild(function (ripplePath) { + ripplePath.attr({ + z: effectCfg.z, + zlevel: effectCfg.zlevel, + style: { + stroke: effectCfg.brushType === 'stroke' ? color : null, + fill: effectCfg.brushType === 'fill' ? color : null + } + }); + }); +} +/** + * @constructor + * @param {module:echarts/data/List} data + * @param {number} idx + * @extends {module:zrender/graphic/Group} + */ +function EffectSymbol(data, idx) { + Group.call(this); + + var symbol = new SymbolClz$1(data, idx); + var rippleGroup = new Group(); + this.add(symbol); + this.add(rippleGroup); + + rippleGroup.beforeUpdate = function () { + this.attr(symbol.getScale()); + }; + this.updateData(data, idx); +} + +var effectSymbolProto = EffectSymbol.prototype; + +effectSymbolProto.stopEffectAnimation = function () { + this.childAt(1).removeAll(); +}; + +effectSymbolProto.startEffectAnimation = function (effectCfg) { + var symbolType = effectCfg.symbolType; + var color = effectCfg.color; + var rippleGroup = this.childAt(1); + + for (var i = 0; i < EFFECT_RIPPLE_NUMBER; i++) { + // If width/height are set too small (e.g., set to 1) on ios10 + // and macOS Sierra, a circle stroke become a rect, no matter what + // the scale is set. So we set width/height as 2. See #4136. + var ripplePath = createSymbol( + symbolType, -1, -1, 2, 2, color + ); + ripplePath.attr({ + style: { + strokeNoScale: true + }, + z2: 99, + silent: true, + scale: [0.5, 0.5] + }); + + var delay = -i / EFFECT_RIPPLE_NUMBER * effectCfg.period + effectCfg.effectOffset; + // TODO Configurable effectCfg.period + ripplePath.animate('', true) + .when(effectCfg.period, { + scale: [effectCfg.rippleScale / 2, effectCfg.rippleScale / 2] + }) + .delay(delay) + .start(); + ripplePath.animateStyle(true) + .when(effectCfg.period, { + opacity: 0 + }) + .delay(delay) + .start(); + + rippleGroup.add(ripplePath); + } + + updateRipplePath(rippleGroup, effectCfg); +}; + +/** + * Update effect symbol + */ +effectSymbolProto.updateEffectAnimation = function (effectCfg) { + var oldEffectCfg = this._effectCfg; + var rippleGroup = this.childAt(1); + + // Must reinitialize effect if following configuration changed + var DIFFICULT_PROPS = ['symbolType', 'period', 'rippleScale']; + for (var i = 0; i < DIFFICULT_PROPS.length; i++) { + var propName = DIFFICULT_PROPS[i]; + if (oldEffectCfg[propName] !== effectCfg[propName]) { + this.stopEffectAnimation(); + this.startEffectAnimation(effectCfg); + return; + } + } + + updateRipplePath(rippleGroup, effectCfg); +}; + +/** + * Highlight symbol + */ +effectSymbolProto.highlight = function () { + this.trigger('emphasis'); +}; + +/** + * Downplay symbol + */ +effectSymbolProto.downplay = function () { + this.trigger('normal'); +}; + +/** + * Update symbol properties + * @param {module:echarts/data/List} data + * @param {number} idx + */ +effectSymbolProto.updateData = function (data, idx) { + var seriesModel = data.hostModel; + + this.childAt(0).updateData(data, idx); + + var rippleGroup = this.childAt(1); + var itemModel = data.getItemModel(idx); + var symbolType = data.getItemVisual(idx, 'symbol'); + var symbolSize = normalizeSymbolSize$1(data.getItemVisual(idx, 'symbolSize')); + var color = data.getItemVisual(idx, 'color'); + + rippleGroup.attr('scale', symbolSize); + + rippleGroup.traverse(function (ripplePath) { + ripplePath.attr({ + fill: color + }); + }); + + var symbolOffset = itemModel.getShallow('symbolOffset'); + if (symbolOffset) { + var pos = rippleGroup.position; + pos[0] = parsePercent$1(symbolOffset[0], symbolSize[0]); + pos[1] = parsePercent$1(symbolOffset[1], symbolSize[1]); + } + rippleGroup.rotation = (itemModel.getShallow('symbolRotate') || 0) * Math.PI / 180 || 0; + + var effectCfg = {}; + + effectCfg.showEffectOn = seriesModel.get('showEffectOn'); + effectCfg.rippleScale = itemModel.get('rippleEffect.scale'); + effectCfg.brushType = itemModel.get('rippleEffect.brushType'); + effectCfg.period = itemModel.get('rippleEffect.period') * 1000; + effectCfg.effectOffset = idx / data.count(); + effectCfg.z = itemModel.getShallow('z') || 0; + effectCfg.zlevel = itemModel.getShallow('zlevel') || 0; + effectCfg.symbolType = symbolType; + effectCfg.color = color; + effectCfg.rippleEffectColor = itemModel.get('rippleEffect.color'); + + this.off('mouseover').off('mouseout').off('emphasis').off('normal'); + + if (effectCfg.showEffectOn === 'render') { + this._effectCfg + ? this.updateEffectAnimation(effectCfg) + : this.startEffectAnimation(effectCfg); + + this._effectCfg = effectCfg; + } + else { + // Not keep old effect config + this._effectCfg = null; + + this.stopEffectAnimation(); + var symbol = this.childAt(0); + var onEmphasis = function () { + symbol.highlight(); + if (effectCfg.showEffectOn !== 'render') { + this.startEffectAnimation(effectCfg); + } + }; + var onNormal = function () { + symbol.downplay(); + if (effectCfg.showEffectOn !== 'render') { + this.stopEffectAnimation(); + } + }; + this.on('mouseover', onEmphasis, this) + .on('mouseout', onNormal, this) + .on('emphasis', onEmphasis, this) + .on('normal', onNormal, this); + } + + this._effectCfg = effectCfg; +}; + +effectSymbolProto.fadeOut = function (cb) { + this.off('mouseover').off('mouseout').off('emphasis').off('normal'); + cb && cb(); +}; + +inherits(EffectSymbol, Group); + +/* +* 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. +*/ + +extendChartView({ + + type: 'effectScatter', + + init: function () { + this._symbolDraw = new SymbolDraw(EffectSymbol); + }, + + render: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + var effectSymbolDraw = this._symbolDraw; + effectSymbolDraw.updateData(data); + this.group.add(effectSymbolDraw.group); + }, + + updateTransform: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + + this.group.dirty(); + + var res = pointsLayout().reset(seriesModel); + if (res.progress) { + res.progress({ start: 0, end: data.count() }, data); + } + + this._symbolDraw.updateLayout(data); + }, + + _updateGroupTransform: function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.getRoamTransform) { + this.group.transform = clone$2(coordSys.getRoamTransform()); + this.group.decomposeTransform(); + } + }, + + remove: function (ecModel, api) { + this._symbolDraw && this._symbolDraw.remove(api); + }, + + dispose: function () {} +}); + +/* +* 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. +*/ + +registerVisual(visualSymbol('effectScatter', 'circle')); +registerLayout(pointsLayout('effectScatter')); + +/* +* 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. +*/ + +/* global Uint32Array, Float64Array, Float32Array */ + +var Uint32Arr = typeof Uint32Array === 'undefined' ? Array : Uint32Array; +var Float64Arr = typeof Float64Array === 'undefined' ? Array : Float64Array; + +function compatEc2(seriesOpt) { + var data = seriesOpt.data; + if (data && data[0] && data[0][0] && data[0][0].coord) { + if (__DEV__) { + console.warn('Lines data configuration has been changed to' + + ' { coords:[[1,2],[2,3]] }'); + } + seriesOpt.data = map(data, function (itemOpt) { + var coords = [ + itemOpt[0].coord, itemOpt[1].coord + ]; + var target = { + coords: coords + }; + if (itemOpt[0].name) { + target.fromName = itemOpt[0].name; + } + if (itemOpt[1].name) { + target.toName = itemOpt[1].name; + } + return mergeAll([target, itemOpt[0], itemOpt[1]]); + }); + } +} + +var LinesSeries = SeriesModel.extend({ + + type: 'series.lines', + + dependencies: ['grid', 'polar'], + + visualColorAccessPath: 'lineStyle.color', + + init: function (option) { + // The input data may be null/undefined. + option.data = option.data || []; + + // Not using preprocessor because mergeOption may not have series.type + compatEc2(option); + + var result = this._processFlatCoordsArray(option.data); + this._flatCoords = result.flatCoords; + this._flatCoordsOffset = result.flatCoordsOffset; + if (result.flatCoords) { + option.data = new Float32Array(result.count); + } + + LinesSeries.superApply(this, 'init', arguments); + }, + + mergeOption: function (option) { + // The input data may be null/undefined. + option.data = option.data || []; + + compatEc2(option); + + if (option.data) { + // Only update when have option data to merge. + var result = this._processFlatCoordsArray(option.data); + this._flatCoords = result.flatCoords; + this._flatCoordsOffset = result.flatCoordsOffset; + if (result.flatCoords) { + option.data = new Float32Array(result.count); + } + } + + LinesSeries.superApply(this, 'mergeOption', arguments); + }, + + appendData: function (params) { + var result = this._processFlatCoordsArray(params.data); + if (result.flatCoords) { + if (!this._flatCoords) { + this._flatCoords = result.flatCoords; + this._flatCoordsOffset = result.flatCoordsOffset; + } + else { + this._flatCoords = concatArray(this._flatCoords, result.flatCoords); + this._flatCoordsOffset = concatArray(this._flatCoordsOffset, result.flatCoordsOffset); + } + params.data = new Float32Array(result.count); + } + + this.getRawData().appendData(params.data); + }, + + _getCoordsFromItemModel: function (idx) { + var itemModel = this.getData().getItemModel(idx); + var coords = (itemModel.option instanceof Array) + ? itemModel.option : itemModel.getShallow('coords'); + + if (__DEV__) { + if (!(coords instanceof Array && coords.length > 0 && coords[0] instanceof Array)) { + throw new Error( + 'Invalid coords ' + JSON.stringify(coords) + '. Lines must have 2d coords array in data item.' + ); + } + } + return coords; + }, + + getLineCoordsCount: function (idx) { + if (this._flatCoordsOffset) { + return this._flatCoordsOffset[idx * 2 + 1]; + } + else { + return this._getCoordsFromItemModel(idx).length; + } + }, + + getLineCoords: function (idx, out) { + if (this._flatCoordsOffset) { + var offset = this._flatCoordsOffset[idx * 2]; + var len = this._flatCoordsOffset[idx * 2 + 1]; + for (var i = 0; i < len; i++) { + out[i] = out[i] || []; + out[i][0] = this._flatCoords[offset + i * 2]; + out[i][1] = this._flatCoords[offset + i * 2 + 1]; + } + return len; + } + else { + var coords = this._getCoordsFromItemModel(idx); + for (var i = 0; i < coords.length; i++) { + out[i] = out[i] || []; + out[i][0] = coords[i][0]; + out[i][1] = coords[i][1]; + } + return coords.length; + } + }, + + _processFlatCoordsArray: function (data) { + var startOffset = 0; + if (this._flatCoords) { + startOffset = this._flatCoords.length; + } + // Stored as a typed array. In format + // Points Count(2) | x | y | x | y | Points Count(3) | x | y | x | y | x | y | + if (typeof data[0] === 'number') { + var len = data.length; + // Store offset and len of each segment + var coordsOffsetAndLenStorage = new Uint32Arr(len); + var coordsStorage = new Float64Arr(len); + var coordsCursor = 0; + var offsetCursor = 0; + var dataCount = 0; + for (var i = 0; i < len;) { + dataCount++; + var count = data[i++]; + // Offset + coordsOffsetAndLenStorage[offsetCursor++] = coordsCursor + startOffset; + // Len + coordsOffsetAndLenStorage[offsetCursor++] = count; + for (var k = 0; k < count; k++) { + var x = data[i++]; + var y = data[i++]; + coordsStorage[coordsCursor++] = x; + coordsStorage[coordsCursor++] = y; + + if (i > len) { + if (__DEV__) { + throw new Error('Invalid data format.'); + } + } + } + } + + return { + flatCoordsOffset: new Uint32Array(coordsOffsetAndLenStorage.buffer, 0, offsetCursor), + flatCoords: coordsStorage, + count: dataCount + }; + } + + return { + flatCoordsOffset: null, + flatCoords: null, + count: data.length + }; + }, + + getInitialData: function (option, ecModel) { + if (__DEV__) { + var CoordSys = CoordinateSystemManager.get(option.coordinateSystem); + if (!CoordSys) { + throw new Error('Unkown coordinate system ' + option.coordinateSystem); + } + } + + var lineData = new List(['value'], this); + lineData.hasItemOption = false; + + lineData.initData(option.data, [], function (dataItem, dimName, dataIndex, dimIndex) { + // dataItem is simply coords + if (dataItem instanceof Array) { + return NaN; + } + else { + lineData.hasItemOption = true; + var value = dataItem.value; + if (value != null) { + return value instanceof Array ? value[dimIndex] : value; + } + } + }); + + return lineData; + }, + + formatTooltip: function (dataIndex) { + var data = this.getData(); + var itemModel = data.getItemModel(dataIndex); + var name = itemModel.get('name'); + if (name) { + return name; + } + var fromName = itemModel.get('fromName'); + var toName = itemModel.get('toName'); + var html = []; + fromName != null && html.push(fromName); + toName != null && html.push(toName); + + return encodeHTML(html.join(' > ')); + }, + + preventIncremental: function () { + return !!this.get('effect.show'); + }, + + getProgressive: function () { + var progressive = this.option.progressive; + if (progressive == null) { + return this.option.large ? 1e4 : this.get('progressive'); + } + return progressive; + }, + + getProgressiveThreshold: function () { + var progressiveThreshold = this.option.progressiveThreshold; + if (progressiveThreshold == null) { + return this.option.large ? 2e4 : this.get('progressiveThreshold'); + } + return progressiveThreshold; + }, + + defaultOption: { + coordinateSystem: 'geo', + zlevel: 0, + z: 2, + legendHoverLink: true, + + hoverAnimation: true, + // Cartesian coordinate system + xAxisIndex: 0, + yAxisIndex: 0, + + symbol: ['none', 'none'], + symbolSize: [10, 10], + // Geo coordinate system + geoIndex: 0, + + effect: { + show: false, + period: 4, + // Animation delay. support callback + // delay: 0, + // If move with constant speed px/sec + // period will be ignored if this property is > 0, + constantSpeed: 0, + symbol: 'circle', + symbolSize: 3, + loop: true, + // Length of trail, 0 - 1 + trailLength: 0.2 + // Same with lineStyle.color + // color + }, + + large: false, + // Available when large is true + largeThreshold: 2000, + + // If lines are polyline + // polyline not support curveness, label, animation + polyline: false, + + // If clip the overflow. + // Available when coordinateSystem is cartesian or polar. + clip: true, + + label: { + show: false, + position: 'end' + // distance: 5, + // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调 + }, + + lineStyle: { + opacity: 0.5 + } + } +}); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/** + * Provide effect for line + * @module echarts/chart/helper/EffectLine + */ + +/** + * @constructor + * @extends {module:zrender/graphic/Group} + * @alias {module:echarts/chart/helper/Line} + */ +function EffectLine(lineData, idx, seriesScope) { + Group.call(this); + + this.add(this.createLine(lineData, idx, seriesScope)); + + this._updateEffectSymbol(lineData, idx); +} + +var effectLineProto = EffectLine.prototype; + +effectLineProto.createLine = function (lineData, idx, seriesScope) { + return new Line$1(lineData, idx, seriesScope); +}; + +effectLineProto._updateEffectSymbol = function (lineData, idx) { + var itemModel = lineData.getItemModel(idx); + var effectModel = itemModel.getModel('effect'); + var size = effectModel.get('symbolSize'); + var symbolType = effectModel.get('symbol'); + if (!isArray(size)) { + size = [size, size]; + } + var color = effectModel.get('color') || lineData.getItemVisual(idx, 'color'); + var symbol = this.childAt(1); + + if (this._symbolType !== symbolType) { + // Remove previous + this.remove(symbol); + + symbol = createSymbol( + symbolType, -0.5, -0.5, 1, 1, color + ); + symbol.z2 = 100; + symbol.culling = true; + + this.add(symbol); + } + + // Symbol may be removed if loop is false + if (!symbol) { + return; + } + + // Shadow color is same with color in default + symbol.setStyle('shadowColor', color); + symbol.setStyle(effectModel.getItemStyle(['color'])); + + symbol.attr('scale', size); + + symbol.setColor(color); + symbol.attr('scale', size); + + this._symbolType = symbolType; + + this._updateEffectAnimation(lineData, effectModel, idx); +}; + +effectLineProto._updateEffectAnimation = function (lineData, effectModel, idx) { + + var symbol = this.childAt(1); + if (!symbol) { + return; + } + + var self = this; + + var points = lineData.getItemLayout(idx); + + var period = effectModel.get('period') * 1000; + var loop = effectModel.get('loop'); + var constantSpeed = effectModel.get('constantSpeed'); + var delayExpr = retrieve(effectModel.get('delay'), function (idx) { + return idx / lineData.count() * period / 3; + }); + var isDelayFunc = typeof delayExpr === 'function'; + + // Ignore when updating + symbol.ignore = true; + + this.updateAnimationPoints(symbol, points); + + if (constantSpeed > 0) { + period = this.getLineLength(symbol) / constantSpeed * 1000; + } + + if (period !== this._period || loop !== this._loop) { + + symbol.stopAnimation(); + + var delay = delayExpr; + if (isDelayFunc) { + delay = delayExpr(idx); + } + if (symbol.__t > 0) { + delay = -period * symbol.__t; + } + symbol.__t = 0; + var animator = symbol.animate('', loop) + .when(period, { + __t: 1 + }) + .delay(delay) + .during(function () { + self.updateSymbolPosition(symbol); + }); + if (!loop) { + animator.done(function () { + self.remove(symbol); + }); + } + animator.start(); + } + + this._period = period; + this._loop = loop; +}; + +effectLineProto.getLineLength = function (symbol) { + // Not so accurate + return (dist(symbol.__p1, symbol.__cp1) + + dist(symbol.__cp1, symbol.__p2)); +}; + +effectLineProto.updateAnimationPoints = function (symbol, points) { + symbol.__p1 = points[0]; + symbol.__p2 = points[1]; + symbol.__cp1 = points[2] || [ + (points[0][0] + points[1][0]) / 2, + (points[0][1] + points[1][1]) / 2 + ]; +}; + +effectLineProto.updateData = function (lineData, idx, seriesScope) { + this.childAt(0).updateData(lineData, idx, seriesScope); + this._updateEffectSymbol(lineData, idx); +}; + +effectLineProto.updateSymbolPosition = function (symbol) { + var p1 = symbol.__p1; + var p2 = symbol.__p2; + var cp1 = symbol.__cp1; + var t = symbol.__t; + var pos = symbol.position; + var quadraticAt$$1 = quadraticAt; + var quadraticDerivativeAt$$1 = quadraticDerivativeAt; + pos[0] = quadraticAt$$1(p1[0], cp1[0], p2[0], t); + pos[1] = quadraticAt$$1(p1[1], cp1[1], p2[1], t); + + // Tangent + var tx = quadraticDerivativeAt$$1(p1[0], cp1[0], p2[0], t); + var ty = quadraticDerivativeAt$$1(p1[1], cp1[1], p2[1], t); + + symbol.rotation = -Math.atan2(ty, tx) - Math.PI / 2; + + symbol.ignore = false; +}; + + +effectLineProto.updateLayout = function (lineData, idx) { + this.childAt(0).updateLayout(lineData, idx); + + var effectModel = lineData.getItemModel(idx).getModel('effect'); + this._updateEffectAnimation(lineData, effectModel, idx); +}; + +inherits(EffectLine, Group); + +/* +* 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. +*/ + +/** + * @module echarts/chart/helper/Line + */ + +/** + * @constructor + * @extends {module:zrender/graphic/Group} + * @alias {module:echarts/chart/helper/Polyline} + */ +function Polyline$2(lineData, idx, seriesScope) { + Group.call(this); + + this._createPolyline(lineData, idx, seriesScope); +} + +var polylineProto = Polyline$2.prototype; + +polylineProto._createPolyline = function (lineData, idx, seriesScope) { + // var seriesModel = lineData.hostModel; + var points = lineData.getItemLayout(idx); + + var line = new Polyline({ + shape: { + points: points + } + }); + + this.add(line); + + this._updateCommonStl(lineData, idx, seriesScope); +}; + +polylineProto.updateData = function (lineData, idx, seriesScope) { + var seriesModel = lineData.hostModel; + + var line = this.childAt(0); + var target = { + shape: { + points: lineData.getItemLayout(idx) + } + }; + updateProps(line, target, seriesModel, idx); + + this._updateCommonStl(lineData, idx, seriesScope); +}; + +polylineProto._updateCommonStl = function (lineData, idx, seriesScope) { + var line = this.childAt(0); + var itemModel = lineData.getItemModel(idx); + + var visualColor = lineData.getItemVisual(idx, 'color'); + + var lineStyle = seriesScope && seriesScope.lineStyle; + var hoverLineStyle = seriesScope && seriesScope.hoverLineStyle; + + if (!seriesScope || lineData.hasItemOption) { + lineStyle = itemModel.getModel('lineStyle').getLineStyle(); + hoverLineStyle = itemModel.getModel('emphasis.lineStyle').getLineStyle(); + } + line.useStyle(defaults( + { + strokeNoScale: true, + fill: 'none', + stroke: visualColor + }, + lineStyle + )); + line.hoverStyle = hoverLineStyle; + + setHoverStyle(this); +}; + +polylineProto.updateLayout = function (lineData, idx) { + var polyline = this.childAt(0); + polyline.setShape('points', lineData.getItemLayout(idx)); +}; + +inherits(Polyline$2, Group); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +/** + * Provide effect for line + * @module echarts/chart/helper/EffectLine + */ + +/** + * @constructor + * @extends {module:echarts/chart/helper/EffectLine} + * @alias {module:echarts/chart/helper/Polyline} + */ +function EffectPolyline(lineData, idx, seriesScope) { + EffectLine.call(this, lineData, idx, seriesScope); + this._lastFrame = 0; + this._lastFramePercent = 0; +} + +var effectPolylineProto = EffectPolyline.prototype; + +// Overwrite +effectPolylineProto.createLine = function (lineData, idx, seriesScope) { + return new Polyline$2(lineData, idx, seriesScope); +}; + +// Overwrite +effectPolylineProto.updateAnimationPoints = function (symbol, points) { + this._points = points; + var accLenArr = [0]; + var len$$1 = 0; + for (var i = 1; i < points.length; i++) { + var p1 = points[i - 1]; + var p2 = points[i]; + len$$1 += dist(p1, p2); + accLenArr.push(len$$1); + } + if (len$$1 === 0) { + return; + } + + for (var i = 0; i < accLenArr.length; i++) { + accLenArr[i] /= len$$1; + } + this._offsets = accLenArr; + this._length = len$$1; +}; + +// Overwrite +effectPolylineProto.getLineLength = function (symbol) { + return this._length; +}; + +// Overwrite +effectPolylineProto.updateSymbolPosition = function (symbol) { + var t = symbol.__t; + var points = this._points; + var offsets = this._offsets; + var len$$1 = points.length; + + if (!offsets) { + // Has length 0 + return; + } + + var lastFrame = this._lastFrame; + var frame; + + if (t < this._lastFramePercent) { + // Start from the next frame + // PENDING start from lastFrame ? + var start = Math.min(lastFrame + 1, len$$1 - 1); + for (frame = start; frame >= 0; frame--) { + if (offsets[frame] <= t) { + break; + } + } + // PENDING really need to do this ? + frame = Math.min(frame, len$$1 - 2); + } + else { + for (var frame = lastFrame; frame < len$$1; frame++) { + if (offsets[frame] > t) { + break; + } + } + frame = Math.min(frame - 1, len$$1 - 2); + } + + lerp( + symbol.position, points[frame], points[frame + 1], + (t - offsets[frame]) / (offsets[frame + 1] - offsets[frame]) + ); + + var tx = points[frame + 1][0] - points[frame][0]; + var ty = points[frame + 1][1] - points[frame][1]; + symbol.rotation = -Math.atan2(ty, tx) - Math.PI / 2; + + this._lastFrame = frame; + this._lastFramePercent = t; + + symbol.ignore = false; +}; + +inherits(EffectPolyline, EffectLine); + +/* +* 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. +*/ + +// TODO Batch by color + +var LargeLineShape = extendShape({ + + shape: { + polyline: false, + curveness: 0, + segs: [] + }, + + buildPath: function (path, shape) { + var segs = shape.segs; + var curveness = shape.curveness; + + if (shape.polyline) { + for (var i = 0; i < segs.length;) { + var count = segs[i++]; + if (count > 0) { + path.moveTo(segs[i++], segs[i++]); + for (var k = 1; k < count; k++) { + path.lineTo(segs[i++], segs[i++]); + } + } + } + } + else { + for (var i = 0; i < segs.length;) { + var x0 = segs[i++]; + var y0 = segs[i++]; + var x1 = segs[i++]; + var y1 = segs[i++]; + path.moveTo(x0, y0); + if (curveness > 0) { + var x2 = (x0 + x1) / 2 - (y0 - y1) * curveness; + var y2 = (y0 + y1) / 2 - (x1 - x0) * curveness; + path.quadraticCurveTo(x2, y2, x1, y1); + } + else { + path.lineTo(x1, y1); + } + } + } + }, + + findDataIndex: function (x, y) { + + var shape = this.shape; + var segs = shape.segs; + var curveness = shape.curveness; + + if (shape.polyline) { + var dataIndex = 0; + for (var i = 0; i < segs.length;) { + var count = segs[i++]; + if (count > 0) { + var x0 = segs[i++]; + var y0 = segs[i++]; + for (var k = 1; k < count; k++) { + var x1 = segs[i++]; + var y1 = segs[i++]; + if (containStroke$1(x0, y0, x1, y1)) { + return dataIndex; + } + } + } + + dataIndex++; + } + } + else { + var dataIndex = 0; + for (var i = 0; i < segs.length;) { + var x0 = segs[i++]; + var y0 = segs[i++]; + var x1 = segs[i++]; + var y1 = segs[i++]; + if (curveness > 0) { + var x2 = (x0 + x1) / 2 - (y0 - y1) * curveness; + var y2 = (y0 + y1) / 2 - (x1 - x0) * curveness; + + if (containStroke$3(x0, y0, x2, y2, x1, y1)) { + return dataIndex; + } + } + else { + if (containStroke$1(x0, y0, x1, y1)) { + return dataIndex; + } + } + + dataIndex++; + } + } + + return -1; + } +}); + +function LargeLineDraw() { + this.group = new Group(); +} + +var largeLineProto = LargeLineDraw.prototype; + +largeLineProto.isPersistent = function () { + return !this._incremental; +}; + +/** + * Update symbols draw by new data + * @param {module:echarts/data/List} data + */ +largeLineProto.updateData = function (data) { + this.group.removeAll(); + + var lineEl = new LargeLineShape({ + rectHover: true, + cursor: 'default' + }); + lineEl.setShape({ + segs: data.getLayout('linesPoints') + }); + + this._setCommon(lineEl, data); + + // Add back + this.group.add(lineEl); + + this._incremental = null; +}; + +/** + * @override + */ +largeLineProto.incrementalPrepareUpdate = function (data) { + this.group.removeAll(); + + this._clearIncremental(); + + if (data.count() > 5e5) { + if (!this._incremental) { + this._incremental = new IncrementalDisplayble({ + silent: true + }); + } + this.group.add(this._incremental); + } + else { + this._incremental = null; + } +}; + +/** + * @override + */ +largeLineProto.incrementalUpdate = function (taskParams, data) { + var lineEl = new LargeLineShape(); + lineEl.setShape({ + segs: data.getLayout('linesPoints') + }); + + this._setCommon(lineEl, data, !!this._incremental); + + if (!this._incremental) { + lineEl.rectHover = true; + lineEl.cursor = 'default'; + lineEl.__startIndex = taskParams.start; + this.group.add(lineEl); + } + else { + this._incremental.addDisplayable(lineEl, true); + } +}; + +/** + * @override + */ +largeLineProto.remove = function () { + this._clearIncremental(); + this._incremental = null; + this.group.removeAll(); +}; + +largeLineProto._setCommon = function (lineEl, data, isIncremental) { + var hostModel = data.hostModel; + + lineEl.setShape({ + polyline: hostModel.get('polyline'), + curveness: hostModel.get('lineStyle.curveness') + }); + + lineEl.useStyle( + hostModel.getModel('lineStyle').getLineStyle() + ); + lineEl.style.strokeNoScale = true; + + var visualColor = data.getVisual('color'); + if (visualColor) { + lineEl.setStyle('stroke', visualColor); + } + lineEl.setStyle('fill'); + + if (!isIncremental) { + // Enable tooltip + // PENDING May have performance issue when path is extremely large + lineEl.seriesIndex = hostModel.seriesIndex; + lineEl.on('mousemove', function (e) { + lineEl.dataIndex = null; + var dataIndex = lineEl.findDataIndex(e.offsetX, e.offsetY); + if (dataIndex > 0) { + // Provide dataIndex for tooltip + lineEl.dataIndex = dataIndex + lineEl.__startIndex; + } + }); + } +}; + +largeLineProto._clearIncremental = function () { + var incremental = this._incremental; + if (incremental) { + incremental.clearDisplaybles(); + } +}; + +/* +* 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. +*/ + +/* global Float32Array */ + +var linesLayout = { + seriesType: 'lines', + + plan: createRenderPlanner(), + + reset: function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + var isPolyline = seriesModel.get('polyline'); + var isLarge = seriesModel.pipelineContext.large; + + function progress(params, lineData) { + var lineCoords = []; + if (isLarge) { + var points; + var segCount = params.end - params.start; + if (isPolyline) { + var totalCoordsCount = 0; + for (var i = params.start; i < params.end; i++) { + totalCoordsCount += seriesModel.getLineCoordsCount(i); + } + points = new Float32Array(segCount + totalCoordsCount * 2); + } + else { + points = new Float32Array(segCount * 4); + } + + var offset = 0; + var pt = []; + for (var i = params.start; i < params.end; i++) { + var len = seriesModel.getLineCoords(i, lineCoords); + if (isPolyline) { + points[offset++] = len; + } + for (var k = 0; k < len; k++) { + pt = coordSys.dataToPoint(lineCoords[k], false, pt); + points[offset++] = pt[0]; + points[offset++] = pt[1]; + } + } + + lineData.setLayout('linesPoints', points); + } + else { + for (var i = params.start; i < params.end; i++) { + var itemModel = lineData.getItemModel(i); + var len = seriesModel.getLineCoords(i, lineCoords); + + var pts = []; + if (isPolyline) { + for (var j = 0; j < len; j++) { + pts.push(coordSys.dataToPoint(lineCoords[j])); + } + } + else { + pts[0] = coordSys.dataToPoint(lineCoords[0]); + pts[1] = coordSys.dataToPoint(lineCoords[1]); + + var curveness = itemModel.get('lineStyle.curveness'); + if (+curveness) { + pts[2] = [ + (pts[0][0] + pts[1][0]) / 2 - (pts[0][1] - pts[1][1]) * curveness, + (pts[0][1] + pts[1][1]) / 2 - (pts[1][0] - pts[0][0]) * curveness + ]; + } + } + lineData.setItemLayout(i, pts); + } + } + } + + return { progress: progress }; + } +}; + +/* +* 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. +*/ + +extendChartView({ + + type: 'lines', + + init: function () {}, + + render: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + + var lineDraw = this._updateLineDraw(data, seriesModel); + + var zlevel = seriesModel.get('zlevel'); + var trailLength = seriesModel.get('effect.trailLength'); + + var zr = api.getZr(); + // Avoid the drag cause ghost shadow + // FIXME Better way ? + // SVG doesn't support + var isSvg = zr.painter.getType() === 'svg'; + if (!isSvg) { + zr.painter.getLayer(zlevel).clear(true); + } + // Config layer with motion blur + if (this._lastZlevel != null && !isSvg) { + zr.configLayer(this._lastZlevel, { + motionBlur: false + }); + } + if (this._showEffect(seriesModel) && trailLength) { + if (__DEV__) { + var notInIndividual = false; + ecModel.eachSeries(function (otherSeriesModel) { + if (otherSeriesModel !== seriesModel && otherSeriesModel.get('zlevel') === zlevel) { + notInIndividual = true; + } + }); + notInIndividual && console.warn('Lines with trail effect should have an individual zlevel'); + } + + if (!isSvg) { + zr.configLayer(zlevel, { + motionBlur: true, + lastFrameAlpha: Math.max(Math.min(trailLength / 10 + 0.9, 1), 0) + }); + } + } + + lineDraw.updateData(data); + + var clipPath = seriesModel.get('clip', true) && createClipPath( + seriesModel.coordinateSystem, false, seriesModel + ); + if (clipPath) { + this.group.setClipPath(clipPath); + } + else { + this.group.removeClipPath(); + } + + this._lastZlevel = zlevel; + + this._finished = true; + }, + + incrementalPrepareRender: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + + var lineDraw = this._updateLineDraw(data, seriesModel); + + lineDraw.incrementalPrepareUpdate(data); + + this._clearLayer(api); + + this._finished = false; + }, + + incrementalRender: function (taskParams, seriesModel, ecModel) { + this._lineDraw.incrementalUpdate(taskParams, seriesModel.getData()); + + this._finished = taskParams.end === seriesModel.getData().count(); + }, + + updateTransform: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + var pipelineContext = seriesModel.pipelineContext; + + if (!this._finished || pipelineContext.large || pipelineContext.progressiveRender) { + // TODO Don't have to do update in large mode. Only do it when there are millions of data. + return { + update: true + }; + } + else { + // TODO Use same logic with ScatterView. + // Manually update layout + var res = linesLayout.reset(seriesModel); + if (res.progress) { + res.progress({ start: 0, end: data.count() }, data); + } + this._lineDraw.updateLayout(); + this._clearLayer(api); + } + }, + + _updateLineDraw: function (data, seriesModel) { + var lineDraw = this._lineDraw; + var hasEffect = this._showEffect(seriesModel); + var isPolyline = !!seriesModel.get('polyline'); + var pipelineContext = seriesModel.pipelineContext; + var isLargeDraw = pipelineContext.large; + + if (__DEV__) { + if (hasEffect && isLargeDraw) { + console.warn('Large lines not support effect'); + } + } + if (!lineDraw + || hasEffect !== this._hasEffet + || isPolyline !== this._isPolyline + || isLargeDraw !== this._isLargeDraw + ) { + if (lineDraw) { + lineDraw.remove(); + } + lineDraw = this._lineDraw = isLargeDraw + ? new LargeLineDraw() + : new LineDraw( + isPolyline + ? (hasEffect ? EffectPolyline : Polyline$2) + : (hasEffect ? EffectLine : Line$1) + ); + this._hasEffet = hasEffect; + this._isPolyline = isPolyline; + this._isLargeDraw = isLargeDraw; + this.group.removeAll(); + } + + this.group.add(lineDraw.group); + + return lineDraw; + }, + + _showEffect: function (seriesModel) { + return !!seriesModel.get('effect.show'); + }, + + _clearLayer: function (api) { + // Not use motion when dragging or zooming + var zr = api.getZr(); + var isSvg = zr.painter.getType() === 'svg'; + if (!isSvg && this._lastZlevel != null) { + zr.painter.getLayer(this._lastZlevel).clear(true); + } + }, + + remove: function (ecModel, api) { + this._lineDraw && this._lineDraw.remove(); + this._lineDraw = null; + // Clear motion when lineDraw is removed + this._clearLayer(api); + }, + + dispose: function () {} +}); + +/* +* 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. +*/ + + +function normalize$2(a) { + if (!(a instanceof Array)) { + a = [a, a]; + } + return a; +} + +var opacityQuery = 'lineStyle.opacity'.split('.'); + +var linesVisual = { + seriesType: 'lines', + reset: function (seriesModel, ecModel, api) { + var symbolType = normalize$2(seriesModel.get('symbol')); + var symbolSize = normalize$2(seriesModel.get('symbolSize')); + var data = seriesModel.getData(); + + data.setVisual('fromSymbol', symbolType && symbolType[0]); + data.setVisual('toSymbol', symbolType && symbolType[1]); + data.setVisual('fromSymbolSize', symbolSize && symbolSize[0]); + data.setVisual('toSymbolSize', symbolSize && symbolSize[1]); + data.setVisual('opacity', seriesModel.get(opacityQuery)); + + function dataEach(data, idx) { + var itemModel = data.getItemModel(idx); + var symbolType = normalize$2(itemModel.getShallow('symbol', true)); + var symbolSize = normalize$2(itemModel.getShallow('symbolSize', true)); + var opacity = itemModel.get(opacityQuery); + + symbolType[0] && data.setItemVisual(idx, 'fromSymbol', symbolType[0]); + symbolType[1] && data.setItemVisual(idx, 'toSymbol', symbolType[1]); + symbolSize[0] && data.setItemVisual(idx, 'fromSymbolSize', symbolSize[0]); + symbolSize[1] && data.setItemVisual(idx, 'toSymbolSize', symbolSize[1]); + + data.setItemVisual(idx, 'opacity', opacity); + } + + return {dataEach: data.hasItemOption ? dataEach : null}; + } +}; + +/* +* 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. +*/ + +registerLayout(linesLayout); +registerVisual(linesVisual); + +/* +* 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. +*/ + +SeriesModel.extend({ + type: 'series.heatmap', + + getInitialData: function (option, ecModel) { + return createListFromArray(this.getSource(), this, { + generateCoord: 'value' + }); + }, + + preventIncremental: function () { + var coordSysCreator = CoordinateSystemManager.get(this.get('coordinateSystem')); + if (coordSysCreator && coordSysCreator.dimensions) { + return coordSysCreator.dimensions[0] === 'lng' && coordSysCreator.dimensions[1] === 'lat'; + } + }, + + defaultOption: { + + // Cartesian2D or geo + coordinateSystem: 'cartesian2d', + + zlevel: 0, + + z: 2, + + // Cartesian coordinate system + // xAxisIndex: 0, + // yAxisIndex: 0, + + // Geo coordinate system + geoIndex: 0, + + blurSize: 30, + + pointSize: 20, + + maxOpacity: 1, + + minOpacity: 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. +*/ + +/* global Uint8ClampedArray */ + +var GRADIENT_LEVELS = 256; + +/** + * Heatmap Chart + * + * @class + */ +function Heatmap() { + var canvas = createCanvas(); + this.canvas = canvas; + + this.blurSize = 30; + this.pointSize = 20; + + this.maxOpacity = 1; + this.minOpacity = 0; + + this._gradientPixels = {}; +} + +Heatmap.prototype = { + /** + * Renders Heatmap and returns the rendered canvas + * @param {Array} data array of data, each has x, y, value + * @param {number} width canvas width + * @param {number} height canvas height + */ + update: function (data, width, height, normalize, colorFunc, isInRange) { + var brush = this._getBrush(); + var gradientInRange = this._getGradient(data, colorFunc, 'inRange'); + var gradientOutOfRange = this._getGradient(data, colorFunc, 'outOfRange'); + var r = this.pointSize + this.blurSize; + + var canvas = this.canvas; + var ctx = canvas.getContext('2d'); + var len = data.length; + canvas.width = width; + canvas.height = height; + for (var i = 0; i < len; ++i) { + var p = data[i]; + var x = p[0]; + var y = p[1]; + var value = p[2]; + + // calculate alpha using value + var alpha = normalize(value); + + // draw with the circle brush with alpha + ctx.globalAlpha = alpha; + ctx.drawImage(brush, x - r, y - r); + } + + if (!canvas.width || !canvas.height) { + // Avoid "Uncaught DOMException: Failed to execute 'getImageData' on + // 'CanvasRenderingContext2D': The source height is 0." + return canvas; + } + + // colorize the canvas using alpha value and set with gradient + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + var pixels = imageData.data; + var offset = 0; + var pixelLen = pixels.length; + var minOpacity = this.minOpacity; + var maxOpacity = this.maxOpacity; + var diffOpacity = maxOpacity - minOpacity; + + while (offset < pixelLen) { + var alpha = pixels[offset + 3] / 256; + var gradientOffset = Math.floor(alpha * (GRADIENT_LEVELS - 1)) * 4; + // Simple optimize to ignore the empty data + if (alpha > 0) { + var gradient = isInRange(alpha) ? gradientInRange : gradientOutOfRange; + // Any alpha > 0 will be mapped to [minOpacity, maxOpacity] + alpha > 0 && (alpha = alpha * diffOpacity + minOpacity); + pixels[offset++] = gradient[gradientOffset]; + pixels[offset++] = gradient[gradientOffset + 1]; + pixels[offset++] = gradient[gradientOffset + 2]; + pixels[offset++] = gradient[gradientOffset + 3] * alpha * 256; + } + else { + offset += 4; + } + } + ctx.putImageData(imageData, 0, 0); + + return canvas; + }, + + /** + * get canvas of a black circle brush used for canvas to draw later + * @private + * @returns {Object} circle brush canvas + */ + _getBrush: function () { + var brushCanvas = this._brushCanvas || (this._brushCanvas = createCanvas()); + // set brush size + var r = this.pointSize + this.blurSize; + var d = r * 2; + brushCanvas.width = d; + brushCanvas.height = d; + + var ctx = brushCanvas.getContext('2d'); + ctx.clearRect(0, 0, d, d); + + // in order to render shadow without the distinct circle, + // draw the distinct circle in an invisible place, + // and use shadowOffset to draw shadow in the center of the canvas + ctx.shadowOffsetX = d; + ctx.shadowBlur = this.blurSize; + // draw the shadow in black, and use alpha and shadow blur to generate + // color in color map + ctx.shadowColor = '#000'; + + // draw circle in the left to the canvas + ctx.beginPath(); + ctx.arc(-r, r, this.pointSize, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fill(); + return brushCanvas; + }, + + /** + * get gradient color map + * @private + */ + _getGradient: function (data, colorFunc, state) { + var gradientPixels = this._gradientPixels; + var pixelsSingleState = gradientPixels[state] || (gradientPixels[state] = new Uint8ClampedArray(256 * 4)); + var color = [0, 0, 0, 0]; + var off = 0; + for (var i = 0; i < 256; i++) { + colorFunc[state](i / 255, true, color); + pixelsSingleState[off++] = color[0]; + pixelsSingleState[off++] = color[1]; + pixelsSingleState[off++] = color[2]; + pixelsSingleState[off++] = color[3]; + } + return pixelsSingleState; + } +}; + +/* +* 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. +*/ + +function getIsInPiecewiseRange(dataExtent, pieceList, selected) { + var dataSpan = dataExtent[1] - dataExtent[0]; + pieceList = map(pieceList, function (piece) { + return { + interval: [ + (piece.interval[0] - dataExtent[0]) / dataSpan, + (piece.interval[1] - dataExtent[0]) / dataSpan + ] + }; + }); + var len = pieceList.length; + var lastIndex = 0; + + return function (val) { + // Try to find in the location of the last found + for (var i = lastIndex; i < len; i++) { + var interval = pieceList[i].interval; + if (interval[0] <= val && val <= interval[1]) { + lastIndex = i; + break; + } + } + if (i === len) { // Not found, back interation + for (var i = lastIndex - 1; i >= 0; i--) { + var interval = pieceList[i].interval; + if (interval[0] <= val && val <= interval[1]) { + lastIndex = i; + break; + } + } + } + return i >= 0 && i < len && selected[i]; + }; +} + +function getIsInContinuousRange(dataExtent, range) { + var dataSpan = dataExtent[1] - dataExtent[0]; + range = [ + (range[0] - dataExtent[0]) / dataSpan, + (range[1] - dataExtent[0]) / dataSpan + ]; + return function (val) { + return val >= range[0] && val <= range[1]; + }; +} + +function isGeoCoordSys(coordSys) { + var dimensions = coordSys.dimensions; + // Not use coorSys.type === 'geo' because coordSys maybe extended + return dimensions[0] === 'lng' && dimensions[1] === 'lat'; +} + +extendChartView({ + + type: 'heatmap', + + render: function (seriesModel, ecModel, api) { + var visualMapOfThisSeries; + ecModel.eachComponent('visualMap', function (visualMap) { + visualMap.eachTargetSeries(function (targetSeries) { + if (targetSeries === seriesModel) { + visualMapOfThisSeries = visualMap; + } + }); + }); + + if (__DEV__) { + if (!visualMapOfThisSeries) { + throw new Error('Heatmap must use with visualMap'); + } + } + + this.group.removeAll(); + + this._incrementalDisplayable = null; + + var coordSys = seriesModel.coordinateSystem; + if (coordSys.type === 'cartesian2d' || coordSys.type === 'calendar') { + this._renderOnCartesianAndCalendar(seriesModel, api, 0, seriesModel.getData().count()); + } + else if (isGeoCoordSys(coordSys)) { + this._renderOnGeo( + coordSys, seriesModel, visualMapOfThisSeries, api + ); + } + }, + + incrementalPrepareRender: function (seriesModel, ecModel, api) { + this.group.removeAll(); + }, + + incrementalRender: function (params, seriesModel, ecModel, api) { + var coordSys = seriesModel.coordinateSystem; + if (coordSys) { + this._renderOnCartesianAndCalendar(seriesModel, api, params.start, params.end, true); + } + }, + + _renderOnCartesianAndCalendar: function (seriesModel, api, start, end, incremental) { + + var coordSys = seriesModel.coordinateSystem; + var width; + var height; + + if (coordSys.type === 'cartesian2d') { + var xAxis = coordSys.getAxis('x'); + var yAxis = coordSys.getAxis('y'); + + if (__DEV__) { + if (!(xAxis.type === 'category' && yAxis.type === 'category')) { + throw new Error('Heatmap on cartesian must have two category axes'); + } + if (!(xAxis.onBand && yAxis.onBand)) { + throw new Error('Heatmap on cartesian must have two axes with boundaryGap true'); + } + } + + width = xAxis.getBandWidth(); + height = yAxis.getBandWidth(); + } + + var group = this.group; + var data = seriesModel.getData(); + + var itemStyleQuery = 'itemStyle'; + var hoverItemStyleQuery = 'emphasis.itemStyle'; + var labelQuery = 'label'; + var hoverLabelQuery = 'emphasis.label'; + var style = seriesModel.getModel(itemStyleQuery).getItemStyle(['color']); + var hoverStl = seriesModel.getModel(hoverItemStyleQuery).getItemStyle(); + var labelModel = seriesModel.getModel(labelQuery); + var hoverLabelModel = seriesModel.getModel(hoverLabelQuery); + var coordSysType = coordSys.type; + + + var dataDims = coordSysType === 'cartesian2d' + ? [ + data.mapDimension('x'), + data.mapDimension('y'), + data.mapDimension('value') + ] + : [ + data.mapDimension('time'), + data.mapDimension('value') + ]; + + for (var idx = start; idx < end; idx++) { + var rect; + + if (coordSysType === 'cartesian2d') { + // Ignore empty data + if (isNaN(data.get(dataDims[2], idx))) { + continue; + } + + var point = coordSys.dataToPoint([ + data.get(dataDims[0], idx), + data.get(dataDims[1], idx) + ]); + + rect = new Rect({ + shape: { + x: Math.floor(point[0] - width / 2), + y: Math.floor(point[1] - height / 2), + width: Math.ceil(width), + height: Math.ceil(height) + }, + style: { + fill: data.getItemVisual(idx, 'color'), + opacity: data.getItemVisual(idx, 'opacity') + } + }); + } + else { + // Ignore empty data + if (isNaN(data.get(dataDims[1], idx))) { + continue; + } + + rect = new Rect({ + z2: 1, + shape: coordSys.dataToRect([data.get(dataDims[0], idx)]).contentShape, + style: { + fill: data.getItemVisual(idx, 'color'), + opacity: data.getItemVisual(idx, 'opacity') + } + }); + } + + var itemModel = data.getItemModel(idx); + + // Optimization for large datset + if (data.hasItemOption) { + style = itemModel.getModel(itemStyleQuery).getItemStyle(['color']); + hoverStl = itemModel.getModel(hoverItemStyleQuery).getItemStyle(); + labelModel = itemModel.getModel(labelQuery); + hoverLabelModel = itemModel.getModel(hoverLabelQuery); + } + + var rawValue = seriesModel.getRawValue(idx); + var defaultText = '-'; + if (rawValue && rawValue[2] != null) { + defaultText = rawValue[2]; + } + + setLabelStyle( + style, hoverStl, labelModel, hoverLabelModel, + { + labelFetcher: seriesModel, + labelDataIndex: idx, + defaultText: defaultText, + isRectText: true + } + ); + + rect.setStyle(style); + setHoverStyle(rect, data.hasItemOption ? hoverStl : extend({}, hoverStl)); + + rect.incremental = incremental; + // PENDING + if (incremental) { + // Rect must use hover layer if it's incremental. + rect.useHoverLayer = true; + } + + group.add(rect); + data.setItemGraphicEl(idx, rect); + } + }, + + _renderOnGeo: function (geo, seriesModel, visualMapModel, api) { + var inRangeVisuals = visualMapModel.targetVisuals.inRange; + var outOfRangeVisuals = visualMapModel.targetVisuals.outOfRange; + // if (!visualMapping) { + // throw new Error('Data range must have color visuals'); + // } + + var data = seriesModel.getData(); + var hmLayer = this._hmLayer || (this._hmLayer || new Heatmap()); + hmLayer.blurSize = seriesModel.get('blurSize'); + hmLayer.pointSize = seriesModel.get('pointSize'); + hmLayer.minOpacity = seriesModel.get('minOpacity'); + hmLayer.maxOpacity = seriesModel.get('maxOpacity'); + + var rect = geo.getViewRect().clone(); + var roamTransform = geo.getRoamTransform(); + rect.applyTransform(roamTransform); + + // Clamp on viewport + var x = Math.max(rect.x, 0); + var y = Math.max(rect.y, 0); + var x2 = Math.min(rect.width + rect.x, api.getWidth()); + var y2 = Math.min(rect.height + rect.y, api.getHeight()); + var width = x2 - x; + var height = y2 - y; + + var dims = [ + data.mapDimension('lng'), + data.mapDimension('lat'), + data.mapDimension('value') + ]; + + var points = data.mapArray(dims, function (lng, lat, value) { + var pt = geo.dataToPoint([lng, lat]); + pt[0] -= x; + pt[1] -= y; + pt.push(value); + return pt; + }); + + var dataExtent = visualMapModel.getExtent(); + var isInRange = visualMapModel.type === 'visualMap.continuous' + ? getIsInContinuousRange(dataExtent, visualMapModel.option.range) + : getIsInPiecewiseRange( + dataExtent, visualMapModel.getPieceList(), visualMapModel.option.selected + ); + + hmLayer.update( + points, width, height, + inRangeVisuals.color.getNormalizer(), + { + inRange: inRangeVisuals.color.getColorMapper(), + outOfRange: outOfRangeVisuals.color.getColorMapper() + }, + isInRange + ); + var img = new ZImage({ + style: { + width: width, + height: height, + x: x, + y: y, + image: hmLayer.canvas + }, + silent: true + }); + this.group.add(img); + }, + + dispose: function () {} +}); + +/* +* 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. +*/ + +/* +* 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. +*/ + +var PictorialBarSeries = BaseBarSeries.extend({ + + type: 'series.pictorialBar', + + dependencies: ['grid'], + + defaultOption: { + symbol: 'circle', // Customized bar shape + symbolSize: null, // Can be ['100%', '100%'], null means auto. + symbolRotate: null, + + symbolPosition: null, // 'start' or 'end' or 'center', null means auto. + symbolOffset: null, + symbolMargin: null, // start margin and end margin. Can be a number or a percent string. + // Auto margin by defualt. + symbolRepeat: false, // false/null/undefined, means no repeat. + // Can be true, means auto calculate repeat times and cut by data. + // Can be a number, specifies repeat times, and do not cut by data. + // Can be 'fixed', means auto calculate repeat times but do not cut by data. + symbolRepeatDirection: 'end', // 'end' means from 'start' to 'end'. + + symbolClip: false, + symbolBoundingData: null, // Can be 60 or -40 or [-40, 60] + symbolPatternSize: 400, // 400 * 400 px + + barGap: '-100%', // In most case, overlap is needed. + + // z can be set in data item, which is z2 actually. + + // Disable progressive + progressive: 0, + hoverAnimation: false // Open only when needed. + }, + + getInitialData: function (option) { + // Disable stack. + option.stack = null; + return PictorialBarSeries.superApply(this, 'getInitialData', arguments); + } +}); + +/* +* 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. +*/ + +var BAR_BORDER_WIDTH_QUERY$1 = ['itemStyle', 'borderWidth']; + +// index: +isHorizontal +var LAYOUT_ATTRS = [ + {xy: 'x', wh: 'width', index: 0, posDesc: ['left', 'right']}, + {xy: 'y', wh: 'height', index: 1, posDesc: ['top', 'bottom']} +]; + +var pathForLineWidth = new Circle(); + +var BarView$1 = extendChartView({ + + type: 'pictorialBar', + + render: function (seriesModel, ecModel, api) { + var group = this.group; + var data = seriesModel.getData(); + var oldData = this._data; + + var cartesian = seriesModel.coordinateSystem; + var baseAxis = cartesian.getBaseAxis(); + var isHorizontal = !!baseAxis.isHorizontal(); + var coordSysRect = cartesian.grid.getRect(); + + var opt = { + ecSize: {width: api.getWidth(), height: api.getHeight()}, + seriesModel: seriesModel, + coordSys: cartesian, + coordSysExtent: [ + [coordSysRect.x, coordSysRect.x + coordSysRect.width], + [coordSysRect.y, coordSysRect.y + coordSysRect.height] + ], + isHorizontal: isHorizontal, + valueDim: LAYOUT_ATTRS[+isHorizontal], + categoryDim: LAYOUT_ATTRS[1 - isHorizontal] + }; + + data.diff(oldData) + .add(function (dataIndex) { + if (!data.hasValue(dataIndex)) { + return; + } + + var itemModel = getItemModel(data, dataIndex); + var symbolMeta = getSymbolMeta(data, dataIndex, itemModel, opt); + + var bar = createBar(data, opt, symbolMeta); + + data.setItemGraphicEl(dataIndex, bar); + group.add(bar); + + updateCommon$1(bar, opt, symbolMeta); + }) + .update(function (newIndex, oldIndex) { + var bar = oldData.getItemGraphicEl(oldIndex); + + if (!data.hasValue(newIndex)) { + group.remove(bar); + return; + } + + var itemModel = getItemModel(data, newIndex); + var symbolMeta = getSymbolMeta(data, newIndex, itemModel, opt); + + var pictorialShapeStr = getShapeStr(data, symbolMeta); + if (bar && pictorialShapeStr !== bar.__pictorialShapeStr) { + group.remove(bar); + data.setItemGraphicEl(newIndex, null); + bar = null; + } + + if (bar) { + updateBar(bar, opt, symbolMeta); + } + else { + bar = createBar(data, opt, symbolMeta, true); + } + + data.setItemGraphicEl(newIndex, bar); + bar.__pictorialSymbolMeta = symbolMeta; + // Add back + group.add(bar); + + updateCommon$1(bar, opt, symbolMeta); + }) + .remove(function (dataIndex) { + var bar = oldData.getItemGraphicEl(dataIndex); + bar && removeBar(oldData, dataIndex, bar.__pictorialSymbolMeta.animationModel, bar); + }) + .execute(); + + this._data = data; + + return this.group; + }, + + dispose: noop, + + remove: function (ecModel, api) { + var group = this.group; + var data = this._data; + if (ecModel.get('animation')) { + if (data) { + data.eachItemGraphicEl(function (bar) { + removeBar(data, bar.dataIndex, ecModel, bar); + }); + } + } + else { + group.removeAll(); + } + } +}); + + +// Set or calculate default value about symbol, and calculate layout info. +function getSymbolMeta(data, dataIndex, itemModel, opt) { + var layout = data.getItemLayout(dataIndex); + var symbolRepeat = itemModel.get('symbolRepeat'); + var symbolClip = itemModel.get('symbolClip'); + var symbolPosition = itemModel.get('symbolPosition') || 'start'; + var symbolRotate = itemModel.get('symbolRotate'); + var rotation = (symbolRotate || 0) * Math.PI / 180 || 0; + var symbolPatternSize = itemModel.get('symbolPatternSize') || 2; + var isAnimationEnabled = itemModel.isAnimationEnabled(); + + var symbolMeta = { + dataIndex: dataIndex, + layout: layout, + itemModel: itemModel, + symbolType: data.getItemVisual(dataIndex, 'symbol') || 'circle', + color: data.getItemVisual(dataIndex, 'color'), + symbolClip: symbolClip, + symbolRepeat: symbolRepeat, + symbolRepeatDirection: itemModel.get('symbolRepeatDirection'), + symbolPatternSize: symbolPatternSize, + rotation: rotation, + animationModel: isAnimationEnabled ? itemModel : null, + hoverAnimation: isAnimationEnabled && itemModel.get('hoverAnimation'), + z2: itemModel.getShallow('z', true) || 0 + }; + + prepareBarLength(itemModel, symbolRepeat, layout, opt, symbolMeta); + + prepareSymbolSize( + data, dataIndex, layout, symbolRepeat, symbolClip, symbolMeta.boundingLength, + symbolMeta.pxSign, symbolPatternSize, opt, symbolMeta + ); + + prepareLineWidth(itemModel, symbolMeta.symbolScale, rotation, opt, symbolMeta); + + var symbolSize = symbolMeta.symbolSize; + var symbolOffset = itemModel.get('symbolOffset'); + if (isArray(symbolOffset)) { + symbolOffset = [ + parsePercent$1(symbolOffset[0], symbolSize[0]), + parsePercent$1(symbolOffset[1], symbolSize[1]) + ]; + } + + prepareLayoutInfo( + itemModel, symbolSize, layout, symbolRepeat, symbolClip, symbolOffset, + symbolPosition, symbolMeta.valueLineWidth, symbolMeta.boundingLength, symbolMeta.repeatCutLength, + opt, symbolMeta + ); + + return symbolMeta; +} + +// bar length can be negative. +function prepareBarLength(itemModel, symbolRepeat, layout, opt, output) { + var valueDim = opt.valueDim; + var symbolBoundingData = itemModel.get('symbolBoundingData'); + var valueAxis = opt.coordSys.getOtherAxis(opt.coordSys.getBaseAxis()); + var zeroPx = valueAxis.toGlobalCoord(valueAxis.dataToCoord(0)); + var pxSignIdx = 1 - +(layout[valueDim.wh] <= 0); + var boundingLength; + + if (isArray(symbolBoundingData)) { + var symbolBoundingExtent = [ + convertToCoordOnAxis(valueAxis, symbolBoundingData[0]) - zeroPx, + convertToCoordOnAxis(valueAxis, symbolBoundingData[1]) - zeroPx + ]; + symbolBoundingExtent[1] < symbolBoundingExtent[0] && (symbolBoundingExtent.reverse()); + boundingLength = symbolBoundingExtent[pxSignIdx]; + } + else if (symbolBoundingData != null) { + boundingLength = convertToCoordOnAxis(valueAxis, symbolBoundingData) - zeroPx; + } + else if (symbolRepeat) { + boundingLength = opt.coordSysExtent[valueDim.index][pxSignIdx] - zeroPx; + } + else { + boundingLength = layout[valueDim.wh]; + } + + output.boundingLength = boundingLength; + + if (symbolRepeat) { + output.repeatCutLength = layout[valueDim.wh]; + } + + output.pxSign = boundingLength > 0 ? 1 : boundingLength < 0 ? -1 : 0; +} + +function convertToCoordOnAxis(axis, value) { + return axis.toGlobalCoord(axis.dataToCoord(axis.scale.parse(value))); +} + +// Support ['100%', '100%'] +function prepareSymbolSize( + data, dataIndex, layout, symbolRepeat, symbolClip, boundingLength, + pxSign, symbolPatternSize, opt, output +) { + var valueDim = opt.valueDim; + var categoryDim = opt.categoryDim; + var categorySize = Math.abs(layout[categoryDim.wh]); + + var symbolSize = data.getItemVisual(dataIndex, 'symbolSize'); + if (isArray(symbolSize)) { + symbolSize = symbolSize.slice(); + } + else { + if (symbolSize == null) { + symbolSize = '100%'; + } + symbolSize = [symbolSize, symbolSize]; + } + + // Note: percentage symbolSize (like '100%') do not consider lineWidth, because it is + // to complicated to calculate real percent value if considering scaled lineWidth. + // So the actual size will bigger than layout size if lineWidth is bigger than zero, + // which can be tolerated in pictorial chart. + + symbolSize[categoryDim.index] = parsePercent$1( + symbolSize[categoryDim.index], + categorySize + ); + symbolSize[valueDim.index] = parsePercent$1( + symbolSize[valueDim.index], + symbolRepeat ? categorySize : Math.abs(boundingLength) + ); + + output.symbolSize = symbolSize; + + // If x or y is less than zero, show reversed shape. + var symbolScale = output.symbolScale = [ + symbolSize[0] / symbolPatternSize, + symbolSize[1] / symbolPatternSize + ]; + // Follow convention, 'right' and 'top' is the normal scale. + symbolScale[valueDim.index] *= (opt.isHorizontal ? -1 : 1) * pxSign; +} + +function prepareLineWidth(itemModel, symbolScale, rotation, opt, output) { + // In symbols are drawn with scale, so do not need to care about the case that width + // or height are too small. But symbol use strokeNoScale, where acture lineWidth should + // be calculated. + var valueLineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY$1) || 0; + + if (valueLineWidth) { + pathForLineWidth.attr({ + scale: symbolScale.slice(), + rotation: rotation + }); + pathForLineWidth.updateTransform(); + valueLineWidth /= pathForLineWidth.getLineScale(); + valueLineWidth *= symbolScale[opt.valueDim.index]; + } + + output.valueLineWidth = valueLineWidth; +} + +function prepareLayoutInfo( + itemModel, symbolSize, layout, symbolRepeat, symbolClip, symbolOffset, + symbolPosition, valueLineWidth, boundingLength, repeatCutLength, opt, output +) { + var categoryDim = opt.categoryDim; + var valueDim = opt.valueDim; + var pxSign = output.pxSign; + + var unitLength = Math.max(symbolSize[valueDim.index] + valueLineWidth, 0); + var pathLen = unitLength; + + // Note: rotation will not effect the layout of symbols, because user may + // want symbols to rotate on its center, which should not be translated + // when rotating. + + if (symbolRepeat) { + var absBoundingLength = Math.abs(boundingLength); + + var symbolMargin = retrieve(itemModel.get('symbolMargin'), '15%') + ''; + var hasEndGap = false; + if (symbolMargin.lastIndexOf('!') === symbolMargin.length - 1) { + hasEndGap = true; + symbolMargin = symbolMargin.slice(0, symbolMargin.length - 1); + } + symbolMargin = parsePercent$1(symbolMargin, symbolSize[valueDim.index]); + + var uLenWithMargin = Math.max(unitLength + symbolMargin * 2, 0); + + // When symbol margin is less than 0, margin at both ends will be subtracted + // to ensure that all of the symbols will not be overflow the given area. + var endFix = hasEndGap ? 0 : symbolMargin * 2; + + // Both final repeatTimes and final symbolMargin area calculated based on + // boundingLength. + var repeatSpecified = isNumeric(symbolRepeat); + var repeatTimes = repeatSpecified + ? symbolRepeat + : toIntTimes((absBoundingLength + endFix) / uLenWithMargin); + + // Adjust calculate margin, to ensure each symbol is displayed + // entirely in the given layout area. + var mDiff = absBoundingLength - repeatTimes * unitLength; + symbolMargin = mDiff / 2 / (hasEndGap ? repeatTimes : repeatTimes - 1); + uLenWithMargin = unitLength + symbolMargin * 2; + endFix = hasEndGap ? 0 : symbolMargin * 2; + + // Update repeatTimes when not all symbol will be shown. + if (!repeatSpecified && symbolRepeat !== 'fixed') { + repeatTimes = repeatCutLength + ? toIntTimes((Math.abs(repeatCutLength) + endFix) / uLenWithMargin) + : 0; + } + + pathLen = repeatTimes * uLenWithMargin - endFix; + output.repeatTimes = repeatTimes; + output.symbolMargin = symbolMargin; + } + + var sizeFix = pxSign * (pathLen / 2); + var pathPosition = output.pathPosition = []; + pathPosition[categoryDim.index] = layout[categoryDim.wh] / 2; + pathPosition[valueDim.index] = symbolPosition === 'start' + ? sizeFix + : symbolPosition === 'end' + ? boundingLength - sizeFix + : boundingLength / 2; // 'center' + if (symbolOffset) { + pathPosition[0] += symbolOffset[0]; + pathPosition[1] += symbolOffset[1]; + } + + var bundlePosition = output.bundlePosition = []; + bundlePosition[categoryDim.index] = layout[categoryDim.xy]; + bundlePosition[valueDim.index] = layout[valueDim.xy]; + + var barRectShape = output.barRectShape = extend({}, layout); + barRectShape[valueDim.wh] = pxSign * Math.max( + Math.abs(layout[valueDim.wh]), Math.abs(pathPosition[valueDim.index] + sizeFix) + ); + barRectShape[categoryDim.wh] = layout[categoryDim.wh]; + + var clipShape = output.clipShape = {}; + // Consider that symbol may be overflow layout rect. + clipShape[categoryDim.xy] = -layout[categoryDim.xy]; + clipShape[categoryDim.wh] = opt.ecSize[categoryDim.wh]; + clipShape[valueDim.xy] = 0; + clipShape[valueDim.wh] = layout[valueDim.wh]; +} + +function createPath(symbolMeta) { + var symbolPatternSize = symbolMeta.symbolPatternSize; + var path = createSymbol( + // Consider texture img, make a big size. + symbolMeta.symbolType, + -symbolPatternSize / 2, + -symbolPatternSize / 2, + symbolPatternSize, + symbolPatternSize, + symbolMeta.color + ); + path.attr({ + culling: true + }); + path.type !== 'image' && path.setStyle({ + strokeNoScale: true + }); + + return path; +} + +function createOrUpdateRepeatSymbols(bar, opt, symbolMeta, isUpdate) { + var bundle = bar.__pictorialBundle; + var symbolSize = symbolMeta.symbolSize; + var valueLineWidth = symbolMeta.valueLineWidth; + var pathPosition = symbolMeta.pathPosition; + var valueDim = opt.valueDim; + var repeatTimes = symbolMeta.repeatTimes || 0; + + var index = 0; + var unit = symbolSize[opt.valueDim.index] + valueLineWidth + symbolMeta.symbolMargin * 2; + + eachPath(bar, function (path) { + path.__pictorialAnimationIndex = index; + path.__pictorialRepeatTimes = repeatTimes; + if (index < repeatTimes) { + updateAttr(path, null, makeTarget(index), symbolMeta, isUpdate); + } + else { + updateAttr(path, null, {scale: [0, 0]}, symbolMeta, isUpdate, function () { + bundle.remove(path); + }); + } + + updateHoverAnimation(path, symbolMeta); + + index++; + }); + + for (; index < repeatTimes; index++) { + var path = createPath(symbolMeta); + path.__pictorialAnimationIndex = index; + path.__pictorialRepeatTimes = repeatTimes; + bundle.add(path); + + var target = makeTarget(index); + + updateAttr( + path, + { + position: target.position, + scale: [0, 0] + }, + { + scale: target.scale, + rotation: target.rotation + }, + symbolMeta, + isUpdate + ); + + // FIXME + // If all emphasis/normal through action. + path + .on('mouseover', onMouseOver) + .on('mouseout', onMouseOut); + + updateHoverAnimation(path, symbolMeta); + } + + function makeTarget(index) { + var position = pathPosition.slice(); + // (start && pxSign > 0) || (end && pxSign < 0): i = repeatTimes - index + // Otherwise: i = index; + var pxSign = symbolMeta.pxSign; + var i = index; + if (symbolMeta.symbolRepeatDirection === 'start' ? pxSign > 0 : pxSign < 0) { + i = repeatTimes - 1 - index; + } + position[valueDim.index] = unit * (i - repeatTimes / 2 + 0.5) + pathPosition[valueDim.index]; + + return { + position: position, + scale: symbolMeta.symbolScale.slice(), + rotation: symbolMeta.rotation + }; + } + + function onMouseOver() { + eachPath(bar, function (path) { + path.trigger('emphasis'); + }); + } + + function onMouseOut() { + eachPath(bar, function (path) { + path.trigger('normal'); + }); + } +} + +function createOrUpdateSingleSymbol(bar, opt, symbolMeta, isUpdate) { + var bundle = bar.__pictorialBundle; + var mainPath = bar.__pictorialMainPath; + + if (!mainPath) { + mainPath = bar.__pictorialMainPath = createPath(symbolMeta); + bundle.add(mainPath); + + updateAttr( + mainPath, + { + position: symbolMeta.pathPosition.slice(), + scale: [0, 0], + rotation: symbolMeta.rotation + }, + { + scale: symbolMeta.symbolScale.slice() + }, + symbolMeta, + isUpdate + ); + + mainPath + .on('mouseover', onMouseOver) + .on('mouseout', onMouseOut); + } + else { + updateAttr( + mainPath, + null, + { + position: symbolMeta.pathPosition.slice(), + scale: symbolMeta.symbolScale.slice(), + rotation: symbolMeta.rotation + }, + symbolMeta, + isUpdate + ); + } + + updateHoverAnimation(mainPath, symbolMeta); + + function onMouseOver() { + this.trigger('emphasis'); + } + + function onMouseOut() { + this.trigger('normal'); + } +} + +// bar rect is used for label. +function createOrUpdateBarRect(bar, symbolMeta, isUpdate) { + var rectShape = extend({}, symbolMeta.barRectShape); + + var barRect = bar.__pictorialBarRect; + if (!barRect) { + barRect = bar.__pictorialBarRect = new Rect({ + z2: 2, + shape: rectShape, + silent: true, + style: { + stroke: 'transparent', + fill: 'transparent', + lineWidth: 0 + } + }); + + bar.add(barRect); + } + else { + updateAttr(barRect, null, {shape: rectShape}, symbolMeta, isUpdate); + } +} + +function createOrUpdateClip(bar, opt, symbolMeta, isUpdate) { + // If not clip, symbol will be remove and rebuilt. + if (symbolMeta.symbolClip) { + var clipPath = bar.__pictorialClipPath; + var clipShape = extend({}, symbolMeta.clipShape); + var valueDim = opt.valueDim; + var animationModel = symbolMeta.animationModel; + var dataIndex = symbolMeta.dataIndex; + + if (clipPath) { + updateProps( + clipPath, {shape: clipShape}, animationModel, dataIndex + ); + } + else { + clipShape[valueDim.wh] = 0; + clipPath = new Rect({shape: clipShape}); + bar.__pictorialBundle.setClipPath(clipPath); + bar.__pictorialClipPath = clipPath; + + var target = {}; + target[valueDim.wh] = symbolMeta.clipShape[valueDim.wh]; + + graphic[isUpdate ? 'updateProps' : 'initProps']( + clipPath, {shape: target}, animationModel, dataIndex + ); + } + } +} + +function getItemModel(data, dataIndex) { + var itemModel = data.getItemModel(dataIndex); + itemModel.getAnimationDelayParams = getAnimationDelayParams; + itemModel.isAnimationEnabled = isAnimationEnabled; + return itemModel; +} + +function getAnimationDelayParams(path) { + // The order is the same as the z-order, see `symbolRepeatDiretion`. + return { + index: path.__pictorialAnimationIndex, + count: path.__pictorialRepeatTimes + }; +} + +function isAnimationEnabled() { + // `animation` prop can be set on itemModel in pictorial bar chart. + return this.parentModel.isAnimationEnabled() && !!this.getShallow('animation'); +} + +function updateHoverAnimation(path, symbolMeta) { + path.off('emphasis').off('normal'); + + var scale = symbolMeta.symbolScale.slice(); + + symbolMeta.hoverAnimation && path + .on('emphasis', function () { + this.animateTo({ + scale: [scale[0] * 1.1, scale[1] * 1.1] + }, 400, 'elasticOut'); + }) + .on('normal', function () { + this.animateTo({ + scale: scale.slice() + }, 400, 'elasticOut'); + }); +} + +function createBar(data, opt, symbolMeta, isUpdate) { + // bar is the main element for each data. + var bar = new Group(); + // bundle is used for location and clip. + var bundle = new Group(); + bar.add(bundle); + bar.__pictorialBundle = bundle; + bundle.attr('position', symbolMeta.bundlePosition.slice()); + + if (symbolMeta.symbolRepeat) { + createOrUpdateRepeatSymbols(bar, opt, symbolMeta); + } + else { + createOrUpdateSingleSymbol(bar, opt, symbolMeta); + } + + createOrUpdateBarRect(bar, symbolMeta, isUpdate); + + createOrUpdateClip(bar, opt, symbolMeta, isUpdate); + + bar.__pictorialShapeStr = getShapeStr(data, symbolMeta); + bar.__pictorialSymbolMeta = symbolMeta; + + return bar; +} + +function updateBar(bar, opt, symbolMeta) { + var animationModel = symbolMeta.animationModel; + var dataIndex = symbolMeta.dataIndex; + var bundle = bar.__pictorialBundle; + + updateProps( + bundle, {position: symbolMeta.bundlePosition.slice()}, animationModel, dataIndex + ); + + if (symbolMeta.symbolRepeat) { + createOrUpdateRepeatSymbols(bar, opt, symbolMeta, true); + } + else { + createOrUpdateSingleSymbol(bar, opt, symbolMeta, true); + } + + createOrUpdateBarRect(bar, symbolMeta, true); + + createOrUpdateClip(bar, opt, symbolMeta, true); +} + +function removeBar(data, dataIndex, animationModel, bar) { + // Not show text when animating + var labelRect = bar.__pictorialBarRect; + labelRect && (labelRect.style.text = null); + + var pathes = []; + eachPath(bar, function (path) { + pathes.push(path); + }); + bar.__pictorialMainPath && pathes.push(bar.__pictorialMainPath); + + // I do not find proper remove animation for clip yet. + bar.__pictorialClipPath && (animationModel = null); + + each$1(pathes, function (path) { + updateProps( + path, {scale: [0, 0]}, animationModel, dataIndex, + function () { + bar.parent && bar.parent.remove(bar); + } + ); + }); + + data.setItemGraphicEl(dataIndex, null); +} + +function getShapeStr(data, symbolMeta) { + return [ + data.getItemVisual(symbolMeta.dataIndex, 'symbol') || 'none', + !!symbolMeta.symbolRepeat, + !!symbolMeta.symbolClip + ].join(':'); +} + +function eachPath(bar, cb, context) { + // Do not use Group#eachChild, because it do not support remove. + each$1(bar.__pictorialBundle.children(), function (el) { + el !== bar.__pictorialBarRect && cb.call(context, el); + }); +} + +function updateAttr(el, immediateAttrs, animationAttrs, symbolMeta, isUpdate, cb) { + immediateAttrs && el.attr(immediateAttrs); + // when symbolCip used, only clip path has init animation, otherwise it would be weird effect. + if (symbolMeta.symbolClip && !isUpdate) { + animationAttrs && el.attr(animationAttrs); + } + else { + animationAttrs && graphic[isUpdate ? 'updateProps' : 'initProps']( + el, animationAttrs, symbolMeta.animationModel, symbolMeta.dataIndex, cb + ); + } +} + +function updateCommon$1(bar, opt, symbolMeta) { + var color = symbolMeta.color; + var dataIndex = symbolMeta.dataIndex; + var itemModel = symbolMeta.itemModel; + // Color must be excluded. + // Because symbol provide setColor individually to set fill and stroke + var normalStyle = itemModel.getModel('itemStyle').getItemStyle(['color']); + var hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle(); + var cursorStyle = itemModel.getShallow('cursor'); + + eachPath(bar, function (path) { + // PENDING setColor should be before setStyle!!! + path.setColor(color); + path.setStyle(defaults( + { + fill: color, + opacity: symbolMeta.opacity + }, + normalStyle + )); + setHoverStyle(path, hoverStyle); + + cursorStyle && (path.cursor = cursorStyle); + path.z2 = symbolMeta.z2; + }); + + var barRectHoverStyle = {}; + var barPositionOutside = opt.valueDim.posDesc[+(symbolMeta.boundingLength > 0)]; + var barRect = bar.__pictorialBarRect; + + setLabel( + barRect.style, barRectHoverStyle, itemModel, + color, opt.seriesModel, dataIndex, barPositionOutside + ); + + setHoverStyle(barRect, barRectHoverStyle); +} + +function toIntTimes(times) { + var roundedTimes = Math.round(times); + // Escapse accurate error + return Math.abs(times - roundedTimes) < 1e-4 + ? roundedTimes + : Math.ceil(times); +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// In case developer forget to include grid component +registerLayout(curry( + layout, 'pictorialBar' +)); +registerVisual(visualSymbol('pictorialBar', 'roundRect')); + +/* +* 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. +*/ + +/** + * @constructor module:echarts/coord/single/SingleAxis + * @extends {module:echarts/coord/Axis} + * @param {string} dim + * @param {*} scale + * @param {Array.} coordExtent + * @param {string} axisType + * @param {string} position + */ +var SingleAxis = function (dim, scale, coordExtent, axisType, position) { + + Axis.call(this, dim, scale, coordExtent); + + /** + * Axis type + * - 'category' + * - 'value' + * - 'time' + * - 'log' + * @type {string} + */ + this.type = axisType || 'value'; + + /** + * Axis position + * - 'top' + * - 'bottom' + * - 'left' + * - 'right' + * @type {string} + */ + this.position = position || 'bottom'; + + /** + * Axis orient + * - 'horizontal' + * - 'vertical' + * @type {[type]} + */ + this.orient = null; + +}; + +SingleAxis.prototype = { + + constructor: SingleAxis, + + /** + * Axis model + * @type {module:echarts/coord/single/AxisModel} + */ + model: null, + + /** + * Judge the orient of the axis. + * @return {boolean} + */ + isHorizontal: function () { + var position = this.position; + return position === 'top' || position === 'bottom'; + + }, + + /** + * @override + */ + pointToData: function (point, clamp) { + return this.coordinateSystem.pointToData(point, clamp)[0]; + }, + + /** + * Convert the local coord(processed by dataToCoord()) + * to global coord(concrete pixel coord). + * designated by module:echarts/coord/single/Single. + * @type {Function} + */ + toGlobalCoord: null, + + /** + * Convert the global coord to local coord. + * designated by module:echarts/coord/single/Single. + * @type {Function} + */ + toLocalCoord: null + +}; + +inherits(SingleAxis, Axis); + +/* +* 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. +*/ + +/** + * Single coordinates system. + */ + +/** + * Create a single coordinates system. + * + * @param {module:echarts/coord/single/AxisModel} axisModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ +function Single(axisModel, ecModel, api) { + + /** + * @type {string} + * @readOnly + */ + this.dimension = 'single'; + + /** + * Add it just for draw tooltip. + * + * @type {Array.} + * @readOnly + */ + this.dimensions = ['single']; + + /** + * @private + * @type {module:echarts/coord/single/SingleAxis}. + */ + this._axis = null; + + /** + * @private + * @type {module:zrender/core/BoundingRect} + */ + this._rect; + + this._init(axisModel, ecModel, api); + + /** + * @type {module:echarts/coord/single/AxisModel} + */ + this.model = axisModel; +} + +Single.prototype = { + + type: 'singleAxis', + + axisPointerEnabled: true, + + constructor: Single, + + /** + * Initialize single coordinate system. + * + * @param {module:echarts/coord/single/AxisModel} axisModel + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @private + */ + _init: function (axisModel, ecModel, api) { + + var dim = this.dimension; + + var axis = new SingleAxis( + dim, + createScaleByModel(axisModel), + [0, 0], + axisModel.get('type'), + axisModel.get('position') + ); + + var isCategory = axis.type === 'category'; + axis.onBand = isCategory && axisModel.get('boundaryGap'); + axis.inverse = axisModel.get('inverse'); + axis.orient = axisModel.get('orient'); + + axisModel.axis = axis; + axis.model = axisModel; + axis.coordinateSystem = this; + this._axis = axis; + }, + + /** + * Update axis scale after data processed + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ + update: function (ecModel, api) { + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.coordinateSystem === this) { + var data = seriesModel.getData(); + each$1(data.mapDimension(this.dimension, true), function (dim) { + this._axis.scale.unionExtentFromData(data, dim); + }, this); + niceScaleExtent(this._axis.scale, this._axis.model); + } + }, this); + }, + + /** + * Resize the single coordinate system. + * + * @param {module:echarts/coord/single/AxisModel} axisModel + * @param {module:echarts/ExtensionAPI} api + */ + resize: function (axisModel, api) { + this._rect = getLayoutRect( + { + left: axisModel.get('left'), + top: axisModel.get('top'), + right: axisModel.get('right'), + bottom: axisModel.get('bottom'), + width: axisModel.get('width'), + height: axisModel.get('height') + }, + { + width: api.getWidth(), + height: api.getHeight() + } + ); + + this._adjustAxis(); + }, + + /** + * @return {module:zrender/core/BoundingRect} + */ + getRect: function () { + return this._rect; + }, + + /** + * @private + */ + _adjustAxis: function () { + + var rect = this._rect; + var axis = this._axis; + + var isHorizontal = axis.isHorizontal(); + var extent = isHorizontal ? [0, rect.width] : [0, rect.height]; + var idx = axis.reverse ? 1 : 0; + + axis.setExtent(extent[idx], extent[1 - idx]); + + this._updateAxisTransform(axis, isHorizontal ? rect.x : rect.y); + + }, + + /** + * @param {module:echarts/coord/single/SingleAxis} axis + * @param {number} coordBase + */ + _updateAxisTransform: function (axis, coordBase) { + + var axisExtent = axis.getExtent(); + var extentSum = axisExtent[0] + axisExtent[1]; + var isHorizontal = axis.isHorizontal(); + + axis.toGlobalCoord = isHorizontal + ? function (coord) { + return coord + coordBase; + } + : function (coord) { + return extentSum - coord + coordBase; + }; + + axis.toLocalCoord = isHorizontal + ? function (coord) { + return coord - coordBase; + } + : function (coord) { + return extentSum - coord + coordBase; + }; + }, + + /** + * Get axis. + * + * @return {module:echarts/coord/single/SingleAxis} + */ + getAxis: function () { + return this._axis; + }, + + /** + * Get axis, add it just for draw tooltip. + * + * @return {[type]} [description] + */ + getBaseAxis: function () { + return this._axis; + }, + + /** + * @return {Array.} + */ + getAxes: function () { + return [this._axis]; + }, + + /** + * @return {Object} {baseAxes: [], otherAxes: []} + */ + getTooltipAxes: function () { + return {baseAxes: [this.getAxis()]}; + }, + + /** + * If contain point. + * + * @param {Array.} point + * @return {boolean} + */ + containPoint: function (point) { + var rect = this.getRect(); + var axis = this.getAxis(); + var orient = axis.orient; + if (orient === 'horizontal') { + return axis.contain(axis.toLocalCoord(point[0])) + && (point[1] >= rect.y && point[1] <= (rect.y + rect.height)); + } + else { + return axis.contain(axis.toLocalCoord(point[1])) + && (point[0] >= rect.y && point[0] <= (rect.y + rect.height)); + } + }, + + /** + * @param {Array.} point + * @return {Array.} + */ + pointToData: function (point) { + var axis = this.getAxis(); + return [axis.coordToData(axis.toLocalCoord( + point[axis.orient === 'horizontal' ? 0 : 1] + ))]; + }, + + /** + * Convert the series data to concrete point. + * + * @param {number|Array.} val + * @return {Array.} + */ + dataToPoint: function (val) { + var axis = this.getAxis(); + var rect = this.getRect(); + var pt = []; + var idx = axis.orient === 'horizontal' ? 0 : 1; + + if (val instanceof Array) { + val = val[0]; + } + + pt[idx] = axis.toGlobalCoord(axis.dataToCoord(+val)); + pt[1 - idx] = idx === 0 ? (rect.y + rect.height / 2) : (rect.x + rect.width / 2); + return pt; + } + +}; + +/* +* 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. +*/ + +/** + * Single coordinate system creator. + */ + +/** + * Create single coordinate system and inject it into seriesModel. + * + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @return {Array.} + */ +function create$3(ecModel, api) { + var singles = []; + + ecModel.eachComponent('singleAxis', function (axisModel, idx) { + + var single = new Single(axisModel, ecModel, api); + single.name = 'single_' + idx; + single.resize(axisModel, api); + axisModel.coordinateSystem = single; + singles.push(single); + + }); + + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.get('coordinateSystem') === 'singleAxis') { + var singleAxisModel = ecModel.queryComponents({ + mainType: 'singleAxis', + index: seriesModel.get('singleAxisIndex'), + id: seriesModel.get('singleAxisId') + })[0]; + seriesModel.coordinateSystem = singleAxisModel && singleAxisModel.coordinateSystem; + } + }); + + return singles; +} + +CoordinateSystemManager.register('single', { + create: create$3, + dimensions: Single.prototype.dimensions +}); + +/* +* 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. +*/ + +/** + * @param {Object} opt {labelInside} + * @return {Object} { + * position, rotation, labelDirection, labelOffset, + * tickDirection, labelRotate, z2 + * } + */ +function layout$2(axisModel, opt) { + opt = opt || {}; + var single = axisModel.coordinateSystem; + var axis = axisModel.axis; + var layout = {}; + + var axisPosition = axis.position; + var orient = axis.orient; + + var rect = single.getRect(); + var rectBound = [rect.x, rect.x + rect.width, rect.y, rect.y + rect.height]; + + var positionMap = { + horizontal: {top: rectBound[2], bottom: rectBound[3]}, + vertical: {left: rectBound[0], right: rectBound[1]} + }; + + layout.position = [ + orient === 'vertical' + ? positionMap.vertical[axisPosition] + : rectBound[0], + orient === 'horizontal' + ? positionMap.horizontal[axisPosition] + : rectBound[3] + ]; + + var r = {horizontal: 0, vertical: 1}; + layout.rotation = Math.PI / 2 * r[orient]; + + var directionMap = {top: -1, bottom: 1, right: 1, left: -1}; + + layout.labelDirection = layout.tickDirection = + layout.nameDirection = directionMap[axisPosition]; + + if (axisModel.get('axisTick.inside')) { + layout.tickDirection = -layout.tickDirection; + } + + if (retrieve(opt.labelInside, axisModel.get('axisLabel.inside'))) { + layout.labelDirection = -layout.labelDirection; + } + + var labelRotation = opt.rotate; + labelRotation == null && (labelRotation = axisModel.get('axisLabel.rotate')); + layout.labelRotation = axisPosition === 'top' ? -labelRotation : labelRotation; + + layout.z2 = 1; + + return layout; +} + +/* +* 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. +*/ + + +var axisBuilderAttrs$2 = [ + 'axisLine', 'axisTickLabel', 'axisName' +]; + +var selfBuilderAttr = 'splitLine'; + +var SingleAxisView = AxisView.extend({ + + type: 'singleAxis', + + axisPointerClass: 'SingleAxisPointer', + + render: function (axisModel, ecModel, api, payload) { + + var group = this.group; + + group.removeAll(); + + var layout = layout$2(axisModel); + + var axisBuilder = new AxisBuilder(axisModel, layout); + + each$1(axisBuilderAttrs$2, axisBuilder.add, axisBuilder); + + group.add(axisBuilder.getGroup()); + + if (axisModel.get(selfBuilderAttr + '.show')) { + this['_' + selfBuilderAttr](axisModel); + } + + SingleAxisView.superCall(this, 'render', axisModel, ecModel, api, payload); + }, + + _splitLine: function (axisModel) { + var axis = axisModel.axis; + + if (axis.scale.isBlank()) { + return; + } + + var splitLineModel = axisModel.getModel('splitLine'); + var lineStyleModel = splitLineModel.getModel('lineStyle'); + var lineWidth = lineStyleModel.get('width'); + var lineColors = lineStyleModel.get('color'); + + lineColors = lineColors instanceof Array ? lineColors : [lineColors]; + + var gridRect = axisModel.coordinateSystem.getRect(); + var isHorizontal = axis.isHorizontal(); + + var splitLines = []; + var lineCount = 0; + + var ticksCoords = axis.getTicksCoords({ + tickModel: splitLineModel + }); + + var p1 = []; + var p2 = []; + + for (var i = 0; i < ticksCoords.length; ++i) { + var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord); + if (isHorizontal) { + p1[0] = tickCoord; + p1[1] = gridRect.y; + p2[0] = tickCoord; + p2[1] = gridRect.y + gridRect.height; + } + else { + p1[0] = gridRect.x; + p1[1] = tickCoord; + p2[0] = gridRect.x + gridRect.width; + p2[1] = tickCoord; + } + var colorIndex = (lineCount++) % lineColors.length; + splitLines[colorIndex] = splitLines[colorIndex] || []; + splitLines[colorIndex].push(new Line({ + subPixelOptimize: true, + shape: { + x1: p1[0], + y1: p1[1], + x2: p2[0], + y2: p2[1] + }, + style: { + lineWidth: lineWidth + }, + silent: true + })); + } + + for (var i = 0; i < splitLines.length; ++i) { + this.group.add(mergePath(splitLines[i], { + style: { + stroke: lineColors[i % lineColors.length], + lineDash: lineStyleModel.getLineDash(lineWidth), + lineWidth: lineWidth + }, + silent: true + })); + } + } +}); + +/* +* 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. +*/ + +var AxisModel$4 = ComponentModel.extend({ + + type: 'singleAxis', + + layoutMode: 'box', + + /** + * @type {module:echarts/coord/single/SingleAxis} + */ + axis: null, + + /** + * @type {module:echarts/coord/single/Single} + */ + coordinateSystem: null, + + /** + * @override + */ + getCoordSysModel: function () { + return this; + } + +}); + +var defaultOption$2 = { + + left: '5%', + top: '5%', + right: '5%', + bottom: '5%', + + type: 'value', + + position: 'bottom', + + orient: 'horizontal', + + axisLine: { + show: true, + lineStyle: { + width: 1, + type: 'solid' + } + }, + + // Single coordinate system and single axis is the, + // which is used as the parent tooltip model. + // same model, so we set default tooltip show as true. + tooltip: { + show: true + }, + + axisTick: { + show: true, + length: 6, + lineStyle: { + width: 1 + } + }, + + axisLabel: { + show: true, + interval: 'auto' + }, + + splitLine: { + show: true, + lineStyle: { + type: 'dashed', + opacity: 0.2 + } + } +}; + +function getAxisType$2(axisName, option) { + return option.type || (option.data ? 'category' : 'value'); +} + +merge(AxisModel$4.prototype, axisModelCommonMixin); + +axisModelCreator('single', AxisModel$4, getAxisType$2, defaultOption$2); + +/* +* 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. +*/ + +/** + * @param {Object} finder contains {seriesIndex, dataIndex, dataIndexInside} + * @param {module:echarts/model/Global} ecModel + * @return {Object} {point: [x, y], el: ...} point Will not be null. + */ +var findPointFromSeries = function (finder, ecModel) { + var point = []; + var seriesIndex = finder.seriesIndex; + var seriesModel; + if (seriesIndex == null || !( + seriesModel = ecModel.getSeriesByIndex(seriesIndex) + )) { + return {point: []}; + } + + var data = seriesModel.getData(); + var dataIndex = queryDataIndex(data, finder); + if (dataIndex == null || dataIndex < 0 || isArray(dataIndex)) { + return {point: []}; + } + + var el = data.getItemGraphicEl(dataIndex); + var coordSys = seriesModel.coordinateSystem; + + if (seriesModel.getTooltipPosition) { + point = seriesModel.getTooltipPosition(dataIndex) || []; + } + else if (coordSys && coordSys.dataToPoint) { + point = coordSys.dataToPoint( + data.getValues( + map(coordSys.dimensions, function (dim) { + return data.mapDimension(dim); + }), dataIndex, true + ) + ) || []; + } + else if (el) { + // Use graphic bounding rect + var rect = el.getBoundingRect().clone(); + rect.applyTransform(el.transform); + point = [ + rect.x + rect.width / 2, + rect.y + rect.height / 2 + ]; + } + + return {point: point, el: el}; +}; + +/* +* 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. +*/ + +var each$14 = each$1; +var curry$3 = curry; +var inner$9 = makeInner(); + +/** + * Basic logic: check all axis, if they do not demand show/highlight, + * then hide/downplay them. + * + * @param {Object} coordSysAxesInfo + * @param {Object} payload + * @param {string} [payload.currTrigger] 'click' | 'mousemove' | 'leave' + * @param {Array.} [payload.x] x and y, which are mandatory, specify a point to + * trigger axisPointer and tooltip. + * @param {Array.} [payload.y] x and y, which are mandatory, specify a point to + * trigger axisPointer and tooltip. + * @param {Object} [payload.seriesIndex] finder, optional, restrict target axes. + * @param {Object} [payload.dataIndex] finder, restrict target axes. + * @param {Object} [payload.axesInfo] finder, restrict target axes. + * [{ + * axisDim: 'x'|'y'|'angle'|..., + * axisIndex: ..., + * value: ... + * }, ...] + * @param {Function} [payload.dispatchAction] + * @param {Object} [payload.tooltipOption] + * @param {Object|Array.|Function} [payload.position] Tooltip position, + * which can be specified in dispatchAction + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + * @return {Object} content of event obj for echarts.connect. + */ +var axisTrigger = function (payload, ecModel, api) { + var currTrigger = payload.currTrigger; + var point = [payload.x, payload.y]; + var finder = payload; + var dispatchAction = payload.dispatchAction || bind(api.dispatchAction, api); + var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo; + + // Pending + // See #6121. But we are not able to reproduce it yet. + if (!coordSysAxesInfo) { + return; + } + + if (illegalPoint(point)) { + // Used in the default behavior of `connection`: use the sample seriesIndex + // and dataIndex. And also used in the tooltipView trigger. + point = findPointFromSeries({ + seriesIndex: finder.seriesIndex, + // Do not use dataIndexInside from other ec instance. + // FIXME: auto detect it? + dataIndex: finder.dataIndex + }, ecModel).point; + } + var isIllegalPoint = illegalPoint(point); + + // Axis and value can be specified when calling dispatchAction({type: 'updateAxisPointer'}). + // Notice: In this case, it is difficult to get the `point` (which is necessary to show + // tooltip, so if point is not given, we just use the point found by sample seriesIndex + // and dataIndex. + var inputAxesInfo = finder.axesInfo; + + var axesInfo = coordSysAxesInfo.axesInfo; + var shouldHide = currTrigger === 'leave' || illegalPoint(point); + var outputFinder = {}; + + var showValueMap = {}; + var dataByCoordSys = {list: [], map: {}}; + var updaters = { + showPointer: curry$3(showPointer, showValueMap), + showTooltip: curry$3(showTooltip, dataByCoordSys) + }; + + // Process for triggered axes. + each$14(coordSysAxesInfo.coordSysMap, function (coordSys, coordSysKey) { + // If a point given, it must be contained by the coordinate system. + var coordSysContainsPoint = isIllegalPoint || coordSys.containPoint(point); + + each$14(coordSysAxesInfo.coordSysAxesInfo[coordSysKey], function (axisInfo, key) { + var axis = axisInfo.axis; + var inputAxisInfo = findInputAxisInfo(inputAxesInfo, axisInfo); + // If no inputAxesInfo, no axis is restricted. + if (!shouldHide && coordSysContainsPoint && (!inputAxesInfo || inputAxisInfo)) { + var val = inputAxisInfo && inputAxisInfo.value; + if (val == null && !isIllegalPoint) { + val = axis.pointToData(point); + } + val != null && processOnAxis(axisInfo, val, updaters, false, outputFinder); + } + }); + }); + + // Process for linked axes. + var linkTriggers = {}; + each$14(axesInfo, function (tarAxisInfo, tarKey) { + var linkGroup = tarAxisInfo.linkGroup; + + // If axis has been triggered in the previous stage, it should not be triggered by link. + if (linkGroup && !showValueMap[tarKey]) { + each$14(linkGroup.axesInfo, function (srcAxisInfo, srcKey) { + var srcValItem = showValueMap[srcKey]; + // If srcValItem exist, source axis is triggered, so link to target axis. + if (srcAxisInfo !== tarAxisInfo && srcValItem) { + var val = srcValItem.value; + linkGroup.mapper && (val = tarAxisInfo.axis.scale.parse(linkGroup.mapper( + val, makeMapperParam(srcAxisInfo), makeMapperParam(tarAxisInfo) + ))); + linkTriggers[tarAxisInfo.key] = val; + } + }); + } + }); + each$14(linkTriggers, function (val, tarKey) { + processOnAxis(axesInfo[tarKey], val, updaters, true, outputFinder); + }); + + updateModelActually(showValueMap, axesInfo, outputFinder); + dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction); + dispatchHighDownActually(axesInfo, dispatchAction, api); + + return outputFinder; +}; + +function processOnAxis(axisInfo, newValue, updaters, dontSnap, outputFinder) { + var axis = axisInfo.axis; + + if (axis.scale.isBlank() || !axis.containData(newValue)) { + return; + } + + if (!axisInfo.involveSeries) { + updaters.showPointer(axisInfo, newValue); + return; + } + + // Heavy calculation. So put it after axis.containData checking. + var payloadInfo = buildPayloadsBySeries(newValue, axisInfo); + var payloadBatch = payloadInfo.payloadBatch; + var snapToValue = payloadInfo.snapToValue; + + // Fill content of event obj for echarts.connect. + // By defualt use the first involved series data as a sample to connect. + if (payloadBatch[0] && outputFinder.seriesIndex == null) { + extend(outputFinder, payloadBatch[0]); + } + + // If no linkSource input, this process is for collecting link + // target, where snap should not be accepted. + if (!dontSnap && axisInfo.snap) { + if (axis.containData(snapToValue) && snapToValue != null) { + newValue = snapToValue; + } + } + + updaters.showPointer(axisInfo, newValue, payloadBatch, outputFinder); + // Tooltip should always be snapToValue, otherwise there will be + // incorrect "axis value ~ series value" mapping displayed in tooltip. + updaters.showTooltip(axisInfo, payloadInfo, snapToValue); +} + +function buildPayloadsBySeries(value, axisInfo) { + var axis = axisInfo.axis; + var dim = axis.dim; + var snapToValue = value; + var payloadBatch = []; + var minDist = Number.MAX_VALUE; + var minDiff = -1; + + each$14(axisInfo.seriesModels, function (series, idx) { + var dataDim = series.getData().mapDimension(dim, true); + var seriesNestestValue; + var dataIndices; + + if (series.getAxisTooltipData) { + var result = series.getAxisTooltipData(dataDim, value, axis); + dataIndices = result.dataIndices; + seriesNestestValue = result.nestestValue; + } + else { + dataIndices = series.getData().indicesOfNearest( + dataDim[0], + value, + // Add a threshold to avoid find the wrong dataIndex + // when data length is not same. + // false, + axis.type === 'category' ? 0.5 : null + ); + if (!dataIndices.length) { + return; + } + seriesNestestValue = series.getData().get(dataDim[0], dataIndices[0]); + } + + if (seriesNestestValue == null || !isFinite(seriesNestestValue)) { + return; + } + + var diff = value - seriesNestestValue; + var dist = Math.abs(diff); + // Consider category case + if (dist <= minDist) { + if (dist < minDist || (diff >= 0 && minDiff < 0)) { + minDist = dist; + minDiff = diff; + snapToValue = seriesNestestValue; + payloadBatch.length = 0; + } + each$14(dataIndices, function (dataIndex) { + payloadBatch.push({ + seriesIndex: series.seriesIndex, + dataIndexInside: dataIndex, + dataIndex: series.getData().getRawIndex(dataIndex) + }); + }); + } + }); + + return { + payloadBatch: payloadBatch, + snapToValue: snapToValue + }; +} + +function showPointer(showValueMap, axisInfo, value, payloadBatch) { + showValueMap[axisInfo.key] = {value: value, payloadBatch: payloadBatch}; +} + +function showTooltip(dataByCoordSys, axisInfo, payloadInfo, value) { + var payloadBatch = payloadInfo.payloadBatch; + var axis = axisInfo.axis; + var axisModel = axis.model; + var axisPointerModel = axisInfo.axisPointerModel; + + // If no data, do not create anything in dataByCoordSys, + // whose length will be used to judge whether dispatch action. + if (!axisInfo.triggerTooltip || !payloadBatch.length) { + return; + } + + var coordSysModel = axisInfo.coordSys.model; + var coordSysKey = makeKey(coordSysModel); + var coordSysItem = dataByCoordSys.map[coordSysKey]; + if (!coordSysItem) { + coordSysItem = dataByCoordSys.map[coordSysKey] = { + coordSysId: coordSysModel.id, + coordSysIndex: coordSysModel.componentIndex, + coordSysType: coordSysModel.type, + coordSysMainType: coordSysModel.mainType, + dataByAxis: [] + }; + dataByCoordSys.list.push(coordSysItem); + } + + coordSysItem.dataByAxis.push({ + axisDim: axis.dim, + axisIndex: axisModel.componentIndex, + axisType: axisModel.type, + axisId: axisModel.id, + value: value, + // Caustion: viewHelper.getValueLabel is actually on "view stage", which + // depends that all models have been updated. So it should not be performed + // here. Considering axisPointerModel used here is volatile, which is hard + // to be retrieve in TooltipView, we prepare parameters here. + valueLabelOpt: { + precision: axisPointerModel.get('label.precision'), + formatter: axisPointerModel.get('label.formatter') + }, + seriesDataIndices: payloadBatch.slice() + }); +} + +function updateModelActually(showValueMap, axesInfo, outputFinder) { + var outputAxesInfo = outputFinder.axesInfo = []; + // Basic logic: If no 'show' required, 'hide' this axisPointer. + each$14(axesInfo, function (axisInfo, key) { + var option = axisInfo.axisPointerModel.option; + var valItem = showValueMap[key]; + + if (valItem) { + !axisInfo.useHandle && (option.status = 'show'); + option.value = valItem.value; + // For label formatter param and highlight. + option.seriesDataIndices = (valItem.payloadBatch || []).slice(); + } + // When always show (e.g., handle used), remain + // original value and status. + else { + // If hide, value still need to be set, consider + // click legend to toggle axis blank. + !axisInfo.useHandle && (option.status = 'hide'); + } + + // If status is 'hide', should be no info in payload. + option.status === 'show' && outputAxesInfo.push({ + axisDim: axisInfo.axis.dim, + axisIndex: axisInfo.axis.model.componentIndex, + value: option.value + }); + }); +} + +function dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction) { + // Basic logic: If no showTip required, hideTip will be dispatched. + if (illegalPoint(point) || !dataByCoordSys.list.length) { + dispatchAction({type: 'hideTip'}); + return; + } + + // In most case only one axis (or event one series is used). It is + // convinient to fetch payload.seriesIndex and payload.dataIndex + // dirtectly. So put the first seriesIndex and dataIndex of the first + // axis on the payload. + var sampleItem = ((dataByCoordSys.list[0].dataByAxis[0] || {}).seriesDataIndices || [])[0] || {}; + + dispatchAction({ + type: 'showTip', + escapeConnect: true, + x: point[0], + y: point[1], + tooltipOption: payload.tooltipOption, + position: payload.position, + dataIndexInside: sampleItem.dataIndexInside, + dataIndex: sampleItem.dataIndex, + seriesIndex: sampleItem.seriesIndex, + dataByCoordSys: dataByCoordSys.list + }); +} + +function dispatchHighDownActually(axesInfo, dispatchAction, api) { + // FIXME + // highlight status modification shoule be a stage of main process? + // (Consider confilct (e.g., legend and axisPointer) and setOption) + + var zr = api.getZr(); + var highDownKey = 'axisPointerLastHighlights'; + var lastHighlights = inner$9(zr)[highDownKey] || {}; + var newHighlights = inner$9(zr)[highDownKey] = {}; + + // Update highlight/downplay status according to axisPointer model. + // Build hash map and remove duplicate incidentally. + each$14(axesInfo, function (axisInfo, key) { + var option = axisInfo.axisPointerModel.option; + option.status === 'show' && each$14(option.seriesDataIndices, function (batchItem) { + var key = batchItem.seriesIndex + ' | ' + batchItem.dataIndex; + newHighlights[key] = batchItem; + }); + }); + + // Diff. + var toHighlight = []; + var toDownplay = []; + each$1(lastHighlights, function (batchItem, key) { + !newHighlights[key] && toDownplay.push(batchItem); + }); + each$1(newHighlights, function (batchItem, key) { + !lastHighlights[key] && toHighlight.push(batchItem); + }); + + toDownplay.length && api.dispatchAction({ + type: 'downplay', escapeConnect: true, batch: toDownplay + }); + toHighlight.length && api.dispatchAction({ + type: 'highlight', escapeConnect: true, batch: toHighlight + }); +} + +function findInputAxisInfo(inputAxesInfo, axisInfo) { + for (var i = 0; i < (inputAxesInfo || []).length; i++) { + var inputAxisInfo = inputAxesInfo[i]; + if (axisInfo.axis.dim === inputAxisInfo.axisDim + && axisInfo.axis.model.componentIndex === inputAxisInfo.axisIndex + ) { + return inputAxisInfo; + } + } +} + +function makeMapperParam(axisInfo) { + var axisModel = axisInfo.axis.model; + var item = {}; + var dim = item.axisDim = axisInfo.axis.dim; + item.axisIndex = item[dim + 'AxisIndex'] = axisModel.componentIndex; + item.axisName = item[dim + 'AxisName'] = axisModel.name; + item.axisId = item[dim + 'AxisId'] = axisModel.id; + return item; +} + +function illegalPoint(point) { + return !point || point[0] == null || isNaN(point[0]) || point[1] == null || isNaN(point[1]); +} + +/* +* 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. +*/ + +var AxisPointerModel = extendComponentModel({ + + type: 'axisPointer', + + coordSysAxesInfo: null, + + defaultOption: { + // 'auto' means that show when triggered by tooltip or handle. + show: 'auto', + // 'click' | 'mousemove' | 'none' + triggerOn: null, // set default in AxisPonterView.js + + zlevel: 0, + z: 50, + + type: 'line', // 'line' 'shadow' 'cross' 'none'. + // axispointer triggered by tootip determine snap automatically, + // see `modelHelper`. + snap: false, + triggerTooltip: true, + + value: null, + status: null, // Init value depends on whether handle is used. + + // [group0, group1, ...] + // Each group can be: { + // mapper: function () {}, + // singleTooltip: 'multiple', // 'multiple' or 'single' + // xAxisId: ..., + // yAxisName: ..., + // angleAxisIndex: ... + // } + // mapper: can be ignored. + // input: {axisInfo, value} + // output: {axisInfo, value} + link: [], + + // Do not set 'auto' here, otherwise global animation: false + // will not effect at this axispointer. + animation: null, + animationDurationUpdate: 200, + + lineStyle: { + color: '#aaa', + width: 1, + type: 'solid' + }, + + shadowStyle: { + color: 'rgba(150,150,150,0.3)' + }, + + label: { + show: true, + formatter: null, // string | Function + precision: 'auto', // Or a number like 0, 1, 2 ... + margin: 3, + color: '#fff', + padding: [5, 7, 5, 7], + backgroundColor: 'auto', // default: axis line color + borderColor: null, + borderWidth: 0, + shadowBlur: 3, + shadowColor: '#aaa' + // Considering applicability, common style should + // better not have shadowOffset. + // shadowOffsetX: 0, + // shadowOffsetY: 2 + }, + + handle: { + show: false, + /* eslint-disable */ + icon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z', // jshint ignore:line + /* eslint-enable */ + size: 45, + // handle margin is from symbol center to axis, which is stable when circular move. + margin: 50, + // color: '#1b8bbd' + // color: '#2f4554' + color: '#333', + shadowBlur: 3, + shadowColor: '#aaa', + shadowOffsetX: 0, + shadowOffsetY: 2, + + // For mobile performance + throttle: 40 + } + } + +}); + +/* +* 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. +*/ + +var inner$10 = makeInner(); +var each$15 = each$1; + +/** + * @param {string} key + * @param {module:echarts/ExtensionAPI} api + * @param {Function} handler + * param: {string} currTrigger + * param: {Array.} point + */ +function register(key, api, handler) { + if (env$1.node) { + return; + } + + var zr = api.getZr(); + inner$10(zr).records || (inner$10(zr).records = {}); + + initGlobalListeners(zr, api); + + var record = inner$10(zr).records[key] || (inner$10(zr).records[key] = {}); + record.handler = handler; +} + +function initGlobalListeners(zr, api) { + if (inner$10(zr).initialized) { + return; + } + + inner$10(zr).initialized = true; + + useHandler('click', curry(doEnter, 'click')); + useHandler('mousemove', curry(doEnter, 'mousemove')); + // useHandler('mouseout', onLeave); + useHandler('globalout', onLeave); + + function useHandler(eventType, cb) { + zr.on(eventType, function (e) { + var dis = makeDispatchAction(api); + + each$15(inner$10(zr).records, function (record) { + record && cb(record, e, dis.dispatchAction); + }); + + dispatchTooltipFinally(dis.pendings, api); + }); + } +} + +function dispatchTooltipFinally(pendings, api) { + var showLen = pendings.showTip.length; + var hideLen = pendings.hideTip.length; + + var actuallyPayload; + if (showLen) { + actuallyPayload = pendings.showTip[showLen - 1]; + } + else if (hideLen) { + actuallyPayload = pendings.hideTip[hideLen - 1]; + } + if (actuallyPayload) { + actuallyPayload.dispatchAction = null; + api.dispatchAction(actuallyPayload); + } +} + +function onLeave(record, e, dispatchAction) { + record.handler('leave', null, dispatchAction); +} + +function doEnter(currTrigger, record, e, dispatchAction) { + record.handler(currTrigger, e, dispatchAction); +} + +function makeDispatchAction(api) { + var pendings = { + showTip: [], + hideTip: [] + }; + // FIXME + // better approach? + // 'showTip' and 'hideTip' can be triggered by axisPointer and tooltip, + // which may be conflict, (axisPointer call showTip but tooltip call hideTip); + // So we have to add "final stage" to merge those dispatched actions. + var dispatchAction = function (payload) { + var pendingList = pendings[payload.type]; + if (pendingList) { + pendingList.push(payload); + } + else { + payload.dispatchAction = dispatchAction; + api.dispatchAction(payload); + } + }; + + return { + dispatchAction: dispatchAction, + pendings: pendings + }; +} + +/** + * @param {string} key + * @param {module:echarts/ExtensionAPI} api + */ +function unregister(key, api) { + if (env$1.node) { + return; + } + var zr = api.getZr(); + var record = (inner$10(zr).records || {})[key]; + if (record) { + inner$10(zr).records[key] = null; + } +} + +/* +* 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. +*/ + +var AxisPointerView = extendComponentView({ + + type: 'axisPointer', + + render: function (globalAxisPointerModel, ecModel, api) { + var globalTooltipModel = ecModel.getComponent('tooltip'); + var triggerOn = globalAxisPointerModel.get('triggerOn') + || (globalTooltipModel && globalTooltipModel.get('triggerOn') || 'mousemove|click'); + + // Register global listener in AxisPointerView to enable + // AxisPointerView to be independent to Tooltip. + register( + 'axisPointer', + api, + function (currTrigger, e, dispatchAction) { + // If 'none', it is not controlled by mouse totally. + if (triggerOn !== 'none' + && (currTrigger === 'leave' || triggerOn.indexOf(currTrigger) >= 0) + ) { + dispatchAction({ + type: 'updateAxisPointer', + currTrigger: currTrigger, + x: e && e.offsetX, + y: e && e.offsetY + }); + } + } + ); + }, + + /** + * @override + */ + remove: function (ecModel, api) { + unregister(api.getZr(), 'axisPointer'); + AxisPointerView.superApply(this._model, 'remove', arguments); + }, + + /** + * @override + */ + dispose: function (ecModel, api) { + unregister('axisPointer', api); + AxisPointerView.superApply(this._model, 'dispose', arguments); + } + +}); + +/* +* 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. +*/ + +var inner$11 = makeInner(); +var clone$4 = clone; +var bind$2 = bind; + +/** + * Base axis pointer class in 2D. + * Implemenents {module:echarts/component/axis/IAxisPointer}. + */ +function BaseAxisPointer() { +} + +BaseAxisPointer.prototype = { + + /** + * @private + */ + _group: null, + + /** + * @private + */ + _lastGraphicKey: null, + + /** + * @private + */ + _handle: null, + + /** + * @private + */ + _dragging: false, + + /** + * @private + */ + _lastValue: null, + + /** + * @private + */ + _lastStatus: null, + + /** + * @private + */ + _payloadInfo: null, + + /** + * In px, arbitrary value. Do not set too small, + * no animation is ok for most cases. + * @protected + */ + animationThreshold: 15, + + /** + * @implement + */ + render: function (axisModel, axisPointerModel, api, forceRender) { + var value = axisPointerModel.get('value'); + var status = axisPointerModel.get('status'); + + // Bind them to `this`, not in closure, otherwise they will not + // be replaced when user calling setOption in not merge mode. + this._axisModel = axisModel; + this._axisPointerModel = axisPointerModel; + this._api = api; + + // Optimize: `render` will be called repeatly during mouse move. + // So it is power consuming if performing `render` each time, + // especially on mobile device. + if (!forceRender + && this._lastValue === value + && this._lastStatus === status + ) { + return; + } + this._lastValue = value; + this._lastStatus = status; + + var group = this._group; + var handle = this._handle; + + if (!status || status === 'hide') { + // Do not clear here, for animation better. + group && group.hide(); + handle && handle.hide(); + return; + } + group && group.show(); + handle && handle.show(); + + // Otherwise status is 'show' + var elOption = {}; + this.makeElOption(elOption, value, axisModel, axisPointerModel, api); + + // Enable change axis pointer type. + var graphicKey = elOption.graphicKey; + if (graphicKey !== this._lastGraphicKey) { + this.clear(api); + } + this._lastGraphicKey = graphicKey; + + var moveAnimation = this._moveAnimation = + this.determineAnimation(axisModel, axisPointerModel); + + if (!group) { + group = this._group = new Group(); + this.createPointerEl(group, elOption, axisModel, axisPointerModel); + this.createLabelEl(group, elOption, axisModel, axisPointerModel); + api.getZr().add(group); + } + else { + var doUpdateProps = curry(updateProps$1, axisPointerModel, moveAnimation); + this.updatePointerEl(group, elOption, doUpdateProps, axisPointerModel); + this.updateLabelEl(group, elOption, doUpdateProps, axisPointerModel); + } + + updateMandatoryProps(group, axisPointerModel, true); + + this._renderHandle(value); + }, + + /** + * @implement + */ + remove: function (api) { + this.clear(api); + }, + + /** + * @implement + */ + dispose: function (api) { + this.clear(api); + }, + + /** + * @protected + */ + determineAnimation: function (axisModel, axisPointerModel) { + var animation = axisPointerModel.get('animation'); + var axis = axisModel.axis; + var isCategoryAxis = axis.type === 'category'; + var useSnap = axisPointerModel.get('snap'); + + // Value axis without snap always do not snap. + if (!useSnap && !isCategoryAxis) { + return false; + } + + if (animation === 'auto' || animation == null) { + var animationThreshold = this.animationThreshold; + if (isCategoryAxis && axis.getBandWidth() > animationThreshold) { + return true; + } + + // It is important to auto animation when snap used. Consider if there is + // a dataZoom, animation will be disabled when too many points exist, while + // it will be enabled for better visual effect when little points exist. + if (useSnap) { + var seriesDataCount = getAxisInfo(axisModel).seriesDataCount; + var axisExtent = axis.getExtent(); + // Approximate band width + return Math.abs(axisExtent[0] - axisExtent[1]) / seriesDataCount > animationThreshold; + } + + return false; + } + + return animation === true; + }, + + /** + * add {pointer, label, graphicKey} to elOption + * @protected + */ + makeElOption: function (elOption, value, axisModel, axisPointerModel, api) { + // Shoule be implemenented by sub-class. + }, + + /** + * @protected + */ + createPointerEl: function (group, elOption, axisModel, axisPointerModel) { + var pointerOption = elOption.pointer; + if (pointerOption) { + var pointerEl = inner$11(group).pointerEl = new graphic[pointerOption.type]( + clone$4(elOption.pointer) + ); + group.add(pointerEl); + } + }, + + /** + * @protected + */ + createLabelEl: function (group, elOption, axisModel, axisPointerModel) { + if (elOption.label) { + var labelEl = inner$11(group).labelEl = new Rect( + clone$4(elOption.label) + ); + + group.add(labelEl); + updateLabelShowHide(labelEl, axisPointerModel); + } + }, + + /** + * @protected + */ + updatePointerEl: function (group, elOption, updateProps$$1) { + var pointerEl = inner$11(group).pointerEl; + if (pointerEl && elOption.pointer) { + pointerEl.setStyle(elOption.pointer.style); + updateProps$$1(pointerEl, {shape: elOption.pointer.shape}); + } + }, + + /** + * @protected + */ + updateLabelEl: function (group, elOption, updateProps$$1, axisPointerModel) { + var labelEl = inner$11(group).labelEl; + if (labelEl) { + labelEl.setStyle(elOption.label.style); + updateProps$$1(labelEl, { + // Consider text length change in vertical axis, animation should + // be used on shape, otherwise the effect will be weird. + shape: elOption.label.shape, + position: elOption.label.position + }); + + updateLabelShowHide(labelEl, axisPointerModel); + } + }, + + /** + * @private + */ + _renderHandle: function (value) { + if (this._dragging || !this.updateHandleTransform) { + return; + } + + var axisPointerModel = this._axisPointerModel; + var zr = this._api.getZr(); + var handle = this._handle; + var handleModel = axisPointerModel.getModel('handle'); + + var status = axisPointerModel.get('status'); + if (!handleModel.get('show') || !status || status === 'hide') { + handle && zr.remove(handle); + this._handle = null; + return; + } + + var isInit; + if (!this._handle) { + isInit = true; + handle = this._handle = createIcon( + handleModel.get('icon'), + { + cursor: 'move', + draggable: true, + onmousemove: function (e) { + // Fot mobile devicem, prevent screen slider on the button. + stop(e.event); + }, + onmousedown: bind$2(this._onHandleDragMove, this, 0, 0), + drift: bind$2(this._onHandleDragMove, this), + ondragend: bind$2(this._onHandleDragEnd, this) + } + ); + zr.add(handle); + } + + updateMandatoryProps(handle, axisPointerModel, false); + + // update style + var includeStyles = [ + 'color', 'borderColor', 'borderWidth', 'opacity', + 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY' + ]; + handle.setStyle(handleModel.getItemStyle(null, includeStyles)); + + // update position + var handleSize = handleModel.get('size'); + if (!isArray(handleSize)) { + handleSize = [handleSize, handleSize]; + } + handle.attr('scale', [handleSize[0] / 2, handleSize[1] / 2]); + + createOrUpdate( + this, + '_doDispatchAxisPointer', + handleModel.get('throttle') || 0, + 'fixRate' + ); + + this._moveHandleToValue(value, isInit); + }, + + /** + * @private + */ + _moveHandleToValue: function (value, isInit) { + updateProps$1( + this._axisPointerModel, + !isInit && this._moveAnimation, + this._handle, + getHandleTransProps(this.getHandleTransform( + value, this._axisModel, this._axisPointerModel + )) + ); + }, + + /** + * @private + */ + _onHandleDragMove: function (dx, dy) { + var handle = this._handle; + if (!handle) { + return; + } + + this._dragging = true; + + // Persistent for throttle. + var trans = this.updateHandleTransform( + getHandleTransProps(handle), + [dx, dy], + this._axisModel, + this._axisPointerModel + ); + this._payloadInfo = trans; + + handle.stopAnimation(); + handle.attr(getHandleTransProps(trans)); + inner$11(handle).lastProp = null; + + this._doDispatchAxisPointer(); + }, + + /** + * Throttled method. + * @private + */ + _doDispatchAxisPointer: function () { + var handle = this._handle; + if (!handle) { + return; + } + + var payloadInfo = this._payloadInfo; + var axisModel = this._axisModel; + this._api.dispatchAction({ + type: 'updateAxisPointer', + x: payloadInfo.cursorPoint[0], + y: payloadInfo.cursorPoint[1], + tooltipOption: payloadInfo.tooltipOption, + axesInfo: [{ + axisDim: axisModel.axis.dim, + axisIndex: axisModel.componentIndex + }] + }); + }, + + /** + * @private + */ + _onHandleDragEnd: function (moveAnimation) { + this._dragging = false; + var handle = this._handle; + if (!handle) { + return; + } + + var value = this._axisPointerModel.get('value'); + // Consider snap or categroy axis, handle may be not consistent with + // axisPointer. So move handle to align the exact value position when + // drag ended. + this._moveHandleToValue(value); + + // For the effect: tooltip will be shown when finger holding on handle + // button, and will be hidden after finger left handle button. + this._api.dispatchAction({ + type: 'hideTip' + }); + }, + + /** + * Should be implemenented by sub-class if support `handle`. + * @protected + * @param {number} value + * @param {module:echarts/model/Model} axisModel + * @param {module:echarts/model/Model} axisPointerModel + * @return {Object} {position: [x, y], rotation: 0} + */ + getHandleTransform: null, + + /** + * * Should be implemenented by sub-class if support `handle`. + * @protected + * @param {Object} transform {position, rotation} + * @param {Array.} delta [dx, dy] + * @param {module:echarts/model/Model} axisModel + * @param {module:echarts/model/Model} axisPointerModel + * @return {Object} {position: [x, y], rotation: 0, cursorPoint: [x, y]} + */ + updateHandleTransform: null, + + /** + * @private + */ + clear: function (api) { + this._lastValue = null; + this._lastStatus = null; + + var zr = api.getZr(); + var group = this._group; + var handle = this._handle; + if (zr && group) { + this._lastGraphicKey = null; + group && zr.remove(group); + handle && zr.remove(handle); + this._group = null; + this._handle = null; + this._payloadInfo = null; + } + }, + + /** + * @protected + */ + doClear: function () { + // Implemented by sub-class if necessary. + }, + + /** + * @protected + * @param {Array.} xy + * @param {Array.} wh + * @param {number} [xDimIndex=0] or 1 + */ + buildLabel: function (xy, wh, xDimIndex) { + xDimIndex = xDimIndex || 0; + return { + x: xy[xDimIndex], + y: xy[1 - xDimIndex], + width: wh[xDimIndex], + height: wh[1 - xDimIndex] + }; + } +}; + +BaseAxisPointer.prototype.constructor = BaseAxisPointer; + + +function updateProps$1(animationModel, moveAnimation, el, props) { + // Animation optimize. + if (!propsEqual(inner$11(el).lastProp, props)) { + inner$11(el).lastProp = props; + moveAnimation + ? updateProps(el, props, animationModel) + : (el.stopAnimation(), el.attr(props)); + } +} + +function propsEqual(lastProps, newProps) { + if (isObject$1(lastProps) && isObject$1(newProps)) { + var equals = true; + each$1(newProps, function (item, key) { + equals = equals && propsEqual(lastProps[key], item); + }); + return !!equals; + } + else { + return lastProps === newProps; + } +} + +function updateLabelShowHide(labelEl, axisPointerModel) { + labelEl[axisPointerModel.get('label.show') ? 'show' : 'hide'](); +} + +function getHandleTransProps(trans) { + return { + position: trans.position.slice(), + rotation: trans.rotation || 0 + }; +} + +function updateMandatoryProps(group, axisPointerModel, silent) { + var z = axisPointerModel.get('z'); + var zlevel = axisPointerModel.get('zlevel'); + + group && group.traverse(function (el) { + if (el.type !== 'group') { + z != null && (el.z = z); + zlevel != null && (el.zlevel = zlevel); + el.silent = silent; + } + }); +} + +enableClassExtend(BaseAxisPointer); + +/* +* 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. +*/ + +/** + * @param {module:echarts/model/Model} axisPointerModel + */ +function buildElStyle(axisPointerModel) { + var axisPointerType = axisPointerModel.get('type'); + var styleModel = axisPointerModel.getModel(axisPointerType + 'Style'); + var style; + if (axisPointerType === 'line') { + style = styleModel.getLineStyle(); + style.fill = null; + } + else if (axisPointerType === 'shadow') { + style = styleModel.getAreaStyle(); + style.stroke = null; + } + return style; +} + +/** + * @param {Function} labelPos {align, verticalAlign, position} + */ +function buildLabelElOption( + elOption, axisModel, axisPointerModel, api, labelPos +) { + var value = axisPointerModel.get('value'); + var text = getValueLabel( + value, axisModel.axis, axisModel.ecModel, + axisPointerModel.get('seriesDataIndices'), + { + precision: axisPointerModel.get('label.precision'), + formatter: axisPointerModel.get('label.formatter') + } + ); + var labelModel = axisPointerModel.getModel('label'); + var paddings = normalizeCssArray$1(labelModel.get('padding') || 0); + + var font = labelModel.getFont(); + var textRect = getBoundingRect(text, font); + + var position = labelPos.position; + var width = textRect.width + paddings[1] + paddings[3]; + var height = textRect.height + paddings[0] + paddings[2]; + + // Adjust by align. + var align = labelPos.align; + align === 'right' && (position[0] -= width); + align === 'center' && (position[0] -= width / 2); + var verticalAlign = labelPos.verticalAlign; + verticalAlign === 'bottom' && (position[1] -= height); + verticalAlign === 'middle' && (position[1] -= height / 2); + + // Not overflow ec container + confineInContainer(position, width, height, api); + + var bgColor = labelModel.get('backgroundColor'); + if (!bgColor || bgColor === 'auto') { + bgColor = axisModel.get('axisLine.lineStyle.color'); + } + + elOption.label = { + shape: {x: 0, y: 0, width: width, height: height, r: labelModel.get('borderRadius')}, + position: position.slice(), + // TODO: rich + style: { + text: text, + textFont: font, + textFill: labelModel.getTextColor(), + textPosition: 'inside', + textPadding: paddings, + fill: bgColor, + stroke: labelModel.get('borderColor') || 'transparent', + lineWidth: labelModel.get('borderWidth') || 0, + shadowBlur: labelModel.get('shadowBlur'), + shadowColor: labelModel.get('shadowColor'), + shadowOffsetX: labelModel.get('shadowOffsetX'), + shadowOffsetY: labelModel.get('shadowOffsetY') + }, + // Lable should be over axisPointer. + z2: 10 + }; +} + +// Do not overflow ec container +function confineInContainer(position, width, height, api) { + var viewWidth = api.getWidth(); + var viewHeight = api.getHeight(); + position[0] = Math.min(position[0] + width, viewWidth) - width; + position[1] = Math.min(position[1] + height, viewHeight) - height; + position[0] = Math.max(position[0], 0); + position[1] = Math.max(position[1], 0); +} + +/** + * @param {number} value + * @param {module:echarts/coord/Axis} axis + * @param {module:echarts/model/Global} ecModel + * @param {Object} opt + * @param {Array.} seriesDataIndices + * @param {number|string} opt.precision 'auto' or a number + * @param {string|Function} opt.formatter label formatter + */ +function getValueLabel(value, axis, ecModel, seriesDataIndices, opt) { + value = axis.scale.parse(value); + var text = axis.scale.getLabel( + // If `precision` is set, width can be fixed (like '12.00500'), which + // helps to debounce when when moving label. + value, {precision: opt.precision} + ); + var formatter = opt.formatter; + + if (formatter) { + var params = { + value: getAxisRawValue(axis, value), + axisDimension: axis.dim, + axisIndex: axis.index, + seriesData: [] + }; + each$1(seriesDataIndices, function (idxItem) { + var series = ecModel.getSeriesByIndex(idxItem.seriesIndex); + var dataIndex = idxItem.dataIndexInside; + var dataParams = series && series.getDataParams(dataIndex); + dataParams && params.seriesData.push(dataParams); + }); + + if (isString(formatter)) { + text = formatter.replace('{value}', text); + } + else if (isFunction$1(formatter)) { + text = formatter(params); + } + } + + return text; +} + +/** + * @param {module:echarts/coord/Axis} axis + * @param {number} value + * @param {Object} layoutInfo { + * rotation, position, labelOffset, labelDirection, labelMargin + * } + */ +function getTransformedPosition(axis, value, layoutInfo) { + var transform = create$1(); + rotate(transform, transform, layoutInfo.rotation); + translate(transform, transform, layoutInfo.position); + + return applyTransform$1([ + axis.dataToCoord(value), + (layoutInfo.labelOffset || 0) + + (layoutInfo.labelDirection || 1) * (layoutInfo.labelMargin || 0) + ], transform); +} + +function buildCartesianSingleLabelElOption( + value, elOption, layoutInfo, axisModel, axisPointerModel, api +) { + var textLayout = AxisBuilder.innerTextLayout( + layoutInfo.rotation, 0, layoutInfo.labelDirection + ); + layoutInfo.labelMargin = axisPointerModel.get('label.margin'); + buildLabelElOption(elOption, axisModel, axisPointerModel, api, { + position: getTransformedPosition(axisModel.axis, value, layoutInfo), + align: textLayout.textAlign, + verticalAlign: textLayout.textVerticalAlign + }); +} + +/** + * @param {Array.} p1 + * @param {Array.} p2 + * @param {number} [xDimIndex=0] or 1 + */ +function makeLineShape(p1, p2, xDimIndex) { + xDimIndex = xDimIndex || 0; + return { + x1: p1[xDimIndex], + y1: p1[1 - xDimIndex], + x2: p2[xDimIndex], + y2: p2[1 - xDimIndex] + }; +} + +/** + * @param {Array.} xy + * @param {Array.} wh + * @param {number} [xDimIndex=0] or 1 + */ +function makeRectShape(xy, wh, xDimIndex) { + xDimIndex = xDimIndex || 0; + return { + x: xy[xDimIndex], + y: xy[1 - xDimIndex], + width: wh[xDimIndex], + height: wh[1 - xDimIndex] + }; +} + +function makeSectorShape(cx, cy, r0, r, startAngle, endAngle) { + return { + cx: cx, + cy: cy, + r0: r0, + r: r, + startAngle: startAngle, + endAngle: endAngle, + clockwise: true + }; +} + +/* +* 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. +*/ + +var CartesianAxisPointer = BaseAxisPointer.extend({ + + /** + * @override + */ + makeElOption: function (elOption, value, axisModel, axisPointerModel, api) { + var axis = axisModel.axis; + var grid = axis.grid; + var axisPointerType = axisPointerModel.get('type'); + var otherExtent = getCartesian(grid, axis).getOtherAxis(axis).getGlobalExtent(); + var pixelValue = axis.toGlobalCoord(axis.dataToCoord(value, true)); + + if (axisPointerType && axisPointerType !== 'none') { + var elStyle = buildElStyle(axisPointerModel); + var pointerOption = pointerShapeBuilder[axisPointerType]( + axis, pixelValue, otherExtent + ); + pointerOption.style = elStyle; + elOption.graphicKey = pointerOption.type; + elOption.pointer = pointerOption; + } + + var layoutInfo = layout$1(grid.model, axisModel); + buildCartesianSingleLabelElOption( + value, elOption, layoutInfo, axisModel, axisPointerModel, api + ); + }, + + /** + * @override + */ + getHandleTransform: function (value, axisModel, axisPointerModel) { + var layoutInfo = layout$1(axisModel.axis.grid.model, axisModel, { + labelInside: false + }); + layoutInfo.labelMargin = axisPointerModel.get('handle.margin'); + return { + position: getTransformedPosition(axisModel.axis, value, layoutInfo), + rotation: layoutInfo.rotation + (layoutInfo.labelDirection < 0 ? Math.PI : 0) + }; + }, + + /** + * @override + */ + updateHandleTransform: function (transform, delta, axisModel, axisPointerModel) { + var axis = axisModel.axis; + var grid = axis.grid; + var axisExtent = axis.getGlobalExtent(true); + var otherExtent = getCartesian(grid, axis).getOtherAxis(axis).getGlobalExtent(); + var dimIndex = axis.dim === 'x' ? 0 : 1; + + var currPosition = transform.position; + currPosition[dimIndex] += delta[dimIndex]; + currPosition[dimIndex] = Math.min(axisExtent[1], currPosition[dimIndex]); + currPosition[dimIndex] = Math.max(axisExtent[0], currPosition[dimIndex]); + + var cursorOtherValue = (otherExtent[1] + otherExtent[0]) / 2; + var cursorPoint = [cursorOtherValue, cursorOtherValue]; + cursorPoint[dimIndex] = currPosition[dimIndex]; + + // Make tooltip do not overlap axisPointer and in the middle of the grid. + var tooltipOptions = [{verticalAlign: 'middle'}, {align: 'center'}]; + + return { + position: currPosition, + rotation: transform.rotation, + cursorPoint: cursorPoint, + tooltipOption: tooltipOptions[dimIndex] + }; + } + +}); + +function getCartesian(grid, axis) { + var opt = {}; + opt[axis.dim + 'AxisIndex'] = axis.index; + return grid.getCartesian(opt); +} + +var pointerShapeBuilder = { + + line: function (axis, pixelValue, otherExtent) { + var targetShape = makeLineShape( + [pixelValue, otherExtent[0]], + [pixelValue, otherExtent[1]], + getAxisDimIndex(axis) + ); + return { + type: 'Line', + subPixelOptimize: true, + shape: targetShape + }; + }, + + shadow: function (axis, pixelValue, otherExtent) { + var bandWidth = Math.max(1, axis.getBandWidth()); + var span = otherExtent[1] - otherExtent[0]; + return { + type: 'Rect', + shape: makeRectShape( + [pixelValue - bandWidth / 2, otherExtent[0]], + [bandWidth, span], + getAxisDimIndex(axis) + ) + }; + } +}; + +function getAxisDimIndex(axis) { + return axis.dim === 'x' ? 0 : 1; +} + +AxisView.registerAxisPointerClass('CartesianAxisPointer', CartesianAxisPointer); + +/* +* 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. +*/ + +// CartesianAxisPointer is not supposed to be required here. But consider +// echarts.simple.js and online build tooltip, which only require gridSimple, +// CartesianAxisPointer should be able to required somewhere. +registerPreprocessor(function (option) { + // Always has a global axisPointerModel for default setting. + if (option) { + (!option.axisPointer || option.axisPointer.length === 0) + && (option.axisPointer = {}); + + var link = option.axisPointer.link; + // Normalize to array to avoid object mergin. But if link + // is not set, remain null/undefined, otherwise it will + // override existent link setting. + if (link && !isArray(link)) { + option.axisPointer.link = [link]; + } + } +}); + +// This process should proformed after coordinate systems created +// and series data processed. So put it on statistic processing stage. +registerProcessor(PRIORITY.PROCESSOR.STATISTIC, function (ecModel, api) { + // Build axisPointerModel, mergin tooltip.axisPointer model for each axis. + // allAxesInfo should be updated when setOption performed. + ecModel.getComponent('axisPointer').coordSysAxesInfo = + collect(ecModel, api); +}); + +// Broadcast to all views. +registerAction({ + type: 'updateAxisPointer', + event: 'updateAxisPointer', + update: ':updateAxisPointer' +}, axisTrigger); + +/* +* 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. +*/ + +var XY = ['x', 'y']; +var WH = ['width', 'height']; + +var SingleAxisPointer = BaseAxisPointer.extend({ + + /** + * @override + */ + makeElOption: function (elOption, value, axisModel, axisPointerModel, api) { + var axis = axisModel.axis; + var coordSys = axis.coordinateSystem; + var otherExtent = getGlobalExtent(coordSys, 1 - getPointDimIndex(axis)); + var pixelValue = coordSys.dataToPoint(value)[0]; + + var axisPointerType = axisPointerModel.get('type'); + if (axisPointerType && axisPointerType !== 'none') { + var elStyle = buildElStyle(axisPointerModel); + var pointerOption = pointerShapeBuilder$1[axisPointerType]( + axis, pixelValue, otherExtent + ); + pointerOption.style = elStyle; + + elOption.graphicKey = pointerOption.type; + elOption.pointer = pointerOption; + } + + var layoutInfo = layout$2(axisModel); + buildCartesianSingleLabelElOption( + value, elOption, layoutInfo, axisModel, axisPointerModel, api + ); + }, + + /** + * @override + */ + getHandleTransform: function (value, axisModel, axisPointerModel) { + var layoutInfo = layout$2(axisModel, {labelInside: false}); + layoutInfo.labelMargin = axisPointerModel.get('handle.margin'); + return { + position: getTransformedPosition(axisModel.axis, value, layoutInfo), + rotation: layoutInfo.rotation + (layoutInfo.labelDirection < 0 ? Math.PI : 0) + }; + }, + + /** + * @override + */ + updateHandleTransform: function (transform, delta, axisModel, axisPointerModel) { + var axis = axisModel.axis; + var coordSys = axis.coordinateSystem; + var dimIndex = getPointDimIndex(axis); + var axisExtent = getGlobalExtent(coordSys, dimIndex); + var currPosition = transform.position; + currPosition[dimIndex] += delta[dimIndex]; + currPosition[dimIndex] = Math.min(axisExtent[1], currPosition[dimIndex]); + currPosition[dimIndex] = Math.max(axisExtent[0], currPosition[dimIndex]); + var otherExtent = getGlobalExtent(coordSys, 1 - dimIndex); + var cursorOtherValue = (otherExtent[1] + otherExtent[0]) / 2; + var cursorPoint = [cursorOtherValue, cursorOtherValue]; + cursorPoint[dimIndex] = currPosition[dimIndex]; + + return { + position: currPosition, + rotation: transform.rotation, + cursorPoint: cursorPoint, + tooltipOption: { + verticalAlign: 'middle' + } + }; + } +}); + +var pointerShapeBuilder$1 = { + + line: function (axis, pixelValue, otherExtent) { + var targetShape = makeLineShape( + [pixelValue, otherExtent[0]], + [pixelValue, otherExtent[1]], + getPointDimIndex(axis) + ); + return { + type: 'Line', + subPixelOptimize: true, + shape: targetShape + }; + }, + + shadow: function (axis, pixelValue, otherExtent) { + var bandWidth = axis.getBandWidth(); + var span = otherExtent[1] - otherExtent[0]; + return { + type: 'Rect', + shape: makeRectShape( + [pixelValue - bandWidth / 2, otherExtent[0]], + [bandWidth, span], + getPointDimIndex(axis) + ) + }; + } +}; + +function getPointDimIndex(axis) { + return axis.isHorizontal() ? 0 : 1; +} + +function getGlobalExtent(coordSys, dimIndex) { + var rect = coordSys.getRect(); + return [rect[XY[dimIndex]], rect[XY[dimIndex]] + rect[WH[dimIndex]]]; +} + +AxisView.registerAxisPointerClass('SingleAxisPointer', SingleAxisPointer); + +/* +* 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. +*/ + +extendComponentView({ + type: 'single' +}); + +/* +* 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. +*/ + +var DATA_NAME_INDEX = 2; + +var ThemeRiverSeries = SeriesModel.extend({ + + type: 'series.themeRiver', + + dependencies: ['singleAxis'], + + /** + * @readOnly + * @type {module:zrender/core/util#HashMap} + */ + nameMap: null, + + /** + * @override + */ + init: function (option) { + // eslint-disable-next-line + ThemeRiverSeries.superApply(this, 'init', arguments); + + // Put this function here is for the sake of consistency of code style. + // Enable legend selection for each data item + // Use a function instead of direct access because data reference may changed + this.legendVisualProvider = new LegendVisualProvider( + bind(this.getData, this), bind(this.getRawData, this) + ); + }, + + /** + * If there is no value of a certain point in the time for some event,set it value to 0. + * + * @param {Array} data initial data in the option + * @return {Array} + */ + fixData: function (data) { + var rawDataLength = data.length; + + // grouped data by name + var groupResult = groupData(data, function (item) { + return item[2]; + }); + var layData = []; + groupResult.buckets.each(function (items, key) { + layData.push({name: key, dataList: items}); + }); + + var layerNum = layData.length; + var largestLayer = -1; + var index = -1; + for (var i = 0; i < layerNum; ++i) { + var len = layData[i].dataList.length; + if (len > largestLayer) { + largestLayer = len; + index = i; + } + } + + for (var k = 0; k < layerNum; ++k) { + if (k === index) { + continue; + } + var name = layData[k].name; + for (var j = 0; j < largestLayer; ++j) { + var timeValue = layData[index].dataList[j][0]; + var length = layData[k].dataList.length; + var keyIndex = -1; + for (var l = 0; l < length; ++l) { + var value = layData[k].dataList[l][0]; + if (value === timeValue) { + keyIndex = l; + break; + } + } + if (keyIndex === -1) { + data[rawDataLength] = []; + data[rawDataLength][0] = timeValue; + data[rawDataLength][1] = 0; + data[rawDataLength][2] = name; + rawDataLength++; + + } + } + } + return data; + }, + + /** + * @override + * @param {Object} option the initial option that user gived + * @param {module:echarts/model/Model} ecModel the model object for themeRiver option + * @return {module:echarts/data/List} + */ + getInitialData: function (option, ecModel) { + + var singleAxisModel = ecModel.queryComponents({ + mainType: 'singleAxis', + index: this.get('singleAxisIndex'), + id: this.get('singleAxisId') + })[0]; + + var axisType = singleAxisModel.get('type'); + + // filter the data item with the value of label is undefined + var filterData = filter(option.data, function (dataItem) { + return dataItem[2] !== undefined; + }); + + // ??? TODO design a stage to transfer data for themeRiver and lines? + var data = this.fixData(filterData || []); + var nameList = []; + var nameMap = this.nameMap = createHashMap(); + var count = 0; + + for (var i = 0; i < data.length; ++i) { + nameList.push(data[i][DATA_NAME_INDEX]); + if (!nameMap.get(data[i][DATA_NAME_INDEX])) { + nameMap.set(data[i][DATA_NAME_INDEX], count); + count++; + } + } + + var dimensionsInfo = createDimensions(data, { + coordDimensions: ['single'], + dimensionsDefine: [ + { + name: 'time', + type: getDimensionTypeByAxis(axisType) + }, + { + name: 'value', + type: 'float' + }, + { + name: 'name', + type: 'ordinal' + } + ], + encodeDefine: { + single: 0, + value: 1, + itemName: 2 + } + }); + + var list = new List(dimensionsInfo, this); + list.initData(data); + + return list; + }, + + /** + * The raw data is divided into multiple layers and each layer + * has same name. + * + * @return {Array.>} + */ + getLayerSeries: function () { + var data = this.getData(); + var lenCount = data.count(); + var indexArr = []; + + for (var i = 0; i < lenCount; ++i) { + indexArr[i] = i; + } + + var timeDim = data.mapDimension('single'); + + // data group by name + var groupResult = groupData(indexArr, function (index) { + return data.get('name', index); + }); + var layerSeries = []; + groupResult.buckets.each(function (items, key) { + items.sort(function (index1, index2) { + return data.get(timeDim, index1) - data.get(timeDim, index2); + }); + layerSeries.push({name: key, indices: items}); + }); + + return layerSeries; + }, + + /** + * Get data indices for show tooltip content + + * @param {Array.|string} dim single coordinate dimension + * @param {number} value axis value + * @param {module:echarts/coord/single/SingleAxis} baseAxis single Axis used + * the themeRiver. + * @return {Object} {dataIndices, nestestValue} + */ + getAxisTooltipData: function (dim, value, baseAxis) { + if (!isArray(dim)) { + dim = dim ? [dim] : []; + } + + var data = this.getData(); + var layerSeries = this.getLayerSeries(); + var indices = []; + var layerNum = layerSeries.length; + var nestestValue; + + for (var i = 0; i < layerNum; ++i) { + var minDist = Number.MAX_VALUE; + var nearestIdx = -1; + var pointNum = layerSeries[i].indices.length; + for (var j = 0; j < pointNum; ++j) { + var theValue = data.get(dim[0], layerSeries[i].indices[j]); + var dist = Math.abs(theValue - value); + if (dist <= minDist) { + nestestValue = theValue; + minDist = dist; + nearestIdx = layerSeries[i].indices[j]; + } + } + indices.push(nearestIdx); + } + + return {dataIndices: indices, nestestValue: nestestValue}; + }, + + /** + * @override + * @param {number} dataIndex index of data + */ + formatTooltip: function (dataIndex) { + var data = this.getData(); + var htmlName = data.getName(dataIndex); + var htmlValue = data.get(data.mapDimension('value'), dataIndex); + if (isNaN(htmlValue) || htmlValue == null) { + htmlValue = '-'; + } + return encodeHTML(htmlName + ' : ' + htmlValue); + }, + + defaultOption: { + zlevel: 0, + z: 2, + + coordinateSystem: 'singleAxis', + + // gap in axis's orthogonal orientation + boundaryGap: ['10%', '10%'], + + // legendHoverLink: true, + + singleAxisIndex: 0, + + animationEasing: 'linear', + + label: { + margin: 4, + show: true, + position: 'left', + color: '#000', + fontSize: 11 + }, + + emphasis: { + label: { + show: true + } + } + } +}); + +/* +* 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. +*/ + +extendChartView({ + + type: 'themeRiver', + + init: function () { + this._layers = []; + }, + + render: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + + var group = this.group; + + var layerSeries = seriesModel.getLayerSeries(); + + var layoutInfo = data.getLayout('layoutInfo'); + var rect = layoutInfo.rect; + var boundaryGap = layoutInfo.boundaryGap; + + group.attr('position', [0, rect.y + boundaryGap[0]]); + + function keyGetter(item) { + return item.name; + } + var dataDiffer = new DataDiffer( + this._layersSeries || [], layerSeries, + keyGetter, keyGetter + ); + + var newLayersGroups = {}; + + dataDiffer + .add(bind(process, this, 'add')) + .update(bind(process, this, 'update')) + .remove(bind(process, this, 'remove')) + .execute(); + + function process(status, idx, oldIdx) { + var oldLayersGroups = this._layers; + if (status === 'remove') { + group.remove(oldLayersGroups[idx]); + return; + } + var points0 = []; + var points1 = []; + var color; + var indices = layerSeries[idx].indices; + for (var j = 0; j < indices.length; j++) { + var layout = data.getItemLayout(indices[j]); + var x = layout.x; + var y0 = layout.y0; + var y = layout.y; + + points0.push([x, y0]); + points1.push([x, y0 + y]); + + color = data.getItemVisual(indices[j], 'color'); + } + + var polygon; + var text; + var textLayout = data.getItemLayout(indices[0]); + var itemModel = data.getItemModel(indices[j - 1]); + var labelModel = itemModel.getModel('label'); + var margin = labelModel.get('margin'); + if (status === 'add') { + var layerGroup = newLayersGroups[idx] = new Group(); + polygon = new Polygon$1({ + shape: { + points: points0, + stackedOnPoints: points1, + smooth: 0.4, + stackedOnSmooth: 0.4, + smoothConstraint: false + }, + z2: 0 + }); + text = new Text({ + style: { + x: textLayout.x - margin, + y: textLayout.y0 + textLayout.y / 2 + } + }); + layerGroup.add(polygon); + layerGroup.add(text); + group.add(layerGroup); + + polygon.setClipPath(createGridClipShape$2(polygon.getBoundingRect(), seriesModel, function () { + polygon.removeClipPath(); + })); + } + else { + var layerGroup = oldLayersGroups[oldIdx]; + polygon = layerGroup.childAt(0); + text = layerGroup.childAt(1); + group.add(layerGroup); + + newLayersGroups[idx] = layerGroup; + + updateProps(polygon, { + shape: { + points: points0, + stackedOnPoints: points1 + } + }, seriesModel); + + updateProps(text, { + style: { + x: textLayout.x - margin, + y: textLayout.y0 + textLayout.y / 2 + } + }, seriesModel); + } + + var hoverItemStyleModel = itemModel.getModel('emphasis.itemStyle'); + var itemStyleModel = itemModel.getModel('itemStyle'); + + setTextStyle(text.style, labelModel, { + text: labelModel.get('show') + ? seriesModel.getFormattedLabel(indices[j - 1], 'normal') + || data.getName(indices[j - 1]) + : null, + textVerticalAlign: 'middle' + }); + + polygon.setStyle(extend({ + fill: color + }, itemStyleModel.getItemStyle(['color']))); + + setHoverStyle(polygon, hoverItemStyleModel.getItemStyle()); + } + + this._layersSeries = layerSeries; + this._layers = newLayersGroups; + }, + + dispose: function () {} +}); + +// add animation to the view +function createGridClipShape$2(rect, seriesModel, cb) { + var rectEl = new Rect({ + shape: { + x: rect.x - 10, + y: rect.y - 10, + width: 0, + height: rect.height + 20 + } + }); + initProps(rectEl, { + shape: { + width: rect.width + 20, + height: rect.height + 20 + } + }, seriesModel, cb); + + return rectEl; +} + +/* +* 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. +*/ + +var themeRiverLayout = function (ecModel, api) { + + ecModel.eachSeriesByType('themeRiver', function (seriesModel) { + + var data = seriesModel.getData(); + + var single = seriesModel.coordinateSystem; + + var layoutInfo = {}; + + // use the axis boundingRect for view + var rect = single.getRect(); + + layoutInfo.rect = rect; + + var boundaryGap = seriesModel.get('boundaryGap'); + + var axis = single.getAxis(); + + layoutInfo.boundaryGap = boundaryGap; + + if (axis.orient === 'horizontal') { + boundaryGap[0] = parsePercent$1(boundaryGap[0], rect.height); + boundaryGap[1] = parsePercent$1(boundaryGap[1], rect.height); + var height = rect.height - boundaryGap[0] - boundaryGap[1]; + themeRiverLayout$1(data, seriesModel, height); + } + else { + boundaryGap[0] = parsePercent$1(boundaryGap[0], rect.width); + boundaryGap[1] = parsePercent$1(boundaryGap[1], rect.width); + var width = rect.width - boundaryGap[0] - boundaryGap[1]; + themeRiverLayout$1(data, seriesModel, width); + } + + data.setLayout('layoutInfo', layoutInfo); + }); +}; + +/** + * The layout information about themeriver + * + * @param {module:echarts/data/List} data data in the series + * @param {module:echarts/model/Series} seriesModel the model object of themeRiver series + * @param {number} height value used to compute every series height + */ +function themeRiverLayout$1(data, seriesModel, height) { + if (!data.count()) { + return; + } + var coordSys = seriesModel.coordinateSystem; + // the data in each layer are organized into a series. + var layerSeries = seriesModel.getLayerSeries(); + + // the points in each layer. + var timeDim = data.mapDimension('single'); + var valueDim = data.mapDimension('value'); + var layerPoints = map(layerSeries, function (singleLayer) { + return map(singleLayer.indices, function (idx) { + var pt = coordSys.dataToPoint(data.get(timeDim, idx)); + pt[1] = data.get(valueDim, idx); + return pt; + }); + }); + + var base = computeBaseline(layerPoints); + var baseLine = base.y0; + var ky = height / base.max; + + // set layout information for each item. + var n = layerSeries.length; + var m = layerSeries[0].indices.length; + var baseY0; + for (var j = 0; j < m; ++j) { + baseY0 = baseLine[j] * ky; + data.setItemLayout(layerSeries[0].indices[j], { + layerIndex: 0, + x: layerPoints[0][j][0], + y0: baseY0, + y: layerPoints[0][j][1] * ky + }); + for (var i = 1; i < n; ++i) { + baseY0 += layerPoints[i - 1][j][1] * ky; + data.setItemLayout(layerSeries[i].indices[j], { + layerIndex: i, + x: layerPoints[i][j][0], + y0: baseY0, + y: layerPoints[i][j][1] * ky + }); + } + } +} + +/** + * Compute the baseLine of the rawdata + * Inspired by Lee Byron's paper Stacked Graphs - Geometry & Aesthetics + * + * @param {Array.} data the points in each layer + * @return {Object} + */ +function computeBaseline(data) { + var layerNum = data.length; + var pointNum = data[0].length; + var sums = []; + var y0 = []; + var max = 0; + var temp; + var base = {}; + + for (var i = 0; i < pointNum; ++i) { + for (var j = 0, temp = 0; j < layerNum; ++j) { + temp += data[j][i][1]; + } + if (temp > max) { + max = temp; + } + sums.push(temp); + } + + for (var k = 0; k < pointNum; ++k) { + y0[k] = (max - sums[k]) / 2; + } + max = 0; + + for (var l = 0; l < pointNum; ++l) { + var sum = sums[l] + y0[l]; + if (sum > max) { + max = sum; + } + } + base.y0 = y0; + base.max = max; + + return base; +} + +/* +* 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. +*/ + +var themeRiverVisual = function (ecModel) { + ecModel.eachSeriesByType('themeRiver', function (seriesModel) { + var data = seriesModel.getData(); + var rawData = seriesModel.getRawData(); + var colorList = seriesModel.get('color'); + var idxMap = createHashMap(); + + data.each(function (idx) { + idxMap.set(data.getRawIndex(idx), idx); + }); + + rawData.each(function (rawIndex) { + var name = rawData.getName(rawIndex); + var color = colorList[(seriesModel.nameMap.get(name) - 1) % colorList.length]; + + rawData.setItemVisual(rawIndex, 'color', color); + + var idx = idxMap.get(rawIndex); + + if (idx != null) { + data.setItemVisual(idx, 'color', color); + } + }); + }); +}; + +/* +* 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. +*/ + +registerLayout(themeRiverLayout); +registerVisual(themeRiverVisual); +registerProcessor(dataFilter('themeRiver')); + +/* +* 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. +*/ + +SeriesModel.extend({ + + type: 'series.sunburst', + + /** + * @type {module:echarts/data/Tree~Node} + */ + _viewRoot: null, + + getInitialData: function (option, ecModel) { + // Create a virtual root. + var root = { name: option.name, children: option.data }; + + completeTreeValue$1(root); + + var levels = option.levels || []; + + // levels = option.levels = setDefault(levels, ecModel); + + var treeOption = {}; + + treeOption.levels = levels; + + // Make sure always a new tree is created when setOption, + // in TreemapView, we check whether oldTree === newTree + // to choose mappings approach among old shapes and new shapes. + return Tree.createTree(root, this, treeOption).data; + }, + + optionUpdated: function () { + this.resetViewRoot(); + }, + + /* + * @override + */ + getDataParams: function (dataIndex) { + var params = SeriesModel.prototype.getDataParams.apply(this, arguments); + + var node = this.getData().tree.getNodeByDataIndex(dataIndex); + params.treePathInfo = wrapTreePathInfo(node, this); + + return params; + }, + + defaultOption: { + zlevel: 0, + z: 2, + + // 默认全局居中 + center: ['50%', '50%'], + radius: [0, '75%'], + // 默认顺时针 + clockwise: true, + startAngle: 90, + // 最小角度改为0 + minAngle: 0, + + percentPrecision: 2, + + // If still show when all data zero. + stillShowZeroSum: true, + + // Policy of highlighting pieces when hover on one + // Valid values: 'none' (for not downplay others), 'descendant', + // 'ancestor', 'self' + highlightPolicy: 'descendant', + + // 'rootToNode', 'link', or false + nodeClick: 'rootToNode', + + renderLabelForZeroData: false, + + label: { + // could be: 'radial', 'tangential', or 'none' + rotate: 'radial', + show: true, + opacity: 1, + // 'left' is for inner side of inside, and 'right' is for outter + // side for inside + align: 'center', + position: 'inside', + distance: 5, + silent: true, + emphasis: {} + }, + itemStyle: { + borderWidth: 1, + borderColor: 'white', + borderType: 'solid', + shadowBlur: 0, + shadowColor: 'rgba(0, 0, 0, 0.2)', + shadowOffsetX: 0, + shadowOffsetY: 0, + opacity: 1, + emphasis: {}, + highlight: { + opacity: 1 + }, + downplay: { + opacity: 0.9 + } + }, + + // Animation type canbe expansion, scale + animationType: 'expansion', + animationDuration: 1000, + animationDurationUpdate: 500, + animationEasing: 'cubicOut', + + data: [], + + levels: [], + + /** + * Sort order. + * + * Valid values: 'desc', 'asc', null, or callback function. + * 'desc' and 'asc' for descend and ascendant order; + * null for not sorting; + * example of callback function: + * function(nodeA, nodeB) { + * return nodeA.getValue() - nodeB.getValue(); + * } + */ + sort: 'desc' + }, + + getViewRoot: function () { + return this._viewRoot; + }, + + /** + * @param {module:echarts/data/Tree~Node} [viewRoot] + */ + resetViewRoot: function (viewRoot) { + viewRoot + ? (this._viewRoot = viewRoot) + : (viewRoot = this._viewRoot); + + var root = this.getRawData().tree.root; + + if (!viewRoot + || (viewRoot !== root && !root.contains(viewRoot)) + ) { + this._viewRoot = root; + } + } +}); + + + +/** + * @param {Object} dataNode + */ +function completeTreeValue$1(dataNode) { + // Postorder travel tree. + // If value of none-leaf node is not set, + // calculate it by suming up the value of all children. + var sum = 0; + + each$1(dataNode.children, function (child) { + + completeTreeValue$1(child); + + var childValue = child.value; + isArray(childValue) && (childValue = childValue[0]); + + sum += childValue; + }); + + var thisValue = dataNode.value; + if (isArray(thisValue)) { + thisValue = thisValue[0]; + } + + if (thisValue == null || isNaN(thisValue)) { + thisValue = sum; + } + // Value should not less than 0. + if (thisValue < 0) { + thisValue = 0; + } + + isArray(dataNode.value) + ? (dataNode.value[0] = thisValue) + : (dataNode.value = thisValue); +} + +/* +* 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. +*/ + +var NodeHighlightPolicy = { + NONE: 'none', // not downplay others + DESCENDANT: 'descendant', + ANCESTOR: 'ancestor', + SELF: 'self' +}; + +var DEFAULT_SECTOR_Z = 2; +var DEFAULT_TEXT_Z = 4; + +/** + * Sunburstce of Sunburst including Sector, Label, LabelLine + * @constructor + * @extends {module:zrender/graphic/Group} + */ +function SunburstPiece(node, seriesModel, ecModel) { + + Group.call(this); + + var sector = new Sector({ + z2: DEFAULT_SECTOR_Z + }); + sector.seriesIndex = seriesModel.seriesIndex; + + var text = new Text({ + z2: DEFAULT_TEXT_Z, + silent: node.getModel('label').get('silent') + }); + this.add(sector); + this.add(text); + + this.updateData(true, node, 'normal', seriesModel, ecModel); + + // Hover to change label and labelLine + function onEmphasis() { + text.ignore = text.hoverIgnore; + } + function onNormal() { + text.ignore = text.normalIgnore; + } + this.on('emphasis', onEmphasis) + .on('normal', onNormal) + .on('mouseover', onEmphasis) + .on('mouseout', onNormal); +} + +var SunburstPieceProto = SunburstPiece.prototype; + +SunburstPieceProto.updateData = function ( + firstCreate, + node, + state, + seriesModel, + ecModel +) { + this.node = node; + node.piece = this; + + seriesModel = seriesModel || this._seriesModel; + ecModel = ecModel || this._ecModel; + + var sector = this.childAt(0); + sector.dataIndex = node.dataIndex; + + var itemModel = node.getModel(); + var layout = node.getLayout(); + // if (!layout) { + // console.log(node.getLayout()); + // } + var sectorShape = extend({}, layout); + sectorShape.label = null; + + var visualColor = getNodeColor(node, seriesModel, ecModel); + + fillDefaultColor(node, seriesModel, visualColor); + + var normalStyle = itemModel.getModel('itemStyle').getItemStyle(); + var style; + if (state === 'normal') { + style = normalStyle; + } + else { + var stateStyle = itemModel.getModel(state + '.itemStyle') + .getItemStyle(); + style = merge(stateStyle, normalStyle); + } + style = defaults( + { + lineJoin: 'bevel', + fill: style.fill || visualColor + }, + style + ); + + if (firstCreate) { + sector.setShape(sectorShape); + sector.shape.r = layout.r0; + updateProps( + sector, + { + shape: { + r: layout.r + } + }, + seriesModel, + node.dataIndex + ); + sector.useStyle(style); + } + else if (typeof style.fill === 'object' && style.fill.type + || typeof sector.style.fill === 'object' && sector.style.fill.type + ) { + // Disable animation for gradient since no interpolation method + // is supported for gradient + updateProps(sector, { + shape: sectorShape + }, seriesModel); + sector.useStyle(style); + } + else { + updateProps(sector, { + shape: sectorShape, + style: style + }, seriesModel); + } + + this._updateLabel(seriesModel, visualColor, state); + + var cursorStyle = itemModel.getShallow('cursor'); + cursorStyle && sector.attr('cursor', cursorStyle); + + if (firstCreate) { + var highlightPolicy = seriesModel.getShallow('highlightPolicy'); + this._initEvents(sector, node, seriesModel, highlightPolicy); + } + + this._seriesModel = seriesModel || this._seriesModel; + this._ecModel = ecModel || this._ecModel; +}; + +SunburstPieceProto.onEmphasis = function (highlightPolicy) { + var that = this; + this.node.hostTree.root.eachNode(function (n) { + if (n.piece) { + if (that.node === n) { + n.piece.updateData(false, n, 'emphasis'); + } + else if (isNodeHighlighted(n, that.node, highlightPolicy)) { + n.piece.childAt(0).trigger('highlight'); + } + else if (highlightPolicy !== NodeHighlightPolicy.NONE) { + n.piece.childAt(0).trigger('downplay'); + } + } + }); +}; + +SunburstPieceProto.onNormal = function () { + this.node.hostTree.root.eachNode(function (n) { + if (n.piece) { + n.piece.updateData(false, n, 'normal'); + } + }); +}; + +SunburstPieceProto.onHighlight = function () { + this.updateData(false, this.node, 'highlight'); +}; + +SunburstPieceProto.onDownplay = function () { + this.updateData(false, this.node, 'downplay'); +}; + +SunburstPieceProto._updateLabel = function (seriesModel, visualColor, state) { + var itemModel = this.node.getModel(); + var normalModel = itemModel.getModel('label'); + var labelModel = state === 'normal' || state === 'emphasis' + ? normalModel + : itemModel.getModel(state + '.label'); + var labelHoverModel = itemModel.getModel('emphasis.label'); + + var text = retrieve( + seriesModel.getFormattedLabel( + this.node.dataIndex, state, null, null, 'label' + ), + this.node.name + ); + if (getLabelAttr('show') === false) { + text = ''; + } + + var layout = this.node.getLayout(); + var labelMinAngle = labelModel.get('minAngle'); + if (labelMinAngle == null) { + labelMinAngle = normalModel.get('minAngle'); + } + labelMinAngle = labelMinAngle / 180 * Math.PI; + var angle = layout.endAngle - layout.startAngle; + if (labelMinAngle != null && Math.abs(angle) < labelMinAngle) { + // Not displaying text when angle is too small + text = ''; + } + + var label = this.childAt(1); + + setLabelStyle( + label.style, label.hoverStyle || {}, normalModel, labelHoverModel, + { + defaultText: labelModel.getShallow('show') ? text : null, + autoColor: visualColor, + useInsideStyle: true + } + ); + + var midAngle = (layout.startAngle + layout.endAngle) / 2; + var dx = Math.cos(midAngle); + var dy = Math.sin(midAngle); + + var r; + var labelPosition = getLabelAttr('position'); + var labelPadding = getLabelAttr('distance') || 0; + var textAlign = getLabelAttr('align'); + if (labelPosition === 'outside') { + r = layout.r + labelPadding; + textAlign = midAngle > Math.PI / 2 ? 'right' : 'left'; + } + else { + if (!textAlign || textAlign === 'center') { + r = (layout.r + layout.r0) / 2; + textAlign = 'center'; + } + else if (textAlign === 'left') { + r = layout.r0 + labelPadding; + if (midAngle > Math.PI / 2) { + textAlign = 'right'; + } + } + else if (textAlign === 'right') { + r = layout.r - labelPadding; + if (midAngle > Math.PI / 2) { + textAlign = 'left'; + } + } + } + + label.attr('style', { + text: text, + textAlign: textAlign, + textVerticalAlign: getLabelAttr('verticalAlign') || 'middle', + opacity: getLabelAttr('opacity') + }); + + var textX = r * dx + layout.cx; + var textY = r * dy + layout.cy; + label.attr('position', [textX, textY]); + + var rotateType = getLabelAttr('rotate'); + var rotate = 0; + if (rotateType === 'radial') { + rotate = -midAngle; + if (rotate < -Math.PI / 2) { + rotate += Math.PI; + } + } + else if (rotateType === 'tangential') { + rotate = Math.PI / 2 - midAngle; + if (rotate > Math.PI / 2) { + rotate -= Math.PI; + } + else if (rotate < -Math.PI / 2) { + rotate += Math.PI; + } + } + else if (typeof rotateType === 'number') { + rotate = rotateType * Math.PI / 180; + } + label.attr('rotation', rotate); + + function getLabelAttr(name) { + var stateAttr = labelModel.get(name); + if (stateAttr == null) { + return normalModel.get(name); + } + else { + return stateAttr; + } + } +}; + +SunburstPieceProto._initEvents = function ( + sector, + node, + seriesModel, + highlightPolicy +) { + sector.off('mouseover').off('mouseout').off('emphasis').off('normal'); + + var that = this; + var onEmphasis = function () { + that.onEmphasis(highlightPolicy); + }; + var onNormal = function () { + that.onNormal(); + }; + var onDownplay = function () { + that.onDownplay(); + }; + var onHighlight = function () { + that.onHighlight(); + }; + + if (seriesModel.isAnimationEnabled()) { + sector + .on('mouseover', onEmphasis) + .on('mouseout', onNormal) + .on('emphasis', onEmphasis) + .on('normal', onNormal) + .on('downplay', onDownplay) + .on('highlight', onHighlight); + } +}; + +inherits(SunburstPiece, Group); + +/** + * Get node color + * + * @param {TreeNode} node the node to get color + * @param {module:echarts/model/Series} seriesModel series + * @param {module:echarts/model/Global} ecModel echarts defaults + */ +function getNodeColor(node, seriesModel, ecModel) { + // Color from visualMap + var visualColor = node.getVisual('color'); + var visualMetaList = node.getVisual('visualMeta'); + if (!visualMetaList || visualMetaList.length === 0) { + // Use first-generation color if has no visualMap + visualColor = null; + } + + // Self color or level color + var color = node.getModel('itemStyle').get('color'); + if (color) { + return color; + } + else if (visualColor) { + // Color mapping + return visualColor; + } + else if (node.depth === 0) { + // Virtual root node + return ecModel.option.color[0]; + } + else { + // First-generation color + var length = ecModel.option.color.length; + color = ecModel.option.color[getRootId(node) % length]; + } + return color; +} + +/** + * Get index of root in sorted order + * + * @param {TreeNode} node current node + * @return {number} index in root + */ +function getRootId(node) { + var ancestor = node; + while (ancestor.depth > 1) { + ancestor = ancestor.parentNode; + } + + var virtualRoot = node.getAncestors()[0]; + return indexOf(virtualRoot.children, ancestor); +} + +function isNodeHighlighted(node, activeNode, policy) { + if (policy === NodeHighlightPolicy.NONE) { + return false; + } + else if (policy === NodeHighlightPolicy.SELF) { + return node === activeNode; + } + else if (policy === NodeHighlightPolicy.ANCESTOR) { + return node === activeNode || node.isAncestorOf(activeNode); + } + else { + return node === activeNode || node.isDescendantOf(activeNode); + } +} + +// Fix tooltip callback function params.color incorrect when pick a default color +function fillDefaultColor(node, seriesModel, color) { + var data = seriesModel.getData(); + data.setItemVisual(node.dataIndex, 'color', color); +} + +/* +* 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. +*/ + +var ROOT_TO_NODE_ACTION = 'sunburstRootToNode'; + +var SunburstView = Chart.extend({ + + type: 'sunburst', + + init: function () { + }, + + render: function (seriesModel, ecModel, api, payload) { + var that = this; + + this.seriesModel = seriesModel; + this.api = api; + this.ecModel = ecModel; + + var data = seriesModel.getData(); + var virtualRoot = data.tree.root; + + var newRoot = seriesModel.getViewRoot(); + + var group = this.group; + + var renderLabelForZeroData = seriesModel.get('renderLabelForZeroData'); + + var newChildren = []; + newRoot.eachNode(function (node) { + newChildren.push(node); + }); + var oldChildren = this._oldChildren || []; + + dualTravel(newChildren, oldChildren); + + renderRollUp(virtualRoot, newRoot); + + if (payload && payload.highlight && payload.highlight.piece) { + var highlightPolicy = seriesModel.getShallow('highlightPolicy'); + payload.highlight.piece.onEmphasis(highlightPolicy); + } + else if (payload && payload.unhighlight) { + var piece = this.virtualPiece; + if (!piece && virtualRoot.children.length) { + piece = virtualRoot.children[0].piece; + } + if (piece) { + piece.onNormal(); + } + } + + this._initEvents(); + + this._oldChildren = newChildren; + + function dualTravel(newChildren, oldChildren) { + if (newChildren.length === 0 && oldChildren.length === 0) { + return; + } + + new DataDiffer(oldChildren, newChildren, getKey, getKey) + .add(processNode) + .update(processNode) + .remove(curry(processNode, null)) + .execute(); + + function getKey(node) { + return node.getId(); + } + + function processNode(newId, oldId) { + var newNode = newId == null ? null : newChildren[newId]; + var oldNode = oldId == null ? null : oldChildren[oldId]; + + doRenderNode(newNode, oldNode); + } + } + + function doRenderNode(newNode, oldNode) { + if (!renderLabelForZeroData && newNode && !newNode.getValue()) { + // Not render data with value 0 + newNode = null; + } + + if (newNode !== virtualRoot && oldNode !== virtualRoot) { + if (oldNode && oldNode.piece) { + if (newNode) { + // Update + oldNode.piece.updateData( + false, newNode, 'normal', seriesModel, ecModel); + + // For tooltip + data.setItemGraphicEl(newNode.dataIndex, oldNode.piece); + } + else { + // Remove + removeNode(oldNode); + } + } + else if (newNode) { + // Add + var piece = new SunburstPiece( + newNode, + seriesModel, + ecModel + ); + group.add(piece); + + // For tooltip + data.setItemGraphicEl(newNode.dataIndex, piece); + } + } + } + + function removeNode(node) { + if (!node) { + return; + } + + if (node.piece) { + group.remove(node.piece); + node.piece = null; + } + } + + function renderRollUp(virtualRoot, viewRoot) { + if (viewRoot.depth > 0) { + // Render + if (that.virtualPiece) { + // Update + that.virtualPiece.updateData( + false, virtualRoot, 'normal', seriesModel, ecModel); + } + else { + // Add + that.virtualPiece = new SunburstPiece( + virtualRoot, + seriesModel, + ecModel + ); + group.add(that.virtualPiece); + } + + if (viewRoot.piece._onclickEvent) { + viewRoot.piece.off('click', viewRoot.piece._onclickEvent); + } + var event = function (e) { + that._rootToNode(viewRoot.parentNode); + }; + viewRoot.piece._onclickEvent = event; + that.virtualPiece.on('click', event); + } + else if (that.virtualPiece) { + // Remove + group.remove(that.virtualPiece); + that.virtualPiece = null; + } + } + }, + + dispose: function () { + }, + + /** + * @private + */ + _initEvents: function () { + var that = this; + + var event = function (e) { + var targetFound = false; + var viewRoot = that.seriesModel.getViewRoot(); + viewRoot.eachNode(function (node) { + if (!targetFound + && node.piece && node.piece.childAt(0) === e.target + ) { + var nodeClick = node.getModel().get('nodeClick'); + if (nodeClick === 'rootToNode') { + that._rootToNode(node); + } + else if (nodeClick === 'link') { + var itemModel = node.getModel(); + var link = itemModel.get('link'); + if (link) { + var linkTarget = itemModel.get('target', true) + || '_blank'; + window.open(link, linkTarget); + } + } + targetFound = true; + } + }); + }; + + if (this.group._onclickEvent) { + this.group.off('click', this.group._onclickEvent); + } + this.group.on('click', event); + this.group._onclickEvent = event; + }, + + /** + * @private + */ + _rootToNode: function (node) { + if (node !== this.seriesModel.getViewRoot()) { + this.api.dispatchAction({ + type: ROOT_TO_NODE_ACTION, + from: this.uid, + seriesId: this.seriesModel.id, + targetNode: node + }); + } + }, + + /** + * @implement + */ + containPoint: function (point, seriesModel) { + var treeRoot = seriesModel.getData(); + var itemLayout = treeRoot.getItemLayout(0); + if (itemLayout) { + var dx = point[0] - itemLayout.cx; + var dy = point[1] - itemLayout.cy; + var radius = Math.sqrt(dx * dx + dy * dy); + return radius <= itemLayout.r && radius >= itemLayout.r0; + } + } + +}); + +/* +* 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. +*/ + +/** + * @file Sunburst action + */ + +var ROOT_TO_NODE_ACTION$1 = 'sunburstRootToNode'; + +registerAction( + {type: ROOT_TO_NODE_ACTION$1, update: 'updateView'}, + function (payload, ecModel) { + + ecModel.eachComponent( + {mainType: 'series', subType: 'sunburst', query: payload}, + handleRootToNode + ); + + function handleRootToNode(model, index) { + var targetInfo = retrieveTargetInfo(payload, [ROOT_TO_NODE_ACTION$1], model); + + if (targetInfo) { + var originViewRoot = model.getViewRoot(); + if (originViewRoot) { + payload.direction = aboveViewRoot(originViewRoot, targetInfo.node) + ? 'rollUp' : 'drillDown'; + } + model.resetViewRoot(targetInfo.node); + } + } + } +); + + +var HIGHLIGHT_ACTION = 'sunburstHighlight'; + +registerAction( + {type: HIGHLIGHT_ACTION, update: 'updateView'}, + function (payload, ecModel) { + + ecModel.eachComponent( + {mainType: 'series', subType: 'sunburst', query: payload}, + handleHighlight + ); + + function handleHighlight(model, index) { + var targetInfo = retrieveTargetInfo(payload, [HIGHLIGHT_ACTION], model); + + if (targetInfo) { + payload.highlight = targetInfo.node; + } + } + } +); + + +var UNHIGHLIGHT_ACTION = 'sunburstUnhighlight'; + +registerAction( + {type: UNHIGHLIGHT_ACTION, update: 'updateView'}, + function (payload, ecModel) { + + ecModel.eachComponent( + {mainType: 'series', subType: 'sunburst', query: payload}, + handleUnhighlight + ); + + function handleUnhighlight(model, index) { + payload.unhighlight = true; + } + } +); + +/* +* 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. +*/ + + +// var PI2 = Math.PI * 2; +var RADIAN$2 = Math.PI / 180; + +var sunburstLayout = function (seriesType, ecModel, api, payload) { + ecModel.eachSeriesByType(seriesType, function (seriesModel) { + var center = seriesModel.get('center'); + var radius = seriesModel.get('radius'); + + if (!isArray(radius)) { + radius = [0, radius]; + } + if (!isArray(center)) { + center = [center, center]; + } + + var width = api.getWidth(); + var height = api.getHeight(); + var size = Math.min(width, height); + var cx = parsePercent$1(center[0], width); + var cy = parsePercent$1(center[1], height); + var r0 = parsePercent$1(radius[0], size / 2); + var r = parsePercent$1(radius[1], size / 2); + + var startAngle = -seriesModel.get('startAngle') * RADIAN$2; + var minAngle = seriesModel.get('minAngle') * RADIAN$2; + + var virtualRoot = seriesModel.getData().tree.root; + var treeRoot = seriesModel.getViewRoot(); + var rootDepth = treeRoot.depth; + + var sort = seriesModel.get('sort'); + if (sort != null) { + initChildren$1(treeRoot, sort); + } + + var validDataCount = 0; + each$1(treeRoot.children, function (child) { + !isNaN(child.getValue()) && validDataCount++; + }); + + var sum = treeRoot.getValue(); + // Sum may be 0 + var unitRadian = Math.PI / (sum || validDataCount) * 2; + + var renderRollupNode = treeRoot.depth > 0; + var levels = treeRoot.height - (renderRollupNode ? -1 : 1); + var rPerLevel = (r - r0) / (levels || 1); + + var clockwise = seriesModel.get('clockwise'); + + var stillShowZeroSum = seriesModel.get('stillShowZeroSum'); + + // In the case some sector angle is smaller than minAngle + // var restAngle = PI2; + // var valueSumLargerThanMinAngle = 0; + + var dir = clockwise ? 1 : -1; + + /** + * Render a tree + * @return increased angle + */ + var renderNode = function (node, startAngle) { + if (!node) { + return; + } + + var endAngle = startAngle; + + // Render self + if (node !== virtualRoot) { + // Tree node is virtual, so it doesn't need to be drawn + var value = node.getValue(); + + var angle = (sum === 0 && stillShowZeroSum) + ? unitRadian : (value * unitRadian); + if (angle < minAngle) { + angle = minAngle; + // restAngle -= minAngle; + } + // else { + // valueSumLargerThanMinAngle += value; + // } + + endAngle = startAngle + dir * angle; + + var depth = node.depth - rootDepth + - (renderRollupNode ? -1 : 1); + var rStart = r0 + rPerLevel * depth; + var rEnd = r0 + rPerLevel * (depth + 1); + + var itemModel = node.getModel(); + if (itemModel.get('r0') != null) { + rStart = parsePercent$1(itemModel.get('r0'), size / 2); + } + if (itemModel.get('r') != null) { + rEnd = parsePercent$1(itemModel.get('r'), size / 2); + } + + node.setLayout({ + angle: angle, + startAngle: startAngle, + endAngle: endAngle, + clockwise: clockwise, + cx: cx, + cy: cy, + r0: rStart, + r: rEnd + }); + } + + // Render children + if (node.children && node.children.length) { + // currentAngle = startAngle; + var siblingAngle = 0; + each$1(node.children, function (node) { + siblingAngle += renderNode(node, startAngle + siblingAngle); + }); + } + + return endAngle - startAngle; + }; + + // Virtual root node for roll up + if (renderRollupNode) { + var rStart = r0; + var rEnd = r0 + rPerLevel; + + var angle = Math.PI * 2; + virtualRoot.setLayout({ + angle: angle, + startAngle: startAngle, + endAngle: startAngle + angle, + clockwise: clockwise, + cx: cx, + cy: cy, + r0: rStart, + r: rEnd + }); + } + + renderNode(treeRoot, startAngle); + }); +}; + +/** + * Init node children by order and update visual + * + * @param {TreeNode} node root node + * @param {boolean} isAsc if is in ascendant order + */ +function initChildren$1(node, isAsc) { + var children = node.children || []; + + node.children = sort$2(children, isAsc); + + // Init children recursively + if (children.length) { + each$1(node.children, function (child) { + initChildren$1(child, isAsc); + }); + } +} + +/** + * Sort children nodes + * + * @param {TreeNode[]} children children of node to be sorted + * @param {string | function | null} sort sort method + * See SunburstSeries.js for details. + */ +function sort$2(children, sortOrder) { + if (typeof sortOrder === 'function') { + return children.sort(sortOrder); + } + else { + var isAsc = sortOrder === 'asc'; + return children.sort(function (a, b) { + var diff = (a.getValue() - b.getValue()) * (isAsc ? 1 : -1); + return diff === 0 + ? (a.dataIndex - b.dataIndex) * (isAsc ? -1 : 1) + : diff; + }); + } +} + +/* +* 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. +*/ + +registerVisual(curry(dataColor, 'sunburst')); +registerLayout(curry(sunburstLayout, 'sunburst')); +registerProcessor(curry(dataFilter, 'sunburst')); + +/* +* 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. +*/ + +function dataToCoordSize(dataSize, dataItem) { + // dataItem is necessary in log axis. + dataItem = dataItem || [0, 0]; + return map(['x', 'y'], function (dim, dimIdx) { + var axis = this.getAxis(dim); + var val = dataItem[dimIdx]; + var halfSize = dataSize[dimIdx] / 2; + return axis.type === 'category' + ? axis.getBandWidth() + : Math.abs(axis.dataToCoord(val - halfSize) - axis.dataToCoord(val + halfSize)); + }, this); +} + +var prepareCartesian2d = function (coordSys) { + var rect = coordSys.grid.getRect(); + return { + coordSys: { + // The name exposed to user is always 'cartesian2d' but not 'grid'. + type: 'cartesian2d', + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + }, + api: { + coord: function (data) { + // do not provide "out" param + return coordSys.dataToPoint(data); + }, + size: bind(dataToCoordSize, coordSys) + } + }; +}; + +/* +* 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. +*/ + +function dataToCoordSize$1(dataSize, dataItem) { + dataItem = dataItem || [0, 0]; + return map([0, 1], function (dimIdx) { + var val = dataItem[dimIdx]; + var halfSize = dataSize[dimIdx] / 2; + var p1 = []; + var p2 = []; + p1[dimIdx] = val - halfSize; + p2[dimIdx] = val + halfSize; + p1[1 - dimIdx] = p2[1 - dimIdx] = dataItem[1 - dimIdx]; + return Math.abs(this.dataToPoint(p1)[dimIdx] - this.dataToPoint(p2)[dimIdx]); + }, this); +} + +var prepareGeo = function (coordSys) { + var rect = coordSys.getBoundingRect(); + return { + coordSys: { + type: 'geo', + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + zoom: coordSys.getZoom() + }, + api: { + coord: function (data) { + // do not provide "out" and noRoam param, + // Compatible with this usage: + // echarts.util.map(item.points, api.coord) + return coordSys.dataToPoint(data); + }, + size: bind(dataToCoordSize$1, coordSys) + } + }; +}; + +/* +* 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. +*/ + +function dataToCoordSize$2(dataSize, dataItem) { + // dataItem is necessary in log axis. + var axis = this.getAxis(); + var val = dataItem instanceof Array ? dataItem[0] : dataItem; + var halfSize = (dataSize instanceof Array ? dataSize[0] : dataSize) / 2; + return axis.type === 'category' + ? axis.getBandWidth() + : Math.abs(axis.dataToCoord(val - halfSize) - axis.dataToCoord(val + halfSize)); +} + +var prepareSingleAxis = function (coordSys) { + var rect = coordSys.getRect(); + + return { + coordSys: { + type: 'singleAxis', + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + }, + api: { + coord: function (val) { + // do not provide "out" param + return coordSys.dataToPoint(val); + }, + size: bind(dataToCoordSize$2, coordSys) + } + }; +}; + +/* +* 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. +*/ + +function dataToCoordSize$3(dataSize, dataItem) { + // dataItem is necessary in log axis. + return map(['Radius', 'Angle'], function (dim, dimIdx) { + var axis = this['get' + dim + 'Axis'](); + var val = dataItem[dimIdx]; + var halfSize = dataSize[dimIdx] / 2; + var method = 'dataTo' + dim; + + var result = axis.type === 'category' + ? axis.getBandWidth() + : Math.abs(axis[method](val - halfSize) - axis[method](val + halfSize)); + + if (dim === 'Angle') { + result = result * Math.PI / 180; + } + + return result; + + }, this); +} + +var preparePolar = function (coordSys) { + var radiusAxis = coordSys.getRadiusAxis(); + var angleAxis = coordSys.getAngleAxis(); + var radius = radiusAxis.getExtent(); + radius[0] > radius[1] && radius.reverse(); + + return { + coordSys: { + type: 'polar', + cx: coordSys.cx, + cy: coordSys.cy, + r: radius[1], + r0: radius[0] + }, + api: { + coord: bind(function (data) { + var radius = radiusAxis.dataToRadius(data[0]); + var angle = angleAxis.dataToAngle(data[1]); + var coord = coordSys.coordToPoint([radius, angle]); + coord.push(radius, angle * Math.PI / 180); + return coord; + }), + size: bind(dataToCoordSize$3, coordSys) + } + }; +}; + +/* +* 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. +*/ + +var prepareCalendar = function (coordSys) { + var rect = coordSys.getRect(); + var rangeInfo = coordSys.getRangeInfo(); + + return { + coordSys: { + type: 'calendar', + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + cellWidth: coordSys.getCellWidth(), + cellHeight: coordSys.getCellHeight(), + rangeInfo: { + start: rangeInfo.start, + end: rangeInfo.end, + weeks: rangeInfo.weeks, + dayCount: rangeInfo.allDay + } + }, + api: { + coord: function (data, clamp) { + return coordSys.dataToPoint(data, clamp); + } + } + }; +}; + +/* +* 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. +*/ + +var CACHED_LABEL_STYLE_PROPERTIES$1 = CACHED_LABEL_STYLE_PROPERTIES; +var ITEM_STYLE_NORMAL_PATH = ['itemStyle']; +var ITEM_STYLE_EMPHASIS_PATH = ['emphasis', 'itemStyle']; +var LABEL_NORMAL = ['label']; +var LABEL_EMPHASIS = ['emphasis', 'label']; +// Use prefix to avoid index to be the same as el.name, +// which will cause weird udpate animation. +var GROUP_DIFF_PREFIX = 'e\0\0'; + + +/** + * To reduce total package size of each coordinate systems, the modules `prepareCustom` + * of each coordinate systems are not required by each coordinate systems directly, but + * required by the module `custom`. + * + * prepareInfoForCustomSeries {Function}: optional + * @return {Object} {coordSys: {...}, api: { + * coord: function (data, clamp) {}, // return point in global. + * size: function (dataSize, dataItem) {} // return size of each axis in coordSys. + * }} + */ +var prepareCustoms = { + cartesian2d: prepareCartesian2d, + geo: prepareGeo, + singleAxis: prepareSingleAxis, + polar: preparePolar, + calendar: prepareCalendar +}; + + +// ------ +// Model +// ------ + +SeriesModel.extend({ + + type: 'series.custom', + + dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'], + + defaultOption: { + coordinateSystem: 'cartesian2d', // Can be set as 'none' + zlevel: 0, + z: 2, + legendHoverLink: true, + + useTransform: true, + + // Custom series will not clip by default. + // Some case will use custom series to draw label + // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight + // Only works on polar and cartesian2d coordinate system. + clip: false + + // Cartesian coordinate system + // xAxisIndex: 0, + // yAxisIndex: 0, + + // Polar coordinate system + // polarIndex: 0, + + // Geo coordinate system + // geoIndex: 0, + + // label: {} + // itemStyle: {} + }, + + /** + * @override + */ + getInitialData: function (option, ecModel) { + return createListFromArray(this.getSource(), this); + }, + + /** + * @override + */ + getDataParams: function (dataIndex, dataType, el) { + var params = SeriesModel.prototype.getDataParams.apply(this, arguments); + el && (params.info = el.info); + return params; + } +}); + +// ----- +// View +// ----- + +Chart.extend({ + + type: 'custom', + + /** + * @private + * @type {module:echarts/data/List} + */ + _data: null, + + /** + * @override + */ + 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); + + // 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$1( + null, newIdx, renderItem(newIdx, payload), customSeries, group, data + ); + }) + .update(function (newIdx, oldIdx) { + var el = oldData.getItemGraphicEl(oldIdx); + createOrUpdate$1( + el, newIdx, renderItem(newIdx, payload), customSeries, group, data + ); + }) + .remove(function (oldIdx) { + var el = oldData.getItemGraphicEl(oldIdx); + el && group.remove(el); + }) + .execute(); + + // Do clipping + var clipPath = customSeries.get('clip', true) + ? createClipPath(customSeries.coordinateSystem, false, customSeries) + : null; + if (clipPath) { + group.setClipPath(clipPath); + } + else { + group.removeClipPath(); + } + + this._data = data; + }, + + incrementalPrepareRender: function (customSeries, ecModel, api) { + this.group.removeAll(); + this._data = null; + }, + + incrementalRender: function (params, customSeries, ecModel, api, payload) { + var data = customSeries.getData(); + var renderItem = makeRenderItem(customSeries, data, ecModel, api); + function setIncrementalAndHoverLayer(el) { + if (!el.isGroup) { + el.incremental = true; + el.useHoverLayer = true; + } + } + for (var idx = params.start; idx < params.end; idx++) { + var el = createOrUpdate$1(null, idx, renderItem(idx, payload), customSeries, this.group, data); + el.traverse(setIncrementalAndHoverLayer); + } + }, + + /** + * @override + */ + dispose: noop, + + /** + * @override + */ + filterForExposedEvent: function (eventType, query, targetEl, packedEvent) { + var elementName = query.element; + if (elementName == null || targetEl.name === elementName) { + return true; + } + + // Enable to give a name on a group made by `renderItem`, and listen + // events that triggerd by its descendents. + while ((targetEl = targetEl.parent) && targetEl !== this.group) { + if (targetEl.name === elementName) { + return true; + } + } + + return false; + } +}); + + +function createEl(elOption) { + var graphicType = elOption.type; + var el; + + // Those graphic elements are not shapes. They should not be + // overwritten by users, so do them first. + if (graphicType === 'path') { + var shape = elOption.shape; + // Using pathRect brings convenience to users sacle svg path. + var pathRect = (shape.width != null && shape.height != null) + ? { + x: shape.x || 0, + y: shape.y || 0, + width: shape.width, + height: shape.height + } + : null; + var pathData = getPathData(shape); + // Path is also used for icon, so layout 'center' by default. + el = makePath(pathData, null, pathRect, shape.layout || 'center'); + el.__customPathData = pathData; + } + else if (graphicType === 'image') { + el = new ZImage({}); + el.__customImagePath = elOption.style.image; + } + else if (graphicType === 'text') { + el = new Text({}); + el.__customText = elOption.style.text; + } + else if (graphicType === 'group') { + el = new Group(); + } + else if (graphicType === 'compoundPath') { + throw new Error('"compoundPath" is not supported yet.'); + } + else { + var Clz = getShapeClass(graphicType); + + if (__DEV__) { + assert$1(Clz, 'graphic type "' + graphicType + '" can not be found.'); + } + + el = new Clz(); + } + + el.__customGraphicType = graphicType; + el.name = elOption.name; + + return el; +} + +function updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot) { + var transitionProps = {}; + var elOptionStyle = elOption.style || {}; + + elOption.shape && (transitionProps.shape = clone(elOption.shape)); + elOption.position && (transitionProps.position = elOption.position.slice()); + elOption.scale && (transitionProps.scale = elOption.scale.slice()); + elOption.origin && (transitionProps.origin = elOption.origin.slice()); + elOption.rotation && (transitionProps.rotation = elOption.rotation); + + if (el.type === 'image' && elOption.style) { + var targetStyle = transitionProps.style = {}; + each$1(['x', 'y', 'width', 'height'], function (prop) { + prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit); + }); + } + + if (el.type === 'text' && elOption.style) { + var targetStyle = transitionProps.style = {}; + each$1(['x', 'y'], function (prop) { + prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit); + }); + // Compatible with previous: both support + // textFill and fill, textStroke and stroke in 'text' element. + !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && ( + elOptionStyle.textFill = elOptionStyle.fill + ); + !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && ( + elOptionStyle.textStroke = elOptionStyle.stroke + ); + } + + if (el.type !== 'group') { + el.useStyle(elOptionStyle); + + // Init animation. + if (isInit) { + el.style.opacity = 0; + var targetOpacity = elOptionStyle.opacity; + targetOpacity == null && (targetOpacity = 1); + initProps(el, {style: {opacity: targetOpacity}}, animatableModel, dataIndex); + } + } + + if (isInit) { + el.attr(transitionProps); + } + else { + updateProps(el, transitionProps, animatableModel, dataIndex); + } + + // Merge by default. + // z2 must not be null/undefined, otherwise sort error may occur. + elOption.hasOwnProperty('z2') && el.attr('z2', elOption.z2 || 0); + elOption.hasOwnProperty('silent') && el.attr('silent', elOption.silent); + elOption.hasOwnProperty('invisible') && el.attr('invisible', elOption.invisible); + elOption.hasOwnProperty('ignore') && el.attr('ignore', elOption.ignore); + // `elOption.info` enables user to mount some info on + // elements and use them in event handlers. + // Update them only when user specified, otherwise, remain. + elOption.hasOwnProperty('info') && el.attr('info', elOption.info); + + // If `elOption.styleEmphasis` is `false`, remove hover style. The + // logic is ensured by `graphicUtil.setElementHoverStyle`. + var styleEmphasis = elOption.styleEmphasis; + // hoverStyle should always be set here, because if the hover style + // may already be changed, where the inner cache should be reset. + setElementHoverStyle(el, styleEmphasis); + if (isRoot) { + setAsHighDownDispatcher(el, styleEmphasis !== false); + } +} + +function prepareStyleTransition(prop, targetStyle, elOptionStyle, oldElStyle, isInit) { + if (elOptionStyle[prop] != null && !isInit) { + targetStyle[prop] = elOptionStyle[prop]; + elOptionStyle[prop] = oldElStyle[prop]; + } +} + +function makeRenderItem(customSeries, data, ecModel, api) { + var renderItem = customSeries.get('renderItem'); + var coordSys = customSeries.coordinateSystem; + var prepareResult = {}; + + if (coordSys) { + if (__DEV__) { + assert$1(renderItem, 'series.render is required.'); + assert$1( + coordSys.prepareCustoms || prepareCustoms[coordSys.type], + 'This coordSys does not support custom series.' + ); + } + + prepareResult = coordSys.prepareCustoms + ? coordSys.prepareCustoms() + : prepareCustoms[coordSys.type](coordSys); + } + + var userAPI = defaults({ + getWidth: api.getWidth, + getHeight: api.getHeight, + getZr: api.getZr, + getDevicePixelRatio: api.getDevicePixelRatio, + value: value, + style: style, + styleEmphasis: styleEmphasis, + visual: visual, + barLayout: barLayout, + currentSeriesIndices: currentSeriesIndices, + font: font + }, prepareResult.api || {}); + + var userParams = { + // The life cycle of context: current round of rendering. + // The global life cycle is probably not necessary, because + // user can store global status by themselves. + context: {}, + seriesId: customSeries.id, + seriesName: customSeries.name, + seriesIndex: customSeries.seriesIndex, + coordSys: prepareResult.coordSys, + dataInsideLength: data.count(), + encode: wrapEncodeDef(customSeries.getData()) + }; + + // Do not support call `api` asynchronously without dataIndexInside input. + var currDataIndexInside; + var currDirty = true; + var currItemModel; + var currLabelNormalModel; + var currLabelEmphasisModel; + var currVisualColor; + + return function (dataIndexInside, payload) { + currDataIndexInside = dataIndexInside; + currDirty = true; + + return renderItem && renderItem( + defaults({ + dataIndexInside: dataIndexInside, + dataIndex: data.getRawIndex(dataIndexInside), + // Can be used for optimization when zoom or roam. + actionType: payload ? payload.type : null + }, userParams), + userAPI + ); + }; + + // Do not update cache until api called. + function updateCache(dataIndexInside) { + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + if (currDirty) { + currItemModel = data.getItemModel(dataIndexInside); + currLabelNormalModel = currItemModel.getModel(LABEL_NORMAL); + currLabelEmphasisModel = currItemModel.getModel(LABEL_EMPHASIS); + currVisualColor = data.getItemVisual(dataIndexInside, 'color'); + + currDirty = false; + } + } + + /** + * @public + * @param {number|string} dim + * @param {number} [dataIndexInside=currDataIndexInside] + * @return {number|string} value + */ + function value(dim, dataIndexInside) { + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + return data.get(data.getDimension(dim || 0), dataIndexInside); + } + + /** + * By default, `visual` is applied to style (to support visualMap). + * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`, + * it can be implemented as: + * `api.style({stroke: api.visual('color'), fill: null})`; + * @public + * @param {Object} [extra] + * @param {number} [dataIndexInside=currDataIndexInside] + */ + function style(extra, dataIndexInside) { + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + updateCache(dataIndexInside); + + var itemStyle = currItemModel.getModel(ITEM_STYLE_NORMAL_PATH).getItemStyle(); + + currVisualColor != null && (itemStyle.fill = currVisualColor); + var opacity = data.getItemVisual(dataIndexInside, 'opacity'); + opacity != null && (itemStyle.opacity = opacity); + + var labelModel = extra + ? applyExtraBefore(extra, currLabelNormalModel) + : currLabelNormalModel; + + setTextStyle(itemStyle, labelModel, null, { + autoColor: currVisualColor, + isRectText: true + }); + + itemStyle.text = labelModel.getShallow('show') + ? retrieve2( + customSeries.getFormattedLabel(dataIndexInside, 'normal'), + getDefaultLabel(data, dataIndexInside) + ) + : null; + + extra && applyExtraAfter(itemStyle, extra); + + return itemStyle; + } + + /** + * @public + * @param {Object} [extra] + * @param {number} [dataIndexInside=currDataIndexInside] + */ + function styleEmphasis(extra, dataIndexInside) { + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + updateCache(dataIndexInside); + + var itemStyle = currItemModel.getModel(ITEM_STYLE_EMPHASIS_PATH).getItemStyle(); + + var labelModel = extra + ? applyExtraBefore(extra, currLabelEmphasisModel) + : currLabelEmphasisModel; + + setTextStyle(itemStyle, labelModel, null, { + isRectText: true + }, true); + + itemStyle.text = labelModel.getShallow('show') + ? retrieve3( + customSeries.getFormattedLabel(dataIndexInside, 'emphasis'), + customSeries.getFormattedLabel(dataIndexInside, 'normal'), + getDefaultLabel(data, dataIndexInside) + ) + : null; + + extra && applyExtraAfter(itemStyle, extra); + + return itemStyle; + } + + /** + * @public + * @param {string} visualType + * @param {number} [dataIndexInside=currDataIndexInside] + */ + function visual(visualType, dataIndexInside) { + dataIndexInside == null && (dataIndexInside = currDataIndexInside); + return data.getItemVisual(dataIndexInside, visualType); + } + + /** + * @public + * @param {number} opt.count Positive interger. + * @param {number} [opt.barWidth] + * @param {number} [opt.barMaxWidth] + * @param {number} [opt.barMinWidth] + * @param {number} [opt.barGap] + * @param {number} [opt.barCategoryGap] + * @return {Object} {width, offset, offsetCenter} is not support, return undefined. + */ + function barLayout(opt) { + if (coordSys.getBaseAxis) { + var baseAxis = coordSys.getBaseAxis(); + return getLayoutOnAxis(defaults({axis: baseAxis}, opt), api); + } + } + + /** + * @public + * @return {Array.} + */ + function currentSeriesIndices() { + return ecModel.getCurrentSeriesIndices(); + } + + /** + * @public + * @param {Object} opt + * @param {string} [opt.fontStyle] + * @param {number} [opt.fontWeight] + * @param {number} [opt.fontSize] + * @param {string} [opt.fontFamily] + * @return {string} font string + */ + function font(opt) { + return getFont(opt, ecModel); + } +} + +function wrapEncodeDef(data) { + var encodeDef = {}; + each$1(data.dimensions, function (dimName, dataDimIndex) { + var dimInfo = data.getDimensionInfo(dimName); + if (!dimInfo.isExtraCoord) { + var coordDim = dimInfo.coordDim; + var dataDims = encodeDef[coordDim] = encodeDef[coordDim] || []; + dataDims[dimInfo.coordDimIndex] = dataDimIndex; + } + }); + return encodeDef; +} + +function createOrUpdate$1(el, dataIndex, elOption, animatableModel, group, data) { + el = doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, true); + el && data.setItemGraphicEl(dataIndex, el); + + return el; +} + +function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, isRoot) { + + // [Rule] + // By default, follow merge mode. + // (It probably brings benifit for performance in some cases of large data, where + // user program can be optimized to that only updated props needed to be re-calculated, + // or according to `actionType` some calculation can be skipped.) + // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing. + // (It seems that violate the "merge" principle, but most of users probably intuitively + // regard "return;" as "show nothing element whatever", so make a exception to meet the + // most cases.) + + var simplyRemove = !elOption; // `null`/`undefined`/`false` + elOption = elOption || {}; + var elOptionType = elOption.type; + var elOptionShape = elOption.shape; + var elOptionStyle = elOption.style; + + if (el && ( + simplyRemove + // || elOption.$merge === false + // If `elOptionType` is `null`, follow the merge principle. + || (elOptionType != null + && elOptionType !== el.__customGraphicType + ) + || (elOptionType === 'path' + && hasOwnPathData(elOptionShape) && getPathData(elOptionShape) !== el.__customPathData + ) + || (elOptionType === 'image' + && hasOwn(elOptionStyle, 'image') && elOptionStyle.image !== el.__customImagePath + ) + // FIXME test and remove this restriction? + || (elOptionType === 'text' + && hasOwn(elOptionShape, 'text') && elOptionStyle.text !== el.__customText + ) + )) { + group.remove(el); + el = null; + } + + // `elOption.type` is undefined when `renderItem` returns nothing. + if (simplyRemove) { + return; + } + + var isInit = !el; + !el && (el = createEl(elOption)); + updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot); + + if (elOptionType === 'group') { + mergeChildren(el, dataIndex, elOption, animatableModel, data); + } + + // Always add whatever already added to ensure sequence. + group.add(el); + + return el; +} + +// Usage: +// (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that +// the existing children will not be removed, and enables the feature that +// update some of the props of some of the children simply by construct +// the returned children of `renderItem` like: +// `var children = group.children = []; children[3] = {opacity: 0.5};` +// (2) If `elOption.$mergeChildren` is `'byName'`, add/update/remove children +// by child.name. But that might be lower performance. +// (3) If `elOption.$mergeChildren` is `false`, the existing children will be +// replaced totally. +// (4) If `!elOption.children`, following the "merge" principle, nothing will happen. +// +// For implementation simpleness, do not provide a direct way to remove sinlge +// child (otherwise the total indicies of the children array have to be modified). +// User can remove a single child by set its `ignore` as `true` or replace +// it by another element, where its `$merge` can be set as `true` if necessary. +function mergeChildren(el, dataIndex, elOption, animatableModel, data) { + var newChildren = elOption.children; + var newLen = newChildren ? newChildren.length : 0; + var mergeChildren = elOption.$mergeChildren; + // `diffChildrenByName` has been deprecated. + var byName = mergeChildren === 'byName' || elOption.diffChildrenByName; + var notMerge = mergeChildren === false; + + // For better performance on roam update, only enter if necessary. + if (!newLen && !byName && !notMerge) { + return; + } + + if (byName) { + diffGroupChildren({ + oldChildren: el.children() || [], + newChildren: newChildren || [], + dataIndex: dataIndex, + animatableModel: animatableModel, + group: el, + data: data + }); + return; + } + + notMerge && el.removeAll(); + + // Mapping children of a group simply by index, which + // might be better performance. + var index = 0; + for (; index < newLen; index++) { + newChildren[index] && doCreateOrUpdate( + el.childAt(index), + dataIndex, + newChildren[index], + animatableModel, + el, + data + ); + } + if (__DEV__) { + assert$1( + !notMerge || el.childCount() === index, + 'MUST NOT contain empty item in children array when `group.$mergeChildren` is `false`.' + ); + } +} + +function diffGroupChildren(context) { + (new DataDiffer( + context.oldChildren, + context.newChildren, + getKey, + getKey, + context + )) + .add(processAddUpdate) + .update(processAddUpdate) + .remove(processRemove) + .execute(); +} + +function getKey(item, idx) { + var name = item && item.name; + return name != null ? name : GROUP_DIFF_PREFIX + idx; +} + +function processAddUpdate(newIndex, oldIndex) { + var context = this.context; + var childOption = newIndex != null ? context.newChildren[newIndex] : null; + var child = oldIndex != null ? context.oldChildren[oldIndex] : null; + + doCreateOrUpdate( + child, + context.dataIndex, + childOption, + context.animatableModel, + context.group, + context.data + ); +} + +// `graphic#applyDefaultTextStyle` will cache +// textFill, textStroke, textStrokeWidth. +// We have to do this trick. +function applyExtraBefore(extra, model) { + var dummyModel = new Model({}, model); + each$1(CACHED_LABEL_STYLE_PROPERTIES$1, function (stylePropName, modelPropName) { + if (extra.hasOwnProperty(stylePropName)) { + dummyModel.option[modelPropName] = extra[stylePropName]; + } + }); + return dummyModel; +} + +function applyExtraAfter(itemStyle, extra) { + for (var key in extra) { + if (extra.hasOwnProperty(key) + || !CACHED_LABEL_STYLE_PROPERTIES$1.hasOwnProperty(key) + ) { + itemStyle[key] = extra[key]; + } + } +} + +function processRemove(oldIndex) { + var context = this.context; + var child = context.oldChildren[oldIndex]; + child && context.group.remove(child); +} + +function getPathData(shape) { + // "d" follows the SVG convention. + return shape && (shape.pathData || shape.d); +} + +function hasOwnPathData(shape) { + return shape && (shape.hasOwnProperty('pathData') || shape.hasOwnProperty('d')); +} + +function hasOwn(host, prop) { + return host && host.hasOwnProperty(prop); +} + +/* +* 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. +*/ + +/* +* 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. +*/ + +function getSeriesStackId$1(seriesModel) { + return seriesModel.get('stack') + || '__ec_stack_' + seriesModel.seriesIndex; +} + +function getAxisKey$1(polar, axis) { + return axis.dim + polar.model.componentIndex; +} + +/** + * @param {string} seriesType + * @param {module:echarts/model/Global} ecModel + * @param {module:echarts/ExtensionAPI} api + */ +function barLayoutPolar(seriesType, ecModel, api) { + + var lastStackCoords = {}; + + var barWidthAndOffset = calRadialBar( + filter( + ecModel.getSeriesByType(seriesType), + function (seriesModel) { + return !ecModel.isSeriesFiltered(seriesModel) + && seriesModel.coordinateSystem + && seriesModel.coordinateSystem.type === 'polar'; + } + ) + ); + + ecModel.eachSeriesByType(seriesType, function (seriesModel) { + + // Check series coordinate, do layout for polar only + if (seriesModel.coordinateSystem.type !== 'polar') { + return; + } + + var data = seriesModel.getData(); + var polar = seriesModel.coordinateSystem; + var baseAxis = polar.getBaseAxis(); + var axisKey = getAxisKey$1(polar, baseAxis); + + var stackId = getSeriesStackId$1(seriesModel); + var columnLayoutInfo = barWidthAndOffset[axisKey][stackId]; + var columnOffset = columnLayoutInfo.offset; + var columnWidth = columnLayoutInfo.width; + var valueAxis = polar.getOtherAxis(baseAxis); + + var cx = seriesModel.coordinateSystem.cx; + var cy = seriesModel.coordinateSystem.cy; + + var barMinHeight = seriesModel.get('barMinHeight') || 0; + var barMinAngle = seriesModel.get('barMinAngle') || 0; + + lastStackCoords[stackId] = lastStackCoords[stackId] || []; + + var valueDim = data.mapDimension(valueAxis.dim); + var baseDim = data.mapDimension(baseAxis.dim); + var stacked = isDimensionStacked(data, valueDim /*, baseDim*/); + var clampLayout = baseAxis.dim !== 'radius' + || !seriesModel.get('roundCap', true); + + var valueAxisStart = valueAxis.getExtent()[0]; + + for (var idx = 0, len = data.count(); idx < len; idx++) { + var value = data.get(valueDim, idx); + var baseValue = data.get(baseDim, idx); + + if (isNaN(value)) { + continue; + } + + var sign = value >= 0 ? 'p' : 'n'; + var baseCoord = valueAxisStart; + + // Because of the barMinHeight, we can not use the value in + // stackResultDimension directly. + // Only ordinal axis can be stacked. + if (stacked) { + if (!lastStackCoords[stackId][baseValue]) { + lastStackCoords[stackId][baseValue] = { + p: valueAxisStart, // Positive stack + n: valueAxisStart // Negative stack + }; + } + // Should also consider #4243 + baseCoord = lastStackCoords[stackId][baseValue][sign]; + } + + var r0; + var r; + var startAngle; + var endAngle; + + // radial sector + if (valueAxis.dim === 'radius') { + var radiusSpan = valueAxis.dataToRadius(value) - valueAxisStart; + var angle = baseAxis.dataToAngle(baseValue); + + if (Math.abs(radiusSpan) < barMinHeight) { + radiusSpan = (radiusSpan < 0 ? -1 : 1) * barMinHeight; + } + + r0 = baseCoord; + r = baseCoord + radiusSpan; + startAngle = angle - columnOffset; + endAngle = startAngle - columnWidth; + + stacked && (lastStackCoords[stackId][baseValue][sign] = r); + } + // tangential sector + else { + var angleSpan = valueAxis.dataToAngle(value, clampLayout) - valueAxisStart; + var radius = baseAxis.dataToRadius(baseValue); + + if (Math.abs(angleSpan) < barMinAngle) { + angleSpan = (angleSpan < 0 ? -1 : 1) * barMinAngle; + } + + r0 = radius + columnOffset; + r = r0 + columnWidth; + startAngle = baseCoord; + endAngle = baseCoord + angleSpan; + + // if the previous stack is at the end of the ring, + // add a round to differentiate it from origin + // var extent = angleAxis.getExtent(); + // var stackCoord = angle; + // if (stackCoord === extent[0] && value > 0) { + // stackCoord = extent[1]; + // } + // else if (stackCoord === extent[1] && value < 0) { + // stackCoord = extent[0]; + // } + stacked && (lastStackCoords[stackId][baseValue][sign] = endAngle); + } + + data.setItemLayout(idx, { + cx: cx, + cy: cy, + r0: r0, + r: r, + // Consider that positive angle is anti-clockwise, + // while positive radian of sector is clockwise + startAngle: -startAngle * Math.PI / 180, + endAngle: -endAngle * Math.PI / 180 + }); + + } + + }, this); + +} + +/** + * Calculate bar width and offset for radial bar charts + */ +function calRadialBar(barSeries, api) { + // Columns info on each category axis. Key is polar name + var columnsMap = {}; + + each$1(barSeries, function (seriesModel, idx) { + var data = seriesModel.getData(); + var polar = seriesModel.coordinateSystem; + + var baseAxis = polar.getBaseAxis(); + var axisKey = getAxisKey$1(polar, baseAxis); + + var axisExtent = baseAxis.getExtent(); + var bandWidth = baseAxis.type === 'category' + ? baseAxis.getBandWidth() + : (Math.abs(axisExtent[1] - axisExtent[0]) / data.count()); + + var columnsOnAxis = columnsMap[axisKey] || { + bandWidth: bandWidth, + remainedWidth: bandWidth, + autoWidthCount: 0, + categoryGap: '20%', + gap: '30%', + stacks: {} + }; + var stacks = columnsOnAxis.stacks; + columnsMap[axisKey] = columnsOnAxis; + + var stackId = getSeriesStackId$1(seriesModel); + + if (!stacks[stackId]) { + columnsOnAxis.autoWidthCount++; + } + stacks[stackId] = stacks[stackId] || { + width: 0, + maxWidth: 0 + }; + + var barWidth = parsePercent$1( + seriesModel.get('barWidth'), + bandWidth + ); + var barMaxWidth = parsePercent$1( + seriesModel.get('barMaxWidth'), + bandWidth + ); + var barGap = seriesModel.get('barGap'); + var barCategoryGap = seriesModel.get('barCategoryGap'); + + if (barWidth && !stacks[stackId].width) { + barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth); + stacks[stackId].width = barWidth; + columnsOnAxis.remainedWidth -= barWidth; + } + + barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth); + (barGap != null) && (columnsOnAxis.gap = barGap); + (barCategoryGap != null) && (columnsOnAxis.categoryGap = barCategoryGap); + }); + + + var result = {}; + + each$1(columnsMap, function (columnsOnAxis, coordSysName) { + + result[coordSysName] = {}; + + var stacks = columnsOnAxis.stacks; + var bandWidth = columnsOnAxis.bandWidth; + var categoryGap = parsePercent$1(columnsOnAxis.categoryGap, bandWidth); + var barGapPercent = parsePercent$1(columnsOnAxis.gap, 1); + + var remainedWidth = columnsOnAxis.remainedWidth; + var autoWidthCount = columnsOnAxis.autoWidthCount; + var autoWidth = (remainedWidth - categoryGap) + / (autoWidthCount + (autoWidthCount - 1) * barGapPercent); + autoWidth = Math.max(autoWidth, 0); + + // Find if any auto calculated bar exceeded maxBarWidth + each$1(stacks, function (column, stack) { + var maxWidth = column.maxWidth; + if (maxWidth && maxWidth < autoWidth) { + maxWidth = Math.min(maxWidth, remainedWidth); + if (column.width) { + maxWidth = Math.min(maxWidth, column.width); + } + remainedWidth -= maxWidth; + column.width = maxWidth; + autoWidthCount--; + } + }); + + // Recalculate width again + autoWidth = (remainedWidth - categoryGap) + / (autoWidthCount + (autoWidthCount - 1) * barGapPercent); + autoWidth = Math.max(autoWidth, 0); + + var widthSum = 0; + var lastColumn; + each$1(stacks, function (column, idx) { + if (!column.width) { + column.width = autoWidth; + } + lastColumn = column; + widthSum += column.width * (1 + barGapPercent); + }); + if (lastColumn) { + widthSum -= lastColumn.width * barGapPercent; + } + + var offset = -widthSum / 2; + each$1(stacks, function (column, stackId) { + result[coordSysName][stackId] = result[coordSysName][stackId] || { + offset: offset, + width: column.width + }; + + offset += column.width * (1 + barGapPercent); + }); + }); + + return result; +} + +/* +* 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. +*/ + +function RadiusAxis(scale, radiusExtent) { + + Axis.call(this, 'radius', scale, radiusExtent); + + /** + * Axis type + * - 'category' + * - 'value' + * - 'time' + * - 'log' + * @type {string} + */ + this.type = 'category'; +} + +RadiusAxis.prototype = { + + constructor: RadiusAxis, + + /** + * @override + */ + pointToData: function (point, clamp) { + return this.polar.pointToData(point, clamp)[this.dim === 'radius' ? 0 : 1]; + }, + + dataToRadius: Axis.prototype.dataToCoord, + + radiusToData: Axis.prototype.coordToData +}; + +inherits(RadiusAxis, Axis); + +/* +* 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. +*/ + +var inner$12 = makeInner(); + +function AngleAxis(scale, angleExtent) { + + angleExtent = angleExtent || [0, 360]; + + Axis.call(this, 'angle', scale, angleExtent); + + /** + * Axis type + * - 'category' + * - 'value' + * - 'time' + * - 'log' + * @type {string} + */ + this.type = 'category'; +} + +AngleAxis.prototype = { + + constructor: AngleAxis, + + /** + * @override + */ + pointToData: function (point, clamp) { + return this.polar.pointToData(point, clamp)[this.dim === 'radius' ? 0 : 1]; + }, + + dataToAngle: Axis.prototype.dataToCoord, + + angleToData: Axis.prototype.coordToData, + + /** + * Only be called in category axis. + * Angle axis uses text height to decide interval + * + * @override + * @return {number} Auto interval for cateogry axis tick and label + */ + calculateCategoryInterval: function () { + var axis = this; + var labelModel = axis.getLabelModel(); + + var ordinalScale = axis.scale; + var ordinalExtent = ordinalScale.getExtent(); + // Providing this method is for optimization: + // avoid generating a long array by `getTicks` + // in large category data case. + var tickCount = ordinalScale.count(); + + if (ordinalExtent[1] - ordinalExtent[0] < 1) { + return 0; + } + + var tickValue = ordinalExtent[0]; + var unitSpan = axis.dataToCoord(tickValue + 1) - axis.dataToCoord(tickValue); + var unitH = Math.abs(unitSpan); + + // Not precise, just use height as text width + // and each distance from axis line yet. + var rect = getBoundingRect( + tickValue, labelModel.getFont(), 'center', 'top' + ); + var maxH = Math.max(rect.height, 7); + + var dh = maxH / unitH; + // 0/0 is NaN, 1/0 is Infinity. + isNaN(dh) && (dh = Infinity); + var interval = Math.max(0, Math.floor(dh)); + + var cache = inner$12(axis.model); + var lastAutoInterval = cache.lastAutoInterval; + var lastTickCount = cache.lastTickCount; + + // Use cache to keep interval stable while moving zoom window, + // otherwise the calculated interval might jitter when the zoom + // window size is close to the interval-changing size. + if (lastAutoInterval != null + && lastTickCount != null + && Math.abs(lastAutoInterval - interval) <= 1 + && Math.abs(lastTickCount - tickCount) <= 1 + // Always choose the bigger one, otherwise the critical + // point is not the same when zooming in or zooming out. + && lastAutoInterval > interval + ) { + interval = lastAutoInterval; + } + // Only update cache if cache not used, otherwise the + // changing of interval is too insensitive. + else { + cache.lastTickCount = tickCount; + cache.lastAutoInterval = interval; + } + + return interval; + } +}; + +inherits(AngleAxis, Axis); + +/* +* 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. +*/ + +/** + * @module echarts/coord/polar/Polar + */ + +/** + * @alias {module:echarts/coord/polar/Polar} + * @constructor + * @param {string} name + */ +var Polar = function (name) { + + /** + * @type {string} + */ + this.name = name || ''; + + /** + * x of polar center + * @type {number} + */ + this.cx = 0; + + /** + * y of polar center + * @type {number} + */ + this.cy = 0; + + /** + * @type {module:echarts/coord/polar/RadiusAxis} + * @private + */ + this._radiusAxis = new RadiusAxis(); + + /** + * @type {module:echarts/coord/polar/AngleAxis} + * @private + */ + this._angleAxis = new AngleAxis(); + + this._radiusAxis.polar = this._angleAxis.polar = this; +}; + +Polar.prototype = { + + type: 'polar', + + axisPointerEnabled: true, + + constructor: Polar, + + /** + * @param {Array.} + * @readOnly + */ + dimensions: ['radius', 'angle'], + + /** + * @type {module:echarts/coord/PolarModel} + */ + model: null, + + /** + * If contain coord + * @param {Array.} point + * @return {boolean} + */ + containPoint: function (point) { + var coord = this.pointToCoord(point); + return this._radiusAxis.contain(coord[0]) + && this._angleAxis.contain(coord[1]); + }, + + /** + * If contain data + * @param {Array.} data + * @return {boolean} + */ + containData: function (data) { + return this._radiusAxis.containData(data[0]) + && this._angleAxis.containData(data[1]); + }, + + /** + * @param {string} dim + * @return {module:echarts/coord/polar/AngleAxis|module:echarts/coord/polar/RadiusAxis} + */ + getAxis: function (dim) { + return this['_' + dim + 'Axis']; + }, + + /** + * @return {Array.} + */ + getAxes: function () { + return [this._radiusAxis, this._angleAxis]; + }, + + /** + * Get axes by type of scale + * @param {string} scaleType + * @return {module:echarts/coord/polar/AngleAxis|module:echarts/coord/polar/RadiusAxis} + */ + getAxesByScale: function (scaleType) { + var axes = []; + var angleAxis = this._angleAxis; + var radiusAxis = this._radiusAxis; + angleAxis.scale.type === scaleType && axes.push(angleAxis); + radiusAxis.scale.type === scaleType && axes.push(radiusAxis); + + return axes; + }, + + /** + * @return {module:echarts/coord/polar/AngleAxis} + */ + getAngleAxis: function () { + return this._angleAxis; + }, + + /** + * @return {module:echarts/coord/polar/RadiusAxis} + */ + getRadiusAxis: function () { + return this._radiusAxis; + }, + + /** + * @param {module:echarts/coord/polar/Axis} + * @return {module:echarts/coord/polar/Axis} + */ + getOtherAxis: function (axis) { + var angleAxis = this._angleAxis; + return axis === angleAxis ? this._radiusAxis : angleAxis; + }, + + /** + * Base axis will be used on stacking. + * + * @return {module:echarts/coord/polar/Axis} + */ + getBaseAxis: function () { + return this.getAxesByScale('ordinal')[0] + || this.getAxesByScale('time')[0] + || this.getAngleAxis(); + }, + + /** + * @param {string} [dim] 'radius' or 'angle' or 'auto' or null/undefined + * @return {Object} {baseAxes: [], otherAxes: []} + */ + getTooltipAxes: function (dim) { + var baseAxis = (dim != null && dim !== 'auto') + ? this.getAxis(dim) : this.getBaseAxis(); + return { + baseAxes: [baseAxis], + otherAxes: [this.getOtherAxis(baseAxis)] + }; + }, + + /** + * Convert a single data item to (x, y) point. + * Parameter data is an array which the first element is radius and the second is angle + * @param {Array.} data + * @param {boolean} [clamp=false] + * @return {Array.} + */ + dataToPoint: function (data, clamp) { + return this.coordToPoint([ + this._radiusAxis.dataToRadius(data[0], clamp), + this._angleAxis.dataToAngle(data[1], clamp) + ]); + }, + + /** + * Convert a (x, y) point to data + * @param {Array.} point + * @param {boolean} [clamp=false] + * @return {Array.} + */ + pointToData: function (point, clamp) { + var coord = this.pointToCoord(point); + return [ + this._radiusAxis.radiusToData(coord[0], clamp), + this._angleAxis.angleToData(coord[1], clamp) + ]; + }, + + /** + * Convert a (x, y) point to (radius, angle) coord + * @param {Array.} point + * @return {Array.} + */ + pointToCoord: function (point) { + var dx = point[0] - this.cx; + var dy = point[1] - this.cy; + var angleAxis = this.getAngleAxis(); + var extent = angleAxis.getExtent(); + var minAngle = Math.min(extent[0], extent[1]); + var maxAngle = Math.max(extent[0], extent[1]); + // Fix fixed extent in polarCreator + // FIXME + angleAxis.inverse + ? (minAngle = maxAngle - 360) + : (maxAngle = minAngle + 360); + + var radius = Math.sqrt(dx * dx + dy * dy); + dx /= radius; + dy /= radius; + + var radian = Math.atan2(-dy, dx) / Math.PI * 180; + + // move to angleExtent + var dir = radian < minAngle ? 1 : -1; + while (radian < minAngle || radian > maxAngle) { + radian += dir * 360; + } + + return [radius, radian]; + }, + + /** + * Convert a (radius, angle) coord to (x, y) point + * @param {Array.} coord + * @return {Array.} + */ + coordToPoint: function (coord) { + var radius = coord[0]; + var radian = coord[1] / 180 * Math.PI; + var x = Math.cos(radian) * radius + this.cx; + // Inverse the y + var y = -Math.sin(radian) * radius + this.cy; + + return [x, y]; + }, + + /** + * Get ring area of cartesian. + * Area will have a contain function to determine if a point is in the coordinate system. + * @return {Ring} + */ + getArea: function () { + + var angleAxis = this.getAngleAxis(); + var radiusAxis = this.getRadiusAxis(); + + var radiusExtent = radiusAxis.getExtent().slice(); + radiusExtent[0] > radiusExtent[1] && radiusExtent.reverse(); + var angleExtent = angleAxis.getExtent(); + + var RADIAN = Math.PI / 180; + + return { + cx: this.cx, + cy: this.cy, + r0: radiusExtent[0], + r: radiusExtent[1], + startAngle: -angleExtent[0] * RADIAN, + endAngle: -angleExtent[1] * RADIAN, + clockwise: angleAxis.inverse, + contain: function (x, y) { + // It's a ring shape. + // Start angle and end angle don't matter + var dx = x - this.cx; + var dy = y - this.cy; + var d2 = dx * dx + dy * dy; + var r = this.r; + var r0 = this.r0; + + return d2 <= r * r && d2 >= r0 * r0; + } + }; + } + +}; + +/* +* 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. +*/ + +var PolarAxisModel = ComponentModel.extend({ + + type: 'polarAxis', + + /** + * @type {module:echarts/coord/polar/AngleAxis|module:echarts/coord/polar/RadiusAxis} + */ + axis: null, + + /** + * @override + */ + getCoordSysModel: function () { + return this.ecModel.queryComponents({ + mainType: 'polar', + index: this.option.polarIndex, + id: this.option.polarId + })[0]; + } + +}); + +merge(PolarAxisModel.prototype, axisModelCommonMixin); + +var polarAxisDefaultExtendedOption = { + angle: { + // polarIndex: 0, + // polarId: '', + + startAngle: 90, + + clockwise: true, + + splitNumber: 12, + + axisLabel: { + rotate: false + } + }, + radius: { + // polarIndex: 0, + // polarId: '', + + splitNumber: 5 + } +}; + +function getAxisType$3(axisDim, option) { + // Default axis with data is category axis + return option.type || (option.data ? 'category' : 'value'); +} + +axisModelCreator('angle', PolarAxisModel, getAxisType$3, polarAxisDefaultExtendedOption.angle); +axisModelCreator('radius', PolarAxisModel, getAxisType$3, polarAxisDefaultExtendedOption.radius); + +/* +* 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. +*/ + +extendComponentModel({ + + type: 'polar', + + dependencies: ['polarAxis', 'angleAxis'], + + /** + * @type {module:echarts/coord/polar/Polar} + */ + coordinateSystem: null, + + /** + * @param {string} axisType + * @return {module:echarts/coord/polar/AxisModel} + */ + findAxisModel: function (axisType) { + var foundAxisModel; + var ecModel = this.ecModel; + + ecModel.eachComponent(axisType, function (axisModel) { + if (axisModel.getCoordSysModel() === this) { + foundAxisModel = axisModel; + } + }, this); + return foundAxisModel; + }, + + defaultOption: { + + zlevel: 0, + + z: 0, + + center: ['50%', '50%'], + + radius: '80%' + } +}); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +// TODO Axis scale + +/** + * Resize method bound to the polar + * @param {module:echarts/coord/polar/PolarModel} polarModel + * @param {module:echarts/ExtensionAPI} api + */ +function resizePolar(polar, polarModel, api) { + var center = polarModel.get('center'); + var width = api.getWidth(); + var height = api.getHeight(); + + polar.cx = parsePercent$1(center[0], width); + polar.cy = parsePercent$1(center[1], height); + + var radiusAxis = polar.getRadiusAxis(); + var size = Math.min(width, height) / 2; + + var radius = polarModel.get('radius'); + if (radius == null) { + radius = [0, '100%']; + } + else if (!isArray(radius)) { + // r0 = 0 + radius = [0, radius]; + } + radius = [ + parsePercent$1(radius[0], size), + parsePercent$1(radius[1], size) + ]; + + radiusAxis.inverse + ? radiusAxis.setExtent(radius[1], radius[0]) + : radiusAxis.setExtent(radius[0], radius[1]); +} + +/** + * Update polar + */ +function updatePolarScale(ecModel, api) { + var polar = this; + var angleAxis = polar.getAngleAxis(); + var radiusAxis = polar.getRadiusAxis(); + // Reset scale + angleAxis.scale.setExtent(Infinity, -Infinity); + radiusAxis.scale.setExtent(Infinity, -Infinity); + + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.coordinateSystem === polar) { + var data = seriesModel.getData(); + each$1(data.mapDimension('radius', true), function (dim) { + radiusAxis.scale.unionExtentFromData( + data, getStackedDimension(data, dim) + ); + }); + each$1(data.mapDimension('angle', true), function (dim) { + angleAxis.scale.unionExtentFromData( + data, getStackedDimension(data, dim) + ); + }); + } + }); + + niceScaleExtent(angleAxis.scale, angleAxis.model); + niceScaleExtent(radiusAxis.scale, radiusAxis.model); + + // Fix extent of category angle axis + if (angleAxis.type === 'category' && !angleAxis.onBand) { + var extent = angleAxis.getExtent(); + var diff = 360 / angleAxis.scale.count(); + angleAxis.inverse ? (extent[1] += diff) : (extent[1] -= diff); + angleAxis.setExtent(extent[0], extent[1]); + } +} + +/** + * Set common axis properties + * @param {module:echarts/coord/polar/AngleAxis|module:echarts/coord/polar/RadiusAxis} + * @param {module:echarts/coord/polar/AxisModel} + * @inner + */ +function setAxis(axis, axisModel) { + axis.type = axisModel.get('type'); + axis.scale = createScaleByModel(axisModel); + axis.onBand = axisModel.get('boundaryGap') && axis.type === 'category'; + axis.inverse = axisModel.get('inverse'); + + if (axisModel.mainType === 'angleAxis') { + axis.inverse ^= axisModel.get('clockwise'); + var startAngle = axisModel.get('startAngle'); + axis.setExtent(startAngle, startAngle + (axis.inverse ? -360 : 360)); + } + + // Inject axis instance + axisModel.axis = axis; + axis.model = axisModel; +} + + +var polarCreator = { + + dimensions: Polar.prototype.dimensions, + + create: function (ecModel, api) { + var polarList = []; + ecModel.eachComponent('polar', function (polarModel, idx) { + var polar = new Polar(idx); + // Inject resize and update method + polar.update = updatePolarScale; + + var radiusAxis = polar.getRadiusAxis(); + var angleAxis = polar.getAngleAxis(); + + var radiusAxisModel = polarModel.findAxisModel('radiusAxis'); + var angleAxisModel = polarModel.findAxisModel('angleAxis'); + + setAxis(radiusAxis, radiusAxisModel); + setAxis(angleAxis, angleAxisModel); + + resizePolar(polar, polarModel, api); + + polarList.push(polar); + + polarModel.coordinateSystem = polar; + polar.model = polarModel; + }); + // Inject coordinateSystem to series + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.get('coordinateSystem') === 'polar') { + var polarModel = ecModel.queryComponents({ + mainType: 'polar', + index: seriesModel.get('polarIndex'), + id: seriesModel.get('polarId') + })[0]; + + if (__DEV__) { + if (!polarModel) { + throw new Error( + 'Polar "' + retrieve( + seriesModel.get('polarIndex'), + seriesModel.get('polarId'), + 0 + ) + '" not found' + ); + } + } + seriesModel.coordinateSystem = polarModel.coordinateSystem; + } + }); + + return polarList; + } +}; + +CoordinateSystemManager.register('polar', polarCreator); + +/* +* 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. +*/ + +var elementList$1 = ['axisLine', 'axisLabel', 'axisTick', 'minorTick', 'splitLine', 'minorSplitLine', 'splitArea']; + +function getAxisLineShape(polar, rExtent, angle) { + rExtent[1] > rExtent[0] && (rExtent = rExtent.slice().reverse()); + var start = polar.coordToPoint([rExtent[0], angle]); + var end = polar.coordToPoint([rExtent[1], angle]); + + return { + x1: start[0], + y1: start[1], + x2: end[0], + y2: end[1] + }; +} + +function getRadiusIdx(polar) { + var radiusAxis = polar.getRadiusAxis(); + return radiusAxis.inverse ? 0 : 1; +} + +// Remove the last tick which will overlap the first tick +function fixAngleOverlap(list) { + var firstItem = list[0]; + var lastItem = list[list.length - 1]; + if (firstItem + && lastItem + && Math.abs(Math.abs(firstItem.coord - lastItem.coord) - 360) < 1e-4 + ) { + list.pop(); + } +} + +AxisView.extend({ + + type: 'angleAxis', + + axisPointerClass: 'PolarAxisPointer', + + render: function (angleAxisModel, ecModel) { + this.group.removeAll(); + if (!angleAxisModel.get('show')) { + return; + } + + var angleAxis = angleAxisModel.axis; + var polar = angleAxis.polar; + var radiusExtent = polar.getRadiusAxis().getExtent(); + + var ticksAngles = angleAxis.getTicksCoords(); + var minorTickAngles = angleAxis.getMinorTicksCoords(); + + var labels = map(angleAxis.getViewLabels(), function (labelItem) { + var labelItem = clone(labelItem); + labelItem.coord = angleAxis.dataToCoord(labelItem.tickValue); + return labelItem; + }); + + fixAngleOverlap(labels); + fixAngleOverlap(ticksAngles); + + each$1(elementList$1, function (name) { + if (angleAxisModel.get(name + '.show') + && (!angleAxis.scale.isBlank() || name === 'axisLine') + ) { + this['_' + name](angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent, labels); + } + }, this); + }, + + /** + * @private + */ + _axisLine: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) { + var lineStyleModel = angleAxisModel.getModel('axisLine.lineStyle'); + + // extent id of the axis radius (r0 and r) + var rId = getRadiusIdx(polar); + var r0Id = rId ? 0 : 1; + + var shape; + if (radiusExtent[r0Id] === 0) { + shape = new Circle({ + shape: { + cx: polar.cx, + cy: polar.cy, + r: radiusExtent[rId] + }, + style: lineStyleModel.getLineStyle(), + z2: 1, + silent: true + }); + } + else { + shape = new Ring({ + shape: { + cx: polar.cx, + cy: polar.cy, + r: radiusExtent[rId], + r0: radiusExtent[r0Id] + }, + style: lineStyleModel.getLineStyle(), + z2: 1, + silent: true + }); + } + shape.style.fill = null; + this.group.add(shape); + }, + + /** + * @private + */ + _axisTick: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) { + var tickModel = angleAxisModel.getModel('axisTick'); + + var tickLen = (tickModel.get('inside') ? -1 : 1) * tickModel.get('length'); + var radius = radiusExtent[getRadiusIdx(polar)]; + + var lines = map(ticksAngles, function (tickAngleItem) { + return new Line({ + shape: getAxisLineShape(polar, [radius, radius + tickLen], tickAngleItem.coord) + }); + }); + this.group.add(mergePath( + lines, { + style: defaults( + tickModel.getModel('lineStyle').getLineStyle(), + { + stroke: angleAxisModel.get('axisLine.lineStyle.color') + } + ) + } + )); + }, + + /** + * @private + */ + _minorTick: function (angleAxisModel, polar, tickAngles, minorTickAngles, radiusExtent) { + if (!minorTickAngles.length) { + return; + } + + var tickModel = angleAxisModel.getModel('axisTick'); + var minorTickModel = angleAxisModel.getModel('minorTick'); + + var tickLen = (tickModel.get('inside') ? -1 : 1) * minorTickModel.get('length'); + var radius = radiusExtent[getRadiusIdx(polar)]; + + var lines = []; + + for (var i = 0; i < minorTickAngles.length; i++) { + for (var k = 0; k < minorTickAngles[i].length; k++) { + lines.push(new Line({ + shape: getAxisLineShape(polar, [radius, radius + tickLen], minorTickAngles[i][k].coord) + })); + } + } + + this.group.add(mergePath( + lines, { + style: defaults( + minorTickModel.getModel('lineStyle').getLineStyle(), + defaults( + tickModel.getLineStyle(), { + stroke: angleAxisModel.get('axisLine.lineStyle.color') + } + ) + ) + } + )); + }, + + /** + * @private + */ + _axisLabel: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent, labels) { + var rawCategoryData = angleAxisModel.getCategories(true); + + var commonLabelModel = angleAxisModel.getModel('axisLabel'); + + var labelMargin = commonLabelModel.get('margin'); + var triggerEvent = angleAxisModel.get('triggerEvent'); + + // Use length of ticksAngles because it may remove the last tick to avoid overlapping + each$1(labels, function (labelItem, idx) { + var labelModel = commonLabelModel; + var tickValue = labelItem.tickValue; + + var r = radiusExtent[getRadiusIdx(polar)]; + var p = polar.coordToPoint([r + labelMargin, labelItem.coord]); + var cx = polar.cx; + var cy = polar.cy; + + var labelTextAlign = Math.abs(p[0] - cx) / r < 0.3 + ? 'center' : (p[0] > cx ? 'left' : 'right'); + var labelTextVerticalAlign = Math.abs(p[1] - cy) / r < 0.3 + ? 'middle' : (p[1] > cy ? 'top' : 'bottom'); + + if (rawCategoryData && rawCategoryData[tickValue] && rawCategoryData[tickValue].textStyle) { + labelModel = new Model( + rawCategoryData[tickValue].textStyle, commonLabelModel, commonLabelModel.ecModel + ); + } + + var textEl = new Text({ + silent: AxisBuilder.isLabelSilent(angleAxisModel) + }); + this.group.add(textEl); + setTextStyle(textEl.style, labelModel, { + x: p[0], + y: p[1], + textFill: labelModel.getTextColor() || angleAxisModel.get('axisLine.lineStyle.color'), + text: labelItem.formattedLabel, + textAlign: labelTextAlign, + textVerticalAlign: labelTextVerticalAlign + }); + + // Pack data for mouse event + if (triggerEvent) { + textEl.eventData = AxisBuilder.makeAxisEventDataBase(angleAxisModel); + textEl.eventData.targetType = 'axisLabel'; + textEl.eventData.value = labelItem.rawLabel; + } + + }, this); + }, + + /** + * @private + */ + _splitLine: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) { + var splitLineModel = angleAxisModel.getModel('splitLine'); + var lineStyleModel = splitLineModel.getModel('lineStyle'); + var lineColors = lineStyleModel.get('color'); + var lineCount = 0; + + lineColors = lineColors instanceof Array ? lineColors : [lineColors]; + + var splitLines = []; + + for (var i = 0; i < ticksAngles.length; i++) { + var colorIndex = (lineCount++) % lineColors.length; + splitLines[colorIndex] = splitLines[colorIndex] || []; + splitLines[colorIndex].push(new Line({ + shape: getAxisLineShape(polar, radiusExtent, ticksAngles[i].coord) + })); + } + + // Simple optimization + // Batching the lines if color are the same + for (var i = 0; i < splitLines.length; i++) { + this.group.add(mergePath(splitLines[i], { + style: defaults({ + stroke: lineColors[i % lineColors.length] + }, lineStyleModel.getLineStyle()), + silent: true, + z: angleAxisModel.get('z') + })); + } + }, + + /** + * @private + */ + _minorSplitLine: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) { + if (!minorTickAngles.length) { + return; + } + + var minorSplitLineModel = angleAxisModel.getModel('minorSplitLine'); + var lineStyleModel = minorSplitLineModel.getModel('lineStyle'); + + var lines = []; + + for (var i = 0; i < minorTickAngles.length; i++) { + for (var k = 0; k < minorTickAngles[i].length; k++) { + lines.push(new Line({ + shape: getAxisLineShape(polar, radiusExtent, minorTickAngles[i][k].coord) + })); + } + } + + this.group.add(mergePath(lines, { + style: lineStyleModel.getLineStyle(), + silent: true, + z: angleAxisModel.get('z') + })); + }, + + /** + * @private + */ + _splitArea: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) { + if (!ticksAngles.length) { + return; + } + + var splitAreaModel = angleAxisModel.getModel('splitArea'); + var areaStyleModel = splitAreaModel.getModel('areaStyle'); + var areaColors = areaStyleModel.get('color'); + var lineCount = 0; + + areaColors = areaColors instanceof Array ? areaColors : [areaColors]; + + var splitAreas = []; + + var RADIAN = Math.PI / 180; + var prevAngle = -ticksAngles[0].coord * RADIAN; + var r0 = Math.min(radiusExtent[0], radiusExtent[1]); + var r1 = Math.max(radiusExtent[0], radiusExtent[1]); + + var clockwise = angleAxisModel.get('clockwise'); + + for (var i = 1; i < ticksAngles.length; i++) { + var colorIndex = (lineCount++) % areaColors.length; + splitAreas[colorIndex] = splitAreas[colorIndex] || []; + splitAreas[colorIndex].push(new Sector({ + shape: { + cx: polar.cx, + cy: polar.cy, + r0: r0, + r: r1, + startAngle: prevAngle, + endAngle: -ticksAngles[i].coord * RADIAN, + clockwise: clockwise + }, + silent: true + })); + prevAngle = -ticksAngles[i].coord * RADIAN; + } + + // Simple optimization + // Batching the lines if color are the same + for (var i = 0; i < splitAreas.length; i++) { + this.group.add(mergePath(splitAreas[i], { + style: defaults({ + fill: areaColors[i % areaColors.length] + }, areaStyleModel.getAreaStyle()), + silent: true + })); + } + } +}); + +/* +* 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. +*/ + +/* +* 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. +*/ + +var axisBuilderAttrs$3 = [ + 'axisLine', 'axisTickLabel', 'axisName' +]; +var selfBuilderAttrs$1 = [ + 'splitLine', 'splitArea', 'minorSplitLine' +]; + +AxisView.extend({ + + type: 'radiusAxis', + + axisPointerClass: 'PolarAxisPointer', + + render: function (radiusAxisModel, ecModel) { + this.group.removeAll(); + if (!radiusAxisModel.get('show')) { + return; + } + var radiusAxis = radiusAxisModel.axis; + var polar = radiusAxis.polar; + var angleAxis = polar.getAngleAxis(); + var ticksCoords = radiusAxis.getTicksCoords(); + var minorTicksCoords = radiusAxis.getMinorTicksCoords(); + var axisAngle = angleAxis.getExtent()[0]; + var radiusExtent = radiusAxis.getExtent(); + + var layout = layoutAxis(polar, radiusAxisModel, axisAngle); + var axisBuilder = new AxisBuilder(radiusAxisModel, layout); + each$1(axisBuilderAttrs$3, axisBuilder.add, axisBuilder); + this.group.add(axisBuilder.getGroup()); + + each$1(selfBuilderAttrs$1, function (name) { + if (radiusAxisModel.get(name + '.show') && !radiusAxis.scale.isBlank()) { + this['_' + name](radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords, minorTicksCoords); + } + }, this); + }, + + /** + * @private + */ + _splitLine: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords) { + var splitLineModel = radiusAxisModel.getModel('splitLine'); + var lineStyleModel = splitLineModel.getModel('lineStyle'); + var lineColors = lineStyleModel.get('color'); + var lineCount = 0; + + lineColors = lineColors instanceof Array ? lineColors : [lineColors]; + + var splitLines = []; + + for (var i = 0; i < ticksCoords.length; i++) { + var colorIndex = (lineCount++) % lineColors.length; + splitLines[colorIndex] = splitLines[colorIndex] || []; + splitLines[colorIndex].push(new Circle({ + shape: { + cx: polar.cx, + cy: polar.cy, + r: ticksCoords[i].coord + } + })); + } + + // Simple optimization + // Batching the lines if color are the same + for (var i = 0; i < splitLines.length; i++) { + this.group.add(mergePath(splitLines[i], { + style: defaults({ + stroke: lineColors[i % lineColors.length], + fill: null + }, lineStyleModel.getLineStyle()), + silent: true + })); + } + }, + + /** + * @private + */ + _minorSplitLine: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords, minorTicksCoords) { + if (!minorTicksCoords.length) { + return; + } + + var minorSplitLineModel = radiusAxisModel.getModel('minorSplitLine'); + var lineStyleModel = minorSplitLineModel.getModel('lineStyle'); + + var lines = []; + + for (var i = 0; i < minorTicksCoords.length; i++) { + for (var k = 0; k < minorTicksCoords[i].length; k++) { + lines.push(new Circle({ + shape: { + cx: polar.cx, + cy: polar.cy, + r: minorTicksCoords[i][k].coord + } + })); + } + } + + this.group.add(mergePath(lines, { + style: defaults({ + fill: null + }, lineStyleModel.getLineStyle()), + silent: true + })); + }, + + /** + * @private + */ + _splitArea: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords) { + if (!ticksCoords.length) { + return; + } + + var splitAreaModel = radiusAxisModel.getModel('splitArea'); + var areaStyleModel = splitAreaModel.getModel('areaStyle'); + var areaColors = areaStyleModel.get('color'); + var lineCount = 0; + + areaColors = areaColors instanceof Array ? areaColors : [areaColors]; + + var splitAreas = []; + + var prevRadius = ticksCoords[0].coord; + for (var i = 1; i < ticksCoords.length; i++) { + var colorIndex = (lineCount++) % areaColors.length; + splitAreas[colorIndex] = splitAreas[colorIndex] || []; + splitAreas[colorIndex].push(new Sector({ + shape: { + cx: polar.cx, + cy: polar.cy, + r0: prevRadius, + r: ticksCoords[i].coord, + startAngle: 0, + endAngle: Math.PI * 2 + }, + silent: true + })); + prevRadius = ticksCoords[i].coord; + } + + // Simple optimization + // Batching the lines if color are the same + for (var i = 0; i < splitAreas.length; i++) { + this.group.add(mergePath(splitAreas[i], { + style: defaults({ + fill: areaColors[i % areaColors.length] + }, areaStyleModel.getAreaStyle()), + silent: true + })); + } + } +}); + +/** + * @inner + */ +function layoutAxis(polar, radiusAxisModel, axisAngle) { + return { + position: [polar.cx, polar.cy], + rotation: axisAngle / 180 * Math.PI, + labelDirection: -1, + tickDirection: -1, + nameDirection: 1, + labelRotate: radiusAxisModel.getModel('axisLabel').get('rotate'), + // Over splitLine and splitArea + z2: 1 + }; +} + +/* +* 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. +*/ + +/* +* 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. +*/ + +var PolarAxisPointer = BaseAxisPointer.extend({ + + /** + * @override + */ + makeElOption: function (elOption, value, axisModel, axisPointerModel, api) { + var axis = axisModel.axis; + + if (axis.dim === 'angle') { + this.animationThreshold = Math.PI / 18; + } + + var polar = axis.polar; + var otherAxis = polar.getOtherAxis(axis); + var otherExtent = otherAxis.getExtent(); + + var coordValue; + coordValue = axis['dataTo' + capitalFirst(axis.dim)](value); + + var axisPointerType = axisPointerModel.get('type'); + if (axisPointerType && axisPointerType !== 'none') { + var elStyle = buildElStyle(axisPointerModel); + var pointerOption = pointerShapeBuilder$2[axisPointerType]( + axis, polar, coordValue, otherExtent, elStyle + ); + pointerOption.style = elStyle; + elOption.graphicKey = pointerOption.type; + elOption.pointer = pointerOption; + } + + var labelMargin = axisPointerModel.get('label.margin'); + var labelPos = getLabelPosition(value, axisModel, axisPointerModel, polar, labelMargin); + buildLabelElOption(elOption, axisModel, axisPointerModel, api, labelPos); + } + + // Do not support handle, utill any user requires it. + +}); + +function getLabelPosition(value, axisModel, axisPointerModel, polar, labelMargin) { + var axis = axisModel.axis; + var coord = axis.dataToCoord(value); + var axisAngle = polar.getAngleAxis().getExtent()[0]; + axisAngle = axisAngle / 180 * Math.PI; + var radiusExtent = polar.getRadiusAxis().getExtent(); + var position; + var align; + var verticalAlign; + + if (axis.dim === 'radius') { + var transform = create$1(); + rotate(transform, transform, axisAngle); + translate(transform, transform, [polar.cx, polar.cy]); + position = applyTransform$1([coord, -labelMargin], transform); + + var labelRotation = axisModel.getModel('axisLabel').get('rotate') || 0; + var labelLayout = AxisBuilder.innerTextLayout( + axisAngle, labelRotation * Math.PI / 180, -1 + ); + align = labelLayout.textAlign; + verticalAlign = labelLayout.textVerticalAlign; + } + else { // angle axis + var r = radiusExtent[1]; + position = polar.coordToPoint([r + labelMargin, coord]); + var cx = polar.cx; + var cy = polar.cy; + align = Math.abs(position[0] - cx) / r < 0.3 + ? 'center' : (position[0] > cx ? 'left' : 'right'); + verticalAlign = Math.abs(position[1] - cy) / r < 0.3 + ? 'middle' : (position[1] > cy ? 'top' : 'bottom'); + } + + return { + position: position, + align: align, + verticalAlign: verticalAlign + }; +} + + +var pointerShapeBuilder$2 = { + + line: function (axis, polar, coordValue, otherExtent, elStyle) { + return axis.dim === 'angle' + ? { + type: 'Line', + shape: makeLineShape( + polar.coordToPoint([otherExtent[0], coordValue]), + polar.coordToPoint([otherExtent[1], coordValue]) + ) + } + : { + type: 'Circle', + shape: { + cx: polar.cx, + cy: polar.cy, + r: coordValue + } + }; + }, + + shadow: function (axis, polar, coordValue, otherExtent, elStyle) { + var bandWidth = Math.max(1, axis.getBandWidth()); + var radian = Math.PI / 180; + + return axis.dim === 'angle' + ? { + type: 'Sector', + shape: makeSectorShape( + polar.cx, polar.cy, + otherExtent[0], otherExtent[1], + // In ECharts y is negative if angle is positive + (-coordValue - bandWidth / 2) * radian, + (-coordValue + bandWidth / 2) * radian + ) + } + : { + type: 'Sector', + shape: makeSectorShape( + polar.cx, polar.cy, + coordValue - bandWidth / 2, + coordValue + bandWidth / 2, + 0, Math.PI * 2 + ) + }; + } +}; + +AxisView.registerAxisPointerClass('PolarAxisPointer', PolarAxisPointer); + +/* +* 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. +*/ + +// For reducing size of echarts.min, barLayoutPolar is required by polar. +registerLayout(curry(barLayoutPolar, 'bar')); + +// Polar view +extendComponentView({ + type: 'polar' +}); + +/* +* 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. +*/ + +var GeoModel = ComponentModel.extend({ + + type: 'geo', + + /** + * @type {module:echarts/coord/geo/Geo} + */ + coordinateSystem: null, + + layoutMode: 'box', + + init: function (option) { + ComponentModel.prototype.init.apply(this, arguments); + + // Default label emphasis `show` + defaultEmphasis(option, 'label', ['show']); + }, + + optionUpdated: function () { + var option = this.option; + var self = this; + + option.regions = geoCreator.getFilledRegions(option.regions, option.map, option.nameMap); + + this._optionModelMap = reduce(option.regions || [], function (optionModelMap, regionOpt) { + if (regionOpt.name) { + optionModelMap.set(regionOpt.name, new Model(regionOpt, self)); + } + return optionModelMap; + }, createHashMap()); + + this.updateSelectedMap(option.regions); + }, + + defaultOption: { + + zlevel: 0, + + z: 0, + + show: true, + + left: 'center', + + top: 'center', + + + // width:, + // height:, + // right + // bottom + + // Aspect is width / height. Inited to be geoJson bbox aspect + // This parameter is used for scale this aspect + // 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 + // This two properties may more conveninet + // layoutCenter: [50%, 50%] + // layoutSize: 100 + + silent: false, + + // Map type + map: '', + + // Define left-top, right-bottom coords to control view + // For example, [ [180, 90], [-180, -90] ] + boundingCoords: null, + + // Default on center of map + center: null, + + zoom: 1, + + scaleLimit: null, + + // selectedMode: false + + label: { + show: false, + color: '#000' + }, + + itemStyle: { + // color: 各异, + borderWidth: 0.5, + borderColor: '#444', + color: '#eee' + }, + + emphasis: { + label: { + show: true, + color: 'rgb(100,0,0)' + }, + itemStyle: { + color: 'rgba(255,215,0,0.8)' + } + }, + + regions: [] + }, + + /** + * Get model of region + * @param {string} name + * @return {module:echarts/model/Model} + */ + getRegionModel: function (name) { + return this._optionModelMap.get(name) || new Model(null, this, this.ecModel); + }, + + /** + * Format label + * @param {string} name Region name + * @param {string} [status='normal'] 'normal' or 'emphasis' + * @return {string} + */ + getFormattedLabel: function (name, status) { + var regionModel = this.getRegionModel(name); + var formatter = regionModel.get( + 'label' + + (status === 'normal' ? '.' : status + '.') + + 'formatter' + ); + var params = { + name: name + }; + if (typeof formatter === 'function') { + params.status = status; + return formatter(params); + } + else if (typeof formatter === 'string') { + return formatter.replace('{a}', name != null ? name : ''); + } + }, + + setZoom: function (zoom) { + this.option.zoom = zoom; + }, + + setCenter: function (center) { + this.option.center = center; + } +}); + +mixin(GeoModel, selectableMixin); + +/* +* 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. +*/ + +extendComponentView({ + + type: 'geo', + + init: function (ecModel, api) { + var mapDraw = new MapDraw(api, true); + this._mapDraw = mapDraw; + + this.group.add(mapDraw.group); + }, + + render: function (geoModel, ecModel, api, payload) { + // Not render if it is an toggleSelect action from self + if (payload && payload.type === 'geoToggleSelect' + && payload.from === this.uid + ) { + return; + } + + var mapDraw = this._mapDraw; + if (geoModel.get('show')) { + mapDraw.draw(geoModel, ecModel, api, this, payload); + } + else { + this._mapDraw.group.removeAll(); + } + + this.group.silent = geoModel.get('silent'); + }, + + dispose: function () { + this._mapDraw && this._mapDraw.remove(); + } + +}); + +/* +* 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. +*/ + +function makeAction(method, actionInfo) { + actionInfo.update = 'updateView'; + registerAction(actionInfo, function (payload, ecModel) { + var selected = {}; + + ecModel.eachComponent( + { mainType: 'geo', query: payload}, + function (geoModel) { + geoModel[method](payload.name); + var geo = geoModel.coordinateSystem; + each$1(geo.regions, function (region) { + selected[region.name] = geoModel.isSelected(region.name) || false; + }); + } + ); + + return { + selected: selected, + name: payload.name + }; + }); +} + +makeAction('toggleSelected', { + type: 'geoToggleSelect', + event: 'geoselectchanged' +}); +makeAction('select', { + type: 'geoSelect', + event: 'geoselected' +}); +makeAction('unSelect', { + type: 'geoUnSelect', + event: 'geounselected' +}); + +/* +* 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. +*/ + +// (24*60*60*1000) +var PROXIMATE_ONE_DAY = 86400000; + +/** + * Calendar + * + * @constructor + * + * @param {Object} calendarModel calendarModel + * @param {Object} ecModel ecModel + * @param {Object} api api + */ +function Calendar(calendarModel, ecModel, api) { + this._model = calendarModel; +} + +Calendar.prototype = { + + constructor: Calendar, + + type: 'calendar', + + dimensions: ['time', 'value'], + + // Required in createListFromData + getDimensionsInfo: function () { + return [{name: 'time', type: 'time'}, 'value']; + }, + + getRangeInfo: function () { + return this._rangeInfo; + }, + + getModel: function () { + return this._model; + }, + + getRect: function () { + return this._rect; + }, + + getCellWidth: function () { + return this._sw; + }, + + getCellHeight: function () { + return this._sh; + }, + + getOrient: function () { + return this._orient; + }, + + /** + * getFirstDayOfWeek + * + * @example + * 0 : start at Sunday + * 1 : start at Monday + * + * @return {number} + */ + getFirstDayOfWeek: function () { + return this._firstDayOfWeek; + }, + + /** + * get date info + * + * @param {string|number} date date + * @return {Object} + * { + * y: string, local full year, eg., '1940', + * m: string, local month, from '01' ot '12', + * d: string, local date, from '01' to '31' (if exists), + * day: It is not date.getDay(). It is the location of the cell in a week, from 0 to 6, + * time: timestamp, + * formatedDate: string, yyyy-MM-dd, + * date: original date object. + * } + */ + getDateInfo: function (date) { + + date = parseDate(date); + + var y = date.getFullYear(); + + var m = date.getMonth() + 1; + m = m < 10 ? '0' + m : m; + + var d = date.getDate(); + d = d < 10 ? '0' + d : d; + + var day = date.getDay(); + + day = Math.abs((day + 7 - this.getFirstDayOfWeek()) % 7); + + return { + y: y, + m: m, + d: d, + day: day, + time: date.getTime(), + formatedDate: y + '-' + m + '-' + d, + date: date + }; + }, + + getNextNDay: function (date, n) { + n = n || 0; + if (n === 0) { + return this.getDateInfo(date); + } + + date = new Date(this.getDateInfo(date).time); + date.setDate(date.getDate() + n); + + return this.getDateInfo(date); + }, + + update: function (ecModel, api) { + + this._firstDayOfWeek = +this._model.getModel('dayLabel').get('firstDay'); + this._orient = this._model.get('orient'); + this._lineWidth = this._model.getModel('itemStyle').getItemStyle().lineWidth || 0; + + + this._rangeInfo = this._getRangeInfo(this._initRangeOption()); + var weeks = this._rangeInfo.weeks || 1; + var whNames = ['width', 'height']; + var cellSize = this._model.get('cellSize').slice(); + var layoutParams = this._model.getBoxLayoutParams(); + var cellNumbers = this._orient === 'horizontal' ? [weeks, 7] : [7, weeks]; + + each$1([0, 1], function (idx) { + if (cellSizeSpecified(cellSize, idx)) { + layoutParams[whNames[idx]] = cellSize[idx] * cellNumbers[idx]; + } + }); + + var whGlobal = { + width: api.getWidth(), + height: api.getHeight() + }; + var calendarRect = this._rect = getLayoutRect(layoutParams, whGlobal); + + each$1([0, 1], function (idx) { + if (!cellSizeSpecified(cellSize, idx)) { + cellSize[idx] = calendarRect[whNames[idx]] / cellNumbers[idx]; + } + }); + + function cellSizeSpecified(cellSize, idx) { + return cellSize[idx] != null && cellSize[idx] !== 'auto'; + } + + this._sw = cellSize[0]; + this._sh = cellSize[1]; + }, + + + /** + * Convert a time data(time, value) item to (x, y) point. + * + * @override + * @param {Array|number} data data + * @param {boolean} [clamp=true] out of range + * @return {Array} point + */ + dataToPoint: function (data, clamp) { + isArray(data) && (data = data[0]); + clamp == null && (clamp = true); + + var dayInfo = this.getDateInfo(data); + var range = this._rangeInfo; + var date = dayInfo.formatedDate; + + // if not in range return [NaN, NaN] + if (clamp && !( + dayInfo.time >= range.start.time + && dayInfo.time < range.end.time + PROXIMATE_ONE_DAY + )) { + return [NaN, NaN]; + } + + var week = dayInfo.day; + var nthWeek = this._getRangeInfo([range.start.time, date]).nthWeek; + + if (this._orient === 'vertical') { + return [ + this._rect.x + week * this._sw + this._sw / 2, + this._rect.y + nthWeek * this._sh + this._sh / 2 + ]; + + } + + return [ + this._rect.x + nthWeek * this._sw + this._sw / 2, + this._rect.y + week * this._sh + this._sh / 2 + ]; + + }, + + /** + * Convert a (x, y) point to time data + * + * @override + * @param {string} point point + * @return {string} data + */ + pointToData: function (point) { + + var date = this.pointToDate(point); + + return date && date.time; + }, + + /** + * Convert a time date item to (x, y) four point. + * + * @param {Array} data date[0] is date + * @param {boolean} [clamp=true] out of range + * @return {Object} point + */ + dataToRect: function (data, clamp) { + var point = this.dataToPoint(data, clamp); + + return { + contentShape: { + x: point[0] - (this._sw - this._lineWidth) / 2, + y: point[1] - (this._sh - this._lineWidth) / 2, + width: this._sw - this._lineWidth, + height: this._sh - this._lineWidth + }, + + center: point, + + tl: [ + point[0] - this._sw / 2, + point[1] - this._sh / 2 + ], + + tr: [ + point[0] + this._sw / 2, + point[1] - this._sh / 2 + ], + + br: [ + point[0] + this._sw / 2, + point[1] + this._sh / 2 + ], + + bl: [ + point[0] - this._sw / 2, + point[1] + this._sh / 2 + ] + + }; + }, + + /** + * Convert a (x, y) point to time date + * + * @param {Array} point point + * @return {Object} date + */ + pointToDate: function (point) { + var nthX = Math.floor((point[0] - this._rect.x) / this._sw) + 1; + var nthY = Math.floor((point[1] - this._rect.y) / this._sh) + 1; + var range = this._rangeInfo.range; + + if (this._orient === 'vertical') { + return this._getDateByWeeksAndDay(nthY, nthX - 1, range); + } + + return this._getDateByWeeksAndDay(nthX, nthY - 1, range); + }, + + /** + * @inheritDoc + */ + convertToPixel: curry(doConvert$2, 'dataToPoint'), + + /** + * @inheritDoc + */ + convertFromPixel: curry(doConvert$2, 'pointToData'), + + /** + * initRange + * + * @private + * @return {Array} [start, end] + */ + _initRangeOption: function () { + var range = this._model.get('range'); + + var rg = range; + + if (isArray(rg) && rg.length === 1) { + rg = rg[0]; + } + + if (/^\d{4}$/.test(rg)) { + range = [rg + '-01-01', rg + '-12-31']; + } + + if (/^\d{4}[\/|-]\d{1,2}$/.test(rg)) { + + var start = this.getDateInfo(rg); + var firstDay = start.date; + firstDay.setMonth(firstDay.getMonth() + 1); + + var end = this.getNextNDay(firstDay, -1); + range = [start.formatedDate, end.formatedDate]; + } + + if (/^\d{4}[\/|-]\d{1,2}[\/|-]\d{1,2}$/.test(rg)) { + range = [rg, rg]; + } + + var tmp = this._getRangeInfo(range); + + if (tmp.start.time > tmp.end.time) { + range.reverse(); + } + + return range; + }, + + /** + * range info + * + * @private + * @param {Array} range range ['2017-01-01', '2017-07-08'] + * If range[0] > range[1], they will not be reversed. + * @return {Object} obj + */ + _getRangeInfo: function (range) { + range = [ + this.getDateInfo(range[0]), + this.getDateInfo(range[1]) + ]; + + var reversed; + if (range[0].time > range[1].time) { + reversed = true; + range.reverse(); + } + + var allDay = Math.floor(range[1].time / PROXIMATE_ONE_DAY) + - Math.floor(range[0].time / PROXIMATE_ONE_DAY) + 1; + + // Consider case: + // Firstly set system timezone as "Time Zone: America/Toronto", + // ``` + // var first = new Date(1478412000000 - 3600 * 1000 * 2.5); + // var second = new Date(1478412000000); + // var allDays = Math.floor(second / ONE_DAY) - Math.floor(first / ONE_DAY) + 1; + // ``` + // will get wrong result because of DST. So we should fix it. + var date = new Date(range[0].time); + var startDateNum = date.getDate(); + var endDateNum = range[1].date.getDate(); + date.setDate(startDateNum + allDay - 1); + // The bias can not over a month, so just compare date. + if (date.getDate() !== endDateNum) { + var sign = date.getTime() - range[1].time > 0 ? 1 : -1; + while (date.getDate() !== endDateNum && (date.getTime() - range[1].time) * sign > 0) { + allDay -= sign; + date.setDate(startDateNum + allDay - 1); + } + } + + var weeks = Math.floor((allDay + range[0].day + 6) / 7); + var nthWeek = reversed ? -weeks + 1 : weeks - 1; + + reversed && range.reverse(); + + return { + range: [range[0].formatedDate, range[1].formatedDate], + start: range[0], + end: range[1], + allDay: allDay, + weeks: weeks, + // From 0. + nthWeek: nthWeek, + fweek: range[0].day, + lweek: range[1].day + }; + }, + + /** + * get date by nthWeeks and week day in range + * + * @private + * @param {number} nthWeek the week + * @param {number} day the week day + * @param {Array} range [d1, d2] + * @return {Object} + */ + _getDateByWeeksAndDay: function (nthWeek, day, range) { + var rangeInfo = this._getRangeInfo(range); + + if (nthWeek > rangeInfo.weeks + || (nthWeek === 0 && day < rangeInfo.fweek) + || (nthWeek === rangeInfo.weeks && day > rangeInfo.lweek) + ) { + return false; + } + + var nthDay = (nthWeek - 1) * 7 - rangeInfo.fweek + day; + var date = new Date(rangeInfo.start.time); + date.setDate(rangeInfo.start.d + nthDay); + + return this.getDateInfo(date); + } +}; + +Calendar.dimensions = Calendar.prototype.dimensions; + +Calendar.getDimensionsInfo = Calendar.prototype.getDimensionsInfo; + +Calendar.create = function (ecModel, api) { + var calendarList = []; + + ecModel.eachComponent('calendar', function (calendarModel) { + var calendar = new Calendar(calendarModel, ecModel, api); + calendarList.push(calendar); + calendarModel.coordinateSystem = calendar; + }); + + ecModel.eachSeries(function (calendarSeries) { + if (calendarSeries.get('coordinateSystem') === 'calendar') { + // Inject coordinate system + calendarSeries.coordinateSystem = calendarList[calendarSeries.get('calendarIndex') || 0]; + } + }); + return calendarList; +}; + +function doConvert$2(methodName, ecModel, finder, value) { + var calendarModel = finder.calendarModel; + var seriesModel = finder.seriesModel; + + var coordSys = calendarModel + ? calendarModel.coordinateSystem + : seriesModel + ? seriesModel.coordinateSystem + : null; + + return coordSys === this ? coordSys[methodName](value) : null; +} + +CoordinateSystemManager.register('calendar', Calendar); + +/* +* 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. +*/ + +var CalendarModel = ComponentModel.extend({ + + type: 'calendar', + + /** + * @type {module:echarts/coord/calendar/Calendar} + */ + coordinateSystem: null, + + defaultOption: { + zlevel: 0, + z: 2, + left: 80, + top: 60, + + cellSize: 20, + + // horizontal vertical + orient: 'horizontal', + + // month separate line style + splitLine: { + show: true, + lineStyle: { + color: '#000', + width: 1, + type: 'solid' + } + }, + + // rect style temporarily unused emphasis + itemStyle: { + color: '#fff', + borderWidth: 1, + borderColor: '#ccc' + }, + + // week text style + dayLabel: { + show: true, + + // a week first day + firstDay: 0, + + // start end + position: 'start', + margin: '50%', // 50% of cellSize + nameMap: 'en', + color: '#000' + }, + + // month text style + monthLabel: { + show: true, + + // start end + position: 'start', + margin: 5, + + // center or left + align: 'center', + + // cn en [] + nameMap: 'en', + formatter: null, + color: '#000' + }, + + // year text style + yearLabel: { + show: true, + + // top bottom left right + position: null, + margin: 30, + formatter: null, + color: '#ccc', + fontFamily: 'sans-serif', + fontWeight: 'bolder', + fontSize: 20 + } + }, + + /** + * @override + */ + init: function (option, parentModel, ecModel, extraOpt) { + var inputPositionParams = getLayoutParams(option); + + CalendarModel.superApply(this, 'init', arguments); + + mergeAndNormalizeLayoutParams(option, inputPositionParams); + }, + + /** + * @override + */ + mergeOption: function (option, extraOpt) { + CalendarModel.superApply(this, 'mergeOption', arguments); + + mergeAndNormalizeLayoutParams(this.option, option); + } +}); + +function mergeAndNormalizeLayoutParams(target, raw) { + // Normalize cellSize + var cellSize = target.cellSize; + + if (!isArray(cellSize)) { + cellSize = target.cellSize = [cellSize, cellSize]; + } + else if (cellSize.length === 1) { + cellSize[1] = cellSize[0]; + } + + var ignoreSize = map([0, 1], function (hvIdx) { + // If user have set `width` or both `left` and `right`, cellSize + // will be automatically set to 'auto', otherwise the default + // setting of cellSize will make `width` setting not work. + if (sizeCalculable(raw, hvIdx)) { + cellSize[hvIdx] = 'auto'; + } + return cellSize[hvIdx] != null && cellSize[hvIdx] !== 'auto'; + }); + + mergeLayoutParam(target, raw, { + type: 'box', ignoreSize: ignoreSize + }); +} + +/* +* 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. +*/ + +var MONTH_TEXT = { + EN: [ + 'Jan', 'Feb', 'Mar', + 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec' + ], + CN: [ + '一月', '二月', '三月', + '四月', '五月', '六月', + '七月', '八月', '九月', + '十月', '十一月', '十二月' + ] +}; + +var WEEK_TEXT = { + EN: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + CN: ['日', '一', '二', '三', '四', '五', '六'] +}; + +extendComponentView({ + + type: 'calendar', + + /** + * top/left line points + * @private + */ + _tlpoints: null, + + /** + * bottom/right line points + * @private + */ + _blpoints: null, + + /** + * first day of month + * @private + */ + _firstDayOfMonth: null, + + /** + * first day point of month + * @private + */ + _firstDayPoints: null, + + render: function (calendarModel, ecModel, api) { + + var group = this.group; + + group.removeAll(); + + var coordSys = calendarModel.coordinateSystem; + + // range info + var rangeData = coordSys.getRangeInfo(); + var orient = coordSys.getOrient(); + + this._renderDayRect(calendarModel, rangeData, group); + + // _renderLines must be called prior to following function + this._renderLines(calendarModel, rangeData, orient, group); + + this._renderYearText(calendarModel, rangeData, orient, group); + + this._renderMonthText(calendarModel, orient, group); + + this._renderWeekText(calendarModel, rangeData, orient, group); + }, + + // render day rect + _renderDayRect: function (calendarModel, rangeData, group) { + var coordSys = calendarModel.coordinateSystem; + var itemRectStyleModel = calendarModel.getModel('itemStyle').getItemStyle(); + var sw = coordSys.getCellWidth(); + var sh = coordSys.getCellHeight(); + + for (var i = rangeData.start.time; + i <= rangeData.end.time; + i = coordSys.getNextNDay(i, 1).time + ) { + + var point = coordSys.dataToRect([i], false).tl; + + // every rect + var rect = new Rect({ + shape: { + x: point[0], + y: point[1], + width: sw, + height: sh + }, + cursor: 'default', + style: itemRectStyleModel + }); + + group.add(rect); + } + + }, + + // render separate line + _renderLines: function (calendarModel, rangeData, orient, group) { + + var self = this; + + var coordSys = calendarModel.coordinateSystem; + + var lineStyleModel = calendarModel.getModel('splitLine.lineStyle').getLineStyle(); + var show = calendarModel.get('splitLine.show'); + + var lineWidth = lineStyleModel.lineWidth; + + this._tlpoints = []; + this._blpoints = []; + this._firstDayOfMonth = []; + this._firstDayPoints = []; + + + var firstDay = rangeData.start; + + for (var i = 0; firstDay.time <= rangeData.end.time; i++) { + addPoints(firstDay.formatedDate); + + if (i === 0) { + firstDay = coordSys.getDateInfo(rangeData.start.y + '-' + rangeData.start.m); + } + + var date = firstDay.date; + date.setMonth(date.getMonth() + 1); + firstDay = coordSys.getDateInfo(date); + } + + addPoints(coordSys.getNextNDay(rangeData.end.time, 1).formatedDate); + + function addPoints(date) { + + self._firstDayOfMonth.push(coordSys.getDateInfo(date)); + self._firstDayPoints.push(coordSys.dataToRect([date], false).tl); + + var points = self._getLinePointsOfOneWeek(calendarModel, date, orient); + + self._tlpoints.push(points[0]); + self._blpoints.push(points[points.length - 1]); + + show && self._drawSplitline(points, lineStyleModel, group); + } + + + // render top/left line + show && this._drawSplitline(self._getEdgesPoints(self._tlpoints, lineWidth, orient), lineStyleModel, group); + + // render bottom/right line + show && this._drawSplitline(self._getEdgesPoints(self._blpoints, lineWidth, orient), lineStyleModel, group); + + }, + + // get points at both ends + _getEdgesPoints: function (points, lineWidth, orient) { + var rs = [points[0].slice(), points[points.length - 1].slice()]; + var idx = orient === 'horizontal' ? 0 : 1; + + // both ends of the line are extend half lineWidth + rs[0][idx] = rs[0][idx] - lineWidth / 2; + rs[1][idx] = rs[1][idx] + lineWidth / 2; + + return rs; + }, + + // render split line + _drawSplitline: function (points, lineStyleModel, group) { + + var poyline = new Polyline({ + z2: 20, + shape: { + points: points + }, + style: lineStyleModel + }); + + group.add(poyline); + }, + + // render month line of one week points + _getLinePointsOfOneWeek: function (calendarModel, date, orient) { + + var coordSys = calendarModel.coordinateSystem; + date = coordSys.getDateInfo(date); + + var points = []; + + for (var i = 0; i < 7; i++) { + + var tmpD = coordSys.getNextNDay(date.time, i); + var point = coordSys.dataToRect([tmpD.time], false); + + points[2 * tmpD.day] = point.tl; + points[2 * tmpD.day + 1] = point[orient === 'horizontal' ? 'bl' : 'tr']; + } + + return points; + + }, + + _formatterLabel: function (formatter, params) { + + if (typeof formatter === 'string' && formatter) { + return formatTplSimple(formatter, params); + } + + if (typeof formatter === 'function') { + return formatter(params); + } + + return params.nameMap; + + }, + + _yearTextPositionControl: function (textEl, point, orient, position, margin) { + + point = point.slice(); + var aligns = ['center', 'bottom']; + + if (position === 'bottom') { + point[1] += margin; + aligns = ['center', 'top']; + } + else if (position === 'left') { + point[0] -= margin; + } + else if (position === 'right') { + point[0] += margin; + aligns = ['center', 'top']; + } + else { // top + point[1] -= margin; + } + + var rotate = 0; + if (position === 'left' || position === 'right') { + rotate = Math.PI / 2; + } + + return { + rotation: rotate, + position: point, + style: { + textAlign: aligns[0], + textVerticalAlign: aligns[1] + } + }; + }, + + // render year + _renderYearText: function (calendarModel, rangeData, orient, group) { + var yearLabel = calendarModel.getModel('yearLabel'); + + if (!yearLabel.get('show')) { + return; + } + + var margin = yearLabel.get('margin'); + var pos = yearLabel.get('position'); + + if (!pos) { + pos = orient !== 'horizontal' ? 'top' : 'left'; + } + + var points = [this._tlpoints[this._tlpoints.length - 1], this._blpoints[0]]; + var xc = (points[0][0] + points[1][0]) / 2; + var yc = (points[0][1] + points[1][1]) / 2; + + var idx = orient === 'horizontal' ? 0 : 1; + + var posPoints = { + top: [xc, points[idx][1]], + bottom: [xc, points[1 - idx][1]], + left: [points[1 - idx][0], yc], + right: [points[idx][0], yc] + }; + + var name = rangeData.start.y; + + if (+rangeData.end.y > +rangeData.start.y) { + name = name + '-' + rangeData.end.y; + } + + var formatter = yearLabel.get('formatter'); + + var params = { + start: rangeData.start.y, + end: rangeData.end.y, + nameMap: name + }; + + var content = this._formatterLabel(formatter, params); + + var yearText = new Text({z2: 30}); + setTextStyle(yearText.style, yearLabel, {text: content}), + yearText.attr(this._yearTextPositionControl(yearText, posPoints[pos], orient, pos, margin)); + + group.add(yearText); + }, + + _monthTextPositionControl: function (point, isCenter, orient, position, margin) { + var align = 'left'; + var vAlign = 'top'; + var x = point[0]; + var y = point[1]; + + if (orient === 'horizontal') { + y = y + margin; + + if (isCenter) { + align = 'center'; + } + + if (position === 'start') { + vAlign = 'bottom'; + } + } + else { + x = x + margin; + + if (isCenter) { + vAlign = 'middle'; + } + + if (position === 'start') { + align = 'right'; + } + } + + return { + x: x, + y: y, + textAlign: align, + textVerticalAlign: vAlign + }; + }, + + // render month and year text + _renderMonthText: function (calendarModel, orient, group) { + var monthLabel = calendarModel.getModel('monthLabel'); + + if (!monthLabel.get('show')) { + return; + } + + var nameMap = monthLabel.get('nameMap'); + var margin = monthLabel.get('margin'); + var pos = monthLabel.get('position'); + var align = monthLabel.get('align'); + + var termPoints = [this._tlpoints, this._blpoints]; + + if (isString(nameMap)) { + nameMap = MONTH_TEXT[nameMap.toUpperCase()] || []; + } + + var idx = pos === 'start' ? 0 : 1; + var axis = orient === 'horizontal' ? 0 : 1; + margin = pos === 'start' ? -margin : margin; + var isCenter = (align === 'center'); + + for (var i = 0; i < termPoints[idx].length - 1; i++) { + + var tmp = termPoints[idx][i].slice(); + var firstDay = this._firstDayOfMonth[i]; + + if (isCenter) { + var firstDayPoints = this._firstDayPoints[i]; + tmp[axis] = (firstDayPoints[axis] + termPoints[0][i + 1][axis]) / 2; + } + + var formatter = monthLabel.get('formatter'); + var name = nameMap[+firstDay.m - 1]; + var params = { + yyyy: firstDay.y, + yy: (firstDay.y + '').slice(2), + MM: firstDay.m, + M: +firstDay.m, + nameMap: name + }; + + var content = this._formatterLabel(formatter, params); + + var monthText = new Text({z2: 30}); + extend( + setTextStyle(monthText.style, monthLabel, {text: content}), + this._monthTextPositionControl(tmp, isCenter, orient, pos, margin) + ); + + group.add(monthText); + } + }, + + _weekTextPositionControl: function (point, orient, position, margin, cellSize) { + var align = 'center'; + var vAlign = 'middle'; + var x = point[0]; + var y = point[1]; + var isStart = position === 'start'; + + if (orient === 'horizontal') { + x = x + margin + (isStart ? 1 : -1) * cellSize[0] / 2; + align = isStart ? 'right' : 'left'; + } + else { + y = y + margin + (isStart ? 1 : -1) * cellSize[1] / 2; + vAlign = isStart ? 'bottom' : 'top'; + } + + return { + x: x, + y: y, + textAlign: align, + textVerticalAlign: vAlign + }; + }, + + // render weeks + _renderWeekText: function (calendarModel, rangeData, orient, group) { + var dayLabel = calendarModel.getModel('dayLabel'); + + if (!dayLabel.get('show')) { + return; + } + + var coordSys = calendarModel.coordinateSystem; + var pos = dayLabel.get('position'); + var nameMap = dayLabel.get('nameMap'); + var margin = dayLabel.get('margin'); + var firstDayOfWeek = coordSys.getFirstDayOfWeek(); + + if (isString(nameMap)) { + nameMap = WEEK_TEXT[nameMap.toUpperCase()] || []; + } + + var start = coordSys.getNextNDay( + rangeData.end.time, (7 - rangeData.lweek) + ).time; + + var cellSize = [coordSys.getCellWidth(), coordSys.getCellHeight()]; + margin = parsePercent$1(margin, cellSize[orient === 'horizontal' ? 0 : 1]); + + if (pos === 'start') { + start = coordSys.getNextNDay( + rangeData.start.time, -(7 + rangeData.fweek) + ).time; + margin = -margin; + } + + for (var i = 0; i < 7; i++) { + + var tmpD = coordSys.getNextNDay(start, i); + var point = coordSys.dataToRect([tmpD.time], false).center; + var day = i; + day = Math.abs((i + firstDayOfWeek) % 7); + var weekText = new Text({z2: 30}); + + extend( + setTextStyle(weekText.style, dayLabel, {text: nameMap[day]}), + this._weekTextPositionControl(point, orient, pos, margin, cellSize) + ); + group.add(weekText); + } + } +}); + +/* +* 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. +*/ + +/* +* 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. +*/ + +var _nonShapeGraphicElements = { + + // Reserved but not supported in graphic component. + path: null, + compoundPath: null, + + // Supported in graphic component. + group: Group, + image: ZImage, + text: Text +}; + +// ------------- +// Preprocessor +// ------------- + +registerPreprocessor(function (option) { + var graphicOption = option.graphic; + + // Convert + // {graphic: [{left: 10, type: 'circle'}, ...]} + // or + // {graphic: {left: 10, type: 'circle'}} + // to + // {graphic: [{elements: [{left: 10, type: 'circle'}, ...]}]} + if (isArray(graphicOption)) { + if (!graphicOption[0] || !graphicOption[0].elements) { + option.graphic = [{elements: graphicOption}]; + } + else { + // Only one graphic instance can be instantiated. (We dont + // want that too many views are created in echarts._viewMap) + option.graphic = [option.graphic[0]]; + } + } + else if (graphicOption && !graphicOption.elements) { + option.graphic = [{elements: [graphicOption]}]; + } +}); + +// ------ +// Model +// ------ + +var GraphicModel = extendComponentModel({ + + type: 'graphic', + + defaultOption: { + + // Extra properties for each elements: + // + // left/right/top/bottom: (like 12, '22%', 'center', default undefined) + // If left/rigth is set, shape.x/shape.cx/position will not be used. + // If top/bottom is set, shape.y/shape.cy/position will not be used. + // This mechanism is useful when you want to position a group/element + // against the right side or the center of this container. + // + // width/height: (can only be pixel value, default 0) + // Only be used to specify contianer(group) size, if needed. And + // can not be percentage value (like '33%'). See the reason in the + // layout algorithm below. + // + // bounding: (enum: 'all' (default) | 'raw') + // Specify how to calculate boundingRect when locating. + // 'all': Get uioned and transformed boundingRect + // from both itself and its descendants. + // This mode simplies confining a group of elements in the bounding + // of their ancester container (e.g., using 'right: 0'). + // 'raw': Only use the boundingRect of itself and before transformed. + // This mode is similar to css behavior, which is useful when you + // want an element to be able to overflow its container. (Consider + // a rotated circle needs to be located in a corner.) + // info: custom info. enables user to mount some info on elements and use them + // in event handlers. Update them only when user specified, otherwise, remain. + + // Note: elements is always behind its ancestors in this elements array. + elements: [], + parentId: null + }, + + /** + * Save el options for the sake of the performance (only update modified graphics). + * The order is the same as those in option. (ancesters -> descendants) + * + * @private + * @type {Array.} + */ + _elOptionsToUpdate: null, + + /** + * @override + */ + mergeOption: function (option) { + // Prevent default merge to elements + var elements = this.option.elements; + this.option.elements = null; + + GraphicModel.superApply(this, 'mergeOption', arguments); + + this.option.elements = elements; + }, + + /** + * @override + */ + optionUpdated: function (newOption, isInit) { + var thisOption = this.option; + var newList = (isInit ? thisOption : newOption).elements; + var existList = thisOption.elements = isInit ? [] : thisOption.elements; + + var flattenedList = []; + this._flatten(newList, flattenedList); + + var mappingResult = mappingToExists(existList, flattenedList); + makeIdAndName(mappingResult); + + // Clear elOptionsToUpdate + var elOptionsToUpdate = this._elOptionsToUpdate = []; + + each$1(mappingResult, function (resultItem, index) { + var newElOption = resultItem.option; + + if (__DEV__) { + assert$1( + isObject$1(newElOption) || resultItem.exist, + 'Empty graphic option definition' + ); + } + + if (!newElOption) { + return; + } + + elOptionsToUpdate.push(newElOption); + + setKeyInfoToNewElOption(resultItem, newElOption); + + mergeNewElOptionToExist(existList, index, newElOption); + + setLayoutInfoToExist(existList[index], newElOption); + + }, this); + + // Clean + for (var i = existList.length - 1; i >= 0; i--) { + if (existList[i] == null) { + existList.splice(i, 1); + } + else { + // $action should be volatile, otherwise option gotten from + // `getOption` will contain unexpected $action. + delete existList[i].$action; + } + } + }, + + /** + * Convert + * [{ + * type: 'group', + * id: 'xx', + * children: [{type: 'circle'}, {type: 'polygon'}] + * }] + * to + * [ + * {type: 'group', id: 'xx'}, + * {type: 'circle', parentId: 'xx'}, + * {type: 'polygon', parentId: 'xx'} + * ] + * + * @private + * @param {Array.} optionList option list + * @param {Array.} result result of flatten + * @param {Object} parentOption parent option + */ + _flatten: function (optionList, result, parentOption) { + each$1(optionList, function (option) { + if (!option) { + return; + } + + if (parentOption) { + option.parentOption = parentOption; + } + + result.push(option); + + var children = option.children; + if (option.type === 'group' && children) { + this._flatten(children, result, option); + } + // Deleting for JSON output, and for not affecting group creation. + delete option.children; + }, this); + }, + + // FIXME + // Pass to view using payload? setOption has a payload? + useElOptionsToUpdate: function () { + var els = this._elOptionsToUpdate; + // Clear to avoid render duplicately when zooming. + this._elOptionsToUpdate = null; + return els; + } +}); + +// ----- +// View +// ----- + +extendComponentView({ + + type: 'graphic', + + /** + * @override + */ + init: function (ecModel, api) { + + /** + * @private + * @type {module:zrender/core/util.HashMap} + */ + this._elMap = createHashMap(); + + /** + * @private + * @type {module:echarts/graphic/GraphicModel} + */ + this._lastGraphicModel; + }, + + /** + * @override + */ + render: function (graphicModel, ecModel, api) { + + // Having leveraged between use cases and algorithm complexity, a very + // simple layout mechanism is used: + // The size(width/height) can be determined by itself or its parent (not + // implemented yet), but can not by its children. (Top-down travel) + // The location(x/y) can be determined by the bounding rect of itself + // (can including its descendants or not) and the size of its parent. + // (Bottom-up travel) + + // When `chart.clear()` or `chart.setOption({...}, true)` with the same id, + // view will be reused. + if (graphicModel !== this._lastGraphicModel) { + this._clear(); + } + this._lastGraphicModel = graphicModel; + + this._updateElements(graphicModel); + this._relocate(graphicModel, api); + }, + + /** + * Update graphic elements. + * + * @private + * @param {Object} graphicModel graphic model + */ + _updateElements: function (graphicModel) { + var elOptionsToUpdate = graphicModel.useElOptionsToUpdate(); + + if (!elOptionsToUpdate) { + return; + } + + var elMap = this._elMap; + var rootGroup = this.group; + + // Top-down tranverse to assign graphic settings to each elements. + each$1(elOptionsToUpdate, function (elOption) { + var $action = elOption.$action; + var id = elOption.id; + var existEl = elMap.get(id); + var parentId = elOption.parentId; + var targetElParent = parentId != null ? elMap.get(parentId) : rootGroup; + + var elOptionStyle = elOption.style; + if (elOption.type === 'text' && elOptionStyle) { + // In top/bottom mode, textVerticalAlign should not be used, which cause + // inaccurately locating. + if (elOption.hv && elOption.hv[1]) { + elOptionStyle.textVerticalAlign = elOptionStyle.textBaseline = null; + } + + // Compatible with previous setting: both support fill and textFill, + // stroke and textStroke. + !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && ( + elOptionStyle.textFill = elOptionStyle.fill + ); + !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && ( + elOptionStyle.textStroke = elOptionStyle.stroke + ); + } + + // Remove unnecessary props to avoid potential problems. + var elOptionCleaned = getCleanedElOption(elOption); + + // For simple, do not support parent change, otherwise reorder is needed. + if (__DEV__) { + existEl && assert$1( + targetElParent === existEl.parent, + 'Changing parent is not supported.' + ); + } + + if (!$action || $action === 'merge') { + existEl + ? existEl.attr(elOptionCleaned) + : createEl$1(id, targetElParent, elOptionCleaned, elMap); + } + else if ($action === 'replace') { + removeEl(existEl, elMap); + createEl$1(id, targetElParent, elOptionCleaned, elMap); + } + else if ($action === 'remove') { + removeEl(existEl, elMap); + } + + var el = elMap.get(id); + if (el) { + el.__ecGraphicWidthOption = elOption.width; + el.__ecGraphicHeightOption = elOption.height; + setEventData(el, graphicModel, elOption); + } + }); + }, + + /** + * Locate graphic elements. + * + * @private + * @param {Object} graphicModel graphic model + * @param {module:echarts/ExtensionAPI} api extension API + */ + _relocate: function (graphicModel, api) { + var elOptions = graphicModel.option.elements; + var rootGroup = this.group; + var elMap = this._elMap; + var apiWidth = api.getWidth(); + var apiHeight = api.getHeight(); + + // Top-down to calculate percentage width/height of group + for (var i = 0; i < elOptions.length; i++) { + var elOption = elOptions[i]; + var el = elMap.get(elOption.id); + + if (!el || !el.isGroup) { + continue; + } + var parentEl = el.parent; + var isParentRoot = parentEl === rootGroup; + // Like 'position:absolut' in css, default 0. + el.__ecGraphicWidth = parsePercent$1( + el.__ecGraphicWidthOption, + isParentRoot ? apiWidth : parentEl.__ecGraphicWidth + ) || 0; + el.__ecGraphicHeight = parsePercent$1( + el.__ecGraphicHeightOption, + isParentRoot ? apiHeight : parentEl.__ecGraphicHeight + ) || 0; + } + + // Bottom-up tranvese all elements (consider ec resize) to locate elements. + for (var i = elOptions.length - 1; i >= 0; i--) { + var elOption = elOptions[i]; + var el = elMap.get(elOption.id); + + if (!el) { + continue; + } + + var parentEl = el.parent; + var containerInfo = parentEl === rootGroup + ? { + width: apiWidth, + height: apiHeight + } + : { + width: parentEl.__ecGraphicWidth, + height: parentEl.__ecGraphicHeight + }; + + // PENDING + // Currently, when `bounding: 'all'`, the union bounding rect of the group + // does not include the rect of [0, 0, group.width, group.height], which + // is probably weird for users. Should we make a break change for it? + positionElement( + el, elOption, containerInfo, null, + {hv: elOption.hv, boundingMode: elOption.bounding} + ); + } + }, + + /** + * Clear all elements. + * + * @private + */ + _clear: function () { + var elMap = this._elMap; + elMap.each(function (el) { + removeEl(el, elMap); + }); + this._elMap = createHashMap(); + }, + + /** + * @override + */ + dispose: function () { + this._clear(); + } +}); + +function createEl$1(id, targetElParent, elOption, elMap) { + var graphicType = elOption.type; + + if (__DEV__) { + assert$1(graphicType, 'graphic type MUST be set'); + } + + var Clz = _nonShapeGraphicElements.hasOwnProperty(graphicType) + // Those graphic elements are not shapes. They should not be + // overwritten by users, so do them first. + ? _nonShapeGraphicElements[graphicType] + : getShapeClass(graphicType); + + if (__DEV__) { + assert$1(Clz, 'graphic type can not be found'); + } + + var el = new Clz(elOption); + targetElParent.add(el); + elMap.set(id, el); + el.__ecGraphicId = id; +} + +function removeEl(existEl, elMap) { + var existElParent = existEl && existEl.parent; + if (existElParent) { + existEl.type === 'group' && existEl.traverse(function (el) { + removeEl(el, elMap); + }); + elMap.removeKey(existEl.__ecGraphicId); + existElParent.remove(existEl); + } +} + +// Remove unnecessary props to avoid potential problems. +function getCleanedElOption(elOption) { + elOption = extend({}, elOption); + each$1( + ['id', 'parentId', '$action', 'hv', 'bounding'].concat(LOCATION_PARAMS), + function (name) { + delete elOption[name]; + } + ); + return elOption; +} + +function isSetLoc(obj, props) { + var isSet; + each$1(props, function (prop) { + obj[prop] != null && obj[prop] !== 'auto' && (isSet = true); + }); + return isSet; +} + +function setKeyInfoToNewElOption(resultItem, newElOption) { + var existElOption = resultItem.exist; + + // Set id and type after id assigned. + newElOption.id = resultItem.keyInfo.id; + !newElOption.type && existElOption && (newElOption.type = existElOption.type); + + // Set parent id if not specified + if (newElOption.parentId == null) { + var newElParentOption = newElOption.parentOption; + if (newElParentOption) { + newElOption.parentId = newElParentOption.id; + } + else if (existElOption) { + newElOption.parentId = existElOption.parentId; + } + } + + // Clear + newElOption.parentOption = null; +} + +function mergeNewElOptionToExist(existList, index, newElOption) { + // Update existing options, for `getOption` feature. + var newElOptCopy = extend({}, newElOption); + var existElOption = existList[index]; + + var $action = newElOption.$action || 'merge'; + if ($action === 'merge') { + if (existElOption) { + + if (__DEV__) { + var newType = newElOption.type; + assert$1( + !newType || existElOption.type === newType, + 'Please set $action: "replace" to change `type`' + ); + } + + // We can ensure that newElOptCopy and existElOption are not + // the same object, so `merge` will not change newElOptCopy. + merge(existElOption, newElOptCopy, true); + // Rigid body, use ignoreSize. + mergeLayoutParam(existElOption, newElOptCopy, {ignoreSize: true}); + // Will be used in render. + copyLayoutParams(newElOption, existElOption); + } + else { + existList[index] = newElOptCopy; + } + } + else if ($action === 'replace') { + existList[index] = newElOptCopy; + } + else if ($action === 'remove') { + // null will be cleaned later. + existElOption && (existList[index] = null); + } +} + +function setLayoutInfoToExist(existItem, newElOption) { + if (!existItem) { + return; + } + existItem.hv = newElOption.hv = [ + // Rigid body, dont care `width`. + isSetLoc(newElOption, ['left', 'right']), + // Rigid body, dont care `height`. + isSetLoc(newElOption, ['top', 'bottom']) + ]; + // Give default group size. Otherwise layout error may occur. + if (existItem.type === 'group') { + existItem.width == null && (existItem.width = newElOption.width = 0); + existItem.height == null && (existItem.height = newElOption.height = 0); + } +} + +function setEventData(el, graphicModel, elOption) { + var eventData = el.eventData; + // Simple optimize for large amount of elements that no need event. + if (!el.silent && !el.ignore && !eventData) { + eventData = el.eventData = { + componentType: 'graphic', + componentIndex: graphicModel.componentIndex, + name: el.name + }; + } + + // `elOption.info` enables user to mount some info on + // elements and use them in event handlers. + if (eventData) { + eventData.info = el.info; + } +} + +/* +* 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. +*/ + + +var features = {}; + +function register$1(name, ctor) { + features[name] = ctor; +} + +function get$1(name) { + return features[name]; +} + +/* +* 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. +*/ + +var ToolboxModel = extendComponentModel({ + + type: 'toolbox', + + layoutMode: { + type: 'box', + ignoreSize: true + }, + + optionUpdated: function () { + ToolboxModel.superApply(this, 'optionUpdated', arguments); + + each$1(this.option.feature, function (featureOpt, featureName) { + var Feature = get$1(featureName); + Feature && merge(featureOpt, Feature.defaultOption); + }); + }, + + defaultOption: { + + show: true, + + z: 6, + + zlevel: 0, + + orient: 'horizontal', + + left: 'right', + + top: 'top', + + // right + // bottom + + backgroundColor: 'transparent', + + borderColor: '#ccc', + + borderRadius: 0, + + borderWidth: 0, + + padding: 5, + + itemSize: 15, + + itemGap: 8, + + showTitle: true, + + iconStyle: { + borderColor: '#666', + color: 'none' + }, + emphasis: { + iconStyle: { + borderColor: '#3E98C5' + } + }, + // textStyle: {}, + + // feature + + tooltip: { + show: false + } + } +}); + +/* +* 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. +*/ + +/** + * Layout list like component. + * It will box layout each items in group of component and then position the whole group in the viewport + * @param {module:zrender/group/Group} group + * @param {module:echarts/model/Component} componentModel + * @param {module:echarts/ExtensionAPI} + */ +function layout$3(group, componentModel, api) { + var boxLayoutParams = componentModel.getBoxLayoutParams(); + var padding = componentModel.get('padding'); + var viewportSize = {width: api.getWidth(), height: api.getHeight()}; + + var rect = getLayoutRect( + boxLayoutParams, + viewportSize, + padding + ); + + box( + componentModel.get('orient'), + group, + componentModel.get('itemGap'), + rect.width, + rect.height + ); + + positionElement( + group, + boxLayoutParams, + viewportSize, + padding + ); +} + +function makeBackground(rect, componentModel) { + var padding = normalizeCssArray$1( + componentModel.get('padding') + ); + var style = componentModel.getItemStyle(['color', 'opacity']); + style.fill = componentModel.get('backgroundColor'); + var rect = new Rect({ + shape: { + x: rect.x - padding[3], + y: rect.y - padding[0], + width: rect.width + padding[1] + padding[3], + height: rect.height + padding[0] + padding[2], + r: componentModel.get('borderRadius') + }, + style: style, + silent: true, + z2: -1 + }); + // FIXME + // `subPixelOptimizeRect` may bring some gap between edge of viewpart + // and background rect when setting like `left: 0`, `top: 0`. + // graphic.subPixelOptimizeRect(rect); + + return rect; +} + +/* +* 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. +*/ + +extendComponentView({ + + type: 'toolbox', + + render: function (toolboxModel, ecModel, api, payload) { + var group = this.group; + group.removeAll(); + + if (!toolboxModel.get('show')) { + return; + } + + var itemSize = +toolboxModel.get('itemSize'); + var featureOpts = toolboxModel.get('feature') || {}; + var features = this._features || (this._features = {}); + + var featureNames = []; + each$1(featureOpts, function (opt, name) { + featureNames.push(name); + }); + + (new DataDiffer(this._featureNames || [], featureNames)) + .add(processFeature) + .update(processFeature) + .remove(curry(processFeature, null)) + .execute(); + + // Keep for diff. + this._featureNames = featureNames; + + function processFeature(newIndex, oldIndex) { + var featureName = featureNames[newIndex]; + var oldName = featureNames[oldIndex]; + var featureOpt = featureOpts[featureName]; + var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel); + var feature; + + // FIX#11236, merge feature title from MagicType newOption. TODO: consider seriesIndex ? + if (payload && payload.newTitle != null) { + featureOpt.title = payload.newTitle; + } + + if (featureName && !oldName) { // Create + if (isUserFeatureName(featureName)) { + feature = { + model: featureModel, + onclick: featureModel.option.onclick, + featureName: featureName + }; + } + else { + var Feature = get$1(featureName); + if (!Feature) { + return; + } + feature = new Feature(featureModel, ecModel, api); + } + features[featureName] = feature; + } + else { + feature = features[oldName]; + // If feature does not exsit. + if (!feature) { + return; + } + feature.model = featureModel; + feature.ecModel = ecModel; + feature.api = api; + } + + if (!featureName && oldName) { + feature.dispose && feature.dispose(ecModel, api); + return; + } + + if (!featureModel.get('show') || feature.unusable) { + feature.remove && feature.remove(ecModel, api); + return; + } + + createIconPaths(featureModel, feature, featureName); + + featureModel.setIconStatus = function (iconName, status) { + var option = this.option; + var iconPaths = this.iconPaths; + option.iconStatus = option.iconStatus || {}; + option.iconStatus[iconName] = status; + // FIXME + iconPaths[iconName] && iconPaths[iconName].trigger(status); + }; + + if (feature.render) { + feature.render(featureModel, ecModel, api, payload); + } + } + + function createIconPaths(featureModel, feature, featureName) { + var iconStyleModel = featureModel.getModel('iconStyle'); + var iconStyleEmphasisModel = featureModel.getModel('emphasis.iconStyle'); + + // If one feature has mutiple icon. they are orginaized as + // { + // icon: { + // foo: '', + // bar: '' + // }, + // title: { + // foo: '', + // bar: '' + // } + // } + var icons = feature.getIcons ? feature.getIcons() : featureModel.get('icon'); + var titles = featureModel.get('title') || {}; + if (typeof icons === 'string') { + var icon = icons; + var title = titles; + icons = {}; + titles = {}; + icons[featureName] = icon; + titles[featureName] = title; + } + var iconPaths = featureModel.iconPaths = {}; + each$1(icons, function (iconStr, iconName) { + var path = createIcon( + iconStr, + {}, + { + x: -itemSize / 2, + y: -itemSize / 2, + width: itemSize, + height: itemSize + } + ); + path.setStyle(iconStyleModel.getItemStyle()); + path.hoverStyle = iconStyleEmphasisModel.getItemStyle(); + + // Text position calculation + path.setStyle({ + text: titles[iconName], + textAlign: iconStyleEmphasisModel.get('textAlign'), + textBorderRadius: iconStyleEmphasisModel.get('textBorderRadius'), + textPadding: iconStyleEmphasisModel.get('textPadding'), + textFill: null + }); + + var tooltipModel = toolboxModel.getModel('tooltip'); + if (tooltipModel && tooltipModel.get('show')) { + path.attr('tooltip', extend({ + content: titles[iconName], + formatter: tooltipModel.get('formatter', true) + || function () { + return titles[iconName]; + }, + formatterParams: { + componentType: 'toolbox', + name: iconName, + title: titles[iconName], + $vars: ['name', 'title'] + }, + position: tooltipModel.get('position', true) || 'bottom' + }, tooltipModel.option)); + } + + setHoverStyle(path); + + if (toolboxModel.get('showTitle')) { + path.__title = titles[iconName]; + path.on('mouseover', function () { + // Should not reuse above hoverStyle, which might be modified. + var hoverStyle = iconStyleEmphasisModel.getItemStyle(); + var defaultTextPosition = toolboxModel.get('orient') === 'vertical' + ? (toolboxModel.get('right') == null ? 'right' : 'left') + : (toolboxModel.get('bottom') == null ? 'bottom' : 'top'); + path.setStyle({ + textFill: iconStyleEmphasisModel.get('textFill') + || hoverStyle.fill || hoverStyle.stroke || '#000', + textBackgroundColor: iconStyleEmphasisModel.get('textBackgroundColor'), + textPosition: iconStyleEmphasisModel.get('textPosition') || defaultTextPosition + }); + }) + .on('mouseout', function () { + path.setStyle({ + textFill: null, + textBackgroundColor: null + }); + }); + } + path.trigger(featureModel.get('iconStatus.' + iconName) || 'normal'); + + group.add(path); + path.on('click', bind( + feature.onclick, feature, ecModel, api, iconName + )); + + iconPaths[iconName] = path; + }); + } + + layout$3(group, toolboxModel, api); + // Render background after group is layout + // FIXME + group.add(makeBackground(group.getBoundingRect(), toolboxModel)); + + // Adjust icon title positions to avoid them out of screen + group.eachChild(function (icon) { + var titleText = icon.__title; + var hoverStyle = icon.hoverStyle; + // May be background element + if (hoverStyle && titleText) { + var rect = getBoundingRect( + titleText, makeFont(hoverStyle) + ); + var offsetX = icon.position[0] + group.position[0]; + var offsetY = icon.position[1] + group.position[1] + itemSize; + + var needPutOnTop = false; + if (offsetY + rect.height > api.getHeight()) { + hoverStyle.textPosition = 'top'; + needPutOnTop = true; + } + var topOffset = needPutOnTop ? (-5 - rect.height) : (itemSize + 8); + if (offsetX + rect.width / 2 > api.getWidth()) { + hoverStyle.textPosition = ['100%', topOffset]; + hoverStyle.textAlign = 'right'; + } + else if (offsetX - rect.width / 2 < 0) { + hoverStyle.textPosition = [0, topOffset]; + hoverStyle.textAlign = 'left'; + } + } + }); + }, + + updateView: function (toolboxModel, ecModel, api, payload) { + each$1(this._features, function (feature) { + feature.updateView && feature.updateView(feature.model, ecModel, api, payload); + }); + }, + + // updateLayout: function (toolboxModel, ecModel, api, payload) { + // zrUtil.each(this._features, function (feature) { + // feature.updateLayout && feature.updateLayout(feature.model, ecModel, api, payload); + // }); + // }, + + remove: function (ecModel, api) { + each$1(this._features, function (feature) { + feature.remove && feature.remove(ecModel, api); + }); + this.group.removeAll(); + }, + + dispose: function (ecModel, api) { + each$1(this._features, function (feature) { + feature.dispose && feature.dispose(ecModel, api); + }); + } +}); + +function isUserFeatureName(featureName) { + return featureName.indexOf('my') === 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. +*/ + +/* global Uint8Array */ + +var saveAsImageLang = lang.toolbox.saveAsImage; + +function SaveAsImage(model) { + this.model = model; +} + +SaveAsImage.defaultOption = { + show: true, + icon: 'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0', + title: saveAsImageLang.title, + type: 'png', + // Default use option.backgroundColor + // backgroundColor: '#fff', + connectedBackgroundColor: '#fff', + name: '', + excludeComponents: ['toolbox'], + pixelRatio: 1, + lang: saveAsImageLang.lang.slice() +}; + +SaveAsImage.prototype.unusable = !env$1.canvasSupported; + +var proto$2 = SaveAsImage.prototype; + +proto$2.onclick = function (ecModel, api) { + var model = this.model; + var title = model.get('name') || ecModel.get('title.0.text') || 'echarts'; + var type = model.get('type', true) || 'png'; + var url = api.getConnectedDataURL({ + type: type, + backgroundColor: model.get('backgroundColor', true) + || ecModel.get('backgroundColor') || '#fff', + connectedBackgroundColor: model.get('connectedBackgroundColor'), + excludeComponents: model.get('excludeComponents'), + pixelRatio: model.get('pixelRatio') + }); + // Chrome and Firefox + if (typeof MouseEvent === 'function' && !env$1.browser.ie && !env$1.browser.edge) { + var $a = document.createElement('a'); + $a.download = title + '.' + type; + $a.target = '_blank'; + $a.href = url; + var evt = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: false + }); + $a.dispatchEvent(evt); + } + // IE + else { + if (window.navigator.msSaveOrOpenBlob) { + var bstr = atob(url.split(',')[1]); + var n = bstr.length; + var u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + var blob = new Blob([u8arr]); + window.navigator.msSaveOrOpenBlob(blob, title + '.' + type); + } + else { + var lang$$1 = model.get('lang'); + var html = '' + + '' + + '' + + ''; + var tab = window.open(); + tab.document.write(html); + } + } +}; + +register$1( + 'saveAsImage', SaveAsImage +); + +/* +* 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. +*/ + +var magicTypeLang = lang.toolbox.magicType; +var INNER_STACK_KEYWORD = '__ec_magicType_stack__'; + +function MagicType(model) { + this.model = model; +} + +MagicType.defaultOption = { + show: true, + type: [], + // Icon group + icon: { + /* eslint-disable */ + line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4', + bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7', + stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line + /* eslint-enable */ + }, + // `line`, `bar`, `stack`, `tiled` + title: clone(magicTypeLang.title), + option: {}, + seriesIndex: {} +}; + +var proto$3 = MagicType.prototype; + +proto$3.getIcons = function () { + var model = this.model; + var availableIcons = model.get('icon'); + var icons = {}; + each$1(model.get('type'), function (type) { + if (availableIcons[type]) { + icons[type] = availableIcons[type]; + } + }); + return icons; +}; + +var seriesOptGenreator = { + 'line': function (seriesType, seriesId, seriesModel, model) { + if (seriesType === 'bar') { + return merge({ + id: seriesId, + type: 'line', + // Preserve data related option + data: seriesModel.get('data'), + stack: seriesModel.get('stack'), + markPoint: seriesModel.get('markPoint'), + markLine: seriesModel.get('markLine') + }, model.get('option.line') || {}, true); + } + }, + 'bar': function (seriesType, seriesId, seriesModel, model) { + if (seriesType === 'line') { + return merge({ + id: seriesId, + type: 'bar', + // Preserve data related option + data: seriesModel.get('data'), + stack: seriesModel.get('stack'), + markPoint: seriesModel.get('markPoint'), + markLine: seriesModel.get('markLine') + }, model.get('option.bar') || {}, true); + } + }, + 'stack': function (seriesType, seriesId, seriesModel, model) { + var isStack = seriesModel.get('stack') === INNER_STACK_KEYWORD; + if (seriesType === 'line' || seriesType === 'bar') { + model.setIconStatus('stack', isStack ? 'normal' : 'emphasis'); + return merge({ + id: seriesId, + stack: isStack ? '' : INNER_STACK_KEYWORD + }, model.get('option.stack') || {}, true); + } + } +}; + +var radioTypes = [ + ['line', 'bar'], + ['stack'] +]; + +proto$3.onclick = function (ecModel, api, type) { + var model = this.model; + var seriesIndex = model.get('seriesIndex.' + type); + // Not supported magicType + if (!seriesOptGenreator[type]) { + return; + } + var newOption = { + series: [] + }; + var generateNewSeriesTypes = function (seriesModel) { + var seriesType = seriesModel.subType; + var seriesId = seriesModel.id; + var newSeriesOpt = seriesOptGenreator[type]( + seriesType, seriesId, seriesModel, model + ); + if (newSeriesOpt) { + // PENDING If merge original option? + defaults(newSeriesOpt, seriesModel.option); + newOption.series.push(newSeriesOpt); + } + // Modify boundaryGap + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.type === 'cartesian2d' && (type === 'line' || type === 'bar')) { + var categoryAxis = coordSys.getAxesByScale('ordinal')[0]; + if (categoryAxis) { + var axisDim = categoryAxis.dim; + var axisType = axisDim + 'Axis'; + var axisModel = ecModel.queryComponents({ + mainType: axisType, + index: seriesModel.get(name + 'Index'), + id: seriesModel.get(name + 'Id') + })[0]; + var axisIndex = axisModel.componentIndex; + + newOption[axisType] = newOption[axisType] || []; + for (var i = 0; i <= axisIndex; i++) { + newOption[axisType][axisIndex] = newOption[axisType][axisIndex] || {}; + } + newOption[axisType][axisIndex].boundaryGap = type === 'bar'; + } + } + }; + + each$1(radioTypes, function (radio) { + if (indexOf(radio, type) >= 0) { + each$1(radio, function (item) { + model.setIconStatus(item, 'normal'); + }); + } + }); + + model.setIconStatus(type, 'emphasis'); + + ecModel.eachComponent( + { + mainType: 'series', + query: seriesIndex == null ? null : { + seriesIndex: seriesIndex + } + }, generateNewSeriesTypes + ); + + var newTitle; + // Change title of stack + if (type === 'stack') { + var isStack = newOption.series && newOption.series[0] && newOption.series[0].stack === INNER_STACK_KEYWORD; + newTitle = isStack + ? merge({ stack: magicTypeLang.title.tiled }, magicTypeLang.title) + : clone(magicTypeLang.title); + } + + api.dispatchAction({ + type: 'changeMagicType', + currentType: type, + newOption: newOption, + newTitle: newTitle + }); +}; + +registerAction({ + type: 'changeMagicType', + event: 'magicTypeChanged', + update: 'prepareAndUpdate' +}, function (payload, ecModel) { + ecModel.mergeOption(payload.newOption); +}); + +register$1('magicType', MagicType); + +/* +* 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. +*/ + +var dataViewLang = lang.toolbox.dataView; + +var BLOCK_SPLITER = new Array(60).join('-'); +var ITEM_SPLITER = '\t'; +/** + * Group series into two types + * 1. on category axis, like line, bar + * 2. others, like scatter, pie + * @param {module:echarts/model/Global} ecModel + * @return {Object} + * @inner + */ +function groupSeries(ecModel) { + var seriesGroupByCategoryAxis = {}; + var otherSeries = []; + var meta = []; + ecModel.eachRawSeries(function (seriesModel) { + var coordSys = seriesModel.coordinateSystem; + + if (coordSys && (coordSys.type === 'cartesian2d' || coordSys.type === 'polar')) { + var baseAxis = coordSys.getBaseAxis(); + if (baseAxis.type === 'category') { + var key = baseAxis.dim + '_' + baseAxis.index; + if (!seriesGroupByCategoryAxis[key]) { + seriesGroupByCategoryAxis[key] = { + categoryAxis: baseAxis, + valueAxis: coordSys.getOtherAxis(baseAxis), + series: [] + }; + meta.push({ + axisDim: baseAxis.dim, + axisIndex: baseAxis.index + }); + } + seriesGroupByCategoryAxis[key].series.push(seriesModel); + } + else { + otherSeries.push(seriesModel); + } + } + else { + otherSeries.push(seriesModel); + } + }); + + return { + seriesGroupByCategoryAxis: seriesGroupByCategoryAxis, + other: otherSeries, + meta: meta + }; +} + +/** + * Assemble content of series on cateogory axis + * @param {Array.} series + * @return {string} + * @inner + */ +function assembleSeriesWithCategoryAxis(series) { + var tables = []; + each$1(series, function (group, key) { + var categoryAxis = group.categoryAxis; + var valueAxis = group.valueAxis; + var valueAxisDim = valueAxis.dim; + + var headers = [' '].concat(map(group.series, function (series) { + return series.name; + })); + var columns = [categoryAxis.model.getCategories()]; + each$1(group.series, function (series) { + columns.push(series.getRawData().mapArray(valueAxisDim, function (val) { + return val; + })); + }); + // Assemble table content + var lines = [headers.join(ITEM_SPLITER)]; + for (var i = 0; i < columns[0].length; i++) { + var items = []; + for (var j = 0; j < columns.length; j++) { + items.push(columns[j][i]); + } + lines.push(items.join(ITEM_SPLITER)); + } + tables.push(lines.join('\n')); + }); + return tables.join('\n\n' + BLOCK_SPLITER + '\n\n'); +} + +/** + * Assemble content of other series + * @param {Array.} series + * @return {string} + * @inner + */ +function assembleOtherSeries(series) { + return map(series, function (series) { + var data = series.getRawData(); + var lines = [series.name]; + var vals = []; + data.each(data.dimensions, function () { + var argLen = arguments.length; + var dataIndex = arguments[argLen - 1]; + var name = data.getName(dataIndex); + for (var i = 0; i < argLen - 1; i++) { + vals[i] = arguments[i]; + } + lines.push((name ? (name + ITEM_SPLITER) : '') + vals.join(ITEM_SPLITER)); + }); + return lines.join('\n'); + }).join('\n\n' + BLOCK_SPLITER + '\n\n'); +} + +/** + * @param {module:echarts/model/Global} + * @return {Object} + * @inner + */ +function getContentFromModel(ecModel) { + + var result = groupSeries(ecModel); + + return { + value: filter([ + assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis), + assembleOtherSeries(result.other) + ], function (str) { + return str.replace(/[\n\t\s]/g, ''); + }).join('\n\n' + BLOCK_SPLITER + '\n\n'), + + meta: result.meta + }; +} + + +function trim$1(str) { + return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); +} +/** + * If a block is tsv format + */ +function isTSVFormat(block) { + // Simple method to find out if a block is tsv format + var firstLine = block.slice(0, block.indexOf('\n')); + if (firstLine.indexOf(ITEM_SPLITER) >= 0) { + return true; + } +} + +var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + ']+', 'g'); +/** + * @param {string} tsv + * @return {Object} + */ +function parseTSVContents(tsv) { + var tsvLines = tsv.split(/\n+/g); + var headers = trim$1(tsvLines.shift()).split(itemSplitRegex); + + var categories = []; + var series = map(headers, function (header) { + return { + name: header, + data: [] + }; + }); + for (var i = 0; i < tsvLines.length; i++) { + var items = trim$1(tsvLines[i]).split(itemSplitRegex); + categories.push(items.shift()); + for (var j = 0; j < items.length; j++) { + series[j] && (series[j].data[i] = items[j]); + } + } + return { + series: series, + categories: categories + }; +} + +/** + * @param {string} str + * @return {Array.} + * @inner + */ +function parseListContents(str) { + var lines = str.split(/\n+/g); + var seriesName = trim$1(lines.shift()); + + var data = []; + for (var i = 0; i < lines.length; i++) { + var items = trim$1(lines[i]).split(itemSplitRegex); + var name = ''; + var value; + var hasName = false; + if (isNaN(items[0])) { // First item is name + hasName = true; + name = items[0]; + items = items.slice(1); + data[i] = { + name: name, + value: [] + }; + value = data[i].value; + } + else { + value = data[i] = []; + } + for (var j = 0; j < items.length; j++) { + value.push(+items[j]); + } + if (value.length === 1) { + hasName ? (data[i].value = value[0]) : (data[i] = value[0]); + } + } + + return { + name: seriesName, + data: data + }; +} + +/** + * @param {string} str + * @param {Array.} blockMetaList + * @return {Object} + * @inner + */ +function parseContents(str, blockMetaList) { + var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g')); + var newOption = { + series: [] + }; + each$1(blocks, function (block, idx) { + if (isTSVFormat(block)) { + var result = parseTSVContents(block); + var blockMeta = blockMetaList[idx]; + var axisKey = blockMeta.axisDim + 'Axis'; + + if (blockMeta) { + newOption[axisKey] = newOption[axisKey] || []; + newOption[axisKey][blockMeta.axisIndex] = { + data: result.categories + }; + newOption.series = newOption.series.concat(result.series); + } + } + else { + var result = parseListContents(block); + newOption.series.push(result); + } + }); + return newOption; +} + +/** + * @alias {module:echarts/component/toolbox/feature/DataView} + * @constructor + * @param {module:echarts/model/Model} model + */ +function DataView(model) { + + this._dom = null; + + this.model = model; +} + +DataView.defaultOption = { + show: true, + readOnly: false, + optionToContent: null, + contentToOption: null, + + icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28', + title: clone(dataViewLang.title), + lang: clone(dataViewLang.lang), + backgroundColor: '#fff', + textColor: '#000', + textareaColor: '#fff', + textareaBorderColor: '#333', + buttonColor: '#c23531', + buttonTextColor: '#fff' +}; + +DataView.prototype.onclick = function (ecModel, api) { + var container = api.getDom(); + var model = this.model; + if (this._dom) { + container.removeChild(this._dom); + } + var root = document.createElement('div'); + root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;'; + root.style.backgroundColor = model.get('backgroundColor') || '#fff'; + + // Create elements + var header = document.createElement('h4'); + var lang$$1 = model.get('lang') || []; + header.innerHTML = lang$$1[0] || model.get('title'); + header.style.cssText = 'margin: 10px 20px;'; + header.style.color = model.get('textColor'); + + var viewMain = document.createElement('div'); + var textarea = document.createElement('textarea'); + viewMain.style.cssText = 'display:block;width:100%;overflow:auto;'; + + var optionToContent = model.get('optionToContent'); + var contentToOption = model.get('contentToOption'); + var result = getContentFromModel(ecModel); + if (typeof optionToContent === 'function') { + var htmlOrDom = optionToContent(api.getOption()); + if (typeof htmlOrDom === 'string') { + viewMain.innerHTML = htmlOrDom; + } + else if (isDom(htmlOrDom)) { + viewMain.appendChild(htmlOrDom); + } + } + else { + // Use default textarea + viewMain.appendChild(textarea); + textarea.readOnly = model.get('readOnly'); + textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;'; + textarea.style.color = model.get('textColor'); + textarea.style.borderColor = model.get('textareaBorderColor'); + textarea.style.backgroundColor = model.get('textareaColor'); + textarea.value = result.value; + } + + var blockMetaList = result.meta; + + var buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;'; + + var buttonStyle = 'float:right;margin-right:20px;border:none;' + + 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px'; + var closeButton = document.createElement('div'); + var refreshButton = document.createElement('div'); + + buttonStyle += ';background-color:' + model.get('buttonColor'); + buttonStyle += ';color:' + model.get('buttonTextColor'); + + var self = this; + + function close() { + container.removeChild(root); + self._dom = null; + } + addEventListener(closeButton, 'click', close); + + addEventListener(refreshButton, 'click', function () { + var newOption; + try { + if (typeof contentToOption === 'function') { + newOption = contentToOption(viewMain, api.getOption()); + } + else { + newOption = parseContents(textarea.value, blockMetaList); + } + } + catch (e) { + close(); + throw new Error('Data view format error ' + e); + } + if (newOption) { + api.dispatchAction({ + type: 'changeDataView', + newOption: newOption + }); + } + + close(); + }); + + closeButton.innerHTML = lang$$1[1]; + refreshButton.innerHTML = lang$$1[2]; + refreshButton.style.cssText = buttonStyle; + closeButton.style.cssText = buttonStyle; + + !model.get('readOnly') && buttonContainer.appendChild(refreshButton); + buttonContainer.appendChild(closeButton); + + root.appendChild(header); + root.appendChild(viewMain); + root.appendChild(buttonContainer); + + viewMain.style.height = (container.clientHeight - 80) + 'px'; + + container.appendChild(root); + this._dom = root; +}; + +DataView.prototype.remove = function (ecModel, api) { + this._dom && api.getDom().removeChild(this._dom); +}; + +DataView.prototype.dispose = function (ecModel, api) { + this.remove(ecModel, api); +}; + +/** + * @inner + */ +function tryMergeDataOption(newData, originalData) { + return map(newData, function (newVal, idx) { + var original = originalData && originalData[idx]; + if (isObject$1(original) && !isArray(original)) { + if (isObject$1(newVal) && !isArray(newVal)) { + newVal = newVal.value; + } + // Original data has option + return defaults({ + value: newVal + }, original); + } + else { + return newVal; + } + }); +} + +register$1('dataView', DataView); + +registerAction({ + type: 'changeDataView', + event: 'dataViewChanged', + update: 'prepareAndUpdate' +}, function (payload, ecModel) { + var newSeriesOptList = []; + each$1(payload.newOption.series, function (seriesOpt) { + var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0]; + if (!seriesModel) { + // New created series + // Geuss the series type + newSeriesOptList.push(extend({ + // Default is scatter + type: 'scatter' + }, seriesOpt)); + } + else { + var originalData = seriesModel.get('data'); + newSeriesOptList.push({ + name: seriesOpt.name, + data: tryMergeDataOption(seriesOpt.data, originalData) + }); + } + }); + + ecModel.mergeOption(defaults({ + series: newSeriesOptList + }, payload.newOption)); +}); + +/* +* 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. +*/ + +var each$17 = each$1; +var indexOf$1 = indexOf; +var curry$4 = curry; + +var COORD_CONVERTS = ['dataToPoint', 'pointToData']; + +// FIXME +// how to genarialize to more coordinate systems. +var INCLUDE_FINDER_MAIN_TYPES = [ + 'grid', 'xAxis', 'yAxis', 'geo', 'graph', + 'polar', 'radiusAxis', 'angleAxis', 'bmap' +]; + +/** + * [option in constructor]: + * { + * Index/Id/Name of geo, xAxis, yAxis, grid: See util/model#parseFinder. + * } + * + * + * [targetInfo]: + * + * There can be multiple axes in a single targetInfo. Consider the case + * of `grid` component, a targetInfo represents a grid which contains one or more + * cartesian and one or more axes. And consider the case of parallel system, + * which has multiple axes in a coordinate system. + * Can be { + * panelId: ..., + * coordSys: , + * coordSyses: all cartesians. + * gridModel: + * xAxes: correspond to coordSyses on index + * yAxes: correspond to coordSyses on index + * } + * or { + * panelId: ..., + * coordSys: + * coordSyses: [] + * geoModel: + * } + * + * + * [panelOpt]: + * + * Make from targetInfo. Input to BrushController. + * { + * panelId: ..., + * rect: ... + * } + * + * + * [area]: + * + * Generated by BrushController or user input. + * { + * panelId: Used to locate coordInfo directly. If user inpput, no panelId. + * brushType: determine how to convert to/from coord('rect' or 'polygon' or 'lineX/Y'). + * Index/Id/Name of geo, xAxis, yAxis, grid: See util/model#parseFinder. + * range: pixel range. + * coordRange: representitive coord range (the first one of coordRanges). + * coordRanges: coord ranges, used in multiple cartesian in one grid. + * } + */ + +/** + * @param {Object} option contains Index/Id/Name of xAxis/yAxis/geo/grid + * Each can be {number|Array.}. like: {xAxisIndex: [3, 4]} + * @param {module:echarts/model/Global} ecModel + * @param {Object} [opt] + * @param {Array.} [opt.include] include coordinate system types. + */ +function BrushTargetManager(option, ecModel, opt) { + /** + * @private + * @type {Array.} + */ + var targetInfoList = this._targetInfoList = []; + var info = {}; + var foundCpts = parseFinder$1(ecModel, option); + + each$17(targetInfoBuilders, function (builder, type) { + if (!opt || !opt.include || indexOf$1(opt.include, type) >= 0) { + builder(foundCpts, targetInfoList, info); + } + }); +} + +var proto$5 = BrushTargetManager.prototype; + +proto$5.setOutputRanges = function (areas, ecModel) { + this.matchOutputRanges(areas, ecModel, function (area, coordRange, coordSys) { + (area.coordRanges || (area.coordRanges = [])).push(coordRange); + // area.coordRange is the first of area.coordRanges + if (!area.coordRange) { + area.coordRange = coordRange; + // In 'category' axis, coord to pixel is not reversible, so we can not + // rebuild range by coordRange accrately, which may bring trouble when + // brushing only one item. So we use __rangeOffset to rebuilding range + // by coordRange. And this it only used in brush component so it is no + // need to be adapted to coordRanges. + var result = coordConvert[area.brushType](0, coordSys, coordRange); + area.__rangeOffset = { + offset: diffProcessor[area.brushType](result.values, area.range, [1, 1]), + xyMinMax: result.xyMinMax + }; + } + }); +}; + +proto$5.matchOutputRanges = function (areas, ecModel, cb) { + each$17(areas, function (area) { + var targetInfo = this.findTargetInfo(area, ecModel); + + if (targetInfo && targetInfo !== true) { + each$1( + targetInfo.coordSyses, + function (coordSys) { + var result = coordConvert[area.brushType](1, coordSys, area.range); + cb(area, result.values, coordSys, ecModel); + } + ); + } + }, this); +}; + +proto$5.setInputRanges = function (areas, ecModel) { + each$17(areas, function (area) { + var targetInfo = this.findTargetInfo(area, ecModel); + + if (__DEV__) { + assert$1( + !targetInfo || targetInfo === true || area.coordRange, + 'coordRange must be specified when coord index specified.' + ); + assert$1( + !targetInfo || targetInfo !== true || area.range, + 'range must be specified in global brush.' + ); + } + + area.range = area.range || []; + + // convert coordRange to global range and set panelId. + if (targetInfo && targetInfo !== true) { + area.panelId = targetInfo.panelId; + // (1) area.range shoule always be calculate from coordRange but does + // not keep its original value, for the sake of the dataZoom scenario, + // where area.coordRange remains unchanged but area.range may be changed. + // (2) Only support converting one coordRange to pixel range in brush + // component. So do not consider `coordRanges`. + // (3) About __rangeOffset, see comment above. + var result = coordConvert[area.brushType](0, targetInfo.coordSys, area.coordRange); + var rangeOffset = area.__rangeOffset; + area.range = rangeOffset + ? diffProcessor[area.brushType]( + result.values, + rangeOffset.offset, + getScales(result.xyMinMax, rangeOffset.xyMinMax) + ) + : result.values; + } + }, this); +}; + +proto$5.makePanelOpts = function (api, getDefaultBrushType) { + return map(this._targetInfoList, function (targetInfo) { + var rect = targetInfo.getPanelRect(); + return { + panelId: targetInfo.panelId, + defaultBrushType: getDefaultBrushType && getDefaultBrushType(targetInfo), + clipPath: makeRectPanelClipPath(rect), + isTargetByCursor: makeRectIsTargetByCursor( + rect, api, targetInfo.coordSysModel + ), + getLinearBrushOtherExtent: makeLinearBrushOtherExtent(rect) + }; + }); +}; + +proto$5.controlSeries = function (area, seriesModel, ecModel) { + // Check whether area is bound in coord, and series do not belong to that coord. + // If do not do this check, some brush (like lineX) will controll all axes. + var targetInfo = this.findTargetInfo(area, ecModel); + return targetInfo === true || ( + targetInfo && indexOf$1(targetInfo.coordSyses, seriesModel.coordinateSystem) >= 0 + ); +}; + +/** + * If return Object, a coord found. + * If reutrn true, global found. + * Otherwise nothing found. + * + * @param {Object} area + * @param {Array} targetInfoList + * @return {Object|boolean} + */ +proto$5.findTargetInfo = function (area, ecModel) { + var targetInfoList = this._targetInfoList; + var foundCpts = parseFinder$1(ecModel, area); + + for (var i = 0; i < targetInfoList.length; i++) { + var targetInfo = targetInfoList[i]; + var areaPanelId = area.panelId; + if (areaPanelId) { + if (targetInfo.panelId === areaPanelId) { + return targetInfo; + } + } + else { + for (var i = 0; i < targetInfoMatchers.length; i++) { + if (targetInfoMatchers[i](foundCpts, targetInfo)) { + return targetInfo; + } + } + } + } + + return true; +}; + +function formatMinMax(minMax) { + minMax[0] > minMax[1] && minMax.reverse(); + return minMax; +} + +function parseFinder$1(ecModel, option) { + return parseFinder( + ecModel, option, {includeMainTypes: INCLUDE_FINDER_MAIN_TYPES} + ); +} + +var targetInfoBuilders = { + + grid: function (foundCpts, targetInfoList) { + var xAxisModels = foundCpts.xAxisModels; + var yAxisModels = foundCpts.yAxisModels; + var gridModels = foundCpts.gridModels; + // Remove duplicated. + var gridModelMap = createHashMap(); + var xAxesHas = {}; + var yAxesHas = {}; + + if (!xAxisModels && !yAxisModels && !gridModels) { + return; + } + + each$17(xAxisModels, function (axisModel) { + var gridModel = axisModel.axis.grid.model; + gridModelMap.set(gridModel.id, gridModel); + xAxesHas[gridModel.id] = true; + }); + each$17(yAxisModels, function (axisModel) { + var gridModel = axisModel.axis.grid.model; + gridModelMap.set(gridModel.id, gridModel); + yAxesHas[gridModel.id] = true; + }); + each$17(gridModels, function (gridModel) { + gridModelMap.set(gridModel.id, gridModel); + xAxesHas[gridModel.id] = true; + yAxesHas[gridModel.id] = true; + }); + + gridModelMap.each(function (gridModel) { + var grid = gridModel.coordinateSystem; + var cartesians = []; + + each$17(grid.getCartesians(), function (cartesian, index) { + if (indexOf$1(xAxisModels, cartesian.getAxis('x').model) >= 0 + || indexOf$1(yAxisModels, cartesian.getAxis('y').model) >= 0 + ) { + cartesians.push(cartesian); + } + }); + targetInfoList.push({ + panelId: 'grid--' + gridModel.id, + gridModel: gridModel, + coordSysModel: gridModel, + // Use the first one as the representitive coordSys. + coordSys: cartesians[0], + coordSyses: cartesians, + getPanelRect: panelRectBuilder.grid, + xAxisDeclared: xAxesHas[gridModel.id], + yAxisDeclared: yAxesHas[gridModel.id] + }); + }); + }, + + geo: function (foundCpts, targetInfoList) { + each$17(foundCpts.geoModels, function (geoModel) { + var coordSys = geoModel.coordinateSystem; + targetInfoList.push({ + panelId: 'geo--' + geoModel.id, + geoModel: geoModel, + coordSysModel: geoModel, + coordSys: coordSys, + coordSyses: [coordSys], + getPanelRect: panelRectBuilder.geo + }); + }); + } +}; + +var targetInfoMatchers = [ + + // grid + function (foundCpts, targetInfo) { + var xAxisModel = foundCpts.xAxisModel; + var yAxisModel = foundCpts.yAxisModel; + var gridModel = foundCpts.gridModel; + + !gridModel && xAxisModel && (gridModel = xAxisModel.axis.grid.model); + !gridModel && yAxisModel && (gridModel = yAxisModel.axis.grid.model); + + return gridModel && gridModel === targetInfo.gridModel; + }, + + // geo + function (foundCpts, targetInfo) { + var geoModel = foundCpts.geoModel; + return geoModel && geoModel === targetInfo.geoModel; + } +]; + +var panelRectBuilder = { + + grid: function () { + // grid is not Transformable. + return this.coordSys.grid.getRect().clone(); + }, + + geo: function () { + var coordSys = this.coordSys; + var rect = coordSys.getBoundingRect().clone(); + // geo roam and zoom transform + rect.applyTransform(getTransform(coordSys)); + return rect; + } +}; + +var coordConvert = { + + lineX: curry$4(axisConvert, 0), + + lineY: curry$4(axisConvert, 1), + + rect: function (to, coordSys, rangeOrCoordRange) { + var xminymin = coordSys[COORD_CONVERTS[to]]([rangeOrCoordRange[0][0], rangeOrCoordRange[1][0]]); + var xmaxymax = coordSys[COORD_CONVERTS[to]]([rangeOrCoordRange[0][1], rangeOrCoordRange[1][1]]); + var values = [ + formatMinMax([xminymin[0], xmaxymax[0]]), + formatMinMax([xminymin[1], xmaxymax[1]]) + ]; + return {values: values, xyMinMax: values}; + }, + + polygon: function (to, coordSys, rangeOrCoordRange) { + var xyMinMax = [[Infinity, -Infinity], [Infinity, -Infinity]]; + var values = map(rangeOrCoordRange, function (item) { + var p = coordSys[COORD_CONVERTS[to]](item); + xyMinMax[0][0] = Math.min(xyMinMax[0][0], p[0]); + xyMinMax[1][0] = Math.min(xyMinMax[1][0], p[1]); + xyMinMax[0][1] = Math.max(xyMinMax[0][1], p[0]); + xyMinMax[1][1] = Math.max(xyMinMax[1][1], p[1]); + return p; + }); + return {values: values, xyMinMax: xyMinMax}; + } +}; + +function axisConvert(axisNameIndex, to, coordSys, rangeOrCoordRange) { + if (__DEV__) { + assert$1( + coordSys.type === 'cartesian2d', + 'lineX/lineY brush is available only in cartesian2d.' + ); + } + + var axis = coordSys.getAxis(['x', 'y'][axisNameIndex]); + var values = formatMinMax(map([0, 1], function (i) { + return to + ? axis.coordToData(axis.toLocalCoord(rangeOrCoordRange[i])) + : axis.toGlobalCoord(axis.dataToCoord(rangeOrCoordRange[i])); + })); + var xyMinMax = []; + xyMinMax[axisNameIndex] = values; + xyMinMax[1 - axisNameIndex] = [NaN, NaN]; + + return {values: values, xyMinMax: xyMinMax}; +} + +var diffProcessor = { + lineX: curry$4(axisDiffProcessor, 0), + + lineY: curry$4(axisDiffProcessor, 1), + + rect: function (values, refer, scales) { + return [ + [values[0][0] - scales[0] * refer[0][0], values[0][1] - scales[0] * refer[0][1]], + [values[1][0] - scales[1] * refer[1][0], values[1][1] - scales[1] * refer[1][1]] + ]; + }, + + polygon: function (values, refer, scales) { + return map(values, function (item, idx) { + return [item[0] - scales[0] * refer[idx][0], item[1] - scales[1] * refer[idx][1]]; + }); + } +}; + +function axisDiffProcessor(axisNameIndex, values, refer, scales) { + return [ + values[0] - scales[axisNameIndex] * refer[0], + values[1] - scales[axisNameIndex] * refer[1] + ]; +} + +// We have to process scale caused by dataZoom manually, +// although it might be not accurate. +function getScales(xyMinMaxCurr, xyMinMaxOrigin) { + var sizeCurr = getSize(xyMinMaxCurr); + var sizeOrigin = getSize(xyMinMaxOrigin); + var scales = [sizeCurr[0] / sizeOrigin[0], sizeCurr[1] / sizeOrigin[1]]; + isNaN(scales[0]) && (scales[0] = 1); + isNaN(scales[1]) && (scales[1] = 1); + return scales; +} + +function getSize(xyMinMax) { + return xyMinMax + ? [xyMinMax[0][1] - xyMinMax[0][0], xyMinMax[1][1] - xyMinMax[1][0]] + : [NaN, NaN]; +} + +/* +* 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. +*/ + +var each$18 = each$1; + +var ATTR$1 = '\0_ec_hist_store'; + +/** + * @param {module:echarts/model/Global} ecModel + * @param {Object} newSnapshot {dataZoomId, batch: [payloadInfo, ...]} + */ +function push(ecModel, newSnapshot) { + var store = giveStore(ecModel); + + // If previous dataZoom can not be found, + // complete an range with current range. + each$18(newSnapshot, function (batchItem, dataZoomId) { + var i = store.length - 1; + for (; i >= 0; i--) { + var snapshot = store[i]; + if (snapshot[dataZoomId]) { + break; + } + } + if (i < 0) { + // No origin range set, create one by current range. + var dataZoomModel = ecModel.queryComponents( + {mainType: 'dataZoom', subType: 'select', id: dataZoomId} + )[0]; + if (dataZoomModel) { + var percentRange = dataZoomModel.getPercentRange(); + store[0][dataZoomId] = { + dataZoomId: dataZoomId, + start: percentRange[0], + end: percentRange[1] + }; + } + } + }); + + store.push(newSnapshot); +} + +/** + * @param {module:echarts/model/Global} ecModel + * @return {Object} snapshot + */ +function pop(ecModel) { + var store = giveStore(ecModel); + var head = store[store.length - 1]; + store.length > 1 && store.pop(); + + // Find top for all dataZoom. + var snapshot = {}; + each$18(head, function (batchItem, dataZoomId) { + for (var i = store.length - 1; i >= 0; i--) { + var batchItem = store[i][dataZoomId]; + if (batchItem) { + snapshot[dataZoomId] = batchItem; + break; + } + } + }); + + return snapshot; +} + +/** + * @param {module:echarts/model/Global} ecModel + */ +function clear$1(ecModel) { + ecModel[ATTR$1] = null; +} + +/** + * @param {module:echarts/model/Global} ecModel + * @return {number} records. always >= 1. + */ +function count(ecModel) { + return giveStore(ecModel).length; +} + +/** + * [{key: dataZoomId, value: {dataZoomId, range}}, ...] + * History length of each dataZoom may be different. + * this._history[0] is used to store origin range. + * @type {Array.} + */ +function giveStore(ecModel) { + var store = ecModel[ATTR$1]; + if (!store) { + store = ecModel[ATTR$1] = [{}]; + } + return store; +} + +/* +* 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. +*/ + +ComponentModel.registerSubTypeDefaulter('dataZoom', function () { + // Default 'slider' when no type specified. + return 'slider'; +}); + +/* +* 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. +*/ + +var AXIS_DIMS = ['x', 'y', 'z', 'radius', 'angle', 'single']; +// Supported coords. +var COORDS = ['cartesian2d', 'polar', 'singleAxis']; + +/** + * @param {string} coordType + * @return {boolean} + */ +function isCoordSupported(coordType) { + return indexOf(COORDS, coordType) >= 0; +} + +/** + * Create "each" method to iterate names. + * + * @pubilc + * @param {Array.} names + * @param {Array.=} attrs + * @return {Function} + */ +function createNameEach(names, attrs) { + names = names.slice(); + var capitalNames = map(names, capitalFirst); + attrs = (attrs || []).slice(); + var capitalAttrs = map(attrs, capitalFirst); + + return function (callback, context) { + each$1(names, function (name, index) { + var nameObj = {name: name, capital: capitalNames[index]}; + + for (var j = 0; j < attrs.length; j++) { + nameObj[attrs[j]] = name + capitalAttrs[j]; + } + + callback.call(context, nameObj); + }); + }; +} + +/** + * Iterate each dimension name. + * + * @public + * @param {Function} callback The parameter is like: + * { + * name: 'angle', + * capital: 'Angle', + * axis: 'angleAxis', + * axisIndex: 'angleAixs', + * index: 'angleIndex' + * } + * @param {Object} context + */ +var eachAxisDim$1 = createNameEach(AXIS_DIMS, ['axisIndex', 'axis', 'index', 'id']); + +/** + * If tow dataZoomModels has the same axis controlled, we say that they are 'linked'. + * dataZoomModels and 'links' make up one or more graphics. + * This function finds the graphic where the source dataZoomModel is in. + * + * @public + * @param {Function} forEachNode Node iterator. + * @param {Function} forEachEdgeType edgeType iterator + * @param {Function} edgeIdGetter Giving node and edgeType, return an array of edge id. + * @return {Function} Input: sourceNode, Output: Like {nodes: [], dims: {}} + */ +function createLinkedNodesFinder(forEachNode, forEachEdgeType, edgeIdGetter) { + + return function (sourceNode) { + var result = { + nodes: [], + records: {} // key: edgeType.name, value: Object (key: edge id, value: boolean). + }; + + forEachEdgeType(function (edgeType) { + result.records[edgeType.name] = {}; + }); + + if (!sourceNode) { + return result; + } + + absorb(sourceNode, result); + + var existsLink; + do { + existsLink = false; + forEachNode(processSingleNode); + } + while (existsLink); + + function processSingleNode(node) { + if (!isNodeAbsorded(node, result) && isLinked(node, result)) { + absorb(node, result); + existsLink = true; + } + } + + return result; + }; + + function isNodeAbsorded(node, result) { + return indexOf(result.nodes, node) >= 0; + } + + function isLinked(node, result) { + var hasLink = false; + forEachEdgeType(function (edgeType) { + each$1(edgeIdGetter(node, edgeType) || [], function (edgeId) { + result.records[edgeType.name][edgeId] && (hasLink = true); + }); + }); + return hasLink; + } + + function absorb(node, result) { + result.nodes.push(node); + forEachEdgeType(function (edgeType) { + each$1(edgeIdGetter(node, edgeType) || [], function (edgeId) { + result.records[edgeType.name][edgeId] = true; + }); + }); + } +} + +/* +* 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. +*/ + +var each$20 = each$1; +var asc$1 = asc; + +/** + * Operate single axis. + * One axis can only operated by one axis operator. + * Different dataZoomModels may be defined to operate the same axis. + * (i.e. 'inside' data zoom and 'slider' data zoom components) + * So dataZoomModels share one axisProxy in that case. + * + * @class + */ +var AxisProxy = function (dimName, axisIndex, dataZoomModel, ecModel) { + + /** + * @private + * @type {string} + */ + this._dimName = dimName; + + /** + * @private + */ + this._axisIndex = axisIndex; + + /** + * @private + * @type {Array.} + */ + this._valueWindow; + + /** + * @private + * @type {Array.} + */ + this._percentWindow; + + /** + * @private + * @type {Array.} + */ + this._dataExtent; + + /** + * {minSpan, maxSpan, minValueSpan, maxValueSpan} + * @private + * @type {Object} + */ + this._minMaxSpan; + + /** + * @readOnly + * @type {module: echarts/model/Global} + */ + this.ecModel = ecModel; + + /** + * @private + * @type {module: echarts/component/dataZoom/DataZoomModel} + */ + this._dataZoomModel = dataZoomModel; + + // /** + // * @readOnly + // * @private + // */ + // this.hasSeriesStacked; +}; + +AxisProxy.prototype = { + + constructor: AxisProxy, + + /** + * Whether the axisProxy is hosted by dataZoomModel. + * + * @public + * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel + * @return {boolean} + */ + hostedBy: function (dataZoomModel) { + return this._dataZoomModel === dataZoomModel; + }, + + /** + * @return {Array.} Value can only be NaN or finite value. + */ + getDataValueWindow: function () { + return this._valueWindow.slice(); + }, + + /** + * @return {Array.} + */ + getDataPercentWindow: function () { + return this._percentWindow.slice(); + }, + + /** + * @public + * @param {number} axisIndex + * @return {Array} seriesModels + */ + getTargetSeriesModels: function () { + var seriesModels = []; + var ecModel = this.ecModel; + + ecModel.eachSeries(function (seriesModel) { + if (isCoordSupported(seriesModel.get('coordinateSystem'))) { + var dimName = this._dimName; + var axisModel = ecModel.queryComponents({ + mainType: dimName + 'Axis', + index: seriesModel.get(dimName + 'AxisIndex'), + id: seriesModel.get(dimName + 'AxisId') + })[0]; + if (this._axisIndex === (axisModel && axisModel.componentIndex)) { + seriesModels.push(seriesModel); + } + } + }, this); + + return seriesModels; + }, + + getAxisModel: function () { + return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex); + }, + + getOtherAxisModel: function () { + var axisDim = this._dimName; + var ecModel = this.ecModel; + var axisModel = this.getAxisModel(); + var isCartesian = axisDim === 'x' || axisDim === 'y'; + var otherAxisDim; + var coordSysIndexName; + if (isCartesian) { + coordSysIndexName = 'gridIndex'; + otherAxisDim = axisDim === 'x' ? 'y' : 'x'; + } + else { + coordSysIndexName = 'polarIndex'; + otherAxisDim = axisDim === 'angle' ? 'radius' : 'angle'; + } + var foundOtherAxisModel; + ecModel.eachComponent(otherAxisDim + 'Axis', function (otherAxisModel) { + if ((otherAxisModel.get(coordSysIndexName) || 0) + === (axisModel.get(coordSysIndexName) || 0) + ) { + foundOtherAxisModel = otherAxisModel; + } + }); + return foundOtherAxisModel; + }, + + getMinMaxSpan: function () { + return clone(this._minMaxSpan); + }, + + /** + * Only calculate by given range and this._dataExtent, do not change anything. + * + * @param {Object} opt + * @param {number} [opt.start] + * @param {number} [opt.end] + * @param {number} [opt.startValue] + * @param {number} [opt.endValue] + */ + calculateDataWindow: function (opt) { + var dataExtent = this._dataExtent; + var axisModel = this.getAxisModel(); + var scale = axisModel.axis.scale; + var rangePropMode = this._dataZoomModel.getRangePropMode(); + var percentExtent = [0, 100]; + var percentWindow = []; + var valueWindow = []; + var hasPropModeValue; + + each$20(['start', 'end'], function (prop, idx) { + var boundPercent = opt[prop]; + var boundValue = opt[prop + 'Value']; + + // Notice: dataZoom is based either on `percentProp` ('start', 'end') or + // on `valueProp` ('startValue', 'endValue'). (They are based on the data extent + // but not min/max of axis, which will be calculated by data window then). + // The former one is suitable for cases that a dataZoom component controls multiple + // axes with different unit or extent, and the latter one is suitable for accurate + // zoom by pixel (e.g., in dataZoomSelect). + // we use `getRangePropMode()` to mark which prop is used. `rangePropMode` is updated + // only when setOption or dispatchAction, otherwise it remains its original value. + // (Why not only record `percentProp` and always map to `valueProp`? Because + // the map `valueProp` -> `percentProp` -> `valueProp` probably not the original + // `valueProp`. consider two axes constrolled by one dataZoom. They have different + // data extent. All of values that are overflow the `dataExtent` will be calculated + // to percent '100%'). + + if (rangePropMode[idx] === 'percent') { + boundPercent == null && (boundPercent = percentExtent[idx]); + // Use scale.parse to math round for category or time axis. + boundValue = scale.parse(linearMap( + boundPercent, percentExtent, dataExtent + )); + } + else { + hasPropModeValue = true; + boundValue = boundValue == null ? dataExtent[idx] : scale.parse(boundValue); + // Calculating `percent` from `value` may be not accurate, because + // This calculation can not be inversed, because all of values that + // are overflow the `dataExtent` will be calculated to percent '100%' + boundPercent = linearMap( + boundValue, dataExtent, percentExtent + ); + } + + // valueWindow[idx] = round(boundValue); + // percentWindow[idx] = round(boundPercent); + valueWindow[idx] = boundValue; + percentWindow[idx] = boundPercent; + }); + + asc$1(valueWindow); + asc$1(percentWindow); + + // The windows from user calling of `dispatchAction` might be out of the extent, + // or do not obey the `min/maxSpan`, `min/maxValueSpan`. But we dont restrict window + // by `zoomLock` here, because we see `zoomLock` just as a interaction constraint, + // where API is able to initialize/modify the window size even though `zoomLock` + // specified. + var spans = this._minMaxSpan; + hasPropModeValue + ? restrictSet(valueWindow, percentWindow, dataExtent, percentExtent, false) + : restrictSet(percentWindow, valueWindow, percentExtent, dataExtent, true); + + function restrictSet(fromWindow, toWindow, fromExtent, toExtent, toValue) { + var suffix = toValue ? 'Span' : 'ValueSpan'; + sliderMove(0, fromWindow, fromExtent, 'all', spans['min' + suffix], spans['max' + suffix]); + for (var i = 0; i < 2; i++) { + toWindow[i] = linearMap(fromWindow[i], fromExtent, toExtent, true); + toValue && (toWindow[i] = scale.parse(toWindow[i])); + } + } + + return { + valueWindow: valueWindow, + percentWindow: percentWindow + }; + }, + + /** + * Notice: reset should not be called before series.restoreData() called, + * so it is recommanded to be called in "process stage" but not "model init + * stage". + * + * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel + */ + reset: function (dataZoomModel) { + if (dataZoomModel !== this._dataZoomModel) { + return; + } + + var targetSeries = this.getTargetSeriesModels(); + // Culculate data window and data extent, and record them. + this._dataExtent = calculateDataExtent(this, this._dimName, targetSeries); + + // this.hasSeriesStacked = false; + // each(targetSeries, function (series) { + // var data = series.getData(); + // var dataDim = data.mapDimension(this._dimName); + // var stackedDimension = data.getCalculationInfo('stackedDimension'); + // if (stackedDimension && stackedDimension === dataDim) { + // this.hasSeriesStacked = true; + // } + // }, this); + + // `calculateDataWindow` uses min/maxSpan. + setMinMaxSpan(this); + + var dataWindow = this.calculateDataWindow(dataZoomModel.settledOption); + + this._valueWindow = dataWindow.valueWindow; + this._percentWindow = dataWindow.percentWindow; + + // Update axis setting then. + setAxisModel(this); + }, + + /** + * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel + */ + restore: function (dataZoomModel) { + if (dataZoomModel !== this._dataZoomModel) { + return; + } + + this._valueWindow = this._percentWindow = null; + setAxisModel(this, true); + }, + + /** + * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel + */ + filterData: function (dataZoomModel, api) { + if (dataZoomModel !== this._dataZoomModel) { + return; + } + + var axisDim = this._dimName; + var seriesModels = this.getTargetSeriesModels(); + var filterMode = dataZoomModel.get('filterMode'); + var valueWindow = this._valueWindow; + + if (filterMode === 'none') { + return; + } + + // FIXME + // Toolbox may has dataZoom injected. And if there are stacked bar chart + // with NaN data, NaN will be filtered and stack will be wrong. + // So we need to force the mode to be set empty. + // In fect, it is not a big deal that do not support filterMode-'filter' + // when using toolbox#dataZoom, utill tooltip#dataZoom support "single axis + // selection" some day, which might need "adapt to data extent on the + // otherAxis", which is disabled by filterMode-'empty'. + // But currently, stack has been fixed to based on value but not index, + // so this is not an issue any more. + // var otherAxisModel = this.getOtherAxisModel(); + // if (dataZoomModel.get('$fromToolbox') + // && otherAxisModel + // && otherAxisModel.hasSeriesStacked + // ) { + // filterMode = 'empty'; + // } + + // TODO + // filterMode 'weakFilter' and 'empty' is not optimized for huge data yet. + + each$20(seriesModels, function (seriesModel) { + var seriesData = seriesModel.getData(); + var dataDims = seriesData.mapDimension(axisDim, true); + + if (!dataDims.length) { + return; + } + + if (filterMode === 'weakFilter') { + seriesData.filterSelf(function (dataIndex) { + var leftOut; + var rightOut; + var hasValue; + for (var i = 0; i < dataDims.length; i++) { + var value = seriesData.get(dataDims[i], dataIndex); + var thisHasValue = !isNaN(value); + var thisLeftOut = value < valueWindow[0]; + var thisRightOut = value > valueWindow[1]; + if (thisHasValue && !thisLeftOut && !thisRightOut) { + return true; + } + thisHasValue && (hasValue = true); + thisLeftOut && (leftOut = true); + thisRightOut && (rightOut = true); + } + // If both left out and right out, do not filter. + return hasValue && leftOut && rightOut; + }); + } + else { + each$20(dataDims, function (dim) { + if (filterMode === 'empty') { + seriesModel.setData( + seriesData = seriesData.map(dim, function (value) { + return !isInWindow(value) ? NaN : value; + }) + ); + } + else { + var range = {}; + range[dim] = valueWindow; + + // console.time('select'); + seriesData.selectRange(range); + // console.timeEnd('select'); + } + }); + } + + each$20(dataDims, function (dim) { + seriesData.setApproximateExtent(valueWindow, dim); + }); + }); + + function isInWindow(value) { + return value >= valueWindow[0] && value <= valueWindow[1]; + } + } +}; + +function calculateDataExtent(axisProxy, axisDim, seriesModels) { + var dataExtent = [Infinity, -Infinity]; + + each$20(seriesModels, function (seriesModel) { + var seriesData = seriesModel.getData(); + if (seriesData) { + each$20(seriesData.mapDimension(axisDim, true), function (dim) { + var seriesExtent = seriesData.getApproximateExtent(dim); + seriesExtent[0] < dataExtent[0] && (dataExtent[0] = seriesExtent[0]); + seriesExtent[1] > dataExtent[1] && (dataExtent[1] = seriesExtent[1]); + }); + } + }); + + if (dataExtent[1] < dataExtent[0]) { + dataExtent = [NaN, NaN]; + } + + // It is important to get "consistent" extent when more then one axes is + // controlled by a `dataZoom`, otherwise those axes will not be synchronized + // when zooming. But it is difficult to know what is "consistent", considering + // axes have different type or even different meanings (For example, two + // time axes are used to compare data of the same date in different years). + // So basically dataZoom just obtains extent by series.data (in category axis + // extent can be obtained from axis.data). + // Nevertheless, user can set min/max/scale on axes to make extent of axes + // consistent. + fixExtentByAxis(axisProxy, dataExtent); + + return dataExtent; +} + +function fixExtentByAxis(axisProxy, dataExtent) { + var axisModel = axisProxy.getAxisModel(); + var min = axisModel.getMin(true); + + // For category axis, if min/max/scale are not set, extent is determined + // by axis.data by default. + var isCategoryAxis = axisModel.get('type') === 'category'; + var axisDataLen = isCategoryAxis && axisModel.getCategories().length; + + if (min != null && min !== 'dataMin' && typeof min !== 'function') { + dataExtent[0] = min; + } + else if (isCategoryAxis) { + dataExtent[0] = axisDataLen > 0 ? 0 : NaN; + } + + var max = axisModel.getMax(true); + if (max != null && max !== 'dataMax' && typeof max !== 'function') { + dataExtent[1] = max; + } + else if (isCategoryAxis) { + dataExtent[1] = axisDataLen > 0 ? axisDataLen - 1 : NaN; + } + + if (!axisModel.get('scale', true)) { + dataExtent[0] > 0 && (dataExtent[0] = 0); + dataExtent[1] < 0 && (dataExtent[1] = 0); + } + + // For value axis, if min/max/scale are not set, we just use the extent obtained + // by series data, which may be a little different from the extent calculated by + // `axisHelper.getScaleExtent`. But the different just affects the experience a + // little when zooming. So it will not be fixed until some users require it strongly. + + return dataExtent; +} + +function setAxisModel(axisProxy, isRestore) { + var axisModel = axisProxy.getAxisModel(); + + var percentWindow = axisProxy._percentWindow; + var valueWindow = axisProxy._valueWindow; + + if (!percentWindow) { + return; + } + + // [0, 500]: arbitrary value, guess axis extent. + var precision = getPixelPrecision(valueWindow, [0, 500]); + precision = Math.min(precision, 20); + // isRestore or isFull + var useOrigin = isRestore || (percentWindow[0] === 0 && percentWindow[1] === 100); + + axisModel.setRange( + useOrigin ? null : +valueWindow[0].toFixed(precision), + useOrigin ? null : +valueWindow[1].toFixed(precision) + ); +} + +function setMinMaxSpan(axisProxy) { + var minMaxSpan = axisProxy._minMaxSpan = {}; + var dataZoomModel = axisProxy._dataZoomModel; + var dataExtent = axisProxy._dataExtent; + + each$20(['min', 'max'], function (minMax) { + var percentSpan = dataZoomModel.get(minMax + 'Span'); + var valueSpan = dataZoomModel.get(minMax + 'ValueSpan'); + valueSpan != null && (valueSpan = axisProxy.getAxisModel().axis.scale.parse(valueSpan)); + + // minValueSpan and maxValueSpan has higher priority than minSpan and maxSpan + if (valueSpan != null) { + percentSpan = linearMap( + dataExtent[0] + valueSpan, dataExtent, [0, 100], true + ); + } + else if (percentSpan != null) { + valueSpan = linearMap( + percentSpan, [0, 100], dataExtent, true + ) - dataExtent[0]; + } + + minMaxSpan[minMax + 'Span'] = percentSpan; + minMaxSpan[minMax + 'ValueSpan'] = valueSpan; + }); +} + +/* +* 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. +*/ + +var each$19 = each$1; +var eachAxisDim = eachAxisDim$1; + +var DataZoomModel = extendComponentModel({ + + type: 'dataZoom', + + dependencies: [ + 'xAxis', 'yAxis', 'zAxis', 'radiusAxis', 'angleAxis', 'singleAxis', 'series' + ], + + /** + * @protected + */ + defaultOption: { + zlevel: 0, + z: 4, // Higher than normal component (z: 2). + orient: null, // Default auto by axisIndex. Possible value: 'horizontal', 'vertical'. + xAxisIndex: null, // Default the first horizontal category axis. + yAxisIndex: null, // Default the first vertical category axis. + + filterMode: 'filter', // Possible values: 'filter' or 'empty' or 'weakFilter'. + // 'filter': data items which are out of window will be removed. This option is + // applicable when filtering outliers. For each data item, it will be + // filtered if one of the relevant dimensions is out of the window. + // 'weakFilter': data items which are out of window will be removed. This option + // is applicable when filtering outliers. For each data item, it will be + // filtered only if all of the relevant dimensions are out of the same + // side of the window. + // 'empty': data items which are out of window will be set to empty. + // This option is applicable when user should not neglect + // that there are some data items out of window. + // 'none': Do not filter. + // Taking line chart as an example, line will be broken in + // the filtered points when filterModel is set to 'empty', but + // be connected when set to 'filter'. + + throttle: null, // Dispatch action by the fixed rate, avoid frequency. + // default 100. Do not throttle when use null/undefined. + // If animation === true and animationDurationUpdate > 0, + // default value is 100, otherwise 20. + start: 0, // Start percent. 0 ~ 100 + end: 100, // End percent. 0 ~ 100 + startValue: null, // Start value. If startValue specified, start is ignored. + endValue: null, // End value. If endValue specified, end is ignored. + minSpan: null, // 0 ~ 100 + maxSpan: null, // 0 ~ 100 + minValueSpan: null, // The range of dataZoom can not be smaller than that. + maxValueSpan: null, // The range of dataZoom can not be larger than that. + rangeMode: null // Array, can be 'value' or 'percent'. + }, + + /** + * @override + */ + init: function (option, parentModel, ecModel) { + + /** + * key like x_0, y_1 + * @private + * @type {Object} + */ + this._dataIntervalByAxis = {}; + + /** + * @private + */ + this._dataInfo = {}; + + /** + * key like x_0, y_1 + * @private + */ + this._axisProxies = {}; + + /** + * @readOnly + */ + this.textStyleModel; + + /** + * @private + */ + this._autoThrottle = true; + + /** + * It is `[rangeModeForMin, rangeModeForMax]`. + * The optional values for `rangeMode`: + * + `'value'` mode: the axis extent will always be determined by + * `dataZoom.startValue` and `dataZoom.endValue`, despite + * how data like and how `axis.min` and `axis.max` are. + * + `'percent'` mode: `100` represents 100% of the `[dMin, dMax]`, + * where `dMin` is `axis.min` if `axis.min` specified, otherwise `data.extent[0]`, + * and `dMax` is `axis.max` if `axis.max` specified, otherwise `data.extent[1]`. + * Axis extent will be determined by the result of the percent of `[dMin, dMax]`. + * + * For example, when users are using dynamic data (update data periodically via `setOption`), + * if in `'value`' mode, the window will be kept in a fixed value range despite how + * data are appended, while if in `'percent'` mode, whe window range will be changed alone with + * the appended data (suppose `axis.min` and `axis.max` are not specified). + * + * @private + */ + this._rangePropMode = ['percent', 'percent']; + + var inputRawOption = retrieveRawOption(option); + + /** + * Suppose a "main process" start at the point that model prepared (that is, + * model initialized or merged or method called in `action`). + * We should keep the `main process` idempotent, that is, given a set of values + * on `option`, we get the same result. + * + * But sometimes, values on `option` will be updated for providing users + * a "final calculated value" (`dataZoomProcessor` will do that). Those value + * should not be the base/input of the `main process`. + * + * So in that case we should save and keep the input of the `main process` + * separately, called `settledOption`. + * + * For example, consider the case: + * (Step_1) brush zoom the grid by `toolbox.dataZoom`, + * where the original input `option.startValue`, `option.endValue` are earsed by + * calculated value. + * (Step)2) click the legend to hide and show a series, + * where the new range is calculated by the earsed `startValue` and `endValue`, + * which brings incorrect result. + * + * @readOnly + */ + this.settledOption = inputRawOption; + + this.mergeDefaultAndTheme(option, ecModel); + + this.doInit(inputRawOption); + }, + + /** + * @override + */ + mergeOption: function (newOption) { + var inputRawOption = retrieveRawOption(newOption); + + //FIX #2591 + merge(this.option, newOption, true); + merge(this.settledOption, inputRawOption, true); + + this.doInit(inputRawOption); + }, + + /** + * @protected + */ + doInit: function (inputRawOption) { + var thisOption = this.option; + + // Disable realtime view update if canvas is not supported. + if (!env$1.canvasSupported) { + thisOption.realtime = false; + } + + this._setDefaultThrottle(inputRawOption); + + updateRangeUse(this, inputRawOption); + + var settledOption = this.settledOption; + each$19([['start', 'startValue'], ['end', 'endValue']], function (names, index) { + // start/end has higher priority over startValue/endValue if they + // both set, but we should make chart.setOption({endValue: 1000}) + // effective, rather than chart.setOption({endValue: 1000, end: null}). + if (this._rangePropMode[index] === 'value') { + thisOption[names[0]] = settledOption[names[0]] = null; + } + // Otherwise do nothing and use the merge result. + }, this); + + this.textStyleModel = this.getModel('textStyle'); + + this._resetTarget(); + + this._giveAxisProxies(); + }, + + /** + * @private + */ + _giveAxisProxies: function () { + var axisProxies = this._axisProxies; + + this.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel, ecModel) { + var axisModel = this.dependentModels[dimNames.axis][axisIndex]; + + // If exists, share axisProxy with other dataZoomModels. + var axisProxy = axisModel.__dzAxisProxy || ( + // Use the first dataZoomModel as the main model of axisProxy. + axisModel.__dzAxisProxy = new AxisProxy( + dimNames.name, axisIndex, this, ecModel + ) + ); + // FIXME + // dispose __dzAxisProxy + + axisProxies[dimNames.name + '_' + axisIndex] = axisProxy; + }, this); + }, + + /** + * @private + */ + _resetTarget: function () { + var thisOption = this.option; + + var autoMode = this._judgeAutoMode(); + + eachAxisDim(function (dimNames) { + var axisIndexName = dimNames.axisIndex; + thisOption[axisIndexName] = normalizeToArray( + thisOption[axisIndexName] + ); + }, this); + + if (autoMode === 'axisIndex') { + this._autoSetAxisIndex(); + } + else if (autoMode === 'orient') { + this._autoSetOrient(); + } + }, + + /** + * @private + */ + _judgeAutoMode: function () { + // Auto set only works for setOption at the first time. + // The following is user's reponsibility. So using merged + // option is OK. + var thisOption = this.option; + + var hasIndexSpecified = false; + eachAxisDim(function (dimNames) { + // When user set axisIndex as a empty array, we think that user specify axisIndex + // but do not want use auto mode. Because empty array may be encountered when + // some error occured. + if (thisOption[dimNames.axisIndex] != null) { + hasIndexSpecified = true; + } + }, this); + + var orient = thisOption.orient; + + if (orient == null && hasIndexSpecified) { + return 'orient'; + } + else if (!hasIndexSpecified) { + if (orient == null) { + thisOption.orient = 'horizontal'; + } + return 'axisIndex'; + } + }, + + /** + * @private + */ + _autoSetAxisIndex: function () { + var autoAxisIndex = true; + var orient = this.get('orient', true); + var thisOption = this.option; + var dependentModels = this.dependentModels; + + if (autoAxisIndex) { + // Find axis that parallel to dataZoom as default. + var dimName = orient === 'vertical' ? 'y' : 'x'; + + if (dependentModels[dimName + 'Axis'].length) { + thisOption[dimName + 'AxisIndex'] = [0]; + autoAxisIndex = false; + } + else { + each$19(dependentModels.singleAxis, function (singleAxisModel) { + if (autoAxisIndex && singleAxisModel.get('orient', true) === orient) { + thisOption.singleAxisIndex = [singleAxisModel.componentIndex]; + autoAxisIndex = false; + } + }); + } + } + + if (autoAxisIndex) { + // Find the first category axis as default. (consider polar) + eachAxisDim(function (dimNames) { + if (!autoAxisIndex) { + return; + } + var axisIndices = []; + var axisModels = this.dependentModels[dimNames.axis]; + if (axisModels.length && !axisIndices.length) { + for (var i = 0, len = axisModels.length; i < len; i++) { + if (axisModels[i].get('type') === 'category') { + axisIndices.push(i); + } + } + } + thisOption[dimNames.axisIndex] = axisIndices; + if (axisIndices.length) { + autoAxisIndex = false; + } + }, this); + } + + if (autoAxisIndex) { + // FIXME + // 这里是兼容ec2的写法(没指定xAxisIndex和yAxisIndex时把scatter和双数值轴折柱纳入dataZoom控制), + // 但是实际是否需要Grid.js#getScaleByOption来判断(考虑time,log等axis type)? + + // If both dataZoom.xAxisIndex and dataZoom.yAxisIndex is not specified, + // dataZoom component auto adopts series that reference to + // both xAxis and yAxis which type is 'value'. + this.ecModel.eachSeries(function (seriesModel) { + if (this._isSeriesHasAllAxesTypeOf(seriesModel, 'value')) { + eachAxisDim(function (dimNames) { + var axisIndices = thisOption[dimNames.axisIndex]; + + var axisIndex = seriesModel.get(dimNames.axisIndex); + var axisId = seriesModel.get(dimNames.axisId); + + var axisModel = seriesModel.ecModel.queryComponents({ + mainType: dimNames.axis, + index: axisIndex, + id: axisId + })[0]; + + if (__DEV__) { + if (!axisModel) { + throw new Error( + dimNames.axis + ' "' + retrieve( + axisIndex, + axisId, + 0 + ) + '" not found' + ); + } + } + axisIndex = axisModel.componentIndex; + + if (indexOf(axisIndices, axisIndex) < 0) { + axisIndices.push(axisIndex); + } + }); + } + }, this); + } + }, + + /** + * @private + */ + _autoSetOrient: function () { + var dim; + + // Find the first axis + this.eachTargetAxis(function (dimNames) { + !dim && (dim = dimNames.name); + }, this); + + this.option.orient = dim === 'y' ? 'vertical' : 'horizontal'; + }, + + /** + * @private + */ + _isSeriesHasAllAxesTypeOf: function (seriesModel, axisType) { + // FIXME + // 需要series的xAxisIndex和yAxisIndex都首先自动设置上。 + // 例如series.type === scatter时。 + + var is = true; + eachAxisDim(function (dimNames) { + var seriesAxisIndex = seriesModel.get(dimNames.axisIndex); + var axisModel = this.dependentModels[dimNames.axis][seriesAxisIndex]; + + if (!axisModel || axisModel.get('type') !== axisType) { + is = false; + } + }, this); + return is; + }, + + /** + * @private + */ + _setDefaultThrottle: function (inputRawOption) { + // When first time user set throttle, auto throttle ends. + if (inputRawOption.hasOwnProperty('throttle')) { + this._autoThrottle = false; + } + if (this._autoThrottle) { + var globalOption = this.ecModel.option; + this.option.throttle = ( + globalOption.animation && globalOption.animationDurationUpdate > 0 + ) ? 100 : 20; + } + }, + + /** + * @public + */ + getFirstTargetAxisModel: function () { + var firstAxisModel; + eachAxisDim(function (dimNames) { + if (firstAxisModel == null) { + var indices = this.get(dimNames.axisIndex); + if (indices.length) { + firstAxisModel = this.dependentModels[dimNames.axis][indices[0]]; + } + } + }, this); + + return firstAxisModel; + }, + + /** + * @public + * @param {Function} callback param: axisModel, dimNames, axisIndex, dataZoomModel, ecModel + */ + eachTargetAxis: function (callback, context) { + var ecModel = this.ecModel; + eachAxisDim(function (dimNames) { + each$19( + this.get(dimNames.axisIndex), + function (axisIndex) { + callback.call(context, dimNames, axisIndex, this, ecModel); + }, + this + ); + }, this); + }, + + /** + * @param {string} dimName + * @param {number} axisIndex + * @return {module:echarts/component/dataZoom/AxisProxy} If not found, return null/undefined. + */ + getAxisProxy: function (dimName, axisIndex) { + return this._axisProxies[dimName + '_' + axisIndex]; + }, + + /** + * @param {string} dimName + * @param {number} axisIndex + * @return {module:echarts/model/Model} If not found, return null/undefined. + */ + getAxisModel: function (dimName, axisIndex) { + var axisProxy = this.getAxisProxy(dimName, axisIndex); + return axisProxy && axisProxy.getAxisModel(); + }, + + /** + * If not specified, set to undefined. + * + * @public + * @param {Object} opt + * @param {number} [opt.start] + * @param {number} [opt.end] + * @param {number} [opt.startValue] + * @param {number} [opt.endValue] + */ + setRawRange: function (opt) { + var thisOption = this.option; + var settledOption = this.settledOption; + each$19([['start', 'startValue'], ['end', 'endValue']], function (names) { + // Consider the pair : + // If one has value and the other one is `null/undefined`, we both set them + // to `settledOption`. This strategy enables the feature to clear the original + // value in `settledOption` to `null/undefined`. + // But if both of them are `null/undefined`, we do not set them to `settledOption` + // and keep `settledOption` with the original value. This strategy enables users to + // only set but not set when calling + // `dispatchAction`. + // The pair is treated in the same way. + if (opt[names[0]] != null || opt[names[1]] != null) { + thisOption[names[0]] = settledOption[names[0]] = opt[names[0]]; + thisOption[names[1]] = settledOption[names[1]] = opt[names[1]]; + } + }, this); + + updateRangeUse(this, opt); + }, + + /** + * @public + * @param {Object} opt + * @param {number} [opt.start] + * @param {number} [opt.end] + * @param {number} [opt.startValue] + * @param {number} [opt.endValue] + */ + setCalculatedRange: function (opt) { + var option = this.option; + each$19(['start', 'startValue', 'end', 'endValue'], function (name) { + option[name] = opt[name]; + }); + }, + + /** + * @public + * @return {Array.} [startPercent, endPercent] + */ + getPercentRange: function () { + var axisProxy = this.findRepresentativeAxisProxy(); + if (axisProxy) { + return axisProxy.getDataPercentWindow(); + } + }, + + /** + * @public + * For example, chart.getModel().getComponent('dataZoom').getValueRange('y', 0); + * + * @param {string} [axisDimName] + * @param {number} [axisIndex] + * @return {Array.} [startValue, endValue] value can only be '-' or finite number. + */ + getValueRange: function (axisDimName, axisIndex) { + if (axisDimName == null && axisIndex == null) { + var axisProxy = this.findRepresentativeAxisProxy(); + if (axisProxy) { + return axisProxy.getDataValueWindow(); + } + } + else { + return this.getAxisProxy(axisDimName, axisIndex).getDataValueWindow(); + } + }, + + /** + * @public + * @param {module:echarts/model/Model} [axisModel] If axisModel given, find axisProxy + * corresponding to the axisModel + * @return {module:echarts/component/dataZoom/AxisProxy} + */ + findRepresentativeAxisProxy: function (axisModel) { + if (axisModel) { + return axisModel.__dzAxisProxy; + } + + // Find the first hosted axisProxy + var axisProxies = this._axisProxies; + for (var key in axisProxies) { + if (axisProxies.hasOwnProperty(key) && axisProxies[key].hostedBy(this)) { + return axisProxies[key]; + } + } + + // If no hosted axis find not hosted axisProxy. + // Consider this case: dataZoomModel1 and dataZoomModel2 control the same axis, + // and the option.start or option.end settings are different. The percentRange + // should follow axisProxy. + // (We encounter this problem in toolbox data zoom.) + for (var key in axisProxies) { + if (axisProxies.hasOwnProperty(key) && !axisProxies[key].hostedBy(this)) { + return axisProxies[key]; + } + } + }, + + /** + * @return {Array.} + */ + getRangePropMode: function () { + return this._rangePropMode.slice(); + } + +}); + +/** + * Retrieve the those raw params from option, which will be cached separately. + * becasue they will be overwritten by normalized/calculated values in the main + * process. + */ +function retrieveRawOption(option) { + var ret = {}; + each$19( + ['start', 'end', 'startValue', 'endValue', 'throttle'], + function (name) { + option.hasOwnProperty(name) && (ret[name] = option[name]); + } + ); + return ret; +} + +function updateRangeUse(dataZoomModel, inputRawOption) { + var rangePropMode = dataZoomModel._rangePropMode; + var rangeModeInOption = dataZoomModel.get('rangeMode'); + + each$19([['start', 'startValue'], ['end', 'endValue']], function (names, index) { + var percentSpecified = inputRawOption[names[0]] != null; + var valueSpecified = inputRawOption[names[1]] != null; + if (percentSpecified && !valueSpecified) { + rangePropMode[index] = 'percent'; + } + else if (!percentSpecified && valueSpecified) { + rangePropMode[index] = 'value'; + } + else if (rangeModeInOption) { + rangePropMode[index] = rangeModeInOption[index]; + } + else if (percentSpecified) { // percentSpecified && valueSpecified + rangePropMode[index] = 'percent'; + } + // else remain its original setting. + }); +} + +/* +* 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. +*/ + +var DataZoomView = Component$1.extend({ + + type: 'dataZoom', + + render: function (dataZoomModel, ecModel, api, payload) { + this.dataZoomModel = dataZoomModel; + this.ecModel = ecModel; + this.api = api; + }, + + /** + * Find the first target coordinate system. + * + * @protected + * @return {Object} { + * grid: [ + * {model: coord0, axisModels: [axis1, axis3], coordIndex: 1}, + * {model: coord1, axisModels: [axis0, axis2], coordIndex: 0}, + * ... + * ], // cartesians must not be null/undefined. + * polar: [ + * {model: coord0, axisModels: [axis4], coordIndex: 0}, + * ... + * ], // polars must not be null/undefined. + * singleAxis: [ + * {model: coord0, axisModels: [], coordIndex: 0} + * ] + */ + getTargetCoordInfo: function () { + var dataZoomModel = this.dataZoomModel; + var ecModel = this.ecModel; + var coordSysLists = {}; + + dataZoomModel.eachTargetAxis(function (dimNames, axisIndex) { + var axisModel = ecModel.getComponent(dimNames.axis, axisIndex); + if (axisModel) { + var coordModel = axisModel.getCoordSysModel(); + coordModel && save( + coordModel, + axisModel, + coordSysLists[coordModel.mainType] || (coordSysLists[coordModel.mainType] = []), + coordModel.componentIndex + ); + } + }, this); + + function save(coordModel, axisModel, store, coordIndex) { + var item; + for (var i = 0; i < store.length; i++) { + if (store[i].model === coordModel) { + item = store[i]; + break; + } + } + if (!item) { + store.push(item = { + model: coordModel, axisModels: [], coordIndex: coordIndex + }); + } + item.axisModels.push(axisModel); + } + + return coordSysLists; + } + +}); + +/* +* 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. +*/ + +DataZoomModel.extend({ + type: 'dataZoom.select' +}); + +/* +* 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. +*/ + +DataZoomView.extend({ + type: 'dataZoom.select' +}); + +/* +* 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. +*/ + +registerProcessor({ + + // `dataZoomProcessor` will only be performed in needed series. Consider if + // there is a line series and a pie series, it is better not to update the + // line series if only pie series is needed to be updated. + getTargetSeries: function (ecModel) { + var seriesModelMap = createHashMap(); + + ecModel.eachComponent('dataZoom', function (dataZoomModel) { + dataZoomModel.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel) { + var axisProxy = dataZoomModel.getAxisProxy(dimNames.name, axisIndex); + each$1(axisProxy.getTargetSeriesModels(), function (seriesModel) { + seriesModelMap.set(seriesModel.uid, seriesModel); + }); + }); + }); + + return seriesModelMap; + }, + + modifyOutputEnd: true, + + // Consider appendData, where filter should be performed. Because data process is + // in block mode currently, it is not need to worry about that the overallProgress + // execute every frame. + overallReset: function (ecModel, api) { + + ecModel.eachComponent('dataZoom', function (dataZoomModel) { + // We calculate window and reset axis here but not in model + // init stage and not after action dispatch handler, because + // reset should be called after seriesData.restoreData. + dataZoomModel.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel) { + dataZoomModel.getAxisProxy(dimNames.name, axisIndex).reset(dataZoomModel, api); + }); + + // Caution: data zoom filtering is order sensitive when using + // percent range and no min/max/scale set on axis. + // For example, we have dataZoom definition: + // [ + // {xAxisIndex: 0, start: 30, end: 70}, + // {yAxisIndex: 0, start: 20, end: 80} + // ] + // In this case, [20, 80] of y-dataZoom should be based on data + // that have filtered by x-dataZoom using range of [30, 70], + // but should not be based on full raw data. Thus sliding + // x-dataZoom will change both ranges of xAxis and yAxis, + // while sliding y-dataZoom will only change the range of yAxis. + // So we should filter x-axis after reset x-axis immediately, + // and then reset y-axis and filter y-axis. + dataZoomModel.eachTargetAxis(function (dimNames, axisIndex, dataZoomModel) { + dataZoomModel.getAxisProxy(dimNames.name, axisIndex).filterData(dataZoomModel, api); + }); + }); + + ecModel.eachComponent('dataZoom', function (dataZoomModel) { + // Fullfill all of the range props so that user + // is able to get them from chart.getOption(). + var axisProxy = dataZoomModel.findRepresentativeAxisProxy(); + var percentRange = axisProxy.getDataPercentWindow(); + var valueRange = axisProxy.getDataValueWindow(); + + dataZoomModel.setCalculatedRange({ + start: percentRange[0], + end: percentRange[1], + startValue: valueRange[0], + endValue: valueRange[1] + }); + }); + } +}); + +/* +* 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. +*/ + +registerAction('dataZoom', function (payload, ecModel) { + + var linkedNodesFinder = createLinkedNodesFinder( + bind(ecModel.eachComponent, ecModel, 'dataZoom'), + eachAxisDim$1, + function (model, dimNames) { + return model.get(dimNames.axisIndex); + } + ); + + var effectedModels = []; + + ecModel.eachComponent( + {mainType: 'dataZoom', query: payload}, + function (model, index) { + effectedModels.push.apply( + effectedModels, linkedNodesFinder(model).nodes + ); + } + ); + + each$1(effectedModels, function (dataZoomModel, index) { + dataZoomModel.setRawRange({ + start: payload.start, + end: payload.end, + startValue: payload.startValue, + endValue: payload.endValue + }); + }); + +}); + +/* +* 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. +*/ + +/** + * Only work for toolbox dataZoom. User + * MUST NOT import this module directly. + */ + +/* +* 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. +*/ + +// Use dataZoomSelect +var dataZoomLang = lang.toolbox.dataZoom; +var each$16 = each$1; + +// Spectial component id start with \0ec\0, see echarts/model/Global.js~hasInnerId +var DATA_ZOOM_ID_BASE = '\0_ec_\0toolbox-dataZoom_'; + +function DataZoom(model, ecModel, api) { + + /** + * @private + * @type {module:echarts/component/helper/BrushController} + */ + (this._brushController = new BrushController(api.getZr())) + .on('brush', bind(this._onBrush, this)) + .mount(); + + /** + * @private + * @type {boolean} + */ + this._isZoomActive; +} + +DataZoom.defaultOption = { + show: true, + filterMode: 'filter', + // Icon group + icon: { + zoom: 'M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1', + back: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26' + }, + // `zoom`, `back` + title: clone(dataZoomLang.title) +}; + +var proto$4 = DataZoom.prototype; + +proto$4.render = function (featureModel, ecModel, api, payload) { + this.model = featureModel; + this.ecModel = ecModel; + this.api = api; + + updateZoomBtnStatus(featureModel, ecModel, this, payload, api); + updateBackBtnStatus(featureModel, ecModel); +}; + +proto$4.onclick = function (ecModel, api, type) { + handlers$1[type].call(this); +}; + +proto$4.remove = function (ecModel, api) { + this._brushController.unmount(); +}; + +proto$4.dispose = function (ecModel, api) { + this._brushController.dispose(); +}; + +/** + * @private + */ +var handlers$1 = { + + zoom: function () { + var nextActive = !this._isZoomActive; + + this.api.dispatchAction({ + type: 'takeGlobalCursor', + key: 'dataZoomSelect', + dataZoomSelectActive: nextActive + }); + }, + + back: function () { + this._dispatchZoomAction(pop(this.ecModel)); + } +}; + +/** + * @private + */ +proto$4._onBrush = function (areas, opt) { + if (!opt.isEnd || !areas.length) { + return; + } + var snapshot = {}; + var ecModel = this.ecModel; + + this._brushController.updateCovers([]); // remove cover + + var brushTargetManager = new BrushTargetManager( + retrieveAxisSetting(this.model.option), ecModel, {include: ['grid']} + ); + brushTargetManager.matchOutputRanges(areas, ecModel, function (area, coordRange, coordSys) { + if (coordSys.type !== 'cartesian2d') { + return; + } + + var brushType = area.brushType; + if (brushType === 'rect') { + setBatch('x', coordSys, coordRange[0]); + setBatch('y', coordSys, coordRange[1]); + } + else { + setBatch(({lineX: 'x', lineY: 'y'})[brushType], coordSys, coordRange); + } + }); + + push(ecModel, snapshot); + + this._dispatchZoomAction(snapshot); + + function setBatch(dimName, coordSys, minMax) { + var axis = coordSys.getAxis(dimName); + var axisModel = axis.model; + var dataZoomModel = findDataZoom(dimName, axisModel, ecModel); + + // Restrict range. + var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy(axisModel).getMinMaxSpan(); + if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) { + minMax = sliderMove( + 0, minMax.slice(), axis.scale.getExtent(), 0, + minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan + ); + } + + dataZoomModel && (snapshot[dataZoomModel.id] = { + dataZoomId: dataZoomModel.id, + startValue: minMax[0], + endValue: minMax[1] + }); + } + + function findDataZoom(dimName, axisModel, ecModel) { + var found; + ecModel.eachComponent({mainType: 'dataZoom', subType: 'select'}, function (dzModel) { + var has = dzModel.getAxisModel(dimName, axisModel.componentIndex); + has && (found = dzModel); + }); + return found; + } +}; + +/** + * @private + */ +proto$4._dispatchZoomAction = function (snapshot) { + var batch = []; + + // Convert from hash map to array. + each$16(snapshot, function (batchItem, dataZoomId) { + batch.push(clone(batchItem)); + }); + + batch.length && this.api.dispatchAction({ + type: 'dataZoom', + from: this.uid, + batch: batch + }); +}; + +function retrieveAxisSetting(option) { + var setting = {}; + // Compatible with previous setting: null => all axis, false => no axis. + each$1(['xAxisIndex', 'yAxisIndex'], function (name) { + setting[name] = option[name]; + setting[name] == null && (setting[name] = 'all'); + (setting[name] === false || setting[name] === 'none') && (setting[name] = []); + }); + return setting; +} + +function updateBackBtnStatus(featureModel, ecModel) { + featureModel.setIconStatus( + 'back', + count(ecModel) > 1 ? 'emphasis' : 'normal' + ); +} + +function updateZoomBtnStatus(featureModel, ecModel, view, payload, api) { + var zoomActive = view._isZoomActive; + + if (payload && payload.type === 'takeGlobalCursor') { + zoomActive = payload.key === 'dataZoomSelect' + ? payload.dataZoomSelectActive : false; + } + + view._isZoomActive = zoomActive; + + featureModel.setIconStatus('zoom', zoomActive ? 'emphasis' : 'normal'); + + var brushTargetManager = new BrushTargetManager( + retrieveAxisSetting(featureModel.option), ecModel, {include: ['grid']} + ); + + view._brushController + .setPanels(brushTargetManager.makePanelOpts(api, function (targetInfo) { + return (targetInfo.xAxisDeclared && !targetInfo.yAxisDeclared) + ? 'lineX' + : (!targetInfo.xAxisDeclared && targetInfo.yAxisDeclared) + ? 'lineY' + : 'rect'; + })) + .enableBrush( + zoomActive + ? { + brushType: 'auto', + brushStyle: { + // FIXME user customized? + lineWidth: 0, + fill: 'rgba(0,0,0,0.2)' + } + } + : false + ); +} + + +register$1('dataZoom', DataZoom); + + +// Create special dataZoom option for select +// FIXME consider the case of merge option, where axes options are not exists. +registerPreprocessor(function (option) { + if (!option) { + return; + } + + var dataZoomOpts = option.dataZoom || (option.dataZoom = []); + if (!isArray(dataZoomOpts)) { + option.dataZoom = dataZoomOpts = [dataZoomOpts]; + } + + var toolboxOpt = option.toolbox; + if (toolboxOpt) { + // Assume there is only one toolbox + if (isArray(toolboxOpt)) { + toolboxOpt = toolboxOpt[0]; + } + + if (toolboxOpt && toolboxOpt.feature) { + var dataZoomOpt = toolboxOpt.feature.dataZoom; + // FIXME: If add dataZoom when setOption in merge mode, + // no axis info to be added. See `test/dataZoom-extreme.html` + addForAxis('xAxis', dataZoomOpt); + addForAxis('yAxis', dataZoomOpt); + } + } + + function addForAxis(axisName, dataZoomOpt) { + if (!dataZoomOpt) { + return; + } + + // Try not to modify model, because it is not merged yet. + var axisIndicesName = axisName + 'Index'; + var givenAxisIndices = dataZoomOpt[axisIndicesName]; + if (givenAxisIndices != null + && givenAxisIndices !== 'all' + && !isArray(givenAxisIndices) + ) { + givenAxisIndices = (givenAxisIndices === false || givenAxisIndices === 'none') ? [] : [givenAxisIndices]; + } + + forEachComponent(axisName, function (axisOpt, axisIndex) { + if (givenAxisIndices != null + && givenAxisIndices !== 'all' + && indexOf(givenAxisIndices, axisIndex) === -1 + ) { + return; + } + var newOpt = { + type: 'select', + $fromToolbox: true, + // Default to be filter + filterMode: dataZoomOpt.filterMode || 'filter', + // Id for merge mapping. + id: DATA_ZOOM_ID_BASE + axisName + axisIndex + }; + // FIXME + // Only support one axis now. + newOpt[axisIndicesName] = axisIndex; + dataZoomOpts.push(newOpt); + }); + } + + function forEachComponent(mainType, cb) { + var opts = option[mainType]; + if (!isArray(opts)) { + opts = opts ? [opts] : []; + } + each$16(opts, cb); + } +}); + +/* +* 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. +*/ + +var restoreLang = lang.toolbox.restore; + +function Restore(model) { + this.model = model; +} + +Restore.defaultOption = { + show: true, + /* eslint-disable */ + icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5', + /* eslint-enable */ + title: restoreLang.title +}; + +var proto$6 = Restore.prototype; + +proto$6.onclick = function (ecModel, api, type) { + clear$1(ecModel); + + api.dispatchAction({ + type: 'restore', + from: this.uid + }); +}; + +register$1('restore', Restore); + +registerAction( + {type: 'restore', event: 'restore', update: 'prepareAndUpdate'}, + function (payload, ecModel) { + ecModel.resetOption('recreate'); + } +); + +/* +* 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. +*/ + +/* +* 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. +*/ + +extendComponentModel({ + + type: 'tooltip', + + dependencies: ['axisPointer'], + + defaultOption: { + zlevel: 0, + + z: 60, + + show: true, + + // tooltip主体内容 + showContent: true, + + // 'trigger' only works on coordinate system. + // 'item' | 'axis' | 'none' + trigger: 'item', + + // 'click' | 'mousemove' | 'none' + triggerOn: 'mousemove|click', + + alwaysShowContent: false, + + displayMode: 'single', // 'single' | 'multipleByCoordSys' + + renderMode: 'auto', // 'auto' | 'html' | 'richText' + // 'auto': use html by default, and use non-html if `document` is not defined + // 'html': use html for tooltip + // 'richText': use canvas, svg, and etc. for tooltip + + // 位置 {Array} | {Function} + // position: null + // Consider triggered from axisPointer handle, verticalAlign should be 'middle' + // align: null, + // verticalAlign: null, + + // 是否约束 content 在 viewRect 中。默认 false 是为了兼容以前版本。 + confine: false, + + // 内容格式器:{string}(Template) ¦ {Function} + // formatter: null + + showDelay: 0, + + // 隐藏延迟,单位ms + hideDelay: 100, + + // 动画变换时间,单位s + transitionDuration: 0.4, + + enterable: false, + + // 提示背景颜色,默认为透明度为0.7的黑色 + backgroundColor: 'rgba(50,50,50,0.7)', + + // 提示边框颜色 + borderColor: '#333', + + // 提示边框圆角,单位px,默认为4 + borderRadius: 4, + + // 提示边框线宽,单位px,默认为0(无边框) + borderWidth: 0, + + // 提示内边距,单位px,默认各方向内边距为5, + // 接受数组分别设定上右下左边距,同css + padding: 5, + + // Extra css text + extraCssText: '', + + // 坐标轴指示器,坐标轴触发有效 + axisPointer: { + // 默认为直线 + // 可选为:'line' | 'shadow' | 'cross' + type: 'line', + + // type 为 line 的时候有效,指定 tooltip line 所在的轴,可选 + // 可选 'x' | 'y' | 'angle' | 'radius' | 'auto' + // 默认 'auto',会选择类型为 category 的轴,对于双数值轴,笛卡尔坐标系会默认选择 x 轴 + // 极坐标系会默认选择 angle 轴 + axis: 'auto', + + animation: 'auto', + animationDurationUpdate: 200, + animationEasingUpdate: 'exponentialOut', + + crossStyle: { + color: '#999', + width: 1, + type: 'dashed', + + // TODO formatter + textStyle: {} + } + + // lineStyle and shadowStyle should not be specified here, + // otherwise it will always override those styles on option.axisPointer. + }, + textStyle: { + color: '#fff', + fontSize: 14 + } + } +}); + +/* +* 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. +*/ + +var each$22 = each$1; +var toCamelCase$1 = toCamelCase; + +var vendors = ['', '-webkit-', '-moz-', '-o-']; + +var gCssText = 'position:absolute;display:block;border-style:solid;white-space:nowrap;z-index:9999999;'; + +/** + * @param {number} duration + * @return {string} + * @inner + */ +function assembleTransition(duration) { + var transitionCurve = 'cubic-bezier(0.23, 1, 0.32, 1)'; + var transitionText = 'left ' + duration + 's ' + transitionCurve + ',' + + 'top ' + duration + 's ' + transitionCurve; + return map(vendors, function (vendorPrefix) { + return vendorPrefix + 'transition:' + transitionText; + }).join(';'); +} + +/** + * @param {Object} textStyle + * @return {string} + * @inner + */ +function assembleFont(textStyleModel) { + var cssText = []; + + var fontSize = textStyleModel.get('fontSize'); + var color = textStyleModel.getTextColor(); + + color && cssText.push('color:' + color); + + cssText.push('font:' + textStyleModel.getFont()); + + fontSize + && cssText.push('line-height:' + Math.round(fontSize * 3 / 2) + 'px'); + + each$22(['decoration', 'align'], function (name) { + var val = textStyleModel.get(name); + val && cssText.push('text-' + name + ':' + val); + }); + + return cssText.join(';'); +} + +/** + * @param {Object} tooltipModel + * @return {string} + * @inner + */ +function assembleCssText(tooltipModel) { + + var cssText = []; + + var transitionDuration = tooltipModel.get('transitionDuration'); + var backgroundColor = tooltipModel.get('backgroundColor'); + var textStyleModel = tooltipModel.getModel('textStyle'); + var padding = tooltipModel.get('padding'); + + // Animation transition. Do not animate when transitionDuration is 0. + transitionDuration + && cssText.push(assembleTransition(transitionDuration)); + + if (backgroundColor) { + if (env$1.canvasSupported) { + cssText.push('background-Color:' + backgroundColor); + } + else { + // for ie + cssText.push( + 'background-Color:#' + toHex(backgroundColor) + ); + cssText.push('filter:alpha(opacity=70)'); + } + } + + // Border style + each$22(['width', 'color', 'radius'], function (name) { + var borderName = 'border-' + name; + var camelCase = toCamelCase$1(borderName); + var val = tooltipModel.get(camelCase); + val != null + && cssText.push(borderName + ':' + val + (name === 'color' ? '' : 'px')); + }); + + // Text style + cssText.push(assembleFont(textStyleModel)); + + // Padding + if (padding != null) { + cssText.push('padding:' + normalizeCssArray$1(padding).join('px ') + 'px'); + } + + return cssText.join(';') + ';'; +} + +/** + * @alias module:echarts/component/tooltip/TooltipContent + * @constructor + */ +function TooltipContent(container, api) { + if (env$1.wxa) { + return null; + } + + var el = document.createElement('div'); + var zr = this._zr = api.getZr(); + + this.el = el; + + this._x = api.getWidth() / 2; + this._y = api.getHeight() / 2; + + container.appendChild(el); + + this._container = container; + + this._show = false; + + /** + * @private + */ + this._hideTimeout; + + // FIXME + // Is it needed to trigger zr event manually if + // the browser do not support `pointer-events: none`. + + var self = this; + el.onmouseenter = function () { + // clear the timeout in hideLater and keep showing tooltip + if (self._enterable) { + clearTimeout(self._hideTimeout); + self._show = true; + } + self._inContent = true; + }; + el.onmousemove = function (e) { + e = e || window.event; + if (!self._enterable) { + // `pointer-events: none` is set to tooltip content div + // if `enterable` is set as `false`, and `el.onmousemove` + // can not be triggered. But in browser that do not + // support `pointer-events`, we need to do this: + // Try trigger zrender event to avoid mouse + // in and out shape too frequently + var handler = zr.handler; + normalizeEvent(container, e, true); + handler.dispatch('mousemove', e); + } + }; + el.onmouseleave = function () { + if (self._enterable) { + if (self._show) { + self.hideLater(self._hideDelay); + } + } + self._inContent = false; + }; +} + +TooltipContent.prototype = { + + constructor: TooltipContent, + + /** + * @private + * @type {boolean} + */ + _enterable: true, + + /** + * Update when tooltip is rendered + */ + update: function () { + // FIXME + // Move this logic to ec main? + var container = this._container; + var stl = container.currentStyle + || document.defaultView.getComputedStyle(container); + var domStyle = container.style; + if (domStyle.position !== 'absolute' && stl.position !== 'absolute') { + domStyle.position = 'relative'; + } + // Hide the tooltip + // PENDING + // this.hide(); + }, + + show: function (tooltipModel) { + clearTimeout(this._hideTimeout); + var el = this.el; + + el.style.cssText = gCssText + assembleCssText(tooltipModel) + // Because of the reason described in: + // http://stackoverflow.com/questions/21125587/css3-transition-not-working-in-chrome-anymore + // we should set initial value to `left` and `top`. + + ';left:' + this._x + 'px;top:' + this._y + 'px;' + + (tooltipModel.get('extraCssText') || ''); + + 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; + }, + + setContent: function (content) { + this.el.innerHTML = content == null ? '' : content; + }, + + setEnterable: function (enterable) { + this._enterable = enterable; + }, + + getSize: function () { + var el = this.el; + return [el.clientWidth, el.clientHeight]; + }, + + moveTo: function (x, y) { + // xy should be based on canvas root. But tooltipContent is + // the sibling of canvas root. So padding of ec container + // should be considered here. + var zr = this._zr; + var viewportRootOffset; + if (zr && zr.painter && (viewportRootOffset = zr.painter.getViewportRootOffset())) { + x += viewportRootOffset.offsetLeft; + y += viewportRootOffset.offsetTop; + } + + var style = this.el.style; + style.left = x + 'px'; + style.top = y + 'px'; + + this._x = x; + this._y = y; + }, + + hide: function () { + this.el.style.display = 'none'; + this._show = false; + }, + + hideLater: function (time) { + if (this._show && !(this._inContent && this._enterable)) { + if (time) { + this._hideDelay = time; + // Set show false to avoid invoke hideLater mutiple times + this._show = false; + this._hideTimeout = setTimeout(bind(this.hide, this), time); + } + else { + this.hide(); + } + } + }, + + isShow: function () { + return this._show; + }, + + getOuterSize: function () { + var width = this.el.clientWidth; + var height = this.el.clientHeight; + + // Consider browser compatibility. + // IE8 does not support getComputedStyle. + if (document.defaultView && document.defaultView.getComputedStyle) { + var stl = document.defaultView.getComputedStyle(this.el); + if (stl) { + width += parseInt(stl.borderLeftWidth, 10) + parseInt(stl.borderRightWidth, 10); + height += parseInt(stl.borderTopWidth, 10) + parseInt(stl.borderBottomWidth, 10); + } + } + + return {width: width, height: height}; + } +}; + +/* +* 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 Group from 'zrender/src/container/Group'; +/** + * @alias module:echarts/component/tooltip/TooltipRichContent + * @constructor + */ +function TooltipRichContent(api) { + + this._zr = api.getZr(); + + this._show = false; + + /** + * @private + */ + this._hideTimeout; +} + +TooltipRichContent.prototype = { + + constructor: TooltipRichContent, + + /** + * @private + * @type {boolean} + */ + _enterable: true, + + /** + * Update when tooltip is rendered + */ + update: function () { + // noop + }, + + show: function (tooltipModel) { + if (this._hideTimeout) { + clearTimeout(this._hideTimeout); + } + + this.el.attr('show', true); + this._show = true; + }, + + /** + * Set tooltip content + * + * @param {string} content rich text string of content + * @param {Object} markerRich rich text style + * @param {Object} tooltipModel tooltip model + */ + setContent: function (content, markerRich, tooltipModel) { + if (this.el) { + this._zr.remove(this.el); + } + + var markers = {}; + var text = content; + var prefix = '{marker'; + var suffix = '|}'; + var startId = text.indexOf(prefix); + while (startId >= 0) { + var endId = text.indexOf(suffix); + var name = text.substr(startId + prefix.length, endId - startId - prefix.length); + if (name.indexOf('sub') > -1) { + markers['marker' + name] = { + textWidth: 4, + textHeight: 4, + textBorderRadius: 2, + textBackgroundColor: markerRich[name], + // TODO: textOffset is not implemented for rich text + textOffset: [3, 0] + }; + } + else { + markers['marker' + name] = { + textWidth: 10, + textHeight: 10, + textBorderRadius: 5, + textBackgroundColor: markerRich[name] + }; + } + + text = text.substr(endId + 1); + startId = text.indexOf('{marker'); + } + + this.el = new Text({ + style: { + rich: markers, + text: content, + textLineHeight: 20, + textBackgroundColor: tooltipModel.get('backgroundColor'), + textBorderRadius: tooltipModel.get('borderRadius'), + textFill: tooltipModel.get('textStyle.color'), + textPadding: tooltipModel.get('padding') + }, + z: tooltipModel.get('z') + }); + this._zr.add(this.el); + + var self = this; + this.el.on('mouseover', function () { + // clear the timeout in hideLater and keep showing tooltip + if (self._enterable) { + clearTimeout(self._hideTimeout); + self._show = true; + } + self._inContent = true; + }); + this.el.on('mouseout', function () { + if (self._enterable) { + if (self._show) { + self.hideLater(self._hideDelay); + } + } + self._inContent = false; + }); + }, + + setEnterable: function (enterable) { + this._enterable = enterable; + }, + + getSize: function () { + var bounding = this.el.getBoundingRect(); + return [bounding.width, bounding.height]; + }, + + moveTo: function (x, y) { + if (this.el) { + this.el.attr('position', [x, y]); + } + }, + + hide: function () { + if (this.el) { + this.el.hide(); + } + this._show = false; + }, + + hideLater: function (time) { + if (this._show && !(this._inContent && this._enterable)) { + if (time) { + this._hideDelay = time; + // Set show false to avoid invoke hideLater mutiple times + this._show = false; + this._hideTimeout = setTimeout(bind(this.hide, this), time); + } + else { + this.hide(); + } + } + }, + + isShow: function () { + return this._show; + }, + + getOuterSize: function () { + var size = this.getSize(); + return { + width: size[0], + height: size[1] + }; + } +}; + +/* +* 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. +*/ + +var bind$3 = bind; +var each$21 = each$1; +var parsePercent$2 = parsePercent$1; + +var proxyRect = new Rect({ + shape: {x: -1, y: -1, width: 2, height: 2} +}); + +extendComponentView({ + + type: 'tooltip', + + init: function (ecModel, api) { + if (env$1.node) { + return; + } + + var tooltipModel = ecModel.getComponent('tooltip'); + var renderMode = tooltipModel.get('renderMode'); + this._renderMode = getTooltipRenderMode(renderMode); + + var tooltipContent; + if (this._renderMode === 'html') { + tooltipContent = new TooltipContent(api.getDom(), api); + this._newLine = '
    '; + } + else { + tooltipContent = new TooltipRichContent(api); + this._newLine = '\n'; + } + + this._tooltipContent = tooltipContent; + }, + + render: function (tooltipModel, ecModel, api) { + if (env$1.node) { + return; + } + + // Reset + this.group.removeAll(); + + /** + * @private + * @type {module:echarts/component/tooltip/TooltipModel} + */ + this._tooltipModel = tooltipModel; + + /** + * @private + * @type {module:echarts/model/Global} + */ + this._ecModel = ecModel; + + /** + * @private + * @type {module:echarts/ExtensionAPI} + */ + this._api = api; + + /** + * Should be cleaned when render. + * @private + * @type {Array.>} + */ + this._lastDataByCoordSys = null; + + /** + * @private + * @type {boolean} + */ + this._alwaysShowContent = tooltipModel.get('alwaysShowContent'); + + var tooltipContent = this._tooltipContent; + tooltipContent.update(); + tooltipContent.setEnterable(tooltipModel.get('enterable')); + + this._initGlobalListener(); + + this._keepShow(); + }, + + _initGlobalListener: function () { + var tooltipModel = this._tooltipModel; + var triggerOn = tooltipModel.get('triggerOn'); + + register( + 'itemTooltip', + this._api, + bind$3(function (currTrigger, e, dispatchAction) { + // If 'none', it is not controlled by mouse totally. + if (triggerOn !== 'none') { + if (triggerOn.indexOf(currTrigger) >= 0) { + this._tryShow(e, dispatchAction); + } + else if (currTrigger === 'leave') { + this._hide(dispatchAction); + } + } + }, this) + ); + }, + + _keepShow: function () { + var tooltipModel = this._tooltipModel; + var ecModel = this._ecModel; + var api = this._api; + + // Try to keep the tooltip show when refreshing + if (this._lastX != null + && this._lastY != null + // When user is willing to control tooltip totally using API, + // self.manuallyShowTip({x, y}) might cause tooltip hide, + // which is not expected. + && tooltipModel.get('triggerOn') !== 'none' + ) { + var self = this; + clearTimeout(this._refreshUpdateTimeout); + this._refreshUpdateTimeout = setTimeout(function () { + // Show tip next tick after other charts are rendered + // In case highlight action has wrong result + // FIXME + !api.isDisposed() && self.manuallyShowTip(tooltipModel, ecModel, api, { + x: self._lastX, + y: self._lastY + }); + }); + } + }, + + /** + * Show tip manually by + * dispatchAction({ + * type: 'showTip', + * x: 10, + * y: 10 + * }); + * Or + * dispatchAction({ + * type: 'showTip', + * seriesIndex: 0, + * dataIndex or dataIndexInside or name + * }); + * + * TODO Batch + */ + manuallyShowTip: function (tooltipModel, ecModel, api, payload) { + if (payload.from === this.uid || env$1.node) { + return; + } + + var dispatchAction = makeDispatchAction$1(payload, api); + + // Reset ticket + this._ticket = ''; + + // When triggered from axisPointer. + var dataByCoordSys = payload.dataByCoordSys; + + if (payload.tooltip && payload.x != null && payload.y != null) { + var el = proxyRect; + el.position = [payload.x, payload.y]; + el.update(); + el.tooltip = payload.tooltip; + // Manually show tooltip while view is not using zrender elements. + this._tryShow({ + offsetX: payload.x, + offsetY: payload.y, + target: el + }, dispatchAction); + } + else if (dataByCoordSys) { + this._tryShow({ + offsetX: payload.x, + offsetY: payload.y, + position: payload.position, + event: {}, + dataByCoordSys: payload.dataByCoordSys, + tooltipOption: payload.tooltipOption + }, dispatchAction); + } + else if (payload.seriesIndex != null) { + + if (this._manuallyAxisShowTip(tooltipModel, ecModel, api, payload)) { + return; + } + + var pointInfo = findPointFromSeries(payload, ecModel); + var cx = pointInfo.point[0]; + var cy = pointInfo.point[1]; + if (cx != null && cy != null) { + this._tryShow({ + offsetX: cx, + offsetY: cy, + position: payload.position, + target: pointInfo.el, + event: {} + }, dispatchAction); + } + } + else if (payload.x != null && payload.y != null) { + // FIXME + // should wrap dispatchAction like `axisPointer/globalListener` ? + api.dispatchAction({ + type: 'updateAxisPointer', + x: payload.x, + y: payload.y + }); + + this._tryShow({ + offsetX: payload.x, + offsetY: payload.y, + position: payload.position, + target: api.getZr().findHover(payload.x, payload.y).target, + event: {} + }, dispatchAction); + } + }, + + manuallyHideTip: function (tooltipModel, ecModel, api, payload) { + var tooltipContent = this._tooltipContent; + + if (!this._alwaysShowContent && this._tooltipModel) { + tooltipContent.hideLater(this._tooltipModel.get('hideDelay')); + } + + this._lastX = this._lastY = null; + + if (payload.from !== this.uid) { + this._hide(makeDispatchAction$1(payload, api)); + } + }, + + // Be compatible with previous design, that is, when tooltip.type is 'axis' and + // dispatchAction 'showTip' with seriesIndex and dataIndex will trigger axis pointer + // and tooltip. + _manuallyAxisShowTip: function (tooltipModel, ecModel, api, payload) { + var seriesIndex = payload.seriesIndex; + var dataIndex = payload.dataIndex; + var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo; + + if (seriesIndex == null || dataIndex == null || coordSysAxesInfo == null) { + return; + } + + var seriesModel = ecModel.getSeriesByIndex(seriesIndex); + if (!seriesModel) { + return; + } + + var data = seriesModel.getData(); + var tooltipModel = buildTooltipModel([ + data.getItemModel(dataIndex), + seriesModel, + (seriesModel.coordinateSystem || {}).model, + tooltipModel + ]); + + if (tooltipModel.get('trigger') !== 'axis') { + return; + } + + api.dispatchAction({ + type: 'updateAxisPointer', + seriesIndex: seriesIndex, + dataIndex: dataIndex, + position: payload.position + }); + + return true; + }, + + _tryShow: function (e, dispatchAction) { + var el = e.target; + var tooltipModel = this._tooltipModel; + + if (!tooltipModel) { + return; + } + + // Save mouse x, mouse y. So we can try to keep showing the tip if chart is refreshed + this._lastX = e.offsetX; + this._lastY = e.offsetY; + + var dataByCoordSys = e.dataByCoordSys; + if (dataByCoordSys && dataByCoordSys.length) { + this._showAxisTooltip(dataByCoordSys, e); + } + // Always show item tooltip if mouse is on the element with dataIndex + else if (el && el.dataIndex != null) { + this._lastDataByCoordSys = null; + this._showSeriesItemTooltip(e, el, dispatchAction); + } + // Tooltip provided directly. Like legend. + else if (el && el.tooltip) { + this._lastDataByCoordSys = null; + this._showComponentItemTooltip(e, el, dispatchAction); + } + else { + this._lastDataByCoordSys = null; + this._hide(dispatchAction); + } + }, + + _showOrMove: function (tooltipModel, cb) { + // showDelay is used in this case: tooltip.enterable is set + // as true. User intent to move mouse into tooltip and click + // something. `showDelay` makes it easyer to enter the content + // but tooltip do not move immediately. + var delay = tooltipModel.get('showDelay'); + cb = bind(cb, this); + clearTimeout(this._showTimout); + delay > 0 + ? (this._showTimout = setTimeout(cb, delay)) + : cb(); + }, + + _showAxisTooltip: function (dataByCoordSys, e) { + var ecModel = this._ecModel; + var globalTooltipModel = this._tooltipModel; + var point = [e.offsetX, e.offsetY]; + var singleDefaultHTML = []; + var singleParamsList = []; + var singleTooltipModel = buildTooltipModel([ + e.tooltipOption, + globalTooltipModel + ]); + + var renderMode = this._renderMode; + var newLine = this._newLine; + + var markers = {}; + + each$21(dataByCoordSys, function (itemCoordSys) { + // var coordParamList = []; + // var coordDefaultHTML = []; + // var coordTooltipModel = buildTooltipModel([ + // e.tooltipOption, + // itemCoordSys.tooltipOption, + // ecModel.getComponent(itemCoordSys.coordSysMainType, itemCoordSys.coordSysIndex), + // globalTooltipModel + // ]); + // var displayMode = coordTooltipModel.get('displayMode'); + // var paramsList = displayMode === 'single' ? singleParamsList : []; + + each$21(itemCoordSys.dataByAxis, function (item) { + var axisModel = ecModel.getComponent(item.axisDim + 'Axis', item.axisIndex); + var axisValue = item.value; + var seriesDefaultHTML = []; + + if (!axisModel || axisValue == null) { + return; + } + + var valueLabel = getValueLabel( + axisValue, axisModel.axis, ecModel, + item.seriesDataIndices, + item.valueLabelOpt + ); + + each$1(item.seriesDataIndices, function (idxItem) { + var series = ecModel.getSeriesByIndex(idxItem.seriesIndex); + var dataIndex = idxItem.dataIndexInside; + var dataParams = series && series.getDataParams(dataIndex); + dataParams.axisDim = item.axisDim; + dataParams.axisIndex = item.axisIndex; + dataParams.axisType = item.axisType; + dataParams.axisId = item.axisId; + dataParams.axisValue = getAxisRawValue(axisModel.axis, axisValue); + dataParams.axisValueLabel = valueLabel; + + if (dataParams) { + singleParamsList.push(dataParams); + var seriesTooltip = series.formatTooltip(dataIndex, true, null, renderMode); + + var html; + if (isObject$1(seriesTooltip)) { + html = seriesTooltip.html; + var newMarkers = seriesTooltip.markers; + merge(markers, newMarkers); + } + else { + html = seriesTooltip; + } + seriesDefaultHTML.push(html); + } + }); + + // Default tooltip content + // FIXME + // (1) shold be the first data which has name? + // (2) themeRiver, firstDataIndex is array, and first line is unnecessary. + var firstLine = valueLabel; + if (renderMode !== 'html') { + singleDefaultHTML.push(seriesDefaultHTML.join(newLine)); + } + else { + singleDefaultHTML.push( + (firstLine ? encodeHTML(firstLine) + newLine : '') + + seriesDefaultHTML.join(newLine) + ); + } + }); + }, this); + + // In most case, the second axis is shown upper than the first one. + singleDefaultHTML.reverse(); + singleDefaultHTML = singleDefaultHTML.join(this._newLine + this._newLine); + + var positionExpr = e.position; + this._showOrMove(singleTooltipModel, function () { + if (this._updateContentNotChangedOnAxis(dataByCoordSys)) { + this._updatePosition( + singleTooltipModel, + positionExpr, + point[0], point[1], + this._tooltipContent, + singleParamsList + ); + } + else { + this._showTooltipContent( + singleTooltipModel, singleDefaultHTML, singleParamsList, Math.random(), + point[0], point[1], positionExpr, undefined, markers + ); + } + }); + + // Do not trigger events here, because this branch only be entered + // from dispatchAction. + }, + + _showSeriesItemTooltip: function (e, el, dispatchAction) { + var ecModel = this._ecModel; + // Use dataModel in element if possible + // Used when mouseover on a element like markPoint or edge + // In which case, the data is not main data in series. + var seriesIndex = el.seriesIndex; + var seriesModel = ecModel.getSeriesByIndex(seriesIndex); + + // For example, graph link. + var dataModel = el.dataModel || seriesModel; + var dataIndex = el.dataIndex; + var dataType = el.dataType; + var data = dataModel.getData(); + + var tooltipModel = buildTooltipModel([ + data.getItemModel(dataIndex), + dataModel, + seriesModel && (seriesModel.coordinateSystem || {}).model, + this._tooltipModel + ]); + + var tooltipTrigger = tooltipModel.get('trigger'); + if (tooltipTrigger != null && tooltipTrigger !== 'item') { + return; + } + + var params = dataModel.getDataParams(dataIndex, dataType); + var seriesTooltip = dataModel.formatTooltip(dataIndex, false, dataType, this._renderMode); + var defaultHtml; + var markers; + if (isObject$1(seriesTooltip)) { + defaultHtml = seriesTooltip.html; + markers = seriesTooltip.markers; + } + else { + defaultHtml = seriesTooltip; + markers = null; + } + + var asyncTicket = 'item_' + dataModel.name + '_' + dataIndex; + + this._showOrMove(tooltipModel, function () { + this._showTooltipContent( + tooltipModel, defaultHtml, params, asyncTicket, + e.offsetX, e.offsetY, e.position, e.target, markers + ); + }); + + // FIXME + // duplicated showtip if manuallyShowTip is called from dispatchAction. + dispatchAction({ + type: 'showTip', + dataIndexInside: dataIndex, + dataIndex: data.getRawIndex(dataIndex), + seriesIndex: seriesIndex, + from: this.uid + }); + }, + + _showComponentItemTooltip: function (e, el, dispatchAction) { + var tooltipOpt = el.tooltip; + if (typeof tooltipOpt === 'string') { + var content = tooltipOpt; + tooltipOpt = { + content: content, + // Fixed formatter + formatter: content + }; + } + var subTooltipModel = new Model(tooltipOpt, this._tooltipModel, this._ecModel); + var defaultHtml = subTooltipModel.get('content'); + var asyncTicket = Math.random(); + + // Do not check whether `trigger` is 'none' here, because `trigger` + // only works on cooridinate system. In fact, we have not found case + // that requires setting `trigger` nothing on component yet. + + this._showOrMove(subTooltipModel, function () { + this._showTooltipContent( + subTooltipModel, defaultHtml, subTooltipModel.get('formatterParams') || {}, + asyncTicket, e.offsetX, e.offsetY, e.position, el + ); + }); + + // If not dispatch showTip, tip may be hide triggered by axis. + dispatchAction({ + type: 'showTip', + from: this.uid + }); + }, + + _showTooltipContent: function ( + tooltipModel, defaultHtml, params, asyncTicket, x, y, positionExpr, el, markers + ) { + // Reset ticket + this._ticket = ''; + + if (!tooltipModel.get('showContent') || !tooltipModel.get('show')) { + return; + } + + var tooltipContent = this._tooltipContent; + + var formatter = tooltipModel.get('formatter'); + positionExpr = positionExpr || tooltipModel.get('position'); + var html = defaultHtml; + + if (formatter && typeof formatter === 'string') { + html = formatTpl(formatter, params, true); + } + else if (typeof formatter === 'function') { + var callback = bind$3(function (cbTicket, html) { + if (cbTicket === this._ticket) { + tooltipContent.setContent(html, markers, tooltipModel); + this._updatePosition( + tooltipModel, positionExpr, x, y, tooltipContent, params, el + ); + } + }, this); + this._ticket = asyncTicket; + html = formatter(params, asyncTicket, callback); + } + + tooltipContent.setContent(html, markers, tooltipModel); + tooltipContent.show(tooltipModel); + + this._updatePosition( + tooltipModel, positionExpr, x, y, tooltipContent, params, el + ); + }, + + /** + * @param {string|Function|Array.|Object} positionExpr + * @param {number} x Mouse x + * @param {number} y Mouse y + * @param {boolean} confine Whether confine tooltip content in view rect. + * @param {Object|} params + * @param {module:zrender/Element} el target element + * @param {module:echarts/ExtensionAPI} api + * @return {Array.} + */ + _updatePosition: function (tooltipModel, positionExpr, x, y, content, params, el) { + var viewWidth = this._api.getWidth(); + var viewHeight = this._api.getHeight(); + positionExpr = positionExpr || tooltipModel.get('position'); + + var contentSize = content.getSize(); + var align = tooltipModel.get('align'); + var vAlign = tooltipModel.get('verticalAlign'); + var rect = el && el.getBoundingRect().clone(); + el && rect.applyTransform(el.transform); + + if (typeof positionExpr === 'function') { + // Callback of position can be an array or a string specify the position + positionExpr = positionExpr([x, y], params, content.el, rect, { + viewSize: [viewWidth, viewHeight], + contentSize: contentSize.slice() + }); + } + + if (isArray(positionExpr)) { + x = parsePercent$2(positionExpr[0], viewWidth); + y = parsePercent$2(positionExpr[1], viewHeight); + } + else if (isObject$1(positionExpr)) { + positionExpr.width = contentSize[0]; + positionExpr.height = contentSize[1]; + var layoutRect = getLayoutRect( + positionExpr, {width: viewWidth, height: viewHeight} + ); + x = layoutRect.x; + y = layoutRect.y; + align = null; + // When positionExpr is left/top/right/bottom, + // align and verticalAlign will not work. + vAlign = null; + } + // Specify tooltip position by string 'top' 'bottom' 'left' 'right' around graphic element + else if (typeof positionExpr === 'string' && el) { + var pos = calcTooltipPosition( + positionExpr, rect, contentSize + ); + x = pos[0]; + y = pos[1]; + } + else { + var pos = refixTooltipPosition( + x, y, content, viewWidth, viewHeight, align ? null : 20, vAlign ? null : 20 + ); + x = pos[0]; + y = pos[1]; + } + + align && (x -= isCenterAlign(align) ? contentSize[0] / 2 : align === 'right' ? contentSize[0] : 0); + vAlign && (y -= isCenterAlign(vAlign) ? contentSize[1] / 2 : vAlign === 'bottom' ? contentSize[1] : 0); + + if (tooltipModel.get('confine')) { + var pos = confineTooltipPosition( + x, y, content, viewWidth, viewHeight + ); + x = pos[0]; + y = pos[1]; + } + + content.moveTo(x, y); + }, + + // FIXME + // Should we remove this but leave this to user? + _updateContentNotChangedOnAxis: function (dataByCoordSys) { + var lastCoordSys = this._lastDataByCoordSys; + var contentNotChanged = !!lastCoordSys + && lastCoordSys.length === dataByCoordSys.length; + + contentNotChanged && each$21(lastCoordSys, function (lastItemCoordSys, indexCoordSys) { + var lastDataByAxis = lastItemCoordSys.dataByAxis || {}; + var thisItemCoordSys = dataByCoordSys[indexCoordSys] || {}; + var thisDataByAxis = thisItemCoordSys.dataByAxis || []; + contentNotChanged &= lastDataByAxis.length === thisDataByAxis.length; + + contentNotChanged && each$21(lastDataByAxis, function (lastItem, indexAxis) { + var thisItem = thisDataByAxis[indexAxis] || {}; + var lastIndices = lastItem.seriesDataIndices || []; + var newIndices = thisItem.seriesDataIndices || []; + + contentNotChanged + &= lastItem.value === thisItem.value + && lastItem.axisType === thisItem.axisType + && lastItem.axisId === thisItem.axisId + && lastIndices.length === newIndices.length; + + contentNotChanged && each$21(lastIndices, function (lastIdxItem, j) { + var newIdxItem = newIndices[j]; + contentNotChanged + &= lastIdxItem.seriesIndex === newIdxItem.seriesIndex + && lastIdxItem.dataIndex === newIdxItem.dataIndex; + }); + }); + }); + + this._lastDataByCoordSys = dataByCoordSys; + + return !!contentNotChanged; + }, + + _hide: function (dispatchAction) { + // Do not directly hideLater here, because this behavior may be prevented + // in dispatchAction when showTip is dispatched. + + // FIXME + // duplicated hideTip if manuallyHideTip is called from dispatchAction. + this._lastDataByCoordSys = null; + dispatchAction({ + type: 'hideTip', + from: this.uid + }); + }, + + dispose: function (ecModel, api) { + if (env$1.node) { + return; + } + this._tooltipContent.hide(); + unregister('itemTooltip', api); + } +}); + + +/** + * @param {Array.} modelCascade + * From top to bottom. (the last one should be globalTooltipModel); + */ +function buildTooltipModel(modelCascade) { + var resultModel = modelCascade.pop(); + while (modelCascade.length) { + var tooltipOpt = modelCascade.pop(); + if (tooltipOpt) { + if (Model.isInstance(tooltipOpt)) { + tooltipOpt = tooltipOpt.get('tooltip', true); + } + // In each data item tooltip can be simply write: + // { + // value: 10, + // tooltip: 'Something you need to know' + // } + if (typeof tooltipOpt === 'string') { + tooltipOpt = {formatter: tooltipOpt}; + } + resultModel = new Model(tooltipOpt, resultModel, resultModel.ecModel); + } + } + return resultModel; +} + +function makeDispatchAction$1(payload, api) { + return payload.dispatchAction || bind(api.dispatchAction, api); +} + +function refixTooltipPosition(x, y, content, viewWidth, viewHeight, gapH, gapV) { + var size = content.getOuterSize(); + var width = size.width; + var height = size.height; + + if (gapH != null) { + if (x + width + gapH > viewWidth) { + x -= width + gapH; + } + else { + x += gapH; + } + } + if (gapV != null) { + if (y + height + gapV > viewHeight) { + y -= height + gapV; + } + else { + y += gapV; + } + } + return [x, y]; +} + +function confineTooltipPosition(x, y, content, viewWidth, viewHeight) { + var size = content.getOuterSize(); + var width = size.width; + var height = size.height; + + x = Math.min(x + width, viewWidth) - width; + y = Math.min(y + height, viewHeight) - height; + x = Math.max(x, 0); + y = Math.max(y, 0); + + return [x, y]; +} + +function calcTooltipPosition(position, rect, contentSize) { + var domWidth = contentSize[0]; + var domHeight = contentSize[1]; + var gap = 5; + var x = 0; + var y = 0; + var rectWidth = rect.width; + var rectHeight = rect.height; + switch (position) { + case 'inside': + x = rect.x + rectWidth / 2 - domWidth / 2; + y = rect.y + rectHeight / 2 - domHeight / 2; + break; + case 'top': + x = rect.x + rectWidth / 2 - domWidth / 2; + y = rect.y - domHeight - gap; + break; + case 'bottom': + x = rect.x + rectWidth / 2 - domWidth / 2; + y = rect.y + rectHeight + gap; + break; + case 'left': + x = rect.x - domWidth - gap; + y = rect.y + rectHeight / 2 - domHeight / 2; + break; + case 'right': + x = rect.x + rectWidth + gap; + y = rect.y + rectHeight / 2 - domHeight / 2; + } + return [x, y]; +} + +function isCenterAlign(align) { + return align === 'center' || align === 'middle'; +} + +/* +* 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. +*/ + +// FIXME Better way to pack data in graphic element + +/** + * @action + * @property {string} type + * @property {number} seriesIndex + * @property {number} dataIndex + * @property {number} [x] + * @property {number} [y] + */ +registerAction( + { + type: 'showTip', + event: 'showTip', + update: 'tooltip:manuallyShowTip' + }, + // noop + function () {} +); + +registerAction( + { + type: 'hideTip', + event: 'hideTip', + update: 'tooltip:manuallyHideTip' + }, + // noop + function () {} +); + +/* +* 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. +*/ + +var DEFAULT_TOOLBOX_BTNS = ['rect', 'polygon', 'keep', 'clear']; + +var preprocessor$1 = function (option, isNew) { + var brushComponents = option && option.brush; + if (!isArray(brushComponents)) { + brushComponents = brushComponents ? [brushComponents] : []; + } + + if (!brushComponents.length) { + return; + } + + var brushComponentSpecifiedBtns = []; + + each$1(brushComponents, function (brushOpt) { + var tbs = brushOpt.hasOwnProperty('toolbox') + ? brushOpt.toolbox : []; + + if (tbs instanceof Array) { + brushComponentSpecifiedBtns = brushComponentSpecifiedBtns.concat(tbs); + } + }); + + var toolbox = option && option.toolbox; + + if (isArray(toolbox)) { + toolbox = toolbox[0]; + } + if (!toolbox) { + toolbox = {feature: {}}; + option.toolbox = [toolbox]; + } + + var toolboxFeature = (toolbox.feature || (toolbox.feature = {})); + var toolboxBrush = toolboxFeature.brush || (toolboxFeature.brush = {}); + var brushTypes = toolboxBrush.type || (toolboxBrush.type = []); + + brushTypes.push.apply(brushTypes, brushComponentSpecifiedBtns); + + removeDuplicate(brushTypes); + + if (isNew && !brushTypes.length) { + brushTypes.push.apply(brushTypes, DEFAULT_TOOLBOX_BTNS); + } +}; + +function removeDuplicate(arr) { + var map$$1 = {}; + each$1(arr, function (val) { + map$$1[val] = 1; + }); + arr.length = 0; + each$1(map$$1, function (flag, val) { + arr.push(val); + }); +} + +/* +* 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. +*/ + +/** + * @file Visual solution, for consistent option specification. + */ + +var each$23 = each$1; + +function hasKeys(obj) { + if (obj) { + for (var name in obj) { + if (obj.hasOwnProperty(name)) { + return true; + } + } + } +} + +/** + * @param {Object} option + * @param {Array.} stateList + * @param {Function} [supplementVisualOption] + * @return {Object} visualMappings > + */ +function createVisualMappings(option, stateList, supplementVisualOption) { + var visualMappings = {}; + + each$23(stateList, function (state) { + var mappings = visualMappings[state] = createMappings(); + + each$23(option[state], function (visualData, visualType) { + if (!VisualMapping.isValidType(visualType)) { + return; + } + var mappingOption = { + type: visualType, + visual: visualData + }; + supplementVisualOption && supplementVisualOption(mappingOption, state); + mappings[visualType] = new VisualMapping(mappingOption); + + // Prepare a alpha for opacity, for some case that opacity + // is not supported, such as rendering using gradient color. + if (visualType === 'opacity') { + mappingOption = clone(mappingOption); + mappingOption.type = 'colorAlpha'; + mappings.__hidden.__alphaForOpacity = new VisualMapping(mappingOption); + } + }); + }); + + return visualMappings; + + function createMappings() { + var Creater = function () {}; + // Make sure hidden fields will not be visited by + // object iteration (with hasOwnProperty checking). + Creater.prototype.__hidden = Creater.prototype; + var obj = new Creater(); + return obj; + } +} + +/** + * @param {Object} thisOption + * @param {Object} newOption + * @param {Array.} keys + */ +function replaceVisualOption(thisOption, newOption, keys) { + // Visual attributes merge is not supported, otherwise it + // brings overcomplicated merge logic. See #2853. So if + // newOption has anyone of these keys, all of these keys + // will be reset. Otherwise, all keys remain. + var has; + each$1(keys, function (key) { + if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) { + has = true; + } + }); + has && each$1(keys, function (key) { + if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) { + thisOption[key] = clone(newOption[key]); + } + else { + delete thisOption[key]; + } + }); +} + +/** + * @param {Array.} stateList + * @param {Object} visualMappings > + * @param {module:echarts/data/List} list + * @param {Function} getValueState param: valueOrIndex, return: state. + * @param {object} [scope] Scope for getValueState + * @param {string} [dimension] Concrete dimension, if used. + */ +// ???! handle brush? +function applyVisual(stateList, visualMappings, data, getValueState, scope, dimension) { + var visualTypesMap = {}; + each$1(stateList, function (state) { + var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]); + visualTypesMap[state] = visualTypes; + }); + + var dataIndex; + + function getVisual(key) { + return data.getItemVisual(dataIndex, key); + } + + function setVisual(key, value) { + data.setItemVisual(dataIndex, key, value); + } + + if (dimension == null) { + data.each(eachItem); + } + else { + data.each([dimension], eachItem); + } + + function eachItem(valueOrIndex, index) { + dataIndex = dimension == null ? valueOrIndex : index; + + var rawDataItem = data.getRawDataItem(dataIndex); + // Consider performance + if (rawDataItem && rawDataItem.visualMap === false) { + return; + } + + var valueState = getValueState.call(scope, valueOrIndex); + var mappings = visualMappings[valueState]; + var visualTypes = visualTypesMap[valueState]; + + for (var i = 0, len = visualTypes.length; i < len; i++) { + var type = visualTypes[i]; + mappings[type] && mappings[type].applyVisual( + valueOrIndex, getVisual, setVisual + ); + } + } +} + +/** + * @param {module:echarts/data/List} data + * @param {Array.} stateList + * @param {Object} visualMappings > + * @param {Function} getValueState param: valueOrIndex, return: state. + * @param {number} [dim] dimension or dimension index. + */ +function incrementalApplyVisual(stateList, visualMappings, getValueState, dim) { + var visualTypesMap = {}; + each$1(stateList, function (state) { + var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]); + visualTypesMap[state] = visualTypes; + }); + + function progress(params, data) { + if (dim != null) { + dim = data.getDimension(dim); + } + + function getVisual(key) { + return data.getItemVisual(dataIndex, key); + } + + function setVisual(key, value) { + data.setItemVisual(dataIndex, key, value); + } + + var dataIndex; + while ((dataIndex = params.next()) != null) { + var rawDataItem = data.getRawDataItem(dataIndex); + + // Consider performance + if (rawDataItem && rawDataItem.visualMap === false) { + continue; + } + + var value = dim != null + ? data.get(dim, dataIndex, true) + : dataIndex; + + var valueState = getValueState(value); + var mappings = visualMappings[valueState]; + var visualTypes = visualTypesMap[valueState]; + + for (var i = 0, len = visualTypes.length; i < len; i++) { + var type = visualTypes[i]; + mappings[type] && mappings[type].applyVisual(value, getVisual, setVisual); + } + } + } + + return {progress: progress}; +} + +/* +* 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. +*/ + +// Key of the first level is brushType: `line`, `rect`, `polygon`. +// Key of the second level is chart element type: `point`, `rect`. +// See moudule:echarts/component/helper/BrushController +// function param: +// {Object} itemLayout fetch from data.getItemLayout(dataIndex) +// {Object} selectors {point: selector, rect: selector, ...} +// {Object} area {range: [[], [], ..], boudingRect} +// function return: +// {boolean} Whether in the given brush. +var selector = { + lineX: getLineSelectors(0), + lineY: getLineSelectors(1), + rect: { + point: function (itemLayout, selectors, area) { + return itemLayout && area.boundingRect.contain(itemLayout[0], itemLayout[1]); + }, + rect: function (itemLayout, selectors, area) { + return itemLayout && area.boundingRect.intersect(itemLayout); + } + }, + polygon: { + point: function (itemLayout, selectors, area) { + return itemLayout + && area.boundingRect.contain(itemLayout[0], itemLayout[1]) + && contain$1(area.range, itemLayout[0], itemLayout[1]); + }, + rect: function (itemLayout, selectors, area) { + var points = area.range; + + if (!itemLayout || points.length <= 1) { + return false; + } + + var x = itemLayout.x; + var y = itemLayout.y; + var width = itemLayout.width; + var height = itemLayout.height; + var p = points[0]; + + if (contain$1(points, x, y) + || contain$1(points, x + width, y) + || contain$1(points, x, y + height) + || contain$1(points, x + width, y + height) + || BoundingRect.create(itemLayout).contain(p[0], p[1]) + || linePolygonIntersect(x, y, x + width, y, points) + || linePolygonIntersect(x, y, x, y + height, points) + || linePolygonIntersect(x + width, y, x + width, y + height, points) + || linePolygonIntersect(x, y + height, x + width, y + height, points) + ) { + return true; + } + } + } +}; + +function getLineSelectors(xyIndex) { + var xy = ['x', 'y']; + var wh = ['width', 'height']; + + return { + point: function (itemLayout, selectors, area) { + if (itemLayout) { + var range = area.range; + var p = itemLayout[xyIndex]; + return inLineRange(p, range); + } + }, + rect: function (itemLayout, selectors, area) { + if (itemLayout) { + var range = area.range; + var layoutRange = [ + itemLayout[xy[xyIndex]], + itemLayout[xy[xyIndex]] + itemLayout[wh[xyIndex]] + ]; + layoutRange[1] < layoutRange[0] && layoutRange.reverse(); + return inLineRange(layoutRange[0], range) + || inLineRange(layoutRange[1], range) + || inLineRange(range[0], layoutRange) + || inLineRange(range[1], layoutRange); + } + } + }; +} + +function inLineRange(p, range) { + return range[0] <= p && p <= range[1]; +} + +/* +* 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. +*/ + +var STATE_LIST = ['inBrush', 'outOfBrush']; +var DISPATCH_METHOD = '__ecBrushSelect'; +var DISPATCH_FLAG = '__ecInBrushSelectEvent'; +var PRIORITY_BRUSH = PRIORITY.VISUAL.BRUSH; + +/** + * Layout for visual, the priority higher than other layout, and before brush visual. + */ +registerLayout(PRIORITY_BRUSH, function (ecModel, api, payload) { + ecModel.eachComponent({mainType: 'brush'}, function (brushModel) { + + payload && payload.type === 'takeGlobalCursor' && brushModel.setBrushOption( + payload.key === 'brush' ? payload.brushOption : {brushType: false} + ); + + var brushTargetManager = brushModel.brushTargetManager = new BrushTargetManager(brushModel.option, ecModel); + + brushTargetManager.setInputRanges(brushModel.areas, ecModel); + }); +}); + +/** + * Register the visual encoding if this modules required. + */ +registerVisual(PRIORITY_BRUSH, function (ecModel, api, payload) { + + var brushSelected = []; + var throttleType; + var throttleDelay; + + ecModel.eachComponent({mainType: 'brush'}, function (brushModel, brushIndex) { + + var thisBrushSelected = { + brushId: brushModel.id, + brushIndex: brushIndex, + brushName: brushModel.name, + areas: clone(brushModel.areas), + selected: [] + }; + // Every brush component exists in event params, convenient + // for user to find by index. + brushSelected.push(thisBrushSelected); + + var brushOption = brushModel.option; + var brushLink = brushOption.brushLink; + var linkedSeriesMap = []; + var selectedDataIndexForLink = []; + var rangeInfoBySeries = []; + var hasBrushExists = 0; + + if (!brushIndex) { // Only the first throttle setting works. + throttleType = brushOption.throttleType; + throttleDelay = brushOption.throttleDelay; + } + + // Add boundingRect and selectors to range. + var areas = map(brushModel.areas, function (area) { + return bindSelector( + defaults( + {boundingRect: boundingRectBuilders[area.brushType](area)}, + area + ) + ); + }); + + var visualMappings = createVisualMappings( + brushModel.option, STATE_LIST, function (mappingOption) { + mappingOption.mappingMethod = 'fixed'; + } + ); + + isArray(brushLink) && each$1(brushLink, function (seriesIndex) { + linkedSeriesMap[seriesIndex] = 1; + }); + + function linkOthers(seriesIndex) { + return brushLink === 'all' || linkedSeriesMap[seriesIndex]; + } + + // If no supported brush or no brush on the series, + // all visuals should be in original state. + function brushed(rangeInfoList) { + return !!rangeInfoList.length; + } + + /** + * Logic for each series: (If the logic has to be modified one day, do it carefully!) + * + * ( brushed ┬ && ┬hasBrushExist ┬ && linkOthers ) => StepA: ┬record, ┬ StepB: ┬visualByRecord. + * !brushed┘ ├hasBrushExist ┤ └nothing,┘ ├visualByRecord. + * └!hasBrushExist┘ └nothing. + * ( !brushed && ┬hasBrushExist ┬ && linkOthers ) => StepA: nothing, StepB: ┬visualByRecord. + * └!hasBrushExist┘ └nothing. + * ( brushed ┬ && !linkOthers ) => StepA: nothing, StepB: ┬visualByCheck. + * !brushed┘ └nothing. + * ( !brushed && !linkOthers ) => StepA: nothing, StepB: nothing. + */ + + // Step A + ecModel.eachSeries(function (seriesModel, seriesIndex) { + var rangeInfoList = rangeInfoBySeries[seriesIndex] = []; + + seriesModel.subType === 'parallel' + ? stepAParallel(seriesModel, seriesIndex, rangeInfoList) + : stepAOthers(seriesModel, seriesIndex, rangeInfoList); + }); + + function stepAParallel(seriesModel, seriesIndex) { + var coordSys = seriesModel.coordinateSystem; + hasBrushExists |= coordSys.hasAxisBrushed(); + + linkOthers(seriesIndex) && coordSys.eachActiveState( + seriesModel.getData(), + function (activeState, dataIndex) { + activeState === 'active' && (selectedDataIndexForLink[dataIndex] = 1); + } + ); + } + + function stepAOthers(seriesModel, seriesIndex, rangeInfoList) { + var selectorsByBrushType = getSelectorsByBrushType(seriesModel); + if (!selectorsByBrushType || brushModelNotControll(brushModel, seriesIndex)) { + return; + } + + each$1(areas, function (area) { + selectorsByBrushType[area.brushType] + && brushModel.brushTargetManager.controlSeries(area, seriesModel, ecModel) + && rangeInfoList.push(area); + hasBrushExists |= brushed(rangeInfoList); + }); + + if (linkOthers(seriesIndex) && brushed(rangeInfoList)) { + var data = seriesModel.getData(); + data.each(function (dataIndex) { + if (checkInRange(selectorsByBrushType, rangeInfoList, data, dataIndex)) { + selectedDataIndexForLink[dataIndex] = 1; + } + }); + } + } + + // Step B + ecModel.eachSeries(function (seriesModel, seriesIndex) { + var seriesBrushSelected = { + seriesId: seriesModel.id, + seriesIndex: seriesIndex, + seriesName: seriesModel.name, + dataIndex: [] + }; + // Every series exists in event params, convenient + // for user to find series by seriesIndex. + thisBrushSelected.selected.push(seriesBrushSelected); + + var selectorsByBrushType = getSelectorsByBrushType(seriesModel); + var rangeInfoList = rangeInfoBySeries[seriesIndex]; + + var data = seriesModel.getData(); + var getValueState = linkOthers(seriesIndex) + ? function (dataIndex) { + return selectedDataIndexForLink[dataIndex] + ? (seriesBrushSelected.dataIndex.push(data.getRawIndex(dataIndex)), 'inBrush') + : 'outOfBrush'; + } + : function (dataIndex) { + return checkInRange(selectorsByBrushType, rangeInfoList, data, dataIndex) + ? (seriesBrushSelected.dataIndex.push(data.getRawIndex(dataIndex)), 'inBrush') + : 'outOfBrush'; + }; + + // If no supported brush or no brush, all visuals are in original state. + (linkOthers(seriesIndex) ? hasBrushExists : brushed(rangeInfoList)) + && applyVisual( + STATE_LIST, visualMappings, data, getValueState + ); + }); + + }); + + dispatchAction(api, throttleType, throttleDelay, brushSelected, payload); +}); + +function dispatchAction(api, throttleType, throttleDelay, brushSelected, payload) { + // This event will not be triggered when `setOpion`, otherwise dead lock may + // triggered when do `setOption` in event listener, which we do not find + // satisfactory way to solve yet. Some considered resolutions: + // (a) Diff with prevoius selected data ant only trigger event when changed. + // But store previous data and diff precisely (i.e., not only by dataIndex, but + // also detect value changes in selected data) might bring complexity or fragility. + // (b) Use spectial param like `silent` to suppress event triggering. + // But such kind of volatile param may be weird in `setOption`. + if (!payload) { + return; + } + + var zr = api.getZr(); + if (zr[DISPATCH_FLAG]) { + return; + } + + if (!zr[DISPATCH_METHOD]) { + zr[DISPATCH_METHOD] = doDispatch; + } + + var fn = createOrUpdate(zr, DISPATCH_METHOD, throttleDelay, throttleType); + + fn(api, brushSelected); +} + +function doDispatch(api, brushSelected) { + if (!api.isDisposed()) { + var zr = api.getZr(); + zr[DISPATCH_FLAG] = true; + api.dispatchAction({ + type: 'brushSelect', + batch: brushSelected + }); + zr[DISPATCH_FLAG] = false; + } +} + +function checkInRange(selectorsByBrushType, rangeInfoList, data, dataIndex) { + for (var i = 0, len = rangeInfoList.length; i < len; i++) { + var area = rangeInfoList[i]; + if (selectorsByBrushType[area.brushType]( + dataIndex, data, area.selectors, area + )) { + return true; + } + } +} + +function getSelectorsByBrushType(seriesModel) { + var brushSelector = seriesModel.brushSelector; + if (isString(brushSelector)) { + var sels = []; + each$1(selector, function (selectorsByElementType, brushType) { + sels[brushType] = function (dataIndex, data, selectors, area) { + var itemLayout = data.getItemLayout(dataIndex); + return selectorsByElementType[brushSelector](itemLayout, selectors, area); + }; + }); + return sels; + } + else if (isFunction$1(brushSelector)) { + var bSelector = {}; + each$1(selector, function (sel, brushType) { + bSelector[brushType] = brushSelector; + }); + return bSelector; + } + return brushSelector; +} + +function brushModelNotControll(brushModel, seriesIndex) { + var seriesIndices = brushModel.option.seriesIndex; + return seriesIndices != null + && seriesIndices !== 'all' + && ( + isArray(seriesIndices) + ? indexOf(seriesIndices, seriesIndex) < 0 + : seriesIndex !== seriesIndices + ); +} + +function bindSelector(area) { + var selectors = area.selectors = {}; + each$1(selector[area.brushType], function (selFn, elType) { + // Do not use function binding or curry for performance. + selectors[elType] = function (itemLayout) { + return selFn(itemLayout, selectors, area); + }; + }); + return area; +} + +var boundingRectBuilders = { + + lineX: noop, + + lineY: noop, + + rect: function (area) { + return getBoundingRectFromMinMax(area.range); + }, + + polygon: function (area) { + var minMax; + var range = area.range; + + for (var i = 0, len = range.length; i < len; i++) { + minMax = minMax || [[Infinity, -Infinity], [Infinity, -Infinity]]; + var rg = range[i]; + rg[0] < minMax[0][0] && (minMax[0][0] = rg[0]); + rg[0] > minMax[0][1] && (minMax[0][1] = rg[0]); + rg[1] < minMax[1][0] && (minMax[1][0] = rg[1]); + rg[1] > minMax[1][1] && (minMax[1][1] = rg[1]); + } + + return minMax && getBoundingRectFromMinMax(minMax); + } +}; + +function getBoundingRectFromMinMax(minMax) { + return new BoundingRect( + minMax[0][0], + minMax[1][0], + minMax[0][1] - minMax[0][0], + minMax[1][1] - minMax[1][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. +*/ + +var DEFAULT_OUT_OF_BRUSH_COLOR = ['#ddd']; + +var BrushModel = extendComponentModel({ + + type: 'brush', + + dependencies: ['geo', 'grid', 'xAxis', 'yAxis', 'parallel', 'series'], + + /** + * @protected + */ + defaultOption: { + // inBrush: null, + // outOfBrush: null, + toolbox: null, // Default value see preprocessor. + brushLink: null, // Series indices array, broadcast using dataIndex. + // or 'all', which means all series. 'none' or null means no series. + seriesIndex: 'all', // seriesIndex array, specify series controlled by this brush component. + geoIndex: null, // + xAxisIndex: null, + yAxisIndex: null, + + brushType: 'rect', // Default brushType, see BrushController. + brushMode: 'single', // Default brushMode, 'single' or 'multiple' + transformable: true, // Default transformable. + brushStyle: { // Default brushStyle + borderWidth: 1, + color: 'rgba(120,140,180,0.3)', + borderColor: 'rgba(120,140,180,0.8)' + }, + + throttleType: 'fixRate', // Throttle in brushSelected event. 'fixRate' or 'debounce'. + // If null, no throttle. Valid only in the first brush component + throttleDelay: 0, // Unit: ms, 0 means every event will be triggered. + + // FIXME + // 试验效果 + removeOnClick: true, + + z: 10000 + }, + + /** + * @readOnly + * @type {Array.} + */ + areas: [], + + /** + * Current activated brush type. + * If null, brush is inactived. + * see module:echarts/component/helper/BrushController + * @readOnly + * @type {string} + */ + brushType: null, + + /** + * Current brush opt. + * see module:echarts/component/helper/BrushController + * @readOnly + * @type {Object} + */ + brushOption: {}, + + /** + * @readOnly + * @type {Array.} + */ + coordInfoList: [], + + optionUpdated: function (newOption, isInit) { + var thisOption = this.option; + + !isInit && replaceVisualOption( + thisOption, newOption, ['inBrush', 'outOfBrush'] + ); + + var inBrush = thisOption.inBrush = thisOption.inBrush || {}; + // Always give default visual, consider setOption at the second time. + thisOption.outOfBrush = thisOption.outOfBrush || {color: DEFAULT_OUT_OF_BRUSH_COLOR}; + + if (!inBrush.hasOwnProperty('liftZ')) { + // Bigger than the highlight z lift, otherwise it will + // be effected by the highlight z when brush. + inBrush.liftZ = 5; + } + }, + + /** + * If ranges is null/undefined, range state remain. + * + * @param {Array.} [ranges] + */ + setAreas: function (areas) { + if (__DEV__) { + assert$1(isArray(areas)); + each$1(areas, function (area) { + assert$1(area.brushType, 'Illegal areas'); + }); + } + + // If ranges is null/undefined, range state remain. + // This helps user to dispatchAction({type: 'brush'}) with no areas + // set but just want to get the current brush select info from a `brush` event. + if (!areas) { + return; + } + + this.areas = map(areas, function (area) { + return generateBrushOption(this.option, area); + }, this); + }, + + /** + * see module:echarts/component/helper/BrushController + * @param {Object} brushOption + */ + setBrushOption: function (brushOption) { + this.brushOption = generateBrushOption(this.option, brushOption); + this.brushType = this.brushOption.brushType; + } + +}); + +function generateBrushOption(option, brushOption) { + return merge( + { + brushType: option.brushType, + brushMode: option.brushMode, + transformable: option.transformable, + brushStyle: new Model(option.brushStyle).getItemStyle(), + removeOnClick: option.removeOnClick, + z: option.z + }, + brushOption, + true + ); +} + +/* +* 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. +*/ + +extendComponentView({ + + type: 'brush', + + init: function (ecModel, api) { + + /** + * @readOnly + * @type {module:echarts/model/Global} + */ + this.ecModel = ecModel; + + /** + * @readOnly + * @type {module:echarts/ExtensionAPI} + */ + this.api = api; + + /** + * @readOnly + * @type {module:echarts/component/brush/BrushModel} + */ + this.model; + + /** + * @private + * @type {module:echarts/component/helper/BrushController} + */ + (this._brushController = new BrushController(api.getZr())) + .on('brush', bind(this._onBrush, this)) + .mount(); + }, + + /** + * @override + */ + render: function (brushModel) { + this.model = brushModel; + return updateController.apply(this, arguments); + }, + + /** + * @override + */ + updateTransform: updateController, + + /** + * @override + */ + updateView: updateController, + + // /** + // * @override + // */ + // updateLayout: updateController, + + // /** + // * @override + // */ + // updateVisual: updateController, + + /** + * @override + */ + dispose: function () { + this._brushController.dispose(); + }, + + /** + * @private + */ + _onBrush: function (areas, opt) { + var modelId = this.model.id; + + this.model.brushTargetManager.setOutputRanges(areas, this.ecModel); + + // Action is not dispatched on drag end, because the drag end + // emits the same params with the last drag move event, and + // may have some delay when using touch pad, which makes + // animation not smooth (when using debounce). + (!opt.isEnd || opt.removeOnClick) && this.api.dispatchAction({ + type: 'brush', + brushId: modelId, + areas: clone(areas), + $from: modelId + }); + opt.isEnd && this.api.dispatchAction({ + type: 'brushEnd', + brushId: modelId, + areas: clone(areas), + $from: modelId + }); + } + +}); + +function updateController(brushModel, ecModel, api, payload) { + // Do not update controller when drawing. + (!payload || payload.$from !== brushModel.id) && this._brushController + .setPanels(brushModel.brushTargetManager.makePanelOpts(api)) + .enableBrush(brushModel.brushOption) + .updateCovers(brushModel.areas.slice()); +} + +/* +* 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. +*/ + +/** + * payload: { + * brushIndex: number, or, + * brushId: string, or, + * brushName: string, + * globalRanges: Array + * } + */ +registerAction( + {type: 'brush', event: 'brush' /*, update: 'updateView' */}, + function (payload, ecModel) { + ecModel.eachComponent({mainType: 'brush', query: payload}, function (brushModel) { + brushModel.setAreas(payload.areas); + }); + } +); + +/** + * payload: { + * brushComponents: [ + * { + * brushId, + * brushIndex, + * brushName, + * series: [ + * { + * seriesId, + * seriesIndex, + * seriesName, + * rawIndices: [21, 34, ...] + * }, + * ... + * ] + * }, + * ... + * ] + * } + */ +registerAction( + {type: 'brushSelect', event: 'brushSelected', update: 'none'}, + function () {} +); + +registerAction( + {type: 'brushEnd', event: 'brushEnd', update: 'none'}, + function () {} +); + +/* +* 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. +*/ + +var brushLang = lang.toolbox.brush; + +function Brush(model, ecModel, api) { + this.model = model; + this.ecModel = ecModel; + this.api = api; + + /** + * @private + * @type {string} + */ + this._brushType; + + /** + * @private + * @type {string} + */ + this._brushMode; +} + +Brush.defaultOption = { + show: true, + type: ['rect', 'polygon', 'lineX', 'lineY', 'keep', 'clear'], + icon: { + /* eslint-disable */ + rect: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13', // jshint ignore:line + polygon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2', // jshint ignore:line + lineX: 'M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4', // jshint ignore:line + lineY: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4', // jshint ignore:line + keep: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z', // jshint ignore:line + clear: 'M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2' // jshint ignore:line + /* eslint-enable */ + }, + // `rect`, `polygon`, `lineX`, `lineY`, `keep`, `clear` + title: clone(brushLang.title) +}; + +var proto$7 = Brush.prototype; + +// proto.updateLayout = function (featureModel, ecModel, api) { +/* eslint-disable */ +proto$7.render = +/* eslint-enable */ +proto$7.updateView = function (featureModel, ecModel, api) { + var brushType; + var brushMode; + var isBrushed; + + ecModel.eachComponent({mainType: 'brush'}, function (brushModel) { + brushType = brushModel.brushType; + brushMode = brushModel.brushOption.brushMode || 'single'; + isBrushed |= brushModel.areas.length; + }); + this._brushType = brushType; + this._brushMode = brushMode; + + each$1(featureModel.get('type', true), function (type) { + featureModel.setIconStatus( + type, + ( + type === 'keep' + ? brushMode === 'multiple' + : type === 'clear' + ? isBrushed + : type === brushType + ) ? 'emphasis' : 'normal' + ); + }); +}; + +proto$7.getIcons = function () { + var model = this.model; + var availableIcons = model.get('icon', true); + var icons = {}; + each$1(model.get('type', true), function (type) { + if (availableIcons[type]) { + icons[type] = availableIcons[type]; + } + }); + return icons; +}; + +proto$7.onclick = function (ecModel, api, type) { + var brushType = this._brushType; + var brushMode = this._brushMode; + + if (type === 'clear') { + // Trigger parallel action firstly + api.dispatchAction({ + type: 'axisAreaSelect', + intervals: [] + }); + + api.dispatchAction({ + type: 'brush', + command: 'clear', + // Clear all areas of all brush components. + areas: [] + }); + } + else { + api.dispatchAction({ + type: 'takeGlobalCursor', + key: 'brush', + brushOption: { + brushType: type === 'keep' + ? brushType + : (brushType === type ? false : type), + brushMode: type === 'keep' + ? (brushMode === 'multiple' ? 'single' : 'multiple') + : brushMode + } + }); + } +}; + +register$1('brush', Brush); + +/* +* 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. +*/ + +/** + * Brush component entry + */ + +registerPreprocessor(preprocessor$1); + +/* +* 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. +*/ + +// Model +extendComponentModel({ + + type: 'title', + + layoutMode: {type: 'box', ignoreSize: true}, + + defaultOption: { + // 一级层叠 + zlevel: 0, + // 二级层叠 + z: 6, + show: true, + + text: '', + // 超链接跳转 + // link: null, + // 仅支持self | blank + target: 'blank', + subtext: '', + + // 超链接跳转 + // sublink: null, + // 仅支持self | blank + subtarget: 'blank', + + // 'center' ¦ 'left' ¦ 'right' + // ¦ {number}(x坐标,单位px) + left: 0, + // 'top' ¦ 'bottom' ¦ 'center' + // ¦ {number}(y坐标,单位px) + top: 0, + + // 水平对齐 + // 'auto' | 'left' | 'right' | 'center' + // 默认根据 left 的位置判断是左对齐还是右对齐 + // textAlign: null + // + // 垂直对齐 + // 'auto' | 'top' | 'bottom' | 'middle' + // 默认根据 top 位置判断是上对齐还是下对齐 + // textVerticalAlign: null + // textBaseline: null // The same as textVerticalAlign. + + backgroundColor: 'rgba(0,0,0,0)', + + // 标题边框颜色 + borderColor: '#ccc', + + // 标题边框线宽,单位px,默认为0(无边框) + borderWidth: 0, + + // 标题内边距,单位px,默认各方向内边距为5, + // 接受数组分别设定上右下左边距,同css + padding: 5, + + // 主副标题纵向间隔,单位px,默认为10, + itemGap: 10, + textStyle: { + fontSize: 18, + fontWeight: 'bolder', + color: '#333' + }, + subtextStyle: { + color: '#aaa' + } + } +}); + +// View +extendComponentView({ + + type: 'title', + + render: function (titleModel, ecModel, api) { + this.group.removeAll(); + + if (!titleModel.get('show')) { + return; + } + + var group = this.group; + + var textStyleModel = titleModel.getModel('textStyle'); + var subtextStyleModel = titleModel.getModel('subtextStyle'); + + var textAlign = titleModel.get('textAlign'); + var textVerticalAlign = retrieve2( + titleModel.get('textBaseline'), titleModel.get('textVerticalAlign') + ); + + var textEl = new Text({ + style: setTextStyle({}, textStyleModel, { + text: titleModel.get('text'), + textFill: textStyleModel.getTextColor() + }, {disableBox: true}), + z2: 10 + }); + + var textRect = textEl.getBoundingRect(); + + var subText = titleModel.get('subtext'); + var subTextEl = new Text({ + style: setTextStyle({}, subtextStyleModel, { + text: subText, + textFill: subtextStyleModel.getTextColor(), + y: textRect.height + titleModel.get('itemGap'), + textVerticalAlign: 'top' + }, {disableBox: true}), + z2: 10 + }); + + var link = titleModel.get('link'); + var sublink = titleModel.get('sublink'); + var triggerEvent = titleModel.get('triggerEvent', true); + + textEl.silent = !link && !triggerEvent; + subTextEl.silent = !sublink && !triggerEvent; + + if (link) { + textEl.on('click', function () { + window.open(link, '_' + titleModel.get('target')); + }); + } + if (sublink) { + subTextEl.on('click', function () { + window.open(sublink, '_' + titleModel.get('subtarget')); + }); + } + + textEl.eventData = subTextEl.eventData = triggerEvent + ? { + componentType: 'title', + componentIndex: titleModel.componentIndex + } + : null; + + group.add(textEl); + subText && group.add(subTextEl); + // If no subText, but add subTextEl, there will be an empty line. + + var groupRect = group.getBoundingRect(); + var layoutOption = titleModel.getBoxLayoutParams(); + layoutOption.width = groupRect.width; + layoutOption.height = groupRect.height; + var layoutRect = getLayoutRect( + layoutOption, { + width: api.getWidth(), + height: api.getHeight() + }, titleModel.get('padding') + ); + // Adjust text align based on position + if (!textAlign) { + // Align left if title is on the left. center and right is same + textAlign = titleModel.get('left') || titleModel.get('right'); + if (textAlign === 'middle') { + textAlign = 'center'; + } + // Adjust layout by text align + if (textAlign === 'right') { + layoutRect.x += layoutRect.width; + } + else if (textAlign === 'center') { + layoutRect.x += layoutRect.width / 2; + } + } + if (!textVerticalAlign) { + textVerticalAlign = titleModel.get('top') || titleModel.get('bottom'); + if (textVerticalAlign === 'center') { + textVerticalAlign = 'middle'; + } + if (textVerticalAlign === 'bottom') { + layoutRect.y += layoutRect.height; + } + else if (textVerticalAlign === 'middle') { + layoutRect.y += layoutRect.height / 2; + } + + textVerticalAlign = textVerticalAlign || 'top'; + } + + group.attr('position', [layoutRect.x, layoutRect.y]); + var alignStyle = { + textAlign: textAlign, + textVerticalAlign: textVerticalAlign + }; + textEl.setStyle(alignStyle); + subTextEl.setStyle(alignStyle); + + // Render background + // Get groupRect again because textAlign has been changed + groupRect = group.getBoundingRect(); + var padding = layoutRect.margin; + var style = titleModel.getItemStyle(['color', 'opacity']); + style.fill = titleModel.get('backgroundColor'); + var rect = new Rect({ + shape: { + x: groupRect.x - padding[3], + y: groupRect.y - padding[0], + width: groupRect.width + padding[1] + padding[3], + height: groupRect.height + padding[0] + padding[2], + r: titleModel.get('borderRadius') + }, + style: style, + subPixelOptimize: true, + silent: true + }); + + group.add(rect); + } +}); + +/* +* 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. +*/ + +var preprocessor$2 = function (option) { + var timelineOpt = option && option.timeline; + + if (!isArray(timelineOpt)) { + timelineOpt = timelineOpt ? [timelineOpt] : []; + } + + each$1(timelineOpt, function (opt) { + if (!opt) { + return; + } + + compatibleEC2(opt); + }); +}; + +function compatibleEC2(opt) { + var type = opt.type; + + var ec2Types = {'number': 'value', 'time': 'time'}; + + // Compatible with ec2 + if (ec2Types[type]) { + opt.axisType = ec2Types[type]; + delete opt.type; + } + + transferItem(opt); + + if (has$1(opt, 'controlPosition')) { + var controlStyle = opt.controlStyle || (opt.controlStyle = {}); + if (!has$1(controlStyle, 'position')) { + controlStyle.position = opt.controlPosition; + } + if (controlStyle.position === 'none' && !has$1(controlStyle, 'show')) { + controlStyle.show = false; + delete controlStyle.position; + } + delete opt.controlPosition; + } + + each$1(opt.data || [], function (dataItem) { + if (isObject$1(dataItem) && !isArray(dataItem)) { + if (!has$1(dataItem, 'value') && has$1(dataItem, 'name')) { + // In ec2, using name as value. + dataItem.value = dataItem.name; + } + transferItem(dataItem); + } + }); +} + +function transferItem(opt) { + var itemStyle = opt.itemStyle || (opt.itemStyle = {}); + + var itemStyleEmphasis = itemStyle.emphasis || (itemStyle.emphasis = {}); + + // Transfer label out + var label = opt.label || (opt.label || {}); + var labelNormal = label.normal || (label.normal = {}); + var excludeLabelAttr = {normal: 1, emphasis: 1}; + + each$1(label, function (value, name) { + if (!excludeLabelAttr[name] && !has$1(labelNormal, name)) { + labelNormal[name] = value; + } + }); + + if (itemStyleEmphasis.label && !has$1(label, 'emphasis')) { + label.emphasis = itemStyleEmphasis.label; + delete itemStyleEmphasis.label; + } +} + +function has$1(obj, attr) { + return obj.hasOwnProperty(attr); +} + +/* +* 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. +*/ + +ComponentModel.registerSubTypeDefaulter('timeline', function () { + // Only slider now. + return 'slider'; +}); + +/* +* 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. +*/ + +registerAction( + + {type: 'timelineChange', event: 'timelineChanged', update: 'prepareAndUpdate'}, + + function (payload, ecModel) { + + var timelineModel = ecModel.getComponent('timeline'); + if (timelineModel && payload.currentIndex != null) { + timelineModel.setCurrentIndex(payload.currentIndex); + + if (!timelineModel.get('loop', true) && timelineModel.isIndexMax()) { + timelineModel.setPlayState(false); + } + } + + // Set normalized currentIndex to payload. + ecModel.resetOption('timeline'); + + return defaults({ + currentIndex: timelineModel.option.currentIndex + }, payload); + } +); + +registerAction( + + {type: 'timelinePlayChange', event: 'timelinePlayChanged', update: 'update'}, + + function (payload, ecModel) { + var timelineModel = ecModel.getComponent('timeline'); + if (timelineModel && payload.playState != null) { + timelineModel.setPlayState(payload.playState); + } + } +); + +/* +* 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. +*/ + +var TimelineModel = ComponentModel.extend({ + + type: 'timeline', + + layoutMode: 'box', + + /** + * @protected + */ + defaultOption: { + + zlevel: 0, // 一级层叠 + z: 4, // 二级层叠 + show: true, + + axisType: 'time', // 模式是时间类型,支持 value, category + + realtime: true, + + left: '20%', + top: null, + right: '20%', + bottom: 0, + width: null, + height: 40, + padding: 5, + + controlPosition: 'left', // 'left' 'right' 'top' 'bottom' 'none' + autoPlay: false, + rewind: false, // 反向播放 + loop: true, + playInterval: 2000, // 播放时间间隔,单位ms + + currentIndex: 0, + + itemStyle: {}, + label: { + color: '#000' + }, + + data: [] + }, + + /** + * @override + */ + init: function (option, parentModel, ecModel) { + + /** + * @private + * @type {module:echarts/data/List} + */ + this._data; + + /** + * @private + * @type {Array.} + */ + this._names; + + this.mergeDefaultAndTheme(option, ecModel); + this._initData(); + }, + + /** + * @override + */ + mergeOption: function (option) { + TimelineModel.superApply(this, 'mergeOption', arguments); + this._initData(); + }, + + /** + * @param {number} [currentIndex] + */ + setCurrentIndex: function (currentIndex) { + if (currentIndex == null) { + currentIndex = this.option.currentIndex; + } + var count = this._data.count(); + + if (this.option.loop) { + currentIndex = (currentIndex % count + count) % count; + } + else { + currentIndex >= count && (currentIndex = count - 1); + currentIndex < 0 && (currentIndex = 0); + } + + this.option.currentIndex = currentIndex; + }, + + /** + * @return {number} currentIndex + */ + getCurrentIndex: function () { + return this.option.currentIndex; + }, + + /** + * @return {boolean} + */ + isIndexMax: function () { + return this.getCurrentIndex() >= this._data.count() - 1; + }, + + /** + * @param {boolean} state true: play, false: stop + */ + setPlayState: function (state) { + this.option.autoPlay = !!state; + }, + + /** + * @return {boolean} true: play, false: stop + */ + getPlayState: function () { + return !!this.option.autoPlay; + }, + + /** + * @private + */ + _initData: function () { + var thisOption = this.option; + var dataArr = thisOption.data || []; + var axisType = thisOption.axisType; + var names = this._names = []; + + if (axisType === 'category') { + var idxArr = []; + each$1(dataArr, function (item, index) { + var value = getDataItemValue(item); + var newItem; + + if (isObject$1(item)) { + newItem = clone(item); + newItem.value = index; + } + else { + newItem = index; + } + + idxArr.push(newItem); + + if (!isString(value) && (value == null || isNaN(value))) { + value = ''; + } + + names.push(value + ''); + }); + dataArr = idxArr; + } + + var dimType = ({category: 'ordinal', time: 'time'})[axisType] || 'number'; + + var data = this._data = new List([{name: 'value', type: dimType}], this); + + data.initData(dataArr, names); + }, + + getData: function () { + return this._data; + }, + + /** + * @public + * @return {Array.} categoreis + */ + getCategories: function () { + if (this.get('axisType') === 'category') { + return this._names.slice(); + } + } + +}); + +/* +* 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. +*/ + +var SliderTimelineModel = TimelineModel.extend({ + + type: 'timeline.slider', + + /** + * @protected + */ + defaultOption: { + + backgroundColor: 'rgba(0,0,0,0)', // 时间轴背景颜色 + borderColor: '#ccc', // 时间轴边框颜色 + borderWidth: 0, // 时间轴边框线宽,单位px,默认为0(无边框) + + orient: 'horizontal', // 'vertical' + inverse: false, + + tooltip: { // boolean or Object + trigger: 'item' // data item may also have tootip attr. + }, + + symbol: 'emptyCircle', + symbolSize: 10, + + lineStyle: { + show: true, + width: 2, + color: '#304654' + }, + label: { // 文本标签 + position: 'auto', // auto left right top bottom + // When using number, label position is not + // restricted by viewRect. + // positive: right/bottom, negative: left/top + show: true, + interval: 'auto', + rotate: 0, + // formatter: null, + // 其余属性默认使用全局文本样式,详见TEXTSTYLE + color: '#304654' + }, + itemStyle: { + color: '#304654', + borderWidth: 1 + }, + + checkpointStyle: { + symbol: 'circle', + symbolSize: 13, + color: '#c23531', + borderWidth: 5, + borderColor: 'rgba(194,53,49, 0.5)', + animation: true, + animationDuration: 300, + animationEasing: 'quinticInOut' + }, + + controlStyle: { + show: true, + showPlayBtn: true, + showPrevBtn: true, + showNextBtn: true, + itemSize: 22, + itemGap: 12, + position: 'left', // 'left' 'right' 'top' 'bottom' + playIcon: 'path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z', // jshint ignore:line + stopIcon: 'path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z', // jshint ignore:line + nextIcon: 'path://M18.6,50.8l22.5-22.5c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7L18.7,4.4c-0.1-0.1-0.2-0.3-0.2-0.5 c0-0.4,0.3-0.8,0.8-0.8c0.2,0,0.5,0.1,0.6,0.3l23.5,23.5l0,0c0.2,0.2,0.3,0.4,0.3,0.7c0,0.3-0.1,0.5-0.3,0.7l-0.1,0.1L19.7,52 c-0.1,0.1-0.3,0.2-0.5,0.2c-0.4,0-0.8-0.3-0.8-0.8C18.4,51.2,18.5,51,18.6,50.8z', // jshint ignore:line + prevIcon: 'path://M43,52.8L20.4,30.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.5,0.3-0.7L42.9,6.4c0.1-0.1,0.2-0.3,0.2-0.5 c0-0.4-0.3-0.8-0.8-0.8c-0.2,0-0.5,0.1-0.6,0.3L18.3,28.8l0,0c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l0.1,0.1L41.9,54 c0.1,0.1,0.3,0.2,0.5,0.2c0.4,0,0.8-0.3,0.8-0.8C43.2,53.2,43.1,53,43,52.8z', // jshint ignore:line + + color: '#304654', + borderColor: '#304654', + borderWidth: 1 + }, + + emphasis: { + label: { + show: true, + // 其余属性默认使用全局文本样式,详见TEXTSTYLE + color: '#c23531' + }, + + itemStyle: { + color: '#c23531' + }, + + controlStyle: { + color: '#c23531', + borderColor: '#c23531', + borderWidth: 2 + } + }, + data: [] + } + +}); + +mixin(SliderTimelineModel, dataFormatMixin); + +/* +* 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. +*/ + +var TimelineView = Component$1.extend({ + type: 'timeline' +}); + +/* +* 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. +*/ + +/** + * Extend axis 2d + * @constructor module:echarts/coord/cartesian/Axis2D + * @extends {module:echarts/coord/cartesian/Axis} + * @param {string} dim + * @param {*} scale + * @param {Array.} coordExtent + * @param {string} axisType + * @param {string} position + */ +var TimelineAxis = function (dim, scale, coordExtent, axisType) { + + Axis.call(this, dim, scale, coordExtent); + + /** + * Axis type + * - 'category' + * - 'value' + * - 'time' + * - 'log' + * @type {string} + */ + this.type = axisType || 'value'; + + /** + * Axis model + * @param {module:echarts/component/TimelineModel} + */ + this.model = null; +}; + +TimelineAxis.prototype = { + + constructor: TimelineAxis, + + /** + * @override + */ + getLabelModel: function () { + return this.model.getModel('label'); + }, + + /** + * @override + */ + isHorizontal: function () { + return this.model.get('orient') === 'horizontal'; + } + +}; + +inherits(TimelineAxis, Axis); + +/* +* 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. +*/ + +var bind$4 = bind; +var each$24 = each$1; + +var PI$5 = Math.PI; + +TimelineView.extend({ + + type: 'timeline.slider', + + init: function (ecModel, api) { + + this.api = api; + + /** + * @private + * @type {module:echarts/component/timeline/TimelineAxis} + */ + this._axis; + + /** + * @private + * @type {module:zrender/core/BoundingRect} + */ + this._viewRect; + + /** + * @type {number} + */ + this._timer; + + /** + * @type {module:zrender/Element} + */ + this._currentPointer; + + /** + * @type {module:zrender/container/Group} + */ + this._mainGroup; + + /** + * @type {module:zrender/container/Group} + */ + this._labelGroup; + }, + + /** + * @override + */ + render: function (timelineModel, ecModel, api, payload) { + this.model = timelineModel; + this.api = api; + this.ecModel = ecModel; + + this.group.removeAll(); + + if (timelineModel.get('show', true)) { + + var layoutInfo = this._layout(timelineModel, api); + var mainGroup = this._createGroup('mainGroup'); + var labelGroup = this._createGroup('labelGroup'); + + /** + * @private + * @type {module:echarts/component/timeline/TimelineAxis} + */ + var axis = this._axis = this._createAxis(layoutInfo, timelineModel); + + timelineModel.formatTooltip = function (dataIndex) { + return encodeHTML(axis.scale.getLabel(dataIndex)); + }; + + each$24( + ['AxisLine', 'AxisTick', 'Control', 'CurrentPointer'], + function (name) { + this['_render' + name](layoutInfo, mainGroup, axis, timelineModel); + }, + this + ); + + this._renderAxisLabel(layoutInfo, labelGroup, axis, timelineModel); + this._position(layoutInfo, timelineModel); + } + + this._doPlayStop(); + }, + + /** + * @override + */ + remove: function () { + this._clearTimer(); + this.group.removeAll(); + }, + + /** + * @override + */ + dispose: function () { + this._clearTimer(); + }, + + _layout: function (timelineModel, api) { + var labelPosOpt = timelineModel.get('label.position'); + var orient = timelineModel.get('orient'); + var viewRect = getViewRect$5(timelineModel, api); + // Auto label offset. + if (labelPosOpt == null || labelPosOpt === 'auto') { + labelPosOpt = orient === 'horizontal' + ? ((viewRect.y + viewRect.height / 2) < api.getHeight() / 2 ? '-' : '+') + : ((viewRect.x + viewRect.width / 2) < api.getWidth() / 2 ? '+' : '-'); + } + else if (isNaN(labelPosOpt)) { + labelPosOpt = ({ + horizontal: {top: '-', bottom: '+'}, + vertical: {left: '-', right: '+'} + })[orient][labelPosOpt]; + } + + var labelAlignMap = { + horizontal: 'center', + vertical: (labelPosOpt >= 0 || labelPosOpt === '+') ? 'left' : 'right' + }; + + var labelBaselineMap = { + horizontal: (labelPosOpt >= 0 || labelPosOpt === '+') ? 'top' : 'bottom', + vertical: 'middle' + }; + var rotationMap = { + horizontal: 0, + vertical: PI$5 / 2 + }; + + // Position + var mainLength = orient === 'vertical' ? viewRect.height : viewRect.width; + + var controlModel = timelineModel.getModel('controlStyle'); + var showControl = controlModel.get('show', true); + var controlSize = showControl ? controlModel.get('itemSize') : 0; + var controlGap = showControl ? controlModel.get('itemGap') : 0; + var sizePlusGap = controlSize + controlGap; + + // Special label rotate. + var labelRotation = timelineModel.get('label.rotate') || 0; + labelRotation = labelRotation * PI$5 / 180; // To radian. + + var playPosition; + var prevBtnPosition; + var nextBtnPosition; + var axisExtent; + var controlPosition = controlModel.get('position', true); + var showPlayBtn = showControl && controlModel.get('showPlayBtn', true); + var showPrevBtn = showControl && controlModel.get('showPrevBtn', true); + var showNextBtn = showControl && controlModel.get('showNextBtn', true); + var xLeft = 0; + var xRight = mainLength; + + // position[0] means left, position[1] means middle. + if (controlPosition === 'left' || controlPosition === 'bottom') { + showPlayBtn && (playPosition = [0, 0], xLeft += sizePlusGap); + showPrevBtn && (prevBtnPosition = [xLeft, 0], xLeft += sizePlusGap); + showNextBtn && (nextBtnPosition = [xRight - controlSize, 0], xRight -= sizePlusGap); + } + else { // 'top' 'right' + showPlayBtn && (playPosition = [xRight - controlSize, 0], xRight -= sizePlusGap); + showPrevBtn && (prevBtnPosition = [0, 0], xLeft += sizePlusGap); + showNextBtn && (nextBtnPosition = [xRight - controlSize, 0], xRight -= sizePlusGap); + } + axisExtent = [xLeft, xRight]; + + if (timelineModel.get('inverse')) { + axisExtent.reverse(); + } + + return { + viewRect: viewRect, + mainLength: mainLength, + orient: orient, + + rotation: rotationMap[orient], + labelRotation: labelRotation, + labelPosOpt: labelPosOpt, + labelAlign: timelineModel.get('label.align') || labelAlignMap[orient], + labelBaseline: timelineModel.get('label.verticalAlign') + || timelineModel.get('label.baseline') + || labelBaselineMap[orient], + + // Based on mainGroup. + playPosition: playPosition, + prevBtnPosition: prevBtnPosition, + nextBtnPosition: nextBtnPosition, + axisExtent: axisExtent, + + controlSize: controlSize, + controlGap: controlGap + }; + }, + + _position: function (layoutInfo, timelineModel) { + // Position is be called finally, because bounding rect is needed for + // adapt content to fill viewRect (auto adapt offset). + + // Timeline may be not all in the viewRect when 'offset' is specified + // as a number, because it is more appropriate that label aligns at + // 'offset' but not the other edge defined by viewRect. + + var mainGroup = this._mainGroup; + var labelGroup = this._labelGroup; + + var viewRect = layoutInfo.viewRect; + if (layoutInfo.orient === 'vertical') { + // transform to horizontal, inverse rotate by left-top point. + var m = create$1(); + var rotateOriginX = viewRect.x; + var rotateOriginY = viewRect.y + viewRect.height; + translate(m, m, [-rotateOriginX, -rotateOriginY]); + rotate(m, m, -PI$5 / 2); + translate(m, m, [rotateOriginX, rotateOriginY]); + viewRect = viewRect.clone(); + viewRect.applyTransform(m); + } + + var viewBound = getBound(viewRect); + var mainBound = getBound(mainGroup.getBoundingRect()); + var labelBound = getBound(labelGroup.getBoundingRect()); + + var mainPosition = mainGroup.position; + var labelsPosition = labelGroup.position; + + labelsPosition[0] = mainPosition[0] = viewBound[0][0]; + + var labelPosOpt = layoutInfo.labelPosOpt; + + if (isNaN(labelPosOpt)) { // '+' or '-' + var mainBoundIdx = labelPosOpt === '+' ? 0 : 1; + toBound(mainPosition, mainBound, viewBound, 1, mainBoundIdx); + toBound(labelsPosition, labelBound, viewBound, 1, 1 - mainBoundIdx); + } + else { + var mainBoundIdx = labelPosOpt >= 0 ? 0 : 1; + toBound(mainPosition, mainBound, viewBound, 1, mainBoundIdx); + labelsPosition[1] = mainPosition[1] + labelPosOpt; + } + + mainGroup.attr('position', mainPosition); + labelGroup.attr('position', labelsPosition); + mainGroup.rotation = labelGroup.rotation = layoutInfo.rotation; + + setOrigin(mainGroup); + setOrigin(labelGroup); + + function setOrigin(targetGroup) { + var pos = targetGroup.position; + targetGroup.origin = [ + viewBound[0][0] - pos[0], + viewBound[1][0] - pos[1] + ]; + } + + function getBound(rect) { + // [[xmin, xmax], [ymin, ymax]] + return [ + [rect.x, rect.x + rect.width], + [rect.y, rect.y + rect.height] + ]; + } + + function toBound(fromPos, from, to, dimIdx, boundIdx) { + fromPos[dimIdx] += to[dimIdx][boundIdx] - from[dimIdx][boundIdx]; + } + }, + + _createAxis: function (layoutInfo, timelineModel) { + var data = timelineModel.getData(); + var axisType = timelineModel.get('axisType'); + + var scale = createScaleByModel(timelineModel, axisType); + + // Customize scale. The `tickValue` is `dataIndex`. + scale.getTicks = function () { + return data.mapArray(['value'], function (value) { + return value; + }); + }; + + var dataExtent = data.getDataExtent('value'); + scale.setExtent(dataExtent[0], dataExtent[1]); + scale.niceTicks(); + + var axis = new TimelineAxis('value', scale, layoutInfo.axisExtent, axisType); + axis.model = timelineModel; + + return axis; + }, + + _createGroup: function (name) { + var newGroup = this['_' + name] = new Group(); + this.group.add(newGroup); + return newGroup; + }, + + _renderAxisLine: function (layoutInfo, group, axis, timelineModel) { + var axisExtent = axis.getExtent(); + + if (!timelineModel.get('lineStyle.show')) { + return; + } + + group.add(new Line({ + shape: { + x1: axisExtent[0], y1: 0, + x2: axisExtent[1], y2: 0 + }, + style: extend( + {lineCap: 'round'}, + timelineModel.getModel('lineStyle').getLineStyle() + ), + silent: true, + z2: 1 + })); + }, + + /** + * @private + */ + _renderAxisTick: function (layoutInfo, group, axis, timelineModel) { + var data = timelineModel.getData(); + // Show all ticks, despite ignoring strategy. + var ticks = axis.scale.getTicks(); + + // The value is dataIndex, see the costomized scale. + each$24(ticks, function (value) { + var tickCoord = axis.dataToCoord(value); + var itemModel = data.getItemModel(value); + var itemStyleModel = itemModel.getModel('itemStyle'); + var hoverStyleModel = itemModel.getModel('emphasis.itemStyle'); + var symbolOpt = { + position: [tickCoord, 0], + onclick: bind$4(this._changeTimeline, this, value) + }; + var el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt); + setHoverStyle(el, hoverStyleModel.getItemStyle()); + + if (itemModel.get('tooltip')) { + el.dataIndex = value; + el.dataModel = timelineModel; + } + else { + el.dataIndex = el.dataModel = null; + } + + }, this); + }, + + /** + * @private + */ + _renderAxisLabel: function (layoutInfo, group, axis, timelineModel) { + var labelModel = axis.getLabelModel(); + + if (!labelModel.get('show')) { + return; + } + + var data = timelineModel.getData(); + var labels = axis.getViewLabels(); + + each$24(labels, function (labelItem) { + // The tickValue is dataIndex, see the costomized scale. + var dataIndex = labelItem.tickValue; + + var itemModel = data.getItemModel(dataIndex); + var normalLabelModel = itemModel.getModel('label'); + var hoverLabelModel = itemModel.getModel('emphasis.label'); + var tickCoord = axis.dataToCoord(labelItem.tickValue); + var textEl = new Text({ + position: [tickCoord, 0], + rotation: layoutInfo.labelRotation - layoutInfo.rotation, + onclick: bind$4(this._changeTimeline, this, dataIndex), + silent: false + }); + setTextStyle(textEl.style, normalLabelModel, { + text: labelItem.formattedLabel, + textAlign: layoutInfo.labelAlign, + textVerticalAlign: layoutInfo.labelBaseline + }); + + group.add(textEl); + setHoverStyle( + textEl, setTextStyle({}, hoverLabelModel) + ); + + }, this); + }, + + /** + * @private + */ + _renderControl: function (layoutInfo, group, axis, timelineModel) { + var controlSize = layoutInfo.controlSize; + var rotation = layoutInfo.rotation; + + var itemStyle = timelineModel.getModel('controlStyle').getItemStyle(); + var hoverStyle = timelineModel.getModel('emphasis.controlStyle').getItemStyle(); + var rect = [0, -controlSize / 2, controlSize, controlSize]; + var playState = timelineModel.getPlayState(); + var inverse = timelineModel.get('inverse', true); + + makeBtn( + layoutInfo.nextBtnPosition, + 'controlStyle.nextIcon', + bind$4(this._changeTimeline, this, inverse ? '-' : '+') + ); + makeBtn( + layoutInfo.prevBtnPosition, + 'controlStyle.prevIcon', + bind$4(this._changeTimeline, this, inverse ? '+' : '-') + ); + makeBtn( + layoutInfo.playPosition, + 'controlStyle.' + (playState ? 'stopIcon' : 'playIcon'), + bind$4(this._handlePlayClick, this, !playState), + true + ); + + function makeBtn(position, iconPath, onclick, willRotate) { + if (!position) { + return; + } + var opt = { + position: position, + origin: [controlSize / 2, 0], + rotation: willRotate ? -rotation : 0, + rectHover: true, + style: itemStyle, + onclick: onclick + }; + var btn = makeIcon(timelineModel, iconPath, rect, opt); + group.add(btn); + setHoverStyle(btn, hoverStyle); + } + }, + + _renderCurrentPointer: function (layoutInfo, group, axis, timelineModel) { + var data = timelineModel.getData(); + var currentIndex = timelineModel.getCurrentIndex(); + var pointerModel = data.getItemModel(currentIndex).getModel('checkpointStyle'); + var me = this; + + var callback = { + onCreate: function (pointer) { + pointer.draggable = true; + pointer.drift = bind$4(me._handlePointerDrag, me); + pointer.ondragend = bind$4(me._handlePointerDragend, me); + pointerMoveTo(pointer, currentIndex, axis, timelineModel, true); + }, + onUpdate: function (pointer) { + pointerMoveTo(pointer, currentIndex, axis, timelineModel); + } + }; + + // Reuse when exists, for animation and drag. + this._currentPointer = giveSymbol( + pointerModel, pointerModel, this._mainGroup, {}, this._currentPointer, callback + ); + }, + + _handlePlayClick: function (nextState) { + this._clearTimer(); + this.api.dispatchAction({ + type: 'timelinePlayChange', + playState: nextState, + from: this.uid + }); + }, + + _handlePointerDrag: function (dx, dy, e) { + this._clearTimer(); + this._pointerChangeTimeline([e.offsetX, e.offsetY]); + }, + + _handlePointerDragend: function (e) { + this._pointerChangeTimeline([e.offsetX, e.offsetY], true); + }, + + _pointerChangeTimeline: function (mousePos, trigger) { + var toCoord = this._toAxisCoord(mousePos)[0]; + + var axis = this._axis; + var axisExtent = asc(axis.getExtent().slice()); + + toCoord > axisExtent[1] && (toCoord = axisExtent[1]); + toCoord < axisExtent[0] && (toCoord = axisExtent[0]); + + this._currentPointer.position[0] = toCoord; + this._currentPointer.dirty(); + + var targetDataIndex = this._findNearestTick(toCoord); + var timelineModel = this.model; + + if (trigger || ( + targetDataIndex !== timelineModel.getCurrentIndex() + && timelineModel.get('realtime') + )) { + this._changeTimeline(targetDataIndex); + } + }, + + _doPlayStop: function () { + this._clearTimer(); + + if (this.model.getPlayState()) { + this._timer = setTimeout( + bind$4(handleFrame, this), + this.model.get('playInterval') + ); + } + + function handleFrame() { + // Do not cache + var timelineModel = this.model; + this._changeTimeline( + timelineModel.getCurrentIndex() + + (timelineModel.get('rewind', true) ? -1 : 1) + ); + } + }, + + _toAxisCoord: function (vertex) { + var trans = this._mainGroup.getLocalTransform(); + return applyTransform$1(vertex, trans, true); + }, + + _findNearestTick: function (axisCoord) { + var data = this.model.getData(); + var dist = Infinity; + var targetDataIndex; + var axis = this._axis; + + data.each(['value'], function (value, dataIndex) { + var coord = axis.dataToCoord(value); + var d = Math.abs(coord - axisCoord); + if (d < dist) { + dist = d; + targetDataIndex = dataIndex; + } + }); + + return targetDataIndex; + }, + + _clearTimer: function () { + if (this._timer) { + clearTimeout(this._timer); + this._timer = null; + } + }, + + _changeTimeline: function (nextIndex) { + var currentIndex = this.model.getCurrentIndex(); + + if (nextIndex === '+') { + nextIndex = currentIndex + 1; + } + else if (nextIndex === '-') { + nextIndex = currentIndex - 1; + } + + this.api.dispatchAction({ + type: 'timelineChange', + currentIndex: nextIndex, + from: this.uid + }); + } + +}); + +function getViewRect$5(model, api) { + return getLayoutRect( + model.getBoxLayoutParams(), + { + width: api.getWidth(), + height: api.getHeight() + }, + model.get('padding') + ); +} + +function makeIcon(timelineModel, objPath, rect, opts) { + var icon = makePath( + timelineModel.get(objPath).replace(/^path:\/\//, ''), + clone(opts || {}), + new BoundingRect(rect[0], rect[1], rect[2], rect[3]), + 'center' + ); + + return icon; +} + +/** + * Create symbol or update symbol + * opt: basic position and event handlers + */ +function giveSymbol(hostModel, itemStyleModel, group, opt, symbol, callback) { + var color = itemStyleModel.get('color'); + + if (!symbol) { + var symbolType = hostModel.get('symbol'); + symbol = createSymbol(symbolType, -1, -1, 2, 2, color); + symbol.setStyle('strokeNoScale', true); + group.add(symbol); + callback && callback.onCreate(symbol); + } + else { + symbol.setColor(color); + group.add(symbol); // Group may be new, also need to add. + callback && callback.onUpdate(symbol); + } + + // Style + var itemStyle = itemStyleModel.getItemStyle(['color', 'symbol', 'symbolSize']); + symbol.setStyle(itemStyle); + + // Transform and events. + opt = merge({ + rectHover: true, + z2: 100 + }, opt, true); + + var symbolSize = hostModel.get('symbolSize'); + symbolSize = symbolSize instanceof Array + ? symbolSize.slice() + : [+symbolSize, +symbolSize]; + symbolSize[0] /= 2; + symbolSize[1] /= 2; + opt.scale = symbolSize; + + var symbolOffset = hostModel.get('symbolOffset'); + if (symbolOffset) { + var pos = opt.position = opt.position || [0, 0]; + pos[0] += parsePercent$1(symbolOffset[0], symbolSize[0]); + pos[1] += parsePercent$1(symbolOffset[1], symbolSize[1]); + } + + var symbolRotate = hostModel.get('symbolRotate'); + opt.rotation = (symbolRotate || 0) * Math.PI / 180 || 0; + + symbol.attr(opt); + + // FIXME + // (1) When symbol.style.strokeNoScale is true and updateTransform is not performed, + // getBoundingRect will return wrong result. + // (This is supposed to be resolved in zrender, but it is a little difficult to + // leverage performance and auto updateTransform) + // (2) All of ancesters of symbol do not scale, so we can just updateTransform symbol. + symbol.updateTransform(); + + return symbol; +} + +function pointerMoveTo(pointer, dataIndex, axis, timelineModel, noAnimation) { + if (pointer.dragging) { + return; + } + + var pointerModel = timelineModel.getModel('checkpointStyle'); + var toCoord = axis.dataToCoord(timelineModel.getData().get(['value'], dataIndex)); + + if (noAnimation || !pointerModel.get('animation', true)) { + pointer.attr({position: [toCoord, 0]}); + } + else { + pointer.stopAnimation(true); + pointer.animateTo( + {position: [toCoord, 0]}, + pointerModel.get('animationDuration', true), + pointerModel.get('animationEasing', true) + ); + } +} + +/* +* 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. +*/ + +/** + * DataZoom component entry + */ + +registerPreprocessor(preprocessor$2); + +/* +* 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. +*/ + +var addCommas$1 = addCommas; +var encodeHTML$1 = encodeHTML; + +function fillLabel(opt) { + defaultEmphasis(opt, 'label', ['show']); +} +var MarkerModel = extendComponentModel({ + + type: 'marker', + + dependencies: ['series', 'grid', 'polar', 'geo'], + + /** + * @overrite + */ + init: function (option, parentModel, ecModel) { + + if (__DEV__) { + if (this.type === 'marker') { + throw new Error('Marker component is abstract component. Use markLine, markPoint, markArea instead.'); + } + } + this.mergeDefaultAndTheme(option, ecModel); + this._mergeOption(option, ecModel, false, true); + }, + + /** + * @return {boolean} + */ + isAnimationEnabled: function () { + if (env$1.node) { + return false; + } + + var hostSeries = this.__hostSeries; + return this.getShallow('animation') && hostSeries && hostSeries.isAnimationEnabled(); + }, + + /** + * @overrite + */ + mergeOption: function (newOpt, ecModel) { + this._mergeOption(newOpt, ecModel, false, false); + }, + + _mergeOption: function (newOpt, ecModel, createdBySelf, isInit) { + var MarkerModel = this.constructor; + var modelPropName = this.mainType + 'Model'; + if (!createdBySelf) { + ecModel.eachSeries(function (seriesModel) { + + var markerOpt = seriesModel.get(this.mainType, true); + + var markerModel = seriesModel[modelPropName]; + if (!markerOpt || !markerOpt.data) { + seriesModel[modelPropName] = null; + return; + } + if (!markerModel) { + if (isInit) { + // Default label emphasis `position` and `show` + fillLabel(markerOpt); + } + each$1(markerOpt.data, function (item) { + // FIXME Overwrite fillLabel method ? + if (item instanceof Array) { + fillLabel(item[0]); + fillLabel(item[1]); + } + else { + fillLabel(item); + } + }); + + markerModel = new MarkerModel( + markerOpt, this, ecModel + ); + + extend(markerModel, { + mainType: this.mainType, + // Use the same series index and name + seriesIndex: seriesModel.seriesIndex, + name: seriesModel.name, + createdBySelf: true + }); + + markerModel.__hostSeries = seriesModel; + } + else { + markerModel._mergeOption(markerOpt, ecModel, true); + } + seriesModel[modelPropName] = markerModel; + }, this); + } + }, + + formatTooltip: function (dataIndex) { + var data = this.getData(); + var value = this.getRawValue(dataIndex); + var formattedValue = isArray(value) + ? map(value, addCommas$1).join(', ') : addCommas$1(value); + var name = data.getName(dataIndex); + var html = encodeHTML$1(this.name); + if (value != null || name) { + html += '
    '; + } + if (name) { + html += encodeHTML$1(name); + if (value != null) { + html += ' : '; + } + } + if (value != null) { + html += encodeHTML$1(formattedValue); + } + return html; + }, + + getData: function () { + return this._data; + }, + + setData: function (data) { + this._data = data; + } +}); + +mixin(MarkerModel, dataFormatMixin); + +/* +* 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. +*/ + +MarkerModel.extend({ + + type: 'markPoint', + + defaultOption: { + zlevel: 0, + z: 5, + symbol: 'pin', + symbolSize: 50, + //symbolRotate: 0, + //symbolOffset: [0, 0] + tooltip: { + trigger: 'item' + }, + label: { + show: true, + position: 'inside' + }, + itemStyle: { + borderWidth: 2 + }, + emphasis: { + label: { + show: true + } + } + } +}); + +/* +* 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. +*/ + +var indexOf$2 = indexOf; + +function hasXOrY(item) { + return !(isNaN(parseFloat(item.x)) && isNaN(parseFloat(item.y))); +} + +function hasXAndY(item) { + return !isNaN(parseFloat(item.x)) && !isNaN(parseFloat(item.y)); +} + +// Make it simple, do not visit all stacked value to count precision. +// function getPrecision(data, valueAxisDim, dataIndex) { +// var precision = -1; +// var stackedDim = data.mapDimension(valueAxisDim); +// do { +// precision = Math.max( +// numberUtil.getPrecision(data.get(stackedDim, dataIndex)), +// precision +// ); +// var stackedOnSeries = data.getCalculationInfo('stackedOnSeries'); +// if (stackedOnSeries) { +// var byValue = data.get(data.getCalculationInfo('stackedByDimension'), dataIndex); +// data = stackedOnSeries.getData(); +// dataIndex = data.indexOf(data.getCalculationInfo('stackedByDimension'), byValue); +// stackedDim = data.getCalculationInfo('stackedDimension'); +// } +// else { +// data = null; +// } +// } while (data); + +// return precision; +// } + +function markerTypeCalculatorWithExtent( + mlType, data, otherDataDim, targetDataDim, otherCoordIndex, targetCoordIndex +) { + var coordArr = []; + + var stacked = isDimensionStacked(data, targetDataDim /*, otherDataDim*/); + var calcDataDim = stacked + ? data.getCalculationInfo('stackResultDimension') + : targetDataDim; + + var value = numCalculate(data, calcDataDim, mlType); + + var dataIndex = data.indicesOfNearest(calcDataDim, value)[0]; + coordArr[otherCoordIndex] = data.get(otherDataDim, dataIndex); + coordArr[targetCoordIndex] = data.get(targetDataDim, dataIndex); + + // Make it simple, do not visit all stacked value to count precision. + var precision = getPrecision(data.get(targetDataDim, dataIndex)); + precision = Math.min(precision, 20); + if (precision >= 0) { + coordArr[targetCoordIndex] = +coordArr[targetCoordIndex].toFixed(precision); + } + + return coordArr; +} + +var curry$5 = curry; +// TODO Specified percent +var markerTypeCalculator = { + /** + * @method + * @param {module:echarts/data/List} data + * @param {string} baseAxisDim + * @param {string} valueAxisDim + */ + min: curry$5(markerTypeCalculatorWithExtent, 'min'), + /** + * @method + * @param {module:echarts/data/List} data + * @param {string} baseAxisDim + * @param {string} valueAxisDim + */ + max: curry$5(markerTypeCalculatorWithExtent, 'max'), + + /** + * @method + * @param {module:echarts/data/List} data + * @param {string} baseAxisDim + * @param {string} valueAxisDim + */ + average: curry$5(markerTypeCalculatorWithExtent, 'average') +}; + +/** + * Transform markPoint data item to format used in List by do the following + * 1. Calculate statistic like `max`, `min`, `average` + * 2. Convert `item.xAxis`, `item.yAxis` to `item.coord` array + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/coord/*} [coordSys] + * @param {Object} item + * @return {Object} + */ +function dataTransform(seriesModel, item) { + var data = seriesModel.getData(); + var coordSys = seriesModel.coordinateSystem; + + // 1. If not specify the position with pixel directly + // 2. If `coord` is not a data array. Which uses `xAxis`, + // `yAxis` to specify the coord on each dimension + + // parseFloat first because item.x and item.y can be percent string like '20%' + if (item && !hasXAndY(item) && !isArray(item.coord) && coordSys) { + var dims = coordSys.dimensions; + var axisInfo = getAxisInfo$1(item, data, coordSys, seriesModel); + + // Clone the option + // Transform the properties xAxis, yAxis, radiusAxis, angleAxis, geoCoord to value + item = clone(item); + + if (item.type + && markerTypeCalculator[item.type] + && axisInfo.baseAxis && axisInfo.valueAxis + ) { + var otherCoordIndex = indexOf$2(dims, axisInfo.baseAxis.dim); + var targetCoordIndex = indexOf$2(dims, axisInfo.valueAxis.dim); + + item.coord = markerTypeCalculator[item.type]( + data, axisInfo.baseDataDim, axisInfo.valueDataDim, + otherCoordIndex, targetCoordIndex + ); + // Force to use the value of calculated value. + item.value = item.coord[targetCoordIndex]; + } + else { + // FIXME Only has one of xAxis and yAxis. + var coord = [ + item.xAxis != null ? item.xAxis : item.radiusAxis, + item.yAxis != null ? item.yAxis : item.angleAxis + ]; + // Each coord support max, min, average + for (var i = 0; i < 2; i++) { + if (markerTypeCalculator[coord[i]]) { + coord[i] = numCalculate(data, data.mapDimension(dims[i]), coord[i]); + } + } + item.coord = coord; + } + } + return item; +} + +function getAxisInfo$1(item, data, coordSys, seriesModel) { + var ret = {}; + + if (item.valueIndex != null || item.valueDim != null) { + ret.valueDataDim = item.valueIndex != null + ? data.getDimension(item.valueIndex) : item.valueDim; + ret.valueAxis = coordSys.getAxis(dataDimToCoordDim(seriesModel, ret.valueDataDim)); + ret.baseAxis = coordSys.getOtherAxis(ret.valueAxis); + ret.baseDataDim = data.mapDimension(ret.baseAxis.dim); + } + else { + ret.baseAxis = seriesModel.getBaseAxis(); + ret.valueAxis = coordSys.getOtherAxis(ret.baseAxis); + ret.baseDataDim = data.mapDimension(ret.baseAxis.dim); + ret.valueDataDim = data.mapDimension(ret.valueAxis.dim); + } + + return ret; +} + +function dataDimToCoordDim(seriesModel, dataDim) { + var data = seriesModel.getData(); + var dimensions = data.dimensions; + dataDim = data.getDimension(dataDim); + for (var i = 0; i < dimensions.length; i++) { + var dimItem = data.getDimensionInfo(dimensions[i]); + if (dimItem.name === dataDim) { + return dimItem.coordDim; + } + } +} + +/** + * Filter data which is out of coordinateSystem range + * [dataFilter description] + * @param {module:echarts/coord/*} [coordSys] + * @param {Object} item + * @return {boolean} + */ +function dataFilter$1(coordSys, item) { + // Alwalys return true if there is no coordSys + return (coordSys && coordSys.containData && item.coord && !hasXOrY(item)) + ? coordSys.containData(item.coord) : true; +} + +function dimValueGetter(item, dimName, dataIndex, dimIndex) { + // x, y, radius, angle + if (dimIndex < 2) { + return item.coord && item.coord[dimIndex]; + } + return item.value; +} + +function numCalculate(data, valueDataDim, type) { + if (type === 'average') { + var sum = 0; + var count = 0; + data.each(valueDataDim, function (val, idx) { + if (!isNaN(val)) { + sum += val; + count++; + } + }); + return sum / count; + } + else if (type === 'median') { + return data.getMedian(valueDataDim); + } + else { + // max & min + return data.getDataExtent(valueDataDim, true)[type === 'max' ? 1 : 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. +*/ + +var MarkerView = extendComponentView({ + + type: 'marker', + + init: function () { + /** + * Markline grouped by series + * @private + * @type {module:zrender/core/util.HashMap} + */ + this.markerGroupMap = createHashMap(); + }, + + render: function (markerModel, ecModel, api) { + var markerGroupMap = this.markerGroupMap; + markerGroupMap.each(function (item) { + item.__keep = false; + }); + + var markerModelKey = this.type + 'Model'; + ecModel.eachSeries(function (seriesModel) { + var markerModel = seriesModel[markerModelKey]; + markerModel && this.renderSeries(seriesModel, markerModel, ecModel, api); + }, this); + + markerGroupMap.each(function (item) { + !item.__keep && this.group.remove(item.group); + }, this); + }, + + renderSeries: function () {} +}); + +/* +* 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. +*/ + +function updateMarkerLayout(mpData, seriesModel, api) { + var coordSys = seriesModel.coordinateSystem; + mpData.each(function (idx) { + var itemModel = mpData.getItemModel(idx); + var point; + var xPx = parsePercent$1(itemModel.get('x'), api.getWidth()); + var yPx = parsePercent$1(itemModel.get('y'), api.getHeight()); + if (!isNaN(xPx) && !isNaN(yPx)) { + point = [xPx, yPx]; + } + // Chart like bar may have there own marker positioning logic + else if (seriesModel.getMarkerPosition) { + // Use the getMarkerPoisition + point = seriesModel.getMarkerPosition( + mpData.getValues(mpData.dimensions, idx) + ); + } + else if (coordSys) { + var x = mpData.get(coordSys.dimensions[0], idx); + var y = mpData.get(coordSys.dimensions[1], idx); + point = coordSys.dataToPoint([x, y]); + + } + + // Use x, y if has any + if (!isNaN(xPx)) { + point[0] = xPx; + } + if (!isNaN(yPx)) { + point[1] = yPx; + } + + mpData.setItemLayout(idx, point); + }); +} + +MarkerView.extend({ + + type: 'markPoint', + + // updateLayout: function (markPointModel, ecModel, api) { + // ecModel.eachSeries(function (seriesModel) { + // var mpModel = seriesModel.markPointModel; + // if (mpModel) { + // updateMarkerLayout(mpModel.getData(), seriesModel, api); + // this.markerGroupMap.get(seriesModel.id).updateLayout(mpModel); + // } + // }, this); + // }, + + updateTransform: function (markPointModel, ecModel, api) { + ecModel.eachSeries(function (seriesModel) { + var mpModel = seriesModel.markPointModel; + if (mpModel) { + updateMarkerLayout(mpModel.getData(), seriesModel, api); + this.markerGroupMap.get(seriesModel.id).updateLayout(mpModel); + } + }, this); + }, + + renderSeries: function (seriesModel, mpModel, ecModel, api) { + var coordSys = seriesModel.coordinateSystem; + var seriesId = seriesModel.id; + var seriesData = seriesModel.getData(); + + var symbolDrawMap = this.markerGroupMap; + var symbolDraw = symbolDrawMap.get(seriesId) + || symbolDrawMap.set(seriesId, new SymbolDraw()); + + var mpData = createList$1(coordSys, seriesModel, mpModel); + + // FIXME + mpModel.setData(mpData); + + updateMarkerLayout(mpModel.getData(), seriesModel, api); + + mpData.each(function (idx) { + var itemModel = mpData.getItemModel(idx); + var symbol = itemModel.getShallow('symbol'); + var symbolSize = itemModel.getShallow('symbolSize'); + var isFnSymbol = isFunction$1(symbol); + var isFnSymbolSize = isFunction$1(symbolSize); + + if (isFnSymbol || isFnSymbolSize) { + var rawIdx = mpModel.getRawValue(idx); + var dataParams = mpModel.getDataParams(idx); + if (isFnSymbol) { + symbol = symbol(rawIdx, dataParams); + } + if (isFnSymbolSize) { + // FIXME 这里不兼容 ECharts 2.x,2.x 貌似参数是整个数据? + symbolSize = symbolSize(rawIdx, dataParams); + } + } + + mpData.setItemVisual(idx, { + symbol: symbol, + symbolSize: symbolSize, + color: itemModel.get('itemStyle.color') + || seriesData.getVisual('color') + }); + }); + + // TODO Text are wrong + symbolDraw.updateData(mpData); + this.group.add(symbolDraw.group); + + // Set host model for tooltip + // FIXME + mpData.eachItemGraphicEl(function (el) { + el.traverse(function (child) { + child.dataModel = mpModel; + }); + }); + + symbolDraw.__keep = true; + + symbolDraw.group.silent = mpModel.get('silent') || seriesModel.get('silent'); + } +}); + +/** + * @inner + * @param {module:echarts/coord/*} [coordSys] + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Model} mpModel + */ +function createList$1(coordSys, seriesModel, mpModel) { + var coordDimsInfos; + if (coordSys) { + coordDimsInfos = map(coordSys && coordSys.dimensions, function (coordDim) { + var info = seriesModel.getData().getDimensionInfo( + seriesModel.getData().mapDimension(coordDim) + ) || {}; + // In map series data don't have lng and lat dimension. Fallback to same with coordSys + return defaults({name: coordDim}, info); + }); + } + else { + coordDimsInfos = [{ + name: 'value', + type: 'float' + }]; + } + + var mpData = new List(coordDimsInfos, mpModel); + var dataOpt = map(mpModel.get('data'), curry( + dataTransform, seriesModel + )); + if (coordSys) { + dataOpt = filter( + dataOpt, curry(dataFilter$1, coordSys) + ); + } + + mpData.initData(dataOpt, null, + coordSys ? dimValueGetter : function (item) { + return item.value; + } + ); + + return mpData; +} + +/* +* 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. +*/ + +// HINT Markpoint can't be used too much +registerPreprocessor(function (opt) { + // Make sure markPoint component is enabled + opt.markPoint = opt.markPoint || {}; +}); + +/* +* 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. +*/ + +MarkerModel.extend({ + + type: 'markLine', + + defaultOption: { + zlevel: 0, + z: 5, + + symbol: ['circle', 'arrow'], + symbolSize: [8, 16], + + //symbolRotate: 0, + + precision: 2, + tooltip: { + trigger: 'item' + }, + label: { + show: true, + position: 'end' + }, + lineStyle: { + type: 'dashed' + }, + emphasis: { + label: { + show: true + }, + lineStyle: { + width: 3 + } + }, + animationEasing: 'linear' + } +}); + +/* +* 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. +*/ + +var markLineTransform = function (seriesModel, coordSys, mlModel, item) { + var data = seriesModel.getData(); + // Special type markLine like 'min', 'max', 'average', 'median' + var mlType = item.type; + + if (!isArray(item) + && ( + mlType === 'min' || mlType === 'max' || mlType === 'average' || mlType === 'median' + // In case + // data: [{ + // yAxis: 10 + // }] + || (item.xAxis != null || item.yAxis != null) + ) + ) { + var valueAxis; + var value; + + if (item.yAxis != null || item.xAxis != null) { + valueAxis = coordSys.getAxis(item.yAxis != null ? 'y' : 'x'); + value = retrieve(item.yAxis, item.xAxis); + } + else { + var axisInfo = getAxisInfo$1(item, data, coordSys, seriesModel); + valueAxis = axisInfo.valueAxis; + var valueDataDim = getStackedDimension(data, axisInfo.valueDataDim); + value = numCalculate(data, valueDataDim, mlType); + } + var valueIndex = valueAxis.dim === 'x' ? 0 : 1; + var baseIndex = 1 - valueIndex; + + var mlFrom = clone(item); + var mlTo = {}; + + mlFrom.type = null; + + mlFrom.coord = []; + mlTo.coord = []; + mlFrom.coord[baseIndex] = -Infinity; + mlTo.coord[baseIndex] = Infinity; + + var precision = mlModel.get('precision'); + if (precision >= 0 && typeof value === 'number') { + value = +value.toFixed(Math.min(precision, 20)); + } + + mlFrom.coord[valueIndex] = mlTo.coord[valueIndex] = value; + + item = [mlFrom, mlTo, { // Extra option for tooltip and label + type: mlType, + valueIndex: item.valueIndex, + // Force to use the value of calculated value. + value: value + }]; + } + + item = [ + dataTransform(seriesModel, item[0]), + dataTransform(seriesModel, item[1]), + extend({}, item[2]) + ]; + + // Avoid line data type is extended by from(to) data type + item[2].type = item[2].type || ''; + + // Merge from option and to option into line option + merge(item[2], item[0]); + merge(item[2], item[1]); + + return item; +}; + +function isInifinity(val) { + return !isNaN(val) && !isFinite(val); +} + +// If a markLine has one dim +function ifMarkLineHasOnlyDim(dimIndex, fromCoord, toCoord, coordSys) { + var otherDimIndex = 1 - dimIndex; + var dimName = coordSys.dimensions[dimIndex]; + return isInifinity(fromCoord[otherDimIndex]) && isInifinity(toCoord[otherDimIndex]) + && fromCoord[dimIndex] === toCoord[dimIndex] && coordSys.getAxis(dimName).containData(fromCoord[dimIndex]); +} + +function markLineFilter(coordSys, item) { + if (coordSys.type === 'cartesian2d') { + var fromCoord = item[0].coord; + var toCoord = item[1].coord; + // In case + // { + // markLine: { + // data: [{ yAxis: 2 }] + // } + // } + if ( + fromCoord && toCoord + && (ifMarkLineHasOnlyDim(1, fromCoord, toCoord, coordSys) + || ifMarkLineHasOnlyDim(0, fromCoord, toCoord, coordSys)) + ) { + return true; + } + } + return dataFilter$1(coordSys, item[0]) + && dataFilter$1(coordSys, item[1]); +} + +function updateSingleMarkerEndLayout( + data, idx, isFrom, seriesModel, api +) { + var coordSys = seriesModel.coordinateSystem; + var itemModel = data.getItemModel(idx); + + var point; + var xPx = parsePercent$1(itemModel.get('x'), api.getWidth()); + var yPx = parsePercent$1(itemModel.get('y'), api.getHeight()); + if (!isNaN(xPx) && !isNaN(yPx)) { + point = [xPx, yPx]; + } + else { + // Chart like bar may have there own marker positioning logic + if (seriesModel.getMarkerPosition) { + // Use the getMarkerPoisition + point = seriesModel.getMarkerPosition( + data.getValues(data.dimensions, idx) + ); + } + else { + var dims = coordSys.dimensions; + var x = data.get(dims[0], idx); + var y = data.get(dims[1], idx); + point = coordSys.dataToPoint([x, y]); + } + // Expand line to the edge of grid if value on one axis is Inifnity + // In case + // markLine: { + // data: [{ + // yAxis: 2 + // // or + // type: 'average' + // }] + // } + if (coordSys.type === 'cartesian2d') { + var xAxis = coordSys.getAxis('x'); + var yAxis = coordSys.getAxis('y'); + var dims = coordSys.dimensions; + if (isInifinity(data.get(dims[0], idx))) { + point[0] = xAxis.toGlobalCoord(xAxis.getExtent()[isFrom ? 0 : 1]); + } + else if (isInifinity(data.get(dims[1], idx))) { + point[1] = yAxis.toGlobalCoord(yAxis.getExtent()[isFrom ? 0 : 1]); + } + } + + // Use x, y if has any + if (!isNaN(xPx)) { + point[0] = xPx; + } + if (!isNaN(yPx)) { + point[1] = yPx; + } + } + + data.setItemLayout(idx, point); +} + +MarkerView.extend({ + + type: 'markLine', + + // updateLayout: function (markLineModel, ecModel, api) { + // ecModel.eachSeries(function (seriesModel) { + // var mlModel = seriesModel.markLineModel; + // if (mlModel) { + // var mlData = mlModel.getData(); + // var fromData = mlModel.__from; + // var toData = mlModel.__to; + // // Update visual and layout of from symbol and to symbol + // fromData.each(function (idx) { + // updateSingleMarkerEndLayout(fromData, idx, true, seriesModel, api); + // updateSingleMarkerEndLayout(toData, idx, false, seriesModel, api); + // }); + // // Update layout of line + // mlData.each(function (idx) { + // mlData.setItemLayout(idx, [ + // fromData.getItemLayout(idx), + // toData.getItemLayout(idx) + // ]); + // }); + + // this.markerGroupMap.get(seriesModel.id).updateLayout(); + + // } + // }, this); + // }, + + updateTransform: function (markLineModel, ecModel, api) { + ecModel.eachSeries(function (seriesModel) { + var mlModel = seriesModel.markLineModel; + if (mlModel) { + var mlData = mlModel.getData(); + var fromData = mlModel.__from; + var toData = mlModel.__to; + // Update visual and layout of from symbol and to symbol + fromData.each(function (idx) { + updateSingleMarkerEndLayout(fromData, idx, true, seriesModel, api); + updateSingleMarkerEndLayout(toData, idx, false, seriesModel, api); + }); + // Update layout of line + mlData.each(function (idx) { + mlData.setItemLayout(idx, [ + fromData.getItemLayout(idx), + toData.getItemLayout(idx) + ]); + }); + + this.markerGroupMap.get(seriesModel.id).updateLayout(); + + } + }, this); + }, + + renderSeries: function (seriesModel, mlModel, ecModel, api) { + var coordSys = seriesModel.coordinateSystem; + var seriesId = seriesModel.id; + var seriesData = seriesModel.getData(); + + var lineDrawMap = this.markerGroupMap; + var lineDraw = lineDrawMap.get(seriesId) + || lineDrawMap.set(seriesId, new LineDraw()); + this.group.add(lineDraw.group); + + var mlData = createList$2(coordSys, seriesModel, mlModel); + + var fromData = mlData.from; + var toData = mlData.to; + var lineData = mlData.line; + + mlModel.__from = fromData; + mlModel.__to = toData; + // Line data for tooltip and formatter + mlModel.setData(lineData); + + var symbolType = mlModel.get('symbol'); + var symbolSize = mlModel.get('symbolSize'); + if (!isArray(symbolType)) { + symbolType = [symbolType, symbolType]; + } + if (typeof symbolSize === 'number') { + symbolSize = [symbolSize, symbolSize]; + } + + // Update visual and layout of from symbol and to symbol + mlData.from.each(function (idx) { + updateDataVisualAndLayout(fromData, idx, true); + updateDataVisualAndLayout(toData, idx, false); + }); + + // Update visual and layout of line + lineData.each(function (idx) { + var lineColor = lineData.getItemModel(idx).get('lineStyle.color'); + lineData.setItemVisual(idx, { + color: lineColor || fromData.getItemVisual(idx, 'color') + }); + lineData.setItemLayout(idx, [ + fromData.getItemLayout(idx), + toData.getItemLayout(idx) + ]); + + lineData.setItemVisual(idx, { + 'fromSymbolSize': fromData.getItemVisual(idx, 'symbolSize'), + 'fromSymbol': fromData.getItemVisual(idx, 'symbol'), + 'toSymbolSize': toData.getItemVisual(idx, 'symbolSize'), + 'toSymbol': toData.getItemVisual(idx, 'symbol') + }); + }); + + lineDraw.updateData(lineData); + + // Set host model for tooltip + // FIXME + mlData.line.eachItemGraphicEl(function (el, idx) { + el.traverse(function (child) { + child.dataModel = mlModel; + }); + }); + + function updateDataVisualAndLayout(data, idx, isFrom) { + var itemModel = data.getItemModel(idx); + + updateSingleMarkerEndLayout( + data, idx, isFrom, seriesModel, api + ); + + data.setItemVisual(idx, { + symbolSize: itemModel.get('symbolSize') || symbolSize[isFrom ? 0 : 1], + symbol: itemModel.get('symbol', true) || symbolType[isFrom ? 0 : 1], + color: itemModel.get('itemStyle.color') || seriesData.getVisual('color') + }); + } + + lineDraw.__keep = true; + + lineDraw.group.silent = mlModel.get('silent') || seriesModel.get('silent'); + } +}); + +/** + * @inner + * @param {module:echarts/coord/*} coordSys + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Model} mpModel + */ +function createList$2(coordSys, seriesModel, mlModel) { + + var coordDimsInfos; + if (coordSys) { + coordDimsInfos = map(coordSys && coordSys.dimensions, function (coordDim) { + var info = seriesModel.getData().getDimensionInfo( + seriesModel.getData().mapDimension(coordDim) + ) || {}; + // In map series data don't have lng and lat dimension. Fallback to same with coordSys + return defaults({name: coordDim}, info); + }); + } + else { + coordDimsInfos = [{ + name: 'value', + type: 'float' + }]; + } + + var fromData = new List(coordDimsInfos, mlModel); + var toData = new List(coordDimsInfos, mlModel); + // No dimensions + var lineData = new List([], mlModel); + + var optData = map(mlModel.get('data'), curry( + markLineTransform, seriesModel, coordSys, mlModel + )); + if (coordSys) { + optData = filter( + optData, curry(markLineFilter, coordSys) + ); + } + var dimValueGetter$$1 = coordSys ? dimValueGetter : function (item) { + return item.value; + }; + fromData.initData( + map(optData, function (item) { + return item[0]; + }), + null, + dimValueGetter$$1 + ); + toData.initData( + map(optData, function (item) { + return item[1]; + }), + null, + dimValueGetter$$1 + ); + lineData.initData( + map(optData, function (item) { + return item[2]; + }) + ); + lineData.hasItemOption = true; + + return { + from: fromData, + to: toData, + line: lineData + }; +} + +/* +* 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. +*/ + +registerPreprocessor(function (opt) { + // Make sure markLine component is enabled + opt.markLine = opt.markLine || {}; +}); + +/* +* 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. +*/ + +MarkerModel.extend({ + + type: 'markArea', + + defaultOption: { + zlevel: 0, + // PENDING + z: 1, + tooltip: { + trigger: 'item' + }, + // markArea should fixed on the coordinate system + animation: false, + label: { + show: true, + position: 'top' + }, + itemStyle: { + // color and borderColor default to use color from series + // color: 'auto' + // borderColor: 'auto' + borderWidth: 0 + }, + + emphasis: { + label: { + show: true, + position: 'top' + } + } + } +}); + +/* +* 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. +*/ + +// TODO Better on polar + +var markAreaTransform = function (seriesModel, coordSys, maModel, item) { + var lt = dataTransform(seriesModel, item[0]); + var rb = dataTransform(seriesModel, item[1]); + var retrieve$$1 = retrieve; + + // FIXME make sure lt is less than rb + var ltCoord = lt.coord; + var rbCoord = rb.coord; + ltCoord[0] = retrieve$$1(ltCoord[0], -Infinity); + ltCoord[1] = retrieve$$1(ltCoord[1], -Infinity); + + rbCoord[0] = retrieve$$1(rbCoord[0], Infinity); + rbCoord[1] = retrieve$$1(rbCoord[1], Infinity); + + // Merge option into one + var result = mergeAll([{}, lt, rb]); + + result.coord = [ + lt.coord, rb.coord + ]; + result.x0 = lt.x; + result.y0 = lt.y; + result.x1 = rb.x; + result.y1 = rb.y; + return result; +}; + +function isInifinity$1(val) { + return !isNaN(val) && !isFinite(val); +} + +// If a markArea has one dim +function ifMarkLineHasOnlyDim$1(dimIndex, fromCoord, toCoord, coordSys) { + var otherDimIndex = 1 - dimIndex; + return isInifinity$1(fromCoord[otherDimIndex]) && isInifinity$1(toCoord[otherDimIndex]); +} + +function markAreaFilter(coordSys, item) { + var fromCoord = item.coord[0]; + var toCoord = item.coord[1]; + if (coordSys.type === 'cartesian2d') { + // In case + // { + // markArea: { + // data: [{ yAxis: 2 }] + // } + // } + if ( + fromCoord && toCoord + && (ifMarkLineHasOnlyDim$1(1, fromCoord, toCoord, coordSys) + || ifMarkLineHasOnlyDim$1(0, fromCoord, toCoord, coordSys)) + ) { + return true; + } + } + return dataFilter$1(coordSys, { + coord: fromCoord, + x: item.x0, + y: item.y0 + }) + || dataFilter$1(coordSys, { + coord: toCoord, + x: item.x1, + y: item.y1 + }); +} + +// dims can be ['x0', 'y0'], ['x1', 'y1'], ['x0', 'y1'], ['x1', 'y0'] +function getSingleMarkerEndPoint(data, idx, dims, seriesModel, api) { + var coordSys = seriesModel.coordinateSystem; + var itemModel = data.getItemModel(idx); + + var point; + var xPx = parsePercent$1(itemModel.get(dims[0]), api.getWidth()); + var yPx = parsePercent$1(itemModel.get(dims[1]), api.getHeight()); + if (!isNaN(xPx) && !isNaN(yPx)) { + point = [xPx, yPx]; + } + else { + // Chart like bar may have there own marker positioning logic + if (seriesModel.getMarkerPosition) { + // Use the getMarkerPoisition + point = seriesModel.getMarkerPosition( + data.getValues(dims, idx) + ); + } + else { + var x = data.get(dims[0], idx); + var y = data.get(dims[1], idx); + var pt = [x, y]; + coordSys.clampData && coordSys.clampData(pt, pt); + point = coordSys.dataToPoint(pt, true); + } + if (coordSys.type === 'cartesian2d') { + var xAxis = coordSys.getAxis('x'); + var yAxis = coordSys.getAxis('y'); + var x = data.get(dims[0], idx); + var y = data.get(dims[1], idx); + if (isInifinity$1(x)) { + point[0] = xAxis.toGlobalCoord(xAxis.getExtent()[dims[0] === 'x0' ? 0 : 1]); + } + else if (isInifinity$1(y)) { + point[1] = yAxis.toGlobalCoord(yAxis.getExtent()[dims[1] === 'y0' ? 0 : 1]); + } + } + + // Use x, y if has any + if (!isNaN(xPx)) { + point[0] = xPx; + } + if (!isNaN(yPx)) { + point[1] = yPx; + } + } + + return point; +} + +var dimPermutations = [['x0', 'y0'], ['x1', 'y0'], ['x1', 'y1'], ['x0', 'y1']]; + +MarkerView.extend({ + + type: 'markArea', + + // updateLayout: function (markAreaModel, ecModel, api) { + // ecModel.eachSeries(function (seriesModel) { + // var maModel = seriesModel.markAreaModel; + // if (maModel) { + // var areaData = maModel.getData(); + // areaData.each(function (idx) { + // var points = zrUtil.map(dimPermutations, function (dim) { + // return getSingleMarkerEndPoint(areaData, idx, dim, seriesModel, api); + // }); + // // Layout + // areaData.setItemLayout(idx, points); + // var el = areaData.getItemGraphicEl(idx); + // el.setShape('points', points); + // }); + // } + // }, this); + // }, + + updateTransform: function (markAreaModel, ecModel, api) { + ecModel.eachSeries(function (seriesModel) { + var maModel = seriesModel.markAreaModel; + if (maModel) { + var areaData = maModel.getData(); + areaData.each(function (idx) { + var points = map(dimPermutations, function (dim) { + return getSingleMarkerEndPoint(areaData, idx, dim, seriesModel, api); + }); + // Layout + areaData.setItemLayout(idx, points); + var el = areaData.getItemGraphicEl(idx); + el.setShape('points', points); + }); + } + }, this); + }, + + renderSeries: function (seriesModel, maModel, ecModel, api) { + var coordSys = seriesModel.coordinateSystem; + var seriesId = seriesModel.id; + var seriesData = seriesModel.getData(); + + var areaGroupMap = this.markerGroupMap; + var polygonGroup = areaGroupMap.get(seriesId) + || areaGroupMap.set(seriesId, {group: new Group()}); + + this.group.add(polygonGroup.group); + polygonGroup.__keep = true; + + var areaData = createList$3(coordSys, seriesModel, maModel); + + // Line data for tooltip and formatter + maModel.setData(areaData); + + // Update visual and layout of line + areaData.each(function (idx) { + // Layout + areaData.setItemLayout(idx, map(dimPermutations, function (dim) { + return getSingleMarkerEndPoint(areaData, idx, dim, seriesModel, api); + })); + + // Visual + areaData.setItemVisual(idx, { + color: seriesData.getVisual('color') + }); + }); + + + areaData.diff(polygonGroup.__data) + .add(function (idx) { + var polygon = new Polygon({ + shape: { + points: areaData.getItemLayout(idx) + } + }); + areaData.setItemGraphicEl(idx, polygon); + polygonGroup.group.add(polygon); + }) + .update(function (newIdx, oldIdx) { + var polygon = polygonGroup.__data.getItemGraphicEl(oldIdx); + updateProps(polygon, { + shape: { + points: areaData.getItemLayout(newIdx) + } + }, maModel, newIdx); + polygonGroup.group.add(polygon); + areaData.setItemGraphicEl(newIdx, polygon); + }) + .remove(function (idx) { + var polygon = polygonGroup.__data.getItemGraphicEl(idx); + polygonGroup.group.remove(polygon); + }) + .execute(); + + areaData.eachItemGraphicEl(function (polygon, idx) { + var itemModel = areaData.getItemModel(idx); + var labelModel = itemModel.getModel('label'); + var labelHoverModel = itemModel.getModel('emphasis.label'); + var color = areaData.getItemVisual(idx, 'color'); + polygon.useStyle( + defaults( + itemModel.getModel('itemStyle').getItemStyle(), + { + fill: modifyAlpha(color, 0.4), + stroke: color + } + ) + ); + + polygon.hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle(); + + setLabelStyle( + polygon.style, polygon.hoverStyle, labelModel, labelHoverModel, + { + labelFetcher: maModel, + labelDataIndex: idx, + defaultText: areaData.getName(idx) || '', + isRectText: true, + autoColor: color + } + ); + + setHoverStyle(polygon, {}); + + polygon.dataModel = maModel; + }); + + polygonGroup.__data = areaData; + + polygonGroup.group.silent = maModel.get('silent') || seriesModel.get('silent'); + } +}); + +/** + * @inner + * @param {module:echarts/coord/*} coordSys + * @param {module:echarts/model/Series} seriesModel + * @param {module:echarts/model/Model} mpModel + */ +function createList$3(coordSys, seriesModel, maModel) { + + var coordDimsInfos; + var areaData; + var dims = ['x0', 'y0', 'x1', 'y1']; + if (coordSys) { + coordDimsInfos = map(coordSys && coordSys.dimensions, function (coordDim) { + var data = seriesModel.getData(); + var info = data.getDimensionInfo( + data.mapDimension(coordDim) + ) || {}; + // In map series data don't have lng and lat dimension. Fallback to same with coordSys + return defaults({name: coordDim}, info); + }); + areaData = new List(map(dims, function (dim, idx) { + return { + name: dim, + type: coordDimsInfos[idx % 2].type + }; + }), maModel); + } + else { + coordDimsInfos = [{ + name: 'value', + type: 'float' + }]; + areaData = new List(coordDimsInfos, maModel); + } + + var optData = map(maModel.get('data'), curry( + markAreaTransform, seriesModel, coordSys, maModel + )); + if (coordSys) { + optData = filter( + optData, curry(markAreaFilter, coordSys) + ); + } + + var dimValueGetter$$1 = coordSys ? function (item, dimName, dataIndex, dimIndex) { + return item.coord[Math.floor(dimIndex / 2)][dimIndex % 2]; + } : function (item) { + return item.value; + }; + areaData.initData(optData, null, dimValueGetter$$1); + areaData.hasItemOption = true; + return areaData; +} + +/* +* 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. +*/ + +registerPreprocessor(function (opt) { + // Make sure markArea component is enabled + opt.markArea = opt.markArea || {}; +}); + +/* +* 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. +*/ + +var langSelector = lang.legend.selector; + +var defaultSelectorOption = { + all: { + type: 'all', + title: clone(langSelector.all) + }, + inverse: { + type: 'inverse', + title: clone(langSelector.inverse) + } +}; + +var LegendModel = extendComponentModel({ + + type: 'legend.plain', + + dependencies: ['series'], + + layoutMode: { + type: 'box', + // legend.width/height are maxWidth/maxHeight actually, + // whereas realy width/height is calculated by its content. + // (Setting {left: 10, right: 10} does not make sense). + // So consider the case: + // `setOption({legend: {left: 10});` + // then `setOption({legend: {right: 10});` + // The previous `left` should be cleared by setting `ignoreSize`. + ignoreSize: true + }, + + init: function (option, parentModel, ecModel) { + this.mergeDefaultAndTheme(option, ecModel); + + option.selected = option.selected || {}; + this._updateSelector(option); + }, + + mergeOption: function (option) { + LegendModel.superCall(this, 'mergeOption', option); + this._updateSelector(option); + }, + + _updateSelector: function (option) { + var selector = option.selector; + if (selector === true) { + selector = option.selector = ['all', 'inverse']; + } + if (isArray(selector)) { + each$1(selector, function (item, index) { + isString(item) && (item = {type: item}); + selector[index] = merge(item, defaultSelectorOption[item.type]); + }); + } + }, + + optionUpdated: function () { + this._updateData(this.ecModel); + + var legendData = this._data; + + // If selectedMode is single, try to select one + if (legendData[0] && this.get('selectedMode') === 'single') { + var hasSelected = false; + // If has any selected in option.selected + for (var i = 0; i < legendData.length; i++) { + var name = legendData[i].get('name'); + if (this.isSelected(name)) { + // Force to unselect others + this.select(name); + hasSelected = true; + break; + } + } + // Try select the first if selectedMode is single + !hasSelected && this.select(legendData[0].get('name')); + } + }, + + _updateData: function (ecModel) { + var potentialData = []; + var availableNames = []; + + ecModel.eachRawSeries(function (seriesModel) { + var seriesName = seriesModel.name; + availableNames.push(seriesName); + var isPotential; + + if (seriesModel.legendVisualProvider) { + var provider = seriesModel.legendVisualProvider; + var names = provider.getAllNames(); + + if (!ecModel.isSeriesFiltered(seriesModel)) { + availableNames = availableNames.concat(names); + } + + if (names.length) { + potentialData = potentialData.concat(names); + } + else { + isPotential = true; + } + } + else { + isPotential = true; + } + + if (isPotential && isNameSpecified(seriesModel)) { + potentialData.push(seriesModel.name); + } + }); + + /** + * @type {Array.} + * @private + */ + this._availableNames = availableNames; + + // If legend.data not specified in option, use availableNames as data, + // which is convinient for user preparing option. + var rawData = this.get('data') || potentialData; + + var legendData = map(rawData, function (dataItem) { + // Can be string or number + if (typeof dataItem === 'string' || typeof dataItem === 'number') { + dataItem = { + name: dataItem + }; + } + return new Model(dataItem, this, this.ecModel); + }, this); + + /** + * @type {Array.} + * @private + */ + this._data = legendData; + }, + + /** + * @return {Array.} + */ + getData: function () { + return this._data; + }, + + /** + * @param {string} name + */ + select: function (name) { + var selected = this.option.selected; + var selectedMode = this.get('selectedMode'); + if (selectedMode === 'single') { + var data = this._data; + each$1(data, function (dataItem) { + selected[dataItem.get('name')] = false; + }); + } + selected[name] = true; + }, + + /** + * @param {string} name + */ + unSelect: function (name) { + if (this.get('selectedMode') !== 'single') { + this.option.selected[name] = false; + } + }, + + /** + * @param {string} name + */ + toggleSelected: function (name) { + var selected = this.option.selected; + // Default is true + if (!selected.hasOwnProperty(name)) { + selected[name] = true; + } + this[selected[name] ? 'unSelect' : 'select'](name); + }, + + allSelect: function () { + var data = this._data; + var selected = this.option.selected; + each$1(data, function (dataItem) { + selected[dataItem.get('name', true)] = true; + }); + }, + + inverseSelect: function () { + var data = this._data; + var selected = this.option.selected; + each$1(data, function (dataItem) { + var name = dataItem.get('name', true); + // Initially, default value is true + if (!selected.hasOwnProperty(name)) { + selected[name] = true; + } + selected[name] = !selected[name]; + }); + }, + + /** + * @param {string} name + */ + isSelected: function (name) { + var selected = this.option.selected; + return !(selected.hasOwnProperty(name) && !selected[name]) + && indexOf(this._availableNames, name) >= 0; + }, + + getOrient: function () { + return this.get('orient') === 'vertical' + ? {index: 1, name: 'vertical'} + : {index: 0, name: 'horizontal'}; + }, + + defaultOption: { + // 一级层叠 + zlevel: 0, + // 二级层叠 + z: 4, + show: true, + + // 布局方式,默认为水平布局,可选为: + // 'horizontal' | 'vertical' + orient: 'horizontal', + + left: 'center', + // right: 'center', + + top: 0, + // bottom: null, + + // 水平对齐 + // 'auto' | 'left' | 'right' + // 默认为 'auto', 根据 x 的位置判断是左对齐还是右对齐 + align: 'auto', + + backgroundColor: 'rgba(0,0,0,0)', + // 图例边框颜色 + borderColor: '#ccc', + borderRadius: 0, + // 图例边框线宽,单位px,默认为0(无边框) + borderWidth: 0, + // 图例内边距,单位px,默认各方向内边距为5, + // 接受数组分别设定上右下左边距,同css + padding: 5, + // 各个item之间的间隔,单位px,默认为10, + // 横向布局时为水平间隔,纵向布局时为纵向间隔 + itemGap: 10, + // the width of legend symbol + itemWidth: 25, + // the height of legend symbol + itemHeight: 14, + + // the color of unselected legend symbol + inactiveColor: '#ccc', + + // the borderColor of unselected legend symbol + inactiveBorderColor: '#ccc', + + itemStyle: { + // the default borderWidth of legend symbol + borderWidth: 0 + }, + + textStyle: { + // 图例文字颜色 + color: '#333' + }, + // formatter: '', + // 选择模式,默认开启图例开关 + selectedMode: true, + // 配置默认选中状态,可配合LEGEND.SELECTED事件做动态数据载入 + // selected: null, + // 图例内容(详见legend.data,数组中每一项代表一个item + // data: [], + + // Usage: + // selector: [{type: 'all or inverse', title: xxx}] + // or + // selector: true + // or + // selector: ['all', 'inverse'] + selector: false, + + selectorLabel: { + show: true, + borderRadius: 10, + padding: [3, 5, 3, 5], + fontSize: 12, + fontFamily: ' sans-serif', + color: '#666', + borderWidth: 1, + borderColor: '#666' + }, + + emphasis: { + selectorLabel: { + show: true, + color: '#eee', + backgroundColor: '#666' + } + }, + + // Value can be 'start' or 'end' + selectorPosition: 'auto', + + selectorItemGap: 7, + + selectorButtonGap: 10, + + // Tooltip 相关配置 + tooltip: { + show: false + } + } +}); + +/* +* 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. +*/ + +function legendSelectActionHandler(methodName, payload, ecModel) { + var selectedMap = {}; + var isToggleSelect = methodName === 'toggleSelected'; + var isSelected; + // Update all legend components + ecModel.eachComponent('legend', function (legendModel) { + if (isToggleSelect && isSelected != null) { + // Force other legend has same selected status + // Or the first is toggled to true and other are toggled to false + // In the case one legend has some item unSelected in option. And if other legend + // doesn't has the item, they will assume it is selected. + legendModel[isSelected ? 'select' : 'unSelect'](payload.name); + } + else if (methodName === 'allSelect' || methodName === 'inverseSelect') { + legendModel[methodName](); + } + else { + legendModel[methodName](payload.name); + isSelected = legendModel.isSelected(payload.name); + } + var legendData = legendModel.getData(); + each$1(legendData, function (model) { + var name = model.get('name'); + // Wrap element + if (name === '\n' || name === '') { + return; + } + var isItemSelected = legendModel.isSelected(name); + if (selectedMap.hasOwnProperty(name)) { + // Unselected if any legend is unselected + selectedMap[name] = selectedMap[name] && isItemSelected; + } + else { + selectedMap[name] = isItemSelected; + } + }); + }); + // Return the event explicitly + return (methodName === 'allSelect' || methodName === 'inverseSelect') + ? { + selected: selectedMap + } + : { + name: payload.name, + selected: selectedMap + }; +} +/** + * @event legendToggleSelect + * @type {Object} + * @property {string} type 'legendToggleSelect' + * @property {string} [from] + * @property {string} name Series name or data item name + */ +registerAction( + 'legendToggleSelect', 'legendselectchanged', + curry(legendSelectActionHandler, 'toggleSelected') +); + +registerAction( + 'legendAllSelect', 'legendselectall', + curry(legendSelectActionHandler, 'allSelect') +); + +registerAction( + 'legendInverseSelect', 'legendinverseselect', + curry(legendSelectActionHandler, 'inverseSelect') +); + +/** + * @event legendSelect + * @type {Object} + * @property {string} type 'legendSelect' + * @property {string} name Series name or data item name + */ +registerAction( + 'legendSelect', 'legendselected', + curry(legendSelectActionHandler, 'select') +); + +/** + * @event legendUnSelect + * @type {Object} + * @property {string} type 'legendUnSelect' + * @property {string} name Series name or data item name + */ +registerAction( + 'legendUnSelect', 'legendunselected', + curry(legendSelectActionHandler, 'unSelect') +); + +/* +* 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. +*/ + +var curry$6 = curry; +var each$25 = each$1; +var Group$3 = Group; + +var LegendView = extendComponentView({ + + type: 'legend.plain', + + newlineDisabled: false, + + /** + * @override + */ + init: function () { + + /** + * @private + * @type {module:zrender/container/Group} + */ + this.group.add(this._contentGroup = new Group$3()); + + /** + * @private + * @type {module:zrender/Element} + */ + this._backgroundEl; + + /** + * @private + * @type {module:zrender/container/Group} + */ + this.group.add(this._selectorGroup = new Group$3()); + + /** + * If first rendering, `contentGroup.position` is [0, 0], which + * does not make sense and may cause unexepcted animation if adopted. + * @private + * @type {boolean} + */ + this._isFirstRender = true; + }, + + /** + * @protected + */ + getContentGroup: function () { + return this._contentGroup; + }, + + /** + * @protected + */ + getSelectorGroup: function () { + return this._selectorGroup; + }, + + /** + * @override + */ + render: function (legendModel, ecModel, api) { + var isFirstRender = this._isFirstRender; + this._isFirstRender = false; + + this.resetInner(); + + if (!legendModel.get('show', true)) { + return; + } + + var itemAlign = legendModel.get('align'); + var orient = legendModel.get('orient'); + if (!itemAlign || itemAlign === 'auto') { + itemAlign = ( + legendModel.get('left') === 'right' + && orient === 'vertical' + ) ? 'right' : 'left'; + } + + var selector = legendModel.get('selector', true); + var selectorPosition = legendModel.get('selectorPosition', true); + if (selector && (!selectorPosition || selectorPosition === 'auto')) { + selectorPosition = orient === 'horizontal' ? 'end' : 'start'; + } + + this.renderInner(itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition); + + // Perform layout. + var positionInfo = legendModel.getBoxLayoutParams(); + var viewportSize = {width: api.getWidth(), height: api.getHeight()}; + var padding = legendModel.get('padding'); + + var maxSize = getLayoutRect(positionInfo, viewportSize, padding); + + var mainRect = this.layoutInner(legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition); + + // Place mainGroup, based on the calculated `mainRect`. + var layoutRect = getLayoutRect( + defaults({width: mainRect.width, height: mainRect.height}, positionInfo), + viewportSize, + padding + ); + this.group.attr('position', [layoutRect.x - mainRect.x, layoutRect.y - mainRect.y]); + + // Render background after group is layout. + this.group.add( + this._backgroundEl = makeBackground(mainRect, legendModel) + ); + }, + + /** + * @protected + */ + resetInner: function () { + this.getContentGroup().removeAll(); + this._backgroundEl && this.group.remove(this._backgroundEl); + this.getSelectorGroup().removeAll(); + }, + + /** + * @protected + */ + renderInner: function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) { + var contentGroup = this.getContentGroup(); + var legendDrawnMap = createHashMap(); + var selectMode = legendModel.get('selectedMode'); + + var excludeSeriesId = []; + ecModel.eachRawSeries(function (seriesModel) { + !seriesModel.get('legendHoverLink') && excludeSeriesId.push(seriesModel.id); + }); + + each$25(legendModel.getData(), function (itemModel, dataIndex) { + var name = itemModel.get('name'); + + // Use empty string or \n as a newline string + if (!this.newlineDisabled && (name === '' || name === '\n')) { + contentGroup.add(new Group$3({ + newline: true + })); + return; + } + + // Representitive series. + var seriesModel = ecModel.getSeriesByName(name)[0]; + + if (legendDrawnMap.get(name)) { + // Have been drawed + return; + } + + // Legend to control series. + if (seriesModel) { + var data = seriesModel.getData(); + var color = data.getVisual('color'); + var borderColor = data.getVisual('borderColor'); + + // If color is a callback function + if (typeof color === 'function') { + // Use the first data + color = color(seriesModel.getDataParams(0)); + } + + // If borderColor is a callback function + if (typeof borderColor === 'function') { + // Use the first data + borderColor = borderColor(seriesModel.getDataParams(0)); + } + + // Using rect symbol defaultly + var legendSymbolType = data.getVisual('legendSymbol') || 'roundRect'; + var symbolType = data.getVisual('symbol'); + + var itemGroup = this._createItem( + name, dataIndex, itemModel, legendModel, + legendSymbolType, symbolType, + itemAlign, color, borderColor, + selectMode + ); + + itemGroup.on('click', curry$6(dispatchSelectAction, name, null, api, excludeSeriesId)) + .on('mouseover', curry$6(dispatchHighlightAction, seriesModel.name, null, api, excludeSeriesId)) + .on('mouseout', curry$6(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId)); + + legendDrawnMap.set(name, true); + } + else { + // Legend to control data. In pie and funnel. + ecModel.eachRawSeries(function (seriesModel) { + + // In case multiple series has same data name + if (legendDrawnMap.get(name)) { + return; + } + + if (seriesModel.legendVisualProvider) { + var provider = seriesModel.legendVisualProvider; + if (!provider.containName(name)) { + return; + } + + var idx = provider.indexOfName(name); + + var color = provider.getItemVisual(idx, 'color'); + var borderColor = provider.getItemVisual(idx, 'borderColor'); + + var legendSymbolType = 'roundRect'; + + var itemGroup = this._createItem( + name, dataIndex, itemModel, legendModel, + legendSymbolType, null, + itemAlign, color, borderColor, + selectMode + ); + + // FIXME: consider different series has items with the same name. + itemGroup.on('click', curry$6(dispatchSelectAction, null, name, api, excludeSeriesId)) + // Should not specify the series name, consider legend controls + // more than one pie series. + .on('mouseover', curry$6(dispatchHighlightAction, null, name, api, excludeSeriesId)) + .on('mouseout', curry$6(dispatchDownplayAction, null, name, api, excludeSeriesId)); + + legendDrawnMap.set(name, true); + } + + }, this); + } + + if (__DEV__) { + if (!legendDrawnMap.get(name)) { + console.warn( + name + ' series not exists. Legend data should be same with series name or data name.' + ); + } + } + }, this); + + if (selector) { + this._createSelector(selector, legendModel, api, orient, selectorPosition); + } + }, + + _createSelector: function (selector, legendModel, api, orient, selectorPosition) { + var selectorGroup = this.getSelectorGroup(); + + each$25(selector, function (selectorItem) { + createSelectorButton(selectorItem); + }); + + function createSelectorButton(selectorItem) { + var type = selectorItem.type; + + var labelText = new Text({ + style: { + x: 0, + y: 0, + align: 'center', + verticalAlign: 'middle' + }, + onclick: function () { + api.dispatchAction({ + type: type === 'all' ? 'legendAllSelect' : 'legendInverseSelect' + }); + } + }); + + selectorGroup.add(labelText); + + var labelModel = legendModel.getModel('selectorLabel'); + var emphasisLabelModel = legendModel.getModel('emphasis.selectorLabel'); + + setLabelStyle( + labelText.style, labelText.hoverStyle = {}, labelModel, emphasisLabelModel, + { + defaultText: selectorItem.title, + isRectText: false + } + ); + setHoverStyle(labelText); + } + }, + + _createItem: function ( + name, dataIndex, itemModel, legendModel, + legendSymbolType, symbolType, + itemAlign, color, borderColor, selectMode + ) { + var itemWidth = legendModel.get('itemWidth'); + var itemHeight = legendModel.get('itemHeight'); + var inactiveColor = legendModel.get('inactiveColor'); + var inactiveBorderColor = legendModel.get('inactiveBorderColor'); + var symbolKeepAspect = legendModel.get('symbolKeepAspect'); + var legendModelItemStyle = legendModel.getModel('itemStyle'); + + var isSelected = legendModel.isSelected(name); + var itemGroup = new Group$3(); + + var textStyleModel = itemModel.getModel('textStyle'); + + var itemIcon = itemModel.get('icon'); + + var tooltipModel = itemModel.getModel('tooltip'); + var legendGlobalTooltipModel = tooltipModel.parentModel; + + // Use user given icon first + legendSymbolType = itemIcon || legendSymbolType; + var legendSymbol = createSymbol( + legendSymbolType, + 0, + 0, + itemWidth, + itemHeight, + isSelected ? color : inactiveColor, + // symbolKeepAspect default true for legend + symbolKeepAspect == null ? true : symbolKeepAspect + ); + itemGroup.add( + setSymbolStyle( + legendSymbol, legendSymbolType, legendModelItemStyle, + borderColor, inactiveBorderColor, isSelected + ) + ); + + // Compose symbols + // PENDING + if (!itemIcon && symbolType + // At least show one symbol, can't be all none + && ((symbolType !== legendSymbolType) || symbolType === 'none') + ) { + var size = itemHeight * 0.8; + if (symbolType === 'none') { + symbolType = 'circle'; + } + var legendSymbolCenter = createSymbol( + symbolType, + (itemWidth - size) / 2, + (itemHeight - size) / 2, + size, + size, + isSelected ? color : inactiveColor, + // symbolKeepAspect default true for legend + symbolKeepAspect == null ? true : symbolKeepAspect + ); + // Put symbol in the center + itemGroup.add( + setSymbolStyle( + legendSymbolCenter, symbolType, legendModelItemStyle, + borderColor, inactiveBorderColor, isSelected + ) + ); + } + + var textX = itemAlign === 'left' ? itemWidth + 5 : -5; + var textAlign = itemAlign; + + var formatter = legendModel.get('formatter'); + var content = name; + if (typeof formatter === 'string' && formatter) { + content = formatter.replace('{name}', name != null ? name : ''); + } + else if (typeof formatter === 'function') { + content = formatter(name); + } + + itemGroup.add(new Text({ + style: setTextStyle({}, textStyleModel, { + text: content, + x: textX, + y: itemHeight / 2, + textFill: isSelected ? textStyleModel.getTextColor() : inactiveColor, + textAlign: textAlign, + textVerticalAlign: 'middle' + }) + })); + + // Add a invisible rect to increase the area of mouse hover + var hitRect = new Rect({ + shape: itemGroup.getBoundingRect(), + invisible: true, + tooltip: tooltipModel.get('show') ? extend({ + content: name, + // Defaul formatter + formatter: legendGlobalTooltipModel.get('formatter', true) || function () { + return name; + }, + formatterParams: { + componentType: 'legend', + legendIndex: legendModel.componentIndex, + name: name, + $vars: ['name'] + } + }, tooltipModel.option) : null + }); + itemGroup.add(hitRect); + + itemGroup.eachChild(function (child) { + child.silent = true; + }); + + hitRect.silent = !selectMode; + + this.getContentGroup().add(itemGroup); + + setHoverStyle(itemGroup); + + itemGroup.__legendDataIndex = dataIndex; + + return itemGroup; + }, + + /** + * @protected + */ + layoutInner: function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) { + var contentGroup = this.getContentGroup(); + var selectorGroup = this.getSelectorGroup(); + + // Place items in contentGroup. + box( + legendModel.get('orient'), + contentGroup, + legendModel.get('itemGap'), + maxSize.width, + maxSize.height + ); + + var contentRect = contentGroup.getBoundingRect(); + var contentPos = [-contentRect.x, -contentRect.y]; + + if (selector) { + // Place buttons in selectorGroup + box( + // Buttons in selectorGroup always layout horizontally + 'horizontal', + selectorGroup, + legendModel.get('selectorItemGap', true) + ); + + var selectorRect = selectorGroup.getBoundingRect(); + var selectorPos = [-selectorRect.x, -selectorRect.y]; + var selectorButtonGap = legendModel.get('selectorButtonGap', true); + + var orientIdx = legendModel.getOrient().index; + var wh = orientIdx === 0 ? 'width' : 'height'; + var hw = orientIdx === 0 ? 'height' : 'width'; + var yx = orientIdx === 0 ? 'y' : 'x'; + + if (selectorPosition === 'end') { + selectorPos[orientIdx] += contentRect[wh] + selectorButtonGap; + } + else { + contentPos[orientIdx] += selectorRect[wh] + selectorButtonGap; + } + + //Always align selector to content as 'middle' + selectorPos[1 - orientIdx] += contentRect[hw] / 2 - selectorRect[hw] / 2; + selectorGroup.attr('position', selectorPos); + contentGroup.attr('position', contentPos); + + var mainRect = {x: 0, y: 0}; + mainRect[wh] = contentRect[wh] + selectorButtonGap + selectorRect[wh]; + mainRect[hw] = Math.max(contentRect[hw], selectorRect[hw]); + mainRect[yx] = Math.min(0, selectorRect[yx] + selectorPos[1 - orientIdx]); + return mainRect; + } + else { + contentGroup.attr('position', contentPos); + return this.group.getBoundingRect(); + } + }, + + /** + * @protected + */ + remove: function () { + this.getContentGroup().removeAll(); + this._isFirstRender = true; + } + +}); + +function setSymbolStyle(symbol, symbolType, legendModelItemStyle, borderColor, inactiveBorderColor, isSelected) { + var itemStyle; + if (symbolType !== 'line' && symbolType.indexOf('empty') < 0) { + itemStyle = legendModelItemStyle.getItemStyle(); + symbol.style.stroke = borderColor; + if (!isSelected) { + itemStyle.stroke = inactiveBorderColor; + } + } + else { + itemStyle = legendModelItemStyle.getItemStyle(['borderWidth', 'borderColor']); + } + return symbol.setStyle(itemStyle); +} + +function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) { + // downplay before unselect + dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId); + api.dispatchAction({ + type: 'legendToggleSelect', + name: seriesName != null ? seriesName : dataName + }); + // highlight after select + dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId); +} + +function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) { + // If element hover will move to a hoverLayer. + var el = api.getZr().storage.getDisplayList()[0]; + if (!(el && el.useHoverLayer)) { + api.dispatchAction({ + type: 'highlight', + seriesName: seriesName, + name: dataName, + excludeSeriesId: excludeSeriesId + }); + } +} + +function dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId) { + // If element hover will move to a hoverLayer. + var el = api.getZr().storage.getDisplayList()[0]; + if (!(el && el.useHoverLayer)) { + api.dispatchAction({ + type: 'downplay', + seriesName: seriesName, + name: dataName, + excludeSeriesId: excludeSeriesId + }); + } +} + +/* +* 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. +*/ + +var legendFilter = function (ecModel) { + + var legendModels = ecModel.findComponents({ + mainType: 'legend' + }); + if (legendModels && legendModels.length) { + ecModel.filterSeries(function (series) { + // If in any legend component the status is not selected. + // Because in legend series is assumed selected when it is not in the legend data. + for (var i = 0; i < legendModels.length; i++) { + if (!legendModels[i].isSelected(series.name)) { + return false; + } + } + return true; + }); + } + +}; + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +// Do not contain scrollable legend, for sake of file size. + +// Series Filter +registerProcessor(PRIORITY.PROCESSOR.SERIES_FILTER, legendFilter); + +ComponentModel.registerSubTypeDefaulter('legend', function () { + // Default 'plain' when no type specified. + return 'plain'; +}); + +/* +* 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. +*/ + +var ScrollableLegendModel = LegendModel.extend({ + + type: 'legend.scroll', + + /** + * @param {number} scrollDataIndex + */ + setScrollDataIndex: function (scrollDataIndex) { + this.option.scrollDataIndex = scrollDataIndex; + }, + + defaultOption: { + scrollDataIndex: 0, + pageButtonItemGap: 5, + pageButtonGap: null, + pageButtonPosition: 'end', // 'start' or 'end' + pageFormatter: '{current}/{total}', // If null/undefined, do not show page. + pageIcons: { + horizontal: ['M0,0L12,-10L12,10z', 'M0,0L-12,-10L-12,10z'], + vertical: ['M0,0L20,0L10,-20z', 'M0,0L20,0L10,20z'] + }, + pageIconColor: '#2f4554', + pageIconInactiveColor: '#aaa', + pageIconSize: 15, // Can be [10, 3], which represents [width, height] + pageTextStyle: { + color: '#333' + }, + + animationDurationUpdate: 800 + }, + + /** + * @override + */ + init: function (option, parentModel, ecModel, extraOpt) { + var inputPositionParams = getLayoutParams(option); + + ScrollableLegendModel.superCall(this, 'init', option, parentModel, ecModel, extraOpt); + + mergeAndNormalizeLayoutParams$1(this, option, inputPositionParams); + }, + + /** + * @override + */ + mergeOption: function (option, extraOpt) { + ScrollableLegendModel.superCall(this, 'mergeOption', option, extraOpt); + + mergeAndNormalizeLayoutParams$1(this, this.option, option); + } + +}); + +// Do not `ignoreSize` to enable setting {left: 10, right: 10}. +function mergeAndNormalizeLayoutParams$1(legendModel, target, raw) { + var orient = legendModel.getOrient(); + var ignoreSize = [1, 1]; + ignoreSize[orient.index] = 0; + mergeLayoutParam(target, raw, { + type: 'box', ignoreSize: ignoreSize + }); +} + +/* +* 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. +*/ + +/** + * Separate legend and scrollable legend to reduce package size. + */ + +var Group$4 = Group; + +var WH$1 = ['width', 'height']; +var XY$1 = ['x', 'y']; + +var ScrollableLegendView = LegendView.extend({ + + type: 'legend.scroll', + + newlineDisabled: true, + + init: function () { + + ScrollableLegendView.superCall(this, 'init'); + + /** + * @private + * @type {number} For `scroll`. + */ + this._currentIndex = 0; + + /** + * @private + * @type {module:zrender/container/Group} + */ + this.group.add(this._containerGroup = new Group$4()); + this._containerGroup.add(this.getContentGroup()); + + /** + * @private + * @type {module:zrender/container/Group} + */ + this.group.add(this._controllerGroup = new Group$4()); + + /** + * + * @private + */ + this._showController; + }, + + /** + * @override + */ + resetInner: function () { + ScrollableLegendView.superCall(this, 'resetInner'); + + this._controllerGroup.removeAll(); + this._containerGroup.removeClipPath(); + this._containerGroup.__rectSize = null; + }, + + /** + * @override + */ + renderInner: function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) { + var me = this; + + // Render content items. + ScrollableLegendView.superCall(this, 'renderInner', itemAlign, + legendModel, ecModel, api, selector, orient, selectorPosition); + + var controllerGroup = this._controllerGroup; + + // FIXME: support be 'auto' adapt to size number text length, + // e.g., '3/12345' should not overlap with the control arrow button. + var pageIconSize = legendModel.get('pageIconSize', true); + if (!isArray(pageIconSize)) { + pageIconSize = [pageIconSize, pageIconSize]; + } + + createPageButton('pagePrev', 0); + + var pageTextStyleModel = legendModel.getModel('pageTextStyle'); + controllerGroup.add(new Text({ + name: 'pageText', + style: { + textFill: pageTextStyleModel.getTextColor(), + font: pageTextStyleModel.getFont(), + textVerticalAlign: 'middle', + textAlign: 'center' + }, + silent: true + })); + + createPageButton('pageNext', 1); + + function createPageButton(name, iconIdx) { + var pageDataIndexName = name + 'DataIndex'; + var icon = createIcon( + legendModel.get('pageIcons', true)[legendModel.getOrient().name][iconIdx], + { + // Buttons will be created in each render, so we do not need + // to worry about avoiding using legendModel kept in scope. + onclick: bind( + me._pageGo, me, pageDataIndexName, legendModel, api + ) + }, + { + x: -pageIconSize[0] / 2, + y: -pageIconSize[1] / 2, + width: pageIconSize[0], + height: pageIconSize[1] + } + ); + icon.name = name; + controllerGroup.add(icon); + } + }, + + /** + * @override + */ + layoutInner: function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) { + var selectorGroup = this.getSelectorGroup(); + + var orientIdx = legendModel.getOrient().index; + var wh = WH$1[orientIdx]; + var xy = XY$1[orientIdx]; + var hw = WH$1[1 - orientIdx]; + var yx = XY$1[1 - orientIdx]; + + selector && box( + // Buttons in selectorGroup always layout horizontally + 'horizontal', + selectorGroup, + legendModel.get('selectorItemGap', true) + ); + + var selectorButtonGap = legendModel.get('selectorButtonGap', true); + var selectorRect = selectorGroup.getBoundingRect(); + var selectorPos = [-selectorRect.x, -selectorRect.y]; + + var processMaxSize = clone(maxSize); + selector && (processMaxSize[wh] = maxSize[wh] - selectorRect[wh] - selectorButtonGap); + + var mainRect = this._layoutContentAndController(legendModel, isFirstRender, + processMaxSize, orientIdx, wh, hw, yx + ); + + if (selector) { + if (selectorPosition === 'end') { + selectorPos[orientIdx] += mainRect[wh] + selectorButtonGap; + } + else { + var offset = selectorRect[wh] + selectorButtonGap; + selectorPos[orientIdx] -= offset; + mainRect[xy] -= offset; + } + mainRect[wh] += selectorRect[wh] + selectorButtonGap; + + selectorPos[1 - orientIdx] += mainRect[yx] + mainRect[hw] / 2 - selectorRect[hw] / 2; + mainRect[hw] = Math.max(mainRect[hw], selectorRect[hw]); + mainRect[yx] = Math.min(mainRect[yx], selectorRect[yx] + selectorPos[1 - orientIdx]); + + selectorGroup.attr('position', selectorPos); + } + + return mainRect; + }, + + _layoutContentAndController: function (legendModel, isFirstRender, maxSize, orientIdx, wh, hw, yx) { + var contentGroup = this.getContentGroup(); + var containerGroup = this._containerGroup; + var controllerGroup = this._controllerGroup; + + // Place items in contentGroup. + box( + legendModel.get('orient'), + contentGroup, + legendModel.get('itemGap'), + !orientIdx ? null : maxSize.width, + orientIdx ? null : maxSize.height + ); + + box( + // Buttons in controller are layout always horizontally. + 'horizontal', + controllerGroup, + legendModel.get('pageButtonItemGap', true) + ); + + var contentRect = contentGroup.getBoundingRect(); + var controllerRect = controllerGroup.getBoundingRect(); + var showController = this._showController = contentRect[wh] > maxSize[wh]; + + var contentPos = [-contentRect.x, -contentRect.y]; + // Remain contentPos when scroll animation perfroming. + // If first rendering, `contentGroup.position` is [0, 0], which + // does not make sense and may cause unexepcted animation if adopted. + if (!isFirstRender) { + contentPos[orientIdx] = contentGroup.position[orientIdx]; + } + + // Layout container group based on 0. + var containerPos = [0, 0]; + var controllerPos = [-controllerRect.x, -controllerRect.y]; + var pageButtonGap = retrieve2( + legendModel.get('pageButtonGap', true), legendModel.get('itemGap', true) + ); + + // Place containerGroup and controllerGroup and contentGroup. + if (showController) { + var pageButtonPosition = legendModel.get('pageButtonPosition', true); + // controller is on the right / bottom. + if (pageButtonPosition === 'end') { + controllerPos[orientIdx] += maxSize[wh] - controllerRect[wh]; + } + // controller is on the left / top. + else { + containerPos[orientIdx] += controllerRect[wh] + pageButtonGap; + } + } + + // Always align controller to content as 'middle'. + controllerPos[1 - orientIdx] += contentRect[hw] / 2 - controllerRect[hw] / 2; + + contentGroup.attr('position', contentPos); + containerGroup.attr('position', containerPos); + controllerGroup.attr('position', controllerPos); + + // Calculate `mainRect` and set `clipPath`. + // mainRect should not be calculated by `this.group.getBoundingRect()` + // for sake of the overflow. + var mainRect = {x: 0, y: 0}; + + // Consider content may be overflow (should be clipped). + mainRect[wh] = showController ? maxSize[wh] : contentRect[wh]; + mainRect[hw] = Math.max(contentRect[hw], controllerRect[hw]); + + // `containerRect[yx] + containerPos[1 - orientIdx]` is 0. + mainRect[yx] = Math.min(0, controllerRect[yx] + controllerPos[1 - orientIdx]); + + containerGroup.__rectSize = maxSize[wh]; + if (showController) { + var clipShape = {x: 0, y: 0}; + clipShape[wh] = Math.max(maxSize[wh] - controllerRect[wh] - pageButtonGap, 0); + clipShape[hw] = mainRect[hw]; + containerGroup.setClipPath(new Rect({shape: clipShape})); + // Consider content may be larger than container, container rect + // can not be obtained from `containerGroup.getBoundingRect()`. + containerGroup.__rectSize = clipShape[wh]; + } + else { + // Do not remove or ignore controller. Keep them set as placeholders. + controllerGroup.eachChild(function (child) { + child.attr({invisible: true, silent: true}); + }); + } + + // Content translate animation. + var pageInfo = this._getPageInfo(legendModel); + pageInfo.pageIndex != null && updateProps( + contentGroup, + {position: pageInfo.contentPosition}, + // When switch from "show controller" to "not show controller", view should be + // updated immediately without animation, otherwise causes weird effect. + showController ? legendModel : false + ); + + this._updatePageInfoView(legendModel, pageInfo); + + return mainRect; + }, + + _pageGo: function (to, legendModel, api) { + var scrollDataIndex = this._getPageInfo(legendModel)[to]; + + scrollDataIndex != null && api.dispatchAction({ + type: 'legendScroll', + scrollDataIndex: scrollDataIndex, + legendId: legendModel.id + }); + }, + + _updatePageInfoView: function (legendModel, pageInfo) { + var controllerGroup = this._controllerGroup; + + each$1(['pagePrev', 'pageNext'], function (name) { + var canJump = pageInfo[name + 'DataIndex'] != null; + var icon = controllerGroup.childOfName(name); + if (icon) { + icon.setStyle( + 'fill', + canJump + ? legendModel.get('pageIconColor', true) + : legendModel.get('pageIconInactiveColor', true) + ); + icon.cursor = canJump ? 'pointer' : 'default'; + } + }); + + var pageText = controllerGroup.childOfName('pageText'); + var pageFormatter = legendModel.get('pageFormatter'); + var pageIndex = pageInfo.pageIndex; + var current = pageIndex != null ? pageIndex + 1 : 0; + var total = pageInfo.pageCount; + + pageText && pageFormatter && pageText.setStyle( + 'text', + isString(pageFormatter) + ? pageFormatter.replace('{current}', current).replace('{total}', total) + : pageFormatter({current: current, total: total}) + ); + }, + + /** + * @param {module:echarts/model/Model} legendModel + * @return {Object} { + * contentPosition: Array., null when data item not found. + * pageIndex: number, null when data item not found. + * pageCount: number, always be a number, can be 0. + * pagePrevDataIndex: number, null when no previous page. + * pageNextDataIndex: number, null when no next page. + * } + */ + _getPageInfo: function (legendModel) { + var scrollDataIndex = legendModel.get('scrollDataIndex', true); + var contentGroup = this.getContentGroup(); + var containerRectSize = this._containerGroup.__rectSize; + var orientIdx = legendModel.getOrient().index; + var wh = WH$1[orientIdx]; + var xy = XY$1[orientIdx]; + + var targetItemIndex = this._findTargetItemIndex(scrollDataIndex); + var children = contentGroup.children(); + var targetItem = children[targetItemIndex]; + var itemCount = children.length; + var pCount = !itemCount ? 0 : 1; + + var result = { + contentPosition: contentGroup.position.slice(), + pageCount: pCount, + pageIndex: pCount - 1, + pagePrevDataIndex: null, + pageNextDataIndex: null + }; + + if (!targetItem) { + return result; + } + + var targetItemInfo = getItemInfo(targetItem); + result.contentPosition[orientIdx] = -targetItemInfo.s; + + // Strategy: + // (1) Always align based on the left/top most item. + // (2) It is user-friendly that the last item shown in the + // current window is shown at the begining of next window. + // Otherwise if half of the last item is cut by the window, + // it will have no chance to display entirely. + // (3) Consider that item size probably be different, we + // have calculate pageIndex by size rather than item index, + // and we can not get page index directly by division. + // (4) The window is to narrow to contain more than + // one item, we should make sure that the page can be fliped. + + for (var i = targetItemIndex + 1, + winStartItemInfo = targetItemInfo, + winEndItemInfo = targetItemInfo, + currItemInfo = null; + i <= itemCount; + ++i + ) { + currItemInfo = getItemInfo(children[i]); + if ( + // Half of the last item is out of the window. + (!currItemInfo && winEndItemInfo.e > winStartItemInfo.s + containerRectSize) + // If the current item does not intersect with the window, the new page + // can be started at the current item or the last item. + || (currItemInfo && !intersect(currItemInfo, winStartItemInfo.s)) + ) { + if (winEndItemInfo.i > winStartItemInfo.i) { + winStartItemInfo = winEndItemInfo; + } + else { // e.g., when page size is smaller than item size. + winStartItemInfo = currItemInfo; + } + if (winStartItemInfo) { + if (result.pageNextDataIndex == null) { + result.pageNextDataIndex = winStartItemInfo.i; + } + ++result.pageCount; + } + } + winEndItemInfo = currItemInfo; + } + + for (var i = targetItemIndex - 1, + winStartItemInfo = targetItemInfo, + winEndItemInfo = targetItemInfo, + currItemInfo = null; + i >= -1; + --i + ) { + currItemInfo = getItemInfo(children[i]); + if ( + // If the the end item does not intersect with the window started + // from the current item, a page can be settled. + (!currItemInfo || !intersect(winEndItemInfo, currItemInfo.s)) + // e.g., when page size is smaller than item size. + && winStartItemInfo.i < winEndItemInfo.i + ) { + winEndItemInfo = winStartItemInfo; + if (result.pagePrevDataIndex == null) { + result.pagePrevDataIndex = winStartItemInfo.i; + } + ++result.pageCount; + ++result.pageIndex; + } + winStartItemInfo = currItemInfo; + } + + return result; + + function getItemInfo(el) { + if (el) { + var itemRect = el.getBoundingRect(); + var start = itemRect[xy] + el.position[orientIdx]; + return { + s: start, + e: start + itemRect[wh], + i: el.__legendDataIndex + }; + } + } + + function intersect(itemInfo, winStart) { + return itemInfo.e >= winStart && itemInfo.s <= winStart + containerRectSize; + } + }, + + _findTargetItemIndex: function (targetDataIndex) { + var index; + var contentGroup = this.getContentGroup(); + var defaultIndex; + + if (this._showController) { + contentGroup.eachChild(function (child, idx) { + var legendDataIdx = child.__legendDataIndex; + // FIXME + // If the given targetDataIndex (from model) is illegal, + // we use defualtIndex. But the index on the legend model and + // action payload is still illegal. That case will not be + // changed until some scenario requires. + if (defaultIndex == null && legendDataIdx != null) { + defaultIndex = idx; + } + if (legendDataIdx === targetDataIndex) { + index = idx; + } + }); + } + + return index != null ? index : defaultIndex; + } + +}); + +/* +* 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. +*/ + +/** + * @event legendScroll + * @type {Object} + * @property {string} type 'legendScroll' + * @property {string} scrollDataIndex + */ +registerAction( + 'legendScroll', 'legendscroll', + function (payload, ecModel) { + var scrollDataIndex = payload.scrollDataIndex; + + scrollDataIndex != null && ecModel.eachComponent( + {mainType: 'legend', subType: 'scroll', query: payload}, + function (legendModel) { + legendModel.setScrollDataIndex(scrollDataIndex); + } + ); + } +); + +/* +* 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. +*/ + +/** + * Legend component entry file8 + */ + +/* +* 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. +*/ + +var SliderZoomModel = DataZoomModel.extend({ + + type: 'dataZoom.slider', + + layoutMode: 'box', + + /** + * @protected + */ + defaultOption: { + show: true, + + // ph => placeholder. Using placehoder here because + // deault value can only be drived in view stage. + right: 'ph', // Default align to grid rect. + top: 'ph', // Default align to grid rect. + width: 'ph', // Default align to grid rect. + height: 'ph', // Default align to grid rect. + left: null, // Default align to grid rect. + bottom: null, // Default align to grid rect. + + backgroundColor: 'rgba(47,69,84,0)', // Background of slider zoom component. + // dataBackgroundColor: '#ddd', // Background coor of data shadow and border of box, + // highest priority, remain for compatibility of + // previous version, but not recommended any more. + dataBackground: { + lineStyle: { + color: '#2f4554', + width: 0.5, + opacity: 0.3 + }, + areaStyle: { + color: 'rgba(47,69,84,0.3)', + opacity: 0.3 + } + }, + borderColor: '#ddd', // border color of the box. For compatibility, + // if dataBackgroundColor is set, borderColor + // is ignored. + + fillerColor: 'rgba(167,183,204,0.4)', // Color of selected area. + // handleColor: 'rgba(89,170,216,0.95)', // Color of handle. + // handleIcon: 'path://M4.9,17.8c0-1.4,4.5-10.5,5.5-12.4c0-0.1,0.6-1.1,0.9-1.1c0.4,0,0.9,1,0.9,1.1c1.1,2.2,5.4,11,5.4,12.4v17.8c0,1.5-0.6,2.1-1.3,2.1H6.1c-0.7,0-1.3-0.6-1.3-2.1V17.8z', + /* eslint-disable */ + handleIcon: 'M8.2,13.6V3.9H6.3v9.7H3.1v14.9h3.3v9.7h1.8v-9.7h3.3V13.6H8.2z M9.7,24.4H4.8v-1.4h4.9V24.4z M9.7,19.1H4.8v-1.4h4.9V19.1z', + /* eslint-enable */ + // Percent of the slider height + handleSize: '100%', + + handleStyle: { + color: '#a7b7cc' + }, + + labelPrecision: null, + labelFormatter: null, + showDetail: true, + showDataShadow: 'auto', // Default auto decision. + realtime: true, + zoomLock: false, // Whether disable zoom. + textStyle: { + color: '#333' + } + } + +}); + +/* +* 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. +*/ + +var Rect$2 = Rect; +var linearMap$1 = linearMap; +var asc$2 = asc; +var bind$5 = bind; +var each$26 = each$1; + +// Constants +var DEFAULT_LOCATION_EDGE_GAP = 7; +var DEFAULT_FRAME_BORDER_WIDTH = 1; +var DEFAULT_FILLER_SIZE = 30; +var HORIZONTAL = 'horizontal'; +var VERTICAL = 'vertical'; +var LABEL_GAP = 5; +var SHOW_DATA_SHADOW_SERIES_TYPE = ['line', 'bar', 'candlestick', 'scatter']; + +var SliderZoomView = DataZoomView.extend({ + + type: 'dataZoom.slider', + + init: function (ecModel, api) { + + /** + * @private + * @type {Object} + */ + this._displayables = {}; + + /** + * @private + * @type {string} + */ + this._orient; + + /** + * [0, 100] + * @private + */ + this._range; + + /** + * [coord of the first handle, coord of the second handle] + * @private + */ + this._handleEnds; + + /** + * [length, thick] + * @private + * @type {Array.} + */ + this._size; + + /** + * @private + * @type {number} + */ + this._handleWidth; + + /** + * @private + * @type {number} + */ + this._handleHeight; + + /** + * @private + */ + this._location; + + /** + * @private + */ + this._dragging; + + /** + * @private + */ + this._dataShadowInfo; + + this.api = api; + }, + + /** + * @override + */ + render: function (dataZoomModel, ecModel, api, payload) { + SliderZoomView.superApply(this, 'render', arguments); + + createOrUpdate( + this, + '_dispatchZoomAction', + this.dataZoomModel.get('throttle'), + 'fixRate' + ); + + this._orient = dataZoomModel.get('orient'); + + if (this.dataZoomModel.get('show') === false) { + this.group.removeAll(); + return; + } + + // Notice: this._resetInterval() should not be executed when payload.type + // is 'dataZoom', origin this._range should be maintained, otherwise 'pan' + // or 'zoom' info will be missed because of 'throttle' of this.dispatchAction, + if (!payload || payload.type !== 'dataZoom' || payload.from !== this.uid) { + this._buildView(); + } + + this._updateView(); + }, + + /** + * @override + */ + remove: function () { + SliderZoomView.superApply(this, 'remove', arguments); + clear(this, '_dispatchZoomAction'); + }, + + /** + * @override + */ + dispose: function () { + SliderZoomView.superApply(this, 'dispose', arguments); + clear(this, '_dispatchZoomAction'); + }, + + _buildView: function () { + var thisGroup = this.group; + + thisGroup.removeAll(); + + this._resetLocation(); + this._resetInterval(); + + var barGroup = this._displayables.barGroup = new Group(); + + this._renderBackground(); + + this._renderHandle(); + + this._renderDataShadow(); + + thisGroup.add(barGroup); + + this._positionGroup(); + }, + + /** + * @private + */ + _resetLocation: function () { + var dataZoomModel = this.dataZoomModel; + var api = this.api; + + // If some of x/y/width/height are not specified, + // auto-adapt according to target grid. + var coordRect = this._findCoordRect(); + var ecSize = {width: api.getWidth(), height: api.getHeight()}; + // Default align by coordinate system rect. + var positionInfo = this._orient === HORIZONTAL + ? { + // Why using 'right', because right should be used in vertical, + // and it is better to be consistent for dealing with position param merge. + right: ecSize.width - coordRect.x - coordRect.width, + top: (ecSize.height - DEFAULT_FILLER_SIZE - DEFAULT_LOCATION_EDGE_GAP), + width: coordRect.width, + height: DEFAULT_FILLER_SIZE + } + : { // vertical + right: DEFAULT_LOCATION_EDGE_GAP, + top: coordRect.y, + width: DEFAULT_FILLER_SIZE, + height: coordRect.height + }; + + // Do not write back to option and replace value 'ph', because + // the 'ph' value should be recalculated when resize. + var layoutParams = getLayoutParams(dataZoomModel.option); + + // Replace the placeholder value. + each$1(['right', 'top', 'width', 'height'], function (name) { + if (layoutParams[name] === 'ph') { + layoutParams[name] = positionInfo[name]; + } + }); + + var layoutRect = getLayoutRect( + layoutParams, + ecSize, + dataZoomModel.padding + ); + + this._location = {x: layoutRect.x, y: layoutRect.y}; + this._size = [layoutRect.width, layoutRect.height]; + this._orient === VERTICAL && this._size.reverse(); + }, + + /** + * @private + */ + _positionGroup: function () { + var thisGroup = this.group; + var location = this._location; + var orient = this._orient; + + // Just use the first axis to determine mapping. + var targetAxisModel = this.dataZoomModel.getFirstTargetAxisModel(); + var inverse = targetAxisModel && targetAxisModel.get('inverse'); + + var barGroup = this._displayables.barGroup; + var otherAxisInverse = (this._dataShadowInfo || {}).otherAxisInverse; + + // Transform barGroup. + barGroup.attr( + (orient === HORIZONTAL && !inverse) + ? {scale: otherAxisInverse ? [1, 1] : [1, -1]} + : (orient === HORIZONTAL && inverse) + ? {scale: otherAxisInverse ? [-1, 1] : [-1, -1]} + : (orient === VERTICAL && !inverse) + ? {scale: otherAxisInverse ? [1, -1] : [1, 1], rotation: Math.PI / 2} + // Dont use Math.PI, considering shadow direction. + : {scale: otherAxisInverse ? [-1, -1] : [-1, 1], rotation: Math.PI / 2} + ); + + // Position barGroup + var rect = thisGroup.getBoundingRect([barGroup]); + thisGroup.attr('position', [location.x - rect.x, location.y - rect.y]); + }, + + /** + * @private + */ + _getViewExtent: function () { + return [0, this._size[0]]; + }, + + _renderBackground: function () { + var dataZoomModel = this.dataZoomModel; + var size = this._size; + var barGroup = this._displayables.barGroup; + + barGroup.add(new Rect$2({ + silent: true, + shape: { + x: 0, y: 0, width: size[0], height: size[1] + }, + style: { + fill: dataZoomModel.get('backgroundColor') + }, + z2: -40 + })); + + // Click panel, over shadow, below handles. + barGroup.add(new Rect$2({ + shape: { + x: 0, y: 0, width: size[0], height: size[1] + }, + style: { + fill: 'transparent' + }, + z2: 0, + onclick: bind(this._onClickPanelClick, this) + })); + }, + + _renderDataShadow: function () { + var info = this._dataShadowInfo = this._prepareDataShadowInfo(); + + if (!info) { + return; + } + + var size = this._size; + var seriesModel = info.series; + var data = seriesModel.getRawData(); + + var otherDim = seriesModel.getShadowDim + ? seriesModel.getShadowDim() // @see candlestick + : info.otherDim; + + if (otherDim == null) { + return; + } + + var otherDataExtent = data.getDataExtent(otherDim); + // Nice extent. + var otherOffset = (otherDataExtent[1] - otherDataExtent[0]) * 0.3; + otherDataExtent = [ + otherDataExtent[0] - otherOffset, + otherDataExtent[1] + otherOffset + ]; + var otherShadowExtent = [0, size[1]]; + + var thisShadowExtent = [0, size[0]]; + + var areaPoints = [[size[0], 0], [0, 0]]; + var linePoints = []; + var step = thisShadowExtent[1] / (data.count() - 1); + var thisCoord = 0; + + // Optimize for large data shadow + var stride = Math.round(data.count() / size[0]); + var lastIsEmpty; + data.each([otherDim], function (value, index) { + if (stride > 0 && (index % stride)) { + thisCoord += step; + return; + } + + // FIXME + // Should consider axis.min/axis.max when drawing dataShadow. + + // FIXME + // 应该使用统一的空判断?还是在list里进行空判断? + var isEmpty = value == null || isNaN(value) || value === ''; + // See #4235. + var otherCoord = isEmpty + ? 0 : linearMap$1(value, otherDataExtent, otherShadowExtent, true); + + // Attempt to draw data shadow precisely when there are empty value. + if (isEmpty && !lastIsEmpty && index) { + areaPoints.push([areaPoints[areaPoints.length - 1][0], 0]); + linePoints.push([linePoints[linePoints.length - 1][0], 0]); + } + else if (!isEmpty && lastIsEmpty) { + areaPoints.push([thisCoord, 0]); + linePoints.push([thisCoord, 0]); + } + + areaPoints.push([thisCoord, otherCoord]); + linePoints.push([thisCoord, otherCoord]); + + thisCoord += step; + lastIsEmpty = isEmpty; + }); + + var dataZoomModel = this.dataZoomModel; + // var dataBackgroundModel = dataZoomModel.getModel('dataBackground'); + this._displayables.barGroup.add(new Polygon({ + shape: {points: areaPoints}, + style: defaults( + {fill: dataZoomModel.get('dataBackgroundColor')}, + dataZoomModel.getModel('dataBackground.areaStyle').getAreaStyle() + ), + silent: true, + z2: -20 + })); + this._displayables.barGroup.add(new Polyline({ + shape: {points: linePoints}, + style: dataZoomModel.getModel('dataBackground.lineStyle').getLineStyle(), + silent: true, + z2: -19 + })); + }, + + _prepareDataShadowInfo: function () { + var dataZoomModel = this.dataZoomModel; + var showDataShadow = dataZoomModel.get('showDataShadow'); + + if (showDataShadow === false) { + return; + } + + // Find a representative series. + var result; + var ecModel = this.ecModel; + + dataZoomModel.eachTargetAxis(function (dimNames, axisIndex) { + var seriesModels = dataZoomModel + .getAxisProxy(dimNames.name, axisIndex) + .getTargetSeriesModels(); + + each$1(seriesModels, function (seriesModel) { + if (result) { + return; + } + + if (showDataShadow !== true && indexOf( + SHOW_DATA_SHADOW_SERIES_TYPE, seriesModel.get('type') + ) < 0 + ) { + return; + } + + var thisAxis = ecModel.getComponent(dimNames.axis, axisIndex).axis; + var otherDim = getOtherDim(dimNames.name); + var otherAxisInverse; + var coordSys = seriesModel.coordinateSystem; + + if (otherDim != null && coordSys.getOtherAxis) { + otherAxisInverse = coordSys.getOtherAxis(thisAxis).inverse; + } + + otherDim = seriesModel.getData().mapDimension(otherDim); + + result = { + thisAxis: thisAxis, + series: seriesModel, + thisDim: dimNames.name, + otherDim: otherDim, + otherAxisInverse: otherAxisInverse + }; + + }, this); + + }, this); + + return result; + }, + + _renderHandle: function () { + var displaybles = this._displayables; + var handles = displaybles.handles = []; + var handleLabels = displaybles.handleLabels = []; + var barGroup = this._displayables.barGroup; + var size = this._size; + var dataZoomModel = this.dataZoomModel; + + barGroup.add(displaybles.filler = new Rect$2({ + draggable: true, + cursor: getCursor(this._orient), + drift: bind$5(this._onDragMove, this, 'all'), + ondragstart: bind$5(this._showDataInfo, this, true), + ondragend: bind$5(this._onDragEnd, this), + onmouseover: bind$5(this._showDataInfo, this, true), + onmouseout: bind$5(this._showDataInfo, this, false), + style: { + fill: dataZoomModel.get('fillerColor'), + textPosition: 'inside' + } + })); + + // Frame border. + barGroup.add(new Rect$2({ + silent: true, + subPixelOptimize: true, + shape: { + x: 0, + y: 0, + width: size[0], + height: size[1] + }, + style: { + stroke: dataZoomModel.get('dataBackgroundColor') + || dataZoomModel.get('borderColor'), + lineWidth: DEFAULT_FRAME_BORDER_WIDTH, + fill: 'rgba(0,0,0,0)' + } + })); + + each$26([0, 1], function (handleIndex) { + var path = createIcon( + dataZoomModel.get('handleIcon'), + { + cursor: getCursor(this._orient), + draggable: true, + drift: bind$5(this._onDragMove, this, handleIndex), + ondragend: bind$5(this._onDragEnd, this), + onmouseover: bind$5(this._showDataInfo, this, true), + onmouseout: bind$5(this._showDataInfo, this, false) + }, + {x: -1, y: 0, width: 2, height: 2} + ); + + var bRect = path.getBoundingRect(); + this._handleHeight = parsePercent$1(dataZoomModel.get('handleSize'), this._size[1]); + this._handleWidth = bRect.width / bRect.height * this._handleHeight; + + path.setStyle(dataZoomModel.getModel('handleStyle').getItemStyle()); + var handleColor = dataZoomModel.get('handleColor'); + // Compatitable with previous version + if (handleColor != null) { + path.style.fill = handleColor; + } + + barGroup.add(handles[handleIndex] = path); + + var textStyleModel = dataZoomModel.textStyleModel; + + this.group.add( + handleLabels[handleIndex] = new Text({ + silent: true, + invisible: true, + style: { + x: 0, y: 0, text: '', + textVerticalAlign: 'middle', + textAlign: 'center', + textFill: textStyleModel.getTextColor(), + textFont: textStyleModel.getFont() + }, + z2: 10 + })); + + }, this); + }, + + /** + * @private + */ + _resetInterval: function () { + var range = this._range = this.dataZoomModel.getPercentRange(); + var viewExtent = this._getViewExtent(); + + this._handleEnds = [ + linearMap$1(range[0], [0, 100], viewExtent, true), + linearMap$1(range[1], [0, 100], viewExtent, true) + ]; + }, + + /** + * @private + * @param {(number|string)} handleIndex 0 or 1 or 'all' + * @param {number} delta + * @return {boolean} changed + */ + _updateInterval: function (handleIndex, delta) { + var dataZoomModel = this.dataZoomModel; + var handleEnds = this._handleEnds; + var viewExtend = this._getViewExtent(); + var minMaxSpan = dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan(); + var percentExtent = [0, 100]; + + sliderMove( + delta, + handleEnds, + viewExtend, + dataZoomModel.get('zoomLock') ? 'all' : handleIndex, + minMaxSpan.minSpan != null + ? linearMap$1(minMaxSpan.minSpan, percentExtent, viewExtend, true) : null, + minMaxSpan.maxSpan != null + ? linearMap$1(minMaxSpan.maxSpan, percentExtent, viewExtend, true) : null + ); + + var lastRange = this._range; + var range = this._range = asc$2([ + linearMap$1(handleEnds[0], viewExtend, percentExtent, true), + linearMap$1(handleEnds[1], viewExtend, percentExtent, true) + ]); + + return !lastRange || lastRange[0] !== range[0] || lastRange[1] !== range[1]; + }, + + /** + * @private + */ + _updateView: function (nonRealtime) { + var displaybles = this._displayables; + var handleEnds = this._handleEnds; + var handleInterval = asc$2(handleEnds.slice()); + var size = this._size; + + each$26([0, 1], function (handleIndex) { + // Handles + var handle = displaybles.handles[handleIndex]; + var handleHeight = this._handleHeight; + handle.attr({ + scale: [handleHeight / 2, handleHeight / 2], + position: [handleEnds[handleIndex], size[1] / 2 - handleHeight / 2] + }); + }, this); + + // Filler + displaybles.filler.setShape({ + x: handleInterval[0], + y: 0, + width: handleInterval[1] - handleInterval[0], + height: size[1] + }); + + this._updateDataInfo(nonRealtime); + }, + + /** + * @private + */ + _updateDataInfo: function (nonRealtime) { + var dataZoomModel = this.dataZoomModel; + var displaybles = this._displayables; + var handleLabels = displaybles.handleLabels; + var orient = this._orient; + var labelTexts = ['', '']; + + // FIXME + // date型,支持formatter,autoformatter(ec2 date.getAutoFormatter) + if (dataZoomModel.get('showDetail')) { + var axisProxy = dataZoomModel.findRepresentativeAxisProxy(); + + if (axisProxy) { + var axis = axisProxy.getAxisModel().axis; + var range = this._range; + + var dataInterval = nonRealtime + // See #4434, data and axis are not processed and reset yet in non-realtime mode. + ? axisProxy.calculateDataWindow({ + start: range[0], end: range[1] + }).valueWindow + : axisProxy.getDataValueWindow(); + + labelTexts = [ + this._formatLabel(dataInterval[0], axis), + this._formatLabel(dataInterval[1], axis) + ]; + } + } + + var orderedHandleEnds = asc$2(this._handleEnds.slice()); + + setLabel.call(this, 0); + setLabel.call(this, 1); + + function setLabel(handleIndex) { + // Label + // Text should not transform by barGroup. + // Ignore handlers transform + var barTransform = getTransform( + displaybles.handles[handleIndex].parent, this.group + ); + var direction = transformDirection( + handleIndex === 0 ? 'right' : 'left', barTransform + ); + var offset = this._handleWidth / 2 + LABEL_GAP; + var textPoint = applyTransform$1( + [ + orderedHandleEnds[handleIndex] + (handleIndex === 0 ? -offset : offset), + this._size[1] / 2 + ], + barTransform + ); + handleLabels[handleIndex].setStyle({ + x: textPoint[0], + y: textPoint[1], + textVerticalAlign: orient === HORIZONTAL ? 'middle' : direction, + textAlign: orient === HORIZONTAL ? direction : 'center', + text: labelTexts[handleIndex] + }); + } + }, + + /** + * @private + */ + _formatLabel: function (value, axis) { + var dataZoomModel = this.dataZoomModel; + var labelFormatter = dataZoomModel.get('labelFormatter'); + + var labelPrecision = dataZoomModel.get('labelPrecision'); + if (labelPrecision == null || labelPrecision === 'auto') { + labelPrecision = axis.getPixelPrecision(); + } + + var valueStr = (value == null || isNaN(value)) + ? '' + // FIXME Glue code + : (axis.type === 'category' || axis.type === 'time') + ? axis.scale.getLabel(Math.round(value)) + // param of toFixed should less then 20. + : value.toFixed(Math.min(labelPrecision, 20)); + + return isFunction$1(labelFormatter) + ? labelFormatter(value, valueStr) + : isString(labelFormatter) + ? labelFormatter.replace('{value}', valueStr) + : valueStr; + }, + + /** + * @private + * @param {boolean} showOrHide true: show, false: hide + */ + _showDataInfo: function (showOrHide) { + // Always show when drgging. + showOrHide = this._dragging || showOrHide; + + var handleLabels = this._displayables.handleLabels; + handleLabels[0].attr('invisible', !showOrHide); + handleLabels[1].attr('invisible', !showOrHide); + }, + + _onDragMove: function (handleIndex, dx, dy, event) { + this._dragging = true; + + // For mobile device, prevent screen slider on the button. + stop(event.event); + + // Transform dx, dy to bar coordination. + var barTransform = this._displayables.barGroup.getLocalTransform(); + var vertex = applyTransform$1([dx, dy], barTransform, true); + + var changed = this._updateInterval(handleIndex, vertex[0]); + + var realtime = this.dataZoomModel.get('realtime'); + + this._updateView(!realtime); + + // Avoid dispatch dataZoom repeatly but range not changed, + // which cause bad visual effect when progressive enabled. + changed && realtime && this._dispatchZoomAction(); + }, + + _onDragEnd: function () { + this._dragging = false; + this._showDataInfo(false); + + // While in realtime mode and stream mode, dispatch action when + // drag end will cause the whole view rerender, which is unnecessary. + var realtime = this.dataZoomModel.get('realtime'); + !realtime && this._dispatchZoomAction(); + }, + + _onClickPanelClick: function (e) { + var size = this._size; + var localPoint = this._displayables.barGroup.transformCoordToLocal(e.offsetX, e.offsetY); + + if (localPoint[0] < 0 || localPoint[0] > size[0] + || localPoint[1] < 0 || localPoint[1] > size[1] + ) { + return; + } + + var handleEnds = this._handleEnds; + var center = (handleEnds[0] + handleEnds[1]) / 2; + + var changed = this._updateInterval('all', localPoint[0] - center); + this._updateView(); + changed && this._dispatchZoomAction(); + }, + + /** + * This action will be throttled. + * @private + */ + _dispatchZoomAction: function () { + var range = this._range; + + this.api.dispatchAction({ + type: 'dataZoom', + from: this.uid, + dataZoomId: this.dataZoomModel.id, + start: range[0], + end: range[1] + }); + }, + + /** + * @private + */ + _findCoordRect: function () { + // Find the grid coresponding to the first axis referred by dataZoom. + var rect; + each$26(this.getTargetCoordInfo(), function (coordInfoList) { + if (!rect && coordInfoList.length) { + var coordSys = coordInfoList[0].model.coordinateSystem; + rect = coordSys.getRect && coordSys.getRect(); + } + }); + if (!rect) { + var width = this.api.getWidth(); + var height = this.api.getHeight(); + rect = { + x: width * 0.2, + y: height * 0.2, + width: width * 0.6, + height: height * 0.6 + }; + } + + return rect; + } + +}); + +function getOtherDim(thisDim) { + // FIXME + // 这个逻辑和getOtherAxis里一致,但是写在这里是否不好 + var map$$1 = {x: 'y', y: 'x', radius: 'angle', angle: 'radius'}; + return map$$1[thisDim]; +} + +function getCursor(orient) { + return orient === 'vertical' ? 'ns-resize' : 'ew-resize'; +} + +/* +* 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. +*/ + +/* +* 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. +*/ + +DataZoomModel.extend({ + + type: 'dataZoom.inside', + + /** + * @protected + */ + defaultOption: { + disabled: false, // Whether disable this inside zoom. + 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 + } +}); + +/* +* 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. +*/ + +// Only create one roam controller for each coordinate system. +// one roam controller might be refered by two inside data zoom +// components (for example, one for x and one for y). When user +// pan or zoom, only dispatch one action for those data zoom +// components. + +var ATTR$2 = '\0_ec_dataZoom_roams'; + + +/** + * @public + * @param {module:echarts/ExtensionAPI} api + * @param {Object} dataZoomInfo + * @param {string} dataZoomInfo.coordId + * @param {Function} dataZoomInfo.containsPoint + * @param {Array.} dataZoomInfo.allCoordIds + * @param {string} dataZoomInfo.dataZoomId + * @param {Object} dataZoomInfo.getRange + * @param {Function} dataZoomInfo.getRange.pan + * @param {Function} dataZoomInfo.getRange.zoom + * @param {Function} dataZoomInfo.getRange.scrollMove + * @param {boolean} dataZoomInfo.dataZoomModel + */ +function register$2(api, dataZoomInfo) { + var store = giveStore$1(api); + var theDataZoomId = dataZoomInfo.dataZoomId; + var theCoordId = dataZoomInfo.coordId; + + // Do clean when a dataZoom changes its target coordnate system. + // Avoid memory leak, dispose all not-used-registered. + each$1(store, function (record, coordId) { + var dataZoomInfos = record.dataZoomInfos; + if (dataZoomInfos[theDataZoomId] + && indexOf(dataZoomInfo.allCoordIds, theCoordId) < 0 + ) { + delete dataZoomInfos[theDataZoomId]; + record.count--; + } + }); + + cleanStore(store); + + var record = store[theCoordId]; + // Create if needed. + if (!record) { + record = store[theCoordId] = { + coordId: theCoordId, + dataZoomInfos: {}, + count: 0 + }; + record.controller = createController(api, record); + record.dispatchAction = curry(dispatchAction$1, api); + } + + // Update reference of dataZoom. + !(record.dataZoomInfos[theDataZoomId]) && record.count++; + record.dataZoomInfos[theDataZoomId] = dataZoomInfo; + + var controllerParams = mergeControllerParams(record.dataZoomInfos); + record.controller.enable(controllerParams.controlType, controllerParams.opt); + + // Consider resize, area should be always updated. + record.controller.setPointerChecker(dataZoomInfo.containsPoint); + + // Update throttle. + createOrUpdate( + record, + 'dispatchAction', + dataZoomInfo.dataZoomModel.get('throttle', true), + 'fixRate' + ); +} + +/** + * @public + * @param {module:echarts/ExtensionAPI} api + * @param {string} dataZoomId + */ +function unregister$1(api, dataZoomId) { + var store = giveStore$1(api); + + each$1(store, function (record) { + record.controller.dispose(); + var dataZoomInfos = record.dataZoomInfos; + if (dataZoomInfos[dataZoomId]) { + delete dataZoomInfos[dataZoomId]; + record.count--; + } + }); + + cleanStore(store); +} + +/** + * @public + */ +function generateCoordId(coordModel) { + return coordModel.type + '\0_' + coordModel.id; +} + +/** + * Key: coordId, value: {dataZoomInfos: [], count, controller} + * @type {Array.} + */ +function giveStore$1(api) { + // Mount store on zrender instance, so that we do not + // need to worry about dispose. + var zr = api.getZr(); + return zr[ATTR$2] || (zr[ATTR$2] = {}); +} + +function createController(api, newRecord) { + var controller = new RoamController(api.getZr()); + + each$1(['pan', 'zoom', 'scrollMove'], function (eventName) { + controller.on(eventName, function (event) { + var batch = []; + + each$1(newRecord.dataZoomInfos, function (info) { + // Check whether the behaviors (zoomOnMouseWheel, moveOnMouseMove, + // moveOnMouseWheel, ...) enabled. + 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; +} + +function cleanStore(store) { + each$1(store, function (record, coordId) { + if (!record.count) { + record.controller.dispose(); + delete store[coordId]; + } + }); +} + +/** + * This action will be throttled. + */ +function dispatchAction$1(api, batch) { + api.dispatchAction({ + type: 'dataZoom', + batch: batch + }); +} + +/** + * Merge roamController settings when multiple dataZooms share one roamController. + */ +function mergeControllerParams(dataZoomInfos) { + var controlType; + // 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_'; + var typePriority = { + 'type_true': 2, + 'type_move': 1, + 'type_false': 0, + 'type_undefined': -1 + }; + var preventDefaultMouseMove = true; + + each$1(dataZoomInfos, function (dataZoomInfo) { + 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; + } + + // 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: { + // RoamController will enable all of these functionalities, + // and the final behavior is determined by its event listener + // provided by each inside zoom. + zoomOnMouseWheel: true, + moveOnMouseMove: true, + moveOnMouseWheel: true, + preventDefaultMouseMove: !!preventDefaultMouseMove + } + }; +} + +/* +* 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. +*/ + +var bind$6 = bind; + +var InsideZoomView = DataZoomView.extend({ + + type: 'dataZoom.inside', + + /** + * @override + */ + init: function (ecModel, api) { + /** + * 'throttle' is used in this.dispatchAction, so we save range + * to avoid missing some 'pan' info. + * @private + * @type {Array.} + */ + this._range; + }, + + /** + * @override + */ + render: function (dataZoomModel, ecModel, api, payload) { + InsideZoomView.superApply(this, 'render', arguments); + + // Hence the `throttle` util ensures to preserve command order, + // here simply updating range all the time will not cause missing + // any of the the roam change. + this._range = dataZoomModel.getPercentRange(); + + // Reset controllers. + each$1(this.getTargetCoordInfo(), function (coordInfoList, coordSysName) { + + var allCoordIds = map(coordInfoList, function (coordInfo) { + return generateCoordId(coordInfo.model); + }); + + each$1(coordInfoList, function (coordInfo) { + var coordModel = coordInfo.model; + + var getRange = {}; + each$1(['pan', 'zoom', 'scrollMove'], function (eventName) { + getRange[eventName] = bind$6(roamHandlers[eventName], this, coordInfo, coordSysName); + }, this); + + register$2( + api, + { + coordId: generateCoordId(coordModel), + allCoordIds: allCoordIds, + containsPoint: function (e, x, y) { + return coordModel.coordinateSystem.containPoint([x, y]); + }, + dataZoomId: dataZoomModel.id, + dataZoomModel: dataZoomModel, + getRange: getRange + } + ); + }, this); + + }, this); + }, + + /** + * @override + */ + dispose: function () { + unregister$1(this.api, this.dataZoomModel.id); + InsideZoomView.superApply(this, 'dispose', arguments); + this._range = null; + } + +}); + +var roamHandlers = { + + /** + * @this {module:echarts/component/dataZoom/InsideZoomView} + */ + zoom: function (coordInfo, coordSysName, controller, e) { + var lastRange = this._range; + var range = lastRange.slice(); + + // Calculate transform by the first axis. + var axisModel = coordInfo.axisModels[0]; + if (!axisModel) { + return; + } + + var directionInfo = getDirectionInfo[coordSysName]( + 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 scale = Math.max(1 / e.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); + + this._range = range; + + if (lastRange[0] !== range[0] || lastRange[1] !== range[1]) { + return range; + } + }, + + /** + * @this {module:echarts/component/dataZoom/InsideZoomView} + */ + 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) { + var directionInfo = getDirectionInfo[coordSysName]( + [0, 0], [e.scrollDelta, e.scrollDelta], axisModel, controller, coordInfo + ); + return directionInfo.signal * (range[1] - range[0]) * e.scrollDelta; + }) +}; + +function makeMover(getPercentDelta) { + return function (coordInfo, coordSysName, controller, e) { + var lastRange = this._range; + var range = lastRange.slice(); + + // Calculate transform by the first axis. + var axisModel = coordInfo.axisModels[0]; + if (!axisModel) { + return; + } + + var percentDelta = getPercentDelta( + range, axisModel, coordInfo, coordSysName, controller, e + ); + + sliderMove(percentDelta, range, [0, 100], 'all'); + + this._range = range; + + if (lastRange[0] !== range[0] || lastRange[1] !== range[1]) { + return range; + } + }; +} + +var getDirectionInfo = { + + grid: function (oldPoint, newPoint, axisModel, controller, coordInfo) { + var axis = axisModel.axis; + var ret = {}; + var rect = coordInfo.model.coordinateSystem.getRect(); + oldPoint = oldPoint || [0, 0]; + + if (axis.dim === 'x') { + ret.pixel = newPoint[0] - oldPoint[0]; + ret.pixelLength = rect.width; + ret.pixelStart = rect.x; + ret.signal = axis.inverse ? 1 : -1; + } + else { // axis.dim === 'y' + ret.pixel = newPoint[1] - oldPoint[1]; + ret.pixelLength = rect.height; + ret.pixelStart = rect.y; + ret.signal = axis.inverse ? -1 : 1; + } + + return ret; + }, + + polar: function (oldPoint, newPoint, axisModel, controller, coordInfo) { + var axis = axisModel.axis; + var ret = {}; + var polar = coordInfo.model.coordinateSystem; + var radiusExtent = polar.getRadiusAxis().getExtent(); + var angleExtent = polar.getAngleAxis().getExtent(); + + oldPoint = oldPoint ? polar.pointToCoord(oldPoint) : [0, 0]; + newPoint = polar.pointToCoord(newPoint); + + if (axisModel.mainType === 'radiusAxis') { + ret.pixel = newPoint[0] - oldPoint[0]; + // ret.pixelLength = Math.abs(radiusExtent[1] - radiusExtent[0]); + // ret.pixelStart = Math.min(radiusExtent[0], radiusExtent[1]); + ret.pixelLength = radiusExtent[1] - radiusExtent[0]; + ret.pixelStart = radiusExtent[0]; + ret.signal = axis.inverse ? 1 : -1; + } + else { // 'angleAxis' + ret.pixel = newPoint[1] - oldPoint[1]; + // ret.pixelLength = Math.abs(angleExtent[1] - angleExtent[0]); + // ret.pixelStart = Math.min(angleExtent[0], angleExtent[1]); + ret.pixelLength = angleExtent[1] - angleExtent[0]; + ret.pixelStart = angleExtent[0]; + ret.signal = axis.inverse ? -1 : 1; + } + + return ret; + }, + + singleAxis: function (oldPoint, newPoint, axisModel, controller, coordInfo) { + var axis = axisModel.axis; + var rect = coordInfo.model.coordinateSystem.getRect(); + var ret = {}; + + oldPoint = oldPoint || [0, 0]; + + if (axis.orient === 'horizontal') { + ret.pixel = newPoint[0] - oldPoint[0]; + ret.pixelLength = rect.width; + ret.pixelStart = rect.x; + ret.signal = axis.inverse ? 1 : -1; + } + else { // 'vertical' + ret.pixel = newPoint[1] - oldPoint[1]; + ret.pixelLength = rect.height; + ret.pixelStart = rect.y; + ret.signal = axis.inverse ? -1 : 1; + } + + return ret; + } +}; + +/* +* 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. +*/ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + + +// Do not include './dataZoomSelect', +// since it only work for toolbox dataZoom. + +/* +* 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. +*/ + +var each$27 = each$1; + +var preprocessor$3 = function (option) { + var visualMap = option && option.visualMap; + + if (!isArray(visualMap)) { + visualMap = visualMap ? [visualMap] : []; + } + + each$27(visualMap, function (opt) { + if (!opt) { + return; + } + + // rename splitList to pieces + if (has$2(opt, 'splitList') && !has$2(opt, 'pieces')) { + opt.pieces = opt.splitList; + delete opt.splitList; + } + + var pieces = opt.pieces; + if (pieces && isArray(pieces)) { + each$27(pieces, function (piece) { + if (isObject$1(piece)) { + if (has$2(piece, 'start') && !has$2(piece, 'min')) { + piece.min = piece.start; + } + if (has$2(piece, 'end') && !has$2(piece, 'max')) { + piece.max = piece.end; + } + } + }); + } + }); +}; + +function has$2(obj, name) { + return obj && obj.hasOwnProperty && obj.hasOwnProperty(name); +} + +/* +* 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. +*/ + +ComponentModel.registerSubTypeDefaulter('visualMap', function (option) { + // Compatible with ec2, when splitNumber === 0, continuous visualMap will be used. + return ( + !option.categories + && ( + !( + option.pieces + ? option.pieces.length > 0 + : option.splitNumber > 0 + ) + || option.calculable + ) + ) + ? 'continuous' : 'piecewise'; +}); + +/* +* 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. +*/ + +var VISUAL_PRIORITY = PRIORITY.VISUAL.COMPONENT; + +registerVisual(VISUAL_PRIORITY, { + createOnAllSeries: true, + reset: function (seriesModel, ecModel) { + var resetDefines = []; + ecModel.eachComponent('visualMap', function (visualMapModel) { + var pipelineContext = seriesModel.pipelineContext; + if (!visualMapModel.isTargetSeries(seriesModel) + || (pipelineContext && pipelineContext.large) + ) { + return; + } + + resetDefines.push(incrementalApplyVisual( + visualMapModel.stateList, + visualMapModel.targetVisuals, + bind(visualMapModel.getValueState, visualMapModel), + visualMapModel.getDataDimension(seriesModel.getData()) + )); + }); + + return resetDefines; + } +}); + +// Only support color. +registerVisual(VISUAL_PRIORITY, { + createOnAllSeries: true, + reset: function (seriesModel, ecModel) { + var data = seriesModel.getData(); + var visualMetaList = []; + + ecModel.eachComponent('visualMap', function (visualMapModel) { + if (visualMapModel.isTargetSeries(seriesModel)) { + var visualMeta = visualMapModel.getVisualMeta( + bind(getColorVisual, null, seriesModel, visualMapModel) + ) || {stops: [], outerColors: []}; + + var concreteDim = visualMapModel.getDataDimension(data); + var dimInfo = data.getDimensionInfo(concreteDim); + if (dimInfo != null) { + // visualMeta.dimension should be dimension index, but not concrete dimension. + visualMeta.dimension = dimInfo.index; + visualMetaList.push(visualMeta); + } + } + }); + + // console.log(JSON.stringify(visualMetaList.map(a => a.stops))); + seriesModel.getData().setVisual('visualMeta', visualMetaList); + } +}); + +// FIXME +// performance and export for heatmap? +// value can be Infinity or -Infinity +function getColorVisual(seriesModel, visualMapModel, value, valueState) { + var mappings = visualMapModel.targetVisuals[valueState]; + var visualTypes = VisualMapping.prepareVisualTypes(mappings); + var resultVisual = { + color: seriesModel.getData().getVisual('color') // default color. + }; + + for (var i = 0, len = visualTypes.length; i < len; i++) { + var type = visualTypes[i]; + var mapping = mappings[ + type === 'opacity' ? '__alphaForOpacity' : type + ]; + mapping && mapping.applyVisual(value, getVisual, setVisual); + } + + return resultVisual.color; + + function getVisual(key) { + return resultVisual[key]; + } + + function setVisual(key, value) { + resultVisual[key] = value; + } +} + +/* +* 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. +*/ + +/** + * @file Visual mapping. + */ + +var visualDefault = { + + /** + * @public + */ + get: function (visualType, key, isCategory) { + var value = clone( + (defaultOption$3[visualType] || {})[key] + ); + + return isCategory + ? (isArray(value) ? value[value.length - 1] : value) + : value; + } + +}; + +var defaultOption$3 = { + + color: { + active: ['#006edd', '#e0ffff'], + inactive: ['rgba(0,0,0,0)'] + }, + + colorHue: { + active: [0, 360], + inactive: [0, 0] + }, + + colorSaturation: { + active: [0.3, 1], + inactive: [0, 0] + }, + + colorLightness: { + active: [0.9, 0.5], + inactive: [0, 0] + }, + + colorAlpha: { + active: [0.3, 1], + inactive: [0, 0] + }, + + opacity: { + active: [0.3, 1], + inactive: [0, 0] + }, + + symbol: { + active: ['circle', 'roundRect', 'diamond'], + inactive: ['none'] + }, + + symbolSize: { + active: [10, 50], + inactive: [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. +*/ + +var mapVisual$2 = VisualMapping.mapVisual; +var eachVisual = VisualMapping.eachVisual; +var isArray$3 = isArray; +var each$28 = each$1; +var asc$3 = asc; +var linearMap$2 = linearMap; +var noop$2 = noop; + +var VisualMapModel = extendComponentModel({ + + type: 'visualMap', + + dependencies: ['series'], + + /** + * @readOnly + * @type {Array.} + */ + stateList: ['inRange', 'outOfRange'], + + /** + * @readOnly + * @type {Array.} + */ + replacableOptionKeys: [ + 'inRange', 'outOfRange', 'target', 'controller', 'color' + ], + + /** + * [lowerBound, upperBound] + * + * @readOnly + * @type {Array.} + */ + dataBound: [-Infinity, Infinity], + + /** + * @readOnly + * @type {string|Object} + */ + layoutMode: {type: 'box', ignoreSize: true}, + + /** + * @protected + */ + defaultOption: { + show: true, + + zlevel: 0, + z: 4, + + seriesIndex: 'all', // 'all' or null/undefined: all series. + // A number or an array of number: the specified series. + + // set min: 0, max: 200, only for campatible with ec2. + // In fact min max should not have default value. + min: 0, // min value, must specified if pieces is not specified. + max: 200, // max value, must specified if pieces is not specified. + + dimension: null, + inRange: null, // 'color', 'colorHue', 'colorSaturation', 'colorLightness', 'colorAlpha', + // 'symbol', 'symbolSize' + outOfRange: null, // 'color', 'colorHue', 'colorSaturation', + // 'colorLightness', 'colorAlpha', + // 'symbol', 'symbolSize' + + left: 0, // 'center' ¦ 'left' ¦ 'right' ¦ {number} (px) + right: null, // The same as left. + top: null, // 'top' ¦ 'bottom' ¦ 'center' ¦ {number} (px) + bottom: 0, // The same as top. + + itemWidth: null, + itemHeight: null, + inverse: false, + orient: 'vertical', // 'horizontal' ¦ 'vertical' + + backgroundColor: 'rgba(0,0,0,0)', + borderColor: '#ccc', // 值域边框颜色 + contentColor: '#5793f3', + inactiveColor: '#aaa', + borderWidth: 0, // 值域边框线宽,单位px,默认为0(无边框) + padding: 5, // 值域内边距,单位px,默认各方向内边距为5, + // 接受数组分别设定上右下左边距,同css + textGap: 10, // + precision: 0, // 小数精度,默认为0,无小数点 + color: null, //颜色(deprecated,兼容ec2,顺序同pieces,不同于inRange/outOfRange) + + formatter: null, + text: null, // 文本,如['高', '低'],兼容ec2,text[0]对应高值,text[1]对应低值 + textStyle: { + color: '#333' // 值域文字颜色 + } + }, + + /** + * @protected + */ + init: function (option, parentModel, ecModel) { + + /** + * @private + * @type {Array.} + */ + this._dataExtent; + + /** + * @readOnly + */ + this.targetVisuals = {}; + + /** + * @readOnly + */ + this.controllerVisuals = {}; + + /** + * @readOnly + */ + this.textStyleModel; + + /** + * [width, height] + * @readOnly + * @type {Array.} + */ + this.itemSize; + + this.mergeDefaultAndTheme(option, ecModel); + }, + + /** + * @protected + */ + optionUpdated: function (newOption, isInit) { + var thisOption = this.option; + + // FIXME + // necessary? + // Disable realtime view update if canvas is not supported. + if (!env$1.canvasSupported) { + thisOption.realtime = false; + } + + !isInit && replaceVisualOption( + thisOption, newOption, this.replacableOptionKeys + ); + + this.textStyleModel = this.getModel('textStyle'); + + this.resetItemSize(); + + this.completeVisualOption(); + }, + + /** + * @protected + */ + resetVisual: function (supplementVisualOption) { + var stateList = this.stateList; + supplementVisualOption = bind(supplementVisualOption, this); + + this.controllerVisuals = createVisualMappings( + this.option.controller, stateList, supplementVisualOption + ); + this.targetVisuals = createVisualMappings( + this.option.target, stateList, supplementVisualOption + ); + }, + + /** + * @protected + * @return {Array.} An array of series indices. + */ + getTargetSeriesIndices: function () { + var optionSeriesIndex = this.option.seriesIndex; + var seriesIndices = []; + + if (optionSeriesIndex == null || optionSeriesIndex === 'all') { + this.ecModel.eachSeries(function (seriesModel, index) { + seriesIndices.push(index); + }); + } + else { + seriesIndices = normalizeToArray(optionSeriesIndex); + } + + return seriesIndices; + }, + + /** + * @public + */ + eachTargetSeries: function (callback, context) { + each$1(this.getTargetSeriesIndices(), function (seriesIndex) { + callback.call(context, this.ecModel.getSeriesByIndex(seriesIndex)); + }, this); + }, + + /** + * @pubilc + */ + isTargetSeries: function (seriesModel) { + var is = false; + this.eachTargetSeries(function (model) { + model === seriesModel && (is = true); + }); + return is; + }, + + /** + * @example + * this.formatValueText(someVal); // format single numeric value to text. + * this.formatValueText(someVal, true); // format single category value to text. + * this.formatValueText([min, max]); // format numeric min-max to text. + * this.formatValueText([this.dataBound[0], max]); // using data lower bound. + * this.formatValueText([min, this.dataBound[1]]); // using data upper bound. + * + * @param {number|Array.} value Real value, or this.dataBound[0 or 1]. + * @param {boolean} [isCategory=false] Only available when value is number. + * @param {Array.} edgeSymbols Open-close symbol when value is interval. + * @return {string} + * @protected + */ + formatValueText: function (value, isCategory, edgeSymbols) { + var option = this.option; + var precision = option.precision; + var dataBound = this.dataBound; + var formatter = option.formatter; + var isMinMax; + var textValue; + edgeSymbols = edgeSymbols || ['<', '>']; + + if (isArray(value)) { + value = value.slice(); + isMinMax = true; + } + + textValue = isCategory + ? value + : (isMinMax + ? [toFixed(value[0]), toFixed(value[1])] + : toFixed(value) + ); + + if (isString(formatter)) { + return formatter + .replace('{value}', isMinMax ? textValue[0] : textValue) + .replace('{value2}', isMinMax ? textValue[1] : textValue); + } + else if (isFunction$1(formatter)) { + return isMinMax + ? formatter(value[0], value[1]) + : formatter(value); + } + + if (isMinMax) { + if (value[0] === dataBound[0]) { + return edgeSymbols[0] + ' ' + textValue[1]; + } + else if (value[1] === dataBound[1]) { + return edgeSymbols[1] + ' ' + textValue[0]; + } + else { + return textValue[0] + ' - ' + textValue[1]; + } + } + else { // Format single value (includes category case). + return textValue; + } + + function toFixed(val) { + return val === dataBound[0] + ? 'min' + : val === dataBound[1] + ? 'max' + : (+val).toFixed(Math.min(precision, 20)); + } + }, + + /** + * @protected + */ + resetExtent: function () { + var thisOption = this.option; + + // Can not calculate data extent by data here. + // Because series and data may be modified in processing stage. + // So we do not support the feature "auto min/max". + + var extent = asc$3([thisOption.min, thisOption.max]); + + this._dataExtent = extent; + }, + + /** + * @public + * @param {module:echarts/data/List} list + * @return {string} Concrete dimention. If return null/undefined, + * no dimension used. + */ + getDataDimension: function (list) { + var optDim = this.option.dimension; + var listDimensions = list.dimensions; + if (optDim == null && !listDimensions.length) { + return; + } + + if (optDim != null) { + return list.getDimension(optDim); + } + + var dimNames = list.dimensions; + for (var i = dimNames.length - 1; i >= 0; i--) { + var dimName = dimNames[i]; + var dimInfo = list.getDimensionInfo(dimName); + if (!dimInfo.isCalculationCoord) { + return dimName; + } + } + }, + + /** + * @public + * @override + */ + getExtent: function () { + return this._dataExtent.slice(); + }, + + /** + * @protected + */ + completeVisualOption: function () { + var ecModel = this.ecModel; + var thisOption = this.option; + var base = {inRange: thisOption.inRange, outOfRange: thisOption.outOfRange}; + + var target = thisOption.target || (thisOption.target = {}); + var controller = thisOption.controller || (thisOption.controller = {}); + + merge(target, base); // Do not override + merge(controller, base); // Do not override + + var isCategory = this.isCategory(); + + completeSingle.call(this, target); + completeSingle.call(this, controller); + completeInactive.call(this, target, 'inRange', 'outOfRange'); + // completeInactive.call(this, target, 'outOfRange', 'inRange'); + completeController.call(this, controller); + + function completeSingle(base) { + // Compatible with ec2 dataRange.color. + // The mapping order of dataRange.color is: [high value, ..., low value] + // whereas inRange.color and outOfRange.color is [low value, ..., high value] + // Notice: ec2 has no inverse. + if (isArray$3(thisOption.color) + // If there has been inRange: {symbol: ...}, adding color is a mistake. + // So adding color only when no inRange defined. + && !base.inRange + ) { + base.inRange = {color: thisOption.color.slice().reverse()}; + } + + // Compatible with previous logic, always give a defautl color, otherwise + // simple config with no inRange and outOfRange will not work. + // Originally we use visualMap.color as the default color, but setOption at + // the second time the default color will be erased. So we change to use + // constant DEFAULT_COLOR. + // If user do not want the defualt color, set inRange: {color: null}. + base.inRange = base.inRange || {color: ecModel.get('gradientColor')}; + + // If using shortcut like: {inRange: 'symbol'}, complete default value. + each$28(this.stateList, function (state) { + var visualType = base[state]; + + if (isString(visualType)) { + var defa = visualDefault.get(visualType, 'active', isCategory); + if (defa) { + base[state] = {}; + base[state][visualType] = defa; + } + else { + // Mark as not specified. + delete base[state]; + } + } + }, this); + } + + function completeInactive(base, stateExist, stateAbsent) { + var optExist = base[stateExist]; + var optAbsent = base[stateAbsent]; + + if (optExist && !optAbsent) { + optAbsent = base[stateAbsent] = {}; + each$28(optExist, function (visualData, visualType) { + if (!VisualMapping.isValidType(visualType)) { + return; + } + + var defa = visualDefault.get(visualType, 'inactive', isCategory); + + if (defa != null) { + optAbsent[visualType] = defa; + + // Compatibable with ec2: + // Only inactive color to rgba(0,0,0,0) can not + // make label transparent, so use opacity also. + if (visualType === 'color' + && !optAbsent.hasOwnProperty('opacity') + && !optAbsent.hasOwnProperty('colorAlpha') + ) { + optAbsent.opacity = [0, 0]; + } + } + }); + } + } + + function completeController(controller) { + var symbolExists = (controller.inRange || {}).symbol + || (controller.outOfRange || {}).symbol; + var symbolSizeExists = (controller.inRange || {}).symbolSize + || (controller.outOfRange || {}).symbolSize; + var inactiveColor = this.get('inactiveColor'); + + each$28(this.stateList, function (state) { + + var itemSize = this.itemSize; + var visuals = controller[state]; + + // Set inactive color for controller if no other color + // attr (like colorAlpha) specified. + if (!visuals) { + visuals = controller[state] = { + color: isCategory ? inactiveColor : [inactiveColor] + }; + } + + // Consistent symbol and symbolSize if not specified. + if (visuals.symbol == null) { + visuals.symbol = symbolExists + && clone(symbolExists) + || (isCategory ? 'roundRect' : ['roundRect']); + } + if (visuals.symbolSize == null) { + visuals.symbolSize = symbolSizeExists + && clone(symbolSizeExists) + || (isCategory ? itemSize[0] : [itemSize[0], itemSize[0]]); + } + + // Filter square and none. + visuals.symbol = mapVisual$2(visuals.symbol, function (symbol) { + return (symbol === 'none' || symbol === 'square') ? 'roundRect' : symbol; + }); + + // Normalize symbolSize + var symbolSize = visuals.symbolSize; + + if (symbolSize != null) { + var max = -Infinity; + // symbolSize can be object when categories defined. + eachVisual(symbolSize, function (value) { + value > max && (max = value); + }); + visuals.symbolSize = mapVisual$2(symbolSize, function (value) { + return linearMap$2(value, [0, max], [0, itemSize[0]], true); + }); + } + + }, this); + } + }, + + /** + * @protected + */ + resetItemSize: function () { + this.itemSize = [ + parseFloat(this.get('itemWidth')), + parseFloat(this.get('itemHeight')) + ]; + }, + + /** + * @public + */ + isCategory: function () { + return !!this.option.categories; + }, + + /** + * @public + * @abstract + */ + setSelected: noop$2, + + /** + * @public + * @abstract + * @param {*|module:echarts/data/List} valueOrData + * @param {number} dataIndex + * @return {string} state See this.stateList + */ + getValueState: noop$2, + + /** + * FIXME + * Do not publish to thirt-part-dev temporarily + * util the interface is stable. (Should it return + * a function but not visual meta?) + * + * @pubilc + * @abstract + * @param {Function} getColorVisual + * params: value, valueState + * return: color + * @return {Object} visualMeta + * should includes {stops, outerColors} + * outerColor means [colorBeyondMinValue, colorBeyondMaxValue] + */ + getVisualMeta: noop$2 + +}); + +/* +* 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. +*/ + +// Constant +var DEFAULT_BAR_BOUND = [20, 140]; + +var ContinuousModel = VisualMapModel.extend({ + + type: 'visualMap.continuous', + + /** + * @protected + */ + defaultOption: { + align: 'auto', // 'auto', 'left', 'right', 'top', 'bottom' + calculable: false, // This prop effect default component type determine, + // See echarts/component/visualMap/typeDefaulter. + range: null, // selected range. In default case `range` is [min, max] + // and can auto change along with modification of min max, + // util use specifid a range. + realtime: true, // Whether realtime update. + itemHeight: null, // The length of the range control edge. + itemWidth: null, // The length of the other side. + hoverLink: true, // Enable hover highlight. + hoverLinkDataSize: null, // The size of hovered data. + hoverLinkOnHandle: null // Whether trigger hoverLink when hover handle. + // If not specified, follow the value of `realtime`. + }, + + /** + * @override + */ + optionUpdated: function (newOption, isInit) { + ContinuousModel.superApply(this, 'optionUpdated', arguments); + + this.resetExtent(); + + this.resetVisual(function (mappingOption) { + mappingOption.mappingMethod = 'linear'; + mappingOption.dataExtent = this.getExtent(); + }); + + this._resetRange(); + }, + + /** + * @protected + * @override + */ + resetItemSize: function () { + ContinuousModel.superApply(this, 'resetItemSize', arguments); + + var itemSize = this.itemSize; + + this._orient === 'horizontal' && itemSize.reverse(); + + (itemSize[0] == null || isNaN(itemSize[0])) && (itemSize[0] = DEFAULT_BAR_BOUND[0]); + (itemSize[1] == null || isNaN(itemSize[1])) && (itemSize[1] = DEFAULT_BAR_BOUND[1]); + }, + + /** + * @private + */ + _resetRange: function () { + var dataExtent = this.getExtent(); + var range = this.option.range; + + if (!range || range.auto) { + // `range` should always be array (so we dont use other + // value like 'auto') for user-friend. (consider getOption). + dataExtent.auto = 1; + this.option.range = dataExtent; + } + else if (isArray(range)) { + if (range[0] > range[1]) { + range.reverse(); + } + range[0] = Math.max(range[0], dataExtent[0]); + range[1] = Math.min(range[1], dataExtent[1]); + } + }, + + /** + * @protected + * @override + */ + completeVisualOption: function () { + VisualMapModel.prototype.completeVisualOption.apply(this, arguments); + + each$1(this.stateList, function (state) { + var symbolSize = this.option.controller[state].symbolSize; + if (symbolSize && symbolSize[0] !== symbolSize[1]) { + symbolSize[0] = 0; // For good looking. + } + }, this); + }, + + /** + * @override + */ + setSelected: function (selected) { + this.option.range = selected.slice(); + this._resetRange(); + }, + + /** + * @public + */ + getSelected: function () { + var dataExtent = this.getExtent(); + + var dataInterval = asc( + (this.get('range') || []).slice() + ); + + // Clamp + dataInterval[0] > dataExtent[1] && (dataInterval[0] = dataExtent[1]); + dataInterval[1] > dataExtent[1] && (dataInterval[1] = dataExtent[1]); + dataInterval[0] < dataExtent[0] && (dataInterval[0] = dataExtent[0]); + dataInterval[1] < dataExtent[0] && (dataInterval[1] = dataExtent[0]); + + return dataInterval; + }, + + /** + * @override + */ + getValueState: function (value) { + var range = this.option.range; + var dataExtent = this.getExtent(); + + // When range[0] === dataExtent[0], any value larger than dataExtent[0] maps to 'inRange'. + // range[1] is processed likewise. + return ( + (range[0] <= dataExtent[0] || range[0] <= value) + && (range[1] >= dataExtent[1] || value <= range[1]) + ) ? 'inRange' : 'outOfRange'; + }, + + /** + * @params {Array.} range target value: range[0] <= value && value <= range[1] + * @return {Array.} [{seriesId, dataIndices: >}, ...] + */ + findTargetDataIndices: function (range) { + var result = []; + + this.eachTargetSeries(function (seriesModel) { + var dataIndices = []; + var data = seriesModel.getData(); + + data.each(this.getDataDimension(data), function (value, dataIndex) { + range[0] <= value && value <= range[1] && dataIndices.push(dataIndex); + }, this); + + result.push({seriesId: seriesModel.id, dataIndex: dataIndices}); + }, this); + + return result; + }, + + /** + * @implement + */ + getVisualMeta: function (getColorVisual) { + var oVals = getColorStopValues(this, 'outOfRange', this.getExtent()); + var iVals = getColorStopValues(this, 'inRange', this.option.range.slice()); + var stops = []; + + function setStop(value, valueState) { + stops.push({ + value: value, + color: getColorVisual(value, valueState) + }); + } + + // Format to: outOfRange -- inRange -- outOfRange. + var iIdx = 0; + var oIdx = 0; + var iLen = iVals.length; + var oLen = oVals.length; + + for (; oIdx < oLen && (!iVals.length || oVals[oIdx] <= iVals[0]); oIdx++) { + // If oVal[oIdx] === iVals[iIdx], oVal[oIdx] should be ignored. + if (oVals[oIdx] < iVals[iIdx]) { + setStop(oVals[oIdx], 'outOfRange'); + } + } + for (var first = 1; iIdx < iLen; iIdx++, first = 0) { + // If range is full, value beyond min, max will be clamped. + // make a singularity + first && stops.length && setStop(iVals[iIdx], 'outOfRange'); + setStop(iVals[iIdx], 'inRange'); + } + for (var first = 1; oIdx < oLen; oIdx++) { + if (!iVals.length || iVals[iVals.length - 1] < oVals[oIdx]) { + // make a singularity + if (first) { + stops.length && setStop(stops[stops.length - 1].value, 'outOfRange'); + first = 0; + } + setStop(oVals[oIdx], 'outOfRange'); + } + } + + var stopsLen = stops.length; + + return { + stops: stops, + outerColors: [ + stopsLen ? stops[0].color : 'transparent', + stopsLen ? stops[stopsLen - 1].color : 'transparent' + ] + }; + } + +}); + +function getColorStopValues(visualMapModel, valueState, dataExtent) { + if (dataExtent[0] === dataExtent[1]) { + return dataExtent.slice(); + } + + // When using colorHue mapping, it is not linear color any more. + // Moreover, canvas gradient seems not to be accurate linear. + // FIXME + // Should be arbitrary value 100? or based on pixel size? + var count = 200; + var step = (dataExtent[1] - dataExtent[0]) / count; + + var value = dataExtent[0]; + var stopValues = []; + for (var i = 0; i <= count && value < dataExtent[1]; i++) { + stopValues.push(value); + value += step; + } + stopValues.push(dataExtent[1]); + + return stopValues; +} + +/* +* 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. +*/ + +var VisualMapView = extendComponentView({ + + type: 'visualMap', + + /** + * @readOnly + * @type {Object} + */ + autoPositionValues: {left: 1, right: 1, top: 1, bottom: 1}, + + init: function (ecModel, api) { + /** + * @readOnly + * @type {module:echarts/model/Global} + */ + this.ecModel = ecModel; + + /** + * @readOnly + * @type {module:echarts/ExtensionAPI} + */ + this.api = api; + + /** + * @readOnly + * @type {module:echarts/component/visualMap/visualMapModel} + */ + this.visualMapModel; + }, + + /** + * @protected + */ + render: function (visualMapModel, ecModel, api, payload) { + this.visualMapModel = visualMapModel; + + if (visualMapModel.get('show') === false) { + this.group.removeAll(); + return; + } + + this.doRender.apply(this, arguments); + }, + + /** + * @protected + */ + renderBackground: function (group) { + var visualMapModel = this.visualMapModel; + var padding = normalizeCssArray$1(visualMapModel.get('padding') || 0); + var rect = group.getBoundingRect(); + + group.add(new Rect({ + z2: -1, // Lay background rect on the lowest layer. + silent: true, + shape: { + x: rect.x - padding[3], + y: rect.y - padding[0], + width: rect.width + padding[3] + padding[1], + height: rect.height + padding[0] + padding[2] + }, + style: { + fill: visualMapModel.get('backgroundColor'), + stroke: visualMapModel.get('borderColor'), + lineWidth: visualMapModel.get('borderWidth') + } + })); + }, + + /** + * @protected + * @param {number} targetValue can be Infinity or -Infinity + * @param {string=} visualCluster Only can be 'color' 'opacity' 'symbol' 'symbolSize' + * @param {Object} [opts] + * @param {string=} [opts.forceState] Specify state, instead of using getValueState method. + * @param {string=} [opts.convertOpacityToAlpha=false] For color gradient in controller widget. + * @return {*} Visual value. + */ + getControllerVisual: function (targetValue, visualCluster, opts) { + opts = opts || {}; + + var forceState = opts.forceState; + var visualMapModel = this.visualMapModel; + var visualObj = {}; + + // Default values. + if (visualCluster === 'symbol') { + visualObj.symbol = visualMapModel.get('itemSymbol'); + } + if (visualCluster === 'color') { + var defaultColor = visualMapModel.get('contentColor'); + visualObj.color = defaultColor; + } + + function getter(key) { + return visualObj[key]; + } + + function setter(key, value) { + visualObj[key] = value; + } + + var mappings = visualMapModel.controllerVisuals[ + forceState || visualMapModel.getValueState(targetValue) + ]; + var visualTypes = VisualMapping.prepareVisualTypes(mappings); + + each$1(visualTypes, function (type) { + var visualMapping = mappings[type]; + if (opts.convertOpacityToAlpha && type === 'opacity') { + type = 'colorAlpha'; + visualMapping = mappings.__alphaForOpacity; + } + if (VisualMapping.dependsOn(type, visualCluster)) { + visualMapping && visualMapping.applyVisual( + targetValue, getter, setter + ); + } + }); + + return visualObj[visualCluster]; + }, + + /** + * @protected + */ + positionGroup: function (group) { + var model = this.visualMapModel; + var api = this.api; + + positionElement( + group, + model.getBoxLayoutParams(), + {width: api.getWidth(), height: api.getHeight()} + ); + }, + + /** + * @protected + * @abstract + */ + doRender: noop + +}); + +/* +* 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. +*/ + +/** + * @param {module:echarts/component/visualMap/VisualMapModel} visualMapModel\ + * @param {module:echarts/ExtensionAPI} api + * @param {Array.} itemSize always [short, long] + * @return {string} 'left' or 'right' or 'top' or 'bottom' + */ +function getItemAlign(visualMapModel, api, itemSize) { + var modelOption = visualMapModel.option; + var itemAlign = modelOption.align; + + if (itemAlign != null && itemAlign !== 'auto') { + return itemAlign; + } + + // Auto decision align. + var ecSize = {width: api.getWidth(), height: api.getHeight()}; + var realIndex = modelOption.orient === 'horizontal' ? 1 : 0; + + var paramsSet = [ + ['left', 'right', 'width'], + ['top', 'bottom', 'height'] + ]; + var reals = paramsSet[realIndex]; + var fakeValue = [0, null, 10]; + + var layoutInput = {}; + for (var i = 0; i < 3; i++) { + layoutInput[paramsSet[1 - realIndex][i]] = fakeValue[i]; + layoutInput[reals[i]] = i === 2 ? itemSize[0] : modelOption[reals[i]]; + } + + var rParam = [['x', 'width', 3], ['y', 'height', 0]][realIndex]; + var rect = getLayoutRect(layoutInput, ecSize, modelOption.padding); + + return reals[ + (rect.margin[rParam[2]] || 0) + rect[rParam[0]] + rect[rParam[1]] * 0.5 + < ecSize[rParam[1]] * 0.5 ? 0 : 1 + ]; +} + +/** + * Prepare dataIndex for outside usage, where dataIndex means rawIndex, and + * dataIndexInside means filtered index. + */ +function makeHighDownBatch(batch, visualMapModel) { + each$1(batch || [], function (batchItem) { + if (batchItem.dataIndex != null) { + batchItem.dataIndexInside = batchItem.dataIndex; + batchItem.dataIndex = null; + } + batchItem.highlightKey = 'visualMap' + (visualMapModel ? visualMapModel.componentIndex : ''); + }); + return batch; +} + +/* +* 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. +*/ + +var linearMap$3 = linearMap; +var each$29 = each$1; +var mathMin$8 = Math.min; +var mathMax$8 = Math.max; + +// Arbitrary value +var HOVER_LINK_SIZE = 12; +var HOVER_LINK_OUT = 6; + +// Notice: +// Any "interval" should be by the order of [low, high]. +// "handle0" (handleIndex === 0) maps to +// low data value: this._dataInterval[0] and has low coord. +// "handle1" (handleIndex === 1) maps to +// high data value: this._dataInterval[1] and has high coord. +// The logic of transform is implemented in this._createBarGroup. + +var ContinuousView = VisualMapView.extend({ + + type: 'visualMap.continuous', + + /** + * @override + */ + init: function () { + + ContinuousView.superApply(this, 'init', arguments); + + /** + * @private + */ + this._shapes = {}; + + /** + * @private + */ + this._dataInterval = []; + + /** + * @private + */ + this._handleEnds = []; + + /** + * @private + */ + this._orient; + + /** + * @private + */ + this._useHandle; + + /** + * @private + */ + this._hoverLinkDataIndices = []; + + /** + * @private + */ + this._dragging; + + /** + * @private + */ + this._hovering; + }, + + /** + * @protected + * @override + */ + doRender: function (visualMapModel, ecModel, api, payload) { + if (!payload || payload.type !== 'selectDataRange' || payload.from !== this.uid) { + this._buildView(); + } + }, + + /** + * @private + */ + _buildView: function () { + this.group.removeAll(); + + var visualMapModel = this.visualMapModel; + var thisGroup = this.group; + + this._orient = visualMapModel.get('orient'); + this._useHandle = visualMapModel.get('calculable'); + + this._resetInterval(); + + this._renderBar(thisGroup); + + var dataRangeText = visualMapModel.get('text'); + this._renderEndsText(thisGroup, dataRangeText, 0); + this._renderEndsText(thisGroup, dataRangeText, 1); + + // Do this for background size calculation. + this._updateView(true); + + // After updating view, inner shapes is built completely, + // and then background can be rendered. + this.renderBackground(thisGroup); + + // Real update view + this._updateView(); + + this._enableHoverLinkToSeries(); + this._enableHoverLinkFromSeries(); + + this.positionGroup(thisGroup); + }, + + /** + * @private + */ + _renderEndsText: function (group, dataRangeText, endsIndex) { + if (!dataRangeText) { + return; + } + + // Compatible with ec2, text[0] map to high value, text[1] map low value. + var text = dataRangeText[1 - endsIndex]; + text = text != null ? text + '' : ''; + + var visualMapModel = this.visualMapModel; + var textGap = visualMapModel.get('textGap'); + var itemSize = visualMapModel.itemSize; + + var barGroup = this._shapes.barGroup; + var position = this._applyTransform( + [ + itemSize[0] / 2, + endsIndex === 0 ? -textGap : itemSize[1] + textGap + ], + barGroup + ); + var align = this._applyTransform( + endsIndex === 0 ? 'bottom' : 'top', + barGroup + ); + var orient = this._orient; + var textStyleModel = this.visualMapModel.textStyleModel; + + this.group.add(new Text({ + style: { + x: position[0], + y: position[1], + textVerticalAlign: orient === 'horizontal' ? 'middle' : align, + textAlign: orient === 'horizontal' ? align : 'center', + text: text, + textFont: textStyleModel.getFont(), + textFill: textStyleModel.getTextColor() + } + })); + }, + + /** + * @private + */ + _renderBar: function (targetGroup) { + var visualMapModel = this.visualMapModel; + var shapes = this._shapes; + var itemSize = visualMapModel.itemSize; + var orient = this._orient; + var useHandle = this._useHandle; + var itemAlign = getItemAlign(visualMapModel, this.api, itemSize); + var barGroup = shapes.barGroup = this._createBarGroup(itemAlign); + + // Bar + barGroup.add(shapes.outOfRange = createPolygon()); + barGroup.add(shapes.inRange = createPolygon( + null, + useHandle ? getCursor$1(this._orient) : null, + bind(this._dragHandle, this, 'all', false), + bind(this._dragHandle, this, 'all', true) + )); + + var textRect = visualMapModel.textStyleModel.getTextRect('国'); + var textSize = mathMax$8(textRect.width, textRect.height); + + // Handle + if (useHandle) { + shapes.handleThumbs = []; + shapes.handleLabels = []; + shapes.handleLabelPoints = []; + + this._createHandle(barGroup, 0, itemSize, textSize, orient, itemAlign); + this._createHandle(barGroup, 1, itemSize, textSize, orient, itemAlign); + } + + this._createIndicator(barGroup, itemSize, textSize, orient); + + targetGroup.add(barGroup); + }, + + /** + * @private + */ + _createHandle: function (barGroup, handleIndex, itemSize, textSize, orient) { + var onDrift = bind(this._dragHandle, this, handleIndex, false); + var onDragEnd = bind(this._dragHandle, this, handleIndex, true); + var handleThumb = createPolygon( + createHandlePoints(handleIndex, textSize), + getCursor$1(this._orient), + onDrift, + onDragEnd + ); + handleThumb.position[0] = itemSize[0]; + barGroup.add(handleThumb); + + // Text is always horizontal layout but should not be effected by + // transform (orient/inverse). So label is built separately but not + // use zrender/graphic/helper/RectText, and is located based on view + // group (according to handleLabelPoint) but not barGroup. + var textStyleModel = this.visualMapModel.textStyleModel; + var handleLabel = new Text({ + draggable: true, + drift: onDrift, + onmousemove: function (e) { + // Fot mobile devicem, prevent screen slider on the button. + stop(e.event); + }, + ondragend: onDragEnd, + style: { + x: 0, y: 0, text: '', + textFont: textStyleModel.getFont(), + textFill: textStyleModel.getTextColor() + } + }); + this.group.add(handleLabel); + + var handleLabelPoint = [ + orient === 'horizontal' + ? textSize / 2 + : textSize * 1.5, + orient === 'horizontal' + ? (handleIndex === 0 ? -(textSize * 1.5) : (textSize * 1.5)) + : (handleIndex === 0 ? -textSize / 2 : textSize / 2) + ]; + + var shapes = this._shapes; + shapes.handleThumbs[handleIndex] = handleThumb; + shapes.handleLabelPoints[handleIndex] = handleLabelPoint; + shapes.handleLabels[handleIndex] = handleLabel; + }, + + /** + * @private + */ + _createIndicator: function (barGroup, itemSize, textSize, orient) { + var indicator = createPolygon([[0, 0]], 'move'); + indicator.position[0] = itemSize[0]; + indicator.attr({invisible: true, silent: true}); + barGroup.add(indicator); + + var textStyleModel = this.visualMapModel.textStyleModel; + var indicatorLabel = new Text({ + silent: true, + invisible: true, + style: { + x: 0, y: 0, text: '', + textFont: textStyleModel.getFont(), + textFill: textStyleModel.getTextColor() + } + }); + this.group.add(indicatorLabel); + + var indicatorLabelPoint = [ + orient === 'horizontal' ? textSize / 2 : HOVER_LINK_OUT + 3, + 0 + ]; + + var shapes = this._shapes; + shapes.indicator = indicator; + shapes.indicatorLabel = indicatorLabel; + shapes.indicatorLabelPoint = indicatorLabelPoint; + }, + + /** + * @private + */ + _dragHandle: function (handleIndex, isEnd, dx, dy) { + if (!this._useHandle) { + return; + } + + this._dragging = !isEnd; + + if (!isEnd) { + // Transform dx, dy to bar coordination. + var vertex = this._applyTransform([dx, dy], this._shapes.barGroup, true); + this._updateInterval(handleIndex, vertex[1]); + + // Considering realtime, update view should be executed + // before dispatch action. + this._updateView(); + } + + // dragEnd do not dispatch action when realtime. + if (isEnd === !this.visualMapModel.get('realtime')) { // jshint ignore:line + this.api.dispatchAction({ + type: 'selectDataRange', + from: this.uid, + visualMapId: this.visualMapModel.id, + selected: this._dataInterval.slice() + }); + } + + if (isEnd) { + !this._hovering && this._clearHoverLinkToSeries(); + } + else if (useHoverLinkOnHandle(this.visualMapModel)) { + this._doHoverLinkToSeries(this._handleEnds[handleIndex], false); + } + }, + + /** + * @private + */ + _resetInterval: function () { + var visualMapModel = this.visualMapModel; + + var dataInterval = this._dataInterval = visualMapModel.getSelected(); + var dataExtent = visualMapModel.getExtent(); + var sizeExtent = [0, visualMapModel.itemSize[1]]; + + this._handleEnds = [ + linearMap$3(dataInterval[0], dataExtent, sizeExtent, true), + linearMap$3(dataInterval[1], dataExtent, sizeExtent, true) + ]; + }, + + /** + * @private + * @param {(number|string)} handleIndex 0 or 1 or 'all' + * @param {number} dx + * @param {number} dy + */ + _updateInterval: function (handleIndex, delta) { + delta = delta || 0; + var visualMapModel = this.visualMapModel; + var handleEnds = this._handleEnds; + var sizeExtent = [0, visualMapModel.itemSize[1]]; + + sliderMove( + delta, + handleEnds, + sizeExtent, + handleIndex, + // cross is forbiden + 0 + ); + + var dataExtent = visualMapModel.getExtent(); + // Update data interval. + this._dataInterval = [ + linearMap$3(handleEnds[0], sizeExtent, dataExtent, true), + linearMap$3(handleEnds[1], sizeExtent, dataExtent, true) + ]; + }, + + /** + * @private + */ + _updateView: function (forSketch) { + var visualMapModel = this.visualMapModel; + var dataExtent = visualMapModel.getExtent(); + var shapes = this._shapes; + + var outOfRangeHandleEnds = [0, visualMapModel.itemSize[1]]; + var inRangeHandleEnds = forSketch ? outOfRangeHandleEnds : this._handleEnds; + + var visualInRange = this._createBarVisual( + this._dataInterval, dataExtent, inRangeHandleEnds, 'inRange' + ); + var visualOutOfRange = this._createBarVisual( + dataExtent, dataExtent, outOfRangeHandleEnds, 'outOfRange' + ); + + shapes.inRange + .setStyle({ + fill: visualInRange.barColor, + opacity: visualInRange.opacity + }) + .setShape('points', visualInRange.barPoints); + shapes.outOfRange + .setStyle({ + fill: visualOutOfRange.barColor, + opacity: visualOutOfRange.opacity + }) + .setShape('points', visualOutOfRange.barPoints); + + this._updateHandle(inRangeHandleEnds, visualInRange); + }, + + /** + * @private + */ + _createBarVisual: function (dataInterval, dataExtent, handleEnds, forceState) { + var opts = { + forceState: forceState, + convertOpacityToAlpha: true + }; + var colorStops = this._makeColorGradient(dataInterval, opts); + + var symbolSizes = [ + this.getControllerVisual(dataInterval[0], 'symbolSize', opts), + this.getControllerVisual(dataInterval[1], 'symbolSize', opts) + ]; + var barPoints = this._createBarPoints(handleEnds, symbolSizes); + + return { + barColor: new LinearGradient(0, 0, 0, 1, colorStops), + barPoints: barPoints, + handlesColor: [ + colorStops[0].color, + colorStops[colorStops.length - 1].color + ] + }; + }, + + /** + * @private + */ + _makeColorGradient: function (dataInterval, opts) { + // Considering colorHue, which is not linear, so we have to sample + // to calculate gradient color stops, but not only caculate head + // and tail. + var sampleNumber = 100; // Arbitrary value. + var colorStops = []; + var step = (dataInterval[1] - dataInterval[0]) / sampleNumber; + + colorStops.push({ + color: this.getControllerVisual(dataInterval[0], 'color', opts), + offset: 0 + }); + + for (var i = 1; i < sampleNumber; i++) { + var currValue = dataInterval[0] + step * i; + if (currValue > dataInterval[1]) { + break; + } + colorStops.push({ + color: this.getControllerVisual(currValue, 'color', opts), + offset: i / sampleNumber + }); + } + + colorStops.push({ + color: this.getControllerVisual(dataInterval[1], 'color', opts), + offset: 1 + }); + + return colorStops; + }, + + /** + * @private + */ + _createBarPoints: function (handleEnds, symbolSizes) { + var itemSize = this.visualMapModel.itemSize; + + return [ + [itemSize[0] - symbolSizes[0], handleEnds[0]], + [itemSize[0], handleEnds[0]], + [itemSize[0], handleEnds[1]], + [itemSize[0] - symbolSizes[1], handleEnds[1]] + ]; + }, + + /** + * @private + */ + _createBarGroup: function (itemAlign) { + var orient = this._orient; + var inverse = this.visualMapModel.get('inverse'); + + return new Group( + (orient === 'horizontal' && !inverse) + ? {scale: itemAlign === 'bottom' ? [1, 1] : [-1, 1], rotation: Math.PI / 2} + : (orient === 'horizontal' && inverse) + ? {scale: itemAlign === 'bottom' ? [-1, 1] : [1, 1], rotation: -Math.PI / 2} + : (orient === 'vertical' && !inverse) + ? {scale: itemAlign === 'left' ? [1, -1] : [-1, -1]} + : {scale: itemAlign === 'left' ? [1, 1] : [-1, 1]} + ); + }, + + /** + * @private + */ + _updateHandle: function (handleEnds, visualInRange) { + if (!this._useHandle) { + return; + } + + var shapes = this._shapes; + var visualMapModel = this.visualMapModel; + var handleThumbs = shapes.handleThumbs; + var handleLabels = shapes.handleLabels; + + each$29([0, 1], function (handleIndex) { + var handleThumb = handleThumbs[handleIndex]; + handleThumb.setStyle('fill', visualInRange.handlesColor[handleIndex]); + handleThumb.position[1] = handleEnds[handleIndex]; + + // Update handle label position. + var textPoint = applyTransform$1( + shapes.handleLabelPoints[handleIndex], + getTransform(handleThumb, this.group) + ); + handleLabels[handleIndex].setStyle({ + x: textPoint[0], + y: textPoint[1], + text: visualMapModel.formatValueText(this._dataInterval[handleIndex]), + textVerticalAlign: 'middle', + textAlign: this._applyTransform( + this._orient === 'horizontal' + ? (handleIndex === 0 ? 'bottom' : 'top') + : 'left', + shapes.barGroup + ) + }); + }, this); + }, + + /** + * @private + * @param {number} cursorValue + * @param {number} textValue + * @param {string} [rangeSymbol] + * @param {number} [halfHoverLinkSize] + */ + _showIndicator: function (cursorValue, textValue, rangeSymbol, halfHoverLinkSize) { + var visualMapModel = this.visualMapModel; + var dataExtent = visualMapModel.getExtent(); + var itemSize = visualMapModel.itemSize; + var sizeExtent = [0, itemSize[1]]; + var pos = linearMap$3(cursorValue, dataExtent, sizeExtent, true); + + var shapes = this._shapes; + var indicator = shapes.indicator; + if (!indicator) { + return; + } + + indicator.position[1] = pos; + indicator.attr('invisible', false); + indicator.setShape('points', createIndicatorPoints( + !!rangeSymbol, halfHoverLinkSize, pos, itemSize[1] + )); + + var opts = {convertOpacityToAlpha: true}; + var color = this.getControllerVisual(cursorValue, 'color', opts); + indicator.setStyle('fill', color); + + // Update handle label position. + var textPoint = applyTransform$1( + shapes.indicatorLabelPoint, + getTransform(indicator, this.group) + ); + + var indicatorLabel = shapes.indicatorLabel; + indicatorLabel.attr('invisible', false); + var align = this._applyTransform('left', shapes.barGroup); + var orient = this._orient; + indicatorLabel.setStyle({ + text: (rangeSymbol ? rangeSymbol : '') + visualMapModel.formatValueText(textValue), + textVerticalAlign: orient === 'horizontal' ? align : 'middle', + textAlign: orient === 'horizontal' ? 'center' : align, + x: textPoint[0], + y: textPoint[1] + }); + }, + + /** + * @private + */ + _enableHoverLinkToSeries: function () { + var self = this; + this._shapes.barGroup + + .on('mousemove', function (e) { + self._hovering = true; + + if (!self._dragging) { + var itemSize = self.visualMapModel.itemSize; + var pos = self._applyTransform( + [e.offsetX, e.offsetY], self._shapes.barGroup, true, true + ); + // For hover link show when hover handle, which might be + // below or upper than sizeExtent. + pos[1] = mathMin$8(mathMax$8(0, pos[1]), itemSize[1]); + self._doHoverLinkToSeries( + pos[1], + 0 <= pos[0] && pos[0] <= itemSize[0] + ); + } + }) + + .on('mouseout', function () { + // When mouse is out of handle, hoverLink still need + // to be displayed when realtime is set as false. + self._hovering = false; + !self._dragging && self._clearHoverLinkToSeries(); + }); + }, + + /** + * @private + */ + _enableHoverLinkFromSeries: function () { + var zr = this.api.getZr(); + + if (this.visualMapModel.option.hoverLink) { + zr.on('mouseover', this._hoverLinkFromSeriesMouseOver, this); + zr.on('mouseout', this._hideIndicator, this); + } + else { + this._clearHoverLinkFromSeries(); + } + }, + + /** + * @private + */ + _doHoverLinkToSeries: function (cursorPos, hoverOnBar) { + var visualMapModel = this.visualMapModel; + var itemSize = visualMapModel.itemSize; + + if (!visualMapModel.option.hoverLink) { + return; + } + + var sizeExtent = [0, itemSize[1]]; + var dataExtent = visualMapModel.getExtent(); + + // For hover link show when hover handle, which might be below or upper than sizeExtent. + cursorPos = mathMin$8(mathMax$8(sizeExtent[0], cursorPos), sizeExtent[1]); + + var halfHoverLinkSize = getHalfHoverLinkSize(visualMapModel, dataExtent, sizeExtent); + var hoverRange = [cursorPos - halfHoverLinkSize, cursorPos + halfHoverLinkSize]; + var cursorValue = linearMap$3(cursorPos, sizeExtent, dataExtent, true); + var valueRange = [ + linearMap$3(hoverRange[0], sizeExtent, dataExtent, true), + linearMap$3(hoverRange[1], sizeExtent, dataExtent, true) + ]; + // Consider data range is out of visualMap range, see test/visualMap-continuous.html, + // where china and india has very large population. + hoverRange[0] < sizeExtent[0] && (valueRange[0] = -Infinity); + hoverRange[1] > sizeExtent[1] && (valueRange[1] = Infinity); + + // Do not show indicator when mouse is over handle, + // otherwise labels overlap, especially when dragging. + if (hoverOnBar) { + if (valueRange[0] === -Infinity) { + this._showIndicator(cursorValue, valueRange[1], '< ', halfHoverLinkSize); + } + else if (valueRange[1] === Infinity) { + this._showIndicator(cursorValue, valueRange[0], '> ', halfHoverLinkSize); + } + else { + this._showIndicator(cursorValue, cursorValue, '≈ ', halfHoverLinkSize); + } + } + + // When realtime is set as false, handles, which are in barGroup, + // also trigger hoverLink, which help user to realize where they + // focus on when dragging. (see test/heatmap-large.html) + // When realtime is set as true, highlight will not show when hover + // handle, because the label on handle, which displays a exact value + // but not range, might mislead users. + var oldBatch = this._hoverLinkDataIndices; + var newBatch = []; + if (hoverOnBar || useHoverLinkOnHandle(visualMapModel)) { + newBatch = this._hoverLinkDataIndices = visualMapModel.findTargetDataIndices(valueRange); + } + + var resultBatches = compressBatches(oldBatch, newBatch); + + this._dispatchHighDown('downplay', makeHighDownBatch(resultBatches[0], visualMapModel)); + this._dispatchHighDown('highlight', makeHighDownBatch(resultBatches[1], visualMapModel)); + }, + + /** + * @private + */ + _hoverLinkFromSeriesMouseOver: function (e) { + var el = e.target; + var visualMapModel = this.visualMapModel; + + if (!el || el.dataIndex == null) { + return; + } + + var dataModel = this.ecModel.getSeriesByIndex(el.seriesIndex); + + if (!visualMapModel.isTargetSeries(dataModel)) { + return; + } + + var data = dataModel.getData(el.dataType); + var value = data.get(visualMapModel.getDataDimension(data), el.dataIndex, true); + + if (!isNaN(value)) { + this._showIndicator(value, value); + } + }, + + /** + * @private + */ + _hideIndicator: function () { + var shapes = this._shapes; + shapes.indicator && shapes.indicator.attr('invisible', true); + shapes.indicatorLabel && shapes.indicatorLabel.attr('invisible', true); + }, + + /** + * @private + */ + _clearHoverLinkToSeries: function () { + this._hideIndicator(); + + var indices = this._hoverLinkDataIndices; + this._dispatchHighDown('downplay', makeHighDownBatch(indices, this.visualMapModel)); + + indices.length = 0; + }, + + /** + * @private + */ + _clearHoverLinkFromSeries: function () { + this._hideIndicator(); + + var zr = this.api.getZr(); + zr.off('mouseover', this._hoverLinkFromSeriesMouseOver); + zr.off('mouseout', this._hideIndicator); + }, + + /** + * @private + */ + _applyTransform: function (vertex, element, inverse, global) { + var transform = getTransform(element, global ? null : this.group); + + return graphic[ + isArray(vertex) ? 'applyTransform' : 'transformDirection' + ](vertex, transform, inverse); + }, + + /** + * @private + */ + _dispatchHighDown: function (type, batch) { + batch && batch.length && this.api.dispatchAction({ + type: type, + batch: batch + }); + }, + + /** + * @override + */ + dispose: function () { + this._clearHoverLinkFromSeries(); + this._clearHoverLinkToSeries(); + }, + + /** + * @override + */ + remove: function () { + this._clearHoverLinkFromSeries(); + this._clearHoverLinkToSeries(); + } + +}); + +function createPolygon(points, cursor, onDrift, onDragEnd) { + return new Polygon({ + shape: {points: points}, + draggable: !!onDrift, + cursor: cursor, + drift: onDrift, + onmousemove: function (e) { + // Fot mobile devicem, prevent screen slider on the button. + stop(e.event); + }, + ondragend: onDragEnd + }); +} + +function createHandlePoints(handleIndex, textSize) { + return handleIndex === 0 + ? [[0, 0], [textSize, 0], [textSize, -textSize]] + : [[0, 0], [textSize, 0], [textSize, textSize]]; +} + +function createIndicatorPoints(isRange, halfHoverLinkSize, pos, extentMax) { + return isRange + ? [ // indicate range + [0, -mathMin$8(halfHoverLinkSize, mathMax$8(pos, 0))], + [HOVER_LINK_OUT, 0], + [0, mathMin$8(halfHoverLinkSize, mathMax$8(extentMax - pos, 0))] + ] + : [ // indicate single value + [0, 0], [5, -5], [5, 5] + ]; +} + +function getHalfHoverLinkSize(visualMapModel, dataExtent, sizeExtent) { + var halfHoverLinkSize = HOVER_LINK_SIZE / 2; + var hoverLinkDataSize = visualMapModel.get('hoverLinkDataSize'); + if (hoverLinkDataSize) { + halfHoverLinkSize = linearMap$3(hoverLinkDataSize, dataExtent, sizeExtent, true) / 2; + } + return halfHoverLinkSize; +} + +function useHoverLinkOnHandle(visualMapModel) { + var hoverLinkOnHandle = visualMapModel.get('hoverLinkOnHandle'); + return !!(hoverLinkOnHandle == null ? visualMapModel.get('realtime') : hoverLinkOnHandle); +} + +function getCursor$1(orient) { + return orient === 'vertical' ? 'ns-resize' : 'ew-resize'; +} + +/* +* 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. +*/ + +var actionInfo$2 = { + type: 'selectDataRange', + event: 'dataRangeSelected', + // FIXME use updateView appears wrong + update: 'update' +}; + +registerAction(actionInfo$2, function (payload, ecModel) { + + ecModel.eachComponent({mainType: 'visualMap', query: payload}, function (model) { + model.setSelected(payload.selected); + }); + +}); + +/* +* 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. +*/ + +/** + * DataZoom component entry + */ + +registerPreprocessor(preprocessor$3); + +/* +* 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. +*/ + +var PiecewiseModel = VisualMapModel.extend({ + + type: 'visualMap.piecewise', + + /** + * Order Rule: + * + * option.categories / option.pieces / option.text / option.selected: + * If !option.inverse, + * Order when vertical: ['top', ..., 'bottom']. + * Order when horizontal: ['left', ..., 'right']. + * If option.inverse, the meaning of + * the order should be reversed. + * + * this._pieceList: + * The order is always [low, ..., high]. + * + * Mapping from location to low-high: + * If !option.inverse + * When vertical, top is high. + * When horizontal, right is high. + * If option.inverse, reverse. + */ + + /** + * @protected + */ + defaultOption: { + selected: null, // Object. If not specified, means selected. + // When pieces and splitNumber: {'0': true, '5': true} + // When categories: {'cate1': false, 'cate3': true} + // When selected === false, means all unselected. + + minOpen: false, // Whether include values that smaller than `min`. + maxOpen: false, // Whether include values that bigger than `max`. + + align: 'auto', // 'auto', 'left', 'right' + itemWidth: 20, // When put the controller vertically, it is the length of + // horizontal side of each item. Otherwise, vertical side. + itemHeight: 14, // When put the controller vertically, it is the length of + // vertical side of each item. Otherwise, horizontal side. + itemSymbol: 'roundRect', + pieceList: null, // Each item is Object, with some of those attrs: + // {min, max, lt, gt, lte, gte, value, + // color, colorSaturation, colorAlpha, opacity, + // symbol, symbolSize}, which customize the range or visual + // coding of the certain piece. Besides, see "Order Rule". + categories: null, // category names, like: ['some1', 'some2', 'some3']. + // Attr min/max are ignored when categories set. See "Order Rule" + splitNumber: 5, // If set to 5, auto split five pieces equally. + // If set to 0 and component type not set, component type will be + // determined as "continuous". (It is less reasonable but for ec2 + // compatibility, see echarts/component/visualMap/typeDefaulter) + selectedMode: 'multiple', // Can be 'multiple' or 'single'. + itemGap: 10, // The gap between two items, in px. + hoverLink: true, // Enable hover highlight. + + showLabel: null // By default, when text is used, label will hide (the logic + // is remained for compatibility reason) + }, + + /** + * @override + */ + optionUpdated: function (newOption, isInit) { + PiecewiseModel.superApply(this, 'optionUpdated', arguments); + + /** + * The order is always [low, ..., high]. + * [{text: string, interval: Array.}, ...] + * @private + * @type {Array.} + */ + this._pieceList = []; + + this.resetExtent(); + + /** + * 'pieces', 'categories', 'splitNumber' + * @type {string} + */ + var mode = this._mode = this._determineMode(); + + resetMethods[this._mode].call(this); + + this._resetSelected(newOption, isInit); + + var categories = this.option.categories; + + this.resetVisual(function (mappingOption, state) { + if (mode === 'categories') { + mappingOption.mappingMethod = 'category'; + mappingOption.categories = clone(categories); + } + else { + mappingOption.dataExtent = this.getExtent(); + mappingOption.mappingMethod = 'piecewise'; + mappingOption.pieceList = map(this._pieceList, function (piece) { + var piece = clone(piece); + if (state !== 'inRange') { + // FIXME + // outOfRange do not support special visual in pieces. + piece.visual = null; + } + return piece; + }); + } + }); + }, + + /** + * @protected + * @override + */ + completeVisualOption: function () { + // Consider this case: + // visualMap: { + // pieces: [{symbol: 'circle', lt: 0}, {symbol: 'rect', gte: 0}] + // } + // where no inRange/outOfRange set but only pieces. So we should make + // default inRange/outOfRange for this case, otherwise visuals that only + // appear in `pieces` will not be taken into account in visual encoding. + + var option = this.option; + var visualTypesInPieces = {}; + var visualTypes = VisualMapping.listVisualTypes(); + var isCategory = this.isCategory(); + + each$1(option.pieces, function (piece) { + each$1(visualTypes, function (visualType) { + if (piece.hasOwnProperty(visualType)) { + visualTypesInPieces[visualType] = 1; + } + }); + }); + + each$1(visualTypesInPieces, function (v, visualType) { + var exists = 0; + each$1(this.stateList, function (state) { + exists |= has(option, state, visualType) + || has(option.target, state, visualType); + }, this); + + !exists && each$1(this.stateList, function (state) { + (option[state] || (option[state] = {}))[visualType] = visualDefault.get( + visualType, state === 'inRange' ? 'active' : 'inactive', isCategory + ); + }); + }, this); + + function has(obj, state, visualType) { + return obj && obj[state] && ( + isObject$1(obj[state]) + ? obj[state].hasOwnProperty(visualType) + : obj[state] === visualType // e.g., inRange: 'symbol' + ); + } + + VisualMapModel.prototype.completeVisualOption.apply(this, arguments); + }, + + _resetSelected: function (newOption, isInit) { + var thisOption = this.option; + var pieceList = this._pieceList; + + // Selected do not merge but all override. + var selected = (isInit ? thisOption : newOption).selected || {}; + thisOption.selected = selected; + + // Consider 'not specified' means true. + each$1(pieceList, function (piece, index) { + var key = this.getSelectedMapKey(piece); + if (!selected.hasOwnProperty(key)) { + selected[key] = true; + } + }, this); + + if (thisOption.selectedMode === 'single') { + // Ensure there is only one selected. + var hasSel = false; + + each$1(pieceList, function (piece, index) { + var key = this.getSelectedMapKey(piece); + if (selected[key]) { + hasSel + ? (selected[key] = false) + : (hasSel = true); + } + }, this); + } + // thisOption.selectedMode === 'multiple', default: all selected. + }, + + /** + * @public + */ + getSelectedMapKey: function (piece) { + return this._mode === 'categories' + ? piece.value + '' : piece.index + ''; + }, + + /** + * @public + */ + getPieceList: function () { + return this._pieceList; + }, + + /** + * @private + * @return {string} + */ + _determineMode: function () { + var option = this.option; + + return option.pieces && option.pieces.length > 0 + ? 'pieces' + : this.option.categories + ? 'categories' + : 'splitNumber'; + }, + + /** + * @public + * @override + */ + setSelected: function (selected) { + this.option.selected = clone(selected); + }, + + /** + * @public + * @override + */ + getValueState: function (value) { + var index = VisualMapping.findPieceIndex(value, this._pieceList); + + return index != null + ? (this.option.selected[this.getSelectedMapKey(this._pieceList[index])] + ? 'inRange' : 'outOfRange' + ) + : 'outOfRange'; + }, + + /** + * @public + * @params {number} pieceIndex piece index in visualMapModel.getPieceList() + * @return {Array.} [{seriesId, dataIndex: >}, ...] + */ + findTargetDataIndices: function (pieceIndex) { + var result = []; + + this.eachTargetSeries(function (seriesModel) { + var dataIndices = []; + var data = seriesModel.getData(); + + data.each(this.getDataDimension(data), function (value, dataIndex) { + // Should always base on model pieceList, because it is order sensitive. + var pIdx = VisualMapping.findPieceIndex(value, this._pieceList); + pIdx === pieceIndex && dataIndices.push(dataIndex); + }, this); + + result.push({seriesId: seriesModel.id, dataIndex: dataIndices}); + }, this); + + return result; + }, + + /** + * @private + * @param {Object} piece piece.value or piece.interval is required. + * @return {number} Can be Infinity or -Infinity + */ + getRepresentValue: function (piece) { + var representValue; + if (this.isCategory()) { + representValue = piece.value; + } + else { + if (piece.value != null) { + representValue = piece.value; + } + else { + var pieceInterval = piece.interval || []; + representValue = (pieceInterval[0] === -Infinity && pieceInterval[1] === Infinity) + ? 0 + : (pieceInterval[0] + pieceInterval[1]) / 2; + } + } + return representValue; + }, + + getVisualMeta: function (getColorVisual) { + // Do not support category. (category axis is ordinal, numerical) + if (this.isCategory()) { + return; + } + + var stops = []; + var outerColors = []; + var visualMapModel = this; + + function setStop(interval, valueState) { + var representValue = visualMapModel.getRepresentValue({interval: interval}); + if (!valueState) { + valueState = visualMapModel.getValueState(representValue); + } + var color = getColorVisual(representValue, valueState); + if (interval[0] === -Infinity) { + outerColors[0] = color; + } + else if (interval[1] === Infinity) { + outerColors[1] = color; + } + else { + stops.push( + {value: interval[0], color: color}, + {value: interval[1], color: color} + ); + } + } + + // Suplement + var pieceList = this._pieceList.slice(); + if (!pieceList.length) { + pieceList.push({interval: [-Infinity, Infinity]}); + } + else { + var edge = pieceList[0].interval[0]; + edge !== -Infinity && pieceList.unshift({interval: [-Infinity, edge]}); + edge = pieceList[pieceList.length - 1].interval[1]; + edge !== Infinity && pieceList.push({interval: [edge, Infinity]}); + } + + var curr = -Infinity; + each$1(pieceList, function (piece) { + var interval = piece.interval; + if (interval) { + // Fulfill gap. + interval[0] > curr && setStop([curr, interval[0]], 'outOfRange'); + setStop(interval.slice()); + curr = interval[1]; + } + }, this); + + return {stops: stops, outerColors: outerColors}; + } + +}); + +/** + * Key is this._mode + * @type {Object} + * @this {module:echarts/component/viusalMap/PiecewiseMode} + */ +var resetMethods = { + + splitNumber: function () { + var thisOption = this.option; + var pieceList = this._pieceList; + var precision = Math.min(thisOption.precision, 20); + var dataExtent = this.getExtent(); + var splitNumber = thisOption.splitNumber; + splitNumber = Math.max(parseInt(splitNumber, 10), 1); + thisOption.splitNumber = splitNumber; + + var splitStep = (dataExtent[1] - dataExtent[0]) / splitNumber; + // Precision auto-adaption + while (+splitStep.toFixed(precision) !== splitStep && precision < 5) { + precision++; + } + thisOption.precision = precision; + splitStep = +splitStep.toFixed(precision); + + var index = 0; + + if (thisOption.minOpen) { + pieceList.push({ + index: index++, + interval: [-Infinity, dataExtent[0]], + close: [0, 0] + }); + } + + for ( + var curr = dataExtent[0], len = index + splitNumber; + index < len; + curr += splitStep + ) { + var max = index === splitNumber - 1 ? dataExtent[1] : (curr + splitStep); + + pieceList.push({ + index: index++, + interval: [curr, max], + close: [1, 1] + }); + } + + if (thisOption.maxOpen) { + pieceList.push({ + index: index++, + interval: [dataExtent[1], Infinity], + close: [0, 0] + }); + } + + reformIntervals(pieceList); + + each$1(pieceList, function (piece) { + piece.text = this.formatValueText(piece.interval); + }, this); + }, + + categories: function () { + var thisOption = this.option; + each$1(thisOption.categories, function (cate) { + // FIXME category模式也使用pieceList,但在visualMapping中不是使用pieceList。 + // 是否改一致。 + this._pieceList.push({ + text: this.formatValueText(cate, true), + value: cate + }); + }, this); + + // See "Order Rule". + normalizeReverse(thisOption, this._pieceList); + }, + + pieces: function () { + var thisOption = this.option; + var pieceList = this._pieceList; + + each$1(thisOption.pieces, function (pieceListItem, index) { + + if (!isObject$1(pieceListItem)) { + pieceListItem = {value: pieceListItem}; + } + + var item = {text: '', index: index}; + + if (pieceListItem.label != null) { + item.text = pieceListItem.label; + } + + if (pieceListItem.hasOwnProperty('value')) { + var value = item.value = pieceListItem.value; + item.interval = [value, value]; + item.close = [1, 1]; + } + else { + // `min` `max` is legacy option. + // `lt` `gt` `lte` `gte` is recommanded. + var interval = item.interval = []; + var close = item.close = [0, 0]; + + var closeList = [1, 0, 1]; + var infinityList = [-Infinity, Infinity]; + + var useMinMax = []; + for (var lg = 0; lg < 2; lg++) { + var names = [['gte', 'gt', 'min'], ['lte', 'lt', 'max']][lg]; + for (var i = 0; i < 3 && interval[lg] == null; i++) { + interval[lg] = pieceListItem[names[i]]; + close[lg] = closeList[i]; + useMinMax[lg] = i === 2; + } + interval[lg] == null && (interval[lg] = infinityList[lg]); + } + useMinMax[0] && interval[1] === Infinity && (close[0] = 0); + useMinMax[1] && interval[0] === -Infinity && (close[1] = 0); + + if (__DEV__) { + if (interval[0] > interval[1]) { + console.warn( + 'Piece ' + index + 'is illegal: ' + interval + + ' lower bound should not greater then uppper bound.' + ); + } + } + + if (interval[0] === interval[1] && close[0] && close[1]) { + // Consider: [{min: 5, max: 5, visual: {...}}, {min: 0, max: 5}], + // we use value to lift the priority when min === max + item.value = interval[0]; + } + } + + item.visual = VisualMapping.retrieveVisuals(pieceListItem); + + pieceList.push(item); + + }, this); + + // See "Order Rule". + normalizeReverse(thisOption, pieceList); + // Only pieces + reformIntervals(pieceList); + + each$1(pieceList, function (piece) { + var close = piece.close; + var edgeSymbols = [['<', '≤'][close[1]], ['>', '≥'][close[0]]]; + piece.text = piece.text || this.formatValueText( + piece.value != null ? piece.value : piece.interval, + false, + edgeSymbols + ); + }, this); + } +}; + +function normalizeReverse(thisOption, pieceList) { + var inverse = thisOption.inverse; + if (thisOption.orient === 'vertical' ? !inverse : inverse) { + pieceList.reverse(); + } +} + +/* +* 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. +*/ + +var PiecewiseVisualMapView = VisualMapView.extend({ + + type: 'visualMap.piecewise', + + /** + * @protected + * @override + */ + doRender: function () { + var thisGroup = this.group; + + thisGroup.removeAll(); + + var visualMapModel = this.visualMapModel; + var textGap = visualMapModel.get('textGap'); + var textStyleModel = visualMapModel.textStyleModel; + var textFont = textStyleModel.getFont(); + var textFill = textStyleModel.getTextColor(); + var itemAlign = this._getItemAlign(); + var itemSize = visualMapModel.itemSize; + var viewData = this._getViewData(); + var endsText = viewData.endsText; + var showLabel = retrieve(visualMapModel.get('showLabel', true), !endsText); + + endsText && this._renderEndsText( + thisGroup, endsText[0], itemSize, showLabel, itemAlign + ); + + each$1(viewData.viewPieceList, renderItem, this); + + endsText && this._renderEndsText( + thisGroup, endsText[1], itemSize, showLabel, itemAlign + ); + + box( + visualMapModel.get('orient'), thisGroup, visualMapModel.get('itemGap') + ); + + this.renderBackground(thisGroup); + + this.positionGroup(thisGroup); + + function renderItem(item) { + var piece = item.piece; + + var itemGroup = new Group(); + itemGroup.onclick = bind(this._onItemClick, this, piece); + + this._enableHoverLink(itemGroup, item.indexInModelPieceList); + + var representValue = visualMapModel.getRepresentValue(piece); + + this._createItemSymbol( + itemGroup, representValue, [0, 0, itemSize[0], itemSize[1]] + ); + + if (showLabel) { + var visualState = this.visualMapModel.getValueState(representValue); + + itemGroup.add(new Text({ + style: { + x: itemAlign === 'right' ? -textGap : itemSize[0] + textGap, + y: itemSize[1] / 2, + text: piece.text, + textVerticalAlign: 'middle', + textAlign: itemAlign, + textFont: textFont, + textFill: textFill, + opacity: visualState === 'outOfRange' ? 0.5 : 1 + } + })); + } + + thisGroup.add(itemGroup); + } + }, + + /** + * @private + */ + _enableHoverLink: function (itemGroup, pieceIndex) { + itemGroup + .on('mouseover', bind(onHoverLink, this, 'highlight')) + .on('mouseout', bind(onHoverLink, this, 'downplay')); + + function onHoverLink(method) { + var visualMapModel = this.visualMapModel; + + visualMapModel.option.hoverLink && this.api.dispatchAction({ + type: method, + batch: makeHighDownBatch( + visualMapModel.findTargetDataIndices(pieceIndex), + visualMapModel + ) + }); + } + }, + + /** + * @private + */ + _getItemAlign: function () { + var visualMapModel = this.visualMapModel; + var modelOption = visualMapModel.option; + + if (modelOption.orient === 'vertical') { + return getItemAlign( + visualMapModel, this.api, visualMapModel.itemSize + ); + } + else { // horizontal, most case left unless specifying right. + var align = modelOption.align; + if (!align || align === 'auto') { + align = 'left'; + } + return align; + } + }, + + /** + * @private + */ + _renderEndsText: function (group, text, itemSize, showLabel, itemAlign) { + if (!text) { + return; + } + + var itemGroup = new Group(); + var textStyleModel = this.visualMapModel.textStyleModel; + + itemGroup.add(new Text({ + style: { + x: showLabel ? (itemAlign === 'right' ? itemSize[0] : 0) : itemSize[0] / 2, + y: itemSize[1] / 2, + textVerticalAlign: 'middle', + textAlign: showLabel ? itemAlign : 'center', + text: text, + textFont: textStyleModel.getFont(), + textFill: textStyleModel.getTextColor() + } + })); + + group.add(itemGroup); + }, + + /** + * @private + * @return {Object} {peiceList, endsText} The order is the same as screen pixel order. + */ + _getViewData: function () { + var visualMapModel = this.visualMapModel; + + var viewPieceList = map(visualMapModel.getPieceList(), function (piece, index) { + return {piece: piece, indexInModelPieceList: index}; + }); + var endsText = visualMapModel.get('text'); + + // Consider orient and inverse. + var orient = visualMapModel.get('orient'); + var inverse = visualMapModel.get('inverse'); + + // Order of model pieceList is always [low, ..., high] + if (orient === 'horizontal' ? inverse : !inverse) { + viewPieceList.reverse(); + } + // Origin order of endsText is [high, low] + else if (endsText) { + endsText = endsText.slice().reverse(); + } + + return {viewPieceList: viewPieceList, endsText: endsText}; + }, + + /** + * @private + */ + _createItemSymbol: function (group, representValue, shapeParam) { + group.add(createSymbol( + this.getControllerVisual(representValue, 'symbol'), + shapeParam[0], shapeParam[1], shapeParam[2], shapeParam[3], + this.getControllerVisual(representValue, 'color') + )); + }, + + /** + * @private + */ + _onItemClick: function (piece) { + var visualMapModel = this.visualMapModel; + var option = visualMapModel.option; + var selected = clone(option.selected); + var newKey = visualMapModel.getSelectedMapKey(piece); + + if (option.selectedMode === 'single') { + selected[newKey] = true; + each$1(selected, function (o, key) { + selected[key] = key === newKey; + }); + } + else { + selected[newKey] = !selected[newKey]; + } + + this.api.dispatchAction({ + type: 'selectDataRange', + from: this.uid, + visualMapId: this.visualMapModel.id, + selected: selected + }); + } +}); + +/* +* 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. +*/ + +/** + * DataZoom component entry + */ + +registerPreprocessor(preprocessor$3); + +/* +* 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. +*/ + +/** + * visualMap component entry + */ + +var urn = 'urn:schemas-microsoft-com:vml'; +var win = typeof window === 'undefined' ? null : window; + +var vmlInited = false; + +var doc = win && win.document; + +function createNode(tagName) { + return doCreateNode(tagName); +} + +// Avoid assign to an exported variable, for transforming to cjs. +var doCreateNode; + +if (doc && !env$1.canvasSupported) { + try { + !doc.namespaces.zrvml && doc.namespaces.add('zrvml', urn); + doCreateNode = function (tagName) { + return doc.createElement(''); + }; + } + catch (e) { + doCreateNode = function (tagName) { + return doc.createElement('<' + tagName + ' xmlns="' + urn + '" class="zrvml">'); + }; + } +} + +// From raphael +function initVML() { + if (vmlInited || !doc) { + return; + } + vmlInited = true; + + var styleSheets = doc.styleSheets; + if (styleSheets.length < 31) { + doc.createStyleSheet().addRule('.zrvml', 'behavior:url(#default#VML)'); + } + else { + // http://msdn.microsoft.com/en-us/library/ms531194%28VS.85%29.aspx + styleSheets[0].addRule('.zrvml', 'behavior:url(#default#VML)'); + } +} + +// http://www.w3.org/TR/NOTE-VML +// TODO Use proxy like svg instead of overwrite brush methods + +var CMD$3 = PathProxy.CMD; +var round$3 = Math.round; +var sqrt = Math.sqrt; +var abs$1 = Math.abs; +var cos = Math.cos; +var sin = Math.sin; +var mathMax$9 = Math.max; + +if (!env$1.canvasSupported) { + + var comma = ','; + var imageTransformPrefix = 'progid:DXImageTransform.Microsoft'; + + var Z = 21600; + var Z2 = Z / 2; + + var ZLEVEL_BASE = 100000; + var Z_BASE$1 = 1000; + + var initRootElStyle = function (el) { + el.style.cssText = 'position:absolute;left:0;top:0;width:1px;height:1px;'; + el.coordsize = Z + ',' + Z; + el.coordorigin = '0,0'; + }; + + var encodeHtmlAttribute = function (s) { + return String(s).replace(/&/g, '&').replace(/"/g, '"'); + }; + + var rgb2Str = function (r, g, b) { + return 'rgb(' + [r, g, b].join(',') + ')'; + }; + + var append = function (parent, child) { + if (child && parent && child.parentNode !== parent) { + parent.appendChild(child); + } + }; + + var remove = function (parent, child) { + if (child && parent && child.parentNode === parent) { + parent.removeChild(child); + } + }; + + var getZIndex = function (zlevel, z, z2) { + // z 的取值范围为 [0, 1000] + return (parseFloat(zlevel) || 0) * ZLEVEL_BASE + (parseFloat(z) || 0) * Z_BASE$1 + z2; + }; + + var parsePercent$3 = parsePercent; + + /*************************************************** + * PATH + **************************************************/ + + var setColorAndOpacity = function (el, color, opacity) { + var colorArr = parse(color); + opacity = +opacity; + if (isNaN(opacity)) { + opacity = 1; + } + if (colorArr) { + el.color = rgb2Str(colorArr[0], colorArr[1], colorArr[2]); + el.opacity = opacity * colorArr[3]; + } + }; + + var getColorAndAlpha = function (color) { + var colorArr = parse(color); + return [ + rgb2Str(colorArr[0], colorArr[1], colorArr[2]), + colorArr[3] + ]; + }; + + var updateFillNode = function (el, style, zrEl) { + // TODO pattern + var fill = style.fill; + if (fill != null) { + // Modified from excanvas + if (fill instanceof Gradient) { + var gradientType; + var angle = 0; + var focus = [0, 0]; + // additional offset + var shift = 0; + // scale factor for offset + var expansion = 1; + var rect = zrEl.getBoundingRect(); + var rectWidth = rect.width; + var rectHeight = rect.height; + if (fill.type === 'linear') { + gradientType = 'gradient'; + var transform = zrEl.transform; + var p0 = [fill.x * rectWidth, fill.y * rectHeight]; + var p1 = [fill.x2 * rectWidth, fill.y2 * rectHeight]; + if (transform) { + applyTransform(p0, p0, transform); + applyTransform(p1, p1, transform); + } + var dx = p1[0] - p0[0]; + var dy = p1[1] - p0[1]; + angle = Math.atan2(dx, dy) * 180 / Math.PI; + // The angle should be a non-negative number. + if (angle < 0) { + angle += 360; + } + + // Very small angles produce an unexpected result because they are + // converted to a scientific notation string. + if (angle < 1e-6) { + angle = 0; + } + } + else { + gradientType = 'gradientradial'; + var p0 = [fill.x * rectWidth, fill.y * rectHeight]; + var transform = zrEl.transform; + var scale$$1 = zrEl.scale; + var width = rectWidth; + var height = rectHeight; + focus = [ + // Percent in bounding rect + (p0[0] - rect.x) / width, + (p0[1] - rect.y) / height + ]; + if (transform) { + applyTransform(p0, p0, transform); + } + + width /= scale$$1[0] * Z; + height /= scale$$1[1] * Z; + var dimension = mathMax$9(width, height); + shift = 2 * 0 / dimension; + expansion = 2 * fill.r / dimension - shift; + } + + // We need to sort the color stops in ascending order by offset, + // otherwise IE won't interpret it correctly. + var stops = fill.colorStops.slice(); + stops.sort(function (cs1, cs2) { + return cs1.offset - cs2.offset; + }); + + var length$$1 = stops.length; + // Color and alpha list of first and last stop + var colorAndAlphaList = []; + var colors = []; + for (var i = 0; i < length$$1; i++) { + var stop = stops[i]; + var colorAndAlpha = getColorAndAlpha(stop.color); + colors.push(stop.offset * expansion + shift + ' ' + colorAndAlpha[0]); + if (i === 0 || i === length$$1 - 1) { + colorAndAlphaList.push(colorAndAlpha); + } + } + + if (length$$1 >= 2) { + var color1 = colorAndAlphaList[0][0]; + var color2 = colorAndAlphaList[1][0]; + var opacity1 = colorAndAlphaList[0][1] * style.opacity; + var opacity2 = colorAndAlphaList[1][1] * style.opacity; + + el.type = gradientType; + el.method = 'none'; + el.focus = '100%'; + el.angle = angle; + el.color = color1; + el.color2 = color2; + el.colors = colors.join(','); + // When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + el.opacity = opacity2; + // FIXME g_o_:opacity ? + el.opacity2 = opacity1; + } + if (gradientType === 'radial') { + el.focusposition = focus.join(','); + } + } + else { + // FIXME Change from Gradient fill to color fill + setColorAndOpacity(el, fill, style.opacity); + } + } + }; + + var updateStrokeNode = function (el, style) { + // if (style.lineJoin != null) { + // el.joinstyle = style.lineJoin; + // } + // if (style.miterLimit != null) { + // el.miterlimit = style.miterLimit * Z; + // } + // if (style.lineCap != null) { + // el.endcap = style.lineCap; + // } + if (style.lineDash) { + el.dashstyle = style.lineDash.join(' '); + } + if (style.stroke != null && !(style.stroke instanceof Gradient)) { + setColorAndOpacity(el, style.stroke, style.opacity); + } + }; + + var updateFillAndStroke = function (vmlEl, type, style, zrEl) { + var isFill = type === 'fill'; + var el = vmlEl.getElementsByTagName(type)[0]; + // Stroke must have lineWidth + if (style[type] != null && style[type] !== 'none' && (isFill || (!isFill && style.lineWidth))) { + vmlEl[isFill ? 'filled' : 'stroked'] = 'true'; + // FIXME Remove before updating, or set `colors` will throw error + if (style[type] instanceof Gradient) { + remove(vmlEl, el); + } + if (!el) { + el = createNode(type); + } + + isFill ? updateFillNode(el, style, zrEl) : updateStrokeNode(el, style); + append(vmlEl, el); + } + else { + vmlEl[isFill ? 'filled' : 'stroked'] = 'false'; + remove(vmlEl, el); + } + }; + + var points$3 = [[], [], []]; + var pathDataToString = function (path, m) { + var M = CMD$3.M; + var C = CMD$3.C; + var L = CMD$3.L; + var A = CMD$3.A; + var Q = CMD$3.Q; + + var str = []; + var nPoint; + var cmdStr; + var cmd; + var i; + var xi; + var yi; + var data = path.data; + var dataLength = path.len(); + for (i = 0; i < dataLength;) { + cmd = data[i++]; + cmdStr = ''; + nPoint = 0; + switch (cmd) { + case M: + cmdStr = ' m '; + nPoint = 1; + xi = data[i++]; + yi = data[i++]; + points$3[0][0] = xi; + points$3[0][1] = yi; + break; + case L: + cmdStr = ' l '; + nPoint = 1; + xi = data[i++]; + yi = data[i++]; + points$3[0][0] = xi; + points$3[0][1] = yi; + break; + case Q: + case C: + cmdStr = ' c '; + nPoint = 3; + var x1 = data[i++]; + var y1 = data[i++]; + var x2 = data[i++]; + var y2 = data[i++]; + var x3; + var y3; + if (cmd === Q) { + // Convert quadratic to cubic using degree elevation + x3 = x2; + y3 = y2; + x2 = (x2 + 2 * x1) / 3; + y2 = (y2 + 2 * y1) / 3; + x1 = (xi + 2 * x1) / 3; + y1 = (yi + 2 * y1) / 3; + } + else { + x3 = data[i++]; + y3 = data[i++]; + } + points$3[0][0] = x1; + points$3[0][1] = y1; + points$3[1][0] = x2; + points$3[1][1] = y2; + points$3[2][0] = x3; + points$3[2][1] = y3; + + xi = x3; + yi = y3; + break; + case A: + var x = 0; + var y = 0; + var sx = 1; + var sy = 1; + var angle = 0; + if (m) { + // Extract SRT from matrix + x = m[4]; + y = m[5]; + sx = sqrt(m[0] * m[0] + m[1] * m[1]); + sy = sqrt(m[2] * m[2] + m[3] * m[3]); + angle = Math.atan2(-m[1] / sy, m[0] / sx); + } + + var cx = data[i++]; + var cy = data[i++]; + var rx = data[i++]; + var ry = data[i++]; + var startAngle = data[i++] + angle; + var endAngle = data[i++] + startAngle + angle; + // FIXME + // var psi = data[i++]; + i++; + var clockwise = data[i++]; + + var x0 = cx + cos(startAngle) * rx; + var y0 = cy + sin(startAngle) * ry; + + var x1 = cx + cos(endAngle) * rx; + var y1 = cy + sin(endAngle) * ry; + + var type = clockwise ? ' wa ' : ' at '; + if (Math.abs(x0 - x1) < 1e-4) { + // IE won't render arches drawn counter clockwise if x0 == x1. + if (Math.abs(endAngle - startAngle) > 1e-2) { + // Offset x0 by 1/80 of a pixel. Use something + // that can be represented in binary + if (clockwise) { + x0 += 270 / Z; + } + } + else { + // Avoid case draw full circle + if (Math.abs(y0 - cy) < 1e-4) { + if ((clockwise && x0 < cx) || (!clockwise && x0 > cx)) { + y1 -= 270 / Z; + } + else { + y1 += 270 / Z; + } + } + else if ((clockwise && y0 < cy) || (!clockwise && y0 > cy)) { + x1 += 270 / Z; + } + else { + x1 -= 270 / Z; + } + } + } + str.push( + type, + round$3(((cx - rx) * sx + x) * Z - Z2), comma, + round$3(((cy - ry) * sy + y) * Z - Z2), comma, + round$3(((cx + rx) * sx + x) * Z - Z2), comma, + round$3(((cy + ry) * sy + y) * Z - Z2), comma, + round$3((x0 * sx + x) * Z - Z2), comma, + round$3((y0 * sy + y) * Z - Z2), comma, + round$3((x1 * sx + x) * Z - Z2), comma, + round$3((y1 * sy + y) * Z - Z2) + ); + + xi = x1; + yi = y1; + break; + case CMD$3.R: + var p0 = points$3[0]; + var p1 = points$3[1]; + // x0, y0 + p0[0] = data[i++]; + p0[1] = data[i++]; + // x1, y1 + p1[0] = p0[0] + data[i++]; + p1[1] = p0[1] + data[i++]; + + if (m) { + applyTransform(p0, p0, m); + applyTransform(p1, p1, m); + } + + p0[0] = round$3(p0[0] * Z - Z2); + p1[0] = round$3(p1[0] * Z - Z2); + p0[1] = round$3(p0[1] * Z - Z2); + p1[1] = round$3(p1[1] * Z - Z2); + str.push( + // x0, y0 + ' m ', p0[0], comma, p0[1], + // x1, y0 + ' l ', p1[0], comma, p0[1], + // x1, y1 + ' l ', p1[0], comma, p1[1], + // x0, y1 + ' l ', p0[0], comma, p1[1] + ); + break; + case CMD$3.Z: + // FIXME Update xi, yi + str.push(' x '); + } + + if (nPoint > 0) { + str.push(cmdStr); + for (var k = 0; k < nPoint; k++) { + var p = points$3[k]; + + m && applyTransform(p, p, m); + // 不 round 会非常慢 + str.push( + round$3(p[0] * Z - Z2), comma, round$3(p[1] * Z - Z2), + k < nPoint - 1 ? comma : '' + ); + } + } + } + + return str.join(''); + }; + + // Rewrite the original path method + Path.prototype.brushVML = function (vmlRoot) { + var style = this.style; + + var vmlEl = this._vmlEl; + if (!vmlEl) { + vmlEl = createNode('shape'); + initRootElStyle(vmlEl); + + this._vmlEl = vmlEl; + } + + updateFillAndStroke(vmlEl, 'fill', style, this); + updateFillAndStroke(vmlEl, 'stroke', style, this); + + var m = this.transform; + var needTransform = m != null; + var strokeEl = vmlEl.getElementsByTagName('stroke')[0]; + if (strokeEl) { + var lineWidth = style.lineWidth; + // Get the line scale. + // Determinant of this.m_ means how much the area is enlarged by the + // transformation. So its square root can be used as a scale factor + // for width. + if (needTransform && !style.strokeNoScale) { + var det = m[0] * m[3] - m[1] * m[2]; + lineWidth *= sqrt(abs$1(det)); + } + strokeEl.weight = lineWidth + 'px'; + } + + var path = this.path || (this.path = new PathProxy()); + if (this.__dirtyPath) { + path.beginPath(); + path.subPixelOptimize = false; + this.buildPath(path, this.shape); + path.toStatic(); + this.__dirtyPath = false; + } + + vmlEl.path = pathDataToString(path, this.transform); + + vmlEl.style.zIndex = getZIndex(this.zlevel, this.z, this.z2); + + // Append to root + append(vmlRoot, vmlEl); + + // Text + if (style.text != null) { + this.drawRectText(vmlRoot, this.getBoundingRect()); + } + else { + this.removeRectText(vmlRoot); + } + }; + + Path.prototype.onRemove = function (vmlRoot) { + remove(vmlRoot, this._vmlEl); + this.removeRectText(vmlRoot); + }; + + Path.prototype.onAdd = function (vmlRoot) { + append(vmlRoot, this._vmlEl); + this.appendRectText(vmlRoot); + }; + + /*************************************************** + * IMAGE + **************************************************/ + var isImage = function (img) { + // FIXME img instanceof Image 如果 img 是一个字符串的时候,IE8 下会报错 + return (typeof img === 'object') && img.tagName && img.tagName.toUpperCase() === 'IMG'; + // return img instanceof Image; + }; + + // Rewrite the original path method + ZImage.prototype.brushVML = function (vmlRoot) { + var style = this.style; + var image = style.image; + + // Image original width, height + var ow; + var oh; + + if (isImage(image)) { + var src = image.src; + if (src === this._imageSrc) { + ow = this._imageWidth; + oh = this._imageHeight; + } + else { + var imageRuntimeStyle = image.runtimeStyle; + var oldRuntimeWidth = imageRuntimeStyle.width; + var oldRuntimeHeight = imageRuntimeStyle.height; + imageRuntimeStyle.width = 'auto'; + imageRuntimeStyle.height = 'auto'; + + // get the original size + ow = image.width; + oh = image.height; + + // and remove overides + imageRuntimeStyle.width = oldRuntimeWidth; + imageRuntimeStyle.height = oldRuntimeHeight; + + // Caching image original width, height and src + this._imageSrc = src; + this._imageWidth = ow; + this._imageHeight = oh; + } + image = src; + } + else { + if (image === this._imageSrc) { + ow = this._imageWidth; + oh = this._imageHeight; + } + } + if (!image) { + return; + } + + var x = style.x || 0; + var y = style.y || 0; + + var dw = style.width; + var dh = style.height; + + var sw = style.sWidth; + var sh = style.sHeight; + var sx = style.sx || 0; + var sy = style.sy || 0; + + var hasCrop = sw && sh; + + var vmlEl = this._vmlEl; + if (!vmlEl) { + // FIXME 使用 group 在 left, top 都不是 0 的时候就无法显示了。 + // vmlEl = vmlCore.createNode('group'); + vmlEl = doc.createElement('div'); + initRootElStyle(vmlEl); + + this._vmlEl = vmlEl; + } + + var vmlElStyle = vmlEl.style; + var hasRotation = false; + var m; + var scaleX = 1; + var scaleY = 1; + if (this.transform) { + m = this.transform; + scaleX = sqrt(m[0] * m[0] + m[1] * m[1]); + scaleY = sqrt(m[2] * m[2] + m[3] * m[3]); + + hasRotation = m[1] || m[2]; + } + if (hasRotation) { + // If filters are necessary (rotation exists), create them + // filters are bog-slow, so only create them if abbsolutely necessary + // The following check doesn't account for skews (which don't exist + // in the canvas spec (yet) anyway. + // From excanvas + var p0 = [x, y]; + var p1 = [x + dw, y]; + var p2 = [x, y + dh]; + var p3 = [x + dw, y + dh]; + applyTransform(p0, p0, m); + applyTransform(p1, p1, m); + applyTransform(p2, p2, m); + applyTransform(p3, p3, m); + + var maxX = mathMax$9(p0[0], p1[0], p2[0], p3[0]); + var maxY = mathMax$9(p0[1], p1[1], p2[1], p3[1]); + + var transformFilter = []; + transformFilter.push('M11=', m[0] / scaleX, comma, + 'M12=', m[2] / scaleY, comma, + 'M21=', m[1] / scaleX, comma, + 'M22=', m[3] / scaleY, comma, + 'Dx=', round$3(x * scaleX + m[4]), comma, + 'Dy=', round$3(y * scaleY + m[5])); + + vmlElStyle.padding = '0 ' + round$3(maxX) + 'px ' + round$3(maxY) + 'px 0'; + // FIXME DXImageTransform 在 IE11 的兼容模式下不起作用 + vmlElStyle.filter = imageTransformPrefix + '.Matrix(' + + transformFilter.join('') + ', SizingMethod=clip)'; + + } + else { + if (m) { + x = x * scaleX + m[4]; + y = y * scaleY + m[5]; + } + vmlElStyle.filter = ''; + vmlElStyle.left = round$3(x) + 'px'; + vmlElStyle.top = round$3(y) + 'px'; + } + + var imageEl = this._imageEl; + var cropEl = this._cropEl; + + if (!imageEl) { + imageEl = doc.createElement('div'); + this._imageEl = imageEl; + } + var imageELStyle = imageEl.style; + if (hasCrop) { + // Needs know image original width and height + if (!(ow && oh)) { + var tmpImage = new Image(); + var self = this; + tmpImage.onload = function () { + tmpImage.onload = null; + ow = tmpImage.width; + oh = tmpImage.height; + // Adjust image width and height to fit the ratio destinationSize / sourceSize + imageELStyle.width = round$3(scaleX * ow * dw / sw) + 'px'; + imageELStyle.height = round$3(scaleY * oh * dh / sh) + 'px'; + + // Caching image original width, height and src + self._imageWidth = ow; + self._imageHeight = oh; + self._imageSrc = image; + }; + tmpImage.src = image; + } + else { + imageELStyle.width = round$3(scaleX * ow * dw / sw) + 'px'; + imageELStyle.height = round$3(scaleY * oh * dh / sh) + 'px'; + } + + if (!cropEl) { + cropEl = doc.createElement('div'); + cropEl.style.overflow = 'hidden'; + this._cropEl = cropEl; + } + var cropElStyle = cropEl.style; + cropElStyle.width = round$3((dw + sx * dw / sw) * scaleX); + cropElStyle.height = round$3((dh + sy * dh / sh) * scaleY); + cropElStyle.filter = imageTransformPrefix + '.Matrix(Dx=' + + (-sx * dw / sw * scaleX) + ',Dy=' + (-sy * dh / sh * scaleY) + ')'; + + if (!cropEl.parentNode) { + vmlEl.appendChild(cropEl); + } + if (imageEl.parentNode !== cropEl) { + cropEl.appendChild(imageEl); + } + } + else { + imageELStyle.width = round$3(scaleX * dw) + 'px'; + imageELStyle.height = round$3(scaleY * dh) + 'px'; + + vmlEl.appendChild(imageEl); + + if (cropEl && cropEl.parentNode) { + vmlEl.removeChild(cropEl); + this._cropEl = null; + } + } + + var filterStr = ''; + var alpha = style.opacity; + if (alpha < 1) { + filterStr += '.Alpha(opacity=' + round$3(alpha * 100) + ') '; + } + filterStr += imageTransformPrefix + '.AlphaImageLoader(src=' + image + ', SizingMethod=scale)'; + + imageELStyle.filter = filterStr; + + vmlEl.style.zIndex = getZIndex(this.zlevel, this.z, this.z2); + + // Append to root + append(vmlRoot, vmlEl); + + // Text + if (style.text != null) { + this.drawRectText(vmlRoot, this.getBoundingRect()); + } + }; + + ZImage.prototype.onRemove = function (vmlRoot) { + remove(vmlRoot, this._vmlEl); + + this._vmlEl = null; + this._cropEl = null; + this._imageEl = null; + + this.removeRectText(vmlRoot); + }; + + ZImage.prototype.onAdd = function (vmlRoot) { + append(vmlRoot, this._vmlEl); + this.appendRectText(vmlRoot); + }; + + + /*************************************************** + * TEXT + **************************************************/ + + var DEFAULT_STYLE_NORMAL = 'normal'; + + var fontStyleCache = {}; + var fontStyleCacheCount = 0; + var MAX_FONT_CACHE_SIZE = 100; + var fontEl = document.createElement('div'); + + var getFontStyle = function (fontString) { + var fontStyle = fontStyleCache[fontString]; + if (!fontStyle) { + // Clear cache + if (fontStyleCacheCount > MAX_FONT_CACHE_SIZE) { + fontStyleCacheCount = 0; + fontStyleCache = {}; + } + + var style = fontEl.style; + var fontFamily; + try { + style.font = fontString; + fontFamily = style.fontFamily.split(',')[0]; + } + catch (e) { + } + + fontStyle = { + style: style.fontStyle || DEFAULT_STYLE_NORMAL, + variant: style.fontVariant || DEFAULT_STYLE_NORMAL, + weight: style.fontWeight || DEFAULT_STYLE_NORMAL, + size: parseFloat(style.fontSize || 12) | 0, + family: fontFamily || 'Microsoft YaHei' + }; + + fontStyleCache[fontString] = fontStyle; + fontStyleCacheCount++; + } + return fontStyle; + }; + + var textMeasureEl; + // Overwrite measure text method + $override$1('measureText', function (text, textFont) { + var doc$$1 = doc; + if (!textMeasureEl) { + textMeasureEl = doc$$1.createElement('div'); + textMeasureEl.style.cssText = 'position:absolute;top:-20000px;left:0;' + + 'padding:0;margin:0;border:none;white-space:pre;'; + doc.body.appendChild(textMeasureEl); + } + + try { + textMeasureEl.style.font = textFont; + } + catch (ex) { + // Ignore failures to set to invalid font. + } + textMeasureEl.innerHTML = ''; + // Don't use innerHTML or innerText because they allow markup/whitespace. + textMeasureEl.appendChild(doc$$1.createTextNode(text)); + return { + width: textMeasureEl.offsetWidth + }; + }); + + var tmpRect$2 = new BoundingRect(); + + var drawRectText = function (vmlRoot, rect, textRect, fromTextEl) { + + var style = this.style; + + // Optimize, avoid normalize every time. + this.__dirty && normalizeTextStyle(style, true); + + var text = style.text; + // Convert to string + text != null && (text += ''); + if (!text) { + return; + } + + // Convert rich text to plain text. Rich text is not supported in + // IE8-, but tags in rich text template will be removed. + if (style.rich) { + var contentBlock = parseRichText(text, style); + text = []; + for (var i = 0; i < contentBlock.lines.length; i++) { + var tokens = contentBlock.lines[i].tokens; + var textLine = []; + for (var j = 0; j < tokens.length; j++) { + textLine.push(tokens[j].text); + } + text.push(textLine.join('')); + } + text = text.join('\n'); + } + + var x; + var y; + var align = style.textAlign; + var verticalAlign = style.textVerticalAlign; + + var fontStyle = getFontStyle(style.font); + // FIXME encodeHtmlAttribute ? + var font = fontStyle.style + ' ' + fontStyle.variant + ' ' + fontStyle.weight + ' ' + + fontStyle.size + 'px "' + fontStyle.family + '"'; + + textRect = textRect || getBoundingRect( + text, font, align, verticalAlign, style.textPadding, style.textLineHeight + ); + + // Transform rect to view space + var m = this.transform; + // Ignore transform for text in other element + if (m && !fromTextEl) { + tmpRect$2.copy(rect); + tmpRect$2.applyTransform(m); + rect = tmpRect$2; + } + + if (!fromTextEl) { + var textPosition = style.textPosition; + // Text position represented by coord + if (textPosition instanceof Array) { + x = rect.x + parsePercent$3(textPosition[0], rect.width); + y = rect.y + parsePercent$3(textPosition[1], rect.height); + + align = align || 'left'; + } + else { + var res = this.calculateTextPosition + ? this.calculateTextPosition({}, style, rect) + : calculateTextPosition({}, style, rect); + x = res.x; + y = res.y; + + // Default align and baseline when has textPosition + align = align || res.textAlign; + verticalAlign = verticalAlign || res.textVerticalAlign; + } + } + else { + x = rect.x; + y = rect.y; + } + + x = adjustTextX(x, textRect.width, align); + y = adjustTextY(y, textRect.height, verticalAlign); + + // Force baseline 'middle' + y += textRect.height / 2; + + // var fontSize = fontStyle.size; + // 1.75 is an arbitrary number, as there is no info about the text baseline + // switch (baseline) { + // case 'hanging': + // case 'top': + // y += fontSize / 1.75; + // break; + // case 'middle': + // break; + // default: + // // case null: + // // case 'alphabetic': + // // case 'ideographic': + // // case 'bottom': + // y -= fontSize / 2.25; + // break; + // } + + // switch (align) { + // case 'left': + // break; + // case 'center': + // x -= textRect.width / 2; + // break; + // case 'right': + // x -= textRect.width; + // break; + // case 'end': + // align = elementStyle.direction == 'ltr' ? 'right' : 'left'; + // break; + // case 'start': + // align = elementStyle.direction == 'rtl' ? 'right' : 'left'; + // break; + // default: + // align = 'left'; + // } + + var createNode$$1 = createNode; + + var textVmlEl = this._textVmlEl; + var pathEl; + var textPathEl; + var skewEl; + if (!textVmlEl) { + textVmlEl = createNode$$1('line'); + pathEl = createNode$$1('path'); + textPathEl = createNode$$1('textpath'); + skewEl = createNode$$1('skew'); + + // FIXME Why here is not cammel case + // Align 'center' seems wrong + textPathEl.style['v-text-align'] = 'left'; + + initRootElStyle(textVmlEl); + + pathEl.textpathok = true; + textPathEl.on = true; + + textVmlEl.from = '0 0'; + textVmlEl.to = '1000 0.05'; + + append(textVmlEl, skewEl); + append(textVmlEl, pathEl); + append(textVmlEl, textPathEl); + + this._textVmlEl = textVmlEl; + } + else { + // 这里是在前面 appendChild 保证顺序的前提下 + skewEl = textVmlEl.firstChild; + pathEl = skewEl.nextSibling; + textPathEl = pathEl.nextSibling; + } + + var coords = [x, y]; + var textVmlElStyle = textVmlEl.style; + // Ignore transform for text in other element + if (m && fromTextEl) { + applyTransform(coords, coords, m); + + skewEl.on = true; + + skewEl.matrix = m[0].toFixed(3) + comma + m[2].toFixed(3) + comma + + m[1].toFixed(3) + comma + m[3].toFixed(3) + ',0,0'; + + // Text position + skewEl.offset = (round$3(coords[0]) || 0) + ',' + (round$3(coords[1]) || 0); + // Left top point as origin + skewEl.origin = '0 0'; + + textVmlElStyle.left = '0px'; + textVmlElStyle.top = '0px'; + } + else { + skewEl.on = false; + textVmlElStyle.left = round$3(x) + 'px'; + textVmlElStyle.top = round$3(y) + 'px'; + } + + textPathEl.string = encodeHtmlAttribute(text); + // TODO + try { + textPathEl.style.font = font; + } + // Error font format + catch (e) {} + + updateFillAndStroke(textVmlEl, 'fill', { + fill: style.textFill, + opacity: style.opacity + }, this); + updateFillAndStroke(textVmlEl, 'stroke', { + stroke: style.textStroke, + opacity: style.opacity, + lineDash: style.lineDash || null // style.lineDash can be `false`. + }, this); + + textVmlEl.style.zIndex = getZIndex(this.zlevel, this.z, this.z2); + + // Attached to root + append(vmlRoot, textVmlEl); + }; + + var removeRectText = function (vmlRoot) { + remove(vmlRoot, this._textVmlEl); + this._textVmlEl = null; + }; + + var appendRectText = function (vmlRoot) { + append(vmlRoot, this._textVmlEl); + }; + + var list = [RectText, Displayable, ZImage, Path, Text]; + + // In case Displayable has been mixed in RectText + for (var i$3 = 0; i$3 < list.length; i$3++) { + var proto$8 = list[i$3].prototype; + proto$8.drawRectText = drawRectText; + proto$8.removeRectText = removeRectText; + proto$8.appendRectText = appendRectText; + } + + Text.prototype.brushVML = function (vmlRoot) { + var style = this.style; + if (style.text != null) { + this.drawRectText(vmlRoot, { + x: style.x || 0, y: style.y || 0, + width: 0, height: 0 + }, this.getBoundingRect(), true); + } + else { + this.removeRectText(vmlRoot); + } + }; + + Text.prototype.onRemove = function (vmlRoot) { + this.removeRectText(vmlRoot); + }; + + Text.prototype.onAdd = function (vmlRoot) { + this.appendRectText(vmlRoot); + }; +} + +/** + * VML Painter. + * + * @module zrender/vml/Painter + */ + +function parseInt10$1(val) { + return parseInt(val, 10); +} + +/** + * @alias module:zrender/vml/Painter + */ +function VMLPainter(root, storage) { + + initVML(); + + this.root = root; + + this.storage = storage; + + var vmlViewport = document.createElement('div'); + + var vmlRoot = document.createElement('div'); + + vmlViewport.style.cssText = 'display:inline-block;overflow:hidden;position:relative;width:300px;height:150px;'; + + vmlRoot.style.cssText = 'position:absolute;left:0;top:0;'; + + root.appendChild(vmlViewport); + + this._vmlRoot = vmlRoot; + this._vmlViewport = vmlViewport; + + this.resize(); + + // Modify storage + var oldDelFromStorage = storage.delFromStorage; + var oldAddToStorage = storage.addToStorage; + storage.delFromStorage = function (el) { + oldDelFromStorage.call(storage, el); + + if (el) { + el.onRemove && el.onRemove(vmlRoot); + } + }; + + storage.addToStorage = function (el) { + // Displayable already has a vml node + el.onAdd && el.onAdd(vmlRoot); + + oldAddToStorage.call(storage, el); + }; + + this._firstPaint = true; +} + +VMLPainter.prototype = { + + constructor: VMLPainter, + + getType: function () { + return 'vml'; + }, + + /** + * @return {HTMLDivElement} + */ + getViewportRoot: function () { + return this._vmlViewport; + }, + + getViewportRootOffset: function () { + var viewportRoot = this.getViewportRoot(); + if (viewportRoot) { + return { + offsetLeft: viewportRoot.offsetLeft || 0, + offsetTop: viewportRoot.offsetTop || 0 + }; + } + }, + + /** + * 刷新 + */ + refresh: function () { + + var list = this.storage.getDisplayList(true, true); + + this._paintList(list); + }, + + _paintList: function (list) { + var vmlRoot = this._vmlRoot; + for (var i = 0; i < list.length; i++) { + var el = list[i]; + if (el.invisible || el.ignore) { + if (!el.__alreadyNotVisible) { + el.onRemove(vmlRoot); + } + // Set as already invisible + el.__alreadyNotVisible = true; + } + else { + if (el.__alreadyNotVisible) { + el.onAdd(vmlRoot); + } + el.__alreadyNotVisible = false; + if (el.__dirty) { + el.beforeBrush && el.beforeBrush(); + (el.brushVML || el.brush).call(el, vmlRoot); + el.afterBrush && el.afterBrush(); + } + } + el.__dirty = false; + } + + if (this._firstPaint) { + // Detached from document at first time + // to avoid page refreshing too many times + + // FIXME 如果每次都先 removeChild 可能会导致一些填充和描边的效果改变 + this._vmlViewport.appendChild(vmlRoot); + this._firstPaint = false; + } + }, + + resize: function (width, height) { + var width = width == null ? this._getWidth() : width; + var height = height == null ? this._getHeight() : height; + + if (this._width !== width || this._height !== height) { + this._width = width; + this._height = height; + + var vmlViewportStyle = this._vmlViewport.style; + vmlViewportStyle.width = width + 'px'; + vmlViewportStyle.height = height + 'px'; + } + }, + + dispose: function () { + this.root.innerHTML = ''; + + this._vmlRoot = + this._vmlViewport = + this.storage = null; + }, + + getWidth: function () { + return this._width; + }, + + getHeight: function () { + return this._height; + }, + + clear: function () { + if (this._vmlViewport) { + this.root.removeChild(this._vmlViewport); + } + }, + + _getWidth: function () { + var root = this.root; + var stl = root.currentStyle; + + return ((root.clientWidth || parseInt10$1(stl.width)) + - parseInt10$1(stl.paddingLeft) + - parseInt10$1(stl.paddingRight)) | 0; + }, + + _getHeight: function () { + var root = this.root; + var stl = root.currentStyle; + + return ((root.clientHeight || parseInt10$1(stl.height)) + - parseInt10$1(stl.paddingTop) + - parseInt10$1(stl.paddingBottom)) | 0; + } +}; + +// Not supported methods +function createMethodNotSupport(method) { + return function () { + logError$1('In IE8.0 VML mode painter not support method "' + method + '"'); + }; +} + +// Unsupported methods +each$1([ + 'getLayer', 'insertLayer', 'eachLayer', 'eachBuiltinLayer', 'eachOtherLayer', 'getLayers', + 'modLayer', 'delLayer', 'clearLayer', 'toDataURL', 'pathToImage' +], function (name) { + VMLPainter.prototype[name] = createMethodNotSupport(name); +}); + +registerPainter('vml', VMLPainter); + +var svgURI = 'http://www.w3.org/2000/svg'; + +function createElement(name) { + return document.createElementNS(svgURI, name); +} + +// TODO +// 1. shadow +// 2. Image: sx, sy, sw, sh + +var CMD$4 = PathProxy.CMD; +var arrayJoin = Array.prototype.join; + +var NONE = 'none'; +var mathRound = Math.round; +var mathSin$3 = Math.sin; +var mathCos$3 = Math.cos; +var PI$6 = Math.PI; +var PI2$6 = Math.PI * 2; +var degree = 180 / PI$6; + +var EPSILON$4 = 1e-4; + +function round4(val) { + return mathRound(val * 1e4) / 1e4; +} + +function isAroundZero$1(val) { + return val < EPSILON$4 && val > -EPSILON$4; +} + +function pathHasFill(style, isText) { + var fill = isText ? style.textFill : style.fill; + return fill != null && fill !== NONE; +} + +function pathHasStroke(style, isText) { + var stroke = isText ? style.textStroke : style.stroke; + return stroke != null && stroke !== NONE; +} + +function setTransform(svgEl, m) { + if (m) { + attr(svgEl, 'transform', 'matrix(' + arrayJoin.call(m, ',') + ')'); + } +} + +function attr(el, key, val) { + if (!val || val.type !== 'linear' && val.type !== 'radial') { + // Don't set attribute for gradient, since it need new dom nodes + el.setAttribute(key, val); + } +} + +function attrXLink(el, key, val) { + el.setAttributeNS('http://www.w3.org/1999/xlink', key, val); +} + +function bindStyle(svgEl, style, isText, el) { + if (pathHasFill(style, isText)) { + var fill = isText ? style.textFill : style.fill; + fill = fill === 'transparent' ? NONE : fill; + attr(svgEl, 'fill', fill); + attr(svgEl, 'fill-opacity', style.fillOpacity != null ? style.fillOpacity * style.opacity : style.opacity); + } + else { + attr(svgEl, 'fill', NONE); + } + + if (pathHasStroke(style, isText)) { + var stroke = isText ? style.textStroke : style.stroke; + stroke = stroke === 'transparent' ? NONE : stroke; + attr(svgEl, 'stroke', stroke); + var strokeWidth = isText + ? style.textStrokeWidth + : style.lineWidth; + var strokeScale = !isText && style.strokeNoScale + ? el.getLineScale() + : 1; + attr(svgEl, 'stroke-width', strokeWidth / strokeScale); + // stroke then fill for text; fill then stroke for others + attr(svgEl, 'paint-order', isText ? 'stroke' : 'fill'); + attr(svgEl, 'stroke-opacity', style.strokeOpacity != null ? style.strokeOpacity : style.opacity); + var lineDash = style.lineDash; + if (lineDash) { + attr(svgEl, 'stroke-dasharray', style.lineDash.join(',')); + attr(svgEl, 'stroke-dashoffset', mathRound(style.lineDashOffset || 0)); + } + else { + attr(svgEl, 'stroke-dasharray', ''); + } + + // PENDING + style.lineCap && attr(svgEl, 'stroke-linecap', style.lineCap); + style.lineJoin && attr(svgEl, 'stroke-linejoin', style.lineJoin); + style.miterLimit && attr(svgEl, 'stroke-miterlimit', style.miterLimit); + } + else { + attr(svgEl, 'stroke', NONE); + } +} + +/*************************************************** + * PATH + **************************************************/ +function pathDataToString$1(path) { + var str = []; + var data = path.data; + var dataLength = path.len(); + for (var i = 0; i < dataLength;) { + var cmd = data[i++]; + var cmdStr = ''; + var nData = 0; + switch (cmd) { + case CMD$4.M: + cmdStr = 'M'; + nData = 2; + break; + case CMD$4.L: + cmdStr = 'L'; + nData = 2; + break; + case CMD$4.Q: + cmdStr = 'Q'; + nData = 4; + break; + case CMD$4.C: + cmdStr = 'C'; + nData = 6; + break; + case CMD$4.A: + var cx = data[i++]; + var cy = data[i++]; + var rx = data[i++]; + var ry = data[i++]; + var theta = data[i++]; + var dTheta = data[i++]; + var psi = data[i++]; + var clockwise = data[i++]; + + var dThetaPositive = Math.abs(dTheta); + var isCircle = isAroundZero$1(dThetaPositive - PI2$6) + || (clockwise ? dTheta >= PI2$6 : -dTheta >= PI2$6); + + // Mapping to 0~2PI + var unifiedTheta = dTheta > 0 ? dTheta % PI2$6 : (dTheta % PI2$6 + PI2$6); + + var large = false; + if (isCircle) { + large = true; + } + else if (isAroundZero$1(dThetaPositive)) { + large = false; + } + else { + large = (unifiedTheta >= PI$6) === !!clockwise; + } + + var x0 = round4(cx + rx * mathCos$3(theta)); + var y0 = round4(cy + ry * mathSin$3(theta)); + + // It will not draw if start point and end point are exactly the same + // We need to shift the end point with a small value + // FIXME A better way to draw circle ? + if (isCircle) { + if (clockwise) { + dTheta = PI2$6 - 1e-4; + } + else { + dTheta = -PI2$6 + 1e-4; + } + + large = true; + + if (i === 9) { + // Move to (x0, y0) only when CMD.A comes at the + // first position of a shape. + // For instance, when drawing a ring, CMD.A comes + // after CMD.M, so it's unnecessary to move to + // (x0, y0). + str.push('M', x0, y0); + } + } + + var x = round4(cx + rx * mathCos$3(theta + dTheta)); + var y = round4(cy + ry * mathSin$3(theta + dTheta)); + + // FIXME Ellipse + str.push('A', round4(rx), round4(ry), + mathRound(psi * degree), +large, +clockwise, x, y); + break; + case CMD$4.Z: + cmdStr = 'Z'; + break; + case CMD$4.R: + var x = round4(data[i++]); + var y = round4(data[i++]); + var w = round4(data[i++]); + var h = round4(data[i++]); + str.push( + 'M', x, y, + 'L', x + w, y, + 'L', x + w, y + h, + 'L', x, y + h, + 'L', x, y + ); + break; + } + cmdStr && str.push(cmdStr); + for (var j = 0; j < nData; j++) { + // PENDING With scale + str.push(round4(data[i++])); + } + } + return str.join(' '); +} + +var svgPath = {}; +svgPath.brush = function (el) { + var style = el.style; + + var svgEl = el.__svgEl; + if (!svgEl) { + svgEl = createElement('path'); + el.__svgEl = svgEl; + } + + if (!el.path) { + el.createPathProxy(); + } + var path = el.path; + + if (el.__dirtyPath) { + path.beginPath(); + path.subPixelOptimize = false; + el.buildPath(path, el.shape); + el.__dirtyPath = false; + + var pathStr = pathDataToString$1(path); + if (pathStr.indexOf('NaN') < 0) { + // Ignore illegal path, which may happen such in out-of-range + // data in Calendar series. + attr(svgEl, 'd', pathStr); + } + } + + bindStyle(svgEl, style, false, el); + setTransform(svgEl, el.transform); + + if (style.text != null) { + svgTextDrawRectText(el, el.getBoundingRect()); + } + else { + removeOldTextNode(el); + } +}; + +/*************************************************** + * IMAGE + **************************************************/ +var svgImage = {}; +svgImage.brush = function (el) { + var style = el.style; + var image = style.image; + + if (image instanceof HTMLImageElement) { + var src = image.src; + image = src; + } + if (!image) { + return; + } + + var x = style.x || 0; + var y = style.y || 0; + + var dw = style.width; + var dh = style.height; + + var svgEl = el.__svgEl; + if (!svgEl) { + svgEl = createElement('image'); + el.__svgEl = svgEl; + } + + if (image !== el.__imageSrc) { + attrXLink(svgEl, 'href', image); + // Caching image src + el.__imageSrc = image; + } + + attr(svgEl, 'width', dw); + attr(svgEl, 'height', dh); + + attr(svgEl, 'x', x); + attr(svgEl, 'y', y); + + setTransform(svgEl, el.transform); + + if (style.text != null) { + svgTextDrawRectText(el, el.getBoundingRect()); + } + else { + removeOldTextNode(el); + } +}; + +/*************************************************** + * TEXT + **************************************************/ +var svgText = {}; +var _tmpTextHostRect = new BoundingRect(); +var _tmpTextBoxPos = {}; +var _tmpTextTransform = []; +var TEXT_ALIGN_TO_ANCHRO = { + left: 'start', + right: 'end', + center: 'middle', + middle: 'middle' +}; + +/** + * @param {module:zrender/Element} el + * @param {Object|boolean} [hostRect] {x, y, width, height} + * If set false, rect text is not used. + */ +var svgTextDrawRectText = function (el, hostRect) { + var style = el.style; + var elTransform = el.transform; + var needTransformTextByHostEl = el instanceof Text || style.transformText; + + el.__dirty && normalizeTextStyle(style, true); + + var text = style.text; + // Convert to string + text != null && (text += ''); + if (!needDrawText(text, style)) { + return; + } + // render empty text for svg if no text but need draw text. + text == null && (text = ''); + + // Follow the setting in the canvas renderer, if not transform the + // text, transform the hostRect, by which the text is located. + if (!needTransformTextByHostEl && elTransform) { + _tmpTextHostRect.copy(hostRect); + _tmpTextHostRect.applyTransform(elTransform); + hostRect = _tmpTextHostRect; + } + + var textSvgEl = el.__textSvgEl; + if (!textSvgEl) { + textSvgEl = createElement('text'); + el.__textSvgEl = textSvgEl; + } + + // style.font has been normalized by `normalizeTextStyle`. + var textSvgElStyle = textSvgEl.style; + var font = style.font || DEFAULT_FONT$1; + var computedFont = textSvgEl.__computedFont; + if (font !== textSvgEl.__styleFont) { + textSvgElStyle.font = textSvgEl.__styleFont = font; + // The computedFont might not be the orginal font if it is illegal font. + computedFont = textSvgEl.__computedFont = textSvgElStyle.font; + } + + var textPadding = style.textPadding; + var textLineHeight = style.textLineHeight; + + var contentBlock = el.__textCotentBlock; + if (!contentBlock || el.__dirtyText) { + contentBlock = el.__textCotentBlock = parsePlainText( + text, computedFont, textPadding, textLineHeight, style.truncate + ); + } + + var outerHeight = contentBlock.outerHeight; + var lineHeight = contentBlock.lineHeight; + + getBoxPosition(_tmpTextBoxPos, el, style, hostRect); + var baseX = _tmpTextBoxPos.baseX; + var baseY = _tmpTextBoxPos.baseY; + var textAlign = _tmpTextBoxPos.textAlign || 'left'; + var textVerticalAlign = _tmpTextBoxPos.textVerticalAlign; + + setTextTransform( + textSvgEl, needTransformTextByHostEl, elTransform, style, hostRect, baseX, baseY + ); + + var boxY = adjustTextY(baseY, outerHeight, textVerticalAlign); + var textX = baseX; + var textY = boxY; + + // TODO needDrawBg + if (textPadding) { + textX = getTextXForPadding$1(baseX, textAlign, textPadding); + textY += textPadding[0]; + } + + // `textBaseline` is set as 'middle'. + textY += lineHeight / 2; + + bindStyle(textSvgEl, style, true, el); + + // FIXME + // Add a "].join("")),u=o.find("body");c.append(s),u.attr("contenteditable","true").css({"min-height":a.height}).html(i.value||""),y.apply(l,[r,n,i,a]),g.call(l,r,t,a)})},u=function(t){var i=e("#LAY_layedit_"+t),a=i.prop("contentWindow");return[a,i]},d=function(t){return 8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),t},y=function(t,a,n,o){var r=t.document,c=e(r.body);c.on("keydown",function(t){var e=t.keyCode;if(13===e){var a=m(r),l=p(a),n=l.parentNode;if("pre"===n.tagName.toLowerCase()){if(t.shiftKey)return;return i.msg("请暂时用shift+enter"),!1}r.execCommand("formatBlock",!1,"

    ")}}),e(n).parents("form").on("submit",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on("paste",function(e){r.execCommand("formatBlock",!1,"

    "),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find("*[style]").each(function(){var t=this.style.textAlign;this.removeAttribute("style"),e(this).css({"text-align":t||""})}),t.find("table").addClass("layui-table"),t.find("script,link").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute("text"),l.selection){var r=a.text||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop("outerHTML")),a.select()}else{var r=a.toString()||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l="layedit-tool-active",n=p(m(a)),o=function(e){return t.find(".layedit-tool-"+e)};i&&i[i.hasClass(l)?"removeClass":"addClass"](l),t.find(">i").removeClass(l),o("unlink").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;"b"!==t&&"strong"!==t||o("b").addClass(l),"i"!==t&&"em"!==t||o("i").addClass(l),"u"===t&&o("u").addClass(l),"strike"===t&&o("d").addClass(l),"p"===t&&("center"===e?o("center").addClass(l):"right"===e?o("right").addClass(l):o("left").addClass(l)),"a"===t&&(o("link").addClass(l),o("unlink").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr("href"),target:l.attr("target")},function(e){var a=l[0];"A"===a.tagName?a.href=e.url:v.call(t,"a",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand("unlink")},face:function(e){x.call(this,function(i){v.call(t,"img",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use("upload",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find("input")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,"img",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||"上传失败")}})})},code:function(e){k.call(o,function(i){v.call(t,"pre",{text:i.code,"lay-lang":i.lang},e)})},help:function(){i.open({type:2,title:"帮助",area:["600px","380px"],shadeClose:!0,shade:.1,skin:"layui-layer-msg",content:["http://www.layui.com/about/layedit/help.html","no"]})}},s=a.find(".layui-layedit-tool"),u=function(){var i=e(this),a=i.attr("layedit-event"),l=i.attr("lay-command");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand("formatBlock",!1,"

    "),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(">i").on("mousedown",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)||u.call(this)}).on("click",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)&&u.call(this)}),o.on("click",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:"LAY_layedit_link",area:"350px",shade:.05,shadeClose:!0,moveType:1,title:"超链接",skin:"layui-layer-msg",content:['

      ','
    • ','','
      ','',"
      ","
    • ",'
    • ','','
      ','",'","
      ","
    • ",'
    • ','','',"
    • ","
    "].join(""),success:function(t,n){var o="submit(layedit-link-yes)";a.render("radio"),t.find(".layui-btn-primary").on("click",function(){i.close(n),l.focus()}),a.on(o,function(t){i.close(b.index),e&&e(t.field)})}});b.index=n},x=function(t){var a=function(){var t=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],e={};return layui.each(t,function(t,i){e[i]=layui.cache.dir+"images/face/"+t+".gif"}),e}();return x.hide=x.hide||function(t){"face"!==e(t.target).attr("layedit-event")&&i.close(x.index)},x.index=i.tips(function(){var t=[];return layui.each(a,function(e,i){t.push('
  1. '+e+'
  2. ')}),'
      '+t.join("")+"
    "}(),this,{tips:1,time:0,skin:"layui-box layui-util-face",maxWidth:500,success:function(l,n){l.css({marginTop:-4,marginLeft:-10}).find(".layui-clear>li").on("click",function(){t&&t({src:a[this.title],alt:this.title}),i.close(n)}),e(document).off("click",x.hide).on("click",x.hide)}})},k=function(t){var e=this,l=i.open({type:1,id:"LAY_layedit_code",area:"550px",shade:.05,shadeClose:!0,moveType:1,title:"插入代码",skin:"layui-layer-msg",content:['
      ','
    • ','','
      ','","
      ","
    • ",'
    • ','','
      ','',"
      ","
    • ",'
    • ','','',"
    • ","
    "].join(""),success:function(l,n){var o="submit(layedit-code-yes)";a.render("select"),l.find(".layui-btn-primary").on("click",function(){i.close(n),e.focus()}),a.on(o,function(e){i.close(k.index),t&&t(e.field)})}});k.index=l},C={html:'',strong:'',italic:'',underline:'',del:'',"|":'',left:'',center:'',right:'',link:'',unlink:'',face:'',image:'',code:'',help:''},w=new c;t(n,w)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/layer.js b/public/lib/layui/lay/modules/layer.js new file mode 100644 index 0000000..03e780c --- /dev/null +++ b/public/lib/layui/lay/modules/layer.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if("interactive"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.1",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'
    '+(f?r.title[0]:r.title)+"
    ":"";return r.zIndex=s,t([r.shade?'
    ':"",'
    '+(e&&2!=r.type?"":u)+'
    '+(0==r.type&&r.icon!==-1?'':"")+(1==r.type&&e?"":r.content||"")+'
    '+function(){var e=c?'':"";return r.closeBtn&&(e+=''),e}()+""+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t'+r.btn[t]+"";return'
    '+e+"
    "}():"")+(r.resize?'':"")+"
    "],u,i('
    ')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"","auto"];t.content='';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(t){s=t.find(".layui-layer-input"),s.val(e.value||"").focus(),"function"==typeof f&&f(t)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
      '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
    • '+(t[0].content||"no content")+"
    • ";i'+(t[i].content||"no content")+"";return a}()+"
    ",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
    '+(u.length>1?'':"")+'
    '+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
    ",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
    是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/laypage.js b/public/lib/layui/lay/modules/laypage.js new file mode 100644 index 0000000..2e2ab0d --- /dev/null +++ b/public/lib/layui/lay/modules/laypage.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['
    ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
    "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/laytpl.js b/public/lib/layui/lay/modules/laytpl.js new file mode 100644 index 0000000..11cc3c4 --- /dev/null +++ b/public/lib/layui/lay/modules/laytpl.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/mobile.js b/public/lib/layui/lay/modules/mobile.js new file mode 100644 index 0000000..ebda601 --- /dev/null +++ b/public/lib/layui/lay/modules/mobile.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define(function(i){i("layui.mobile",layui.v)});layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)});layui.define(function(e){"use strict";var t=(window,document),i="querySelectorAll",n="getElementsByClassName",a=function(e){return t[i](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var i in e)t[i]=e[i];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var o=0,r=["layui-m-layer"],d=function(e){var t=this;t.config=l.extend(e),t.view()};d.prototype.view=function(){var e=this,i=e.config,s=t.createElement("div");e.id=s.id=r[0]+o,s.setAttribute("class",r[0]+" "+r[0]+(i.type||0)),s.setAttribute("index",o);var l=function(){var e="object"==typeof i.title;return i.title?'

    '+(e?i.title[0]:i.title)+"

    ":""}(),d=function(){"string"==typeof i.btn&&(i.btn=[i.btn]);var e,t=(i.btn||[]).length;return 0!==t&&i.btn?(e=''+i.btn[0]+"",2===t&&(e=''+i.btn[1]+""+e),'
    '+e+"
    "):""}();if(i.fixed||(i.top=i.hasOwnProperty("top")?i.top:100,i.style=i.style||"",i.style+=" top:"+(t.body.scrollTop+i.top)+"px"),2===i.type&&(i.content='

    '+(i.content||"")+"

    "),i.skin&&(i.anim="up"),"msg"===i.skin&&(i.shade=!1),s.innerHTML=(i.shade?"
    ':"")+'
    "+l+'
    '+i.content+"
    "+d+"
    ",!i.type||2===i.type){var y=t[n](r[0]+i.type),u=y.length;u>=1&&c.close(y[0].getAttribute("index"))}document.body.appendChild(s);var m=e.elem=a("#"+e.id)[0];i.success&&i.success(m),e.index=o++,e.action(i,m)},d.prototype.action=function(e,t){var i=this;e.time&&(l.timer[i.index]=setTimeout(function(){c.close(i.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),c.close(i.index)):e.yes?e.yes(i.index):c.close(i.index)};if(e.btn)for(var s=t[n]("layui-m-layerbtn")[0].children,o=s.length,r=0;r0&&e-1 in t)}function s(t){return A.call(t,function(t){return null!=t})}function u(t){return t.length>0?T.fn.concat.apply([],t):t}function c(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(t){return t in F?F[t]:F[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function f(t,e){return"number"!=typeof e||k[c(t)]?e:e+"px"}function h(t){var e,n;return $[t]||(e=L.createElement(t),L.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),$[t]=n),$[t]}function p(t){return"children"in t?D.call(t.children):T.map(t.childNodes,function(t){if(1==t.nodeType)return t})}function d(t,e){var n,r=t?t.length:0;for(n=0;n]*>/,R=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Z=/^(?:body|html)$/i,q=/([A-Z])/g,H=["val","css","html","text","data","width","height","offset"],I=["after","prepend","before","append"],V=L.createElement("table"),_=L.createElement("tr"),B={tr:L.createElement("tbody"),tbody:V,thead:V,tfoot:V,td:_,th:_,"*":L.createElement("div")},U=/complete|loaded|interactive/,X=/^[\w-]*$/,J={},W=J.toString,Y={},G=L.createElement("div"),K={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},Q=Array.isArray||function(t){return t instanceof Array};return Y.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=G).appendChild(t),r=~Y.qsa(i,e).indexOf(t),o&&G.removeChild(t),r},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return A.call(t,function(e,n){return t.indexOf(e)==n})},Y.fragment=function(t,e,n){var r,i,a;return R.test(t)&&(r=T(L.createElement(RegExp.$1))),r||(t.replace&&(t=t.replace(z,"<$1>")),e===E&&(e=M.test(t)&&RegExp.$1),e in B||(e="*"),a=B[e],a.innerHTML=""+t,r=T.each(D.call(a.childNodes),function(){a.removeChild(this)})),o(n)&&(i=T(r),T.each(n,function(t,e){H.indexOf(t)>-1?i[t](e):i.attr(t,e)})),r},Y.Z=function(t,e){return new d(t,e)},Y.isZ=function(t){return t instanceof Y.Z},Y.init=function(t,n){var r;if(!t)return Y.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&M.test(t))r=Y.fragment(t,RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}else{if(e(t))return T(L).ready(t);if(Y.isZ(t))return t;if(Q(t))r=s(t);else if(i(t))r=[t],t=null;else if(M.test(t))r=Y.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}}return Y.Z(r,t)},T=function(t,e){return Y.init(t,e)},T.extend=function(t){var e,n=D.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){m(t,n,e)}),t},Y.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,a=X.test(o);return t.getElementById&&a&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:D.call(a&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},T.contains=L.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},T.type=t,T.isFunction=e,T.isWindow=n,T.isArray=Q,T.isPlainObject=o,T.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},T.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},T.inArray=function(t,e,n){return O.indexOf.call(e,t,n)},T.camelCase=C,T.trim=function(t){return null==t?"":String.prototype.trim.call(t)},T.uuid=0,T.support={},T.expr={},T.noop=function(){},T.map=function(t,e){var n,r,i,o=[];if(a(t))for(r=0;r=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return O.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return e(t)?this.not(this.not(t)):T(A.call(this,function(e){return Y.matches(e,t)}))},add:function(t,e){return T(N(this.concat(T(t,e))))},is:function(t){return this.length>0&&Y.matches(this[0],t)},not:function(t){var n=[];if(e(t)&&t.call!==E)this.each(function(e){t.call(this,e)||n.push(this)});else{var r="string"==typeof t?this.filter(t):a(t)&&e(t.item)?D.call(t):T(t);this.forEach(function(t){r.indexOf(t)<0&&n.push(t)})}return T(n)},has:function(t){return this.filter(function(){return i(t)?T.contains(this,t):T(this).find(t).size()})},eq:function(t){return t===-1?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!i(t)?t:T(t)},last:function(){var t=this[this.length-1];return t&&!i(t)?t:T(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?T(t).filter(function(){var t=this;return O.some.call(n,function(e){return T.contains(e,t)})}):1==this.length?T(Y.qsa(this[0],t)):this.map(function(){return Y.qsa(this,t)}):T()},closest:function(t,e){var n=[],i="object"==typeof t&&T(t);return this.each(function(o,a){for(;a&&!(i?i.indexOf(a)>=0:Y.matches(a,t));)a=a!==e&&!r(a)&&a.parentNode;a&&n.indexOf(a)<0&&n.push(a)}),T(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=T.map(n,function(t){if((t=t.parentNode)&&!r(t)&&e.indexOf(t)<0)return e.push(t),t});return v(e,t)},parent:function(t){return v(N(this.pluck("parentNode")),t)},children:function(t){return v(this.map(function(){return p(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||D.call(this.childNodes)})},siblings:function(t){return v(this.map(function(t,e){return A.call(p(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return T.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=h(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var n=e(t);if(this[0]&&!n)var r=T(t).get(0),i=r.parentNode||this.length>1;return this.each(function(e){T(this).wrapAll(n?t.call(this,e):i?r.cloneNode(!0):r)})},wrapAll:function(t){if(this[0]){T(this[0]).before(t=T(t));for(var e;(e=t.children()).length;)t=e.first();T(t).append(this)}return this},wrapInner:function(t){var n=e(t);return this.each(function(e){var r=T(this),i=r.contents(),o=n?t.call(this,e):t;i.length?i.wrapAll(o):r.append(o)})},unwrap:function(){return this.parent().each(function(){T(this).replaceWith(T(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var e=T(this);(t===E?"none"==e.css("display"):t)?e.show():e.hide()})},prev:function(t){return T(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return T(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;T(this).empty().append(g(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=g(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,e){var n;return"string"!=typeof t||1 in arguments?this.each(function(n){if(1===this.nodeType)if(i(t))for(j in t)y(this,j,t[j]);else y(this,t,g(this,e,n,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(n=this[0].getAttribute(t))?n:E},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){y(this,t)},this)})},prop:function(t,e){return t=K[t]||t,1 in arguments?this.each(function(n){this[t]=g(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=K[t]||t,this.each(function(){delete this[t]})},data:function(t,e){var n="data-"+t.replace(q,"-$1").toLowerCase(),r=1 in arguments?this.attr(n,e):this.attr(n);return null!==r?b(r):E},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=g(this,t,e,this.value)})):this[0]&&(this[0].multiple?T(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var n=T(this),r=g(this,t,e,n.offset()),i=n.offsetParent().offset(),o={top:r.top-i.top,left:r.left-i.left};"static"==n.css("position")&&(o.position="relative"),n.css(o)});if(!this.length)return null;if(L.documentElement!==this[0]&&!T.contains(L.documentElement,this[0]))return{top:0,left:0};var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(e,n){if(arguments.length<2){var r=this[0];if("string"==typeof e){if(!r)return;return r.style[C(e)]||getComputedStyle(r,"").getPropertyValue(e)}if(Q(e)){if(!r)return;var i={},o=getComputedStyle(r,"");return T.each(e,function(t,e){i[e]=r.style[C(e)]||o.getPropertyValue(e)}),i}}var a="";if("string"==t(e))n||0===n?a=c(e)+":"+f(e,n):this.each(function(){this.style.removeProperty(c(e))});else for(j in e)e[j]||0===e[j]?a+=c(j)+":"+f(j,e[j])+";":this.each(function(){this.style.removeProperty(c(j))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(T(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return!!t&&O.some.call(this,function(t){return this.test(x(t))},l(t))},addClass:function(t){return t?this.each(function(e){if("className"in this){S=[];var n=x(this),r=g(this,t,e,n);r.split(/\s+/g).forEach(function(t){T(this).hasClass(t)||S.push(t)},this),S.length&&x(this,n+(n?" ":"")+S.join(" "))}}):this},removeClass:function(t){return this.each(function(e){if("className"in this){if(t===E)return x(this,"");S=x(this),g(this,t,e,S).split(/\s+/g).forEach(function(t){S=S.replace(l(t)," ")}),x(this,S.trim())}})},toggleClass:function(t,e){return t?this.each(function(n){var r=T(this),i=g(this,t,n,x(this));i.split(/\s+/g).forEach(function(t){(e===E?!r.hasClass(t):e)?r.addClass(t):r.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var e="scrollTop"in this[0];return t===E?e?this[0].scrollTop:this[0].pageYOffset:this.each(e?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var e="scrollLeft"in this[0];return t===E?e?this[0].scrollLeft:this[0].pageXOffset:this.each(e?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),r=Z.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(T(t).css("margin-top"))||0,n.left-=parseFloat(T(t).css("margin-left"))||0,r.top+=parseFloat(T(e[0]).css("border-top-width"))||0,r.left+=parseFloat(T(e[0]).css("border-left-width"))||0,{top:n.top-r.top,left:n.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||L.body;t&&!Z.test(t.nodeName)&&"static"==T(t).css("position");)t=t.offsetParent;return t})}},T.fn.detach=T.fn.remove,["width","height"].forEach(function(t){var e=t.replace(/./,function(t){return t[0].toUpperCase()});T.fn[t]=function(i){var o,a=this[0];return i===E?n(a)?a["inner"+e]:r(a)?a.documentElement["scroll"+e]:(o=this.offset())&&o[t]:this.each(function(e){a=T(this),a.css(t,g(this,i,e,a[t]()))})}}),I.forEach(function(e,n){var r=n%2;T.fn[e]=function(){var e,i,o=T.map(arguments,function(n){var r=[];return e=t(n),"array"==e?(n.forEach(function(t){return t.nodeType!==E?r.push(t):T.zepto.isZ(t)?r=r.concat(t.get()):void(r=r.concat(Y.fragment(t)))}),r):"object"==e||null==n?n:Y.fragment(n)}),a=this.length>1;return o.length<1?this:this.each(function(t,e){i=r?e:e.parentNode,e=0==n?e.nextSibling:1==n?e.firstChild:2==n?e:null;var s=T.contains(L.documentElement,i);o.forEach(function(t){if(a)t=t.cloneNode(!0);else if(!i)return T(t).remove();i.insertBefore(t,e),s&&w(t,function(t){if(!(null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src)){var e=t.ownerDocument?t.ownerDocument.defaultView:window;e.eval.call(e,t.innerHTML)}})})})},T.fn[r?e+"To":"insert"+(n?"Before":"After")]=function(t){return T(t)[e](this),this}}),Y.Z.prototype=d.prototype=T.fn,Y.uniq=N,Y.deserializeValue=b,T.zepto=Y,T}();!function(t){function e(t){return t._zid||(t._zid=h++)}function n(t,n,o,a){if(n=r(n),n.ns)var s=i(n.ns);return(v[e(t)]||[]).filter(function(t){return t&&(!n.e||t.e==n.e)&&(!n.ns||s.test(t.ns))&&(!o||e(t.fn)===e(o))&&(!a||t.sel==a)})}function r(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function i(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function o(t,e){return t.del&&!y&&t.e in x||!!e}function a(t){return b[t]||y&&x[t]||t}function s(n,i,s,u,l,h,p){var d=e(n),m=v[d]||(v[d]=[]);i.split(/\s/).forEach(function(e){if("ready"==e)return t(document).ready(s);var i=r(e);i.fn=s,i.sel=l,i.e in b&&(s=function(e){var n=e.relatedTarget;if(!n||n!==this&&!t.contains(this,n))return i.fn.apply(this,arguments)}),i.del=h;var d=h||s;i.proxy=function(t){if(t=c(t),!t.isImmediatePropagationStopped()){t.data=u;var e=d.apply(n,t._args==f?[t]:[t].concat(t._args));return e===!1&&(t.preventDefault(),t.stopPropagation()),e}},i.i=m.length,m.push(i),"addEventListener"in n&&n.addEventListener(a(i.e),i.proxy,o(i,p))})}function u(t,r,i,s,u){var c=e(t);(r||"").split(/\s/).forEach(function(e){n(t,e,i,s).forEach(function(e){delete v[c][e.i],"removeEventListener"in t&&t.removeEventListener(a(e.e),e.proxy,o(e,u))})})}function c(e,n){return!n&&e.isDefaultPrevented||(n||(n=e),t.each(T,function(t,r){var i=n[t];e[t]=function(){return this[r]=w,i&&i.apply(n,arguments)},e[r]=E}),e.timeStamp||(e.timeStamp=Date.now()),(n.defaultPrevented!==f?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(e.isDefaultPrevented=w)),e}function l(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===f||(n[e]=t[e]);return c(n,t)}var f,h=1,p=Array.prototype.slice,d=t.isFunction,m=function(t){return"string"==typeof t},v={},g={},y="onfocusin"in window,x={focus:"focusin",blur:"focusout"},b={mouseenter:"mouseover",mouseleave:"mouseout"};g.click=g.mousedown=g.mouseup=g.mousemove="MouseEvents",t.event={add:s,remove:u},t.proxy=function(n,r){var i=2 in arguments&&p.call(arguments,2);if(d(n)){var o=function(){return n.apply(r,i?i.concat(p.call(arguments)):arguments)};return o._zid=e(n),o}if(m(r))return i?(i.unshift(n[r],n),t.proxy.apply(null,i)):t.proxy(n[r],n);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var w=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,T={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,n,r,i,o){var a,c,h=this;return e&&!m(e)?(t.each(e,function(t,e){h.on(t,n,r,e,o)}),h):(m(n)||d(i)||i===!1||(i=r,r=n,n=f),i!==f&&r!==!1||(i=r,r=f),i===!1&&(i=E),h.each(function(f,h){o&&(a=function(t){return u(h,t.type,i),i.apply(this,arguments)}),n&&(c=function(e){var r,o=t(e.target).closest(n,h).get(0);if(o&&o!==h)return r=t.extend(l(e),{currentTarget:o,liveFired:h}),(a||i).apply(o,[r].concat(p.call(arguments,1)))}),s(h,e,i,r,n,c||a)}))},t.fn.off=function(e,n,r){var i=this;return e&&!m(e)?(t.each(e,function(t,e){i.off(t,n,e)}),i):(m(n)||d(r)||r===!1||(r=n,n=f),r===!1&&(r=E),i.each(function(){u(this,e,r,n)}))},t.fn.trigger=function(e,n){return e=m(e)||t.isPlainObject(e)?t.Event(e):c(e),e._args=n,this.each(function(){e.type in x&&"function"==typeof this[e.type]?this[e.type]():"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,r){var i,o;return this.each(function(a,s){i=l(m(e)?t.Event(e):e),i._args=r,i.target=s,t.each(n(s,e.type||e),function(t,e){if(o=e.proxy(i),i.isImmediatePropagationStopped())return!1})}),o},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return 0 in arguments?this.bind(e,t):this.trigger(e)}}),t.Event=function(t,e){m(t)||(e=t,t=e.type);var n=document.createEvent(g[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),c(n)}}(e),function(t){function e(e,n,r){var i=t.Event(n);return t(e).trigger(i,r),!i.isDefaultPrevented()}function n(t,n,r,i){if(t.global)return e(n||x,r,i)}function r(e){e.global&&0===t.active++&&n(e,null,"ajaxStart")}function i(e){e.global&&!--t.active&&n(e,null,"ajaxStop")}function o(t,e){var r=e.context;return e.beforeSend.call(r,t,e)!==!1&&n(e,r,"ajaxBeforeSend",[t,e])!==!1&&void n(e,r,"ajaxSend",[t,e])}function a(t,e,r,i){var o=r.context,a="success";r.success.call(o,t,a,e),i&&i.resolveWith(o,[t,a,e]),n(r,o,"ajaxSuccess",[e,r,t]),u(a,e,r)}function s(t,e,r,i,o){var a=i.context;i.error.call(a,r,e,t),o&&o.rejectWith(a,[r,e,t]),n(i,a,"ajaxError",[r,i,t||e]),u(e,r,i)}function u(t,e,r){var o=r.context;r.complete.call(o,e,t),n(r,o,"ajaxComplete",[e,r]),i(r)}function c(t,e,n){if(n.dataFilter==l)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function l(){}function f(t){return t&&(t=t.split(";",2)[0]),t&&(t==T?"html":t==j?"json":w.test(t)?"script":E.test(t)&&"xml")||"text"}function h(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function p(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()&&"jsonp"!=e.dataType||(e.url=h(e.url,e.data),e.data=void 0)}function d(e,n,r,i){return t.isFunction(n)&&(i=r,r=n,n=void 0),t.isFunction(r)||(i=r,r=void 0),{url:e,data:n,success:r,dataType:i}}function m(e,n,r,i){var o,a=t.isArray(n),s=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),i&&(n=r?i:i+"["+(s||"object"==o||"array"==o?n:"")+"]"),!i&&a?e.add(u.name,u.value):"array"==o||!r&&"object"==o?m(e,u,r,n):e.add(n,u)})}var v,g,y=+new Date,x=window.document,b=/)<[^<]*)*<\/script>/gi,w=/^(?:text|application)\/javascript/i,E=/^(?:text|application)\/xml/i,j="application/json",T="text/html",S=/^\s*$/,C=x.createElement("a");C.href=window.location.href,t.active=0,t.ajaxJSONP=function(e,n){if(!("type"in e))return t.ajax(e);var r,i,u=e.jsonpCallback,c=(t.isFunction(u)?u():u)||"Zepto"+y++,l=x.createElement("script"),f=window[c],h=function(e){t(l).triggerHandler("error",e||"abort")},p={abort:h};return n&&n.promise(p),t(l).on("load error",function(o,u){clearTimeout(i),t(l).off().remove(),"error"!=o.type&&r?a(r[0],p,e,n):s(null,u||"error",p,e,n),window[c]=f,r&&t.isFunction(f)&&f(r[0]),f=r=void 0}),o(p,e)===!1?(h("abort"),p):(window[c]=function(){r=arguments},l.src=e.url.replace(/\?(.+)=\?/,"?$1="+c),x.head.appendChild(l),e.timeout>0&&(i=setTimeout(function(){h("timeout")},e.timeout)),p)},t.ajaxSettings={type:"GET",beforeSend:l,success:l,error:l,complete:l,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:j,xml:"application/xml, text/xml",html:T,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0,dataFilter:l},t.ajax=function(e){var n,i,u=t.extend({},e||{}),d=t.Deferred&&t.Deferred();for(v in t.ajaxSettings)void 0===u[v]&&(u[v]=t.ajaxSettings[v]);r(u),u.crossDomain||(n=x.createElement("a"),n.href=u.url,n.href=n.href,u.crossDomain=C.protocol+"//"+C.host!=n.protocol+"//"+n.host),u.url||(u.url=window.location.toString()),(i=u.url.indexOf("#"))>-1&&(u.url=u.url.slice(0,i)),p(u);var m=u.dataType,y=/\?.+=\?/.test(u.url);if(y&&(m="jsonp"),u.cache!==!1&&(e&&e.cache===!0||"script"!=m&&"jsonp"!=m)||(u.url=h(u.url,"_="+Date.now())),"jsonp"==m)return y||(u.url=h(u.url,u.jsonp?u.jsonp+"=?":u.jsonp===!1?"":"callback=?")),t.ajaxJSONP(u,d);var b,w=u.accepts[m],E={},j=function(t,e){E[t.toLowerCase()]=[t,e]},T=/^([\w-]+:)\/\//.test(u.url)?RegExp.$1:window.location.protocol,N=u.xhr(),O=N.setRequestHeader;if(d&&d.promise(N),u.crossDomain||j("X-Requested-With","XMLHttpRequest"),j("Accept",w||"*/*"),(w=u.mimeType||w)&&(w.indexOf(",")>-1&&(w=w.split(",",2)[0]),N.overrideMimeType&&N.overrideMimeType(w)),(u.contentType||u.contentType!==!1&&u.data&&"GET"!=u.type.toUpperCase())&&j("Content-Type",u.contentType||"application/x-www-form-urlencoded"),u.headers)for(g in u.headers)j(g,u.headers[g]);if(N.setRequestHeader=j,N.onreadystatechange=function(){if(4==N.readyState){N.onreadystatechange=l,clearTimeout(b);var e,n=!1;if(N.status>=200&&N.status<300||304==N.status||0==N.status&&"file:"==T){if(m=m||f(u.mimeType||N.getResponseHeader("content-type")),"arraybuffer"==N.responseType||"blob"==N.responseType)e=N.response;else{e=N.responseText;try{e=c(e,m,u),"script"==m?(0,eval)(e):"xml"==m?e=N.responseXML:"json"==m&&(e=S.test(e)?null:t.parseJSON(e))}catch(r){n=r}if(n)return s(n,"parsererror",N,u,d)}a(e,N,u,d)}else s(N.statusText||null,N.status?"error":"abort",N,u,d)}},o(N,u)===!1)return N.abort(),s(null,"abort",N,u,d),N;var P=!("async"in u)||u.async;if(N.open(u.type,u.url,P,u.username,u.password),u.xhrFields)for(g in u.xhrFields)N[g]=u.xhrFields[g];for(g in E)O.apply(N,E[g]);return u.timeout>0&&(b=setTimeout(function(){N.onreadystatechange=l,N.abort(),s(null,"timeout",N,u,d)},u.timeout)),N.send(u.data?u.data:null),N},t.get=function(){return t.ajax(d.apply(null,arguments))},t.post=function(){var e=d.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=d.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,r){if(!this.length)return this;var i,o=this,a=e.split(/\s/),s=d(e,n,r),u=s.success;return a.length>1&&(s.url=a[0],i=a[1]),s.success=function(e){o.html(i?t("
    ").html(e.replace(b,"")).find(i):e),u&&u.apply(o,arguments)},t.ajax(s),this};var N=encodeURIComponent;t.param=function(e,n){var r=[];return r.add=function(e,n){t.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(N(e)+"="+N(n))},m(r,e,n),r.join("&").replace(/%20/g,"+")}}(e),function(t){t.fn.serializeArray=function(){var e,n,r=[],i=function(t){return t.forEach?t.forEach(i):void r.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(r,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&i(t(o).val())}),r},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(e),function(){try{getComputedStyle(void 0)}catch(t){var e=getComputedStyle;window.getComputedStyle=function(t,n){try{return e(t,n)}catch(r){return null}}}}(),t("zepto",e)});layui.define(function(i){i("layim-mobile",layui.v)});layui["layui.mobile"]||layui.config({base:layui.cache.dir+"lay/modules/mobile/"}).extend({"layer-mobile":"layer-mobile",zepto:"zepto","upload-mobile":"upload-mobile","layim-mobile":"layim-mobile"}),layui.define(["layer-mobile","zepto","layim-mobile"],function(l){l("mobile",{layer:layui["layer-mobile"],layim:layui["layim-mobile"]})}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/rate.js b/public/lib/layui/lay/modules/rate.js new file mode 100644 index 0000000..7feef34 --- /dev/null +++ b/public/lib/layui/lay/modules/rate.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define("jquery",function(e){"use strict";var a=layui.jquery,i={config:{},index:layui.rate?layui.rate.index+1e4:0,set:function(e){var i=this;return i.config=a.extend({},i.config,e),i},on:function(e,a){return layui.onevent.call(this,n,e,a)}},l=function(){var e=this,a=e.config;return{setvalue:function(a){e.setvalue.call(e,a)},config:a}},n="rate",t="layui-rate",o="layui-icon-rate",s="layui-icon-rate-solid",u="layui-icon-rate-half",r="layui-icon-rate-solid layui-icon-rate-half",c="layui-icon-rate-solid layui-icon-rate",f="layui-icon-rate layui-icon-rate-half",v=function(e){var l=this;l.index=++i.index,l.config=a.extend({},l.config,i.config,e),l.render()};v.prototype.config={length:5,text:!1,readonly:!1,half:!1,value:0,theme:""},v.prototype.render=function(){var e=this,i=e.config,l=i.theme?'style="color: '+i.theme+';"':"";i.elem=a(i.elem),parseInt(i.value)!==i.value&&(i.half||(i.value=Math.ceil(i.value)-i.value<.5?Math.ceil(i.value):Math.floor(i.value)));for(var n='
      ",u=1;u<=i.length;u++){var r='
    • ";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'
    • ":n+=r}n+="
    "+(i.text?''+i.value+"星":"")+"";var c=i.elem,f=c.next("."+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next("span"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass("layui-inline"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find("i").width();l.children("li").each(function(e){var t=e+1,v=a(this);v.on("click",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next("span").text(i.value+"星"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on("mousemove",function(e){if(l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+t+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children("i").addClass(u).removeClass(s)}}),v.on("mouseleave",function(){l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+Math.floor(i.value)+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children("li:eq("+Math.floor(i.value)+")").children("i").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/slider.js b/public/lib/layui/lay/modules/slider.js new file mode 100644 index 0000000..6f824a9 --- /dev/null +++ b/public/lib/layui/lay/modules/slider.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define("jquery",function(e){"use strict";var i=layui.jquery,t={config:{},index:layui.slider?layui.slider.index+1e4:0,set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,n,e,i)}},a=function(){var e=this,i=e.config;return{setValue:function(i,t){return e.slide("set",i,t||0)},config:i}},n="slider",l="layui-disabled",s="layui-slider",r="layui-slider-bar",o="layui-slider-wrap",u="layui-slider-wrap-btn",d="layui-slider-tips",v="layui-slider-input",c="layui-slider-input-txt",m="layui-slider-input-btn",p="layui-slider-hover",f=function(e){var a=this;a.index=++t.index,a.config=i.extend({},a.config,t.config,e),a.render()};f.prototype.config={type:"default",min:0,max:100,value:0,step:1,showstep:!1,tips:!0,input:!1,range:!1,height:200,disabled:!1,theme:"#009688"},f.prototype.render=function(){var e=this,t=e.config;if(t.step<1&&(t.step=1),t.maxt.min?a:t.min,t.value[1]=n>t.min?n:t.min,t.value[0]=t.value[0]>t.max?t.max:t.value[0],t.value[1]=t.value[1]>t.max?t.max:t.value[1];var r=Math.floor((t.value[0]-t.min)/(t.max-t.min)*100),v=Math.floor((t.value[1]-t.min)/(t.max-t.min)*100),m=v-r+"%";r+="%",v+="%"}else{"object"==typeof t.value&&(t.value=Math.min.apply(null,t.value)),t.valuet.max&&(t.value=t.max);var m=Math.floor((t.value-t.min)/(t.max-t.min)*100)+"%"}var p=t.disabled?"#c2c2c2":t.theme,f='
    '+(t.tips?'
    ':"")+'
    '+(t.range?'
    ':"")+"
    ",h=i(t.elem),y=h.next("."+s);if(y[0]&&y.remove(),e.elemTemp=i(f),t.range?(e.elemTemp.find("."+o).eq(0).data("value",t.value[0]),e.elemTemp.find("."+o).eq(1).data("value",t.value[1])):e.elemTemp.find("."+o).data("value",t.value),h.html(e.elemTemp),"vertical"===t.type&&e.elemTemp.height(t.height+"px"),t.showstep){for(var g=(t.max-t.min)/t.step,b="",x=1;x
    ')}e.elemTemp.append(b)}if(t.input&&!t.range){var w=i('
    ');h.css("position","relative"),h.append(w),h.find("."+c).children("input").val(t.value),"vertical"===t.type?w.css({left:0,top:-48}):e.elemTemp.css("margin-right",w.outerWidth()+15)}t.disabled?(e.elemTemp.addClass(l),e.elemTemp.find("."+u).addClass(l)):e.slide(),e.elemTemp.find("."+u).on("mouseover",function(){var a="vertical"===t.type?t.height:e.elemTemp[0].offsetWidth,n=e.elemTemp.find("."+o),l="vertical"===t.type?a-i(this).parent()[0].offsetTop-n.height():i(this).parent()[0].offsetLeft,s=l/a*100,r=i(this).parent().data("value"),u=t.setTips?t.setTips(r):r;e.elemTemp.find("."+d).html(u),"vertical"===t.type?e.elemTemp.find("."+d).css({bottom:s+"%","margin-bottom":"20px",display:"inline-block"}):e.elemTemp.find("."+d).css({left:s+"%",display:"inline-block"})}).on("mouseout",function(){e.elemTemp.find("."+d).css("display","none")})},f.prototype.slide=function(e,t,a){var n=this,l=n.config,s=n.elemTemp,f=function(){return"vertical"===l.type?l.height:s[0].offsetWidth},h=s.find("."+o),y=s.next("."+v),g=y.children("."+c).children("input").val(),b=100/((l.max-l.min)/Math.ceil(l.step)),x=function(e,i){e=Math.ceil(e)*b>100?Math.ceil(e)*b:Math.round(e)*b,e=e>100?100:e,h.eq(i).css("vertical"===l.type?"bottom":"left",e+"%");var t=T(h[0].offsetLeft),a=l.range?T(h[1].offsetLeft):0;"vertical"===l.type?(s.find("."+d).css({bottom:e+"%","margin-bottom":"20px"}),t=T(f()-h[0].offsetTop-h.height()),a=l.range?T(f()-h[1].offsetTop-h.height()):0):s.find("."+d).css("left",e+"%"),t=t>100?100:t,a=a>100?100:a;var n=Math.min(t,a),o=Math.abs(t-a);"vertical"===l.type?s.find("."+r).css({height:o+"%",bottom:n+"%"}):s.find("."+r).css({width:o+"%",left:n+"%"});var u=l.min+Math.round((l.max-l.min)*e/100);if(g=u,y.children("."+c).children("input").val(g),h.eq(i).data("value",u),u=l.setTips?l.setTips(u):u,s.find("."+d).html(u),l.range){var v=[h.eq(0).data("value"),h.eq(1).data("value")];v[0]>v[1]&&v.reverse()}l.change&&l.change(l.range?v:u)},T=function(e){var i=e/f()*100/b,t=Math.round(i)*b;return e==f()&&(t=Math.ceil(i)*b),t},w=i(['
    f()&&(r=f());var o=r/f()*100/b;x(o,e),t.addClass(p),s.find("."+d).show(),i.preventDefault()},o=function(){t.removeClass(p),s.find("."+d).hide()};M(r,o)})}),s.on("click",function(e){var t=i("."+u);if(!t.is(event.target)&&0===t.has(event.target).length&&t.length){var a,n="vertical"===l.type?f()-e.clientY+i(this).offset().top:e.clientX-i(this).offset().left;n<0&&(n=0),n>f()&&(n=f());var s=n/f()*100/b;a=l.range?"vertical"===l.type?Math.abs(n-parseInt(i(h[0]).css("bottom")))>Math.abs(n-parseInt(i(h[1]).css("bottom")))?1:0:Math.abs(n-h[0].offsetLeft)>Math.abs(n-h[1].offsetLeft)?1:0:0,x(s,a),e.preventDefault()}}),y.hover(function(){var e=i(this);e.children("."+m).fadeIn("fast")},function(){var e=i(this);e.children("."+m).fadeOut("fast")}),y.children("."+m).children("i").each(function(e){i(this).on("click",function(){g=1==e?g-l.stepl.max?l.max:Number(g)+l.step;var i=(g-l.min)/(l.max-l.min)*100/b;x(i,0)})});var q=function(){var e=this.value;e=isNaN(e)?0:e,e=el.max?l.max:e,this.value=e;var i=(e-l.min)/(l.max-l.min)*100/b;x(i,0)};y.children("."+c).children("input").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),q.call(this))}).on("change",q)},f.prototype.events=function(){var e=this;e.config},t.render=function(e){var i=new f(e);return a.call(i)},e(n,t)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/table.js b/public/lib/layui/lay/modules/table.js new file mode 100644 index 0000000..0882b71 --- /dev/null +++ b/public/lib/layui/lay/modules/table.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define(["laytpl","laypage","layer","form","util"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=(layui.util,layui.hint()),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,y,e,t)}},c=function(){var e=this,t=e.config,i=t.id||t.index;return i&&(c.that[i]=e,c.config[i]=t),{config:t,reload:function(t){e.reload.call(e,t)},setColsWidth:function(){e.setColsWidth.call(e)},resize:function(){e.resize.call(e)}}},s=function(e){var t=c.config[e];return t||o.error("The ID option was not found in the table instance"),t||null},u=function(e,a,l,n){var o=e.templet?function(){return"function"==typeof e.templet?e.templet(l):i(t(e.templet).html()||String(a)).render(l)}():a;return n?t("
    "+o+"
    ").text():o},y="table",h=".layui-table",f="layui-hide",p="layui-none",v="layui-table-view",m=".layui-table-tool",g=".layui-table-box",b=".layui-table-init",x=".layui-table-header",k=".layui-table-body",C=".layui-table-main",w=".layui-table-fixed",T=".layui-table-fixed-l",A=".layui-table-fixed-r",L=".layui-table-total",N=".layui-table-page",S=".layui-table-sort",R="layui-table-edit",W="layui-table-hover",_=function(e){var t='{{#if(item2.colspan){}} colspan="{{item2.colspan}}"{{#} if(item2.rowspan){}} rowspan="{{item2.rowspan}}"{{#}}}';return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),"{{# var isSort = !(item2.colGroup) && item2.sort; }}",'",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
    ','
    ','{{# if(item2.type === "checkbox"){ }}','',"{{# } else { }}",'{{item2.title||""}}',"{{# if(isSort){ }}",'',"{{# } }}","{{# } }}","
    ","
    "].join("")},E=['',"","
    "].join(""),z=['
    ',"{{# if(d.data.toolbar){ }}",'
    ','
    ','
    ',"
    ","{{# } }}",'
    ',"{{# if(d.data.loading){ }}",'
    ','',"
    ","{{# } }}","{{# var left, right; }}",'
    ',_(),"
    ",'
    ',E,"
    ","{{# if(left){ }}",'
    ','
    ',_({fixed:!0}),"
    ",'
    ',E,"
    ","
    ","{{# }; }}","{{# if(right){ }}",'
    ','
    ',_({fixed:"right"}),'
    ',"
    ",'
    ',E,"
    ","
    ","{{# }; }}","
    ","{{# if(d.data.totalRow){ }}",'
    ','','',"
    ","
    ","{{# } }}","{{# if(d.data.page){ }}",'
    ','
    ',"
    ","{{# } }}","","
    "].join(""),H=t(window),j=t(document),F=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};F.prototype.config={limit:10,loading:!0,cellMinWidth:60,defaultToolbar:["filter","exports","print"],autoSort:!0,text:{none:"无数据"}},F.prototype.render=function(){var e=this,a=e.config;if(a.elem=t(a.elem),a.where=a.where||{},a.id=a.id||a.elem.attr("id")||e.index,a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",totalRowName:"totalRow",countName:"count"},a.response),"object"==typeof a.page&&(a.limit=a.page.limit||a.limit,a.limits=a.page.limits||a.limits,e.page=a.page.curr=a.page.curr||1,delete a.page.elem,delete a.page.jump),!a.elem[0])return e;a.height&&/^full-\d+$/.test(a.height)&&(e.fullHeightGap=a.height.split("-")[1],a.height=H.height()-e.fullHeightGap),e.setInit();var l=a.elem,n=l.next("."+v),o=e.elem=t(i(z).render({VIEW_CLASS:v,data:a,index:e.index}));if(a.index=e.index,e.key=a.id||a.index,n[0]&&n.remove(),l.after(o),e.layTool=o.find(m),e.layBox=o.find(g),e.layHeader=o.find(x),e.layMain=o.find(C),e.layBody=o.find(k),e.layFixed=o.find(w),e.layFixLeft=o.find(T),e.layFixRight=o.find(A),e.layTotal=o.find(L),e.layPage=o.find(N),e.renderToolbar(),e.fullSize(),a.cols.length>1){var r=e.layFixed.find(x).find("th");r.height(e.layHeader.height()-1-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom")))}e.pullData(e.page),e.events()},F.prototype.initOpts=function(e){var t=this,i=(t.config,{checkbox:48,radio:48,space:15,numbers:40});e.checkbox&&(e.type="checkbox"),e.space&&(e.type="space"),e.type||(e.type="normal"),"normal"!==e.type&&(e.unresize=!0,e.width=e.width||i[e.type])},F.prototype.setInit=function(e){var t=this,i=t.config;return i.clientWidth=i.width||function(){var e=function(t){var a,l;t=t||i.elem.parent(),a=t.width();try{l="none"===t.css("display")}catch(n){}return!t[0]||a&&!l?a:e(t.parent())};return e()}(),"width"===e?i.clientWidth:void layui.each(i.cols,function(e,a){layui.each(a,function(l,n){if(!n)return void a.splice(l,1);if(n.key=e+"-"+l,n.hide=n.hide||!1,n.colGroup||n.colspan>1){var o=0;layui.each(i.cols[e+1],function(t,i){i.HAS_PARENT||o>1&&o==n.colspan||(i.HAS_PARENT=!0,i.parentKey=e+"-"+l,o+=parseInt(i.colspan>1?i.colspan:1))}),n.colGroup=!0}t.initOpts(n)})})},F.prototype.renderToolbar=function(){var e=this,a=e.config,l=['
    ','
    ','
    '].join(""),n=e.layTool.find(".layui-table-tool-temp");if("default"===a.toolbar)n.html(l);else if("string"==typeof a.toolbar){var o=t(a.toolbar).html()||"";o&&n.html(i(o).render(a))}var r={filter:{title:"筛选列",layEvent:"LAYTABLE_COLS",icon:"layui-icon-cols"},exports:{title:"导出",layEvent:"LAYTABLE_EXPORT",icon:"layui-icon-export"},print:{title:"打印",layEvent:"LAYTABLE_PRINT",icon:"layui-icon-print"}},d=[];"object"==typeof a.defaultToolbar&&layui.each(a.defaultToolbar,function(e,t){var i="string"==typeof t?r[t]:t;i&&d.push('
    ')}),e.layTool.find(".layui-table-tool-self").html(d.join(""))},F.prototype.setParentCol=function(e,t){var i=this,a=i.config,l=i.layHeader.find('th[data-key="'+a.index+"-"+t+'"]'),n=parseInt(l.attr("colspan"))||0;if(l[0]){var o=t.split("-"),r=a.cols[o[0]][o[1]];e?n--:n++,l.attr("colspan",n),l[n<1?"addClass":"removeClass"](f),r.colspan=n,r.hide=n<1;var d=l.data("parentkey");d&&i.setParentCol(e,d)}},F.prototype.setColsPatch=function(){var e=this,t=e.config;layui.each(t.cols,function(t,i){layui.each(i,function(t,i){i.hide&&e.setParentCol(i.hide,i.parentKey)})})},F.prototype.setColsWidth=function(){var e=this,t=e.config,i=0,a=0,l=0,n=0,o=e.setInit("width");e.eachCols(function(e,t){t.hide||i++}),o=o-function(){return"line"===t.skin||"nob"===t.skin?2:i+1}()-e.getScrollWidth(e.layMain[0])-1;var r=function(e){layui.each(t.cols,function(i,r){layui.each(r,function(i,d){var c=0,s=d.minWidth||t.cellMinWidth;return d?void(d.colGroup||d.hide||(e?l&&ln&&a&&(l=(o-n)/a)};r(),r(!0),e.autoColNums=a,e.eachCols(function(i,a){var n=a.minWidth||t.cellMinWidth;a.colGroup||a.hide||(0===a.width?e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(l>=n?l:n)+"px"}):/\d+%$/.test(a.width)&&e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(parseFloat(a.width)/100*o)+"px"}))});var d=e.layMain.width()-e.getScrollWidth(e.layMain[0])-e.layMain.children("table").outerWidth();if(e.autoColNums&&d>=-i&&d<=i){var c=function(t){var i;return t=t||e.layHeader.eq(0).find("thead th:last-child"),i=t.data("field"),!i&&t.prev()[0]?c(t.prev()):t},s=c(),u=s.data("key");e.getCssRule(u,function(t){var i=t.style.width||s.outerWidth();t.style.width=parseFloat(i)+d+"px",e.layMain.height()-e.layMain.prop("clientHeight")>0&&(t.style.width=parseFloat(t.style.width)-1+"px")})}e.loading(!0)},F.prototype.resize=function(){var e=this;e.fullSize(),e.setColsWidth(),e.scrollPatch()},F.prototype.reload=function(e){var i=this;e=e||{},delete i.haveInit,e.data&&e.data.constructor===Array&&delete i.config.data,i.config=t.extend(!0,{},i.config,e),i.render()},F.prototype.errorView=function(e){var i=this,a=i.layMain.find("."+p),l=t('
    '+(e||"Error")+"
    ");a[0]&&(i.layNone.remove(),a.remove()),i.layFixed.addClass(f),i.layMain.find("tbody").html(""),i.layMain.append(i.layNone=l),d.cache[i.key]=[]},F.prototype.page=1,F.prototype.pullData=function(e){var i=this,a=i.config,l=a.request,n=a.response,o=function(){"object"==typeof a.initSort&&i.sort(a.initSort.field,a.initSort.type)};if(i.startTime=(new Date).getTime(),a.url){var r={};r[l.pageName]=e,r[l.limitName]=a.limit;var d=t.extend(r,a.where);a.contentType&&0==a.contentType.indexOf("application/json")&&(d=JSON.stringify(d)),i.loading(),t.ajax({type:a.method||"get",url:a.url,contentType:a.contentType,data:d,dataType:"json",headers:a.headers||{},success:function(t){"function"==typeof a.parseData&&(t=a.parseData(t)||t),t[n.statusName]!=n.statusCode?(i.renderForm(),i.errorView(t[n.msgName]||'返回的数据不符合规范,正确的成功状态码应为:"'+n.statusName+'": '+n.statusCode)):(i.renderData(t,e,t[n.countName]),o(),a.time=(new Date).getTime()-i.startTime+" ms"),i.setColsWidth(),"function"==typeof a.done&&a.done(t,e,t[n.countName])},error:function(e,t){i.errorView("数据接口请求异常:"+t),i.renderForm(),i.setColsWidth()}})}else if(a.data&&a.data.constructor===Array){var c={},s=e*a.limit-a.limit;c[n.dataName]=a.data.concat().splice(s,a.limit),c[n.countName]=a.data.length,"object"==typeof a.totalRow&&(c[n.totalRowName]=t.extend({},a.totalRow)),i.renderData(c,e,c[n.countName]),o(),i.setColsWidth(),"function"==typeof a.done&&a.done(c,e,c[n.countName])}},F.prototype.eachCols=function(e){var t=this;return d.eachCols(null,e,t.config.cols),t},F.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,y=e[s.response.dataName]||[],h=e[s.response.totalRowName],v=[],m=[],g=[],b=function(){var e;return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(y,function(a,l){var o=[],y=[],h=[],p=a+s.limit*(n-1)+1;0!==l.length&&(r||(l[d.config.indexName]=a),c.eachCols(function(n,r){var c=r.field||n,v=s.index+"-"+r.key,m=l[c];if(void 0!==m&&null!==m||(m=""),!r.colGroup){var g=['','
    '+function(){var n=t.extend(!0,{LAY_INDEX:p},l),o=d.config.checkName;switch(r.type){case"checkbox":return'";case"radio":return n[o]&&(e=a),'';case"numbers":return p}return r.toolbar?i(t(r.toolbar).html()||"").render(n):u(r,m,n)}(),"
    "].join("");o.push(g),r.fixed&&"right"!==r.fixed&&y.push(g),"right"===r.fixed&&h.push(g)}}),v.push(''+o.join("")+""),m.push(''+y.join("")+""),g.push(''+h.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+p).remove(),c.layMain.find("tbody").html(v.join("")),c.layFixLeft.find("tbody").html(m.join("")),c.layFixRight.find("tbody").html(g.join("")),c.renderForm(),"number"==typeof e&&c.setThisRowChecked(e),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,l.close(c.tipsIndex),s.HAS_SET_COLS_PATCH||c.setColsPatch(),void(s.HAS_SET_COLS_PATCH=!0))};return d.cache[c.key]=y,c.layPage[0==o||0===y.length&&1==n?"addClass":"removeClass"](f),r?b():0===y.length?(c.renderForm(),c.errorView(s.text.none)):(c.layFixed.removeClass(f),b(),c.renderTotal(y,h),void(s.page&&(s.page=t.extend({elem:"layui-table-page"+s.index,count:o,limit:s.limit,limits:s.limits||[10,20,30,40,50,60,70,80,90],groups:3,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.pullData(e.curr))}},s.page),s.page.count=o,a.render(s.page))))},F.prototype.renderTotal=function(e,t){var i=this,a=i.config,l={};if(a.totalRow){layui.each(e,function(e,t){0!==t.length&&i.eachCols(function(e,i){var a=i.field||e,n=t[a];i.totalRow&&(l[a]=(l[a]||0)+(parseFloat(n)||0))})}),i.dataTotal={};var n=[];i.eachCols(function(e,o){var r=o.field||e,d=function(){var e=o.totalRowText||"",i=parseFloat(l[r]).toFixed(2),a={};return a[r]=i,i=u(o,i,a),t?t[o.field]||e:o.totalRow?i||e:e}(),c=['','
    '+d,"
    "].join("");o.field&&(i.dataTotal[r]=d),n.push(c)}),i.layTotal.find("tbody").html(""+n.join("")+"")}},F.prototype.getColElem=function(e,t){var i=this,a=i.config;return e.eq(0).find(".laytable-cell-"+(a.index+"-"+t)+":eq(0)")},F.prototype.renderForm=function(e){n.render(e,"LAY-table-"+this.index)},F.prototype.setThisRowChecked=function(e){var t=this,i=(t.config,"layui-table-click"),a=t.layBody.find('tr[data-index="'+e+'"]');a.addClass(i).siblings("tr").removeClass(i)},F.prototype.sort=function(e,i,a,l){var n,r,c=this,s={},u=c.config,h=u.elem.attr("lay-filter"),f=d.cache[c.key];"string"==typeof e&&c.layHeader.find("th").each(function(i,a){var l=t(this),o=l.data("field");if(o===e)return e=l,n=o,!1});try{var n=n||e.data("field"),p=e.data("key");if(c.sortKey&&!a&&n===c.sortKey.field&&i===c.sortKey.sort)return;var v=c.layHeader.find("th .laytable-cell-"+p).find(S);c.layHeader.find("th").find(S).removeAttr("lay-sort"),v.attr("lay-sort",i||null),c.layFixed.find("th")}catch(m){return o.error("Table modules: Did not match to field")}c.sortKey={field:n,sort:i},u.autoSort&&("asc"===i?r=layui.sort(f,n):"desc"===i?r=layui.sort(f,n,!0):(r=layui.sort(f,d.config.indexName),delete c.sortKey)),s[u.response.dataName]=r||f,c.renderData(s,c.page,c.count,!0),l&&layui.event.call(e,y,"sort("+h+")",{field:n,type:i})},F.prototype.loading=function(e){var i=this,a=i.config;a.loading&&(e?(i.layInit&&i.layInit.remove(),delete i.layInit,i.layBox.find(b).remove()):(i.layInit=t(['
    ','',"
    "].join("")),i.layBox.append(i.layInit)))},F.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&l[e].constructor!==Array&&(l[e][a.checkName]=t)},F.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){"checkbox"===a.type&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm("checkbox")),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm("checkbox")),a(!1)))},F.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet||{},n=l.cssRules||l.rules;layui.each(n,function(i,a){if(a.selectorText===".laytable-cell-"+e)return t(a),!0})},F.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=H.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),a&&(e=parseFloat(a)-(t.layHeader.outerHeight()||38),i.toolbar&&(e-=t.layTool.outerHeight()||50),i.totalRow&&(e-=t.layTotal.outerHeight()||40),i.page&&(e-=t.layPage.outerHeight()||41),t.layMain.css("height",e-2))},F.prototype.getScrollWidth=function(e){var t=0;return e?t=e.offsetWidth-e.clientWidth:(e=document.createElement("div"),e.style.width="100px",e.style.height="100px",e.style.overflowY="scroll",document.body.appendChild(e),t=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),t},F.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight"),n=(e.getScrollWidth(e.layMain[0]),i.outerWidth()-e.layMain.width()),o=function(e){if(a&&l){if(e=e.eq(0),!e.find(".layui-table-patch")[0]){var i=t('
    ');i.find("div").css({width:a}),e.find("tr").append(i)}}else e.find(".layui-table-patch").remove()};o(e.layHeader),o(e.layTotal);var r=e.layMain.height(),d=r-l;e.layFixed.find(k).css("height",i.height()>=d?d:"auto"),e.layFixRight[n>0?"removeClass":"addClass"](f),e.layFixRight.css("right",a-1)},F.prototype.events=function(){var e,i=this,a=i.config,o=t("body"),c={},s=i.layHeader.find("th"),h=".layui-table-cell",p=a.elem.attr("lay-filter");i.layTool.on("click","*[lay-event]",function(e){var o=t(this),c=o.attr("lay-event"),s=function(e){var l=t(e.list),n=t('
      ');n.html(l),a.height&&n.css("max-height",a.height-(i.layTool.outerHeight()||50)),o.find(".layui-table-tool-panel")[0]||o.append(n),i.renderForm(),n.on("click",function(e){layui.stope(e)}),e.done&&e.done(n,l)};switch(layui.stope(e),j.trigger("table.tool.panel.remove"),l.close(i.tipsIndex),c){case"LAYTABLE_COLS":s({list:function(){var e=[];return i.eachCols(function(t,i){i.field&&"normal"==i.type&&e.push('
    • ')}),e.join("")}(),done:function(){n.on("checkbox(LAY_TABLE_TOOL_COLS)",function(e){var l=t(e.elem),n=this.checked,o=l.data("key"),r=l.data("parentkey");layui.each(a.cols,function(e,t){layui.each(t,function(t,l){if(e+"-"+t===o){var d=l.hide;l.hide=!n,i.elem.find('*[data-key="'+a.index+"-"+o+'"]')[n?"removeClass":"addClass"](f),d!=l.hide&&i.setParentCol(!n,r),i.resize()}})})})}});break;case"LAYTABLE_EXPORT":r.ie?l.tips("导出功能不支持 IE,请用 Chrome 等高级浏览器导出",this,{tips:3}):s({list:function(){return['
    • 导出到 Csv 文件
    • ','
    • 导出到 Excel 文件
    • '].join("")}(),done:function(e,l){l.on("click",function(){var e=t(this).data("type");d.exportFile.call(i,a.id,null,e)})}});break;case"LAYTABLE_PRINT":var u=window.open("打印窗口","_blank"),h=[""].join(""),v=t(i.layHeader.html());v.append(i.layMain.find("table").html()),v.append(i.layTotal.find("table").html()),v.find("th.layui-table-patch").remove(),v.find(".layui-table-col-special").remove(),u.document.write(h+v.prop("outerHTML")),u.document.close(),u.print(),u.close()}layui.event.call(this,y,"toolbar("+p+")",t.extend({event:c,config:a},{}))}),s.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.data("unresize")||c.resizeStart||(c.allowResize=i.width()-l<=10,o.css("cursor",c.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);c.resizeStart||o.css("cursor","")}).on("mousedown",function(e){var l=t(this);if(c.allowResize){var n=l.data("key");e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],i.getCssRule(n,function(e){var t=e.style.width||l.outerWidth();c.rule=e,c.ruleWidth=parseFloat(t),c.minWidth=l.data("minwidth")||a.cellMinWidth})}}),j.on("mousemove",function(t){if(c.resizeStart){if(t.preventDefault(),c.rule){var a=c.ruleWidth+t.clientX-c.offset[0];a');return n[0].value=i.data("content")||l.text(),i.find("."+R)[0]||i.append(n),n.focus(),void layui.stope(e)}}).on("mouseenter","td",function(){b.call(this)}).on("mouseleave","td",function(){b.call(this,"hide")});var g="layui-table-grid-down",b=function(e){var i=t(this),a=i.children(h);if(!i.data("off"))if(e)i.find(".layui-table-grid-down").remove();else if(a.prop("scrollWidth")>a.outerWidth()){if(a.find("."+g)[0])return;i.append('
      ')}};i.layBody.on("click","."+g,function(e){var n=t(this),o=n.parent(),d=o.children(h);i.tipsIndex=l.tips(['
      ',d.html(),"
      ",''].join(""),d[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:i.elem.width()/2,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}}),layui.stope(e)}),i.layBody.on("click","*[lay-event]",function(){var e=t(this),a=e.parents("tr").eq(0).data("index");layui.event.call(this,y,"tool("+p+")",v.call(this,{event:e.attr("lay-event")})),i.setThisRowChecked(a)}),i.layMain.on("scroll",function(){var e=t(this),a=e.scrollLeft(),n=e.scrollTop();i.layHeader.scrollLeft(a),i.layTotal.scrollLeft(a),i.layFixed.find(k).scrollTop(n),l.close(i.tipsIndex)}),H.on("resize",function(){i.resize()})},function(){j.on("click",function(){j.trigger("table.remove.tool.panel")}),j.on("table.remove.tool.panel",function(){t(".layui-table-tool-panel").remove()})}(),d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':h+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},c.that={},c.config={},d.eachCols=function(e,i,a){var l=c.config[e]||{},n=[],o=0;a=t.extend(!0,[],a||l.cols),layui.each(a,function(e,t){layui.each(t,function(t,i){if(i.colGroup){var l=0;o++,i.CHILD_COLS=[],layui.each(a[e+1],function(e,t){t.PARENT_COL_INDEX||l>1&&l==i.colspan||(t.PARENT_COL_INDEX=o,i.CHILD_COLS.push(t),l+=parseInt(t.colspan>1?t.colspan:1))})}i.PARENT_COL_INDEX||n.push(i)})});var r=function(e){layui.each(e||n,function(e,t){return t.CHILD_COLS?r(t.CHILD_COLS):void("function"==typeof i&&i(e,t))})};r()},d.checkStatus=function(e){var t=0,i=0,a=[],l=d.cache[e]||[];return layui.each(l,function(e,l){return l.constructor===Array?void i++:void(l[d.config.checkName]&&(t++,a.push(d.clearCacheKey(l))))}),{data:a,isAll:!!l.length&&t===l.length-i}},d.exportFile=function(e,t,i){var a=this;t=t||d.clearCacheKey(d.cache[e]),i=i||"csv";var l=c.config[e]||{},n={csv:"text/csv",xls:"application/vnd.ms-excel"}[i],s=document.createElement("a");return r.ie?o.error("IE_NOT_SUPPORT_EXPORTS"):(s.href="data:"+n+";charset=utf-8,\ufeff"+encodeURIComponent(function(){var i=[],l=[],n=[];return layui.each(t,function(t,a){var n=[];"object"==typeof e?(layui.each(e,function(e,a){0==t&&i.push(a||"")}),layui.each(d.clearCacheKey(a),function(e,t){n.push('"'+(t||"")+'"')})):d.eachCols(e,function(e,l){if(l.field&&"normal"==l.type&&!l.hide){var o=a[l.field];void 0!==o&&null!==o||(o=""),0==t&&i.push(l.title||""),n.push('"'+u(l,o,a,"text")+'"')}}),l.push(n.join(","))}),layui.each(a.dataTotal,function(e,t){n.push(t)}),i.join(",")+"\r\n"+l.join("\r\n")+"\r\n"+n.join(",")}()),s.download=(l.title||"table_"+(l.index||""))+"."+i,document.body.appendChild(s),s.click(),void document.body.removeChild(s))},d.resize=function(e){if(e){var t=s(e);if(!t)return;c.that[e].resize()}else layui.each(c.that,function(){this.resize()})},d.reload=function(e,t){var i=s(e);if(i){var a=c.that[e];return a.reload(t),c.call(a)}},d.render=function(e){var t=new F(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(y,d)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/transfer.js b/public/lib/layui/lay/modules/transfer.js new file mode 100644 index 0000000..2483921 --- /dev/null +++ b/public/lib/layui/lay/modules/transfer.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define(["laytpl","form"],function(e){"use strict";var a=layui.$,t=layui.laytpl,n=layui.form,i="transfer",l={config:{},index:layui[i]?layui[i].index+1e4:0,set:function(e){var t=this;return t.config=a.extend({},t.config,e),t},on:function(e,a){return layui.onevent.call(this,i,e,a)}},r=function(){var e=this,a=e.config,t=a.id||e.index;return r.that[t]=e,r.config[t]=a,{config:a,reload:function(a){e.reload.call(e,a)},getData:function(){return e.getData.call(e)}}},c="layui-hide",o="layui-btn-disabled",d="layui-none",s="layui-transfer-box",u="layui-transfer-header",h="layui-transfer-search",f="layui-transfer-active",y="layui-transfer-data",p=function(e){return e=e||{},['
      ','
      ','","
      ","{{# if(d.data.showSearch){ }}",'","{{# } }}",'
        ',"
        "].join("")},v=['
        ',p({index:0,checkAllName:"layTransferLeftCheckAll"}),'
        ','",'","
        ",p({index:1,checkAllName:"layTransferRightCheckAll"}),"
        "].join(""),x=function(e){var t=this;t.index=++l.index,t.config=a.extend({},t.config,l.config,e),t.render()};x.prototype.config={title:["列表一","列表二"],width:200,height:360,data:[],value:[],showSearch:!1,id:"",text:{none:"无数据",searchNone:"无匹配数据"}},x.prototype.reload=function(e){var t=this;layui.each(e,function(e,a){a.constructor===Array&&delete t.config[e]}),t.config=a.extend(!0,{},t.config,e),t.render()},x.prototype.render=function(){var e=this,n=e.config,i=e.elem=a(t(v).render({data:n,index:e.index})),l=n.elem=a(n.elem);l[0]&&(n.data=n.data||[],n.value=n.value||[],e.key=n.id||e.index,l.html(e.elem),e.layBox=e.elem.find("."+s),e.layHeader=e.elem.find("."+u),e.laySearch=e.elem.find("."+h),e.layData=i.find("."+y),e.layBtn=i.find("."+f+" .layui-btn"),e.layBox.css({width:n.width,height:n.height}),e.layData.css({height:function(){return n.height-e.layHeader.outerHeight()-e.laySearch.outerHeight()-2}()}),e.renderData(),e.events())},x.prototype.renderData=function(){var e=this,a=(e.config,[{checkName:"layTransferLeftCheck",views:[]},{checkName:"layTransferRightCheck",views:[]}]);e.parseData(function(e){var t=e.selected?1:0,n=["
      • ",'',"
      • "].join("");a[t].views.push(n),delete e.selected}),e.layData.eq(0).html(a[0].views.join("")),e.layData.eq(1).html(a[1].views.join("")),e.renderCheckBtn()},x.prototype.renderForm=function(e){n.render(e,"LAY-transfer-"+this.index)},x.prototype.renderCheckBtn=function(e){var t=this,n=t.config;e=e||{},t.layBox.each(function(i){var l=a(this),r=l.find("."+y),d=l.find("."+u).find('input[type="checkbox"]'),s=r.find('input[type="checkbox"]'),h=0,f=!1;if(s.each(function(){var e=a(this).data("hide");(this.checked||this.disabled||e)&&h++,this.checked&&!e&&(f=!0)}),d.prop("checked",f&&h===s.length),t.layBtn.eq(i)[f?"removeClass":"addClass"](o),!e.stopNone){var p=r.children("li:not(."+c+")").length;t.noneView(r,p?"":n.text.none)}}),t.renderForm("checkbox")},x.prototype.noneView=function(e,t){var n=a('

        '+(t||"")+"

        ");e.find("."+d)[0]&&e.find("."+d).remove(),t.replace(/\s/g,"")&&e.append(n)},x.prototype.setValue=function(){var e=this,t=e.config,n=[];return e.layBox.eq(1).find("."+y+' input[type="checkbox"]').each(function(){var e=a(this).data("hide");e||n.push(this.value)}),t.value=n,e},x.prototype.parseData=function(e){var t=this,n=t.config,i=[];return layui.each(n.data,function(t,l){l=("function"==typeof n.parseData?n.parseData(l):l)||l,i.push(l=a.extend({},l)),layui.each(n.value,function(e,a){a==l.value&&(l.selected=!0)}),e&&e(l)}),n.data=i,t},x.prototype.getData=function(e){var a=this,t=a.config,n=[];return a.setValue(),layui.each(e||t.value,function(e,a){layui.each(t.data,function(e,t){delete t.selected,a==t.value&&n.push(t)})}),n},x.prototype.events=function(){var e=this,t=e.config;e.elem.on("click",'input[lay-filter="layTransferCheckbox"]+',function(){var t=a(this).prev(),n=t[0].checked,i=t.parents("."+s).eq(0).find("."+y);t[0].disabled||("all"===t.attr("lay-type")&&i.find('input[type="checkbox"]').each(function(){this.disabled||(this.checked=n)}),e.renderCheckBtn({stopNone:!0}))}),e.layBtn.on("click",function(){var n=a(this),i=n.data("index"),l=e.layBox.eq(i),r=[];if(!n.hasClass(o)){e.layBox.eq(i).each(function(t){var n=a(this),i=n.find("."+y);i.children("li").each(function(){var t=a(this),n=t.find('input[type="checkbox"]'),i=n.data("hide");n[0].checked&&!i&&(n[0].checked=!1,l.siblings("."+s).find("."+y).append(t.clone()),t.remove(),r.push(n[0].value)),e.setValue()})}),e.renderCheckBtn();var c=l.siblings("."+s).find("."+h+" input");""===c.val()||c.trigger("keyup"),t.onchange&&t.onchange(e.getData(r),i)}}),e.laySearch.find("input").on("keyup",function(){var n=this.value,i=a(this).parents("."+h).eq(0).siblings("."+y),l=i.children("li");l.each(function(){var e=a(this),t=e.find('input[type="checkbox"]'),i=t[0].title.indexOf(n)!==-1;e[i?"removeClass":"addClass"](c),t.data("hide",!i)}),e.renderCheckBtn();var r=l.length===i.children("li."+c).length;e.noneView(i,r?t.text.searchNone:"")})},r.that={},r.config={},l.reload=function(e,a){var t=r.that[e];return t.reload(a),r.call(t)},l.getData=function(e){var a=r.that[e];return a.getData()},l.render=function(e){var a=new x(e);return r.call(a)},e(i,l)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/tree.js b/public/lib/layui/lay/modules/tree.js new file mode 100644 index 0000000..c9c93a2 --- /dev/null +++ b/public/lib/layui/lay/modules/tree.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define("form",function(e){"use strict";var i=layui.$,a=layui.form,n=layui.layer,t="tree",r={config:{},index:layui[t]?layui[t].index+1e4:0,set:function(e){var a=this;return a.config=i.extend({},a.config,e),a},on:function(e,i){return layui.onevent.call(this,t,e,i)}},l=function(){var e=this,i=e.config,a=i.id||e.index;return l.that[a]=e,l.config[a]=i,{config:i,reload:function(i){e.reload.call(e,i)},getChecked:function(){return e.getChecked.call(e)},setChecked:function(i){return e.setChecked.call(e,i)}}},c="layui-hide",d="layui-disabled",s="layui-tree-set",o="layui-tree-iconClick",h="layui-icon-addition",u="layui-icon-subtraction",p="layui-tree-entry",f="layui-tree-main",y="layui-tree-txt",v="layui-tree-pack",C="layui-tree-spread",k="layui-tree-setLineShort",m="layui-tree-showLine",x="layui-tree-lineExtend",b=function(e){var a=this;a.index=++r.index,a.config=i.extend({},a.config,r.config,e),a.render()};b.prototype.config={data:[],showCheckbox:!1,showLine:!0,accordion:!1,onlyIconControl:!1,isJump:!1,edit:!1,text:{defaultNodeName:"未命名",none:"无数据"}},b.prototype.reload=function(e){var a=this;layui.each(e,function(e,i){i.constructor===Array&&delete a.config[e]}),a.config=i.extend(!0,{},a.config,e),a.render()},b.prototype.render=function(){var e=this,a=e.config;e.checkids=[];var n=i('
        ');e.tree(n);var t=a.elem=i(a.elem);if(t[0]){if(e.key=a.id||e.index,e.elem=n,e.elemNone=i('
        '+a.text.none+"
        "),t.html(e.elem),0==e.elem.find(".layui-tree-set").length)return e.elem.append(e.elemNone);a.showCheckbox&&e.renderForm("checkbox"),e.elem.find(".layui-tree-set").each(function(){var e=i(this);e.parent(".layui-tree-pack")[0]||e.addClass("layui-tree-setHide"),!e.next()[0]&&e.parents(".layui-tree-pack").eq(1).hasClass("layui-tree-lineExtend")&&e.addClass(k),e.next()[0]||e.parents(".layui-tree-set").eq(0).next()[0]||e.addClass(k)}),e.events()}},b.prototype.renderForm=function(e){a.render(e,"LAY-tree-"+this.index)},b.prototype.tree=function(e,a){var n=this,t=n.config,r=a||t.data;layui.each(r,function(a,r){var l=r.children&&r.children.length>0,o=i('
        '),h=i(['
        ','
        ','
        ',function(){return t.showLine?l?'':'':''}(),function(){return t.showCheckbox?'':""}(),function(){return t.isJump&&r.href?''+(r.title||r.label||t.text.defaultNodeName)+"":''+(r.title||r.label||t.text.defaultNodeName)+""}(),"
        ",function(){if(!t.edit)return"";var e={add:'',update:'',del:''},i=['
        '];return t.edit===!0&&(t.edit=["update","del"]),"object"==typeof t.edit?(layui.each(t.edit,function(a,n){i.push(e[n]||"")}),i.join("")+"
        "):void 0}(),"
        "].join(""));l&&(h.append(o),n.tree(o,r.children)),e.append(h),h.prev("."+s)[0]&&h.prev().children(".layui-tree-pack").addClass("layui-tree-showLine"),l||h.parent(".layui-tree-pack").addClass("layui-tree-lineExtend"),n.spread(h,r),t.showCheckbox&&(r.checked&&n.checkids.push(r.id),n.checkClick(h,r)),t.edit&&n.operate(h,r)})},b.prototype.spread=function(e,a){var n=this,t=n.config,r=e.children("."+p),l=r.children("."+f),c=r.find("."+o),k=r.find("."+y),m=t.onlyIconControl?c:l,x="";m.on("click",function(i){var a=e.children("."+v),n=m.children(".layui-icon")[0]?m.children(".layui-icon"):m.find(".layui-tree-icon").children(".layui-icon");if(a[0]){if(e.hasClass(C))e.removeClass(C),a.slideUp(200),n.removeClass(u).addClass(h);else if(e.addClass(C),a.slideDown(200),n.addClass(u).removeClass(h),t.accordion){var r=e.siblings("."+s);r.removeClass(C),r.children("."+v).slideUp(200),r.find(".layui-tree-icon").children(".layui-icon").removeClass(u).addClass(h)}}else x="normal"}),k.on("click",function(){var n=i(this);n.hasClass(d)||(x=e.hasClass(C)?t.onlyIconControl?"open":"close":t.onlyIconControl?"close":"open",t.click&&t.click({elem:e,state:x,data:a}))})},b.prototype.setCheckbox=function(e,i,a){var n=this,t=(n.config,a.prop("checked"));if(!a.prop("disabled")){if("object"==typeof i.children||e.find("."+v)[0]){var r=e.find("."+v).find('input[same="layuiTreeCheck"]');r.each(function(){this.disabled||(this.checked=t)})}var l=function(e){if(e.parents("."+s)[0]){var i,a=e.parent("."+v),n=a.parent(),r=a.prev().find('input[same="layuiTreeCheck"]');t?r.prop("checked",t):(a.find('input[same="layuiTreeCheck"]').each(function(){this.checked&&(i=!0)}),i||r.prop("checked",!1)),l(n)}};l(e),n.renderForm("checkbox")}},b.prototype.checkClick=function(e,a){var n=this,t=n.config,r=e.children("."+p),l=r.children("."+f);l.on("click",'input[same="layuiTreeCheck"]+',function(r){layui.stope(r);var l=i(this).prev(),c=l.prop("checked");l.prop("disabled")||(n.setCheckbox(e,a,l),t.oncheck&&t.oncheck({elem:e,checked:c,data:a}))})},b.prototype.operate=function(e,a){var t=this,r=t.config,l=e.children("."+p),d=l.children("."+f);l.children(".layui-tree-btnGroup").on("click",".layui-icon",function(l){layui.stope(l);var f=i(this).data("type"),b=e.children("."+v),g={data:a,type:f,elem:e};if("add"==f){b[0]||(r.showLine?(d.find("."+o).addClass("layui-tree-icon"),d.find("."+o).children(".layui-icon").addClass(h).removeClass("layui-icon-file")):d.find(".layui-tree-iconArrow").removeClass(c),e.append('
        '));var w=r.operate&&r.operate(g),N={};if(N.title=r.text.defaultNodeName,N.id=w,t.tree(e.children("."+v),[N]),r.showLine)if(b[0])b.hasClass(x)||b.addClass(x),e.find("."+v).each(function(){i(this).children("."+s).last().addClass(k)}),b.children("."+s).last().prev().hasClass(k)?b.children("."+s).last().prev().removeClass(k):b.children("."+s).last().removeClass(k),!e.parent("."+v)[0]&&e.next()[0]&&b.children("."+s).last().removeClass(k);else{var T=e.siblings("."+s),L=1,A=e.parent("."+v);layui.each(T,function(e,a){i(a).children("."+v)[0]||(L=0)}),1==L?(T.children("."+v).addClass(m),T.children("."+v).children("."+s).removeClass(k),e.children("."+v).addClass(m),A.removeClass(x),A.children("."+s).last().children("."+v).children("."+s).last().addClass(k)):e.children("."+v).children("."+s).addClass(k)}if(!r.showCheckbox)return;if(d.find('input[same="layuiTreeCheck"]')[0].checked){var I=e.children("."+v).children("."+s).last();I.find('input[same="layuiTreeCheck"]')[0].checked=!0}t.renderForm("checkbox")}else if("update"==f){var F=d.children("."+y).html();d.children("."+y).html(""),d.append(''),d.children(".layui-tree-editInput").val(F).focus();var j=function(e){var i=e.val().trim();i=i?i:r.text.defaultNodeName,e.remove(),d.children("."+y).html(i),g.data.title=i,r.operate&&r.operate(g)};d.children(".layui-tree-editInput").blur(function(){j(i(this))}),d.children(".layui-tree-editInput").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),j(i(this)))})}else n.confirm('确认删除该节点 "'+(a.title||"")+'" 吗?',function(a){if(r.operate&&r.operate(g),g.status="remove",n.close(a),!e.prev("."+s)[0]&&!e.next("."+s)[0]&&!e.parent("."+v)[0])return e.remove(),void t.elem.append(t.elemNone);if(e.siblings("."+s).children("."+p)[0]){if(r.showCheckbox){var l=function(e){if(e.parents("."+s)[0]){var a=e.siblings("."+s).children("."+p),n=e.parent("."+v).prev(),r=n.find('input[same="layuiTreeCheck"]')[0],c=1,d=0;0==r.checked&&(a.each(function(e,a){var n=i(a).find('input[same="layuiTreeCheck"]')[0];0!=n.checked||n.disabled||(c=0),n.disabled||(d=1)}),1==c&&1==d&&(r.checked=!0,t.renderForm("checkbox"),l(n.parent("."+s))))}};l(e)}if(r.showLine){var d=e.siblings("."+s),h=1,f=e.parent("."+v);layui.each(d,function(e,a){i(a).children("."+v)[0]||(h=0)}),1==h?(b[0]||(f.removeClass(x),d.children("."+v).addClass(m),d.children("."+v).children("."+s).removeClass(k)),e.next()[0]?f.children("."+s).last().children("."+v).children("."+s).last().addClass(k):e.prev().children("."+v).children("."+s).last().addClass(k),e.next()[0]||e.parents("."+s)[1]||e.parents("."+s).eq(0).next()[0]||e.prev("."+s).addClass(k)):!e.next()[0]&&e.hasClass(k)&&e.prev().addClass(k)}}else{var y=e.parent("."+v).prev();if(r.showLine){y.find("."+o).removeClass("layui-tree-icon"),y.find("."+o).children(".layui-icon").removeClass(u).addClass("layui-icon-file");var w=y.parents("."+v).eq(0);w.addClass(x),w.children("."+s).each(function(){i(this).children("."+v).children("."+s).last().addClass(k)})}else y.find(".layui-tree-iconArrow").addClass(c);e.parents("."+s).eq(0).removeClass(C),e.parent("."+v).remove()}e.remove()})})},b.prototype.events=function(){var e=this,a=e.config;e.elem.find(".layui-tree-checkedFirst");e.setChecked(e.checkids),e.elem.find(".layui-tree-search").on("keyup",function(){var n=i(this),t=n.val(),r=n.nextAll(),l=[];r.find("."+y).each(function(){var e=i(this).parents("."+p);if(i(this).html().indexOf(t)!=-1){l.push(i(this).parent());var a=function(e){e.addClass("layui-tree-searchShow"),e.parent("."+v)[0]&&a(e.parent("."+v).parent("."+s))};a(e.parent("."+s))}}),r.find("."+p).each(function(){var e=i(this).parent("."+s);e.hasClass("layui-tree-searchShow")||e.addClass(c)}),0==r.find(".layui-tree-searchShow").length&&e.elem.append(e.elemNone),a.onsearch&&a.onsearch({elem:l})}),e.elem.find(".layui-tree-search").on("keydown",function(){i(this).nextAll().find("."+p).each(function(){var e=i(this).parent("."+s);e.removeClass("layui-tree-searchShow "+c)}),i(".layui-tree-emptyText")[0]&&i(".layui-tree-emptyText").remove()})},b.prototype.getChecked=function(){var e=this,a=e.config,n=[],t=[];e.elem.find(".layui-form-checked").each(function(){n.push(i(this).prev()[0].value)});var r=function(e,a){layui.each(e,function(e,t){layui.each(n,function(e,n){if(t.id==n){var l=i.extend({},t);return delete l.children,a.push(l),t.children&&(l.children=[],r(t.children,l.children)),!0}})})};return r(i.extend({},a.data),t),t},b.prototype.setChecked=function(e){var a=this;a.config;a.elem.find("."+s).each(function(a,n){var t=i(this).data("id"),r=i(n).children("."+p).find('input[same="layuiTreeCheck"]'),l=r.next();if("number"==typeof e){if(t==e)return r[0].checked||l.click(),!1}else"object"==typeof e&&layui.each(e,function(e,i){if(i==t&&!r[0].checked)return l.click(),!0})})},l.that={},l.config={},r.reload=function(e,i){var a=l.that[e];return a.reload(i),l.call(a)},r.getChecked=function(e){var i=l.that[e];return i.getChecked()},r.setChecked=function(e,i){var a=l.that[e];return a.setChecked(i)},r.render=function(e){var i=new b(e);return l.call(i)},e(t,r)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/upload.js b/public/lib/layui/lay/modules/upload.js new file mode 100644 index 0000000..9298ecf --- /dev/null +++ b/public/lib/layui/lay/modules/upload.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define("layer",function(e){"use strict";var t=layui.$,i=layui.layer,n=layui.hint(),o=layui.device(),a={config:{},set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,r,e,t)}},l=function(){var e=this;return{upload:function(t){e.upload.call(e,t)},reload:function(t){e.reload.call(e,t)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",f="layui-upload-iframe",s="layui-upload-choose",p=function(e){var i=this;i.config=t.extend({},i.config,a.config,e),i.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",acceptMime:"",method:"post",data:{},drag:!0,size:0,number:0,multiple:!1},p.prototype.render=function(e){var i=this,e=i.config;e.elem=t(e.elem),e.bindAction=t(e.bindAction),i.file(),i.events()},p.prototype.file=function(){var e=this,i=e.config,n=e.elemFile=t(['"].join("")),a=i.elem.next();(a.hasClass(u)||a.hasClass(c))&&a.remove(),o.ie&&o.ie<10&&i.elem.wrap('
        '),e.isFile()?(e.elemFile=i.elem,i.field=i.elem[0].name):i.elem.after(n),o.ie&&o.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,i=e.config,n=t(''),o=t(['
        ',"
        "].join(""));t("#"+f)[0]||t("body").append(n),i.elem.next().hasClass(c)||(e.elemFile.wrap(o),i.elem.next("."+c).append(function(){var e=[];return layui.each(i.data,function(t,i){i="function"==typeof i?i():i,e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return i.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var t=this;window.FileReader&&layui.each(t.chooseFiles,function(t,i){var n=new FileReader;n.readAsDataURL(i),n.onload=function(){e&&e(t,i,this.result)}})},p.prototype.upload=function(e,i){var n,a=this,l=a.config,r=a.elemFile[0],u=function(){var i=0,n=0,o=e||a.files||a.chooseFiles||r.files,u=function(){l.multiple&&i+n===a.fileLength&&"function"==typeof l.allDone&&l.allDone({total:a.fileLength,successful:i,aborted:n})};layui.each(o,function(e,o){var r=new FormData;r.append(l.field,o),layui.each(l.data,function(e,t){t="function"==typeof t?t():t,r.append(e,t)});var c={url:l.url,type:"post",data:r,contentType:!1,processData:!1,dataType:"json",headers:l.headers||{},success:function(t){i++,d(e,t),u()},error:function(){n++,a.msg("请求上传接口出现异常"),m(e),u()}};"function"==typeof l.progress&&(c.xhr=function(){var e=t.ajaxSettings.xhr();return e.upload.addEventListener("progress",function(e){if(e.lengthComputable){var t=Math.floor(e.loaded/e.total*100);l.progress(t,l.item[0],e)}}),e}),t.ajax(c)})},c=function(){var e=t("#"+f);a.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var t,i=e.contents().find("body");try{t=i.text()}catch(n){a.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}t&&(clearInterval(p.timer),i.html(""),d(0,t))},30)},d=function(e,t){if(a.elemFile.next("."+s).remove(),r.value="","object"!=typeof t)try{t=JSON.parse(t)}catch(i){return t={},a.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(t,e||0,function(e){a.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){a.upload(e)})},h=l.exts,v=function(){var t=[];return layui.each(e||a.chooseFiles,function(e,i){t.push(i.name)}),t}(),g={preview:function(e){a.preview(e)},upload:function(e,t){var i={};i[e]=t,a.upload(i)},pushFile:function(){return a.files=a.files||{},layui.each(a.chooseFiles,function(e,t){a.files[e]=t}),a.files},resetFile:function(e,t,i){var n=new File([t],i);a.files=a.files||{},a.files[e]=n}},y=function(){if("choose"!==i&&!l.auto||(l.choose&&l.choose(g),"choose"!==i))return l.before&&l.before(g),o.ie?o.ie>9?u():c():void u()};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return a.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return a.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return a.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,t){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(t))||(n=!0)}),n)return a.msg("选择的图片中包含不支持的格式"),r.value=""}if(a.fileLength=function(){var t=0,i=e||a.files||a.chooseFiles||r.files;return layui.each(i,function(){t++}),t}(),l.number&&a.fileLength>l.number)return a.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(o.ie&&o.ie<10)){var F;if(layui.each(a.chooseFiles,function(e,t){if(t.size>1024*l.size){var i=l.size/1024;i=i>=1?i.toFixed(2)+"MB":l.size+"KB",r.value="",F=i}}),F)return a.msg("文件不能超过"+F)}y()}},p.prototype.reload=function(e){e=e||{},delete e.elem,delete e.bindAction;var i=this,e=i.config=t.extend({},i.config,a.config,e),n=e.elem.next();n.attr({name:e.name,accept:e.acceptMime,multiple:e.multiple})},p.prototype.events=function(){var e=this,i=e.config,a=function(t){e.chooseFiles={},layui.each(t,function(t,i){var n=(new Date).getTime();e.chooseFiles[n+"-"+t]=i})},l=function(t,n){var o=e.elemFile,a=t.length>1?t.length+"个文件":(t[0]||{}).name||o[0].value.match(/[^\/\\]+\..+/g)||[]||"";o.next().hasClass(s)&&o.next().remove(),e.upload(null,"choose"),e.isFile()||i.choose||o.after(''+a+"")};i.elem.off("upload.start").on("upload.start",function(){var o=t(this),a=o.attr("lay-data");if(a)try{a=new Function("return "+a)(),e.config=t.extend({},i,a)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+a)}e.config.item=o,e.elemFile[0].click()}),o.ie&&o.ie<10||i.elem.off("upload.over").on("upload.over",function(){var e=t(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=t(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,o){var r=t(this),u=o.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),a(u),i.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var t=this.files||[];a(t),i.auto?e.upload():l(t)}),i.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),i.elem.data("haveEvents")||(e.elemFile.on("change",function(){t(this).trigger("upload.change")}),i.elem.on("click",function(){e.isFile()||t(this).trigger("upload.start")}),i.drag&&i.elem.on("dragover",function(e){e.preventDefault(),t(this).trigger("upload.over")}).on("dragleave",function(e){t(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),t(this).trigger("upload.drop",e)}),i.bindAction.on("click",function(){t(this).trigger("upload.action")}),i.elem.data("haveEvents",!0))},a.render=function(e){var t=new p(e);return l.call(t)},e(r,a)}); \ No newline at end of file diff --git a/public/lib/layui/lay/modules/util.js b/public/lib/layui/lay/modules/util.js new file mode 100644 index 0000000..fdc1ec2 --- /dev/null +++ b/public/lib/layui/lay/modules/util.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;layui.define("jquery",function(e){"use strict";var t=layui.$,i={fixbar:function(e){var i,n,a="layui-fixbar",o="layui-fixbar-top",r=t(document),l=t("body");e=t.extend({showHeight:200},e),e.bar1=e.bar1===!0?"":e.bar1,e.bar2=e.bar2===!0?"":e.bar2,e.bgcolor=e.bgcolor?"background-color:"+e.bgcolor:"";var c=[e.bar1,e.bar2,""],u=t(['
          ',e.bar1?'
        • '+c[0]+"
        • ":"",e.bar2?'
        • '+c[1]+"
        • ":"",'
        • '+c[2]+"
        • ","
        "].join("")),g=u.find("."+o),s=function(){var t=r.scrollTop();t>=e.showHeight?i||(g.show(),i=1):i&&(g.hide(),i=0)};t("."+a)[0]||("object"==typeof e.css&&u.css(e.css),l.append(u),s(),u.find("li").on("click",function(){var i=t(this),n=i.attr("lay-type");"top"===n&&t("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,n)}),r.on("scroll",function(){clearTimeout(n),n=setTimeout(function(){s()},100)}))},countdown:function(e,t,i){var n=this,a="function"==typeof t,o=new Date(e).getTime(),r=new Date(!t||a?(new Date).getTime():t).getTime(),l=o-r,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];a&&(i=t);var u=setTimeout(function(){n.countdown(e,r+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],t,u),l<=0&&clearTimeout(u),u},timeAgo:function(e,t){var i=this,n=[[],[]],a=(new Date).getTime()-new Date(e).getTime();return a>26784e5?(a=new Date(e),n[0][0]=i.digit(a.getFullYear(),4),n[0][1]=i.digit(a.getMonth()+1),n[0][2]=i.digit(a.getDate()),t||(n[1][0]=i.digit(a.getHours()),n[1][1]=i.digit(a.getMinutes()),n[1][2]=i.digit(a.getSeconds())),n[0].join("-")+" "+n[1].join(":")):a>=864e5?(a/1e3/60/60/24|0)+"天前":a>=36e5?(a/1e3/60/60|0)+"小时前":a>=18e4?(a/1e3/60|0)+"分钟前":a<0?"未来":"刚刚"},digit:function(e,t){var i="";e=String(e),t=t||2;for(var n=e.length;n/g,">").replace(/'/g,"'").replace(/"/g,""")},event:function(e,n,a){var o=t("body");return a=a||"click",n=i.event[e]=t.extend(!0,i.event[e],n)||{},i.event.UTIL_EVENT_CALLBACK=i.event.UTIL_EVENT_CALLBACK||{},o.off(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),i.event.UTIL_EVENT_CALLBACK[e]=function(){var i=t(this),a=i.attr(e);"function"==typeof n[a]&&n[a].call(this,i)},o.on(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),n}};!function(e,t,i){"$:nomunge";function n(){a=t[l](function(){o.each(function(){var t=e(this),i=t.width(),n=t.height(),a=e.data(this,u);(i!==a.w||n!==a.h)&&t.trigger(c,[a.w=i,a.h=n])}),n()},r[g])}var a,o=e([]),r=e.resize=e.extend(e.resize,{}),l="setTimeout",c="resize",u=c+"-special-event",g="delay",s="throttleWindow";r[g]=250,r[s]=!0,e.event.special[c]={setup:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.add(t),e.data(this,u,{w:t.width(),h:t.height()}),1===o.length&&n()},teardown:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.not(t),t.removeData(u),o.length||clearTimeout(a)},add:function(t){function n(t,n,o){var r=e(this),l=e.data(this,u)||{};l.w=n!==i?n:r.width(),l.h=o!==i?o:r.height(),a.apply(this,arguments)}if(!r[s]&&this[l])return!1;var a;return e.isFunction(t)?(a=t,n):(a=t.handler,void(t.handler=n))}}}(t,window),e("util",i)}); \ No newline at end of file diff --git a/public/lib/layui/layui.all.js b/public/lib/layui/layui.all.js new file mode 100644 index 0000000..af3355c --- /dev/null +++ b/public/lib/layui/layui.all.js @@ -0,0 +1,5 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;!function(e){"use strict";var t=document,n={modules:{},status:{},timeout:10,event:{}},r=function(){this.v="2.5.6"},o=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,n=t.scripts,r=n.length-1,o=r;o>0;o--)if("interactive"===n[o].readyState){e=n[o].src;break}return e||n[r].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),a=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},i="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",transfer:"modules/transfer",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};r.prototype.cache=n,r.prototype.define=function(e,t){var r=this,o="function"==typeof e,a=function(){var e=function(e,t){layui[e]=t,n.status[e]=!0};return"function"==typeof t&&t(function(r,o){e(r,o),n.callback[r]=function(){t(e)}}),this};return o&&(t=e,e=[]),!layui["layui.all"]&&layui["layui.mobile"]?a.call(r):(r.use(e,a),r)},r.prototype.use=function(e,r,l){function c(e,t){var r="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||r.test((e.currentTarget||e.srcElement).readyState))&&(n.modules[d]=t,y.removeChild(h),function o(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void(n.status[d]?s():setTimeout(o,4))}())}function s(){l.push(layui[d]),e.length>1?p.use(e.slice(1),r,l):"function"==typeof r&&r.apply(layui,l)}var p=this,f=n.dir=n.dir?n.dir:o,y=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(p.each(e,function(t,n){"jquery"===n&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var d=e[0],m=0;if(l=l||[],n.host=n.host||(f.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[d]||!layui["layui.all"]&&layui["layui.mobile"]&&u[d])return s(),p;var v=(u[d]?f+"lay/":/^\{\/\}/.test(p.modules[d])?"":n.base||"")+(p.modules[d]||d)+".js";if(v=v.replace(/^\{\/\}/,""),!n.modules[d]&&layui[d]&&(n.modules[d]=v),n.modules[d])!function g(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void("string"==typeof n.modules[d]&&n.status[d]?s():setTimeout(g,4))}();else{var h=t.createElement("script");h.async=!0,h.charset="utf-8",h.src=v+function(){var e=n.version===!0?n.v||(new Date).getTime():n.version||"";return e?"?v="+e:""}(),y.appendChild(h),!h.attachEvent||h.attachEvent.toString&&h.attachEvent.toString().indexOf("[native code")<0||i?h.addEventListener("load",function(e){c(e,v)},!1):h.attachEvent("onreadystatechange",function(e){c(e,v)}),n.modules[d]=v}return p},r.prototype.getStyle=function(t,n){var r=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return r[r.getPropertyValue?"getPropertyValue":"getAttribute"](n)},r.prototype.link=function(e,r,o){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof r&&(o=r);var c=(o||e).replace(/\.|\//g,""),s=u.id="layuicss-"+c,p=0;return u.rel="stylesheet",u.href=e+(n.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(s)||l.appendChild(u),"function"!=typeof r?i:(function f(){return++p>1e3*n.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(s),"width"))?function(){r()}():setTimeout(f,100))}(),i)},n.callback={},r.prototype.factory=function(e){if(layui[e])return"function"==typeof n.callback[e]?n.callback[e]:null},r.prototype.addcss=function(e,t,r){return layui.link(n.dir+"css/"+e,t,r)},r.prototype.img=function(e,t,n){var r=new Image;return r.src=e,r.complete?t(r):(r.onload=function(){r.onload=null,"function"==typeof t&&t(r)},void(r.onerror=function(e){r.onerror=null,"function"==typeof n&&n(e)}))},r.prototype.config=function(e){e=e||{};for(var t in e)n[t]=e[t];return this},r.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),r.prototype.extend=function(e){var t=this;e=e||{};for(var n in e)t[n]||t.modules[n]?a("模块名 "+n+" 已被占用"):t.modules[n]=e[n];return t},r.prototype.router=function(e){var t=this,e=e||location.hash,n={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),n.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),n.search[t[0]]=t[1]}():n.path.push(t)}),n):n},r.prototype.url=function(e){var t=this,n={pathname:function(){var t=e?function(){var t=(e.match(/\.[^.]+?\/.+/)||[])[0]||"";return t.replace(/^[^\/]+/,"").replace(/\?.+/,"")}():location.pathname;return t.replace(/^\//,"").split("/")}(),search:function(){var n={},r=(e?function(){var t=(e.match(/\?.+/)||[])[0]||"";return t.replace(/\#.+/,"")}():location.search).replace(/^\?+/,"").split("&");return t.each(r,function(e,t){var r=t.indexOf("="),o=function(){return r<0?t.substr(0,t.length):0!==r&&t.substr(0,r)}();o&&(n[o]=r>0?t.substr(r+1):null)}),n}(),hash:t.router(function(){return e?(e.match(/#.+/)||[])[0]||"":location.hash}())};return n},r.prototype.data=function(t,n,r){if(t=t||"layui",r=r||localStorage,e.JSON&&e.JSON.parse){if(null===n)return delete r[t];n="object"==typeof n?n:{key:n};try{var o=JSON.parse(r[t])}catch(a){var o={}}return"value"in n&&(o[n.key]=n.value),n.remove&&delete o[n.key],r[t]=JSON.stringify(o),n.key?o[n.key]:o}},r.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},r.prototype.device=function(t){var n=navigator.userAgent.toLowerCase(),r=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(n.match(t)||[])[1],e||!1},o={os:function(){return/windows/.test(n)?"windows":/linux/.test(n)?"linux":/iphone|ipod|ipad|ios/.test(n)?"ios":/mac/.test(n)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((n.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:r("micromessenger")};return t&&!o[t]&&(o[t]=r(t)),o.android=/android/.test(n),o.ios="ios"===o.os,o.mobile=!(!o.android&&!o.ios),o},r.prototype.hint=function(){return{error:a}},r.prototype.each=function(e,t){var n,r=this;if("function"!=typeof t)return r;if(e=e||[],e.constructor===Object){for(n in e)if(t.call(e[n],n,e[n]))break}else for(n=0;na?1:o/g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)});layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['
        ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
        "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)});!function(){"use strict";var e=window.layui&&layui.define,t={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,n=t.length-1,a=n;a>0;a--)if("interactive"===t[a].readyState){e=t[a].src;break}return e||t[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),getStyle:function(e,t){var n=e.currentStyle?e.currentStyle:window.getComputedStyle(e,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](t)},link:function(e,a,i){if(n.path){var r=document.getElementsByTagName("head")[0],o=document.createElement("link");"string"==typeof a&&(i=a);var s=(i||e).replace(/\.|\//g,""),l="layuicss-"+s,d=0;o.rel="stylesheet",o.href=n.path+e,o.id=l,document.getElementById(l)||r.appendChild(o),"function"==typeof a&&!function c(){return++d>80?window.console&&console.error("laydate.css: Invalid"):void(1989===parseInt(t.getStyle(document.getElementById(l),"width"))?a():setTimeout(c,100))}()}}},n={v:"5.0.9",config:{},index:window.laydate&&window.laydate.v?1e5:0,path:t.getPath,set:function(e){var t=this;return t.config=w.extend({},t.config,e),t},ready:function(a){var i="laydate",r="",o=(e?"modules/laydate/":"theme/")+"default/laydate.css?v="+n.v+r;return e?layui.addcss(o,a,i):t.link(o,a,i),this}},a=function(){var e=this;return{hint:function(t){e.hint.call(e,t)},config:e.config}},i="laydate",r=".layui-laydate",o="layui-this",s="laydate-disabled",l="开始日期超出了结束日期
        建议重新选择",d=[100,2e5],c="layui-laydate-static",m="layui-laydate-list",u="laydate-selected",h="layui-laydate-hint",y="laydate-day-prev",f="laydate-day-next",p="layui-laydate-footer",g=".laydate-btns-confirm",v="laydate-time-text",D=".laydate-btns-time",T=function(e){var t=this;t.index=++n.index,t.config=w.extend({},t.config,n.config,e),n.ready(function(){t.init()})},w=function(e){return new C(e)},C=function(e){for(var t=0,n="object"==typeof e?[e]:(this.selector=e,document.querySelectorAll(e||null));t0)return n[0].getAttribute(e)}():n.each(function(n,a){a.setAttribute(e,t)})},C.prototype.removeAttr=function(e){return this.each(function(t,n){n.removeAttribute(e)})},C.prototype.html=function(e){return this.each(function(t,n){n.innerHTML=e})},C.prototype.val=function(e){return this.each(function(t,n){n.value=e})},C.prototype.append=function(e){return this.each(function(t,n){"object"==typeof e?n.appendChild(e):n.innerHTML=n.innerHTML+e})},C.prototype.remove=function(e){return this.each(function(t,n){e?n.removeChild(e):n.parentNode.removeChild(n)})},C.prototype.on=function(e,t){return this.each(function(n,a){a.attachEvent?a.attachEvent("on"+e,function(e){e.target=e.srcElement,t.call(a,e)}):a.addEventListener(e,t,!1)})},C.prototype.off=function(e,t){return this.each(function(n,a){a.detachEvent?a.detachEvent("on"+e,t):a.removeEventListener(e,t,!1)})},T.isLeapYear=function(e){return e%4===0&&e%100!==0||e%400===0},T.prototype.config={type:"date",range:!1,format:"yyyy-MM-dd",value:null,isInitValue:!0,min:"1900-1-1",max:"2099-12-31",trigger:"focus",show:!1,showBottom:!0,btns:["clear","now","confirm"],lang:"cn",theme:"default",position:null,calendar:!1,mark:{},zIndex:null,done:null,change:null},T.prototype.lang=function(){var e=this,t=e.config,n={cn:{weeks:["日","一","二","三","四","五","六"],time:["时","分","秒"],timeTips:"选择时间",startTime:"开始时间",endTime:"结束时间",dateTips:"返回日期",month:["一","二","三","四","五","六","七","八","九","十","十一","十二"],tools:{confirm:"确定",clear:"清空",now:"现在"}},en:{weeks:["Su","Mo","Tu","We","Th","Fr","Sa"],time:["Hours","Minutes","Seconds"],timeTips:"Select Time",startTime:"Start Time",endTime:"End Time",dateTips:"Select Date",month:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],tools:{confirm:"Confirm",clear:"Clear",now:"Now"}}};return n[t.lang]||n.cn},T.prototype.init=function(){var e=this,t=e.config,n="yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s",a="static"===t.position,i={year:"yyyy",month:"yyyy-MM",date:"yyyy-MM-dd",time:"HH:mm:ss",datetime:"yyyy-MM-dd HH:mm:ss"};t.elem=w(t.elem),t.eventElem=w(t.eventElem),t.elem[0]&&(t.range===!0&&(t.range="-"),t.format===i.date&&(t.format=i[t.type]),e.format=t.format.match(new RegExp(n+"|.","g"))||[],e.EXP_IF="",e.EXP_SPLIT="",w.each(e.format,function(t,a){var i=new RegExp(n).test(a)?"\\d{"+function(){return new RegExp(n).test(e.format[0===t?t+1:t-1]||"")?/^yyyy|y$/.test(a)?4:a.length:/^yyyy$/.test(a)?"1,4":/^y$/.test(a)?"1,308":"1,2"}()+"}":"\\"+a;e.EXP_IF=e.EXP_IF+i,e.EXP_SPLIT=e.EXP_SPLIT+"("+i+")"}),e.EXP_IF=new RegExp("^"+(t.range?e.EXP_IF+"\\s\\"+t.range+"\\s"+e.EXP_IF:e.EXP_IF)+"$"),e.EXP_SPLIT=new RegExp("^"+e.EXP_SPLIT+"$",""),e.isInput(t.elem[0])||"focus"===t.trigger&&(t.trigger="click"),t.elem.attr("lay-key")||(t.elem.attr("lay-key",e.index),t.eventElem.attr("lay-key",e.index)),t.mark=w.extend({},t.calendar&&"cn"===t.lang?{"0-1-1":"元旦","0-2-14":"情人","0-3-8":"妇女","0-3-12":"植树","0-4-1":"愚人","0-5-1":"劳动","0-5-4":"青年","0-6-1":"儿童","0-9-10":"教师","0-9-18":"国耻","0-10-1":"国庆","0-12-25":"圣诞"}:{},t.mark),w.each(["min","max"],function(e,n){var a=[],i=[];if("number"==typeof t[n]){var r=t[n],o=(new Date).getTime(),s=864e5,l=new Date(r?r0)return!0;var a=w.elem("div",{"class":"layui-laydate-header"}),i=[function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-y"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-m"});return e.innerHTML="",e}(),function(){var e=w.elem("div",{"class":"laydate-set-ym"}),t=w.elem("span"),n=w.elem("span");return e.appendChild(t),e.appendChild(n),e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-m"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-y"});return e.innerHTML="",e}()],d=w.elem("div",{"class":"layui-laydate-content"}),c=w.elem("table"),m=w.elem("thead"),u=w.elem("tr");w.each(i,function(e,t){a.appendChild(t)}),m.appendChild(u),w.each(new Array(6),function(e){var t=c.insertRow(0);w.each(new Array(7),function(a){if(0===e){var i=w.elem("th");i.innerHTML=n.weeks[a],u.appendChild(i)}t.insertCell(a)})}),c.insertBefore(m,c.children[0]),d.appendChild(c),r[e]=w.elem("div",{"class":"layui-laydate-main laydate-main-list-"+e}),r[e].appendChild(a),r[e].appendChild(d),o.push(i),s.push(d),l.push(c)}),w(d).html(function(){var e=[],i=[];return"datetime"===t.type&&e.push(''+n.timeTips+""),w.each(t.btns,function(e,r){var o=n.tools[r]||"btn";t.range&&"now"===r||(a&&"clear"===r&&(o="cn"===t.lang?"重置":"Reset"),i.push(''+o+""))}),e.push('"),e.join("")}()),w.each(r,function(e,t){i.appendChild(t)}),t.showBottom&&i.appendChild(d),/^#/.test(t.theme)){var m=w.elem("style"),u=["#{{id}} .layui-laydate-header{background-color:{{theme}};}","#{{id}} .layui-this{background-color:{{theme}} !important;}"].join("").replace(/{{id}}/g,e.elemID).replace(/{{theme}}/g,t.theme);"styleSheet"in m?(m.setAttribute("type","text/css"),m.styleSheet.cssText=u):m.innerHTML=u,w(i).addClass("laydate-theme-molv"),i.appendChild(m)}e.remove(T.thisElemDate),a?t.elem.append(i):(document.body.appendChild(i),e.position()),e.checkDate().calendar(),e.changeEvent(),T.thisElemDate=e.elemID,"function"==typeof t.ready&&t.ready(w.extend({},t.dateTime,{month:t.dateTime.month+1}))},T.prototype.remove=function(e){var t=this,n=(t.config,w("#"+(e||t.elemID)));return n.hasClass(c)||t.checkDate(function(){n.remove()}),t},T.prototype.position=function(){var e=this,t=e.config,n=e.bindElem||t.elem[0],a=n.getBoundingClientRect(),i=e.elem.offsetWidth,r=e.elem.offsetHeight,o=function(e){return e=e?"scrollLeft":"scrollTop",document.body[e]|document.documentElement[e]},s=function(e){return document.documentElement[e?"clientWidth":"clientHeight"]},l=5,d=a.left,c=a.bottom;d+i+l>s("width")&&(d=s("width")-i-l),c+r+l>s()&&(c=a.top>r?a.top-r:s()-r,c-=2*l),t.position&&(e.elem.style.position=t.position),e.elem.style.left=d+("fixed"===t.position?0:o(1))+"px",e.elem.style.top=c+("fixed"===t.position?0:o())+"px"},T.prototype.hint=function(e){var t=this,n=(t.config,w.elem("div",{"class":h}));t.elem&&(n.innerHTML=e||"",w(t.elem).find("."+h).remove(),t.elem.appendChild(n),clearTimeout(t.hinTimer),t.hinTimer=setTimeout(function(){w(t.elem).find("."+h).remove()},3e3))},T.prototype.getAsYM=function(e,t,n){return n?t--:t++,t<0&&(t=11,e--),t>11&&(t=0,e++),[e,t]},T.prototype.systemDate=function(e){var t=e||new Date;return{year:t.getFullYear(),month:t.getMonth(),date:t.getDate(),hours:e?e.getHours():0,minutes:e?e.getMinutes():0,seconds:e?e.getSeconds():0}},T.prototype.checkDate=function(e){var t,a,i=this,r=(new Date,i.config),o=r.dateTime=r.dateTime||i.systemDate(),s=i.bindElem||r.elem[0],l=(i.isInput(s)?"val":"html",i.isInput(s)?s.value:"static"===r.position?"":s.innerHTML),c=function(e){e.year>d[1]&&(e.year=d[1],a=!0),e.month>11&&(e.month=11,a=!0),e.hours>23&&(e.hours=0,a=!0),e.minutes>59&&(e.minutes=0,e.hours++,a=!0),e.seconds>59&&(e.seconds=0,e.minutes++,a=!0),t=n.getEndDate(e.month+1,e.year),e.date>t&&(e.date=t,a=!0)},m=function(e,t,n){var o=["startTime","endTime"];t=(t.match(i.EXP_SPLIT)||[]).slice(1),n=n||0,r.range&&(i[o[n]]=i[o[n]]||{}),w.each(i.format,function(s,l){var c=parseFloat(t[s]);t[s].length必须遵循下述格式:
        "+(r.range?r.format+" "+r.range+" "+r.format:r.format)+"
        已为你重置"),a=!0):l&&l.constructor===Date?r.dateTime=i.systemDate(l):(r.dateTime=i.systemDate(),delete i.startState,delete i.endState,delete i.startDate,delete i.endDate,delete i.startTime,delete i.endTime),c(o),a&&l&&i.setValue(r.range?i.endDate?i.parse():"":i.parse()),e&&e(),i)},T.prototype.mark=function(e,t){var n,a=this,i=a.config;return w.each(i.mark,function(e,a){var i=e.split("-");i[0]!=t[0]&&0!=i[0]||i[1]!=t[1]&&0!=i[1]||i[2]!=t[2]||(n=a||t[2])}),n&&e.html(''+n+""),a},T.prototype.limit=function(e,t,n,a){var i,r=this,o=r.config,l={},d=o[n>41?"endDate":"dateTime"],c=w.extend({},d,t||{});return w.each({now:c,min:o.min,max:o.max},function(e,t){l[e]=r.newDate(w.extend({year:t.year,month:t.month,date:t.date},function(){var e={};return w.each(a,function(n,a){e[a]=t[a]}),e}())).getTime()}),i=l.nowl.max,e&&e[i?"addClass":"removeClass"](s),i},T.prototype.calendar=function(e){var t,a,i,r=this,s=r.config,l=e||s.dateTime,c=new Date,m=r.lang(),u="date"!==s.type&&"datetime"!==s.type,h=e?1:0,y=w(r.table[h]).find("td"),f=w(r.elemHeader[h][2]).find("span");if(l.yeard[1]&&(l.year=d[1],r.hint("最高只能支持到公元"+d[1]+"年")),r.firstDate||(r.firstDate=w.extend({},l)),c.setFullYear(l.year,l.month,1),t=c.getDay(),a=n.getEndDate(l.month||12,l.year),i=n.getEndDate(l.month+1,l.year),w.each(y,function(e,n){var d=[l.year,l.month],c=0;n=w(n),n.removeAttr("class"),e=t&&e=n.firstDate.year&&(r.month=a.max.month,r.date=a.max.date),n.limit(w(i),r,t),M++}),w(u[f?0:1]).attr("lay-ym",M-8+"-"+T[1]).html(b+p+" - "+(M-1+p))}else if("month"===e)w.each(new Array(12),function(e){var i=w.elem("li",{"lay-ym":e}),s={year:T[0],month:e};e+1==T[1]&&w(i).addClass(o),i.innerHTML=r.month[e]+(f?"月":""),d.appendChild(i),T[0]=n.firstDate.year&&(s.date=a.max.date),n.limit(w(i),s,t)}),w(u[f?0:1]).attr("lay-ym",T[0]+"-"+T[1]).html(T[0]+p);else if("time"===e){var E=function(){w(d).find("ol").each(function(e,a){w(a).find("li").each(function(a,i){n.limit(w(i),[{hours:a},{hours:n[x].hours,minutes:a},{hours:n[x].hours,minutes:n[x].minutes,seconds:a}][e],t,[["hours"],["hours","minutes"],["hours","minutes","seconds"]][e])})}),a.range||n.limit(w(n.footer).find(g),n[x],0,["hours","minutes","seconds"])};a.range?n[x]||(n[x]={hours:0,minutes:0,seconds:0}):n[x]=i,w.each([24,60,60],function(e,t){var a=w.elem("li"),i=["

        "+r.time[e]+"

          "];w.each(new Array(t),function(t){i.push(""+w.digit(t,2)+"")}),a.innerHTML=i.join("")+"
        ",d.appendChild(a)}),E()}if(y&&h.removeChild(y),h.appendChild(d),"year"===e||"month"===e)w(n.elemMain[t]).addClass("laydate-ym-show"),w(d).find("li").on("click",function(){var r=0|w(this).attr("lay-ym");if(!w(this).hasClass(s)){if(0===t)i[e]=r,l&&(n.startDate[e]=r),n.limit(w(n.footer).find(g),null,0);else if(l)n.endDate[e]=r;else{var c="year"===e?n.getAsYM(r,T[1]-1,"sub"):n.getAsYM(T[0],r,"sub");w.extend(i,{year:c[0],month:c[1]})}"year"===a.type||"month"===a.type?(w(d).find("."+o).removeClass(o),w(this).addClass(o),"month"===a.type&&"year"===e&&(n.listYM[t][0]=r,l&&(n[["startDate","endDate"][t]].year=r),n.list("month",t))):(n.checkDate("limit").calendar(),n.closeList()),n.setBtnStatus(),a.range||n.done(null,"change"),w(n.footer).find(D).removeClass(s)}});else{var S=w.elem("span",{"class":v}),k=function(){w(d).find("ol").each(function(e){var t=this,a=w(t).find("li");t.scrollTop=30*(n[x][C[e]]-2),t.scrollTop<=0&&a.each(function(e,n){if(!w(this).hasClass(s))return t.scrollTop=30*(e-2),!0})})},H=w(c[2]).find("."+v);k(),S.innerHTML=a.range?[r.startTime,r.endTime][t]:r.timeTips,w(n.elemMain[t]).addClass("laydate-time-show"),H[0]&&H.remove(),c[2].appendChild(S),w(d).find("ol").each(function(e){var t=this;w(t).find("li").on("click",function(){var r=0|this.innerHTML;w(this).hasClass(s)||(a.range?n[x][C[e]]=r:i[C[e]]=r,w(t).find("."+o).removeClass(o),w(this).addClass(o),E(),k(),(n.endDate||"time"===a.type)&&n.done(null,"change"),n.setBtnStatus())})})}return n},T.prototype.listYM=[],T.prototype.closeList=function(){var e=this;e.config;w.each(e.elemCont,function(t,n){w(this).find("."+m).remove(),w(e.elemMain[t]).removeClass("laydate-ym-show laydate-time-show")}),w(e.elem).find("."+v).remove()},T.prototype.setBtnStatus=function(e,t,n){var a,i=this,r=i.config,o=w(i.footer).find(g),d=r.range&&"date"!==r.type&&"time"!==r.type;d&&(t=t||i.startDate,n=n||i.endDate,a=i.newDate(t).getTime()>i.newDate(n).getTime(),i.limit(null,t)||i.limit(null,n)?o.addClass(s):o[a?"addClass":"removeClass"](s),e&&a&&i.hint("string"==typeof e?l.replace(/日期/g,e):l))},T.prototype.parse=function(e,t){var n=this,a=n.config,i=t||(e?w.extend({},n.endDate,n.endTime):a.range?w.extend({},n.startDate,n.startTime):a.dateTime),r=n.format.concat();return w.each(r,function(e,t){/yyyy|y/.test(t)?r[e]=w.digit(i.year,t.length):/MM|M/.test(t)?r[e]=w.digit(i.month+1,t.length):/dd|d/.test(t)?r[e]=w.digit(i.date,t.length):/HH|H/.test(t)?r[e]=w.digit(i.hours,t.length):/mm|m/.test(t)?r[e]=w.digit(i.minutes,t.length):/ss|s/.test(t)&&(r[e]=w.digit(i.seconds,t.length))}),a.range&&!e?r.join("")+" "+a.range+" "+n.parse(1):r.join("")},T.prototype.newDate=function(e){return e=e||{},new Date(e.year||1,e.month||0,e.date||1,e.hours||0,e.minutes||0,e.seconds||0)},T.prototype.setValue=function(e){var t=this,n=t.config,a=t.bindElem||n.elem[0],i=t.isInput(a)?"val":"html";return"static"===n.position||w(a)[i](e||""),this},T.prototype.stampRange=function(){var e,t,n=this,a=n.config,i=w(n.elem).find("td");if(a.range&&!n.endDate&&w(n.footer).find(g).addClass(s),n.endDate)return e=n.newDate({year:n.startDate.year,month:n.startDate.month,date:n.startDate.date}).getTime(),t=n.newDate({year:n.endDate.year,month:n.endDate.month,date:n.endDate.date}).getTime(),e>t?n.hint(l):void w.each(i,function(a,i){var r=w(i).attr("lay-ymd").split("-"),s=n.newDate({year:r[0],month:r[1]-1,date:r[2]}).getTime();w(i).removeClass(u+" "+o),s!==e&&s!==t||w(i).addClass(w(i).hasClass(y)||w(i).hasClass(f)?u:o),s>e&&s0&&t-1 in e)}function r(e,t,n){if(pe.isFunction(t))return pe.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return pe.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(Ce.test(t))return pe.filter(t,e,n);t=pe.filter(t,e)}return pe.grep(e,function(e){return pe.inArray(e,t)>-1!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t={};return pe.each(e.match(De)||[],function(e,n){t[n]=!0}),t}function a(){re.addEventListener?(re.removeEventListener("DOMContentLoaded",s),e.removeEventListener("load",s)):(re.detachEvent("onreadystatechange",s),e.detachEvent("onload",s))}function s(){(re.addEventListener||"load"===e.event.type||"complete"===re.readyState)&&(a(),pe.ready())}function u(e,t,n){if(void 0===n&&1===e.nodeType){var r="data-"+t.replace(_e,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:qe.test(n)?pe.parseJSON(n):n)}catch(i){}pe.data(e,t,n)}else n=void 0}return n}function l(e){var t;for(t in e)if(("data"!==t||!pe.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,r){if(He(e)){var i,o,a=pe.expando,s=e.nodeType,u=s?pe.cache:e,l=s?e[a]:e[a]&&a;if(l&&u[l]&&(r||u[l].data)||void 0!==n||"string"!=typeof t)return l||(l=s?e[a]=ne.pop()||pe.guid++:a),u[l]||(u[l]=s?{}:{toJSON:pe.noop}),"object"!=typeof t&&"function"!=typeof t||(r?u[l]=pe.extend(u[l],t):u[l].data=pe.extend(u[l].data,t)),o=u[l],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[pe.camelCase(t)]=n),"string"==typeof t?(i=o[t],null==i&&(i=o[pe.camelCase(t)])):i=o,i}}function f(e,t,n){if(He(e)){var r,i,o=e.nodeType,a=o?pe.cache:e,s=o?e[pe.expando]:pe.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){pe.isArray(t)?t=t.concat(pe.map(t,pe.camelCase)):t in r?t=[t]:(t=pe.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;for(;i--;)delete r[t[i]];if(n?!l(r):!pe.isEmptyObject(r))return}(n||(delete a[s].data,l(a[s])))&&(o?pe.cleanData([e],!0):fe.deleteExpando||a!=a.window?delete a[s]:a[s]=void 0)}}}function d(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return pe.css(e,t,"")},u=s(),l=n&&n[3]||(pe.cssNumber[t]?"":"px"),c=(pe.cssNumber[t]||"px"!==l&&+u)&&Me.exec(pe.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do o=o||".5",c/=o,pe.style(e,t,c+l);while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function p(e){var t=ze.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function h(e,t){var n,r,i=0,o="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||pe.nodeName(r,t)?o.push(r):pe.merge(o,h(r,t));return void 0===t||t&&pe.nodeName(e,t)?pe.merge([e],o):o}function g(e,t){for(var n,r=0;null!=(n=e[r]);r++)pe._data(n,"globalEval",!t||pe._data(t[r],"globalEval"))}function m(e){Be.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t,n,r,i){for(var o,a,s,u,l,c,f,d=e.length,y=p(t),v=[],x=0;x"!==f[1]||Ve.test(a)?0:u:u.firstChild,o=a&&a.childNodes.length;o--;)pe.nodeName(c=a.childNodes[o],"tbody")&&!c.childNodes.length&&a.removeChild(c);for(pe.merge(v,u.childNodes),u.textContent="";u.firstChild;)u.removeChild(u.firstChild);u=y.lastChild}else v.push(t.createTextNode(a));for(u&&y.removeChild(u),fe.appendChecked||pe.grep(h(v,"input"),m),x=0;a=v[x++];)if(r&&pe.inArray(a,r)>-1)i&&i.push(a);else if(s=pe.contains(a.ownerDocument,a),u=h(y.appendChild(a),"script"),s&&g(u),n)for(o=0;a=u[o++];)Ie.test(a.type||"")&&n.push(a);return u=null,y}function v(){return!0}function x(){return!1}function b(){try{return re.activeElement}catch(e){}}function w(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)w(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),i===!1)i=x;else if(!i)return e;return 1===o&&(a=i,i=function(e){return pe().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=pe.guid++)),e.each(function(){pe.event.add(this,t,i,r,n)})}function T(e,t){return pe.nodeName(e,"table")&&pe.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function C(e){return e.type=(null!==pe.find.attr(e,"type"))+"/"+e.type,e}function E(e){var t=it.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function N(e,t){if(1===t.nodeType&&pe.hasData(e)){var n,r,i,o=pe._data(e),a=pe._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;r1&&"string"==typeof p&&!fe.checkClone&&rt.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),S(o,t,n,r)});if(f&&(l=y(t,e[0].ownerDocument,!1,e,r),i=l.firstChild,1===l.childNodes.length&&(l=i),i||r)){for(s=pe.map(h(l,"script"),C),a=s.length;c")).appendTo(t.documentElement),t=(ut[0].contentWindow||ut[0].contentDocument).document,t.write(),t.close(),n=D(e,t),ut.detach()),lt[e]=n),n}function L(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function H(e){if(e in Et)return e;for(var t=e.charAt(0).toUpperCase()+e.slice(1),n=Ct.length;n--;)if(e=Ct[n]+t,e in Et)return e}function q(e,t){for(var n,r,i,o=[],a=0,s=e.length;a=0&&n=0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},isPlainObject:function(e){var t;if(!e||"object"!==pe.type(e)||e.nodeType||pe.isWindow(e))return!1;try{if(e.constructor&&!ce.call(e,"constructor")&&!ce.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}if(!fe.ownFirst)for(t in e)return ce.call(e,t);for(t in e);return void 0===t||ce.call(e,t)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?ue[le.call(e)]||"object":typeof e},globalEval:function(t){t&&pe.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(ge,"ms-").replace(me,ye)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var r,i=0;if(n(e))for(r=e.length;iT.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[];return e}function r(e){return e[P]=!0,e}function i(e){var t=H.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)T.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||V)-(~e.sourceIndex||V);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function u(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function l(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function c(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function f(){}function d(e){for(var t=0,n=e.length,r="";t1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,n,r){for(var i=0,o=n.length;i-1&&(r[l]=!(a[l]=f))}}else x=m(x===a?x.splice(h,x.length):x),o?o(null,a,x,u):Q.apply(a,x)})}function v(e){for(var t,n,r,i=e.length,o=T.relative[e[0].type],a=o||T.relative[" "],s=o?1:0,u=p(function(e){return e===t},a,!0),l=p(function(e){return ee(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==A)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];s1&&h(c),s>1&&d(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(se,"$1"),n,s0,o=e.length>0,a=function(r,a,s,u,l){var c,f,d,p=0,h="0",g=r&&[],y=[],v=A,x=r||o&&T.find.TAG("*",l),b=W+=null==v?1:Math.random()||.1,w=x.length;for(l&&(A=a===H||a||l);h!==w&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===H||(L(c),s=!_);d=e[f++];)if(d(c,a||H,s)){u.push(c);break}l&&(W=b)}i&&((c=!d&&c)&&p--,r&&g.push(c))}if(p+=h,i&&h!==p){for(f=0;d=n[f++];)d(g,y,a,s);if(r){if(p>0)for(;h--;)g[h]||y[h]||(y[h]=G.call(u));y=m(y)}Q.apply(u,y),l&&!r&&y.length>0&&p+n.length>1&&t.uniqueSort(u)}return l&&(W=b,A=v),g};return i?r(a):a}var b,w,T,C,E,N,k,S,A,D,j,L,H,q,_,F,M,O,R,P="sizzle"+1*new Date,B=e.document,W=0,I=0,$=n(),z=n(),X=n(),U=function(e,t){return e===t&&(j=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],G=J.pop,K=J.push,Q=J.push,Z=J.slice,ee=function(e,t){for(var n=0,r=e.length;n+~]|"+ne+")"+ne+"*"),ce=new RegExp("="+ne+"*([^\\]'\"]*?)"+ne+"*\\]","g"),fe=new RegExp(oe),de=new RegExp("^"+re+"$"),pe={ID:new RegExp("^#("+re+")"),CLASS:new RegExp("^\\.("+re+")"),TAG:new RegExp("^("+re+"|[*])"),ATTR:new RegExp("^"+ie),PSEUDO:new RegExp("^"+oe),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ne+"*(even|odd|(([+-]|)(\\d*)n|)"+ne+"*(?:([+-]|)"+ne+"*(\\d+)|))"+ne+"*\\)|)","i"),bool:new RegExp("^(?:"+te+")$","i"),needsContext:new RegExp("^"+ne+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ne+"*((?:-\\d)?\\d*)"+ne+"*\\)|)(?=[^-]|$)","i")},he=/^(?:input|select|textarea|button)$/i,ge=/^h\d$/i,me=/^[^{]+\{\s*\[native \w/,ye=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ve=/[+~]/,xe=/'|\\/g,be=new RegExp("\\\\([\\da-f]{1,6}"+ne+"?|("+ne+")|.)","ig"),we=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},Te=function(){L()};try{Q.apply(J=Z.call(B.childNodes),B.childNodes),J[B.childNodes.length].nodeType}catch(Ce){Q={apply:J.length?function(e,t){K.apply(e,Z.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}w=t.support={},E=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},L=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:B;return r!==H&&9===r.nodeType&&r.documentElement?(H=r,q=H.documentElement,_=!E(H),(n=H.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Te,!1):n.attachEvent&&n.attachEvent("onunload",Te)),w.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),w.getElementsByTagName=i(function(e){return e.appendChild(H.createComment("")),!e.getElementsByTagName("*").length}),w.getElementsByClassName=me.test(H.getElementsByClassName),w.getById=i(function(e){return q.appendChild(e).id=P,!H.getElementsByName||!H.getElementsByName(P).length}),w.getById?(T.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&_){var n=t.getElementById(e);return n?[n]:[]}},T.filter.ID=function(e){var t=e.replace(be,we);return function(e){return e.getAttribute("id")===t}}):(delete T.find.ID,T.filter.ID=function(e){var t=e.replace(be,we);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}}),T.find.TAG=w.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):w.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},T.find.CLASS=w.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&_)return t.getElementsByClassName(e)},M=[],F=[],(w.qsa=me.test(H.querySelectorAll))&&(i(function(e){q.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&F.push("[*^$]="+ne+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||F.push("\\["+ne+"*(?:value|"+te+")"),e.querySelectorAll("[id~="+P+"-]").length||F.push("~="),e.querySelectorAll(":checked").length||F.push(":checked"),e.querySelectorAll("a#"+P+"+*").length||F.push(".#.+[+~]")}),i(function(e){var t=H.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&F.push("name"+ne+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||F.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),F.push(",.*:")})),(w.matchesSelector=me.test(O=q.matches||q.webkitMatchesSelector||q.mozMatchesSelector||q.oMatchesSelector||q.msMatchesSelector))&&i(function(e){w.disconnectedMatch=O.call(e,"div"),O.call(e,"[s!='']:x"),M.push("!=",oe)}),F=F.length&&new RegExp(F.join("|")),M=M.length&&new RegExp(M.join("|")),t=me.test(q.compareDocumentPosition),R=t||me.test(q.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},U=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n?n:(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!w.sortDetached&&t.compareDocumentPosition(e)===n?e===H||e.ownerDocument===B&&R(B,e)?-1:t===H||t.ownerDocument===B&&R(B,t)?1:D?ee(D,e)-ee(D,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===H?-1:t===H?1:i?-1:o?1:D?ee(D,e)-ee(D,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===B?-1:u[r]===B?1:0},H):H},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==H&&L(e),n=n.replace(ce,"='$1']"),w.matchesSelector&&_&&!X[n+" "]&&(!M||!M.test(n))&&(!F||!F.test(n)))try{var r=O.call(e,n);if(r||w.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return t(n,H,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==H&&L(e),R(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==H&&L(e);var n=T.attrHandle[t.toLowerCase()],r=n&&Y.call(T.attrHandle,t.toLowerCase())?n(e,t,!_):void 0;return void 0!==r?r:w.attributes||!_?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!w.detectDuplicates,D=!w.sortStable&&e.slice(0),e.sort(U),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return D=null,e},C=t.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=C(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=C(t);return n},T=t.selectors={cacheLength:50,createPseudo:r,match:pe,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(be,we),e[3]=(e[3]||e[4]||e[5]||"").replace(be,we),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&fe.test(n)&&(t=N(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(be,we).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=$[e+" "];return t||(t=new RegExp("(^|"+ne+")"+e+"("+ne+"|$)"))&&$(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?"!="===n:!n||(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ae," ")+" ").indexOf(r)>-1:"|="===n&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,d,p,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s,x=!1;if(m){if(o){for(;g;){for(d=t;d=d[g];)if(s?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(d=m,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}), +l=c[e]||[],p=l[0]===W&&l[1],x=p&&l[2],d=p&&m.childNodes[p];d=++p&&d&&d[g]||(x=p=0)||h.pop();)if(1===d.nodeType&&++x&&d===t){c[e]=[W,p,x];break}}else if(v&&(d=t,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),l=c[e]||[],p=l[0]===W&&l[1],x=p),x===!1)for(;(d=++p&&d&&d[g]||(x=p=0)||h.pop())&&((s?d.nodeName.toLowerCase()!==y:1!==d.nodeType)||!++x||(v&&(f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),c[e]=[W,x]),d!==t)););return x-=i,x===r||x%r===0&&x/r>=0}}},PSEUDO:function(e,n){var i,o=T.pseudos[e]||T.setFilters[e.toLowerCase()]||t.error("unsupported pseudo: "+e);return o[P]?o(n):o.length>1?(i=[e,e,"",n],T.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),a=i.length;a--;)r=ee(e,i[a]),e[r]=!(t[r]=i[a])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=k(e.replace(se,"$1"));return i[P]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(be,we),function(t){return(t.textContent||t.innerText||C(t)).indexOf(e)>-1}}),lang:r(function(e){return de.test(e||"")||t.error("unsupported lang: "+e),e=e.replace(be,we).toLowerCase(),function(t){var n;do if(n=_?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===q},focus:function(e){return e===H.activeElement&&(!H.hasFocus||H.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!T.pseudos.empty(e)},header:function(e){return ge.test(e.nodeName)},input:function(e){return he.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:l(function(){return[0]}),last:l(function(e,t){return[t-1]}),eq:l(function(e,t,n){return[n<0?n+t:n]}),even:l(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:l(function(e,t,n){for(var r=n<0?n+t:n;++r2&&"ID"===(a=o[0]).type&&w.getById&&9===t.nodeType&&_&&T.relative[o[1].type]){if(t=(T.find.ID(a.matches[0].replace(be,we),t)||[])[0],!t)return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=pe.needsContext.test(e)?0:o.length;i--&&(a=o[i],!T.relative[s=a.type]);)if((u=T.find[s])&&(r=u(a.matches[0].replace(be,we),ve.test(o[0].type)&&c(t.parentNode)||t))){if(o.splice(i,1),e=r.length&&d(o),!e)return Q.apply(n,r),n;break}}return(l||k(e,f))(r,t,!_,n,!t||ve.test(e)&&c(t.parentNode)||t),n},w.sortStable=P.split("").sort(U).join("")===P,w.detectDuplicates=!!j,L(),w.sortDetached=i(function(e){return 1&e.compareDocumentPosition(H.createElement("div"))}),i(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||o("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),w.attributes&&i(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||o("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),i(function(e){return null==e.getAttribute("disabled")})||o(te,function(e,t,n){var r;if(!n)return e[t]===!0?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e);pe.find=ve,pe.expr=ve.selectors,pe.expr[":"]=pe.expr.pseudos,pe.uniqueSort=pe.unique=ve.uniqueSort,pe.text=ve.getText,pe.isXMLDoc=ve.isXML,pe.contains=ve.contains;var xe=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&pe(e).is(n))break;r.push(e)}return r},be=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},we=pe.expr.match.needsContext,Te=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Ce=/^.[^:#\[\.,]*$/;pe.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?pe.find.matchesSelector(r,e)?[r]:[]:pe.find.matches(e,pe.grep(t,function(e){return 1===e.nodeType}))},pe.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(pe(e).filter(function(){for(t=0;t1?pe.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},filter:function(e){return this.pushStack(r(this,e||[],!1))},not:function(e){return this.pushStack(r(this,e||[],!0))},is:function(e){return!!r(this,"string"==typeof e&&we.test(e)?pe(e):e||[],!1).length}});var Ee,Ne=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,ke=pe.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||Ee,"string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:Ne.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof pe?t[0]:t,pe.merge(this,pe.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:re,!0)),Te.test(r[1])&&pe.isPlainObject(t))for(r in t)pe.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}if(i=re.getElementById(r[2]),i&&i.parentNode){if(i.id!==r[2])return Ee.find(e);this.length=1,this[0]=i}return this.context=re,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):pe.isFunction(e)?"undefined"!=typeof n.ready?n.ready(e):e(pe):(void 0!==e.selector&&(this.selector=e.selector,this.context=e.context),pe.makeArray(e,this))};ke.prototype=pe.fn,Ee=pe(re);var Se=/^(?:parents|prev(?:Until|All))/,Ae={children:!0,contents:!0,next:!0,prev:!0};pe.fn.extend({has:function(e){var t,n=pe(e,this),r=n.length;return this.filter(function(){for(t=0;t-1:1===n.nodeType&&pe.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?pe.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?pe.inArray(this[0],pe(e)):pe.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(pe.uniqueSort(pe.merge(this.get(),pe(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),pe.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return xe(e,"parentNode")},parentsUntil:function(e,t,n){return xe(e,"parentNode",n)},next:function(e){return i(e,"nextSibling")},prev:function(e){return i(e,"previousSibling")},nextAll:function(e){return xe(e,"nextSibling")},prevAll:function(e){return xe(e,"previousSibling")},nextUntil:function(e,t,n){return xe(e,"nextSibling",n)},prevUntil:function(e,t,n){return xe(e,"previousSibling",n)},siblings:function(e){return be((e.parentNode||{}).firstChild,e)},children:function(e){return be(e.firstChild)},contents:function(e){return pe.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:pe.merge([],e.childNodes)}},function(e,t){pe.fn[e]=function(n,r){var i=pe.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=pe.filter(r,i)),this.length>1&&(Ae[e]||(i=pe.uniqueSort(i)),Se.test(e)&&(i=i.reverse())),this.pushStack(i)}});var De=/\S+/g;pe.Callbacks=function(e){e="string"==typeof e?o(e):pe.extend({},e);var t,n,r,i,a=[],s=[],u=-1,l=function(){for(i=e.once,r=t=!0;s.length;u=-1)for(n=s.shift();++u-1;)a.splice(n,1),n<=u&&u--}),this},has:function(e){return e?pe.inArray(e,a)>-1:a.length>0},empty:function(){return a&&(a=[]),this},disable:function(){return i=s=[],a=n="",this},disabled:function(){return!a},lock:function(){return i=!0,n||c.disable(),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],s.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},pe.extend({Deferred:function(e){var t=[["resolve","done",pe.Callbacks("once memory"),"resolved"],["reject","fail",pe.Callbacks("once memory"),"rejected"],["notify","progress",pe.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return pe.Deferred(function(n){pe.each(t,function(t,o){var a=pe.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&pe.isFunction(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[o[0]+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?pe.extend(e,r):r}},i={};return r.pipe=r.then,pe.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=ie.call(arguments),a=o.length,s=1!==a||e&&pe.isFunction(e.promise)?a:0,u=1===s?e:pe.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?ie.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=new Array(a),n=new Array(a),r=new Array(a);i0||(je.resolveWith(re,[pe]),pe.fn.triggerHandler&&(pe(re).triggerHandler("ready"),pe(re).off("ready"))))}}),pe.ready.promise=function(t){if(!je)if(je=pe.Deferred(),"complete"===re.readyState||"loading"!==re.readyState&&!re.documentElement.doScroll)e.setTimeout(pe.ready);else if(re.addEventListener)re.addEventListener("DOMContentLoaded",s),e.addEventListener("load",s);else{re.attachEvent("onreadystatechange",s),e.attachEvent("onload",s);var n=!1;try{n=null==e.frameElement&&re.documentElement}catch(r){}n&&n.doScroll&&!function i(){if(!pe.isReady){try{n.doScroll("left")}catch(t){return e.setTimeout(i,50)}a(),pe.ready()}}()}return je.promise(t)},pe.ready.promise();var Le;for(Le in pe(fe))break;fe.ownFirst="0"===Le,fe.inlineBlockNeedsLayout=!1,pe(function(){var e,t,n,r;n=re.getElementsByTagName("body")[0],n&&n.style&&(t=re.createElement("div"),r=re.createElement("div"),r.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",n.appendChild(r).appendChild(t),"undefined"!=typeof t.style.zoom&&(t.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",fe.inlineBlockNeedsLayout=e=3===t.offsetWidth,e&&(n.style.zoom=1)),n.removeChild(r))}),function(){var e=re.createElement("div");fe.deleteExpando=!0;try{delete e.test}catch(t){fe.deleteExpando=!1}e=null}();var He=function(e){var t=pe.noData[(e.nodeName+" ").toLowerCase()],n=+e.nodeType||1;return(1===n||9===n)&&(!t||t!==!0&&e.getAttribute("classid")===t)},qe=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,_e=/([A-Z])/g;pe.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?pe.cache[e[pe.expando]]:e[pe.expando],!!e&&!l(e)},data:function(e,t,n){return c(e,t,n)},removeData:function(e,t){return f(e,t)},_data:function(e,t,n){return c(e,t,n,!0)},_removeData:function(e,t){return f(e,t,!0)}}),pe.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=pe.data(o),1===o.nodeType&&!pe._data(o,"parsedAttrs"))){for(n=a.length;n--;)a[n]&&(r=a[n].name,0===r.indexOf("data-")&&(r=pe.camelCase(r.slice(5)),u(o,r,i[r])));pe._data(o,"parsedAttrs",!0)}return i}return"object"==typeof e?this.each(function(){pe.data(this,e)}):arguments.length>1?this.each(function(){pe.data(this,e,t)}):o?u(o,e,pe.data(o,e)):void 0},removeData:function(e){return this.each(function(){pe.removeData(this,e)})}}),pe.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=pe._data(e,t),n&&(!r||pe.isArray(n)?r=pe._data(e,t,pe.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=pe.queue(e,t),r=n.length,i=n.shift(),o=pe._queueHooks(e,t),a=function(){pe.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return pe._data(e,n)||pe._data(e,n,{empty:pe.Callbacks("once memory").add(function(){pe._removeData(e,t+"queue"),pe._removeData(e,n)})})}}),pe.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length
        a",fe.leadingWhitespace=3===e.firstChild.nodeType,fe.tbody=!e.getElementsByTagName("tbody").length,fe.htmlSerialize=!!e.getElementsByTagName("link").length,fe.html5Clone="<:nav>"!==re.createElement("nav").cloneNode(!0).outerHTML,n.type="checkbox",n.checked=!0,t.appendChild(n),fe.appendChecked=n.checked,e.innerHTML="",fe.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,t.appendChild(e),n=re.createElement("input"),n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),e.appendChild(n),fe.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,fe.noCloneEvent=!!e.addEventListener,e[pe.expando]=1,fe.attributes=!e.getAttribute(pe.expando)}();var Xe={option:[1,""],legend:[1,"
        ","
        "],area:[1,"",""],param:[1,"",""],thead:[1,"","
        "],tr:[2,"","
        "],col:[2,"","
        "],td:[3,"","
        "],_default:fe.htmlSerialize?[0,"",""]:[1,"X
        ","
        "]};Xe.optgroup=Xe.option,Xe.tbody=Xe.tfoot=Xe.colgroup=Xe.caption=Xe.thead,Xe.th=Xe.td;var Ue=/<|&#?\w+;/,Ve=/-1&&(h=p.split("."),p=h.shift(),h.sort()),a=p.indexOf(":")<0&&"on"+p,t=t[pe.expando]?t:new pe.Event(p,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=h.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:pe.makeArray(n,[t]),l=pe.event.special[p]||{},i||!l.trigger||l.trigger.apply(r,n)!==!1)){if(!i&&!l.noBubble&&!pe.isWindow(r)){for(u=l.delegateType||p,Ke.test(u+p)||(s=s.parentNode);s;s=s.parentNode)d.push(s),c=s;c===(r.ownerDocument||re)&&d.push(c.defaultView||c.parentWindow||e)}for(f=0;(s=d[f++])&&!t.isPropagationStopped();)t.type=f>1?u:l.bindType||p,o=(pe._data(s,"events")||{})[t.type]&&pe._data(s,"handle"),o&&o.apply(s,n),o=a&&s[a],o&&o.apply&&He(s)&&(t.result=o.apply(s,n),t.result===!1&&t.preventDefault());if(t.type=p,!i&&!t.isDefaultPrevented()&&(!l._default||l._default.apply(d.pop(),n)===!1)&&He(r)&&a&&r[p]&&!pe.isWindow(r)){c=r[a],c&&(r[a]=null),pe.event.triggered=p;try{r[p]()}catch(g){}pe.event.triggered=void 0,c&&(r[a]=c)}return t.result}},dispatch:function(e){e=pe.event.fix(e);var t,n,r,i,o,a=[],s=ie.call(arguments),u=(pe._data(this,"events")||{})[e.type]||[],l=pe.event.special[e.type]||{};if(s[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){for(a=pe.event.handlers.call(this,e,u),t=0;(i=a[t++])&&!e.isPropagationStopped();)for(e.currentTarget=i.elem,n=0;(o=i.handlers[n++])&&!e.isImmediatePropagationStopped();)e.rnamespace&&!e.rnamespace.test(o.namespace)||(e.handleObj=o,e.data=o.data,r=((pe.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s),void 0!==r&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()));return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,a=[],s=t.delegateCount,u=e.target;if(s&&u.nodeType&&("click"!==e.type||isNaN(e.button)||e.button<1))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(r=[],n=0;n-1:pe.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&a.push({elem:u,handlers:r})}return s]","i"),tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,nt=/\s*$/g,at=p(re),st=at.appendChild(re.createElement("div"));pe.extend({htmlPrefilter:function(e){return e.replace(tt,"<$1>")},clone:function(e,t,n){var r,i,o,a,s,u=pe.contains(e.ownerDocument,e);if(fe.html5Clone||pe.isXMLDoc(e)||!et.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(st.innerHTML=e.outerHTML,st.removeChild(o=st.firstChild)),!(fe.noCloneEvent&&fe.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||pe.isXMLDoc(e)))for(r=h(o),s=h(e),a=0;null!=(i=s[a]);++a)r[a]&&k(i,r[a]);if(t)if(n)for(s=s||h(e),r=r||h(o),a=0;null!=(i=s[a]);a++)N(i,r[a]);else N(e,o);return r=h(o,"script"),r.length>0&&g(r,!u&&h(e,"script")),r=s=i=null,o},cleanData:function(e,t){for(var n,r,i,o,a=0,s=pe.expando,u=pe.cache,l=fe.attributes,c=pe.event.special;null!=(n=e[a]);a++)if((t||He(n))&&(i=n[s],o=i&&u[i])){if(o.events)for(r in o.events)c[r]?pe.event.remove(n,r):pe.removeEvent(n,r,o.handle);u[i]&&(delete u[i],l||"undefined"==typeof n.removeAttribute?n[s]=void 0:n.removeAttribute(s),ne.push(i))}}}),pe.fn.extend({domManip:S,detach:function(e){return A(this,e,!0)},remove:function(e){return A(this,e)},text:function(e){return Pe(this,function(e){return void 0===e?pe.text(this):this.empty().append((this[0]&&this[0].ownerDocument||re).createTextNode(e))},null,e,arguments.length)},append:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.appendChild(e)}})},prepend:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++){for(1===e.nodeType&&pe.cleanData(h(e,!1));e.firstChild;)e.removeChild(e.firstChild);e.options&&pe.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return pe.clone(this,e,t)})},html:function(e){return Pe(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e)return 1===t.nodeType?t.innerHTML.replace(Ze,""):void 0;if("string"==typeof e&&!nt.test(e)&&(fe.htmlSerialize||!et.test(e))&&(fe.leadingWhitespace||!$e.test(e))&&!Xe[(We.exec(e)||["",""])[1].toLowerCase()]){e=pe.htmlPrefilter(e);try{for(;nt",t=l.getElementsByTagName("td"),t[0].style.cssText="margin:0;border:0;padding:0;display:none",o=0===t[0].offsetHeight,o&&(t[0].style.display="",t[1].style.display="none",o=0===t[0].offsetHeight)),f.removeChild(u)}var n,r,i,o,a,s,u=re.createElement("div"),l=re.createElement("div");l.style&&(l.style.cssText="float:left;opacity:.5",fe.opacity="0.5"===l.style.opacity,fe.cssFloat=!!l.style.cssFloat,l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",fe.clearCloneStyle="content-box"===l.style.backgroundClip,u=re.createElement("div"),u.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",l.innerHTML="",u.appendChild(l),fe.boxSizing=""===l.style.boxSizing||""===l.style.MozBoxSizing||""===l.style.WebkitBoxSizing,pe.extend(fe,{reliableHiddenOffsets:function(){return null==n&&t(),o},boxSizingReliable:function(){return null==n&&t(),i},pixelMarginRight:function(){return null==n&&t(),r},pixelPosition:function(){return null==n&&t(),n},reliableMarginRight:function(){return null==n&&t(),a},reliableMarginLeft:function(){return null==n&&t(),s}}))}();var ht,gt,mt=/^(top|right|bottom|left)$/;e.getComputedStyle?(ht=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n.getPropertyValue(t)||n[t]:void 0,""!==a&&void 0!==a||pe.contains(e.ownerDocument,e)||(a=pe.style(e,t)),n&&!fe.pixelMarginRight()&&ft.test(a)&&ct.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o),void 0===a?a:a+""}):pt.currentStyle&&(ht=function(e){return e.currentStyle},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n[t]:void 0,null==a&&s&&s[t]&&(a=s[t]),ft.test(a)&&!mt.test(t)&&(r=s.left,i=e.runtimeStyle,o=i&&i.left,o&&(i.left=e.currentStyle.left),s.left="fontSize"===t?"1em":a,a=s.pixelLeft+"px",s.left=r,o&&(i.left=o)),void 0===a?a:a+""||"auto"});var yt=/alpha\([^)]*\)/i,vt=/opacity\s*=\s*([^)]*)/i,xt=/^(none|table(?!-c[ea]).+)/,bt=new RegExp("^("+Fe+")(.*)$","i"),wt={position:"absolute",visibility:"hidden",display:"block"},Tt={letterSpacing:"0",fontWeight:"400"},Ct=["Webkit","O","Moz","ms"],Et=re.createElement("div").style;pe.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=gt(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":fe.cssFloat?"cssFloat":"styleFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=pe.camelCase(t),u=e.style;if(t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:u[t];if(o=typeof n,"string"===o&&(i=Me.exec(n))&&i[1]&&(n=d(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(pe.cssNumber[s]?"":"px")),fe.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),!(a&&"set"in a&&void 0===(n=a.set(e,n,r)))))try{u[t]=n}catch(l){}}},css:function(e,t,n,r){var i,o,a,s=pe.camelCase(t);return t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],a&&"get"in a&&(o=a.get(e,!0,n)),void 0===o&&(o=gt(e,t,r)),"normal"===o&&t in Tt&&(o=Tt[t]),""===n||n?(i=parseFloat(o),n===!0||isFinite(i)?i||0:o):o}}),pe.each(["height","width"],function(e,t){pe.cssHooks[t]={get:function(e,n,r){if(n)return xt.test(pe.css(e,"display"))&&0===e.offsetWidth?dt(e,wt,function(){return M(e,t,r)}):M(e,t,r)},set:function(e,n,r){var i=r&&ht(e);return _(e,n,r?F(e,t,r,fe.boxSizing&&"border-box"===pe.css(e,"boxSizing",!1,i),i):0)}}}),fe.opacity||(pe.cssHooks.opacity={get:function(e,t){return vt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=pe.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===pe.trim(o.replace(yt,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=yt.test(o)?o.replace(yt,i):o+" "+i)}}),pe.cssHooks.marginRight=L(fe.reliableMarginRight,function(e,t){if(t)return dt(e,{display:"inline-block"},gt,[e,"marginRight"])}),pe.cssHooks.marginLeft=L(fe.reliableMarginLeft,function(e,t){if(t)return(parseFloat(gt(e,"marginLeft"))||(pe.contains(e.ownerDocument,e)?e.getBoundingClientRect().left-dt(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}):0))+"px"}),pe.each({margin:"",padding:"",border:"Width"},function(e,t){pe.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+Oe[r]+t]=o[r]||o[r-2]||o[0];return i}},ct.test(e)||(pe.cssHooks[e+t].set=_)}),pe.fn.extend({css:function(e,t){return Pe(this,function(e,t,n){var r,i,o={},a=0;if(pe.isArray(t)){for(r=ht(e),i=t.length;a1)},show:function(){return q(this,!0)},hide:function(){return q(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Re(this)?pe(this).show():pe(this).hide()})}}),pe.Tween=O,O.prototype={constructor:O,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||pe.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(pe.cssNumber[n]?"":"px")},cur:function(){var e=O.propHooks[this.prop];return e&&e.get?e.get(this):O.propHooks._default.get(this)},run:function(e){var t,n=O.propHooks[this.prop];return this.options.duration?this.pos=t=pe.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):O.propHooks._default.set(this),this}},O.prototype.init.prototype=O.prototype,O.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=pe.css(e.elem,e.prop,""),t&&"auto"!==t?t:0)},set:function(e){pe.fx.step[e.prop]?pe.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[pe.cssProps[e.prop]]&&!pe.cssHooks[e.prop]?e.elem[e.prop]=e.now:pe.style(e.elem,e.prop,e.now+e.unit)}}},O.propHooks.scrollTop=O.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},pe.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},pe.fx=O.prototype.init,pe.fx.step={};var Nt,kt,St=/^(?:toggle|show|hide)$/,At=/queueHooks$/;pe.Animation=pe.extend($,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return d(n.elem,e,Me.exec(t),n),n}]},tweener:function(e,t){pe.isFunction(e)?(t=e,e=["*"]):e=e.match(De);for(var n,r=0,i=e.length;r
        a",e=n.getElementsByTagName("a")[0],t.setAttribute("type","checkbox"),n.appendChild(t),e=n.getElementsByTagName("a")[0],e.style.cssText="top:1px",fe.getSetAttribute="t"!==n.className,fe.style=/top/.test(e.getAttribute("style")),fe.hrefNormalized="/a"===e.getAttribute("href"),fe.checkOn=!!t.value,fe.optSelected=i.selected,fe.enctype=!!re.createElement("form").enctype,r.disabled=!0,fe.optDisabled=!i.disabled,t=re.createElement("input"),t.setAttribute("value",""),fe.input=""===t.getAttribute("value"),t.value="t",t.setAttribute("type","radio"),fe.radioValue="t"===t.value}();var Dt=/\r/g,jt=/[\x20\t\r\n\f]+/g;pe.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=pe.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,pe(this).val()):e,null==i?i="":"number"==typeof i?i+="":pe.isArray(i)&&(i=pe.map(i,function(e){return null==e?"":e+""})),t=pe.valHooks[this.type]||pe.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return t=pe.valHooks[i.type]||pe.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:(n=i.value,"string"==typeof n?n.replace(Dt,""):null==n?"":n)}}}),pe.extend({valHooks:{option:{get:function(e){var t=pe.find.attr(e,"value");return null!=t?t:pe.trim(pe.text(e)).replace(jt," ")}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||i<0,a=o?null:[],s=o?i+1:r.length,u=i<0?s:o?i:0;u-1)try{r.selected=n=!0}catch(s){r.scrollHeight}else r.selected=!1;return n||(e.selectedIndex=-1),i}}}}),pe.each(["radio","checkbox"],function(){pe.valHooks[this]={set:function(e,t){if(pe.isArray(t))return e.checked=pe.inArray(pe(e).val(),t)>-1}},fe.checkOn||(pe.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Lt,Ht,qt=pe.expr.attrHandle,_t=/^(?:checked|selected)$/i,Ft=fe.getSetAttribute,Mt=fe.input;pe.fn.extend({attr:function(e,t){return Pe(this,pe.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){pe.removeAttr(this,e)})}}),pe.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?pe.prop(e,t,n):(1===o&&pe.isXMLDoc(e)||(t=t.toLowerCase(),i=pe.attrHooks[t]||(pe.expr.match.bool.test(t)?Ht:Lt)),void 0!==n?null===n?void pe.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:(r=pe.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!fe.radioValue&&"radio"===t&&pe.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(De);if(o&&1===e.nodeType)for(;n=o[i++];)r=pe.propFix[n]||n,pe.expr.match.bool.test(n)?Mt&&Ft||!_t.test(n)?e[r]=!1:e[pe.camelCase("default-"+n)]=e[r]=!1:pe.attr(e,n,""),e.removeAttribute(Ft?n:r)}}),Ht={set:function(e,t,n){return t===!1?pe.removeAttr(e,n):Mt&&Ft||!_t.test(n)?e.setAttribute(!Ft&&pe.propFix[n]||n,n):e[pe.camelCase("default-"+n)]=e[n]=!0,n}},pe.each(pe.expr.match.bool.source.match(/\w+/g),function(e,t){var n=qt[t]||pe.find.attr;Mt&&Ft||!_t.test(t)?qt[t]=function(e,t,r){var i,o;return r||(o=qt[t],qt[t]=i,i=null!=n(e,t,r)?t.toLowerCase():null,qt[t]=o),i}:qt[t]=function(e,t,n){if(!n)return e[pe.camelCase("default-"+t)]?t.toLowerCase():null}}),Mt&&Ft||(pe.attrHooks.value={set:function(e,t,n){return pe.nodeName(e,"input")?void(e.defaultValue=t):Lt&&Lt.set(e,t,n)}}),Ft||(Lt={set:function(e,t,n){var r=e.getAttributeNode(n);if(r||e.setAttributeNode(r=e.ownerDocument.createAttribute(n)),r.value=t+="","value"===n||t===e.getAttribute(n))return t}},qt.id=qt.name=qt.coords=function(e,t,n){var r;if(!n)return(r=e.getAttributeNode(t))&&""!==r.value?r.value:null},pe.valHooks.button={get:function(e,t){var n=e.getAttributeNode(t);if(n&&n.specified)return n.value},set:Lt.set},pe.attrHooks.contenteditable={set:function(e,t,n){Lt.set(e,""!==t&&t,n)}},pe.each(["width","height"],function(e,t){pe.attrHooks[t]={set:function(e,n){if(""===n)return e.setAttribute(t,"auto"),n}}})),fe.style||(pe.attrHooks.style={get:function(e){return e.style.cssText||void 0},set:function(e,t){return e.style.cssText=t+""}});var Ot=/^(?:input|select|textarea|button|object)$/i,Rt=/^(?:a|area)$/i;pe.fn.extend({prop:function(e,t){return Pe(this,pe.prop,e,t,arguments.length>1)},removeProp:function(e){return e=pe.propFix[e]||e,this.each(function(){try{this[e]=void 0,delete this[e]}catch(t){}})}}),pe.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&pe.isXMLDoc(e)||(t=pe.propFix[t]||t,i=pe.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=pe.find.attr(e,"tabindex");return t?parseInt(t,10):Ot.test(e.nodeName)||Rt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),fe.hrefNormalized||pe.each(["href","src"],function(e,t){pe.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),fe.optSelected||(pe.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),pe.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){pe.propFix[this.toLowerCase()]=this}),fe.enctype||(pe.propFix.enctype="encoding");var Pt=/[\t\r\n\f]/g;pe.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).addClass(e.call(this,t,z(this)))});if("string"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(" "+i+" ").replace(Pt," ")){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");s=pe.trim(r),i!==s&&pe.attr(n,"class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).removeClass(e.call(this,t,z(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(" "+i+" ").replace(Pt," ")){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");s=pe.trim(r),i!==s&&pe.attr(n,"class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):pe.isFunction(e)?this.each(function(n){pe(this).toggleClass(e.call(this,n,z(this),t),t)}):this.each(function(){var t,r,i,o;if("string"===n)for(r=0,i=pe(this),o=e.match(De)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||(t=z(this),t&&pe._data(this,"__className__",t),pe.attr(this,"class",t||e===!1?"":pe._data(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+z(n)+" ").replace(Pt," ").indexOf(t)>-1)return!0;return!1}}),pe.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){pe.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),pe.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}});var Bt=e.location,Wt=pe.now(),It=/\?/,$t=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;pe.parseJSON=function(t){if(e.JSON&&e.JSON.parse)return e.JSON.parse(t+"");var n,r=null,i=pe.trim(t+"");return i&&!pe.trim(i.replace($t,function(e,t,i,o){return n&&t&&(r=0),0===r?e:(n=i||t,r+=!o-!i,"")}))?Function("return "+i)():pe.error("Invalid JSON: "+t)},pe.parseXML=function(t){var n,r;if(!t||"string"!=typeof t)return null;try{e.DOMParser?(r=new e.DOMParser,n=r.parseFromString(t,"text/xml")):(n=new e.ActiveXObject("Microsoft.XMLDOM"),n.async="false",n.loadXML(t))}catch(i){n=void 0}return n&&n.documentElement&&!n.getElementsByTagName("parsererror").length||pe.error("Invalid XML: "+t),n};var zt=/#.*$/,Xt=/([?&])_=[^&]*/,Ut=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Vt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Yt=/^(?:GET|HEAD)$/,Jt=/^\/\//,Gt=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Kt={},Qt={},Zt="*/".concat("*"),en=Bt.href,tn=Gt.exec(en.toLowerCase())||[];pe.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:en,type:"GET",isLocal:Vt.test(tn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Zt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":pe.parseJSON,"text xml":pe.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?V(V(e,pe.ajaxSettings),t):V(pe.ajaxSettings,e)},ajaxPrefilter:X(Kt),ajaxTransport:X(Qt),ajax:function(t,n){function r(t,n,r,i){var o,f,v,x,w,C=n;2!==b&&(b=2,u&&e.clearTimeout(u),c=void 0,s=i||"",T.readyState=t>0?4:0,o=t>=200&&t<300||304===t,r&&(x=Y(d,T,r)),x=J(d,x,T,o),o?(d.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(pe.lastModified[a]=w),w=T.getResponseHeader("etag"),w&&(pe.etag[a]=w)),204===t||"HEAD"===d.type?C="nocontent":304===t?C="notmodified":(C=x.state,f=x.data,v=x.error,o=!v)):(v=C,!t&&C||(C="error",t<0&&(t=0))),T.status=t,T.statusText=(n||C)+"",o?g.resolveWith(p,[f,C,T]):g.rejectWith(p,[T,C,v]),T.statusCode(y),y=void 0,l&&h.trigger(o?"ajaxSuccess":"ajaxError",[T,d,o?f:v]),m.fireWith(p,[T,C]),l&&(h.trigger("ajaxComplete",[T,d]),--pe.active||pe.event.trigger("ajaxStop")))}"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,d=pe.ajaxSetup({},n),p=d.context||d,h=d.context&&(p.nodeType||p.jquery)?pe(p):pe.event,g=pe.Deferred(),m=pe.Callbacks("once memory"),y=d.statusCode||{},v={},x={},b=0,w="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!f)for(f={};t=Ut.exec(s);)f[t[1].toLowerCase()]=t[2];t=f[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?s:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=x[n]=x[n]||e,v[e]=t),this},overrideMimeType:function(e){return b||(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(b<2)for(t in e)y[t]=[y[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||w;return c&&c.abort(t),r(0,t),this}};if(g.promise(T).complete=m.add,T.success=T.done,T.error=T.fail,d.url=((t||d.url||en)+"").replace(zt,"").replace(Jt,tn[1]+"//"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=pe.trim(d.dataType||"*").toLowerCase().match(De)||[""],null==d.crossDomain&&(i=Gt.exec(d.url.toLowerCase()),d.crossDomain=!(!i||i[1]===tn[1]&&i[2]===tn[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(tn[3]||("http:"===tn[1]?"80":"443")))),d.data&&d.processData&&"string"!=typeof d.data&&(d.data=pe.param(d.data,d.traditional)),U(Kt,d,n,T),2===b)return T;l=pe.event&&d.global,l&&0===pe.active++&&pe.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!Yt.test(d.type),a=d.url,d.hasContent||(d.data&&(a=d.url+=(It.test(a)?"&":"?")+d.data,delete d.data),d.cache===!1&&(d.url=Xt.test(a)?a.replace(Xt,"$1_="+Wt++):a+(It.test(a)?"&":"?")+"_="+Wt++)),d.ifModified&&(pe.lastModified[a]&&T.setRequestHeader("If-Modified-Since",pe.lastModified[a]),pe.etag[a]&&T.setRequestHeader("If-None-Match",pe.etag[a])),(d.data&&d.hasContent&&d.contentType!==!1||n.contentType)&&T.setRequestHeader("Content-Type",d.contentType),T.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+Zt+"; q=0.01":""):d.accepts["*"]);for(o in d.headers)T.setRequestHeader(o,d.headers[o]);if(d.beforeSend&&(d.beforeSend.call(p,T,d)===!1||2===b))return T.abort();w="abort";for(o in{success:1,error:1,complete:1})T[o](d[o]);if(c=U(Qt,d,n,T)){if(T.readyState=1,l&&h.trigger("ajaxSend",[T,d]),2===b)return T;d.async&&d.timeout>0&&(u=e.setTimeout(function(){T.abort("timeout")},d.timeout));try{b=1,c.send(v,r)}catch(C){if(!(b<2))throw C;r(-1,C)}}else r(-1,"No Transport");return T},getJSON:function(e,t,n){return pe.get(e,t,n,"json")},getScript:function(e,t){return pe.get(e,void 0,t,"script")}}),pe.each(["get","post"],function(e,t){pe[t]=function(e,n,r,i){return pe.isFunction(n)&&(i=i||r,r=n,n=void 0),pe.ajax(pe.extend({url:e,type:t,dataType:i,data:n,success:r},pe.isPlainObject(e)&&e))}}),pe._evalUrl=function(e){return pe.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},pe.fn.extend({wrapAll:function(e){if(pe.isFunction(e))return this.each(function(t){pe(this).wrapAll(e.call(this,t))});if(this[0]){var t=pe(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstChild&&1===e.firstChild.nodeType;)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return pe.isFunction(e)?this.each(function(t){pe(this).wrapInner(e.call(this,t))}):this.each(function(){var t=pe(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=pe.isFunction(e);return this.each(function(n){pe(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){pe.nodeName(this,"body")||pe(this).replaceWith(this.childNodes)}).end()}}),pe.expr.filters.hidden=function(e){return fe.reliableHiddenOffsets()?e.offsetWidth<=0&&e.offsetHeight<=0&&!e.getClientRects().length:K(e)},pe.expr.filters.visible=function(e){return!pe.expr.filters.hidden(e)};var nn=/%20/g,rn=/\[\]$/,on=/\r?\n/g,an=/^(?:submit|button|image|reset|file)$/i,sn=/^(?:input|select|textarea|keygen)/i;pe.param=function(e,t){var n,r=[],i=function(e,t){t=pe.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(void 0===t&&(t=pe.ajaxSettings&&pe.ajaxSettings.traditional),pe.isArray(e)||e.jquery&&!pe.isPlainObject(e))pe.each(e,function(){i(this.name,this.value)});else for(n in e)Q(n,e[n],t,i);return r.join("&").replace(nn,"+")},pe.fn.extend({serialize:function(){return pe.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=pe.prop(this,"elements");return e?pe.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!pe(this).is(":disabled")&&sn.test(this.nodeName)&&!an.test(e)&&(this.checked||!Be.test(e))}).map(function(e,t){var n=pe(this).val();return null==n?null:pe.isArray(n)?pe.map(n,function(e){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),pe.ajaxSettings.xhr=void 0!==e.ActiveXObject?function(){return this.isLocal?ee():re.documentMode>8?Z():/^(get|post|head|put|delete|options)$/i.test(this.type)&&Z()||ee()}:Z;var un=0,ln={},cn=pe.ajaxSettings.xhr();e.attachEvent&&e.attachEvent("onunload",function(){for(var e in ln)ln[e](void 0,!0)}),fe.cors=!!cn&&"withCredentials"in cn,cn=fe.ajax=!!cn,cn&&pe.ajaxTransport(function(t){if(!t.crossDomain||fe.cors){var n;return{send:function(r,i){var o,a=t.xhr(),s=++un;if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)a[o]=t.xhrFields[o];t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||r["X-Requested-With"]||(r["X-Requested-With"]="XMLHttpRequest");for(o in r)void 0!==r[o]&&a.setRequestHeader(o,r[o]+"");a.send(t.hasContent&&t.data||null),n=function(e,r){var o,u,l;if(n&&(r||4===a.readyState))if(delete ln[s],n=void 0,a.onreadystatechange=pe.noop,r)4!==a.readyState&&a.abort();else{l={},o=a.status,"string"==typeof a.responseText&&(l.text=a.responseText);try{u=a.statusText}catch(c){u=""}o||!t.isLocal||t.crossDomain?1223===o&&(o=204):o=l.text?200:404}l&&i(o,u,l,a.getAllResponseHeaders())},t.async?4===a.readyState?e.setTimeout(n):a.onreadystatechange=ln[s]=n:n()},abort:function(){n&&n(void 0,!0)}}}}),pe.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return pe.globalEval(e),e}}}),pe.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),pe.ajaxTransport("script",function(e){if(e.crossDomain){var t,n=re.head||pe("head")[0]||re.documentElement;return{send:function(r,i){t=re.createElement("script"),t.async=!0,e.scriptCharset&&(t.charset=e.scriptCharset),t.src=e.url,t.onload=t.onreadystatechange=function(e,n){(n||!t.readyState||/loaded|complete/.test(t.readyState))&&(t.onload=t.onreadystatechange=null,t.parentNode&&t.parentNode.removeChild(t),t=null,n||i(200,"success"))},n.insertBefore(t,n.firstChild)},abort:function(){t&&t.onload(void 0,!0)}}}});var fn=[],dn=/(=)\?(?=&|$)|\?\?/;pe.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=fn.pop()||pe.expando+"_"+Wt++;return this[e]=!0,e}}),pe.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,a,s=t.jsonp!==!1&&(dn.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&dn.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return i=t.jsonpCallback=pe.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(dn,"$1"+i):t.jsonp!==!1&&(t.url+=(It.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return a||pe.error(i+" was not called"),a[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?pe(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,fn.push(i)),a&&pe.isFunction(o)&&o(a[0]),a=o=void 0}),"script"}),pe.parseHTML=function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||re;var r=Te.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=y([e],t,i),i&&i.length&&pe(i).remove(),pe.merge([],r.childNodes))};var pn=pe.fn.load;return pe.fn.load=function(e,t,n){if("string"!=typeof e&&pn)return pn.apply(this,arguments);var r,i,o,a=this,s=e.indexOf(" ");return s>-1&&(r=pe.trim(e.slice(s,e.length)),e=e.slice(0,s)),pe.isFunction(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),a.length>0&&pe.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?pe("
        ").append(pe.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},pe.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){pe.fn[t]=function(e){return this.on(t,e)}}),pe.expr.filters.animated=function(e){return pe.grep(pe.timers,function(t){return e===t.elem}).length},pe.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=pe.css(e,"position"),f=pe(e),d={};"static"===c&&(e.style.position="relative"),s=f.offset(),o=pe.css(e,"top"),u=pe.css(e,"left"),l=("absolute"===c||"fixed"===c)&&pe.inArray("auto",[o,u])>-1,l?(r=f.position(),a=r.top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),pe.isFunction(t)&&(t=t.call(e,n,pe.extend({},s))),null!=t.top&&(d.top=t.top-s.top+a),null!=t.left&&(d.left=t.left-s.left+i),"using"in t?t.using.call(e,d):f.css(d)}},pe.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){pe.offset.setOffset(this,e,t)});var t,n,r={top:0,left:0},i=this[0],o=i&&i.ownerDocument;if(o)return t=o.documentElement,pe.contains(t,i)?("undefined"!=typeof i.getBoundingClientRect&&(r=i.getBoundingClientRect()),n=te(o),{top:r.top+(n.pageYOffset||t.scrollTop)-(t.clientTop||0),left:r.left+(n.pageXOffset||t.scrollLeft)-(t.clientLeft||0)}):r},position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===pe.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),pe.nodeName(e[0],"html")||(n=e.offset()),n.top+=pe.css(e[0],"borderTopWidth",!0),n.left+=pe.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-pe.css(r,"marginTop",!0),left:t.left-n.left-pe.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){ +for(var e=this.offsetParent;e&&!pe.nodeName(e,"html")&&"static"===pe.css(e,"position");)e=e.offsetParent;return e||pt})}}),pe.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n=/Y/.test(t);pe.fn[e]=function(r){return Pe(this,function(e,r,i){var o=te(e);return void 0===i?o?t in o?o[t]:o.document.documentElement[r]:e[r]:void(o?o.scrollTo(n?pe(o).scrollLeft():i,n?i:pe(o).scrollTop()):e[r]=i)},e,r,arguments.length,null)}}),pe.each(["top","left"],function(e,t){pe.cssHooks[t]=L(fe.pixelPosition,function(e,n){if(n)return n=gt(e,t),ft.test(n)?pe(e).position()[t]+"px":n})}),pe.each({Height:"height",Width:"width"},function(e,t){pe.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){pe.fn[r]=function(r,i){var o=arguments.length&&(n||"boolean"!=typeof r),a=n||(r===!0||i===!0?"margin":"border");return Pe(this,function(t,n,r){var i;return pe.isWindow(t)?t.document.documentElement["client"+e]:9===t.nodeType?(i=t.documentElement,Math.max(t.body["scroll"+e],i["scroll"+e],t.body["offset"+e],i["offset"+e],i["client"+e])):void 0===r?pe.css(t,n,a):pe.style(t,n,r,a)},t,o?r:void 0,o,null)}})}),pe.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),pe.fn.size=function(){return this.length},pe.fn.andSelf=pe.fn.addBack,layui.define(function(e){layui.$=pe,e("jquery",pe)}),pe});!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if("interactive"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.1",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'
        '+(f?r.title[0]:r.title)+"
        ":"";return r.zIndex=s,t([r.shade?'
        ':"",'
        '+(e&&2!=r.type?"":u)+'
        '+(0==r.type&&r.icon!==-1?'':"")+(1==r.type&&e?"":r.content||"")+'
        '+function(){var e=c?'':"";return r.closeBtn&&(e+=''),e}()+""+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t'+r.btn[t]+"";return'
        '+e+"
        "}():"")+(r.resize?'':"")+"
        "],u,i('
        ')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"","auto"];t.content='';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(t){s=t.find(".layui-layer-input"),s.val(e.value||"").focus(),"function"==typeof f&&f(t)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
          '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
        • '+(t[0].content||"no content")+"
        • ";i'+(t[i].content||"no content")+"";return a}()+"
        ",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
        '+(u.length>1?'':"")+'
        '+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
        ",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
        是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window);layui.define("jquery",function(t){"use strict";var a=layui.$,i=(layui.hint(),layui.device()),e="element",l="layui-this",n="layui-show",s=function(){this.config={}};s.prototype.set=function(t){var i=this;return a.extend(!0,i.config,t),i},s.prototype.on=function(t,a){return layui.onevent.call(this,e,t,a)},s.prototype.tabAdd=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.children(".layui-tab-bar"),o=l.children(".layui-tab-content"),r='
      • "+(i.title||"unnaming")+"
      • ";return s[0]?s.before(r):n.append(r),o.append('
        '+(i.content||"")+"
        "),f.hideTabMore(!0),f.tabAuto(),this},s.prototype.tabDelete=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.find('>li[lay-id="'+i+'"]');return f.tabDelete(null,s),this},s.prototype.tabChange=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.find('>li[lay-id="'+i+'"]');return f.tabClick.call(s[0],null,null,s),this},s.prototype.tab=function(t){t=t||{},b.on("click",t.headerElem,function(i){var e=a(this).index();f.tabClick.call(this,i,e,null,t)})},s.prototype.progress=function(t,i){var e="layui-progress",l=a("."+e+"[lay-filter="+t+"]"),n=l.find("."+e+"-bar"),s=n.find("."+e+"-text");return n.css("width",i),s.text(i),this};var o=".layui-nav",r="layui-nav-item",c="layui-nav-bar",u="layui-nav-tree",d="layui-nav-child",y="layui-nav-more",h="layui-anim layui-anim-upbit",f={tabClick:function(t,i,s,o){o=o||{};var r=s||a(this),i=i||r.parent().children("li").index(r),c=o.headerElem?r.parent():r.parents(".layui-tab").eq(0),u=o.bodyElem?a(o.bodyElem):c.children(".layui-tab-content").children(".layui-tab-item"),d=r.find("a"),y=c.attr("lay-filter");"javascript:;"!==d.attr("href")&&"_blank"===d.attr("target")||(r.addClass(l).siblings().removeClass(l),u.eq(i).addClass(n).siblings().removeClass(n)),layui.event.call(this,e,"tab("+y+")",{elem:c,index:i})},tabDelete:function(t,i){var n=i||a(this).parent(),s=n.index(),o=n.parents(".layui-tab").eq(0),r=o.children(".layui-tab-content").children(".layui-tab-item"),c=o.attr("lay-filter");n.hasClass(l)&&(n.next()[0]?f.tabClick.call(n.next()[0],null,s+1):n.prev()[0]&&f.tabClick.call(n.prev()[0],null,s-1)),n.remove(),r.eq(s).remove(),setTimeout(function(){f.tabAuto()},50),layui.event.call(this,e,"tabDelete("+c+")",{elem:o,index:s})},tabAuto:function(){var t="layui-tab-more",e="layui-tab-bar",l="layui-tab-close",n=this;a(".layui-tab").each(function(){var s=a(this),o=s.children(".layui-tab-title"),r=(s.children(".layui-tab-content").children(".layui-tab-item"),'lay-stope="tabmore"'),c=a('');if(n===window&&8!=i.ie&&f.hideTabMore(!0),s.attr("lay-allowClose")&&o.find("li").each(function(){var t=a(this);if(!t.find("."+l)[0]){var i=a('');i.on("click",f.tabDelete),t.append(i)}}),"string"!=typeof s.attr("lay-unauto"))if(o.prop("scrollWidth")>o.outerWidth()+1){if(o.find("."+e)[0])return;o.append(c),s.attr("overflow",""),c.on("click",function(a){o[this.title?"removeClass":"addClass"](t),this.title=this.title?"":"收缩"})}else o.find("."+e).remove(),s.removeAttr("overflow")})},hideTabMore:function(t){var i=a(".layui-tab-title");t!==!0&&"tabmore"===a(t.target).attr("lay-stope")||(i.removeClass("layui-tab-more"),i.find(".layui-tab-bar").attr("title",""))},clickThis:function(){var t=a(this),i=t.parents(o),n=i.attr("lay-filter"),s=t.parent(),c=t.siblings("."+d),y="string"==typeof s.attr("lay-unselect");"javascript:;"!==t.attr("href")&&"_blank"===t.attr("target")||y||c[0]||(i.find("."+l).removeClass(l),s.addClass(l)),i.hasClass(u)&&(c.removeClass(h),c[0]&&(s["none"===c.css("display")?"addClass":"removeClass"](r+"ed"),"all"===i.attr("lay-shrink")&&s.siblings().removeClass(r+"ed"))),layui.event.call(this,e,"nav("+n+")",t)},collapse:function(){var t=a(this),i=t.find(".layui-colla-icon"),l=t.siblings(".layui-colla-content"),s=t.parents(".layui-collapse").eq(0),o=s.attr("lay-filter"),r="none"===l.css("display");if("string"==typeof s.attr("lay-accordion")){var c=s.children(".layui-colla-item").children("."+n);c.siblings(".layui-colla-title").children(".layui-colla-icon").html(""),c.removeClass(n)}l[r?"addClass":"removeClass"](n),i.html(r?"":""),layui.event.call(this,e,"collapse("+o+")",{title:t,content:l,show:r})}};s.prototype.init=function(t,e){var l=function(){return e?'[lay-filter="'+e+'"]':""}(),s={tab:function(){f.tabAuto.call({})},nav:function(){var t=200,e={},s={},p={},b=function(l,o,r){var c=a(this),f=c.find("."+d);o.hasClass(u)?l.css({top:c.position().top,height:c.children("a").outerHeight(),opacity:1}):(f.addClass(h),l.css({left:c.position().left+parseFloat(c.css("marginLeft")),top:c.position().top+c.height()-l.height()}),e[r]=setTimeout(function(){l.css({width:c.width(),opacity:1})},i.ie&&i.ie<10?0:t),clearTimeout(p[r]),"block"===f.css("display")&&clearTimeout(s[r]),s[r]=setTimeout(function(){f.addClass(n),c.find("."+y).addClass(y+"d")},300))};a(o+l).each(function(i){var l=a(this),o=a(''),h=l.find("."+r);l.find("."+c)[0]||(l.append(o),h.on("mouseenter",function(){b.call(this,o,l,i)}).on("mouseleave",function(){l.hasClass(u)||(clearTimeout(s[i]),s[i]=setTimeout(function(){l.find("."+d).removeClass(n),l.find("."+y).removeClass(y+"d")},300))}),l.on("mouseleave",function(){clearTimeout(e[i]),p[i]=setTimeout(function(){l.hasClass(u)?o.css({height:0,top:o.position().top+o.height()/2,opacity:0}):o.css({width:0,left:o.position().left+o.width()/2,opacity:0})},t)})),h.find("a").each(function(){var t=a(this),i=(t.parent(),t.siblings("."+d));i[0]&&!t.children("."+y)[0]&&t.append(''),t.off("click",f.clickThis).on("click",f.clickThis)})})},breadcrumb:function(){var t=".layui-breadcrumb";a(t+l).each(function(){var t=a(this),i="lay-separator",e=t.attr(i)||"/",l=t.find("a");l.next("span["+i+"]")[0]||(l.each(function(t){t!==l.length-1&&a(this).after(""+e+"")}),t.css("visibility","visible"))})},progress:function(){var t="layui-progress";a("."+t+l).each(function(){var i=a(this),e=i.find(".layui-progress-bar"),l=e.attr("lay-percent");e.css("width",function(){return/^.+\/.+$/.test(l)?100*new Function("return "+l)()+"%":l}()),i.attr("lay-showPercent")&&setTimeout(function(){e.html(''+l+"")},350)})},collapse:function(){var t="layui-collapse";a("."+t+l).each(function(){var t=a(this).find(".layui-colla-item");t.each(function(){var t=a(this),i=t.find(".layui-colla-title"),e=t.find(".layui-colla-content"),l="none"===e.css("display");i.find(".layui-colla-icon").remove(),i.append(''+(l?"":"")+""),i.off("click",f.collapse).on("click",f.collapse)})})}};return s[t]?s[t]():layui.each(s,function(t,a){a()})},s.prototype.render=s.prototype.init;var p=new s,b=a(document);p.render();var v=".layui-tab-title li";b.on("click",v,f.tabClick),b.on("click",f.hideTabMore),a(window).on("resize",f.tabAuto),t(e,p)});layui.define("layer",function(e){"use strict";var t=layui.$,i=layui.layer,n=layui.hint(),o=layui.device(),a={config:{},set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,r,e,t)}},l=function(){var e=this;return{upload:function(t){e.upload.call(e,t)},reload:function(t){e.reload.call(e,t)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",f="layui-upload-iframe",s="layui-upload-choose",p=function(e){var i=this;i.config=t.extend({},i.config,a.config,e),i.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",acceptMime:"",method:"post",data:{},drag:!0,size:0,number:0,multiple:!1},p.prototype.render=function(e){var i=this,e=i.config;e.elem=t(e.elem),e.bindAction=t(e.bindAction),i.file(),i.events()},p.prototype.file=function(){var e=this,i=e.config,n=e.elemFile=t(['"].join("")),a=i.elem.next();(a.hasClass(u)||a.hasClass(c))&&a.remove(),o.ie&&o.ie<10&&i.elem.wrap('
        '),e.isFile()?(e.elemFile=i.elem,i.field=i.elem[0].name):i.elem.after(n),o.ie&&o.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,i=e.config,n=t(''),o=t(['
        ',"
        "].join(""));t("#"+f)[0]||t("body").append(n),i.elem.next().hasClass(c)||(e.elemFile.wrap(o),i.elem.next("."+c).append(function(){var e=[];return layui.each(i.data,function(t,i){i="function"==typeof i?i():i,e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return i.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var t=this;window.FileReader&&layui.each(t.chooseFiles,function(t,i){var n=new FileReader;n.readAsDataURL(i),n.onload=function(){e&&e(t,i,this.result)}})},p.prototype.upload=function(e,i){var n,a=this,l=a.config,r=a.elemFile[0],u=function(){var i=0,n=0,o=e||a.files||a.chooseFiles||r.files,u=function(){l.multiple&&i+n===a.fileLength&&"function"==typeof l.allDone&&l.allDone({total:a.fileLength,successful:i,aborted:n})};layui.each(o,function(e,o){var r=new FormData;r.append(l.field,o),layui.each(l.data,function(e,t){t="function"==typeof t?t():t,r.append(e,t)});var c={url:l.url,type:"post",data:r,contentType:!1,processData:!1,dataType:"json",headers:l.headers||{},success:function(t){i++,d(e,t),u()},error:function(){n++,a.msg("请求上传接口出现异常"),m(e),u()}};"function"==typeof l.progress&&(c.xhr=function(){var e=t.ajaxSettings.xhr();return e.upload.addEventListener("progress",function(e){if(e.lengthComputable){var t=Math.floor(e.loaded/e.total*100);l.progress(t,l.item[0],e)}}),e}),t.ajax(c)})},c=function(){var e=t("#"+f);a.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var t,i=e.contents().find("body");try{t=i.text()}catch(n){a.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}t&&(clearInterval(p.timer),i.html(""),d(0,t))},30)},d=function(e,t){if(a.elemFile.next("."+s).remove(),r.value="","object"!=typeof t)try{t=JSON.parse(t)}catch(i){return t={},a.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(t,e||0,function(e){a.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){a.upload(e)})},h=l.exts,v=function(){var t=[];return layui.each(e||a.chooseFiles,function(e,i){t.push(i.name)}),t}(),g={preview:function(e){a.preview(e)},upload:function(e,t){var i={};i[e]=t,a.upload(i)},pushFile:function(){return a.files=a.files||{},layui.each(a.chooseFiles,function(e,t){a.files[e]=t}),a.files},resetFile:function(e,t,i){var n=new File([t],i);a.files=a.files||{},a.files[e]=n}},y=function(){if("choose"!==i&&!l.auto||(l.choose&&l.choose(g),"choose"!==i))return l.before&&l.before(g),o.ie?o.ie>9?u():c():void u()};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return a.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return a.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return a.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,t){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(t))||(n=!0)}),n)return a.msg("选择的图片中包含不支持的格式"),r.value=""}if(a.fileLength=function(){var t=0,i=e||a.files||a.chooseFiles||r.files;return layui.each(i,function(){t++}),t}(),l.number&&a.fileLength>l.number)return a.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(o.ie&&o.ie<10)){var F;if(layui.each(a.chooseFiles,function(e,t){if(t.size>1024*l.size){var i=l.size/1024;i=i>=1?i.toFixed(2)+"MB":l.size+"KB",r.value="",F=i}}),F)return a.msg("文件不能超过"+F)}y()}},p.prototype.reload=function(e){e=e||{},delete e.elem,delete e.bindAction;var i=this,e=i.config=t.extend({},i.config,a.config,e),n=e.elem.next();n.attr({name:e.name,accept:e.acceptMime,multiple:e.multiple})},p.prototype.events=function(){var e=this,i=e.config,a=function(t){e.chooseFiles={},layui.each(t,function(t,i){var n=(new Date).getTime();e.chooseFiles[n+"-"+t]=i})},l=function(t,n){var o=e.elemFile,a=t.length>1?t.length+"个文件":(t[0]||{}).name||o[0].value.match(/[^\/\\]+\..+/g)||[]||"";o.next().hasClass(s)&&o.next().remove(),e.upload(null,"choose"),e.isFile()||i.choose||o.after(''+a+"")};i.elem.off("upload.start").on("upload.start",function(){var o=t(this),a=o.attr("lay-data");if(a)try{a=new Function("return "+a)(),e.config=t.extend({},i,a)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+a)}e.config.item=o,e.elemFile[0].click()}),o.ie&&o.ie<10||i.elem.off("upload.over").on("upload.over",function(){var e=t(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=t(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,o){var r=t(this),u=o.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),a(u),i.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var t=this.files||[];a(t),i.auto?e.upload():l(t)}),i.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),i.elem.data("haveEvents")||(e.elemFile.on("change",function(){t(this).trigger("upload.change")}),i.elem.on("click",function(){e.isFile()||t(this).trigger("upload.start")}),i.drag&&i.elem.on("dragover",function(e){e.preventDefault(),t(this).trigger("upload.over")}).on("dragleave",function(e){t(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),t(this).trigger("upload.drop",e)}),i.bindAction.on("click",function(){t(this).trigger("upload.action")}),i.elem.data("haveEvents",!0))},a.render=function(e){var t=new p(e);return l.call(t)},e(r,a)});layui.define("jquery",function(e){"use strict";var i=layui.jquery,t={config:{},index:layui.slider?layui.slider.index+1e4:0,set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,n,e,i)}},a=function(){var e=this,i=e.config;return{setValue:function(i,t){return e.slide("set",i,t||0)},config:i}},n="slider",l="layui-disabled",s="layui-slider",r="layui-slider-bar",o="layui-slider-wrap",u="layui-slider-wrap-btn",d="layui-slider-tips",v="layui-slider-input",c="layui-slider-input-txt",m="layui-slider-input-btn",p="layui-slider-hover",f=function(e){var a=this;a.index=++t.index,a.config=i.extend({},a.config,t.config,e),a.render()};f.prototype.config={type:"default",min:0,max:100,value:0,step:1,showstep:!1,tips:!0,input:!1,range:!1,height:200,disabled:!1,theme:"#009688"},f.prototype.render=function(){var e=this,t=e.config;if(t.step<1&&(t.step=1),t.maxt.min?a:t.min,t.value[1]=n>t.min?n:t.min,t.value[0]=t.value[0]>t.max?t.max:t.value[0],t.value[1]=t.value[1]>t.max?t.max:t.value[1];var r=Math.floor((t.value[0]-t.min)/(t.max-t.min)*100),v=Math.floor((t.value[1]-t.min)/(t.max-t.min)*100),m=v-r+"%";r+="%",v+="%"}else{"object"==typeof t.value&&(t.value=Math.min.apply(null,t.value)),t.valuet.max&&(t.value=t.max);var m=Math.floor((t.value-t.min)/(t.max-t.min)*100)+"%"}var p=t.disabled?"#c2c2c2":t.theme,f='
        '+(t.tips?'
        ':"")+'
        '+(t.range?'
        ':"")+"
        ",h=i(t.elem),y=h.next("."+s);if(y[0]&&y.remove(),e.elemTemp=i(f),t.range?(e.elemTemp.find("."+o).eq(0).data("value",t.value[0]),e.elemTemp.find("."+o).eq(1).data("value",t.value[1])):e.elemTemp.find("."+o).data("value",t.value),h.html(e.elemTemp),"vertical"===t.type&&e.elemTemp.height(t.height+"px"),t.showstep){for(var g=(t.max-t.min)/t.step,b="",x=1;x
        ')}e.elemTemp.append(b)}if(t.input&&!t.range){var w=i('
        ');h.css("position","relative"),h.append(w),h.find("."+c).children("input").val(t.value),"vertical"===t.type?w.css({left:0,top:-48}):e.elemTemp.css("margin-right",w.outerWidth()+15)}t.disabled?(e.elemTemp.addClass(l),e.elemTemp.find("."+u).addClass(l)):e.slide(),e.elemTemp.find("."+u).on("mouseover",function(){var a="vertical"===t.type?t.height:e.elemTemp[0].offsetWidth,n=e.elemTemp.find("."+o),l="vertical"===t.type?a-i(this).parent()[0].offsetTop-n.height():i(this).parent()[0].offsetLeft,s=l/a*100,r=i(this).parent().data("value"),u=t.setTips?t.setTips(r):r;e.elemTemp.find("."+d).html(u),"vertical"===t.type?e.elemTemp.find("."+d).css({bottom:s+"%","margin-bottom":"20px",display:"inline-block"}):e.elemTemp.find("."+d).css({left:s+"%",display:"inline-block"})}).on("mouseout",function(){e.elemTemp.find("."+d).css("display","none")})},f.prototype.slide=function(e,t,a){var n=this,l=n.config,s=n.elemTemp,f=function(){return"vertical"===l.type?l.height:s[0].offsetWidth},h=s.find("."+o),y=s.next("."+v),g=y.children("."+c).children("input").val(),b=100/((l.max-l.min)/Math.ceil(l.step)),x=function(e,i){e=Math.ceil(e)*b>100?Math.ceil(e)*b:Math.round(e)*b,e=e>100?100:e,h.eq(i).css("vertical"===l.type?"bottom":"left",e+"%");var t=T(h[0].offsetLeft),a=l.range?T(h[1].offsetLeft):0;"vertical"===l.type?(s.find("."+d).css({bottom:e+"%","margin-bottom":"20px"}),t=T(f()-h[0].offsetTop-h.height()),a=l.range?T(f()-h[1].offsetTop-h.height()):0):s.find("."+d).css("left",e+"%"),t=t>100?100:t,a=a>100?100:a;var n=Math.min(t,a),o=Math.abs(t-a);"vertical"===l.type?s.find("."+r).css({height:o+"%",bottom:n+"%"}):s.find("."+r).css({width:o+"%",left:n+"%"});var u=l.min+Math.round((l.max-l.min)*e/100);if(g=u,y.children("."+c).children("input").val(g),h.eq(i).data("value",u),u=l.setTips?l.setTips(u):u,s.find("."+d).html(u),l.range){var v=[h.eq(0).data("value"),h.eq(1).data("value")];v[0]>v[1]&&v.reverse()}l.change&&l.change(l.range?v:u)},T=function(e){var i=e/f()*100/b,t=Math.round(i)*b;return e==f()&&(t=Math.ceil(i)*b),t},w=i(['
        f()&&(r=f());var o=r/f()*100/b;x(o,e),t.addClass(p),s.find("."+d).show(),i.preventDefault()},o=function(){t.removeClass(p),s.find("."+d).hide()};M(r,o)})}),s.on("click",function(e){var t=i("."+u);if(!t.is(event.target)&&0===t.has(event.target).length&&t.length){var a,n="vertical"===l.type?f()-e.clientY+i(this).offset().top:e.clientX-i(this).offset().left;n<0&&(n=0),n>f()&&(n=f());var s=n/f()*100/b;a=l.range?"vertical"===l.type?Math.abs(n-parseInt(i(h[0]).css("bottom")))>Math.abs(n-parseInt(i(h[1]).css("bottom")))?1:0:Math.abs(n-h[0].offsetLeft)>Math.abs(n-h[1].offsetLeft)?1:0:0,x(s,a),e.preventDefault()}}),y.hover(function(){var e=i(this);e.children("."+m).fadeIn("fast")},function(){var e=i(this);e.children("."+m).fadeOut("fast")}),y.children("."+m).children("i").each(function(e){i(this).on("click",function(){g=1==e?g-l.stepl.max?l.max:Number(g)+l.step;var i=(g-l.min)/(l.max-l.min)*100/b;x(i,0)})});var q=function(){var e=this.value;e=isNaN(e)?0:e,e=el.max?l.max:e,this.value=e;var i=(e-l.min)/(l.max-l.min)*100/b;x(i,0)};y.children("."+c).children("input").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),q.call(this))}).on("change",q)},f.prototype.events=function(){var e=this;e.config},t.render=function(e){var i=new f(e);return a.call(i)},e(n,t)});layui.define("jquery",function(e){"use strict";var i=layui.jquery,o={config:{},index:layui.colorpicker?layui.colorpicker.index+1e4:0,set:function(e){var o=this;return o.config=i.extend({},o.config,e),o},on:function(e,i){return layui.onevent.call(this,"colorpicker",e,i)}},r=function(){var e=this,i=e.config;return{config:i}},t="colorpicker",n="layui-show",l="layui-colorpicker",c=".layui-colorpicker-main",a="layui-icon-down",s="layui-icon-close",f="layui-colorpicker-trigger-span",d="layui-colorpicker-trigger-i",u="layui-colorpicker-side",p="layui-colorpicker-side-slider",g="layui-colorpicker-basis",v="layui-colorpicker-alpha-bgcolor",h="layui-colorpicker-alpha-slider",m="layui-colorpicker-basis-cursor",b="layui-colorpicker-main-input",k=function(e){var i={h:0,s:0,b:0},o=Math.min(e.r,e.g,e.b),r=Math.max(e.r,e.g,e.b),t=r-o;return i.b=r,i.s=0!=r?255*t/r:0,0!=i.s?e.r==r?i.h=(e.g-e.b)/t:e.g==r?i.h=2+(e.b-e.r)/t:i.h=4+(e.r-e.g)/t:i.h=-1,r==o&&(i.h=0),i.h*=60,i.h<0&&(i.h+=360),i.s*=100/255,i.b*=100/255,i},y=function(e){var e=e.indexOf("#")>-1?e.substring(1):e;if(3==e.length){var i=e.split("");e=i[0]+i[0]+i[1]+i[1]+i[2]+i[2]}e=parseInt(e,16);var o={r:e>>16,g:(65280&e)>>8,b:255&e};return k(o)},x=function(e){var i={},o=e.h,r=255*e.s/100,t=255*e.b/100;if(0==r)i.r=i.g=i.b=t;else{var n=t,l=(255-r)*t/255,c=(n-l)*(o%60)/60;360==o&&(o=0),o<60?(i.r=n,i.b=l,i.g=l+c):o<120?(i.g=n,i.b=l,i.r=n-c):o<180?(i.g=n,i.r=l,i.b=l+c):o<240?(i.b=n,i.r=l,i.g=n-c):o<300?(i.b=n,i.g=l,i.r=l+c):o<360?(i.r=n,i.g=l,i.b=n-c):(i.r=0,i.g=0,i.b=0)}return{r:Math.round(i.r),g:Math.round(i.g),b:Math.round(i.b)}},C=function(e){var o=x(e),r=[o.r.toString(16),o.g.toString(16),o.b.toString(16)];return i.each(r,function(e,i){1==i.length&&(r[e]="0"+i)}),r.join("")},P=function(e){var i=/[0-9]{1,3}/g,o=e.match(i)||[];return{r:o[0],g:o[1],b:o[2]}},B=i(window),w=i(document),D=function(e){var r=this;r.index=++o.index,r.config=i.extend({},r.config,o.config,e),r.render()};D.prototype.config={color:"",size:null,alpha:!1,format:"hex",predefine:!1,colors:["#009688","#5FB878","#1E9FFF","#FF5722","#FFB800","#01AAED","#999","#c00","#ff8c00","#ffd700","#90ee90","#00ced1","#1e90ff","#c71585","rgb(0, 186, 189)","rgb(255, 120, 0)","rgb(250, 212, 0)","#393D49","rgba(0,0,0,.5)","rgba(255, 69, 0, 0.68)","rgba(144, 240, 144, 0.5)","rgba(31, 147, 255, 0.73)"]},D.prototype.render=function(){var e=this,o=e.config,r=i(['
        ',"",'3&&(o.alpha&&"rgb"==o.format||(e="#"+C(k(P(o.color))))),"background: "+e):e}()+'">','',"","","
        "].join("")),t=i(o.elem);o.size&&r.addClass("layui-colorpicker-"+o.size),t.addClass("layui-inline").html(e.elemColorBox=r),e.color=e.elemColorBox.find("."+f)[0].style.background,e.events()},D.prototype.renderPicker=function(){var e=this,o=e.config,r=e.elemColorBox[0],t=e.elemPicker=i(['
        ','
        ','
        ','
        ','
        ','
        ',"
        ",'
        ','
        ',"
        ","
        ",'
        ','
        ','
        ',"
        ","
        ",function(){if(o.predefine){var e=['
        '];return layui.each(o.colors,function(i,o){e.push(['
        ','
        ',"
        "].join(""))}),e.push("
        "),e.join("")}return""}(),'
        ','
        ','',"
        ",'
        ','','',"","
        "].join(""));e.elemColorBox.find("."+f)[0];i(c)[0]&&i(c).data("index")==e.index?e.removePicker(D.thisElemInd):(e.removePicker(D.thisElemInd),i("body").append(t)),D.thisElemInd=e.index,D.thisColor=r.style.background,e.position(),e.pickerEvents()},D.prototype.removePicker=function(e){var o=this;o.config;return i("#layui-colorpicker"+(e||o.index)).remove(),o},D.prototype.position=function(){var e=this,i=e.config,o=e.bindElem||e.elemColorBox[0],r=e.elemPicker[0],t=o.getBoundingClientRect(),n=r.offsetWidth,l=r.offsetHeight,c=function(e){return e=e?"scrollLeft":"scrollTop",document.body[e]|document.documentElement[e]},a=function(e){return document.documentElement[e?"clientWidth":"clientHeight"]},s=5,f=t.left,d=t.bottom;f-=(n-o.offsetWidth)/2,d+=s,f+n+s>a("width")?f=a("width")-n-s:fa()&&(d=t.top>l?t.top-l:a()-l,d-=2*s),i.position&&(r.style.position=i.position),r.style.left=f+("fixed"===i.position?0:c(1))+"px",r.style.top=d+("fixed"===i.position?0:c())+"px"},D.prototype.val=function(){var e=this,i=(e.config,e.elemColorBox.find("."+f)),o=e.elemPicker.find("."+b),r=i[0],t=r.style.backgroundColor;if(t){var n=k(P(t)),l=i.attr("lay-type");if(e.select(n.h,n.s,n.b),"torgb"===l&&o.find("input").val(t),"rgba"===l){var c=P(t);if(3==(t.match(/[0-9]{1,3}/g)||[]).length)o.find("input").val("rgba("+c.r+", "+c.g+", "+c.b+", 1)"),e.elemPicker.find("."+h).css("left",280);else{o.find("input").val(t);var a=280*t.slice(t.lastIndexOf(",")+1,t.length-1);e.elemPicker.find("."+h).css("left",a)}e.elemPicker.find("."+v)[0].style.background="linear-gradient(to right, rgba("+c.r+", "+c.g+", "+c.b+", 0), rgb("+c.r+", "+c.g+", "+c.b+"))"}}else e.select(0,100,100),o.find("input").val(""),e.elemPicker.find("."+v)[0].style.background="",e.elemPicker.find("."+h).css("left",280)},D.prototype.side=function(){var e=this,o=e.config,r=e.elemColorBox.find("."+f),t=r.attr("lay-type"),n=e.elemPicker.find("."+u),l=e.elemPicker.find("."+p),c=e.elemPicker.find("."+g),y=e.elemPicker.find("."+m),C=e.elemPicker.find("."+v),w=e.elemPicker.find("."+h),D=l[0].offsetTop/180*360,E=100-(y[0].offsetTop+3)/180*100,H=(y[0].offsetLeft+3)/260*100,W=Math.round(w[0].offsetLeft/280*100)/100,j=e.elemColorBox.find("."+d),F=e.elemPicker.find(".layui-colorpicker-pre").children("div"),L=function(i,n,l,c){e.select(i,n,l);var f=x({h:i,s:n,b:l});if(j.addClass(a).removeClass(s),r[0].style.background="rgb("+f.r+", "+f.g+", "+f.b+")","torgb"===t&&e.elemPicker.find("."+b).find("input").val("rgb("+f.r+", "+f.g+", "+f.b+")"),"rgba"===t){var d=0;d=280*c,w.css("left",d),e.elemPicker.find("."+b).find("input").val("rgba("+f.r+", "+f.g+", "+f.b+", "+c+")"),r[0].style.background="rgba("+f.r+", "+f.g+", "+f.b+", "+c+")",C[0].style.background="linear-gradient(to right, rgba("+f.r+", "+f.g+", "+f.b+", 0), rgb("+f.r+", "+f.g+", "+f.b+"))"}o.change&&o.change(e.elemPicker.find("."+b).find("input").val())},M=i(['
        t&&(r=t);var l=r/180*360;D=l,L(l,H,E,W),e.preventDefault()};Y(r),e.preventDefault()}),n.on("click",function(e){var o=e.clientY-i(this).offset().top;o<0&&(o=0),o>this.offsetHeight&&(o=this.offsetHeight);var r=o/180*360;D=r,L(r,H,E,W),e.preventDefault()}),y.on("mousedown",function(e){var i=this.offsetTop,o=this.offsetLeft,r=e.clientY,t=e.clientX,n=function(e){var n=i+(e.clientY-r),l=o+(e.clientX-t),a=c[0].offsetHeight-3,s=c[0].offsetWidth-3;n<-3&&(n=-3),n>a&&(n=a),l<-3&&(l=-3),l>s&&(l=s);var f=(l+3)/260*100,d=100-(n+3)/180*100;E=d,H=f,L(D,f,d,W),e.preventDefault()};layui.stope(e),Y(n),e.preventDefault()}),c.on("mousedown",function(e){var o=e.clientY-i(this).offset().top-3+B.scrollTop(),r=e.clientX-i(this).offset().left-3+B.scrollLeft();o<-3&&(o=-3),o>this.offsetHeight-3&&(o=this.offsetHeight-3),r<-3&&(r=-3),r>this.offsetWidth-3&&(r=this.offsetWidth-3);var t=(r+3)/260*100,n=100-(o+3)/180*100;E=n,H=t,L(D,t,n,W),e.preventDefault(),y.trigger(e,"mousedown")}),w.on("mousedown",function(e){var i=this.offsetLeft,o=e.clientX,r=function(e){var r=i+(e.clientX-o),t=C[0].offsetWidth;r<0&&(r=0),r>t&&(r=t);var n=Math.round(r/280*100)/100;W=n,L(D,H,E,n),e.preventDefault()};Y(r),e.preventDefault()}),C.on("click",function(e){var o=e.clientX-i(this).offset().left;o<0&&(o=0),o>this.offsetWidth&&(o=this.offsetWidth);var r=Math.round(o/280*100)/100;W=r,L(D,H,E,r),e.preventDefault()}),F.each(function(){i(this).on("click",function(){i(this).parent(".layui-colorpicker-pre").addClass("selected").siblings().removeClass("selected");var e,o=this.style.backgroundColor,r=k(P(o)),t=o.slice(o.lastIndexOf(",")+1,o.length-1);D=r.h,H=r.s,E=r.b,3==(o.match(/[0-9]{1,3}/g)||[]).length&&(t=1),W=t,e=280*t,L(r.h,r.s,r.b,t)})})},D.prototype.select=function(e,i,o,r){var t=this,n=(t.config,C({h:e,s:100,b:100})),l=C({h:e,s:i,b:o}),c=e/360*180,a=180-o/100*180-3,s=i/100*260-3;t.elemPicker.find("."+p).css("top",c),t.elemPicker.find("."+g)[0].style.background="#"+n,t.elemPicker.find("."+m).css({top:a,left:s}),"change"!==r&&t.elemPicker.find("."+b).find("input").val("#"+l)},D.prototype.pickerEvents=function(){var e=this,o=e.config,r=e.elemColorBox.find("."+f),t=e.elemPicker.find("."+b+" input"),n={clear:function(i){r[0].style.background="",e.elemColorBox.find("."+d).removeClass(a).addClass(s),e.color="",o.done&&o.done(""),e.removePicker()},confirm:function(i,n){var l=t.val(),c=l,f={};if(l.indexOf(",")>-1){if(f=k(P(l)),e.select(f.h,f.s,f.b),r[0].style.background=c="#"+C(f),(l.match(/[0-9]{1,3}/g)||[]).length>3&&"rgba"===r.attr("lay-type")){var u=280*l.slice(l.lastIndexOf(",")+1,l.length-1);e.elemPicker.find("."+h).css("left",u),r[0].style.background=l,c=l}}else f=y(l),r[0].style.background=c="#"+C(f),e.elemColorBox.find("."+d).removeClass(s).addClass(a);return"change"===n?(e.select(f.h,f.s,f.b,n),void(o.change&&o.change(c))):(e.color=l,o.done&&o.done(l),void e.removePicker())}};e.elemPicker.on("click","*[colorpicker-events]",function(){var e=i(this),o=e.attr("colorpicker-events");n[o]&&n[o].call(this,e)}),t.on("keyup",function(e){var o=i(this);n.confirm.call(this,o,13===e.keyCode?null:"change")})},D.prototype.events=function(){var e=this,o=e.config,r=e.elemColorBox.find("."+f);e.elemColorBox.on("click",function(){e.renderPicker(),i(c)[0]&&(e.val(),e.side())}),o.elem[0]&&!e.elemColorBox[0].eventHandler&&(w.on("click",function(o){if(!i(o.target).hasClass(l)&&!i(o.target).parents("."+l)[0]&&!i(o.target).hasClass(c.replace(/\./g,""))&&!i(o.target).parents(c)[0]&&e.elemPicker){if(e.color){var t=k(P(e.color));e.select(t.h,t.s,t.b)}else e.elemColorBox.find("."+d).removeClass(a).addClass(s);r[0].style.background=e.color||"",e.removePicker()}}),B.on("resize",function(){return!(!e.elemPicker||!i(c)[0])&&void e.position()}),e.elemColorBox[0].eventHandler=!0)},o.render=function(e){var i=new D(e);return r.call(i)},e(t,o)});layui.define("layer",function(e){"use strict";var t=layui.$,i=layui.layer,a=layui.hint(),n=layui.device(),l="form",r=".layui-form",s="layui-this",o="layui-hide",c="layui-disabled",u=function(){this.config={verify:{required:[/[\S]+/,"必填项不能为空"],phone:[/^1\d{10}$/,"请输入正确的手机号"],email:[/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,"邮箱格式不正确"],url:[/(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/,"链接格式不正确"],number:function(e){if(!e||isNaN(e))return"只能填写数字"},date:[/^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/,"日期格式不正确"],identity:[/(^\d{15}$)|(^\d{17}(x|X|\d)$)/,"请输入正确的身份证号"]}}};u.prototype.set=function(e){var i=this;return t.extend(!0,i.config,e),i},u.prototype.verify=function(e){var i=this;return t.extend(!0,i.config.verify,e),i},u.prototype.on=function(e,t){return layui.onevent.call(this,l,e,t)},u.prototype.val=function(e,i){var a=this,n=t(r+'[lay-filter="'+e+'"]');return n.each(function(e,a){var n=t(this);layui.each(i,function(e,t){var i,a=n.find('[name="'+e+'"]');a[0]&&(i=a[0].type,"checkbox"===i?a[0].checked=t:"radio"===i?a.each(function(){this.value==t&&(this.checked=!0)}):a.val(t))})}),f.render(null,e),a.getValue(e)},u.prototype.getValue=function(e,i){i=i||t(r+'[lay-filter="'+e+'"]').eq(0);var a={},n={},l=i.find("input,select,textarea");return layui.each(l,function(e,t){if(t.name=(t.name||"").replace(/^\s*|\s*&/,""),t.name){if(/^.*\[\]$/.test(t.name)){var i=t.name.match(/^(.*)\[\]$/g)[0];a[i]=0|a[i],t.name=t.name.replace(/^(.*)\[\]$/,"$1["+a[i]++ +"]")}/^checkbox|radio$/.test(t.type)&&!t.checked||(n[t.name]=t.value)}}),n},u.prototype.render=function(e,i){var n=this,u=t(r+function(){return i?'[lay-filter="'+i+'"]':""}()),d={select:function(){var e,i="请选择",a="layui-form-select",n="layui-select-title",r="layui-select-none",d="",f=u.find("select"),v=function(i,l){t(i.target).parent().hasClass(n)&&!l||(t("."+a).removeClass(a+"ed "+a+"up"),e&&d&&e.val(d)),e=null},y=function(i,u,f){var y,p=t(this),m=i.find("."+n),k=m.find("input"),g=i.find("dl"),x=g.children("dd"),b=this.selectedIndex;if(!u){var C=function(){var e=i.offset().top+i.outerHeight()+5-h.scrollTop(),t=g.outerHeight();b=p[0].selectedIndex,i.addClass(a+"ed"),x.removeClass(o),y=null,x.eq(b).addClass(s).siblings().removeClass(s),e+t>h.height()&&e>=t&&i.addClass(a+"up"),T()},w=function(e){i.removeClass(a+"ed "+a+"up"),k.blur(),y=null,e||$(k.val(),function(e){var i=p[0].selectedIndex;e&&(d=t(p[0].options[i]).html(),0===i&&d===k.attr("placeholder")&&(d=""),k.val(d||""))})},T=function(){var e=g.children("dd."+s);if(e[0]){var t=e.position().top,i=g.height(),a=e.height();t>i&&g.scrollTop(t+g.scrollTop()-i+a-5),t<0&&g.scrollTop(t+g.scrollTop()-5)}};m.on("click",function(e){i.hasClass(a+"ed")?w():(v(e,!0),C()),g.find("."+r).remove()}),m.find(".layui-edge").on("click",function(){k.focus()}),k.on("keyup",function(e){var t=e.keyCode;9===t&&C()}).on("keydown",function(e){var t=e.keyCode;9===t&&w();var i=function(t,a){var n,l;e.preventDefault();var r=function(){var e=g.children("dd."+s);if(g.children("dd."+o)[0]&&"next"===t){var i=g.children("dd:not(."+o+",."+c+")"),n=i.eq(0).index();if(n>=0&&n无匹配项

        '):g.find("."+r).remove()},"keyup"),""===t&&g.find("."+r).remove(),void T())};f&&k.on("keyup",q).on("blur",function(i){var a=p[0].selectedIndex;e=k,d=t(p[0].options[a]).html(),0===a&&d===k.attr("placeholder")&&(d=""),setTimeout(function(){$(k.val(),function(e){d||k.val("")},"blur")},200)}),x.on("click",function(){var e=t(this),a=e.attr("lay-value"),n=p.attr("lay-filter");return!e.hasClass(c)&&(e.hasClass("layui-select-tips")?k.val(""):(k.val(e.text()),e.addClass(s)),e.siblings().removeClass(s),p.val(a).removeClass("layui-form-danger"),layui.event.call(this,l,"select("+n+")",{elem:p[0],value:a,othis:i}),w(!0),!1)}),i.find("dl>dt").on("click",function(e){return!1}),t(document).off("click",v).on("click",v)}};f.each(function(e,l){var r=t(this),o=r.next("."+a),u=this.disabled,d=l.value,f=t(l.options[l.selectedIndex]),v=l.options[0];if("string"==typeof r.attr("lay-ignore"))return r.show();var h="string"==typeof r.attr("lay-search"),p=v?v.value?i:v.innerHTML||i:i,m=t(['
        ','
        ','','
        ','
        ',function(e){var t=[];return layui.each(e,function(e,a){0!==e||a.value?"optgroup"===a.tagName.toLowerCase()?t.push("
        "+a.label+"
        "):t.push('
        '+a.innerHTML+"
        "):t.push('
        '+(a.innerHTML||i)+"
        ")}),0===t.length&&t.push('
        没有选项
        '),t.join("")}(r.find("*"))+"
        ","
        "].join(""));o[0]&&o.remove(),r.after(m),y.call(this,m,u,h)})},checkbox:function(){var e={checkbox:["layui-form-checkbox","layui-form-checked","checkbox"],_switch:["layui-form-switch","layui-form-onswitch","switch"]},i=u.find("input[type=checkbox]"),a=function(e,i){var a=t(this);e.on("click",function(){var t=a.attr("lay-filter"),n=(a.attr("lay-text")||"").split("|");a[0].disabled||(a[0].checked?(a[0].checked=!1,e.removeClass(i[1]).find("em").text(n[1])):(a[0].checked=!0,e.addClass(i[1]).find("em").text(n[0])),layui.event.call(a[0],l,i[2]+"("+t+")",{elem:a[0],value:a[0].value,othis:e}))})};i.each(function(i,n){var l=t(this),r=l.attr("lay-skin"),s=(l.attr("lay-text")||"").split("|"),o=this.disabled;"switch"===r&&(r="_"+r);var u=e[r]||e.checkbox;if("string"==typeof l.attr("lay-ignore"))return l.show();var d=l.next("."+u[0]),f=t(['
        ",function(){var e=n.title.replace(/\s/g,""),t={checkbox:[e?""+n.title+"":"",''].join(""),_switch:""+((n.checked?s[0]:s[1])||"")+""};return t[r]||t.checkbox}(),"
        "].join(""));d[0]&&d.remove(),l.after(f),a.call(this,f,u)})},radio:function(){var e="layui-form-radio",i=["",""],a=u.find("input[type=radio]"),n=function(a){var n=t(this),s="layui-anim-scaleSpring";a.on("click",function(){var o=n[0].name,c=n.parents(r),u=n.attr("lay-filter"),d=c.find("input[name="+o.replace(/(\.|#|\[|\])/g,"\\$1")+"]");n[0].disabled||(layui.each(d,function(){var a=t(this).next("."+e);this.checked=!1,a.removeClass(e+"ed"),a.find(".layui-icon").removeClass(s).html(i[1])}),n[0].checked=!0,a.addClass(e+"ed"),a.find(".layui-icon").addClass(s).html(i[0]),layui.event.call(n[0],l,"radio("+u+")",{elem:n[0],value:n[0].value,othis:a}))})};a.each(function(a,l){var r=t(this),s=r.next("."+e),o=this.disabled;if("string"==typeof r.attr("lay-ignore"))return r.show();s[0]&&s.remove();var u=t(['
        ',''+i[l.checked?0:1]+"","
        "+function(){var e=l.title||"";return"string"==typeof r.next().attr("lay-radio")&&(e=r.next().html(),r.next().remove()),e}()+"
        ","
        "].join(""));r.after(u),n.call(this,u)})}};return e?d[e]?d[e]():a.error("不支持的"+e+"表单渲染"):layui.each(d,function(e,t){t()}),n};var d=function(){var e=null,a=f.config.verify,s="layui-form-danger",o={},c=t(this),u=c.parents(r),d=u.find("*[lay-verify]"),v=c.parents("form")[0],h=c.attr("lay-filter");return layui.each(d,function(l,r){var o=t(this),c=o.attr("lay-verify").split("|"),u=o.attr("lay-verType"),d=o.val();if(o.removeClass(s),layui.each(c,function(t,l){var c,f="",v="function"==typeof a[l];if(a[l]){var c=v?f=a[l](d,r):!a[l][0].test(d);if(f=f||a[l][1],"required"===l&&(f=o.attr("lay-reqText")||f),c)return"tips"===u?i.tips(f,function(){return"string"==typeof o.attr("lay-ignore")||"select"!==r.tagName.toLowerCase()&&!/^checkbox|radio$/.test(r.type)?o:o.next()}(),{tips:1}):"alert"===u?i.alert(f,{title:"提示",shadeClose:!0}):i.msg(f,{icon:5,shift:6}),n.android||n.ios||setTimeout(function(){r.focus()},7),o.addClass(s),e=!0}}),e)return e}),!e&&(o=f.getValue(null,u),layui.event.call(this,l,"submit("+h+")",{elem:this,form:v,field:o}))},f=new u,v=t(document),h=t(window);f.render(),v.on("reset",r,function(){var e=t(this).attr("lay-filter");setTimeout(function(){f.render(null,e)},50)}),v.on("submit",r,d).on("click","*[lay-submit]",d),e(l,f)});layui.define("form",function(e){"use strict";var i=layui.$,a=layui.form,n=layui.layer,t="tree",r={config:{},index:layui[t]?layui[t].index+1e4:0,set:function(e){var a=this;return a.config=i.extend({},a.config,e),a},on:function(e,i){return layui.onevent.call(this,t,e,i)}},l=function(){var e=this,i=e.config,a=i.id||e.index;return l.that[a]=e,l.config[a]=i,{config:i,reload:function(i){e.reload.call(e,i)},getChecked:function(){return e.getChecked.call(e)},setChecked:function(i){return e.setChecked.call(e,i)}}},c="layui-hide",d="layui-disabled",s="layui-tree-set",o="layui-tree-iconClick",h="layui-icon-addition",u="layui-icon-subtraction",p="layui-tree-entry",f="layui-tree-main",y="layui-tree-txt",v="layui-tree-pack",C="layui-tree-spread",k="layui-tree-setLineShort",m="layui-tree-showLine",x="layui-tree-lineExtend",b=function(e){var a=this;a.index=++r.index,a.config=i.extend({},a.config,r.config,e),a.render()};b.prototype.config={data:[],showCheckbox:!1,showLine:!0,accordion:!1,onlyIconControl:!1,isJump:!1,edit:!1,text:{defaultNodeName:"未命名",none:"无数据"}},b.prototype.reload=function(e){var a=this;layui.each(e,function(e,i){i.constructor===Array&&delete a.config[e]}),a.config=i.extend(!0,{},a.config,e),a.render()},b.prototype.render=function(){var e=this,a=e.config;e.checkids=[];var n=i('
        ');e.tree(n);var t=a.elem=i(a.elem);if(t[0]){if(e.key=a.id||e.index,e.elem=n,e.elemNone=i('
        '+a.text.none+"
        "),t.html(e.elem),0==e.elem.find(".layui-tree-set").length)return e.elem.append(e.elemNone);a.showCheckbox&&e.renderForm("checkbox"),e.elem.find(".layui-tree-set").each(function(){var e=i(this);e.parent(".layui-tree-pack")[0]||e.addClass("layui-tree-setHide"),!e.next()[0]&&e.parents(".layui-tree-pack").eq(1).hasClass("layui-tree-lineExtend")&&e.addClass(k),e.next()[0]||e.parents(".layui-tree-set").eq(0).next()[0]||e.addClass(k)}),e.events()}},b.prototype.renderForm=function(e){a.render(e,"LAY-tree-"+this.index)},b.prototype.tree=function(e,a){var n=this,t=n.config,r=a||t.data;layui.each(r,function(a,r){var l=r.children&&r.children.length>0,o=i('
        '),h=i(['
        ','
        ','
        ',function(){return t.showLine?l?'':'':''}(),function(){return t.showCheckbox?'':""}(),function(){return t.isJump&&r.href?''+(r.title||r.label||t.text.defaultNodeName)+"":''+(r.title||r.label||t.text.defaultNodeName)+""}(),"
        ",function(){if(!t.edit)return"";var e={add:'',update:'',del:''},i=['
        '];return t.edit===!0&&(t.edit=["update","del"]),"object"==typeof t.edit?(layui.each(t.edit,function(a,n){i.push(e[n]||"")}),i.join("")+"
        "):void 0}(),"
        "].join(""));l&&(h.append(o),n.tree(o,r.children)),e.append(h),h.prev("."+s)[0]&&h.prev().children(".layui-tree-pack").addClass("layui-tree-showLine"),l||h.parent(".layui-tree-pack").addClass("layui-tree-lineExtend"),n.spread(h,r),t.showCheckbox&&(r.checked&&n.checkids.push(r.id),n.checkClick(h,r)),t.edit&&n.operate(h,r)})},b.prototype.spread=function(e,a){var n=this,t=n.config,r=e.children("."+p),l=r.children("."+f),c=r.find("."+o),k=r.find("."+y),m=t.onlyIconControl?c:l,x="";m.on("click",function(i){var a=e.children("."+v),n=m.children(".layui-icon")[0]?m.children(".layui-icon"):m.find(".layui-tree-icon").children(".layui-icon");if(a[0]){if(e.hasClass(C))e.removeClass(C),a.slideUp(200),n.removeClass(u).addClass(h);else if(e.addClass(C),a.slideDown(200),n.addClass(u).removeClass(h),t.accordion){var r=e.siblings("."+s);r.removeClass(C),r.children("."+v).slideUp(200),r.find(".layui-tree-icon").children(".layui-icon").removeClass(u).addClass(h)}}else x="normal"}),k.on("click",function(){var n=i(this);n.hasClass(d)||(x=e.hasClass(C)?t.onlyIconControl?"open":"close":t.onlyIconControl?"close":"open",t.click&&t.click({elem:e,state:x,data:a}))})},b.prototype.setCheckbox=function(e,i,a){var n=this,t=(n.config,a.prop("checked"));if(!a.prop("disabled")){if("object"==typeof i.children||e.find("."+v)[0]){var r=e.find("."+v).find('input[same="layuiTreeCheck"]');r.each(function(){this.disabled||(this.checked=t)})}var l=function(e){if(e.parents("."+s)[0]){var i,a=e.parent("."+v),n=a.parent(),r=a.prev().find('input[same="layuiTreeCheck"]');t?r.prop("checked",t):(a.find('input[same="layuiTreeCheck"]').each(function(){this.checked&&(i=!0)}),i||r.prop("checked",!1)),l(n)}};l(e),n.renderForm("checkbox")}},b.prototype.checkClick=function(e,a){var n=this,t=n.config,r=e.children("."+p),l=r.children("."+f);l.on("click",'input[same="layuiTreeCheck"]+',function(r){layui.stope(r);var l=i(this).prev(),c=l.prop("checked");l.prop("disabled")||(n.setCheckbox(e,a,l),t.oncheck&&t.oncheck({elem:e,checked:c,data:a}))})},b.prototype.operate=function(e,a){var t=this,r=t.config,l=e.children("."+p),d=l.children("."+f);l.children(".layui-tree-btnGroup").on("click",".layui-icon",function(l){layui.stope(l);var f=i(this).data("type"),b=e.children("."+v),g={data:a,type:f,elem:e};if("add"==f){b[0]||(r.showLine?(d.find("."+o).addClass("layui-tree-icon"),d.find("."+o).children(".layui-icon").addClass(h).removeClass("layui-icon-file")):d.find(".layui-tree-iconArrow").removeClass(c),e.append('
        '));var w=r.operate&&r.operate(g),N={};if(N.title=r.text.defaultNodeName,N.id=w,t.tree(e.children("."+v),[N]),r.showLine)if(b[0])b.hasClass(x)||b.addClass(x),e.find("."+v).each(function(){i(this).children("."+s).last().addClass(k)}),b.children("."+s).last().prev().hasClass(k)?b.children("."+s).last().prev().removeClass(k):b.children("."+s).last().removeClass(k),!e.parent("."+v)[0]&&e.next()[0]&&b.children("."+s).last().removeClass(k);else{var T=e.siblings("."+s),L=1,A=e.parent("."+v);layui.each(T,function(e,a){i(a).children("."+v)[0]||(L=0)}),1==L?(T.children("."+v).addClass(m),T.children("."+v).children("."+s).removeClass(k),e.children("."+v).addClass(m),A.removeClass(x),A.children("."+s).last().children("."+v).children("."+s).last().addClass(k)):e.children("."+v).children("."+s).addClass(k)}if(!r.showCheckbox)return;if(d.find('input[same="layuiTreeCheck"]')[0].checked){var I=e.children("."+v).children("."+s).last();I.find('input[same="layuiTreeCheck"]')[0].checked=!0}t.renderForm("checkbox")}else if("update"==f){var F=d.children("."+y).html();d.children("."+y).html(""),d.append(''),d.children(".layui-tree-editInput").val(F).focus();var j=function(e){var i=e.val().trim();i=i?i:r.text.defaultNodeName,e.remove(),d.children("."+y).html(i),g.data.title=i,r.operate&&r.operate(g)};d.children(".layui-tree-editInput").blur(function(){j(i(this))}),d.children(".layui-tree-editInput").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),j(i(this)))})}else n.confirm('确认删除该节点 "'+(a.title||"")+'" 吗?',function(a){if(r.operate&&r.operate(g),g.status="remove",n.close(a),!e.prev("."+s)[0]&&!e.next("."+s)[0]&&!e.parent("."+v)[0])return e.remove(),void t.elem.append(t.elemNone);if(e.siblings("."+s).children("."+p)[0]){if(r.showCheckbox){var l=function(e){if(e.parents("."+s)[0]){var a=e.siblings("."+s).children("."+p),n=e.parent("."+v).prev(),r=n.find('input[same="layuiTreeCheck"]')[0],c=1,d=0;0==r.checked&&(a.each(function(e,a){var n=i(a).find('input[same="layuiTreeCheck"]')[0];0!=n.checked||n.disabled||(c=0),n.disabled||(d=1)}),1==c&&1==d&&(r.checked=!0,t.renderForm("checkbox"),l(n.parent("."+s))))}};l(e)}if(r.showLine){var d=e.siblings("."+s),h=1,f=e.parent("."+v);layui.each(d,function(e,a){i(a).children("."+v)[0]||(h=0)}),1==h?(b[0]||(f.removeClass(x),d.children("."+v).addClass(m),d.children("."+v).children("."+s).removeClass(k)),e.next()[0]?f.children("."+s).last().children("."+v).children("."+s).last().addClass(k):e.prev().children("."+v).children("."+s).last().addClass(k),e.next()[0]||e.parents("."+s)[1]||e.parents("."+s).eq(0).next()[0]||e.prev("."+s).addClass(k)):!e.next()[0]&&e.hasClass(k)&&e.prev().addClass(k)}}else{var y=e.parent("."+v).prev();if(r.showLine){y.find("."+o).removeClass("layui-tree-icon"),y.find("."+o).children(".layui-icon").removeClass(u).addClass("layui-icon-file");var w=y.parents("."+v).eq(0);w.addClass(x),w.children("."+s).each(function(){i(this).children("."+v).children("."+s).last().addClass(k)})}else y.find(".layui-tree-iconArrow").addClass(c);e.parents("."+s).eq(0).removeClass(C),e.parent("."+v).remove()}e.remove()})})},b.prototype.events=function(){var e=this,a=e.config;e.elem.find(".layui-tree-checkedFirst");e.setChecked(e.checkids),e.elem.find(".layui-tree-search").on("keyup",function(){var n=i(this),t=n.val(),r=n.nextAll(),l=[];r.find("."+y).each(function(){var e=i(this).parents("."+p);if(i(this).html().indexOf(t)!=-1){l.push(i(this).parent());var a=function(e){e.addClass("layui-tree-searchShow"),e.parent("."+v)[0]&&a(e.parent("."+v).parent("."+s))};a(e.parent("."+s))}}),r.find("."+p).each(function(){var e=i(this).parent("."+s);e.hasClass("layui-tree-searchShow")||e.addClass(c)}),0==r.find(".layui-tree-searchShow").length&&e.elem.append(e.elemNone),a.onsearch&&a.onsearch({elem:l})}),e.elem.find(".layui-tree-search").on("keydown",function(){i(this).nextAll().find("."+p).each(function(){var e=i(this).parent("."+s);e.removeClass("layui-tree-searchShow "+c)}),i(".layui-tree-emptyText")[0]&&i(".layui-tree-emptyText").remove()})},b.prototype.getChecked=function(){var e=this,a=e.config,n=[],t=[];e.elem.find(".layui-form-checked").each(function(){n.push(i(this).prev()[0].value)});var r=function(e,a){layui.each(e,function(e,t){layui.each(n,function(e,n){if(t.id==n){var l=i.extend({},t);return delete l.children,a.push(l),t.children&&(l.children=[],r(t.children,l.children)),!0}})})};return r(i.extend({},a.data),t),t},b.prototype.setChecked=function(e){var a=this;a.config;a.elem.find("."+s).each(function(a,n){var t=i(this).data("id"),r=i(n).children("."+p).find('input[same="layuiTreeCheck"]'),l=r.next();if("number"==typeof e){if(t==e)return r[0].checked||l.click(),!1}else"object"==typeof e&&layui.each(e,function(e,i){if(i==t&&!r[0].checked)return l.click(),!0})})},l.that={},l.config={},r.reload=function(e,i){var a=l.that[e];return a.reload(i),l.call(a)},r.getChecked=function(e){var i=l.that[e];return i.getChecked()},r.setChecked=function(e,i){var a=l.that[e];return a.setChecked(i)},r.render=function(e){var i=new b(e);return l.call(i)},e(t,r)});layui.define(["laytpl","form"],function(e){"use strict";var a=layui.$,t=layui.laytpl,n=layui.form,i="transfer",l={config:{},index:layui[i]?layui[i].index+1e4:0,set:function(e){var t=this;return t.config=a.extend({},t.config,e),t},on:function(e,a){return layui.onevent.call(this,i,e,a)}},r=function(){var e=this,a=e.config,t=a.id||e.index;return r.that[t]=e,r.config[t]=a,{config:a,reload:function(a){e.reload.call(e,a)},getData:function(){return e.getData.call(e)}}},c="layui-hide",o="layui-btn-disabled",d="layui-none",s="layui-transfer-box",u="layui-transfer-header",h="layui-transfer-search",f="layui-transfer-active",y="layui-transfer-data",p=function(e){return e=e||{},['
        ','
        ','","
        ","{{# if(d.data.showSearch){ }}",'","{{# } }}",'
          ',"
          "].join("")},v=['
          ',p({index:0,checkAllName:"layTransferLeftCheckAll"}),'
          ','",'","
          ",p({index:1,checkAllName:"layTransferRightCheckAll"}),"
          "].join(""),x=function(e){var t=this;t.index=++l.index,t.config=a.extend({},t.config,l.config,e),t.render()};x.prototype.config={title:["列表一","列表二"],width:200,height:360,data:[],value:[],showSearch:!1,id:"",text:{none:"无数据",searchNone:"无匹配数据"}},x.prototype.reload=function(e){var t=this;layui.each(e,function(e,a){a.constructor===Array&&delete t.config[e]}),t.config=a.extend(!0,{},t.config,e),t.render()},x.prototype.render=function(){var e=this,n=e.config,i=e.elem=a(t(v).render({data:n,index:e.index})),l=n.elem=a(n.elem);l[0]&&(n.data=n.data||[],n.value=n.value||[],e.key=n.id||e.index,l.html(e.elem),e.layBox=e.elem.find("."+s),e.layHeader=e.elem.find("."+u),e.laySearch=e.elem.find("."+h),e.layData=i.find("."+y),e.layBtn=i.find("."+f+" .layui-btn"),e.layBox.css({width:n.width,height:n.height}),e.layData.css({height:function(){return n.height-e.layHeader.outerHeight()-e.laySearch.outerHeight()-2}()}),e.renderData(),e.events())},x.prototype.renderData=function(){var e=this,a=(e.config,[{checkName:"layTransferLeftCheck",views:[]},{checkName:"layTransferRightCheck",views:[]}]);e.parseData(function(e){var t=e.selected?1:0,n=["
        • ",'',"
        • "].join("");a[t].views.push(n),delete e.selected}),e.layData.eq(0).html(a[0].views.join("")),e.layData.eq(1).html(a[1].views.join("")),e.renderCheckBtn()},x.prototype.renderForm=function(e){n.render(e,"LAY-transfer-"+this.index)},x.prototype.renderCheckBtn=function(e){var t=this,n=t.config;e=e||{},t.layBox.each(function(i){var l=a(this),r=l.find("."+y),d=l.find("."+u).find('input[type="checkbox"]'),s=r.find('input[type="checkbox"]'),h=0,f=!1;if(s.each(function(){var e=a(this).data("hide");(this.checked||this.disabled||e)&&h++,this.checked&&!e&&(f=!0)}),d.prop("checked",f&&h===s.length),t.layBtn.eq(i)[f?"removeClass":"addClass"](o),!e.stopNone){var p=r.children("li:not(."+c+")").length;t.noneView(r,p?"":n.text.none)}}),t.renderForm("checkbox")},x.prototype.noneView=function(e,t){var n=a('

          '+(t||"")+"

          ");e.find("."+d)[0]&&e.find("."+d).remove(),t.replace(/\s/g,"")&&e.append(n)},x.prototype.setValue=function(){var e=this,t=e.config,n=[];return e.layBox.eq(1).find("."+y+' input[type="checkbox"]').each(function(){var e=a(this).data("hide");e||n.push(this.value)}),t.value=n,e},x.prototype.parseData=function(e){var t=this,n=t.config,i=[];return layui.each(n.data,function(t,l){l=("function"==typeof n.parseData?n.parseData(l):l)||l,i.push(l=a.extend({},l)),layui.each(n.value,function(e,a){a==l.value&&(l.selected=!0)}),e&&e(l)}),n.data=i,t},x.prototype.getData=function(e){var a=this,t=a.config,n=[];return a.setValue(),layui.each(e||t.value,function(e,a){layui.each(t.data,function(e,t){delete t.selected,a==t.value&&n.push(t)})}),n},x.prototype.events=function(){var e=this,t=e.config;e.elem.on("click",'input[lay-filter="layTransferCheckbox"]+',function(){var t=a(this).prev(),n=t[0].checked,i=t.parents("."+s).eq(0).find("."+y);t[0].disabled||("all"===t.attr("lay-type")&&i.find('input[type="checkbox"]').each(function(){this.disabled||(this.checked=n)}),e.renderCheckBtn({stopNone:!0}))}),e.layBtn.on("click",function(){var n=a(this),i=n.data("index"),l=e.layBox.eq(i),r=[];if(!n.hasClass(o)){e.layBox.eq(i).each(function(t){var n=a(this),i=n.find("."+y);i.children("li").each(function(){var t=a(this),n=t.find('input[type="checkbox"]'),i=n.data("hide");n[0].checked&&!i&&(n[0].checked=!1,l.siblings("."+s).find("."+y).append(t.clone()),t.remove(),r.push(n[0].value)),e.setValue()})}),e.renderCheckBtn();var c=l.siblings("."+s).find("."+h+" input");""===c.val()||c.trigger("keyup"),t.onchange&&t.onchange(e.getData(r),i)}}),e.laySearch.find("input").on("keyup",function(){var n=this.value,i=a(this).parents("."+h).eq(0).siblings("."+y),l=i.children("li");l.each(function(){var e=a(this),t=e.find('input[type="checkbox"]'),i=t[0].title.indexOf(n)!==-1;e[i?"removeClass":"addClass"](c),t.data("hide",!i)}),e.renderCheckBtn();var r=l.length===i.children("li."+c).length;e.noneView(i,r?t.text.searchNone:"")})},r.that={},r.config={},l.reload=function(e,a){var t=r.that[e];return t.reload(a),r.call(t)},l.getData=function(e){var a=r.that[e];return a.getData()},l.render=function(e){var a=new x(e);return r.call(a)},e(i,l)});layui.define(["laytpl","laypage","layer","form","util"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=(layui.util,layui.hint()),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,y,e,t)}},c=function(){var e=this,t=e.config,i=t.id||t.index;return i&&(c.that[i]=e,c.config[i]=t),{config:t,reload:function(t){e.reload.call(e,t)},setColsWidth:function(){e.setColsWidth.call(e)},resize:function(){e.resize.call(e)}}},s=function(e){var t=c.config[e];return t||o.error("The ID option was not found in the table instance"),t||null},u=function(e,a,l,n){var o=e.templet?function(){return"function"==typeof e.templet?e.templet(l):i(t(e.templet).html()||String(a)).render(l)}():a;return n?t("
          "+o+"
          ").text():o},y="table",h=".layui-table",f="layui-hide",p="layui-none",v="layui-table-view",m=".layui-table-tool",g=".layui-table-box",b=".layui-table-init",x=".layui-table-header",k=".layui-table-body",C=".layui-table-main",w=".layui-table-fixed",T=".layui-table-fixed-l",A=".layui-table-fixed-r",L=".layui-table-total",N=".layui-table-page",S=".layui-table-sort",R="layui-table-edit",W="layui-table-hover",_=function(e){var t='{{#if(item2.colspan){}} colspan="{{item2.colspan}}"{{#} if(item2.rowspan){}} rowspan="{{item2.rowspan}}"{{#}}}';return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),"{{# var isSort = !(item2.colGroup) && item2.sort; }}",'",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
          ','
          ','{{# if(item2.type === "checkbox"){ }}','',"{{# } else { }}",'{{item2.title||""}}',"{{# if(isSort){ }}",'',"{{# } }}","{{# } }}","
          ","
          "].join("")},E=['',"","
          "].join(""),z=['
          ',"{{# if(d.data.toolbar){ }}",'
          ','
          ','
          ',"
          ","{{# } }}",'
          ',"{{# if(d.data.loading){ }}",'
          ','',"
          ","{{# } }}","{{# var left, right; }}",'
          ',_(),"
          ",'
          ',E,"
          ","{{# if(left){ }}",'
          ','
          ',_({fixed:!0}),"
          ",'
          ',E,"
          ","
          ","{{# }; }}","{{# if(right){ }}",'
          ','
          ',_({fixed:"right"}),'
          ',"
          ",'
          ',E,"
          ","
          ","{{# }; }}","
          ","{{# if(d.data.totalRow){ }}",'
          ','','',"
          ","
          ","{{# } }}","{{# if(d.data.page){ }}",'
          ','
          ',"
          ","{{# } }}","","
          "].join(""),H=t(window),j=t(document),F=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};F.prototype.config={limit:10,loading:!0,cellMinWidth:60,defaultToolbar:["filter","exports","print"],autoSort:!0,text:{none:"无数据"}},F.prototype.render=function(){var e=this,a=e.config;if(a.elem=t(a.elem),a.where=a.where||{},a.id=a.id||a.elem.attr("id")||e.index,a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",totalRowName:"totalRow",countName:"count"},a.response),"object"==typeof a.page&&(a.limit=a.page.limit||a.limit,a.limits=a.page.limits||a.limits,e.page=a.page.curr=a.page.curr||1,delete a.page.elem,delete a.page.jump),!a.elem[0])return e;a.height&&/^full-\d+$/.test(a.height)&&(e.fullHeightGap=a.height.split("-")[1],a.height=H.height()-e.fullHeightGap),e.setInit();var l=a.elem,n=l.next("."+v),o=e.elem=t(i(z).render({VIEW_CLASS:v,data:a,index:e.index}));if(a.index=e.index,e.key=a.id||a.index,n[0]&&n.remove(),l.after(o),e.layTool=o.find(m),e.layBox=o.find(g),e.layHeader=o.find(x),e.layMain=o.find(C),e.layBody=o.find(k),e.layFixed=o.find(w),e.layFixLeft=o.find(T),e.layFixRight=o.find(A),e.layTotal=o.find(L),e.layPage=o.find(N),e.renderToolbar(),e.fullSize(),a.cols.length>1){var r=e.layFixed.find(x).find("th");r.height(e.layHeader.height()-1-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom")))}e.pullData(e.page),e.events()},F.prototype.initOpts=function(e){var t=this,i=(t.config,{checkbox:48,radio:48,space:15,numbers:40});e.checkbox&&(e.type="checkbox"),e.space&&(e.type="space"),e.type||(e.type="normal"),"normal"!==e.type&&(e.unresize=!0,e.width=e.width||i[e.type])},F.prototype.setInit=function(e){var t=this,i=t.config;return i.clientWidth=i.width||function(){var e=function(t){var a,l;t=t||i.elem.parent(),a=t.width();try{l="none"===t.css("display")}catch(n){}return!t[0]||a&&!l?a:e(t.parent())};return e()}(),"width"===e?i.clientWidth:void layui.each(i.cols,function(e,a){layui.each(a,function(l,n){if(!n)return void a.splice(l,1);if(n.key=e+"-"+l,n.hide=n.hide||!1,n.colGroup||n.colspan>1){var o=0;layui.each(i.cols[e+1],function(t,i){i.HAS_PARENT||o>1&&o==n.colspan||(i.HAS_PARENT=!0,i.parentKey=e+"-"+l,o+=parseInt(i.colspan>1?i.colspan:1))}),n.colGroup=!0}t.initOpts(n)})})},F.prototype.renderToolbar=function(){var e=this,a=e.config,l=['
          ','
          ','
          '].join(""),n=e.layTool.find(".layui-table-tool-temp");if("default"===a.toolbar)n.html(l);else if("string"==typeof a.toolbar){var o=t(a.toolbar).html()||"";o&&n.html(i(o).render(a))}var r={filter:{title:"筛选列",layEvent:"LAYTABLE_COLS",icon:"layui-icon-cols"},exports:{title:"导出",layEvent:"LAYTABLE_EXPORT",icon:"layui-icon-export"},print:{title:"打印",layEvent:"LAYTABLE_PRINT",icon:"layui-icon-print"}},d=[];"object"==typeof a.defaultToolbar&&layui.each(a.defaultToolbar,function(e,t){var i="string"==typeof t?r[t]:t;i&&d.push('
          ')}),e.layTool.find(".layui-table-tool-self").html(d.join(""))},F.prototype.setParentCol=function(e,t){var i=this,a=i.config,l=i.layHeader.find('th[data-key="'+a.index+"-"+t+'"]'),n=parseInt(l.attr("colspan"))||0;if(l[0]){var o=t.split("-"),r=a.cols[o[0]][o[1]];e?n--:n++,l.attr("colspan",n),l[n<1?"addClass":"removeClass"](f),r.colspan=n,r.hide=n<1;var d=l.data("parentkey");d&&i.setParentCol(e,d)}},F.prototype.setColsPatch=function(){var e=this,t=e.config;layui.each(t.cols,function(t,i){layui.each(i,function(t,i){i.hide&&e.setParentCol(i.hide,i.parentKey)})})},F.prototype.setColsWidth=function(){var e=this,t=e.config,i=0,a=0,l=0,n=0,o=e.setInit("width");e.eachCols(function(e,t){t.hide||i++}),o=o-function(){return"line"===t.skin||"nob"===t.skin?2:i+1}()-e.getScrollWidth(e.layMain[0])-1;var r=function(e){layui.each(t.cols,function(i,r){layui.each(r,function(i,d){var c=0,s=d.minWidth||t.cellMinWidth;return d?void(d.colGroup||d.hide||(e?l&&ln&&a&&(l=(o-n)/a)};r(),r(!0),e.autoColNums=a,e.eachCols(function(i,a){var n=a.minWidth||t.cellMinWidth;a.colGroup||a.hide||(0===a.width?e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(l>=n?l:n)+"px"}):/\d+%$/.test(a.width)&&e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(parseFloat(a.width)/100*o)+"px"}))});var d=e.layMain.width()-e.getScrollWidth(e.layMain[0])-e.layMain.children("table").outerWidth();if(e.autoColNums&&d>=-i&&d<=i){var c=function(t){var i;return t=t||e.layHeader.eq(0).find("thead th:last-child"),i=t.data("field"),!i&&t.prev()[0]?c(t.prev()):t},s=c(),u=s.data("key");e.getCssRule(u,function(t){var i=t.style.width||s.outerWidth();t.style.width=parseFloat(i)+d+"px",e.layMain.height()-e.layMain.prop("clientHeight")>0&&(t.style.width=parseFloat(t.style.width)-1+"px")})}e.loading(!0)},F.prototype.resize=function(){var e=this;e.fullSize(),e.setColsWidth(),e.scrollPatch()},F.prototype.reload=function(e){var i=this;e=e||{},delete i.haveInit,e.data&&e.data.constructor===Array&&delete i.config.data,i.config=t.extend(!0,{},i.config,e),i.render()},F.prototype.errorView=function(e){var i=this,a=i.layMain.find("."+p),l=t('
          '+(e||"Error")+"
          ");a[0]&&(i.layNone.remove(),a.remove()),i.layFixed.addClass(f),i.layMain.find("tbody").html(""),i.layMain.append(i.layNone=l),d.cache[i.key]=[]},F.prototype.page=1,F.prototype.pullData=function(e){var i=this,a=i.config,l=a.request,n=a.response,o=function(){"object"==typeof a.initSort&&i.sort(a.initSort.field,a.initSort.type)};if(i.startTime=(new Date).getTime(),a.url){var r={};r[l.pageName]=e,r[l.limitName]=a.limit;var d=t.extend(r,a.where);a.contentType&&0==a.contentType.indexOf("application/json")&&(d=JSON.stringify(d)),i.loading(),t.ajax({type:a.method||"get",url:a.url,contentType:a.contentType,data:d,dataType:"json",headers:a.headers||{},success:function(t){"function"==typeof a.parseData&&(t=a.parseData(t)||t),t[n.statusName]!=n.statusCode?(i.renderForm(),i.errorView(t[n.msgName]||'返回的数据不符合规范,正确的成功状态码应为:"'+n.statusName+'": '+n.statusCode)):(i.renderData(t,e,t[n.countName]),o(),a.time=(new Date).getTime()-i.startTime+" ms"),i.setColsWidth(),"function"==typeof a.done&&a.done(t,e,t[n.countName])},error:function(e,t){i.errorView("数据接口请求异常:"+t),i.renderForm(),i.setColsWidth()}})}else if(a.data&&a.data.constructor===Array){var c={},s=e*a.limit-a.limit;c[n.dataName]=a.data.concat().splice(s,a.limit),c[n.countName]=a.data.length,"object"==typeof a.totalRow&&(c[n.totalRowName]=t.extend({},a.totalRow)),i.renderData(c,e,c[n.countName]),o(),i.setColsWidth(),"function"==typeof a.done&&a.done(c,e,c[n.countName])}},F.prototype.eachCols=function(e){var t=this;return d.eachCols(null,e,t.config.cols),t},F.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,y=e[s.response.dataName]||[],h=e[s.response.totalRowName],v=[],m=[],g=[],b=function(){var e;return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(y,function(a,l){var o=[],y=[],h=[],p=a+s.limit*(n-1)+1;0!==l.length&&(r||(l[d.config.indexName]=a),c.eachCols(function(n,r){var c=r.field||n,v=s.index+"-"+r.key,m=l[c];if(void 0!==m&&null!==m||(m=""),!r.colGroup){var g=['','
          '+function(){var n=t.extend(!0,{LAY_INDEX:p},l),o=d.config.checkName;switch(r.type){case"checkbox":return'";case"radio":return n[o]&&(e=a),'';case"numbers":return p}return r.toolbar?i(t(r.toolbar).html()||"").render(n):u(r,m,n)}(),"
          "].join("");o.push(g),r.fixed&&"right"!==r.fixed&&y.push(g),"right"===r.fixed&&h.push(g)}}),v.push(''+o.join("")+""),m.push(''+y.join("")+""),g.push(''+h.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+p).remove(),c.layMain.find("tbody").html(v.join("")),c.layFixLeft.find("tbody").html(m.join("")),c.layFixRight.find("tbody").html(g.join("")),c.renderForm(),"number"==typeof e&&c.setThisRowChecked(e),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,l.close(c.tipsIndex),s.HAS_SET_COLS_PATCH||c.setColsPatch(),void(s.HAS_SET_COLS_PATCH=!0))};return d.cache[c.key]=y,c.layPage[0==o||0===y.length&&1==n?"addClass":"removeClass"](f),r?b():0===y.length?(c.renderForm(),c.errorView(s.text.none)):(c.layFixed.removeClass(f),b(),c.renderTotal(y,h),void(s.page&&(s.page=t.extend({elem:"layui-table-page"+s.index,count:o,limit:s.limit,limits:s.limits||[10,20,30,40,50,60,70,80,90],groups:3,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.pullData(e.curr))}},s.page),s.page.count=o,a.render(s.page))))},F.prototype.renderTotal=function(e,t){var i=this,a=i.config,l={};if(a.totalRow){layui.each(e,function(e,t){0!==t.length&&i.eachCols(function(e,i){var a=i.field||e,n=t[a];i.totalRow&&(l[a]=(l[a]||0)+(parseFloat(n)||0))})}),i.dataTotal={};var n=[];i.eachCols(function(e,o){var r=o.field||e,d=function(){var e=o.totalRowText||"",i=parseFloat(l[r]).toFixed(2),a={};return a[r]=i,i=u(o,i,a),t?t[o.field]||e:o.totalRow?i||e:e}(),c=['','
          '+d,"
          "].join("");o.field&&(i.dataTotal[r]=d),n.push(c)}),i.layTotal.find("tbody").html(""+n.join("")+"")}},F.prototype.getColElem=function(e,t){var i=this,a=i.config;return e.eq(0).find(".laytable-cell-"+(a.index+"-"+t)+":eq(0)")},F.prototype.renderForm=function(e){n.render(e,"LAY-table-"+this.index)},F.prototype.setThisRowChecked=function(e){var t=this,i=(t.config,"layui-table-click"),a=t.layBody.find('tr[data-index="'+e+'"]');a.addClass(i).siblings("tr").removeClass(i)},F.prototype.sort=function(e,i,a,l){var n,r,c=this,s={},u=c.config,h=u.elem.attr("lay-filter"),f=d.cache[c.key];"string"==typeof e&&c.layHeader.find("th").each(function(i,a){var l=t(this),o=l.data("field");if(o===e)return e=l,n=o,!1});try{var n=n||e.data("field"),p=e.data("key");if(c.sortKey&&!a&&n===c.sortKey.field&&i===c.sortKey.sort)return;var v=c.layHeader.find("th .laytable-cell-"+p).find(S);c.layHeader.find("th").find(S).removeAttr("lay-sort"),v.attr("lay-sort",i||null),c.layFixed.find("th")}catch(m){return o.error("Table modules: Did not match to field")}c.sortKey={field:n,sort:i},u.autoSort&&("asc"===i?r=layui.sort(f,n):"desc"===i?r=layui.sort(f,n,!0):(r=layui.sort(f,d.config.indexName),delete c.sortKey)),s[u.response.dataName]=r||f,c.renderData(s,c.page,c.count,!0),l&&layui.event.call(e,y,"sort("+h+")",{field:n,type:i})},F.prototype.loading=function(e){var i=this,a=i.config;a.loading&&(e?(i.layInit&&i.layInit.remove(),delete i.layInit,i.layBox.find(b).remove()):(i.layInit=t(['
          ','',"
          "].join("")),i.layBox.append(i.layInit)))},F.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&l[e].constructor!==Array&&(l[e][a.checkName]=t)},F.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){"checkbox"===a.type&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm("checkbox")),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm("checkbox")),a(!1)))},F.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet||{},n=l.cssRules||l.rules;layui.each(n,function(i,a){if(a.selectorText===".laytable-cell-"+e)return t(a),!0})},F.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=H.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),a&&(e=parseFloat(a)-(t.layHeader.outerHeight()||38),i.toolbar&&(e-=t.layTool.outerHeight()||50),i.totalRow&&(e-=t.layTotal.outerHeight()||40),i.page&&(e-=t.layPage.outerHeight()||41),t.layMain.css("height",e-2))},F.prototype.getScrollWidth=function(e){var t=0;return e?t=e.offsetWidth-e.clientWidth:(e=document.createElement("div"),e.style.width="100px",e.style.height="100px",e.style.overflowY="scroll",document.body.appendChild(e),t=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),t},F.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight"),n=(e.getScrollWidth(e.layMain[0]),i.outerWidth()-e.layMain.width()),o=function(e){if(a&&l){if(e=e.eq(0),!e.find(".layui-table-patch")[0]){var i=t('
          ');i.find("div").css({width:a}),e.find("tr").append(i)}}else e.find(".layui-table-patch").remove()};o(e.layHeader),o(e.layTotal);var r=e.layMain.height(),d=r-l;e.layFixed.find(k).css("height",i.height()>=d?d:"auto"),e.layFixRight[n>0?"removeClass":"addClass"](f),e.layFixRight.css("right",a-1)},F.prototype.events=function(){var e,i=this,a=i.config,o=t("body"),c={},s=i.layHeader.find("th"),h=".layui-table-cell",p=a.elem.attr("lay-filter");i.layTool.on("click","*[lay-event]",function(e){var o=t(this),c=o.attr("lay-event"),s=function(e){var l=t(e.list),n=t('
            ');n.html(l),a.height&&n.css("max-height",a.height-(i.layTool.outerHeight()||50)),o.find(".layui-table-tool-panel")[0]||o.append(n),i.renderForm(),n.on("click",function(e){layui.stope(e)}),e.done&&e.done(n,l)};switch(layui.stope(e),j.trigger("table.tool.panel.remove"),l.close(i.tipsIndex),c){case"LAYTABLE_COLS":s({list:function(){var e=[];return i.eachCols(function(t,i){i.field&&"normal"==i.type&&e.push('
          • ')}),e.join("")}(),done:function(){n.on("checkbox(LAY_TABLE_TOOL_COLS)",function(e){var l=t(e.elem),n=this.checked,o=l.data("key"),r=l.data("parentkey");layui.each(a.cols,function(e,t){layui.each(t,function(t,l){if(e+"-"+t===o){var d=l.hide;l.hide=!n,i.elem.find('*[data-key="'+a.index+"-"+o+'"]')[n?"removeClass":"addClass"](f),d!=l.hide&&i.setParentCol(!n,r),i.resize()}})})})}});break;case"LAYTABLE_EXPORT":r.ie?l.tips("导出功能不支持 IE,请用 Chrome 等高级浏览器导出",this,{tips:3}):s({list:function(){return['
          • 导出到 Csv 文件
          • ','
          • 导出到 Excel 文件
          • '].join("")}(),done:function(e,l){l.on("click",function(){var e=t(this).data("type");d.exportFile.call(i,a.id,null,e)})}});break;case"LAYTABLE_PRINT":var u=window.open("打印窗口","_blank"),h=[""].join(""),v=t(i.layHeader.html());v.append(i.layMain.find("table").html()),v.append(i.layTotal.find("table").html()),v.find("th.layui-table-patch").remove(),v.find(".layui-table-col-special").remove(),u.document.write(h+v.prop("outerHTML")),u.document.close(),u.print(),u.close()}layui.event.call(this,y,"toolbar("+p+")",t.extend({event:c,config:a},{}))}),s.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.data("unresize")||c.resizeStart||(c.allowResize=i.width()-l<=10,o.css("cursor",c.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);c.resizeStart||o.css("cursor","")}).on("mousedown",function(e){var l=t(this);if(c.allowResize){var n=l.data("key");e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],i.getCssRule(n,function(e){var t=e.style.width||l.outerWidth();c.rule=e,c.ruleWidth=parseFloat(t),c.minWidth=l.data("minwidth")||a.cellMinWidth})}}),j.on("mousemove",function(t){if(c.resizeStart){if(t.preventDefault(),c.rule){var a=c.ruleWidth+t.clientX-c.offset[0];a');return n[0].value=i.data("content")||l.text(),i.find("."+R)[0]||i.append(n),n.focus(),void layui.stope(e)}}).on("mouseenter","td",function(){b.call(this)}).on("mouseleave","td",function(){b.call(this,"hide")});var g="layui-table-grid-down",b=function(e){var i=t(this),a=i.children(h);if(!i.data("off"))if(e)i.find(".layui-table-grid-down").remove();else if(a.prop("scrollWidth")>a.outerWidth()){if(a.find("."+g)[0])return;i.append('
            ')}};i.layBody.on("click","."+g,function(e){var n=t(this),o=n.parent(),d=o.children(h);i.tipsIndex=l.tips(['
            ',d.html(),"
            ",''].join(""),d[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:i.elem.width()/2,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}}),layui.stope(e)}),i.layBody.on("click","*[lay-event]",function(){var e=t(this),a=e.parents("tr").eq(0).data("index");layui.event.call(this,y,"tool("+p+")",v.call(this,{event:e.attr("lay-event")})),i.setThisRowChecked(a)}),i.layMain.on("scroll",function(){var e=t(this),a=e.scrollLeft(),n=e.scrollTop();i.layHeader.scrollLeft(a),i.layTotal.scrollLeft(a),i.layFixed.find(k).scrollTop(n),l.close(i.tipsIndex)}),H.on("resize",function(){i.resize()})},function(){j.on("click",function(){j.trigger("table.remove.tool.panel")}),j.on("table.remove.tool.panel",function(){t(".layui-table-tool-panel").remove()})}(),d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':h+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},c.that={},c.config={},d.eachCols=function(e,i,a){var l=c.config[e]||{},n=[],o=0;a=t.extend(!0,[],a||l.cols),layui.each(a,function(e,t){layui.each(t,function(t,i){if(i.colGroup){var l=0;o++,i.CHILD_COLS=[],layui.each(a[e+1],function(e,t){t.PARENT_COL_INDEX||l>1&&l==i.colspan||(t.PARENT_COL_INDEX=o,i.CHILD_COLS.push(t),l+=parseInt(t.colspan>1?t.colspan:1))})}i.PARENT_COL_INDEX||n.push(i)})});var r=function(e){layui.each(e||n,function(e,t){return t.CHILD_COLS?r(t.CHILD_COLS):void("function"==typeof i&&i(e,t))})};r()},d.checkStatus=function(e){var t=0,i=0,a=[],l=d.cache[e]||[];return layui.each(l,function(e,l){return l.constructor===Array?void i++:void(l[d.config.checkName]&&(t++,a.push(d.clearCacheKey(l))))}),{data:a,isAll:!!l.length&&t===l.length-i}},d.exportFile=function(e,t,i){var a=this;t=t||d.clearCacheKey(d.cache[e]),i=i||"csv";var l=c.config[e]||{},n={csv:"text/csv",xls:"application/vnd.ms-excel"}[i],s=document.createElement("a");return r.ie?o.error("IE_NOT_SUPPORT_EXPORTS"):(s.href="data:"+n+";charset=utf-8,\ufeff"+encodeURIComponent(function(){var i=[],l=[],n=[];return layui.each(t,function(t,a){var n=[];"object"==typeof e?(layui.each(e,function(e,a){0==t&&i.push(a||"")}),layui.each(d.clearCacheKey(a),function(e,t){n.push('"'+(t||"")+'"')})):d.eachCols(e,function(e,l){if(l.field&&"normal"==l.type&&!l.hide){var o=a[l.field];void 0!==o&&null!==o||(o=""),0==t&&i.push(l.title||""),n.push('"'+u(l,o,a,"text")+'"')}}),l.push(n.join(","))}),layui.each(a.dataTotal,function(e,t){n.push(t)}),i.join(",")+"\r\n"+l.join("\r\n")+"\r\n"+n.join(",")}()),s.download=(l.title||"table_"+(l.index||""))+"."+i,document.body.appendChild(s),s.click(),void document.body.removeChild(s))},d.resize=function(e){if(e){var t=s(e);if(!t)return;c.that[e].resize()}else layui.each(c.that,function(){this.resize()})},d.reload=function(e,t){var i=s(e);if(i){var a=c.that[e];return a.reload(t),c.call(a)}},d.render=function(e){var t=new F(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(y,d)});layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",l=">*[carousel-item]>*",o="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(clearInterval(e.timer),e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
              ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
            "].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a",u=1;u<=i.length;u++){var r='
          • ";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'
          • ":n+=r}n+=""+(i.text?''+i.value+"星":"")+"";var c=i.elem,f=c.next("."+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next("span"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass("layui-inline"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find("i").width();l.children("li").each(function(e){var t=e+1,v=a(this);v.on("click",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next("span").text(i.value+"星"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on("mousemove",function(e){if(l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+t+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children("i").addClass(u).removeClass(s)}}),v.on("mouseleave",function(){l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+Math.floor(i.value)+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children("li:eq("+Math.floor(i.value)+")").children("i").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)});layui.define("jquery",function(e){"use strict";var t=layui.$,i={fixbar:function(e){var i,n,a="layui-fixbar",o="layui-fixbar-top",r=t(document),l=t("body");e=t.extend({showHeight:200},e),e.bar1=e.bar1===!0?"":e.bar1,e.bar2=e.bar2===!0?"":e.bar2,e.bgcolor=e.bgcolor?"background-color:"+e.bgcolor:"";var c=[e.bar1,e.bar2,""],u=t(['
              ',e.bar1?'
            • '+c[0]+"
            • ":"",e.bar2?'
            • '+c[1]+"
            • ":"",'
            • '+c[2]+"
            • ","
            "].join("")),g=u.find("."+o),s=function(){var t=r.scrollTop();t>=e.showHeight?i||(g.show(),i=1):i&&(g.hide(),i=0)};t("."+a)[0]||("object"==typeof e.css&&u.css(e.css),l.append(u),s(),u.find("li").on("click",function(){var i=t(this),n=i.attr("lay-type");"top"===n&&t("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,n)}),r.on("scroll",function(){clearTimeout(n),n=setTimeout(function(){s()},100)}))},countdown:function(e,t,i){var n=this,a="function"==typeof t,o=new Date(e).getTime(),r=new Date(!t||a?(new Date).getTime():t).getTime(),l=o-r,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];a&&(i=t);var u=setTimeout(function(){n.countdown(e,r+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],t,u),l<=0&&clearTimeout(u),u},timeAgo:function(e,t){var i=this,n=[[],[]],a=(new Date).getTime()-new Date(e).getTime();return a>26784e5?(a=new Date(e),n[0][0]=i.digit(a.getFullYear(),4),n[0][1]=i.digit(a.getMonth()+1),n[0][2]=i.digit(a.getDate()),t||(n[1][0]=i.digit(a.getHours()),n[1][1]=i.digit(a.getMinutes()),n[1][2]=i.digit(a.getSeconds())),n[0].join("-")+" "+n[1].join(":")):a>=864e5?(a/1e3/60/60/24|0)+"天前":a>=36e5?(a/1e3/60/60|0)+"小时前":a>=18e4?(a/1e3/60|0)+"分钟前":a<0?"未来":"刚刚"},digit:function(e,t){var i="";e=String(e),t=t||2;for(var n=e.length;n/g,">").replace(/'/g,"'").replace(/"/g,""")},event:function(e,n,a){var o=t("body");return a=a||"click",n=i.event[e]=t.extend(!0,i.event[e],n)||{},i.event.UTIL_EVENT_CALLBACK=i.event.UTIL_EVENT_CALLBACK||{},o.off(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),i.event.UTIL_EVENT_CALLBACK[e]=function(){var i=t(this),a=i.attr(e);"function"==typeof n[a]&&n[a].call(this,i)},o.on(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),n}};!function(e,t,i){"$:nomunge";function n(){a=t[l](function(){o.each(function(){var t=e(this),i=t.width(),n=t.height(),a=e.data(this,u);(i!==a.w||n!==a.h)&&t.trigger(c,[a.w=i,a.h=n])}),n()},r[g])}var a,o=e([]),r=e.resize=e.extend(e.resize,{}),l="setTimeout",c="resize",u=c+"-special-event",g="delay",s="throttleWindow";r[g]=250,r[s]=!0,e.event.special[c]={setup:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.add(t),e.data(this,u,{w:t.width(),h:t.height()}),1===o.length&&n()},teardown:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.not(t),t.removeData(u),o.length||clearTimeout(a)},add:function(t){function n(t,n,o){var r=e(this),l=e.data(this,u)||{};l.w=n!==i?n:r.width(),l.h=o!==i?o:r.height(),a.apply(this,arguments)}if(!r[s]&&this[l])return!1;var a;return e.isFunction(t)?(a=t,n):(a=t.handler,void(t.handler=n))}}}(t,window),e("util",i)});layui.define("jquery",function(e){"use strict";var l=layui.$,o=function(e){},t='';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),!i&&f.width()&&(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)});layui.define(["layer","form"],function(t){"use strict";var e=layui.$,i=layui.layer,a=layui.form,l=(layui.hint(),layui.device()),n="layedit",o="layui-show",r="layui-disabled",c=function(){var t=this;t.index=0,t.config={tool:["strong","italic","underline","del","|","left","center","right","|","link","unlink","face","image"],hideTool:[],height:280}};c.prototype.set=function(t){var i=this;return e.extend(!0,i.config,t),i},c.prototype.on=function(t,e){return layui.onevent(n,t,e)},c.prototype.build=function(t,i){i=i||{};var a=this,n=a.config,r="layui-layedit",c=e("string"==typeof t?"#"+t:t),u="LAY_layedit_"+ ++a.index,d=c.next("."+r),y=e.extend({},n,i),f=function(){var t=[],e={};return layui.each(y.hideTool,function(t,i){e[i]=!0}),layui.each(y.tool,function(i,a){C[a]&&!e[a]&&t.push(C[a])}),t.join("")}(),m=e(['
            ','
            '+f+"
            ",'
            ','',"
            ","
            "].join(""));return l.ie&&l.ie<8?c.removeClass("layui-hide").addClass(o):(d[0]&&d.remove(),s.call(a,m,c[0],y),c.addClass("layui-hide").after(m),a.index)},c.prototype.getContent=function(t){var e=u(t);if(e[0])return d(e[0].document.body.innerHTML)},c.prototype.getText=function(t){var i=u(t);if(i[0])return e(i[0].document.body).text()},c.prototype.setContent=function(t,i,a){var l=u(t);l[0]&&(a?e(l[0].document.body).append(i):e(l[0].document.body).html(i),layedit.sync(t))},c.prototype.sync=function(t){var i=u(t);if(i[0]){var a=e("#"+i[1].attr("textarea"));a.val(d(i[0].document.body.innerHTML))}},c.prototype.getSelection=function(t){var e=u(t);if(e[0]){var i=m(e[0].document);return document.selection?i.text:i.toString()}};var s=function(t,i,a){var l=this,n=t.find("iframe");n.css({height:a.height}).on("load",function(){var o=n.contents(),r=n.prop("contentWindow"),c=o.find("head"),s=e([""].join("")),u=o.find("body");c.append(s),u.attr("contenteditable","true").css({"min-height":a.height}).html(i.value||""),y.apply(l,[r,n,i,a]),g.call(l,r,t,a)})},u=function(t){var i=e("#LAY_layedit_"+t),a=i.prop("contentWindow");return[a,i]},d=function(t){return 8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),t},y=function(t,a,n,o){var r=t.document,c=e(r.body);c.on("keydown",function(t){var e=t.keyCode;if(13===e){var a=m(r),l=p(a),n=l.parentNode;if("pre"===n.tagName.toLowerCase()){if(t.shiftKey)return;return i.msg("请暂时用shift+enter"),!1}r.execCommand("formatBlock",!1,"

            ")}}),e(n).parents("form").on("submit",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on("paste",function(e){r.execCommand("formatBlock",!1,"

            "),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find("*[style]").each(function(){var t=this.style.textAlign;this.removeAttribute("style"),e(this).css({"text-align":t||""})}),t.find("table").addClass("layui-table"),t.find("script,link").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute("text"),l.selection){var r=a.text||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop("outerHTML")),a.select()}else{var r=a.toString()||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l="layedit-tool-active",n=p(m(a)),o=function(e){return t.find(".layedit-tool-"+e)};i&&i[i.hasClass(l)?"removeClass":"addClass"](l),t.find(">i").removeClass(l),o("unlink").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;"b"!==t&&"strong"!==t||o("b").addClass(l),"i"!==t&&"em"!==t||o("i").addClass(l),"u"===t&&o("u").addClass(l),"strike"===t&&o("d").addClass(l),"p"===t&&("center"===e?o("center").addClass(l):"right"===e?o("right").addClass(l):o("left").addClass(l)),"a"===t&&(o("link").addClass(l),o("unlink").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr("href"),target:l.attr("target")},function(e){var a=l[0];"A"===a.tagName?a.href=e.url:v.call(t,"a",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand("unlink")},face:function(e){x.call(this,function(i){v.call(t,"img",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use("upload",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find("input")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,"img",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||"上传失败")}})})},code:function(e){k.call(o,function(i){v.call(t,"pre",{text:i.code,"lay-lang":i.lang},e)})},help:function(){i.open({type:2,title:"帮助",area:["600px","380px"],shadeClose:!0,shade:.1,skin:"layui-layer-msg",content:["http://www.layui.com/about/layedit/help.html","no"]})}},s=a.find(".layui-layedit-tool"),u=function(){var i=e(this),a=i.attr("layedit-event"),l=i.attr("lay-command");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand("formatBlock",!1,"

            "),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(">i").on("mousedown",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)||u.call(this)}).on("click",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)&&u.call(this)}),o.on("click",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:"LAY_layedit_link",area:"350px",shade:.05,shadeClose:!0,moveType:1,title:"超链接",skin:"layui-layer-msg",content:['

              ','
            • ','','
              ','',"
              ","
            • ",'
            • ','','
              ','",'","
              ","
            • ",'
            • ','','',"
            • ","
            "].join(""),success:function(t,n){var o="submit(layedit-link-yes)";a.render("radio"),t.find(".layui-btn-primary").on("click",function(){i.close(n),l.focus()}),a.on(o,function(t){i.close(b.index),e&&e(t.field)})}});b.index=n},x=function(t){var a=function(){var t=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],e={};return layui.each(t,function(t,i){e[i]=layui.cache.dir+"images/face/"+t+".gif"}),e}();return x.hide=x.hide||function(t){"face"!==e(t.target).attr("layedit-event")&&i.close(x.index)},x.index=i.tips(function(){var t=[];return layui.each(a,function(e,i){t.push('
          • '+e+'
          • ')}),'
              '+t.join("")+"
            "}(),this,{tips:1,time:0,skin:"layui-box layui-util-face",maxWidth:500,success:function(l,n){l.css({marginTop:-4,marginLeft:-10}).find(".layui-clear>li").on("click",function(){t&&t({src:a[this.title],alt:this.title}),i.close(n)}),e(document).off("click",x.hide).on("click",x.hide)}})},k=function(t){var e=this,l=i.open({type:1,id:"LAY_layedit_code",area:"550px",shade:.05,shadeClose:!0,moveType:1,title:"插入代码",skin:"layui-layer-msg",content:['
              ','
            • ','','
              ','","
              ","
            • ",'
            • ','','
              ','',"
              ","
            • ",'
            • ','','',"
            • ","
            "].join(""),success:function(l,n){var o="submit(layedit-code-yes)";a.render("select"),l.find(".layui-btn-primary").on("click",function(){i.close(n),e.focus()}),a.on(o,function(e){i.close(k.index),t&&t(e.field)})}});k.index=l},C={html:'',strong:'',italic:'',underline:'',del:'',"|":'',left:'',center:'',right:'',link:'',unlink:'',face:'',image:'',code:'',help:''},w=new c;t(n,w)});layui.define("jquery",function(e){"use strict";var a=layui.$,l="http://www.layui.com/doc/modules/code.html";e("code",function(e){var t=[];e=e||{},e.elem=a(e.elem||".layui-code"),e.about=!("about"in e)||e.about,e.elem.each(function(){t.push(this)}),layui.each(t.reverse(),function(t,i){var c=a(i),o=c.html();(c.attr("lay-encode")||e.encode)&&(o=o.replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
            1. '+o.replace(/[\r\t\n]+/g,"
            2. ")+"
            "),c.find(">.layui-code-h3")[0]||c.prepend('

            '+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

            ");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); \ No newline at end of file diff --git a/public/lib/layui/layui.js b/public/lib/layui/layui.js new file mode 100644 index 0000000..99dba90 --- /dev/null +++ b/public/lib/layui/layui.js @@ -0,0 +1,2 @@ +/** layui-v2.5.6 MIT License By https://www.layui.com */ + ;!function(e){"use strict";var t=document,n={modules:{},status:{},timeout:10,event:{}},r=function(){this.v="2.5.6"},o=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,n=t.scripts,r=n.length-1,o=r;o>0;o--)if("interactive"===n[o].readyState){e=n[o].src;break}return e||n[r].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),a=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},i="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",transfer:"modules/transfer",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};r.prototype.cache=n,r.prototype.define=function(e,t){var r=this,o="function"==typeof e,a=function(){var e=function(e,t){layui[e]=t,n.status[e]=!0};return"function"==typeof t&&t(function(r,o){e(r,o),n.callback[r]=function(){t(e)}}),this};return o&&(t=e,e=[]),!layui["layui.all"]&&layui["layui.mobile"]?a.call(r):(r.use(e,a),r)},r.prototype.use=function(e,r,l){function c(e,t){var r="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||r.test((e.currentTarget||e.srcElement).readyState))&&(n.modules[d]=t,y.removeChild(h),function o(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void(n.status[d]?s():setTimeout(o,4))}())}function s(){l.push(layui[d]),e.length>1?p.use(e.slice(1),r,l):"function"==typeof r&&r.apply(layui,l)}var p=this,f=n.dir=n.dir?n.dir:o,y=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(p.each(e,function(t,n){"jquery"===n&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var d=e[0],m=0;if(l=l||[],n.host=n.host||(f.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[d]||!layui["layui.all"]&&layui["layui.mobile"]&&u[d])return s(),p;var v=(u[d]?f+"lay/":/^\{\/\}/.test(p.modules[d])?"":n.base||"")+(p.modules[d]||d)+".js";if(v=v.replace(/^\{\/\}/,""),!n.modules[d]&&layui[d]&&(n.modules[d]=v),n.modules[d])!function g(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void("string"==typeof n.modules[d]&&n.status[d]?s():setTimeout(g,4))}();else{var h=t.createElement("script");h.async=!0,h.charset="utf-8",h.src=v+function(){var e=n.version===!0?n.v||(new Date).getTime():n.version||"";return e?"?v="+e:""}(),y.appendChild(h),!h.attachEvent||h.attachEvent.toString&&h.attachEvent.toString().indexOf("[native code")<0||i?h.addEventListener("load",function(e){c(e,v)},!1):h.attachEvent("onreadystatechange",function(e){c(e,v)}),n.modules[d]=v}return p},r.prototype.getStyle=function(t,n){var r=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return r[r.getPropertyValue?"getPropertyValue":"getAttribute"](n)},r.prototype.link=function(e,r,o){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof r&&(o=r);var c=(o||e).replace(/\.|\//g,""),s=u.id="layuicss-"+c,p=0;return u.rel="stylesheet",u.href=e+(n.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(s)||l.appendChild(u),"function"!=typeof r?i:(function f(){return++p>1e3*n.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(s),"width"))?function(){r()}():setTimeout(f,100))}(),i)},n.callback={},r.prototype.factory=function(e){if(layui[e])return"function"==typeof n.callback[e]?n.callback[e]:null},r.prototype.addcss=function(e,t,r){return layui.link(n.dir+"css/"+e,t,r)},r.prototype.img=function(e,t,n){var r=new Image;return r.src=e,r.complete?t(r):(r.onload=function(){r.onload=null,"function"==typeof t&&t(r)},void(r.onerror=function(e){r.onerror=null,"function"==typeof n&&n(e)}))},r.prototype.config=function(e){e=e||{};for(var t in e)n[t]=e[t];return this},r.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),r.prototype.extend=function(e){var t=this;e=e||{};for(var n in e)t[n]||t.modules[n]?a("模块名 "+n+" 已被占用"):t.modules[n]=e[n];return t},r.prototype.router=function(e){var t=this,e=e||location.hash,n={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),n.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),n.search[t[0]]=t[1]}():n.path.push(t)}),n):n},r.prototype.url=function(e){var t=this,n={pathname:function(){var t=e?function(){var t=(e.match(/\.[^.]+?\/.+/)||[])[0]||"";return t.replace(/^[^\/]+/,"").replace(/\?.+/,"")}():location.pathname;return t.replace(/^\//,"").split("/")}(),search:function(){var n={},r=(e?function(){var t=(e.match(/\?.+/)||[])[0]||"";return t.replace(/\#.+/,"")}():location.search).replace(/^\?+/,"").split("&");return t.each(r,function(e,t){var r=t.indexOf("="),o=function(){return r<0?t.substr(0,t.length):0!==r&&t.substr(0,r)}();o&&(n[o]=r>0?t.substr(r+1):null)}),n}(),hash:t.router(function(){return e?(e.match(/#.+/)||[])[0]||"":location.hash}())};return n},r.prototype.data=function(t,n,r){if(t=t||"layui",r=r||localStorage,e.JSON&&e.JSON.parse){if(null===n)return delete r[t];n="object"==typeof n?n:{key:n};try{var o=JSON.parse(r[t])}catch(a){var o={}}return"value"in n&&(o[n.key]=n.value),n.remove&&delete o[n.key],r[t]=JSON.stringify(o),n.key?o[n.key]:o}},r.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},r.prototype.device=function(t){var n=navigator.userAgent.toLowerCase(),r=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(n.match(t)||[])[1],e||!1},o={os:function(){return/windows/.test(n)?"windows":/linux/.test(n)?"linux":/iphone|ipod|ipad|ios/.test(n)?"ios":/mac/.test(n)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((n.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:r("micromessenger")};return t&&!o[t]&&(o[t]=r(t)),o.android=/android/.test(n),o.ios="ios"===o.os,o.mobile=!(!o.android&&!o.ios),o},r.prototype.hint=function(){return{error:a}},r.prototype.each=function(e,t){var n,r=this;if("function"!=typeof t)return r;if(e=e||[],e.constructor===Object){for(n in e)if(t.call(e[n],n,e[n]))break}else for(n=0;na?1:o +// +---------------------------------------------------------------------- +// $Id$ + +if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { + return false; +} else { + if (!isset($_SERVER['PATH_INFO'])) { + $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI']; + } + require __DIR__ . "/index.php"; +} diff --git a/public/static/.gitignore b/public/static/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/public/static/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/think b/think new file mode 100644 index 0000000..4e2688f --- /dev/null +++ b/think @@ -0,0 +1,17 @@ +#!/usr/bin/env php + +// +---------------------------------------------------------------------- + +// 定义项目路径 +define('APP_PATH', __DIR__ . '/application/'); + +// 加载框架引导文件 +require __DIR__.'/thinkphp/console.php'; diff --git a/thinkphp/.gitignore b/thinkphp/.gitignore new file mode 100644 index 0000000..7e31ef5 --- /dev/null +++ b/thinkphp/.gitignore @@ -0,0 +1,4 @@ +/composer.lock +/vendor +.idea +.DS_Store diff --git a/thinkphp/.htaccess b/thinkphp/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/thinkphp/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/thinkphp/.travis.yml b/thinkphp/.travis.yml new file mode 100644 index 0000000..f74ffca --- /dev/null +++ b/thinkphp/.travis.yml @@ -0,0 +1,47 @@ +sudo: false + +language: php + +services: + - memcached + - mongodb + - mysql + - postgresql + - redis-server + +matrix: + fast_finish: true + include: + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: hhvm + allow_failures: + - php: hhvm + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + - mysql -e "create database IF NOT EXISTS test;" -uroot + - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres + - psql -c 'create database test;' -U postgres + +install: + - ./tests/script/install.sh + +script: + ## LINT + - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; + ## PHP Copy/Paste Detector + - vendor/bin/phpcpd --verbose --exclude vendor ./ || true + ## PHPLOC + - vendor/bin/phploc --exclude vendor ./ + ## PHPUNIT + - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/thinkphp/CONTRIBUTING.md b/thinkphp/CONTRIBUTING.md new file mode 100644 index 0000000..dc8e91c --- /dev/null +++ b/thinkphp/CONTRIBUTING.md @@ -0,0 +1,119 @@ +如何贡献我的源代码 +=== + +此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。 + +## 通过 Github 贡献代码 + +ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 + +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 + +我们希望你贡献的代码符合: + +* ThinkPHP 的编码规范 +* 适当的注释,能让其他人读懂 +* 遵循 Apache2 开源协议 + +**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容** + +### 注意事项 + +* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141); +* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144); +* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。 +* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范; +* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests); + +## GitHub Issue + +GitHub 提供了 Issue 功能,该功能可以用于: + +* 提出 bug +* 提出功能改进 +* 反馈使用体验 + +该功能不应该用于: + + * 提出修改意见(涉及代码署名和修订追溯问题) + * 不友善的言论 + +## 快速修改 + +**GitHub 提供了快速编辑文件的功能** + +1. 登录 GitHub 帐号; +2. 浏览项目文件,找到要进行修改的文件; +3. 点击右上角铅笔图标进行修改; +4. 填写 `Commit changes` 相关内容(Title 必填); +5. 提交修改,等待 CI 验证和管理员合并。 + +**若您需要一次提交大量修改,请继续阅读下面的内容** + +## 完整流程 + +1. `fork`本项目; +2. 克隆(`clone`)你 `fork` 的项目到本地; +3. 新建分支(`branch`)并检出(`checkout`)新分支; +4. 添加本项目到你的本地 git 仓库作为上游(`upstream`); +5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests); +6. 变基(衍合 `rebase`)你的分支到上游 master 分支; +7. `push` 你的本地仓库到 GitHub; +8. 提交 `pull request`; +9. 等待 CI 验证(若不通过则重复 5~7,不需要重新提交 `pull request`,GitHub 会自动更新你的 `pull request`); +10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 + +*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* + +*绝对不可以使用 `git push -f` 强行推送修改到上游* + +### 注意事项 + +* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/); +* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分); +* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/) + +## 推荐资源 + +### 开发环境 + +* XAMPP for Windows 5.5.x +* WampServer (for Windows) +* upupw Apache PHP5.4 ( for Windows) + +或自行安装 + +- Apache / Nginx +- PHP 5.4 ~ 5.6 +- MySQL / MariaDB + +*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer* + +*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB* + +### 编辑器 + +Sublime Text 3 + phpfmt 插件 + +phpfmt 插件参数 + +```json +{ + "autocomplete": true, + "enable_auto_align": true, + "format_on_save": true, + "indent_with_space": true, + "psr1_naming": false, + "psr2": true, + "version": 4 +} +``` + +或其他 编辑器 / IDE 配合 PSR2 自动格式化工具 + +### Git GUI + +* SourceTree +* GitHub Desktop + +或其他 Git 图形界面客户端 diff --git a/thinkphp/LICENSE.txt b/thinkphp/LICENSE.txt new file mode 100644 index 0000000..2cb9a8a --- /dev/null +++ b/thinkphp/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +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 THE +COPYRIGHT OWNER OR CONTRIBUTORS 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. diff --git a/thinkphp/README.md b/thinkphp/README.md new file mode 100644 index 0000000..f01fd2b --- /dev/null +++ b/thinkphp/README.md @@ -0,0 +1,114 @@ +ThinkPHP 5.0 +=============== + +[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) +[![codecov.io](http://codecov.io/github/top-think/framework/coverage.svg?branch=master)](http://codecov.io/github/github/top-think/framework?branch=master) +[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) +[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) +[![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括: + + + 基于命名空间和众多PHP新特性 + + 核心功能组件化 + + 强化路由功能 + + 更灵活的控制器 + + 重构的模型和数据库类 + + 配置文件可分离 + + 重写的自动验证和完成 + + 简化扩展机制 + + API支持完善 + + 改进的Log类 + + 命令行访问支持 + + REST支持 + + 引导文件支持 + + 方便的自动生成定义 + + 真正惰性加载 + + 分布式环境支持 + + 支持Composer + + 支持MongoDb + +> ThinkPHP5的运行环境要求PHP5.4以上。 + +详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) + +## 目录结构 + +初始的目录结构如下: + +~~~ +www WEB部署目录(或者子目录) +├─application 应用目录 +│ ├─common 公共模块目录(可以更改) +│ ├─module_name 模块目录 +│ │ ├─config.php 模块配置文件 +│ │ ├─common.php 模块函数文件 +│ │ ├─controller 控制器目录 +│ │ ├─model 模型目录 +│ │ ├─view 视图目录 +│ │ └─ ... 更多类库目录 +│ │ +│ ├─command.php 命令行工具配置文件 +│ ├─common.php 公共函数文件 +│ ├─config.php 公共配置文件 +│ ├─route.php 路由配置文件 +│ ├─tags.php 应用行为扩展定义文件 +│ └─database.php 数据库配置文件 +│ +├─public WEB目录(对外访问目录) +│ ├─index.php 入口文件 +│ ├─router.php 快速测试文件 +│ └─.htaccess 用于apache的重写 +│ +├─thinkphp 框架系统目录 +│ ├─lang 语言文件目录 +│ ├─library 框架类库目录 +│ │ ├─think Think类库包目录 +│ │ └─traits 系统Trait目录 +│ │ +│ ├─tpl 系统模板目录 +│ ├─base.php 基础定义文件 +│ ├─console.php 控制台入口文件 +│ ├─convention.php 框架惯例配置文件 +│ ├─helper.php 助手函数文件 +│ ├─phpunit.xml phpunit配置文件 +│ └─start.php 框架入口文件 +│ +├─extend 扩展类库目录 +├─runtime 应用的运行时目录(可写,可定制) +├─vendor 第三方类库目录(Composer依赖库) +├─build.php 自动生成定义文件(参考) +├─composer.json composer 定义文件 +├─LICENSE.txt 授权说明文件 +├─README.md README 文件 +├─think 命令行入口文件 +~~~ + +> router.php用于php自带webserver支持,可用于快速测试 +> 切换到public目录后,启动命令:php -S localhost:8888 router.php +> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 + +## 命名规范 + +ThinkPHP5的命名规范遵循`PSR-2`规范以及`PSR-4`自动加载规范。 + +## 参与开发 +注册并登录 Github 帐号, fork 本项目并进行改动。 + +更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md) + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/thinkphp/base.php b/thinkphp/base.php new file mode 100644 index 0000000..92c4fa5 --- /dev/null +++ b/thinkphp/base.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- + +define('THINK_VERSION', '5.0.24'); +define('THINK_START_TIME', microtime(true)); +define('THINK_START_MEM', memory_get_usage()); +define('EXT', '.php'); +define('DS', DIRECTORY_SEPARATOR); +defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); +define('LIB_PATH', THINK_PATH . 'library' . DS); +define('CORE_PATH', LIB_PATH . 'think' . DS); +define('TRAIT_PATH', LIB_PATH . 'traits' . DS); +defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); +defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); +defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); +defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); +defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); +defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); +defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); +defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); +defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录 +defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀 +defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀 + +// 环境常量 +define('IS_CLI', PHP_SAPI == 'cli' ? true : false); +define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); + +// 载入Loader类 +require CORE_PATH . 'Loader.php'; + +// 加载环境变量配置文件 +if (is_file(ROOT_PATH . '.env')) { + $env = parse_ini_file(ROOT_PATH . '.env', true); + + foreach ($env as $key => $val) { + $name = ENV_PREFIX . strtoupper($key); + + if (is_array($val)) { + foreach ($val as $k => $v) { + $item = $name . '_' . strtoupper($k); + putenv("$item=$v"); + } + } else { + putenv("$name=$val"); + } + } +} + +// 注册自动加载 +\think\Loader::register(); + +// 注册错误和异常处理机制 +\think\Error::register(); + +// 加载惯例配置文件 +\think\Config::set(include THINK_PATH . 'convention' . EXT); diff --git a/thinkphp/codecov.yml b/thinkphp/codecov.yml new file mode 100644 index 0000000..bef9d64 --- /dev/null +++ b/thinkphp/codecov.yml @@ -0,0 +1,12 @@ +comment: + layout: header, changes, diff +coverage: + ignore: + - base.php + - helper.php + - convention.php + - lang/zh-cn.php + - start.php + - console.php + status: + patch: false diff --git a/thinkphp/composer.json b/thinkphp/composer.json new file mode 100644 index 0000000..c546e11 --- /dev/null +++ b/thinkphp/composer.json @@ -0,0 +1,35 @@ +{ + "name": "topthink/framework", + "description": "the new thinkphp framework", + "type": "think-framework", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsStream": "~1.6", + "phploc/phploc": "2.*", + "sebastian/phpcpd": "2.*", + "phpdocumentor/reflection-docblock": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\": "library/think" + } + } +} diff --git a/thinkphp/console.php b/thinkphp/console.php new file mode 100644 index 0000000..578e4a7 --- /dev/null +++ b/thinkphp/console.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 加载基础文件 +require __DIR__ . '/base.php'; + +// 执行应用 +App::initCommon(); +Console::init(); diff --git a/thinkphp/convention.php b/thinkphp/convention.php new file mode 100644 index 0000000..31a0a0c --- /dev/null +++ b/thinkphp/convention.php @@ -0,0 +1,298 @@ + '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 扩展函数文件 + 'extra_file_list' => [THINK_PATH . 'helper' . EXT], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // HTTPS代理标识 + 'https_agent_name' => '', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由 + 'url_route_on' => true, + // 路由配置文件(支持配置多个) + 'route_config_file' => ['route'], + // 路由使用完整匹配 + 'route_complete_match' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 域名部署 + 'url_domain_deploy' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // 视图输出字符串内容替换 + 'view_replace_str' => [], + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 异常页面的模板文件 + 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + // 是否记录trace信息到日志 + 'record_trace' => false, + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + 'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => 'think', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + 'httponly' => true, + 'secure' => false, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + // +---------------------------------------------------------------------- + // | 数据库设置 + // +---------------------------------------------------------------------- + + 'database' => [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], + + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + ], + +]; diff --git a/thinkphp/helper.php b/thinkphp/helper.php new file mode 100644 index 0000000..12683cf --- /dev/null +++ b/thinkphp/helper.php @@ -0,0 +1,589 @@ + +// +---------------------------------------------------------------------- + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\Cache; +use think\Config; +use think\Cookie; +use think\Db; +use think\Debug; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\Lang; +use think\Loader; +use think\Log; +use think\Model; +use think\Request; +use think\Response; +use think\Session; +use think\Url; +use think\View; + +if (!function_exists('load_trait')) { + /** + * 快速导入Traits PHP5.5以上无需调用 + * @param string $class trait库 + * @param string $ext 类库后缀 + * @return boolean + */ + function load_trait($class, $ext = EXT) + { + return Loader::import($class, TRAIT_PATH, $ext); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 + * @return mixed + */ + function debug($start, $end = '', $dec = 6) + { + if ('' == $end) { + Debug::remark($start); + } else { + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @param string $range 作用域 + * @return mixed + */ + function config($name = '', $value = null, $range = '') + { + if (is_null($value) && is_string($name)) { + return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range); + } else { + return Config::set($name, $value, $range); + } + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + if ($pos = strpos($key, '.')) { + // 指定参数来源 + list($method, $key) = explode('.', $key, 2); + if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = $method . '.' . $key; + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + return Loader::action($name, $data, 'widget'); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return Loader::model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return Loader::validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('db')) { + /** + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = false) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return Loader::controller($name, $layer, $appendSuffix); + } +} + +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + return Loader::action($url, $vars, $layer, $appendSuffix); + } +} + +if (!function_exists('import')) { + /** + * 导入所需的类库 同java的Import 本函数有缓存功能 + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return boolean + */ + function import($class, $baseUrl = '', $ext = EXT) + { + return Loader::import($class, $baseUrl, $ext); + } +} + +if (!function_exists('vendor')) { + /** + * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 + * @param string $class 类库 + * @param string $ext 类库后缀 + * @return boolean + */ + function vendor($class, $ext = EXT) + { + return Loader::import($class, VENDOR_PATH, $ext); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear('' === $value ? null : $value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string|array $name cookie名称,如果为数组表示进行cookie设置 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie($name, $value = '', $option = null) + { + if (is_array($name)) { + // 初始化 + Cookie::init($name); + } elseif (is_null($name)) { + // 清除 + Cookie::clear($value); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); + } elseif (is_null($value)) { + // 删除 + return Cookie::delete($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + $cache = Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); + } else { + $cache = Cache::init(); + } + + if (is_null($name)) { + return $cache->clear($value); + } elseif ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); + } elseif (is_null($value)) { + // 删除缓存 + return $cache->rm($name); + } elseif (0 === strpos($name, '?') && '' !== $value) { + $expire = is_numeric($options) ? $options : null; + return $cache->remember(substr($name, 1), $value, $expire); + } else { + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + if (is_null($tag)) { + return $cache->set($name, $value, $expire); + } else { + return $cache->tag($tag)->set($name, $value, $expire); + } + } + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return void|array + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return Request::instance(); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = [], $code = 200, $header = [], $type = 'html') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $replace 模板替换 + * @param integer $code 状态码 + * @return \think\response\View + */ + function view($template = '', $vars = [], $replace = [], $code = 200) + { + return Response::create($template, 'view', $code)->replace($replace)->assign($vars); + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'json', $code, $header, $options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'jsonp', $code, $header, $options); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'xml', $code, $header, $options); + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @param array $with 隐式传参 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + return Response::create($url, 'redirect', $code)->params($params)->with($with); + } +} + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, $message = null, $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::instance()->token($name, $type); + return ''; + } +} + +if (!function_exists('load_relation')) { + /** + * 延迟预载入关联查询 + * @param mixed $resultSet 数据集 + * @param mixed $relation 关联 + * @return array + */ + function load_relation($resultSet, $relation) + { + $item = current($resultSet); + if ($item instanceof Model) { + $item->eagerlyResultSet($resultSet, $relation); + } + return $resultSet; + } +} + +if (!function_exists('collection')) { + /** + * 数组转换为数据集对象 + * @param array $resultSet 数据集数组 + * @return \think\model\Collection|\think\Collection + */ + function collection($resultSet) + { + $item = current($resultSet); + if ($item instanceof Model) { + return \think\model\Collection::make($resultSet); + } else { + return \think\Collection::make($resultSet); + } + } +} diff --git a/thinkphp/lang/zh-cn.php b/thinkphp/lang/zh-cn.php new file mode 100644 index 0000000..eb7a914 --- /dev/null +++ b/thinkphp/lang/zh-cn.php @@ -0,0 +1,136 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 系统错误提示 + 'Undefined variable' => '未定义变量', + 'Undefined index' => '未定义数组索引', + 'Undefined offset' => '未定义数组下标', + 'Parse error' => '语法解析错误', + 'Type error' => '类型错误', + 'Fatal error' => '致命错误', + 'syntax error' => '语法错误', + + // 框架核心错误提示 + 'dispatch type not support' => '不支持的调度类型', + 'method param miss' => '方法参数错误', + 'method not exists' => '方法不存在', + 'module not exists' => '模块不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Route Not Found' => '当前访问路由未定义', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + 'redisd master' => 'Redisd 主服务器错误', + 'redisd slave' => 'Redisd 从服务器错误', + 'must run at sae' => '必须在SAE运行', + 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务', + 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', + 'fields not exists' => '数据表字段不存在', + 'where express error' => '查询表达式错误', + 'not support data' => '不支持的数据表达式', + 'no data to update' => '没有任何数据需要更新', + 'miss data to insert' => '缺少需要写入的数据', + 'miss complex primary data' => '缺少复合主键数据', + 'miss update condition' => '缺少更新条件', + 'model data Not Found' => '模型数据不存在', + 'table data not Found' => '表数据不存在', + 'delete without condition' => '没有条件不会执行删除操作', + 'miss relation data' => '缺少关联表数据', + 'tag attr must' => '模板标签属性必须', + 'tag error' => '模板标签错误', + 'cache write error' => '缓存写入失败', + 'sae mc write error' => 'SAE mc 写入错误', + 'route name not exists' => '路由标识不存在(或参数不够)', + 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', + 'chunk not support order' => 'Chunk不支持调用order方法', + 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', + + // 上传错误信息 + 'unknown upload error' => '未知上传错误!', + 'file write error' => '文件写入失败!', + 'upload temp dir not found' => '找不到临时文件夹!', + 'no file to uploaded' => '没有文件被上传!', + 'only the portion of file is uploaded' => '文件只有部分被上传!', + 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', + 'upload write error' => '文件上传保存错误!', + 'has the same filename: {:filename}' => '存在同名文件:{:filename}', + 'upload illegal files' => '非法上传文件', + 'illegal image files' => '非法图片文件', + 'extensions to upload is not allowed' => '上传文件后缀不允许', + 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!', + 'filesize not match' => '上传文件大小不符!', + 'directory {:path} creation failed' => '目录 {:path} 创建失败!', + + // Validate Error Message + ':attribute require' => ':attribute不能为空', + ':attribute must be numeric' => ':attribute必须是数字', + ':attribute must be integer' => ':attribute必须是整数', + ':attribute must be float' => ':attribute必须是浮点数', + ':attribute must be bool' => ':attribute必须是布尔值', + ':attribute not a valid email address' => ':attribute格式不符', + ':attribute not a valid mobile' => ':attribute格式不符', + ':attribute must be a array' => ':attribute必须是数组', + ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1', + ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式', + ':attribute not a valid file' => ':attribute不是有效的上传文件', + ':attribute not a valid image' => ':attribute不是有效的图像文件', + ':attribute must be alpha' => ':attribute只能是字母', + ':attribute must be alpha-numeric' => ':attribute只能是字母和数字', + ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-', + ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP', + ':attribute must be chinese' => ':attribute只能是汉字', + ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母', + ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字', + ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-', + ':attribute not a valid url' => ':attribute不是有效的URL地址', + ':attribute not a valid ip' => ':attribute不是有效的IP地址', + ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule', + ':attribute must be in :rule' => ':attribute必须在 :rule 范围内', + ':attribute be notin :rule' => ':attribute不能在 :rule 范围内', + ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间', + ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间', + 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule', + 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule', + 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule', + ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule', + ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule', + ':attribute not within :rule' => '不在有效期内 :rule', + 'access IP is not allowed' => '不允许的IP访问', + 'access IP denied' => '禁止的IP访问', + ':attribute out of accord with :2' => ':attribute和确认字段:2不一致', + ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同', + ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule', + ':attribute must greater than :rule' => ':attribute必须大于 :rule', + ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule', + ':attribute must less than :rule' => ':attribute必须小于 :rule', + ':attribute must equal :rule' => ':attribute必须等于 :rule', + ':attribute has exists' => ':attribute已存在', + ':attribute not conform to the rules' => ':attribute不符合指定规则', + 'invalid Request method' => '无效的请求类型', + 'invalid token' => '令牌数据无效', + 'not conform to the rules' => '规则错误', +]; diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php new file mode 100644 index 0000000..f572b90 --- /dev/null +++ b/thinkphp/library/think/App.php @@ -0,0 +1,677 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\exception\RouteNotFoundException; + +/** + * App 应用管理 + * @author liu21st + */ +class App +{ + /** + * @var bool 是否初始化过 + */ + protected static $init = false; + + /** + * @var string 当前模块路径 + */ + public static $modulePath; + + /** + * @var bool 应用调试模式 + */ + public static $debug = true; + + /** + * @var string 应用类库命名空间 + */ + public static $namespace = 'app'; + + /** + * @var bool 应用类库后缀 + */ + public static $suffix = false; + + /** + * @var bool 应用路由检测 + */ + protected static $routeCheck; + + /** + * @var bool 严格路由检测 + */ + protected static $routeMust; + + /** + * @var array 请求调度分发 + */ + protected static $dispatch; + + /** + * @var array 额外加载文件 + */ + protected static $file = []; + + /** + * 执行应用程序 + * @access public + * @param Request $request 请求对象 + * @return Response + * @throws Exception + */ + public static function run(Request $request = null) + { + $request = is_null($request) ? Request::instance() : $request; + + try { + $config = self::initCommon(); + + // 模块/控制器绑定 + if (defined('BIND_MODULE')) { + BIND_MODULE && Route::bind(BIND_MODULE); + } elseif ($config['auto_bind_module']) { + // 入口自动绑定 + $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { + Route::bind($name); + } + } + + $request->filter($config['default_filter']); + + // 默认语言 + Lang::range($config['default_lang']); + // 开启多语言机制 检测当前语言 + $config['lang_switch_on'] && Lang::detect(); + $request->langset(Lang::range()); + + // 加载系统语言包 + Lang::load([ + THINK_PATH . 'lang' . DS . $request->langset() . EXT, + APP_PATH . 'lang' . DS . $request->langset() . EXT, + ]); + + // 监听 app_dispatch + Hook::listen('app_dispatch', self::$dispatch); + // 获取应用调度信息 + $dispatch = self::$dispatch; + + // 未设置调度信息则进行 URL 路由检测 + if (empty($dispatch)) { + $dispatch = self::routeCheck($request, $config); + } + + // 记录当前调度信息 + $request->dispatch($dispatch); + + // 记录路由和请求信息 + if (self::$debug) { + Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); + Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); + Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); + } + + // 监听 app_begin + Hook::listen('app_begin', $dispatch); + + // 请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + + $data = self::exec($dispatch, $config); + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } + + // 清空类的实例化 + Loader::clearInstance(); + + // 输出数据到客户端 + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $type = $request->isAjax() ? + Config::get('default_ajax_return') : + Config::get('default_return_type'); + + $response = Response::create($data, $type); + } else { + $response = Response::create(); + } + + // 监听 app_end + Hook::listen('app_end', $response); + + return $response; + } + + /** + * 初始化应用,并返回配置信息 + * @access public + * @return array + */ + public static function initCommon() + { + if (empty(self::$init)) { + if (defined('APP_NAMESPACE')) { + self::$namespace = APP_NAMESPACE; + } + + Loader::addNamespace(self::$namespace, APP_PATH); + + // 初始化应用 + $config = self::init(); + self::$suffix = $config['class_suffix']; + + // 应用调试模式 + self::$debug = Env::get('app_debug', Config::get('app_debug')); + + if (!self::$debug) { + ini_set('display_errors', 'Off'); + } elseif (!IS_CLI) { + // 重新申请一块比较大的 buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + + ob_start(); + + if (!empty($output)) { + echo $output; + } + + } + + if (!empty($config['root_namespace'])) { + Loader::addNamespace($config['root_namespace']); + } + + // 加载额外文件 + if (!empty($config['extra_file_list'])) { + foreach ($config['extra_file_list'] as $file) { + $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; + if (is_file($file) && !isset(self::$file[$file])) { + include $file; + self::$file[$file] = true; + } + } + } + + // 设置系统时区 + date_default_timezone_set($config['default_timezone']); + + // 监听 app_init + Hook::listen('app_init'); + + self::$init = true; + } + + return Config::get(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return array + */ + private static function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DS : ''; + + // 加载初始化文件 + if (is_file(APP_PATH . $module . 'init' . EXT)) { + include APP_PATH . $module . 'init' . EXT; + } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { + include RUNTIME_PATH . $module . 'init' . EXT; + } else { + // 加载模块配置 + $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + Config::load($filename, 'database'); + + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { + $filename = $dir . DS . $file; + Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + // 加载应用状态配置 + if ($config['app_status']) { + Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + Hook::import(include CONF_PATH . $module . 'tags' . EXT); + } + + // 加载公共文件 + $path = APP_PATH . $module; + if (is_file($path . 'common' . EXT)) { + include $path . 'common' . EXT; + } + + // 加载当前模块语言包 + if ($module) { + Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); + } + } + + return Config::get(); + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param array|string $dispatch 调度信息 + * @param string $type 调度类型 + * @return void + */ + public static function dispatch($dispatch, $type = 'module') + { + self::$dispatch = ['type' => $type, $type => $dispatch]; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|array|\Closure $function 函数或者闭包 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeFunction($function, $vars = []) + { + $reflect = new \ReflectionFunction($function); + $args = self::bindParams($reflect, $vars); + + // 记录执行信息 + self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); + + return $reflect->invokeArgs($args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param string|array $method 方法 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeMethod($method, $vars = []) + { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); + $reflect = new \ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new \ReflectionMethod($method); + } + + $args = self::bindParams($reflect, $vars); + + self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeClass($class, $vars = []) + { + $reflect = new \ReflectionClass($class); + $constructor = $reflect->getConstructor(); + $args = $constructor ? self::bindParams($constructor, $vars) : []; + + return $reflect->newInstanceArgs($args); + } + + /** + * 绑定参数 + * @access private + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 变量 + * @return array + */ + private static function bindParams($reflect, $vars = []) + { + // 自动获取请求变量 + if (empty($vars)) { + $vars = Config::get('url_param_type') ? + Request::instance()->route() : + Request::instance()->param(); + } + + $args = []; + if ($reflect->getNumberOfParameters() > 0) { + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + + foreach ($reflect->getParameters() as $param) { + $args[] = self::getParamValue($param, $vars, $type); + } + } + + return $args; + } + + /** + * 获取参数值 + * @access private + * @param \ReflectionParameter $param 参数 + * @param array $vars 变量 + * @param string $type 类别 + * @return array + */ + private static function getParamValue($param, &$vars, $type) + { + $name = $param->getName(); + $class = $param->getClass(); + + if ($class) { + $className = $class->getName(); + $bind = Request::instance()->$name; + + if ($bind instanceof $className) { + $result = $bind; + } else { + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + + if ($method->isPublic() && $method->isStatic()) { + return $className::invoke(Request::instance()); + } + } + + $result = method_exists($className, 'instance') ? + $className::instance() : + new $className; + } + } elseif (1 == $type && !empty($vars)) { + $result = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $result = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $result = $param->getDefaultValue(); + } else { + throw new \InvalidArgumentException('method param miss:' . $name); + } + + return $result; + } + + /** + * 执行调用分发 + * @access protected + * @param array $dispatch 调用信息 + * @param array $config 配置信息 + * @return Response|mixed + * @throws \InvalidArgumentException + */ + protected static function exec($dispatch, $config) + { + switch ($dispatch['type']) { + case 'redirect': // 重定向跳转 + $data = Response::create($dispatch['url'], 'redirect') + ->code($dispatch['status']); + break; + case 'module': // 模块/控制器/操作 + $data = self::module( + $dispatch['module'], + $config, + isset($dispatch['convert']) ? $dispatch['convert'] : null + ); + break; + case 'controller': // 执行控制器操作 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action( + $dispatch['controller'], + $vars, + $config['url_controller_layer'], + $config['controller_suffix'] + ); + break; + case 'method': // 回调方法 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = self::invokeMethod($dispatch['method'], $vars); + break; + case 'function': // 闭包 + $data = self::invokeFunction($dispatch['function']); + break; + case 'response': // Response 实例 + $data = $dispatch['response']; + break; + default: + throw new \InvalidArgumentException('dispatch type not support'); + } + + return $data; + } + + /** + * 执行模块 + * @access public + * @param array $result 模块/控制器/操作 + * @param array $config 配置参数 + * @param bool $convert 是否自动转换控制器和操作名 + * @return mixed + * @throws HttpException + */ + public static function module($result, $config, $convert = null) + { + if (is_string($result)) { + $result = explode('/', $result); + } + + $request = Request::instance(); + + if ($config['app_multi_module']) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); + $bind = Route::getBind('module'); + $available = false; + + if ($bind) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + + if (empty($result[0])) { + $module = $bindModule; + $available = true; + } elseif ($module == $bindModule) { + $available = true; + } + } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $request->module($module); + $config = self::init($module); + + // 模块请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } else { + // 单一模块部署 + $module = ''; + $request->module($module); + } + + // 设置默认过滤机制 + $request->filter($config['default_filter']); + + // 当前模块路径 + App::$modulePath = APP_PATH . ($module ? $module . DS : ''); + + // 是否自动转换控制器和操作名 + $convert = is_bool($convert) ? $convert : $config['url_convert']; + + // 获取控制器名 + $controller = strip_tags($result[1] ?: $config['default_controller']); + + if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + $controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $actionName = strip_tags($result[2] ?: $config['default_action']); + if (!empty($config['action_convert'])) { + $actionName = Loader::parseName($actionName, 1); + } else { + $actionName = $convert ? strtolower($actionName) : $actionName; + } + + // 设置当前请求的控制器、操作 + $request->controller(Loader::parseName($controller, 1))->action($actionName); + + // 监听module_init + Hook::listen('module_init', $request); + + try { + $instance = Loader::controller( + $controller, + $config['url_controller_layer'], + $config['controller_suffix'], + $config['empty_controller'] + ); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 获取当前操作名 + $action = $actionName . $config['action_suffix']; + + $vars = []; + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + // 严格获取当前操作方法名 + $reflect = new \ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $config['action_suffix']; + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $request->action($actionName); + + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + Hook::listen('action_begin', $call); + + return self::invokeMethod($call, $vars); + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @param \think\Request $request 请求实例 + * @param array $config 配置信息 + * @return array + * @throws \think\Exception + */ + public static function routeCheck($request, array $config) + { + $path = $request->path(); + $depr = $config['pathinfo_depr']; + $result = false; + + // 路由检测 + $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; + if ($check) { + // 开启路由 + if (is_file(RUNTIME_PATH . 'route.php')) { + // 读取路由缓存 + $rules = include RUNTIME_PATH . 'route.php'; + is_array($rules) && Route::rules($rules); + } else { + $files = $config['route_config_file']; + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + // 导入路由配置 + $rules = include CONF_PATH . $file . CONF_EXT; + is_array($rules) && Route::import($rules); + } + } + } + + // 路由检测(根据路由定义返回不同的URL调度) + $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); + $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; + + if ($must && false === $result) { + // 路由无效 + throw new RouteNotFoundException(); + } + } + + // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 + if (false === $result) { + $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); + } + + return $result; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $route 是否需要检测路由 + * @param bool $must 是否强制检测路由 + * @return void + */ + public static function route($route, $must = false) + { + self::$routeCheck = $route; + self::$routeMust = $must; + } +} diff --git a/thinkphp/library/think/Build.php b/thinkphp/library/think/Build.php new file mode 100644 index 0000000..de7c327 --- /dev/null +++ b/thinkphp/library/think/Build.php @@ -0,0 +1,235 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Build +{ + /** + * 根据传入的 build 资料创建目录和文件 + * @access public + * @param array $build build 列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + * @throws Exception + */ + public static function run(array $build = [], $namespace = 'app', $suffix = false) + { + // 锁定 + $lock = APP_PATH . 'build.lock'; + + // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了 + if (!is_writable($lock)) { + if (!touch($lock)) { + throw new Exception( + '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!
            请手动生成项目目录~', + 10006 + ); + } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + self::buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + self::buildFile($list); + } else { + // 创建模块 + self::module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lock); + } + } + + /** + * 创建目录 + * @access protected + * @param array $list 目录列表 + * @return void + */ + protected static function buildDir($list) + { + foreach ($list as $dir) { + // 目录不存在则创建目录 + !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true); + } + } + + /** + * 创建文件 + * @access protected + * @param array $list 文件列表 + * @return void + */ + protected static function buildFile($list) + { + foreach ($list as $file) { + // 先创建目录 + if (!is_dir(APP_PATH . dirname($file))) { + mkdir(APP_PATH . dirname($file), 0755, true); + } + + // 再创建文件 + if (!is_file(APP_PATH . $file)) { + file_put_contents( + APP_PATH . $file, + 'php' == pathinfo($file, PATHINFO_EXTENSION) ? " ['config.php', 'common.php'], + '__dir__' => ['controller', 'model', 'view'], + ]; + } + + // 创建子目录和文件 + foreach ($list as $path => $file) { + $modulePath = APP_PATH . $module . DS; + + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + self::checkDirBuild($modulePath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($modulePath . $name)) { + file_put_contents( + $modulePath . $name, + 'php' == pathinfo($name, PATHINFO_EXTENSION) ? " +// +---------------------------------------------------------------------- + +namespace think; + +use think\cache\Driver; + +class Cache +{ + /** + * @var array 缓存的实例 + */ + public static $instance = []; + + /** + * @var int 缓存读取次数 + */ + public static $readTimes = 0; + + /** + * @var int 缓存写入次数 + */ + public static $writeTimes = 0; + + /** + * @var object 操作句柄 + */ + public static $handler; + + /** + * 连接缓存驱动 + * @access public + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @return Driver + */ + public static function connect(array $options = [], $name = false) + { + $type = !empty($options['type']) ? $options['type'] : 'File'; + + if (false === $name) { + $name = md5(serialize($options)); + } + + if (true === $name || !isset(self::$instance[$name])) { + $class = false === strpos($type, '\\') ? + '\\think\\cache\\driver\\' . ucwords($type) : + $type; + + // 记录初始化信息 + App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); + + if (true === $name) { + return new $class($options); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 自动初始化缓存 + * @access public + * @param array $options 配置数组 + * @return Driver + */ + public static function init(array $options = []) + { + if (is_null(self::$handler)) { + if (empty($options) && 'complex' == Config::get('cache.type')) { + $default = Config::get('cache.default'); + // 获取默认缓存配置,并连接 + $options = Config::get('cache.' . $default['type']) ?: $default; + } elseif (empty($options)) { + $options = Config::get('cache'); + } + + self::$handler = self::connect($options); + } + + return self::$handler; + } + + /** + * 切换缓存类型 需要配置 cache.type 为 complex + * @access public + * @param string $name 缓存标识 + * @return Driver + */ + public static function store($name = '') + { + if ('' !== $name && 'complex' == Config::get('cache.type')) { + return self::connect(Config::get('cache.' . $name), strtolower($name)); + } + + return self::init(); + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public static function has($name) + { + self::$readTimes++; + + return self::init()->has($name); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $default 默认值 + * @return mixed + */ + public static function get($name, $default = false) + { + self::$readTimes++; + + return self::init()->get($name, $default); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $value 存储数据 + * @param int|null $expire 有效时间 0为永久 + * @return boolean + */ + public static function set($name, $value, $expire = null) + { + self::$writeTimes++; + + return self::init()->set($name, $value, $expire); + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function inc($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->inc($name, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function dec($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->dec($name, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存标识 + * @return boolean + */ + public static function rm($name) + { + self::$writeTimes++; + + return self::init()->rm($name); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public static function clear($tag = null) + { + self::$writeTimes++; + + return self::init()->clear($tag); + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public static function pull($name) + { + self::$readTimes++; + self::$writeTimes++; + + return self::init()->pull($name); + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public static function remember($name, $value, $expire = null) + { + self::$readTimes++; + + return self::init()->remember($name, $value, $expire); + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return Driver + */ + public static function tag($name, $keys = null, $overlay = false) + { + return self::init()->tag($name, $keys, $overlay); + } + +} diff --git a/thinkphp/library/think/Collection.php b/thinkphp/library/think/Collection.php new file mode 100644 index 0000000..f872476 --- /dev/null +++ b/thinkphp/library/think/Collection.php @@ -0,0 +1,467 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * @var array 数据 + */ + protected $items = []; + + /** + * Collection constructor. + * @access public + * @param array $items 数据 + */ + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + /** + * 创建 Collection 实例 + * @access public + * @param array $items 数据 + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * 判断数据是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * 将数据转成数组 + * @access public + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + return ($value instanceof Model || $value instanceof self) ? + $value->toArray() : + $value; + }, $this->items); + } + + /** + * 获取全部的数据 + * @access public + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * 交换数组中的键和值 + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名组成的新 Collection 实例 + * @access public + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * 返回数组中所有的值组成的新 Collection 实例 + * @access public + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * 合并数组并返回一个新的 Collection 实例 + * @access public + * @param mixed $items 新的数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回差集生成的新 Collection 实例 + * @access public + * @param mixed $items 做比较的数据 + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回交集组成的 Collection 新实例 + * @access public + * @param mixed $items 比较数据 + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->convertToArray($items))); + } + + /** + * 返回并删除数据中的的最后一个元素(出栈) + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 返回并删除数据中首个元素 + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function push($value, $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * @access public + * @param callable $callback 回调函数 + * @param mixed $initial 初始值 + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序创建一个新的 Collection 实例 + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 把数据分割为新的数组块 + * @access public + * @param int $size 分隔长度 + * @param bool $preserveKeys 是否保持原数据索引 + * @return static + */ + public function chunk($size, $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 给数据中的每个元素执行回调 + * @access public + * @param callable $callback 回调函数 + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } + + if (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数过滤数据中的元素 + * @access public + * @param callable|null $callback 回调函数 + * @return static + */ + public function filter(callable $callback = null) + { + return new static(array_filter($this->items, $callback ?: null)); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param mixed $columnKey 键名 + * @param null $indexKey 作为索引值的列 + * @return array + */ + public function column($columnKey, $indexKey = null) + { + if (function_exists('array_column')) { + return array_column($this->items, $columnKey, $indexKey); + } + + $result = []; + foreach ($this->items as $row) { + $key = $value = null; + $keySet = $valueSet = false; + + if (null !== $indexKey && array_key_exists($indexKey, $row)) { + $key = (string) $row[$indexKey]; + $keySet = true; + } + + if (null === $columnKey) { + $valueSet = true; + $value = $row; + } elseif (is_array($row) && array_key_exists($columnKey, $row)) { + $valueSet = true; + $value = $row[$columnKey]; + } + + if ($valueSet) { + if ($keySet) { + $result[$key] = $value; + } else { + $result[] = $value; + } + } + } + + return $result; + } + + /** + * 对数据排序,并返回排序后的数据组成的新 Collection 实例 + * @access public + * @param callable|null $callback 回调函数 + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + $callback = $callback ?: function ($a, $b) { + return $a == $b ? 0 : (($a < $b) ? -1 : 1); + }; + + uasort($items, $callback); + return new static($items); + } + + /** + * 将数据打乱后组成新的 Collection 实例 + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + return new static($items); + } + + /** + * 截取数据并返回新的 Collection 实例 + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys 是否保持原先的键名 + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + /** + * 指定的键是否存在 + * @access public + * @param mixed $offset 键名 + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + /** + * 获取指定键对应的值 + * @access public + * @param mixed $offset 键名 + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + /** + * 设置键值 + * @access public + * @param mixed $offset 键名 + * @param mixed $value 值 + * @return void + */ + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + /** + * 删除指定键值 + * @access public + * @param mixed $offset 键名 + * @return void + */ + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + /** + * 统计数据的个数 + * @access public + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * 获取数据的迭代器 + * @access public + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * 将数据反序列化成数组 + * @access public + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为 JSON 字符串 + * @access public + * @param integer $options json 参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 将数据转换成字符串 + * @access public + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * 将数据转换成数组 + * @access protected + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items) + { + return $items instanceof self ? $items->all() : (array) $items; + } +} diff --git a/thinkphp/library/think/Config.php b/thinkphp/library/think/Config.php new file mode 100644 index 0000000..8fa668d --- /dev/null +++ b/thinkphp/library/think/Config.php @@ -0,0 +1,214 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Config +{ + /** + * @var array 配置参数 + */ + private static $config = []; + + /** + * @var string 参数作用域 + */ + private static $range = '_sys_'; + + /** + * 设定配置参数的作用域 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function range($range) + { + self::$range = $range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + } + + /** + * 解析配置文件或内容 + * @access public + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function parse($config, $type = '', $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION); + + $class = false !== strpos($type, '\\') ? + $type : + '\\think\\config\\driver\\' . ucwords($type); + + return self::set((new $class())->parse($config), $name, $range); + } + + /** + * 加载配置文件(PHP格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function load($file, $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + if (is_file($file)) { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + + if ('php' == $type) { + return self::set(include $file, $name, $range); + } + + if ('yaml' == $type && function_exists('yaml_parse_file')) { + return self::set(yaml_parse_file($file), $name, $range); + } + + return self::parse($file, $type, $name, $range); + } + + return self::$config[$range]; + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return bool + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + if (!strpos($name, '.')) { + return isset(self::$config[$range][strtolower($name)]); + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + return isset(self::$config[$range][strtolower($name[0])][$name[1]]); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return mixed + */ + public static function get($name = null, $range = '') + { + $range = $range ?: self::$range; + + // 无参数时获取所有 + if (empty($name) && isset(self::$config[$range])) { + return self::$config[$range]; + } + + // 非二级配置时直接返回 + if (!strpos($name, '.')) { + $name = strtolower($name); + return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + $name[0] = strtolower($name[0]); + + if (!isset(self::$config[$range][$name[0]])) { + // 动态载入额外配置 + $module = Request::instance()->module(); + $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT; + + is_file($file) && self::load($file, $name[0]); + } + + return isset(self::$config[$range][$name[0]][$name[1]]) ? + self::$config[$range][$name[0]][$name[1]] : + null; + } + + /** + * 设置配置参数 name 为数组则为批量设置 + * @access public + * @param string|array $name 配置参数名(支持二级配置 . 号分割) + * @param mixed $value 配置值 + * @param string $range 作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + // 字符串则表示单个配置设置 + if (is_string($name)) { + if (!strpos($name, '.')) { + self::$config[$range][strtolower($name)] = $value; + } else { + // 二维数组 + $name = explode('.', $name, 2); + self::$config[$range][strtolower($name[0])][$name[1]] = $value; + } + + return $value; + } + + // 数组则表示批量设置 + if (is_array($name)) { + if (!empty($value)) { + self::$config[$range][$value] = isset(self::$config[$range][$value]) ? + array_merge(self::$config[$range][$value], $name) : + $name; + + return self::$config[$range][$value]; + } + + return self::$config[$range] = array_merge( + self::$config[$range], array_change_key_case($name) + ); + } + + // 为空直接返回已有配置 + return self::$config[$range]; + } + + /** + * 重置配置参数 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function reset($range = '') + { + $range = $range ?: self::$range; + + if (true === $range) { + self::$config = []; + } else { + self::$config[$range] = []; + } + } +} diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php new file mode 100644 index 0000000..32b2572 --- /dev/null +++ b/thinkphp/library/think/Console.php @@ -0,0 +1,863 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Command; +use think\console\command\Help as HelpCommand; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\driver\Buffer; + +class Console +{ + /** + * @var string 命令名称 + */ + private $name; + + /** + * @var string 命令版本 + */ + private $version; + + /** + * @var Command[] 命令 + */ + private $commands = []; + + /** + * @var bool 是否需要帮助信息 + */ + private $wantHelps = false; + + /** + * @var bool 是否捕获异常 + */ + private $catchExceptions = true; + + /** + * @var bool 是否自动退出执行 + */ + private $autoExit = true; + + /** + * @var InputDefinition 输入定义 + */ + private $definition; + + /** + * @var string 默认执行的命令 + */ + private $defaultCommand; + + /** + * @var array 默认提供的命令 + */ + private static $defaultCommands = [ + "think\\console\\command\\Help", + "think\\console\\command\\Lists", + "think\\console\\command\\Build", + "think\\console\\command\\Clear", + "think\\console\\command\\make\\Controller", + "think\\console\\command\\make\\Model", + "think\\console\\command\\optimize\\Autoload", + "think\\console\\command\\optimize\\Config", + "think\\console\\command\\optimize\\Route", + "think\\console\\command\\optimize\\Schema", + ]; + + /** + * Console constructor. + * @access public + * @param string $name 名称 + * @param string $version 版本 + * @param null|string $user 执行用户 + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) + { + $this->name = $name; + $this->version = $version; + + if ($user) { + $this->setUser($user); + } + + $this->defaultCommand = 'list'; + $this->definition = $this->getDefaultInputDefinition(); + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + /** + * 设置执行用户 + * @param $user + */ + public function setUser($user) + { + $user = posix_getpwnam($user); + if ($user) { + posix_setuid($user['uid']); + posix_setgid($user['gid']); + } + } + + /** + * 初始化 Console + * @access public + * @param bool $run 是否运行 Console + * @return int|Console + */ + public static function init($run = true) + { + static $console; + + if (!$console) { + $config = Config::get('console'); + // 实例化 console + $console = new self($config['name'], $config['version'], $config['user']); + + // 读取指令集 + if (is_file(CONF_PATH . 'command' . EXT)) { + $commands = include CONF_PATH . 'command' . EXT; + + if (is_array($commands)) { + foreach ($commands as $command) { + class_exists($command) && + is_subclass_of($command, "\\think\\console\\Command") && + $console->add(new $command()); // 注册指令 + } + } + } + } + + return $run ? $console->run() : $console; + } + + /** + * 调用命令 + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output + */ + public static function call($command, array $parameters = [], $driver = 'buffer') + { + $console = self::init(false); + + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $console->setCatchExceptions(false); + $console->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + */ + public function run() + { + $input = new Input(); + $output = new Output(); + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) throw $e; + + $output->renderException($e); + + $exitCode = $e->getCode(); + + if (is_numeric($exitCode)) { + $exitCode = ((int) $exitCode) ?: 1; + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) $exitCode = 255; + + exit($exitCode); + } + + return $exitCode; + } + + /** + * 执行指令 + * @access public + * @param Input $input 输入 + * @param Output $output 输出 + * @return int + */ + public function doRun(Input $input, Output $output) + { + // 获取版本信息 + if (true === $input->hasParameterOption(['--version', '-V'])) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + + // 获取帮助信息 + if (true === $input->hasParameterOption(['--help', '-h'])) { + if (!$name) { + $name = 'help'; + $input = new Input(['help']); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $input = new Input([$this->defaultCommand]); + } + + return $this->doRunCommand($this->find($name), $input, $output); + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition 输入定义 + * @return $this; + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + + return $this; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取帮助信息 + * @access public + * @return string + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * 设置是否捕获异常 + * @access public + * @param bool $boolean 是否捕获 + * @return $this + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + + return $this; + } + + /** + * 设置是否自动退出 + * @access public + * @param bool $boolean 是否自动退出 + * @return $this + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + + return $this; + } + + /** + * 获取名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置名称 + * @access public + * @param string $name 名称 + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * 获取版本 + * @access public + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * 设置版本 + * @access public + * @param string $version 版本信息 + * @return $this + */ + public function setVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf( + '%s version %s', + $this->getName(), + $this->getVersion() + ); + } + + return 'Console Tool'; + } + + /** + * 注册一个指令 + * @access public + * @param string $name 指令名称 + * @return Command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * 批量添加指令 + * @access public + * @param Command[] $commands 指令实例 + * @return $this + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) $this->add($command); + + return $this; + } + + /** + * 添加一个指令 + * @access public + * @param Command $command 命令实例 + * @return Command|bool + */ + public function add(Command $command) + { + if (!$command->isEnabled()) { + $command->setConsole(null); + return false; + } + + $command->setConsole($this); + + if (null === $command->getDefinition()) { + throw new \LogicException( + sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)) + ); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * 获取指令 + * @access public + * @param string $name 指令名称 + * @return Command + * @throws \InvalidArgumentException + */ + public function get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException( + sprintf('The command "%s" does not exist.', $name) + ); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces() + { + $namespaces = []; + + foreach ($this->commands as $command) { + $namespaces = array_merge( + $namespaces, $this->extractAllNamespaces($command->getName()) + ); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge( + $namespaces, $this->extractAllNamespaces($alias) + ); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * 查找注册命名空间中的名称或缩写 + * @access public + * @param string $namespace + * @return string + * @throws \InvalidArgumentException + */ + public function findNamespace($namespace) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + + $allNamespaces = $this->getNamespaces(); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf( + 'There are no commands defined in the "%s" namespace.', $namespace + ); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + + if (count($namespaces) > 1 && !$exact) { + throw new \InvalidArgumentException( + sprintf( + 'The namespace "%s" is ambiguous (%s).', + $namespace, + $this->getAbbreviationSuggestions(array_values($namespaces))) + ); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * 查找指令 + * @access public + * @param string $name 名称或者别名 + * @return Command + * @throws \InvalidArgumentException + */ + public function find($name) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $allCommands = array_keys($this->commands); + $commands = preg_grep('{^' . $expr . '}', $allCommands); + + if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { + if (false !== ($pos = strrpos($name, ':'))) { + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + if (count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { + $commandName = $commandList[$nameOrAlias]->getName(); + + return $commandName === $nameOrAlias || !in_array($commandName, $commands); + }); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new \InvalidArgumentException( + sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions) + ); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + */ + public function all($namespace = null) + { + if (null === $namespace) return $this->commands; + + $commands = []; + + foreach ($this->commands as $name => $command) { + $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1); + + if ($ext === $namespace) $commands[$name] = $command; + } + + return $commands; + } + + /** + * 获取可能的指令名 + * @access public + * @param array $names 指令名 + * @return array + */ + public static function getAbbreviations($names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return void + */ + protected function configureIO(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--ansi'])) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'])) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { + $input->setInteractive(false); + } + + if (true === $input->hasParameterOption(['--quiet', '-q'])) { + $output->setVerbosity(Output::VERBOSITY_QUIET); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + } + } + } + + /** + * 执行指令 + * @access protected + * @param Command $command 指令实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return int + * @throws \Exception + */ + protected function doRunCommand(Command $command, Input $input, Output $output) + { + return $command->run($input, $output); + } + + /** + * 获取指令的名称 + * @access protected + * @param Input $input 输入实例 + * @return string + */ + protected function getCommandName(Input $input) + { + return $input->getFirstArgument(); + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * 获取默认命令 + * @access protected + * @return Command[] + */ + protected function getDefaultCommands() + { + $defaultCommands = []; + + foreach (self::$defaultCommands as $class) { + if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) { + $defaultCommands[] = new $class(); + } + } + + return $defaultCommands; + } + + /** + * 添加默认指令 + * @access public + * @param array $classes 指令 + * @return void + */ + public static function addDefaultCommands(array $classes) + { + self::$defaultCommands = array_merge(self::$defaultCommands, $classes); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions($abbrevs) + { + return sprintf( + '%s, %s%s', + $abbrevs[0], + $abbrevs[1], + count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '' + ); + } + + /** + * 返回指令的命名空间部分 + * @access public + * @param string $name 指令名称 + * @param string $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name 指令名称 + * @param array|\Traversable $collection 建议集合 + * @return array + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = []; + $collectionParts = []; + + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + + if ($lev <= strlen($subname) / 3 || + '' !== $subname && + false !== strpos($parts[$i], $subname) + ) { + $alternatives[$collectionName] = $exists ? + $alternatives[$collectionName] + $lev : + $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? + $alternatives[$item] - $lev : + $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { + return $lev < 2 * $threshold; + }); + + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * 设置默认的指令 + * @access public + * @param string $commandName 指令名称 + * @return $this + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + + return $this; + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name 指令名称 + * @return array + */ + private function extractAllNamespaces($name) + { + $namespaces = []; + + foreach (explode(':', $name, -1) as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + +} diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php new file mode 100644 index 0000000..77225b7 --- /dev/null +++ b/thinkphp/library/think/Controller.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ValidateException; +use traits\controller\Jump; + +Loader::import('controller/Jump', TRAIT_PATH, EXT); + +class Controller +{ + use Jump; + + /** + * @var \think\View 视图类实例 + */ + protected $view; + + /** + * @var \think\Request Request 实例 + */ + protected $request; + + /** + * @var bool 验证失败是否抛出异常 + */ + protected $failException = false; + + /** + * @var bool 是否批量验证 + */ + protected $batchValidate = false; + + /** + * @var array 前置操作方法列表 + */ + protected $beforeActionList = []; + + /** + * 构造方法 + * @access public + * @param Request $request Request 对象 + */ + public function __construct(Request $request = null) + { + $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); + $this->request = is_null($request) ? Request::instance() : $request; + + // 控制器初始化 + $this->_initialize(); + + // 前置操作方法 + if ($this->beforeActionList) { + foreach ($this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); + } + } + } + + /** + * 初始化操作 + * @access protected + */ + protected function _initialize() + { + } + + /** + * 前置操作 + * @access protected + * @param string $method 前置操作方法名 + * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]] + * @return void + */ + protected function beforeAction($method, $options = []) + { + if (isset($options['only'])) { + if (is_string($options['only'])) { + $options['only'] = explode(',', $options['only']); + } + + if (!in_array($this->request->action(), $options['only'])) { + return; + } + } elseif (isset($options['except'])) { + if (is_string($options['except'])) { + $options['except'] = explode(',', $options['except']); + } + + if (in_array($this->request->action(), $options['except'])) { + return; + } + } + + call_user_func([$this, $method]); + } + + /** + * 加载模板输出 + * @access protected + * @param string $template 模板文件名 + * @param array $vars 模板输出变量 + * @param array $replace 模板替换 + * @param array $config 模板参数 + * @return mixed + */ + protected function fetch($template = '', $vars = [], $replace = [], $config = []) + { + return $this->view->fetch($template, $vars, $replace, $config); + } + + /** + * 渲染内容输出 + * @access protected + * @param string $content 模板内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + protected function display($content = '', $vars = [], $replace = [], $config = []) + { + return $this->view->display($content, $vars, $replace, $config); + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return $this + */ + protected function assign($name, $value = '') + { + $this->view->assign($name, $value); + + return $this; + } + + /** + * 初始化模板引擎 + * @access protected + * @param array|string $engine 引擎参数 + * @return $this + */ + protected function engine($engine) + { + $this->view->engine($engine); + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + protected function validateFailException($fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @param mixed $callback 回调方法(闭包) + * @return array|string|true + * @throws ValidateException + */ + protected function validate($data, $validate, $message = [], $batch = false, $callback = null) + { + if (is_array($validate)) { + $v = Loader::validate(); + $v->rule($validate); + } else { + // 支持场景 + if (strpos($validate, '.')) { + list($validate, $scene) = explode('.', $validate); + } + + $v = Loader::validate($validate); + + !empty($scene) && $v->scene($scene); + } + + // 批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + // 设置错误信息 + if (is_array($message)) { + $v->message($message); + } + + // 使用回调验证 + if ($callback && is_callable($callback)) { + call_user_func_array($callback, [$v, &$data]); + } + + if (!$v->check($data)) { + if ($this->failException) { + throw new ValidateException($v->getError()); + } + + return $v->getError(); + } + + return true; + } +} diff --git a/thinkphp/library/think/Cookie.php b/thinkphp/library/think/Cookie.php new file mode 100644 index 0000000..61b47cc --- /dev/null +++ b/thinkphp/library/think/Cookie.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Cookie +{ + /** + * @var array cookie 设置参数 + */ + protected static $config = [ + 'prefix' => '', // cookie 名称前缀 + 'expire' => 0, // cookie 保存时间 + 'path' => '/', // cookie 保存路径 + 'domain' => '', // cookie 有效域名 + 'secure' => false, // cookie 启用安全传输 + 'httponly' => false, // httponly 设置 + 'setcookie' => true, // 是否使用 setcookie + ]; + + /** + * @var bool 是否完成初始化了 + */ + protected static $init; + + /** + * Cookie初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('cookie'); + } + + self::$config = array_merge(self::$config, array_change_key_case($config)); + + if (!empty(self::$config['httponly'])) { + ini_set('session.cookie_httponly', 1); + } + + self::$init = true; + } + + /** + * 设置或者获取 cookie 作用域(前缀) + * @access public + * @param string $prefix 前缀 + * @return string| + */ + public static function prefix($prefix = '') + { + if (empty($prefix)) { + return self::$config['prefix']; + } + + return self::$config['prefix'] = $prefix; + } + + /** + * Cookie 设置、获取、删除 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function set($name, $value = '', $option = null) + { + !isset(self::$init) && self::init(); + + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) { + $option = ['expire' => $option]; + } elseif (is_string($option)) { + parse_str($option, $option); + } + + $config = array_merge(self::$config, array_change_key_case($option)); + } else { + $config = self::$config; + } + + $name = $config['prefix'] . $name; + + // 设置 cookie + if (is_array($value)) { + array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); + $value = 'think:' . json_encode($value); + } + + $expire = !empty($config['expire']) ? + $_SERVER['REQUEST_TIME'] + intval($config['expire']) : + 0; + + if ($config['setcookie']) { + setcookie( + $name, $value, $expire, $config['path'], $config['domain'], + $config['secure'], $config['httponly'] + ); + } + + $_COOKIE[$name] = $value; + } + + /** + * 永久保存 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + self::set($name, $value, $option); + } + + /** + * 判断是否有 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return bool + */ + public static function has($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + + return isset($_COOKIE[$prefix . $name]); + } + + /** + * 获取 Cookie 的值 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + $key = $prefix . $name; + + if ('' == $name) { + // 获取全部 + if ($prefix) { + $value = []; + + foreach ($_COOKIE as $k => $val) { + if (0 === strpos($k, $prefix)) { + $value[$k] = $val; + } + + } + } else { + $value = $_COOKIE; + } + } elseif (isset($_COOKIE[$key])) { + $value = $_COOKIE[$key]; + + if (0 === strpos($value, 'think:')) { + $value = json_decode(substr($value, 6), true); + array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); + } + } else { + $value = null; + } + + return $value; + } + + /** + * 删除 Cookie + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function delete($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + $name = $prefix . $name; + + if ($config['setcookie']) { + setcookie( + $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + // 删除指定 cookie + unset($_COOKIE[$name]); + } + + /** + * 清除指定前缀的所有 cookie + * @access public + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function clear($prefix = null) + { + if (empty($_COOKIE)) { + return; + } + + !isset(self::$init) && self::init(); + + // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀 + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + + if ($prefix) { + foreach ($_COOKIE as $key => $val) { + if (0 === strpos($key, $prefix)) { + if ($config['setcookie']) { + setcookie( + $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + unset($_COOKIE[$key]); + } + } + } + } + + /** + * json 转换时的格式保护 + * @access protected + * @param mixed $val 要转换的值 + * @param string $key 键名 + * @param string $type 转换类别 + * @return void + */ + protected static function jsonFormatProtect(&$val, $key, $type = 'encode') + { + if (!empty($val) && true !== $val) { + $val = 'decode' == $type ? urldecode($val) : urlencode($val); + } + } +} diff --git a/thinkphp/library/think/Db.php b/thinkphp/library/think/Db.php new file mode 100644 index 0000000..80f08d2 --- /dev/null +++ b/thinkphp/library/think/Db.php @@ -0,0 +1,180 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\db\Connection; +use think\db\Query; + +/** + * Class Db + * @package think + * @method Query table(string $table) static 指定数据表(含前缀) + * @method Query name(string $name) static 指定数据表(不含前缀) + * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method Query union(mixed $union, boolean $all = false) static UNION查询 + * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method Query order(mixed $field, string $order = null) static 查询ORDER + * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method mixed value(string $field) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 + * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 + * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID + * @method integer insertAll(array $dataSet) static 插入多条记录 + * @method integer update(array $data) static 更新记录 + * @method integer delete(mixed $data = null) static 删除记录 + * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 + * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 + * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 + * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 + * @method mixed transaction(callable $callback) static 执行数据库事务 + * @method void startTrans() static 启动事务 + * @method void commit() static 用于非自动提交状态下面的查询提交 + * @method void rollback() static 事务回滚 + * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 + * @method string quote(string $str) static SQL指令安全过滤 + * @method string getLastInsID($sequence = null) static 获取最近插入的ID + */ +class Db +{ + /** + * @var Connection[] 数据库连接实例 + */ + private static $instance = []; + + /** + * @var int 查询次数 + */ + public static $queryTimes = 0; + + /** + * @var int 执行次数 + */ + public static $executeTimes = 0; + + /** + * 数据库初始化,并取得数据库类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function connect($config = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + // 解析连接参数 支持数组和字符串 + $options = self::parseConfig($config); + + if (empty($options['type'])) { + throw new \InvalidArgumentException('Undefined db type'); + } + + $class = false !== strpos($options['type'], '\\') ? + $options['type'] : + '\\think\\db\\connector\\' . ucwords($options['type']); + + // 记录初始化信息 + if (App::$debug) { + Log::record('[ DB ] INIT ' . $options['type'], 'info'); + } + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 清除连接实例 + * @access public + * @return void + */ + public static function clear() + { + self::$instance = []; + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config 连接参数 + * @return array + */ + private static function parseConfig($config) + { + if (empty($config)) { + $config = Config::get('database'); + } elseif (is_string($config) && false === strpos($config, '/')) { + $config = Config::get($config); // 支持读取配置参数 + } + + return is_string($config) ? self::parseDsn($config) : $config; + } + + /** + * DSN 解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr 数据库 DSN 字符串解析 + * @return array + */ + private static function parseDsn($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + + /** + * 调用驱动类的方法 + * @access public + * @param string $method 方法名 + * @param array $params 参数 + * @return mixed + */ + public static function __callStatic($method, $params) + { + return call_user_func_array([self::connect(), $method], $params); + } +} diff --git a/thinkphp/library/think/Debug.php b/thinkphp/library/think/Debug.php new file mode 100644 index 0000000..df48748 --- /dev/null +++ b/thinkphp/library/think/Debug.php @@ -0,0 +1,252 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\response\Redirect; + +class Debug +{ + /** + * @var array 区间时间信息 + */ + protected static $info = []; + + /** + * @var array 区间内存信息 + */ + protected static $mem = []; + + /** + * 记录时间(微秒)和内存使用情况 + * @access public + * @param string $name 标记位置 + * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存) + * @return void + */ + public static function remark($name, $value = '') + { + self::$info[$name] = is_float($value) ? $value : microtime(true); + + if ('time' != $value) { + self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + self::$mem['peak'][$name] = memory_get_peak_usage(); + } + } + + /** + * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeTime($start, $end, $dec = 6) + { + if (!isset(self::$info[$end])) { + self::$info[$end] = microtime(true); + } + + return number_format((self::$info[$end] - self::$info[$start]), $dec); + } + + /** + * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseTime($dec = 6) + { + return number_format((microtime(true) - THINK_START_TIME), $dec); + } + + /** + * 获取当前访问的吞吐率情况 + * @access public + * @return string + */ + public static function getThroughputRate() + { + return number_format(1 / self::getUseTime(), 2) . 'req/s'; + } + + /** + * 记录区间的内存使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeMem($start, $end, $dec = 2) + { + if (!isset(self::$mem['mem'][$end])) { + self::$mem['mem'][$end] = memory_get_usage(); + } + + $size = self::$mem['mem'][$end] - self::$mem['mem'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计从开始到统计时的内存使用情况 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseMem($dec = 2) + { + $size = memory_get_usage() - THINK_START_MEM; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计区间的内存峰值情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getMemPeak($start, $end, $dec = 2) + { + if (!isset(self::$mem['peak'][$end])) { + self::$mem['peak'][$end] = memory_get_peak_usage(); + } + + $size = self::$mem['peak'][$end] - self::$mem['peak'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 获取文件加载信息 + * @access public + * @param bool $detail 是否显示详细 + * @return integer|array + */ + public static function getFile($detail = false) + { + $files = get_included_files(); + + if ($detail) { + $info = []; + + foreach ($files as $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } + + return count($files); + } + + /** + * 浏览器友好的变量输出 + * @access public + * @param mixed $var 变量 + * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串) + * @param string|null $label 标签(默认为空) + * @param integer $flags htmlspecialchars 的标志 + * @return null|string + */ + public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + { + $label = (null === $label) ? '' : rtrim($label) . ':'; + + ob_start(); + var_dump($var); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean()); + + if (IS_CLI) { + $output = PHP_EOL . $label . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, $flags); + } + + $output = '
            ' . $label . $output . '
            '; + } + + if ($echo) { + echo($output); + return; + } + + return $output; + } + + /** + * 调试信息注入到响应中 + * @access public + * @param Response $response 响应实例 + * @param string $content 返回的字符串 + * @return void + */ + public static function inject(Response $response, &$content) + { + $config = Config::get('trace'); + $type = isset($config['type']) ? $config['type'] : 'Html'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); + + unset($config['type']); + + if (!class_exists($class)) { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** @var \think\debug\Console|\think\debug\Html $trace */ + $trace = new $class($config); + + if ($response instanceof Redirect) { + // TODO 记录 + } else { + $output = $trace->output($response, Log::getLog()); + + if (is_string($output)) { + // trace 调试信息注入 + $pos = strripos($content, ''); + if (false !== $pos) { + $content = substr($content, 0, $pos) . $output . substr($content, $pos); + } else { + $content = $content . $output; + } + } + } + } +} diff --git a/thinkphp/library/think/Env.php b/thinkphp/library/think/Env.php new file mode 100644 index 0000000..0a8b250 --- /dev/null +++ b/thinkphp/library/think/Env.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Env +{ + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 . 号分割) + * @param string $default 默认值 + * @return mixed + */ + public static function get($name, $default = null) + { + $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name))); + + if (false !== $result) { + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + return $result; + } + + return $default; + } +} diff --git a/thinkphp/library/think/Error.php b/thinkphp/library/think/Error.php new file mode 100644 index 0000000..5f361d5 --- /dev/null +++ b/thinkphp/library/think/Error.php @@ -0,0 +1,136 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use think\exception\ThrowableError; + +class Error +{ + /** + * 注册异常处理 + * @access public + * @return void + */ + public static function register() + { + error_reporting(E_ALL); + set_error_handler([__CLASS__, 'appError']); + set_exception_handler([__CLASS__, 'appException']); + register_shutdown_function([__CLASS__, 'appShutdown']); + } + + /** + * 异常处理 + * @access public + * @param \Exception|\Throwable $e 异常 + * @return void + */ + public static function appException($e) + { + if (!$e instanceof \Exception) { + $e = new ThrowableError($e); + } + + $handler = self::getExceptionHandler(); + $handler->report($e); + + if (IS_CLI) { + $handler->renderForConsole(new ConsoleOutput, $e); + } else { + $handler->render($e)->send(); + } + } + + /** + * 错误处理 + * @access public + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @return void + * @throws ErrorException + */ + public static function appError($errno, $errstr, $errfile = '', $errline = 0) + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + + // 符合异常处理的则将错误信息托管至 think\exception\ErrorException + if (error_reporting() & $errno) { + throw $exception; + } + + self::getExceptionHandler()->report($exception); + } + + /** + * 异常中止处理 + * @access public + * @return void + */ + public static function appShutdown() + { + // 将错误信息托管至 think\ErrorException + if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { + self::appException(new ErrorException( + $error['type'], $error['message'], $error['file'], $error['line'] + )); + } + + // 写入日志 + Log::save(); + } + + /** + * 确定错误类型是否致命 + * @access protected + * @param int $type 错误类型 + * @return bool + */ + protected static function isFatal($type) + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * 获取异常处理的实例 + * @access public + * @return Handle + */ + public static function getExceptionHandler() + { + static $handle; + + if (!$handle) { + // 异常处理 handle + $class = Config::get('exception_handle'); + + if ($class && is_string($class) && class_exists($class) && + is_subclass_of($class, "\\think\\exception\\Handle") + ) { + $handle = new $class; + } else { + $handle = new Handle; + + if ($class instanceof \Closure) { + $handle->setRender($class); + } + + } + } + + return $handle; + } +} diff --git a/thinkphp/library/think/Exception.php b/thinkphp/library/think/Exception.php new file mode 100644 index 0000000..1ef06bd --- /dev/null +++ b/thinkphp/library/think/Exception.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Exception extends \Exception +{ + /** + * @var array 保存异常页面显示的额外 Debug 数据 + */ + protected $data = []; + + /** + * 设置异常额外的 Debug 数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + * @return void + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外 Debug 数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php new file mode 100644 index 0000000..d2ed220 --- /dev/null +++ b/thinkphp/library/think/File.php @@ -0,0 +1,478 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use SplFileObject; + +class File extends SplFileObject +{ + /** + * @var string 错误信息 + */ + private $error = ''; + + /** + * @var string 当前完整文件名 + */ + protected $filename; + + /** + * @var string 上传文件名 + */ + protected $saveName; + + /** + * @var string 文件上传命名规则 + */ + protected $rule = 'date'; + + /** + * @var array 文件上传验证规则 + */ + protected $validate = []; + + /** + * @var bool 单元测试 + */ + protected $isTest; + + /** + * @var array 上传文件信息 + */ + protected $info; + + /** + * @var array 文件 hash 信息 + */ + protected $hash = []; + + /** + * File constructor. + * @access public + * @param string $filename 文件名称 + * @param string $mode 访问模式 + */ + public function __construct($filename, $mode = 'r') + { + parent::__construct($filename, $mode); + $this->filename = $this->getRealPath() ?: $this->getPathname(); + } + + /** + * 设置是否是单元测试 + * @access public + * @param bool $test 是否是测试 + * @return $this + */ + public function isTest($test = false) + { + $this->isTest = $test; + + return $this; + } + + /** + * 设置上传信息 + * @access public + * @param array $info 上传文件信息 + * @return $this + */ + public function setUploadInfo($info) + { + $this->info = $info; + + return $this; + } + + /** + * 获取上传文件的信息 + * @access public + * @param string $name 信息名称 + * @return array|string + */ + public function getInfo($name = '') + { + return isset($this->info[$name]) ? $this->info[$name] : $this->info; + } + + /** + * 获取上传文件的文件名 + * @access public + * @return string + */ + public function getSaveName() + { + return $this->saveName; + } + + /** + * 设置上传文件的保存文件名 + * @access public + * @param string $saveName 保存名称 + * @return $this + */ + public function setSaveName($saveName) + { + $this->saveName = $saveName; + + return $this; + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type 类型 + * @return string + */ + public function hash($type = 'sha1') + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->filename); + } + + return $this->hash[$type]; + } + + /** + * 检查目录是否可写 + * @access protected + * @param string $path 目录 + * @return boolean + */ + protected function checkPath($path) + { + if (is_dir($path) || mkdir($path, 0755, true)) { + return true; + } + + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + + return false; + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime() + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->filename); + } + + /** + * 设置文件的命名规则 + * @access public + * @param string $rule 文件命名规则 + * @return $this + */ + public function rule($rule) + { + $this->rule = $rule; + + return $this; + } + + /** + * 设置上传文件的验证规则 + * @access public + * @param array $rule 验证规则 + * @return $this + */ + public function validate(array $rule = []) + { + $this->validate = $rule; + + return $this; + } + + /** + * 检测是否合法的上传文件 + * @access public + * @return bool + */ + public function isValid() + { + return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename); + } + + /** + * 检测上传文件 + * @access public + * @param array $rule 验证规则 + * @return bool + */ + public function check($rule = []) + { + $rule = $rule ?: $this->validate; + + /* 检查文件大小 */ + if (isset($rule['size']) && !$this->checkSize($rule['size'])) { + $this->error = 'filesize not match'; + return false; + } + + /* 检查文件 Mime 类型 */ + if (isset($rule['type']) && !$this->checkMime($rule['type'])) { + $this->error = 'mimetype to upload is not allowed'; + return false; + } + + /* 检查文件后缀 */ + if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) { + $this->error = 'extensions to upload is not allowed'; + return false; + } + + /* 检查图像文件 */ + if (!$this->checkImg()) { + $this->error = 'illegal image files'; + return false; + } + + return true; + } + + /** + * 检测上传文件后缀 + * @access public + * @param array|string $ext 允许后缀 + * @return bool + */ + public function checkExt($ext) + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + return in_array($extension, $ext); + } + + /** + * 检测图像文件 + * @access public + * @return bool + */ + public function checkImg() + { + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true + return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]); + } + + /** + * 判断图像类型 + * @access protected + * @param string $image 图片名称 + * @return bool|int + */ + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 检测上传文件大小 + * @access public + * @param integer $size 最大大小 + * @return bool + */ + public function checkSize($size) + { + return $this->getSize() <= $size; + } + + /** + * 检测上传文件类型 + * @access public + * @param array|string $mime 允许类型 + * @return bool + */ + public function checkMime($mime) + { + $mime = is_string($mime) ? explode(',', $mime) : $mime; + + return in_array(strtolower($this->getMime()), $mime); + } + + /** + * 移动文件 + * @access public + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @return false|File + */ + public function move($path, $savename = true, $replace = true) + { + // 文件上传失败,捕获错误代码 + if (!empty($this->info['error'])) { + $this->error($this->info['error']); + return false; + } + + // 检测合法性 + if (!$this->isValid()) { + $this->error = 'upload illegal files'; + return false; + } + + // 验证上传 + if (!$this->check()) { + return false; + } + + $path = rtrim($path, DS) . DS; + // 文件保存命名规则 + $saveName = $this->buildSaveName($savename); + $filename = $path . $saveName; + + // 检测目录 + if (false === $this->checkPath(dirname($filename))) { + return false; + } + + // 不覆盖同名文件 + if (!$replace && is_file($filename)) { + $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; + return false; + } + + /* 移动文件 */ + if ($this->isTest) { + rename($this->filename, $filename); + } elseif (!move_uploaded_file($this->filename, $filename)) { + $this->error = 'upload write error'; + return false; + } + + // 返回 File 对象实例 + $file = new self($filename); + $file->setSaveName($saveName)->setUploadInfo($this->info); + + return $file; + } + + /** + * 获取保存文件名 + * @access protected + * @param string|bool $savename 保存的文件名 默认自动生成 + * @return string + */ + protected function buildSaveName($savename) + { + // 自动生成文件名 + if (true === $savename) { + if ($this->rule instanceof \Closure) { + $savename = call_user_func_array($this->rule, [$this]); + } else { + switch ($this->rule) { + case 'date': + $savename = date('Ymd') . DS . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DS . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DS . md5(microtime(true)); + } + } + } + } elseif ('' === $savename || false === $savename) { + $savename = $this->getInfo('name'); + } + + if (!strpos($savename, '.')) { + $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); + } + + return $savename; + } + + /** + * 获取错误代码信息 + * @access private + * @param int $errorNo 错误号 + * @return $this + */ + private function error($errorNo) + { + switch ($errorNo) { + case 1: + case 2: + $this->error = 'upload File size exceeds the maximum value'; + break; + case 3: + $this->error = 'only the portion of file is uploaded'; + break; + case 4: + $this->error = 'no file to uploaded'; + break; + case 6: + $this->error = 'upload temp dir not found'; + break; + case 7: + $this->error = 'file write error'; + break; + default: + $this->error = 'unknown upload error'; + } + + return $this; + } + + /** + * 获取错误信息(支持多语言) + * @access public + * @return string + */ + public function getError() + { + if (is_array($this->error)) { + list($msg, $vars) = $this->error; + } else { + $msg = $this->error; + $vars = []; + } + + return Lang::has($msg) ? Lang::get($msg, $vars) : $msg; + } + + /** + * 魔法方法,获取文件的 hash 值 + * @access public + * @param string $method 方法名 + * @param mixed $args 调用参数 + * @return string + */ + public function __call($method, $args) + { + return $this->hash($method); + } +} diff --git a/thinkphp/library/think/Hook.php b/thinkphp/library/think/Hook.php new file mode 100644 index 0000000..a69ce54 --- /dev/null +++ b/thinkphp/library/think/Hook.php @@ -0,0 +1,148 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Hook +{ + /** + * @var array 标签 + */ + private static $tags = []; + + /** + * 动态添加行为扩展到某个标签 + * @access public + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 + * @return void + */ + public static function add($tag, $behavior, $first = false) + { + isset(self::$tags[$tag]) || self::$tags[$tag] = []; + + if (is_array($behavior) && !is_callable($behavior)) { + if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) { + unset($behavior['_overlay']); + self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior); + } else { + unset($behavior['_overlay']); + self::$tags[$tag] = $behavior; + } + } elseif ($first) { + array_unshift(self::$tags[$tag], $behavior); + } else { + self::$tags[$tag][] = $behavior; + } + } + + /** + * 批量导入插件 + * @access public + * @param array $tags 插件信息 + * @param boolean $recursive 是否递归合并 + * @return void + */ + public static function import(array $tags, $recursive = true) + { + if ($recursive) { + foreach ($tags as $tag => $behavior) { + self::add($tag, $behavior); + } + } else { + self::$tags = $tags + self::$tags; + } + } + + /** + * 获取插件信息 + * @access public + * @param string $tag 插件位置(留空获取全部) + * @return array + */ + public static function get($tag = '') + { + if (empty($tag)) { + return self::$tags; + } + + return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; + } + + /** + * 监听标签的行为 + * @access public + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @param mixed $extra 额外参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public static function listen($tag, &$params = null, $extra = null, $once = false) + { + $results = []; + + foreach (static::get($tag) as $key => $name) { + $results[$key] = self::exec($name, $tag, $params, $extra); + + // 如果返回 false,或者仅获取一个有效返回则中断行为执行 + if (false === $results[$key] || (!is_null($results[$key]) && $once)) { + break; + } + } + + return $once ? end($results) : $results; + } + + /** + * 执行某个行为 + * @access public + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 传人的参数 + * @param mixed $extra 额外参数 + * @return mixed + */ + public static function exec($class, $tag = '', &$params = null, $extra = null) + { + App::$debug && Debug::remark('behavior_start', 'time'); + + $method = Loader::parseName($tag, 1, false); + + if ($class instanceof \Closure) { + $result = call_user_func_array($class, [ & $params, $extra]); + $class = 'Closure'; + } elseif (is_array($class)) { + list($class, $method) = $class; + + $result = (new $class())->$method($params, $extra); + $class = $class . '->' . $method; + } elseif (is_object($class)) { + $result = $class->$method($params, $extra); + $class = get_class($class); + } elseif (strpos($class, '::')) { + $result = call_user_func_array($class, [ & $params, $extra]); + } else { + $obj = new $class(); + $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; + $result = $obj->$method($params, $extra); + } + + if (App::$debug) { + Debug::remark('behavior_end', 'time'); + Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); + } + + return $result; + } + +} diff --git a/thinkphp/library/think/Lang.php b/thinkphp/library/think/Lang.php new file mode 100644 index 0000000..a50d838 --- /dev/null +++ b/thinkphp/library/think/Lang.php @@ -0,0 +1,265 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Lang +{ + /** + * @var array 语言数据 + */ + private static $lang = []; + + /** + * @var string 语言作用域 + */ + private static $range = 'zh-cn'; + + /** + * @var string 语言自动侦测的变量 + */ + protected static $langDetectVar = 'lang'; + + /** + * @var string 语言 Cookie 变量 + */ + protected static $langCookieVar = 'think_var'; + + /** + * @var int 语言 Cookie 的过期时间 + */ + protected static $langCookieExpire = 3600; + + /** + * @var array 允许语言列表 + */ + protected static $allowLangList = []; + + /** + * @var array Accept-Language 转义为对应语言包名称 系统默认配置 + */ + protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn']; + + /** + * 设定当前的语言 + * @access public + * @param string $range 语言作用域 + * @return string + */ + public static function range($range = '') + { + if ($range) { + self::$range = $range; + } + + return self::$range; + } + + /** + * 设置语言定义(不区分大小写) + * @access public + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 语言作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + if (is_array($name)) { + return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; + } + + return self::$lang[$range][strtolower($name)] = $value; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param array|string $file 语言文件 + * @param string $range 语言作用域 + * @return mixed + */ + public static function load($file, $range = '') + { + $range = $range ?: self::$range; + $file = is_string($file) ? [$file] : $file; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + $lang = []; + + foreach ($file as $_file) { + if (is_file($_file)) { + // 记录加载信息 + App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); + + $_lang = include $_file; + + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } + } + } + + if (!empty($lang)) { + self::$lang[$range] = $lang + self::$lang[$range]; + } + + return self::$lang[$range]; + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return mixed + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + return isset(self::$lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public static function get($name = null, $vars = [], $range = '') + { + $range = $range ?: self::$range; + + // 空参数返回所有定义 + if (empty($name)) { + return self::$lang[$range]; + } + + $key = strtolower($name); + $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $name; + + // 变量解析 + if (!empty($vars) && is_array($vars)) { + /** + * Notes: + * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 + * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 + */ + if (key($vars) === 0) { + // 数字索引解析 + array_unshift($vars, $value); + $value = call_user_func_array('sprintf', $vars); + } else { + // 关联索引解析 + $replace = array_keys($vars); + foreach ($replace as &$v) { + $v = "{:{$v}}"; + } + $value = str_replace($replace, $vars, $value); + } + + } + + return $value; + } + + /** + * 自动侦测设置获取语言选择 + * @access public + * @return string + */ + public static function detect() + { + $langSet = ''; + + if (isset($_GET[self::$langDetectVar])) { + // url 中设置了语言变量 + $langSet = strtolower($_GET[self::$langDetectVar]); + } elseif (isset($_COOKIE[self::$langCookieVar])) { + // Cookie 中设置了语言变量 + $langSet = strtolower($_COOKIE[self::$langCookieVar]); + } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + // 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = strtolower($matches[1]); + $acceptLangs = Config::get('header_accept_lang'); + + if (isset($acceptLangs[$langSet])) { + $langSet = $acceptLangs[$langSet]; + } elseif (isset(self::$acceptLanguage[$langSet])) { + $langSet = self::$acceptLanguage[$langSet]; + } + } + + // 合法的语言 + if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { + self::$range = $langSet ?: self::$range; + } + + return self::$range; + } + + /** + * 设置语言自动侦测的变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangDetectVar($var) + { + self::$langDetectVar = $var; + } + + /** + * 设置语言的 cookie 保存变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangCookieVar($var) + { + self::$langCookieVar = $var; + } + + /** + * 设置语言的 cookie 的过期时间 + * @access public + * @param string $expire 过期时间 + * @return void + */ + public static function setLangCookieExpire($expire) + { + self::$langCookieExpire = $expire; + } + + /** + * 设置允许的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public static function setAllowLangList($list) + { + self::$allowLangList = $list; + } +} diff --git a/thinkphp/library/think/Loader.php b/thinkphp/library/think/Loader.php new file mode 100644 index 0000000..d813a5d --- /dev/null +++ b/thinkphp/library/think/Loader.php @@ -0,0 +1,677 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Loader +{ + /** + * @var array 实例数组 + */ + protected static $instance = []; + + /** + * @var array 类名映射 + */ + protected static $classMap = []; + + /** + * @var array 命名空间别名 + */ + protected static $namespaceAlias = []; + + /** + * @var array PSR-4 命名空间前缀长度映射 + */ + private static $prefixLengthsPsr4 = []; + + /** + * @var array PSR-4 的加载目录 + */ + private static $prefixDirsPsr4 = []; + + /** + * @var array PSR-4 加载失败的回退目录 + */ + private static $fallbackDirsPsr4 = []; + + /** + * @var array PSR-0 命名空间前缀映射 + */ + private static $prefixesPsr0 = []; + + /** + * @var array PSR-0 加载失败的回退目录 + */ + private static $fallbackDirsPsr0 = []; + + /** + * @var array 需要加载的文件 + */ + private static $files = []; + + /** + * 自动加载 + * @access public + * @param string $class 类名 + * @return bool + */ + public static function autoload($class) + { + // 检测命名空间别名 + if (!empty(self::$namespaceAlias)) { + $namespace = dirname($class); + if (isset(self::$namespaceAlias[$namespace])) { + $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); + if (class_exists($original)) { + return class_alias($original, $class, false); + } + } + } + + if ($file = self::findFile($class)) { + // 非 Win 环境不严格区分大小写 + if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) { + __include_file($file); + return true; + } + } + + return false; + } + + /** + * 查找文件 + * @access private + * @param string $class 类名 + * @return bool|string + */ + private static function findFile($class) + { + // 类库映射 + if (!empty(self::$classMap[$class])) { + return self::$classMap[$class]; + } + + // 查找 PSR-4 + $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; + $first = $class[0]; + + if (isset(self::$prefixLengthsPsr4[$first])) { + foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach (self::$prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // 查找 PSR-4 fallback dirs + foreach (self::$fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr4)) { + return $file; + } + } + + // 查找 PSR-0 + if (false !== $pos = strrpos($class, '\\')) { + // namespace class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DS) . EXT; + } + + if (isset(self::$prefixesPsr0[$first])) { + foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // 查找 PSR-0 fallback dirs + foreach (self::$fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + + // 找不到则设置映射为 false 并返回 + return self::$classMap[$class] = false; + } + + /** + * 注册 classmap + * @access public + * @param string|array $class 类名 + * @param string $map 映射 + * @return void + */ + public static function addClassMap($class, $map = '') + { + if (is_array($class)) { + self::$classMap = array_merge(self::$classMap, $class); + } else { + self::$classMap[$class] = $map; + } + } + + /** + * 注册命名空间 + * @access public + * @param string|array $namespace 命名空间 + * @param string $path 路径 + * @return void + */ + public static function addNamespace($namespace, $path = '') + { + if (is_array($namespace)) { + foreach ($namespace as $prefix => $paths) { + self::addPsr4($prefix . '\\', rtrim($paths, DS), true); + } + } else { + self::addPsr4($namespace . '\\', rtrim($path, DS), true); + } + } + + /** + * 添加 PSR-0 命名空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param array $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr0($prefix, $paths, $prepend = false) + { + if (!$prefix) { + self::$fallbackDirsPsr0 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr0) : + array_merge(self::$fallbackDirsPsr0, (array) $paths); + } else { + $first = $prefix[0]; + + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + } else { + self::$prefixesPsr0[$first][$prefix] = $prepend ? + array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) : + array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths); + } + } + } + + /** + * 添加 PSR-4 空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param string $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + self::$fallbackDirsPsr4 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr4) : + array_merge(self::$fallbackDirsPsr4, (array) $paths); + + } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException( + "A non-empty PSR-4 prefix must end with a namespace separator." + ); + } + + self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + self::$prefixDirsPsr4[$prefix] = (array) $paths; + + } else { + self::$prefixDirsPsr4[$prefix] = $prepend ? + // Prepend directories for an already registered namespace. + array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) : + // Append directories for an already registered namespace. + array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths); + } + } + + /** + * 注册命名空间别名 + * @access public + * @param array|string $namespace 命名空间 + * @param string $original 源文件 + * @return void + */ + public static function addNamespaceAlias($namespace, $original = '') + { + if (is_array($namespace)) { + self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace); + } else { + self::$namespaceAlias[$namespace] = $original; + } + } + + /** + * 注册自动加载机制 + * @access public + * @param callable $autoload 自动加载处理方法 + * @return void + */ + public static function register($autoload = null) + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + // Composer 自动加载支持 + if (is_dir(VENDOR_PATH . 'composer')) { + if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) { + require VENDOR_PATH . 'composer' . DS . 'autoload_static.php'; + + $declaredClass = get_declared_classes(); + $composerClass = array_pop($declaredClass); + + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { + if (property_exists($composerClass, $attr)) { + self::${$attr} = $composerClass::${$attr}; + } + } + } else { + self::registerComposerLoader(); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => LIB_PATH . 'think' . DS, + 'behavior' => LIB_PATH . 'behavior' . DS, + 'traits' => LIB_PATH . 'traits' . DS, + ]); + + // 加载类库映射文件 + if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { + self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); + } + + self::loadComposerAutoloadFiles(); + + // 自动加载 extend 目录 + self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); + } + + /** + * 注册 composer 自动加载 + * @access private + * @return void + */ + private static function registerComposerLoader() + { + if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) { + $map = require VENDOR_PATH . 'composer/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + self::addPsr0($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) { + $map = require VENDOR_PATH . 'composer/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + self::addPsr4($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) { + $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php'; + if ($classMap) { + self::addClassMap($classMap); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) { + self::$files = require VENDOR_PATH . 'composer/autoload_files.php'; + } + } + + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + foreach (self::$files as $fileIdentifier => $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + __require_file($file); + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } + } + } + + /** + * 导入所需的类库 同 Java 的 Import 本函数有缓存功能 + * @access public + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return bool + */ + public static function import($class, $baseUrl = '', $ext = EXT) + { + static $_file = []; + $key = $class . $baseUrl; + $class = str_replace(['.', '#'], [DS, '.'], $class); + + if (isset($_file[$key])) { + return true; + } + + if (empty($baseUrl)) { + list($name, $class) = explode(DS, $class, 2); + + if (isset(self::$prefixDirsPsr4[$name . '\\'])) { + // 注册的命名空间 + $baseUrl = self::$prefixDirsPsr4[$name . '\\']; + } elseif ('@' == $name) { + // 加载当前模块应用类库 + $baseUrl = App::$modulePath; + } elseif (is_dir(EXTEND_PATH . $name)) { + $baseUrl = EXTEND_PATH . $name . DS; + } else { + // 加载其它模块的类库 + $baseUrl = APP_PATH . $name . DS; + } + } elseif (substr($baseUrl, -1) != DS) { + $baseUrl .= DS; + } + + // 如果类存在则导入类库文件 + if (is_array($baseUrl)) { + foreach ($baseUrl as $path) { + if (is_file($filename = $path . DS . $class . $ext)) { + break; + } + } + } else { + $filename = $baseUrl . $class . $ext; + } + + if (!empty($filename) && + is_file($filename) && + (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME)) + ) { + __include_file($filename); + $_file[$key] = true; + + return true; + } + + return false; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + $uid = $name . $layer; + + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $model = new $class(); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $model = new $class(); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $model; + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return App::invokeClass($class); + } + + if ($empty) { + $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix); + + if (class_exists($emptyClass)) { + return new $emptyClass(Request::instance()); + } + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object|false + * @throws ClassNotFoundException + */ + public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: Config::get('default_validate'); + + if (empty($name)) { + return new Validate; + } + + $uid = $name . $layer; + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $validate = new $class; + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $validate = new $class; + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $validate; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected static function getModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $module = Request::instance()->module(); + $class = $name; + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = Request::instance()->module(); + } + + $class = self::parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 数据库初始化 并取得数据库类实例 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Connection + */ + public static function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller(); + $class = self::controller($module, $layer, $appendSuffix); + + if ($class) { + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars); + } + + return false; + } + + /** + * 字符串命名风格转换 + * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格 + * @access public + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + public static function parseName($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } + + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix 是否添加类名后缀 + * @return string + */ + public static function parseClass($module, $layer, $name, $appendSuffix = false) + { + + $array = explode('\\', str_replace(['/', '.'], '\\', $name)); + $class = self::parseName(array_pop($array), 1); + $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return App::$namespace . '\\' . + ($module ? $module . '\\' : '') . + $layer . '\\' . $path . $class; + } + + /** + * 初始化类的实例 + * @access public + * @return void + */ + public static function clearInstance() + { + self::$instance = []; + } +} + +// 作用范围隔离 + +/** + * include + * @param string $file 文件路径 + * @return mixed + */ +function __include_file($file) +{ + return include $file; +} + +/** + * require + * @param string $file 文件路径 + * @return mixed + */ +function __require_file($file) +{ + return require $file; +} diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php new file mode 100644 index 0000000..c064306 --- /dev/null +++ b/thinkphp/library/think/Log.php @@ -0,0 +1,237 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +/** + * Class Log + * @package think + * + * @method void log($msg) static 记录一般日志 + * @method void error($msg) static 记录错误日志 + * @method void info($msg) static 记录一般信息日志 + * @method void sql($msg) static 记录 SQL 查询日志 + * @method void notice($msg) static 记录提示日志 + * @method void alert($msg) static 记录报警日志 + */ +class Log +{ + const LOG = 'log'; + const ERROR = 'error'; + const INFO = 'info'; + const SQL = 'sql'; + const NOTICE = 'notice'; + const ALERT = 'alert'; + const DEBUG = 'debug'; + + /** + * @var array 日志信息 + */ + protected static $log = []; + + /** + * @var array 配置参数 + */ + protected static $config = []; + + /** + * @var array 日志类型 + */ + protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; + + /** + * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动 + */ + protected static $driver; + + /** + * @var string 当前日志授权 key + */ + protected static $key; + + /** + * 日志初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init($config = []) + { + $type = isset($config['type']) ? $config['type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type); + + self::$config = $config; + unset($config['type']); + + if (class_exists($class)) { + self::$driver = new $class($config); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + // 记录初始化信息 + App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); + } + + /** + * 获取日志信息 + * @access public + * @param string $type 信息类型 + * @return array|string + */ + public static function getLog($type = '') + { + return $type ? self::$log[$type] : self::$log; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public static function record($msg, $type = 'log') + { + self::$log[$type][] = $msg; + + // 命令行下面日志写入改进 + IS_CLI && self::save(); + } + + /** + * 清空日志信息 + * @access public + * @return void + */ + public static function clear() + { + self::$log = []; + } + + /** + * 设置当前日志记录的授权 key + * @access public + * @param string $key 授权 key + * @return void + */ + public static function key($key) + { + self::$key = $key; + } + + /** + * 检查日志写入权限 + * @access public + * @param array $config 当前日志配置参数 + * @return bool + */ + public static function check($config) + { + return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']); + } + + /** + * 保存调试信息 + * @access public + * @return bool + */ + public static function save() + { + // 没有需要保存的记录则直接返回 + if (empty(self::$log)) { + return true; + } + + is_null(self::$driver) && self::init(Config::get('log')); + + // 检测日志写入权限 + if (!self::check(self::$config)) { + return false; + } + + if (empty(self::$config['level'])) { + // 获取全部日志 + $log = self::$log; + if (!App::$debug && isset($log['debug'])) { + unset($log['debug']); + } + } else { + // 记录允许级别 + $log = []; + foreach (self::$config['level'] as $level) { + if (isset(self::$log[$level])) { + $log[$level] = self::$log[$level]; + } + } + } + + if ($result = self::$driver->save($log, true)) { + self::$log = []; + } + + Hook::listen('log_write_done', $log); + + return $result; + } + + /** + * 实时写入日志信息 并支持行为 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @param bool $force 是否强制写入 + * @return bool + */ + public static function write($msg, $type = 'log', $force = false) + { + $log = self::$log; + + // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录 + if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) { + return false; + } + + // 封装日志信息 + $log[$type][] = $msg; + + // 监听 log_write + Hook::listen('log_write', $log); + + is_null(self::$driver) && self::init(Config::get('log')); + + // 写入日志 + if ($result = self::$driver->save($log, false)) { + self::$log = []; + } + + return $result; + } + + /** + * 静态方法调用 + * @access public + * @param string $method 调用方法 + * @param mixed $args 参数 + * @return void + */ + public static function __callStatic($method, $args) + { + if (in_array($method, self::$type)) { + array_push($args, $method); + + call_user_func_array('\\think\\Log::record', $args); + } + } + +} diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php new file mode 100644 index 0000000..2dc27b4 --- /dev/null +++ b/thinkphp/library/think/Model.php @@ -0,0 +1,2350 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use BadMethodCallException; +use InvalidArgumentException; +use think\db\Query; +use think\exception\ValidateException; +use think\model\Collection as ModelCollection; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; + +/** + * Class Model + * @package think + * @mixin Query + */ +abstract class Model implements \JsonSerializable, \ArrayAccess +{ + // 数据库查询对象池 + protected static $links = []; + // 数据库配置 + protected $connection = []; + // 父关联模型对象 + protected $parent; + // 数据库查询对象 + protected $query; + // 当前模型名称 + protected $name; + // 数据表名称 + protected $table; + // 当前类名称 + protected $class; + // 回调事件 + private static $event = []; + // 错误信息 + protected $error; + // 字段验证规则 + protected $validate; + // 数据表主键 复合主键使用数组定义 不设置则自动获取 + protected $pk; + // 数据表字段信息 留空则自动获取 + protected $field = []; + // 数据排除字段 + protected $except = []; + // 数据废弃字段 + protected $disuse = []; + // 只读字段 + protected $readonly = []; + // 显示属性 + protected $visible = []; + // 隐藏属性 + protected $hidden = []; + // 追加属性 + protected $append = []; + // 数据信息 + protected $data = []; + // 原始数据 + protected $origin = []; + // 关联模型 + protected $relation = []; + + // 保存自动完成列表 + protected $auto = []; + // 新增自动完成列表 + protected $insert = []; + // 更新自动完成列表 + protected $update = []; + // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + protected $autoWriteTimestamp; + // 创建时间字段 + protected $createTime = 'create_time'; + // 更新时间字段 + protected $updateTime = 'update_time'; + // 时间字段取出后的默认时间格式 + protected $dateFormat; + // 字段类型或者格式转换 + protected $type = []; + // 是否为更新数据 + protected $isUpdate = false; + // 是否使用Replace + protected $replace = false; + // 是否强制更新所有数据 + protected $force = false; + // 更新条件 + protected $updateWhere; + // 验证失败是否抛出异常 + protected $failException = false; + // 全局查询范围 + protected $useGlobalScope = true; + // 是否采用批量验证 + protected $batchValidate = false; + // 查询数据集对象 + protected $resultSetType; + // 关联自动写入 + protected $relationWrite; + + /** + * 初始化过的模型. + * + * @var array + */ + protected static $initialized = []; + + /** + * 是否从主库读取(主从分布式有效) + * @var array + */ + protected static $readMaster; + + /** + * 构造方法 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + if (is_object($data)) { + $this->data = get_object_vars($data); + } else { + $this->data = $data; + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + // 当前类名 + $this->class = get_called_class(); + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', $this->class); + $this->name = basename($name); + if (Config::get('class_suffix')) { + $suffix = basename(dirname($name)); + $this->name = substr($this->name, 0, -strlen($suffix)); + } + } + + if (is_null($this->autoWriteTimestamp)) { + // 自动写入时间戳 + $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); + } + + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); + } + // 执行初始化操作 + $this->initialize(); + } + + /** + * 是否从主库读取数据(主从分布有效) + * @access public + * @param bool $all 是否所有模型生效 + * @return $this + */ + public function readMaster($all = false) + { + $model = $all ? '*' : $this->class; + + static::$readMaster[$model] = true; + return $this; + } + + /** + * 创建模型的查询对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + // 合并数据库配置 + if (!empty($this->connection)) { + if (is_array($this->connection)) { + $connection = array_merge(Config::get('database'), $this->connection); + } else { + $connection = $this->connection; + } + } else { + $connection = []; + } + + $con = Db::connect($connection); + // 设置当前模型 确保查询返回模型对象 + $queryClass = $this->query ?: $con->getConfig('query'); + $query = new $queryClass($con, $this); + + if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) { + $query->master(true); + } + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->setTable($this->table); + } else { + $query->name($this->name); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; + } + + /** + * 创建新的模型实例 + * @access public + * @param array|object $data 数据 + * @param bool $isUpdate 是否为更新 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance($data = [], $isUpdate = false, $where = null) + { + return (new static($data))->isUpdate($isUpdate, $where); + } + + /** + * 获取当前模型的查询对象 + * @access public + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function getQuery($buildNewQuery = false) + { + if ($buildNewQuery) { + return $this->buildQuery(); + } elseif (!isset(self::$links[$this->class])) { + // 创建模型查询对象 + self::$links[$this->class] = $this->buildQuery(); + } + + return self::$links[$this->class]; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool $useBaseQuery 是否调用全局查询范围 + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function db($useBaseQuery = true, $buildNewQuery = true) + { + $query = $this->getQuery($buildNewQuery); + + // 全局作用域 + if ($useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access protected + * @return void + */ + protected function initialize() + { + $class = get_class($this); + if (!isset(static::$initialized[$class])) { + static::$initialized[$class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据或者属性名 + * @param mixed $value 值 + * @return $this + */ + public function data($data, $value = null) + { + if (is_string($data)) { + $this->data[$data] = $value; + } else { + // 清空数据 + $this->data = []; + if (is_object($data)) { + $data = get_object_vars($data); + } + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } else { + $this->data = $data; + } + } + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData($name = null) + { + if (is_null($name)) { + return $this->data; + } elseif (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $auto; + return $this; + } + + /** + * 更新是否强制写入数据 而不做比较 + * @access public + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + /** + * 修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setAttr($name, $value, $data = []) + { + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp($name); + } else { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data), $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + return $this; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + return; + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @return $this + */ + public function setRelation($name, $value) + { + $this->relation[$name] = $value; + return $this; + } + + /** + * 自动写入时间戳 + * @access public + * @param string $name 时间戳字段 + * @return mixed + */ + protected function autoWriteTimestamp($name) + { + if (isset($this->type[$name])) { + $type = $this->type[$name]; + if (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'datetime': + case 'date': + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(time(), $format); + break; + case 'timestamp': + case 'integer': + default: + $value = time(); + break; + } + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(time(), $this->dateFormat); + } else { + $value = $this->formatDateTime(time(), $this->dateFormat, true); + } + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access public + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($time, $format, $timestamp = false) + { + if (false !== strpos($format, '\\')) { + $time = new $format($time); + } elseif (!$timestamp && false !== $format) { + $time = date($format, $time); + } + return $time; + } + + /** + * 数据写入 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $format = !empty($param) ? $param : $this->dateFormat; + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($value, $format); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + + } + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr($name) + { + try { + $notFound = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $notFound = true; + $value = null; + } + + // 检测属性获取器 + $method = 'get' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, $this->data, $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$name]); + } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(strtotime($value), $this->dateFormat); + } else { + $value = $this->formatDateTime($value, $this->dateFormat); + } + } elseif ($notFound) { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + // 不存在该字段 获取关联数据 + $value = $this->getRelationData($modelRelation); + // 保存关联对象值 + $this->relation[$name] = $value; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + return $value; + } + + /** + * 获取关联模型数据 + * @access public + * @param Relation $modelRelation 模型关联对象 + * @return mixed + * @throws BadMethodCallException + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { + $value = $this->parent; + } else { + // 首先获取关联数据 + if (method_exists($modelRelation, 'getRelation')) { + $value = $modelRelation->getRelation(); + } else { + throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation'); + } + } + return $value; + } + + /** + * 数据读取 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($value, $format); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(strtotime($value), $format); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + return $value; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->append = $override ? $append : array_merge($this->append, $append); + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $relation 关联方法 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($relation, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + + $relation = Loader::parseName($relation, 1, false); + + // 获取关联数据 + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $this->data[$key] = $model->getAttr($attr); + } + } + } + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->visible = $override ? $visible : array_merge($this->visible, $visible); + return $this; + } + + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + if ($visible) { + $array[] = $key; + } + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + return $array; + } + + /** + * 转换子模型对象 + * @access protected + * @param Model|ModelCollection $model + * @param $visible + * @param $hidden + * @param $key + * @return array + */ + protected function subToArray($model, $visible, $hidden, $key) + { + // 关联模型对象 + if (isset($visible[$key])) { + $model->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $model->hidden($hidden[$key]); + } + return $model->toArray(); + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray() + { + $item = []; + $visible = []; + $hidden = []; + + $data = array_merge($this->data, $this->relation); + + // 过滤属性 + if (!empty($this->visible)) { + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($data, array_flip($array)); + } elseif (!empty($this->hidden)) { + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($data, array_flip($array)); + } + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + $item[$key] = $this->subToArray($val, $visible, $hidden, $key); + } elseif (is_array($val) && reset($val) instanceof Model) { + // 关联模型数据集 + $arr = []; + foreach ($val as $k => $value) { + $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); + } + $item[$key] = $arr; + } else { + // 模型属性 + $item[$key] = $this->getAttr($key); + } + } + // 追加属性(必须定义获取器) + if (!empty($this->append)) { + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + + if (method_exists($modelRelation, 'getBindAttr')) { + $bindAttr = $modelRelation->getBindAttr(); + if ($bindAttr) { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + continue; + } + } + $item[$name] = $value; + } else { + $item[$name] = $this->getAttr($name); + } + } + } + } + return !empty($item) ? $item : []; + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } + + /** + * 转换当前模型数据集为数据集对象 + * @access public + * @param array|\think\Collection $collection 数据集 + * @return \think\Collection + */ + public function toCollection($collection) + { + if ($this->resultSetType) { + if ('collection' == $this->resultSetType) { + $collection = new ModelCollection($collection); + } elseif (false !== strpos($this->resultSetType, '\\')) { + $class = $this->resultSetType; + $collection = new $class($collection); + } + } + return $collection; + } + + /** + * 关联数据一起更新 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + $this->relationWrite = $relation; + return $this; + } + + /** + * 获取模型对象的主键 + * @access public + * @param string $name 模型名 + * @return mixed + */ + public function getPk($name = '') + { + if (!empty($name)) { + $table = $this->getQuery()->getTable($name); + return $this->getQuery()->getPk($table); + } elseif (empty($this->pk)) { + $this->pk = $this->getQuery()->getPk(); + } + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk($key) + { + $pk = $this->getPk(); + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + return false; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return integer|false + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + // 数据自动验证 + if (!empty($data)) { + if (!$this->validateData($data)) { + return false; + } + + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } + + if (!empty($where)) { + $this->isUpdate = true; + $this->updateWhere = $where; + } + + // 自动关联写入 + if (!empty($this->relationWrite)) { + $relation = []; + foreach ($this->relationWrite as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $relation[$key] = []; + foreach ($name as $val) { + if (isset($this->data[$val])) { + $relation[$key][$val] = $this->data[$val]; + unset($this->data[$val]); + } + } + } else { + $relation[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $relation[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $relation[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + $pk = $this->getPk(); + if ($this->isUpdate) { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update', $this)) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + return 0; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[$key] = $data[$key]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update)); + + // 模型更新 + if (!empty($allowFields)) { + $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data); + } else { + $result = $this->getQuery()->where($where)->update($data); + } + + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + + // 更新回调 + $this->trigger('after_update', $this); + + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间和更新时间 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); + if (!empty($allowFields)) { + $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence); + } else { + $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence); + } + + // 获取自动增长主键 + if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) { + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (isset($relation)) { + foreach ($relation as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + + // 标记为更新 + $this->isUpdate = true; + + // 新增回调 + $this->trigger('after_insert', $this); + } + // 写入回调 + $this->trigger('after_write', $this); + + // 重新记录原始数据 + $this->origin = $this->data; + + return $result; + } + + protected function checkAllowField($auto = []) + { + if (true === $this->field) { + $this->field = $this->getQuery()->getTableInfo('', 'fields'); + $field = $this->field; + } elseif (!empty($this->field)) { + $field = array_merge($this->field, $auto); + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } elseif (!empty($this->except)) { + $fields = $this->getQuery()->getTableInfo('', 'fields'); + $field = array_diff($fields, (array) $this->except); + $this->field = $field; + } else { + $field = []; + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + return $field; + } + + protected function autoRelationUpdate($relation) + { + foreach ($relation as $name => $val) { + if ($val instanceof Model) { + $val->save(); + } else { + unset($this->data[$name]); + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->save($val); + } + } + } + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + if ($this->force) { + $data = $this->data; + } else { + $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + return is_object($a) || $a != $b ? 1 : 0; + }); + } + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + + $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] += $step; + } + + return $result; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] -= $step; + } + + return $result; + } + + /** + * 获取更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->data[$pk])) { + $where = [$pk => $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + if ($this->validate) { + // 数据批量验证 + $validate = $this->validate; + foreach ($dataSet as $data) { + if (!$this->validateData($data, $validate)) { + return false; + } + } + } + + $result = []; + $db = $this->getQuery(); + $db->startTrans(); + try { + $pk = $this->getPk(); + if (is_string($pk) && $replace) { + $auto = true; + } + foreach ($dataSet as $key => $data) { + if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field); + } + } + $db->commit(); + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 设置允许写入的字段 + * @access public + * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段 + * @return $this + */ + public function allowField($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->field = $field; + return $this; + } + + /** + * 设置排除写入的字段 + * @access public + * @param string|array $field 排除允许写入的字段 + * @return $this + */ + public function except($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->except = $field; + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param mixed $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->readonly = $field; + return $this; + } + + /** + * 是否为更新数据 + * @access public + * @param bool $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + $this->isUpdate = $update; + if (!empty($where)) { + $this->updateWhere = $where; + } + return $this; + } + + /** + * 数据自动完成 + * @access public + * @param array $auto 要自动更新的字段列表 + * @return void + */ + protected function autoCompleteData($auto = []) + { + foreach ($auto as $field => $value) { + if (is_integer($field)) { + $field = $value; + $value = null; + } + + if (!isset($this->data[$field])) { + $default = null; + } else { + $default = $this->data[$field]; + } + + $this->setAttr($field, !is_null($value) ? $value : $default); + } + } + + /** + * 删除当前的记录 + * @access public + * @return integer + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + // 删除条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->getQuery()->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->delete(); + } + } + } + + $this->trigger('after_delete', $this); + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 设置自动完成的字段( 规则通过修改器定义) + * @access public + * @param array $fields 需要自动完成的字段 + * @return $this + */ + public function auto($fields) + { + $this->auto = $fields; + return $this; + } + + /** + * 设置字段验证 + * @access public + * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 + * @param array $msg 提示信息 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($rule = true, $msg = [], $batch = false) + { + if (is_array($rule)) { + $this->validate = [ + 'rule' => $rule, + 'msg' => $msg, + ]; + } else { + $this->validate = true === $rule ? $this->name : $rule; + } + $this->batchValidate = $batch; + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access public + * @param bool $fail 是否抛出异常 + * @return $this + */ + public function validateFailException($fail = true) + { + $this->failException = $fail; + return $this; + } + + /** + * 自动验证数据 + * @access protected + * @param array $data 验证数据 + * @param mixed $rule 验证规则 + * @param bool $batch 批量验证 + * @return bool + */ + protected function validateData($data, $rule = null, $batch = null) + { + $info = is_null($rule) ? $this->validate : $rule; + + if (!empty($info)) { + if (is_array($info)) { + $validate = Loader::validate(); + $validate->rule($info['rule']); + $validate->message($info['msg']); + } else { + $name = is_string($info) ? $info : $this->name; + if (strpos($name, '.')) { + list($name, $scene) = explode('.', $name); + } + $validate = Loader::validate($name); + if (!empty($scene)) { + $validate->scene($scene); + } + } + $batch = is_null($batch) ? $this->batchValidate : $batch; + + if (!$validate->batch($batch)->check($data)) { + $this->error = $validate->getError(); + if ($this->failException) { + throw new ValidateException($this->error); + } else { + return false; + } + } + $this->validate = null; + } + return true; + } + + /** + * 返回模型的错误信息 + * @access public + * @return string|array + */ + public function getError() + { + return $this->error; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 + * @return void + */ + public static function event($event, $callback, $override = false) + { + $class = get_called_class(); + if ($override) { + self::$event[$class][$event] = []; + } + self::$event[$class][$event][] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 传入参数(引用) + * @return bool + */ + protected function trigger($event, &$params) + { + if (isset(self::$event[$this->class][$event])) { + foreach (self::$event[$this->class][$event] as $callback) { + if (is_callable($callback)) { + $result = call_user_func_array($callback, [ & $params]); + if (false === $result) { + return false; + } + } + } + } + return true; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @return $this + */ + public static function create($data = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $model->isUpdate(false)->save($data, []); + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return $this + */ + public static function update($data = [], $where = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $result = $model->isUpdate(true)->save($data, $where); + return $model; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public static function get($data, $with = [], $cache = false) + { + if (is_null($data)) { + return; + } + + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->find($data); + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static[]|false + * @throws exception\DbException + */ + public static function all($data = null, $with = [], $cache = false) + { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->select($data); + } + + /** + * 分析查询表达式 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return Query + */ + protected static function parseQuery(&$data, $with, $cache) + { + $result = self::with($with)->cache($cache); + if (is_array($data) && key($data) !== 0) { + $result = $result->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $result]); + $data = null; + } elseif ($data instanceof Query) { + $result = $data->with($with)->cache($cache); + $data = null; + } + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @return integer 成功删除的记录数 + */ + public static function destroy($data) + { + $model = new static(); + $query = $model->db(); + if (empty($data) && 0 !== $data) { + return 0; + } elseif (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } + $resultSet = $query->select($data); + $count = 0; + if ($resultSet) { + foreach ($resultSet as $data) { + $result = $data->delete(); + $count += $result; + } + } + return $count; + } + + /** + * 命名范围 + * @access public + * @param string|array|\Closure $name 命名范围名称 逗号分隔 + * @internal mixed ...$params 参数调用 + * @return Query + */ + public static function scope($name) + { + $model = new static(); + $query = $model->db(); + $params = func_get_args(); + array_shift($params); + array_unshift($params, $query); + if ($name instanceof \Closure) { + call_user_func_array($name, $params); + } elseif (is_string($name)) { + $name = explode(',', $name); + } + if (is_array($name)) { + foreach ($name as $scope) { + $method = 'scope' . trim($scope); + if (method_exists($model, $method)) { + call_user_func_array([$model, $method], $params); + } + } + } + return $query; + } + + /** + * 设置是否使用全局查询范围 + * @param bool $use 是否启用全局查询范围 + * @access public + * @return Query + */ + public static function useGlobalScope($use) + { + $model = new static(); + return $model->db($use); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Relation|Query + */ + public static function has($relation, $operator = '>=', $count = 1, $id = '*') + { + $relation = (new static())->$relation(); + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + return $relation->has($operator, $count, $id); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Relation|Query + */ + public static function hasWhere($relation, $where = [], $fields = null) + { + return (new static())->$relation()->hasWhere($where, $fields); + } + + /** + * 解析模型的完整命名空间 + * @access public + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (false === strpos($model, '\\')) { + $path = explode('\\', get_called_class()); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param string|array $relations 关联名 + * @return $this + */ + public function relationQuery($relations) + { + if (is_string($relations)) { + $relations = explode(',', $relations); + } + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $method = Loader::parseName($relation, 1, false); + $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); + } + return $this; + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @return Model + */ + public function eagerlyResult(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param string|array $relation 关联名 + * @return void + */ + public function relationCount(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = $this->$relation()->relationCount($result, $closure); + if (!isset($name)) { + $name = Loader::parseName($relation) . '_count'; + } + $result->setAttr($name, $count); + } + } + + /** + * 获取模型的默认外键名 + * @access public + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + return Loader::parseName($name) . '_id'; + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return HasOne + */ + public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasOne($this, $model, $foreignKey, $localKey, $joinType); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return BelongsTo + */ + public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey($model); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @return HasMany + */ + public function hasMany($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前模型主键 + * @return HasManyThrough + */ + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey($through); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Loader::parseName(basename(str_replace('\\', '/', $model))); + $table = $table ?: Loader::parseName($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + public function __call($method, $args) + { + $query = $this->db(true, false); + if (method_exists($this, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + call_user_func_array([$this, $method], $args); + return $this; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + public static function __callStatic($method, $args) + { + $model = new static(); + $query = $model->db(); + if (method_exists($model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + + call_user_func_array([$model, $method], $args); + return $query; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + try { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { + return true; + } else { + $this->getAttr($name); + return true; + } + } catch (InvalidArgumentException $e) { + return false; + } + + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->data[$name], $this->relation[$name]); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 模型事件快捷方法 + * @param $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + +} diff --git a/thinkphp/library/think/Paginator.php b/thinkphp/library/think/Paginator.php new file mode 100644 index 0000000..3655567 --- /dev/null +++ b/thinkphp/library/think/Paginator.php @@ -0,0 +1,409 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; + +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** @var bool 是否为简洁模式 */ + protected $simple = false; + + /** @var Collection 数据集 */ + protected $items; + + /** @var integer 当前页 */ + protected $currentPage; + + /** @var integer 最后一页 */ + protected $lastPage; + + /** @var integer|null 数据总数 */ + protected $total; + + /** @var integer 每页的数量 */ + protected $listRows; + + /** @var bool 是否有下一页 */ + protected $hasMore; + + /** @var array 一些配置 */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** @var mixed simple模式下的下个元素 */ + protected $nextItem; + + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + if ($this->hasMore) { + $this->nextItem = $items->slice($this->listRows, 1); + } + $items = $items->slice(0, $this->listRows); + } else { + $this->total = $total; + $this->lastPage = (int) ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + $this->items = $items; + } + + /** + * @param $items + * @param $listRows + * @param null $currentPage + * @param bool $simple + * @param null $total + * @param array $options + * @return Paginator + */ + public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + return new static($items, $listRows, $currentPage, $total, $simple, $options); + } + + protected function setCurrentPage($currentPage) + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @param $page + * @return string + */ + protected function url($page) + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', $page, $this->options['path']); + } + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + $url = $path; + if (!empty($parameters)) { + $url .= '?' . http_build_query($parameters, null, '&'); + } + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage($varPage = 'page', $default = 1) + { + $page = (int) Request::instance()->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) { + return $page; + } + + return $default; + } + + /** + * 自动获取当前的path + * @return string + */ + public static function getCurrentPath() + { + return Request::instance()->baseUrl(); + } + + public function total() + { + if ($this->simple) { + throw new \DomainException('not support total'); + } + return $this->total; + } + + public function listRows() + { + return $this->listRows; + } + + public function currentPage() + { + return $this->currentPage; + } + + public function lastPage() + { + if ($this->simple) { + throw new \DomainException('not support last'); + } + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @return boolean + */ + public function hasPages() + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @param string|null $fragment + * @return $this + */ + public function fragment($fragment) + { + $this->options['fragment'] = $fragment; + return $this; + } + + /** + * 添加URL参数 + * + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (!is_array($key)) { + $queries = [$key => $value]; + } else { + $queries = $key; + } + + foreach ($queries as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @return string + */ + protected function buildFragment() + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + if ($this->simple) { + return [ + 'per_page' => $this->listRows, + 'current_page' => $this->currentPage, + 'has_more' => $this->hasMore, + 'next_item' => $this->nextItem, + 'data' => $this->items->toArray(), + ]; + } else { + return [ + 'total' => $this->total, + 'per_page' => $this->listRows, + 'current_page' => $this->currentPage, + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; + } + + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + $collection = $this->getCollection(); + + $result = call_user_func_array([$collection, $name], $arguments); + + if ($result === $collection) { + return $this; + } + + return $result; + } + +} diff --git a/thinkphp/library/think/Process.php b/thinkphp/library/think/Process.php new file mode 100644 index 0000000..6f3faa3 --- /dev/null +++ b/thinkphp/library/think/Process.php @@ -0,0 +1,1205 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\process\exception\Failed as ProcessFailedException; +use think\process\exception\Timeout as ProcessTimeoutException; +use think\process\pipes\Pipes; +use think\process\pipes\Unix as UnixPipes; +use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; + +class Process +{ + + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + + private $useFileHandles = false; + + /** @var Pipes */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * @var array + */ + public static $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * 构造方法 + * @param string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 + * @throws \RuntimeException + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) + { + if (!function_exists('proc_open')) { + throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->input = $input; + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DS; + $this->pty = false; + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); + $this->options = array_replace([ + 'suppress_errors' => true, + 'binary_pipes' => true, + ], $options); + } + + public function __destruct() + { + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * 运行指令 + * @param callback|null $callback + * @return int + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * 运行指令 + * @param callable|null $callback + * @return self + * @throws \RuntimeException + * @throws ProcessFailedException + */ + public function mustRun($callback = null) + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + if (0 !== $this->run($callback)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * 启动进程并写到 STDIN 输入后返回。 + * @param callable|null $callback + * @throws \RuntimeException + * @throws \RuntimeException + * @throws \LogicException + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + if ($this->outputDisabled && null !== $callback) { + throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if ('\\' === DS && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); + } + $commandline .= '"'; + + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new \RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * 重启进程 + * @param callable|null $callback + * @return Process + * @throws \RuntimeException + * @throws \RuntimeException + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * 等待要终止的进程 + * @param callable|null $callback + * @return int + */ + public function wait($callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DS || !$running; + $this->readPipes(true, $close); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * 获取PID + * @return int|null + * @throws \RuntimeException + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * 将一个 POSIX 信号发送到进程中 + * @param int $signal + * @return Process + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * 禁用从底层过程获取输出和错误输出。 + * @return Process + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new \LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启从底层过程获取输出和错误输出。 + * @return Process + * @throws \RuntimeException + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * 输出是否禁用 + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * 获取当前的输出管道 + * @return string + * @throws \LogicException + * @throws \LogicException + * @api + */ + public function getOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stdout; + } + + /** + * 以增量方式返回的输出结果。 + * @return string + */ + public function getIncrementalOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空输出 + * @return Process + */ + public function clearOutput() + { + $this->stdout = ''; + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * 返回当前的错误输出的过程 (STDERR)。 + * @return string + */ + public function getErrorOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stderr; + } + + /** + * 以增量方式返回 errorOutput + * @return string + */ + public function getIncrementalErrorOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空 errorOutput + * @return Process + */ + public function clearErrorOutput() + { + $this->stderr = ''; + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * 获取退出码 + * @return null|int + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * 获取退出文本 + * @return null|string + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * 检查是否成功 + * @return bool + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * 是否未捕获的信号已被终止子进程 + * @return bool + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * 返回导致子进程终止其执行的数。 + * @return int + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * 检查子进程信号是否已停止 + * @return bool + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * 返回导致子进程停止其执行的数。 + * @return int + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * 检查是否正在运行 + * @return bool + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * 检查是否已开始 + * @return bool + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * 检查是否已终止 + * @return bool + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * 获取当前的状态 + * @return string + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * 终止进程 + */ + public function stop() + { + if ($this->isRunning()) { + if ('\\' === DS && !$this->isSigchildEnabled()) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode > 0) { + throw new \RuntimeException('Unable to kill the process'); + } + } else { + $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); + foreach ($pids as $pid) { + if (is_numeric($pid)) { + posix_kill($pid, 9); + } + } + } + } + + $this->updateStatus(false); + if ($this->processInformation['running']) { + $this->close(); + } + + return $this->exitcode; + } + + /** + * 添加一行输出 + * @param string $line + */ + public function addOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stdout .= $line; + } + + /** + * 添加一行错误输出 + * @param string $line + */ + public function addErrorOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stderr .= $line; + } + + /** + * 获取被执行的指令 + * @return string + */ + public function getCommandLine() +{ + return $this->commandline; + } + + /** + * 设置指令 + * @param string $commandline + * @return self + */ + public function setCommandLine($commandline) +{ + $this->commandline = $commandline; + + return $this; + } + + /** + * 获取超时时间 + * @return float|null + */ + public function getTimeout() +{ + return $this->timeout; + } + + /** + * 获取idle超时时间 + * @return float|null + */ + public function getIdleTimeout() +{ + return $this->idleTimeout; + } + + /** + * 设置超时时间 + * @param int|float|null $timeout + * @return self + */ + public function setTimeout($timeout) +{ + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置idle超时时间 + * @param int|float|null $timeout + * @return self + */ + public function setIdleTimeout($timeout) +{ + if (null !== $timeout && $this->outputDisabled) { + throw new \LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置TTY + * @param bool $tty + * @return self + */ + public function setTty($tty) +{ + if ('\\' === DS && $tty) { + throw new \RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { + throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * 检查是否是tty模式 + * @return bool + */ + public function isTty() +{ + return $this->tty; + } + + /** + * 设置pty模式 + * @param bool $bool + * @return self + */ + public function setPty($bool) +{ + $this->pty = (bool) $bool; + + return $this; + } + + /** + * 是否是pty模式 + * @return bool + */ + public function isPty() +{ + return $this->pty; + } + + /** + * 获取工作目录 + * @return string|null + */ + public function getWorkingDirectory() +{ + if (null === $this->cwd) { + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * 设置工作目录 + * @param string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) +{ + $this->cwd = $cwd; + + return $this; + } + + /** + * 获取环境变量 + * @return array + */ + public function getEnv() +{ + return $this->env; + } + + /** + * 设置环境变量 + * @param array $env + * @return self + */ + public function setEnv(array $env) +{ + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = []; + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * 获取输入 + * @return null|string + */ + public function getInput() +{ + return $this->input; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) +{ + if ($this->isRunning()) { + throw new \LogicException('Input can not be set while the process is running.'); + } + + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 获取proc_open的选项 + * @return array + */ + public function getOptions() +{ + return $this->options; + } + + /** + * 设置proc_open的选项 + * @param array $options + * @return self + */ + public function setOptions(array $options) +{ + $this->options = $options; + + return $this; + } + + /** + * 是否兼容windows + * @return bool + */ + public function getEnhanceWindowsCompatibility() +{ + return $this->enhanceWindowsCompatibility; + } + + /** + * 设置是否兼容windows + * @param bool $enhance + * @return self + */ + public function setEnhanceWindowsCompatibility($enhance) +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 返回是否 sigchild 兼容模式激活 + * @return bool + */ + public function getEnhanceSigchildCompatibility() +{ + return $this->enhanceSigchildCompatibility; + } + + /** + * 激活 sigchild 兼容性模式。 + * @param bool $enhance + * @return self + */ + public function setEnhanceSigchildCompatibility($enhance) +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 是否超时 + */ + public function checkTimeout() +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE); + } + } + + /** + * 是否支持pty + * @return bool + */ + public static function isPtySupported() +{ + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DS) { + return $result = false; + } + + $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); + if (is_resource($proc)) { + proc_close($proc); + + return $result = true; + } + + return $result = false; + } + + /** + * 创建所需的 proc_open 的描述符 + * @return array + */ + private function getDescriptors() +{ + if ('\\' === DS) { + $this->processPipes = WindowsPipes::create($this, $this->input); + } else { + $this->processPipes = UnixPipes::create($this, $this->input); + } + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + + $descriptors = array_merge($descriptors, [['pipe', 'w']]); + + $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * 建立 wait () 使用的回调。 + * @param callable|null $callback + * @return callable + */ + protected function buildCallback($callback) +{ + $out = self::OUT; + $callback = function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * 更新状态 + * @param bool $blocking + */ + protected function updateStatus($blocking) +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + + $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true); + + if (!$this->processInformation['running']) { + $this->close(); + } + } + + /** + * 是否开启 '--enable-sigchild' + * @return bool + */ + protected function isSigchildEnabled() +{ + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * 验证是否超时 + * @param int|float|null $timeout + * @return float|null + */ + private function validateTimeout($timeout) +{ + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * 读取pipes + * @param bool $blocking + * @param bool $close + */ + private function readPipes($blocking, $close) +{ + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } + } + } + + /** + * 捕获退出码 + */ + private function captureExitCode() +{ + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + /** + * 关闭资源 + * @return int 退出码 + */ + private function close() +{ + $this->processPipes->close(); + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } else { + $exitcode = -1; + } + + $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] + && 0 < $this->processInformation['termsig'] + ) { + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + return $this->exitcode; + } + + /** + * 重置数据 + */ + private function resetProcessData() +{ + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * 将一个 POSIX 信号发送到进程中。 + * @param int $signal + * @param bool $throwException + * @return bool + */ + private function doSignal($signal, $throwException) +{ + if (!$this->isRunning()) { + if ($throwException) { + throw new \LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ($this->isSigchildEnabled()) { + if ($throwException) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + return false; + } + + if (true !== @proc_terminate($this->process, $signal)) { + if ($throwException) { + throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + + $this->latestSignal = $signal; + + return true; + } + + /** + * 确保进程已经开启 + * @param string $functionName + */ + private function requireProcessIsStarted($functionName) +{ + if (!$this->isStarted()) { + throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * 确保进程已经终止 + * @param string $functionName + */ + private function requireProcessIsTerminated($functionName) +{ + if (!$this->isTerminated()) { + throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } +} diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php new file mode 100644 index 0000000..5997a76 --- /dev/null +++ b/thinkphp/library/think/Request.php @@ -0,0 +1,1690 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Request +{ + /** + * @var object 对象实例 + */ + protected static $instance; + + protected $method; + /** + * @var string 域名(含协议和端口) + */ + protected $domain; + + /** + * @var string URL地址 + */ + protected $url; + + /** + * @var string 基础URL + */ + protected $baseUrl; + + /** + * @var string 当前执行的文件 + */ + protected $baseFile; + + /** + * @var string 访问的ROOT地址 + */ + protected $root; + + /** + * @var string pathinfo + */ + protected $pathinfo; + + /** + * @var string pathinfo(不含后缀) + */ + protected $path; + + /** + * @var array 当前路由信息 + */ + protected $routeInfo = []; + + /** + * @var array 环境变量 + */ + protected $env; + + /** + * @var array 当前调度信息 + */ + protected $dispatch = []; + protected $module; + protected $controller; + protected $action; + // 当前语言集 + protected $langset; + + /** + * @var array 请求参数 + */ + protected $param = []; + protected $get = []; + protected $post = []; + protected $request = []; + protected $route = []; + protected $put; + protected $session = []; + protected $file = []; + protected $cookie = []; + protected $server = []; + protected $header = []; + + /** + * @var array 资源类型 + */ + protected $mimeType = [ + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', + ]; + + protected $content; + + // 全局过滤规则 + protected $filter; + // Hook扩展方法 + protected static $hook = []; + // 绑定的属性 + protected $bind = []; + // php://input + protected $input; + // 请求缓存 + protected $cache; + // 缓存是否检查 + protected $isCheckCache; + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 构造函数 + * @access protected + * @param array $options 参数 + */ + protected function __construct($options = []) + { + foreach ($options as $name => $item) { + if (property_exists($this, $name)) { + $this->$name = $item; + } + } + if (is_null($this->filter)) { + $this->filter = Config::get('default_filter'); + } + + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function __call($method, $args) + { + if (array_key_exists($method, self::$hook)) { + array_unshift($args, $this); + return call_user_func_array(self::$hook[$method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + + /** + * Hook 方法注入 + * @access public + * @param string|array $method 方法名 + * @param mixed $callback callable + * @return void + */ + public static function hook($method, $callback = null) + { + if (is_array($method)) { + self::$hook = array_merge(self::$hook, $method); + } else { + self::$hook[$method] = $callback; + } + } + + /** + * 初始化 + * @access public + * @param array $options 参数 + * @return \think\Request + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) { + self::$instance = new static($options); + } + return self::$instance; + } + + /** + * 销毁当前请求对象 + * @access public + * @return void + */ + public static function destroy() + { + if (!is_null(self::$instance)) { + self::$instance = null; + } + } + + /** + * 创建一个URL请求 + * @access public + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content + * @return \think\Request + */ + public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + { + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $info = parse_url($uri); + if (isset($info['host'])) { + $server['SERVER_NAME'] = $info['host']; + $server['HTTP_HOST'] = $info['host']; + } + if (isset($info['scheme'])) { + if ('https' === $info['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + if (isset($info['port'])) { + $server['SERVER_PORT'] = $info['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; + } + if (isset($info['user'])) { + $server['PHP_AUTH_USER'] = $info['user']; + } + if (isset($info['pass'])) { + $server['PHP_AUTH_PW'] = $info['pass']; + } + if (!isset($info['path'])) { + $info['path'] = '/'; + } + $options = []; + $options[strtolower($method)] = $params; + $queryString = ''; + if (isset($info['query'])) { + parse_str(html_entity_decode($info['query']), $query); + if (!empty($params)) { + $params = array_replace($query, $params); + $queryString = http_build_query($params, '', '&'); + } else { + $params = $query; + $queryString = $info['query']; + } + } elseif (!empty($params)) { + $queryString = http_build_query($params, '', '&'); + } + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + $options['cookie'] = $cookie; + $options['param'] = $params; + $options['file'] = $files; + $options['server'] = $server; + $options['url'] = $server['REQUEST_URI']; + $options['baseUrl'] = $info['path']; + $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); + $options['method'] = $server['REQUEST_METHOD']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; + $options['content'] = $content; + self::$instance = new self($options); + return self::$instance; + } + + /** + * 设置或获取当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return string + */ + public function domain($domain = null) + { + if (!is_null($domain)) { + $this->domain = $domain; + return $this; + } elseif (!$this->domain) { + $this->domain = $this->scheme() . '://' . $this->host(); + } + return $this->domain; + } + + /** + * 设置或获取当前完整URL 包括QUERY_STRING + * @access public + * @param string|true $url URL地址 true 带域名获取 + * @return string + */ + public function url($url = null) + { + if (!is_null($url) && true !== $url) { + $this->url = $url; + return $this; + } elseif (!$this->url) { + if (IS_CLI) { + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { + $this->url = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $this->url = $_SERVER['REQUEST_URI']; + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { + $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + } else { + $this->url = ''; + } + } + return true === $url ? $this->domain() . $this->url : $this->url; + } + + /** + * 设置或获取当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return string + */ + public function baseUrl($url = null) + { + if (!is_null($url) && true !== $url) { + $this->baseUrl = $url; + return $this; + } elseif (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 设置或获取当前执行的文件 SCRIPT_NAME + * @access public + * @param string $file 当前执行的文件 + * @return string + */ + public function baseFile($file = null) + { + if (!is_null($file) && true !== $file) { + $this->baseFile = $file; + return $this; + } elseif (!$this->baseFile) { + $url = ''; + if (!IS_CLI) { + $script_name = basename($_SERVER['SCRIPT_FILENAME']); + if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['SCRIPT_NAME']; + } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { + $url = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['ORIG_SCRIPT_NAME']; + } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { + $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; + } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { + $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); + } + } + $this->baseFile = $url; + } + return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置或获取URL访问根地址 + * @access public + * @param string $url URL地址 + * @return string + */ + public function root($url = null) + { + if (!is_null($url) && true !== $url) { + $this->root = $url; + return $this; + } elseif (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + return true === $url ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo() + { + if (is_null($this->pathinfo)) { + if (isset($_GET[Config::get('var_pathinfo')])) { + // 判断URL里面是否有兼容模式参数 + $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; + unset($_GET[Config::get('var_pathinfo')]); + } elseif (IS_CLI) { + // CLI模式下 index.php module/controller/action/params/... + $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } + + // 分析PATHINFO信息 + if (!isset($_SERVER['PATH_INFO'])) { + foreach (Config::get('pathinfo_fetch') as $type) { + if (!empty($_SERVER[$type])) { + $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + break; + } + } + } + $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + } + return $this->pathinfo; + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path() + { + if (is_null($this->path)) { + $suffix = Config::get('url_html_suffix'); + $pathinfo = $this->pathinfo(); + if (false === $suffix) { + // 禁止伪静态访问 + $this->path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); + } + } + return $this->path; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext() + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time($float = false) + { + return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + } + + /** + * 当前请求的资源类型 + * @access public + * @return false|string + */ + public function type() + { + $accept = $this->server('HTTP_ACCEPT'); + if (empty($accept)) { + return false; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + return false; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = '') + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 当前的请求类型 + * @access public + * @param bool $method true 获取原始请求类型 + * @return string + */ + public function method($method = false) + { + if (true === $method) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($_POST[Config::get('var_method')])) { + $method = strtoupper($_POST[Config::get('var_method')]); + if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) { + $this->method = $method; + $this->{$this->method}($_POST); + } else { + $this->method = 'POST'; + } + unset($_POST[Config::get('var_method')]); + } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } else { + $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + } + } + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet() + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost() + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut() + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete() + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead() + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch() + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions() + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli() + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi() + { + return strpos(PHP_SAPI, 'cgi') === 0; + } + + /** + * 获取当前请求的参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function param($name = '', $default = null, $filter = '') + { + if (empty($this->mergeParam)) { + $method = $this->method(true); + // 自动获取请求变量 + switch ($method) { + case 'POST': + $vars = $this->post(false); + break; + case 'PUT': + case 'DELETE': + case 'PATCH': + $vars = $this->put(false); + break; + default: + $vars = []; + } + // 当前请求参数和URL地址中的参数合并 + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + $this->mergeParam = true; + } + if (true === $name) { + // 获取包含文件上传信息的数组 + $file = $this->file(); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + return $this->input($data, '', $default, $filter); + } + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置获取路由参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function route($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->route = array_merge($this->route, $name); + } + return $this->input($this->route, $name, $default, $filter); + } + + /** + * 设置获取GET参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function get($name = '', $default = null, $filter = '') + { + if (empty($this->get)) { + $this->get = $_GET; + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->get = array_merge($this->get, $name); + } + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 设置获取POST参数 + * @access public + * @param string $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (empty($this->post)) { + $content = $this->input; + if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { + $this->post = (array) json_decode($content, true); + } else { + $this->post = $_POST; + } + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->post = array_merge($this->post, $name); + } + return $this->input($this->post, $name, $default, $filter); + } + + /** + * 设置获取PUT参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function put($name = '', $default = null, $filter = '') + { + if (is_null($this->put)) { + $content = $this->input; + if (false !== strpos($this->contentType(), 'application/json')) { + $this->put = (array) json_decode($content, true); + } else { + parse_str($content, $this->put); + } + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); + } + + return $this->input($this->put, $name, $default, $filter); + } + + /** + * 设置获取DELETE参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function delete($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 设置获取PATCH参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @param string $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (empty($this->request)) { + $this->request = $_REQUEST; + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->request = array_merge($this->request, $name); + } + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取session数据 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function session($name = '', $default = null, $filter = '') + { + if (empty($this->session)) { + $this->session = Session::get(); + } + if (is_array($name)) { + return $this->session = array_merge($this->session, $name); + } + return $this->input($this->session, $name, $default, $filter); + } + + /** + * 获取cookie参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie($name = '', $default = null, $filter = '') + { + if (empty($this->cookie)) { + $this->cookie = Cookie::get(); + } + if (is_array($name)) { + return $this->cookie = array_merge($this->cookie, $name); + } elseif (!empty($name)) { + $data = Cookie::has($name) ? Cookie::get($name) : $default; + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function server($name = '', $default = null, $filter = '') + { + if (empty($this->server)) { + $this->server = $_SERVER; + } + if (is_array($name)) { + return $this->server = array_merge($this->server, $name); + } + return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 获取上传的文件信息 + * @access public + * @param string|array $name 名称 + * @return null|array|\think\File + */ + public function file($name = '') + { + if (empty($this->file)) { + $this->file = isset($_FILES) ? $_FILES : []; + } + if (is_array($name)) { + return $this->file = array_merge($this->file, $name); + } + $files = $this->file; + if (!empty($files)) { + // 处理上传文件 + $array = []; + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + for ($i = 0; $i < $count; $i++) { + if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { + continue; + } + $temp['key'] = $key; + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { + continue; + } + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + } + if (strpos($name, '.')) { + list($name, $sub) = explode('.', $name); + } + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + return; + } + + /** + * 获取环境变量 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function env($name = '', $default = null, $filter = '') + { + if (empty($this->env)) { + $this->env = $_ENV; + } + if (is_array($name)) { + return $this->env = array_merge($this->env, $name); + } + return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string|array $name header名称 + * @param string $default 默认值 + * @return string + */ + public function header($name = '', $default = null) + { + if (empty($this->header)) { + $header = []; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->server ?: $_SERVER; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; + } + } + $this->header = array_change_key_case($header); + } + if (is_array($name)) { + return $this->header = array_merge($this->header, $name); + } + if ('' === $name) { + return $this->header; + } + $name = str_replace('_', '-', strtolower($name)); + return isset($this->header[$name]) ? $this->header[$name] : $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input($data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + list($name, $type) = explode('/', $name); + } else { + $type = 's'; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + // 无输入数据,返回默认值 + return $default; + } + } + if (is_object($data)) { + return $data; + } + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } else { + $this->filter = $filter; + } + } + + protected function getFilter($filter, $default) + { + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter) && false === strpos($filter, '/')) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + return $filter; + } + + /** + * 递归过滤给定的值 + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + private function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (false !== strpos($filter, '/')) { + // 正则过滤 + if (!preg_match($filter, $value)) { + // 匹配不成功返回默认值 + $value = $default; + break; + } + } elseif (!empty($filter)) { + // filter函数不存在时, 则使用filter_var进行过滤 + // filter为非整形值时, 调用filter_id取得过滤id + $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); + if (false === $value) { + $value = $default; + break; + } + } + } + } + return $this->filterExp($value); + } + + /** + * 过滤表单中的表达式 + * @param string $value + * @return void + */ + public function filterExp(&$value) + { + // 过滤查询特殊字符 + if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) { + $value .= ' '; + } + // TODO 其他安全过滤 + } + + /** + * 强制类型转换 + * @param string $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, $type) + { + switch (strtolower($type)) { + // 数组 + case 'a': + $data = (array) $data; + break; + // 数字 + case 'd': + $data = (int) $data; + break; + // 浮点 + case 'f': + $data = (float) $data; + break; + // 布尔 + case 'b': + $data = (boolean) $data; + break; + // 字符串 + case 's': + default: + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + } + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return mixed + */ + public function has($name, $type = 'param', $checkEmpty = false) + { + if (empty($this->$type)) { + $param = $this->$type(); + } else { + $param = $this->$type; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function only($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + $item = []; + foreach ($name as $key) { + if (isset($param[$key])) { + $item[$key] = $param[$key]; + } + } + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl() + { + $server = array_merge($_SERVER, $this->server); + if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { + return true; + } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { + return true; + } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { + return true; + } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { + return true; + } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) { + return true; + } + return false; + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax($ajax = false) + { + $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); + $result = ('xmlhttprequest' == $value) ? true : false; + if (true === $ajax) { + return $result; + } else { + $result = $this->param(Config::get('var_ajax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax($pjax = false) + { + $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + if (true === $pjax) { + return $result; + } else { + $result = $this->param(Config::get('var_pjax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 获取客户端IP地址 + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ + public function ip($type = 0, $adv = true) + { + $type = $type ? 1 : 0; + static $ip = null; + if (null !== $ip) { + return $ip[$type]; + } + + $httpAgentIp = Config::get('http_agent_ip'); + + if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { + $ip = $_SERVER[$httpAgentIp]; + } elseif ($adv) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $pos = array_search('unknown', $arr); + if (false !== $pos) { + unset($arr[$pos]); + } + $ip = trim(current($arr)); + } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + // IP地址合法验证 + $long = sprintf("%u", ip2long($ip)); + $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + return $ip[$type]; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile() + { + if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { + return true; + } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { + return true; + } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { + return true; + } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } else { + return false; + } + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme() + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query() + { + return $this->server('QUERY_STRING'); + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host($strict = false) + { + if (isset($_SERVER['HTTP_X_REAL_HOST'])) { + $host = $_SERVER['HTTP_X_REAL_HOST']; + } else { + $host = $this->server('HTTP_HOST'); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return integer + */ + public function port() + { + return $this->server('SERVER_PORT'); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return integer + */ + public function protocol() + { + return $this->server('SERVER_PROTOCOL'); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return integer + */ + public function remotePort() + { + return $this->server('REMOTE_PORT'); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + return ''; + } + + /** + * 获取当前请求的路由信息 + * @access public + * @param array $route 路由名称 + * @return array + */ + public function routeInfo($route = []) + { + if (!empty($route)) { + $this->routeInfo = $route; + } else { + return $this->routeInfo; + } + } + + /** + * 设置或者获取当前请求的调度信息 + * @access public + * @param array $dispatch 调度信息 + * @return array + */ + public function dispatch($dispatch = null) + { + if (!is_null($dispatch)) { + $this->dispatch = $dispatch; + } + return $this->dispatch; + } + + /** + * 设置或者获取当前的模块名 + * @access public + * @param string $module 模块名 + * @return string|Request + */ + public function module($module = null) + { + if (!is_null($module)) { + $this->module = $module; + return $this; + } else { + return $this->module ?: ''; + } + } + + /** + * 设置或者获取当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return string|Request + */ + public function controller($controller = null) + { + if (!is_null($controller)) { + $this->controller = $controller; + return $this; + } else { + return $this->controller ?: ''; + } + } + + /** + * 设置或者获取当前的操作名 + * @access public + * @param string $action 操作名 + * @return string|Request + */ + public function action($action = null) + { + if (!is_null($action) && !is_bool($action)) { + $this->action = $action; + return $this; + } else { + $name = $this->action ?: ''; + return true === $action ? $name : strtolower($name); + } + } + + /** + * 设置或者获取当前的语言 + * @access public + * @param string $lang 语言名 + * @return string|Request + */ + public function langset($lang = null) + { + if (!is_null($lang)) { + $this->langset = $lang; + return $this; + } else { + return $this->langset ?: ''; + } + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent() + { + if (is_null($this->content)) { + $this->content = $this->input; + } + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function token($name = '__token__', $type = 'md5') + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + if ($this->isAjax()) { + header($name . ': ' . $token); + } + Session::set($name, $token); + return $token; + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function cache($key, $expire = null, $except = [], $tag = null) + { + if (!is_array($except)) { + $tag = $except; + $except = []; + } + + if (false !== $key && $this->isGet() && !$this->isCheckCache) { + // 标记请求缓存检查 + $this->isCheckCache = true; + if (false === $expire) { + // 关闭当前缓存 + return; + } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); + } else { + return; + } + } + if (isset($fun)) { + $key = $fun($key); + } + + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { + // 读取缓存 + $response = Response::create()->code(304); + throw new \think\exception\HttpResponseException($response); + } elseif (Cache::has($key)) { + list($content, $header) = Cache::get($key); + $response = Response::create($content)->header($header); + throw new \think\exception\HttpResponseException($response); + } else { + $this->cache = [$key, $expire, $tag]; + } + } + } + + /** + * 读取请求缓存设置 + * @access public + * @return array + */ + public function getCache() + { + return $this->cache; + } + + /** + * 设置当前请求绑定的对象实例 + * @access public + * @param string|array $name 绑定的对象标识 + * @param mixed $obj 绑定的对象实例 + * @return mixed + */ + public function bind($name, $obj = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $obj; + } + } + + public function __set($name, $value) + { + $this->bind[$name] = $value; + } + + public function __get($name) + { + return isset($this->bind[$name]) ? $this->bind[$name] : null; + } + + public function __isset($name) + { + return isset($this->bind[$name]); + } +} diff --git a/thinkphp/library/think/Response.php b/thinkphp/library/think/Response.php new file mode 100644 index 0000000..c5c1520 --- /dev/null +++ b/thinkphp/library/think/Response.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\response\Json as JsonResponse; +use think\response\Jsonp as JsonpResponse; +use think\response\Redirect as RedirectResponse; +use think\response\View as ViewResponse; +use think\response\Xml as XmlResponse; + +class Response +{ + // 原始数据 + protected $data; + + // 当前的contentType + protected $contentType = 'text/html'; + + // 字符集 + protected $charset = 'utf-8'; + + //状态 + protected $code = 200; + + // 输出参数 + protected $options = []; + // header参数 + protected $header = []; + + protected $content = null; + + /** + * 构造函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 + */ + public function __construct($data = '', $code = 200, array $header = [], $options = []) + { + $this->data($data); + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->contentType($this->contentType, $this->charset); + $this->header = array_merge($this->header, $header); + $this->code = $code; + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse + */ + public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + if (class_exists($class)) { + $response = new $class($data, $code, $header, $options); + } else { + $response = new static($data, $code, $header, $options); + } + + return $response; + } + + /** + * 发送数据到客户端 + * @access public + * @return mixed + * @throws \InvalidArgumentException + */ + public function send() + { + // 监听response_send + Hook::listen('response_send', $this); + + // 处理输出数据 + $data = $this->getContent(); + + // Trace调试注入 + if (Env::get('app_trace', Config::get('app_trace'))) { + Debug::inject($this, $data); + } + + if (200 == $this->code) { + $cache = Request::instance()->getCache(); + if ($cache) { + $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; + $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; + Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + } + } + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + if (is_null($val)) { + header($name); + } else { + header($name . ':' . $val); + } + } + } + + echo $data; + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + + // 监听response_end + Hook::listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + Session::flush(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options($options = []) + { + $this->options = array_merge($this->options, $options); + return $this; + } + + /** + * 输出数据设置 + * @access public + * @param mixed $data 输出数据 + * @return $this + */ + public function data($data) + { + $this->data = $data; + return $this; + } + + /** + * 设置响应头 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($name, $value = null) + { + if (is_array($name)) { + $this->header = array_merge($this->header, $name); + } else { + $this->header[$name] = $value; + } + return $this; + } + + /** + * 设置页面输出内容 + * @param $content + * @return $this + */ + public function content($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * 发送HTTP状态 + * @param integer $code 状态码 + * @return $this + */ + public function code($code) + { + $this->code = $code; + return $this; + } + + /** + * LastModified + * @param string $time + * @return $this + */ + public function lastModified($time) + { + $this->header['Last-Modified'] = $time; + return $this; + } + + /** + * Expires + * @param string $time + * @return $this + */ + public function expires($time) + { + $this->header['Expires'] = $time; + return $this; + } + + /** + * ETag + * @param string $eTag + * @return $this + */ + public function eTag($eTag) + { + $this->header['ETag'] = $eTag; + return $this; + } + + /** + * 页面缓存控制 + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl($cache) + { + $this->header['Cache-control'] = $cache; + return $this; + } + + /** + * 页面输出类型 + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType($contentType, $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + return $this; + } + + /** + * 获取头部信息 + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader($name = '') + { + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } else { + return $this->header; + } + } + + /** + * 获取原始数据 + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @return mixed + */ + public function getContent() + { + if (null == $this->content) { + $content = $this->output($this->data); + + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + } + return $this->content; + } + + /** + * 获取状态码 + * @return integer + */ + public function getCode() + { + return $this->code; + } +} diff --git a/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php new file mode 100644 index 0000000..ab53aa2 --- /dev/null +++ b/thinkphp/library/think/Route.php @@ -0,0 +1,1645 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\HttpException; + +class Route +{ + // 路由规则 + private static $rules = [ + 'get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => [], + ]; + + // REST路由操作方法定义 + private static $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '/:id/edit', 'edit'], + 'read' => ['get', '/:id', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/:id', 'update'], + 'delete' => ['delete', '/:id', 'delete'], + ]; + + // 不同请求类型的方法前缀 + private static $methodPrefix = [ + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', + ]; + + // 子域名 + private static $subDomain = ''; + // 域名绑定 + private static $bind = []; + // 当前分组信息 + private static $group = []; + // 当前子域名绑定 + private static $domainBind; + private static $domainRule; + // 当前域名 + private static $domain; + // 当前路由执行过程中的参数 + private static $option = []; + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return void + */ + public static function pattern($name = null, $rule = '') + { + if (is_array($name)) { + self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); + } else { + self::$rules['pattern'][$name] = $rule; + } + } + + /** + * 注册子域名部署规则 + * @access public + * @param string|array $domain 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function domain($domain, $rule = '', $option = [], $pattern = []) + { + if (is_array($domain)) { + foreach ($domain as $key => $item) { + self::domain($key, $item, $option, $pattern); + } + } elseif ($rule instanceof \Closure) { + // 执行闭包 + self::setDomain($domain); + call_user_func_array($rule, []); + self::setDomain(null); + } elseif (is_array($rule)) { + self::setDomain($domain); + self::group('', function () use ($rule) { + // 动态注册域名的路由规则 + self::registerRules($rule); + }, $option, $pattern); + self::setDomain(null); + } else { + self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; + } + } + + private static function setDomain($domain) + { + self::$domain = $domain; + } + + /** + * 设置路由绑定 + * @access public + * @param mixed $bind 绑定信息 + * @param string $type 绑定类型 默认为module 支持 namespace class controller + * @return mixed + */ + public static function bind($bind, $type = 'module') + { + self::$bind = ['type' => $type, $type => $bind]; + } + + /** + * 设置或者获取路由标识 + * @access public + * @param string|array $name 路由命名标识 数组表示批量设置 + * @param array $value 路由地址及变量信息 + * @return array + */ + public static function name($name = '', $value = null) + { + if (is_array($name)) { + return self::$rules['name'] = $name; + } elseif ('' === $name) { + return self::$rules['name']; + } elseif (!is_null($value)) { + self::$rules['name'][strtolower($name)][] = $value; + } else { + $name = strtolower($name); + return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; + } + } + + /** + * 读取路由绑定 + * @access public + * @param string $type 绑定类型 + * @return mixed + */ + public static function getBind($type) + { + return isset(self::$bind[$type]) ? self::$bind[$type] : null; + } + + /** + * 导入配置文件的路由规则 + * @access public + * @param array $rule 路由规则 + * @param string $type 请求类型 + * @return void + */ + public static function import(array $rule, $type = '*') + { + // 检查域名部署 + if (isset($rule['__domain__'])) { + self::domain($rule['__domain__']); + unset($rule['__domain__']); + } + + // 检查变量规则 + if (isset($rule['__pattern__'])) { + self::pattern($rule['__pattern__']); + unset($rule['__pattern__']); + } + + // 检查路由别名 + if (isset($rule['__alias__'])) { + self::alias($rule['__alias__']); + unset($rule['__alias__']); + } + + // 检查资源路由 + if (isset($rule['__rest__'])) { + self::resource($rule['__rest__']); + unset($rule['__rest__']); + } + + self::registerRules($rule, strtolower($type)); + } + + // 批量注册路由 + protected static function registerRules($rules, $type = '*') + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (empty($val)) { + continue; + } + if (is_string($key) && 0 === strpos($key, '[')) { + $key = substr($key, 1, -1); + self::group($key, $val); + } elseif (is_array($val)) { + self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + } else { + self::setRule($key, $val, $type); + } + } + } + + /** + * 注册路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) + { + $group = self::getGroup('name'); + + if (!is_null($group)) { + // 路由分组 + $option = array_merge(self::getGroup('option'), $option); + $pattern = array_merge(self::getGroup('pattern'), $pattern); + } + + $type = strtolower($type); + + if (strpos($type, '|')) { + $option['method'] = $type; + $type = '*'; + } + if (is_array($rule) && empty($route)) { + foreach ($rule as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, $val[1]); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $option1 = null; + $pattern1 = null; + $route = $val; + } + self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group); + } + } else { + self::setRule($rule, $route, $type, $option, $pattern, $group); + } + + } + + /** + * 设置路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param string $group 所属分组 + * @return void + */ + protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') + { + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } + if (!isset($option['complete_match'])) { + if (Config::get('route_complete_match')) { + $option['complete_match'] = true; + } elseif ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + + if ('$' == substr($rule, -1, 1)) { + $rule = substr($rule, 0, -1); + } + + if ('/' != $rule || $group) { + $rule = trim($rule, '/'); + } + $vars = self::parseVar($rule); + if (isset($name)) { + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + $suffix = isset($option['ext']) ? $option['ext'] : null; + self::name($name, [$key, $vars, self::$domain, $suffix]); + } + if (isset($option['modular'])) { + $route = $option['modular'] . '/' . $route; + } + if ($group) { + if ('*' != $type) { + $option['method'] = $type; + } + if (self::$domain) { + self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + } else { + if ('*' != $type && isset(self::$rules['*'][$rule])) { + unset(self::$rules['*'][$rule]); + } + if (self::$domain) { + self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + if ('*' == $type) { + // 注册路由快捷方式 + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) { + self::$rules['domain'][self::$domain][$method][$rule] = true; + } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) { + self::$rules[$method][$rule] = true; + } + } + } + } + } + + /** + * 设置当前执行的参数信息 + * @access public + * @param array $options 参数信息 + * @return mixed + */ + protected static function setOption($options = []) + { + self::$option[] = $options; + } + + /** + * 获取当前执行的所有参数信息 + * @access public + * @return array + */ + public static function getOption() + { + return self::$option; + } + + /** + * 获取当前的分组信息 + * @access public + * @param string $type 分组信息名称 name option pattern + * @return mixed + */ + public static function getGroup($type) + { + if (isset(self::$group[$type])) { + return self::$group[$type]; + } else { + return 'name' == $type ? null : []; + } + } + + /** + * 设置当前的路由分组 + * @access public + * @param string $name 分组名称 + * @param array $option 分组路由参数 + * @param array $pattern 分组变量规则 + * @return void + */ + public static function setGroup($name, $option = [], $pattern = []) + { + self::$group['name'] = $name; + self::$group['option'] = $option ?: []; + self::$group['pattern'] = $pattern ?: []; + } + + /** + * 注册路由分组 + * @access public + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $routes 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function group($name, $routes, $option = [], $pattern = []) + { + if (is_array($name)) { + $option = $name; + $name = isset($option['name']) ? $option['name'] : ''; + } + // 分组 + $currentGroup = self::getGroup('name'); + if ($currentGroup) { + $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); + } + if (!empty($name)) { + if ($routes instanceof \Closure) { + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + if ($currentGroup != $name) { + self::$rules['*'][$name]['route'] = ''; + self::$rules['*'][$name]['var'] = self::parseVar($name); + self::$rules['*'][$name]['option'] = $option; + self::$rules['*'][$name]['pattern'] = $pattern; + } + } else { + $item = []; + $completeMatch = Config::get('route_complete_match'); + foreach ($routes as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $route = $val; + } + + $options = isset($option1) ? $option1 : $option; + $patterns = isset($pattern1) ? $pattern1 : $pattern; + if ('$' == substr($key, -1, 1)) { + // 是否完整匹配 + $options['complete_match'] = true; + $key = substr($key, 0, -1); + } elseif ($completeMatch) { + $options['complete_match'] = true; + } + $key = trim($key, '/'); + $vars = self::parseVar($key); + $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; + // 设置路由标识 + $suffix = isset($options['ext']) ? $options['ext'] : null; + self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); + } + self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; + } + + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (!isset(self::$rules[$method][$name])) { + self::$rules[$method][$name] = true; + } elseif (is_array(self::$rules[$method][$name])) { + self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); + } + } + + } elseif ($routes instanceof \Closure) { + // 闭包注册 + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + } else { + // 批量注册路由 + self::rule($routes, '', '*', $option, $pattern); + } + } + + /** + * 注册路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function any($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, '*', $option, $pattern); + } + + /** + * 注册GET路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function get($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'GET', $option, $pattern); + } + + /** + * 注册POST路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function post($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'POST', $option, $pattern); + } + + /** + * 注册PUT路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function put($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PUT', $option, $pattern); + } + + /** + * 注册DELETE路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function delete($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'DELETE', $option, $pattern); + } + + /** + * 注册PATCH路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function patch($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PATCH', $option, $pattern); + } + + /** + * 注册资源路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function resource($rule, $route = '', $option = [], $pattern = []) + { + if (is_array($rule)) { + foreach ($rule as $key => $val) { + if (is_array($val)) { + list($val, $option, $pattern) = array_pad($val, 3, []); + } + self::resource($key, $val, $option, $pattern); + } + } else { + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + foreach ($array as $val) { + $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); + } + $rule = implode('/', $item) . '/' . $last; + } + // 注册资源路由 + foreach (self::$rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { + $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); + } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { + $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); + } + $item = ltrim($rule . $val[1], '/'); + $option['rest'] = $key; + self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); + } + } + } + + /** + * 注册控制器路由 操作方法对应不同的请求后缀 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function controller($rule, $route = '', $option = [], $pattern = []) + { + foreach (self::$methodPrefix as $type => $val) { + self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + } + } + + /** + * 注册别名路由 + * @access public + * @param string|array $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return void + */ + public static function alias($rule = null, $route = '', $option = []) + { + if (is_array($rule)) { + self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); + } else { + self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; + } + } + + /** + * 设置不同请求类型下面的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return void + */ + public static function setMethodPrefix($method, $prefix = '') + { + if (is_array($method)) { + self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); + } else { + self::$methodPrefix[strtolower($method)] = $prefix; + } + } + + /** + * rest方法定义和修改 + * @access public + * @param string|array $name 方法名称 + * @param array|bool $resource 资源 + * @return void + */ + public static function rest($name, $resource = []) + { + if (is_array($name)) { + self::$rest = $resource ? $name : array_merge(self::$rest, $name); + } else { + self::$rest[$name] = $resource; + } + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return void + */ + public static function miss($route, $method = '*', $option = []) + { + self::rule('__miss__', $route, $method, $option, []); + } + + /** + * 注册一个自动解析的URL路由 + * @access public + * @param string $route 路由地址 + * @return void + */ + public static function auto($route) + { + self::rule('__auto__', $route, '*', [], []); + } + + /** + * 获取或者批量设置路由定义 + * @access public + * @param mixed $rules 请求类型或者路由定义数组 + * @return array + */ + public static function rules($rules = '') + { + if (is_array($rules)) { + self::$rules = $rules; + } elseif ($rules) { + return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; + } else { + $rules = self::$rules; + unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); + return $rules; + } + } + + /** + * 检测子域名部署 + * @access public + * @param Request $request Request请求对象 + * @param array $currentRules 当前路由规则 + * @param string $method 请求类型 + * @return void + */ + public static function checkDomain($request, &$currentRules, $method = 'get') + { + // 域名规则 + $rules = self::$rules['domain']; + // 开启子域名部署 支持二级和三级域名 + if (!empty($rules)) { + $host = $request->host(true); + if (isset($rules[$host])) { + // 完整域名或者IP配置 + $item = $rules[$host]; + } else { + $rootDomain = Config::get('url_domain_root'); + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); + } else { + $domain = explode('.', $host, -2); + } + // 子域名配置 + if (!empty($domain)) { + // 当前子域名 + $subDomain = implode('.', $domain); + self::$subDomain = $subDomain; + $domain2 = array_pop($domain); + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + if ($subDomain && isset($rules[$subDomain])) { + // 子域名配置 + $item = $rules[$subDomain]; + } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $rules['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($rules['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $rules['*']; + $panDomain = $domain2; + } + } + } + } + if (!empty($item)) { + if (isset($panDomain)) { + // 保存当前泛域名 + $request->route(['__domain__' => $panDomain]); + } + if (isset($item['[bind]'])) { + // 解析子域名部署规则 + list($rule, $option, $pattern) = $item['[bind]']; + if (!empty($option['https']) && !$request->isSsl()) { + // https检测 + throw new HttpException(404, 'must use https request:' . $host); + } + + if (strpos($rule, '?')) { + // 传入其它参数 + $array = parse_url($rule); + $result = $array['path']; + parse_str($array['query'], $params); + if (isset($panDomain)) { + $pos = array_search('*', $params); + if (false !== $pos) { + // 泛域名作为参数 + $params[$pos] = $panDomain; + } + } + $_GET = array_merge($_GET, $params); + } else { + $result = $rule; + } + + if (0 === strpos($result, '\\')) { + // 绑定到命名空间 例如 \app\index\behavior + self::$bind = ['type' => 'namespace', 'namespace' => $result]; + } elseif (0 === strpos($result, '@')) { + // 绑定到类 例如 @app\index\controller\User + self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; + } else { + // 绑定到模块/控制器 例如 index/user + self::$bind = ['type' => 'module', 'module' => $result]; + } + self::$domainBind = true; + } else { + self::$domainRule = $item; + $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; + } + } + } + } + + /** + * 检测URL路由 + * @access public + * @param Request $request Request请求对象 + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $checkDomain 是否检测域名规则 + * @return false|array + */ + public static function check($request, $url, $depr = '/', $checkDomain = false) + { + //检查解析缓存 + if (!App::$debug && Config::get('route_check_cache')) { + $key = self::getCheckCacheKey($request); + if (Cache::has($key)) { + list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key); + return self::parseRule($rule, $route, $pathinfo, $option, $matches, true); + } + } + + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace($depr, '|', $url); + + if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { + // 检测路由别名 + $result = self::checkRouteAlias($request, $url, $depr); + if (false !== $result) { + return $result; + } + } + $method = strtolower($request->method()); + // 获取当前请求类型的路由规则 + $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; + // 检测域名部署 + if ($checkDomain) { + self::checkDomain($request, $rules, $method); + } + // 检测URL绑定 + $return = self::checkUrlBind($url, $rules, $depr); + if (false !== $return) { + return $return; + } + if ('|' != $url) { + $url = rtrim($url, '|'); + } + $item = str_replace('|', '/', $url); + if (isset($rules[$item])) { + // 静态路由规则检测 + $rule = $rules[$item]; + if (true === $rule) { + $rule = self::getRouteExpress($item); + } + if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { + self::setOption($rule['option']); + return self::parseRule($item, $rule['route'], $url, $rule['option']); + } + } + + // 路由规则检测 + if (!empty($rules)) { + return self::checkRoute($request, $rules, $url, $depr); + } + return false; + } + + private static function getRouteExpress($key) + { + return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; + } + + /** + * 检测路由规则 + * @access private + * @param Request $request + * @param array $rules 路由规则 + * @param string $url URL地址 + * @param string $depr URL分割符 + * @param string $group 路由分组名 + * @param array $options 路由参数(分组) + * @return mixed + */ + private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) + { + foreach ($rules as $key => $item) { + if (true === $item) { + $item = self::getRouteExpress($key); + } + if (!isset($item['rule'])) { + continue; + } + $rule = $item['rule']; + $route = $item['route']; + $vars = $item['var']; + $option = $item['option']; + $pattern = $item['pattern']; + + // 检查参数有效性 + if (!self::checkOption($option, $request)) { + continue; + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); + } + + if (is_array($rule)) { + // 分组路由 + $pos = strpos(str_replace('<', ':', $key), ':'); + if (false !== $pos) { + $str = substr($key, 0, $pos); + } else { + $str = $key; + } + if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + continue; + } + self::setOption($option); + $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); + if (false !== $result) { + return $result; + } + } elseif ($route) { + if ('__miss__' == $rule || '__auto__' == $rule) { + // 指定特殊路由 + $var = trim($rule, '__'); + ${$var} = $item; + continue; + } + if ($group) { + $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + self::setOption($option); + if (isset($options['bind_model']) && isset($option['bind_model'])) { + $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); + } + $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); + if (false !== $result) { + return $result; + } + } + } + if (isset($auto)) { + // 自动解析URL地址 + return self::parseUrl($auto['route'] . '/' . $url, $depr); + } elseif (isset($miss)) { + // 未匹配所有路由的路由规则处理 + return self::parseRule('', $miss['route'], $url, $miss['option']); + } + return false; + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkRouteAlias($request, $url, $depr) + { + $array = explode('|', $url); + $alias = array_shift($array); + $item = self::$rules['alias'][$alias]; + + if (is_array($item)) { + list($rule, $option) = $item; + $action = $array[0]; + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } + } else { + $rule = $item; + } + $bind = implode('|', $array); + // 参数有效性检查 + if (isset($option) && !self::checkOption($option, $request)) { + // 路由不匹配 + return false; + } elseif (0 === strpos($rule, '\\')) { + // 路由到类 + return self::bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { + // 路由到控制器类 + return self::bindToController($bind, substr($rule, 1), $depr); + } else { + // 路由到模块/控制器 + return self::bindToModule($bind, $rule, $depr); + } + } + + /** + * 检测URL绑定 + * @access private + * @param string $url URL地址 + * @param array $rules 路由规则 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkUrlBind(&$url, &$rules, $depr = '/') + { + if (!empty(self::$bind)) { + $type = self::$bind['type']; + $bind = self::$bind[$type]; + // 记录绑定信息 + App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); + // 如果有URL绑定 则进行绑定检测 + switch ($type) { + case 'class': + // 绑定到类 + return self::bindToClass($url, $bind, $depr); + case 'controller': + // 绑定到控制器类 + return self::bindToController($url, $bind, $depr); + case 'namespace': + // 绑定到命名空间 + return self::bindToNamespace($url, $bind, $depr); + } + } + return false; + } + + /** + * 绑定到类 + * @access public + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToClass($url, $class, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; + } + + /** + * 绑定到命名空间 + * @access public + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToNamespace($url, $namespace, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); + $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); + if (!empty($array[2])) { + self::parseUrlParams($array[2]); + } + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; + } + + /** + * 绑定到控制器类 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToController($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; + } + + /** + * 绑定到模块/控制器 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToModule($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'module', 'module' => $controller . '/' . $action]; + } + + /** + * 路由参数有效性检查 + * @access private + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + private static function checkOption($option, $request) + { + if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) + || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 + || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 + || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 + || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 + || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 + || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 + || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 + || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 + ) { + return false; + } + return true; + } + + /** + * 检测路由规则 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $pattern 变量规则 + * @param array $option 路由参数 + * @param string $depr URL分隔符(全局) + * @return array|false + */ + private static function checkRule($rule, $route, $url, $pattern, $option, $depr) + { + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); + } + + $len1 = substr_count($url, '|'); + $len2 = substr_count($rule, '/'); + // 多余参数是否合并 + $merge = !empty($option['merge_extra_vars']); + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } + + if ($len1 >= $len2 || strpos($rule, '[')) { + if (!empty($option['complete_match'])) { + // 完整匹配 + if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { + return false; + } + } + $pattern = array_merge(self::$rules['pattern'], $pattern); + if (false !== $match = self::match($url, $rule, $pattern)) { + // 匹配到路由规则 + return self::parseRule($rule, $route, $url, $option, $match); + } + } + return false; + } + + /** + * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... + * @access public + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $autoSearch 是否自动深度搜索控制器 + * @return array + */ + public static function parseUrl($url, $depr = '/', $autoSearch = false) + { + + if (isset(self::$bind['module'])) { + $bind = str_replace('/', $depr, self::$bind['module']); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + $url = str_replace($depr, '|', $url); + list($path, $var) = self::parseUrlPath($url); + $route = [null, null, null]; + if (isset($path)) { + // 解析模块 + $module = Config::get('app_multi_module') ? array_shift($path) : null; + if ($autoSearch) { + // 自动搜索控制器 + $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); + $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; + $item = []; + $find = false; + foreach ($path as $val) { + $item[] = $val; + $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; + $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= DS . Loader::parseName($val); + } + } + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + // 解析额外参数 + self::parseUrlParams(empty($path) ? '' : implode('|', $path)); + // 封装路由 + $route = [$module, $controller, $action]; + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + $name2 = ''; + if (empty($module) || isset($bind) && $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + } + return ['type' => 'module', 'module' => $route]; + } + + /** + * 解析URL的pathinfo参数和变量 + * @access private + * @param string $url URL地址 + * @return array + */ + private static function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } else { + $path = [$url]; + } + return [$path, $var]; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param string $rule 路由规则 + * @param array $pattern 变量规则 + * @return array|false + */ + private static function match($url, $rule, $pattern) + { + $m2 = explode('/', $rule); + $m1 = explode('|', $url); + + $var = []; + foreach ($m2 as $key => $val) { + // val中定义了多个变量 + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + $value = []; + $replace = []; + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; + } else { + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; + } + $value[] = $name; + } + $val = str_replace($matches[0], $replace, $val); + if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { + array_shift($match); + foreach ($value as $k => $name) { + if (isset($match[$k])) { + $var[$name] = $match[$k]; + } + } + continue; + } else { + return false; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $val = substr($val, 1, -1); + $optional = true; + } else { + $optional = false; + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + if (!$optional && !isset($m1[$key])) { + return false; + } + if (isset($m1[$key]) && isset($pattern[$name])) { + // 检查变量规则 + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } + } + $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; + } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { + return false; + } + } + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 解析规则路由 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $pathinfo URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @param bool $fromCache 通过缓存解析 + * @return array + */ + private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false) + { + $request = Request::instance(); + + //保存解析缓存 + if (Config::get('route_check_cache') && !$fromCache) { + try { + $key = self::getCheckCacheKey($request); + Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]); + } catch (\Exception $e) { + + } + } + + // 解析路由规则 + if ($rule) { + $rule = explode('/', $rule); + // 获取URL地址中的参数 + $paths = explode('|', $pathinfo); + foreach ($rule as $item) { + $fun = ''; + if (0 === strpos($item, '[:')) { + $item = substr($item, 1, -1); + } + if (0 === strpos($item, ':')) { + $var = substr($item, 1); + $matches[$var] = array_shift($paths); + } else { + // 过滤URL中的静态变量 + array_shift($paths); + } + } + } else { + $paths = explode('|', $pathinfo); + } + + // 获取路由地址规则 + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + foreach ($matches as $key => $val) { + if (false !== strpos($route, ':' . $key)) { + $route = str_replace(':' . $key, $val, $route); + } + } + } + + // 绑定模型数据 + if (isset($option['bind_model'])) { + $bind = []; + foreach ($option['bind_model'] as $key => $val) { + if ($val instanceof \Closure) { + $result = call_user_func_array($val, [$matches]); + } else { + if (is_array($val)) { + $fields = explode('&', $val[1]); + $model = $val[0]; + $exception = isset($val[2]) ? $val[2] : true; + } else { + $fields = ['id']; + $model = $val; + $exception = true; + } + $where = []; + $match = true; + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[$field] = $matches[$field]; + } + } + if ($match) { + $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + if (!empty($result)) { + $bind[$key] = $result; + } + } + $request->bind($bind); + } + + if (!empty($option['response'])) { + Hook::add('response_send', $option['response']); + } + + // 解析额外参数 + self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); + // 记录匹配的路由信息 + $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); + + // 检测路由after行为 + if (!empty($option['after_behavior'])) { + if ($option['after_behavior'] instanceof \Closure) { + $result = call_user_func_array($option['after_behavior'], []); + } else { + foreach ((array) $option['after_behavior'] as $behavior) { + $result = Hook::exec($behavior, ''); + if (!is_null($result)) { + break; + } + } + } + // 路由规则重定向 + if ($result instanceof Response) { + return ['type' => 'response', 'response' => $result]; + } elseif (is_array($result)) { + return $result; + } + } + + if ($route instanceof \Closure) { + // 执行闭包 + $result = ['type' => 'function', 'function' => $route]; + } elseif (0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + list($path, $var) = self::parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = ['type' => 'method', 'method' => $method, 'var' => $var]; + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $route = substr($route, 1); + list($route, $var) = self::parseUrlPath($route); + $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; + $request->action(array_pop($route)); + $request->controller($route ? array_pop($route) : Config::get('default_controller')); + $request->module($route ? array_pop($route) : Config::get('default_module')); + App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); + } else { + // 路由到模块/控制器/操作 + $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false); + } + // 开启请求缓存 + if ($request->isGet() && isset($option['cache'])) { + $cache = $option['cache']; + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $pathinfo); + $expire = $cache; + $tag = null; + } + $request->cache($key, $expire, $tag); + } + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access private + * @param string $url URL地址 + * @param bool $convert 是否自动转换URL地址 + * @return array + */ + private static function parseModule($url, $convert = false) + { + list($path, $var) = self::parseUrlPath($url); + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = Request::instance()->method(); + if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { + // 操作方法前缀支持 + $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; + } + // 设置当前请求的路由变量 + Request::instance()->route($var); + // 路由到模块/控制器/操作 + return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert]; + } + + /** + * 解析URL地址中的参数Request对象 + * @access private + * @param string $url 路由规则 + * @param array $var 变量 + * @return void + */ + private static function parseUrlParams($url, &$var = []) + { + if ($url) { + if (Config::get('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + // 设置当前请求的参数 + Request::instance()->route($var); + } + + // 分析路由规则中的变量 + private static function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + foreach (explode('/', $rule) as $val) { + $optional = false; + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $optional = true; + } else { + $optional = false; + } + $var[$name] = $optional ? 2 : 1; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $optional = true; + $val = substr($val, 1, -1); + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + $var[$name] = $optional ? 2 : 1; + } + } + return $var; + } + + /** + * 获取路由解析缓存的key + * @param Request $request + * @return string + */ + private static function getCheckCacheKey(Request $request) + { + static $key; + + if (empty($key)) { + if ($callback = Config::get('route_check_cache_key')) { + $key = call_user_func($callback, $request); + } else { + $key = "{$request->host(true)}|{$request->method()}|{$request->path()}"; + } + } + + return $key; + } +} diff --git a/thinkphp/library/think/Session.php b/thinkphp/library/think/Session.php new file mode 100644 index 0000000..61150bc --- /dev/null +++ b/thinkphp/library/think/Session.php @@ -0,0 +1,366 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Session +{ + protected static $prefix = ''; + protected static $init = null; + + /** + * 设置或者获取session作用域(前缀) + * @param string $prefix + * @return string|void + */ + public static function prefix($prefix = '') + { + empty(self::$init) && self::boot(); + if (empty($prefix) && null !== $prefix) { + return self::$prefix; + } else { + self::$prefix = $prefix; + } + } + + /** + * session初始化 + * @param array $config + * @return void + * @throws \think\Exception + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('session'); + } + // 记录初始化信息 + App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); + $isDoStart = false; + if (isset($config['use_trans_sid'])) { + ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); + } + + // 启动session + if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.auto_start', 0); + $isDoStart = true; + } + + if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) { + self::$prefix = $config['prefix']; + } + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { + session_id($_REQUEST[$config['var_session_id']]); + } elseif (isset($config['id']) && !empty($config['id'])) { + session_id($config['id']); + } + if (isset($config['name'])) { + session_name($config['name']); + } + if (isset($config['path'])) { + session_save_path($config['path']); + } + if (isset($config['domain'])) { + ini_set('session.cookie_domain', $config['domain']); + } + if (isset($config['expire'])) { + ini_set('session.gc_maxlifetime', $config['expire']); + ini_set('session.cookie_lifetime', $config['expire']); + } + if (isset($config['secure'])) { + ini_set('session.cookie_secure', $config['secure']); + } + if (isset($config['httponly'])) { + ini_set('session.cookie_httponly', $config['httponly']); + } + if (isset($config['use_cookies'])) { + ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); + } + if (isset($config['cache_limiter'])) { + session_cache_limiter($config['cache_limiter']); + } + if (isset($config['cache_expire'])) { + session_cache_expire($config['cache_expire']); + } + if (!empty($config['type'])) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类 + if (!class_exists($class) || !session_set_save_handler(new $class($config))) { + throw new ClassNotFoundException('error session handler:' . $class, $class); + } + } + if ($isDoStart) { + session_start(); + self::$init = true; + } else { + self::$init = false; + } + } + + /** + * session自动启动或者初始化 + * @return void + */ + public static function boot() + { + if (is_null(self::$init)) { + self::init(); + } elseif (false === self::$init) { + if (PHP_SESSION_ACTIVE != session_status()) { + session_start(); + } + self::$init = true; + } + } + + /** + * session设置 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function set($name, $value = '', $prefix = null) + { + empty(self::$init) && self::boot(); + + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 二维数组赋值 + list($name1, $name2) = explode('.', $name); + if ($prefix) { + $_SESSION[$prefix][$name1][$name2] = $value; + } else { + $_SESSION[$name1][$name2] = $value; + } + } elseif ($prefix) { + $_SESSION[$prefix][$name] = $value; + } else { + $_SESSION[$name] = $value; + } + } + + /** + * session获取 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ('' == $name) { + // 获取全部的session + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + } elseif ($prefix) { + // 获取session + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null; + } else { + $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null; + } + } else { + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; + } else { + $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null; + } + } + return $value; + } + + /** + * session获取并删除 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function pull($name, $prefix = null) + { + $result = self::get($name, $prefix); + if ($result) { + self::delete($name, $prefix); + return $result; + } else { + return; + } + } + + /** + * session设置 下一次请求有效 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function flash($name, $value) + { + self::set($name, $value); + if (!self::has('__flash__.__time__')) { + self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + self::push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @return void + */ + public static function flush() + { + if (self::$init) { + $item = self::get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + self::delete($item); + self::set('__flash__', []); + } + } + } + } + + /** + * 删除session数据 + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function delete($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (is_array($name)) { + foreach ($name as $key) { + self::delete($key, $prefix); + } + } elseif (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + if ($prefix) { + unset($_SESSION[$prefix][$name1][$name2]); + } else { + unset($_SESSION[$name1][$name2]); + } + } else { + if ($prefix) { + unset($_SESSION[$prefix][$name]); + } else { + unset($_SESSION[$name]); + } + } + } + + /** + * 清空session数据 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function clear($prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ($prefix) { + unset($_SESSION[$prefix]); + } else { + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @param string $name session名称 + * @param string|null $prefix + * @return bool + */ + public static function has($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 支持数组 + list($name1, $name2) = explode('.', $name); + return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); + } else { + return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); + } + } + + /** + * 添加数据到一个session数组 + * @param string $key + * @param mixed $value + * @return void + */ + public static function push($key, $value) + { + $array = self::get($key); + if (is_null($array)) { + $array = []; + } + $array[] = $value; + self::set($key, $array); + } + + /** + * 启动session + * @return void + */ + public static function start() + { + session_start(); + self::$init = true; + } + + /** + * 销毁session + * @return void + */ + public static function destroy() + { + if (!empty($_SESSION)) { + $_SESSION = []; + } + session_unset(); + session_destroy(); + self::$init = null; + } + + /** + * 重新生成session_id + * @param bool $delete 是否删除关联会话文件 + * @return void + */ + public static function regenerate($delete = false) + { + session_regenerate_id($delete); + } + + /** + * 暂停session + * @return void + */ + public static function pause() + { + // 暂停session + session_write_close(); + self::$init = false; + } +} diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php new file mode 100644 index 0000000..9ba0ff3 --- /dev/null +++ b/thinkphp/library/think/Template.php @@ -0,0 +1,1139 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\TemplateNotFoundException; +use think\template\TagLib; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + // 模板变量 + protected $data = []; + // 引擎配置 + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_base' => '', + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DS, + 'cache_suffix' => 'php', // 默认模板缓存后缀 + 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 + 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 + 'tpl_begin' => '{', // 模板引擎普通标签开始标记 + 'tpl_end' => '}', // 模板引擎普通标签结束标记 + 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 + 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'compile_type' => 'file', // 模板编译类型 + 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 + 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'layout_on' => false, // 布局模板开关 + 'layout_name' => 'layout', // 布局模板入口文件 + 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'taglib_begin' => '{', // 标签库标签开始标记 + 'taglib_end' => '}', // 标签库标签结束标记 + 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + 'display_cache' => false, // 模板渲染缓存 + 'cache_id' => '', // 模板缓存ID + 'tpl_replace_string' => [], + 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + ]; + + private $literal = []; + private $includeFile = []; // 记录所有模板包含的文件路径及更新时间 + protected $storage; + + /** + * 构造函数 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config['cache_path'] = TEMP_PATH; + $this->config = array_merge($this->config, $config); + + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; + $this->config['taglib_end_origin'] = $this->config['taglib_end']; + + $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); + $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); + $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); + $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); + + // 初始化模板编译存储器 + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); + $this->storage = new $class(); + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name + * @param mixed $value + * @return void + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + } + + /** + * 模板引擎参数赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 模板引擎配置项 + * @access public + * @param array|string $config + * @return string|void|array + */ + public function config($config) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } elseif (isset($this->config[$config])) { + return $this->config[$config]; + } else { + return; + } + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get($name = '') + { + if ('' == $name) { + return $this->data; + } else { + $data = $this->data; + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + return $data; + } + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 读取渲染缓存 + $cacheContent = Cache::get($this->config['cache_id']); + if (false !== $cacheContent) { + echo $cacheContent; + return; + } + } + $template = $this->parseTemplateFile($template); + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { + // 缓存无效 重新模板编译 + $content = file_get_contents($template); + $this->compiler($content, $cacheFile); + } + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + // 获取并清空缓存 + $content = ob_get_clean(); + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 缓存页面输出 + Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); + } + echo $content; + } + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($content, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { + // 缓存无效 模板编译 + $this->compiler($content, $cacheFile); + } + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + } + + /** + * 设置布局 + * @access public + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return Template + */ + public function layout($name, $replace = '') + { + if (false === $name) { + // 关闭布局 + $this->config['layout_on'] = false; + } else { + // 开启布局 + $this->config['layout_on'] = true; + // 名称必须为字符串 + if (is_string($name)) { + $this->config['layout_name'] = $name; + } + if (!empty($replace)) { + $this->config['layout_item'] = $replace; + } + } + return $this; + } + + /** + * 检查编译缓存是否有效 + * 如果无效则需要重新编译 + * @access private + * @param string $cacheFile 缓存文件名 + * @return boolean + */ + private function checkCache($cacheFile) + { + // 未开启缓存功能 + if (!$this->config['tpl_cache']) { + return false; + } + // 缓存文件不存在 + if (!is_file($cacheFile)) { + return false; + } + // 读取缓存文件失败 + if (!$handle = @fopen($cacheFile, "r")) { + return false; + } + // 读取第一行 + preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + if (!isset($matches[1])) { + return false; + } + $includeFile = unserialize($matches[1]); + if (!is_array($includeFile)) { + return false; + } + // 检查模板文件是否有更新 + foreach ($includeFile as $path => $time) { + if (is_file($path) && filemtime($path) > $time) { + // 模板文件如果有更新则缓存需要更新 + return false; + } + } + // 检查编译存储是否有效 + return $this->storage->check($cacheFile, $this->config['cache_time']); + } + + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache($cacheId) + { + if ($cacheId && $this->config['display_cache']) { + // 缓存页面输出 + return Cache::has($cacheId); + } + return false; + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(&$content, $cacheFile) + { + // 判断是否启用布局 + if ($this->config['layout_on']) { + if (false !== strpos($content, '{__NOLAYOUT__}')) { + // 可以单独定义不使用布局 + $content = str_replace('{__NOLAYOUT__}', '', $content); + } else { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + if ($layoutFile) { + // 替换布局的主体内容 + $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + + // 模板解析 + $this->parse($content); + if ($this->config['strip_space']) { + /* 去除html空格与换行 */ + $find = ['~>\s+<~', '~>(\s+\n|\r)~']; + $replace = ['><', '>']; + $content = preg_replace($find, $replace, $content); + } + // 优化生成的php代码 + $content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content); + // 模板过滤输出 + $replace = $this->config['tpl_replace_string']; + $content = str_replace(array_keys($replace), array_values($replace), $content); + // 添加安全代码及模板引用记录 + $content = 'includeFile) . '*/ ?>' . "\n" . $content; + // 编译存储 + $this->storage->write($cacheFile, $content); + $this->includeFile = []; + return; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(&$content) + { + // 内容为空不解析 + if (empty($content)) { + return; + } + // 替换literal标签内容 + $this->parseLiteral($content); + // 解析继承 + $this->parseExtend($content); + // 解析布局 + $this->parseLayout($content); + // 检查include语法 + $this->parseInclude($content); + // 替换包含文件中literal标签内容 + $this->parseLiteral($content); + // 检查PHP语法 + $this->parsePhp($content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if ($this->config['taglib_load']) { + $tagLibs = $this->getIncludeTagLib($content); + if (!empty($tagLibs)) { + // 对导入的TagLib进行解析 + foreach ($tagLibs as $tagLibName) { + $this->parseTagLib($tagLibName, $content); + } + } + } + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if ($this->config['taglib_pre_load']) { + $tagLibs = explode(',', $this->config['taglib_pre_load']); + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content); + } + } + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',', $this->config['taglib_build_in']); + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content, true); + } + // 解析普通模板标签 {$tagName} + $this->parseTag($content); + + // 还原被替换的Literal标签 + $this->parseLiteral($content, true); + return; + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws \think\Exception + */ + private function parsePhp(&$content) + { + // 短标签的情况要将' . "\n", $content); + // PHP语法检查 + if ($this->config['tpl_deny_php'] && false !== strpos($content, 'getRegex('layout'), $content, $matches)) { + // 替换Layout标签 + $content = str_replace($matches[0], '', $content); + // 解析Layout标签 + $array = $this->parseAttr($matches[0]); + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($array['name']); + if ($layoutFile) { + $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace, $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + return; + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(&$content) + { + $regex = $this->getRegex('include'); + $func = function ($template) use (&$func, &$regex, &$content) { + if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array = $this->parseAttr($match[0]); + $file = $array['file']; + unset($array['file']); + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($file); + foreach ($array as $k => $v) { + // 以$开头字符串转换成模板变量 + if (0 === strpos($v, '$')) { + $v = $this->get(substr($v, 1)); + } + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); + } + $content = str_replace($match[0], $parseStr, $content); + // 再次对包含文件进行模板分析 + $func($parseStr); + } + unset($matches); + } + }; + // 替换模板中的include标签 + $func($content); + return; + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(&$content) + { + $regex = $this->getRegex('extend'); + $array = $blocks = $baseBlocks = []; + $extend = ''; + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + if (preg_match($regex, $template, $matches)) { + if (!isset($array[$matches['name']])) { + $array[$matches['name']] = 1; + // 读取继承模板 + $extend = $this->parseTemplateName($matches['name']); + // 递归检查继承 + $func($extend); + // 取得block标签内容 + $blocks = array_merge($blocks, $this->parseBlock($template)); + return; + } + } else { + // 取得顶层模板block标签内容 + $baseBlocks = $this->parseBlock($template, true); + if (empty($extend)) { + // 无extend标签但有block标签的情况 + $extend = $template; + } + } + }; + + $func($content); + if (!empty($extend)) { + if ($baseBlocks) { + $children = []; + foreach ($baseBlocks as $name => $val) { + $replace = $val['content']; + if (!empty($children[$name])) { + // 如果包含有子block标签 + foreach ($children[$name] as $key) { + $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); + } + } + if (isset($blocks[$name])) { + // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 + $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + if (!empty($val['parent'])) { + // 如果不是最顶层的block标签 + $parent = $val['parent']; + if (isset($blocks[$parent])) { + $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); + } + $blocks[$name]['content'] = $replace; + $children[$parent][] = $name; + continue; + } + } elseif (!empty($val['parent'])) { + // 如果子标签没有被继承则用原值 + $children[$val['parent']][] = $name; + $blocks[$name] = $val; + } + if (!$val['parent']) { + // 替换模板中的顶级block标签 + $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); + } + } + } + $content = $extend; + unset($blocks, $baseBlocks); + } + return; + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(&$content, $restore = false) + { + $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + if (!$restore) { + $count = count($this->literal); + // 替换literal标签 + foreach ($matches as $match) { + $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); + $content = str_replace($match[0], "", $content); + $count++; + } + } else { + // 还原literal标签 + foreach ($matches as $match) { + $content = str_replace($match[0], $this->literal[$match[1]], $content); + } + // 清空literal记录 + $this->literal = []; + } + unset($matches); + } + return; + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(&$content, $sort = false) + { + $regex = $this->getRegex('block'); + $result = []; + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = $keys = []; + foreach ($matches as $match) { + if (empty($match['name'][0])) { + if (count($right) > 0) { + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + $result[$tag['name']] = [ + 'begin' => $tag['tag'], + 'content' => substr($content, $start, $length), + 'end' => $match[0][0], + 'parent' => count($right) ? end($right)['name'] : '', + ]; + $keys[$tag['name']] = $match[0][1]; + } + } else { + // 标签头压入栈 + $right[] = [ + 'name' => $match[2][0], + 'offset' => $match[0][1], + 'tag' => $match[0][0], + ]; + } + } + unset($right, $matches); + if ($sort) { + // 按block标签结束符在模板中的位置排序 + array_multisort($keys, $result); + } + } + return $result; + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access private + * @param string $content 模板内容 + * @return array|null + */ + private function getIncludeTagLib(&$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + return explode(',', $matches['name']); + } + return; + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib($tagLib, &$content, $hide = false) + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + /** @var Taglib $tLib */ + $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); + return; + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr($str, $name = null) + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $array = []; + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array[$match['name']] = $match['value']; + } + unset($matches); + } + if (!empty($name) && isset($array[$name])) { + return $array[$name]; + } else { + return $array; + } + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(&$content) + { + $regex = $this->getRegex('tag'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $str = stripslashes($match[1]); + $flag = substr($str, 0, 1); + switch ($flag) { + case '$': + // 解析模板变量 格式 {$varName} + // 是否带有?号 + if (false !== $pos = strpos($str, '?')) { + $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); + $name = $array[0]; + $this->parseVar($name); + $this->parseVarFunction($name); + + $str = trim(substr($str, $pos + 1)); + $this->parseVar($str); + $first = substr($str, 0, 1); + if (strpos($name, ')')) { + // $name为对象或是自动识别,或者含有函数 + if (isset($array[1])) { + $this->parseVar($array[2]); + $name .= $array[1] . $array[2]; + } + switch ($first) { + case '?': + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = ''; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = ''; + break; + default: + $str = ''; + } + } + } else { + $this->parseVar($str); + $this->parseVarFunction($str); + $str = ''; + } + break; + case ':': + // 输出某个函数的结果 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '~': + // 执行某个函数 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '-': + case '+': + // 输出计算 + $this->parseVar($str); + $str = ''; + break; + case '/': + // 注释标签 + $flag2 = substr($str, 1, 1); + if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) { + $str = ''; + } + break; + default: + // 未识别的标签直接返回 + $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; + break; + } + $content = str_replace($match[0], $str, $content); + } + unset($matches); + } + return; + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(&$varStr) + { + $varStr = trim($varStr); + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { + static $_varParseList = []; + while ($matches[0]) { + $match = array_pop($matches[0]); + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varParseList[$match[0]])) { + $parseStr = $_varParseList[$match[0]]; + } else { + if (strpos($match[0], '.')) { + $vars = explode('.', $match[0]); + $first = array_shift($vars); + if ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } elseif ('$Request' == $first) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')'; + } else { + switch ($this->config['tpl_var_identify']) { + case 'array': // 识别为数组 + $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']'; + break; + case 'obj': // 识别为对象 + $parseStr = $first . '->' . implode('->', $vars); + break; + default: // 自动判断数组或对象 + $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')'; + } + } + } else { + $parseStr = str_replace(':', '->', $match[0]); + } + $_varParseList[$match[0]] = $parseStr; + } + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); + } + unset($matches); + } + return; + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @return void + */ + public function parseVarFunction(&$varStr) + { + if (false == strpos($varStr, '|')) { + return; + } + static $_varFunctionList = []; + $_key = md5($varStr); + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + // 取得变量名称 + $name = array_shift($varArray); + // 对变量使用函数 + $length = count($varArray); + // 取得模板禁止使用函数列表 + $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + for ($i = 0; $i < $length; $i++) { + $args = explode('=', $varArray[$i], 2); + // 模板函数过滤 + $fun = trim($args[0]); + switch ($fun) { + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (!in_array($fun, $template_deny_funs)) { + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; + } else { + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; + } + } + } + } + } + $_varFunctionList[$_key] = $name; + $varStr = $name; + } + return; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar($vars) + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + if ($vars) { + switch ($type) { + case 'SERVER': + $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; + break; + case 'GET': + $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; + break; + case 'POST': + $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; + break; + case 'COOKIE': + $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; + break; + case 'SESSION': + $parseStr = '\\think\\Session::get(\'' . $param . '\')'; + break; + case 'ENV': + $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; + break; + case 'REQUEST': + $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; + break; + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'LANG': + $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; + break; + case 'CONFIG': + $parseStr = '\\think\\Config::get(\'' . $param . '\')'; + break; + default: + $parseStr = '\'\''; + break; + } + } else { + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } + } + } + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName) + { + $array = explode(',', $templateName); + $parseStr = ''; + foreach ($array as $templateName) { + if (empty($templateName)) { + continue; + } + if (0 === strpos($templateName, '$')) { + //支持加载变量文件名 + $templateName = $this->get(substr($templateName, 1)); + } + $template = $this->parseTemplateFile($templateName); + if ($template) { + // 获取模板文件内容 + $parseStr .= file_get_contents($template); + } + } + return $parseStr; + } + + /** + * 解析模板文件名 + * @access private + * @param string $template 文件名 + * @return string|false + */ + private function parseTemplateFile($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + if (strpos($template, '@')) { + list($module, $template) = explode('@', $template); + } + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + if ($this->config['view_base']) { + $module = isset($module) ? $module : Request::instance()->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; + } + $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.')); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + return $template; + } else { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex($tagName) + { + $regex = ''; + if ('tag' == $tagName) { + $begin = $this->config['tpl_begin']; + $end = $this->config['tpl_end']; + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + } else { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + } + } else { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + switch ($tagName) { + case 'block': + if ($single) { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; + } + break; + case 'literal': + if ($single) { + $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; + $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } else { + $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; + $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } + break; + case 'restoreliteral': + $regex = ''; + break; + case 'include': + $name = 'file'; + case 'taglib': + case 'layout': + case 'extend': + if (empty($name)) { + $name = 'name'; + } + if ($single) { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + return '/' . $regex . '/is'; + } +} diff --git a/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php new file mode 100644 index 0000000..53a545f --- /dev/null +++ b/thinkphp/library/think/Url.php @@ -0,0 +1,333 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Url +{ + // 生成URL地址的root + protected static $root; + protected static $bindCheck; + + /** + * URL生成 支持路由反射 + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @return string + */ + public static function build($url = '', $vars = '', $suffix = true, $domain = false) + { + if (false === $domain && Route::rules('domain')) { + $domain = true; + } + // 解析URL + if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { + // [name] 表示使用路由命名标识生成URL + $name = substr($url, 1, $pos - 1); + $url = 'name' . substr($url, $pos + 1); + } + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + if (false !== strpos($anchor, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + list($url, $domain) = explode('@', $url, 2); + } + } + + // 解析参数 + if (is_string($vars)) { + // aaa=1&bbb=2 转换成数组 + parse_str($vars, $vars); + } + + if ($url) { + $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + if (is_null($rule) && isset($info['query'])) { + $rule = Route::name($url); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { + // 匹配路由命名标识 + $url = $match[0]; + // 替换可选分隔符 + $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); + if (!empty($match[1])) { + $domain = $match[1]; + } + if (!is_null($match[2])) { + $suffix = $match[2]; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检查别名路由 + $alias = Route::rules('alias'); + $matchAlias = false; + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $val) { + if (is_array($val)) { + $val = $val[0]; + } + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = self::parseUrl($url, $domain); + } + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 检测URL绑定 + if (!self::$bindCheck) { + $type = Route::getBind('type'); + if ($type) { + $bind = Route::getBind($type); + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } + } + } + // 还原URL分隔符 + $depr = Config::get('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + // URL后缀 + $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix); + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if (Config::get('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + $paramType = Config::get('url_param_type'); + foreach ($vars as $var => $val) { + if ('' !== trim($val)) { + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } + } + } + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + // 检测域名 + $domain = self::parseDomain($url, $domain); + // URL组装 + $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); + + self::$bindCheck = false; + return $url; + } + + // 直接解析URL地址 + protected static function parseUrl($url, &$domain) + { + $request = Request::instance(); + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } else { + // 解析到 模块/控制器/操作 + $module = $request->module(); + $domains = Route::rules('domain'); + if (true === $domain && 2 == substr_count($url, '/')) { + $current = $request->host(); + $match = []; + $pos = []; + foreach ($domains as $key => $item) { + if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) { + $pos[$key] = strlen($item['[bind]'][0]) + 1; + $match[] = $key; + $module = ''; + } + } + if ($match) { + $domain = current($match); + foreach ($match as $item) { + if (0 === strpos($current, $item)) { + $domain = $item; + } + } + self::$bindCheck = true; + $url = substr($url, $pos[$domain]); + } + } elseif ($domain) { + if (isset($domains[$domain]['[bind]'][0])) { + $bindModule = $domains[$domain]['[bind]'][0]; + if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { + $module = ''; + } + } + } + $module = $module ? $module . '/' : ''; + + $controller = $request->controller(); + if ('' == $url) { + // 空字符串输出当前的 模块/控制器/操作 + $action = $request->action(); + } else { + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $module = empty($path) ? $module : array_pop($path) . '/'; + } + if (Config::get('url_convert')) { + $action = strtolower($action); + $controller = Loader::parseName($controller); + } + $url = $module . $controller . '/' . $action; + } + return $url; + } + + // 检测域名 + protected static function parseDomain(&$url, $domain) + { + if (!$domain) { + return ''; + } + $request = Request::instance(); + $rootDomain = Config::get('url_domain_root'); + if (true === $domain) { + // 自动判断域名 + $domain = Config::get('app_host') ?: $request->host(); + + $domains = Route::rules('domain'); + if ($domains) { + $route_domain = array_keys($domains); + foreach ($route_domain as $domain_prefix) { + if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { + foreach ($domains as $key => $rule) { + $rule = is_array($rule) ? $rule[0] : $rule; + if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { + $url = ltrim($url, $rule); + $domain = $key; + // 生成对应子域名 + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } + } + } + } + } + + } else { + if (empty($rootDomain)) { + $host = Config::get('app_host') ?: $request->host(); + $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; + } + if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + } + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://'; + } + return $scheme . $domain; + } + + // 解析URL后缀 + protected static function parseSuffix($suffix) + { + if ($suffix) { + $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix; + if ($pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; + } + + // 匹配路由地址 + public static function getRuleUrl($rule, &$vars = []) + { + foreach ($rule as $item) { + list($url, $pattern, $domain, $suffix) = $item; + if (empty($pattern)) { + return [rtrim($url, '$'), $domain, $suffix]; + } + $type = Config::get('url_common_param'); + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + unset($vars[$key]); + $result = [$url, $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $result = [$url, $domain, $suffix]; + } else { + break; + } + } + if (isset($result)) { + return $result; + } + } + return false; + } + + // 指定当前生成URL地址的root + public static function root($root) + { + self::$root = $root; + Request::instance()->root($root); + } +} diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php new file mode 100644 index 0000000..608e1e4 --- /dev/null +++ b/thinkphp/library/think/Validate.php @@ -0,0 +1,1371 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Validate +{ + // 实例 + protected static $instance; + + // 自定义的验证类型 + protected static $type = []; + + // 验证类型别名 + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + // 当前验证的规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + // 验证字段描述 + protected $field = []; + + // 验证规则默认提示信息 + protected static $typeMsg = [ + 'require' => ':attribute require', + 'number' => ':attribute must be numeric', + 'integer' => ':attribute must be integer', + 'float' => ':attribute must be float', + 'boolean' => ':attribute must be bool', + 'email' => ':attribute not a valid email address', + 'array' => ':attribute must be a array', + 'accepted' => ':attribute must be yes,on or 1', + 'date' => ':attribute not a valid datetime', + 'file' => ':attribute not a valid file', + 'image' => ':attribute not a valid image', + 'alpha' => ':attribute must be alpha', + 'alphaNum' => ':attribute must be alpha-numeric', + 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', + 'activeUrl' => ':attribute not a valid domain or ip', + 'chs' => ':attribute must be chinese', + 'chsAlpha' => ':attribute must be chinese or alpha', + 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', + 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', + 'url' => ':attribute not a valid url', + 'ip' => ':attribute not a valid ip', + 'dateFormat' => ':attribute must be dateFormat of :rule', + 'in' => ':attribute must be in :rule', + 'notIn' => ':attribute be notin :rule', + 'between' => ':attribute must between :1 - :2', + 'notBetween' => ':attribute not between :1 - :2', + 'length' => 'size of :attribute must be :rule', + 'max' => 'max size of :attribute must be :rule', + 'min' => 'min size of :attribute must be :rule', + 'after' => ':attribute cannot be less than :rule', + 'before' => ':attribute cannot exceed :rule', + 'afterWith' => ':attribute cannot be less than :rule', + 'beforeWith' => ':attribute cannot exceed :rule', + 'expire' => ':attribute not within :rule', + 'allowIp' => 'access IP is not allowed', + 'denyIp' => 'access IP denied', + 'confirm' => ':attribute out of accord with :2', + 'different' => ':attribute cannot be same with :2', + 'egt' => ':attribute must greater than or equal :rule', + 'gt' => ':attribute must greater than :rule', + 'elt' => ':attribute must less than or equal :rule', + 'lt' => ':attribute must less than :rule', + 'eq' => ':attribute must equal :rule', + 'unique' => ':attribute has exists', + 'regex' => ':attribute not conform to the rules', + 'method' => 'invalid Request method', + 'token' => 'invalid token', + 'fileSize' => 'filesize not match', + 'fileExt' => 'extensions to upload is not allowed', + 'fileMime' => 'mimetype to upload is not allowed', + ]; + + // 当前验证场景 + protected $currentScene = null; + + // 正则表达式 regex = ['zip'=>'\d{6}',...] + protected $regex = []; + + // 验证场景 scene = ['edit'=>'name1,name2,...'] + protected $scene = []; + + // 验证失败错误信息 + protected $error = []; + + // 批量验证 + protected $batch = false; + + /** + * 构造函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], $message = [], $field = []) + { + $this->rule = array_merge($this->rule, $rules); + $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); + } + + /** + * 实例化验证 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + * @return Validate + */ + public static function make($rules = [], $message = [], $field = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($rules, $message, $field); + } + return self::$instance; + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则 + * @return Validate + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = array_merge($this->rule, $name); + } else { + $this->rule[$name] = $rule; + } + return $this; + } + + /** + * 注册验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) + * @return void + */ + public static function extend($type, $callback = null) + { + if (is_array($type)) { + self::$type = array_merge(self::$type, $type); + } else { + self::$type[$type] = $callback; + } + } + + /** + * 设置验证规则的默认提示信息 + * @access protected + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public static function setTypeMsg($type, $msg = null) + { + if (is_array($type)) { + self::$typeMsg = array_merge(self::$typeMsg, $type); + } else { + self::$typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param string|array $name 字段名称 + * @param string $message 提示信息 + * @return Validate + */ + public function message($name, $message = '') + { + if (is_array($name)) { + $this->message = array_merge($this->message, $name); + } else { + $this->message[$name] = $message; + } + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string|array $name 场景名或者场景设置数组 + * @param mixed $fields 要验证的字段 + * @return Validate + */ + public function scene($name, $fields = null) + { + if (is_array($name)) { + $this->scene = array_merge($this->scene, $name); + }if (is_null($fields)) { + // 设置当前场景 + $this->currentScene = $name; + } else { + // 设置验证场景 + $this->scene[$name] = $fields; + } + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return Validate + */ + public function batch($batch = true) + { + $this->batch = $batch; + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 + * @return bool + */ + public function check($data, $rules = [], $scene = '') + { + $this->error = []; + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + // 分析验证规则 + $scene = $this->getScene($scene); + if (is_array($scene)) { + // 处理场景验证字段 + $change = []; + $array = []; + foreach ($scene as $k => $val) { + if (is_numeric($k)) { + $array[] = $val; + } else { + $array[] = $k; + $change[$k] = $val; + } + } + } + + foreach ($rules as $key => $item) { + // field => rule1|rule2... field=>['rule1','rule2',...] + if (is_numeric($key)) { + // [field,rule1|rule2,msg1|msg2] + $key = $item[0]; + $rule = $item[1]; + if (isset($item[2])) { + $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; + } else { + $msg = []; + } + } else { + $rule = $item; + $msg = []; + } + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + list($key, $title) = explode('|', $key); + } else { + $title = isset($this->field[$key]) ? $this->field[$key] : $key; + } + + // 场景检测 + if (!empty($scene)) { + if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { + continue; + } elseif (is_array($scene)) { + if (!in_array($key, $array)) { + continue; + } elseif (isset($change[$key])) { + // 重载某个验证规则 + $rule = $change[$key]; + } + } + } + + // 获取数据 支持二维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof \Closure) { + // 匿名函数验证 支持传入当前字段和所有字段两个数据 + $result = call_user_func_array($rule, [$value, $data]); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + if (is_array($result)) { + $this->error = array_merge($this->error, $result); + } else { + $this->error[$key] = $result; + } + } else { + $this->error = $result; + return false; + } + } + } + return !empty($this->error) ? false : true; + } + + /** + * 根据验证规则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + protected function checkRule($value, $rules) + { + if ($rules instanceof \Closure) { + return call_user_func_array($rules, [$value]); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + list($type, $rule) = $this->getValidateType($key, $rule); + + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $result) { + return $result; + } + } + + return true; + } + + /** + * 验证单个字段规则 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 + * @return mixed + */ + protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) + { + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + $i = 0; + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + list($type, $rule, $info) = $this->getValidateType($key, $rule); + + // 如果不是require 有数据才会行验证 + if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证类型 + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + // 验证数据 + $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (isset($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = Lang::get(substr($message, 2, -1)); + } + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); + } + return $result; + } + $i++; + } + return $result; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule) + { + // 判断验证类型 + if (!is_numeric($key)) { + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + list($type, $rule) = explode(':', $rule, 2); + if (isset($this->alias[$type])) { + // 判断别名 + $type = $this->alias[$type]; + } + $info = $type; + } elseif (method_exists($this, $rule)) { + $type = $rule; + $info = $rule; + $rule = ''; + } else { + $type = 'is'; + $info = $rule; + } + + return [$type, $rule, $info]; + } + + /** + * 验证是否和某个字段的值一致 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + protected function confirm($value, $rule, $data, $field = '') + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function different($value, $rule, $data) + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function egt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value >= $val; + } + + /** + * 验证是否大于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function gt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value > $val; + } + + /** + * 验证是否小于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function elt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value <= $val; + } + + /** + * 验证是否小于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function lt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value < $val; + } + + /** + * 验证是否等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function eq($value, $rule) + { + return $value == $rule; + } + + /** + * 验证字段值是否为有效格式 + * @access protected + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + protected function is($value, $rule, $data = []) + { + switch ($rule) { + case 'require': + // 必须 + $result = !empty($value) || '0' == $value; + break; + case 'accepted': + // 接受 + $result = in_array($value, ['1', 'on', 'yes']); + break; + case 'date': + // 是否是一个有效日期 + $result = false !== strtotime($value); + break; + case 'alpha': + // 只允许字母 + $result = $this->regex($value, '/^[A-Za-z]+$/'); + break; + case 'alphaNum': + // 只允许字母和数字 + $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); + break; + case 'alphaDash': + // 只允许字母、数字和下划线 破折号 + $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); + break; + case 'chs': + // 只允许汉字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); + break; + case 'chsAlpha': + // 只允许汉字、字母 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); + break; + case 'chsAlphaNum': + // 只允许汉字、字母和数字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); + break; + case 'chsDash': + // 只允许汉字、字母、数字和下划线_及破折号- + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'ip': + // 是否为IP地址 + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); + break; + case 'url': + // 是否为一个URL地址 + $result = $this->filter($value, FILTER_VALIDATE_URL); + break; + case 'float': + // 是否为float + $result = $this->filter($value, FILTER_VALIDATE_FLOAT); + break; + case 'number': + $result = is_numeric($value); + break; + case 'integer': + // 是否为整型 + $result = $this->filter($value, FILTER_VALIDATE_INT); + break; + case 'email': + // 是否为邮箱地址 + $result = $this->filter($value, FILTER_VALIDATE_EMAIL); + break; + case 'boolean': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + break; + case 'array': + // 是否为数组 + $result = is_array($value); + break; + case 'file': + $result = $value instanceof File; + break; + case 'image': + $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); + break; + case 'token': + $result = $this->token($value, '__token__', $data); + break; + default: + if (isset(self::$type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array(self::$type[$rule], [$value]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } else { + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function activeUrl($value, $rule) + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + protected function ip($value, $rule) + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 验证上传文件后缀 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileExt($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkExt($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkExt($rule); + } else { + return false; + } + } + + /** + * 验证上传文件类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileMime($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkMime($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkMime($rule); + } else { + return false; + } + } + + /** + * 验证上传文件大小 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileSize($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkSize($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkSize($rule); + } else { + return false; + } + } + + /** + * 验证图片的宽高及类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function image($file, $rule) + { + if (!($file instanceof File)) { + return false; + } + if ($rule) { + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + if ('jpeg' == $imageType) { + $imageType = 'jpg'; + } + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + list($w, $h) = $rule; + return $w == $width && $h == $height; + } else { + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + } + + /** + * 验证请求类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function method($value, $rule) + { + $method = Request::instance()->method(); + return strtoupper($rule) == $method; + } + + /** + * 验证时间和日期是否符合指定格式 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function dateFormat($value, $rule) + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + protected function unique($value, $rule, $data, $field) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Loader::model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } + $key = isset($rule[1]) ? $rule[1] : $field; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[$key] = $data[$key]; + } + } + } elseif (strpos($key, '=')) { + parse_str($key, $map); + } elseif (isset($data[$field])) { + $map[$key] = $data[$field]; + } else { + $map = []; + } + + $pk = isset($rule[3]) ? $rule[3] : $db->getPk(); + if (is_string($pk)) { + if (isset($rule[2])) { + $map[$pk] = ['neq', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[$pk] = ['neq', $data[$pk]]; + } + } + if ($db->where($map)->field($pk)->find()) { + return false; + } + return true; + } + + /** + * 使用行为类验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return mixed + */ + protected function behavior($value, $rule, $data) + { + return Hook::exec($rule, '', $data); + } + + /** + * 使用filter_var方式验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function filter($value, $rule) + { + if (is_string($rule) && strpos($rule, ',')) { + list($rule, $param) = explode(',', $rule); + } elseif (is_array($rule)) { + $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; + } else { + $param = null; + } + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireIf($value, $rule, $data) + { + list($field, $val) = explode(',', $rule); + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireCallback($value, $rule, $data) + { + $result = call_user_func_array($rule, [$value, $data]); + if ($result) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证某个字段有值的情况下必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireWith($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + if (!empty($val)) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证是否在范围内 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function in($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notIn($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function between($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notBetween($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function length($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (strpos($rule, ',')) { + // 长度区间 + list($min, $max) = explode(',', $rule); + return $length >= $min && $length <= $max; + } else { + // 指定长度 + return $length == $rule; + } + } + + /** + * 验证数据最大长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function max($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function min($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length >= $rule; + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function after($value, $rule, $data) + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function before($value, $rule, $data) + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function afterWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function beforeWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function expire($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($start, $end) = $rule; + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; + } + + /** + * 验证IP许可 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function allowIp($value, $rule) + { + return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function denyIp($value, $rule) + { + return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return mixed + */ + protected function regex($value, $rule) + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 验证表单令牌 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function token($value, $rule, $data) + { + $rule = !empty($rule) ? $rule : '__token__'; + if (!isset($data[$rule]) || !Session::has($rule)) { + // 令牌数据无效 + return false; + } + + // 令牌验证 + if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { + // 防止重复提交 + Session::delete($rule); // 验证完成销毁session + return true; + } + // 开启TOKEN重置 + Session::delete($rule); + return false; + } + + // 获取错误信息 + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持二维 + * @return mixed + */ + protected function getDataValue($data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (strpos($key, '.')) { + // 支持二维数组验证 + list($name1, $name2) = explode('.', $key); + $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null; + } else { + $value = isset($data[$key]) ? $data[$key] : null; + } + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string + */ + protected function getRuleMsg($attribute, $title, $type, $rule) + { + if (isset($this->message[$attribute . '.' . $type])) { + $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; + } elseif (isset($this->message[$attribute])) { + $msg = $this->message[$attribute]; + } elseif (isset(self::$typeMsg[$type])) { + $msg = self::$typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = self::$typeMsg['require']; + } else { + $msg = $title . Lang::get('not conform to the rules'); + } + + if (is_string($msg) && 0 === strpos($msg, '{%')) { + $msg = Lang::get(substr($msg, 2, -1)); + } elseif (Lang::has($msg)) { + $msg = Lang::get($msg); + } + + if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { + // 变量替换 + if (is_string($rule) && strpos($rule, ',')) { + $array = array_pad(explode(',', $rule), 3, ''); + } else { + $array = array_pad([], 3, ''); + } + $msg = str_replace( + [':attribute', ':rule', ':1', ':2', ':3'], + [$title, (string) $rule, $array[0], $array[1], $array[2]], + $msg); + } + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return array + */ + protected function getScene($scene = '') + { + if (empty($scene)) { + // 读取指定场景 + $scene = $this->currentScene; + } + + if (!empty($scene) && isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $scene = $this->scene[$scene]; + if (is_string($scene)) { + $scene = explode(',', $scene); + } + } else { + $scene = []; + } + return $scene; + } + + public static function __callStatic($method, $params) + { + $class = self::make(); + if (method_exists($class, $method)) { + return call_user_func_array([$class, $method], $params); + } else { + throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/View.php b/thinkphp/library/think/View.php new file mode 100644 index 0000000..ca2dadb --- /dev/null +++ b/thinkphp/library/think/View.php @@ -0,0 +1,239 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class View +{ + // 视图实例 + protected static $instance; + // 模板引擎实例 + public $engine; + // 模板变量 + protected $data = []; + // 用于静态赋值的模板变量 + protected static $var = []; + // 视图输出替换 + protected $replace = []; + + /** + * 构造函数 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + */ + public function __construct($engine = [], $replace = []) + { + // 初始化模板引擎 + $this->engine($engine); + // 基础替换字符串 + $request = Request::instance(); + $base = $request->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base; + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + $baseReplace = [ + '__ROOT__' => $root, + '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()), + '__STATIC__' => $root . '/static', + '__CSS__' => $root . '/static/css', + '__JS__' => $root . '/static/js', + ]; + $this->replace = array_merge($baseReplace, (array) $replace); + } + + /** + * 初始化视图 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + * @return object + */ + public static function instance($engine = [], $replace = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($engine, $replace); + } + return self::$instance; + } + + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return void + */ + public static function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + return $this; + } + + /** + * 设置当前模板解析的引擎 + * @access public + * @param array|string $options 引擎参数 + * @return $this + */ + public function engine($options = []) + { + if (is_string($options)) { + $type = $options; + $options = []; + } else { + $type = !empty($options['type']) ? $options['type'] : 'Think'; + } + + $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type); + if (isset($options['type'])) { + unset($options['type']); + } + $this->engine = new $class($options); + return $this; + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function config($name, $value = null) + { + $this->engine->config($name, $value); + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 + * @return string + * @throws Exception + */ + public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) + { + // 模板变量 + $vars = array_merge(self::$var, $this->data, $vars); + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $method = $renderContent ? 'display' : 'fetch'; + // 允许用户自定义模板的字符串替换 + $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string')); + $this->engine->config('tpl_replace_string', $replace); + $this->engine->$method($template, $vars, $config); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + // 内容过滤标签 + Hook::listen('view_filter', $content); + return $content; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + public function display($content, $vars = [], $replace = [], $config = []) + { + return $this->fetch($content, $vars, $replace, $config, true); + } + + /** + * 模板变量赋值 + * @access public + * @param string $name 变量名 + * @param mixed $value 变量值 + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * 取得模板显示变量的值 + * @access protected + * @param string $name 模板变量 + * @return mixed + */ + public function __get($name) + { + return $this->data[$name]; + } + + /** + * 检测模板变量是否设置 + * @access public + * @param string $name 模板变量名 + * @return bool + */ + public function __isset($name) + { + return isset($this->data[$name]); + } +} diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php new file mode 100644 index 0000000..07805e4 --- /dev/null +++ b/thinkphp/library/think/cache/Driver.php @@ -0,0 +1,231 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache; + +/** + * 缓存基础类 + */ +abstract class Driver +{ + protected $handler = null; + protected $options = []; + protected $tag; + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + abstract public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + abstract public function get($name, $default = false); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolean + */ + abstract public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function inc($name, $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function dec($name, $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + abstract public function rm($name); + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + abstract public function clear($tag = null); + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull($name) + { + $result = $this->get($name, false); + if ($result) { + $this->rm($name); + return $result; + } else { + return; + } + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + $time = time(); + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + if ($value instanceof \Closure) { + $value = call_user_func($value); + } + $this->set($name, $value, $expire); + // 解锁 + $this->rm($name . '_lock'); + } catch (\Exception $e) { + // 解锁 + $this->rm($name . '_lock'); + throw $e; + } catch (\throwable $e) { + $this->rm($name . '_lock'); + throw $e; + } + } else { + $value = $this->get($name); + } + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($name)) { + + } elseif (is_null($keys)) { + $this->tag = $name; + } else { + $key = 'tag_' . md5($name); + if (is_string($keys)) { + $keys = explode(',', $keys); + } + $keys = array_map([$this, 'getCacheKey'], $keys); + if ($overlay) { + $value = $keys; + } else { + $value = array_unique(array_merge($this->getTagItem($name), $keys)); + } + $this->set($key, implode(',', $value), 0); + } + return $this; + } + + /** + * 更新标签 + * @access public + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $key = 'tag_' . md5($this->tag); + $this->tag = null; + if ($this->has($key)) { + $value = explode(',', $this->get($key)); + $value[] = $name; + $value = implode(',', array_unique($value)); + } else { + $value = $name; + } + $this->set($key, $value, 0); + } + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $key = 'tag_' . md5($tag); + $value = $this->get($key); + if ($value) { + return array_filter(explode(',', $value)); + } else { + return []; + } + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } +} diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php new file mode 100644 index 0000000..fee6489 --- /dev/null +++ b/thinkphp/library/think/cache/driver/File.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class File extends Driver +{ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => CACHE_PATH, + 'data_compress' => false, + ]; + + protected $expire; + + /** + * 构造函数 + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolean + */ + private function init() + { + // 创建项目缓存目录 + if (!is_dir($this->options['path'])) { + if (mkdir($this->options['path'], 0755, true)) { + return true; + } + } + return false; + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getCacheKey($name, $auto = false) + { + $name = md5($name); + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DS . substr($name, 2); + } + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DS . $name; + } + $filename = $this->options['path'] . $name . '.php'; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + mkdir($dir, 0755, true); + } + return $filename; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (!is_file($filename)) { + return $default; + } + $content = file_get_contents($filename); + $this->expire = null; + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() > filemtime($filename) + $expire) { + return $default; + } + $this->expire = $expire; + $content = substr($content, 32); + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $filename = $this->getCacheKey($name, true); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $data = serialize($value); + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + $data = "\n" . $data; + $result = file_put_contents($filename, $data); + if ($result) { + isset($first) && $this->setTagItem($filename); + clearstatcache(); + return true; + } else { + return false; + } + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + $expire = $this->expire; + } else { + $value = $step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + $expire = $this->expire; + } else { + $value = -$step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $filename = $this->getCacheKey($name); + try { + return $this->unlink($filename); + } catch (\Exception $e) { + } + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); + foreach ($files as $path) { + if (is_dir($path)) { + $matches = glob($path . '/*.php'); + if (is_array($matches)) { + array_map('unlink', $matches); + } + rmdir($path); + } else { + unlink($path); + } + } + return true; + } + + /** + * 判断文件是否存在后,删除 + * @param $path + * @return bool + * @author byron sampson + * @return boolean + */ + private function unlink($path) + { + return is_file($path) && unlink($path); + } + +} diff --git a/thinkphp/library/think/cache/driver/Lite.php b/thinkphp/library/think/cache/driver/Lite.php new file mode 100644 index 0000000..8cbf08f --- /dev/null +++ b/thinkphp/library/think/cache/driver/Lite.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class Lite extends Driver +{ + protected $options = [ + 'prefix' => '', + 'path' => '', + 'expire' => 0, // 等于 10*365*24*3600(10年) + ]; + + /** + * 构造函数 + * @access public + * + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (is_file($filename)) { + // 判断是否过期 + $mtime = filemtime($filename); + if ($mtime < time()) { + // 清除已经过期的文件 + unlink($filename); + return $default; + } + return include $filename; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; + $expire = time() + $expire; + } + $filename = $this->getCacheKey($name); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $ret = file_put_contents($filename, ("setTagItem($filename); + touch($filename, $expire); + } + return $ret; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/thinkphp/library/think/cache/driver/Memcache.php new file mode 100644 index 0000000..58703ea --- /dev/null +++ b/thinkphp/library/think/cache/driver/Memcache.php @@ -0,0 +1,177 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcache extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!extension_loaded('memcache')) { + throw new \BadFunctionCallException('not support: memcache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Memcache; + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) : + $this->handler->addServer($host, $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return false !== $this->handler->get($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if ($this->handler->set($key, $value, 0, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php new file mode 100644 index 0000000..5aab5a3 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Memcached.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcached extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('memcached')) { + throw new \BadFunctionCallException('not support: memcached'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Memcached; + if (!empty($this->options['option'])) { + $this->handler->setOptions($this->options['option']); + } + // 设置连接超时时间(单位:毫秒) + if ($this->options['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); + } + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + $this->handler->addServers($servers); + if ('' != $this->options['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return $this->handler->get($key) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; + if ($this->handler->set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + $this->handler->deleteMulti($keys); + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php new file mode 100644 index 0000000..027b3ea --- /dev/null +++ b/thinkphp/library/think/cache/driver/Redis.php @@ -0,0 +1,188 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 + * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 + * + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + * @author 尘缘 <130775@qq.com> + */ +class Redis extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('redis')) { + throw new \BadFunctionCallException('not support: redis'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Redis; + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->handler->exists($this->getCacheKey($name)); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $value = $this->handler->get($this->getCacheKey($name)); + if (is_null($value) || false === $value) { + return $default; + } + + try { + $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value; + } catch (\Exception $e) { + $result = $default; + } + + return $result; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value); + if ($expire) { + $result = $this->handler->setex($key, $expire, $value); + } else { + $result = $this->handler->set($key, $value); + } + isset($first) && $this->setTagItem($key); + return $result; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return $this->handler->delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flushDB(); + } + +} diff --git a/thinkphp/library/think/cache/driver/Sqlite.php b/thinkphp/library/think/cache/driver/Sqlite.php new file mode 100644 index 0000000..dc2ee05 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Sqlite.php @@ -0,0 +1,199 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Sqlite缓存驱动 + * @author liu21st + */ +class Sqlite extends Driver +{ + protected $options = [ + 'db' => ':memory:', + 'table' => 'sharedmemory', + 'prefix' => '', + 'expire' => 0, + 'persistent' => false, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('sqlite')) { + throw new \BadFunctionCallException('not support: sqlite'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . sqlite_escape_string($name); + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + return sqlite_num_rows($result); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if (function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return unserialize($content); + } + return $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $name = $this->getCacheKey($name); + $value = sqlite_escape_string(serialize($value)); + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 + } + if (function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value, 3); + } + if ($this->tag) { + $tag = $this->tag; + $this->tag = null; + } else { + $tag = ''; + } + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + if (sqlite_query($this->handler, $sql)) { + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $name = $this->getCacheKey($name); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $name = sqlite_escape_string($tag); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + $sql = 'DELETE FROM ' . $this->options['table']; + sqlite_query($this->handler, $sql); + return true; + } +} diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/thinkphp/library/think/cache/driver/Wincache.php new file mode 100644 index 0000000..03f8d35 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Wincache.php @@ -0,0 +1,152 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + * @author liu21st + */ +class Wincache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($options = []) + { + if (!function_exists('wincache_ucache_info')) { + throw new \BadFunctionCallException('not support: WinCache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $key = $this->getCacheKey($name); + if ($this->tag && !$this->has($name)) { + $first = true; + } + if (wincache_ucache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + wincache_ucache_delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } else { + return wincache_ucache_clear(); + } + } + +} diff --git a/thinkphp/library/think/cache/driver/Xcache.php b/thinkphp/library/think/cache/driver/Xcache.php new file mode 100644 index 0000000..4d94c03 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Xcache.php @@ -0,0 +1,155 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Xcache缓存驱动 + * @author liu21st + */ +class Xcache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('xcache_info')) { + throw new \BadFunctionCallException('not support: Xcache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return xcache_isset($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return xcache_isset($key) ? xcache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if (xcache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return xcache_unset($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + xcache_unset($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + if (function_exists('xcache_unset_by_prefix')) { + return xcache_unset_by_prefix($this->options['prefix']); + } else { + return false; + } + } +} diff --git a/thinkphp/library/think/config/driver/Ini.php b/thinkphp/library/think/config/driver/Ini.php new file mode 100644 index 0000000..bcd12b6 --- /dev/null +++ b/thinkphp/library/think/config/driver/Ini.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Ini +{ + public function parse($config) + { + if (is_file($config)) { + return parse_ini_file($config, true); + } else { + return parse_ini_string($config, true); + } + } +} diff --git a/thinkphp/library/think/config/driver/Json.php b/thinkphp/library/think/config/driver/Json.php new file mode 100644 index 0000000..479dcc8 --- /dev/null +++ b/thinkphp/library/think/config/driver/Json.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Json +{ + public function parse($config) + { + if (is_file($config)) { + $config = file_get_contents($config); + } + $result = json_decode($config, true); + return $result; + } +} diff --git a/thinkphp/library/think/config/driver/Xml.php b/thinkphp/library/think/config/driver/Xml.php new file mode 100644 index 0000000..1158519 --- /dev/null +++ b/thinkphp/library/think/config/driver/Xml.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Xml +{ + public function parse($config) + { + if (is_file($config)) { + $content = simplexml_load_file($config); + } else { + $content = simplexml_load_string($config); + } + $result = (array) $content; + foreach ($result as $key => $val) { + if (is_object($val)) { + $result[$key] = (array) $val; + } + } + return $result; + } +} diff --git a/thinkphp/library/think/console/Command.php b/thinkphp/library/think/console/Command.php new file mode 100644 index 0000000..d0caad2 --- /dev/null +++ b/thinkphp/library/think/console/Command.php @@ -0,0 +1,470 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Command +{ + + /** @var Console */ + private $console; + private $name; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** + * 构造方法 + * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置 + * @throws \LogicException + * @api + */ + public function __construct($name = null) + { + $this->definition = new Definition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * 忽略验证错误 + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null) + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole() + { + return $this->console; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * 配置指令 + */ + protected function configure() + { + } + + /** + * 执行指令 + * @param Input $input + * @param Output $output + * @return null|int + * @throws \LogicException + * @see setCode() + */ + protected function execute(Input $input, Output $output) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * 用户验证 + * @param Input $input + * @param Output $output + */ + protected function interact(Input $input, Output $output) + { + } + + /** + * 初始化 + * @param Input $input An InputInterface instance + * @param Output $output An OutputInterface instance + */ + protected function initialize(Input $input, Output $output) + { + } + + /** + * 执行 + * @param Input $input + * @param Output $output + * @return int + * @throws \Exception + * @see setCode() + * @see execute() + */ + public function run(Input $input, Output $output) + { + $this->input = $input; + $this->output = $output; + + $this->getSynopsis(true); + $this->getSynopsis(false); + + $this->mergeConsoleDefinition(); + + try { + $input->bind($this->definition); + } catch (\Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 设置执行代码 + * @param callable $code callable(InputInterface $input, OutputInterface $output) + * @return Command + * @throws \InvalidArgumentException + * @see execute() + */ + public function setCode(callable $code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + + $this->code = $code; + + return $this; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition($mergeArgs = true) + { + if (null === $this->console + || (true === $this->consoleDefinitionMerged + && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs)) + ) { + return; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->console->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->console->getDefinition()->getOptions()); + + $this->consoleDefinitionMerged = true; + if ($mergeArgs) { + $this->consoleDefinitionMergedWithArgs = true; + } + } + + /** + * 设置参数定义 + * @param array|Definition $definition + * @return Command + * @api + */ + public function setDefinition($definition) + { + if ($definition instanceof Definition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->consoleDefinitionMerged = false; + + return $this; + } + + /** + * 获取参数定义 + * @return Definition + * @api + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new Argument($name, $mode, $description, $default)); + + return $this; + } + + /** + * 添加选项 + * @param string $name 选项名称 + * @param string $shortcut 别名 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * 设置指令名称 + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp() + { + return $this->help; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $_SERVER['PHP_SELF'] . ' ' . $name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + + /** + * 设置别名 + * @param string[] $aliases + * @return Command + * @throws \InvalidArgumentException + */ + public function setAliases($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * 添加用法介绍 + * @param string $usage + * @return $this + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws \InvalidArgumentException + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/thinkphp/library/think/console/Input.php b/thinkphp/library/think/console/Input.php new file mode 100644 index 0000000..2482dfd --- /dev/null +++ b/thinkphp/library/think/console/Input.php @@ -0,0 +1,464 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Input +{ + + /** + * @var Definition + */ + protected $definition; + + /** + * @var Option[] + */ + protected $options = []; + + /** + * @var Argument[] + */ + protected $arguments = []; + + protected $interactive = true; + + private $tokens; + private $parsed; + + public function __construct($argv = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + // 去除命令名 + array_shift($argv); + } + + $this->tokens = $argv; + + $this->definition = new Definition(); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * 绑定实例 + * @param Definition $definition A InputDefinition instance + */ + public function bind(Definition $definition) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * 解析短选项 + * @param string $token 当前的指令. + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) + && $this->definition->getOptionForShortcut($name[0])->acceptValue() + ) { + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * 解析短选项 + * @param string $name 当前指令 + * @throws \RuntimeException + */ + private function parseShortOptionSet($name) + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * 解析完整选项 + * @param string $token 当前指令 + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } else { + $this->addLongOption($name, null); + } + } + + /** + * 解析参数 + * @param string $token 当前指令 + * @throws \RuntimeException + */ + private function parseArgument($token) + { + $c = count($this->arguments); + + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + + $this->arguments[$arg->getName()][] = $token; + } else { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * 添加一个短选项的值 + * @param string $shortcut 短名称 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * 添加一个完整选项的值 + * @param string $name 选项名 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = ''; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * 获取第一个参数 + * @return string|null + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + return; + } + + /** + * 检查原始参数是否包含某个值 + * @param string|array $values 需要检查的值 + * @return bool + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + return true; + } + } + } + + return false; + } + + /** + * 获取原始选项的值 + * @param string|array $values 需要检查的值 + * @param mixed $default 默认值 + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * 验证输入 + * @throws \RuntimeException + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * 检查输入是否是交互的 + * @return bool + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * 检查是否存在某个参数 + * @param string|int $name 参数名或位置 + * @return bool + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * 是否有某个选项 + * @param string $name 选项名 + * @return bool + */ + public function hasOption($name) + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * 返回传递给命令的参数的字符串 + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . $this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/thinkphp/library/think/console/LICENSE b/thinkphp/library/think/console/LICENSE new file mode 100644 index 0000000..0abe056 --- /dev/null +++ b/thinkphp/library/think/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/thinkphp/library/think/console/Output.php b/thinkphp/library/think/console/Output.php new file mode 100644 index 0000000..65dc9fb --- /dev/null +++ b/thinkphp/library/think/console/Output.php @@ -0,0 +1,222 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use Exception; +use think\console\output\Ask; +use think\console\output\Descriptor; +use think\console\output\driver\Buffer; +use think\console\output\driver\Console; +use think\console\output\driver\Nothing; +use think\console\output\Question; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +/** + * Class Output + * @package think\console + * + * @see \think\console\output\driver\Console::setDecorated + * @method void setDecorated($decorated) + * + * @see \think\console\output\driver\Buffer::fetch + * @method string fetch() + * + * @method void info($message) + * @method void error($message) + * @method void comment($message) + * @method void warning($message) + * @method void highlight($message) + * @method void question($message) + */ +class Output +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + private $verbosity = self::VERBOSITY_NORMAL; + + /** @var Buffer|Console|Nothing */ + private $handle = null; + + protected $styles = [ + 'info', + 'error', + 'comment', + 'question', + 'highlight', + 'warning' + ]; + + public function __construct($driver = 'console') + { + $class = '\\think\\console\\output\\driver\\' . ucwords($driver); + + $this->handle = new $class($this); + } + + public function ask(Input $input, $question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function askHidden(Input $input, $question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function confirm(Input $input, $question, $default = true) + { + return $this->askQuestion($input, new Confirmation($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(Input $input, $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion($input, new Choice($question, $choices, $default)); + } + + protected function askQuestion(Input $input, Question $question) + { + $ask = new Ask($input, $this, $question); + $answer = $ask->run(); + + if ($input->isInteractive()) { + $this->newLine(); + } + + return $answer; + } + + protected function block($style, $message) + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine($count = 1) + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(\Exception $e) + { + $this->handle->renderException($e); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []) + { + $descriptor = new Descriptor(); + $options = array_merge([ + 'raw_text' => false, + ], $options); + + $descriptor->describe($this, $object, $options); + } + + public function __call($method, $args) + { + if (in_array($method, $this->styles)) { + array_unshift($args, $method); + return call_user_func_array([$this, 'block'], $args); + } + + if ($this->handle && method_exists($this->handle, $method)) { + return call_user_func_array([$this->handle, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + +} diff --git a/thinkphp/library/think/console/bin/README.md b/thinkphp/library/think/console/bin/README.md new file mode 100644 index 0000000..9acc52f --- /dev/null +++ b/thinkphp/library/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/thinkphp/library/think/console/bin/hiddeninput.exe b/thinkphp/library/think/console/bin/hiddeninput.exe new file mode 100644 index 0000000000000000000000000000000000000000..c8cf65e8d819e6e525121cf6b21f1c2429746038 GIT binary patch literal 9216 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 J0sQ~1{4brj`U(I5 literal 0 HcmV?d00001 diff --git a/thinkphp/library/think/console/command/Build.php b/thinkphp/library/think/console/command/Build.php new file mode 100644 index 0000000..39806c3 --- /dev/null +++ b/thinkphp/library/think/console/command/Build.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class Build extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->setDefinition([ + new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"), + new Option('module', null, Option::VALUE_OPTIONAL, "module name"), + ]) + ->setDescription('Build Application Dirs'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->hasOption('module')) { + \think\Build::module($input->getOption('module')); + $output->writeln("Successed"); + return; + } + + if ($input->hasOption('config')) { + $build = include $input->getOption('config'); + } else { + $build = include APP_PATH . 'build.php'; + } + if (empty($build)) { + $output->writeln("Build Config Is Empty"); + return; + } + \think\Build::run($build); + $output->writeln("Successed"); + + } +} diff --git a/thinkphp/library/think/console/command/Clear.php b/thinkphp/library/think/console/command/Clear.php new file mode 100644 index 0000000..1b5102e --- /dev/null +++ b/thinkphp/library/think/console/command/Clear.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\Cache; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; + +class Clear extends Command +{ + protected function configure() + { + // 指令配置 + $this + ->setName('clear') + ->addArgument('type', Argument::OPTIONAL, 'type to clear', null) + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $path = $input->getOption('path') ?: RUNTIME_PATH; + + $type = $input->getArgument('type'); + + if ($type == 'route') { + Cache::clear('route_check'); + } else { + if (is_dir($path)) { + $this->clearPath($path); + } + } + + $output->writeln("Clear Successed"); + } + + protected function clearPath($path) + { + $path = realpath($path) . DS; + $files = scandir($path); + if ($files) { + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + $this->clearPath($path . $file); + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } + } +} diff --git a/thinkphp/library/think/console/command/Help.php b/thinkphp/library/think/console/command/Help.php new file mode 100644 index 0000000..bae2c65 --- /dev/null +++ b/thinkphp/library/think/console/command/Help.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Help extends Command +{ + + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this->setName('help')->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ])->setDescription('Displays help for a command')->setHelp(<<%command.name% command displays help for a given command: + + php %command.full_name% list + +To display the list of available commands, please use the list command. +EOF + ); + } + + /** + * Sets the command. + * @param Command $command The command to set + */ + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + if (null === $this->command) { + $this->command = $this->getConsole()->find($input->getArgument('command_name')); + } + + $output->describe($this->command, [ + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + } +} diff --git a/thinkphp/library/think/console/command/Lists.php b/thinkphp/library/think/console/command/Lists.php new file mode 100644 index 0000000..084ddaa --- /dev/null +++ b/thinkphp/library/think/console/command/Lists.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\input\Definition as InputDefinition; + +class Lists extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<%command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ); + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + $output->describe($this->getConsole(), [ + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list') + ]); + } +} diff --git a/thinkphp/library/think/console/command/Make.php b/thinkphp/library/think/console/command/Make.php new file mode 100644 index 0000000..d1daf34 --- /dev/null +++ b/thinkphp/library/think/console/command/Make.php @@ -0,0 +1,110 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\App; +use think\Config; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +abstract class Make extends Command +{ + + protected $type; + + abstract protected function getStub(); + + protected function configure() + { + $this->addArgument('name', Argument::REQUIRED, "The name of the class"); + } + + protected function execute(Input $input, Output $output) + { + + $name = trim($input->getArgument('name')); + + $classname = $this->getClassName($name); + + $pathname = $this->getPathName($classname); + + if (is_file($pathname)) { + $output->writeln('' . $this->type . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(strtolower(dirname($pathname)), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ' created successfully.'); + + } + + protected function buildClass($name) + { + $stub = file_get_contents($this->getStub()); + + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + + return str_replace(['{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + $namespace, + App::$namespace, + ], $stub); + + } + + protected function getPathName($name) + { + $name = str_replace(App::$namespace . '\\', '', $name); + + return APP_PATH . str_replace('\\', '/', $name) . '.php'; + } + + protected function getClassName($name) + { + $appNamespace = App::$namespace; + + if (strpos($name, $appNamespace . '\\') === 0) { + return $name; + } + + if (Config::get('app_multi_module')) { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = 'common'; + } + } else { + $module = null; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($appNamespace, $module) . '\\' . $name; + } + + protected function getNamespace($appNamespace, $module) + { + return $module ? ($appNamespace . '\\' . $module) : $appNamespace; + } + +} diff --git a/thinkphp/library/think/console/command/make/Controller.php b/thinkphp/library/think/console/command/make/Controller.php new file mode 100644 index 0000000..afa7be9 --- /dev/null +++ b/thinkphp/library/think/console/command/make/Controller.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\Config; +use think\console\command\Make; +use think\console\input\Option; + +class Controller extends Make +{ + + protected $type = "Controller"; + + protected function configure() + { + parent::configure(); + $this->setName('make:controller') + ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub() + { + if ($this->input->getOption('plain')) { + return __DIR__ . '/stubs/controller.plain.stub'; + } + + return __DIR__ . '/stubs/controller.stub'; + } + + protected function getClassName($name) + { + return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\controller'; + } + +} diff --git a/thinkphp/library/think/console/command/make/Model.php b/thinkphp/library/think/console/command/make/Model.php new file mode 100644 index 0000000..d4e9b5d --- /dev/null +++ b/thinkphp/library/think/console/command/make/Model.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Model extends Make +{ + protected $type = "Model"; + + protected function configure() + { + parent::configure(); + $this->setName('make:model') + ->setDescription('Create a new model class'); + } + + protected function getStub() + { + return __DIR__ . '/stubs/model.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\model'; + } +} diff --git a/thinkphp/library/think/console/command/make/stubs/controller.plain.stub b/thinkphp/library/think/console/command/make/stubs/controller.plain.stub new file mode 100644 index 0000000..b7539dc --- /dev/null +++ b/thinkphp/library/think/console/command/make/stubs/controller.plain.stub @@ -0,0 +1,10 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\Config; +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Autoload extends Command +{ + + protected function configure() + { + $this->setName('optimize:autoload') + ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); + } + + protected function execute(Input $input, Output $output) + { + + $classmapFile = << realpath(rtrim(APP_PATH)), + 'think\\' => LIB_PATH . 'think', + 'behavior\\' => LIB_PATH . 'behavior', + 'traits\\' => LIB_PATH . 'traits', + '' => realpath(rtrim(EXTEND_PATH)), + ]; + + $root_namespace = Config::get('root_namespace'); + foreach ($root_namespace as $namespace => $dir) { + $namespacesToScan[$namespace . '\\'] = realpath($dir); + } + + krsort($namespacesToScan); + $classMap = []; + foreach ($namespacesToScan as $namespace => $dir) { + + if (!is_dir($dir)) { + continue; + } + + $namespaceFilter = $namespace === '' ? null : $namespace; + $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); + } + + ksort($classMap); + foreach ($classMap as $class => $code) { + $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; + } + $classmapFile .= "];\n"; + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); + + $output->writeln('Succeed!'); + } + + protected function addClassMapCode($dir, $namespace, $classMap) + { + foreach ($this->createMap($dir, $namespace) as $class => $path) { + + $pathCode = $this->getPathCode($path) . ",\n"; + + if (!isset($classMap[$class])) { + $classMap[$class] = $pathCode; + } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . str_replace(["',\n"], [ + '', + ], $classMap[$class]) . '" and "' . $path . '", the first will be used.' + ); + } + } + return $classMap; + } + + protected function getPathCode($path) + { + + $baseDir = ''; + $libPath = $this->normalizePath(realpath(LIB_PATH)); + $appPath = $this->normalizePath(realpath(APP_PATH)); + $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); + $rootPath = $this->normalizePath(realpath(ROOT_PATH)); + $path = $this->normalizePath($path); + + if ($libPath !== null && strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen(LIB_PATH)); + $baseDir = 'LIB_PATH'; + } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { + $path = substr($path, strlen($appPath) + 1); + $baseDir = 'APP_PATH'; + } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { + $path = substr($path, strlen($extendPath) + 1); + $baseDir = 'EXTEND_PATH'; + } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { + $path = substr($path, strlen($rootPath) + 1); + $baseDir = 'ROOT_PATH'; + } + + if ($path !== false) { + $baseDir .= " . "; + } + + return $baseDir . (($path !== false) ? var_export($path, true) : ""); + } + + protected function normalizePath($path) + { + if ($path === false) { + return; + } + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = false; + + if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + if (substr($path, 0, 1) === '/') { + $absolute = true; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && ($absolute || $up)) { + array_pop($parts); + $up = !(empty($parts) || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + return $prefix . ($absolute ? '/' : '') . implode('/', $parts); + } + + protected function createMap($path, $namespace = null) + { + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path)) { + + $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); + + $path = []; + + /** @var \SplFileInfo $object */ + foreach ($objects as $object) { + if ($object->isFile() && $object->getExtension() == 'php') { + $path[] = $object; + } + } + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "' . $path . + '" which does not appear to be a file nor a folder' + ); + } + } + + $map = []; + + /** @var \SplFileInfo $file */ + foreach ($path as $file) { + $filePath = $file->getRealPath(); + + if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { + continue; + } + + $classes = $this->findClasses($filePath); + + foreach ($classes as $class) { + if (null !== $namespace && 0 !== strpos($class, $namespace)) { + continue; + } + + if (!isset($map[$class])) { + $map[$class] = $filePath; + } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' + ); + } + } + } + + return $map; + } + + protected function findClasses($path) + { + $extraTypes = '|trait'; + + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!is_readable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim(file_get_contents($path))) { + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { + return []; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); + if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + if ($name[0] === ':') { + $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif ($matches['type'][$i] === 'enum') { + $name = rtrim($name, ':'); + } + $classes[] = ltrim($namespace . $name, '\\'); + } + } + + return $classes; + } + +} diff --git a/thinkphp/library/think/console/command/optimize/Config.php b/thinkphp/library/think/console/command/optimize/Config.php new file mode 100644 index 0000000..59c69a8 --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Config.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\Config as ThinkConfig; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +class Config extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:config') + ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') + ->setDescription('Build config and common file cache.'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getArgument('module')) { + $module = $input->getArgument('module') . DS; + } else { + $module = ''; + } + + $content = 'buildCacheContent($module); + + if (!is_dir(RUNTIME_PATH . $module)) { + @mkdir(RUNTIME_PATH . $module, 0755, true); + } + + file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); + + $output->writeln('Succeed!'); + } + + protected function buildCacheContent($module) + { + $content = ''; + $path = realpath(APP_PATH . $module) . DS; + + if ($module) { + // 加载模块配置 + $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + ThinkConfig::load($filename, 'database'); + + // 加载应用状态配置 + if ($config['app_status']) { + $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if (strpos($file, CONF_EXT)) { + $filename = $dir . DS . $file; + ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; + } + + // 加载公共文件 + if (is_file($path . 'common' . EXT)) { + $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; + } + + $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; + return $content; + } +} diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/thinkphp/library/think/console/command/optimize/Route.php new file mode 100644 index 0000000..6da1d9a --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Route.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Route extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:route') + ->setDescription('Build route cache.'); + } + + protected function execute(Input $input, Output $output) + { + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache() + { + $files = \think\Config::get('route_config_file'); + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + $config = include CONF_PATH . $file . CONF_EXT; + if (is_array($config)) { + \think\Route::import($config); + } + } + } + $rules = \think\Route::rules(true); + array_walk_recursive($rules, [$this, 'buildClosure']); + $content = 'getStartLine(); + $endLine = $reflection->getEndLine(); + $file = $reflection->getFileName(); + $item = file($file); + $content = ''; + for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { + $content .= $item[$i]; + } + $start = strpos($content, 'function'); + $end = strrpos($content, '}'); + $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; + } + } +} diff --git a/thinkphp/library/think/console/command/optimize/Schema.php b/thinkphp/library/think/console/command/optimize/Schema.php new file mode 100644 index 0000000..3353424 --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Schema.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\Db; + +class Schema extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:schema') + ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .') + ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + if (!is_dir(RUNTIME_PATH . 'schema')) { + @mkdir(RUNTIME_PATH . 'schema', 0755, true); + } + $config = []; + if ($input->hasOption('config')) { + $config = $input->getOption('config'); + } + if ($input->hasOption('module')) { + $module = $input->getOption('module'); + // 读取模型 + $path = APP_PATH . $module . DS . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $app = App::$namespace; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } elseif ($input->hasOption('table')) { + $table = $input->getOption('table'); + if (!strpos($table, '.')) { + $dbName = Db::connect($config)->getConfig('database'); + } + $tables[] = $table; + } elseif ($input->hasOption('db')) { + $dbName = $input->getOption('db'); + $tables = Db::connect($config)->getTables($dbName); + } elseif (!\think\Config::get('app_multi_module')) { + $app = App::$namespace; + $path = APP_PATH . 'model'; + $list = is_dir($path) ? scandir($path) : []; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } else { + $tables = Db::connect($config)->getTables(); + } + + $db = isset($dbName) ? $dbName . '.' : ''; + $this->buildDataBaseSchema($tables, $db, $config); + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema($class) + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + $table = $class::getTable(); + $dbName = $class::getConfig('database'); + $content = 'getFields($table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); + } + } + + protected function buildDataBaseSchema($tables, $db, $config) + { + if ('' == $db) { + $dbName = Db::connect($config)->getConfig('database') . '.'; + } else { + $dbName = $db; + } + foreach ($tables as $table) { + $content = 'getFields($db . $table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); + } + } +} diff --git a/thinkphp/library/think/console/input/Argument.php b/thinkphp/library/think/console/input/Argument.php new file mode 100644 index 0000000..16223bb --- /dev/null +++ b/thinkphp/library/think/console/input/Argument.php @@ -0,0 +1,115 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Argument +{ + + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 参数名 + * @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL + * @param string $description 描述 + * @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效) + * @throws \InvalidArgumentException + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * 获取参数名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/thinkphp/library/think/console/input/Definition.php b/thinkphp/library/think/console/input/Definition.php new file mode 100644 index 0000000..c71977e --- /dev/null +++ b/thinkphp/library/think/console/input/Definition.php @@ -0,0 +1,375 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Definition +{ + + /** + * @var Argument[] + */ + private $arguments; + + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + + /** + * @var Option[] + */ + private $options; + private $shortcuts; + + /** + * 构造方法 + * @param array $definition + * @api + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * 设置指令的定义 + * @param array $definition 定义的数组 + */ + public function setDefinition(array $definition) + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof Option) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * 设置参数 + * @param Argument[] $arguments 参数数组 + */ + public function setArguments($arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments($arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * 根据名称或者位置获取参数 + * @param string|int $name 参数名或者位置 + * @return Argument 参数 + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * 根据名称或位置检查是否具有某个参数 + * @param string|int $name 参数名或者位置 + * @return bool + * @api + */ + public function hasArgument($name) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions($options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions($options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) + && !$option->equals($this->options[$this->shortcuts[$shortcut]]) + ) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * 根据名称获取选项 + * @param string $name 选项名 + * @return Option + * @throws \InvalidArgumentException + * @api + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * 根据名称检查是否有这个选项 + * @param string $name 选项名 + * @return bool + * @api + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * 获取该指令的介绍 + * @param bool $short 是否简洁介绍 + * @return string + */ + public function getSynopsis($short = false) + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<' . $argument->getName() . '>'; + if (!$argument->isRequired()) { + $element = '[' . $element . ']'; + } elseif ($argument->isArray()) { + $element .= ' (' . $element . ')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } +} diff --git a/thinkphp/library/think/console/input/Option.php b/thinkphp/library/think/console/input/Option.php new file mode 100644 index 0000000..e5707c9 --- /dev/null +++ b/thinkphp/library/think/console/input/Option.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Option +{ + + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 选项名 + * @param string|array $shortcut 短名称,多个用|隔开或者使用数组 + * @param int $mode 选项类型(可选类型为 self::VALUE_*) + * @param string $description 描述 + * @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null) + * @throws \InvalidArgumentException + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new \InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new \InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * 获取短名称 + * @return string + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * 获取选项名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否可以设置值 + * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * 是否必须 + * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * 是否可选 + * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * 选项值是否接受数组 + * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述文字 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 检查所给选项是否是当前这个 + * @param Option $option + * @return bool + */ + public function equals(Option $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional(); + } +} diff --git a/thinkphp/library/think/console/output/Ask.php b/thinkphp/library/think/console/output/Ask.php new file mode 100644 index 0000000..3933eb2 --- /dev/null +++ b/thinkphp/library/think/console/output/Ask.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\console\Input; +use think\console\Output; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +class Ask +{ + private static $stty; + + private static $shell; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var Question */ + protected $question; + + public function __construct(Input $input, Output $output, Question $question) + { + $this->input = $input; + $this->output = $output; + $this->question = $question; + } + + public function run() + { + if (!$this->input->isInteractive()) { + return $this->question->getDefault(); + } + + if (!$this->question->getValidator()) { + return $this->doAsk(); + } + + $that = $this; + + $interviewer = function () use ($that) { + return $that->doAsk(); + }; + + return $this->validateAttempts($interviewer); + } + + protected function doAsk() + { + $this->writePrompt(); + + $inputStream = STDIN; + $autocomplete = $this->question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($this->question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($inputStream)); + } catch (\RuntimeException $e) { + if (!$this->question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); + + if ($normalizer = $this->question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function autocomplete($inputStream) + { + $autocomplete = $this->question->getAutocompleterValues(); + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -icanon -echo'); + + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + $this->output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + $c .= fread($inputStream, 2); + + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + $this->output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $this->output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $this->output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + $this->output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + $this->output->write("\0337"); + $this->output->highlight(substr($matches[$ofs], $i)); + $this->output->write("\0338"); + } + } + + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + protected function getHiddenResponse($inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__ . '/../bin/hiddeninput.exe'; + + $value = rtrim(shell_exec($exe)); + $this->output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $this->output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $this->output->writeln(''); + + return $value; + } + + throw new \RuntimeException('Unable to hide the response.'); + } + + protected function validateAttempts($interviewer) + { + /** @var \Exception $error */ + $error = null; + $attempts = $this->question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->output->error($error->getMessage()); + } + + try { + return call_user_func($this->question->getValidator(), $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * 显示问题的提示信息 + */ + protected function writePrompt() + { + $text = $this->question->getQuestion(); + $default = $this->question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $this->question instanceof Confirmation: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $this->question instanceof Choice && $this->question->isMultiselect(): + $choices = $this->question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, implode(', ', $default)); + + break; + + case $this->question instanceof Choice: + $choices = $this->question->getChoices(); + $text = sprintf(' %s [%s]:', $text, $choices[$default]); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, $default); + } + + $this->output->writeln($text); + + if ($this->question instanceof Choice) { + $width = max(array_map('strlen', array_keys($this->question->getChoices()))); + + foreach ($this->question->getChoices() as $key => $value) { + $this->output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $this->output->write(' > '); + } + + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } +} diff --git a/thinkphp/library/think/console/output/Descriptor.php b/thinkphp/library/think/console/output/Descriptor.php new file mode 100644 index 0000000..23dc648 --- /dev/null +++ b/thinkphp/library/think/console/output/Descriptor.php @@ -0,0 +1,319 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\Console; +use think\console\Command; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\descriptor\Console as ConsoleDescription; + +class Descriptor +{ + + /** + * @var Output + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(Output $output, $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Console: + $this->describeConsole($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * 输出内容 + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW); + } + + /** + * 描述参数 + * @param InputArgument $argument + * @param array $options + * @return void + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() + && (!is_array($argument->getDefault()) + || count($argument->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; + + $this->writeText(sprintf(" %s%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options); + } + + /** + * 描述选项 + * @param InputOption $option + * @param array $options + * @return void + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() + && (!is_array($option->getDefault()) + || count($option->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '=' . strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '[' . $value . ']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value)); + + $spacingWidth = $totalWidth - strlen($synopsis) + 2; + + $this->writeText(sprintf(" %s%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : ''), $options); + } + + /** + * 描述输入 + * @param InputDefinition $definition + * @param array $options + * @return void + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + /** + * 描述指令 + * @param Command $command + * @param array $options + * @return void + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeConsoleDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' ' . $usage, $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' ' . str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * 描述控制台 + * @param Console $console + * @param array $options + * @return void + */ + protected function describeConsole(Console $console, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ConsoleDescription($console, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $console->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' ' . $namespace['id'] . '', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - strlen($name); + $this->writeText(sprintf(" %s%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name) + ->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = []) + { + $this->write(isset($options['raw_text']) + && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true); + } + + /** + * 格式化 + * @param mixed $default + * @return string + */ + private function formatDefaultValue($default) + { + return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * @param Command[] $commands + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + return $width + 2; + } + + /** + * @param InputOption[] $options + * @return int + */ + private function calculateTotalWidthForOptions($options) + { + $totalWidth = 0; + foreach ($options as $option) { + $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- + + if ($option->acceptValue()) { + $valueLength = 1 + strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/thinkphp/library/think/console/output/Formatter.php b/thinkphp/library/think/console/output/Formatter.php new file mode 100644 index 0000000..f8bee55 --- /dev/null +++ b/thinkphp/library/think/console/output/Formatter.php @@ -0,0 +1,198 @@ + +// +---------------------------------------------------------------------- +namespace think\console\output; + +use think\console\output\formatter\Stack as StyleStack; +use think\console\output\formatter\Style; + +class Formatter +{ + + private $decorated = false; + private $styles = []; + private $styleStack; + + /** + * 转义 + * @param string $text + * @return string + */ + public static function escape($text) + { + return preg_replace('/([^\\\\]?)setStyle('error', new Style('white', 'red')); + $this->setStyle('info', new Style('green')); + $this->setStyle('comment', new Style('yellow')); + $this->setStyle('question', new Style('black', 'cyan')); + $this->setStyle('highlight', new Style('red')); + $this->setStyle('warning', new Style('black', 'yellow')); + + $this->styleStack = new StyleStack(); + } + + /** + * 设置外观标识 + * @param bool $decorated 是否美化文字 + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * 获取外观标识 + * @return bool + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * 添加一个新样式 + * @param string $name 样式名 + * @param Style $style 样式实例 + */ + public function setStyle($name, Style $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * 是否有这个样式 + * @param string $name + * @return bool + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * 获取样式 + * @param string $name + * @return Style + * @throws \InvalidArgumentException + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * 使用所给的样式格式化文字 + * @param string $message 文字 + * @return string + */ + public function format($message) + { + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + return str_replace('\\<', '<', $output); + } + + /** + * @return StyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * 根据字符串创建新的样式实例 + * @param string $string + * @return Style|bool + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new Style(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * 从堆栈应用样式到文字 + * @param string $text 文字 + * @return string + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/thinkphp/library/think/console/output/Question.php b/thinkphp/library/think/console/output/Question.php new file mode 100644 index 0000000..03975f2 --- /dev/null +++ b/thinkphp/library/think/console/output/Question.php @@ -0,0 +1,211 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +class Question +{ + + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * 构造方法 + * @param string $question 问题 + * @param mixed $default 默认答案 + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * 获取问题 + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * 获取默认答案 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 是否隐藏答案 + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * 隐藏答案 + * @param bool $hidden + * @return Question + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * 不能被隐藏是否撤销 + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * 设置不能被隐藏的时候的操作 + * @param bool $fallback + * @return Question + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * 获取自动完成 + * @return null|array|\Traversable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * 设置自动完成的值 + * @param null|array|\Traversable $values + * @return Question + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values) && $this->isAssoc($values)) { + $values = array_merge(array_keys($values), array_values($values)); + } + + if (null !== $values && !is_array($values)) { + if (!$values instanceof \Traversable || $values instanceof \Countable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); + } + } + + if ($this->hidden) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * 设置答案的验证器 + * @param null|callable $validator + * @return Question The current instance + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * 获取验证器 + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * 设置最大重试次数 + * @param null|int $attempts + * @return Question + * @throws \InvalidArgumentException + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * 获取最大重试次数 + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * 设置响应的回调 + * @param string|\Closure $normalizer + * @return Question + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * 获取响应回调 + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * @return string|\Closure + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/thinkphp/library/think/console/output/descriptor/Console.php b/thinkphp/library/think/console/output/descriptor/Console.php new file mode 100644 index 0000000..4648b68 --- /dev/null +++ b/thinkphp/library/think/console/output/descriptor/Console.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\descriptor; + +use think\Console as ThinkConsole; +use think\console\Command; + +class Console +{ + + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var ThinkConsole + */ + private $console; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * 构造方法 + * @param ThinkConsole $console + * @param string|null $namespace + */ + public function __construct(ThinkConsole $console, $namespace = null) + { + $this->console = $console; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectConsole() + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + /** + * @param array $commands + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->console->extractNamespace($name, 1); + if (!$key) { + $key = self::GLOBAL_NAMESPACE; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/thinkphp/library/think/console/output/driver/Buffer.php b/thinkphp/library/think/console/output/driver/Buffer.php new file mode 100644 index 0000000..c77a2ec --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Buffer.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Buffer +{ + /** + * @var string + */ + private $buffer = ''; + + public function __construct(Output $output) + { + // do nothing + } + + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + return $content; + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Exception $e) + { + // do nothing + } + +} diff --git a/thinkphp/library/think/console/output/driver/Console.php b/thinkphp/library/think/console/output/driver/Console.php new file mode 100644 index 0000000..8f29fd0 --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Console.php @@ -0,0 +1,373 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; +use think\console\output\Formatter; + +class Console +{ + + /** @var Resource */ + private $stdout; + + /** @var Formatter */ + private $formatter; + + private $terminalDimensions; + + /** @var Output */ + private $output; + + public function __construct(Output $output) + { + $this->output = $output; + $this->formatter = new Formatter(); + $this->stdout = $this->openOutputStream(); + $decorated = $this->hasColorSupport($this->stdout); + $this->formatter->setDecorated($decorated); + } + + public function getFormatter() + { + return $this->formatter; + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null) + { + if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case Output::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case Output::OUTPUT_RAW: + break; + case Output::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline, $stream); + } + } + + public function renderException(\Exception $e) + { + $stderr = $this->openErrorStream(); + $decorated = $this->hasColorSupport($stderr); + $this->formatter->setDecorated($decorated); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = []; + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = ['', '']; + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); + + if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { + $this->write('Exception trace:', true, Output::OUTPUT_NORMAL, $stderr); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); + } + + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + } + } while ($e = $e->getPrevious()); + + } + + /** + * 获取终端宽度 + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * 获取终端高度 + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * 获取当前终端的尺寸 + * @return array + */ + public function getTerminalDimensions() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DS) { + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + } + + if ($sttyString = $this->getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + } + + return [null, null]; + } + + /** + * 获取stty列数 + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + return; + } + + /** + * 获取终端模式 + * @return string x 或 null + */ + private function getMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2] . 'x' . $matches[1]; + } + } + return; + } + + private function stringWidth($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth($string, $width) + { + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + if (mb_strwidth($line . $char, 'utf8') <= $width) { + $line .= $char; + continue; + } + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + private function isRunningOS400() + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } + + /** + * 将消息写入到输出。 + * @param string $message 消息 + * @param bool $newline 是否另起一行 + * @param null $stream + */ + protected function doWrite($message, $newline, $stream = null) + { + if (null === $stream) { + $stream = $this->stdout; + } + if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { + throw new \RuntimeException('Unable to write output.'); + } + + fflush($stream); + } + + /** + * 是否支持着色 + * @param $stream + * @return bool + */ + protected function hasColorSupport($stream) + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($stream); + } + +} diff --git a/thinkphp/library/think/console/output/driver/Nothing.php b/thinkphp/library/think/console/output/driver/Nothing.php new file mode 100644 index 0000000..9a55f77 --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Nothing.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + // do nothing + } + + public function renderException(\Exception $e) + { + // do nothing + } +} diff --git a/thinkphp/library/think/console/output/formatter/Stack.php b/thinkphp/library/think/console/output/formatter/Stack.php new file mode 100644 index 0000000..4864a3f --- /dev/null +++ b/thinkphp/library/think/console/output/formatter/Stack.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Stack +{ + + /** + * @var Style[] + */ + private $styles; + + /** + * @var Style + */ + private $emptyStyle; + + /** + * 构造方法 + * @param Style|null $emptyStyle + */ + public function __construct(Style $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new Style(); + $this->reset(); + } + + /** + * 重置堆栈 + */ + public function reset() + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style) + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + /** + * @var int $index + * @var Style $stackedStyle + */ + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * 计算堆栈的当前样式。 + * @return Style + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @param Style $emptyStyle + * @return Stack + */ + public function setEmptyStyle(Style $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return Style + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/thinkphp/library/think/console/output/formatter/Style.php b/thinkphp/library/think/console/output/formatter/Style.php new file mode 100644 index 0000000..d9b0999 --- /dev/null +++ b/thinkphp/library/think/console/output/formatter/Style.php @@ -0,0 +1,189 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + + private static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + ]; + private static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + ]; + private static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $options = []; + + /** + * 初始化输出的样式 + * @param string|null $foreground 字体颜色 + * @param string|null $background 背景色 + * @param array $options 格式 + * @api + */ + public function __construct($foreground = null, $background = null, array $options = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * 设置字体颜色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * 设置背景色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * 设置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException When the option name isn't defined + * @api + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * 重置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * 批量设置字体格式 + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * 应用样式到文字 + * @param string $text 文字 + * @return string + */ + public function apply($text) + { + $setCodes = []; + $unsetCodes = []; + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/thinkphp/library/think/console/output/question/Choice.php b/thinkphp/library/think/console/output/question/Choice.php new file mode 100644 index 0000000..f6760e5 --- /dev/null +++ b/thinkphp/library/think/console/output/question/Choice.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Choice extends Question +{ + + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * 构造方法 + * @param string $question 问题 + * @param array $choices 选项 + * @param mixed $default 默认答案 + */ + public function __construct($question, array $choices, $default = null) + { + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * 可选项 + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * 获取默认的验证方法 + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = [$selected]; + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (!empty($result)) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (empty($result) && array_key_exists($value, $choices)) { + $result = $value; + } + + if (empty($result)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/thinkphp/library/think/console/output/question/Confirmation.php b/thinkphp/library/think/console/output/question/Confirmation.php new file mode 100644 index 0000000..6598f9b --- /dev/null +++ b/thinkphp/library/think/console/output/question/Confirmation.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Confirmation extends Question +{ + + private $trueAnswerRegex; + + /** + * 构造方法 + * @param string $question 问题 + * @param bool $default 默认答案 + * @param string $trueAnswerRegex 验证正则 + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * 获取默认的答案回调 + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/thinkphp/library/think/controller/Rest.php b/thinkphp/library/think/controller/Rest.php new file mode 100644 index 0000000..43ab2f6 --- /dev/null +++ b/thinkphp/library/think/controller/Rest.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace think\controller; + +use think\App; +use think\Request; +use think\Response; + +abstract class Rest +{ + + protected $method; // 当前请求类型 + protected $type; // 当前资源类型 + // 输出类型 + protected $restMethodList = 'get|post|put|delete'; + protected $restDefaultMethod = 'get'; + protected $restTypeList = 'html|xml|json|rss'; + protected $restDefaultType = 'html'; + protected $restOutputType = [ // REST允许输出的资源类型列表 + 'xml' => 'application/xml', + 'json' => 'application/json', + 'html' => 'text/html', + ]; + + /** + * 构造函数 取得模板对象实例 + * @access public + */ + public function __construct() + { + // 资源类型检测 + $request = Request::instance(); + $ext = $request->ext(); + if ('' == $ext) { + // 自动检测资源类型 + $this->type = $request->type(); + } elseif (!preg_match('/(' . $this->restTypeList . ')$/i', $ext)) { + // 资源类型非法 则用默认资源类型访问 + $this->type = $this->restDefaultType; + } else { + $this->type = $ext; + } + // 请求方式检测 + $method = strtolower($request->method()); + if (!preg_match('/(' . $this->restMethodList . ')$/i', $method)) { + // 请求方式非法 则用默认请求方法 + $method = $this->restDefaultMethod; + } + $this->method = $method; + } + + /** + * REST 调用 + * @access public + * @param string $method 方法名 + * @return mixed + * @throws \Exception + */ + public function _empty($method) + { + if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) { + // RESTFul方法支持 + $fun = $method . '_' . $this->method . '_' . $this->type; + } elseif ($this->method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) { + $fun = $method . '_' . $this->type; + } elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) { + $fun = $method . '_' . $this->method; + } + if (isset($fun)) { + return App::invokeMethod([$this, $fun]); + } else { + // 抛出异常 + throw new \Exception('error action :' . $method); + } + } + + /** + * 输出返回数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @param integer $code HTTP状态码 + * @return Response + */ + protected function response($data, $type = 'json', $code = 200) + { + return Response::create($data, $type)->code($code); + } + +} diff --git a/thinkphp/library/think/controller/Yar.php b/thinkphp/library/think/controller/Yar.php new file mode 100644 index 0000000..af4e9a1 --- /dev/null +++ b/thinkphp/library/think/controller/Yar.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\controller; + +/** + * ThinkPHP Yar控制器类 + */ +abstract class Yar +{ + + /** + * 构造函数 + * @access public + */ + public function __construct() + { + //控制器初始化 + if (method_exists($this, '_initialize')) { + $this->_initialize(); + } + + //判断扩展是否存在 + if (!extension_loaded('yar')) { + throw new \Exception('not support yar'); + } + + //实例化Yar_Server + $server = new \Yar_Server($this); + // 启动server + $server->handle(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method, $args) + {} +} diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php new file mode 100644 index 0000000..58b45aa --- /dev/null +++ b/thinkphp/library/think/db/Builder.php @@ -0,0 +1,899 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Exception; + +abstract class Builder +{ + // connection对象实例 + protected $connection; + // 查询对象实例 + protected $query; + + // 数据库表达式 + protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; + + // SQL表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT%%LOCK%%COMMENT%'; + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + * @param Query $query 数据库查询对象实例 + */ + public function __construct(Connection $connection, Query $query) + { + $this->connection = $connection; + $this->query = $query; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 获取当前的Query对象实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access protected + * @param string $sql sql语句 + * @return string + */ + protected function parseSqlTable($sql) + { + return $this->query->parseSqlTable($sql); + } + + /** + * 数据分析 + * @access protected + * @param array $data 数据 + * @param array $options 查询参数 + * @return array + * @throws Exception + */ + protected function parseData($data, $options) + { + if (empty($data)) { + return []; + } + + // 获取绑定信息 + $bind = $this->query->getFieldsBind($options['table']); + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + + $result = []; + foreach ($data as $key => $val) { + if ('*' != $options['field'] && !in_array($key, $fields, true)) { + continue; + } + + $item = $this->parseKey($key, $options, true); + if ($val instanceof Expression) { + $result[$item] = $val->getValue(); + continue; + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $val = $val->__toString(); + } + if (false === strpos($key, '.') && !in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } elseif (is_array($val) && !empty($val)) { + switch (strtolower($val[0])) { + case 'inc': + $result[$item] = $item . '+' . floatval($val[1]); + break; + case 'dec': + $result[$item] = $item . '-' . floatval($val[1]); + break; + case 'exp': + throw new Exception('not support data:[' . $val[0] . ']'); + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { + $result[$item] = $val; + } else { + $key = str_replace('.', '_', $key); + $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':data__' . $key; + } + } + } + return $result; + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + return $key; + } + + /** + * value分析 + * @access protected + * @param mixed $value + * @param string $field + * @return string|array + */ + protected function parseValue($value, $field = '') + { + if (is_string($value)) { + $value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); + } elseif (is_array($value)) { + $value = array_map([$this, 'parseValue'], $value); + } elseif (is_bool($value)) { + $value = $value ? '1' : '0'; + } elseif (is_null($value)) { + $value = 'null'; + } + return $value; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @param array $options + * @return string + */ + protected function parseField($fields, $options = []) + { + if ('*' == $fields || empty($fields)) { + $fieldsStr = '*'; + } elseif (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + foreach ($fields as $key => $field) { + if ($field instanceof Expression) { + $array[] = $field->getValue(); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options, true); + } else { + $array[] = $this->parseKey($field, $options); + } + } + $fieldsStr = implode(',', $array); + } + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param mixed $tables + * @param array $options + * @return string + */ + protected function parseTable($tables, $options = []) + { + $item = []; + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + $key = $this->parseSqlTable($key); + $item[] = $this->parseKey($key) . ' ' . (isset($options['alias'][$table]) ? $this->parseKey($options['alias'][$table]) : $this->parseKey($table)); + } else { + $table = $this->parseSqlTable($table); + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + } else { + $item[] = $this->parseKey($table); + } + } + } + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param mixed $where 查询条件 + * @param array $options 查询参数 + * @return string + */ + protected function parseWhere($where, $options) + { + $whereStr = $this->buildWhere($where, $options); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $binds = $this->query->getFieldsBind($options['table']); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + } + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param mixed $where + * @param array $options + * @return string + */ + public function buildWhere($where, $options) + { + if (empty($where)) { + $where = []; + } + + if ($where instanceof Query) { + return $this->buildWhere($where->getOptions('where'), $options); + } + + $whereStr = ''; + $binds = $this->query->getFieldsBind($options['table']); + foreach ($where as $key => $val) { + $str = []; + foreach ($val as $field => $value) { + if ($value instanceof Expression) { + $str[] = ' ' . $key . ' ( ' . $value->getValue() . ' )'; + } elseif ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $whereClause = $this->buildWhere($query->getOptions('where'), $options); + if (!empty($whereClause)) { + $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + } + } elseif (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); + } + } + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + // where子单元分析 + protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) + { + // 字段分析 + $key = $field ? $this->parseKey($field, $options, true) : ''; + + // 查询规则和条件 + if (!is_array($val)) { + $val = is_null($val) ? ['null', ''] : ['=', $val]; + } + list($exp, $value) = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $item = array_pop($val); + // 传入 or 或者 and + if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { + $rule = $item; + } else { + array_push($val, $item); + } + foreach ($val as $k => $item) { + $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; + $str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); + } + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; + } + + // 检测操作符 + if (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } else { + throw new Exception('where express error:' . $exp); + } + } + $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field); + if (preg_match('/\W/', $bindName)) { + // 处理带非单词字符的字段名 + $bindName = md5($bindName); + } + + if ($value instanceof Expression) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; + if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + $this->query->bind($bindName, $value, $bindType); + $value = ':' . $bindName; + } + } + + $whereStr = ''; + if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { + // 比较运算 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); + } + $logic = isset($val[2]) ? $val[2] : 'AND'; + $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('EXP' == $exp) { + // 表达式查询 + if ($value instanceof Expression) { + $whereStr .= '( ' . $key . ' ' . $value->getValue() . ' )'; + } else { + throw new Exception('where express error:' . $exp); + } + } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { + // NULL 查询 + $whereStr .= $key . ' IS ' . $exp; + } elseif (in_array($exp, ['NOT IN', 'IN'])) { + // IN 查询 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + if (array_key_exists($field, $binds)) { + $bind = []; + $array = []; + $i = 0; + foreach ($value as $v) { + $i++; + if ($this->query->isBind($bindName . '_in_' . $i)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; + } else { + $bindKey = $bindName . '_in_' . $i; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; + } + $this->query->bind($bind); + $zone = implode(',', $array); + } else { + $zone = implode(',', $this->parseValue($value, $field)); + } + $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; + } + } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + if (array_key_exists($field, $binds)) { + if ($this->query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } + $bind = [ + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], + ]; + $this->query->bind($bind); + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; + } else { + $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); + } + $whereStr .= $key . ' ' . $exp . ' ' . $between; + } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { + // EXISTS 查询 + if ($value instanceof \Closure) { + $whereStr .= $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $exp . ' (' . $value . ')'; + } + } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { + $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); + } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { + if (is_string($value)) { + $value = explode(',', $value); + } + + $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); + } + return $whereStr; + } + + // 执行闭包子查询 + protected function parseClosure($call, $show = true) + { + $query = new Query($this->connection); + call_user_func_array($call, [ & $query]); + return $query->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param string $value + * @param string $key + * @param array $options + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) + { + // 获取时间字段类型 + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + $type = $this->query->getTableInfo($table, 'type'); + if (isset($type[$key])) { + $info = $type[$key]; + } + if (isset($info)) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (preg_match('/(datetime|timestamp)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + $bindName = $bindName ?: $key; + + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + + $this->query->bind($bindName, $value, $bindType); + return ':' . $bindName; + } + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param array $join + * @param array $options 查询条件 + * @return string + */ + protected function parseJoin($join, $options = []) + { + $joinStr = ''; + if (!empty($join)) { + foreach ($join as $item) { + list($table, $type, $on) = $item; + $condition = []; + foreach ((array) $on as $val) { + if ($val instanceof Expression) { + $condition[] = $val->getValue(); + } elseif (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($table, $options); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } + } + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options 查询条件 + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ''; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($key, $options, true) . $sort; + } + } + $order = implode(',', $array); + + return !empty($order) ? ' ORDER BY ' . $order : ''; + } + + /** + * group分析 + * @access protected + * @param mixed $group + * @return string + */ + protected function parseGroup($group) + { + return !empty($group) ? ' GROUP BY ' . $this->parseKey($group) : ''; + } + + /** + * having分析 + * @access protected + * @param string $having + * @return string + */ + protected function parseHaving($having) + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param string $comment + * @return string + */ + protected function parseComment($comment) + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param mixed $distinct + * @return string + */ + protected function parseDistinct($distinct) + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param mixed $union + * @return string + */ + protected function parseUnion($union) + { + if (empty($union)) { + return ''; + } + $type = $union['type']; + unset($union['type']); + foreach ($union as $u) { + if ($u instanceof \Closure) { + $sql[] = $type . ' ' . $this->parseClosure($u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $this->parseSqlTable($u) . ' )'; + } + } + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param mixed $index + * @return string + */ + protected function parseForce($index) + { + if (empty($index)) { + return ''; + } + + return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); + } + + /** + * 设置锁机制 + * @access protected + * @param bool|string $lock + * @return string + */ + protected function parseLock($lock = false) + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } elseif (is_string($lock)) { + return ' ' . trim($lock) . ' '; + } + } + + /** + * 生成查询SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function select($options = []) + { + $sql = str_replace( + ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($options['table'], $options), + $this->parseDistinct($options['distinct']), + $this->parseField($options['field'], $options), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseGroup($options['group']), + $this->parseHaving($options['having']), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseUnion($options['union']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + $this->parseForce($options['force']), + ], $this->selectSql); + return $sql; + } + + /** + * 生成insert SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + */ + public function insert(array $data, $options = [], $replace = false) + { + // 分析并处理数据 + $data = $this->parseData($data, $options); + if (empty($data)) { + return 0; + } + $fields = array_keys($data); + $values = array_values($data); + + $sql = str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertSql); + + return $sql; + } + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = 'SELECT ' . implode(',', $value); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($field, $options, true); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' UNION ALL ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 生成select insert SQL + * @access public + * @param array $fields 数据 + * @param string $table 数据表 + * @param array $options 表达式 + * @return string + */ + public function selectInsert($fields, $table, $options) + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + $fields = array_map([$this, 'parseKey'], $fields); + $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); + return $sql; + } + + /** + * 生成update SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @return string + */ + public function update($data, $options) + { + $table = $this->parseTable($options['table'], $options); + $data = $this->parseData($data, $options); + if (empty($data)) { + return ''; + } + foreach ($data as $key => $val) { + $set[] = $key . '=' . $val; + } + + $sql = str_replace( + ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + implode(',', $set), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->updateSql); + + return $sql; + } + + /** + * 生成delete SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function delete($options) + { + $sql = str_replace( + ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->deleteSql); + + return $sql; + } +} diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php new file mode 100644 index 0000000..578cc8f --- /dev/null +++ b/thinkphp/library/think/db/Connection.php @@ -0,0 +1,1059 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use PDOStatement; +use think\Db; +use think\db\exception\BindParamException; +use think\Debug; +use think\Exception; +use think\exception\PDOException; +use think\Log; + +/** + * Class Connection + * @package think + * @method Query table(string $table) 指定数据表(含前缀) + * @method Query name(string $name) 指定数据表(不含前缀) + * + */ +abstract class Connection +{ + + /** @var PDOStatement PDO操作实例 */ + protected $PDOStatement; + + /** @var string 当前SQL指令 */ + protected $queryStr = ''; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + + /** @var PDO[] 数据库连接ID 支持多个连接 */ + protected $links = []; + + /** @var PDO 当前连接ID */ + protected $linkID; + protected $linkRead; + protected $linkWrite; + + // 查询结果类型 + protected $fetchType = PDO::FETCH_ASSOC; + // 字段属性大小写 + protected $attrCase = PDO::CASE_LOWER; + // 监听回调 + protected static $event = []; + // 使用Builder类 + protected $builder; + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据返回类型 + 'result_type' => PDO::FETCH_ASSOC, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, + ]; + + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + // 绑定参数 + protected $bind = []; + + /** + * 构造函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 获取新的查询对象 + * @access protected + * @return Query + */ + protected function getQuery() + { + $class = $this->config['query']; + return new $class($this); + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilder() + { + if (!empty($this->builder)) { + return $this->builder; + } else { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + } + + /** + * 调用Query类的查询方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->getQuery(), $method], $args); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn($config); + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + abstract public function getFields($tableName); + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + abstract public function getTables($dbName); + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + abstract protected function getExplain($sql); + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase($info) + { + // 字段大小写转换 + switch ($this->attrCase) { + case PDO::CASE_LOWER: + $info = array_change_key_case($info); + break; + case PDO::CASE_UPPER: + $info = array_change_key_case($info, CASE_UPPER); + break; + case PDO::CASE_NATURAL: + default: + // 不做转换 + } + return $info; + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig($config = '') + { + return $config ? $this->config[$config] : $this->config; + } + + /** + * 设置数据库的配置参数 + * @access public + * @param string|array $config 配置名称 + * @param mixed $value 配置值 + * @return void + */ + public function setConfig($config, $value = '') + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } else { + $this->config[$config] = $value; + } + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws Exception + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false) + { + if (!isset($this->links[$linkNum])) { + if (!$config) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + // 数据返回类型 + if (isset($config['result_type'])) { + $this->fetchType = $config['result_type']; + } + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + if ($config['debug']) { + $startTime = microtime(true); + } + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + if ($config['debug']) { + // 记录数据库连接信息 + Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); + } + } catch (\PDOException $e) { + if ($autoConnection) { + Log::record($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + return $this->links[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } else { + return $this->linkID; + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return mixed + * @throws PDOException + * @throws \Exception + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + $this->initConnect($master); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$queryTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行查询 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', $master); + // 返回结果集 + return $this->getResult($pdo, $procedure); + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param Query $query 查询对象 + * @return int + * @throws PDOException + * @throws \Exception + */ + public function execute($sql, $bind = [], Query $query = null) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$executeTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行语句 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', true); + + if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $query->readMaster(); + } + + $this->numRows = $this->PDOStatement->rowCount(); + return $this->numRows; + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql($sql, array $bind = []) + { + if (is_array($sql)) { + $sql = implode(';', $sql); + } + + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + if (PDO::PARAM_STR == $type) { + $value = $this->quote($value); + } elseif (PDO::PARAM_INT == $type) { + $value = (float) $value; + } + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + str_replace( + [':' . $key . ')', ':' . $key . ',', ':' . $key . ' ', ':' . $key . PHP_EOL], + [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], + $sql . ' '); + } + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []) + { + foreach ($bind as $key => $val) { + // 占位符 + $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + if (!$result) { + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam($bind) + { + foreach ($bind as $key => $val) { + $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + if (!$result) { + $param = array_shift($val); + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 获得数据集数组 + * @access protected + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return PDOStatement|array + */ + protected function getResult($pdo = false, $procedure = false) + { + if ($pdo) { + // 返回PDOStatement对象处理 + return $this->PDOStatement; + } + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + $result = $this->PDOStatement->fetchAll($this->fetchType); + $this->numRows = count($result); + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure() + { + $item = []; + do { + $result = $this->getResult(); + if ($result) { + $item[] = $result; + } + } while ($this->PDOStatement->nextRowset()); + $this->numRows = count($item); + return $item; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction($callback) + { + $this->startTrans(); + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + $this->commit(); + return $result; + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return bool|mixed + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + ++$this->transTimes; + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + + } catch (\Exception $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } catch (\Error $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->rollBack(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepointRollBack('trans' . $this->transTimes) + ); + } + + $this->transTimes = max(0, $this->transTimes - 1); + } + + /** + * 是否支持事务嵌套 + * @return bool + */ + protected function supportSavepoint() + { + return false; + } + + /** + * 生成定义保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepoint($name) + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sqlArray SQL批处理指令 + * @return boolean + */ + public function batchQuery($sqlArray = [], $bind = [], Query $query = null) + { + if (!is_array($sqlArray)) { + return false; + } + // 自动启动事务支持 + $this->startTrans(); + try { + foreach ($sqlArray as $sql) { + $this->execute($sql, $bind, $query); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute = false) + { + return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes() + { + return Db::$executeTimes; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + // 释放查询 + $this->free(); + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e) + { + if (!$this->config['break_reconnect']) { + return false; + } + + $info = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + ]; + + $error = $e->getMessage(); + + foreach ($info as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->linkID->lastInsertId($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->numRows; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + return $error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @param bool $master 是否主库查询 + * @return string + */ + public function quote($str, $master = true) + { + $this->initConnect($master); + return $this->linkID ? $this->linkID->quote($str) : $str; + } + + /** + * 数据库调试 记录当前SQL及分析性能 + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param boolean $master 主从标记 + * @return void + */ + protected function debug($start, $sql = '', $master = false) + { + if (!empty($this->config['debug'])) { + // 开启数据库调试模式 + if ($start) { + Debug::remark('queryStartTime', 'time'); + } else { + // 记录操作结束时间 + Debug::remark('queryEndTime', 'time'); + $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); + $sql = $sql ?: $this->getLastsql(); + $result = []; + // SQL性能分析 + if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { + $result = $this->getExplain($sql); + } + // SQL监听 + $this->trigger($sql, $runtime, $result, $master); + } + } + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + self::$event[] = $callback; + } + + /** + * 触发SQL事件 + * @access protected + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger($sql, $runtime, $explain = [], $master = false) + { + if (!empty(self::$event)) { + foreach (self::$event as $callback) { + if (is_callable($callback)) { + call_user_func_array($callback, [$sql, $runtime, $explain, $master]); + } + } + } else { + // 未注册监听则记录到日志中 + if ($this->config['deploy']) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + Log::record('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]', 'sql'); + if (!empty($explain)) { + Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql'); + } + } + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect($master = true) + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master || $this->transTimes) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + $this->linkID = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + $this->linkID = $this->linkRead; + } + } elseif (!$this->linkID) { + // 默认单数据库 + $this->linkID = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return PDO + */ + protected function multiConnect($master = false) + { + $_config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $_config[$name] = explode(',', $this->config[$name]); + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + $r = $m; + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($_config['hostname']) - 1)); + } + $dbMaster = false; + if ($m != $r) { + $dbMaster = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; + } + } + $dbConfig = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; + } + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 释放查询 + if ($this->PDOStatement) { + $this->free(); + } + // 关闭连接 + $this->close(); + } +} diff --git a/thinkphp/library/think/db/Expression.php b/thinkphp/library/think/db/Expression.php new file mode 100644 index 0000000..f1b92ab --- /dev/null +++ b/thinkphp/library/think/db/Expression.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +class Expression +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php new file mode 100644 index 0000000..ac4adea --- /dev/null +++ b/thinkphp/library/think/db/Query.php @@ -0,0 +1,3045 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\App; +use think\Cache; +use think\Collection; +use think\Config; +use think\Db; +use think\db\exception\BindParamException; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Exception; +use think\exception\DbException; +use think\exception\PDOException; +use think\Loader; +use think\Model; +use think\model\Relation; +use think\model\relation\OneToOne; +use think\Paginator; + +class Query +{ + // 数据库Connection对象实例 + protected $connection; + // 数据库Builder对象实例 + protected $builder; + // 当前模型类名称 + protected $model; + // 当前数据表名称(含前缀) + protected $table = ''; + // 当前数据表名称(不含前缀) + protected $name = ''; + // 当前数据表主键 + protected $pk; + // 当前数据表前缀 + protected $prefix = ''; + // 查询参数 + protected $options = []; + // 参数绑定 + protected $bind = []; + // 数据表信息 + protected static $info = []; + // 回调事件 + private static $event = []; + // 读取主库 + protected static $readMaster = []; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库对象实例 + * @param Model $model 模型对象 + */ + public function __construct(Connection $connection = null, $model = null) + { + $this->connection = $connection ?: Db::connect([], true); + $this->prefix = $this->connection->getConfig('prefix'); + $this->model = $model; + // 设置当前连接的Builder对象 + $this->setBuilder(); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws DbException + * @throws Exception + */ + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Loader::parseName(substr($method, 5)); + $where[$field] = $args[0]; + return $this->where($where)->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Loader::parseName(substr($method, 10)); + $where[$name] = $args[0]; + return $this->where($where)->value($args[1]); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; + } else { + throw new Exception('method not exist:' . __CLASS__ . '->' . $method); + } + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 切换当前的数据库连接 + * @access public + * @param mixed $config + * @return $this + */ + public function connect($config) + { + $this->connection = Db::connect($config); + $this->setBuilder(); + $this->prefix = $this->connection->getConfig('prefix'); + return $this; + } + + /** + * 设置当前的数据库Builder对象 + * @access protected + * @return void + */ + protected function setBuilder() + { + $class = $this->connection->getBuilder(); + $this->builder = new $class($this->connection, $this); + } + + /** + * 获取当前的模型对象实例 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置后续从主库读取数据 + * @access public + * @param bool $allTable + * @return void + */ + public function readMaster($allTable = false) + { + if ($allTable) { + $table = '*'; + } else { + $table = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + static::$readMaster[$table] = true; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 指定默认的数据表名(不含前缀) + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + return $this; + } + + /** + * 指定默认数据表名(含前缀) + * @access public + * @param string $table 表名 + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + return $this; + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name + * @return string + */ + public function getTable($name = '') + { + if ($name || empty($this->table)) { + $name = $name ?: $this->name; + $tableName = $this->prefix; + if ($name) { + $tableName .= Loader::parseName($name); + } + } else { + $tableName = $this->table; + } + return $tableName; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $prefix = $this->prefix; + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { + return $prefix . strtolower($match[1]); + }, $sql); + } + return $sql; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool|string $class 指定返回的数据集对象 + * @return mixed + * @throws BindParamException + * @throws PDOException + */ + public function query($sql, $bind = [], $master = false, $class = false) + { + return $this->connection->query($sql, $bind, $master, $class); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws PDOException + */ + public function execute($sql, $bind = []) + { + return $this->connection->execute($sql, $bind, $this); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->connection->getLastInsID($sequence); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->connection->getLastSql(); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction($callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->connection->rollback(); + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return boolean + */ + public function batchQuery($sql = [], $bind = []) + { + return $this->connection->batchQuery($sql, $bind); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return boolean + */ + public function getConfig($name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到分表的的数据表名 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return string + */ + public function getPartitionTableName($data, $field, $rule = []) + { + // 对数据表进行分区 + if ($field && isset($data[$field])) { + $value = $data[$field]; + $type = $rule['type']; + switch ($type) { + case 'id': + // 按照id范围分表 + $step = $rule['expr']; + $seq = floor($value / $step) + 1; + break; + case 'year': + // 按照年份分表 + if (!is_numeric($value)) { + $value = strtotime($value); + } + $seq = date('Y', $value) - $rule['expr'] + 1; + break; + case 'mod': + // 按照id的模数分表 + $seq = ($value % $rule['num']) + 1; + break; + case 'md5': + // 按照md5的序列分表 + $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1; + break; + default: + if (function_exists($type)) { + // 支持指定函数哈希 + $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1; + } else { + // 按照字段的首字母的值分表 + $seq = (ord($value{0}) % $rule['num']) + 1; + } + } + return $this->getTable() . '_' . $seq; + } else { + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; + return $tableName; + } + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function value($field, $default = null, $force = false) + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($key); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + $pdo = $this->field($field)->limit(1)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + + $result = $pdo->fetchColumn(); + if ($force) { + $result = (float) $result; + } + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, $key = '') + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $guid = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($guid); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + $pdo = $this->field($field)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + if ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + foreach ($resultSet as $val) { + if ($count > 2) { + $result[$val[$key]] = $val; + } elseif (2 == $count) { + $result[$val[$key]] = $val[$key2]; + } elseif (1 == $count) { + $result[$val[$key]] = $val[$key1]; + } + } + } else { + $result = []; + } + } + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer|string + */ + public function count($field = '*') + { + if (isset($this->options['group'])) { + if (!preg_match('/^[\w\.\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + + $count = $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + } else { + $count = $this->aggregate('COUNT', $field, true); + } + + return is_string($count) ? $count : (int) $count; + + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + if (0 === stripos($field, 'DISTINCT ')) { + list($distinct, $field) = explode(' ', $field); + } + + if (!preg_match('/^[\w\.\+\-\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + + $result = $this->value($aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $field . ') AS tp_' . strtolower($aggregate), 0, $force); + + return $result; + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function sum($field) + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function avg($field) + { + return $this->aggregate('AVG', $field, true); + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param mixed $value 字段值 + * @return integer + */ + public function setField($field, $value = '') + { + if (is_array($field)) { + $data = $field; + } else { + $data[$field] = $value; + } + return $this->update($data); + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + } + return $this->setField($field, ['inc', $step]); + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + return $this->setField($field, ['inc', $step]); + } + return $this->setField($field, ['dec', $step]); + } + + /** + * 延时更新检查 返回false表示需要延时 + * 否则返回实际写入的数值 + * @access protected + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) + * @return false|integer + */ + protected function lazyWrite($type, $guid, $step, $lazyTime) + { + if (!Cache::has($guid . '_time')) { + // 计时开始 + Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); + Cache::$type($guid, $step); + } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { + // 删除缓存 + $value = Cache::$type($guid, $step); + Cache::rm($guid); + Cache::rm($guid . '_time'); + return 0 === $value ? false : $value; + } else { + // 更新缓存 + Cache::$type($guid, $step); + } + return false; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @return $this + */ + public function join($join, $condition = null, $type = 'INNER') + { + if (empty($condition)) { + // 如果为组数,则循环调用join + foreach ($join as $key => $value) { + if (is_array($value) && 2 <= count($value)) { + $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type); + } + } + } else { + $table = $this->getJoinTable($join); + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + return $this; + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * @access public + * @param array|string $join + * @return array|string + */ + protected function getJoinTable($join, &$alias = null) + { + // 传入的表名为数组 + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + } else { + $join = trim($join); + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); + } + } + if (isset($alias) && $table != $alias) { + $table = [$table => $alias]; + } + } + return $table; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return $this + */ + public function union($union, $all = false) + { + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; + + if (is_array($union)) { + $this->options['union'] = array_merge($this->options['union'], $union); + } else { + $this->options['union'][] = $union; + } + return $this; + } + + /** + * 指定查询字段 支持字段排除和指定数据表 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['field'][] = $field; + return $this; + } + + if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + $field = array_map('trim', explode(',', $field)); + } + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ?: ['*']; + } elseif ($except) { + // 字段排除 + $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ? array_diff($fields, $field) : $field; + } + if ($tableName) { + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $val = $prefix . '.' . $val . ($alias ? ' AS ' . $alias . $val : ''); + } + $field[$key] = $val; + } + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + $this->options['field'] = array_unique($field); + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @param array $bind 参数绑定 + * @return $this + */ + public function fieldRaw($field, array $bind = []) + { + $this->options['field'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['inc', $step]); + } + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['dec', $step]); + } + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, $this->raw($value)); + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param mixed $value 表达式 + * @return Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param mixed $on JOIN条件 + * @param string $type JOIN类型 + * @return $this + */ + public function view($join, $field = true, $on = null, $type = 'INNER') + { + $this->options['view'] = true; + if (is_array($join) && key($join) === 0) { + foreach ($join as $key => $val) { + $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); + } + } else { + $fields = []; + $table = $this->getJoinTable($join, $alias); + + if (true === $field) { + $fields = $alias . '.*'; + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $fields[] = $alias . '.' . $val; + $this->options['map'][$val] = $alias . '.' . $val; + } else { + if (preg_match('/[,=\.\'\"\(\s]/', $key)) { + $name = $key; + } else { + $name = $alias . '.' . $key; + } + $fields[$name] = $val; + $this->options['map'][$val] = $name; + } + } + } + $this->field($fields); + if ($on) { + $this->join($table, $on, $type); + } else { + $this->table($table); + } + } + return $this; + } + + /** + * 设置分表规则 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return $this + */ + public function partition($data, $field, $rule = []) + { + $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + return $this; + } + + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('AND', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定OR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereOr($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('OR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定XOR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereXor($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('XOR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw($where, $bind = [], $logic = 'AND') + { + $this->options['where'][$logic][] = $this->raw($where); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw($where, $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'null', null, [], true); + return $this; + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'notnull', null, [], true); + return $this; + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'in', $condition, [], true); + return $this; + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); + return $this; + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'like', $condition, [], true); + return $this; + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); + return $this; + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'between', $condition, [], true); + return $this; + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'exp', $this->raw($condition), [], true); + return $this; + } + + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; + } + return $this; + } + + /** + * 分析查询表达式 + * @access public + * @param string $logic 查询逻辑 and or xor + * @param string|array|\Closure $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return void + */ + protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) + { + $logic = strtoupper($logic); + if ($field instanceof \Closure) { + $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; + return; + } + + if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Expression) { + return $this->whereRaw($field, is_array($op) ? $op : []); + } elseif ($strict) { + // 使用严格模式查询 + $where[$field] = [$op, $condition]; + + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { + $where[] = ['exp', $this->raw($field)]; + if (is_array($op)) { + // 参数绑定 + $this->bind($op); + } + } elseif (is_null($op) && is_null($condition)) { + if (is_array($field)) { + // 数组批量查询 + $where = $field; + foreach ($where as $k => $val) { + $this->options['multi'][$logic][$k][] = $val; + } + } elseif ($field && is_string($field)) { + // 字符串查询 + $where[$field] = ['null', '']; + $this->options['multi'][$logic][$field][] = $where[$field]; + } + } elseif (is_array($op)) { + $where[$field] = $param; + } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { + // null查询 + $where[$field] = [$op, '']; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_null($condition)) { + // 字段相等查询 + $where[$field] = ['eq', $op]; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } else { + if ('exp' == strtolower($op)) { + $where[$field] = ['exp', $this->raw($condition)]; + // 参数绑定 + if (isset($param[2]) && is_array($param[2])) { + $this->bind($param[2]); + } + } else { + $where[$field] = [$op, $condition]; + } + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } + + if (!empty($where)) { + if (!isset($this->options['where'][$logic])) { + $this->options['where'][$logic] = []; + } + if (is_string($field) && $this->checkMultiField($field, $logic)) { + $where[$field] = $this->options['multi'][$logic][$field]; + } elseif (is_array($field)) { + foreach ($field as $key => $val) { + if ($this->checkMultiField($key, $logic)) { + $where[$key] = $this->options['multi'][$logic][$key]; + } + } + } + $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); + } + } + + /** + * 检查是否存在一个字段多次查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return bool + */ + private function checkMultiField($field, $logic) + { + return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + if (isset($this->options['where'][$logic][$field])) { + unset($this->options['where'][$logic][$field]); + unset($this->options['multi'][$logic][$field]); + } + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return $this + */ + public function limit($offset, $length = null) + { + if (is_null($length) && strpos($offset, ',')) { + list($offset, $length) = explode(',', $offset); + } + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return $this + */ + public function page($page, $listRows = null) + { + if (is_null($listRows) && strpos($page, ',')) { + list($page, $listRows) = explode(',', $page); + } + $this->options['page'] = [intval($page), intval($listRows)]; + return $this; + } + + /** + * 分页查询 + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator + * @throws DbException + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + if (is_array($listRows)) { + $config = array_merge(Config::get('paginate'), $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge(Config::get('paginate'), $config); + $listRows = $listRows ?: $config['list_rows']; + } + + /** @var Paginator $class */ + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); + $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ + $class, + 'getCurrentPage', + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $bind = $this->bind; + $total = $this->count(); + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + return $class::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $tables = $table; + $table = []; + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } + $this->options['table'] = $table; + return $this; + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, $order = null) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['order'][] = $field; + return $this; + } + + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; + } + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } + } + } + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } + + return $this; + } + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw($field, array $bind = []) + { + $this->options['order'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) + if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (false !== $key) { + $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; + } + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string $group GROUP + * @return $this + */ + public function group($group) + { + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return $this + */ + public function having($having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + $this->options['master'] = true; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param string $distinct 是否唯一 + * @return $this + */ + public function distinct($distinct) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param mixed $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + foreach ($alias as $key => $val) { + if (false !== strpos($key, '__')) { + $table = $this->parseSqlTable($key); + } else { + $table = $key; + } + $this->options['alias'][$table] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force($force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 获取执行的SQL语句 + * @access public + * @param boolean $fetch 是否返回sql + * @return $this + */ + public function fetchSql($fetch = true) + { + $this->options['fetch_sql'] = $fetch; + return $this; + } + + /** + * 不主动获取数据集 + * @access public + * @param bool $pdo 是否返回 PDOStatement 对象 + * @return $this + */ + public function fetchPdo($pdo = true) + { + $this->options['fetch_pdo'] = $pdo; + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @return $this + */ + public function master() + { + $this->options['master'] = true; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict($strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException($fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence($sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @return $this + */ + public function whereTime($field, $op, $range = null) + { + if (is_null($range)) { + if (is_array($op)) { + $range = $op; + } else { + // 使用日期表达式 + switch (strtolower($op)) { + case 'today': + case 'd': + $range = ['today', 'tomorrow']; + break; + case 'week': + case 'w': + $range = ['this week 00:00:00', 'next week 00:00:00']; + break; + case 'month': + case 'm': + $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; + break; + case 'year': + case 'y': + $range = ['this year 1/1', 'next year 1/1']; + break; + case 'yesterday': + $range = ['yesterday', 'today']; + break; + case 'last week': + $range = ['last week 00:00:00', 'this week 00:00:00']; + break; + case 'last month': + $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; + break; + case 'last year': + $range = ['last year 1/1', 'this year 1/1']; + break; + default: + $range = $op; + } + } + $op = is_array($range) ? 'between' : '>'; + } + $this->where($field, strtolower($op) . ' time', $range); + return $this; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName = '', $fetch = '') + { + if (!$tableName) { + $tableName = $this->getTable(); + } + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($guid) = explode(' ', $tableName); + $db = $this->getConfig('database'); + if (!isset(self::$info[$db . '.' . $guid])) { + if (!strpos($guid, '.')) { + $schema = $db . '.' . $guid; + } else { + $schema = $guid; + } + // 读取缓存 + if (!App::$debug && is_file(RUNTIME_PATH . 'schema/' . $schema . '.php')) { + $info = include RUNTIME_PATH . 'schema/' . $schema . '.php'; + } else { + $info = $this->connection->getFields($guid); + } + $fields = array_keys($info); + $bind = $type = []; + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; + } + + /** + * 获取当前数据表的主键 + * @access public + * @param string|array $options 数据表名或者查询参数 + * @return string|array + */ + public function getPk($options = '') + { + if (!empty($this->pk)) { + $pk = $this->pk; + } else { + $pk = $this->getTableInfo(is_array($options) ? $options['table'] : $options, 'pk'); + } + return $pk; + } + + // 获取当前数据表字段信息 + public function getTableFields($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); + } + + // 获取当前数据表字段类型 + public function getFieldsType($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); + } + + // 获取当前数据表绑定信息 + public function getFieldsBind($table = '') + { + $types = $this->getFieldsType($table); + $bind = []; + if ($types) { + foreach ($types as $key => $type) { + $bind[$key] = $this->getFieldBindType($type); + } + } + return $bind; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + protected function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + return $bind; + } + + /** + * 参数绑定 + * @access public + * @param mixed $key 参数名 + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @return $this + */ + public function bind($key, $value = false, $type = PDO::PARAM_STR) + { + if (is_array($key)) { + $this->bind = array_merge($this->bind, $key); + } else { + $this->bind[$key] = [$value, $type]; + } + return $this; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 查询参数赋值 + * @access protected + * @param array $options 表达式参数 + * @return $this + */ + protected function options(array $options) + { + $this->options = $options; + return $this; + } + + /** + * 获取当前的查询参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOptions($name = '') + { + if ('' === $name) { + return $this->options; + } else { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + } + + /** + * 设置关联查询JOIN预查询 + * @access public + * @param string|array $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + $with[$key] = $key; + } elseif (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $with[$key] = $relation; + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $model->eagerly($this, $relation, $subRelation, $closure, $first); + $first = false; + } elseif ($closure) { + $with[$key] = $closure; + } + } + $this->via(); + if (isset($this->options['with'])) { + $this->options['with'] = array_merge($this->options['with'], $with); + } else { + $this->options['with'] = $with; + } + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'] = $relation; + } else { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + if (!isset($this->options['field'])) { + $this->field('*'); + } + foreach ($relations as $key => $relation) { + $closure = $name = null; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (!is_int($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + + $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $name) . ')'; + + if (empty($name)) { + $name = Loader::parseName($relation) . '_count'; + } + + $this->field([$count => $name]); + } + } + return $this; + } + + /** + * 关联预加载中 获取关联指定字段值 + * example: + * Model::with(['relation' => function($query){ + * $query->withField("id,name"); + * }]) + * + * @param string | array $field 指定获取的字段 + * @return $this + */ + public function withField($field) + { + $this->options['with_field'] = $field; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via + * @return $this + */ + public function via($via = '') + { + $this->options['via'] = $via; + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param string|array $relation 关联名称 + * @return $this + */ + public function relation($relation) + { + if (empty($relation)) { + return $this; + } + if (is_string($relation)) { + $relation = explode(',', $relation); + } + if (isset($this->options['relation'])) { + $this->options['relation'] = array_merge($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } + return $this; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @param mixed $options 表达式参数 + * @return void + * @throws Exception + */ + protected function parsePkWhere($data, &$options) + { + $pk = $this->getPk($options); + // 获取当前数据表 + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + if (!empty($options['alias'][$table])) { + $alias = $options['alias'][$table]; + } + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; + } else { + $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$attr] = $data[$key]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($options['where']['AND'])) { + $options['where']['AND'] = array_merge($options['where']['AND'], $where); + } else { + $options['where']['AND'] = $where; + } + } + return; + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + // 生成SQL语句 + $sql = $this->builder->insert($data, $options, $replace); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 执行操作 + $result = 0 === $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + if ($lastInsId) { + $pk = $this->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + $options['data'] = $data; + $this->trigger('after_insert', $options); + + if ($getLastInsID) { + return $lastInsId; + } + } + return $result; + } + + /** + * 插入记录并获取自增ID + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insertGetId(array $data, $replace = false, $sequence = null) + { + return $this->insert($data, $replace, true, $sequence); + } + + /** + * 批量插入记录 + * @access public + * @param mixed $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + */ + public function insertAll(array $dataSet, $replace = false, $limit = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + if (!is_array(reset($dataSet))) { + return false; + } + + // 生成SQL语句 + if (is_null($limit)) { + $sql = $this->builder->insertAll($dataSet, $options, $replace); + } else { + $array = array_chunk($dataSet, $limit, true); + foreach ($array as $item) { + $sql[] = $this->builder->insertAll($item, $options, $replace); + } + } + + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } elseif (is_array($sql)) { + // 执行操作 + return $this->batchQuery($sql, $bind, $this); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert($fields, $table) + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成SQL语句 + $table = $this->parseSqlTable($table); + $sql = $this->builder->selectInsert($fields, $table, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(array $data = []) + { + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = $data[$pk]; + if (!isset($key)) { + $key = 'think:' . $options['table'] . '|' . $data[$pk]; + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = $data[$field]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($data, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + $options['data'] = $data; + $this->trigger('after_update', $options); + } + return $result; + } + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + + /** + * 查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection|false|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null) + { + if ($data instanceof Query) { + return $data->select(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + + if (false === $data) { + // 用于子查询 不查询只返回SQL + $options['fetch_sql'] = true; + } elseif (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data, $options); + } + + $resultSet = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + unset($options['cache']); + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + $resultSet = Cache::get($key); + } + if (false === $resultSet) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + $options['data'] = $data; + if ($resultSet = $this->trigger('before_select', $options)) { + } else { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (isset($cache) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $cache); + } + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + if (count($resultSet) > 0) { + foreach ($resultSet as $key => $result) { + /** @var Model $model */ + $model = $this->model->newInstance($result); + $model->isUpdate(true); + + // 关联查询 + if (!empty($options['relation'])) { + $model->relationQuery($options['relation']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $model->relationCount($model, $options['with_count']); + } + $resultSet[$key] = $model; + } + if (!empty($options['with'])) { + // 预载入 + $model->eagerlyResultSet($resultSet, $options['with']); + } + // 模型数据集转换 + $resultSet = $model->toCollection($resultSet); + } else { + $resultSet = $this->model->toCollection($resultSet); + } + } elseif ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + // 返回结果处理 + if (!empty($options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($options); + } + return $resultSet; + } + + /** + * 缓存数据 + * @access public + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + if (isset($config['tag'])) { + Cache::tag($config['tag'])->set($key, $data, $config['expire']); + } else { + Cache::set($key, $data, $config['expire']); + } + } + + /** + * 生成缓存标识 + * @access public + * @param mixed $value 缓存数据 + * @param array $options 缓存参数 + * @param array $bind 绑定参数 + * @return string + */ + protected function getCacheKey($value, $options, $bind = []) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { + $data = $value[1]; + } + $prefix = $this->connection->getConfig('database') . '.'; + + if (isset($data)) { + return 'think:' . $prefix . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } + + try { + return md5($prefix . serialize($options) . serialize($bind)); + } catch (\Exception $e) { + throw new Exception('closure not support cache(true)'); + } + } + + /** + * 查找单条记录 + * @access public + * @param array|string|Query|\Closure $data + * @return array|false|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if ($data instanceof Query) { + return $data->find(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + $options['limit'] = 1; + $result = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + if (true === $cache['key'] && !is_null($data) && !is_array($data)) { + $key = 'think:' . $this->connection->getConfig('database') . '.' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } elseif (is_string($cache['key'])) { + $key = $cache['key']; + } elseif (!isset($key)) { + $key = md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + } + $result = Cache::get($key); + } + if (false === $result) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + if (is_string($pk)) { + if (!is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + } + $options['data'] = $data; + // 事件回调 + if ($result = $this->trigger('before_find', $options)) { + } else { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } + + // 数据处理 + if (!empty($result)) { + if (!empty($this->model)) { + // 返回模型对象 + $result = $this->model->newInstance($result); + $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation']); + } + // 预载入查询 + if (!empty($options['with'])) { + $result->eagerlyResult($result, $options['with']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $result->relationCount($result, $options['with_count']); + } + } + } elseif (!empty($options['fail'])) { + $this->throwNotFound($options); + } + return $result; + } + + /** + * 查询失败 抛出异常 + * @access public + * @param array $options 查询参数 + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound($options = []) + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); + } else { + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); + } + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string $column 分批处理的字段名 + * @param string $order 排序规则 + * @return boolean + * @throws \LogicException + */ + public function chunk($count, $callback, $column = null, $order = 'asc') + { + $options = $this->getOptions(); + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + $column = $column ?: $this->getPk($options); + + if (isset($options['order'])) { + if (App::$debug) { + throw new \LogicException('chunk not support call order'); + } + unset($options['order']); + } + $bind = $this->bind; + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + $query = $this->options($options)->limit($count); + } + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } + + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (is_array($column)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = end($resultSet); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->bind($bind)->order($column, $order)->select(); + } + + return true; + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @return array + */ + public function getBind() + { + $bind = $this->bind; + $this->bind = []; + return $bind; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub + * @return string + * @throws DbException + */ + public function buildSql($sub = true) + { + return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (!is_null($data) && true !== $data) { + if (!isset($key) && !is_array($data)) { + // 缓存标识 + $key = 'think:' . $options['table'] . '|' . $data; + } + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + // 生成删除SQL语句 + $sql = $this->builder->delete($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = $this->execute($sql, $bind, $this); + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + $options['data'] = $data; + $this->trigger('after_delete', $options); + } + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access protected + * @return array + */ + protected function parseExpress() + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key)) { + if (strpos($val, ' ')) { + list($field, $sort) = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } + } + } + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + if (!isset($options['data'])) { + $options['data'] = []; + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { + $options['master'] = true; + } + + foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + list($page, $listRows) = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = []; + return $options; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 额外参数 + * @return bool + */ + protected function trigger($event, $params = []) + { + $result = false; + if (isset(self::$event[$event])) { + $callback = self::$event[$event]; + $result = call_user_func_array($callback, [$params, $this]); + } + return $result; + } +} diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php new file mode 100644 index 0000000..be2af71 --- /dev/null +++ b/thinkphp/library/think/db/builder/Mysql.php @@ -0,0 +1,137 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\Exception; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = '( ' . implode(',', $value) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_map([$this, 'parseKey'], array_keys($data)); + } + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + return 'json_extract(' . $field . ', \'$.' . $name . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { + $key = '`' . $key . '`'; + } + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + $key = '`' . $table . '`.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Pgsql.php b/thinkphp/library/think/db/builder/Pgsql.php new file mode 100644 index 0000000..acc2289 --- /dev/null +++ b/thinkphp/library/think/db/builder/Pgsql.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + public function parseLimit($limit) + { + $limitStr = ''; + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + return $limitStr; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Sqlite.php b/thinkphp/library/think/db/builder/Sqlite.php new file mode 100644 index 0000000..c727f04 --- /dev/null +++ b/thinkphp/library/think/db/builder/Sqlite.php @@ -0,0 +1,82 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + + /** + * limit + * @access public + * @param string $limit + * @return string + */ + public function parseLimit($limit) + { + $limitStr = ''; + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + return $limitStr; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } +} diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/thinkphp/library/think/db/builder/Sqlsrv.php new file mode 100644 index 0000000..dc425d9 --- /dev/null +++ b/thinkphp/library/think/db/builder/Sqlsrv.php @@ -0,0 +1,137 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_numeric($key)) { + if (false === strpos($val, '(')) { + $array[] = $this->parseKey($val, $options); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + $array[] = $val; + } + } else { + $sort = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : ''; + $array[] = $this->parseKey($key, $options, true) . ' ' . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { + $key = '[' . $key . ']'; + } + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + return $key; + } + + /** + * limit + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + if (empty($limit)) { + return ''; + } + + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; + } else { + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; + } + return 'WHERE ' . $limitStr; + } + + public function selectInsert($fields, $table, $options) + { + $this->selectSql = $this->selectInsertSql; + return parent::selectInsert($fields, $table, $options); + } + +} diff --git a/thinkphp/library/think/db/connector/Mysql.php b/thinkphp/library/think/db/connector/Mysql.php new file mode 100644 index 0000000..be1a85c --- /dev/null +++ b/thinkphp/library/think/db/connector/Mysql.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\Log; + +/** + * mysql数据库驱动 + */ +class Mysql extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Mysql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; + } + $dsn .= ';dbname=' . $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + $sql = 'SHOW COLUMNS FROM ' . $tableName; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + $pdo = $this->linkID->query("EXPLAIN " . $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + $result = array_change_key_case($result); + if (isset($result['extra'])) { + if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { + Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + } + } + return $result; + } + + protected function supportSavepoint() + { + return true; + } + +} diff --git a/thinkphp/library/think/db/connector/Pgsql.php b/thinkphp/library/think/db/connector/Pgsql.php new file mode 100644 index 0000000..bbcf576 --- /dev/null +++ b/thinkphp/library/think/db/connector/Pgsql.php @@ -0,0 +1,103 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Connection +{ + protected $builder = '\\think\\db\\builder\\Pgsql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + if (!empty($config['hostport'])) { + $dsn .= ';port=' . $config['hostport']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + + list($tableName) = explode(' ', $tableName); + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' !== $val['null']), + 'default' => $val['default'], + 'primary' => !empty($val['key']), + 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlite.php b/thinkphp/library/think/db/connector/Sqlite.php new file mode 100644 index 0000000..c4e3a72 --- /dev/null +++ b/thinkphp/library/think/db/connector/Sqlite.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Sqlite'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlite:' . $config['database']; + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['name']] = [ + 'name' => $val['name'], + 'type' => $val['type'], + 'notnull' => 1 === $val['notnull'], + 'default' => $val['dflt_value'], + 'primary' => '1' == $val['pk'], + 'autoinc' => '1' == $val['pk'], + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlsrv.php b/thinkphp/library/think/db/connector/Sqlsrv.php new file mode 100644 index 0000000..35c6600 --- /dev/null +++ b/thinkphp/library/think/db/connector/Sqlsrv.php @@ -0,0 +1,125 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Connection +{ + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + if (!empty($config['hostport'])) { + $dsn .= ',' . $config['hostport']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $tableNames = explode('.', $tableName); + $tableName = isset($tableNames[1]) ? $tableNames[1] : $tableNames[0]; + + $sql = "SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ]; + } + } + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + // 调试开始 + $this->debug(true); + $pdo = $this->linkID->query($sql); + // 调试结束 + $this->debug(false, $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + if ($result) { + $info[$result['column_name']]['primary'] = true; + } + return $this->fieldCase($info); + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } +} diff --git a/thinkphp/library/think/db/connector/pgsql.sql b/thinkphp/library/think/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/thinkphp/library/think/db/connector/pgsql.sql @@ -0,0 +1,117 @@ +CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS +$BODY$ +DECLARE + v_type varchar; +BEGIN + IF a_type='int8' THEN + v_type:='bigint'; + ELSIF a_type='int4' THEN + v_type:='integer'; + ELSIF a_type='int2' THEN + v_type:='smallint'; + ELSIF a_type='bpchar' THEN + v_type:='char'; + ELSE + v_type:=a_type; + END IF; + RETURN v_type; +END; +$BODY$ +LANGUAGE PLPGSQL; + +CREATE TYPE "public"."tablestruct" AS ( + "fields_key_name" varchar(100), + "fields_name" VARCHAR(200), + "fields_type" VARCHAR(20), + "fields_length" BIGINT, + "fields_not_null" VARCHAR(10), + "fields_default" VARCHAR(500), + "fields_comment" VARCHAR(1000) +); + +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; + v_oid oid; + v_sql varchar; + v_rec RECORD; + v_key varchar; +BEGIN + SELECT + pg_class.oid INTO v_oid + FROM + pg_class + INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) + WHERE + pg_class.relname=a_table_name; + IF NOT FOUND THEN + RETURN; + END IF; + + v_sql=' + SELECT + pg_attribute.attname AS fields_name, + pg_attribute.attnum AS fields_index, + pgsql_type(pg_type.typname::varchar) AS fields_type, + pg_attribute.atttypmod-4 as fields_length, + CASE WHEN pg_attribute.attnotnull THEN ''not null'' + ELSE '''' + END AS fields_not_null, + pg_attrdef.adsrc AS fields_default, + pg_description.description AS fields_comment + FROM + pg_attribute + INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid + LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum + LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum + WHERE + pg_attribute.attnum > 0 + AND attisdropped <> ''t'' + AND pg_class.oid = ' || v_oid || ' + ORDER BY pg_attribute.attnum' ; + + FOR v_rec IN EXECUTE v_sql LOOP + v_ret.fields_name=v_rec.fields_name; + v_ret.fields_type=v_rec.fields_type; + IF v_rec.fields_length > 0 THEN + v_ret.fields_length:=v_rec.fields_length; + ELSE + v_ret.fields_length:=NULL; + END IF; + v_ret.fields_not_null=v_rec.fields_not_null; + v_ret.fields_default=v_rec.fields_default; + v_ret.fields_comment=v_rec.fields_comment; + SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; + IF FOUND THEN + v_ret.fields_key_name=v_key; + ELSE + v_ret.fields_key_name=''; + END IF; + RETURN NEXT v_ret; + END LOOP; + RETURN ; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) +IS '获得表信息'; + +---重载一个函数 +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; +BEGIN + FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP + RETURN NEXT v_ret; + END LOOP; + RETURN; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) +IS '获得表信息'; \ No newline at end of file diff --git a/thinkphp/library/think/db/exception/BindParamException.php b/thinkphp/library/think/db/exception/BindParamException.php new file mode 100644 index 0000000..4ed1954 --- /dev/null +++ b/thinkphp/library/think/db/exception/BindParamException.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct($message, $config, $sql, $bind, $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/thinkphp/library/think/db/exception/DataNotFoundException.php b/thinkphp/library/think/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..f2542ac --- /dev/null +++ b/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct($message, $table = '', array $config = []) + { + $this->message = $message; + $this->table = $table; + + $this->setData('Database Config', $config); + } + + /** + * 获取数据表名 + * @access public + * @return string + */ + public function getTable() + { + return $this->table; + } +} diff --git a/thinkphp/library/think/db/exception/ModelNotFoundException.php b/thinkphp/library/think/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..6e5f930 --- /dev/null +++ b/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @param string $message + * @param string $model + */ + public function __construct($message, $model = '', array $config = []) + { + $this->message = $message; + $this->model = $model; + + $this->setData('Database Config', $config); + } + + /** + * 获取模型类名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + +} diff --git a/thinkphp/library/think/debug/Console.php b/thinkphp/library/think/debug/Console.php new file mode 100644 index 0000000..c17911b --- /dev/null +++ b/thinkphp/library/think/debug/Console.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console($type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['trace_tabs']); + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', json_encode($m)); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', $m); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + +} diff --git a/thinkphp/library/think/debug/Html.php b/thinkphp/library/think/debug/Html.php new file mode 100644 index 0000000..b6be7ad --- /dev/null +++ b/thinkphp/library/think/debug/Html.php @@ -0,0 +1,111 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'trace_file' => '', + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + // 页面Trace信息 + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['trace_file']; + return ob_get_clean(); + } + +} diff --git a/thinkphp/library/think/exception/ClassNotFoundException.php b/thinkphp/library/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..eb22e73 --- /dev/null +++ b/thinkphp/library/think/exception/ClassNotFoundException.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ClassNotFoundException extends \RuntimeException +{ + protected $class; + public function __construct($message, $class = '') + { + $this->message = $message; + $this->class = $class; + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/thinkphp/library/think/exception/DbException.php b/thinkphp/library/think/exception/DbException.php new file mode 100644 index 0000000..0ae80ad --- /dev/null +++ b/thinkphp/library/think/exception/DbException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct($message, array $config, $sql, $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } + +} diff --git a/thinkphp/library/think/exception/ErrorException.php b/thinkphp/library/think/exception/ErrorException.php new file mode 100644 index 0000000..b3a9a30 --- /dev/null +++ b/thinkphp/library/think/exception/ErrorException.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * ThinkPHP错误异常 + * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 + * 除开从 think\Exception 继承的功能 + * 其他和PHP系统\ErrorException功能基本一样 + */ +class ErrorException extends Exception +{ + /** + * 用于保存错误级别 + * @var integer + */ + protected $severity; + + /** + * 错误异常构造函数 + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 + */ + public function __construct($severity, $message, $file, $line, array $context = []) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + + empty($context) || $this->setData('Error Context', $context); + } + + /** + * 获取错误级别 + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/thinkphp/library/think/exception/Handle.php b/thinkphp/library/think/exception/Handle.php new file mode 100644 index 0000000..f523db0 --- /dev/null +++ b/thinkphp/library/think/exception/Handle.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\App; +use think\Config; +use think\console\Output; +use think\Lang; +use think\Log; +use think\Response; + +class Handle +{ + protected $render; + protected $ignoreReport = [ + '\\think\\exception\\HttpException', + ]; + + public function setRender($render) + { + $this->render = $render; + } + + /** + * Report or log an exception. + * + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (App::$debug) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'code' => $this->getCode($exception), + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + if (Config::get('record_trace')) { + $log .= "\r\n" . $exception->getTraceAsString(); + } + + Log::record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + if ($result) { + return $result; + } + } + + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Output $output + * @param Exception $e + */ + public function renderForConsole(Output $output, Exception $e) + { + if (App::$debug) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + $output->renderException($e); + } + + /** + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + $template = Config::get('http_exception_template'); + if (!App::$debug && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (App::$debug) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!Config::get('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Config::get('error_message'); + } + } + + //保留一层 + while (ob_get_level() > 1) { + ob_end_clean(); + } + + $data['echo'] = ob_get_clean(); + + ob_start(); + extract($data); + include Config::get('exception_tmpl'); + // 获取并清空缓存 + $content = ob_get_clean(); + $response = new Response($content, 'html'); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return string 错误信息 + */ + protected function getMessage(Exception $exception) + { + $message = $exception->getMessage(); + if (IS_CLI) { + return $message; + } + + if (strpos($message, ':')) { + $name = strstr($message, ':', true); + $message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message; + } elseif (strpos($message, ',')) { + $name = strstr($message, ',', true); + $message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif (Lang::has($message)) { + $message = Lang::get($message); + } + return $message; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()); + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + return $data; + } + + /** + * 获取常量列表 + * @return array 常量列表 + */ + private static function getConst() + { + return get_defined_constants(true)['user']; + } +} diff --git a/thinkphp/library/think/exception/HttpException.php b/thinkphp/library/think/exception/HttpException.php new file mode 100644 index 0000000..01a27fc --- /dev/null +++ b/thinkphp/library/think/exception/HttpException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/thinkphp/library/think/exception/HttpResponseException.php b/thinkphp/library/think/exception/HttpResponseException.php new file mode 100644 index 0000000..5297286 --- /dev/null +++ b/thinkphp/library/think/exception/HttpResponseException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Response; + +class HttpResponseException extends \RuntimeException +{ + /** + * @var Response + */ + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + +} diff --git a/thinkphp/library/think/exception/PDOException.php b/thinkphp/library/think/exception/PDOException.php new file mode 100644 index 0000000..044f82a --- /dev/null +++ b/thinkphp/library/think/exception/PDOException.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) + { + $error = $exception->errorInfo; + + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + + parent::__construct($exception->getMessage(), $config, $sql, $code); + } +} diff --git a/thinkphp/library/think/exception/RouteNotFoundException.php b/thinkphp/library/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..d22e3a6 --- /dev/null +++ b/thinkphp/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/thinkphp/library/think/exception/TemplateNotFoundException.php b/thinkphp/library/think/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..4202069 --- /dev/null +++ b/thinkphp/library/think/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct($message, $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate() + { + return $this->template; + } +} diff --git a/thinkphp/library/think/exception/ThrowableError.php b/thinkphp/library/think/exception/ThrowableError.php new file mode 100644 index 0000000..87b6b9d --- /dev/null +++ b/thinkphp/library/think/exception/ThrowableError.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/thinkphp/library/think/exception/ValidateException.php b/thinkphp/library/think/exception/ValidateException.php new file mode 100644 index 0000000..b368416 --- /dev/null +++ b/thinkphp/library/think/exception/ValidateException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode("\n\r", $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php new file mode 100644 index 0000000..f2296cf --- /dev/null +++ b/thinkphp/library/think/log/driver/File.php @@ -0,0 +1,270 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; +use think\Request; + +/** + * 本地化调试输出到文件 + */ +class File +{ + protected $config = [ + 'time_format' => ' c ', + 'single' => false, + 'file_size' => 2097152, + 'path' => LOG_PATH, + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() + { + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $destination = $this->config['path'] . $name . '.log'; + } else { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $name .= '_' . $type; + } elseif ($this->config['max_files']) { + $name = date('Ymd') . '_' . $type . $cli; + } else { + $name = date('d') . '_' . $type . $cli; + } + + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg; + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode("\r\n", $info); + + $message = "[{$now}]" . $message . "\r\n"; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $request = Request::instance(); + $requestInfo = [ + 'ip' => $request->ip(), + 'method' => $request->method(), + 'host' => $request->host(), + 'uri' => $request->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } + + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode("\r\n", $info) . "\r\n"; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if (App::$debug && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } +} diff --git a/thinkphp/library/think/log/driver/Socket.php b/thinkphp/library/think/log/driver/Socket.php new file mode 100644 index 0000000..4f62915 --- /dev/null +++ b/thinkphp/library/think/log/driver/Socket.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket +{ + public $port = 1116; //SocketLog 服务的http的端口号 + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + ]; + + protected $css = [ + 'sql' => 'color:#009bb4;', + 'sql_warn' => 'color:#009bb4;font-size:14px;', + 'error' => 'color:#f4006b;font-size:14px;', + 'page' => 'color:#40e2ff;background:#171717;', + 'big' => 'font-size:20px;color:red;', + ]; + + protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + + /** + * 构造函数 + * @param array $config 缓存参数 + * @access public + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + if (!$this->check()) { + return false; + } + $trace = []; + if (App::$debug) { + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; + } + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => isset($this->css[$type]) ? $this->css[$type] : '', + ]; + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + $trace[] = [ + 'type' => 'log', + 'msg' => $msg, + 'css' => '', + ]; + } + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + if ($this->config['show_included_files']) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ file ]', + 'css' => '', + ]; + $trace[] = [ + 'type' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '', + ]; + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + + $tabid = $this->getClientArg('tabid'); + if (!$client_id = $this->getClientArg('client_id')) { + $client_id = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; + $this->sendToClient($tabid, $client_id, $trace, $force_client_id); + } + } else { + $this->sendToClient($tabid, $client_id, $trace, ''); + } + return true; + } + + /** + * 发送给指定客户端 + * @author Zjmainstay + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id + */ + protected function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, + ]; + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + $this->send($this->config['host'], $msg, $address); + } + + protected function check() + { + $tabid = $this->getClientArg('tabid'); + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + //用户认证 + $allow_client_ids = $this->config['allow_client_ids']; + if (!empty($allow_client_ids)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $client_id = $this->getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + return true; + } + + protected function getClientArg($name) + { + static $args = []; + + $key = 'HTTP_USER_AGENT'; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $key = 'HTTP_SOCKETLOG'; + } + + if (!isset($_SERVER[$key])) { + return; + } + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = ['tabid' => null]; + return; + } + parse_str($match[1], $args); + } + if (isset($args[$name])) { + return $args[$name]; + } + return; + } + + /** + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $this->port . $address; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $headers = [ + "Content-Type: application/json;charset=UTF-8", + ]; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + return curl_exec($ch); + } + +} diff --git a/thinkphp/library/think/log/driver/Test.php b/thinkphp/library/think/log/driver/Test.php new file mode 100644 index 0000000..7f66338 --- /dev/null +++ b/thinkphp/library/think/log/driver/Test.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +/** + * 模拟测试输出 + */ +class Test +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []) + { + return true; + } + +} diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php new file mode 100644 index 0000000..0406533 --- /dev/null +++ b/thinkphp/library/think/model/Collection.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model && $model->append($append, $override); + }); + return $this; + } + +} diff --git a/thinkphp/library/think/model/Merge.php b/thinkphp/library/think/model/Merge.php new file mode 100644 index 0000000..4a9da81 --- /dev/null +++ b/thinkphp/library/think/model/Merge.php @@ -0,0 +1,322 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Db; +use think\db\Query; +use think\Model; + +class Merge extends Model +{ + + protected $relationModel = []; // HAS ONE 关联的模型列表 + protected $fk = ''; // 外键名 默认为主表名_id + protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) + + /** + * 构造函数 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + parent::__construct($data); + + // 设置默认外键名 仅支持单一外键 + if (empty($this->fk)) { + $this->fk = strtolower($this->name) . '_id'; + } + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 + * @return \think\Model + */ + public static function get($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->find($data); + } + + /** + * 附加查询表达式 + * @access protected + * @param \think\db\Query $query 查询对象 + * @return \think\db\Query + */ + protected static function attachQuery($query) + { + $class = new static(); + $master = $class->name; + $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); + $query->alias($master)->field($fields); + + foreach ($class->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $query->getTable($name) : $model; + $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); + $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); + $query->field($fields); + } + return $query; + } + + /** + * 获取关联模型的字段 并解决混淆 + * @access protected + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 + * @return array + */ + protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) + { + // 获取模型的字段信息 + $fields = $fields ?: $query->getTableInfo($table, 'fields'); + $array = []; + foreach ($fields as $field) { + if ($key = array_search($name . '.' . $field, $map)) { + // 需要处理映射字段 + $array[] = $name . '.' . $field . ' AS ' . $key; + } else { + $array[] = $field; + } + } + return $array; + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache + * @return array|false|string + */ + public static function all($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->select($data); + } + + /** + * 处理写入的模型数据 + * @access public + * @param string $model 模型名称 + * @param array $data 数据 + * @return array + */ + protected function parseData($model, $data) + { + $item = []; + foreach ($data as $key => $val) { + if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { + list($name, $key) = explode('.', $this->mapFields[$key]); + if ($model == $name) { + $item[$key] = $val; + } + } else { + $item[$key] = $val; + } + } + return $item; + } + + /** + * 保存模型数据 以及关联数据 + * @access public + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception + */ + public function save($data = [], $where = [], $sequence = null) + { + if (!empty($data)) { + // 数据自动验证 + if (!$this->validateData($data)) { + return false; + } + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + if (!empty($where)) { + $this->isUpdate = true; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 自动写入更新时间 + if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { + $this->setAttr($this->updateTime, null); + } + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + $pk = $this->getPk(); + try { + if ($this->isUpdate) { + // 自动写入 + $this->autoCompleteData($this->update); + + if (false === $this->trigger('before_update', $this)) { + return false; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + // 处理模型数据 + $data = $this->parseData($this->name, $data); + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } + // 写入主表数据 + $result = $db->strict(false)->where($where)->update($data); + + // 写入附表数据 + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $data); + if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { + $result = 1; + } + } + + // 新增回调 + $this->trigger('after_update', $this); + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间 + if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { + $this->setAttr($this->createTime, null); + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 处理模型数据 + $data = $this->parseData($this->name, $this->data); + // 写入主表数据 + $result = $db->name($this->name)->strict(false)->insert($data); + if ($result) { + $insertId = $db->getLastInsID($sequence); + // 写入外键数据 + if ($insertId) { + if (is_string($pk)) { + $this->data[$pk] = $insertId; + } + $this->data[$this->fk] = $insertId; + } + + // 写入附表数据 + $source = $this->data; + if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { + unset($source[$pk]); + } + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $source); + Db::table($table)->strict(false)->insert($data); + } + } + // 标记为更新 + $this->isUpdate = true; + // 新增回调 + $this->trigger('after_insert', $this); + } + $db->commit(); + // 写入回调 + $this->trigger('after_write', $this); + + $this->origin = $this->data; + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 删除当前的记录 并删除关联数据 + * @access public + * @return int + * @throws \Exception + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + try { + $result = $db->delete($this->data); + if ($result) { + // 获取主键数据 + $pk = $this->data[$this->getPk()]; + + // 删除关联数据 + foreach ($this->relationModel as $key => $model) { + $table = is_int($key) ? $db->getTable($model) : $model; + $query = new Query; + $query->table($table)->where($this->fk, $pk)->delete(); + } + } + $this->trigger('after_delete', $this); + $db->commit(); + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + +} diff --git a/thinkphp/library/think/model/Pivot.php b/thinkphp/library/think/model/Pivot.php new file mode 100644 index 0000000..13525cd --- /dev/null +++ b/thinkphp/library/think/model/Pivot.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Model; + +class Pivot extends Model +{ + + /** @var Model */ + public $parent; + + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct($data = [], Model $parent = null, $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/thinkphp/library/think/model/Relation.php b/thinkphp/library/think/model/Relation.php new file mode 100644 index 0000000..25fe88d --- /dev/null +++ b/thinkphp/library/think/model/Relation.php @@ -0,0 +1,155 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\db\Query; +use think\Exception; +use think\Model; + +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation +{ + // 父模型对象 + protected $parent; + /** @var Model 当前关联的模型类 */ + protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; + // 关联表外键 + protected $foreignKey; + // 关联表主键 + protected $localKey; + // 基础查询 + protected $baseQuery; + // 是否为自关联 + protected $selfRelation; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前的关联模型对象实例 + * @access public + * @return Model + */ + public function getModel() + { + return $this->query->getModel(); + } + + /** + * 获取关联的查询对象 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @return mixed + */ + protected function resultSetBuild($resultSet) + { + return (new $this->model)->toCollection($resultSet); + } + + protected function getQueryFields($model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof Query) { + return $this; + } else { + $this->baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php new file mode 100644 index 0000000..c1cbab9 --- /dev/null +++ b/thinkphp/library/think/model/relation/BelongsTo.php @@ -0,0 +1,243 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class BelongsTo extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + $this->relation = $relation; + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @access public + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [ + $localKey => [ + 'in', + $range, + ], + ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $foreignKey = $this->foreignKey; + $pk = $model->getPk(); + + $this->parent->setAttr($foreignKey, $model->$pk); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $foreignKey = $this->foreignKey; + + $this->parent->setAttr($foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php new file mode 100644 index 0000000..a41c45c --- /dev/null +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,644 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Collection; +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +class BelongsToMany extends Relation +{ + // 中间表表名 + protected $middle; + // 中间表模型名称 + protected $pivotName; + // 中间表模型对象 + protected $pivot; + // 中间表数据名称 + protected $pivotDataName = 'pivot'; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + + if ('think\model\Pivot' == get_class($this->pivot)) { + $this->pivot->name($this->middle); + } + } + + /** + * 设置中间表模型 + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function pivotDataName($name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + + /** + * 实例化中间表模型 + * @param array $data + * @param bool $isUpdate + * @return Pivot + * @throws Exception + */ + protected function newPivot($data = [], $isUpdate = false) + { + $class = $this->pivotName ?: '\\think\\model\\Pivot'; + $pivot = new $class($data, $this->parent, $this->middle); + if ($pivot instanceof Pivot) { + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + } + } + + /** + * 创建关联查询Query对象 + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $pk = $this->parent->getPk(); + // 关联查询 + $condition['pivot.' . $localKey] = $this->parent->$pk; + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载select方法 + * @param null $data + * @return false|\PDOStatement|string|Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载paginate方法 + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载find方法 + * @param null $data + * @return array|false|\PDOStatement|string|Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + if ($result) { + $this->hydratePivot([$result]); + } + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @param $field + * @param null $op + * @param null $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $field = 'pivot.' . $field; + $this->query->where($field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + 'pivot.' . $localKey => [ + 'in', + $range, + ], + ], $relation, $subRelation); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + $pk = $result->$pk; + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->count(); + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + $data[$pivot[$this->localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query->field($fields) + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = false; + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + $result = $this->attach($data, $pivotData); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->insert($pivot, true); + $result[] = $this->newPivot($pivot, true); + } + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot + * @throws Exception + */ + public function attached($data) + { + if ($data instanceof Model) { + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } else { + $id = $data; + } + + $pk = $this->parent->getPk(); + + $pivot = $this->pivot->where($this->localKey, $this->parent->$pk)->where($this->foreignKey, $id)->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $this->pivot->where($pivot)->delete(); + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + /** + * 数据同步 + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + $pk = $this->parent->getPk(); + $current = $this->pivot->where($this->localKey, $this->parent->$pk) + ->column($this->foreignKey); + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && + $this->attach($id, $attributes) + ) { + $changes['updated'][] = $id; + } + } + + return $changes; + + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php new file mode 100644 index 0000000..ebab051 --- /dev/null +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -0,0 +1,318 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany($this->query, [ + $this->foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $localKey = $this->localKey; + $count = 0; + if (isset($result->$localKey)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where($this->foreignKey, $result->$localKey)->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + $localKey = $this->localKey ?: $this->parent->getPk(); + return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure + * @return array + */ + protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + } + $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + $model = new $this->model(); + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->group($model . '.' . $this->localKey) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + } + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php new file mode 100644 index 0000000..3a9a548 --- /dev/null +++ b/thinkphp/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,157 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 关联主键 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->through = $through; + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + {} + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + {} + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + {} + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $through = $this->through; + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $through::getTable(); + $pk = (new $through)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php new file mode 100644 index 0000000..db74e4a --- /dev/null +++ b/thinkphp/library/think/model/relation/HasOne.php @@ -0,0 +1,215 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + // 执行关联定义方法 + $localKey = $this->localKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @return Query + */ + public function has() + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [ + $foreignKey => [ + 'in', + $range, + ], + ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php new file mode 100644 index 0000000..2755d57 --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -0,0 +1,314 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query->where([ + $this->morphKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + $this->morphType => $this->type, + ])->fetchSql()->count(); + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php new file mode 100644 index 0000000..5ec7172 --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -0,0 +1,263 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + $this->morphKey => $pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->find(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php new file mode 100644 index 0000000..7d45265 --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -0,0 +1,299 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + protected $relation; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + return (new $model); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed + */ + public function getRelation($subRelation = '', $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + // 主键数据 + $pk = $this->parent->$morphKey; + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + throw new Exception('relation data not exists :' . $this->model); + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + $result->setRelation($attr, $relationModel); + } + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->setParent(clone $result); + $data->isUpdate(true); + } + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate($model, $type = '') + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php new file mode 100644 index 0000000..353ce21 --- /dev/null +++ b/thinkphp/library/think/model/relation/OneToOne.php @@ -0,0 +1,337 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + // 关联方法名 + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $name); + $field = null; + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + } else { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + } + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [ & $query]); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } + $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } else { + $relationModel = null; + } + $result->setRelation(Loader::parseName($relation), $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result, $bindAttr) + { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model ? $model->$attr : null); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure + * @return array + */ + protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + { + $this->baseQuery = true; + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$key] = $set; + } + return $data; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/thinkphp/library/think/paginator/driver/Bootstrap.php b/thinkphp/library/think/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..c5ac60d --- /dev/null +++ b/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton($text = "«") + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton($text = '»') + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks() + { + if ($this->simple) + return ''; + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
              %s %s
            ', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
              %s %s %s
            ', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
          • ' . $page . '
          • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
          • ' . $text . '
          • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
          • ' . $text . '
          • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getPageLinkWrapper($url, $page) + { + if ($page == $this->currentPage()) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/thinkphp/library/think/process/Builder.php b/thinkphp/library/think/process/Builder.php new file mode 100644 index 0000000..da56163 --- /dev/null +++ b/thinkphp/library/think/process/Builder.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +use think\Process; + +class Builder +{ + private $arguments; + private $cwd; + private $env = null; + private $input; + private $timeout = 60; + private $options = []; + private $inheritEnv = true; + private $prefix = []; + private $outputDisabled = false; + + /** + * 构造方法 + * @param string[] $arguments 参数 + */ + public function __construct(array $arguments = []) + { + $this->arguments = $arguments; + } + + /** + * 创建一个实例 + * @param string[] $arguments 参数 + * @return self + */ + public static function create(array $arguments = []) + { + return new static($arguments); + } + + /** + * 添加一个参数 + * @param string $argument 参数 + * @return self + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * 添加一个前缀 + * @param string|array $prefix + * @return self + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : [$prefix]; + + return $this; + } + + /** + * 设置参数 + * @param string[] $arguments + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * 设置工作目录 + * @param null|string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * 是否初始化环境变量 + * @param bool $inheritEnv + * @return self + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * 设置环境变量 + * @param string $name + * @param null|string $value + * @return self + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * 添加环境变量 + * @param array $variables + * @return self + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) + { + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 设置超时时间 + * @param float|null $timeout + * @return self + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * 设置proc_open选项 + * @param string $name + * @param string $value + * @return self + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * 禁止输出 + * @return self + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启输出 + * @return self + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * 创建一个Process实例 + * @return Process + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new \LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/thinkphp/library/think/process/Utils.php b/thinkphp/library/think/process/Utils.php new file mode 100644 index 0000000..f94c648 --- /dev/null +++ b/thinkphp/library/think/process/Utils.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +class Utils +{ + + /** + * 转义字符串 + * @param string $argument + * @return string + */ + public static function escapeArgument($argument) + { + + if ('' === $argument) { + return escapeshellarg($argument); + } + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"' . $escapedArgument . '"'; + } + return $escapedArgument; + } + + /** + * 验证并进行规范化Process输入。 + * @param string $caller + * @param mixed $input + * @return string + * @throws \InvalidArgumentException + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } + +} diff --git a/thinkphp/library/think/process/exception/Failed.php b/thinkphp/library/think/process/exception/Failed.php new file mode 100644 index 0000000..5295082 --- /dev/null +++ b/thinkphp/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/thinkphp/library/think/process/exception/Timeout.php b/thinkphp/library/think/process/exception/Timeout.php new file mode 100644 index 0000000..d5f1162 --- /dev/null +++ b/thinkphp/library/think/process/exception/Timeout.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Timeout extends \RuntimeException +{ + + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/thinkphp/library/think/process/pipes/Pipes.php b/thinkphp/library/think/process/pipes/Pipes.php new file mode 100644 index 0000000..82396b8 --- /dev/null +++ b/thinkphp/library/think/process/pipes/Pipes.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +abstract class Pipes +{ + + /** @var array */ + public $pipes = []; + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + const CHUNK_SIZE = 16384; + + /** + * 返回用于 proc_open 描述符的数组 + * @return array + */ + abstract public function getDescriptors(); + + /** + * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 + * @return string[] + */ + abstract public function getFiles(); + + /** + * 文件句柄和管道中读取数据。 + * @param bool $blocking 是否使用阻塞调用 + * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 + * @return string[] + */ + abstract public function readAndWrite($blocking, $close = false); + + /** + * 返回当前状态如果有打开的文件句柄或管道。 + * @return bool + */ + abstract public function areOpen(); + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * 检查系统调用已被中断 + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/thinkphp/library/think/process/pipes/Unix.php b/thinkphp/library/think/process/pipes/Unix.php new file mode 100644 index 0000000..fd99a5d --- /dev/null +++ b/thinkphp/library/think/process/pipes/Unix.php @@ -0,0 +1,196 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Unix extends Pipes +{ + + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + + if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return []; + } + + $this->unblock(); + + $read = []; + + if (null !== $this->input) { + $r = array_merge($this->pipes, ['input' => $this->input]); + } else { + $r = $this->pipes; + } + + unset($r[0]); + + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ('input' === $type) { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ('input' === $type) { + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * 创建一个新的 UnixPipes 实例 + * @param Process $process + * @param string|resource $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/thinkphp/library/think/process/pipes/Windows.php b/thinkphp/library/think/process/pipes/Windows.php new file mode 100644 index 0000000..bba7e9b --- /dev/null +++ b/thinkphp/library/think/process/pipes/Windows.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Windows extends Pipes +{ + + /** @var array */ + private $files = []; + /** @var array */ + private $fileHandles = []; + /** @var array */ + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + + $this->files = [ + Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), + ]; + foreach ($this->files as $offset => $file) { + $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); + if (false === $this->fileHandles[$offset]) { + throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = []; + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = []; + } + + /** + * 创建一个新的 WindowsPipes 实例。 + * @param Process $process + * @param $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * 删除临时文件 + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = []; + } + + /** + * 写入到 stdin 输入 + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? ['input' => $this->input] : null; + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return; + } + + if (0 === $n) { + return; + } + + if (null !== $w && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/thinkphp/library/think/response/Json.php b/thinkphp/library/think/response/Json.php new file mode 100644 index 0000000..c906bfc --- /dev/null +++ b/thinkphp/library/think/response/Json.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/thinkphp/library/think/response/Jsonp.php b/thinkphp/library/think/response/Jsonp.php new file mode 100644 index 0000000..404bacb --- /dev/null +++ b/thinkphp/library/think/response/Jsonp.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; + +class Jsonp extends Response +{ + // 输出参数 + protected $options = [ + 'var_jsonp_handler' => 'callback', + 'default_jsonp_handler' => 'jsonpReturn', + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/javascript'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/thinkphp/library/think/response/Redirect.php b/thinkphp/library/think/response/Redirect.php new file mode 100644 index 0000000..91694cb --- /dev/null +++ b/thinkphp/library/think/response/Redirect.php @@ -0,0 +1,105 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; +use think\Session; +use think\Url; + +class Redirect extends Response +{ + + protected $options = []; + + // URL参数 + protected $params = []; + + public function __construct($data = '', $code = 302, array $header = [], array $options = []) + { + parent::__construct($data, $code, $header, $options); + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + $this->header['Location'] = $this->getTargetUrl(); + return; + } + + /** + * 重定向传值(通过Session) + * @access protected + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 + * @return $this + */ + public function with($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + Session::flash($key, $val); + } + } else { + Session::flash($name, $value); + } + return $this; + } + + /** + * 获取跳转地址 + * @return string + */ + public function getTargetUrl() + { + if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { + return $this->data; + } else { + return Url::build($this->data, $this->params); + } + } + + public function params($params = []) + { + $this->params = $params; + return $this; + } + + /** + * 记住当前url后跳转 + * @return $this + */ + public function remember() + { + Session::set('redirect_url', Request::instance()->url()); + return $this; + } + + /** + * 跳转到上次记住的url + * @return $this + */ + public function restore() + { + if (Session::has('redirect_url')) { + $this->data = Session::get('redirect_url'); + Session::delete('redirect_url'); + } + return $this; + } +} diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php new file mode 100644 index 0000000..48f944a --- /dev/null +++ b/thinkphp/library/think/response/View.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Config; +use think\Response; +use think\View as ViewTemplate; + +class View extends Response +{ + // 输出参数 + protected $options = []; + protected $vars = []; + protected $replace = []; + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // 渲染模板输出 + return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch($data, $this->vars, $this->replace); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars($name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return isset($this->vars[$name]) ? $this->vars[$name] : null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + return $this; + } else { + $this->vars[$name] = $value; + } + return $this; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + +} diff --git a/thinkphp/library/think/response/Xml.php b/thinkphp/library/think/response/Xml.php new file mode 100644 index 0000000..3bdc052 --- /dev/null +++ b/thinkphp/library/think/response/Xml.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Collection; +use think\Model; +use think\Response; + +class Xml extends Response +{ + // 输出参数 + protected $options = [ + // 根节点名 + 'root_node' => 'think', + // 根节点属性 + 'root_attr' => '', + //数字索引的子节点名 + 'item_node' => 'item', + // 数字索引子节点key转换的属性名 + 'item_key' => 'id', + // 数据编码 + 'encoding' => 'utf-8', + ]; + + protected $contentType = 'text/xml'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // XML数据转换 + return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); + } + + /** + * XML编码 + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) + { + if (is_array($attr)) { + $array = []; + foreach ($attr as $key => $value) { + $array[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $array); + } + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + return $xml; + } + + /** + * 数据XML编码 + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, $item, $id) + { + $xml = $attr = ''; + + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; + $xml .= ""; + } + return $xml; + } +} diff --git a/thinkphp/library/think/session/driver/Memcache.php b/thinkphp/library/think/session/driver/Memcache.php new file mode 100644 index 0000000..877d7bd --- /dev/null +++ b/thinkphp/library/think/session/driver/Memcache.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcache extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'persistent' => true, // 长连接 + 'session_name' => '', // memcache key前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcache')) { + throw new Exception('not support:memcache'); + } + $this->handler = new \Memcache; + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->config['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : + $this->handler->addServer($host, $port, $this->config['persistent'], 1); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Memcached.php b/thinkphp/library/think/session/driver/Memcached.php new file mode 100644 index 0000000..2994a07 --- /dev/null +++ b/thinkphp/library/think/session/driver/Memcached.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcached extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'session_name' => '', // memcache key前缀 + 'username' => '', //账号 + 'password' => '', //密码 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcached')) { + throw new Exception('not support:memcached'); + } + $this->handler = new \Memcached; + // 设置连接超时时间(单位:毫秒) + if ($this->config['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); + } + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + $this->handler->addServers($servers); + if ('' != $this->config['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->quit(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Redis.php b/thinkphp/library/think/session/driver/Redis.php new file mode 100644 index 0000000..8d05126 --- /dev/null +++ b/thinkphp/library/think/session/driver/Redis.php @@ -0,0 +1,128 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Redis extends SessionHandler +{ + /** @var \Redis */ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // redis主机 + 'port' => 6379, // redis端口 + 'password' => '', // 密码 + 'select' => 0, // 操作库 + 'expire' => 3600, // 有效期(秒) + 'timeout' => 0, // 超时时间(秒) + 'persistent' => true, // 是否长连接 + 'session_name' => '', // sessionkey前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + * @return bool + * @throws Exception + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('redis')) { + throw new Exception('not support:redis'); + } + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + if ($this->config['expire'] > 0) { + return $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + } else { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData); + } + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID) > 0; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return bool + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/template/TagLib.php b/thinkphp/library/think/template/TagLib.php new file mode 100644 index 0000000..c5b72f9 --- /dev/null +++ b/thinkphp/library/think/template/TagLib.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use think\Exception; + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib +{ + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = []; // 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib = ''; + + /** + * 标签库标签列表 + * @var array + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var array + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var bool + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + protected $tpl; + + protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; + + /** + * 构造函数 + * @access public + * @param \stdClass $template 模板引擎对象 + */ + public function __construct($template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $tags = []; + $lib = $lib ? strtolower($lib) . ':' : ''; + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $tags[$close][$lib . $name] = $name; + if (isset($val['alias'])) { + // 别名设置 + $array = (array) $val['alias']; + foreach (explode(',', $array[0]) as $v) { + $tags[$close][$lib . $v] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ('' == $match[1][0]) { + $name = strtolower($match[2][0]); + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0], // 标签结束符 + ]; + } + } else { + // 标签头压入栈 + $right[strtolower($match[1][0])][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name, $alias); + $method = 'tag' . $name; + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break)); + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { + // 对应的标签名 + $name = $tags[0][strtolower($matches[1])]; + $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($matches[0], $name, $alias); + $method = 'tag' . $name; + return $this->$method($attrs, ''); + }, $content); + } + return; + } + + /** + * 按标签生成正则 + * @access private + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, $close) + { + $begin = $this->tpl->config('taglib_begin'); + $end = $this->tpl->config('taglib_end'); + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $tagName = is_array($tags) ? implode('|', $tags) : $tags; + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + return '/' . $regex . '/is'; + } + + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 + * @return array + */ + public function parseAttr($str, $name, $alias = '') + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $result = []; + if (preg_match_all($regex, $str, $matches)) { + foreach ($matches['name'] as $key => $val) { + $result[$val] = $matches['value'][$key]; + } + if (!isset($this->tags[$name])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias'])) { + $array = (array) $val['alias']; + if (in_array($name, explode(',', $array[0]))) { + $tag = $val; + $type = !empty($array[1]) ? $array[1] : 'type'; + $result[$type] = $name; + break; + } + } + } + } else { + $tag = $this->tags[$name]; + // 设置了标签别名 + if (!empty($alias) && isset($tag['alias'])) { + $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type'; + $result[$type] = $alias; + } + } + if (!empty($tag['must'])) { + $must = explode(',', $tag['must']); + foreach ($must as $name) { + if (!isset($result[$name])) { + throw new Exception('tag attr must:' . $name); + } + } + } + } else { + // 允许直接使用表达式的标签 + if (!empty($this->tags[$name]['expression'])) { + static $_taglibs; + if (!isset($_taglibs[$name])) { + $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin')); + } + $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); + } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { + throw new Exception('tag error:' . $name); + } + } + return $result; + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return string + */ + public function parseCondition($condition) + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(&$name) + { + $flag = substr($name, 0, 1); + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { + // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; + } + // 不以$开头并且也不是常量,自动补上$前缀 + $name = '$' . $name; + } + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name); + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + // 获取标签定义 + public function getTags() + { + return $this->tags; + } +} diff --git a/thinkphp/library/think/template/driver/File.php b/thinkphp/library/think/template/driver/File.php new file mode 100644 index 0000000..a9a86bf --- /dev/null +++ b/thinkphp/library/think/template/driver/File.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use think\Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void|array + */ + public function write($cacheFile, $content) + { + // 检测模板目录 + $dir = dirname($cacheFile); + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + // 生成模板缓存文件 + if (false === file_put_contents($cacheFile, $content)) { + throw new Exception('cache write error:' . $cacheFile, 11602); + } + } + + /** + * 读取编译编译 + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read($cacheFile, $vars = []) + { + $this->cacheFile = $cacheFile; + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return boolean + */ + public function check($cacheFile, $cacheTime) + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + return true; + } +} diff --git a/thinkphp/library/think/template/taglib/Cx.php b/thinkphp/library/think/template/taglib/Cx.php new file mode 100644 index 0000000..31e0698 --- /dev/null +++ b/thinkphp/library/think/template/taglib/Cx.php @@ -0,0 +1,673 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\taglib; + +use think\template\TagLib; + +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends Taglib +{ + + // 标签定义 + protected $tags = [ + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => ['attr' => ''], + 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], + 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], + 'if' => ['attr' => 'condition', 'expression' => true], + 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true], + 'else' => ['attr' => '', 'close' => 0], + 'switch' => ['attr' => 'name', 'expression' => true], + 'case' => ['attr' => 'value,break', 'expression' => true], + 'default' => ['attr' => '', 'close' => 0], + 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']], + 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']], + 'empty' => ['attr' => 'name'], + 'notempty' => ['attr' => 'name'], + 'present' => ['attr' => 'name'], + 'notpresent' => ['attr' => 'name'], + 'defined' => ['attr' => 'name'], + 'notdefined' => ['attr' => 'name'], + 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']], + 'assign' => ['attr' => 'name,value', 'close' => 0], + 'define' => ['attr' => 'name,value', 'close' => 0], + 'for' => ['attr' => 'start,end,name,comparison,step'], + 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true], + 'function' => ['attr' => 'name,vars,use,call'], + ]; + + /** + * php标签解析 + * 格式: + * {php}echo $name{/php} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPhp($tag, $content) + { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * {volist name="userList" id="user" empty=""} + * {user.username} + * {user.email} + * {/volist} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagVolist($tag, $content) + { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $key = !empty($tag['key']) ? $tag['key'] : 'i'; + $mod = isset($tag['mod']) ? $tag['mod'] : '2'; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + $parseStr .= '$_result=' . $name . ';'; + $name = '$_result'; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $parseStr .= ' $__LIST__ = ' . $name . ';'; + } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; + $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; + $parseStr .= '++$' . $key . ';?>'; + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * foreach标签解析 循环输出数据集 + * 格式: + * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} + * {user.username} + * {/foreach} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagForeach($tag, $content) + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + $name = $tag['name']; + $key = !empty($tag['key']) ? $tag['key'] : 'key'; + $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + + $parseStr = 'autoBuildVar($name); + $parseStr .= $var . '=' . $name . '; '; + $name = $var; + } else { + $name = $this->autoBuildVar($name); + } + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + if (!isset($var)) { + $var = '$_' . uniqid(); + } + $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $var = &$name; + } + + $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + $parseStr .= '$' . $index . '=0; '; + } + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + if (isset($tag['mod'])) { + $mod = (int) $tag['mod']; + $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; + } + $parseStr .= '++$' . $index . '; '; + } + $parseStr .= '?>'; + // 循环体中的内容 + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * if标签解析 + * 格式: + * {if condition=" $a eq 1"} + * {elseif condition="$a eq 2" /} + * {else /} + * {/if} + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagIf($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse($tag) + { + $parseStr = ''; + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * {switch name="a.name"} + * {case value="1" break="false"}1{/case} + * {case value="2" }2{/case} + * {default /}other + * {/switch} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagSwitch($tag, $content) + { + $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCase($tag, $content) + { + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $value = 'case ' . $value . ':'; + } elseif (strpos($value, '|')) { + $values = explode('|', $value); + $value = ''; + foreach ($values as $val) { + $value .= 'case "' . addslashes($val) . '":'; + } + } else { + $value = 'case "' . $value . '":'; + } + $parseStr = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefault($tag) + { + $parseStr = ''; + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: {compare name="" type="eq" value="" }content{/compare} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCompare($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } else { + $value = '\'' . $value . '\''; + } + switch ($type) { + case 'equal': + $type = 'eq'; + break; + case 'notequal': + $type = 'neq'; + break; + } + $type = $this->parseCondition(' ' . $type . ' '); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} + * example: {range name="a" value="1,2,3" type='in' }content{/range} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagRange($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 + + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; + } else { + $value = '"' . $value . '"'; + $str = 'explode(\',\',' . $value . ')'; + } + if ('between' == $type) { + $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; + } elseif ('notbetween' == $type) { + $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; + } else { + $fun = ('in' == $type) ? 'in_array' : '!in_array'; + $parseStr = '' . $content . ''; + } + return $parseStr; + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: {present name="" }content{/present} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: {notpresent name="" }content{/notpresent} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotpresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: {empty name="" }content{/empty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagEmpty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty())): ?>' . $content . ''; + return $parseStr; + } + + /** + * notempty标签解析 + * 如果某个变量不为empty 则输出内容 + * 格式: {notempty name="" }content{/notempty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotempty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * load 标签解析 {load file="/static/js/base.js" /} + * 格式:{load file="/static/css/base.css" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagLoad($tag, $content) + { + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + $parseStr = ''; + $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; + $parseStr .= ''; + $endStr = ''; + } + + // 文件方式导入 + $array = explode(',', $file); + foreach ($array as $val) { + $type = strtolower(substr(strrchr($val, '.'), 1)); + switch ($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + return $parseStr . $endStr; + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: {assign name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagAssign($tag, $content) + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine($tag, $content) + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor($tag, $content) + { + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 + foreach ($tag as $key => $value) { + $value = trim($value); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } + + switch ($key) { + case 'start': + $start = $value; + break; + case 'end': + $end = $value; + break; + case 'step': + $step = $value; + break; + case 'comparison': + $comparison = $value; + break; + case 'name': + $name = $value; + break; + } + } + + $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + /** + * url函数的tag标签 + * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagUrl($tag, $content) + { + $url = isset($tag['link']) ? $tag['link'] : ''; + $vars = isset($tag['vars']) ? $tag['vars'] : ''; + $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; + $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + return ''; + } + + /** + * function标签解析 匿名函数,可实现递归 + * 使用: + * {function name="func" vars="$data" call="$list" use="&$a,&$b"} + * {if is_array($data)} + * {foreach $data as $val} + * {~func($val) /} + * {/foreach} + * {else /} + * {$data} + * {/if} + * {/function} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFunction($tag, $content) + { + $name = !empty($tag['name']) ? $tag['name'] : 'func'; + $vars = !empty($tag['vars']) ? $tag['vars'] : ''; + $call = !empty($tag['call']) ? $tag['call'] : ''; + $use = ['&$' . $name]; + if (!empty($tag['use'])) { + foreach (explode(',', $tag['use']) as $val) { + $use[] = '&' . ltrim(trim($val), '&'); + } + } + $parseStr = '' . $content . '' : '?>'; + return $parseStr; + } +} diff --git a/thinkphp/library/think/view/driver/Php.php b/thinkphp/library/think/view/driver/Php.php new file mode 100644 index 0000000..f594a43 --- /dev/null +++ b/thinkphp/library/think/view/driver/Php.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; + +class Php +{ + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DS, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + protected $template; + protected $content; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch($template, $data = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + $this->template = $template; + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + + extract($data, EXTR_OVERWRITE); + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display($content, $data = []) + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return void + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return isset($this->config[$name]) ? $this->config[$name] : null; + } else { + $this->config[$name] = $value; + } + } + +} diff --git a/thinkphp/library/think/view/driver/Think.php b/thinkphp/library/think/view/driver/Think.php new file mode 100644 index 0000000..a314ad6 --- /dev/null +++ b/thinkphp/library/think/view/driver/Think.php @@ -0,0 +1,167 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; +use think\Template; + +class Think +{ + // 模板引擎实例 + private $template; + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $this->template = new Template($this->config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $data = [], $config = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + $this->template->fetch($template, $data, $config); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($template, $data = [], $config = []) + { + $this->template->display($template, $data, $config); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + // 分析模板文件规则 + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置或者获取模板引擎参数 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->template->config($name); + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return $this->template->config($name); + } else { + $this->template->$name = $value; + $this->config[$name] = $value; + } + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/thinkphp/library/traits/controller/Jump.php b/thinkphp/library/traits/controller/Jump.php new file mode 100644 index 0000000..6a57224 --- /dev/null +++ b/thinkphp/library/traits/controller/Jump.php @@ -0,0 +1,167 @@ +error(); + * $this->redirect(); + * } + * } + */ +namespace traits\controller; + +use think\Config; +use think\exception\HttpResponseException; +use think\Request; +use think\Response; +use think\response\Redirect; +use think\Url; +use think\View as ViewTemplate; + +trait Jump +{ + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url) && !is_null(Request::instance()->server('HTTP_REFERER'))) { + $url = Request::instance()->server('HTTP_REFERER'); + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_success_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url)) { + $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_error_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 返回封装后的 API 数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param int $code 返回的 code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => Request::instance()->server('REQUEST_TIME'), + 'data' => $data, + ]; + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL 重定向 + * @access protected + * @param string $url 跳转的 URL 表达式 + * @param array|int $params 其它 URL 参数 + * @param int $code http code + * @param array $with 隐式传参 + * @return void + * @throws HttpResponseException + */ + protected function redirect($url, $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + $response = new Redirect($url); + $response->code($code)->params($params)->with($with); + + throw new HttpResponseException($response); + } + + /** + * 获取当前的 response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + return Request::instance()->isAjax() + ? Config::get('default_ajax_return') + : Config::get('default_return_type'); + } +} diff --git a/thinkphp/library/traits/model/SoftDelete.php b/thinkphp/library/traits/model/SoftDelete.php new file mode 100644 index 0000000..70f31ba --- /dev/null +++ b/thinkphp/library/traits/model/SoftDelete.php @@ -0,0 +1,200 @@ +getDeleteTimeField(); + + if ($field && !empty($this->data[$field])) { + return true; + } + return false; + } + + /** + * 查询包含软删除的数据 + * @access public + * @return Query + */ + public static function withTrashed() + { + return (new static )->getQuery(); + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed() + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model->getQuery()->useSoftDelete($field, ['not null', '']); + } else { + return $model->getQuery(); + } + } + + /** + * 删除当前的记录 + * @access public + * @param bool $force 是否强制删除 + * @return integer + */ + public function delete($force = false) + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $name = $this->getDeleteTimeField(); + if ($name && !$force) { + // 软删除 + $this->data[$name] = $this->autoWriteTimestamp($name); + $result = $this->isUpdate()->save(); + } else { + // 强制删除当前模型数据 + $result = $this->getQuery()->where($this->getWhere())->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection || is_array($result)) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } + + $this->trigger('after_delete', $this); + + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表(支持闭包查询条件) + * @param bool $force 是否强制删除 + * @return integer 成功删除的记录数 + */ + public static function destroy($data, $force = false) + { + if (is_null($data)) { + return 0; + } + + // 包含软删除数据 + $query = (new static())->db(false); + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } + + $count = 0; + if ($resultSet = $query->select($data)) { + foreach ($resultSet as $data) { + $result = $data->delete($force); + $count += $result; + } + } + + return $count; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return integer + */ + public function restore($where = []) + { + if (empty($where)) { + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); + } + + $name = $this->getDeleteTimeField(); + + if ($name) { + // 恢复删除 + return $this->getQuery() + ->useSoftDelete($name, ['not null', '']) + ->where($where) + ->update([$name => null]); + } else { + return 0; + } + } + + /** + * 查询默认不包含软删除数据 + * @access protected + * @param Query $query 查询对象 + * @return Query + */ + protected function base($query) + { + $field = $this->getDeleteTimeField(true); + return $field ? $query->useSoftDelete($field) : $query; + } + + /** + * 获取软删除字段 + * @access public + * @param bool $read 是否查询操作(写操作的时候会自动去掉表别名) + * @return string + */ + protected function getDeleteTimeField($read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? + $this->deleteTime : + 'delete_time'; + + if (false === $field) { + return false; + } + + if (!strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } +} diff --git a/thinkphp/library/traits/think/Instance.php b/thinkphp/library/traits/think/Instance.php new file mode 100644 index 0000000..428c8fd --- /dev/null +++ b/thinkphp/library/traits/think/Instance.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- + +namespace traits\think; + +use think\Exception; + +trait Instance +{ + /** + * @var null|static 实例对象 + */ + protected static $instance = null; + + /** + * 获取示例 + * @param array $options 实例配置 + * @return static + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) self::$instance = new self($options); + + return self::$instance; + } + + /** + * 静态调用 + * @param string $method 调用方法 + * @param array $params 调用参数 + * @return mixed + * @throws Exception + */ + public static function __callStatic($method, array $params) + { + if (is_null(self::$instance)) self::$instance = new self(); + + $call = substr($method, 1); + + if (0 !== strpos($method, '_') || !is_callable([self::$instance, $call])) { + throw new Exception("method not exists:" . $method); + } + + return call_user_func_array([self::$instance, $call], $params); + } +} diff --git a/thinkphp/logo.png b/thinkphp/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..25fd0593688de5c9f4cd321da1a72ab9566fe331 GIT binary patch literal 6995 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 literal 0 HcmV?d00001 diff --git a/thinkphp/phpunit.xml b/thinkphp/phpunit.xml new file mode 100644 index 0000000..7c6ef03 --- /dev/null +++ b/thinkphp/phpunit.xml @@ -0,0 +1,35 @@ + + + + + ./tests/thinkphp/ + + + + + + + + ./ + + tests + vendor + + + + + + + + + + diff --git a/thinkphp/start.php b/thinkphp/start.php new file mode 100644 index 0000000..adb1bc6 --- /dev/null +++ b/thinkphp/start.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 1. 加载基础文件 +require __DIR__ . '/base.php'; + +// 2. 执行应用 +App::run()->send(); diff --git a/thinkphp/tpl/default_index.tpl b/thinkphp/tpl/default_index.tpl new file mode 100644 index 0000000..8538b4d --- /dev/null +++ b/thinkphp/tpl/default_index.tpl @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

            :)

            ThinkPHP V5
            十年磨一剑 - 为API开发设计的高性能框架

            [ V5.0 版本由 七牛云 独家赞助发布 ]
            '; + } +} diff --git a/thinkphp/tpl/dispatch_jump.tpl b/thinkphp/tpl/dispatch_jump.tpl new file mode 100644 index 0000000..583376b --- /dev/null +++ b/thinkphp/tpl/dispatch_jump.tpl @@ -0,0 +1,49 @@ +{__NOLAYOUT__} + + + + + 跳转提示 + + + +
            + + +

            :)

            +

            + + +

            :(

            +

            + + +

            +

            + 页面自动 跳转 等待时间: +

            +
            + + + diff --git a/thinkphp/tpl/page_trace.tpl b/thinkphp/tpl/page_trace.tpl new file mode 100644 index 0000000..7c5df6f --- /dev/null +++ b/thinkphp/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
            + + +
            +
            +
            + +
            + + diff --git a/thinkphp/tpl/think_exception.tpl b/thinkphp/tpl/think_exception.tpl new file mode 100644 index 0000000..21bbafc --- /dev/null +++ b/thinkphp/tpl/think_exception.tpl @@ -0,0 +1,537 @@ +'.end($names).''; + } + } + + if(!function_exists('parse_file')){ + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } + } + + if(!function_exists('parse_args')){ + function parse_args($args) + { + $result = []; + + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%s)', parse_class(get_class($item))); + break; + case is_array($item): + if(count($item) > 3){ + $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); + } else { + $value = sprintf('[%s]', parse_args($item)); + } + break; + case is_string($item): + if(strlen($item) > 20){ + $value = sprintf( + '\'%s...\'', + htmlentities($item), + htmlentities(substr($item, 0, 20)) + ); + } else { + $value = sprintf("'%s'", htmlentities($item)); + } + break; + case is_int($item): + case is_float($item): + $value = $item; + break; + case is_null($item): + $value = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } + } +?> + + + + + <?php echo \think\Lang::get('System Error'); ?> + + + + + +
            + +
            + +
            +
            + +
            +
            +

            [

            +
            +

            +
            + +
            + +
            +
              $value) { ?>
            +
            + +
            +

            Call Stack

            +
              +
            1. + +
            2. + +
            3. + +
            +
            +
            + +
            + +

            + +
            + + + +
            +

            Exception Datas

            + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
            empty
            + +
            + +
            + + + +
            +

            Environment Variables

            + $value) { ?> +
            + +
            +
            +
            empty
            +
            + +

            +
            + $val) { ?> +
            +
            +
            + +
            +
            + +
            + +
            + +
            + + + + + + + + diff --git a/vendor/.gitignore b/vendor/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file

          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�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%0D9>= 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