diff --git a/icons/IMDb_IOS-OSX_App.png b/icons/IMDb_IOS-OSX_App.png new file mode 100644 index 0000000..fc5b873 Binary files /dev/null and b/icons/IMDb_IOS-OSX_App.png differ diff --git a/icons/Mihomo_Meta_A.png b/icons/Mihomo_Meta_A.png new file mode 100755 index 0000000..eacd778 Binary files /dev/null and b/icons/Mihomo_Meta_A.png differ diff --git a/package.json b/package.json index 28f01cf..51e0e29 100644 --- a/package.json +++ b/package.json @@ -1,1010 +1,1022 @@ { - "AutoSignIn": { - "name": "站点自动签到", - "description": "自动模拟登录、签到站点。", - "labels": "站点", - "version": "2.4.4", - "icon": "signin.png", - "author": "thsrite", - "level": 2, - "history": { - "v2.4.4": "增加保号风险提示", - "v2.4.3": "修复空签到失败问题", - "v2.4.2": "修复PT时间签到失败问题", - "v2.4.1": "修复海胆签到失败问题", - "v2.4": "适配m-team Api地址变化", - "v2.3.2": "修复YemaPT登录失败,支持YemaPT自动签到", - "v2.3.1": "修复签到报错问题", - "v2.3": "优化模拟登录逻辑,支持YemaPT模拟登录", - "v2.2": "适配馒头最新变化,需要升级至v1.8.5+版本且维护好Authorization", - "v2.1": "增强API安全性", - "v2.0": "站点签到时更新站点使用统计信息,需要主程序升级至v1.8.3+版本", - "v1.9": "支持馒头新架构自动签到" - } - }, - "AutoSubv2": { - "name": "AI字幕自动生成(v2)", - "description": "使用whisper自动生成视频文件字幕,使用大模型翻译字幕成中文。", - "version": "1.2", - "icon": "autosubtitles.jpeg", - "author": "TimoYoung", - "level": 1, - "v2": true, - "history": { - "v1.0": "first stable version", - "v1.1": "优化字幕翻译逻辑,优化日志输出", - "v1.2": "fix openai_proxy打开时,翻译失败的问题,优化日志输出" - } - }, - "CustomSites": { - "name": "自定义站点", - "description": "增加自定义站点为签到和统计使用。", - "labels": "站点", - "version": "1.0", - "icon": "world.png", - "author": "lightolly", - "level": 2, - "v2": true - }, - "SiteStatistic": { - "name": "站点数据统计", - "description": "自动统计和展示站点数据。", - "labels": "站点,仪表板", - "version": "4.0.1", - "icon": "statistic.png", - "author": "lightolly", - "level": 2, - "history": { - "v4.0.1": "修复PTT的魔力值统计", - "v4.0": "修复插件数据页异常", - "v3.9.3": "修复PTT的用户等级统计", - "v3.9.2": "修复YemaPT的上传下载统计错误", - "v3.9.1": "修复mteam域名地址", - "v3.9": "修复YemaPT站点数据统计", - "v3.8": "适配m-team Api地址变化", - "v3.7": "修复观众做种数据统计", - "v3.6": "支持站点数据统计刷新后触发插件事件", - "v3.5": "站点数据统计支持YemaPT", - "v3.4": "修复馒头站点数据统计", - "v3.3": "支持选择仪表板组件规格", - "v3.2": "支持在仪表板中显示站点统计信息,需要主程序升级至v1.8.7+版本", - "v3.1": "修复观众无法统计做总数和做种体积的bug", - "v3.0": "适配馒头数据统计,需要升级至v1.8.5+版本,且在站点信息中维护好API Key", - "v2.9": "增强API安全性", - "v2.8": "修复馒头未读消息统计", - "v2.7": "修复憨憨种子信息只统计第一页的问题,增加移除失效统计选项", - "v2.6": "支持馒头新架构数据统计" - } - }, - "SiteRefresh": { - "name": "站点自动更新", - "description": "使用浏览器模拟登录站点获取Cookie和UA。", - "labels": "站点", - "version": "1.2", - "icon": "Chrome_A.png", - "author": "thsrite", - "level": 2, - "v2": true - }, - "DoubanSync": { - "name": "豆瓣想看", - "description": "同步豆瓣想看数据,自动添加订阅。", - "labels": "订阅", - "version": "1.9.1", - "icon": "douban.png", - "author": "jxxghp", - "level": 2, - "history": { - "v1.9.1": "修复版本兼容问题", - "v1.9": "请求豆瓣RSS时增加请求头", - "v1.8": "不同步在看条目", - "v1.7": "增强API安全性", - "v1.6": "同步历史记录支持手动删除,需要主程序升级至v1.8.4+版本", - "v1.5": "豆瓣信息识别后直接添加订阅,不进行搜索下载" - } - }, - "DirMonitor": { - "name": "目录监控", - "description": "监控目录文件发生变化时实时整理到媒体库。", - "labels": "文件整理", - "version": "2.4", - "icon": "directory.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.4": "修复目录监控不使用ChatGPT辅助识别问题", - "v2.3": "特殊场景下补充转移成功历史记录", - "v2.2": "更新目录设置说明", - "v2.1": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", - "v2.0": "增强API安全性", - "v1.9": "修复目录监控不能正确获取下载历史记录进行识别的问题" - } - }, - "ChineseSubFinder": { - "name": "ChineseSubFinder", - "description": "整理入库时通知ChineseSubFinder下载字幕。", - "labels": "字幕", - "version": "1.1", - "icon": "chinesesubfinder.png", - "author": "jxxghp", - "level": 1 - }, - "DoubanRank": { - "name": "豆瓣榜单订阅", - "description": "监控豆瓣热门榜单,自动添加订阅。", - "labels": "订阅", - "version": "1.9.1", - "icon": "movie.jpg", - "author": "jxxghp", - "level": 2, - "history": { - "v1.9.1": "优化媒体类型的判断处理", - "v1.9": "增强API安全性", - "v1.8": "订阅历史记录支持手动删除,需要主程序升级至v1.8.4+版本" - } - }, - "LibraryScraper": { - "name": "媒体库刮削", - "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", - "labels": "刮削", - "version": "1.5", - "icon": "scraper.png", - "author": "jxxghp", - "level": 1, - "history": { - "v1.5": "修复未获取fanart图片的问题", - "v1.4.1": "修复nfo文件读取失败时任务中断问题" - } - }, - "TorrentRemover": { - "name": "自动删种", - "description": "自动删除下载器中的下载任务。", - "labels": "做种", - "version": "1.2.2", - "icon": "delete.jpg", - "author": "jxxghp", - "level": 2 - }, - "MediaSyncDel": { - "name": "媒体文件同步删除", - "description": "同步删除历史记录、源文件和下载任务。", - "labels": "文件整理", - "version": "1.7.1", - "icon": "mediasyncdel.png", - "author": "thsrite", - "level": 1, - "history": { - "v1.7.1": "修复删除剧集辅种失败报错问题", - "v1.7": "修复重新整理被一并删除问题", - "v1.6": "修复删除辅种", - "v1.5": "支持手动删除订阅历史记录(本次更新之后的历史)" - } - }, - "CustomHosts": { - "name": "自定义Hosts", - "description": "修改系统hosts文件,加速网络访问。", - "labels": "网络", - "version": "1.2", - "icon": "hosts.png", - "author": "thsrite", - "level": 1, - "v2": true, - "history": { - "v1.2": "支持写入注释", - "v1.1": "关闭插件时自动恢复系统hosts" - } - }, - "SpeedLimiter": { - "name": "播放限速", - "description": "外网播放媒体库视频时,自动对下载器进行限速。", - "labels": "网络", - "version": "1.3", - "icon": "Librespeed_A.png", - "author": "Shurelol", - "level": 1, - "history": { - "v1.3": "修复bug;增加预留带宽设置", - "v1.2.1": "修复多下载器时限速比例计算错误问题", - "v1.2": "增加不限速路径配置,以应对网盘直链播放的情况" - } - }, - "CloudflareSpeedTest": { - "name": "Cloudflare IP优选", - "description": "🌩 测试 Cloudflare CDN 延迟和速度,自动优选IP。", - "labels": "网络,站点", - "version": "1.4", - "icon": "cloudflare.jpg", - "author": "thsrite", - "level": 1, - "v2": true, - "history": { - "v1.4": "修复立即运行一次", - "v1.3": "调整插件开启状态判断条件", - "v1.2": "增强API安全性" - } - }, - "BestFilmVersion": { - "name": "收藏洗版", - "description": "Jellyfin/Emby/Plex点击收藏电影后,自动订阅洗版。", - "labels": "订阅", - "version": "2.2", - "icon": "like.jpg", - "author": "wlj", - "level": 2, - "history": { - "v2.3": "修复定时任务运行问题,Jellyfin的Webhook需要主程序大于1.8.7才能正常订阅。", - "v2.2": "修复运行报错问题" - } - }, - "MediaServerMsg": { - "name": "媒体库服务器通知", - "description": "发送Emby/Jellyfin/Plex服务器的播放、入库等通知消息。", - "labels": "消息通知,媒体库", - "version": "1.3", - "icon": "mediaplay.png", - "author": "jxxghp", - "level": 1, - "history": { - "v1.3": "兼容处理Emby部分客户端暂停重复推送停止播放webhook的场景", - "v1.2": "播放通知增加超链接跳转(需要v1.9.4+)" - } - }, - "MediaServerRefresh": { - "name": "媒体库服务器刷新", - "description": "入库后自动刷新Emby/Jellyfin/Plex服务器海报墙。", - "labels": "媒体库", - "version": "1.2", - "icon": "refresh2.png", - "author": "jxxghp", - "level": 1 - }, - "WebHook": { - "name": "Webhook", - "description": "事件发生时向第三方地址发送请求。", - "version": "1.1", - "icon": "webhook.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.1": "兼容MoviePilot V2 版本", - "v1.0": "新增Webhook插件,支持事件发生时向第三方地址发送请求" - } - }, - "ChatGPT": { - "name": "ChatGPT", - "description": "消息交互支持与ChatGPT对话。", - "labels": "消息通知,识别", - "version": "1.3.1", - "icon": "Chatgpt_A.png", - "author": "jxxghp", - "level": 1 - }, - "NAStoolSync": { - "name": "历史记录同步", - "description": "同步NAStool历史记录、下载记录、插件记录到MoviePilot。", - "version": "1.0", - "icon": "Nastools_A.png", - "author": "thsrite", - "level": 1 - }, - "MessageForward": { - "name": "消息转发", - "description": "根据正则转发通知到其他WeChat应用。", - "labels": "消息通知", - "version": "1.1", - "icon": "forward.png", - "author": "thsrite", - "level": 1 - }, - "AutoBackup": { - "name": "自动备份", - "description": "自动备份数据和配置文件。", - "labels": "系统设置", - "version": "1.3", - "icon": "Time_machine_B.png", - "author": "thsrite", - "level": 1, - "history": { - "v1.3": "去除已废弃的环境变量引用", - "v1.2": "增强API安全性" - } - }, - "IYUUAutoSeed": { - "name": "IYUU自动辅种", - "description": "基于IYUU官方Api实现自动辅种。", - "labels": "做种,IYUU", - "version": "1.9.11", - "icon": "IYUU.png", - "author": "jxxghp", - "level": 2, - "history": { - "v1.9.11": "修复馒头不能辅种的问题", - "v1.9.10": "Revert 辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", - "v1.9.9": "修复qb辅种结束后自动开始暂停的种子", - "v1.9.8": "辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", - "v1.9.7": "支持qbittorrent 5", - "v1.9.6": "调整IYUU最新域名", - "v1.9.5": "Revert qBittorrent跳检之后自动开始", - "v1.9.4": "修复qBittorrent辅种后不会自动开始做种", - "v1.9.3": "修复Monika因缺少rsskey,种子下载失败的问题", - "v1.9.2": "适配馒头使用API下载种子", - "v1.9.1": "支持自定义辅种的种子分类", - "v1.9": "支持自定义辅种后标签,支持将站点名作为标签", - "v1.8.2": "qBittorrent 支持跳过校验", - "v1.8.1": "判断辅种失败的情况下,是否是由于token未进行站点绑定导致的", - "v1.8": "适配新版本IYUU开发版", - "v1.7": "适配馒头最新变化,需要升级至v1.8.5+版本且维护好Authorization", - "v1.6": "增加不辅种小体积种子功能", - "v1.5": "支持馒头新架构辅种" - } - }, - "CrossSeed": { - "name": "青蛙辅种助手", - "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。", - "labels": "做种", - "version": "2.4", - "icon": "qingwa.png", - "author": "233@qingwa", - "level": 2, - "history": { - "v2.4": "支持qbittorrent 5", - "v2.2": "站点停用后会同步暂停对该站点的辅种", - "v2.3": "站点辅种支持代理" - } - }, - "VCBAnimeMonitor": { - "name": "整理VCB动漫压制组作品", - "description": "一款辅助整理&提高识别VCB-Stuido动漫压制组作品的插件", - "labels": "文件整理,识别", - "version": "1.8.2.1", - "icon": "vcbmonitor.png", - "author": "pixel@qingwa", - "level": 2, - "history": { - "v1.8.2.1": "修复日志输出&同步目录监控插件功能", - "v1.8.2": "提高识别率", - "v1.8.1": "重构插件,测试版", - "v1.8": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", - "v1.7.1": "修复偶尔安装失败问题" - } - }, - "TorrentTransfer": { - "name": "自动转移做种", - "description": "定期转移下载器中的做种任务到另一个下载器。", - "labels": "做种", - "version": "1.7", - "icon": "seed.png", - "author": "jxxghp", - "level": 2, - "history": { - "v1.7": "支持qbittorrent 5", - "v1.6": "支持根据种子类别进行转移,并允许修改转移后的默认标签", - "v1.5": "修复在转移时只保留了第一个tracker,导致红种问题。此修复确保保留所有的tracker,以提高在不同网络条件下的可达性", - "v1.4": "支持自动删除源下载器在目的下载器中存在的种子" - } - }, - "RssSubscribe": { - "name": "自定义订阅", - "description": "定时刷新RSS报文,识别内容后添加订阅或直接下载。", - "labels": "订阅", - "version": "1.5", - "icon": "rss.png", - "author": "jxxghp", - "level": 2, - "history": { - "v1.5": "支持按种子大小过滤种子", - "v1.4": "修复剧集本地是否存在的判断错误问题", - "v1.3": "支持手动删除订阅历史记录" - } - }, - "SyncDownloadFiles": { - "name": "下载器文件同步", - "description": "同步下载器的文件信息到数据库,删除文件时联动删除下载任务。", - "labels": "下载管理", - "version": "1.1.1", - "icon": "Youtube-dl_A.png", - "author": "thsrite", - "level": 1, - "history": { - "v1.1.1": "修复时区问题导致的上次同步后8h内的种子不同步的问题" - } - }, - "BrushFlow": { - "name": "站点刷流", - "description": "自动托管刷流,将会提高对应站点的访问频率。", - "labels": "刷流,仪表板", - "version": "3.8", - "icon": "brush.jpg", - "author": "jxxghp,InfinityPacer", - "level": 2, - "history": { - "v3.8": "添加自动归档记录天数配置项,支持定时归档已删除数据", - "v3.7": "下载数量调整为仅获取刷流标签种子并修复了一些细节问题", - "v3.6": "优化检查服务中的时间管控", - "v3.5": "移除「删种排除MoviePilot任务」配置项(请使用「删除排除标签」替代),完善刷流任务触发插件事件相关逻辑(联动H&R助手)", - "v3.4": "移除「记录更多日志」配置项并调整为DEBUG日志,支持「删除排除标签」配置项,增加刷流任务时支持触发插件事件(联动H&R助手)", - "v3.3": "支持QB删除种子时强制汇报Tracker,站点独立配置增加「站点全局H&R」配置项", - "v3.2": "支持推送QB种子时启用「先下载首尾文件块」选项", - "v3.1": "支持仪表板显示站点刷流数据,需要主程序升级v1.8.7+版本", - "v3.0": "优化不同站点刷流到相同种子的逻辑,修复数据页滚动闪烁,部分日志优化", - "v2.9": "优化动态删除消息推送,优化配置页UI显示及部分配置项,支持配置种子分类以及开启自动分类管理,取消单独适配站点时区逻辑,可通过配置项「pubtime」自行适配", - "v2.8": "优化UI显示以及提升性能", - "v2.7": "动态删除种子规则调整(请注意查阅插件文档),站点独立配置样式优化、日志优化,修复部分配置项无法配置小数的问题,修复部分场景可能导致重复下载的问题", - "v2.6": "修复排除订阅功能", - "v2.5": "增加H&R做种时间、下载器监控配置项,刷流前置条件逻辑调整,代理下载种子默认为关闭" - } - }, - "DownloadingMsg": { - "name": "下载进度推送", - "description": "定时推送正在下载进度。", - "labels": "消息通知,下载管理", - "version": "1.1", - "icon": "downloadmsg.png", - "author": "thsrite", - "level": 2, - "v2": true - }, - "AutoClean": { - "name": "定时清理媒体库", - "description": "定时清理用户下载的种子、源文件、媒体库文件。", - "labels": "媒体库", - "version": "1.1", - "icon": "clean.png", - "author": "thsrite", - "level": 2 - }, - "InvitesSignin": { - "name": "药丸签到", - "description": "药丸论坛签到。", - "labels": "站点", - "version": "1.4.1", - "icon": "invites.png", - "author": "thsrite", - "level": 2, - "v2": true, - "history": { - "v1.4.1": "更新签到域名前缀", - "v1.4": "自定义保留消息天数" - } - }, - "PersonMeta": { - "name": "演职人员刮削", - "description": "刮削演职人员图片以及中文名称。", - "labels": "媒体库,刮削", - "version": "1.4", - "icon": "actor.png", - "author": "jxxghp", - "level": 1, - "history": { - "v1.4": "人物图片调整为优先从TMDB获取,避免douban图片CDN加载过慢的问题", - "v1.3": "修复v1.8.5版本后刮削报错问题" - } - }, - "MoviePilotUpdateNotify": { - "name": "MoviePilot更新推送", - "description": "MoviePilot推送release更新通知、自动重启。", - "labels": "消息通知,自动更新", - "version": "1.4", - "icon": "Moviepilot_A.png", - "author": "thsrite", - "level": 1, - "history": { - "v1.4": "兼容更新内容带版本号的情况", - "v1.3": "增加前端版本更新检查,需要主程序升级至v1.8.4+版本" - } - }, - "CloudDiskDel": { - "name": "云盘文件删除", - "description": "媒体库删除strm文件后同步删除云盘资源。", - "labels": "媒体库", - "version": "1.3", - "icon": "clouddisk.png", - "author": "thsrite", - "level": 1 - }, - "BarkMsg": { - "name": "Bark消息推送", - "description": "支持使用Bark发送消息通知。", - "labels": "消息通知", - "version": "1.4", - "icon": "Bark_A.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.4": "新增消息性测试功能", - "v1.3": "将标题、推送内容放入请求体中,避免编码后 URI 过长导致无法推送", - "v1.2": "支持多人消息发送" - } - }, - "PushDeerMsg": { - "name": "PushDeer消息推送", - "description": "支持使用PushDeer发送消息通知。", - "labels": "消息通知", - "version": "1.1", - "icon": "pushdeer.png", - "author": "jxxghp", - "level": 1, - "v2": true - }, - "ConfigCenter": { - "name": "配置中心", - "description": "快速调整部分系统设定。", - "labels": "系统设置", - "version": "2.6", - "icon": "setting.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.6": "支持DOH相关配置项", - "v2.5": "增加Github加速服务器设置项" - } - }, - "WorkWechatMsg": { - "name": "企微机器人消息推送", - "description": "支持使用企业微信群聊机器人发送消息通知。", - "labels": "消息通知", - "version": "1.0", - "icon": "Wecom_A.png", - "author": "叮叮当", - "level": 1, - "v2": true - }, - "EpisodeGroupMeta": { - "name": "TMDB剧集组刮削", - "description": "从TMDB剧集组刮削季集的实际顺序。", - "labels": "刮削", - "version": "2.6", - "icon": "Element_A.png", - "author": "叮叮当", - "level": 1, - "v2": true, - "history": { - "v2.6": "修复无法获取媒体库中季0的问题", - "v2.5": "修复当媒体服务器中剧集的季不完整时会中断的问题", - "v2.3": "修复v2版本无法读取媒体库的问题", - "v2.2": "修复v2版本无法读取数据的问题", - "v2.1": "增加发送通知提醒选择剧集组", - "v2.0": "增加手动选择剧集组的功能" - } - }, - "CustomIndexer": { - "name": "自定义索引站点", - "description": "修改或扩展内建索引器支持的站点。", - "labels": "站点", - "version": "1.0", - "icon": "spider.png", - "author": "jxxghp", - "level": 1, - "v2": true - }, - "FFmpegThumb": { - "name": "FFmpeg缩略图", - "description": "TheMovieDb没有背景图片时使用FFmpeg截取视频文件缩略图", - "labels": "刮削", - "version": "1.2", - "icon": "ffmpeg.png", - "author": "jxxghp", - "level": 1 - }, - "PushPlusMsg": { - "name": "PushPlus消息推送", - "description": "支持使用PushPlus发送消息通知。", - "labels": "消息通知", - "version": "1.2", - "icon": "Pushplus_A.png", - "author": "cheng", - "level": 1, - "v2": true, - "history": { - "v1.2": "增加群组编码,支持发送群组消息" - } - }, - "DownloadSiteTag": { - "name": "下载任务分类与标签", - "description": "自动给下载任务分类与打站点标签、剧集名称标签", - "labels": "下载管理", - "version": "2.1", - "icon": "Youtube-dl_B.png", - "author": "叮叮当", - "level": 1, - "history": { - "v2.1": "修复错误的TmdbHelper模块引用" - } - }, - "RemoveLink": { - "name": "清理硬链接", - "description": "监控目录内文件被删除时,同步删除监控目录内所有和它硬链接的文件", - "labels": "文件整理", - "version": "2.2", - "icon": "Ombi_A.png", - "author": "DzAvril", - "level": 1, - "v2": true, - "history": { - "v2.2": "修复直接删除文件夹导致的插件崩溃的bug", - "v2.1": "联动删除历史记录", - "v2.0": "联动删除种子,需安装插件[下载器助手]并打开监听源文件事件", - "v1.9": "增加清理刮削文件功能(beta)", - "v1.8": "增加清理空目录功能(beta)", - "v1.7": "修复因未监测重命名事件导致的清理硬链接失败的问题", - "v1.6": "提升插件性能" - } - }, - "LinkMonitor": { - "name": "实时硬链接", - "description": "监控目录文件变化,实时硬链接。", - "labels": "文件整理", - "version": "1.6", - "icon": "Linkace_C.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.6": "增强API安全性" - } - }, - "CategoryEditor": { - "name": "二级分类策略", - "description": "编辑下载和整理时自动二级分类的目录规则。", - "labels": "文件整理", - "version": "1.3", - "icon": "Bookstack_A.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.3": "插件实际无需一直开启,启用一次后自动关闭" - } - }, - "RemoteIdentifiers": { - "name": "共享识别词", - "description": "从Github、Etherpad等远程文件中获取共享识别词并应用。", - "labels": "识别", - "version": "2.3", - "icon": "words.png", - "author": "honue", - "level": 1, - "v2": true, - "history": { - "v2.3": "更换默认共享识别词地址" - } - }, - "NeoDBSync": { - "name": "NeoDB 想看", - "description": "同步 NeoDB 想看条目,自动添加订阅。", - "labels": "订阅", - "version": "1.1", - "icon": "NeoDB.jpeg", - "author": "hcplantern", - "level": 1, - "v2": true, - "history": { - "v1.1": "直接添加订阅,不提前进行搜索下载" - } - }, - "PlayletCategory": { - "name": "短剧自动分类", - "description": "网络短剧自动整理到独立的分类目录。", - "labels": "文件整理", - "version": "2.0", - "icon": "Amule_A.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.0": "适配新的目录结构变化,短剧分类名称调整为配置目录路径,升级后需要重新调整设置后才能使用。" - } - }, - "DiagParamAdjust": { - "name": "诊断参数调整", - "description": "Emby专用插件|暂时性解决emby字幕偏移问题,需要emby安装Diagnostics插件。", - "labels": "Emby", - "version": "1.3", - "icon": "Gatus_A.png", - "author": "jeblove", - "level": 1 - }, - "QbCommand": { - "name": "QB远程操作", - "description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。", - "labels": "下载管理,Qbittorrent", - "version": "1.6", - "icon": "Qbittorrent_A.png", - "author": "DzAvril", - "level": 1, - "history": { - "v1.6": "支持qbittorrent 5", - "v1.5": "可选特定路径下的做种不会被暂停", - "v1.4": "可选某些站点不再做种(暂停做种后不会被恢复)" - } - }, - "TrCommand": { - "name": "TR远程操作", - "description": "通过定时任务或交互命令远程操作TR暂停/开始/限速等。", - "labels": "下载管理,Transmission", - "version": "1.1", - "icon": "Transmission_A.png", - "author": "Hoey", - "level": 1 - }, - "IpDetect": { - "name": "本地IP检测", - "description": "如果QB、TR等服务在本地部署,当本地IP改变时自动修改其Server IP。", - "labels": "系统设置", - "version": "1.1", - "icon": "ipAddress.png", - "author": "DzAvril", - "level": 1, - "v2": true - }, - "TrackerEditor": { - "name": "Tracker替换", - "description": "批量替换修改种子tracker。", - "labels": "做种", - "version": "1.8", - "icon": "trackereditor_A.png", - "author": "honue", - "level": 1, - "v2": true, - "history": { - "v1.8": "修复老版本tr修改出错问题,优化log输出", - "v1.7": "支持多个tracker替换配置" - } - }, - "ContractCheck": { - "name": "契约检查", - "description": "定时检查保种契约达成情况。", - "labels": "做种", - "version": "1.4", - "icon": "contract.png", - "author": "DzAvril", - "level": 1, - "history": { - "v1.4": "支持仪表板组件显示", - "v1.3": "修复观众做种数据异常问题", - "v1.2": "修复契约检查无数据返回的问题" - }, - "v2": true - }, - "FeiShuMsg": { - "name": "飞书机器人消息通知", - "description": "支持使用飞书群聊机器人发送消息通知。", - "labels": "消息通知", - "version": "1.0", - "icon": "FeiShu_A.png", - "author": "InfinityPacer", - "level": 2, - "v2": true - }, - "IyuuAuth": { - "name": "IYUU站点绑定", - "description": "为IYUU账号绑定认证站点,以便用于用户认证和辅种。", - "labels": "IYUU", - "version": "1.2", - "icon": "Iyuu_A.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.2": "调整IYUU域名地址", - "v1.1": "修复IYUU站点绑定失败问题" - } - }, - "NtfyMsg": { - "name": "ntfy消息推送", - "description": "支持使用ntfy发送消息通知。", - "labels": "消息通知", - "version": "1.0", - "icon": "Ntfy_A.png", - "author": "lethargicScribe", - "level": 1, - "v2": true - }, - "GotifyMsg": { - "name": "gotify消息推送", - "description": "支持使用gotify发送消息通知。", - "labels": "消息通知", - "version": "1.1", - "icon": "https://raw.githubusercontent.com/gotify/logo/refs/heads/master/gotify-logo.png", - "author": "lethargicScribe", - "level": 1, - "v2": true - }, - "AppriseMsg": { - "name": "Apprise 消息推送", - "description": "Apprise - 适用于几乎所有平台的推送通知!", - "labels": "消息通知", - "version": "1.0", - "icon": "Ntfy_A.png", - "author": "lethargicScribe", - "level": 1, - "v2": true - }, - "TmdbWallpaper": { - "name": "登录壁纸本地化", - "description": "将MoviePilot的登录壁纸下载到本地。", - "labels": "工具", - "version": "1.2", - "icon": "Macos_Sierra.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.2": "一次性下载多张壁纸", - "v1.1": "修复下载Bing每日壁纸时文件名错乱的问题" - } - }, - "MPServerStatus": { - "name": "MoviePilot服务器监控", - "description": "在仪表板中实时显示MoviePilot公共服务器状态。", - "labels": "仪表板", - "version": "1.2", - "icon": "Duplicati_A.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.2": "优化数量示", - "v1.1": "增加详情界面显示" - } - }, - "CleanInvalidSeed": { - "name": "清理QB无效做种", - "description": "清理已经被站点删除的种子及对应源文件,仅支持QB", - "labels": "Qbittorrent", - "version": "2.2", - "icon": "clean_a.png", - "author": "DzAvril", - "level": 1, - "history": { - "v2.2": "支持仅标记模式", - "v2.1": "1. 修复删除无效做种没有tg通知的问题。2. 检测未工作做种排除已暂停做种", - "v2.0": "修复检测不到无效做种的bug", - "v1.9": "增加自定义需删除做种的tracker的错误信息", - "v1.8": "增加远程命令切换全量通知;修复bug", - "v1.7": "修复因消息内容包含'_'导致telegram API调用失败的问题", - "v1.6": "修复当种子有多个标签时,通过标签过滤不删除种子会失效的问题", - "v1.5": "1. 增加通过分类、标签过滤不删除种子功能;2. 全量通知提供更多信息", - "v1.4": "修复插件功能失效的问题", - "v1.3": "1. 增加远程命令 2. 根据tracker error_message字段进行过滤,避免误删", - "v1.2": "修复配置页空白的问题", - "v1.1": "更新使用说明,以防使用不当误删文件", - "v1.0": "定时清理已经被站点删除的种子及对应源文件" - } - }, - "TrendingShow": { - "name": "流行趋势轮播", - "description": "在仪表板中显示流行趋势海报轮播图。", - "labels": "仪表板", - "version": "1.3", - "icon": "TrendingShow.jpg", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.3": "调整组件大小", - "v1.2": "不同屏幕大小,支持分开设置" - } - }, - "DailyWord": { - "name": "每日一言", - "description": "在仪表板中显示每日一言卡片。", - "labels": "仪表板", - "version": "1.2", - "icon": "Calibre_B.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.2": "修复每日一言图文API接口" - } - }, - "ZvideoHelper": { - "name": "极影视助手", - "description": "极影视功能扩展", - "labels": "媒体库", - "version": "1.6", - "icon": "zvideo.png", - "author": "DzAvril", - "level": 1, - "v2": true, - "history": { - "v1.6": "增加定期更新豆瓣评分", - "v1.5": "适配新版极影视", - "v1.4": "修复请求失败后返回值数量不正确的问题", - "v1.3": "降低对豆瓣接口的请求频率", - "v1.2": "修复无法获取豆瓣评分的问题", - "v1.1": "支持将极影视评分修改为豆瓣评分", - "v1.0": "同步极影视在看/已看状态到豆瓣" - } - }, - "MqttPush": { - "name": "MQTT消息推送", - "description": "支持使用MQTT发送消息通知。", - "labels": "消息通知", - "version": "1.0", - "icon": "Mosquitto_A.png", - "author": "blacklips", - "level": 1 - }, - "DingdingMsg": { - "name": "钉钉机器人", - "description": "支持使用钉钉机器人发送消息通知。", - "labels": "消息通知,钉钉机器人", - "version": "1.12", - "icon": "Dingding_A.png", - "author": "nnlegenda", - "level": 1, - "v2": true - }, - "DynamicWeChat": { - "name": "动态企微可信IP", - "description": "修改企微应用可信IP,支持Srever酱等第三方通知。验证码以?结尾发送到企业微信应用", - "labels": "消息通知", - "version": "1.7.2", - "icon": "Wecom_A.png", - "author": "RamenRa", - "level": 2, - "v2": true, - "history": { - "v1.7.2": "||wan参数细分,修复使用||wan时立即检测一次实际不生效,修复v1第三方备用通知可能无效,调整验证码获取", - "v1.7.1": "允许使用'||wan2'选项及无法使用'立即检测一次'", - "v1.7.0": "使用第三方通知时可IP变动后通知,拟支持多网络出口检查。", - "v1.6.0": "忽略因网络波动导致获取ip错误。自定义的类合并为helper.py。后续核心功能没问题将不再更新", - "v1.5.2": "可以从指定url获取ip,修复不使用cc时cookie失效过快,v1可配置第三方为备用通知,server酱可以将文本发送到server3,二维码给服务号", - "v1.5.1": "修复v2微信通知,可以指定微信通知ID", - "v1.5.0": "支持企微应用通知和第Serve酱等第三方推送。按要求修改插件名称", - "v1.4.1": "完善面板说明", - "v1.4.0": "修复强制更改IP时配置面板延时过长的问题。庆祝v2进入正式版,显示了一个没用的参数" - } - }, - "SyncCookieCloud": { - "name": "同步CookieCloud", - "description": "同步MoviePilot站点Cookie到本地CookieCloud。", - "labels": "站点", - "version": "1.4", - "icon": "Cookiecloud_A.png", - "author": "thsrite", - "level": 1, - "history": { - "v1.4": "调整逻辑,修复问题", - "v1.3": "感谢MidnightShake共享代码(同步时保留MoviePilot不匹配站点的cookie)", - "v1.2": "同步到本地CookieCloud", - "v1.1": "修复CookieCloud覆盖到浏览器", - "v1.0": "同步MoviePilot站点Cookie到CookieCloud" - } - }, - "BangumiColl": { - "name": "Bangumi收藏订阅", - "description": "Bangumi用户收藏添加到订阅", - "labels": "订阅", - "version": "1.5.6", - "icon": "bangumi_b.png", - "author": "Attente", - "level": 1, - "v2": true, - "history": { - "v1.5.6": "修复远程命令名称, 完善远程命令回执", - "v1.5.5": "添加剧集组(需V2.3.8+), 新增远程命令", - "v1.5.4": "fix: wikrin/MoviePilot-Plugins/issues/2", - "v1.5.3": "增加多语言标题匹配, 去除未实现设置项", - "v1.5.2": "修复定时任务未正确注册的问题", - "v1.5.1": "修复季度信息未传递的问题. 新增站点列表同步删除", - "v1.5": "修复总集数会同步TMDB变动的问题,增加开关选项" - } - }, - "IyuuMsg": { - "name": "IYUU消息推送", - "description": "支持使用IYUU发送消息通知。", - "labels": "消息通知,IYUU", - "version": "1.3", - "icon": "Iyuu_A.png", - "author": "jxxghp", - "level": 1, - "v2": true, - "history": { - "v1.3": "消息限流发送,以缓解IYUU服务器压力" - } + "AutoSignIn": { + "name": "站点自动签到", + "description": "自动模拟登录、签到站点。", + "labels": "站点", + "version": "2.4.4", + "icon": "signin.png", + "author": "thsrite", + "level": 2, + "history": { + "v2.4.4": "增加保号风险提示", + "v2.4.3": "修复空签到失败问题", + "v2.4.2": "修复PT时间签到失败问题", + "v2.4.1": "修复海胆签到失败问题", + "v2.4": "适配m-team Api地址变化", + "v2.3.2": "修复YemaPT登录失败,支持YemaPT自动签到", + "v2.3.1": "修复签到报错问题", + "v2.3": "优化模拟登录逻辑,支持YemaPT模拟登录", + "v2.2": "适配馒头最新变化,需要升级至v1.8.5+版本且维护好Authorization", + "v2.1": "增强API安全性", + "v2.0": "站点签到时更新站点使用统计信息,需要主程序升级至v1.8.3+版本", + "v1.9": "支持馒头新架构自动签到" } + }, + "AutoSubv2": { + "name": "AI字幕自动生成(v2)", + "description": "使用whisper自动生成视频文件字幕,使用大模型翻译字幕成中文。", + "labels": "字幕", + "version": "2.3", + "icon": "autosubtitles.jpeg", + "author": "TimoYoung", + "level": 1, + "v2": true, + "history": { + "v1.0": "first stable version", + "v1.1": "优化字幕翻译逻辑,优化日志输出", + "v1.2": "fix openai_proxy打开时,翻译失败的问题,优化日志输出", + "v2.0": "1.引入任务队列 2.支持监听媒体入库自动生成字幕 3.增加任务状态展示界面", + "v2.1": "支持清除历史记录", + "v2.2": "fix", + "v2.3": "支持独立的大模型调用配置" + } + }, + "CustomSites": { + "name": "自定义站点", + "description": "增加自定义站点为签到和统计使用。", + "labels": "站点", + "version": "1.0", + "icon": "world.png", + "author": "lightolly", + "level": 2, + "v2": true + }, + "SiteStatistic": { + "name": "站点数据统计", + "description": "自动统计和展示站点数据。", + "labels": "站点,仪表板", + "version": "4.0.1", + "icon": "statistic.png", + "author": "lightolly", + "level": 2, + "history": { + "v4.0.1": "修复PTT的魔力值统计", + "v4.0": "修复插件数据页异常", + "v3.9.3": "修复PTT的用户等级统计", + "v3.9.2": "修复YemaPT的上传下载统计错误", + "v3.9.1": "修复mteam域名地址", + "v3.9": "修复YemaPT站点数据统计", + "v3.8": "适配m-team Api地址变化", + "v3.7": "修复观众做种数据统计", + "v3.6": "支持站点数据统计刷新后触发插件事件", + "v3.5": "站点数据统计支持YemaPT", + "v3.4": "修复馒头站点数据统计", + "v3.3": "支持选择仪表板组件规格", + "v3.2": "支持在仪表板中显示站点统计信息,需要主程序升级至v1.8.7+版本", + "v3.1": "修复观众无法统计做总数和做种体积的bug", + "v3.0": "适配馒头数据统计,需要升级至v1.8.5+版本,且在站点信息中维护好API Key", + "v2.9": "增强API安全性", + "v2.8": "修复馒头未读消息统计", + "v2.7": "修复憨憨种子信息只统计第一页的问题,增加移除失效统计选项", + "v2.6": "支持馒头新架构数据统计" + } + }, + "SiteRefresh": { + "name": "站点自动更新", + "description": "使用浏览器模拟登录站点获取Cookie和UA。", + "labels": "站点", + "version": "1.2", + "icon": "Chrome_A.png", + "author": "thsrite", + "level": 2, + "v2": true + }, + "DoubanSync": { + "name": "豆瓣想看", + "description": "同步豆瓣想看数据,自动添加订阅。", + "labels": "订阅", + "version": "1.9.1", + "icon": "douban.png", + "author": "jxxghp", + "level": 2, + "history": { + "v1.9.1": "修复版本兼容问题", + "v1.9": "请求豆瓣RSS时增加请求头", + "v1.8": "不同步在看条目", + "v1.7": "增强API安全性", + "v1.6": "同步历史记录支持手动删除,需要主程序升级至v1.8.4+版本", + "v1.5": "豆瓣信息识别后直接添加订阅,不进行搜索下载" + } + }, + "DirMonitor": { + "name": "目录监控", + "description": "监控目录文件发生变化时实时整理到媒体库。", + "labels": "文件整理", + "version": "2.4", + "icon": "directory.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.4": "修复目录监控不使用ChatGPT辅助识别问题", + "v2.3": "特殊场景下补充转移成功历史记录", + "v2.2": "更新目录设置说明", + "v2.1": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", + "v2.0": "增强API安全性", + "v1.9": "修复目录监控不能正确获取下载历史记录进行识别的问题" + } + }, + "ChineseSubFinder": { + "name": "ChineseSubFinder", + "description": "整理入库时通知ChineseSubFinder下载字幕。", + "labels": "字幕", + "version": "1.1", + "icon": "chinesesubfinder.png", + "author": "jxxghp", + "level": 1 + }, + "DoubanRank": { + "name": "豆瓣榜单订阅", + "description": "监控豆瓣热门榜单,自动添加订阅。", + "labels": "订阅", + "version": "1.9.1", + "icon": "movie.jpg", + "author": "jxxghp", + "level": 2, + "history": { + "v1.9.1": "优化媒体类型的判断处理", + "v1.9": "增强API安全性", + "v1.8": "订阅历史记录支持手动删除,需要主程序升级至v1.8.4+版本" + } + }, + "LibraryScraper": { + "name": "媒体库刮削", + "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", + "labels": "刮削", + "version": "1.5", + "icon": "scraper.png", + "author": "jxxghp", + "level": 1, + "history": { + "v1.5": "修复未获取fanart图片的问题", + "v1.4.1": "修复nfo文件读取失败时任务中断问题" + } + }, + "TorrentRemover": { + "name": "自动删种", + "description": "自动删除下载器中的下载任务。", + "labels": "做种", + "version": "1.2.2", + "icon": "delete.jpg", + "author": "jxxghp", + "level": 2 + }, + "MediaSyncDel": { + "name": "媒体文件同步删除", + "description": "同步删除历史记录、源文件和下载任务。", + "labels": "文件整理", + "version": "1.7.1", + "icon": "mediasyncdel.png", + "author": "thsrite", + "level": 1, + "history": { + "v1.7.1": "修复删除剧集辅种失败报错问题", + "v1.7": "修复重新整理被一并删除问题", + "v1.6": "修复删除辅种", + "v1.5": "支持手动删除订阅历史记录(本次更新之后的历史)" + } + }, + "CustomHosts": { + "name": "自定义Hosts", + "description": "修改系统hosts文件,加速网络访问。", + "labels": "网络", + "version": "1.2", + "icon": "hosts.png", + "author": "thsrite", + "level": 1, + "v2": true, + "history": { + "v1.2": "支持写入注释", + "v1.1": "关闭插件时自动恢复系统hosts" + } + }, + "SpeedLimiter": { + "name": "播放限速", + "description": "外网播放媒体库视频时,自动对下载器进行限速。", + "labels": "网络", + "version": "1.3", + "icon": "Librespeed_A.png", + "author": "Shurelol", + "level": 1, + "history": { + "v1.3": "修复bug;增加预留带宽设置", + "v1.2.1": "修复多下载器时限速比例计算错误问题", + "v1.2": "增加不限速路径配置,以应对网盘直链播放的情况" + } + }, + "CloudflareSpeedTest": { + "name": "Cloudflare IP优选", + "description": "🌩 测试 Cloudflare CDN 延迟和速度,自动优选IP。", + "labels": "网络,站点", + "version": "1.4", + "icon": "cloudflare.jpg", + "author": "thsrite", + "level": 1, + "v2": true, + "history": { + "v1.4": "修复立即运行一次", + "v1.3": "调整插件开启状态判断条件", + "v1.2": "增强API安全性" + } + }, + "BestFilmVersion": { + "name": "收藏洗版", + "description": "Jellyfin/Emby/Plex点击收藏电影后,自动订阅洗版。", + "labels": "订阅", + "version": "2.2", + "icon": "like.jpg", + "author": "wlj", + "level": 2, + "history": { + "v2.3": "修复定时任务运行问题,Jellyfin的Webhook需要主程序大于1.8.7才能正常订阅。", + "v2.2": "修复运行报错问题" + } + }, + "MediaServerMsg": { + "name": "媒体库服务器通知", + "description": "发送Emby/Jellyfin/Plex服务器的播放、入库等通知消息。", + "labels": "消息通知,媒体库", + "version": "1.3", + "icon": "mediaplay.png", + "author": "jxxghp", + "level": 1, + "history": { + "v1.3": "兼容处理Emby部分客户端暂停重复推送停止播放webhook的场景", + "v1.2": "播放通知增加超链接跳转(需要v1.9.4+)" + } + }, + "MediaServerRefresh": { + "name": "媒体库服务器刷新", + "description": "入库后自动刷新Emby/Jellyfin/Plex服务器海报墙。", + "labels": "媒体库", + "version": "1.2", + "icon": "refresh2.png", + "author": "jxxghp", + "level": 1 + }, + "WebHook": { + "name": "Webhook", + "description": "事件发生时向第三方地址发送请求。", + "version": "1.1", + "icon": "webhook.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.1": "兼容MoviePilot V2 版本", + "v1.0": "新增Webhook插件,支持事件发生时向第三方地址发送请求" + } + }, + "ChatGPT": { + "name": "ChatGPT", + "description": "消息交互支持与ChatGPT对话。", + "labels": "消息通知,识别", + "version": "1.3.1", + "icon": "Chatgpt_A.png", + "author": "jxxghp", + "level": 1 + }, + "NAStoolSync": { + "name": "历史记录同步", + "description": "同步NAStool历史记录、下载记录、插件记录到MoviePilot。", + "version": "1.0", + "icon": "Nastools_A.png", + "author": "thsrite", + "level": 1 + }, + "MessageForward": { + "name": "消息转发", + "description": "根据正则转发通知到其他WeChat应用。", + "labels": "消息通知", + "version": "1.1", + "icon": "forward.png", + "author": "thsrite", + "level": 1 + }, + "AutoBackup": { + "name": "自动备份", + "description": "自动备份数据和配置文件。", + "labels": "系统设置", + "version": "1.3", + "icon": "Time_machine_B.png", + "author": "thsrite", + "level": 1, + "history": { + "v1.3": "去除已废弃的环境变量引用", + "v1.2": "增强API安全性" + } + }, + "IYUUAutoSeed": { + "name": "IYUU自动辅种", + "description": "基于IYUU官方Api实现自动辅种。", + "labels": "做种,IYUU", + "version": "1.9.11", + "icon": "IYUU.png", + "author": "jxxghp", + "level": 2, + "history": { + "v1.9.11": "修复馒头不能辅种的问题", + "v1.9.10": "Revert 辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", + "v1.9.9": "修复qb辅种结束后自动开始暂停的种子", + "v1.9.8": "辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", + "v1.9.7": "支持qbittorrent 5", + "v1.9.6": "调整IYUU最新域名", + "v1.9.5": "Revert qBittorrent跳检之后自动开始", + "v1.9.4": "修复qBittorrent辅种后不会自动开始做种", + "v1.9.3": "修复Monika因缺少rsskey,种子下载失败的问题", + "v1.9.2": "适配馒头使用API下载种子", + "v1.9.1": "支持自定义辅种的种子分类", + "v1.9": "支持自定义辅种后标签,支持将站点名作为标签", + "v1.8.2": "qBittorrent 支持跳过校验", + "v1.8.1": "判断辅种失败的情况下,是否是由于token未进行站点绑定导致的", + "v1.8": "适配新版本IYUU开发版", + "v1.7": "适配馒头最新变化,需要升级至v1.8.5+版本且维护好Authorization", + "v1.6": "增加不辅种小体积种子功能", + "v1.5": "支持馒头新架构辅种" + } + }, + "CrossSeed": { + "name": "青蛙辅种助手", + "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。", + "labels": "做种", + "version": "2.4", + "icon": "qingwa.png", + "author": "233@qingwa", + "level": 2, + "history": { + "v2.4": "支持qbittorrent 5", + "v2.2": "站点停用后会同步暂停对该站点的辅种", + "v2.3": "站点辅种支持代理" + } + }, + "VCBAnimeMonitor": { + "name": "整理VCB动漫压制组作品", + "description": "一款辅助整理&提高识别VCB-Stuido动漫压制组作品的插件", + "labels": "文件整理,识别", + "version": "1.8.2.1", + "icon": "vcbmonitor.png", + "author": "pixel@qingwa", + "level": 2, + "history": { + "v1.8.2.1": "修复日志输出&同步目录监控插件功能", + "v1.8.2": "提高识别率", + "v1.8.1": "重构插件,测试版", + "v1.8": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", + "v1.7.1": "修复偶尔安装失败问题" + } + }, + "TorrentTransfer": { + "name": "自动转移做种", + "description": "定期转移下载器中的做种任务到另一个下载器。", + "labels": "做种", + "version": "1.7", + "icon": "seed.png", + "author": "jxxghp", + "level": 2, + "history": { + "v1.7": "支持qbittorrent 5", + "v1.6": "支持根据种子类别进行转移,并允许修改转移后的默认标签", + "v1.5": "修复在转移时只保留了第一个tracker,导致红种问题。此修复确保保留所有的tracker,以提高在不同网络条件下的可达性", + "v1.4": "支持自动删除源下载器在目的下载器中存在的种子" + } + }, + "RssSubscribe": { + "name": "自定义订阅", + "description": "定时刷新RSS报文,识别内容后添加订阅或直接下载。", + "labels": "订阅", + "version": "1.5", + "icon": "rss.png", + "author": "jxxghp", + "level": 2, + "history": { + "v1.5": "支持按种子大小过滤种子", + "v1.4": "修复剧集本地是否存在的判断错误问题", + "v1.3": "支持手动删除订阅历史记录" + } + }, + "SyncDownloadFiles": { + "name": "下载器文件同步", + "description": "同步下载器的文件信息到数据库,删除文件时联动删除下载任务。", + "labels": "下载管理", + "version": "1.1.1", + "icon": "Youtube-dl_A.png", + "author": "thsrite", + "level": 1, + "history": { + "v1.1.1": "修复时区问题导致的上次同步后8h内的种子不同步的问题" + } + }, + "BrushFlow": { + "name": "站点刷流", + "description": "自动托管刷流,将会提高对应站点的访问频率。", + "labels": "刷流,仪表板", + "version": "3.8", + "icon": "brush.jpg", + "author": "jxxghp,InfinityPacer", + "level": 2, + "history": { + "v3.8": "添加自动归档记录天数配置项,支持定时归档已删除数据", + "v3.7": "下载数量调整为仅获取刷流标签种子并修复了一些细节问题", + "v3.6": "优化检查服务中的时间管控", + "v3.5": "移除「删种排除MoviePilot任务」配置项(请使用「删除排除标签」替代),完善刷流任务触发插件事件相关逻辑(联动H&R助手)", + "v3.4": "移除「记录更多日志」配置项并调整为DEBUG日志,支持「删除排除标签」配置项,增加刷流任务时支持触发插件事件(联动H&R助手)", + "v3.3": "支持QB删除种子时强制汇报Tracker,站点独立配置增加「站点全局H&R」配置项", + "v3.2": "支持推送QB种子时启用「先下载首尾文件块」选项", + "v3.1": "支持仪表板显示站点刷流数据,需要主程序升级v1.8.7+版本", + "v3.0": "优化不同站点刷流到相同种子的逻辑,修复数据页滚动闪烁,部分日志优化", + "v2.9": "优化动态删除消息推送,优化配置页UI显示及部分配置项,支持配置种子分类以及开启自动分类管理,取消单独适配站点时区逻辑,可通过配置项「pubtime」自行适配", + "v2.8": "优化UI显示以及提升性能", + "v2.7": "动态删除种子规则调整(请注意查阅插件文档),站点独立配置样式优化、日志优化,修复部分配置项无法配置小数的问题,修复部分场景可能导致重复下载的问题", + "v2.6": "修复排除订阅功能", + "v2.5": "增加H&R做种时间、下载器监控配置项,刷流前置条件逻辑调整,代理下载种子默认为关闭" + } + }, + "DownloadingMsg": { + "name": "下载进度推送", + "description": "定时推送正在下载进度。", + "labels": "消息通知,下载管理", + "version": "1.1", + "icon": "downloadmsg.png", + "author": "thsrite", + "level": 2, + "v2": true + }, + "AutoClean": { + "name": "定时清理媒体库", + "description": "定时清理用户下载的种子、源文件、媒体库文件。", + "labels": "媒体库", + "version": "1.1", + "icon": "clean.png", + "author": "thsrite", + "level": 2 + }, + "InvitesSignin": { + "name": "药丸签到", + "description": "药丸论坛签到。", + "labels": "站点", + "version": "1.4.1", + "icon": "invites.png", + "author": "thsrite", + "level": 2, + "v2": true, + "history": { + "v1.4.1": "更新签到域名前缀", + "v1.4": "自定义保留消息天数" + } + }, + "PersonMeta": { + "name": "演职人员刮削", + "description": "刮削演职人员图片以及中文名称。", + "labels": "媒体库,刮削", + "version": "1.4", + "icon": "actor.png", + "author": "jxxghp", + "level": 1, + "history": { + "v1.4": "人物图片调整为优先从TMDB获取,避免douban图片CDN加载过慢的问题", + "v1.3": "修复v1.8.5版本后刮削报错问题" + } + }, + "MoviePilotUpdateNotify": { + "name": "MoviePilot更新推送", + "description": "MoviePilot推送release更新通知、自动重启。", + "labels": "消息通知,自动更新", + "version": "1.4", + "icon": "Moviepilot_A.png", + "author": "thsrite", + "level": 1, + "history": { + "v1.4": "兼容更新内容带版本号的情况", + "v1.3": "增加前端版本更新检查,需要主程序升级至v1.8.4+版本" + } + }, + "CloudDiskDel": { + "name": "云盘文件删除", + "description": "媒体库删除strm文件后同步删除云盘资源。", + "labels": "媒体库", + "version": "1.3", + "icon": "clouddisk.png", + "author": "thsrite", + "level": 1 + }, + "BarkMsg": { + "name": "Bark消息推送", + "description": "支持使用Bark发送消息通知。", + "labels": "消息通知", + "version": "1.4", + "icon": "Bark_A.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.4": "新增消息性测试功能", + "v1.3": "将标题、推送内容放入请求体中,避免编码后 URI 过长导致无法推送", + "v1.2": "支持多人消息发送" + } + }, + "PushDeerMsg": { + "name": "PushDeer消息推送", + "description": "支持使用PushDeer发送消息通知。", + "labels": "消息通知", + "version": "1.1", + "icon": "pushdeer.png", + "author": "jxxghp", + "level": 1, + "v2": true + }, + "ConfigCenter": { + "name": "配置中心", + "description": "快速调整部分系统设定。", + "labels": "系统设置", + "version": "2.6", + "icon": "setting.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.6": "支持DOH相关配置项", + "v2.5": "增加Github加速服务器设置项" + } + }, + "WorkWechatMsg": { + "name": "企微机器人消息推送", + "description": "支持使用企业微信群聊机器人发送消息通知。", + "labels": "消息通知", + "version": "1.0", + "icon": "Wecom_A.png", + "author": "叮叮当", + "level": 1, + "v2": true + }, + "EpisodeGroupMeta": { + "name": "TMDB剧集组刮削", + "description": "从TMDB剧集组刮削季集的实际顺序。", + "labels": "刮削", + "version": "2.6", + "icon": "Element_A.png", + "author": "叮叮当", + "level": 1, + "v2": true, + "history": { + "v2.6": "修复无法获取媒体库中季0的问题", + "v2.5": "修复当媒体服务器中剧集的季不完整时会中断的问题", + "v2.3": "修复v2版本无法读取媒体库的问题", + "v2.2": "修复v2版本无法读取数据的问题", + "v2.1": "增加发送通知提醒选择剧集组", + "v2.0": "增加手动选择剧集组的功能" + } + }, + "CustomIndexer": { + "name": "自定义索引站点", + "description": "修改或扩展内建索引器支持的站点。", + "labels": "站点", + "version": "1.0", + "icon": "spider.png", + "author": "jxxghp", + "level": 1, + "v2": true + }, + "FFmpegThumb": { + "name": "FFmpeg缩略图", + "description": "TheMovieDb没有背景图片时使用FFmpeg截取视频文件缩略图", + "labels": "刮削", + "version": "1.2", + "icon": "ffmpeg.png", + "author": "jxxghp", + "level": 1 + }, + "PushPlusMsg": { + "name": "PushPlus消息推送", + "description": "支持使用PushPlus发送消息通知。", + "labels": "消息通知", + "version": "1.2", + "icon": "Pushplus_A.png", + "author": "cheng", + "level": 1, + "v2": true, + "history": { + "v1.2": "增加群组编码,支持发送群组消息" + } + }, + "DownloadSiteTag": { + "name": "下载任务分类与标签", + "description": "自动给下载任务分类与打站点标签、剧集名称标签", + "labels": "下载管理", + "version": "2.1", + "icon": "Youtube-dl_B.png", + "author": "叮叮当", + "level": 1, + "history": { + "v2.1": "修复错误的TmdbHelper模块引用" + } + }, + "RemoveLink": { + "name": "清理硬链接", + "description": "监控目录内文件被删除时,同步删除监控目录内所有和它硬链接的文件", + "labels": "文件整理", + "version": "2.2", + "icon": "Ombi_A.png", + "author": "DzAvril", + "level": 1, + "v2": true, + "history": { + "v2.2": "修复直接删除文件夹导致的插件崩溃的bug", + "v2.1": "联动删除历史记录", + "v2.0": "联动删除种子,需安装插件[下载器助手]并打开监听源文件事件", + "v1.9": "增加清理刮削文件功能(beta)", + "v1.8": "增加清理空目录功能(beta)", + "v1.7": "修复因未监测重命名事件导致的清理硬链接失败的问题", + "v1.6": "提升插件性能" + } + }, + "LinkMonitor": { + "name": "实时硬链接", + "description": "监控目录文件变化,实时硬链接。", + "labels": "文件整理", + "version": "1.6", + "icon": "Linkace_C.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.6": "增强API安全性" + } + }, + "CategoryEditor": { + "name": "二级分类策略", + "description": "编辑下载和整理时自动二级分类的目录规则。", + "labels": "文件整理", + "version": "1.3", + "icon": "Bookstack_A.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.3": "插件实际无需一直开启,启用一次后自动关闭" + } + }, + "RemoteIdentifiers": { + "name": "共享识别词", + "description": "从Github、Etherpad等远程文件中获取共享识别词并应用。", + "labels": "识别", + "version": "2.3", + "icon": "words.png", + "author": "honue", + "level": 1, + "v2": true, + "history": { + "v2.3": "更换默认共享识别词地址" + } + }, + "NeoDBSync": { + "name": "NeoDB 想看", + "description": "同步 NeoDB 想看条目,自动添加订阅。", + "labels": "订阅", + "version": "1.1", + "icon": "NeoDB.jpeg", + "author": "hcplantern", + "level": 1, + "v2": true, + "history": { + "v1.1": "直接添加订阅,不提前进行搜索下载" + } + }, + "PlayletCategory": { + "name": "短剧自动分类", + "description": "网络短剧自动整理到独立的分类目录。", + "labels": "文件整理", + "version": "2.0", + "icon": "Amule_A.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.0": "适配新的目录结构变化,短剧分类名称调整为配置目录路径,升级后需要重新调整设置后才能使用。" + } + }, + "DiagParamAdjust": { + "name": "诊断参数调整", + "description": "Emby专用插件|暂时性解决emby字幕偏移问题,需要emby安装Diagnostics插件。", + "labels": "Emby", + "version": "1.3", + "icon": "Gatus_A.png", + "author": "jeblove", + "level": 1 + }, + "QbCommand": { + "name": "QB远程操作", + "description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。", + "labels": "下载管理,Qbittorrent", + "version": "1.6", + "icon": "Qbittorrent_A.png", + "author": "DzAvril", + "level": 1, + "history": { + "v1.6": "支持qbittorrent 5", + "v1.5": "可选特定路径下的做种不会被暂停", + "v1.4": "可选某些站点不再做种(暂停做种后不会被恢复)" + } + }, + "TrCommand": { + "name": "TR远程操作", + "description": "通过定时任务或交互命令远程操作TR暂停/开始/限速等。", + "labels": "下载管理,Transmission", + "version": "1.1", + "icon": "Transmission_A.png", + "author": "Hoey", + "level": 1 + }, + "IpDetect": { + "name": "本地IP检测", + "description": "如果QB、TR等服务在本地部署,当本地IP改变时自动修改其Server IP。", + "labels": "系统设置", + "version": "1.1", + "icon": "ipAddress.png", + "author": "DzAvril", + "level": 1, + "v2": true + }, + "TrackerEditor": { + "name": "Tracker替换", + "description": "批量替换修改种子tracker。", + "labels": "做种", + "version": "1.9", + "icon": "trackereditor_A.png", + "author": "honue", + "level": 1, + "v2": true, + "history": { + "v1.8": "修复老版本tr修改出错问题,优化log输出", + "v1.7": "支持多个tracker替换配置" + } + }, + "ContractCheck": { + "name": "契约检查", + "description": "定时检查保种契约达成情况。", + "labels": "做种", + "version": "1.4", + "icon": "contract.png", + "author": "DzAvril", + "level": 1, + "history": { + "v1.4.1": "增加站点猪猪", + "v1.4": "支持仪表板组件显示", + "v1.3": "修复观众做种数据异常问题", + "v1.2": "修复契约检查无数据返回的问题" + }, + "v2": true + }, + "FeiShuMsg": { + "name": "飞书机器人消息通知", + "description": "支持使用飞书群聊机器人发送消息通知。", + "labels": "消息通知", + "version": "1.0", + "icon": "FeiShu_A.png", + "author": "InfinityPacer", + "level": 2, + "v2": true + }, + "IyuuAuth": { + "name": "IYUU站点绑定", + "description": "为IYUU账号绑定认证站点,以便用于用户认证和辅种。", + "labels": "IYUU", + "version": "1.2", + "icon": "Iyuu_A.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.2": "调整IYUU域名地址", + "v1.1": "修复IYUU站点绑定失败问题" + } + }, + "NtfyMsg": { + "name": "ntfy消息推送", + "description": "支持使用ntfy发送消息通知。", + "labels": "消息通知", + "version": "1.1", + "icon": "Ntfy_A.png", + "author": "lethargicScribe", + "level": 1, + "v2": true, + "history": { + "v1.1": "添加Token认证和用户动作" + } + }, + "GotifyMsg": { + "name": "gotify消息推送", + "description": "支持使用gotify发送消息通知。", + "labels": "消息通知", + "version": "1.1", + "icon": "https://raw.githubusercontent.com/gotify/logo/refs/heads/master/gotify-logo.png", + "author": "lethargicScribe", + "level": 1, + "v2": true + }, + "AppriseMsg": { + "name": "Apprise 消息推送", + "description": "Apprise - 适用于几乎所有平台的推送通知!", + "labels": "消息通知", + "version": "1.0", + "icon": "Ntfy_A.png", + "author": "lethargicScribe", + "level": 1, + "v2": true + }, + "TmdbWallpaper": { + "name": "登录壁纸本地化", + "description": "将MoviePilot的登录壁纸下载到本地。", + "labels": "工具", + "version": "1.4", + "icon": "Macos_Sierra.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.4": "修复Bing壁纸命名问题", + "v1.3": "适配MoviePilot v2.5.3+版本", + "v1.2": "一次性下载多张壁纸", + "v1.1": "修复下载Bing每日壁纸时文件名错乱的问题" + } + }, + "MPServerStatus": { + "name": "MoviePilot服务器监控", + "description": "在仪表板中实时显示MoviePilot公共服务器状态。", + "labels": "仪表板", + "version": "1.2", + "icon": "Duplicati_A.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.2": "优化数量示", + "v1.1": "增加详情界面显示" + } + }, + "CleanInvalidSeed": { + "name": "清理QB无效做种", + "description": "清理已经被站点删除的种子及对应源文件,仅支持QB", + "labels": "Qbittorrent", + "version": "2.2", + "icon": "clean_a.png", + "author": "DzAvril", + "level": 1, + "history": { + "v2.2": "支持仅标记模式", + "v2.1": "1. 修复删除无效做种没有tg通知的问题。2. 检测未工作做种排除已暂停做种", + "v2.0": "修复检测不到无效做种的bug", + "v1.9": "增加自定义需删除做种的tracker的错误信息", + "v1.8": "增加远程命令切换全量通知;修复bug", + "v1.7": "修复因消息内容包含'_'导致telegram API调用失败的问题", + "v1.6": "修复当种子有多个标签时,通过标签过滤不删除种子会失效的问题", + "v1.5": "1. 增加通过分类、标签过滤不删除种子功能;2. 全量通知提供更多信息", + "v1.4": "修复插件功能失效的问题", + "v1.3": "1. 增加远程命令 2. 根据tracker error_message字段进行过滤,避免误删", + "v1.2": "修复配置页空白的问题", + "v1.1": "更新使用说明,以防使用不当误删文件", + "v1.0": "定时清理已经被站点删除的种子及对应源文件" + } + }, + "TrendingShow": { + "name": "流行趋势轮播", + "description": "在仪表板中显示流行趋势海报轮播图。", + "labels": "仪表板", + "version": "1.3", + "icon": "TrendingShow.jpg", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.3": "调整组件大小", + "v1.2": "不同屏幕大小,支持分开设置" + } + }, + "DailyWord": { + "name": "每日一言", + "description": "在仪表板中显示每日一言卡片。", + "labels": "仪表板", + "version": "1.2", + "icon": "Calibre_B.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.2": "修复每日一言图文API接口" + } + }, + "ZvideoHelper": { + "name": "极影视助手", + "description": "极影视功能扩展", + "labels": "媒体库", + "version": "1.6", + "icon": "zvideo.png", + "author": "DzAvril", + "level": 1, + "v2": true, + "history": { + "v1.6": "增加定期更新豆瓣评分", + "v1.5": "适配新版极影视", + "v1.4": "修复请求失败后返回值数量不正确的问题", + "v1.3": "降低对豆瓣接口的请求频率", + "v1.2": "修复无法获取豆瓣评分的问题", + "v1.1": "支持将极影视评分修改为豆瓣评分", + "v1.0": "同步极影视在看/已看状态到豆瓣" + } + }, + "MqttPush": { + "name": "MQTT消息推送", + "description": "支持使用MQTT发送消息通知。", + "labels": "消息通知", + "version": "1.0", + "icon": "Mosquitto_A.png", + "author": "blacklips", + "level": 1 + }, + "DingdingMsg": { + "name": "钉钉机器人", + "description": "支持使用钉钉机器人发送消息通知。", + "labels": "消息通知,钉钉机器人", + "version": "1.12", + "icon": "Dingding_A.png", + "author": "nnlegenda", + "level": 1, + "v2": true + }, + "DynamicWeChat": { + "name": "动态企微可信IP", + "description": "修改企微应用可信IP,支持Srever酱等第三方通知。验证码以?结尾发送到企业微信应用", + "labels": "消息通知", + "version": "1.7.2", + "icon": "Wecom_A.png", + "author": "RamenRa", + "level": 2, + "v2": true, + "history": { + "v1.7.2": "||wan参数细分,修复使用||wan时立即检测一次实际不生效,修复v1第三方备用通知可能无效,调整验证码获取", + "v1.7.1": "允许使用'||wan2'选项及无法使用'立即检测一次'", + "v1.7.0": "使用第三方通知时可IP变动后通知,拟支持多网络出口检查。", + "v1.6.0": "忽略因网络波动导致获取ip错误。自定义的类合并为helper.py。后续核心功能没问题将不再更新", + "v1.5.2": "可以从指定url获取ip,修复不使用cc时cookie失效过快,v1可配置第三方为备用通知,server酱可以将文本发送到server3,二维码给服务号", + "v1.5.1": "修复v2微信通知,可以指定微信通知ID", + "v1.5.0": "支持企微应用通知和第Serve酱等第三方推送。按要求修改插件名称", + "v1.4.1": "完善面板说明", + "v1.4.0": "修复强制更改IP时配置面板延时过长的问题。庆祝v2进入正式版,显示了一个没用的参数" + } + }, + "SyncCookieCloud": { + "name": "同步CookieCloud", + "description": "同步MoviePilot站点Cookie到本地CookieCloud。", + "labels": "站点", + "version": "1.4", + "icon": "Cookiecloud_A.png", + "author": "thsrite", + "level": 1, + "history": { + "v1.4": "调整逻辑,修复问题", + "v1.3": "感谢MidnightShake共享代码(同步时保留MoviePilot不匹配站点的cookie)", + "v1.2": "同步到本地CookieCloud", + "v1.1": "修复CookieCloud覆盖到浏览器", + "v1.0": "同步MoviePilot站点Cookie到CookieCloud" + } + }, + "BangumiColl": { + "name": "Bangumi收藏订阅", + "description": "Bangumi用户收藏添加到订阅", + "labels": "订阅", + "version": "1.5.7", + "icon": "bangumi_b.png", + "author": "Attente", + "level": 1, + "v2": true, + "history": { + "v1.5.7": "适配MoviePilot v2.5.3", + "v1.5.6": "修复远程命令名称, 完善远程命令回执", + "v1.5.5": "添加剧集组(需V2.3.8+), 新增远程命令", + "v1.5.4": "fix: wikrin/MoviePilot-Plugins/issues/2", + "v1.5.3": "增加多语言标题匹配, 去除未实现设置项", + "v1.5.2": "修复定时任务未正确注册的问题", + "v1.5.1": "修复季度信息未传递的问题. 新增站点列表同步删除", + "v1.5": "修复总集数会同步TMDB变动的问题,增加开关选项" + } + }, + "IyuuMsg": { + "name": "IYUU消息推送", + "description": "支持使用IYUU发送消息通知。", + "labels": "消息通知,IYUU", + "version": "1.3", + "icon": "Iyuu_A.png", + "author": "jxxghp", + "level": 1, + "v2": true, + "history": { + "v1.3": "消息限流发送,以缓解IYUU服务器压力" + } + } } diff --git a/package.v2.json b/package.v2.json index 82fcc64..93c5cea 100644 --- a/package.v2.json +++ b/package.v2.json @@ -1,423 +1,453 @@ { - "SiteStatistic": { - "name": "站点数据统计", - "description": "站点统计数据图表。", - "labels": "站点,仪表板", - "version": "1.6", - "icon": "statistic.png", - "author": "lightolly,jxxghp", - "level": 2, - "history": { - "v1.6": "优化了站点数据获取失败时的回退逻辑", - "v1.5": "修复了发送增量通知失败等一些问题", - "v1.4.1": "支持数据刷新时发送消息通知", - "v1.3": "远程刷新命令移植到主程序", - "v1.2": "继续修复增量数据统计问题", - "v1.1": "修复增量数据统计问题", - "v1.0": "MoviePilot V2 版本站点数据统计插件" - } - }, - "BrushFlow": { - "name": "站点刷流", - "description": "自动托管刷流,将会提高对应站点的访问频率。", - "labels": "刷流,仪表板", - "version": "4.3.1", - "icon": "brush.jpg", - "author": "jxxghp,InfinityPacer", - "level": 2, - "history": { - "v4.3.1": "修复了一些细节问题", - "v4.3": "支持带宽采样并计算平均值,以优化刷流效率", - "v4.2": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v4.1": "支持通过CRON表达式配置开启时间,固定10分钟为执行周期", - "v4.0": "站点独立配置项支持配置NexusPHP 站点自动跳过下载提示页", - "v3.9": "MoviePilot V2 版本站点刷流插件" - } - }, - "AutoSignIn": { - "name": "站点自动签到", - "description": "自动模拟登录、签到站点。", - "labels": "站点", - "version": "2.6", - "icon": "signin.png", - "author": "thsrite", - "level": 2, - "history": { - "v2.6": "感谢madrays佬提供的UI!", - "v2.5.4": "增加保号风险提示", - "v2.5.3": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.5.2": "修复HDArea签到", - "v2.5.1": "修复空签到失败问题", - "v2.5": "MoviePilot V2 版本站点自动签到插件" - } - }, - "DownloadSiteTag": { - "name": "下载任务分类与标签", - "description": "自动给下载任务分类与打站点标签、剧集名称标签", - "labels": "下载管理", - "version": "2.2", - "icon": "Youtube-dl_B.png", - "author": "叮叮当", - "level": 1, - "history": { - "v2.2": "MoviePilot V2 版本下载任务分类与标签插件" - } - }, - "MediaServerRefresh": { - "name": "媒体库服务器刷新", - "description": "入库后自动刷新Emby/Jellyfin/Plex服务器海报墙。", - "labels": "媒体库", - "version": "1.3.2", - "icon": "refresh2.png", - "author": "jxxghp", - "level": 1, - "history": { - "v1.3.2": "适配飞牛媒体库", - "v1.3.1": "修复兼容性问题", - "v1.3": "MoviePilot V2 版本媒体库服务器刷新插件" - } - }, - "MediaServerMsg": { - "name": "媒体库服务器通知", - "description": "发送Emby/Jellyfin/Plex服务器的播放、入库等通知消息。", - "labels": "消息通知,媒体库", - "version": "1.6", - "icon": "mediaplay.png", - "author": "jxxghp", - "level": 1, - "history": { - "v1.6": "查询剧集图片兼容没有季集信息的情况", - "v1.5": "支持独立控制媒体服务器通知", - "v1.4": "MoviePilot V2 版本媒体库服务器通知插件" - } - }, - "ChatGPT": { - "name": "ChatGPT", - "description": "消息交互支持与ChatGPT对话。", - "labels": "消息通知,识别", - "version": "2.1.6", - "icon": "Chatgpt_A.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.1.6": "支持自定义辅助识别提示词", - "v2.1.5": "兼容一些模型返回json数据信息用markdown语法包裹的情况", - "v2.1.4": "不处理http链接", - "v2.1.3": "修复通知异常", - "v2.1.2": "支持传入多个api key", - "v2.1.1": "兼容/v1后仍有路径的接口", - "v2.1.0": "优化辅助识别提示词", - "v2.0.1": "修复辅助识别", - "v2.0": "适配MoviePilot V2 版本,采用链式事件机制" - } - }, - "TorrentTransfer": { - "name": "自动转移做种", - "description": "定期转移下载器中的做种任务到另一个下载器。", - "labels": "做种", - "version": "1.10.2", - "icon": "seed.png", - "author": "jxxghp", - "level": 2, - "history": { - "v1.10.2": "增加保留原标签和原分类的选项", - "v1.10.1": "优化“立即运行一次”按钮位置", - "v1.10": "支持跳过校验(仅支持 qBittorrent)", - "v1.9": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v1.8": "支持qbittorrent 5", - "v1.7": "MoviePilot V2 版本自动转移做种插件", - "v1.7.1": "修复兼容性问题" - } - }, - "RssSubscribe": { - "name": "自定义订阅", - "description": "定时刷新RSS报文,识别内容后添加订阅或直接下载。", - "labels": "订阅", - "version": "2.1", - "icon": "rss.png", - "author": "jxxghp", - "level": 2, - "history": { - "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.0": "兼容MoviePilot V2 版本" - } - }, - "FFmpegThumb": { - "name": "FFmpeg缩略图", - "description": "TheMovieDb没有背景图片时使用FFmpeg截取视频文件缩略图", - "labels": "刮削", - "version": "2.1", - "icon": "ffmpeg.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.0": "兼容MoviePilot V2 版本" - } - }, - "LibraryScraper": { - "name": "媒体库刮削", - "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", - "labels": "刮削", - "version": "2.1.1", - "icon": "scraper.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.1.1": "调整目录计算方法,以支持更多重命名格式", - "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.0": "兼容MoviePilot V2 版本", - "v1.5": "修复未获取fanart图片的问题", - "v1.4.1": "修复nfo文件读取失败时任务中断问题" - } - }, - "PersonMeta": { - "name": "演职人员刮削", - "description": "刮削演职人员图片以及中文名称。", - "labels": "媒体库,刮削", - "version": "2.1", - "icon": "actor.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.0": "兼容MoviePilot V2 版本", - "v1.4": "人物图片调整为优先从TMDB获取,避免douban图片CDN加载过慢的问题", - "v1.3": "修复v1.8.5版本后刮削报错问题" - } - }, - "SpeedLimiter": { - "name": "播放限速", - "description": "外网播放媒体库视频时,自动对下载器进行限速。", - "labels": "网络", - "version": "2.1", - "icon": "Librespeed_A.png", - "author": "Shurelol", - "level": 1, - "history": { - "v2.1": "修复表单参数", - "v2.0": "兼容MoviePilot V2 版本", - "v1.2": "增加不限速路径配置,以应对网盘直链播放的情况" - } - }, - "AutoClean": { - "name": "定时清理媒体库", - "description": "定时清理用户下载的种子、源文件、媒体库文件。", - "labels": "媒体库", - "version": "2.1", - "icon": "clean.png", - "author": "thsrite", - "level": 2, - "history": { - "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.0": "兼容MoviePilot V2 版本" - } - }, - "TorrentRemover": { - "name": "自动删种", - "description": "自动删除下载器中的下载任务。", - "labels": "做种", - "version": "2.2", - "icon": "delete.jpg", - "author": "jxxghp", - "level": 2, - "history": { - "v2.2": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.1.1": "修复兼容MoviePilot V2 版本", - "v2.0": "兼容MoviePilot V2 版本" - } - }, - "IYUUAutoSeed": { - "name": "IYUU自动辅种", - "description": "基于IYUU官方Api实现自动辅种。", - "labels": "做种,IYUU", - "version": "2.14", - "icon": "IYUU.png", - "author": "jxxghp,CKun", - "level": 2, - "history": { - "v2.14": "修复馒头不能辅种的问题", - "v2.13": "开启跳过校验后需手动开启自动开始", - "v2.12": "增加qb下载器分类复用配置", - "v2.11": "修复qb跳过校验不自动开始的问题", - "v2.10": "Revert 辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", - "v2.9": "修复开启跳过校验后,Tr下载器不自动开始的问题", - "v2.8": "为配置主辅分离时,不走辅种下载器检查", - "v2.7": "增加主辅分离配置,单独指定辅种下载器", - "v2.6": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.5": "修复qb辅种结束后自动开始暂停的种子", - "v2.4": "辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", - "v2.3": "支持qbittorrent 5", - "v2.2": "修复种子校验服务未生效", - "v2.1": "调整IYUU最新域名", - "v2.0": "兼容MoviePilot V2 版本" - } - }, - "CrossSeed": { - "name": "青蛙辅种助手", - "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。", - "labels": "做种", - "version": "3.0.1", - "icon": "qingwa.png", - "author": "233@qingwa", - "level": 2, - "history": { - "v3.0.1": "遗漏了一个私有属性", - "v3.0": "兼容MoviePilot V2 版本" - } - }, - "QbCommand": { - "name": "QB远程操作", - "description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。", - "labels": "下载管理,Qbittorrent", - "version": "2.1", - "icon": "Qbittorrent_A.png", - "author": "DzAvril", - "level": 1, - "history": { - "v2.1": "支持qbittorrent 5", - "v2.0": "适配MoviePilot V2 版本" - } - }, - "HistoryToV2": { - "name": "历史记录迁移", - "description": "将MoviePilot V1版本的整理历史记录迁移至V2版本。", - "labels": "整理,历史记录", - "version": "1.1", - "icon": "Moviepilot_A.png", - "author": "jxxghp", - "level": 1, - "history": { - "v1.1": "修复启动提示信息" - } - }, - "SyncCookieCloud": { - "name": "同步CookieCloud", - "description": "同步MoviePilot站点Cookie到本地CookieCloud。", - "labels": "站点", - "version": "2.2", - "icon": "Cookiecloud_A.png", - "author": "thsrite", - "level": 1, - "history": { - "v2.2": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.1": "兼容MoviePilot V2" - } - }, - "ChineseSubFinder": { - "name": "ChineseSubFinder", - "description": "整理入库时通知ChineseSubFinder下载字幕。", - "labels": "字幕", - "version": "2.0", - "icon": "chinesesubfinder.png", - "author": "jxxghp", - "level": 1, - "history": { - "v2.0": "兼容MoviePilot V2" - } - }, - "CleanInvalidSeed": { - "name": "清理QB无效做种", - "description": "清理已经被站点删除的种子及对应源文件,仅支持QB", - "labels": "Qbittorrent", - "version": "2.0", - "icon": "clean_a.png", - "author": "DzAvril", - "level": 1, - "history": { - "v2.0": "适配 MoviePilot V2" - } - }, - "PlayletCategory": { - "name": "短剧自动分类", - "description": "网络短剧自动整理到独立的分类目录。", - "labels": "文件整理", - "version": "2.1", - "icon": "Amule_A.png", - "author": "jxxghp,longqiuyu", - "level": 1, - "history": { - "v2.1": "兼容MoviePilot V2", - "v2.0": "适配新的目录结构变化,短剧分类名称调整为配置目录路径,升级后需要重新调整设置后才能使用。" - } - }, - "MoviePilotUpdateNotify": { - "name": "MoviePilot更新推送", - "description": "MoviePilot推送release更新通知、自动重启。", - "labels": "消息通知,自动更新", - "version": "2.1", - "icon": "Moviepilot_A.png", - "author": "thsrite", - "level": 1, - "history": { - "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", - "v2.0": "兼容MoviePilot V2" - } - }, - "DoubanRank": { - "name": "豆瓣榜单订阅", - "description": "监控豆瓣热门榜单,自动添加订阅。", - "labels": "订阅", - "version": "2.0.0", - "icon": "movie.jpg", - "author": "jxxghp", - "level": 2, - "history": { - "v2.0.0": "优化cron表达式输入" - } - }, - "DoubanSync": { - "name": "豆瓣想看", - "description": "同步豆瓣想看数据,自动添加订阅。", - "labels": "订阅", - "version": "2.1.0", - "icon": "douban.png", - "author": "jxxghp,dwhmofly", - "level": 2, - "history": { - "v2.1.0": "新增配置项-搜索下载,开启后会优先搜索站点资源进行下载,下载不到才会添加订阅", - "v2.0.1": "支持将豆瓣ID转换为MoviePilot中已有用户(在用户个人信息中绑定豆瓣ID),需要MoviePilot v2.2.6+", - "v2.0.0": "优化cron表达式输入" - } - }, - "TvdbDiscover": { - "name": "TheTVDB探索", - "description": "让探索支持TheTVDB的数据浏览。", - "labels": "探索", - "version": "1.1", - "icon": "TheTVDB_A.png", - "author": "jxxghp", - "level": 1, - "history": { - "v1.1": "需要MoviePilot v2.2.7-1+ 版本,否则无法显示图片" - } - }, - "SubscribeClear": { - "name": "订阅种子清理", - "description": "删除指定下载信息。", - "labels": "下载管理", - "version": "1.0", - "icon": "Moviepilot_A.jpg", - "author": "k0ala", - "level": 1, - "history": { - "v1.0": "支持清理QB中已下载的订阅文件" - } - }, - "ToBypassTrackers": { - "name": "绕过Trackers", - "description": "提供tracker服务器IP地址列表,帮助IPv6连接绕过OpenClash", - "labels": "工具", - "version": "1.4", - "icon": "Clash_A.png", - "author": "wumode", - "level": 2, - "history": { - "v1.0": "支持自定义Trackers", - "v1.1": "更新列表后发送通知", - "v1.2": "修复Trackers加载错误", - "v1.3": "新增一些Trackers", - "v1.4": "异步查询DNS" - } + "SiteStatistic": { + "name": "站点数据统计", + "description": "站点统计数据图表。", + "labels": "站点,仪表板", + "version": "1.7.1", + "icon": "statistic.png", + "author": "lightolly,jxxghp", + "level": 2, + "history": { + "v1.7.1": "优化内存占用", + "v1.6": "优化了站点数据获取失败时的回退逻辑", + "v1.5": "修复了发送增量通知失败等一些问题", + "v1.4.1": "支持数据刷新时发送消息通知", + "v1.3": "远程刷新命令移植到主程序", + "v1.2": "继续修复增量数据统计问题", + "v1.1": "修复增量数据统计问题", + "v1.0": "MoviePilot V2 版本站点数据统计插件" } + }, + "BrushFlow": { + "name": "站点刷流", + "description": "自动托管刷流,将会提高对应站点的访问频率。", + "labels": "刷流,仪表板", + "version": "4.3.3", + "icon": "brush.jpg", + "author": "jxxghp,InfinityPacer", + "level": 2, + "history": { + "v4.3.2": "增加'删除促销结束的未完成下载'功能", + "v4.3.1": "修复了一些细节问题", + "v4.3": "支持带宽采样并计算平均值,以优化刷流效率", + "v4.2": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v4.1": "支持通过CRON表达式配置开启时间,固定10分钟为执行周期", + "v4.0": "站点独立配置项支持配置NexusPHP 站点自动跳过下载提示页", + "v3.9": "MoviePilot V2 版本站点刷流插件" + } + }, + "AutoSignIn": { + "name": "站点自动签到", + "description": "自动模拟登录、签到站点。", + "labels": "站点", + "version": "2.6", + "icon": "signin.png", + "author": "thsrite", + "level": 2, + "history": { + "v2.6": "感谢madrays佬提供的UI!", + "v2.5.4": "增加保号风险提示", + "v2.5.3": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.5.2": "修复HDArea签到", + "v2.5.1": "修复空签到失败问题", + "v2.5": "MoviePilot V2 版本站点自动签到插件" + } + }, + "DownloadSiteTag": { + "name": "下载任务分类与标签", + "description": "自动给下载任务分类与打站点标签、剧集名称标签", + "labels": "下载管理", + "version": "2.2", + "icon": "Youtube-dl_B.png", + "author": "叮叮当", + "level": 1, + "history": { + "v2.2": "MoviePilot V2 版本下载任务分类与标签插件" + } + }, + "MediaServerRefresh": { + "name": "媒体库服务器刷新", + "description": "入库后自动刷新Emby/Jellyfin/Plex服务器海报墙。", + "labels": "媒体库", + "version": "1.3.2", + "icon": "refresh2.png", + "author": "jxxghp", + "level": 1, + "history": { + "v1.3.2": "适配飞牛媒体库", + "v1.3.1": "修复兼容性问题", + "v1.3": "MoviePilot V2 版本媒体库服务器刷新插件" + } + }, + "MediaServerMsg": { + "name": "媒体库服务器通知", + "description": "发送Emby/Jellyfin/Plex服务器的播放、入库等通知消息。", + "labels": "消息通知,媒体库", + "version": "1.5", + "icon": "mediaplay.png", + "author": "jxxghp", + "level": 1, + "history": { + "v1.5": "支持独立控制媒体服务器通知", + "v1.4": "MoviePilot V2 版本媒体库服务器通知插件" + } + }, + "ChatGPT": { + "name": "ChatGPT", + "description": "消息交互支持与ChatGPT对话。", + "labels": "消息通知,识别", + "version": "2.1.6", + "icon": "Chatgpt_A.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.1.6": "支持自定义辅助识别提示词", + "v2.1.5": "兼容一些模型返回json数据信息用markdown语法包裹的情况", + "v2.1.4": "不处理http链接", + "v2.1.3": "修复通知异常", + "v2.1.2": "支持传入多个api key", + "v2.1.1": "兼容/v1后仍有路径的接口", + "v2.1.0": "优化辅助识别提示词", + "v2.0.1": "修复辅助识别", + "v2.0": "适配MoviePilot V2 版本,采用链式事件机制" + } + }, + "TorrentTransfer": { + "name": "自动转移做种", + "description": "定期转移下载器中的做种任务到另一个下载器。", + "labels": "做种", + "version": "1.10.2", + "icon": "seed.png", + "author": "jxxghp", + "level": 2, + "history": { + "v1.10.2": "增加保留原标签和原分类的选项", + "v1.10.1": "优化“立即运行一次”按钮位置", + "v1.10": "支持跳过校验(仅支持 qBittorrent)", + "v1.9": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v1.8": "支持qbittorrent 5", + "v1.7": "MoviePilot V2 版本自动转移做种插件", + "v1.7.1": "修复兼容性问题" + } + }, + "RssSubscribe": { + "name": "自定义订阅", + "description": "定时刷新RSS报文,识别内容后添加订阅或直接下载。", + "labels": "订阅", + "version": "2.1", + "icon": "rss.png", + "author": "jxxghp", + "level": 2, + "history": { + "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.0": "兼容MoviePilot V2 版本" + } + }, + "FFmpegThumb": { + "name": "FFmpeg缩略图", + "description": "TheMovieDb没有背景图片时使用FFmpeg截取视频文件缩略图", + "labels": "刮削", + "version": "2.1", + "icon": "ffmpeg.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.0": "兼容MoviePilot V2 版本" + } + }, + "LibraryScraper": { + "name": "媒体库刮削", + "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", + "labels": "刮削", + "version": "2.1.1", + "icon": "scraper.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.1.1": "调整目录计算方法,以支持更多重命名格式", + "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.0": "兼容MoviePilot V2 版本", + "v1.5": "修复未获取fanart图片的问题", + "v1.4.1": "修复nfo文件读取失败时任务中断问题" + } + }, + "PersonMeta": { + "name": "演职人员刮削", + "description": "刮削演职人员图片以及中文名称。", + "labels": "媒体库,刮削", + "version": "2.1", + "icon": "actor.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.0": "兼容MoviePilot V2 版本", + "v1.4": "人物图片调整为优先从TMDB获取,避免douban图片CDN加载过慢的问题", + "v1.3": "修复v1.8.5版本后刮削报错问题" + } + }, + "SpeedLimiter": { + "name": "播放限速", + "description": "外网播放媒体库视频时,自动对下载器进行限速。", + "labels": "网络", + "version": "2.1", + "icon": "Librespeed_A.png", + "author": "Shurelol", + "level": 1, + "history": { + "v2.1": "修复表单参数", + "v2.0": "兼容MoviePilot V2 版本", + "v1.2": "增加不限速路径配置,以应对网盘直链播放的情况" + } + }, + "AutoClean": { + "name": "定时清理媒体库", + "description": "定时清理用户下载的种子、源文件、媒体库文件。", + "labels": "媒体库", + "version": "2.1", + "icon": "clean.png", + "author": "thsrite", + "level": 2, + "history": { + "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.0": "兼容MoviePilot V2 版本" + } + }, + "TorrentRemover": { + "name": "自动删种", + "description": "自动删除下载器中的下载任务。", + "labels": "做种", + "version": "2.2", + "icon": "delete.jpg", + "author": "jxxghp", + "level": 2, + "history": { + "v2.2": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.1.1": "修复兼容MoviePilot V2 版本", + "v2.0": "兼容MoviePilot V2 版本" + } + }, + "IYUUAutoSeed": { + "name": "IYUU自动辅种", + "description": "基于IYUU官方Api实现自动辅种。", + "labels": "做种,IYUU", + "version": "2.14", + "icon": "IYUU.png", + "author": "jxxghp,CKun", + "level": 2, + "history": { + "v2.14": "修复馒头不能辅种的问题", + "v2.13": "开启跳过校验后需手动开启自动开始", + "v2.12": "增加qb下载器分类复用配置", + "v2.11": "修复qb跳过校验不自动开始的问题", + "v2.10": "Revert 辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", + "v2.9": "修复开启跳过校验后,Tr下载器不自动开始的问题", + "v2.8": "为配置主辅分离时,不走辅种下载器检查", + "v2.7": "增加主辅分离配置,单独指定辅种下载器", + "v2.6": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.5": "修复qb辅种结束后自动开始暂停的种子", + "v2.4": "辅种结束后,一起开始所有辅种后暂停的种子(排除了出错的种子)", + "v2.3": "支持qbittorrent 5", + "v2.2": "修复种子校验服务未生效", + "v2.1": "调整IYUU最新域名", + "v2.0": "兼容MoviePilot V2 版本" + } + }, + "CrossSeed": { + "name": "青蛙辅种助手", + "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。", + "labels": "做种", + "version": "3.0.1", + "icon": "qingwa.png", + "author": "233@qingwa", + "level": 2, + "history": { + "v3.0.1": "遗漏了一个私有属性", + "v3.0": "兼容MoviePilot V2 版本" + } + }, + "QbCommand": { + "name": "QB远程操作", + "description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。", + "labels": "下载管理,Qbittorrent", + "version": "2.1", + "icon": "Qbittorrent_A.png", + "author": "DzAvril", + "level": 1, + "history": { + "v2.1": "支持qbittorrent 5", + "v2.0": "适配MoviePilot V2 版本" + } + }, + "HistoryToV2": { + "name": "历史记录迁移", + "description": "将MoviePilot V1版本的整理历史记录迁移至V2版本。", + "labels": "整理,历史记录", + "version": "1.1", + "icon": "Moviepilot_A.png", + "author": "jxxghp", + "level": 1, + "history": { + "v1.1": "修复启动提示信息" + } + }, + "SyncCookieCloud": { + "name": "同步CookieCloud", + "description": "同步MoviePilot站点Cookie到本地CookieCloud。", + "labels": "站点", + "version": "2.2", + "icon": "Cookiecloud_A.png", + "author": "thsrite", + "level": 1, + "history": { + "v2.2": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.1": "兼容MoviePilot V2" + } + }, + "ChineseSubFinder": { + "name": "ChineseSubFinder", + "description": "整理入库时通知ChineseSubFinder下载字幕。", + "labels": "字幕", + "version": "2.0", + "icon": "chinesesubfinder.png", + "author": "jxxghp", + "level": 1, + "history": { + "v2.0": "兼容MoviePilot V2" + } + }, + "CleanInvalidSeed": { + "name": "清理QB无效做种", + "description": "清理已经被站点删除的种子及对应源文件,仅支持QB", + "labels": "Qbittorrent", + "version": "2.0", + "icon": "clean_a.png", + "author": "DzAvril", + "level": 1, + "history": { + "v2.0": "适配 MoviePilot V2" + } + }, + "PlayletCategory": { + "name": "短剧自动分类", + "description": "网络短剧自动整理到独立的分类目录。", + "labels": "文件整理", + "version": "2.1", + "icon": "Amule_A.png", + "author": "jxxghp,longqiuyu", + "level": 1, + "history": { + "v2.1": "兼容MoviePilot V2", + "v2.0": "适配新的目录结构变化,短剧分类名称调整为配置目录路径,升级后需要重新调整设置后才能使用。" + } + }, + "MoviePilotUpdateNotify": { + "name": "MoviePilot更新推送", + "description": "MoviePilot推送release更新通知、自动重启。", + "labels": "消息通知,自动更新", + "version": "2.2", + "icon": "Moviepilot_A.png", + "author": "thsrite", + "level": 1, + "history": { + "v2.2": "支持 MoviePilot v2.5.0+", + "v2.1": "优化执行周期输入,需要MoviePilot v2.2.1+", + "v2.0": "兼容MoviePilot V2" + } + }, + "DoubanRank": { + "name": "豆瓣榜单订阅", + "description": "监控豆瓣热门榜单,自动添加订阅。", + "labels": "订阅", + "version": "2.0.0", + "icon": "movie.jpg", + "author": "jxxghp", + "level": 2, + "history": { + "v2.0.0": "优化cron表达式输入" + } + }, + "DoubanSync": { + "name": "豆瓣想看", + "description": "同步豆瓣想看数据,自动添加订阅。", + "labels": "订阅", + "version": "2.1.0", + "icon": "douban.png", + "author": "jxxghp,dwhmofly", + "level": 2, + "history": { + "v2.1.0": "新增配置项-搜索下载,开启后会优先搜索站点资源进行下载,下载不到才会添加订阅", + "v2.0.1": "支持将豆瓣ID转换为MoviePilot中已有用户(在用户个人信息中绑定豆瓣ID),需要MoviePilot v2.2.6+", + "v2.0.0": "优化cron表达式输入" + } + }, + "TvdbDiscover": { + "name": "TheTVDB探索", + "description": "让探索支持TheTVDB的数据浏览。", + "labels": "探索", + "version": "1.1", + "icon": "TheTVDB_A.png", + "author": "jxxghp", + "level": 1, + "history": { + "v1.1": "需要MoviePilot v2.2.7-1+ 版本,否则无法显示图片" + } + }, + "SubscribeClear": { + "name": "订阅种子清理", + "description": "删除指定下载信息。", + "labels": "下载管理", + "version": "1.0", + "icon": "Moviepilot_A.jpg", + "author": "k0ala", + "level": 1, + "history": { + "v1.0": "支持清理QB中已下载的订阅文件" + } + }, + "ToBypassTrackers": { + "name": "绕过Trackers", + "description": "提供tracker服务器IP地址列表,帮助IPv6连接绕过OpenClash", + "labels": "工具", + "version": "1.4", + "icon": "Clash_A.png", + "author": "wumode", + "level": 2, + "history": { + "v1.0": "支持自定义Trackers", + "v1.1": "更新列表后发送通知", + "v1.2": "修复Trackers加载错误", + "v1.3": "新增一些Trackers", + "v1.4": "异步查询DNS" + } + }, + "ImdbSource": { + "name": "IMDb源", + "description": "让探索支持IMDb数据源。", + "labels": "探索", + "version": "1.3.1", + "icon": "IMDb_IOS-OSX_App.png", + "author": "wumode", + "level": 1, + "history": { + "v1.3.1": "修复按日期排序错误", + "v1.3": "优化网络连接", + "v1.2": "推荐热门纪录片", + "v1.1": "推荐支持IMDB数据源; 优化海报尺寸,减少卡顿", + "v1.0": "探索支持IMDb数据源" + } + }, + "ClashRuleProvider": { + "name": "Clash Rule Provider", + "description": "随时为Clash添加一些额外的规则。", + "labels": "工具", + "version": "0.1.0", + "icon": "Mihomo_Meta_A.png", + "author": "wumode", + "level": 1, + "history": { + "v0.1.0": "新增ClashRuleProvider" + } + } } diff --git a/plugins.v2/autoclean/__init__.py b/plugins.v2/autoclean/__init__.py index 26a83dc..70a84e5 100644 --- a/plugins.v2/autoclean/__init__.py +++ b/plugins.v2/autoclean/__init__.py @@ -49,8 +49,6 @@ class AutoClean(_PluginBase): _cleantype = None _cleandate = None _cleanuser = None - _downloadhis = None - _transferhis = None # 定时器 _scheduler: Optional[BackgroundScheduler] = None @@ -70,9 +68,6 @@ class AutoClean(_PluginBase): # 加载模块 if self._enabled: - self._downloadhis = DownloadHistoryOper() - self._transferhis = TransferHistoryOper() - if self._onlyonce: # 定时服务 self._scheduler = BackgroundScheduler(timezone=settings.TZ) @@ -115,9 +110,10 @@ class AutoClean(_PluginBase): return # 查询用户清理日期之前的下载历史,不填默认清理全部用户的下载 + _downloadhis = DownloadHistoryOper() if not self._cleanuser: clean_date = self.__get_clean_date() - downloadhis_list = self._downloadhis.list_by_user_date(date=clean_date) + downloadhis_list = _downloadhis.list_by_user_date(date=clean_date) logger.info(f'获取到日期 {clean_date} 之前的下载历史 {len(downloadhis_list)} 条') self.__clean_history(date=clean_date, clean_type=self._cleantype, downloadhis_list=downloadhis_list) @@ -130,8 +126,8 @@ class AutoClean(_PluginBase): # 1.3.7版本及之前处理多位用户 if str(self._cleanuser).count(','): for username in str(self._cleanuser).split(","): - downloadhis_list = self._downloadhis.list_by_user_date(date=clean_date, - username=username) + downloadhis_list = _downloadhis.list_by_user_date(date=clean_date, + username=username) logger.info( f'获取到用户 {username} 日期 {clean_date} 之前的下载历史 {len(downloadhis_list)} 条') self.__clean_history(date=clean_date, clean_type=self._cleantype, downloadhis_list=downloadhis_list) @@ -152,8 +148,8 @@ class AutoClean(_PluginBase): # 转strftime clean_date = self.__get_clean_date(clean_date) logger.info(f'{username} 使用 {clean_type} 清理方式,清理 {clean_date} 之前的下载历史') - downloadhis_list = self._downloadhis.list_by_user_date(date=clean_date, - username=username) + downloadhis_list = _downloadhis.list_by_user_date(date=clean_date, + username=username) logger.info( f'获取到用户 {username} 日期 {clean_date} 之前的下载历史 {len(downloadhis_list)} 条') self.__clean_history(date=clean_date, clean_type=clean_type, @@ -168,6 +164,7 @@ class AutoClean(_PluginBase): return # 读取历史记录 + _transferhis = TransferHistoryOper() pulgin_history = self.get_data('history') or [] # 创建一个字典来保存分组结果 @@ -197,7 +194,7 @@ class AutoClean(_PluginBase): logger.debug(f'下载历史 {downloadhis.id} {downloadhis.title} 未获取到download_hash,跳过处理') continue # 根据hash获取转移记录 - transferhis_list = self._transferhis.list_by_hash(download_hash=downloadhis.download_hash) + transferhis_list = _transferhis.list_by_hash(download_hash=downloadhis.download_hash) if not transferhis_list: logger.warn(f"下载历史 {downloadhis.download_hash} 未查询到转移记录,跳过处理") continue @@ -208,7 +205,7 @@ class AutoClean(_PluginBase): dest_fileitem = schemas.FileItem(**history.dest_fileitem) StorageChain().delete_file(dest_fileitem) # 删除记录 - self._transferhis.delete(history.id) + _transferhis.delete(history.id) # 删除源文件 if clean_type in ["src", "all"]: src_fileitem = schemas.FileItem(**history.src_fileitem) diff --git a/plugins.v2/autosignin/__init__.py b/plugins.v2/autosignin/__init__.py index fe15ac8..0661da8 100644 --- a/plugins.v2/autosignin/__init__.py +++ b/plugins.v2/autosignin/__init__.py @@ -12,9 +12,8 @@ from apscheduler.triggers.cron import CronTrigger from ruamel.yaml import CommentedMap from app import schemas -from app.chain.site import SiteChain from app.core.config import settings -from app.core.event import EventManager, eventmanager, Event +from app.core.event import eventmanager, Event from app.db.site_oper import SiteOper from app.helper.browser import PlaywrightHelper from app.helper.cloudflare import under_challenge @@ -49,12 +48,6 @@ class AutoSignIn(_PluginBase): # 可使用的用户级别 auth_level = 2 - # 私有属性 - sites: SitesHelper = None - siteoper: SiteOper = None - sitechain: SiteChain = None - # 事件管理器 - event: EventManager = None # 定时器 _scheduler: Optional[BackgroundScheduler] = None # 加载的模块 @@ -75,10 +68,6 @@ class AutoSignIn(_PluginBase): _auto_cf: int = 0 def init_plugin(self, config: dict = None): - self.sites = SitesHelper() - self.siteoper = SiteOper() - self.event = EventManager() - self.sitechain = SiteChain() # 停止现有任务 self.stop_service() @@ -97,8 +86,8 @@ class AutoSignIn(_PluginBase): self._clean = config.get("clean") # 过滤掉已删除的站点 - all_sites = [site.id for site in self.siteoper.list_order_by_pri()] + [site.get("id") for site in - self.__custom_sites()] + all_sites = [site.id for site in SiteOper().list_order_by_pri()] + [site.get("id") for site in + self.__custom_sites()] self._sign_sites = [site_id for site_id in all_sites if site_id in self._sign_sites] self._login_sites = [site_id for site_id in all_sites if site_id in self._login_sites] # 保存配置 @@ -272,7 +261,7 @@ class AutoSignIn(_PluginBase): customSites = self.__custom_sites() site_options = ([{"title": site.name, "value": site.id} - for site in self.siteoper.list_order_by_pri()] + for site in SiteOper().list_order_by_pri()] + [{"title": site.get("name"), "value": site.get("id")} for site in customSites]) return [ @@ -565,7 +554,7 @@ class AutoSignIn(_PluginBase): sites_info = {} # 记录站点信息 # 获取站点信息 - site_indexers = self.sites.get_indexers() + site_indexers = SitesHelper().get_indexers() for site in site_indexers: if not site.get("public"): sites_info[site.get("id")] = site.get("name") @@ -734,8 +723,8 @@ class AutoSignIn(_PluginBase): # 按日期排序,最新的在前面 try: records.sort(key=lambda x: x.get("day_obj", datetime.now().date()), reverse=True) - except: - pass # 排序失败时跳过 + except Exception as e: + logger.debug(f"排序失败: {str(e)}") # 获取最新的状态作为站点概要 latest_status = records[0].get("status", "未知状态") @@ -770,8 +759,8 @@ class AutoSignIn(_PluginBase): # 按日期排序,最新的在前面 try: records.sort(key=lambda x: x.get("day_obj", datetime.now().date()), reverse=True) - except: - pass # 排序失败时跳过 + except Exception as e: + logger.debug(f"排序失败: {str(e)}") # 获取最新的状态作为站点概要 latest_status = records[0].get("status", "未知状态") @@ -1142,7 +1131,8 @@ class AutoSignIn(_PluginBase): } ] - def _create_expansion_panel(self, site_name, records, status_color, status_icon, latest_status): + @staticmethod + def _create_expansion_panel(site_name, records, status_color, status_icon, latest_status): """创建站点折叠面板""" # 生成站点图标(使用站点名的首字母) site_initial = site_name[0].upper() if site_name else "?" @@ -1322,7 +1312,7 @@ class AutoSignIn(_PluginBase): today_history = self.get_data(key=type_str + "-" + today) # 查询所有站点 - all_sites = [site for site in self.sites.get_indexers() if not site.get("public")] + self.__custom_sites() + all_sites = [site for site in SitesHelper().get_indexers() if not site.get("public")] + self.__custom_sites() # 过滤掉没有选中的站点 if do_sites: do_sites = [site for site in all_sites if site.get("id") in do_sites] @@ -1402,7 +1392,8 @@ class AutoSignIn(_PluginBase): # 失败|错误 failed_msg = [] - sites = {site.get('name'): site.get("id") for site in self.sites.get_indexers() if not site.get("public")} + sites = {site.get('name'): site.get("id") for site in SitesHelper().get_indexers() if + not site.get("public")} for s in status: site_name = s[0] site_id = None @@ -1501,7 +1492,7 @@ class AutoSignIn(_PluginBase): if apikey != settings.API_TOKEN: return schemas.Response(success=False, message="API密钥错误") domain = StringUtils.get_url_domain(url) - site_info = self.sites.get_indexer(domain) + site_info = SitesHelper().get_indexer(domain) if not site_info: return schemas.Response( success=True, @@ -1533,9 +1524,9 @@ class AutoSignIn(_PluginBase): seconds = (datetime.now() - start_time).seconds domain = StringUtils.get_url_domain(site_info.get('url')) if state: - self.siteoper.success(domain=domain, seconds=seconds) + SiteOper().success(domain=domain, seconds=seconds) else: - self.siteoper.fail(domain) + SiteOper().fail(domain) return site_info.get("name"), message @staticmethod @@ -1635,9 +1626,9 @@ class AutoSignIn(_PluginBase): seconds = (datetime.now() - start_time).seconds domain = StringUtils.get_url_domain(site_info.get('url')) if state: - self.siteoper.success(domain=domain, seconds=seconds) + SiteOper().success(domain=domain, seconds=seconds) else: - self.siteoper.fail(domain) + SiteOper().fail(domain) return site_info.get("name"), message @staticmethod diff --git a/plugins.v2/brushflow/__init__.py b/plugins.v2/brushflow/__init__.py index 4e692c9..8bb0892 100644 --- a/plugins.v2/brushflow/__init__.py +++ b/plugins.v2/brushflow/__init__.py @@ -54,6 +54,7 @@ class BrushConfig: self.exclude = config.get("exclude") self.size = config.get("size") self.seeder = config.get("seeder") + self.timezone_offset = (self.__parse_number(config.get("timezone_offset", "+0")) or 0) * 60 # 转换到分钟 self.pubtime = config.get("pubtime") self.seed_time = self.__parse_number(config.get("seed_time")) self.hr_seed_time = self.__parse_number(config.get("hr_seed_time")) @@ -72,6 +73,7 @@ class BrushConfig: self.except_subscribe = config.get("except_subscribe", True) self.brush_sequential = config.get("brush_sequential", False) self.proxy_delete = config.get("proxy_delete", False) + self.del_no_free = config.get("del_no_free", False) if self.freeleech in ["free", "2xfree"] else False self.active_time_range = config.get("active_time_range") self.cron = config.get("cron") self.qb_category = config.get("qb_category") @@ -107,6 +109,7 @@ class BrushConfig: "exclude", "size", "seeder", + "timezone_offset", "pubtime", "seed_time", "hr_seed_time", @@ -119,7 +122,8 @@ class BrushConfig: "proxy_delete", "qb_category", "site_hr_active", - "site_skip_tips" + "site_skip_tips", + "del_no_free" # 当新增支持字段时,仅在此处添加字段名 } try: @@ -173,6 +177,8 @@ class BrushConfig: "exclude": "", "size": "10-500", "seeder": "1", + // 用户本地时区与站点时区的时间偏移,单位为小时。例如:主机时区是UTC+8,站点时区是UTC,应配置为+8;主机时区是UTC,站点时区是UTC+8,应配置为-8 + "timezone_offset": "+0", "pubtime": "5-120", "seed_time": 120, "hr_seed_time": 144, @@ -183,6 +189,8 @@ class BrushConfig: "seed_inactivetime": "", "save_path": "/downloads/site1", "proxy_delete": false, + // 是否删除促销超时的未完成下载,仅当freeleech配置为free或2xfree时有效 + "del_no_free": false, "qb_category": "刷流", "site_hr_active": true, "site_skip_tips": true @@ -251,7 +259,7 @@ class BrushFlow(_PluginBase): # 插件图标 plugin_icon = "brush.jpg" # 插件版本 - plugin_version = "4.3.1" + plugin_version = "4.3.3" # 插件作者 plugin_author = "jxxghp,InfinityPacer" # 作者主页 @@ -263,12 +271,6 @@ class BrushFlow(_PluginBase): # 可使用的用户级别 auth_level = 2 - # 私有属性 - sites_helper = None - site_oper = None - torrents_chain = None - subscribe_oper = None - downloader_helper = None # 刷流配置 _brush_config = None # Brush任务是否启动 @@ -288,11 +290,7 @@ class BrushFlow(_PluginBase): # endregion def init_plugin(self, config: dict = None): - self.sites_helper = SitesHelper() - self.site_oper = SiteOper() - self.torrents_chain = TorrentsChain() - self.subscribe_oper = SubscribeOper() - self.downloader_helper = DownloaderHelper() + self._task_brush_enable = False if not config: @@ -314,7 +312,7 @@ class BrushFlow(_PluginBase): # 这里先过滤掉已删除的站点并保存,特别注意的是,这里保留了界面选择站点时的顺序,以便后续站点随机刷流或顺序刷流 if brush_config.brushsites: - site_id_to_public_status = {site.get("id"): site.get("public") for site in self.sites_helper.get_indexers()} + site_id_to_public_status = {site.get("id"): site.get("public") for site in SitesHelper().get_indexers()} brush_config.brushsites = [ site_id for site_id in brush_config.brushsites if site_id in site_id_to_public_status and not site_id_to_public_status[site_id] @@ -386,7 +384,7 @@ class BrushFlow(_PluginBase): 服务信息 """ brush_config = self.__get_brush_config() - service = self.downloader_helper.get_service(name=brush_config.downloader) + service = DownloaderHelper().get_service(name=brush_config.downloader) if not service: self.__log_and_notify_error("站点刷流任务出错,获取下载器实例失败,请检查配置") return None @@ -811,10 +809,10 @@ class BrushFlow(_PluginBase): # 站点选项 site_options = [{"title": site.get("name"), "value": site.get("id")} - for site in self.sites_helper.get_indexers()] + for site in SitesHelper().get_indexers()] # 下载器选项 downloader_options = [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] return [ { 'component': 'VForm', @@ -1621,6 +1619,27 @@ class BrushFlow(_PluginBase): ] } ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'del_no_free', + 'label': '删除促销过期的未完成下载', + } + } + ] + } + ] } ] } @@ -1794,6 +1813,7 @@ class BrushFlow(_PluginBase): "except_subscribe": True, "brush_sequential": False, "proxy_delete": False, + "del_no_free": False, "freeleech": "free", "hr": "yes", "enable_site_config": False, @@ -1941,7 +1961,7 @@ class BrushFlow(_PluginBase): # 获取所有站点的信息,并过滤掉不存在的站点 site_infos = [] for siteid in brush_config.brushsites: - siteinfo = self.site_oper.get(siteid) + siteinfo = SiteOper().get(siteid) if siteinfo: site_infos.append(siteinfo) @@ -1976,13 +1996,13 @@ class BrushFlow(_PluginBase): """ 针对站点进行刷流 """ - siteinfo = self.site_oper.get(siteid) + siteinfo = SiteOper().get(siteid) if not siteinfo: logger.warning(f"站点不存在:{siteid}") return True logger.info(f"开始获取站点 {siteinfo.name} 的新种子 ...") - torrents = self.torrents_chain.browse(domain=siteinfo.domain) + torrents = TorrentsChain().browse(domain=siteinfo.domain) if not torrents: logger.info(f"站点 {siteinfo.name} 没有获取到种子") return True @@ -2232,8 +2252,13 @@ class BrushFlow(_PluginBase): if not (seeders_range[0] <= torrent.seeders <= seeders_range[1]): return False, f"做种人数 {torrent.seeders},不在指定范围内" - # 发布时间 - pubdate_minutes = self.__get_pubminutes(torrent.pubdate) + # 发布时间:用户时间 - 站点时间 - 时区偏移 + # e.g.1: 用户UTC+8,站点UTC,timezone_offset应为+8,种子在UTC 0:00/UTC+8 8:00发布; + # 9:17 - 0:00 - 8:00 = 1:17;1小时17分为正确的发布时间与当前的时间差 + # e.g.2: 用户UTC,站点UTC+8,timezone_offset应为-8,种子在UTC 0:00/UTC+8 8:00发布: + # 1:17 - 8:00 - (-8:00) = 1:17;1小时17分为正确的发布时间与当前的时间差 + # timezone_offset为后加功能,默认为0,方便后续更多与时间相关的功能开发,之前在单独站点配置中使用pubtime计算过时区偏移的用户也不受影响 + pubdate_minutes = self.__get_pubminutes(torrent.pubdate) - brush_config.timezone_offset # 已支持独立站点配置,取消单独适配站点时区逻辑,可通过配置项「pubtime」自行适配 # pubdate_minutes = self.__adjust_site_pubminutes(pubdate_minutes, torrent) if brush_config.pubtime: @@ -2241,11 +2266,11 @@ class BrushFlow(_PluginBase): if len(pubtimes) == 1: # 单个值:选择发布时间小于等于该值的种子 if pubdate_minutes > pubtimes[0]: - return False, f"发布时间 {torrent.pubdate},{pubdate_minutes:.0f} 分钟前,不符合条件" + return False, f"发布时间(站点时区){torrent.pubdate},当前配置时区偏移 {brush_config.timezone_offset} 小时,{pubdate_minutes:.0f} 分钟前,不符合条件" else: # 范围值:选择发布时间在范围内的种子 if not (pubtimes[0] <= pubdate_minutes <= pubtimes[1]): - return False, f"发布时间 {torrent.pubdate},{pubdate_minutes:.0f} 分钟前,不在指定范围内" + return False, f"发布时间(站点时区){torrent.pubdate},当前配置时区偏移 {brush_config.timezone_offset} 小时,{pubdate_minutes:.0f} 分钟前,不在指定范围内" return True, None @@ -2348,7 +2373,7 @@ class BrushFlow(_PluginBase): if need_delete_hashes: # 如果是QB,则重新汇报Tracker - if self.downloader_helper.is_downloader("qbittorrent", service=self.service_info): + if DownloaderHelper().is_downloader("qbittorrent", service=self.service_info): self.__qb_torrents_reannounce(torrent_hashes=need_delete_hashes) # 删除种子 if downloader.delete_torrents(ids=need_delete_hashes, delete_file=True): @@ -2390,7 +2415,7 @@ class BrushFlow(_PluginBase): seeding_torrents_dict: Dict[str, Any]): brush_config = self.__get_brush_config() - if not self.downloader_helper.is_downloader("qbittorrent", service=self.service_info): + if not DownloaderHelper().is_downloader("qbittorrent", service=self.service_info): logger.info("同步种子刷流标签记录目前仅支持qbittorrent") return @@ -2501,6 +2526,25 @@ class BrushFlow(_PluginBase): return True, f"H&R种子,分享率 {torrent_info.get('ratio'):.2f},大于 {brush_config.seed_ratio}" return False, "H&R种子,未能满足设置的H&R删除条件" + while brush_config.del_no_free and torrent_info.get("downloaded") < torrent_info.get("total_size"): + if not torrent_task.get("freedate", None): + logger.warning(f"配置了‘删除促销过期的未完成下载’,但未获取到该种子的促销截止时间,跳过。") + break + try: + now = datetime.now() + freedate_origin = torrent_task.get("freedate") + freedate = freedate_origin.replace("T", " ").replace("Z", "") + freedate = datetime.strptime(freedate, "%Y-%m-%d %H:%M:%S") + delta_minutes = (((freedate - now).total_seconds() + 60) // 60) - brush_config.timezone_offset + logger.debug( + f"促销截止(站点时间): {freedate_origin}, 时区偏移: {brush_config.timezone_offset}, 用户当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')}, 时间差: {delta_minutes}分") + if delta_minutes <= 0: + return True, "促销过期" + except Exception as e: + logger.warning(f"处理‘删除促销过期的未完成下载’时报错,继续判断其他删除条件。") + logger.debug(f"error: {e}") + break + # 处理其他场景,1. 不是H&R种子;2. 是H&R种子但没有特定条件配置 reason = reason if not hit_and_run else "H&R种子(未设置H&R条件),未能满足设置的删除条件" if brush_config.seed_time and torrent_info.get("seeding_time") >= float(brush_config.seed_time) * 3600: @@ -2523,18 +2567,39 @@ class BrushFlow(_PluginBase): return True, reason if not hit_and_run else "H&R种子(未设置H&R条件)," + reason - def __evaluate_proxy_pre_conditions_for_delete(self, site_name: str, torrent_info: dict) -> Tuple[bool, str]: + def __evaluate_proxy_pre_conditions_for_delete(self, site_name: str, + torrent_info: dict, torrent_task: dict) -> Tuple[bool, str]: """ 评估动态删除前置条件并返回是否应删除种子及其原因 """ brush_config = self.__get_brush_config(sitename=site_name) + should_delete = False reason = "未能满足动态删除设置的前置删除条件" + while brush_config.del_no_free and torrent_info.get("downloaded") < torrent_info.get("total_size"): + if not torrent_task.get("freedate", None): + logger.warning(f"配置了‘删除促销过期的未完成下载’,但未获取到该种子的促销截止时间,跳过。") + break + try: + now = datetime.now() + freedate_origin = torrent_task.get("freedate") + freedate = freedate_origin.replace("T", " ").replace("Z", "") + freedate = datetime.strptime(freedate, "%Y-%m-%d %H:%M:%S") + delta_minutes = (((freedate - now).total_seconds() + 60) // 60) - brush_config.timezone_offset + logger.debug( + f"促销截止(站点时间): {freedate_origin}, 时区偏移: {brush_config.timezone_offset}, 用户当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')}, 时间差: {delta_minutes}分") + if delta_minutes <= 0: + return True, f"促销已过期" + except Exception as e: + logger.warning(f"处理‘删除促销过期的未完成下载’时报错,继续判断其他删除条件。") + logger.debug(f"error: {e}") + break + if brush_config.download_time and torrent_info.get("downloaded") < torrent_info.get( "total_size") and torrent_info.get("dltime") >= float(brush_config.download_time) * 3600: reason = f"下载耗时 {torrent_info.get('dltime') / 3600:.1f} 小时,大于 {brush_config.download_time} 小时" - else: + elif not should_delete: return False, reason return True, reason @@ -2599,7 +2664,8 @@ class BrushFlow(_PluginBase): # 删除种子的具体实现可能会根据实际情况略有不同 should_delete, reason = self.__evaluate_proxy_pre_conditions_for_delete(site_name=site_name, - torrent_info=torrent_info) + torrent_info=torrent_info, + torrent_task=torrent_task) if should_delete: delete_hashes.append(torrent_hash) self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc, @@ -2957,6 +3023,7 @@ class BrushFlow(_PluginBase): "exclude": brush_config.exclude, "size": brush_config.size, "seeder": brush_config.seeder, + "timezone_offset": brush_config.timezone_offset, "pubtime": brush_config.pubtime, "seed_time": brush_config.seed_time, "hr_seed_time": brush_config.hr_seed_time, @@ -2980,6 +3047,7 @@ class BrushFlow(_PluginBase): "qb_category": brush_config.qb_category, "enable_site_config": brush_config.enable_site_config, "site_config": brush_config.site_config, + "del_no_free": brush_config.del_no_free, "_tabs": self._tabs } @@ -3041,7 +3109,8 @@ class BrushFlow(_PluginBase): return data return None - def __reset_download_url(self, torrent_url, site_id) -> str: + @staticmethod + def __reset_download_url(torrent_url, site_id) -> str: """ 处理下载地址 """ @@ -3050,7 +3119,7 @@ class BrushFlow(_PluginBase): if not torrent_url or torrent_url.startswith("magnet"): return torrent_url - indexers = self.sites_helper.get_indexers() + indexers = SitesHelper().get_indexers() if not indexers: return torrent_url @@ -3115,7 +3184,8 @@ class BrushFlow(_PluginBase): if not downloader: return None - if self.downloader_helper.is_downloader("qbittorrent", service=self.service_info): + downloader_helper = DownloaderHelper() + if downloader_helper.is_downloader("qbittorrent", service=self.service_info): # 限速值转为bytes up_speed = up_speed * 1024 if up_speed else None down_speed = down_speed * 1024 if down_speed else None @@ -3149,7 +3219,7 @@ class BrushFlow(_PluginBase): return torrent_hash return None - elif self.downloader_helper.is_downloader("transmission", service=self.service_info): + elif downloader_helper.is_downloader("transmission", service=self.service_info): # 如果开启代理下载以及种子地址不是磁力地址,则请求种子到内存再传入下载器 if not torrent_content.startswith("magnet"): response = RequestUtils(cookies=cookies, @@ -3197,7 +3267,7 @@ class BrushFlow(_PluginBase): 获取种子hash """ try: - return torrent.get("hash") if self.downloader_helper.is_downloader("qbittorrent", service=self.service_info) \ + return torrent.get("hash") if DownloaderHelper().is_downloader("qbittorrent", service=self.service_info) \ else torrent.hashString except Exception as e: print(str(e)) @@ -3214,8 +3284,8 @@ class BrushFlow(_PluginBase): all_hashes = [] for torrent in torrents: # 根据下载器类型获取Hash值 - hash_value = torrent.get("hash") if self.downloader_helper.is_downloader("qbittorrent", - service=self.service_info) \ + hash_value = torrent.get("hash") if DownloaderHelper().is_downloader("qbittorrent", + service=self.service_info) \ else torrent.hashString if hash_value: all_hashes.append(hash_value) @@ -3230,8 +3300,8 @@ class BrushFlow(_PluginBase): """ try: return [str(tag).strip() for tag in torrent.get("tags").split(',')] \ - if self.downloader_helper.is_downloader("qbittorrent", - service=self.service_info) else torrent.labels or [] + if DownloaderHelper().is_downloader("qbittorrent", + service=self.service_info) else torrent.labels or [] except Exception as e: print(str(e)) return [] @@ -3242,7 +3312,7 @@ class BrushFlow(_PluginBase): """ date_now = int(time.time()) # QB - if self.downloader_helper.is_downloader("qbittorrent", service=self.service_info): + if DownloaderHelper().is_downloader("qbittorrent", service=self.service_info): """ { "added_on": 1693359031, @@ -3649,7 +3719,7 @@ class BrushFlow(_PluginBase): if not self._subscribe_infos: self._subscribe_infos = {} - subscribes = self.subscribe_oper.list() + subscribes = SubscribeOper().list() if subscribes: # 遍历订阅 for subscribe in subscribes: @@ -3862,7 +3932,8 @@ class BrushFlow(_PluginBase): # 情况2: 时间段跨越午夜 return now >= start_time or now <= end_time - def __get_site_by_torrent(self, torrent: Any) -> Tuple[int, str]: + @staticmethod + def __get_site_by_torrent(torrent: Any) -> Tuple[int, str]: """ 根据tracker获取站点信息 """ @@ -3905,7 +3976,7 @@ class BrushFlow(_PluginBase): # 使用StringUtils工具类获取tracker的域名 domain = StringUtils.get_url_domain(tracker) - site_info = self.sites_helper.get_indexer(domain) + site_info = SitesHelper().get_indexer(domain) if site_info: return site_info.get("id"), site_info.get("name") diff --git a/plugins.v2/chatgpt/__init__.py b/plugins.v2/chatgpt/__init__.py index 1b2d7ac..d7181ed 100644 --- a/plugins.v2/chatgpt/__init__.py +++ b/plugins.v2/chatgpt/__init__.py @@ -337,7 +337,8 @@ class ChatGPT(_PluginBase): def get_page(self) -> List[dict]: pass - def is_api_error(self, response): + @staticmethod + def is_api_error(response): """ 判断响应是否表示API错误 :param response: API响应 @@ -486,4 +487,4 @@ class ChatGPT(_PluginBase): """ 退出插件 """ - pass \ No newline at end of file + pass diff --git a/plugins.v2/clashruleprovider/__init__.py b/plugins.v2/clashruleprovider/__init__.py new file mode 100644 index 0000000..97485e1 --- /dev/null +++ b/plugins.v2/clashruleprovider/__init__.py @@ -0,0 +1,624 @@ +import hashlib +import re +import time +from datetime import datetime, timedelta +from typing import Any, Optional, List, Dict, Tuple, Union + +import pytz +import yaml +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.cron import CronTrigger +from fastapi import Body, Response + +from app.core.config import settings +from app.core.event import eventmanager +from app.log import logger +from app.plugins import _PluginBase +from app.plugins.clashruleprovider.clash_rule_parser import Action, RuleType, ClashRule, MatchRule, LogicRule +from app.plugins.clashruleprovider.clash_rule_parser import ClashRuleParser +from app.schemas.types import EventType +from app.utils.http import RequestUtils + + +class ClashRuleProvider(_PluginBase): + # 插件名称 + plugin_name = "Clash Rule Provider" + # 插件描述 + plugin_desc = "随时为Clash添加一些额外的规则。" + # 插件图标 + plugin_icon = ("https://raw.githubusercontent.com/wumode/MoviePilot-Plugins/" + "refs/heads/imdbsource_assets/icons/Mihomo_Meta_A.png") + # 插件版本 + plugin_version = "0.1.0" + # 插件作者 + plugin_author = "wumode" + # 作者主页 + author_url = "https://github.com/wumode" + # 插件配置项ID前缀 + plugin_config_prefix = "clashruleprovider_" + # 加载顺序 + plugin_order = 99 + # 可使用的用户级别 + auth_level = 1 + + # 插件配置 + # 启用插件 + _enabled = False + _proxy = False + _notify = False + # 订阅链接 + _sub_links = [] + # Clash 面板 URL + _clash_dashboard_url = None + # Clash 面板密钥 + _clash_dashboard_secret = None + # MoviePilot URL + _movie_pilot_url = None + _cron = '' + _timeout = 10 + _retry_times = 3 + _filter_keywords = [] + _auto_update_subscriptions = True + _ruleset_prefix = '📂<-' + + # 插件数据 + _clash_config = None + _top_rules: List[str] = [] + _ruleset_rules: List[str] = [] + _rule_provider: Dict[str, Any] = {} + _subscription_info = {} + _ruleset_names: Dict[str, str] = {} + + # protected variables + _clash_rule_parser = None + _ruleset_rule_parser = None + _custom_rule_sets = None + _scheduler: Optional[BackgroundScheduler] = None + + def init_plugin(self, config: dict = None): + self._clash_config = self.get_data("clash_config") + self._ruleset_rules = self.get_data("ruleset_rules") + self._top_rules = self.get_data("top_rules") + self._subscription_info = self.get_data("subscription_info") or \ + {"download": 0, "upload": 0, "total": 0, "expire": 0, "last_update": 0} + self._rule_provider = self.get_data("rule_provider") or {} + self._ruleset_names = self.get_data("ruleset_names") or {} + if config: + self._enabled = config.get("enabled") + self._proxy = config.get("proxy") + self._notify = config.get("notify"), + self._sub_links = config.get("sub_links") + self._clash_dashboard_url = config.get("clash_dashboard_url") + self._clash_dashboard_secret = config.get("clash_dashboard_secret") + self._movie_pilot_url = config.get("movie_pilot_url") + if self._movie_pilot_url[-1] == '/': + self._movie_pilot_url = self._movie_pilot_url[:-1] + self._cron = config.get("cron_string") + self._timeout = config.get("timeout") + self._retry_times = config.get("retry_times") + self._filter_keywords = config.get("filter_keywords") + self._ruleset_prefix = config.get("ruleset_prefix", "Custom_") + self._auto_update_subscriptions = config.get("auto_update_subscriptions") + self._clash_rule_parser = ClashRuleParser() + self._ruleset_rule_parser = ClashRuleParser() + if self._enabled: + self.__parse_config() + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + self._scheduler.start() + + def get_state(self) -> bool: + return self._enabled + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + pass + + def get_api(self) -> List[Dict[str, Any]]: + return [ + { + "path": "/connectivity", + "endpoint": self.test_connectivity, + "methods": ["POST"], + "auth": "bear", + "summary": "测试连接", + "description": "测试连接" + }, + { + "path": "/clash_outbound", + "endpoint": self.get_clash_outbound, + "methods": ["GET"], + "auth": "bear", + "summary": "clash outbound", + "description": "clash outbound" + }, + { + "path": "/status", + "endpoint": self.get_status, + "methods": ["GET"], + "auth": "bear", + "summary": "stated", + "description": "state" + }, + { + "path": "/rules", + "endpoint": self.get_rules, + "methods": ["GET"], + "auth": "bear", + "summary": "clash rules", + "description": "clash rules" + }, + { + "path": "/rules", + "endpoint": self.update_rules, + "methods": ["PUT"], + "auth": "bear", + "summary": "clash rules", + "description": "clash rules" + }, + { + "path": "/reorder-rules", + "endpoint": self.reorder_rules, + "methods": ["PUT"], + "auth": "bear", + "summary": "clash rules", + "description": "clash rules" + }, + { + "path": "/rule", + "endpoint": self.update_rule, + "methods": ["PUT"], + "auth": "bear", + "summary": "clash rules", + "description": "clash rules" + }, + { + "path": "/rule", + "endpoint": self.add_rule, + "methods": ["POSt"], + "auth": "bear", + "summary": "clash rules", + "description": "clash rules" + }, + { + "path": "/rule", + "endpoint": self.delete_rule, + "methods": ["DELETE"], + "auth": "bear", + "summary": "clash rules", + "description": "clash rules" + }, + { + "path": "/subscription", + "endpoint": self.get_subscription, + "methods": ["GET"], + "auth": "bear", + "summary": "clash rules", + "description": "clash rules" + }, + { + "path": "/subscription", + "endpoint": self.update_subscription, + "methods": ["PUT"], + "auth": "bear", + "summary": "update clash rules", + "description": "update clash rules" + }, + { + "path": "/rule_providers", + "endpoint": self.get_rule_providers, + "methods": ["GET"], + "auth": "bear", + "summary": "update rule providers", + "description": "update rule providers" + }, + { + "path": "/ruleset", + "endpoint": self.get_ruleset, + "methods": ["GET"], + "summary": "update rule providers", + "description": "update rule providers" + }, + { + "path": "/config", + "endpoint": self.get_clash_config, + "methods": ["GET"], + "summary": "update rule providers", + "description": "update rule providers" + } + ] + + def get_render_mode(self) -> Tuple[str, str]: + """ + 获取插件渲染模式 + :return: 1、渲染模式,支持:vue/vuetify,默认vuetify + :return: 2、组件路径,默认 dist/assets + """ + return "vue", "dist/assets" + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + """ + 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 + """ + return [], {} + + def get_page(self) -> List[dict]: + return [] + + def stop_service(self): + """ + 退出插件 + """ + pass + + def get_service(self) -> List[Dict[str, Any]]: + if self.get_state() and self._auto_update_subscriptions: + return [{ + "id": "ClashRuleProvider", + "name": "Clash Rule Provider 服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.update_subscription_service, + "kwargs": {} + }] + return [] + + def __update_config(self): + # 保存配置 + self.update_config( + { + "enabled": self._enabled, + "cron": self._cron, + "proxy": self._proxy, + "notify": self._notify, + "sub_links": self._sub_links, + "clash_dashboard_url": self._clash_dashboard_url, + "clash_dashboard_secret": self._clash_dashboard_secret, + "movie_pilot_url": self._movie_pilot_url, + "retry_times": self._retry_times, + "timeout": self._timeout, + }) + + def __save_data(self): + self.__insert_ruleset() + self._top_rules = self._clash_rule_parser.to_string() + self._ruleset_rules = self._ruleset_rule_parser.to_string() + self.save_data('clash_config', self._clash_config) + self.save_data('ruleset_rules', self._ruleset_rules) + self.save_data('top_rules', self._top_rules) + self.save_data('subscription_info', self._subscription_info) + self.save_data('ruleset_names', self._ruleset_names) + self.save_data('rule_provider', self._rule_provider) + + def __parse_config(self): + if not self._top_rules: + return + self._clash_rule_parser.parse_rules_from_list(self._top_rules) + if not self._ruleset_rules: + return + self._ruleset_rule_parser.parse_rules_from_list(self._ruleset_rules) + + def test_connectivity(self, params: Dict[str, Any]) -> Dict[str, Any]: + if not self._enabled: + return {"success": False, "message": ""} + if not params.get('clash_dashboard_url') or not params.get('clash_dashboard_secret') \ + or not params.get('sub_link'): + return {"success": False, "message": "missing params"} + clash_version_url = f"{params.get('clash_dashboard_url')}/version" + ret = RequestUtils(accept_type="application/json", + headers={"authorization": f"Bearer {params.get('clash_dashboard_secret')}"} + ).get(clash_version_url) + if not ret: + return {"success": False, "message": "无法连接到Clash"} + ret = RequestUtils(accept_type="text/html", + proxies=settings.PROXY if self._proxy else None + ).get(params.get('sub_link')) + if not ret: + return {"success": False, "message": f"Unable to get {params.get('sub_link')}"} + return {"success": True, "message": "测试连接成功"} + + def get_ruleset(self, name): + if not self._ruleset_names.get(name): + return None + name = self._ruleset_names.get(name) + rules = self.__get_ruleset(name) + # if rules or ruleset in self._rule_provider: + # self._rule_provider[ruleset] = rules + res = yaml.dump({"payload": rules}, allow_unicode=True) + return Response(content=res, media_type="text/yaml") + + def get_clash_outbound(self): + outbound = self.clash_outbound(self._clash_config) + return {"success": True, "message": None, "data": {"outbound": outbound}} + + def get_status(self): + rule_size = len(self._clash_config.get("rules", [])) if self._clash_config else 0 + return {"success": True, "message": "", + "data": {"state": self._enabled, + "ruleset_prefix": self._ruleset_prefix, + "clash": {"rule_size": rule_size}, + "subscription_info": self._subscription_info, + "sub_url": f"{self._movie_pilot_url}/api/v1/plugin/ClashRuleProvider/config?" + f"apikey={settings.API_TOKEN}"}} + + def get_clash_config(self): + config = self.clash_config() + if not config: + return {"success": False, "message": ""} + res = yaml.dump(config, allow_unicode=True) + headers = {'Subscription-Userinfo': f'upload={self._subscription_info["upload"]}; ' + f'download={self._subscription_info["download"]}; ' + f'total={self._subscription_info["total"]}; ' + f'expire={self._subscription_info["expire"]}'} + return Response(headers=headers, content=res, media_type="text/yaml") + + def get_rules(self, rule_type: str) -> Dict[str, Any]: + if rule_type == 'ruleset': + return {"success": True, "message": None, "data": {"rules": self._ruleset_rule_parser.to_dict()}} + return {"success": True, "message": None, "data": {"rules": self._clash_rule_parser.to_dict()}} + + def delete_rule(self, params: dict = Body(...)): + if not self._enabled: + return {"success": False, "message": ""} + if params.get('type') == 'ruleset': + res = self.delete_rule_by_priority(params.get('priority'), self._ruleset_rule_parser) + if res: + self.__add_notification_job( + f"{self._ruleset_prefix}{res.action.value if isinstance(res.action, Action) else res.action}") + else: + res = self.delete_rule_by_priority(params.get('priority'), self._clash_rule_parser) + return {"success": res, "message": None} + + def reorder_rules(self, params: Dict[str, Any]): + if not self._enabled: + return {"success": False, "message": ""} + moved_priority = params.get('moved_priority') + target_priority = params.get('target_priority') + try: + if params.get('type') == 'ruleset': + self.__reorder_rules(self._ruleset_rule_parser, moved_priority, target_priority) + self.__add_notification_job(f"{self._ruleset_prefix}{params.get('rule_data').get('action')}") + else: + self.__reorder_rules(self._clash_rule_parser, moved_priority, target_priority) + except Exception as e: + return {"success": False, "message": str(e)} + return {"success": True, "message": None} + + def update_rules(self, params: Dict[str, Any]): + if not self._enabled: + return {"success": False, "message": ""} + if params.get('type') == 'ruleset': + self.__update_rules(params.get('rules'), self._ruleset_rule_parser) + else: + self.__update_rules(params.get('rules'), self._clash_rule_parser) + return {"success": True, "message": None} + + def update_rule(self, params: Dict[str, Any]) -> Dict[str, Any]: + if not self._enabled: + return {"success": False, "message": ""} + if params.get('type') == 'ruleset': + res = self.update_rule_by_priority(params.get('rule_data'), self._ruleset_rule_parser) + if res: + self.__add_notification_job(f"{self._ruleset_prefix}{params.get('rule_data').get('action')}") + else: + res = self.update_rule_by_priority(params.get('rule_data'), self._clash_rule_parser) + return {"success": bool(res), "message": None} + + def add_rule(self, params: Dict[str, Any]) -> Dict[str, Any]: + if not self._enabled: + return {"success": False, "message": ""} + if params.get('type') == 'ruleset': + res = self.add_rule_by_priority(params.get('rule_data'), self._ruleset_rule_parser) + if res: + self.__add_notification_job(f"{self._ruleset_prefix}{params.get('rule_data').get('action')}") + else: + res = self.add_rule_by_priority(params.get('rule_data'), self._clash_rule_parser) + return {"success": bool(res), "message": None} + + def get_subscription(self): + if not self._sub_links: + return None + return {"success": True, "message": None, "data": {"url": self._sub_links[0]}} + + def update_subscription(self, params: Dict[str, Any]): + if not self._enabled: + return {"success": False, "message": ""} + url = params.get('url') + if not url: + return {"success": False, "message": "missing params"} + res = self.update_subscription_service() + if not res: + return {"success": True, "message": f"订阅链接 {self._sub_links[0]} 更新失败"} + return {"success": True, "message": "订阅更新成功"} + + def get_rule_providers(self): + return {"success": True, "message": None, "data": self.rule_providers()} + + @staticmethod + def clash_outbound(clash_config: Dict[str, Any]) -> Optional[List]: + if not clash_config: + return [] + outbound = [{'name': proxy_group.get("name")} for proxy_group in clash_config.get("proxy-groups")] + outbound.extend([{'name': proxy.get("name")} for proxy in clash_config.get("proxies")]) + return outbound + + def rule_providers(self) -> Optional[Dict[str, Any]]: + if not self._clash_config: + return None + rule_providers = {} + for key, value in self._clash_config.get("rule-providers", {}): + if value.get("path", '').startwith("./CRP/"): + continue + rule_providers[key] = value + return rule_providers + + def __update_rules(self, rules: List[Dict[str, Any]], rule_parser: ClashRuleParser): + rule_parser.rules = [] + for rule in rules: + clash_rule = ClashRuleParser.parse_rule_dict(rule) + rule_parser.insert_rule_at_priority(clash_rule, rule.get("priority")) + self.__save_data() + + def __reorder_rules(self, rule_parser: ClashRuleParser, moved_priority, target_priority): + rule_parser.reorder_rules(moved_priority, target_priority) + self.__save_data() + + def __get_ruleset(self, ruleset: str) -> List[str]: + if ruleset.startswith(self._ruleset_prefix): + action = ruleset[len(self._ruleset_prefix):] + else: + return [] + try: + action_enum = Action(action.upper()) + final_action = action_enum + except ValueError: + final_action = action + rules = self._ruleset_rule_parser.filter_rules_by_action(final_action) + res = [] + for rule in rules: + res.append(rule.condition_string()) + return res + + def __insert_ruleset(self): + outbounds = [] + for rule in self._ruleset_rule_parser.rules: + action_str = f"{rule.action.value}" if isinstance(rule.action, Action) else rule.action + if action_str not in outbounds: + outbounds.append(action_str) + self._clash_rule_parser.remove_rules(lambda r: r.rule_type == RuleType.RULE_SET and + r.payload.startswith(self._ruleset_prefix)) + for outbound in outbounds: + clash_rule = ClashRuleParser.parse_rule_line(f"RULE-SET,{self._ruleset_prefix}{outbound},{outbound}") + if not self._clash_rule_parser.has_rule(clash_rule): + self._clash_rule_parser.insert_rule_at_priority(clash_rule, 0) + + def update_rule_by_priority(self, rule: Dict[str, Any], rule_parser: ClashRuleParser) -> bool: + if not isinstance(rule.get("priority"), int): + return False + clash_rule = ClashRuleParser.parse_rule_dict(rule) + if not clash_rule: + return False + res = rule_parser.update_rule_at_priority(clash_rule, rule.get("priority")) + self.__save_data() + return res + + def add_rule_by_priority(self, rule: Dict[str, Any], rule_parser: ClashRuleParser) -> bool: + if not isinstance(rule.get("priority"), int): + return False + try: + clash_rule = self._clash_rule_parser.parse_rule_dict(rule) + except ValueError: + logger.warn(f"无效的输入规则: {rule}") + return False + if not clash_rule: + return False + rule_parser.insert_rule_at_priority(clash_rule, rule.get("priority")) + self.__save_data() + return True + + def delete_rule_by_priority(self, priority: int, rule_parser: ClashRuleParser + ) -> Optional[Union[ClashRule, LogicRule, MatchRule]]: + if not isinstance(priority, int): + return None + res = rule_parser.remove_rule_at_priority(priority) + self.__save_data() + return res + + @eventmanager.register(EventType.PluginAction) + def update_subscription_service(self) -> bool: + if not self._sub_links: + return False + url = self._sub_links[0] + ret = RequestUtils(accept_type="text/html", + proxies=settings.PROXY if self._proxy else None + ).get_res(url) + if not ret: + return False + try: + rs = yaml.load(ret.content, Loader=yaml.FullLoader) + self._clash_config = self.__remove_nodes_by_keywords(rs) + except Exception as e: + logger.error(f"解析配置出错: {e}") + return False + if 'Subscription-Userinfo' in ret.headers: + matches = re.findall(r'(\w+)=(\d+)', ret.headers['Subscription-Userinfo']) + variables = {key: int(value) for key, value in matches} + self._subscription_info['download'] = variables['download'] + self._subscription_info['upload'] = variables['upload'] + self._subscription_info['total'] = variables['total'] + self._subscription_info['expire'] = variables['expire'] + self._subscription_info["last_update"] = int(time.time()) + self.save_data('subscription_info', self._subscription_info) + self.save_data('clash_config', self._clash_config) + return True + + def notify_clash(self, ruleset: str): + url = f'{self._clash_dashboard_url}/providers/rules/{ruleset}' + RequestUtils(content_type="application/json", + headers={"authorization": f"Bearer {self._clash_dashboard_secret}"} + ).put(url) + + def __add_notification_job(self, ruleset: str): + if ruleset in self._rule_provider: + self._scheduler.add_job(self.notify_clash, "date", + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=30), + args=[ruleset], + id='CRP-notify-clash', + replace_existing=True + ) + + def __remove_nodes_by_keywords(self, clash_config: Dict[str, Any]) -> Dict[str, Any]: + removed_proxies = [] + proxies = [] + for proxy in clash_config.get("proxies", []): + has_keywords = bool(len([x for x in self._filter_keywords if x in proxy.get("name", '')])) + if has_keywords: + removed_proxies.append(proxy.get("name")) + else: + proxies.append(proxy) + if proxies: + clash_config["proxies"] = proxies + else: + logger.warn(f"关键词过滤后无可用节点,跳过过滤") + removed_proxies = [] + for proxy_group in clash_config.get("proxy-groups", []): + proxy_group['proxies'] = [x for x in proxy_group.get('proxies') if x not in removed_proxies] + clash_config["proxy-groups"] = [x for x in clash_config.get("proxy-groups", []) if x.get("proxies")] + return clash_config + + def clash_config(self) -> Optional[Dict[str, Any]]: + if not self._clash_config: + return + self.__insert_ruleset() + self._top_rules = self._clash_rule_parser.to_string() + clash_config = self._clash_config.copy() + top_rules = [] + for rule in self._clash_rule_parser.rules: + if (not isinstance(rule.action, Action) and + not len([x for x in self.clash_outbound(clash_config) if rule.action == x.get("name", '')])): + logger.warn(f"出站 {rule.action} 不存在, 绕过 {rule.raw_rule}") + continue + top_rules.append(rule.raw_rule) + clash_config["rules"] = self._top_rules + clash_config.get("rules", []) + self._rule_provider = {} + for r in self._clash_rule_parser.rules: + if r.rule_type == RuleType.RULE_SET and r.payload.startswith(self._ruleset_prefix): + action_str = f"{r.action.value}" if isinstance(r.action, Action) else r.action + path_name = hashlib.sha256(action_str.encode('utf-8')).hexdigest()[:10] + self._ruleset_names[path_name] = r.payload + sub_url = (f"{self._movie_pilot_url}/api/v1/plugin/ClashRuleProvider/ruleset?" + f"name={path_name}&apikey={settings.API_TOKEN}") + self._rule_provider[r.payload] = {"behavior": "classical", + "format": "yaml", + "interval": 3600, + "path": f"./CRP/{path_name}.yaml", + "type": "http", + "url": sub_url} + if clash_config.get("rule-providers"): + clash_config['rule-providers'].update(self._rule_provider) + else: + clash_config['rule-providers'] = self._rule_provider + for key, item in self._ruleset_names.items(): + if item not in clash_config['rule-providers']: + del self._ruleset_names[key] + self.save_data('ruleset_names', self._ruleset_names) + self.save_data('rule_provider', self._rule_provider) + return clash_config diff --git a/plugins.v2/clashruleprovider/clash_rule_parser.py b/plugins.v2/clashruleprovider/clash_rule_parser.py new file mode 100644 index 0000000..53d6bc2 --- /dev/null +++ b/plugins.v2/clashruleprovider/clash_rule_parser.py @@ -0,0 +1,488 @@ +import re +from typing import List, Dict, Any, Optional, Union, Callable +from dataclasses import dataclass +from enum import Enum + + +class RuleType(Enum): + """Enumeration of all supported Clash rule types""" + DOMAIN = "DOMAIN" + DOMAIN_SUFFIX = "DOMAIN-SUFFIX" + DOMAIN_KEYWORD = "DOMAIN-KEYWORD" + DOMAIN_REGEX = "DOMAIN-REGEX" + GEOSITE = "GEOSITE" + + IP_CIDR = "IP-CIDR" + IP_CIDR6 = "IP-CIDR6" + IP_SUFFIX = "IP-SUFFIX" + IP_ASN = "IP-ASN" + GEOIP = "GEOIP" + + SRC_GEOIP = "SRC-GEOIP" + SRC_IP_ASN = "SRC-IP-ASN" + SRC_IP_CIDR = "SRC-IP-CIDR" + SRC_IP_SUFFIX = "SRC-IP-SUFFIX" + + DST_PORT = "DST-PORT" + SRC_PORT = "SRC-PORT" + + IN_PORT = "IN-PORT" + IN_TYPE = "IN-TYPE" + IN_USER = "IN-USER" + IN_NAME = "IN-NAME" + + PROCESS_PATH = "PROCESS-PATH" + PROCESS_PATH_REGEX = "PROCESS-PATH-REGEX" + PROCESS_NAME = "PROCESS-NAME" + PROCESS_NAME_REGEX = "PROCESS-NAME-REGEX" + + UID = "UID" + NETWORK = "NETWORK" + DSCP = "DSCP" + + RULE_SET = "RULE-SET" + AND = "AND" + OR = "OR" + NOT = "NOT" + SUB_RULE = "SUB-RULE" + + MATCH = "MATCH" + + +class Action(Enum): + """Enumeration of rule actions""" + DIRECT = "DIRECT" + REJECT = "REJECT" + REJECT_DROP = "REJECT-DROP" + PASS = "PASS" + COMPATIBLE = "COMPATIBLE" + + +@dataclass +class ClashRule: + """Represents a parsed Clash routing rule""" + rule_type: RuleType + payload: str + action: Union[Action, str] # Can be Action enum or custom proxy group name + additional_params: Optional[List[str]] = None + raw_rule: str = "" + priority: int = 0 + + def __post_init__(self): + if self.additional_params is None: + self.additional_params = [] + + def condition_string(self) -> str: + return f"{self.rule_type.value},{self.payload}" + + +@dataclass +class LogicRule: + """Represents a logic rule (AND, OR, NOT)""" + logic_type: RuleType + conditions: List[Union[ClashRule, 'LogicRule']] + action: Union[Action, str] + raw_rule: str = "" + priority: int = 0 + + def condition_string(self) -> str: + conditions_str = ','.join([f"({c.condition_string()})" for c in self.conditions]) + return f"{self.logic_type.value},({conditions_str})" + + +@dataclass +class MatchRule: + """Represents a match rule""" + action: Union[Action, str] + raw_rule: str = "" + priority: int = 0 + rule_type: RuleType = RuleType.MATCH + + @staticmethod + def condition_string() -> str: + return "MATCH" + + +class ClashRuleParser: + """Parser for Clash routing rules""" + + def __init__(self): + self.rules: List[Union[ClashRule, LogicRule, MatchRule]] = [] + + @staticmethod + def parse_rule_line(line: str) -> Optional[Union[ClashRule, LogicRule, MatchRule]]: + """Parse a single rule line""" + line = line.strip() + try: + # Handle logic rules (AND, OR, NOT) + + if line.startswith(('AND,', 'OR,', 'NOT,')): + return ClashRuleParser._parse_logic_rule(line) + elif line.startswith('MATCH'): + return ClashRuleParser._parse_match_rule(line) + # Handle regular rules + return ClashRuleParser._parse_regular_rule(line) + + except Exception as e: + print(f"Error parsing rule '{line}': {e}") + return None + + @staticmethod + def parse_rule_dict(clash_rule: Dict[str, Any]) -> Optional[Union[ClashRule, LogicRule, MatchRule]]: + if not clash_rule: + return None + if clash_rule.get("type") in ('AND', 'OR', 'NOT'): + conditions = clash_rule.get("conditions") + if not conditions: + return None + conditions_str = '' + for condition in conditions: + conditions_str += f'({condition.get("type")},{condition.get("payload")})' + conditions_str = f"({conditions_str})" + raw_rule = f"{clash_rule.get('type')},{conditions_str},{clash_rule.get('action')}" + return ClashRuleParser._parse_logic_rule(raw_rule) + elif clash_rule.get("type") == 'MATCH': + raw_rule = f"{clash_rule.get('type')},{clash_rule.get('action')}" + return ClashRuleParser._parse_match_rule(raw_rule) + else: + raw_rule = f"{clash_rule.get('type')},{clash_rule.get('payload')},{clash_rule.get('action')}" + return ClashRuleParser._parse_regular_rule(raw_rule) + + @staticmethod + def _parse_match_rule(line: str) -> MatchRule: + parts = line.split(',') + if len(parts) < 2: + raise ValueError(f"Invalid rule format: {line}") + action = parts[1] + # Validate rule type + try: + action_enum = Action(action.upper()) + final_action = action_enum + except ValueError: + final_action = action + + return MatchRule( + action=final_action, + raw_rule=line + ) + + @staticmethod + def _parse_regular_rule(line: str) -> ClashRule: + """Parse a regular (non-logic) rule""" + parts = line.split(',') + + if len(parts) < 3: + raise ValueError(f"Invalid rule format: {line}") + + rule_type_str = parts[0].upper() + payload = parts[1] + action = parts[2] + + if not payload or not rule_type_str: + raise ValueError(f"Invalid rule format: {line}") + + additional_params = parts[3:] if len(parts) > 3 else [] + + # Validate rule type + try: + rule_type = RuleType(rule_type_str) + except ValueError: + raise ValueError(f"Unknown rule type: {rule_type_str}") + + # Try to convert action to enum, otherwise keep as string (custom proxy group) + try: + action_enum = Action(action.upper()) + final_action = action_enum + except ValueError: + final_action = action + + return ClashRule( + rule_type=rule_type, + payload=payload, + action=final_action, + additional_params=additional_params, + raw_rule=line + ) + + @staticmethod + def _parse_logic_rule(line: str) -> LogicRule: + """Parse a logic rule (AND, OR, NOT)""" + # Extract logic type + logic_rule_match = re.match(r'^(AND|OR|NOT),\((.+)\),([^,]+)$', line) + if not logic_rule_match: + raise ValueError(f"Cannot extract action from logic rule: {line}") + logic_type_str = logic_rule_match.group(1).upper() + logic_type = RuleType(logic_type_str) + action = logic_rule_match.group(3) + # Try to convert action to enum + try: + action_enum = Action(action.upper()) + final_action = action_enum + except ValueError: + final_action = action + conditions_str = logic_rule_match.group(2) + conditions = ClashRuleParser._parse_logic_conditions(conditions_str) + + return LogicRule( + logic_type=logic_type, + conditions=conditions, + action=final_action, + raw_rule=line + ) + + @staticmethod + def _parse_logic_conditions(conditions_str: str) -> List[ClashRule]: + """Parse conditions within logic rules""" + conditions = [] + + # Simple parser for conditions like (DOMAIN,baidu.com),(NETWORK,UDP) + # This is a basic implementation - more complex nested logic would need a proper parser + condition_pattern = r'\(([^,]+),([^)]+)\)' + matches = re.findall(condition_pattern, conditions_str) + + for rule_type_str, payload in matches: + try: + rule_type = RuleType(rule_type_str.upper()) + condition = ClashRule( + rule_type=rule_type, + payload=payload, + action="", # Logic conditions don't have actions + raw_rule=f"{rule_type_str},{payload}" + ) + conditions.append(condition) + except ValueError: + print(f"Unknown rule type in logic condition: {rule_type_str}") + + return conditions + + def parse_rules(self, rules_text: str) -> List[Union[ClashRule, LogicRule, MatchRule]]: + """Parse multiple rules from text, preserving order and priority""" + self.rules = [] + lines = rules_text.strip().split('\n') + priority = 0 + + for line in lines: + rule = self.parse_rule_line(line) + if rule: + rule.priority = priority # Assign priority based on position + self.rules.append(rule) + priority += 1 + + return self.rules + + def parse_rules_from_list(self, rules_list: List[str]) -> List[Union[ClashRule, LogicRule, MatchRule]]: + """Parse rules from a list of rule strings, preserving order and priority""" + self.rules = [] + + for priority, rule_str in enumerate(rules_list): + rule = self.parse_rule_line(rule_str) + if rule: + rule.priority = priority # Assign priority based on list position + self.rules.append(rule) + + return self.rules + + @staticmethod + def validate_rule(rule: ClashRule) -> bool: + """Validate a parsed rule""" + try: + # Basic validation based on rule type + if rule.rule_type in [RuleType.IP_CIDR, RuleType.IP_CIDR6]: + # Validate CIDR format + return '/' in rule.payload + + elif rule.rule_type == RuleType.DST_PORT or rule.rule_type == RuleType.SRC_PORT: + # Validate port number/range + return rule.payload.isdigit() or '-' in rule.payload + + elif rule.rule_type == RuleType.NETWORK: + # Validate network type + return rule.payload.lower() in ['tcp', 'udp'] + + elif rule.rule_type == RuleType.DOMAIN_REGEX or rule.rule_type == RuleType.PROCESS_PATH_REGEX: + # Try to compile regex + re.compile(rule.payload) + return True + + return True + + except Exception as e: + print(f"Invalid rule '{rule.raw_rule}': {e}") + return False + + def to_string(self) -> List[str]: + result = [] + for rule in self.rules: + result.append(rule.raw_rule) + return result + + def to_dict(self) -> List[Dict[str, Any]]: + """Convert parsed rules to dictionary format""" + result = [] + + for rule in self.rules: + if isinstance(rule, ClashRule): + rule_dict = { + 'type': rule.rule_type.value, + 'payload': rule.payload, + 'action': rule.action.value if isinstance(rule.action, Action) else rule.action, + 'additional_params': rule.additional_params, + 'priority': rule.priority, + 'raw': rule.raw_rule + } + result.append(rule_dict) + + elif isinstance(rule, LogicRule): + conditions_dict = [] + for condition in rule.conditions: + if isinstance(condition, ClashRule): + conditions_dict.append({ + 'type': condition.rule_type.value, + 'payload': condition.payload + }) + + rule_dict = { + 'type': rule.logic_type.value, + 'conditions': conditions_dict, + 'action': rule.action.value if isinstance(rule.action, Action) else rule.action, + 'priority': rule.priority, + 'raw': rule.raw_rule + } + result.append(rule_dict) + elif isinstance(rule, MatchRule): + rule_dict = { + 'type': 'MATCH', + 'action': rule.action.value if isinstance(rule.action, Action) else rule.action, + 'priority': rule.priority, + 'raw': rule.raw_rule + } + result.append(rule_dict) + return result + + def get_rules_by_priority(self) -> List[Union[ClashRule, LogicRule, MatchRule]]: + """Get rules sorted by priority (highest priority first)""" + return sorted(self.rules, key=lambda rule: rule.priority) + + def append_rule(self, rule: Union[ClashRule, LogicRule, MatchRule]) -> None: + max_priority = max(rule.priority for rule in self.rules) if len(self.rules) else 0 + rule.priority = max_priority + 1 + self.rules.append(rule) + # Re-sort rules to maintain order + self.rules.sort(key=lambda r: r.priority) + + def insert_rule_at_priority(self, rule: Union[ClashRule, LogicRule, MatchRule], priority: int): + """Insert a rule at a specific priority position, adjusting other rules""" + # Adjust priorities of existing rules + for existing_rule in self.rules: + if existing_rule.priority >= priority: + existing_rule.priority += 1 + rule.priority = priority + self.rules.append(rule) + + # Re-sort rules to maintain order + self.rules.sort(key=lambda r: r.priority) + + def update_rule_at_priority(self, clash_rule: Union[ClashRule, LogicRule], priority: int) -> bool: + for index, existing_rule in enumerate(self.rules): + if existing_rule.priority == priority: + self.rules[index] = clash_rule + self.rules[index].priority = priority + return True + return False + + def remove_rule_at_priority(self, priority: int) -> Optional[Union[ClashRule, LogicRule, MatchRule]]: + """Remove rule at specific priority and adjust remaining priorities""" + rule_to_remove = None + for rule in self.rules: + if rule.priority == priority: + rule_to_remove = rule + break + + if rule_to_remove: + self.rules.remove(rule_to_remove) + + # Adjust priorities of remaining rules + for rule in self.rules: + if rule.priority > priority: + rule.priority -= 1 + + return rule_to_remove + return None + + def remove_rules(self, condition: Callable[[Union[ClashRule, LogicRule, MatchRule]], bool]): + """Remove rules by lambda""" + i = 0 + while i < len(self.rules): + if condition(self.rules[i]): + priority = self.rules[i].priority + for rule in self.rules: + if rule.priority > priority: + rule.priority -= 1 + del self.rules[i] + else: + i += 1 + + def move_rule_priority(self, from_priority: int, to_priority: int) -> bool: + """Move a rule from one priority position to another""" + rule_to_move = None + for rule in self.rules: + if rule.priority == from_priority: + rule_to_move = rule + break + + if not rule_to_move: + return False + + # Remove rule temporarily + self.remove_rule_at_priority(from_priority) + + # Insert at new priority + self.insert_rule_at_priority(rule_to_move, to_priority) + + return True + + def filter_rules_by_type(self, rule_type: RuleType) -> List[ClashRule]: + """Filter rules by type""" + return [rule for rule in self.rules + if isinstance(rule, ClashRule) and rule.rule_type == rule_type] + + def filter_rules_by_action(self, action: Union[Action, str]) -> List[Union[ClashRule, LogicRule, MatchRule]]: + """Filter rules by action""" + return [rule for rule in self.rules if rule.action == action] + + def has_rule(self, clash_rule: Union[ClashRule, LogicRule, MatchRule]) -> bool: + for rule in self.rules: + if rule.rule_type == clash_rule.rule_type and rule.action == clash_rule.action \ + and rule.payload == clash_rule.payload: + return True + return False + + def reorder_rules( + self, + moved_rule_priority: int, + target_priority: int, + ): + """ + Reorder rules + + :param moved_rule_priority: 被移动规则的原始优先级 + :param target_priority: 目标位置的优先级 + """ + moved_index = next(i for i, r in enumerate(self.rules) if r.priority == moved_rule_priority) + target_index = next( + (i for i, r in enumerate(self.rules) if r.priority == target_priority), + len(self.rules) + ) + # 直接修改被移动规则的优先级 + moved_rule = self.rules[moved_index] + moved_rule.priority = target_priority + + if moved_index < target_index: + # 向后移动:原位置到目标位置之间的规则优先级 -1 + for i in range(moved_index + 1, target_index + 1): + self.rules[i].priority -= 1 + elif moved_index > target_index: + # 向前移动:目标位置到原位置之间的规则优先级 +1 + for i in range(target_index, moved_index): + self.rules[i].priority += 1 + self.rules.sort(key=lambda x: x.priority) diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-C3BpNVeC.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-C3BpNVeC.js new file mode 100644 index 0000000..786195d --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-C3BpNVeC.js @@ -0,0 +1,828 @@ +import { importShared } from './__federation_fn_import-JrT3xvdd.js'; +import { _ as _export_sfc } from './_plugin-vue_export-helper-pcqpp-6-.js'; + +const {createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,createVNode:_createVNode,toDisplayString:_toDisplayString,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createElementVNode:_createElementVNode,mergeProps:_mergeProps,withModifiers:_withModifiers,createElementBlock:_createElementBlock} = await importShared('vue'); + + +const _hoisted_1 = { class: "plugin-config" }; +const _hoisted_2 = { class: "d-flex align-center" }; +const _hoisted_3 = { class: "font-weight-medium" }; +const _hoisted_4 = { class: "text-body-2" }; + +const {ref,reactive,onMounted,computed} = await importShared('vue'); + + +// Props + +const _sfc_main = { + __name: 'Config', + props: { + initialConfig: { + type: Object, + default: () => ({}), + }, + api: { + type: Object, + default: () => { + }, + }, +}, + emits: ['save', 'close'], + setup(__props, { emit: __emit }) { + +const props = __props; + +// 状态变量 +const form = ref(null); +const isFormValid = ref(true); +const error = ref(null); +const saving = ref(false); +const testing = ref(false); +const showClashSecret = ref(false); +const selectedCronOption = ref('6hours'); + +// Test result state +const testResult = reactive({ + show: false, + success: false, + title: '', + message: '' +}); + +// Cron 选项 +const cronOptions = [ + {text: '每5分钟', value: '5min', cron: '*/5 * * * *'}, + {text: '每15分钟', value: '15min', cron: '*/15 * * * *'}, + {text: '每30分钟', value: '30min', cron: '*/30 * * * *'}, + {text: '每小时', value: '1hour', cron: '0 * * * *'}, + {text: '每2小时', value: '2hours', cron: '0 */2 * * *'}, + {text: '每6小时', value: '6hours', cron: '0 */6 * * *'}, + {text: '每12小时', value: '12hours', cron: '0 */12 * * *'}, + {text: '每天', value: '1day', cron: '0 0 * * *'}, + {text: '自定义', value: 'custom', cron: ''}, +]; + +// 默认配置 +const defaultConfig = { + enabled: false, + sub_links: [], + filter_keywords: ["公益性", "高延迟", "域名", "官网", "重启", "过期时间", "系统代理"], + clash_dashboard_url: '', + clash_dashboard_secret: '', + movie_pilot_url: '', + cron_string: '0 */6 * * *', + timeout: 10, + retry_times: 3, + proxy: false, + notify: false, + auto_update_subscriptions: true, + ruleset_prefix: '📂<-', +}; + +// 响应式配置对象 +const config = reactive({...defaultConfig}); + +// 自定义事件 +const emit = __emit; + +// 初始化 +onMounted(() => { + if (props.initialConfig) { + Object.keys(props.initialConfig).forEach(key => { + if (key in config) { + config[key] = props.initialConfig[key]; + } + }); + + // 设置对应的cron选项 + const cronOption = cronOptions.find(option => option.cron === config.cron_string); + if (cronOption) { + selectedCronOption.value = cronOption.value; + } else { + selectedCronOption.value = 'custom'; + } + } +}); + +// 验证函数 +function isValidUrl(url) { + try { + new URL(url); + return true + } catch { + return false + } +} + +function validateSubLinks(links) { + if (!links || links.length === 0) { + return '至少需要一个订阅链接' + } + + for (const link of links) { + if (!isValidUrl(link)) { + return `无效的订阅链接: ${link}` + } + } + return true +} + +function validateCronExpression(cronStr) { + if (!cronStr) return '请输入Cron表达式' + + // 简单的cron表达式验证 + const parts = cronStr.trim().split(/\s+/); + if (parts.length !== 5) { + return 'Cron表达式应包含5个部分 (分 时 日 月 周)' + } + return true +} + +// 更新cron字符串 +function updateCronString(optionValue) { + const option = cronOptions.find(opt => opt.value === optionValue); + if (option && option.cron) { + config.cron_string = option.cron; + } +} + +// 测试连接 +async function testConnection() { + testing.value = true; + error.value = null; + testResult.show = false; + + try { + // 验证必需的参数 + if (!config.clash_dashboard_url) { + throw new Error('请先配置 Clash 面板 URL') + } + if (!config.clash_dashboard_secret) { + throw new Error('请先配置 Clash 面板密钥') + } + if (!config.sub_links || config.sub_links.length === 0) { + throw new Error('请先配置至少一个订阅链接') + } + if (!config.movie_pilot_url || config.movie_pilot_url.length === 0) { + throw new Error('请先MoviePilot链接') + } + // 准备API请求参数 + const testParams = { + clash_dashboard_url: config.clash_dashboard_url, + clash_dashboard_secret: config.clash_dashboard_secret, + sub_link: config.sub_links[0] // 使用第一个订阅链接进行测试 + }; + + // 调用API进行连接测试 + const result = await props.api.post('/plugin/ClashRuleProvider/connectivity', testParams); + + // 根据返回结果显示相应消息 + if (result.success) { + testResult.success = true; + testResult.title = '连接测试成功!'; + testResult.message = 'Clash面板和订阅链接连接正常,配置验证通过'; + testResult.show = true; + + // Auto hide after 5 seconds + setTimeout(() => { + testResult.show = false; + }, 5000); + } else { + throw new Error(result.message || '连接测试失败,请检查配置') + } + + } catch (err) { + console.error('连接测试失败:', err); + testResult.success = false; + testResult.title = '连接测试失败'; + testResult.message = err.message; + testResult.show = true; + } finally { + testing.value = false; + } +} + +// 保存配置 +async function saveConfig() { + if (!isFormValid.value) { + error.value = '请修正表单中的错误'; + return + } + + saving.value = true; + error.value = null; + + try { + await new Promise(resolve => setTimeout(resolve, 1000)); + emit('save', {...config}); + } catch (err) { + console.error('保存配置失败:', err); + error.value = err.message || '保存配置失败'; + } finally { + saving.value = false; + } +} + +function extractDomain(url) { + try { + const domain = new URL(url).hostname; + return domain.startsWith('www.') ? domain.substring(4) : domain + } catch { + return url // 如果解析失败,返回原始URL + } +} + +// 重置表单 +function resetForm() { + Object.keys(defaultConfig).forEach(key => { + config[key] = defaultConfig[key]; + }); + selectedCronOption.value = '6hours'; + + if (form.value) { + form.value.resetValidation(); + } +} + +// 关闭组件 +function notifyClose() { + emit('close'); +} + +// 通知主应用切换到Page页面 +function notifySwitch() { + emit('switch'); +} + +return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent("v-card-title"); + const _component_v_icon = _resolveComponent("v-icon"); + const _component_v_btn = _resolveComponent("v-btn"); + const _component_v_card_item = _resolveComponent("v-card-item"); + const _component_v_alert = _resolveComponent("v-alert"); + const _component_v_switch = _resolveComponent("v-switch"); + const _component_v_col = _resolveComponent("v-col"); + const _component_v_row = _resolveComponent("v-row"); + const _component_v_chip = _resolveComponent("v-chip"); + const _component_v_combobox = _resolveComponent("v-combobox"); + const _component_v_text_field = _resolveComponent("v-text-field"); + const _component_v_select = _resolveComponent("v-select"); + const _component_v_expansion_panel_title = _resolveComponent("v-expansion-panel-title"); + const _component_v_expansion_panel_text = _resolveComponent("v-expansion-panel-text"); + const _component_v_expansion_panel = _resolveComponent("v-expansion-panel"); + const _component_v_expansion_panels = _resolveComponent("v-expansion-panels"); + const _component_v_form = _resolveComponent("v-form"); + const _component_v_card_text = _resolveComponent("v-card-text"); + const _component_v_spacer = _resolveComponent("v-spacer"); + const _component_v_card_actions = _resolveComponent("v-card-actions"); + const _component_v_card = _resolveComponent("v-card"); + + return (_openBlock(), _createElementBlock("div", _hoisted_1, [ + _createVNode(_component_v_card, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_item, null, { + append: _withCtx(() => [ + _createVNode(_component_v_btn, { + icon: "", + color: "primary", + variant: "text", + onClick: notifyClose + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[18] || (_cache[18] = [ + _createTextVNode("mdi-close") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => _cache[17] || (_cache[17] = [ + _createTextVNode("Clash Rule Provider 插件配置") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, { class: "overflow-y-auto" }, { + default: _withCtx(() => [ + (error.value) + ? (_openBlock(), _createBlock(_component_v_alert, { + key: 0, + type: "error", + class: "mb-4" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(error.value), 1) + ]), + _: 1 + })) + : _createCommentVNode("", true), + _createVNode(_component_v_form, { + ref_key: "form", + ref: form, + modelValue: isFormValid.value, + "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ((isFormValid).value = $event)), + onSubmit: _withModifiers(saveConfig, ["prevent"]) + }, { + default: _withCtx(() => [ + _cache[28] || (_cache[28] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "基本设置", -1)), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.enabled, + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((config.enabled) = $event)), + label: "启用插件", + color: "primary", + inset: "", + hint: "启用后插件将开始监控和同步", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.proxy, + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ((config.proxy) = $event)), + label: "启用代理", + color: "primary", + inset: "", + hint: "是否使用系统代理进行网络请求", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.notify, + "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ((config.notify) = $event)), + label: "启用通知", + color: "primary", + inset: "", + hint: "执行完成后发送通知消息", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _cache[29] || (_cache[29] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "订阅配置", -1)), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_combobox, { + modelValue: config.sub_links, + "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((config.sub_links) = $event)), + label: "订阅链接", + variant: "outlined", + multiple: "", + chips: "", + "closable-chips": "", + hint: "添加一个Clash订阅链接", + "persistent-hint": "", + rules: [validateSubLinks] + }, { + chip: _withCtx(({ props, item }) => [ + _createVNode(_component_v_chip, _mergeProps(props, { + closable: "", + size: "small" + }), { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(extractDomain(item.value)), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_combobox, { + modelValue: config.filter_keywords, + "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ((config.filter_keywords) = $event)), + label: "节点过滤关键词", + variant: "outlined", + multiple: "", + chips: "", + "closable-chips": "", + hint: "添加用于过滤节点的关键词", + "persistent-hint": "" + }, { + chip: _withCtx(({ props, item }) => [ + _createVNode(_component_v_chip, _mergeProps(props, { + closable: "", + size: "small", + color: "info" + }), { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(item.value), 1) + ]), + _: 2 + }, 1040) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _cache[30] || (_cache[30] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "Clash 面板设置", -1)), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.clash_dashboard_url, + "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((config.clash_dashboard_url) = $event)), + label: "Clash 面板 URL", + variant: "outlined", + placeholder: "http://localhost:9090", + hint: "Clash 控制面板的访问地址", + "persistent-hint": "", + rules: [v => !v || isValidUrl(v) || '请输入有效的URL地址'] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "primary" }, { + default: _withCtx(() => _cache[19] || (_cache[19] = [ + _createTextVNode("mdi-web") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.clash_dashboard_secret, + "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((config.clash_dashboard_secret) = $event)), + label: "Clash 面板密钥", + variant: "outlined", + placeholder: "your-clash-secret", + hint: "用于访问Clash API的密钥", + "persistent-hint": "", + "append-inner-icon": showClashSecret.value ? 'mdi-eye-off' : 'mdi-eye', + type: showClashSecret.value ? 'text' : 'password', + "onClick:appendInner": _cache[7] || (_cache[7] = $event => (showClashSecret.value = !showClashSecret.value)) + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "warning" }, { + default: _withCtx(() => _cache[20] || (_cache[20] = [ + _createTextVNode("mdi-key") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "append-inner-icon", "type"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _cache[31] || (_cache[31] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "MoviePilot 设置", -1)), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.movie_pilot_url, + "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((config.movie_pilot_url) = $event)), + label: "MoviePilot URL", + variant: "outlined", + placeholder: "http://localhost:3001", + hint: "MoviePilot 服务的访问地址", + "persistent-hint": "", + rules: [v => !!v || 'MoviePilot URL不能为空', v => isValidUrl(v) || '请输入有效的URL地址'] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "success" }, { + default: _withCtx(() => _cache[21] || (_cache[21] = [ + _createTextVNode("mdi-movie") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _cache[32] || (_cache[32] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "执行设置", -1)), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_select, { + modelValue: selectedCronOption.value, + "onUpdate:modelValue": [ + _cache[9] || (_cache[9] = $event => ((selectedCronOption).value = $event)), + updateCronString + ], + label: "执行周期", + items: cronOptions, + variant: "outlined", + "item-title": "text", + "item-value": "value", + hint: "选择插件执行的时间间隔", + "persistent-hint": "" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + (selectedCronOption.value === 'custom') + ? (_openBlock(), _createBlock(_component_v_col, { + key: 0, + cols: "12" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.cron_string, + "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((config.cron_string) = $event)), + label: "自定义 Cron 表达式", + variant: "outlined", + placeholder: "0 */6 * * *", + hint: "使用标准Cron表达式格式 (分 时 日 月 周)", + "persistent-hint": "", + rules: [validateCronExpression] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[22] || (_cache[22] = [ + _createTextVNode("mdi-clock-outline") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + })) + : _createCommentVNode("", true), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.timeout, + "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((config.timeout) = $event)), + modelModifiers: { number: true }, + label: "超时时间 (秒)", + variant: "outlined", + type: "number", + min: "1", + max: "300", + hint: "请求的超时时间", + "persistent-hint": "", + rules: [v => v > 0 || '超时时间必须大于0'] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "warning" }, { + default: _withCtx(() => _cache[23] || (_cache[23] = [ + _createTextVNode("mdi-timer") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { + cols: "12", + md: "6" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.retry_times, + "onUpdate:modelValue": _cache[12] || (_cache[12] = $event => ((config.retry_times) = $event)), + modelModifiers: { number: true }, + label: "重试次数", + variant: "outlined", + type: "number", + min: "0", + max: "10", + hint: "失败时的重试次数", + "persistent-hint": "", + rules: [v => v >= 0 || '重试次数不能为负数'] + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[24] || (_cache[24] = [ + _createTextVNode("mdi-refresh") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue", "rules"]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_expansion_panels, { + variant: "accordion", + class: "mt-4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_expansion_panel, null, { + default: _withCtx(() => [ + _createVNode(_component_v_expansion_panel_title, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { class: "mr-2" }, { + default: _withCtx(() => _cache[25] || (_cache[25] = [ + _createTextVNode("mdi-cog") + ])), + _: 1 + }), + _cache[26] || (_cache[26] = _createTextVNode(" 高级选项 ")) + ]), + _: 1 + }), + _createVNode(_component_v_expansion_panel_text, null, { + default: _withCtx(() => [ + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_switch, { + modelValue: config.auto_update_subscriptions, + "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((config.auto_update_subscriptions) = $event)), + label: "自动更新订阅", + color: "primary", + inset: "", + hint: "定期自动更新Clash订阅配置" + }, null, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_col, { cols: "12" }, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: config.ruleset_prefix, + "onUpdate:modelValue": _cache[14] || (_cache[14] = $event => ((config.ruleset_prefix) = $event)), + label: "规则集前缀", + variant: "outlined", + placeholder: "📂<-", + hint: "为生成的规则集添加前缀", + "persistent-hint": "" + }, { + "prepend-inner": _withCtx(() => [ + _createVNode(_component_v_icon, { color: "info" }, { + default: _withCtx(() => _cache[27] || (_cache[27] = [ + _createTextVNode("mdi-prefix") + ])), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ]), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_btn, { + color: "primary", + onClick: notifySwitch + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[33] || (_cache[33] = [ + _createTextVNode("mdi-view-dashboard-edit") + ])), + _: 1 + }), + _cache[34] || (_cache[34] = _createTextVNode(" 规则 ")) + ]), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "secondary", + onClick: resetForm + }, { + default: _withCtx(() => _cache[35] || (_cache[35] = [ + _createTextVNode("重置") + ])), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "info", + onClick: testConnection, + loading: testing.value + }, { + default: _withCtx(() => _cache[36] || (_cache[36] = [ + _createTextVNode("测试连接") + ])), + _: 1 + }, 8, ["loading"]), + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "primary", + disabled: !isFormValid.value, + onClick: saveConfig, + loading: saving.value + }, { + default: _withCtx(() => _cache[37] || (_cache[37] = [ + _createTextVNode(" 保存配置 ") + ])), + _: 1 + }, 8, ["disabled", "loading"]) + ]), + _: 1 + }), + (testResult.show) + ? (_openBlock(), _createBlock(_component_v_alert, { + key: 0, + type: testResult.success ? 'success' : 'error', + variant: "tonal", + closable: "", + class: "ma-4 mt-0", + "onClick:close": _cache[16] || (_cache[16] = $event => (testResult.show = false)) + }, { + default: _withCtx(() => [ + _createElementVNode("div", _hoisted_2, [ + _createVNode(_component_v_icon, { class: "mr-2" }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(testResult.success ? 'mdi-check-circle' : 'mdi-alert-circle'), 1) + ]), + _: 1 + }), + _createElementVNode("div", null, [ + _createElementVNode("div", _hoisted_3, _toDisplayString(testResult.title), 1), + _createElementVNode("div", _hoisted_4, _toDisplayString(testResult.message), 1) + ]) + ]) + ]), + _: 1 + }, 8, ["type"])) + : _createCommentVNode("", true) + ]), + _: 1 + }) + ])) +} +} + +}; +const ConfigComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-0e64dae0"]]); + +export { ConfigComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-DXzIavcD.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-DXzIavcD.css new file mode 100644 index 0000000..aadcf97 --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Config-DXzIavcD.css @@ -0,0 +1,5 @@ + +.plugin-config[data-v-0e64dae0] { + max-width: 800px; + margin: 0 auto; +} diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-BkyO-3pr.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-BkyO-3pr.js new file mode 100644 index 0000000..ef6abb8 --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Dashboard-BkyO-3pr.js @@ -0,0 +1,40514 @@ +import { importShared } from './__federation_fn_import-JrT3xvdd.js'; +import { f as defineComponent, A as h, bC as shallowRef, bF as toRefs, a5 as computed$1, g as getCurrentInstance, w as watch, b6 as watchEffect, j as onMounted$1, aD as onBeforeUnmount, n as nextTick, au as inject, br as isRef, u as unref } from './runtime-core.esm-bundler-BQhfUSSX.js'; + +await importShared('vue'); + +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var Browser = (function () { + function Browser() { + this.firefox = false; + this.ie = false; + this.edge = false; + this.newEdge = false; + this.weChat = false; + } + return Browser; +}()); +var Env = (function () { + function Env() { + this.browser = new Browser(); + this.node = false; + this.wxa = false; + this.worker = false; + this.svgSupported = false; + this.touchEventsSupported = false; + this.pointerEventsSupported = false; + this.domSupported = false; + this.transformSupported = false; + this.transform3dSupported = false; + this.hasGlobalWindow = typeof window !== 'undefined'; + } + return Env; +}()); +var env = new Env(); +if (typeof wx === 'object' && typeof wx.getSystemInfoSync === 'function') { + env.wxa = true; + env.touchEventsSupported = true; +} +else if (typeof document === 'undefined' && typeof self !== 'undefined') { + env.worker = true; +} +else if (!env.hasGlobalWindow || 'Deno' in window) { + env.node = true; + env.svgSupported = true; +} +else { + detect(navigator.userAgent, env); +} +function detect(ua, env) { + var browser = env.browser; + var firefox = ua.match(/Firefox\/([\d.]+)/); + var ie = ua.match(/MSIE\s([\d.]+)/) + || ua.match(/Trident\/.+?rv:(([\d.]+))/); + var edge = ua.match(/Edge?\/([\d.]+)/); + var weChat = (/micromessenger/i).test(ua); + if (firefox) { + browser.firefox = true; + browser.version = firefox[1]; + } + if (ie) { + browser.ie = true; + browser.version = ie[1]; + } + if (edge) { + browser.edge = true; + browser.version = edge[1]; + browser.newEdge = +edge[1].split('.')[0] > 18; + } + if (weChat) { + browser.weChat = true; + } + env.svgSupported = typeof SVGRect !== 'undefined'; + env.touchEventsSupported = 'ontouchstart' in window && !browser.ie && !browser.edge; + env.pointerEventsSupported = 'onpointerdown' in window + && (browser.edge || (browser.ie && +browser.version >= 11)); + env.domSupported = typeof document !== 'undefined'; + var style = document.documentElement.style; + env.transform3dSupported = ((browser.ie && 'transition' in style) + || browser.edge + || (('WebKitCSSMatrix' in window) && ('m11' in new WebKitCSSMatrix())) + || 'MozPerspective' in style) + && !('OTransition' in style); + env.transformSupported = env.transform3dSupported + || (browser.ie && +browser.version >= 9); +} + +var DEFAULT_FONT_SIZE = 12; +var DEFAULT_FONT_FAMILY = 'sans-serif'; +var DEFAULT_FONT = DEFAULT_FONT_SIZE + "px " + DEFAULT_FONT_FAMILY; +var OFFSET = 20; +var SCALE = 100; +var defaultWidthMapStr = "007LLmW'55;N0500LLLLLLLLLL00NNNLzWW\\\\WQb\\0FWLg\\bWb\\WQ\\WrWWQ000CL5LLFLL0LL**F*gLLLL5F0LF\\FFF5.5N"; +function getTextWidthMap(mapStr) { + var map = {}; + if (typeof JSON === 'undefined') { + return map; + } + for (var i = 0; i < mapStr.length; i++) { + var char = String.fromCharCode(i + 32); + var size = (mapStr.charCodeAt(i) - OFFSET) / SCALE; + map[char] = size; + } + return map; +} +var DEFAULT_TEXT_WIDTH_MAP = getTextWidthMap(defaultWidthMapStr); +var platformApi = { + createCanvas: function () { + return typeof document !== 'undefined' + && document.createElement('canvas'); + }, + measureText: (function () { + var _ctx; + var _cachedFont; + return function (text, font) { + if (!_ctx) { + var canvas = platformApi.createCanvas(); + _ctx = canvas && canvas.getContext('2d'); + } + if (_ctx) { + if (_cachedFont !== font) { + _cachedFont = _ctx.font = font || DEFAULT_FONT; + } + return _ctx.measureText(text); + } + else { + text = text || ''; + font = font || DEFAULT_FONT; + var res = /((?:\d+)?\.?\d*)px/.exec(font); + var fontSize = res && +res[1] || DEFAULT_FONT_SIZE; + var width = 0; + if (font.indexOf('mono') >= 0) { + width = fontSize * text.length; + } + else { + for (var i = 0; i < text.length; i++) { + var preCalcWidth = DEFAULT_TEXT_WIDTH_MAP[text[i]]; + width += preCalcWidth == null ? fontSize : (preCalcWidth * fontSize); + } + } + return { width: width }; + } + }; + })(), + loadImage: function (src, onload, onerror) { + var image = new Image(); + image.onload = onload; + image.onerror = onerror; + image.src = src; + return image; + } +}; + +var BUILTIN_OBJECT = reduce([ + 'Function', + 'RegExp', + 'Date', + 'Error', + 'CanvasGradient', + 'CanvasPattern', + 'Image', + 'Canvas' +], function (obj, val) { + obj['[object ' + val + ']'] = true; + return obj; +}, {}); +var TYPED_ARRAY = reduce([ + 'Int8', + 'Uint8', + 'Uint8Clamped', + 'Int16', + 'Uint16', + 'Int32', + 'Uint32', + 'Float32', + 'Float64' +], function (obj, val) { + obj['[object ' + val + 'Array]'] = true; + return obj; +}, {}); +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 ctorFunction = function () { }.constructor; +var protoFunction = ctorFunction ? ctorFunction.prototype : null; +var protoKey = '__proto__'; +var idStart = 0x0907; +function guid() { + return idStart++; +} +function logError() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + if (typeof console !== 'undefined') { + console.error.apply(console, args); + } +} +function clone$2(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$2(source[i]); + } + } + } + else if (TYPED_ARRAY[typeStr]) { + if (!isPrimitive(source)) { + var Ctor = source.constructor; + if (Ctor.from) { + result = Ctor.from(source); + } + else { + result = new Ctor(source.length); + for (var i = 0, len = source.length; i < len; i++) { + result[i] = source[i]; + } + } + } + } + else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) { + result = {}; + for (var key in source) { + if (source.hasOwnProperty(key) && key !== protoKey) { + result[key] = clone$2(source[key]); + } + } + } + return result; +} +function merge(target, source, overwrite) { + if (!isObject$2(source) || !isObject$2(target)) { + return overwrite ? clone$2(source) : target; + } + for (var key in source) { + if (source.hasOwnProperty(key) && key !== protoKey) { + var targetProp = target[key]; + var sourceProp = source[key]; + if (isObject$2(sourceProp) + && isObject$2(targetProp) + && !isArray(sourceProp) + && !isArray(targetProp) + && !isDom(sourceProp) + && !isDom(targetProp) + && !isBuiltInObject(sourceProp) + && !isBuiltInObject(targetProp) + && !isPrimitive(sourceProp) + && !isPrimitive(targetProp)) { + merge(targetProp, sourceProp, overwrite); + } + else if (overwrite || !(key in target)) { + target[key] = clone$2(source[key]); + } + } + } + return target; +} +function extend(target, source) { + if (Object.assign) { + Object.assign(target, source); + } + else { + for (var key in source) { + if (source.hasOwnProperty(key) && key !== protoKey) { + target[key] = source[key]; + } + } + } + return target; +} +function defaults(target, source, overlay) { + var keysArr = keys(source); + for (var i = 0, len = keysArr.length; i < len; i++) { + var key = keysArr[i]; + if ((target[key] == null)) { + target[key] = source[key]; + } + } + return target; +} +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; +} +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; +} +function mixin(target, source, override) { + target = 'prototype' in target ? target.prototype : target; + source = 'prototype' in source ? source.prototype : source; + if (Object.getOwnPropertyNames) { + var keyList = Object.getOwnPropertyNames(source); + for (var i = 0; i < keyList.length; i++) { + var key = keyList[i]; + if (key !== 'constructor') { + if ((target[key] == null)) { + target[key] = source[key]; + } + } + } + } + else { + defaults(target, source); + } +} +function isArrayLike(data) { + if (!data) { + return false; + } + if (typeof data === 'string') { + return false; + } + return typeof data.length === 'number'; +} +function each$4(arr, cb, context) { + if (!(arr && cb)) { + return; + } + if (arr.forEach && arr.forEach === nativeForEach) { + arr.forEach(cb, context); + } + else if (arr.length === +arr.length) { + for (var i = 0, len = arr.length; i < len; i++) { + cb.call(context, arr[i], i, arr); + } + } + else { + for (var key in arr) { + if (arr.hasOwnProperty(key)) { + cb.call(context, arr[key], key, arr); + } + } + } +} +function map$1(arr, cb, context) { + if (!arr) { + return []; + } + if (!cb) { + return slice(arr); + } + if (arr.map && arr.map === nativeMap) { + return arr.map(cb, context); + } + else { + var result = []; + for (var i = 0, len = arr.length; i < len; i++) { + result.push(cb.call(context, arr[i], i, arr)); + } + return result; + } +} +function reduce(arr, cb, memo, context) { + if (!(arr && cb)) { + return; + } + for (var i = 0, len = arr.length; i < len; i++) { + memo = cb.call(context, memo, arr[i], i, arr); + } + return memo; +} +function filter(arr, cb, context) { + if (!arr) { + return []; + } + if (!cb) { + return slice(arr); + } + if (arr.filter && arr.filter === nativeFilter) { + return arr.filter(cb, context); + } + else { + var result = []; + for (var i = 0, len = arr.length; i < len; i++) { + if (cb.call(context, arr[i], i, arr)) { + result.push(arr[i]); + } + } + return result; + } +} +function keys(obj) { + if (!obj) { + return []; + } + if (Object.keys) { + return Object.keys(obj); + } + var keyList = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + keyList.push(key); + } + } + return keyList; +} +function bindPolyfill(func, context) { + var args = []; + for (var _i = 2; _i < arguments.length; _i++) { + args[_i - 2] = arguments[_i]; + } + return function () { + return func.apply(context, args.concat(nativeSlice.call(arguments))); + }; +} +var bind$1 = (protoFunction && isFunction(protoFunction.bind)) + ? protoFunction.call.bind(protoFunction.bind) + : bindPolyfill; +function curry$1(func) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + return function () { + return func.apply(this, args.concat(nativeSlice.call(arguments))); + }; +} +function isArray(value) { + if (Array.isArray) { + return Array.isArray(value); + } + return objToString.call(value) === '[object Array]'; +} +function isFunction(value) { + return typeof value === 'function'; +} +function isString(value) { + return typeof value === 'string'; +} +function isStringSafe(value) { + return objToString.call(value) === '[object String]'; +} +function isNumber(value) { + return typeof value === 'number'; +} +function isObject$2(value) { + var type = typeof value; + return type === 'function' || (!!value && type === 'object'); +} +function isBuiltInObject(value) { + return !!BUILTIN_OBJECT[objToString.call(value)]; +} +function isTypedArray(value) { + return !!TYPED_ARRAY[objToString.call(value)]; +} +function isDom(value) { + return typeof value === 'object' + && typeof value.nodeType === 'number' + && typeof value.ownerDocument === 'object'; +} +function isGradientObject(value) { + return value.colorStops != null; +} +function isImagePatternObject(value) { + return value.image != null; +} +function eqNaN(value) { + return value !== value; +} +function retrieve() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + for (var i = 0, len = args.length; i < len; i++) { + if (args[i] != null) { + return args[i]; + } + } +} +function retrieve2(value0, value1) { + return value0 != null + ? value0 + : value1; +} +function retrieve3(value0, value1, value2) { + return value0 != null + ? value0 + : value1 != null + ? value1 + : value2; +} +function slice(arr) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + return nativeSlice.apply(arr, args); +} +function normalizeCssArray$1(val) { + if (typeof (val) === 'number') { + return [val, val, val, val]; + } + var len = val.length; + if (len === 2) { + return [val[0], val[1], val[0], val[1]]; + } + else if (len === 3) { + return [val[0], val[1], val[2], val[1]]; + } + return val; +} +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} +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__'; +function setAsPrimitive(obj) { + obj[primitiveKey] = true; +} +function isPrimitive(obj) { + return obj[primitiveKey]; +} +var MapPolyfill = (function () { + function MapPolyfill() { + this.data = {}; + } + MapPolyfill.prototype["delete"] = function (key) { + var existed = this.has(key); + if (existed) { + delete this.data[key]; + } + return existed; + }; + MapPolyfill.prototype.has = function (key) { + return this.data.hasOwnProperty(key); + }; + MapPolyfill.prototype.get = function (key) { + return this.data[key]; + }; + MapPolyfill.prototype.set = function (key, value) { + this.data[key] = value; + return this; + }; + MapPolyfill.prototype.keys = function () { + return keys(this.data); + }; + MapPolyfill.prototype.forEach = function (callback) { + var data = this.data; + for (var key in data) { + if (data.hasOwnProperty(key)) { + callback(data[key], key); + } + } + }; + return MapPolyfill; +}()); +var isNativeMapSupported = typeof Map === 'function'; +function maybeNativeMap() { + return (isNativeMapSupported ? new Map() : new MapPolyfill()); +} +var HashMap = (function () { + function HashMap(obj) { + var isArr = isArray(obj); + this.data = maybeNativeMap(); + var thisMap = this; + (obj instanceof HashMap) + ? obj.each(visit) + : (obj && each$4(obj, visit)); + function visit(value, key) { + isArr ? thisMap.set(value, key) : thisMap.set(key, value); + } + } + HashMap.prototype.hasKey = function (key) { + return this.data.has(key); + }; + HashMap.prototype.get = function (key) { + return this.data.get(key); + }; + HashMap.prototype.set = function (key, value) { + this.data.set(key, value); + return value; + }; + HashMap.prototype.each = function (cb, context) { + this.data.forEach(function (value, key) { + cb.call(context, value, key); + }); + }; + HashMap.prototype.keys = function () { + var keys = this.data.keys(); + return isNativeMapSupported + ? Array.from(keys) + : keys; + }; + HashMap.prototype.removeKey = function (key) { + this.data["delete"](key); + }; + return HashMap; +}()); +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 (var i = 0; i < b.length; i++) { + newArray[i + offset] = b[i]; + } + return newArray; +} +function createObject(proto, properties) { + var obj; + if (Object.create) { + obj = Object.create(proto); + } + else { + var StyleCtor = function () { }; + StyleCtor.prototype = proto; + obj = new StyleCtor(); + } + if (properties) { + extend(obj, properties); + } + return obj; +} +function disableUserSelect(dom) { + var domStyle = dom.style; + domStyle.webkitUserSelect = 'none'; + domStyle.userSelect = 'none'; + domStyle.webkitTapHighlightColor = 'rgba(0,0,0,0)'; + domStyle['-webkit-touch-callout'] = 'none'; +} +function hasOwn(own, prop) { + return own.hasOwnProperty(prop); +} +function noop() { } +var RADIAN_TO_DEGREE = 180 / Math.PI; + +function create$1(x, y) { + if (x == null) { + x = 0; + } + if (y == null) { + y = 0; + } + return [x, y]; +} +function clone$1(v) { + return [v[0], v[1]]; +} +function add(out, v1, v2) { + out[0] = v1[0] + v2[0]; + out[1] = v1[1] + v2[1]; + return out; +} +function sub(out, v1, v2) { + out[0] = v1[0] - v2[0]; + out[1] = v1[1] - v2[1]; + return out; +} +function len(v) { + return Math.sqrt(lenSquare(v)); +} +function lenSquare(v) { + return v[0] * v[0] + v[1] * v[1]; +} +function scale$2(out, v, s) { + out[0] = v[0] * s; + out[1] = v[1] * s; + return out; +} +function normalize$1(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; +} +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$1 = distance; +function distanceSquare(v1, v2) { + return (v1[0] - v2[0]) * (v1[0] - v2[0]) + + (v1[1] - v2[1]) * (v1[1] - v2[1]); +} +var distSquare = distanceSquare; +function lerp$1(out, v1, v2, t) { + out[0] = v1[0] + t * (v2[0] - v1[0]); + out[1] = v1[1] + t * (v2[1] - v1[1]); + return out; +} +function applyTransform$1(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; +} +function min$1(out, v1, v2) { + out[0] = Math.min(v1[0], v2[0]); + out[1] = Math.min(v1[1], v2[1]); + return out; +} +function max$1(out, v1, v2) { + out[0] = Math.max(v1[0], v2[0]); + out[1] = Math.max(v1[1], v2[1]); + return out; +} + +var Param = (function () { + function Param(target, e) { + this.target = target; + this.topTarget = e && e.topTarget; + } + return Param; +}()); +var Draggable = (function () { + function Draggable(handler) { + this.handler = handler; + handler.on('mousedown', this._dragStart, this); + handler.on('mousemove', this._drag, this); + handler.on('mouseup', this._dragEnd, this); + } + Draggable.prototype._dragStart = function (e) { + var draggingTarget = e.target; + while (draggingTarget && !draggingTarget.draggable) { + draggingTarget = draggingTarget.parent || draggingTarget.__hostTarget; + } + if (draggingTarget) { + this._draggingTarget = draggingTarget; + draggingTarget.dragging = true; + this._x = e.offsetX; + this._y = e.offsetY; + this.handler.dispatchToElement(new Param(draggingTarget, e), 'dragstart', e.event); + } + }; + Draggable.prototype._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.handler.dispatchToElement(new Param(draggingTarget, e), 'drag', e.event); + var dropTarget = this.handler.findHover(x, y, draggingTarget).target; + var lastDropTarget = this._dropTarget; + this._dropTarget = dropTarget; + if (draggingTarget !== dropTarget) { + if (lastDropTarget && dropTarget !== lastDropTarget) { + this.handler.dispatchToElement(new Param(lastDropTarget, e), 'dragleave', e.event); + } + if (dropTarget && dropTarget !== lastDropTarget) { + this.handler.dispatchToElement(new Param(dropTarget, e), 'dragenter', e.event); + } + } + } + }; + Draggable.prototype._dragEnd = function (e) { + var draggingTarget = this._draggingTarget; + if (draggingTarget) { + draggingTarget.dragging = false; + } + this.handler.dispatchToElement(new Param(draggingTarget, e), 'dragend', e.event); + if (this._dropTarget) { + this.handler.dispatchToElement(new Param(this._dropTarget, e), 'drop', e.event); + } + this._draggingTarget = null; + this._dropTarget = null; + }; + return Draggable; +}()); + +var Eventful = (function () { + function Eventful(eventProcessors) { + if (eventProcessors) { + this._$eventProcessor = eventProcessors; + } + } + Eventful.prototype.on = function (event, query, handler, context) { + if (!this._$handlers) { + this._$handlers = {}; + } + var _h = this._$handlers; + if (typeof query === 'function') { + context = handler; + handler = query; + query = null; + } + if (!handler || !event) { + return this; + } + var eventProcessor = this._$eventProcessor; + if (query != null && eventProcessor && eventProcessor.normalizeQuery) { + query = eventProcessor.normalizeQuery(query); + } + if (!_h[event]) { + _h[event] = []; + } + for (var i = 0; i < _h[event].length; i++) { + if (_h[event][i].h === handler) { + return this; + } + } + var wrap = { + h: handler, + query: query, + ctx: (context || this), + 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 this; + }; + Eventful.prototype.isSilent = function (eventName) { + var _h = this._$handlers; + return !_h || !_h[eventName] || !_h[eventName].length; + }; + Eventful.prototype.off = function (eventType, handler) { + var _h = this._$handlers; + if (!_h) { + return this; + } + if (!eventType) { + this._$handlers = {}; + return this; + } + if (handler) { + if (_h[eventType]) { + var newList = []; + for (var i = 0, l = _h[eventType].length; i < l; i++) { + if (_h[eventType][i].h !== handler) { + newList.push(_h[eventType][i]); + } + } + _h[eventType] = newList; + } + if (_h[eventType] && _h[eventType].length === 0) { + delete _h[eventType]; + } + } + else { + delete _h[eventType]; + } + return this; + }; + Eventful.prototype.trigger = function (eventType) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + if (!this._$handlers) { + return this; + } + var _h = this._$handlers[eventType]; + var eventProcessor = this._$eventProcessor; + if (_h) { + var argLen = args.length; + var len = _h.length; + for (var i = 0; i < len; i++) { + var hItem = _h[i]; + if (eventProcessor + && eventProcessor.filter + && hItem.query != null + && !eventProcessor.filter(eventType, hItem.query)) { + continue; + } + switch (argLen) { + case 0: + hItem.h.call(hItem.ctx); + break; + case 1: + hItem.h.call(hItem.ctx, args[0]); + break; + case 2: + hItem.h.call(hItem.ctx, args[0], args[1]); + break; + default: + hItem.h.apply(hItem.ctx, args); + break; + } + } + } + eventProcessor && eventProcessor.afterTrigger + && eventProcessor.afterTrigger(eventType); + return this; + }; + Eventful.prototype.triggerWithContext = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + if (!this._$handlers) { + return this; + } + var _h = this._$handlers[type]; + var eventProcessor = this._$eventProcessor; + if (_h) { + var argLen = args.length; + var ctx = args[argLen - 1]; + var len = _h.length; + for (var i = 0; i < len; i++) { + var hItem = _h[i]; + if (eventProcessor + && eventProcessor.filter + && hItem.query != null + && !eventProcessor.filter(type, hItem.query)) { + continue; + } + switch (argLen) { + case 0: + hItem.h.call(ctx); + break; + case 1: + hItem.h.call(ctx, args[0]); + break; + case 2: + hItem.h.call(ctx, args[0], args[1]); + break; + default: + hItem.h.apply(ctx, args.slice(1, argLen - 1)); + break; + } + } + } + eventProcessor && eventProcessor.afterTrigger + && eventProcessor.afterTrigger(type); + return this; + }; + return Eventful; +}()); + +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) { + 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] + * determinant(rows, rank - 1, subRowStart, subRowMask, colMask | colTag, detCache); + colLocalIdx++; + } + } + detCache[cacheKey] = sum; + return sum; +} +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; + } + 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) + * 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; + }; +} + +var EVENT_SAVED_PROP = '___zrEVENTSAVED'; +var _calcOut$1 = []; +function transformLocalCoord(out, elFrom, elTarget, inX, inY) { + return transformCoordWithViewport(_calcOut$1, elFrom, inX, inY, true) + && transformCoordWithViewport(out, elTarget, _calcOut$1[0], _calcOut$1[1]); +} +function transformCoordWithViewport(out, el, inX, inY, inverse) { + if (el.getBoundingClientRect && env.domSupported && !isCanvasEl(el)) { + var saved = el[EVENT_SAVED_PROP] || (el[EVENT_SAVED_PROP] = {}); + var markers = prepareCoordMarkers(el, saved); + var transformer = preparePointerTransformer(markers, saved, inverse); + if (transformer) { + transformer(out, inX, inY); + return true; + } + } + return false; +} +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', + 'user-select: none', + 'width:0', + 'height:0', + 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, inverse) { + var transformerName = inverse ? 'invTrans' : 'trans'; + var transformer = saved[transformerName]; + var oldSrcCoords = saved.srcCoords; + var srcCoords = []; + var destCoords = []; + var oldCoordTheSame = true; + 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); + oldCoordTheSame = oldCoordTheSame && oldSrcCoords && x === oldSrcCoords[ii] && y === oldSrcCoords[ii + 1]; + destCoords.push(markers[i].offsetLeft, markers[i].offsetTop); + } + return (oldCoordTheSame && transformer) + ? transformer + : (saved.srcCoords = srcCoords, + saved[transformerName] = inverse + ? buildTransformer(destCoords, srcCoords) + : buildTransformer(srcCoords, destCoords)); +} +function isCanvasEl(el) { + return el.nodeName.toUpperCase() === 'CANVAS'; +} +var replaceReg = /([&<>"'])/g; +var replaceMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''' +}; +function encodeHTML(source) { + return source == null + ? '' + : (source + '').replace(replaceReg, function (str, c) { + return replaceMap[c]; + }); +} + +var MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/; +var _calcOut = []; +var firefoxNotSupportOffsetXY = env.browser.firefox + && +env.browser.version.split('.')[0] < 39; +function clientToLocal(el, e, out, calculate) { + out = out || {}; + if (calculate) { + calculateZrXY(el, e, out); + } + else if (firefoxNotSupportOffsetXY + && e.layerX != null + && e.layerX !== e.offsetX) { + out.zrX = e.layerX; + out.zrY = e.layerY; + } + else if (e.offsetX != null) { + out.zrX = e.offsetX; + out.zrY = e.offsetY; + } + else { + calculateZrXY(el, e, out); + } + return out; +} +function calculateZrXY(el, e, out) { + if (env.domSupported && el.getBoundingClientRect) { + var ex = e.clientX; + var ey = e.clientY; + if (isCanvasEl(el)) { + var box = el.getBoundingClientRect(); + out.zrX = ex - box.left; + out.zrY = ey - box.top; + return; + } + else { + if (transformCoordWithViewport(_calcOut, el, ex, ey)) { + out.zrX = _calcOut[0]; + out.zrY = _calcOut[1]; + return; + } + } + } + out.zrX = out.zrY = 0; +} +function getNativeEvent(e) { + return e + || window.event; +} +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); + var wheelDelta = getWheelDeltaMayPolyfill(e); + e.zrDelta = wheelDelta ? wheelDelta / 120 : -(e.detail || 0) / 3; + } + else { + var touch = eventType !== 'touchend' + ? e.targetTouches[0] + : e.changedTouches[0]; + touch && clientToLocal(el, touch, e, calculate); + } + 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))); + } + return e; +} +function getWheelDeltaMayPolyfill(e) { + var rawWheelDelta = e.wheelDelta; + if (rawWheelDelta) { + return rawWheelDelta; + } + var deltaX = e.deltaX; + var deltaY = e.deltaY; + if (deltaX == null || deltaY == null) { + return rawWheelDelta; + } + var delta = deltaY !== 0 ? Math.abs(deltaY) : Math.abs(deltaX); + var sign = deltaY > 0 ? -1 + : deltaY < 0 ? 1 + : deltaX > 0 ? -1 + : 1; + return 3 * delta * sign; +} +function addEventListener(el, name, handler, opt) { + el.addEventListener(name, handler, opt); +} +function removeEventListener(el, name, handler, opt) { + el.removeEventListener(name, handler, opt); +} +var stop = function (e) { + e.preventDefault(); + e.stopPropagation(); + e.cancelBubble = true; +}; + +var GestureMgr = (function () { + function GestureMgr() { + this._track = []; + } + GestureMgr.prototype.recognize = function (event, target, root) { + this._doTrack(event, target, root); + return this._recognize(event); + }; + GestureMgr.prototype.clear = function () { + this._track.length = 0; + return this; + }; + GestureMgr.prototype._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); + }; + GestureMgr.prototype._recognize = function (event) { + for (var eventName in recognizers) { + if (recognizers.hasOwnProperty(eventName)) { + var gestureInfo = recognizers[eventName](this._track, event); + if (gestureInfo) { + return gestureInfo; + } + } + } + }; + return GestureMgr; +}()); +function dist(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 (tracks, event) { + var trackLen = tracks.length; + if (!trackLen) { + return; + } + var pinchEnd = (tracks[trackLen - 1] || {}).points; + var pinchPre = (tracks[trackLen - 2] || {}).points || pinchEnd; + if (pinchPre + && pinchPre.length > 1 + && pinchEnd + && pinchEnd.length > 1) { + var pinchScale = dist(pinchEnd) / dist(pinchPre); + !isFinite(pinchScale) && (pinchScale = 1); + event.pinchScale = pinchScale; + var pinchCenter = center(pinchEnd); + event.pinchX = pinchCenter[0]; + event.pinchY = pinchCenter[1]; + return { + type: 'pinch', + target: tracks[0].target, + event: event + }; + } + } +}; + +function create() { + return [1, 0, 0, 1, 0, 0]; +} +function identity(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = 0; + out[5] = 0; + return out; +} +function copy(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; +} +function mul(out, m1, m2) { + 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; +} +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; +} +function rotate(out, a, rad, pivot) { + if (pivot === void 0) { pivot = [0, 0]; } + 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 - pivot[0]) + st * (aty - pivot[1]) + pivot[0]; + out[5] = ct * (aty - pivot[1]) - st * (atx - pivot[0]) + pivot[1]; + return out; +} +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; +} +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; +} + +var Point = (function () { + function Point(x, y) { + this.x = x || 0; + this.y = y || 0; + } + Point.prototype.copy = function (other) { + this.x = other.x; + this.y = other.y; + return this; + }; + Point.prototype.clone = function () { + return new Point(this.x, this.y); + }; + Point.prototype.set = function (x, y) { + this.x = x; + this.y = y; + return this; + }; + Point.prototype.equal = function (other) { + return other.x === this.x && other.y === this.y; + }; + Point.prototype.add = function (other) { + this.x += other.x; + this.y += other.y; + return this; + }; + Point.prototype.scale = function (scalar) { + this.x *= scalar; + this.y *= scalar; + }; + Point.prototype.scaleAndAdd = function (other, scalar) { + this.x += other.x * scalar; + this.y += other.y * scalar; + }; + Point.prototype.sub = function (other) { + this.x -= other.x; + this.y -= other.y; + return this; + }; + Point.prototype.dot = function (other) { + return this.x * other.x + this.y * other.y; + }; + Point.prototype.len = function () { + return Math.sqrt(this.x * this.x + this.y * this.y); + }; + Point.prototype.lenSquare = function () { + return this.x * this.x + this.y * this.y; + }; + Point.prototype.normalize = function () { + var len = this.len(); + this.x /= len; + this.y /= len; + return this; + }; + Point.prototype.distance = function (other) { + var dx = this.x - other.x; + var dy = this.y - other.y; + return Math.sqrt(dx * dx + dy * dy); + }; + Point.prototype.distanceSquare = function (other) { + var dx = this.x - other.x; + var dy = this.y - other.y; + return dx * dx + dy * dy; + }; + Point.prototype.negate = function () { + this.x = -this.x; + this.y = -this.y; + return this; + }; + Point.prototype.transform = function (m) { + if (!m) { + return; + } + var x = this.x; + var y = this.y; + this.x = m[0] * x + m[2] * y + m[4]; + this.y = m[1] * x + m[3] * y + m[5]; + return this; + }; + Point.prototype.toArray = function (out) { + out[0] = this.x; + out[1] = this.y; + return out; + }; + Point.prototype.fromArray = function (input) { + this.x = input[0]; + this.y = input[1]; + }; + Point.set = function (p, x, y) { + p.x = x; + p.y = y; + }; + Point.copy = function (p, p2) { + p.x = p2.x; + p.y = p2.y; + }; + Point.len = function (p) { + return Math.sqrt(p.x * p.x + p.y * p.y); + }; + Point.lenSquare = function (p) { + return p.x * p.x + p.y * p.y; + }; + Point.dot = function (p0, p1) { + return p0.x * p1.x + p0.y * p1.y; + }; + Point.add = function (out, p0, p1) { + out.x = p0.x + p1.x; + out.y = p0.y + p1.y; + }; + Point.sub = function (out, p0, p1) { + out.x = p0.x - p1.x; + out.y = p0.y - p1.y; + }; + Point.scale = function (out, p0, scalar) { + out.x = p0.x * scalar; + out.y = p0.y * scalar; + }; + Point.scaleAndAdd = function (out, p0, p1, scalar) { + out.x = p0.x + p1.x * scalar; + out.y = p0.y + p1.y * scalar; + }; + Point.lerp = function (out, p0, p1, t) { + var onet = 1 - t; + out.x = onet * p0.x + t * p1.x; + out.y = onet * p0.y + t * p1.y; + }; + return Point; +}()); + +var mathMin$5 = Math.min; +var mathMax$5 = Math.max; +var lt = new Point(); +var rb = new Point(); +var lb = new Point(); +var rt = new Point(); +var minTv$1 = new Point(); +var maxTv$1 = new Point(); +var BoundingRect = (function () { + function BoundingRect(x, y, width, height) { + if (width < 0) { + x = x + width; + width = -width; + } + if (height < 0) { + y = y + height; + height = -height; + } + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + BoundingRect.prototype.union = function (other) { + var x = mathMin$5(other.x, this.x); + var y = mathMin$5(other.y, this.y); + if (isFinite(this.x) && isFinite(this.width)) { + this.width = mathMax$5(other.x + other.width, this.x + this.width) - x; + } + else { + this.width = other.width; + } + if (isFinite(this.y) && isFinite(this.height)) { + this.height = mathMax$5(other.y + other.height, this.y + this.height) - y; + } + else { + this.height = other.height; + } + this.x = x; + this.y = y; + }; + BoundingRect.prototype.applyTransform = function (m) { + BoundingRect.applyTransform(this, this, m); + }; + BoundingRect.prototype.calculateTransform = function (b) { + var a = this; + var sx = b.width / a.width; + var sy = b.height / a.height; + var m = create(); + translate(m, m, [-a.x, -a.y]); + scale$1(m, m, [sx, sy]); + translate(m, m, [b.x, b.y]); + return m; + }; + BoundingRect.prototype.intersect = function (b, mtv) { + if (!b) { + return false; + } + if (!(b instanceof BoundingRect)) { + 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; + var overlap = !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0); + if (mtv) { + var dMin = Infinity; + var dMax = 0; + var d0 = Math.abs(ax1 - bx0); + var d1 = Math.abs(bx1 - ax0); + var d2 = Math.abs(ay1 - by0); + var d3 = Math.abs(by1 - ay0); + var dx = Math.min(d0, d1); + var dy = Math.min(d2, d3); + if (ax1 < bx0 || bx1 < ax0) { + if (dx > dMax) { + dMax = dx; + if (d0 < d1) { + Point.set(maxTv$1, -d0, 0); + } + else { + Point.set(maxTv$1, d1, 0); + } + } + } + else { + if (dx < dMin) { + dMin = dx; + if (d0 < d1) { + Point.set(minTv$1, d0, 0); + } + else { + Point.set(minTv$1, -d1, 0); + } + } + } + if (ay1 < by0 || by1 < ay0) { + if (dy > dMax) { + dMax = dy; + if (d2 < d3) { + Point.set(maxTv$1, 0, -d2); + } + else { + Point.set(maxTv$1, 0, d3); + } + } + } + else { + if (dx < dMin) { + dMin = dx; + if (d2 < d3) { + Point.set(minTv$1, 0, d2); + } + else { + Point.set(minTv$1, 0, -d3); + } + } + } + } + if (mtv) { + Point.copy(mtv, overlap ? minTv$1 : maxTv$1); + } + return overlap; + }; + BoundingRect.prototype.contain = function (x, y) { + var rect = this; + return x >= rect.x + && x <= (rect.x + rect.width) + && y >= rect.y + && y <= (rect.y + rect.height); + }; + BoundingRect.prototype.clone = function () { + return new BoundingRect(this.x, this.y, this.width, this.height); + }; + BoundingRect.prototype.copy = function (other) { + BoundingRect.copy(this, other); + }; + BoundingRect.prototype.plain = function () { + return { + x: this.x, + y: this.y, + width: this.width, + height: this.height + }; + }; + BoundingRect.prototype.isFinite = function () { + return isFinite(this.x) + && isFinite(this.y) + && isFinite(this.width) + && isFinite(this.height); + }; + BoundingRect.prototype.isZero = function () { + return this.width === 0 || this.height === 0; + }; + BoundingRect.create = function (rect) { + return new BoundingRect(rect.x, rect.y, rect.width, rect.height); + }; + BoundingRect.copy = function (target, source) { + target.x = source.x; + target.y = source.y; + target.width = source.width; + target.height = source.height; + }; + BoundingRect.applyTransform = function (target, source, m) { + if (!m) { + if (target !== source) { + BoundingRect.copy(target, source); + } + return; + } + if (m[1] < 1e-5 && m[1] > -1e-5 && m[2] < 1e-5 && m[2] > -1e-5) { + var sx = m[0]; + var sy = m[3]; + var tx = m[4]; + var ty = m[5]; + target.x = source.x * sx + tx; + target.y = source.y * sy + ty; + target.width = source.width * sx; + target.height = source.height * sy; + if (target.width < 0) { + target.x += target.width; + target.width = -target.width; + } + if (target.height < 0) { + target.y += target.height; + target.height = -target.height; + } + return; + } + lt.x = lb.x = source.x; + lt.y = rt.y = source.y; + rb.x = rt.x = source.x + source.width; + rb.y = lb.y = source.y + source.height; + lt.transform(m); + rt.transform(m); + rb.transform(m); + lb.transform(m); + target.x = mathMin$5(lt.x, rb.x, lb.x, rt.x); + target.y = mathMin$5(lt.y, rb.y, lb.y, rt.y); + var maxX = mathMax$5(lt.x, rb.x, lb.x, rt.x); + var maxY = mathMax$5(lt.y, rb.y, lb.y, rt.y); + target.width = maxX - target.x; + target.height = maxY - target.y; + }; + return BoundingRect; +}()); + +var SILENT = 'silent'; +function makeEventPacket(eveType, targetInfo, event) { + return { + type: eveType, + event: event, + target: targetInfo.target, + 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); +} +var EmptyProxy = (function (_super) { + __extends(EmptyProxy, _super); + function EmptyProxy() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handler = null; + return _this; + } + EmptyProxy.prototype.dispose = function () { }; + EmptyProxy.prototype.setCursor = function () { }; + return EmptyProxy; +}(Eventful)); +var HoveredResult = (function () { + function HoveredResult(x, y) { + this.x = x; + this.y = y; + } + return HoveredResult; +}()); +var handlerNames = [ + 'click', 'dblclick', 'mousewheel', 'mouseout', + 'mouseup', 'mousedown', 'mousemove', 'contextmenu' +]; +var tmpRect$1 = new BoundingRect(0, 0, 0, 0); +var Handler = (function (_super) { + __extends(Handler, _super); + function Handler(storage, painter, proxy, painterRoot, pointerSize) { + var _this = _super.call(this) || this; + _this._hovered = new HoveredResult(0, 0); + _this.storage = storage; + _this.painter = painter; + _this.painterRoot = painterRoot; + _this._pointerSize = pointerSize; + proxy = proxy || new EmptyProxy(); + _this.proxy = null; + _this.setHandlerProxy(proxy); + _this._draggingMgr = new Draggable(_this); + return _this; + } + Handler.prototype.setHandlerProxy = function (proxy) { + if (this.proxy) { + this.proxy.dispose(); + } + if (proxy) { + each$4(handlerNames, function (name) { + proxy.on && proxy.on(name, this[name], this); + }, this); + proxy.handler = this; + } + this.proxy = proxy; + }; + Handler.prototype.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 && !lastHoveredTarget.__zr) { + lastHovered = this.findHover(lastHovered.x, lastHovered.y); + lastHoveredTarget = lastHovered.target; + } + var hovered = this._hovered = isOutside ? new HoveredResult(x, y) : this.findHover(x, y); + var hoveredTarget = hovered.target; + var proxy = this.proxy; + proxy.setCursor && proxy.setCursor(hoveredTarget ? hoveredTarget.cursor : 'default'); + if (lastHoveredTarget && hoveredTarget !== lastHoveredTarget) { + this.dispatchToElement(lastHovered, 'mouseout', event); + } + this.dispatchToElement(hovered, 'mousemove', event); + if (hoveredTarget && hoveredTarget !== lastHoveredTarget) { + this.dispatchToElement(hovered, 'mouseover', event); + } + }; + Handler.prototype.mouseout = function (event) { + var eventControl = event.zrEventControl; + if (eventControl !== 'only_globalout') { + this.dispatchToElement(this._hovered, 'mouseout', event); + } + if (eventControl !== 'no_globalout') { + this.trigger('globalout', { type: 'globalout', event: event }); + } + }; + Handler.prototype.resize = function () { + this._hovered = new HoveredResult(0, 0); + }; + Handler.prototype.dispatch = function (eventName, eventArgs) { + var handler = this[eventName]; + handler && handler.call(this, eventArgs); + }; + Handler.prototype.dispose = function () { + this.proxy.dispose(); + this.storage = null; + this.proxy = null; + this.painter = null; + }; + Handler.prototype.setCursorStyle = function (cursorStyle) { + var proxy = this.proxy; + proxy.setCursor && proxy.setCursor(cursorStyle); + }; + Handler.prototype.dispatchToElement = function (targetInfo, eventName, event) { + targetInfo = targetInfo || {}; + var el = targetInfo.target; + if (el && el.silent) { + return; + } + var eventKey = ('on' + eventName); + var eventPacket = makeEventPacket(eventName, targetInfo, event); + while (el) { + el[eventKey] + && (eventPacket.cancelBubble = !!el[eventKey].call(el, eventPacket)); + el.trigger(eventName, eventPacket); + el = el.__hostTarget ? el.__hostTarget : el.parent; + if (eventPacket.cancelBubble) { + break; + } + } + if (!eventPacket.cancelBubble) { + this.trigger(eventName, eventPacket); + if (this.painter && this.painter.eachOtherLayer) { + this.painter.eachOtherLayer(function (layer) { + if (typeof (layer[eventKey]) === 'function') { + layer[eventKey].call(layer, eventPacket); + } + if (layer.trigger) { + layer.trigger(eventName, eventPacket); + } + }); + } + } + }; + Handler.prototype.findHover = function (x, y, exclude) { + var list = this.storage.getDisplayList(); + var out = new HoveredResult(x, y); + setHoverTarget(list, out, x, y, exclude); + if (this._pointerSize && !out.target) { + var candidates = []; + var pointerSize = this._pointerSize; + var targetSizeHalf = pointerSize / 2; + var pointerRect = new BoundingRect(x - targetSizeHalf, y - targetSizeHalf, pointerSize, pointerSize); + for (var i = list.length - 1; i >= 0; i--) { + var el = list[i]; + if (el !== exclude + && !el.ignore + && !el.ignoreCoarsePointer + && (!el.parent || !el.parent.ignoreCoarsePointer)) { + tmpRect$1.copy(el.getBoundingRect()); + if (el.transform) { + tmpRect$1.applyTransform(el.transform); + } + if (tmpRect$1.intersect(pointerRect)) { + candidates.push(el); + } + } + } + if (candidates.length) { + var rStep = 4; + var thetaStep = Math.PI / 12; + var PI2 = Math.PI * 2; + for (var r = 0; r < targetSizeHalf; r += rStep) { + for (var theta = 0; theta < PI2; theta += thetaStep) { + var x1 = x + r * Math.cos(theta); + var y1 = y + r * Math.sin(theta); + setHoverTarget(candidates, out, x1, y1, exclude); + if (out.target) { + return out; + } + } + } + } + } + return out; + }; + Handler.prototype.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(); + if (gestureInfo) { + var type = gestureInfo.type; + event.gestureEvent = type; + var res = new HoveredResult(); + res.target = gestureInfo.target; + this.dispatchToElement(res, type, gestureInfo.event); + } + }; + return Handler; +}(Eventful)); +each$4(['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) { + hovered = this.findHover(x, y); + hoveredTarget = hovered.target; + } + if (name === 'mousedown') { + this._downEl = hoveredTarget; + this._downPoint = [event.zrX, event.zrY]; + this._upEl = hoveredTarget; + } + else if (name === 'mouseup') { + this._upEl = hoveredTarget; + } + else if (name === 'click') { + if (this._downEl !== this._upEl + || !this._downPoint + || dist$1(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 = void 0; + var ignoreClip = false; + while (el) { + if (el.ignoreClip) { + ignoreClip = true; + } + if (!ignoreClip) { + var clipPath = el.getClipPath(); + if (clipPath && !clipPath.contain(x, y)) { + return false; + } + } + if (el.silent) { + isSilent = true; + } + var hostEl = el.__hostTarget; + el = hostEl ? hostEl : el.parent; + } + return isSilent ? SILENT : true; + } + return false; +} +function setHoverTarget(list, out, x, y, exclude) { + for (var i = list.length - 1; i >= 0; i--) { + var el = list[i]; + var hoverCheckResult = void 0; + if (el !== exclude + && !el.ignore + && (hoverCheckResult = isHover(el, x, y))) { + !out.topTarget && (out.topTarget = el); + if (hoverCheckResult !== SILENT) { + out.target = el; + break; + } + } + } +} +function isOutsideBoundary(handlerInstance, x, y) { + var painter = handlerInstance.painter; + return x < 0 || x > painter.getWidth() || y < 0 || y > painter.getHeight(); +} + +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(); + } + 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(); + } + else { + customCursor = dest - (length2 - 1); + for (i = 0; i < length2; i++) { + array[customCursor + i] = tmp[i]; + } + } + } + return { + mergeRuns: mergeRuns, + forceMergeRuns: forceMergeRuns, + 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 = 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(); +} + +var REDRAW_BIT = 1; +var STYLE_CHANGED_BIT = 2; +var SHAPE_CHANGED_BIT = 4; + +var invalidZErrorLogged = false; +function logInvalidZError() { + if (invalidZErrorLogged) { + return; + } + invalidZErrorLogged = true; + console.warn('z / z2 / zlevel of displayable is invalid, which may cause unexpected errors'); +} +function shapeCompareFunc(a, b) { + if (a.zlevel === b.zlevel) { + if (a.z === b.z) { + return a.z2 - b.z2; + } + return a.z - b.z; + } + return a.zlevel - b.zlevel; +} +var Storage = (function () { + function Storage() { + this._roots = []; + this._displayList = []; + this._displayListLen = 0; + this.displayableSortFunc = shapeCompareFunc; + } + Storage.prototype.traverse = function (cb, context) { + for (var i = 0; i < this._roots.length; i++) { + this._roots[i].traverse(cb, context); + } + }; + Storage.prototype.getDisplayList = function (update, includeIgnore) { + includeIgnore = includeIgnore || false; + var displayList = this._displayList; + if (update || !displayList.length) { + this.updateDisplayList(includeIgnore); + } + return displayList; + }; + Storage.prototype.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; + sort(displayList, shapeCompareFunc); + }; + Storage.prototype._updateAndAddDisplayable = function (el, clipPaths, includeIgnore) { + if (el.ignore && !includeIgnore) { + return; + } + el.beforeUpdate(); + el.update(); + el.afterUpdate(); + var userSetClipPath = el.getClipPath(); + if (el.ignoreClip) { + clipPaths = null; + } + else if (userSetClipPath) { + if (clipPaths) { + clipPaths = clipPaths.slice(); + } + else { + clipPaths = []; + } + var currentClipPath = userSetClipPath; + var parentClipPath = el; + while (currentClipPath) { + currentClipPath.parent = parentClipPath; + currentClipPath.updateTransform(); + clipPaths.push(currentClipPath); + parentClipPath = currentClipPath; + currentClipPath = currentClipPath.getClipPath(); + } + } + if (el.childrenRef) { + var children = el.childrenRef(); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (el.__dirty) { + child.__dirty |= REDRAW_BIT; + } + this._updateAndAddDisplayable(child, clipPaths, includeIgnore); + } + el.__dirty = 0; + } + else { + var disp = el; + if (clipPaths && clipPaths.length) { + disp.__clipPaths = clipPaths; + } + else if (disp.__clipPaths && disp.__clipPaths.length > 0) { + disp.__clipPaths = []; + } + if (isNaN(disp.z)) { + logInvalidZError(); + disp.z = 0; + } + if (isNaN(disp.z2)) { + logInvalidZError(); + disp.z2 = 0; + } + if (isNaN(disp.zlevel)) { + logInvalidZError(); + disp.zlevel = 0; + } + this._displayList[this._displayListLen++] = disp; + } + var decalEl = el.getDecalElement && el.getDecalElement(); + if (decalEl) { + this._updateAndAddDisplayable(decalEl, clipPaths, includeIgnore); + } + var textGuide = el.getTextGuideLine(); + if (textGuide) { + this._updateAndAddDisplayable(textGuide, clipPaths, includeIgnore); + } + var textEl = el.getTextContent(); + if (textEl) { + this._updateAndAddDisplayable(textEl, clipPaths, includeIgnore); + } + }; + Storage.prototype.addRoot = function (el) { + if (el.__zr && el.__zr.storage === this) { + return; + } + this._roots.push(el); + }; + Storage.prototype.delRoot = function (el) { + 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._roots.splice(idx, 1); + } + }; + Storage.prototype.delAllRoots = function () { + this._roots = []; + this._displayList = []; + this._displayListLen = 0; + return; + }; + Storage.prototype.getRoots = function () { + return this._roots; + }; + Storage.prototype.dispose = function () { + this._displayList = null; + this._roots = null; + }; + return Storage; +}()); + +var requestAnimationFrame$1; +requestAnimationFrame$1 = (env.hasGlobalWindow + && ((window.requestAnimationFrame && window.requestAnimationFrame.bind(window)) + || (window.msRequestAnimationFrame && window.msRequestAnimationFrame.bind(window)) + || window.mozRequestAnimationFrame + || window.webkitRequestAnimationFrame)) || function (func) { + return setTimeout(func, 16); +}; + +var easingFuncs = { + linear: function (k) { + return k; + }, + quadraticIn: function (k) { + return k * k; + }, + quadraticOut: function (k) { + return k * (2 - k); + }, + quadraticInOut: function (k) { + if ((k *= 2) < 1) { + return 0.5 * k * k; + } + return -0.5 * (--k * (k - 2) - 1); + }, + cubicIn: function (k) { + return k * k * k; + }, + cubicOut: function (k) { + return --k * k * k + 1; + }, + cubicInOut: function (k) { + if ((k *= 2) < 1) { + return 0.5 * k * k * k; + } + return 0.5 * ((k -= 2) * k * k + 2); + }, + quarticIn: function (k) { + return k * k * k * k; + }, + quarticOut: function (k) { + return 1 - (--k * k * k * k); + }, + quarticInOut: function (k) { + if ((k *= 2) < 1) { + return 0.5 * k * k * k * k; + } + return -0.5 * ((k -= 2) * k * k * k - 2); + }, + quinticIn: function (k) { + return k * k * k * k * k; + }, + quinticOut: function (k) { + return --k * k * k * k * k + 1; + }, + 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); + }, + sinusoidalIn: function (k) { + return 1 - Math.cos(k * Math.PI / 2); + }, + sinusoidalOut: function (k) { + return Math.sin(k * Math.PI / 2); + }, + sinusoidalInOut: function (k) { + return 0.5 * (1 - Math.cos(Math.PI * k)); + }, + exponentialIn: function (k) { + return k === 0 ? 0 : Math.pow(1024, k - 1); + }, + exponentialOut: function (k) { + return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); + }, + 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); + }, + circularIn: function (k) { + return 1 - Math.sqrt(1 - k * k); + }, + circularOut: function (k) { + return Math.sqrt(1 - (--k * k)); + }, + 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); + }, + 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)); + }, + 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); + }, + 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; + }, + backIn: function (k) { + var s = 1.70158; + return k * k * ((s + 1) * k - s); + }, + backOut: function (k) { + var s = 1.70158; + return --k * k * ((s + 1) * k + s) + 1; + }, + 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); + }, + bounceIn: function (k) { + return 1 - easingFuncs.bounceOut(1 - k); + }, + 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; + } + }, + bounceInOut: function (k) { + if (k < 0.5) { + return easingFuncs.bounceIn(k * 2) * 0.5; + } + return easingFuncs.bounceOut(k * 2 - 1) * 0.5 + 0.5; + } +}; + +var mathPow$1 = Math.pow; +var mathSqrt$3 = Math.sqrt; +var EPSILON$2 = 1e-8; +var EPSILON_NUMERIC = 1e-4; +var THREE_SQRT = mathSqrt$3(3); +var ONE_THIRD = 1 / 3; +var _v0 = create$1(); +var _v1 = create$1(); +var _v2 = create$1(); +function isAroundZero(val) { + return val > -1e-8 && val < EPSILON$2; +} +function isNotAroundZero$1(val) { + return val > EPSILON$2 || val < -1e-8; +} +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); +} +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); +} +function cubicRootAt(p0, p1, p2, p3, val, roots) { + 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; + 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; + var t2 = -K / 2; + if (t1 >= 0 && t1 <= 1) { + roots[n++] = t1; + } + if (t2 >= 0 && t2 <= 1) { + roots[n++] = t2; + } + } + else if (disc > 0) { + var discSqrt = mathSqrt$3(disc); + var Y1 = A * b + 1.5 * a * (-B + discSqrt); + var Y2 = A * b + 1.5 * a * (-B - discSqrt); + if (Y1 < 0) { + Y1 = -mathPow$1(-Y1, ONE_THIRD); + } + else { + Y1 = mathPow$1(Y1, ONE_THIRD); + } + if (Y2 < 0) { + Y2 = -mathPow$1(-Y2, ONE_THIRD); + } + else { + Y2 = mathPow$1(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$3(A * A * A)); + var theta = Math.acos(T) / 3; + var ASqrt = mathSqrt$3(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; +} +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$3(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; +} +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; + out[0] = p0; + out[1] = p01; + out[2] = p012; + out[3] = p0123; + out[4] = p0123; + out[5] = p123; + out[6] = p23; + out[7] = p3; +} +function cubicProjectPoint(x0, y0, x1, y1, x2, y2, x3, y3, x, y, out) { + var t; + var interval = 0.005; + var d = Infinity; + var prev; + var next; + var d1; + var d2; + _v0[0] = x; + _v0[1] = y; + 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; + for (var i = 0; i < 32; i++) { + if (interval < EPSILON_NUMERIC) { + break; + } + prev = t - interval; + next = 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 { + _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; + } + } + } + return mathSqrt$3(d); +} +function cubicLength(x0, y0, x1, y1, x2, y2, x3, y3, iteration) { + var px = x0; + var py = y0; + var d = 0; + var step = 1 / iteration; + for (var i = 1; i <= iteration; i++) { + var t = i * step; + var x = cubicAt(x0, x1, x2, x3, t); + var y = cubicAt(y0, y1, y2, y3, t); + var dx = x - px; + var dy = y - py; + d += Math.sqrt(dx * dx + dy * dy); + px = x; + py = y; + } + return d; +} +function quadraticAt(p0, p1, p2, t) { + var onet = 1 - t; + return onet * (onet * p0 + 2 * t * p1) + t * t * p2; +} +function quadraticDerivativeAt(p0, p1, p2, t) { + return 2 * ((1 - t) * (p1 - p0) + t * (p2 - p1)); +} +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$3(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; +} +function quadraticExtremum(p0, p1, p2) { + var divider = p0 + p2 - 2 * p1; + if (divider === 0) { + return 0.5; + } + else { + return (p0 - p1) / divider; + } +} +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; + out[0] = p0; + out[1] = p01; + out[2] = p012; + out[3] = p012; + out[4] = p12; + out[5] = p2; +} +function quadraticProjectPoint(x0, y0, x1, y1, x2, y2, x, y, out) { + var t; + var interval = 0.005; + var d = Infinity; + _v0[0] = x; + _v0[1] = y; + 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; + for (var i = 0; i < 32; i++) { + if (interval < EPSILON_NUMERIC) { + break; + } + var prev = t - interval; + var next = 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 { + _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; + } + } + } + return mathSqrt$3(d); +} +function quadraticLength(x0, y0, x1, y1, x2, y2, iteration) { + var px = x0; + var py = y0; + var d = 0; + var step = 1 / iteration; + for (var i = 1; i <= iteration; i++) { + var t = i * step; + var x = quadraticAt(x0, x1, x2, t); + var y = quadraticAt(y0, y1, y2, t); + var dx = x - px; + var dy = y - py; + d += Math.sqrt(dx * dx + dy * dy); + px = x; + py = y; + } + return d; +} + +var regexp = /cubic-bezier\(([0-9,\.e ]+)\)/; +function createCubicEasingFunc(cubicEasingStr) { + var cubic = cubicEasingStr && regexp.exec(cubicEasingStr); + if (cubic) { + var points = cubic[1].split(','); + var a_1 = +trim(points[0]); + var b_1 = +trim(points[1]); + var c_1 = +trim(points[2]); + var d_1 = +trim(points[3]); + if (isNaN(a_1 + b_1 + c_1 + d_1)) { + return; + } + var roots_1 = []; + return function (p) { + return p <= 0 + ? 0 : p >= 1 + ? 1 + : cubicRootAt(0, a_1, c_1, 1, p, roots_1) && cubicAt(0, b_1, d_1, 1, roots_1[0]); + }; + } +} + +var Clip = (function () { + function Clip(opts) { + this._inited = false; + this._startTime = 0; + this._pausedTime = 0; + this._paused = false; + this._life = opts.life || 1000; + this._delay = opts.delay || 0; + this.loop = opts.loop || false; + this.onframe = opts.onframe || noop; + this.ondestroy = opts.ondestroy || noop; + this.onrestart = opts.onrestart || noop; + opts.easing && this.setEasing(opts.easing); + } + Clip.prototype.step = function (globalTime, deltaTime) { + if (!this._inited) { + this._startTime = globalTime + this._delay; + this._inited = true; + } + if (this._paused) { + this._pausedTime += deltaTime; + return; + } + var life = this._life; + var elapsedTime = globalTime - this._startTime - this._pausedTime; + var percent = elapsedTime / life; + if (percent < 0) { + percent = 0; + } + percent = Math.min(percent, 1); + var easingFunc = this.easingFunc; + var schedule = easingFunc ? easingFunc(percent) : percent; + this.onframe(schedule); + if (percent === 1) { + if (this.loop) { + var remainder = elapsedTime % life; + this._startTime = globalTime - remainder; + this._pausedTime = 0; + this.onrestart(); + } + else { + return true; + } + } + return false; + }; + Clip.prototype.pause = function () { + this._paused = true; + }; + Clip.prototype.resume = function () { + this._paused = false; + }; + Clip.prototype.setEasing = function (easing) { + this.easing = easing; + this.easingFunc = isFunction(easing) + ? easing + : easingFuncs[easing] || createCubicEasingFunc(easing); + }; + return Clip; +}()); + +var Entry = (function () { + function Entry(val) { + this.value = val; + } + return Entry; +}()); +var LinkedList = (function () { + function LinkedList() { + this._len = 0; + } + LinkedList.prototype.insert = function (val) { + var entry = new Entry(val); + this.insertEntry(entry); + return entry; + }; + LinkedList.prototype.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++; + }; + LinkedList.prototype.remove = function (entry) { + var prev = entry.prev; + var next = entry.next; + if (prev) { + prev.next = next; + } + else { + this.head = next; + } + if (next) { + next.prev = prev; + } + else { + this.tail = prev; + } + entry.next = entry.prev = null; + this._len--; + }; + LinkedList.prototype.len = function () { + return this._len; + }; + LinkedList.prototype.clear = function () { + this.head = this.tail = null; + this._len = 0; + }; + return LinkedList; +}()); +var LRU = (function () { + function LRU(maxSize) { + this._list = new LinkedList(); + this._maxSize = 10; + this._map = {}; + this._maxSize = maxSize; + } + LRU.prototype.put = function (key, value) { + var list = this._list; + var map = this._map; + var removed = null; + if (map[key] == null) { + var len = list.len(); + var entry = this._lastRemovedEntry; + if (len >= this._maxSize && len > 0) { + 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; + }; + LRU.prototype.get = function (key) { + var entry = this._map[key]; + var list = this._list; + if (entry != null) { + if (entry !== list.tail) { + list.remove(entry); + list.insertEntry(entry); + } + return entry.value; + } + }; + LRU.prototype.clear = function () { + this._list.clear(); + this._map = {}; + }; + LRU.prototype.len = function () { + return this._list.len(); + }; + return LRU; +}()); + +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) { + i = Math.round(i); + return i < 0 ? 0 : i > 255 ? 255 : i; +} +function clampCssFloat(f) { + return f < 0 ? 0 : f > 1 ? 1 : f; +} +function parseCssInt(val) { + var str = val; + if (str.length && str.charAt(str.length - 1) === '%') { + return clampCssByte(parseFloat(str) / 100 * 255); + } + return clampCssByte(parseInt(str, 10)); +} +function parseCssFloat(val) { + var str = val; + 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) { + if (lastRemovedArr) { + copyRgba(lastRemovedArr, rgbaArr); + } + lastRemovedArr = colorCache.put(colorStr, lastRemovedArr || (rgbaArr.slice())); +} +function parse(colorStr, rgbaArr) { + if (!colorStr) { + return; + } + rgbaArr = rgbaArr || []; + var cached = colorCache.get(colorStr); + if (cached) { + return copyRgba(rgbaArr, cached); + } + colorStr = colorStr + ''; + var str = colorStr.replace(/ /g, '').toLowerCase(); + if (str in kCSSColorTable) { + copyRgba(rgbaArr, kCSSColorTable[str]); + putToCache(colorStr, rgbaArr); + return rgbaArr; + } + var strLen = str.length; + if (str.charAt(0) === '#') { + if (strLen === 4 || strLen === 5) { + var iv = parseInt(str.slice(1, 4), 16); + if (!(iv >= 0 && iv <= 0xfff)) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; + } + setRgba(rgbaArr, ((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8), (iv & 0xf0) | ((iv & 0xf0) >> 4), (iv & 0xf) | ((iv & 0xf) << 4), strLen === 5 ? parseInt(str.slice(4), 16) / 0xf : 1); + putToCache(colorStr, rgbaArr); + return rgbaArr; + } + else if (strLen === 7 || strLen === 9) { + var iv = parseInt(str.slice(1, 7), 16); + if (!(iv >= 0 && iv <= 0xffffff)) { + setRgba(rgbaArr, 0, 0, 0, 1); + return; + } + setRgba(rgbaArr, (iv & 0xff0000) >> 16, (iv & 0xff00) >> 8, iv & 0xff, strLen === 9 ? parseInt(str.slice(7), 16) / 0xff : 1); + putToCache(colorStr, rgbaArr); + return rgbaArr; + } + return; + } + var op = str.indexOf('('); + var ep = str.indexOf(')'); + if (op !== -1 && ep + 1 === strLen) { + var fname = str.substr(0, op); + var params = str.substr(op + 1, ep - (op + 1)).split(','); + var alpha = 1; + switch (fname) { + case 'rgba': + if (params.length !== 4) { + return params.length === 3 + ? setRgba(rgbaArr, +params[0], +params[1], +params[2], 1) + : setRgba(rgbaArr, 0, 0, 0, 1); + } + alpha = parseCssFloat(params.pop()); + case 'rgb': + if (params.length >= 3) { + setRgba(rgbaArr, parseCssInt(params[0]), parseCssInt(params[1]), parseCssInt(params[2]), params.length === 3 ? alpha : parseCssFloat(params[3])); + putToCache(colorStr, rgbaArr); + return rgbaArr; + } + else { + setRgba(rgbaArr, 0, 0, 0, 1); + return; + } + 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; +} +function hsla2rgba(hsla, rgba) { + var h = (((parseFloat(hsla[0]) % 360) + 360) % 360) / 360; + 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; +} +function lift(color, level) { + var colorArr = parse(color); + if (colorArr) { + for (var i = 0; i < 3; i++) { + { + colorArr[i] = colorArr[i] * (1 - level) | 0; + } + if (colorArr[i] > 255) { + colorArr[i] = 255; + } + else if (colorArr[i] < 0) { + colorArr[i] = 0; + } + } + return stringify(colorArr, colorArr.length === 4 ? 'rgba' : 'rgb'); + } +} +function lerp(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; +} +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 + ')'; +} +function lum(color, backgroundLum) { + var arr = parse(color); + return arr + ? (0.299 * arr[0] + 0.587 * arr[1] + 0.114 * arr[2]) * arr[3] / 255 + + (1 - arr[3]) * backgroundLum + : 0; +} +var liftedColorCache = new LRU(100); +function liftColor(color) { + if (isString(color)) { + var liftedColor = liftedColorCache.get(color); + if (!liftedColor) { + liftedColor = lift(color, -0.1); + liftedColorCache.put(color, liftedColor); + } + return liftedColor; + } + else if (isGradientObject(color)) { + var ret = extend({}, color); + ret.colorStops = map$1(color.colorStops, function (stop) { return ({ + offset: stop.offset, + color: lift(stop.color, -0.1) + }); }); + return ret; + } + return color; +} + +function isLinearGradient(val) { + return val.type === "linear"; +} +function isRadialGradient(val) { + return val.type === "radial"; +} +(function() { + if (env.hasGlobalWindow && isFunction(window.btoa)) { + return function(str) { + return window.btoa(unescape(encodeURIComponent(str))); + }; + } + if (typeof Buffer !== "undefined") { + return function(str) { + return Buffer.from(str).toString("base64"); + }; + } + return function(str) { + return null; + }; +})(); + +var arraySlice = Array.prototype.slice; +function interpolateNumber$1(p0, p1, percent) { + return (p1 - p0) * percent + p0; +} +function interpolate1DArray(out, p0, p1, percent) { + var len = p0.length; + for (var i = 0; i < len; i++) { + out[i] = interpolateNumber$1(p0[i], p1[i], percent); + } + return out; +} +function interpolate2DArray(out, p0, p1, percent) { + var len = p0.length; + var len2 = len && p0[0].length; + for (var i = 0; i < len; i++) { + if (!out[i]) { + out[i] = []; + } + for (var j = 0; j < len2; j++) { + out[i][j] = interpolateNumber$1(p0[i][j], p1[i][j], percent); + } + } + return out; +} +function add1DArray(out, p0, p1, sign) { + var len = p0.length; + for (var i = 0; i < len; i++) { + out[i] = p0[i] + p1[i] * sign; + } + return out; +} +function add2DArray(out, p0, p1, sign) { + var len = p0.length; + var len2 = len && p0[0].length; + for (var i = 0; i < len; i++) { + if (!out[i]) { + out[i] = []; + } + for (var j = 0; j < len2; j++) { + out[i][j] = p0[i][j] + p1[i][j] * sign; + } + } + return out; +} +function fillColorStops(val0, val1) { + var len0 = val0.length; + var len1 = val1.length; + var shorterArr = len0 > len1 ? val1 : val0; + var shorterLen = Math.min(len0, len1); + var last = shorterArr[shorterLen - 1] || { color: [0, 0, 0, 0], offset: 0 }; + for (var i = shorterLen; i < Math.max(len0, len1); i++) { + shorterArr.push({ + offset: last.offset, + color: last.color.slice() + }); + } +} +function fillArray(val0, val1, arrDim) { + var arr0 = val0; + var arr1 = val1; + if (!arr0.push || !arr1.push) { + return; + } + var arr0Len = arr0.length; + var arr1Len = arr1.length; + if (arr0Len !== arr1Len) { + var isPreviousLarger = arr0Len > arr1Len; + if (isPreviousLarger) { + arr0.length = arr1Len; + } + else { + for (var i = arr0Len; i < arr1Len; i++) { + arr0.push(arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i])); + } + } + } + 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]; + } + } + } + } +} +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]) || 0; + rgba[1] = Math.floor(rgba[1]) || 0; + rgba[2] = Math.floor(rgba[2]) || 0; + rgba[3] = rgba[3] == null ? 1 : rgba[3]; + return 'rgba(' + rgba.join(',') + ')'; +} +function guessArrayDim(value) { + return isArrayLike(value && value[0]) ? 2 : 1; +} +var VALUE_TYPE_NUMBER = 0; +var VALUE_TYPE_1D_ARRAY = 1; +var VALUE_TYPE_2D_ARRAY = 2; +var VALUE_TYPE_COLOR = 3; +var VALUE_TYPE_LINEAR_GRADIENT = 4; +var VALUE_TYPE_RADIAL_GRADIENT = 5; +var VALUE_TYPE_UNKOWN = 6; +function isGradientValueType(valType) { + return valType === VALUE_TYPE_LINEAR_GRADIENT || valType === VALUE_TYPE_RADIAL_GRADIENT; +} +function isArrayValueType(valType) { + return valType === VALUE_TYPE_1D_ARRAY || valType === VALUE_TYPE_2D_ARRAY; +} +var tmpRgba = [0, 0, 0, 0]; +var Track = (function () { + function Track(propName) { + this.keyframes = []; + this.discrete = false; + this._invalid = false; + this._needsSort = false; + this._lastFr = 0; + this._lastFrP = 0; + this.propName = propName; + } + Track.prototype.isFinished = function () { + return this._finished; + }; + Track.prototype.setFinished = function () { + this._finished = true; + if (this._additiveTrack) { + this._additiveTrack.setFinished(); + } + }; + Track.prototype.needsAnimate = function () { + return this.keyframes.length >= 1; + }; + Track.prototype.getAdditiveTrack = function () { + return this._additiveTrack; + }; + Track.prototype.addKeyframe = function (time, rawValue, easing) { + this._needsSort = true; + var keyframes = this.keyframes; + var len = keyframes.length; + var discrete = false; + var valType = VALUE_TYPE_UNKOWN; + var value = rawValue; + if (isArrayLike(rawValue)) { + var arrayDim = guessArrayDim(rawValue); + valType = arrayDim; + if (arrayDim === 1 && !isNumber(rawValue[0]) + || arrayDim === 2 && !isNumber(rawValue[0][0])) { + discrete = true; + } + } + else { + if (isNumber(rawValue) && !eqNaN(rawValue)) { + valType = VALUE_TYPE_NUMBER; + } + else if (isString(rawValue)) { + if (!isNaN(+rawValue)) { + valType = VALUE_TYPE_NUMBER; + } + else { + var colorArray = parse(rawValue); + if (colorArray) { + value = colorArray; + valType = VALUE_TYPE_COLOR; + } + } + } + else if (isGradientObject(rawValue)) { + var parsedGradient = extend({}, value); + parsedGradient.colorStops = map$1(rawValue.colorStops, function (colorStop) { return ({ + offset: colorStop.offset, + color: parse(colorStop.color) + }); }); + if (isLinearGradient(rawValue)) { + valType = VALUE_TYPE_LINEAR_GRADIENT; + } + else if (isRadialGradient(rawValue)) { + valType = VALUE_TYPE_RADIAL_GRADIENT; + } + value = parsedGradient; + } + } + if (len === 0) { + this.valType = valType; + } + else if (valType !== this.valType || valType === VALUE_TYPE_UNKOWN) { + discrete = true; + } + this.discrete = this.discrete || discrete; + var kf = { + time: time, + value: value, + rawValue: rawValue, + percent: 0 + }; + if (easing) { + kf.easing = easing; + kf.easingFunc = isFunction(easing) + ? easing + : easingFuncs[easing] || createCubicEasingFunc(easing); + } + keyframes.push(kf); + return kf; + }; + Track.prototype.prepare = function (maxTime, additiveTrack) { + var kfs = this.keyframes; + if (this._needsSort) { + kfs.sort(function (a, b) { + return a.time - b.time; + }); + } + var valType = this.valType; + var kfsLen = kfs.length; + var lastKf = kfs[kfsLen - 1]; + var isDiscrete = this.discrete; + var isArr = isArrayValueType(valType); + var isGradient = isGradientValueType(valType); + for (var i = 0; i < kfsLen; i++) { + var kf = kfs[i]; + var value = kf.value; + var lastValue = lastKf.value; + kf.percent = kf.time / maxTime; + if (!isDiscrete) { + if (isArr && i !== kfsLen - 1) { + fillArray(value, lastValue, valType); + } + else if (isGradient) { + fillColorStops(value.colorStops, lastValue.colorStops); + } + } + } + if (!isDiscrete + && valType !== VALUE_TYPE_RADIAL_GRADIENT + && additiveTrack + && this.needsAnimate() + && additiveTrack.needsAnimate() + && valType === additiveTrack.valType + && !additiveTrack._finished) { + this._additiveTrack = additiveTrack; + var startValue = kfs[0].value; + for (var i = 0; i < kfsLen; i++) { + if (valType === VALUE_TYPE_NUMBER) { + kfs[i].additiveValue = kfs[i].value - startValue; + } + else if (valType === VALUE_TYPE_COLOR) { + kfs[i].additiveValue = + add1DArray([], kfs[i].value, startValue, -1); + } + else if (isArrayValueType(valType)) { + kfs[i].additiveValue = valType === VALUE_TYPE_1D_ARRAY + ? add1DArray([], kfs[i].value, startValue, -1) + : add2DArray([], kfs[i].value, startValue, -1); + } + } + } + }; + Track.prototype.step = function (target, percent) { + if (this._finished) { + return; + } + if (this._additiveTrack && this._additiveTrack._finished) { + this._additiveTrack = null; + } + var isAdditive = this._additiveTrack != null; + var valueKey = isAdditive ? 'additiveValue' : 'value'; + var valType = this.valType; + var keyframes = this.keyframes; + var kfsNum = keyframes.length; + var propName = this.propName; + var isValueColor = valType === VALUE_TYPE_COLOR; + var frameIdx; + var lastFrame = this._lastFr; + var mathMin = Math.min; + var frame; + var nextFrame; + if (kfsNum === 1) { + frame = nextFrame = keyframes[0]; + } + else { + if (percent < 0) { + frameIdx = 0; + } + else if (percent < this._lastFrP) { + var start = mathMin(lastFrame + 1, kfsNum - 1); + for (frameIdx = start; frameIdx >= 0; frameIdx--) { + if (keyframes[frameIdx].percent <= percent) { + break; + } + } + frameIdx = mathMin(frameIdx, kfsNum - 2); + } + else { + for (frameIdx = lastFrame; frameIdx < kfsNum; frameIdx++) { + if (keyframes[frameIdx].percent > percent) { + break; + } + } + frameIdx = mathMin(frameIdx - 1, kfsNum - 2); + } + nextFrame = keyframes[frameIdx + 1]; + frame = keyframes[frameIdx]; + } + if (!(frame && nextFrame)) { + return; + } + this._lastFr = frameIdx; + this._lastFrP = percent; + var interval = (nextFrame.percent - frame.percent); + var w = interval === 0 ? 1 : mathMin((percent - frame.percent) / interval, 1); + if (nextFrame.easingFunc) { + w = nextFrame.easingFunc(w); + } + var targetArr = isAdditive ? this._additiveValue + : (isValueColor ? tmpRgba : target[propName]); + if ((isArrayValueType(valType) || isValueColor) && !targetArr) { + targetArr = this._additiveValue = []; + } + if (this.discrete) { + target[propName] = w < 1 ? frame.rawValue : nextFrame.rawValue; + } + else if (isArrayValueType(valType)) { + valType === VALUE_TYPE_1D_ARRAY + ? interpolate1DArray(targetArr, frame[valueKey], nextFrame[valueKey], w) + : interpolate2DArray(targetArr, frame[valueKey], nextFrame[valueKey], w); + } + else if (isGradientValueType(valType)) { + var val = frame[valueKey]; + var nextVal_1 = nextFrame[valueKey]; + var isLinearGradient_1 = valType === VALUE_TYPE_LINEAR_GRADIENT; + target[propName] = { + type: isLinearGradient_1 ? 'linear' : 'radial', + x: interpolateNumber$1(val.x, nextVal_1.x, w), + y: interpolateNumber$1(val.y, nextVal_1.y, w), + colorStops: map$1(val.colorStops, function (colorStop, idx) { + var nextColorStop = nextVal_1.colorStops[idx]; + return { + offset: interpolateNumber$1(colorStop.offset, nextColorStop.offset, w), + color: rgba2String(interpolate1DArray([], colorStop.color, nextColorStop.color, w)) + }; + }), + global: nextVal_1.global + }; + if (isLinearGradient_1) { + target[propName].x2 = interpolateNumber$1(val.x2, nextVal_1.x2, w); + target[propName].y2 = interpolateNumber$1(val.y2, nextVal_1.y2, w); + } + else { + target[propName].r = interpolateNumber$1(val.r, nextVal_1.r, w); + } + } + else if (isValueColor) { + interpolate1DArray(targetArr, frame[valueKey], nextFrame[valueKey], w); + if (!isAdditive) { + target[propName] = rgba2String(targetArr); + } + } + else { + var value = interpolateNumber$1(frame[valueKey], nextFrame[valueKey], w); + if (isAdditive) { + this._additiveValue = value; + } + else { + target[propName] = value; + } + } + if (isAdditive) { + this._addToTarget(target); + } + }; + Track.prototype._addToTarget = function (target) { + var valType = this.valType; + var propName = this.propName; + var additiveValue = this._additiveValue; + if (valType === VALUE_TYPE_NUMBER) { + target[propName] = target[propName] + additiveValue; + } + else if (valType === VALUE_TYPE_COLOR) { + parse(target[propName], tmpRgba); + add1DArray(tmpRgba, tmpRgba, additiveValue, 1); + target[propName] = rgba2String(tmpRgba); + } + else if (valType === VALUE_TYPE_1D_ARRAY) { + add1DArray(target[propName], target[propName], additiveValue, 1); + } + else if (valType === VALUE_TYPE_2D_ARRAY) { + add2DArray(target[propName], target[propName], additiveValue, 1); + } + }; + return Track; +}()); +var Animator = (function () { + function Animator(target, loop, allowDiscreteAnimation, additiveTo) { + this._tracks = {}; + this._trackKeys = []; + this._maxTime = 0; + this._started = 0; + this._clip = null; + this._target = target; + this._loop = loop; + if (loop && additiveTo) { + logError('Can\' use additive animation on looped animation.'); + return; + } + this._additiveAnimators = additiveTo; + this._allowDiscrete = allowDiscreteAnimation; + } + Animator.prototype.getMaxTime = function () { + return this._maxTime; + }; + Animator.prototype.getDelay = function () { + return this._delay; + }; + Animator.prototype.getLoop = function () { + return this._loop; + }; + Animator.prototype.getTarget = function () { + return this._target; + }; + Animator.prototype.changeTarget = function (target) { + this._target = target; + }; + Animator.prototype.when = function (time, props, easing) { + return this.whenWithKeys(time, props, keys(props), easing); + }; + Animator.prototype.whenWithKeys = function (time, props, propNames, easing) { + var tracks = this._tracks; + for (var i = 0; i < propNames.length; i++) { + var propName = propNames[i]; + var track = tracks[propName]; + if (!track) { + track = tracks[propName] = new Track(propName); + var initialValue = void 0; + var additiveTrack = this._getAdditiveTrack(propName); + if (additiveTrack) { + var addtiveTrackKfs = additiveTrack.keyframes; + var lastFinalKf = addtiveTrackKfs[addtiveTrackKfs.length - 1]; + initialValue = lastFinalKf && lastFinalKf.value; + if (additiveTrack.valType === VALUE_TYPE_COLOR && initialValue) { + initialValue = rgba2String(initialValue); + } + } + else { + initialValue = this._target[propName]; + } + if (initialValue == null) { + continue; + } + if (time > 0) { + track.addKeyframe(0, cloneValue(initialValue), easing); + } + this._trackKeys.push(propName); + } + track.addKeyframe(time, cloneValue(props[propName]), easing); + } + this._maxTime = Math.max(this._maxTime, time); + return this; + }; + Animator.prototype.pause = function () { + this._clip.pause(); + this._paused = true; + }; + Animator.prototype.resume = function () { + this._clip.resume(); + this._paused = false; + }; + Animator.prototype.isPaused = function () { + return !!this._paused; + }; + Animator.prototype.duration = function (duration) { + this._maxTime = duration; + this._force = true; + return this; + }; + Animator.prototype._doneCallback = function () { + this._setTracksFinished(); + this._clip = null; + var doneList = this._doneCbs; + if (doneList) { + var len = doneList.length; + for (var i = 0; i < len; i++) { + doneList[i].call(this); + } + } + }; + Animator.prototype._abortedCallback = function () { + this._setTracksFinished(); + var animation = this.animation; + var abortedList = this._abortedCbs; + if (animation) { + animation.removeClip(this._clip); + } + this._clip = null; + if (abortedList) { + for (var i = 0; i < abortedList.length; i++) { + abortedList[i].call(this); + } + } + }; + Animator.prototype._setTracksFinished = function () { + var tracks = this._tracks; + var tracksKeys = this._trackKeys; + for (var i = 0; i < tracksKeys.length; i++) { + tracks[tracksKeys[i]].setFinished(); + } + }; + Animator.prototype._getAdditiveTrack = function (trackName) { + var additiveTrack; + var additiveAnimators = this._additiveAnimators; + if (additiveAnimators) { + for (var i = 0; i < additiveAnimators.length; i++) { + var track = additiveAnimators[i].getTrack(trackName); + if (track) { + additiveTrack = track; + } + } + } + return additiveTrack; + }; + Animator.prototype.start = function (easing) { + if (this._started > 0) { + return; + } + this._started = 1; + var self = this; + var tracks = []; + var maxTime = this._maxTime || 0; + for (var i = 0; i < this._trackKeys.length; i++) { + var propName = this._trackKeys[i]; + var track = this._tracks[propName]; + var additiveTrack = this._getAdditiveTrack(propName); + var kfs = track.keyframes; + var kfsNum = kfs.length; + track.prepare(maxTime, additiveTrack); + if (track.needsAnimate()) { + if (!this._allowDiscrete && track.discrete) { + var lastKf = kfs[kfsNum - 1]; + if (lastKf) { + self._target[track.propName] = lastKf.rawValue; + } + track.setFinished(); + } + else { + tracks.push(track); + } + } + } + if (tracks.length || this._force) { + var clip = new Clip({ + life: maxTime, + loop: this._loop, + delay: this._delay || 0, + onframe: function (percent) { + self._started = 2; + var additiveAnimators = self._additiveAnimators; + if (additiveAnimators) { + var stillHasAdditiveAnimator = false; + for (var i = 0; i < additiveAnimators.length; i++) { + if (additiveAnimators[i]._clip) { + stillHasAdditiveAnimator = true; + break; + } + } + if (!stillHasAdditiveAnimator) { + self._additiveAnimators = null; + } + } + for (var i = 0; i < tracks.length; i++) { + tracks[i].step(self._target, percent); + } + var onframeList = self._onframeCbs; + if (onframeList) { + for (var i = 0; i < onframeList.length; i++) { + onframeList[i](self._target, percent); + } + } + }, + ondestroy: function () { + self._doneCallback(); + } + }); + this._clip = clip; + if (this.animation) { + this.animation.addClip(clip); + } + if (easing) { + clip.setEasing(easing); + } + } + else { + this._doneCallback(); + } + return this; + }; + Animator.prototype.stop = function (forwardToLast) { + if (!this._clip) { + return; + } + var clip = this._clip; + if (forwardToLast) { + clip.onframe(1); + } + this._abortedCallback(); + }; + Animator.prototype.delay = function (time) { + this._delay = time; + return this; + }; + Animator.prototype.during = function (cb) { + if (cb) { + if (!this._onframeCbs) { + this._onframeCbs = []; + } + this._onframeCbs.push(cb); + } + return this; + }; + Animator.prototype.done = function (cb) { + if (cb) { + if (!this._doneCbs) { + this._doneCbs = []; + } + this._doneCbs.push(cb); + } + return this; + }; + Animator.prototype.aborted = function (cb) { + if (cb) { + if (!this._abortedCbs) { + this._abortedCbs = []; + } + this._abortedCbs.push(cb); + } + return this; + }; + Animator.prototype.getClip = function () { + return this._clip; + }; + Animator.prototype.getTrack = function (propName) { + return this._tracks[propName]; + }; + Animator.prototype.getTracks = function () { + var _this = this; + return map$1(this._trackKeys, function (key) { return _this._tracks[key]; }); + }; + Animator.prototype.stopTracks = function (propNames, forwardToLast) { + if (!propNames.length || !this._clip) { + return true; + } + var tracks = this._tracks; + var tracksKeys = this._trackKeys; + for (var i = 0; i < propNames.length; i++) { + var track = tracks[propNames[i]]; + if (track && !track.isFinished()) { + if (forwardToLast) { + track.step(this._target, 1); + } + else if (this._started === 1) { + track.step(this._target, 0); + } + track.setFinished(); + } + } + var allAborted = true; + for (var i = 0; i < tracksKeys.length; i++) { + if (!tracks[tracksKeys[i]].isFinished()) { + allAborted = false; + break; + } + } + if (allAborted) { + this._abortedCallback(); + } + return allAborted; + }; + Animator.prototype.saveTo = function (target, trackKeys, firstOrLast) { + if (!target) { + return; + } + trackKeys = trackKeys || this._trackKeys; + for (var i = 0; i < trackKeys.length; i++) { + var propName = trackKeys[i]; + var track = this._tracks[propName]; + if (!track || track.isFinished()) { + continue; + } + var kfs = track.keyframes; + var kf = kfs[firstOrLast ? 0 : kfs.length - 1]; + if (kf) { + target[propName] = cloneValue(kf.rawValue); + } + } + }; + Animator.prototype.__changeFinalValue = function (finalProps, trackKeys) { + trackKeys = trackKeys || keys(finalProps); + for (var i = 0; i < trackKeys.length; i++) { + var propName = trackKeys[i]; + var track = this._tracks[propName]; + if (!track) { + continue; + } + var kfs = track.keyframes; + if (kfs.length > 1) { + var lastKf = kfs.pop(); + track.addKeyframe(lastKf.time, finalProps[propName]); + track.prepare(this._maxTime, track.getAdditiveTrack()); + } + } + }; + return Animator; +}()); + +function getTime() { + return new Date().getTime(); +} +var Animation = (function (_super) { + __extends(Animation, _super); + function Animation(opts) { + var _this = _super.call(this) || this; + _this._running = false; + _this._time = 0; + _this._pausedTime = 0; + _this._pauseStart = 0; + _this._paused = false; + opts = opts || {}; + _this.stage = opts.stage || {}; + return _this; + } + Animation.prototype.addClip = function (clip) { + if (clip.animation) { + this.removeClip(clip); + } + if (!this._head) { + this._head = this._tail = clip; + } + else { + this._tail.next = clip; + clip.prev = this._tail; + clip.next = null; + this._tail = clip; + } + clip.animation = this; + }; + Animation.prototype.addAnimator = function (animator) { + animator.animation = this; + var clip = animator.getClip(); + if (clip) { + this.addClip(clip); + } + }; + Animation.prototype.removeClip = function (clip) { + if (!clip.animation) { + return; + } + var prev = clip.prev; + var next = clip.next; + if (prev) { + prev.next = next; + } + else { + this._head = next; + } + if (next) { + next.prev = prev; + } + else { + this._tail = prev; + } + clip.next = clip.prev = clip.animation = null; + }; + Animation.prototype.removeAnimator = function (animator) { + var clip = animator.getClip(); + if (clip) { + this.removeClip(clip); + } + animator.animation = null; + }; + Animation.prototype.update = function (notTriggerFrameAndStageUpdate) { + var time = getTime() - this._pausedTime; + var delta = time - this._time; + var clip = this._head; + while (clip) { + var nextClip = clip.next; + var finished = clip.step(time, delta); + if (finished) { + clip.ondestroy(); + this.removeClip(clip); + clip = nextClip; + } + else { + clip = nextClip; + } + } + this._time = time; + if (!notTriggerFrameAndStageUpdate) { + this.trigger('frame', delta); + this.stage.update && this.stage.update(); + } + }; + Animation.prototype._startLoop = function () { + var self = this; + this._running = true; + function step() { + if (self._running) { + requestAnimationFrame$1(step); + !self._paused && self.update(); + } + } + requestAnimationFrame$1(step); + }; + Animation.prototype.start = function () { + if (this._running) { + return; + } + this._time = getTime(); + this._pausedTime = 0; + this._startLoop(); + }; + Animation.prototype.stop = function () { + this._running = false; + }; + Animation.prototype.pause = function () { + if (!this._paused) { + this._pauseStart = getTime(); + this._paused = true; + } + }; + Animation.prototype.resume = function () { + if (this._paused) { + this._pausedTime += getTime() - this._pauseStart; + this._paused = false; + } + }; + Animation.prototype.clear = function () { + var clip = this._head; + while (clip) { + var nextClip = clip.next; + clip.prev = clip.next = clip.animation = null; + clip = nextClip; + } + this._head = this._tail = null; + }; + Animation.prototype.isFinished = function () { + return this._head == null; + }; + Animation.prototype.animate = function (target, options) { + options = options || {}; + this.start(); + var animator = new Animator(target, options.loop); + this.addAnimator(animator); + return animator; + }; + return Animation; +}(Eventful)); + +var TOUCH_CLICK_DELAY = 300; +var globalEventSupported = env.domSupported; +var localNativeListenerNames = (function () { + var mouseHandlerNames = [ + 'click', 'dblclick', 'mousewheel', 'wheel', 'mouseout', + 'mouseup', 'mousedown', 'mousemove', 'contextmenu' + ]; + var touchHandlerNames = [ + 'touchstart', 'touchend', 'touchmove' + ]; + var pointerEventNameMap = { + pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1 + }; + var pointerHandlerNames = map$1(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'] +}; +var wheelEventSupported = false; +function isPointerFromTouch(event) { + var pointerType = event.pointerType; + return pointerType === 'pen' || pointerType === 'touch'; +} +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); +} +function markTouch(event) { + event && (event.zrByTouch = true); +} +function normalizeGlobalEvent(instance, event) { + return normalizeEvent(instance.dom, new FakeGlobalEvent(instance, event), true); +} +function isLocalEl(instance, el) { + var elTmp = el; + var isLocal = false; + while (elTmp && elTmp.nodeType !== 9 + && !(isLocal = elTmp.domBelongToZr + || (elTmp !== el && elTmp === instance.painterRoot))) { + elTmp = elTmp.parentNode; + } + return isLocal; +} +var FakeGlobalEvent = (function () { + function FakeGlobalEvent(instance, event) { + this.stopPropagation = noop; + this.stopImmediatePropagation = noop; + this.preventDefault = noop; + this.type = event.type; + this.target = this.currentTarget = instance.dom; + this.pointerType = event.pointerType; + this.clientX = event.clientX; + this.clientY = event.clientY; + } + return FakeGlobalEvent; +}()); +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])) { + this.__togglePointerCapture(true); + } + this.trigger('mousemove', event); + }, + mouseup: function (event) { + event = normalizeEvent(this.dom, event); + this.__togglePointerCapture(false); + this.trigger('mouseup', event); + }, + mouseout: function (event) { + event = normalizeEvent(this.dom, event); + var element = event.toElement || event.relatedTarget; + if (!isLocalEl(this, element)) { + if (this.__pointerCapturing) { + event.zrEventControl = 'no_globalout'; + } + this.trigger('mouseout', event); + } + }, + wheel: function (event) { + wheelEventSupported = true; + event = normalizeEvent(this.dom, event); + this.trigger('mousewheel', event); + }, + mousewheel: function (event) { + if (wheelEventSupported) { + return; + } + event = normalizeEvent(this.dom, event); + this.trigger('mousewheel', event); + }, + touchstart: function (event) { + event = normalizeEvent(this.dom, event); + markTouch(event); + this.__lastTouchMoment = new Date(); + this.handler.processGesture(event, 'start'); + 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'); + 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); + if (+new Date() - (+this.__lastTouchMoment) < TOUCH_CLICK_DELAY) { + localDOMHandlers.click.call(this, event); + } + }, + pointerdown: function (event) { + localDOMHandlers.mousedown.call(this, event); + }, + pointermove: function (event) { + if (!isPointerFromTouch(event)) { + localDOMHandlers.mousemove.call(this, event); + } + }, + pointerup: function (event) { + localDOMHandlers.mouseup.call(this, event); + }, + pointerout: function (event) { + if (!isPointerFromTouch(event)) { + localDOMHandlers.mouseout.call(this, event); + } + } +}; +each$4(['click', 'dblclick', 'contextmenu'], function (name) { + localDOMHandlers[name] = function (event) { + event = normalizeEvent(this.dom, event); + this.trigger(name, event); + }; +}); +var globalDOMHandlers = { + pointermove: function (event) { + 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; + this.__togglePointerCapture(false); + this.trigger('mouseup', event); + if (pointerCaptureReleasing) { + event.zrEventControl = 'only_globalout'; + this.trigger('mouseout', event); + } + } +}; +function mountLocalDOMEventListeners(instance, scope) { + var domHandlers = scope.domHandlers; + if (env.pointerEventsSupported) { + each$4(localNativeListenerNames.pointer, function (nativeEventName) { + mountSingleDOMEventListener(scope, nativeEventName, function (event) { + domHandlers[nativeEventName].call(instance, event); + }); + }); + } + else { + if (env.touchEventsSupported) { + each$4(localNativeListenerNames.touch, function (nativeEventName) { + mountSingleDOMEventListener(scope, nativeEventName, function (event) { + domHandlers[nativeEventName].call(instance, event); + setTouchTimer(scope); + }); + }); + } + each$4(localNativeListenerNames.mouse, function (nativeEventName) { + mountSingleDOMEventListener(scope, nativeEventName, function (event) { + event = getNativeEvent(event); + if (!scope.touching) { + domHandlers[nativeEventName].call(instance, event); + } + }); + }); + } +} +function mountGlobalDOMEventListeners(instance, scope) { + if (env.pointerEventsSupported) { + each$4(globalNativeListenerNames.pointer, mount); + } + else if (!env.touchEventsSupported) { + each$4(globalNativeListenerNames.mouse, mount); + } + function mount(nativeEventName) { + function nativeEventListener(event) { + event = getNativeEvent(event); + if (!isLocalEl(instance, event.target)) { + event = normalizeGlobalEvent(instance, event); + scope.domHandlers[nativeEventName].call(instance, event); + } + } + mountSingleDOMEventListener(scope, nativeEventName, nativeEventListener, { capture: true }); + } +} +function mountSingleDOMEventListener(scope, nativeEventName, listener, opt) { + scope.mounted[nativeEventName] = listener; + scope.listenerOpts[nativeEventName] = opt; + addEventListener(scope.domTarget, nativeEventName, listener, opt); +} +function unmountDOMEventListeners(scope) { + var mounted = scope.mounted; + for (var nativeEventName in mounted) { + if (mounted.hasOwnProperty(nativeEventName)) { + removeEventListener(scope.domTarget, nativeEventName, mounted[nativeEventName], scope.listenerOpts[nativeEventName]); + } + } + scope.mounted = {}; +} +var DOMHandlerScope = (function () { + function DOMHandlerScope(domTarget, domHandlers) { + this.mounted = {}; + this.listenerOpts = {}; + this.touching = false; + this.domTarget = domTarget; + this.domHandlers = domHandlers; + } + return DOMHandlerScope; +}()); +var HandlerDomProxy = (function (_super) { + __extends(HandlerDomProxy, _super); + function HandlerDomProxy(dom, painterRoot) { + var _this = _super.call(this) || this; + _this.__pointerCapturing = false; + _this.dom = dom; + _this.painterRoot = painterRoot; + _this._localHandlerScope = new DOMHandlerScope(dom, localDOMHandlers); + if (globalEventSupported) { + _this._globalHandlerScope = new DOMHandlerScope(document, globalDOMHandlers); + } + mountLocalDOMEventListeners(_this, _this._localHandlerScope); + return _this; + } + HandlerDomProxy.prototype.dispose = function () { + unmountDOMEventListeners(this._localHandlerScope); + if (globalEventSupported) { + unmountDOMEventListeners(this._globalHandlerScope); + } + }; + HandlerDomProxy.prototype.setCursor = function (cursorStyle) { + this.dom.style && (this.dom.style.cursor = cursorStyle || 'default'); + }; + HandlerDomProxy.prototype.__togglePointerCapture = function (isPointerCapturing) { + this.__mayPointerCapture = null; + if (globalEventSupported + && ((+this.__pointerCapturing) ^ (+isPointerCapturing))) { + this.__pointerCapturing = isPointerCapturing; + var globalHandlerScope = this._globalHandlerScope; + isPointerCapturing + ? mountGlobalDOMEventListeners(this, globalHandlerScope) + : unmountDOMEventListeners(globalHandlerScope); + } + }; + return HandlerDomProxy; +}(Eventful)); + +var dpr = 1; +if (env.hasGlobalWindow) { + dpr = Math.max(window.devicePixelRatio + || (window.screen && window.screen.deviceXDPI / window.screen.logicalXDPI) + || 1, 1); +} +var devicePixelRatio = dpr; +var DARK_MODE_THRESHOLD = 0.4; +var DARK_LABEL_COLOR = '#333'; +var LIGHT_LABEL_COLOR = '#ccc'; +var LIGHTER_LABEL_COLOR = '#eee'; + +var mIdentity = identity; +var EPSILON$1 = 5e-5; +function isNotAroundZero(val) { + return val > EPSILON$1 || val < -5e-5; +} +var scaleTmp = []; +var tmpTransform = []; +var originTransform = create(); +var abs = Math.abs; +var Transformable = (function () { + function Transformable() { + } + Transformable.prototype.getLocalTransform = function (m) { + return Transformable.getLocalTransform(this, m); + }; + Transformable.prototype.setPosition = function (arr) { + this.x = arr[0]; + this.y = arr[1]; + }; + Transformable.prototype.setScale = function (arr) { + this.scaleX = arr[0]; + this.scaleY = arr[1]; + }; + Transformable.prototype.setSkew = function (arr) { + this.skewX = arr[0]; + this.skewY = arr[1]; + }; + Transformable.prototype.setOrigin = function (arr) { + this.originX = arr[0]; + this.originY = arr[1]; + }; + Transformable.prototype.needLocalTransform = function () { + return isNotAroundZero(this.rotation) + || isNotAroundZero(this.x) + || isNotAroundZero(this.y) + || isNotAroundZero(this.scaleX - 1) + || isNotAroundZero(this.scaleY - 1) + || isNotAroundZero(this.skewX) + || isNotAroundZero(this.skewY); + }; + Transformable.prototype.updateTransform = function () { + var parentTransform = this.parent && this.parent.transform; + var needLocalTransform = this.needLocalTransform(); + var m = this.transform; + if (!(needLocalTransform || parentTransform)) { + if (m) { + mIdentity(m); + this.invTransform = null; + } + return; + } + m = m || create(); + if (needLocalTransform) { + this.getLocalTransform(m); + } + else { + mIdentity(m); + } + if (parentTransform) { + if (needLocalTransform) { + mul(m, parentTransform, m); + } + else { + copy(m, parentTransform); + } + } + this.transform = m; + this._resolveGlobalScaleRatio(m); + }; + Transformable.prototype._resolveGlobalScaleRatio = function (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(); + invert(this.invTransform, m); + }; + Transformable.prototype.getComputedTransform = function () { + var transformNode = this; + var ancestors = []; + while (transformNode) { + ancestors.push(transformNode); + transformNode = transformNode.parent; + } + while (transformNode = ancestors.pop()) { + transformNode.updateTransform(); + } + return this.transform; + }; + Transformable.prototype.setLocalTransform = function (m) { + if (!m) { + return; + } + var sx = m[0] * m[0] + m[1] * m[1]; + var sy = m[2] * m[2] + m[3] * m[3]; + var rotation = Math.atan2(m[1], m[0]); + var shearX = Math.PI / 2 + rotation - Math.atan2(m[3], m[2]); + sy = Math.sqrt(sy) * Math.cos(shearX); + sx = Math.sqrt(sx); + this.skewX = shearX; + this.skewY = 0; + this.rotation = -rotation; + this.x = +m[4]; + this.y = +m[5]; + this.scaleX = sx; + this.scaleY = sy; + this.originX = 0; + this.originY = 0; + }; + Transformable.prototype.decomposeTransform = function () { + if (!this.transform) { + return; + } + var parent = this.parent; + var m = this.transform; + if (parent && parent.transform) { + parent.invTransform = parent.invTransform || create(); + mul(tmpTransform, parent.invTransform, m); + m = tmpTransform; + } + var ox = this.originX; + var oy = this.originY; + if (ox || oy) { + originTransform[4] = ox; + originTransform[5] = oy; + mul(tmpTransform, m, originTransform); + tmpTransform[4] -= ox; + tmpTransform[5] -= oy; + m = tmpTransform; + } + this.setLocalTransform(m); + }; + Transformable.prototype.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; + }; + Transformable.prototype.transformCoordToLocal = function (x, y) { + var v2 = [x, y]; + var invTransform = this.invTransform; + if (invTransform) { + applyTransform$1(v2, v2, invTransform); + } + return v2; + }; + Transformable.prototype.transformCoordToGlobal = function (x, y) { + var v2 = [x, y]; + var transform = this.transform; + if (transform) { + applyTransform$1(v2, v2, transform); + } + return v2; + }; + Transformable.prototype.getLineScale = function () { + var m = this.transform; + 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; + }; + Transformable.prototype.copyTransform = function (source) { + copyTransform(this, source); + }; + Transformable.getLocalTransform = function (target, m) { + m = m || []; + var ox = target.originX || 0; + var oy = target.originY || 0; + var sx = target.scaleX; + var sy = target.scaleY; + var ax = target.anchorX; + var ay = target.anchorY; + var rotation = target.rotation || 0; + var x = target.x; + var y = target.y; + var skewX = target.skewX ? Math.tan(target.skewX) : 0; + var skewY = target.skewY ? Math.tan(-target.skewY) : 0; + if (ox || oy || ax || ay) { + var dx = ox + ax; + var dy = oy + ay; + m[4] = -dx * sx - skewX * dy * sy; + m[5] = -dy * sy - skewY * dx * sx; + } + else { + m[4] = m[5] = 0; + } + m[0] = sx; + m[3] = sy; + m[1] = skewY * sx; + m[2] = skewX * sy; + rotation && rotate(m, m, rotation); + m[4] += ox + x; + m[5] += oy + y; + return m; + }; + Transformable.initDefaultProps = (function () { + var proto = Transformable.prototype; + proto.scaleX = + proto.scaleY = + proto.globalScaleRatio = 1; + proto.x = + proto.y = + proto.originX = + proto.originY = + proto.skewX = + proto.skewY = + proto.rotation = + proto.anchorX = + proto.anchorY = 0; + })(); + return Transformable; +}()); +var TRANSFORMABLE_PROPS = [ + 'x', 'y', 'originX', 'originY', 'anchorX', 'anchorY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY' +]; +function copyTransform(target, source) { + for (var i = 0; i < TRANSFORMABLE_PROPS.length; i++) { + var propName = TRANSFORMABLE_PROPS[i]; + target[propName] = source[propName]; + } +} + +var textWidthCache = {}; +function getWidth(text, font) { + font = font || DEFAULT_FONT; + var cacheOfFont = textWidthCache[font]; + if (!cacheOfFont) { + cacheOfFont = textWidthCache[font] = new LRU(500); + } + var width = cacheOfFont.get(text); + if (width == null) { + width = platformApi.measureText(text, font).width; + cacheOfFont.put(text, width); + } + return width; +} +function innerGetBoundingRect(text, font, textAlign, textBaseline) { + var width = getWidth(text, font); + var height = getLineHeight(font); + var x = adjustTextX(0, width, textAlign); + var y = adjustTextY(0, height, textBaseline); + var rect = new BoundingRect(x, y, width, height); + return rect; +} +function getBoundingRect(text, font, textAlign, textBaseline) { + var textLines = ((text || '') + '').split('\n'); + var len = textLines.length; + if (len === 1) { + return innerGetBoundingRect(textLines[0], font, textAlign, textBaseline); + } + else { + var uniondRect = new BoundingRect(0, 0, 0, 0); + for (var i = 0; i < textLines.length; i++) { + var rect = innerGetBoundingRect(textLines[i], font, textAlign, textBaseline); + i === 0 ? uniondRect.copy(rect) : uniondRect.union(rect); + } + return uniondRect; + } +} +function adjustTextX(x, width, textAlign) { + if (textAlign === 'right') { + x -= width; + } + else if (textAlign === 'center') { + x -= width / 2; + } + return x; +} +function adjustTextY(y, height, verticalAlign) { + if (verticalAlign === 'middle') { + y -= height / 2; + } + else if (verticalAlign === 'bottom') { + y -= height; + } + return y; +} +function getLineHeight(font) { + return getWidth('国', font); +} +function parsePercent$1(value, maxValue) { + if (typeof value === 'string') { + if (value.lastIndexOf('%') >= 0) { + return parseFloat(value) / 100 * maxValue; + } + return parseFloat(value); + } + return value; +} +function calculateTextPosition(out, opts, rect) { + var textPosition = opts.position || 'inside'; + var distance = opts.distance != null ? opts.distance : 5; + var height = rect.height; + var width = rect.width; + var halfHeight = height / 2; + var x = rect.x; + var y = rect.y; + var textAlign = 'left'; + var textVerticalAlign = 'top'; + if (textPosition instanceof Array) { + x += parsePercent$1(textPosition[0], rect.width); + y += parsePercent$1(textPosition[1], rect.height); + textAlign = null; + textVerticalAlign = null; + } + else { + 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.align = textAlign; + out.verticalAlign = textVerticalAlign; + return out; +} + +var PRESERVED_NORMAL_STATE = "__zr_normal__"; +var PRIMARY_STATES_KEYS$1 = TRANSFORMABLE_PROPS.concat(["ignore"]); +var DEFAULT_ANIMATABLE_MAP = reduce(TRANSFORMABLE_PROPS, function(obj, key) { + obj[key] = true; + return obj; +}, { ignore: false }); +var tmpTextPosCalcRes = {}; +var tmpBoundingRect = new BoundingRect(0, 0, 0, 0); +var Element = function() { + function Element2(props) { + this.id = guid(); + this.animators = []; + this.currentStates = []; + this.states = {}; + this._init(props); + } + Element2.prototype._init = function(props) { + this.attr(props); + }; + Element2.prototype.drift = function(dx, dy, e) { + 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.markRedraw(); + }; + Element2.prototype.beforeUpdate = function() { + }; + Element2.prototype.afterUpdate = function() { + }; + Element2.prototype.update = function() { + this.updateTransform(); + if (this.__dirty) { + this.updateInnerText(); + } + }; + Element2.prototype.updateInnerText = function(forceUpdate) { + var textEl = this._textContent; + if (textEl && (!textEl.ignore || forceUpdate)) { + if (!this.textConfig) { + this.textConfig = {}; + } + var textConfig = this.textConfig; + var isLocal = textConfig.local; + var innerTransformable = textEl.innerTransformable; + var textAlign = void 0; + var textVerticalAlign = void 0; + var textStyleChanged = false; + innerTransformable.parent = isLocal ? this : null; + var innerOrigin = false; + innerTransformable.copyTransform(textEl); + if (textConfig.position != null) { + var layoutRect = tmpBoundingRect; + if (textConfig.layoutRect) { + layoutRect.copy(textConfig.layoutRect); + } else { + layoutRect.copy(this.getBoundingRect()); + } + if (!isLocal) { + layoutRect.applyTransform(this.transform); + } + if (this.calculateTextPosition) { + this.calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect); + } else { + calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect); + } + innerTransformable.x = tmpTextPosCalcRes.x; + innerTransformable.y = tmpTextPosCalcRes.y; + textAlign = tmpTextPosCalcRes.align; + textVerticalAlign = tmpTextPosCalcRes.verticalAlign; + var textOrigin = textConfig.origin; + if (textOrigin && textConfig.rotation != null) { + var relOriginX = void 0; + var relOriginY = void 0; + if (textOrigin === "center") { + relOriginX = layoutRect.width * 0.5; + relOriginY = layoutRect.height * 0.5; + } else { + relOriginX = parsePercent$1(textOrigin[0], layoutRect.width); + relOriginY = parsePercent$1(textOrigin[1], layoutRect.height); + } + innerOrigin = true; + innerTransformable.originX = -innerTransformable.x + relOriginX + (isLocal ? 0 : layoutRect.x); + innerTransformable.originY = -innerTransformable.y + relOriginY + (isLocal ? 0 : layoutRect.y); + } + } + if (textConfig.rotation != null) { + innerTransformable.rotation = textConfig.rotation; + } + var textOffset = textConfig.offset; + if (textOffset) { + innerTransformable.x += textOffset[0]; + innerTransformable.y += textOffset[1]; + if (!innerOrigin) { + innerTransformable.originX = -textOffset[0]; + innerTransformable.originY = -textOffset[1]; + } + } + var isInside = textConfig.inside == null ? typeof textConfig.position === "string" && textConfig.position.indexOf("inside") >= 0 : textConfig.inside; + var innerTextDefaultStyle = this._innerTextDefaultStyle || (this._innerTextDefaultStyle = {}); + var textFill = void 0; + var textStroke = void 0; + var autoStroke = void 0; + if (isInside && this.canBeInsideText()) { + textFill = textConfig.insideFill; + textStroke = textConfig.insideStroke; + if (textFill == null || textFill === "auto") { + textFill = this.getInsideTextFill(); + } + if (textStroke == null || textStroke === "auto") { + textStroke = this.getInsideTextStroke(textFill); + autoStroke = true; + } + } else { + textFill = textConfig.outsideFill; + textStroke = textConfig.outsideStroke; + if (textFill == null || textFill === "auto") { + textFill = this.getOutsideFill(); + } + if (textStroke == null || textStroke === "auto") { + textStroke = this.getOutsideStroke(textFill); + autoStroke = true; + } + } + textFill = textFill || "#000"; + if (textFill !== innerTextDefaultStyle.fill || textStroke !== innerTextDefaultStyle.stroke || autoStroke !== innerTextDefaultStyle.autoStroke || textAlign !== innerTextDefaultStyle.align || textVerticalAlign !== innerTextDefaultStyle.verticalAlign) { + textStyleChanged = true; + innerTextDefaultStyle.fill = textFill; + innerTextDefaultStyle.stroke = textStroke; + innerTextDefaultStyle.autoStroke = autoStroke; + innerTextDefaultStyle.align = textAlign; + innerTextDefaultStyle.verticalAlign = textVerticalAlign; + textEl.setDefaultTextStyle(innerTextDefaultStyle); + } + textEl.__dirty |= REDRAW_BIT; + if (textStyleChanged) { + textEl.dirtyStyle(true); + } + } + }; + Element2.prototype.canBeInsideText = function() { + return true; + }; + Element2.prototype.getInsideTextFill = function() { + return "#fff"; + }; + Element2.prototype.getInsideTextStroke = function(textFill) { + return "#000"; + }; + Element2.prototype.getOutsideFill = function() { + return this.__zr && this.__zr.isDarkMode() ? LIGHT_LABEL_COLOR : DARK_LABEL_COLOR; + }; + Element2.prototype.getOutsideStroke = function(textFill) { + var backgroundColor = this.__zr && this.__zr.getBackgroundColor(); + var colorArr = typeof backgroundColor === "string" && parse(backgroundColor); + if (!colorArr) { + colorArr = [255, 255, 255, 1]; + } + var alpha = colorArr[3]; + var isDark = this.__zr.isDarkMode(); + for (var i = 0; i < 3; i++) { + colorArr[i] = colorArr[i] * alpha + (isDark ? 0 : 255) * (1 - alpha); + } + colorArr[3] = 1; + return stringify(colorArr, "rgba"); + }; + Element2.prototype.traverse = function(cb, context) { + }; + Element2.prototype.attrKV = function(key, value) { + if (key === "textConfig") { + this.setTextConfig(value); + } else if (key === "textContent") { + this.setTextContent(value); + } else if (key === "clipPath") { + this.setClipPath(value); + } else if (key === "extra") { + this.extra = this.extra || {}; + extend(this.extra, value); + } else { + this[key] = value; + } + }; + Element2.prototype.hide = function() { + this.ignore = true; + this.markRedraw(); + }; + Element2.prototype.show = function() { + this.ignore = false; + this.markRedraw(); + }; + Element2.prototype.attr = function(keyOrObj, value) { + if (typeof keyOrObj === "string") { + this.attrKV(keyOrObj, value); + } else if (isObject$2(keyOrObj)) { + var obj = keyOrObj; + var keysArr = keys(obj); + for (var i = 0; i < keysArr.length; i++) { + var key = keysArr[i]; + this.attrKV(key, keyOrObj[key]); + } + } + this.markRedraw(); + return this; + }; + Element2.prototype.saveCurrentToNormalState = function(toState) { + this._innerSaveToNormal(toState); + var normalState = this._normalState; + for (var i = 0; i < this.animators.length; i++) { + var animator = this.animators[i]; + var fromStateTransition = animator.__fromStateTransition; + if (animator.getLoop() || fromStateTransition && fromStateTransition !== PRESERVED_NORMAL_STATE) { + continue; + } + var targetName = animator.targetName; + var target = targetName ? normalState[targetName] : normalState; + animator.saveTo(target); + } + }; + Element2.prototype._innerSaveToNormal = function(toState) { + var normalState = this._normalState; + if (!normalState) { + normalState = this._normalState = {}; + } + if (toState.textConfig && !normalState.textConfig) { + normalState.textConfig = this.textConfig; + } + this._savePrimaryToNormal(toState, normalState, PRIMARY_STATES_KEYS$1); + }; + Element2.prototype._savePrimaryToNormal = function(toState, normalState, primaryKeys) { + for (var i = 0; i < primaryKeys.length; i++) { + var key = primaryKeys[i]; + if (toState[key] != null && !(key in normalState)) { + normalState[key] = this[key]; + } + } + }; + Element2.prototype.hasState = function() { + return this.currentStates.length > 0; + }; + Element2.prototype.getState = function(name) { + return this.states[name]; + }; + Element2.prototype.ensureState = function(name) { + var states = this.states; + if (!states[name]) { + states[name] = {}; + } + return states[name]; + }; + Element2.prototype.clearStates = function(noAnimation) { + this.useState(PRESERVED_NORMAL_STATE, false, noAnimation); + }; + Element2.prototype.useState = function(stateName, keepCurrentStates, noAnimation, forceUseHoverLayer) { + var toNormalState = stateName === PRESERVED_NORMAL_STATE; + var hasStates = this.hasState(); + if (!hasStates && toNormalState) { + return; + } + var currentStates = this.currentStates; + var animationCfg = this.stateTransition; + if (indexOf(currentStates, stateName) >= 0 && (keepCurrentStates || currentStates.length === 1)) { + return; + } + var state; + if (this.stateProxy && !toNormalState) { + state = this.stateProxy(stateName); + } + if (!state) { + state = this.states && this.states[stateName]; + } + if (!state && !toNormalState) { + logError("State " + stateName + " not exists."); + return; + } + if (!toNormalState) { + this.saveCurrentToNormalState(state); + } + var useHoverLayer = !!(state && state.hoverLayer || forceUseHoverLayer); + if (useHoverLayer) { + this._toggleHoverLayerFlag(true); + } + this._applyStateObj(stateName, state, this._normalState, keepCurrentStates, !noAnimation && !this.__inHover && animationCfg && animationCfg.duration > 0, animationCfg); + var textContent = this._textContent; + var textGuide = this._textGuide; + if (textContent) { + textContent.useState(stateName, keepCurrentStates, noAnimation, useHoverLayer); + } + if (textGuide) { + textGuide.useState(stateName, keepCurrentStates, noAnimation, useHoverLayer); + } + if (toNormalState) { + this.currentStates = []; + this._normalState = {}; + } else { + if (!keepCurrentStates) { + this.currentStates = [stateName]; + } else { + this.currentStates.push(stateName); + } + } + this._updateAnimationTargets(); + this.markRedraw(); + if (!useHoverLayer && this.__inHover) { + this._toggleHoverLayerFlag(false); + this.__dirty &= -2; + } + return state; + }; + Element2.prototype.useStates = function(states, noAnimation, forceUseHoverLayer) { + if (!states.length) { + this.clearStates(); + } else { + var stateObjects = []; + var currentStates = this.currentStates; + var len = states.length; + var notChange = len === currentStates.length; + if (notChange) { + for (var i = 0; i < len; i++) { + if (states[i] !== currentStates[i]) { + notChange = false; + break; + } + } + } + if (notChange) { + return; + } + for (var i = 0; i < len; i++) { + var stateName = states[i]; + var stateObj = void 0; + if (this.stateProxy) { + stateObj = this.stateProxy(stateName, states); + } + if (!stateObj) { + stateObj = this.states[stateName]; + } + if (stateObj) { + stateObjects.push(stateObj); + } + } + var lastStateObj = stateObjects[len - 1]; + var useHoverLayer = !!(lastStateObj && lastStateObj.hoverLayer || forceUseHoverLayer); + if (useHoverLayer) { + this._toggleHoverLayerFlag(true); + } + var mergedState = this._mergeStates(stateObjects); + var animationCfg = this.stateTransition; + this.saveCurrentToNormalState(mergedState); + this._applyStateObj(states.join(","), mergedState, this._normalState, false, !noAnimation && !this.__inHover && animationCfg && animationCfg.duration > 0, animationCfg); + var textContent = this._textContent; + var textGuide = this._textGuide; + if (textContent) { + textContent.useStates(states, noAnimation, useHoverLayer); + } + if (textGuide) { + textGuide.useStates(states, noAnimation, useHoverLayer); + } + this._updateAnimationTargets(); + this.currentStates = states.slice(); + this.markRedraw(); + if (!useHoverLayer && this.__inHover) { + this._toggleHoverLayerFlag(false); + this.__dirty &= -2; + } + } + }; + Element2.prototype.isSilent = function() { + var isSilent = this.silent; + var ancestor = this.parent; + while (!isSilent && ancestor) { + if (ancestor.silent) { + isSilent = true; + break; + } + ancestor = ancestor.parent; + } + return isSilent; + }; + Element2.prototype._updateAnimationTargets = function() { + for (var i = 0; i < this.animators.length; i++) { + var animator = this.animators[i]; + if (animator.targetName) { + animator.changeTarget(this[animator.targetName]); + } + } + }; + Element2.prototype.removeState = function(state) { + var idx = indexOf(this.currentStates, state); + if (idx >= 0) { + var currentStates = this.currentStates.slice(); + currentStates.splice(idx, 1); + this.useStates(currentStates); + } + }; + Element2.prototype.replaceState = function(oldState, newState, forceAdd) { + var currentStates = this.currentStates.slice(); + var idx = indexOf(currentStates, oldState); + var newStateExists = indexOf(currentStates, newState) >= 0; + if (idx >= 0) { + if (!newStateExists) { + currentStates[idx] = newState; + } else { + currentStates.splice(idx, 1); + } + } else if (forceAdd && !newStateExists) { + currentStates.push(newState); + } + this.useStates(currentStates); + }; + Element2.prototype.toggleState = function(state, enable) { + if (enable) { + this.useState(state, true); + } else { + this.removeState(state); + } + }; + Element2.prototype._mergeStates = function(states) { + var mergedState = {}; + var mergedTextConfig; + for (var i = 0; i < states.length; i++) { + var state = states[i]; + extend(mergedState, state); + if (state.textConfig) { + mergedTextConfig = mergedTextConfig || {}; + extend(mergedTextConfig, state.textConfig); + } + } + if (mergedTextConfig) { + mergedState.textConfig = mergedTextConfig; + } + return mergedState; + }; + Element2.prototype._applyStateObj = function(stateName, state, normalState, keepCurrentStates, transition, animationCfg) { + var needsRestoreToNormal = !(state && keepCurrentStates); + if (state && state.textConfig) { + this.textConfig = extend({}, keepCurrentStates ? this.textConfig : normalState.textConfig); + extend(this.textConfig, state.textConfig); + } else if (needsRestoreToNormal) { + if (normalState.textConfig) { + this.textConfig = normalState.textConfig; + } + } + var transitionTarget = {}; + var hasTransition = false; + for (var i = 0; i < PRIMARY_STATES_KEYS$1.length; i++) { + var key = PRIMARY_STATES_KEYS$1[i]; + var propNeedsTransition = transition && DEFAULT_ANIMATABLE_MAP[key]; + if (state && state[key] != null) { + if (propNeedsTransition) { + hasTransition = true; + transitionTarget[key] = state[key]; + } else { + this[key] = state[key]; + } + } else if (needsRestoreToNormal) { + if (normalState[key] != null) { + if (propNeedsTransition) { + hasTransition = true; + transitionTarget[key] = normalState[key]; + } else { + this[key] = normalState[key]; + } + } + } + } + if (!transition) { + for (var i = 0; i < this.animators.length; i++) { + var animator = this.animators[i]; + var targetName = animator.targetName; + if (!animator.getLoop()) { + animator.__changeFinalValue(targetName ? (state || normalState)[targetName] : state || normalState); + } + } + } + if (hasTransition) { + this._transitionState(stateName, transitionTarget, animationCfg); + } + }; + Element2.prototype._attachComponent = function(componentEl) { + if (componentEl.__zr && !componentEl.__hostTarget) { + return; + } + if (componentEl === this) { + return; + } + var zr = this.__zr; + if (zr) { + componentEl.addSelfToZr(zr); + } + componentEl.__zr = zr; + componentEl.__hostTarget = this; + }; + Element2.prototype._detachComponent = function(componentEl) { + if (componentEl.__zr) { + componentEl.removeSelfFromZr(componentEl.__zr); + } + componentEl.__zr = null; + componentEl.__hostTarget = null; + }; + Element2.prototype.getClipPath = function() { + return this._clipPath; + }; + Element2.prototype.setClipPath = function(clipPath) { + if (this._clipPath && this._clipPath !== clipPath) { + this.removeClipPath(); + } + this._attachComponent(clipPath); + this._clipPath = clipPath; + this.markRedraw(); + }; + Element2.prototype.removeClipPath = function() { + var clipPath = this._clipPath; + if (clipPath) { + this._detachComponent(clipPath); + this._clipPath = null; + this.markRedraw(); + } + }; + Element2.prototype.getTextContent = function() { + return this._textContent; + }; + Element2.prototype.setTextContent = function(textEl) { + var previousTextContent = this._textContent; + if (previousTextContent === textEl) { + return; + } + if (previousTextContent && previousTextContent !== textEl) { + this.removeTextContent(); + } + textEl.innerTransformable = new Transformable(); + this._attachComponent(textEl); + this._textContent = textEl; + this.markRedraw(); + }; + Element2.prototype.setTextConfig = function(cfg) { + if (!this.textConfig) { + this.textConfig = {}; + } + extend(this.textConfig, cfg); + this.markRedraw(); + }; + Element2.prototype.removeTextConfig = function() { + this.textConfig = null; + this.markRedraw(); + }; + Element2.prototype.removeTextContent = function() { + var textEl = this._textContent; + if (textEl) { + textEl.innerTransformable = null; + this._detachComponent(textEl); + this._textContent = null; + this._innerTextDefaultStyle = null; + this.markRedraw(); + } + }; + Element2.prototype.getTextGuideLine = function() { + return this._textGuide; + }; + Element2.prototype.setTextGuideLine = function(guideLine) { + if (this._textGuide && this._textGuide !== guideLine) { + this.removeTextGuideLine(); + } + this._attachComponent(guideLine); + this._textGuide = guideLine; + this.markRedraw(); + }; + Element2.prototype.removeTextGuideLine = function() { + var textGuide = this._textGuide; + if (textGuide) { + this._detachComponent(textGuide); + this._textGuide = null; + this.markRedraw(); + } + }; + Element2.prototype.markRedraw = function() { + this.__dirty |= REDRAW_BIT; + var zr = this.__zr; + if (zr) { + if (this.__inHover) { + zr.refreshHover(); + } else { + zr.refresh(); + } + } + if (this.__hostTarget) { + this.__hostTarget.markRedraw(); + } + }; + Element2.prototype.dirty = function() { + this.markRedraw(); + }; + Element2.prototype._toggleHoverLayerFlag = function(inHover) { + this.__inHover = inHover; + var textContent = this._textContent; + var textGuide = this._textGuide; + if (textContent) { + textContent.__inHover = inHover; + } + if (textGuide) { + textGuide.__inHover = inHover; + } + }; + Element2.prototype.addSelfToZr = function(zr) { + if (this.__zr === zr) { + return; + } + 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); + } + if (this._textContent) { + this._textContent.addSelfToZr(zr); + } + if (this._textGuide) { + this._textGuide.addSelfToZr(zr); + } + }; + Element2.prototype.removeSelfFromZr = function(zr) { + if (!this.__zr) { + return; + } + 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); + } + if (this._textContent) { + this._textContent.removeSelfFromZr(zr); + } + if (this._textGuide) { + this._textGuide.removeSelfFromZr(zr); + } + }; + Element2.prototype.animate = function(key, loop, allowDiscreteAnimation) { + var target = key ? this[key] : this; + var animator = new Animator(target, loop, allowDiscreteAnimation); + key && (animator.targetName = key); + this.addAnimator(animator, key); + return animator; + }; + Element2.prototype.addAnimator = function(animator, key) { + var zr = this.__zr; + var el = this; + animator.during(function() { + el.updateDuringAnimation(key); + }).done(function() { + var animators = el.animators; + var idx = indexOf(animators, animator); + if (idx >= 0) { + animators.splice(idx, 1); + } + }); + this.animators.push(animator); + if (zr) { + zr.animation.addAnimator(animator); + } + zr && zr.wakeUp(); + }; + Element2.prototype.updateDuringAnimation = function(key) { + this.markRedraw(); + }; + Element2.prototype.stopAnimation = function(scope, forwardToLast) { + var animators = this.animators; + var len = animators.length; + var leftAnimators = []; + for (var i = 0; i < len; i++) { + var animator = animators[i]; + if (!scope || scope === animator.scope) { + animator.stop(forwardToLast); + } else { + leftAnimators.push(animator); + } + } + this.animators = leftAnimators; + return this; + }; + Element2.prototype.animateTo = function(target, cfg, animationProps) { + animateTo(this, target, cfg, animationProps); + }; + Element2.prototype.animateFrom = function(target, cfg, animationProps) { + animateTo(this, target, cfg, animationProps, true); + }; + Element2.prototype._transitionState = function(stateName, target, cfg, animationProps) { + var animators = animateTo(this, target, cfg, animationProps); + for (var i = 0; i < animators.length; i++) { + animators[i].__fromStateTransition = stateName; + } + }; + Element2.prototype.getBoundingRect = function() { + return null; + }; + Element2.prototype.getPaintRect = function() { + return null; + }; + Element2.initDefaultProps = function() { + var elProto = Element2.prototype; + elProto.type = "element"; + elProto.name = ""; + elProto.ignore = elProto.silent = elProto.isGroup = elProto.draggable = elProto.dragging = elProto.ignoreClip = elProto.__inHover = false; + elProto.__dirty = REDRAW_BIT; + function createLegacyProperty(key, privateKey, xKey, yKey) { + Object.defineProperty(elProto, key, { + get: function() { + if (!this[privateKey]) { + var pos = this[privateKey] = []; + enhanceArray(this, pos); + } + return this[privateKey]; + }, + set: function(pos) { + this[xKey] = pos[0]; + this[yKey] = pos[1]; + this[privateKey] = pos; + enhanceArray(this, pos); + } + }); + function enhanceArray(self, pos) { + Object.defineProperty(pos, 0, { + get: function() { + return self[xKey]; + }, + set: function(val) { + self[xKey] = val; + } + }); + Object.defineProperty(pos, 1, { + get: function() { + return self[yKey]; + }, + set: function(val) { + self[yKey] = val; + } + }); + } + } + if (Object.defineProperty) { + createLegacyProperty("position", "_legacyPos", "x", "y"); + createLegacyProperty("scale", "_legacyScale", "scaleX", "scaleY"); + createLegacyProperty("origin", "_legacyOrigin", "originX", "originY"); + } + }(); + return Element2; +}(); +mixin(Element, Eventful); +mixin(Element, Transformable); +function animateTo(animatable, target, cfg, animationProps, reverse) { + cfg = cfg || {}; + var animators = []; + animateToShallow(animatable, "", animatable, target, cfg, animationProps, animators, reverse); + var finishCount = animators.length; + var doneHappened = false; + var cfgDone = cfg.done; + var cfgAborted = cfg.aborted; + var doneCb = function() { + doneHappened = true; + finishCount--; + if (finishCount <= 0) { + doneHappened ? cfgDone && cfgDone() : cfgAborted && cfgAborted(); + } + }; + var abortedCb = function() { + finishCount--; + if (finishCount <= 0) { + doneHappened ? cfgDone && cfgDone() : cfgAborted && cfgAborted(); + } + }; + if (!finishCount) { + cfgDone && cfgDone(); + } + if (animators.length > 0 && cfg.during) { + animators[0].during(function(target2, percent) { + cfg.during(percent); + }); + } + for (var i = 0; i < animators.length; i++) { + var animator = animators[i]; + if (doneCb) { + animator.done(doneCb); + } + if (abortedCb) { + animator.aborted(abortedCb); + } + if (cfg.force) { + animator.duration(cfg.duration); + } + animator.start(cfg.easing); + } + return animators; +} +function copyArrShallow(source, target, len) { + for (var i = 0; i < len; i++) { + source[i] = target[i]; + } +} +function is2DArray(value) { + return isArrayLike(value[0]); +} +function copyValue(target, source, key) { + if (isArrayLike(source[key])) { + if (!isArrayLike(target[key])) { + target[key] = []; + } + if (isTypedArray(source[key])) { + var len = source[key].length; + if (target[key].length !== len) { + target[key] = new source[key].constructor(len); + copyArrShallow(target[key], source[key], len); + } + } else { + var sourceArr = source[key]; + var targetArr = target[key]; + var len0 = sourceArr.length; + if (is2DArray(sourceArr)) { + var len1 = sourceArr[0].length; + for (var i = 0; i < len0; i++) { + if (!targetArr[i]) { + targetArr[i] = Array.prototype.slice.call(sourceArr[i]); + } else { + copyArrShallow(targetArr[i], sourceArr[i], len1); + } + } + } else { + copyArrShallow(targetArr, sourceArr, len0); + } + targetArr.length = sourceArr.length; + } + } else { + target[key] = source[key]; + } +} +function isValueSame(val1, val2) { + return val1 === val2 || isArrayLike(val1) && isArrayLike(val2) && is1DArraySame(val1, val2); +} +function is1DArraySame(arr0, arr1) { + var len = arr0.length; + if (len !== arr1.length) { + return false; + } + for (var i = 0; i < len; i++) { + if (arr0[i] !== arr1[i]) { + return false; + } + } + return true; +} +function animateToShallow(animatable, topKey, animateObj, target, cfg, animationProps, animators, reverse) { + var targetKeys = keys(target); + var duration = cfg.duration; + var delay = cfg.delay; + var additive = cfg.additive; + var setToFinal = cfg.setToFinal; + var animateAll = !isObject$2(animationProps); + var existsAnimators = animatable.animators; + var animationKeys = []; + for (var k = 0; k < targetKeys.length; k++) { + var innerKey = targetKeys[k]; + var targetVal = target[innerKey]; + if (targetVal != null && animateObj[innerKey] != null && (animateAll || animationProps[innerKey])) { + if (isObject$2(targetVal) && !isArrayLike(targetVal) && !isGradientObject(targetVal)) { + if (topKey) { + if (!reverse) { + animateObj[innerKey] = targetVal; + animatable.updateDuringAnimation(topKey); + } + continue; + } + animateToShallow(animatable, innerKey, animateObj[innerKey], targetVal, cfg, animationProps && animationProps[innerKey], animators, reverse); + } else { + animationKeys.push(innerKey); + } + } else if (!reverse) { + animateObj[innerKey] = targetVal; + animatable.updateDuringAnimation(topKey); + animationKeys.push(innerKey); + } + } + var keyLen = animationKeys.length; + if (!additive && keyLen) { + for (var i = 0; i < existsAnimators.length; i++) { + var animator = existsAnimators[i]; + if (animator.targetName === topKey) { + var allAborted = animator.stopTracks(animationKeys); + if (allAborted) { + var idx = indexOf(existsAnimators, animator); + existsAnimators.splice(idx, 1); + } + } + } + } + if (!cfg.force) { + animationKeys = filter(animationKeys, function(key) { + return !isValueSame(target[key], animateObj[key]); + }); + keyLen = animationKeys.length; + } + if (keyLen > 0 || cfg.force && !animators.length) { + var revertedSource = void 0; + var reversedTarget = void 0; + var sourceClone = void 0; + if (reverse) { + reversedTarget = {}; + if (setToFinal) { + revertedSource = {}; + } + for (var i = 0; i < keyLen; i++) { + var innerKey = animationKeys[i]; + reversedTarget[innerKey] = animateObj[innerKey]; + if (setToFinal) { + revertedSource[innerKey] = target[innerKey]; + } else { + animateObj[innerKey] = target[innerKey]; + } + } + } else if (setToFinal) { + sourceClone = {}; + for (var i = 0; i < keyLen; i++) { + var innerKey = animationKeys[i]; + sourceClone[innerKey] = cloneValue(animateObj[innerKey]); + copyValue(animateObj, target, innerKey); + } + } + var animator = new Animator(animateObj, false, false, additive ? filter(existsAnimators, function(animator2) { + return animator2.targetName === topKey; + }) : null); + animator.targetName = topKey; + if (cfg.scope) { + animator.scope = cfg.scope; + } + if (setToFinal && revertedSource) { + animator.whenWithKeys(0, revertedSource, animationKeys); + } + if (sourceClone) { + animator.whenWithKeys(0, sourceClone, animationKeys); + } + animator.whenWithKeys(duration == null ? 500 : duration, reverse ? reversedTarget : target, animationKeys).delay(delay || 0); + animatable.addAnimator(animator, topKey); + animators.push(animator); + } +} + +var Group$2 = function(_super) { + __extends(Group2, _super); + function Group2(opts) { + var _this = _super.call(this) || this; + _this.isGroup = true; + _this._children = []; + _this.attr(opts); + return _this; + } + Group2.prototype.childrenRef = function() { + return this._children; + }; + Group2.prototype.children = function() { + return this._children.slice(); + }; + Group2.prototype.childAt = function(idx) { + return this._children[idx]; + }; + Group2.prototype.childOfName = function(name) { + var children = this._children; + for (var i = 0; i < children.length; i++) { + if (children[i].name === name) { + return children[i]; + } + } + }; + Group2.prototype.childCount = function() { + return this._children.length; + }; + Group2.prototype.add = function(child) { + if (child) { + if (child !== this && child.parent !== this) { + this._children.push(child); + this._doAdd(child); + } + } + return this; + }; + Group2.prototype.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; + }; + Group2.prototype.replace = function(oldChild, newChild) { + var idx = indexOf(this._children, oldChild); + if (idx >= 0) { + this.replaceAt(newChild, idx); + } + return this; + }; + Group2.prototype.replaceAt = function(child, index) { + var children = this._children; + var old = children[index]; + if (child && child !== this && child.parent !== this && child !== old) { + children[index] = child; + old.parent = null; + var zr = this.__zr; + if (zr) { + old.removeSelfFromZr(zr); + } + this._doAdd(child); + } + return this; + }; + Group2.prototype._doAdd = function(child) { + if (child.parent) { + child.parent.remove(child); + } + child.parent = this; + var zr = this.__zr; + if (zr && zr !== child.__zr) { + child.addSelfToZr(zr); + } + zr && zr.refresh(); + }; + Group2.prototype.remove = function(child) { + var zr = this.__zr; + var children = this._children; + var idx = indexOf(children, child); + if (idx < 0) { + return this; + } + children.splice(idx, 1); + child.parent = null; + if (zr) { + child.removeSelfFromZr(zr); + } + zr && zr.refresh(); + return this; + }; + Group2.prototype.removeAll = function() { + var children = this._children; + var zr = this.__zr; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (zr) { + child.removeSelfFromZr(zr); + } + child.parent = null; + } + children.length = 0; + return this; + }; + Group2.prototype.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; + }; + Group2.prototype.traverse = function(cb, context) { + for (var i = 0; i < this._children.length; i++) { + var child = this._children[i]; + var stopped = cb.call(context, child); + if (child.isGroup && !stopped) { + child.traverse(cb, context); + } + } + return this; + }; + Group2.prototype.addSelfToZr = function(zr) { + _super.prototype.addSelfToZr.call(this, zr); + for (var i = 0; i < this._children.length; i++) { + var child = this._children[i]; + child.addSelfToZr(zr); + } + }; + Group2.prototype.removeSelfFromZr = function(zr) { + _super.prototype.removeSelfFromZr.call(this, zr); + for (var i = 0; i < this._children.length; i++) { + var child = this._children[i]; + child.removeSelfFromZr(zr); + } + }; + Group2.prototype.getBoundingRect = function(includeChildren) { + var tmpRect = new BoundingRect(0, 0, 0, 0); + var children = includeChildren || this._children; + var tmpMat = []; + var rect = null; + 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); + if (transform) { + BoundingRect.applyTransform(tmpRect, childRect, transform); + rect = rect || tmpRect.clone(); + rect.union(tmpRect); + } else { + rect = rect || childRect.clone(); + rect.union(childRect); + } + } + return rect || tmpRect; + }; + return Group2; +}(Element); +Group$2.prototype.type = "group"; + +/*! +* 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 painterCtors = {}; +var instances$1 = {}; +function delInstance(id) { + delete instances$1[id]; +} +function isDarkMode(backgroundColor) { + if (!backgroundColor) { + return false; + } + if (typeof backgroundColor === "string") { + return lum(backgroundColor, 1) < DARK_MODE_THRESHOLD; + } else if (backgroundColor.colorStops) { + var colorStops = backgroundColor.colorStops; + var totalLum = 0; + var len = colorStops.length; + for (var i = 0; i < len; i++) { + totalLum += lum(colorStops[i].color, 1); + } + totalLum /= len; + return totalLum < DARK_MODE_THRESHOLD; + } + return false; +} +var ZRender = function() { + function ZRender2(id, dom, opts) { + var _this = this; + this._sleepAfterStill = 10; + this._stillFrameAccum = 0; + this._needsRefresh = true; + this._needsRefreshHover = true; + this._darkMode = false; + opts = opts || {}; + this.dom = dom; + this.id = id; + var storage = new Storage(); + var rendererType = opts.renderer || "canvas"; + if (!painterCtors[rendererType]) { + rendererType = keys(painterCtors)[0]; + } + opts.useDirtyRect = opts.useDirtyRect == null ? false : opts.useDirtyRect; + var painter = new painterCtors[rendererType](dom, storage, opts, id); + var ssrMode = opts.ssr || painter.ssrOnly; + this.storage = storage; + this.painter = painter; + var handlerProxy = !env.node && !env.worker && !ssrMode ? new HandlerDomProxy(painter.getViewportRoot(), painter.root) : null; + var useCoarsePointer = opts.useCoarsePointer; + var usePointerSize = useCoarsePointer == null || useCoarsePointer === "auto" ? env.touchEventsSupported : !!useCoarsePointer; + var defaultPointerSize = 44; + var pointerSize; + if (usePointerSize) { + pointerSize = retrieve2(opts.pointerSize, defaultPointerSize); + } + this.handler = new Handler(storage, painter, handlerProxy, painter.root, pointerSize); + this.animation = new Animation({ + stage: { + update: ssrMode ? null : function() { + return _this._flush(true); + } + } + }); + if (!ssrMode) { + this.animation.start(); + } + } + ZRender2.prototype.add = function(el) { + if (this._disposed || !el) { + return; + } + this.storage.addRoot(el); + el.addSelfToZr(this); + this.refresh(); + }; + ZRender2.prototype.remove = function(el) { + if (this._disposed || !el) { + return; + } + this.storage.delRoot(el); + el.removeSelfFromZr(this); + this.refresh(); + }; + ZRender2.prototype.configLayer = function(zLevel, config) { + if (this._disposed) { + return; + } + if (this.painter.configLayer) { + this.painter.configLayer(zLevel, config); + } + this.refresh(); + }; + ZRender2.prototype.setBackgroundColor = function(backgroundColor) { + if (this._disposed) { + return; + } + if (this.painter.setBackgroundColor) { + this.painter.setBackgroundColor(backgroundColor); + } + this.refresh(); + this._backgroundColor = backgroundColor; + this._darkMode = isDarkMode(backgroundColor); + }; + ZRender2.prototype.getBackgroundColor = function() { + return this._backgroundColor; + }; + ZRender2.prototype.setDarkMode = function(darkMode) { + this._darkMode = darkMode; + }; + ZRender2.prototype.isDarkMode = function() { + return this._darkMode; + }; + ZRender2.prototype.refreshImmediately = function(fromInside) { + if (this._disposed) { + return; + } + if (!fromInside) { + this.animation.update(true); + } + this._needsRefresh = false; + this.painter.refresh(); + this._needsRefresh = false; + }; + ZRender2.prototype.refresh = function() { + if (this._disposed) { + return; + } + this._needsRefresh = true; + this.animation.start(); + }; + ZRender2.prototype.flush = function() { + if (this._disposed) { + return; + } + this._flush(false); + }; + ZRender2.prototype._flush = function(fromInside) { + var triggerRendered; + var start = getTime(); + if (this._needsRefresh) { + triggerRendered = true; + this.refreshImmediately(fromInside); + } + if (this._needsRefreshHover) { + triggerRendered = true; + this.refreshHoverImmediately(); + } + var end = getTime(); + if (triggerRendered) { + this._stillFrameAccum = 0; + this.trigger("rendered", { + elapsedTime: end - start + }); + } else if (this._sleepAfterStill > 0) { + this._stillFrameAccum++; + if (this._stillFrameAccum > this._sleepAfterStill) { + this.animation.stop(); + } + } + }; + ZRender2.prototype.setSleepAfterStill = function(stillFramesCount) { + this._sleepAfterStill = stillFramesCount; + }; + ZRender2.prototype.wakeUp = function() { + if (this._disposed) { + return; + } + this.animation.start(); + this._stillFrameAccum = 0; + }; + ZRender2.prototype.refreshHover = function() { + this._needsRefreshHover = true; + }; + ZRender2.prototype.refreshHoverImmediately = function() { + if (this._disposed) { + return; + } + this._needsRefreshHover = false; + if (this.painter.refreshHover && this.painter.getType() === "canvas") { + this.painter.refreshHover(); + } + }; + ZRender2.prototype.resize = function(opts) { + if (this._disposed) { + return; + } + opts = opts || {}; + this.painter.resize(opts.width, opts.height); + this.handler.resize(); + }; + ZRender2.prototype.clearAnimation = function() { + if (this._disposed) { + return; + } + this.animation.clear(); + }; + ZRender2.prototype.getWidth = function() { + if (this._disposed) { + return; + } + return this.painter.getWidth(); + }; + ZRender2.prototype.getHeight = function() { + if (this._disposed) { + return; + } + return this.painter.getHeight(); + }; + ZRender2.prototype.setCursorStyle = function(cursorStyle) { + if (this._disposed) { + return; + } + this.handler.setCursorStyle(cursorStyle); + }; + ZRender2.prototype.findHover = function(x, y) { + if (this._disposed) { + return; + } + return this.handler.findHover(x, y); + }; + ZRender2.prototype.on = function(eventName, eventHandler, context) { + if (!this._disposed) { + this.handler.on(eventName, eventHandler, context); + } + return this; + }; + ZRender2.prototype.off = function(eventName, eventHandler) { + if (this._disposed) { + return; + } + this.handler.off(eventName, eventHandler); + }; + ZRender2.prototype.trigger = function(eventName, event) { + if (this._disposed) { + return; + } + this.handler.trigger(eventName, event); + }; + ZRender2.prototype.clear = function() { + if (this._disposed) { + return; + } + var roots = this.storage.getRoots(); + for (var i = 0; i < roots.length; i++) { + if (roots[i] instanceof Group$2) { + roots[i].removeSelfFromZr(this); + } + } + this.storage.delAllRoots(); + this.painter.clear(); + }; + ZRender2.prototype.dispose = function() { + if (this._disposed) { + return; + } + this.animation.stop(); + this.clear(); + this.storage.dispose(); + this.painter.dispose(); + this.handler.dispose(); + this.animation = this.storage = this.painter = this.handler = null; + this._disposed = true; + delInstance(this.id); + }; + return ZRender2; +}(); +function init$1(dom, opts) { + var zr = new ZRender(guid(), dom, opts); + instances$1[zr.id] = zr; + return zr; +} +function registerPainter(name, Ctor) { + painterCtors[name] = Ctor; +} + +var RADIAN_EPSILON = 1e-4; +// Although chrome already enlarge this number to 100 for `toFixed`, but +// we sill follow the spec for compatibility. +var ROUND_SUPPORTED_PRECISION_MAX = 20; +function _trim(str) { + return str.replace(/^\s+|\s+$/g, ''); +} +/** + * Linear mapping a value from domain to range + * @param val + * @param domain Domain extent domain[0] can be bigger than domain[1] + * @param range Range extent range[0] can be bigger than range[1] + * @param clamp Default to be false + */ +function linearMap(val, domain, range, clamp) { + var d0 = domain[0]; + var d1 = domain[1]; + var r0 = range[0]; + var r1 = range[1]; + var subDomain = d1 - d0; + var subRange = r1 - r0; + if (subDomain === 0) { + return subRange === 0 ? r0 : (r0 + r1) / 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 <= d0) { + return r0; + } else if (val >= d1) { + return r1; + } + } else { + if (val >= d0) { + return r0; + } else if (val <= d1) { + return r1; + } + } + } else { + if (val === d0) { + return r0; + } + if (val === d1) { + return r1; + } + } + return (val - d0) / subDomain * subRange + r0; +} +/** + * Convert a percent string to absolute number. + * Returns NaN if percent is not a valid string or number + */ +function parsePercent(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 (isString(percent)) { + if (_trim(percent).match(/%$/)) { + return parseFloat(percent) / 100 * all; + } + return parseFloat(percent); + } + return percent == null ? NaN : +percent; +} +function round$1(x, precision, returnStr) { + if (precision == null) { + precision = 10; + } + // Avoid range error + precision = Math.min(Math.max(0, precision), ROUND_SUPPORTED_PRECISION_MAX); + // PENDING: 1.005.toFixed(2) is '1.00' rather than '1.01' + x = (+x).toFixed(precision); + return returnStr ? x : +x; +} +/** + * Get precision. + */ +function getPrecision(val) { + val = +val; + if (isNaN(val)) { + return 0; + } + // It is much faster than methods converting number to string as follows + // let tmp = val.toString(); + // return tmp.length - 1 - tmp.indexOf('.'); + // especially when precision is low + // Notice: + // (1) If the loop count is over about 20, it is slower than `getPrecisionSafe`. + // (see https://jsbench.me/2vkpcekkvw/1) + // (2) If the val is less than for example 1e-15, the result may be incorrect. + // (see test/ut/spec/util/number.test.ts `getPrecision_equal_random`) + if (val > 1e-14) { + var e = 1; + for (var i = 0; i < 15; i++, e *= 10) { + if (Math.round(val * e) / e === val) { + return i; + } + } + } + return getPrecisionSafe(val); +} +/** + * Get precision with slow but safe method + */ +function getPrecisionSafe(val) { + // toLowerCase for: '3.4E-12' + var str = val.toString().toLowerCase(); + // Consider scientific notation: '3.4e-12' '3.4e+12' + var eIndex = str.indexOf('e'); + var exp = eIndex > 0 ? +str.slice(eIndex + 1) : 0; + var significandPartLen = eIndex > 0 ? eIndex : str.length; + var dotIndex = str.indexOf('.'); + var decimalPartLen = dotIndex < 0 ? 0 : significandPartLen - 1 - dotIndex; + return Math.max(0, decimalPartLen - exp); +} +/** + * Minimal dicernible data precisioin according to a single pixel. + */ +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 remainder method is used. + * https://en.wikipedia.org/wiki/Largest_remainder_method + * + * @param valueList a list of all data + * @param precision integer number showing digits of precision + * @return {Array} + */ +function getPercentSeats(valueList, precision) { + var sum = reduce(valueList, function (acc, val) { + return acc + (isNaN(val) ? 0 : val); + }, 0); + if (sum === 0) { + return []; + } + var digits = Math.pow(10, precision); + var votesPerQuota = map$1(valueList, function (val) { + return (isNaN(val) ? 0 : val) / sum * digits * 100; + }); + var targetSeats = digits * 100; + var seats = map$1(votesPerQuota, function (votes) { + // Assign automatic seats. + return Math.floor(votes); + }); + var currentSum = reduce(seats, function (acc, val) { + return acc + val; + }, 0); + var remainder = map$1(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 map$1(seats, function (seat) { + return seat / digits; + }); +} +/** + * Solve the floating point adding problem like 0.1 + 0.2 === 0.30000000000000004 + * See + */ +function addSafe(val0, val1) { + var maxPrecision = Math.max(getPrecision(val0), getPrecision(val1)); + // const multiplier = Math.pow(10, maxPrecision); + // return (Math.round(val0 * multiplier) + Math.round(val1 * multiplier)) / multiplier; + var sum = val0 + val1; + // // PENDING: support more? + return maxPrecision > ROUND_SUPPORTED_PRECISION_MAX ? sum : round$1(sum, maxPrecision); +} +/** + * To 0 - 2 * PI, considering negative radian. + */ +function remRadian(radian) { + var pi2 = Math.PI * 2; + return (radian % pi2 + pi2) % pi2; +} +/** + * @param {type} radian + * @return {boolean} + */ +function isRadianAroundZero(val) { + return val > -1e-4 && val < RADIAN_EPSILON; +} +// eslint-disable-next-line +var TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d{1,2})(?::(\d{1,2})(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // jshint ignore:line +/** + * @param value valid type: number | string | Date, otherwise return `new Date(NaN)` + * 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 local 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 Never be null/undefined. If invalid, return `new Date(NaN)`. + */ +function parseDate(value) { + if (value instanceof Date) { + return value; + } else if (isString(value)) { + // 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 is specified. + 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] ? +match[7].substring(0, 3) : 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] ? +match[7].substring(0, 3) : 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 val + * @return + */ +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 val non-negative value + * @return + */ +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 val Non-negative value. + * @param round + * @return Niced 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 (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; + } + } + 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; +} +/** + * [Numeric is defined as]: + * `parseFloat(val) == val` + * For example: + * numeric: + * typeof number except NaN, '-123', '123', '2e3', '-2e3', '011', 'Infinity', Infinity, + * and they rounded by white-spaces or line-terminal like ' -123 \n ' (see es spec) + * not-numeric: + * null, undefined, [], {}, true, false, 'NaN', NaN, '123ab', + * empty string, string with only white-spaces or line-terminal (see es spec), + * 0x12, '0x12', '-0x12', 012, '012', '-012', + * non-string, ... + * + * @test See full test cases in `test/ut/spec/util/number.js`. + * @return Must be a typeof number. If not numeric, return NaN. + */ +function numericToNumber(val) { + var valFloat = parseFloat(val); + return valFloat == val // eslint-disable-line eqeqeq + && (valFloat !== 0 || !isString(val) || val.indexOf('x') <= 0) // For case ' 0x0 '. + ? valFloat : NaN; +} +/** + * Definition of "numeric": see `numericToNumber`. + */ +function isNumeric(val) { + return !isNaN(numericToNumber(val)); +} +/** + * Use random base to prevent users hard code depending on + * this auto generated marker id. + * @return An positive integer. + */ +function getRandomIdBase() { + return Math.round(Math.random() * 9); +} +/** + * Get the greatest common divisor. + * + * @param {number} a one number + * @param {number} b the other number + */ +function getGreatestCommonDividor(a, b) { + if (b === 0) { + return a; + } + return getGreatestCommonDividor(b, a % b); +} +/** + * Get the least common multiple. + * + * @param {number} a one number + * @param {number} b the other number + */ +function getLeastCommonMultiple(a, b) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + return a * b / getGreatestCommonDividor(a, b); +} + +function throwError(msg) { + throw new Error(msg); +} + +function interpolateNumber(p0, p1, percent) { + return (p1 - p0) * percent + p0; +} +var DUMMY_COMPONENT_NAME_PREFIX = "series\0"; +var INTERNAL_COMPONENT_ID_PREFIX = "\0_ec_\0"; +function normalizeToArray(value) { + return value instanceof Array ? value : value == null ? [] : [value]; +} +function defaultEmphasis(opt, key, subOpts) { + if (opt) { + opt[key] = opt[key] || {}; + opt.emphasis = opt.emphasis || {}; + opt.emphasis[key] = opt.emphasis[key] || {}; + 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"]; +function getDataItemValue(dataItem) { + return isObject$2(dataItem) && !isArray(dataItem) && !(dataItem instanceof Date) ? dataItem.value : dataItem; +} +function isDataItemOption(dataItem) { + return isObject$2(dataItem) && !(dataItem instanceof Array); +} +function mappingToExists(existings, newCmptOptions, mode) { + var isNormalMergeMode = mode === "normalMerge"; + var isReplaceMergeMode = mode === "replaceMerge"; + var isReplaceAllMode = mode === "replaceAll"; + existings = existings || []; + newCmptOptions = (newCmptOptions || []).slice(); + var existingIdIdxMap = createHashMap(); + each$4(newCmptOptions, function(cmptOption, index) { + if (!isObject$2(cmptOption)) { + newCmptOptions[index] = null; + return; + } + }); + var result = prepareResult(existings, existingIdIdxMap, mode); + if (isNormalMergeMode || isReplaceMergeMode) { + mappingById(result, existings, existingIdIdxMap, newCmptOptions); + } + if (isNormalMergeMode) { + mappingByName(result, newCmptOptions); + } + if (isNormalMergeMode || isReplaceMergeMode) { + mappingByIndex(result, newCmptOptions, isReplaceMergeMode); + } else if (isReplaceAllMode) { + mappingInReplaceAllMode(result, newCmptOptions); + } + makeIdAndName(result); + return result; +} +function prepareResult(existings, existingIdIdxMap, mode) { + var result = []; + if (mode === "replaceAll") { + return result; + } + for (var index = 0; index < existings.length; index++) { + var existing = existings[index]; + if (existing && existing.id != null) { + existingIdIdxMap.set(existing.id, index); + } + result.push({ + existing: mode === "replaceMerge" || isComponentIdInternal(existing) ? null : existing, + newOption: null, + keyInfo: null, + brandNew: null + }); + } + return result; +} +function mappingById(result, existings, existingIdIdxMap, newCmptOptions) { + each$4(newCmptOptions, function(cmptOption, index) { + if (!cmptOption || cmptOption.id == null) { + return; + } + var optionId = makeComparableKey(cmptOption.id); + var existingIdx = existingIdIdxMap.get(optionId); + if (existingIdx != null) { + var resultItem = result[existingIdx]; + assert(!resultItem.newOption, 'Duplicated option on id "' + optionId + '".'); + resultItem.newOption = cmptOption; + resultItem.existing = existings[existingIdx]; + newCmptOptions[index] = null; + } + }); +} +function mappingByName(result, newCmptOptions) { + each$4(newCmptOptions, function(cmptOption, index) { + if (!cmptOption || cmptOption.name == null) { + return; + } + for (var i = 0; i < result.length; i++) { + var existing = result[i].existing; + if (!result[i].newOption && existing && (existing.id == null || cmptOption.id == null) && !isComponentIdInternal(cmptOption) && !isComponentIdInternal(existing) && keyExistAndEqual("name", existing, cmptOption)) { + result[i].newOption = cmptOption; + newCmptOptions[index] = null; + return; + } + } + }); +} +function mappingByIndex(result, newCmptOptions, brandNew) { + each$4(newCmptOptions, function(cmptOption) { + if (!cmptOption) { + return; + } + var resultItem; + var nextIdx = 0; + while ( + // Be `!resultItem` only when `nextIdx >= result.length`. + (resultItem = result[nextIdx]) && (resultItem.newOption || isComponentIdInternal(resultItem.existing) || // In mode "replaceMerge", here no not-mapped-non-internal-existing. + resultItem.existing && cmptOption.id != null && !keyExistAndEqual("id", cmptOption, resultItem.existing)) + ) { + nextIdx++; + } + if (resultItem) { + resultItem.newOption = cmptOption; + resultItem.brandNew = brandNew; + } else { + result.push({ + newOption: cmptOption, + brandNew, + existing: null, + keyInfo: null + }); + } + nextIdx++; + }); +} +function mappingInReplaceAllMode(result, newCmptOptions) { + each$4(newCmptOptions, function(cmptOption) { + result.push({ + newOption: cmptOption, + brandNew: true, + existing: null, + keyInfo: null + }); + }); +} +function makeIdAndName(mapResult) { + var idMap = createHashMap(); + each$4(mapResult, function(item) { + var existing = item.existing; + existing && idMap.set(existing.id, item); + }); + each$4(mapResult, function(item) { + var opt = item.newOption; + assert(!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 = {}); + }); + each$4(mapResult, function(item, index) { + var existing = item.existing; + var opt = item.newOption; + var keyInfo = item.keyInfo; + if (!isObject$2(opt)) { + return; + } + keyInfo.name = opt.name != null ? makeComparableKey(opt.name) : existing ? existing.name : DUMMY_COMPONENT_NAME_PREFIX + index; + if (existing) { + keyInfo.id = makeComparableKey(existing.id); + } else if (opt.id != null) { + keyInfo.id = makeComparableKey(opt.id); + } else { + var idNum = 0; + do { + keyInfo.id = "\0" + keyInfo.name + "\0" + idNum++; + } while (idMap.get(keyInfo.id)); + } + idMap.set(keyInfo.id, item); + }); +} +function keyExistAndEqual(attr, obj1, obj2) { + var key1 = convertOptionIdName(obj1[attr], null); + var key2 = convertOptionIdName(obj2[attr], null); + return key1 != null && key2 != null && key1 === key2; +} +function makeComparableKey(val) { + return convertOptionIdName(val, ""); +} +function convertOptionIdName(idOrName, defaultValue) { + if (idOrName == null) { + return defaultValue; + } + return isString(idOrName) ? idOrName : isNumber(idOrName) || isStringSafe(idOrName) ? idOrName + "" : defaultValue; +} +function isNameSpecified(componentModel) { + var name = componentModel.name; + return !!(name && name.indexOf(DUMMY_COMPONENT_NAME_PREFIX)); +} +function isComponentIdInternal(cmptOption) { + return cmptOption && cmptOption.id != null && makeComparableKey(cmptOption.id).indexOf(INTERNAL_COMPONENT_ID_PREFIX) === 0; +} +function setComponentTypeToKeyInfo(mappingResult, mainType, componentModelCtor) { + each$4(mappingResult, function(item) { + var newOption = item.newOption; + if (isObject$2(newOption)) { + item.keyInfo.mainType = mainType; + item.keyInfo.subType = determineSubType(mainType, newOption, item.existing, componentModelCtor); + } + }); +} +function determineSubType(mainType, newCmptOption, existComponent, componentModelCtor) { + var subType = newCmptOption.type ? newCmptOption.type : existComponent ? existComponent.subType : componentModelCtor.determineSubType(mainType, newCmptOption); + return subType; +} +function queryDataIndex(data, payload) { + if (payload.dataIndexInside != null) { + return payload.dataIndexInside; + } else if (payload.dataIndex != null) { + return isArray(payload.dataIndex) ? map$1(payload.dataIndex, function(value) { + return data.indexOfRawIndex(value); + }) : data.indexOfRawIndex(payload.dataIndex); + } else if (payload.name != null) { + return isArray(payload.name) ? map$1(payload.name, function(value) { + return data.indexOfName(value); + }) : data.indexOfName(payload.name); + } +} +function makeInner() { + var key = "__ec_inner_" + innerUniqueIndex++; + return function(hostObj) { + return hostObj[key] || (hostObj[key] = {}); + }; +} +var innerUniqueIndex = getRandomIdBase(); +function parseFinder(ecModel, finderInput, opt) { + var _a = preParseFinder(finderInput, opt), mainTypeSpecified = _a.mainTypeSpecified, queryOptionMap = _a.queryOptionMap, others = _a.others; + var result = others; + var defaultMainType = opt ? opt.defaultMainType : null; + if (!mainTypeSpecified && defaultMainType) { + queryOptionMap.set(defaultMainType, {}); + } + queryOptionMap.each(function(queryOption, mainType) { + var queryResult = queryReferringComponents(ecModel, mainType, queryOption, { + useDefault: defaultMainType === mainType, + enableAll: opt && opt.enableAll != null ? opt.enableAll : true, + enableNone: opt && opt.enableNone != null ? opt.enableNone : true + }); + result[mainType + "Models"] = queryResult.models; + result[mainType + "Model"] = queryResult.models[0]; + }); + return result; +} +function preParseFinder(finderInput, opt) { + var finder; + if (isString(finderInput)) { + var obj = {}; + obj[finderInput + "Index"] = 0; + finder = obj; + } else { + finder = finderInput; + } + var queryOptionMap = createHashMap(); + var others = {}; + var mainTypeSpecified = false; + each$4(finder, function(value, key) { + if (key === "dataIndex" || key === "dataIndexInside") { + others[key] = value; + return; + } + var parsedKey = key.match(/^(\w+)(Index|Id|Name)$/) || []; + var mainType = parsedKey[1]; + var queryType = (parsedKey[2] || "").toLowerCase(); + if (!mainType || !queryType || opt && opt.includeMainTypes && indexOf(opt.includeMainTypes, mainType) < 0) { + return; + } + mainTypeSpecified = mainTypeSpecified || !!mainType; + var queryOption = queryOptionMap.get(mainType) || queryOptionMap.set(mainType, {}); + queryOption[queryType] = value; + }); + return { + mainTypeSpecified, + queryOptionMap, + others + }; +} +var SINGLE_REFERRING = { + useDefault: true, + enableAll: false, + enableNone: false +}; +function queryReferringComponents(ecModel, mainType, userOption, opt) { + opt = opt || SINGLE_REFERRING; + var indexOption = userOption.index; + var idOption = userOption.id; + var nameOption = userOption.name; + var result = { + models: null, + specified: indexOption != null || idOption != null || nameOption != null + }; + if (!result.specified) { + var firstCmpt = void 0; + result.models = opt.useDefault && (firstCmpt = ecModel.getComponent(mainType)) ? [firstCmpt] : []; + return result; + } + if (indexOption === "none" || indexOption === false) { + assert(opt.enableNone, '`"none"` or `false` is not a valid value on index option.'); + result.models = []; + return result; + } + if (indexOption === "all") { + assert(opt.enableAll, '`"all"` is not a valid value on index option.'); + indexOption = idOption = nameOption = null; + } + result.models = ecModel.queryComponents({ + mainType, + index: indexOption, + id: idOption, + name: nameOption + }); + return result; +} +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") { + return env.domSupported ? "html" : "richText"; + } else { + return renderModeOption || "html"; + } +} +function interpolateRawValues(data, precision, sourceValue, targetValue, percent) { + var isAutoPrecision = precision == null || precision === "auto"; + if (targetValue == null) { + return targetValue; + } + if (isNumber(targetValue)) { + var value = interpolateNumber(sourceValue || 0, targetValue, percent); + return round$1(value, isAutoPrecision ? Math.max(getPrecision(sourceValue || 0), getPrecision(targetValue)) : precision); + } else if (isString(targetValue)) { + return percent < 1 ? sourceValue : targetValue; + } else { + var interpolated = []; + var leftArr = sourceValue; + var rightArr = targetValue; + var length_1 = Math.max(leftArr ? leftArr.length : 0, rightArr.length); + for (var i = 0; i < length_1; ++i) { + var info = data.getDimensionInfo(i); + if (info && info.type === "ordinal") { + interpolated[i] = (percent < 1 && leftArr ? leftArr : rightArr)[i]; + } else { + var leftVal = leftArr && leftArr[i] ? leftArr[i] : 0; + var rightVal = rightArr[i]; + var value = interpolateNumber(leftVal, rightVal, percent); + interpolated[i] = round$1(value, isAutoPrecision ? Math.max(getPrecision(leftVal), getPrecision(rightVal)) : precision); + } + } + return interpolated; + } +} + +var TYPE_DELIMITER = "."; +var IS_CONTAINER = "___EC__COMPONENT__CONTAINER___"; +var IS_EXTENDED_CLASS = "___EC__EXTENDED_CLASS___"; +function parseClassType(componentType) { + var ret = { + main: "", + sub: "" + }; + if (componentType) { + var typeArr = componentType.split(TYPE_DELIMITER); + ret.main = typeArr[0] || ""; + ret.sub = typeArr[1] || ""; + } + return ret; +} +function checkClassType(componentType) { + assert(/^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(componentType), 'componentType "' + componentType + '" illegal'); +} +function isExtendedClass(clz) { + return !!(clz && clz[IS_EXTENDED_CLASS]); +} +function enableClassExtend(rootClz, mandatoryMethods) { + rootClz.$constructor = rootClz; + rootClz.extend = function(proto) { + var superClass = this; + var ExtendedClass; + if (isESClass(superClass)) { + ExtendedClass = /** @class */ + function(_super) { + __extends(class_1, _super); + function class_1() { + return _super.apply(this, arguments) || this; + } + return class_1; + }(superClass); + } else { + ExtendedClass = function() { + (proto.$constructor || superClass).apply(this, arguments); + }; + inherits(ExtendedClass, this); + } + extend(ExtendedClass.prototype, proto); + ExtendedClass[IS_EXTENDED_CLASS] = true; + ExtendedClass.extend = this.extend; + ExtendedClass.superCall = superCall; + ExtendedClass.superApply = superApply; + ExtendedClass.superClass = superClass; + return ExtendedClass; + }; +} +function isESClass(fn) { + return isFunction(fn) && /^class\s/.test(Function.prototype.toString.call(fn)); +} +function mountExtend(SubClz, SupperClz) { + SubClz.extend = SupperClz.extend; +} +var classBase = Math.round(Math.random() * 10); +function enableClassCheck(target) { + var classAttr = ["__\0is_clz", classBase++].join("_"); + target.prototype[classAttr] = true; + target.isInstance = function(obj) { + return !!(obj && obj[classAttr]); + }; +} +function superCall(context, methodName) { + var args = []; + for (var _i = 2; _i < arguments.length; _i++) { + args[_i - 2] = arguments[_i]; + } + return this.superClass.prototype[methodName].apply(context, args); +} +function superApply(context, methodName, args) { + return this.superClass.prototype[methodName].apply(context, args); +} +function enableClassManagement(target) { + var storage = {}; + target.registerClass = function(clz) { + var componentFullType = clz.type || clz.prototype.type; + if (componentFullType) { + checkClassType(componentFullType); + clz.prototype.type = componentFullType; + var componentTypeInfo = parseClassType(componentFullType); + if (!componentTypeInfo.sub) { + storage[componentTypeInfo.main] = clz; + } else if (componentTypeInfo.sub !== IS_CONTAINER) { + var container = makeContainer(componentTypeInfo); + container[componentTypeInfo.sub] = clz; + } + } + return clz; + }; + target.getClass = function(mainType, subType, throwWhenNotFound) { + var clz = storage[mainType]; + if (clz && clz[IS_CONTAINER]) { + clz = subType ? clz[subType] : null; + } + if (throwWhenNotFound && !clz) { + throw new Error(!subType ? mainType + ".type should be specified." : "Component " + mainType + "." + (subType || "") + " is used but not imported."); + } + return clz; + }; + target.getClassesByMainType = function(componentType) { + var componentTypeInfo = parseClassType(componentType); + var result = []; + var obj = storage[componentTypeInfo.main]; + if (obj && obj[IS_CONTAINER]) { + each$4(obj, function(o, type) { + type !== IS_CONTAINER && result.push(o); + }); + } else { + result.push(obj); + } + return result; + }; + target.hasClass = function(componentType) { + var componentTypeInfo = parseClassType(componentType); + return !!storage[componentTypeInfo.main]; + }; + target.getAllClassMainTypes = function() { + var types = []; + each$4(storage, function(obj, type) { + types.push(type); + }); + return types; + }; + target.hasSubTypes = function(componentType) { + var componentTypeInfo = parseClassType(componentType); + var obj = storage[componentTypeInfo.main]; + return obj && obj[IS_CONTAINER]; + }; + function makeContainer(componentTypeInfo) { + var container = storage[componentTypeInfo.main]; + if (!container || !container[IS_CONTAINER]) { + container = storage[componentTypeInfo.main] = {}; + container[IS_CONTAINER] = true; + } + return container; + } +} + +function makeStyleMapper(properties, ignoreParent) { + // Normalize + for (var i = 0; i < properties.length; i++) { + if (!properties[i][1]) { + properties[i][1] = properties[i][0]; + } + } + ignoreParent = ignoreParent || false; + 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, ignoreParent); + if (val != null) { + style[properties[i][0]] = val; + } + } + // TODO Text or image? + return style; + }; +} + +var AREA_STYLE_KEY_MAP = [['fill', 'color'], ['shadowBlur'], ['shadowOffsetX'], ['shadowOffsetY'], ['opacity'], ['shadowColor'] +// Option decal is in `DecalObject` but style.decal is in `PatternObject`. +// So do not transfer decal directly. +]; +var getAreaStyle = makeStyleMapper(AREA_STYLE_KEY_MAP); +var AreaStyleMixin = /** @class */function () { + function AreaStyleMixin() {} + AreaStyleMixin.prototype.getAreaStyle = function (excludes, includes) { + return getAreaStyle(this, excludes, includes); + }; + return AreaStyleMixin; +}(); + +var globalImageCache = new LRU(50); +function findExistImage(newImageOrSrc) { + if (typeof newImageOrSrc === 'string') { + var cachedImgObj = globalImageCache.get(newImageOrSrc); + return cachedImgObj && cachedImgObj.image; + } + else { + return newImageOrSrc; + } +} +function createOrUpdateImage(newImageOrSrc, image, hostEl, onload, cbPayload) { + if (!newImageOrSrc) { + return image; + } + else if (typeof newImageOrSrc === 'string') { + if ((image && image.__zrImageSrc === newImageOrSrc) || !hostEl) { + return image; + } + var cachedImgObj = globalImageCache.get(newImageOrSrc); + var pendingWrap = { hostEl: hostEl, cb: onload, cbPayload: cbPayload }; + if (cachedImgObj) { + image = cachedImgObj.image; + !isImageReady(image) && cachedImgObj.pending.push(pendingWrap); + } + else { + image = platformApi.loadImage(newImageOrSrc, imageOnLoad, imageOnLoad); + image.__zrImageSrc = newImageOrSrc; + globalImageCache.put(newImageOrSrc, image.__cachedImgObj = { + image: image, + pending: [pendingWrap] + }); + } + return image; + } + 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 STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g; +function truncateText2(out, text, containerWidth, font, ellipsis, options) { + if (!containerWidth) { + out.text = ''; + out.isTruncated = false; + return; + } + var textLines = (text + '').split('\n'); + options = prepareTruncateOptions(containerWidth, font, ellipsis, options); + var isTruncated = false; + var truncateOut = {}; + for (var i = 0, len = textLines.length; i < len; i++) { + truncateSingleLine(truncateOut, textLines[i], options); + textLines[i] = truncateOut.textLine; + isTruncated = isTruncated || truncateOut.isTruncated; + } + out.text = textLines.join('\n'); + out.isTruncated = isTruncated; +} +function prepareTruncateOptions(containerWidth, font, ellipsis, options) { + options = options || {}; + var preparedOpts = extend({}, options); + preparedOpts.font = font; + ellipsis = retrieve2(ellipsis, '...'); + preparedOpts.maxIterations = retrieve2(options.maxIterations, 2); + var minChar = preparedOpts.minChar = retrieve2(options.minChar, 0); + preparedOpts.cnCharWidth = getWidth('国', font); + var ascCharWidth = preparedOpts.ascCharWidth = getWidth('a', font); + preparedOpts.placeholder = retrieve2(options.placeholder, ''); + var contentWidth = containerWidth = Math.max(0, containerWidth - 1); + 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; + preparedOpts.ellipsis = ellipsis; + preparedOpts.ellipsisWidth = ellipsisWidth; + preparedOpts.contentWidth = contentWidth; + preparedOpts.containerWidth = containerWidth; + return preparedOpts; +} +function truncateSingleLine(out, textLine, options) { + var containerWidth = options.containerWidth; + var font = options.font; + var contentWidth = options.contentWidth; + if (!containerWidth) { + out.textLine = ''; + out.isTruncated = false; + return; + } + var lineWidth = getWidth(textLine, font); + if (lineWidth <= containerWidth) { + out.textLine = textLine; + out.isTruncated = false; + return; + } + 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; + } + out.textLine = textLine; + out.isTruncated = true; +} +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; +} +function parsePlainText(text, style) { + text != null && (text += ''); + var overflow = style.overflow; + var padding = style.padding; + var font = style.font; + var truncate = overflow === 'truncate'; + var calculatedLineHeight = getLineHeight(font); + var lineHeight = retrieve2(style.lineHeight, calculatedLineHeight); + var bgColorDrawn = !!(style.backgroundColor); + var truncateLineOverflow = style.lineOverflow === 'truncate'; + var isTruncated = false; + var width = style.width; + var lines; + if (width != null && (overflow === 'break' || overflow === 'breakAll')) { + lines = text ? wrapText(text, style.font, width, overflow === 'breakAll', 0).lines : []; + } + else { + lines = text ? text.split('\n') : []; + } + var contentHeight = lines.length * lineHeight; + var height = retrieve2(style.height, contentHeight); + if (contentHeight > height && truncateLineOverflow) { + var lineCount = Math.floor(height / lineHeight); + isTruncated = isTruncated || (lines.length > lineCount); + lines = lines.slice(0, lineCount); + } + if (text && truncate && width != null) { + var options = prepareTruncateOptions(width, font, style.ellipsis, { + minChar: style.truncateMinChar, + placeholder: style.placeholder + }); + var singleOut = {}; + for (var i = 0; i < lines.length; i++) { + truncateSingleLine(singleOut, lines[i], options); + lines[i] = singleOut.textLine; + isTruncated = isTruncated || singleOut.isTruncated; + } + } + var outerHeight = height; + var contentWidth = 0; + for (var i = 0; i < lines.length; i++) { + contentWidth = Math.max(getWidth(lines[i], font), contentWidth); + } + if (width == null) { + width = contentWidth; + } + var outerWidth = contentWidth; + if (padding) { + outerHeight += padding[0] + padding[2]; + outerWidth += padding[1] + padding[3]; + width += padding[1] + padding[3]; + } + if (bgColorDrawn) { + outerWidth = width; + } + return { + lines: lines, + height: height, + outerWidth: outerWidth, + outerHeight: outerHeight, + lineHeight: lineHeight, + calculatedLineHeight: calculatedLineHeight, + contentWidth: contentWidth, + contentHeight: contentHeight, + width: width, + isTruncated: isTruncated + }; +} +var RichTextToken = (function () { + function RichTextToken() { + } + return RichTextToken; +}()); +var RichTextLine = (function () { + function RichTextLine(tokens) { + this.tokens = []; + if (tokens) { + this.tokens = tokens; + } + } + return RichTextLine; +}()); +var RichTextContentBlock = (function () { + function RichTextContentBlock() { + this.width = 0; + this.height = 0; + this.contentWidth = 0; + this.contentHeight = 0; + this.outerWidth = 0; + this.outerHeight = 0; + this.lines = []; + this.isTruncated = false; + } + return RichTextContentBlock; +}()); +function parseRichText(text, style) { + var contentBlock = new RichTextContentBlock(); + text != null && (text += ''); + if (!text) { + return contentBlock; + } + var topWidth = style.width; + var topHeight = style.height; + var overflow = style.overflow; + var wrapInfo = (overflow === 'break' || overflow === 'breakAll') && topWidth != null + ? { width: topWidth, accumWidth: 0, breakAll: overflow === 'breakAll' } + : null; + 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), style, wrapInfo); + } + pushTokens(contentBlock, result[2], style, wrapInfo, result[1]); + lastIndex = STYLE_REG.lastIndex; + } + if (lastIndex < text.length) { + pushTokens(contentBlock, text.substring(lastIndex, text.length), style, wrapInfo); + } + var pendingList = []; + var calculatedHeight = 0; + var calculatedWidth = 0; + var stlPadding = style.padding; + var truncate = overflow === 'truncate'; + var truncateLine = style.lineOverflow === 'truncate'; + var tmpTruncateOut = {}; + function finishLine(line, lineWidth, lineHeight) { + line.width = lineWidth; + line.lineHeight = lineHeight; + calculatedHeight += lineHeight; + calculatedWidth = Math.max(calculatedWidth, lineWidth); + } + outer: for (var i = 0; i < contentBlock.lines.length; i++) { + var line = contentBlock.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] || {}; + var textPadding = token.textPadding = tokenStyle.padding; + var paddingH = textPadding ? textPadding[1] + textPadding[3] : 0; + var font = token.font = tokenStyle.font || style.font; + token.contentHeight = getLineHeight(font); + var tokenHeight = retrieve2(tokenStyle.height, token.contentHeight); + token.innerHeight = tokenHeight; + textPadding && (tokenHeight += textPadding[0] + textPadding[2]); + token.height = tokenHeight; + token.lineHeight = retrieve3(tokenStyle.lineHeight, style.lineHeight, tokenHeight); + token.align = tokenStyle && tokenStyle.align || style.align; + token.verticalAlign = tokenStyle && tokenStyle.verticalAlign || 'middle'; + if (truncateLine && topHeight != null && calculatedHeight + token.lineHeight > topHeight) { + var originalLength = contentBlock.lines.length; + if (j > 0) { + line.tokens = line.tokens.slice(0, j); + finishLine(line, lineWidth, lineHeight); + contentBlock.lines = contentBlock.lines.slice(0, i + 1); + } + else { + contentBlock.lines = contentBlock.lines.slice(0, i); + } + contentBlock.isTruncated = contentBlock.isTruncated || (contentBlock.lines.length < originalLength); + break outer; + } + var styleTokenWidth = tokenStyle.width; + var tokenWidthNotSpecified = styleTokenWidth == null || styleTokenWidth === 'auto'; + if (typeof styleTokenWidth === 'string' && styleTokenWidth.charAt(styleTokenWidth.length - 1) === '%') { + token.percentWidth = styleTokenWidth; + pendingList.push(token); + token.contentWidth = getWidth(token.text, font); + } + else { + if (tokenWidthNotSpecified) { + var textBackgroundColor = tokenStyle.backgroundColor; + var bgImg = textBackgroundColor && textBackgroundColor.image; + if (bgImg) { + bgImg = findExistImage(bgImg); + if (isImageReady(bgImg)) { + token.width = Math.max(token.width, bgImg.width * tokenHeight / bgImg.height); + } + } + } + var remainTruncWidth = truncate && topWidth != null + ? topWidth - lineWidth : null; + if (remainTruncWidth != null && remainTruncWidth < token.width) { + if (!tokenWidthNotSpecified || remainTruncWidth < paddingH) { + token.text = ''; + token.width = token.contentWidth = 0; + } + else { + truncateText2(tmpTruncateOut, token.text, remainTruncWidth - paddingH, font, style.ellipsis, { minChar: style.truncateMinChar }); + token.text = tmpTruncateOut.text; + contentBlock.isTruncated = contentBlock.isTruncated || tmpTruncateOut.isTruncated; + token.width = token.contentWidth = getWidth(token.text, font); + } + } + else { + token.contentWidth = getWidth(token.text, font); + } + } + token.width += paddingH; + lineWidth += token.width; + tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight)); + } + finishLine(line, lineWidth, lineHeight); + } + contentBlock.outerWidth = contentBlock.width = retrieve2(topWidth, calculatedWidth); + contentBlock.outerHeight = contentBlock.height = retrieve2(topHeight, calculatedHeight); + contentBlock.contentHeight = calculatedHeight; + contentBlock.contentWidth = calculatedWidth; + 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; + token.width = parseInt(percentWidth, 10) / 100 * contentBlock.width; + } + return contentBlock; +} +function pushTokens(block, str, style, wrapInfo, styleName) { + var isEmptyStr = str === ''; + var tokenStyle = styleName && style.rich[styleName] || {}; + var lines = block.lines; + var font = tokenStyle.font || style.font; + var newLine = false; + var strLines; + var linesWidths; + if (wrapInfo) { + var tokenPadding = tokenStyle.padding; + var tokenPaddingH = tokenPadding ? tokenPadding[1] + tokenPadding[3] : 0; + if (tokenStyle.width != null && tokenStyle.width !== 'auto') { + var outerWidth_1 = parsePercent$1(tokenStyle.width, wrapInfo.width) + tokenPaddingH; + if (lines.length > 0) { + if (outerWidth_1 + wrapInfo.accumWidth > wrapInfo.width) { + strLines = str.split('\n'); + newLine = true; + } + } + wrapInfo.accumWidth = outerWidth_1; + } + else { + var res = wrapText(str, font, wrapInfo.width, wrapInfo.breakAll, wrapInfo.accumWidth); + wrapInfo.accumWidth = res.accumWidth + tokenPaddingH; + linesWidths = res.linesWidths; + strLines = res.lines; + } + } + else { + strLines = str.split('\n'); + } + for (var i = 0; i < strLines.length; i++) { + var text = strLines[i]; + var token = new RichTextToken(); + token.styleName = styleName; + token.text = text; + token.isLineHolder = !text && !isEmptyStr; + if (typeof tokenStyle.width === 'number') { + token.width = tokenStyle.width; + } + else { + token.width = linesWidths + ? linesWidths[i] + : getWidth(text, font); + } + if (!i && !newLine) { + var tokens = (lines[lines.length - 1] || (lines[0] = new RichTextLine())).tokens; + var tokensLen = tokens.length; + (tokensLen === 1 && tokens[0].isLineHolder) + ? (tokens[0] = token) + : ((text || !tokensLen || isEmptyStr) && tokens.push(token)); + } + else { + lines.push(new RichTextLine([token])); + } + } +} +function isAlphabeticLetter(ch) { + var code = ch.charCodeAt(0); + return code >= 0x20 && code <= 0x24F + || code >= 0x370 && code <= 0x10FF + || code >= 0x1200 && code <= 0x13FF + || code >= 0x1E00 && code <= 0x206F; +} +var breakCharMap = reduce(',&?/;] '.split(''), function (obj, ch) { + obj[ch] = true; + return obj; +}, {}); +function isWordBreakChar(ch) { + if (isAlphabeticLetter(ch)) { + if (breakCharMap[ch]) { + return true; + } + return false; + } + return true; +} +function wrapText(text, font, lineWidth, isBreakAll, lastAccumWidth) { + var lines = []; + var linesWidths = []; + var line = ''; + var currentWord = ''; + var currentWordWidth = 0; + var accumWidth = 0; + for (var i = 0; i < text.length; i++) { + var ch = text.charAt(i); + if (ch === '\n') { + if (currentWord) { + line += currentWord; + accumWidth += currentWordWidth; + } + lines.push(line); + linesWidths.push(accumWidth); + line = ''; + currentWord = ''; + currentWordWidth = 0; + accumWidth = 0; + continue; + } + var chWidth = getWidth(ch, font); + var inWord = isBreakAll ? false : !isWordBreakChar(ch); + if (!lines.length + ? lastAccumWidth + accumWidth + chWidth > lineWidth + : accumWidth + chWidth > lineWidth) { + if (!accumWidth) { + if (inWord) { + lines.push(currentWord); + linesWidths.push(currentWordWidth); + currentWord = ch; + currentWordWidth = chWidth; + } + else { + lines.push(ch); + linesWidths.push(chWidth); + } + } + else if (line || currentWord) { + if (inWord) { + if (!line) { + line = currentWord; + currentWord = ''; + currentWordWidth = 0; + accumWidth = currentWordWidth; + } + lines.push(line); + linesWidths.push(accumWidth - currentWordWidth); + currentWord += ch; + currentWordWidth += chWidth; + line = ''; + accumWidth = currentWordWidth; + } + else { + if (currentWord) { + line += currentWord; + currentWord = ''; + currentWordWidth = 0; + } + lines.push(line); + linesWidths.push(accumWidth); + line = ch; + accumWidth = chWidth; + } + } + continue; + } + accumWidth += chWidth; + if (inWord) { + currentWord += ch; + currentWordWidth += chWidth; + } + else { + if (currentWord) { + line += currentWord; + currentWord = ''; + currentWordWidth = 0; + } + line += ch; + } + } + if (!lines.length && !line) { + line = text; + currentWord = ''; + currentWordWidth = 0; + } + if (currentWord) { + line += currentWord; + } + if (line) { + lines.push(line); + linesWidths.push(accumWidth); + } + if (lines.length === 1) { + accumWidth += lastAccumWidth; + } + return { + accumWidth: accumWidth, + lines: lines, + linesWidths: linesWidths + }; +} + +var STYLE_MAGIC_KEY = '__zr_style_' + Math.round((Math.random() * 10)); +var DEFAULT_COMMON_STYLE = { + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + shadowColor: '#000', + opacity: 1, + blend: 'source-over' +}; +var DEFAULT_COMMON_ANIMATION_PROPS = { + style: { + shadowBlur: true, + shadowOffsetX: true, + shadowOffsetY: true, + shadowColor: true, + opacity: true + } +}; +DEFAULT_COMMON_STYLE[STYLE_MAGIC_KEY] = true; +var PRIMARY_STATES_KEYS = ['z', 'z2', 'invisible']; +var PRIMARY_STATES_KEYS_IN_HOVER_LAYER = ['invisible']; +var Displayable = (function (_super) { + __extends(Displayable, _super); + function Displayable(props) { + return _super.call(this, props) || this; + } + Displayable.prototype._init = function (props) { + var keysArr = keys(props); + for (var i = 0; i < keysArr.length; i++) { + var key = keysArr[i]; + if (key === 'style') { + this.useStyle(props[key]); + } + else { + _super.prototype.attrKV.call(this, key, props[key]); + } + } + if (!this.style) { + this.useStyle({}); + } + }; + Displayable.prototype.beforeBrush = function () { }; + Displayable.prototype.afterBrush = function () { }; + Displayable.prototype.innerBeforeBrush = function () { }; + Displayable.prototype.innerAfterBrush = function () { }; + Displayable.prototype.shouldBePainted = function (viewWidth, viewHeight, considerClipPath, considerAncestors) { + var m = this.transform; + if (this.ignore + || this.invisible + || this.style.opacity === 0 + || (this.culling + && isDisplayableCulled(this, viewWidth, viewHeight)) + || (m && !m[0] && !m[3])) { + return false; + } + if (considerClipPath && this.__clipPaths) { + for (var i = 0; i < this.__clipPaths.length; ++i) { + if (this.__clipPaths[i].isZeroArea()) { + return false; + } + } + } + if (considerAncestors && this.parent) { + var parent_1 = this.parent; + while (parent_1) { + if (parent_1.ignore) { + return false; + } + parent_1 = parent_1.parent; + } + } + return true; + }; + Displayable.prototype.contain = function (x, y) { + return this.rectContain(x, y); + }; + Displayable.prototype.traverse = function (cb, context) { + cb.call(context, this); + }; + Displayable.prototype.rectContain = function (x, y) { + var coord = this.transformCoordToLocal(x, y); + var rect = this.getBoundingRect(); + return rect.contain(coord[0], coord[1]); + }; + Displayable.prototype.getPaintRect = function () { + var rect = this._paintRect; + if (!this._paintRect || this.__dirty) { + var transform = this.transform; + var elRect = this.getBoundingRect(); + var style = this.style; + var shadowSize = style.shadowBlur || 0; + var shadowOffsetX = style.shadowOffsetX || 0; + var shadowOffsetY = style.shadowOffsetY || 0; + rect = this._paintRect || (this._paintRect = new BoundingRect(0, 0, 0, 0)); + if (transform) { + BoundingRect.applyTransform(rect, elRect, transform); + } + else { + rect.copy(elRect); + } + if (shadowSize || shadowOffsetX || shadowOffsetY) { + rect.width += shadowSize * 2 + Math.abs(shadowOffsetX); + rect.height += shadowSize * 2 + Math.abs(shadowOffsetY); + rect.x = Math.min(rect.x, rect.x + shadowOffsetX - shadowSize); + rect.y = Math.min(rect.y, rect.y + shadowOffsetY - shadowSize); + } + var tolerance = this.dirtyRectTolerance; + if (!rect.isZero()) { + rect.x = Math.floor(rect.x - tolerance); + rect.y = Math.floor(rect.y - tolerance); + rect.width = Math.ceil(rect.width + 1 + tolerance * 2); + rect.height = Math.ceil(rect.height + 1 + tolerance * 2); + } + } + return rect; + }; + Displayable.prototype.setPrevPaintRect = function (paintRect) { + if (paintRect) { + this._prevPaintRect = this._prevPaintRect || new BoundingRect(0, 0, 0, 0); + this._prevPaintRect.copy(paintRect); + } + else { + this._prevPaintRect = null; + } + }; + Displayable.prototype.getPrevPaintRect = function () { + return this._prevPaintRect; + }; + Displayable.prototype.animateStyle = function (loop) { + return this.animate('style', loop); + }; + Displayable.prototype.updateDuringAnimation = function (targetKey) { + if (targetKey === 'style') { + this.dirtyStyle(); + } + else { + this.markRedraw(); + } + }; + Displayable.prototype.attrKV = function (key, value) { + if (key !== 'style') { + _super.prototype.attrKV.call(this, key, value); + } + else { + if (!this.style) { + this.useStyle(value); + } + else { + this.setStyle(value); + } + } + }; + Displayable.prototype.setStyle = function (keyOrObj, value) { + if (typeof keyOrObj === 'string') { + this.style[keyOrObj] = value; + } + else { + extend(this.style, keyOrObj); + } + this.dirtyStyle(); + return this; + }; + Displayable.prototype.dirtyStyle = function (notRedraw) { + if (!notRedraw) { + this.markRedraw(); + } + this.__dirty |= STYLE_CHANGED_BIT; + if (this._rect) { + this._rect = null; + } + }; + Displayable.prototype.dirty = function () { + this.dirtyStyle(); + }; + Displayable.prototype.styleChanged = function () { + return !!(this.__dirty & STYLE_CHANGED_BIT); + }; + Displayable.prototype.styleUpdated = function () { + this.__dirty &= -3; + }; + Displayable.prototype.createStyle = function (obj) { + return createObject(DEFAULT_COMMON_STYLE, obj); + }; + Displayable.prototype.useStyle = function (obj) { + if (!obj[STYLE_MAGIC_KEY]) { + obj = this.createStyle(obj); + } + if (this.__inHover) { + this.__hoverStyle = obj; + } + else { + this.style = obj; + } + this.dirtyStyle(); + }; + Displayable.prototype.isStyleObject = function (obj) { + return obj[STYLE_MAGIC_KEY]; + }; + Displayable.prototype._innerSaveToNormal = function (toState) { + _super.prototype._innerSaveToNormal.call(this, toState); + var normalState = this._normalState; + if (toState.style && !normalState.style) { + normalState.style = this._mergeStyle(this.createStyle(), this.style); + } + this._savePrimaryToNormal(toState, normalState, PRIMARY_STATES_KEYS); + }; + Displayable.prototype._applyStateObj = function (stateName, state, normalState, keepCurrentStates, transition, animationCfg) { + _super.prototype._applyStateObj.call(this, stateName, state, normalState, keepCurrentStates, transition, animationCfg); + var needsRestoreToNormal = !(state && keepCurrentStates); + var targetStyle; + if (state && state.style) { + if (transition) { + if (keepCurrentStates) { + targetStyle = state.style; + } + else { + targetStyle = this._mergeStyle(this.createStyle(), normalState.style); + this._mergeStyle(targetStyle, state.style); + } + } + else { + targetStyle = this._mergeStyle(this.createStyle(), keepCurrentStates ? this.style : normalState.style); + this._mergeStyle(targetStyle, state.style); + } + } + else if (needsRestoreToNormal) { + targetStyle = normalState.style; + } + if (targetStyle) { + if (transition) { + var sourceStyle = this.style; + this.style = this.createStyle(needsRestoreToNormal ? {} : sourceStyle); + if (needsRestoreToNormal) { + var changedKeys = keys(sourceStyle); + for (var i = 0; i < changedKeys.length; i++) { + var key = changedKeys[i]; + if (key in targetStyle) { + targetStyle[key] = targetStyle[key]; + this.style[key] = sourceStyle[key]; + } + } + } + var targetKeys = keys(targetStyle); + for (var i = 0; i < targetKeys.length; i++) { + var key = targetKeys[i]; + this.style[key] = this.style[key]; + } + this._transitionState(stateName, { + style: targetStyle + }, animationCfg, this.getAnimationStyleProps()); + } + else { + this.useStyle(targetStyle); + } + } + var statesKeys = this.__inHover ? PRIMARY_STATES_KEYS_IN_HOVER_LAYER : PRIMARY_STATES_KEYS; + for (var i = 0; i < statesKeys.length; i++) { + var key = statesKeys[i]; + if (state && state[key] != null) { + this[key] = state[key]; + } + else if (needsRestoreToNormal) { + if (normalState[key] != null) { + this[key] = normalState[key]; + } + } + } + }; + Displayable.prototype._mergeStates = function (states) { + var mergedState = _super.prototype._mergeStates.call(this, states); + var mergedStyle; + for (var i = 0; i < states.length; i++) { + var state = states[i]; + if (state.style) { + mergedStyle = mergedStyle || {}; + this._mergeStyle(mergedStyle, state.style); + } + } + if (mergedStyle) { + mergedState.style = mergedStyle; + } + return mergedState; + }; + Displayable.prototype._mergeStyle = function (targetStyle, sourceStyle) { + extend(targetStyle, sourceStyle); + return targetStyle; + }; + Displayable.prototype.getAnimationStyleProps = function () { + return DEFAULT_COMMON_ANIMATION_PROPS; + }; + Displayable.initDefaultProps = (function () { + var dispProto = Displayable.prototype; + dispProto.type = 'displayable'; + dispProto.invisible = false; + dispProto.z = 0; + dispProto.z2 = 0; + dispProto.zlevel = 0; + dispProto.culling = false; + dispProto.cursor = 'pointer'; + dispProto.rectHover = false; + dispProto.incremental = false; + dispProto._rect = null; + dispProto.dirtyRectTolerance = 0; + dispProto.__dirty = REDRAW_BIT | STYLE_CHANGED_BIT; + })(); + return Displayable; +}(Element)); +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); +} + +var mathMin$4 = Math.min; +var mathMax$4 = Math.max; +var mathSin$3 = Math.sin; +var mathCos$3 = Math.cos; +var PI2$6 = Math.PI * 2; +var start = create$1(); +var end = create$1(); +var extremity = create$1(); +function fromLine(x0, y0, x1, y1, min, max) { + min[0] = mathMin$4(x0, x1); + min[1] = mathMin$4(y0, y1); + max[0] = mathMax$4(x0, x1); + max[1] = mathMax$4(y0, y1); +} +var xDim = []; +var yDim = []; +function fromCubic(x0, y0, x1, y1, x2, y2, x3, y3, min, max) { + var cubicExtrema$1 = cubicExtrema; + var cubicAt$1 = cubicAt; + var n = cubicExtrema$1(x0, x1, x2, x3, xDim); + min[0] = Infinity; + min[1] = Infinity; + max[0] = -Infinity; + max[1] = -Infinity; + for (var i = 0; i < n; i++) { + var x = cubicAt$1(x0, x1, x2, x3, xDim[i]); + min[0] = mathMin$4(x, min[0]); + max[0] = mathMax$4(x, max[0]); + } + n = cubicExtrema$1(y0, y1, y2, y3, yDim); + for (var i = 0; i < n; i++) { + var y = cubicAt$1(y0, y1, y2, y3, yDim[i]); + min[1] = mathMin$4(y, min[1]); + max[1] = mathMax$4(y, max[1]); + } + min[0] = mathMin$4(x0, min[0]); + max[0] = mathMax$4(x0, max[0]); + min[0] = mathMin$4(x3, min[0]); + max[0] = mathMax$4(x3, max[0]); + min[1] = mathMin$4(y0, min[1]); + max[1] = mathMax$4(y0, max[1]); + min[1] = mathMin$4(y3, min[1]); + max[1] = mathMax$4(y3, max[1]); +} +function fromQuadratic(x0, y0, x1, y1, x2, y2, min, max) { + var quadraticExtremum$1 = quadraticExtremum; + var quadraticAt$1 = quadraticAt; + var tx = mathMax$4(mathMin$4(quadraticExtremum$1(x0, x1, x2), 1), 0); + var ty = mathMax$4(mathMin$4(quadraticExtremum$1(y0, y1, y2), 1), 0); + var x = quadraticAt$1(x0, x1, x2, tx); + var y = quadraticAt$1(y0, y1, y2, ty); + min[0] = mathMin$4(x0, x2, x); + min[1] = mathMin$4(y0, y2, y); + max[0] = mathMax$4(x0, x2, x); + max[1] = mathMax$4(y0, y2, y); +} +function fromArc(x, y, rx, ry, startAngle, endAngle, anticlockwise, min, max) { + var vec2Min = min$1; + var vec2Max = max$1; + var diff = Math.abs(startAngle - endAngle); + if (diff % PI2$6 < 1e-4 && diff > 1e-4) { + min[0] = x - rx; + min[1] = y - ry; + max[0] = x + rx; + max[1] = y + ry; + return; + } + start[0] = mathCos$3(startAngle) * rx + x; + start[1] = mathSin$3(startAngle) * ry + y; + end[0] = mathCos$3(endAngle) * rx + x; + end[1] = mathSin$3(endAngle) * ry + y; + vec2Min(min, start, end); + vec2Max(max, start, end); + startAngle = startAngle % (PI2$6); + if (startAngle < 0) { + startAngle = startAngle + PI2$6; + } + endAngle = endAngle % (PI2$6); + if (endAngle < 0) { + endAngle = endAngle + PI2$6; + } + if (startAngle > endAngle && !anticlockwise) { + endAngle += PI2$6; + } + else if (startAngle < endAngle && anticlockwise) { + startAngle += PI2$6; + } + if (anticlockwise) { + var tmp = endAngle; + endAngle = startAngle; + startAngle = tmp; + } + for (var angle = 0; angle < endAngle; angle += Math.PI / 2) { + if (angle > startAngle) { + extremity[0] = mathCos$3(angle) * rx + x; + extremity[1] = mathSin$3(angle) * ry + y; + vec2Min(min, extremity, min); + vec2Max(max, extremity, max); + } + } +} + +var CMD$2 = { + M: 1, + L: 2, + C: 3, + Q: 4, + A: 5, + Z: 6, + R: 7 +}; +var tmpOutX = []; +var tmpOutY = []; +var min = []; +var max = []; +var min2 = []; +var max2 = []; +var mathMin$3 = Math.min; +var mathMax$3 = Math.max; +var mathCos$2 = Math.cos; +var mathSin$2 = Math.sin; +var mathAbs$1 = Math.abs; +var PI$4 = Math.PI; +var PI2$5 = PI$4 * 2; +var hasTypedArray = typeof Float32Array !== 'undefined'; +var tmpAngles = []; +function modPI2(radian) { + var n = Math.round(radian / PI$4 * 1e8) / 1e8; + return (n % 2) * PI$4; +} +function normalizeArcAngles(angles, anticlockwise) { + var newStartAngle = modPI2(angles[0]); + if (newStartAngle < 0) { + newStartAngle += PI2$5; + } + var delta = newStartAngle - angles[0]; + var newEndAngle = angles[1]; + newEndAngle += delta; + if (!anticlockwise && newEndAngle - newStartAngle >= PI2$5) { + newEndAngle = newStartAngle + PI2$5; + } + else if (anticlockwise && newStartAngle - newEndAngle >= PI2$5) { + newEndAngle = newStartAngle - PI2$5; + } + else if (!anticlockwise && newStartAngle > newEndAngle) { + newEndAngle = newStartAngle + (PI2$5 - modPI2(newStartAngle - newEndAngle)); + } + else if (anticlockwise && newStartAngle < newEndAngle) { + newEndAngle = newStartAngle - (PI2$5 - modPI2(newEndAngle - newStartAngle)); + } + angles[0] = newStartAngle; + angles[1] = newEndAngle; +} +var PathProxy = (function () { + function PathProxy(notSaveData) { + this.dpr = 1; + this._xi = 0; + this._yi = 0; + this._x0 = 0; + this._y0 = 0; + this._len = 0; + if (notSaveData) { + this._saveData = false; + } + if (this._saveData) { + this.data = []; + } + } + PathProxy.prototype.increaseVersion = function () { + this._version++; + }; + PathProxy.prototype.getVersion = function () { + return this._version; + }; + PathProxy.prototype.setScale = function (sx, sy, segmentIgnoreThreshold) { + segmentIgnoreThreshold = segmentIgnoreThreshold || 0; + if (segmentIgnoreThreshold > 0) { + this._ux = mathAbs$1(segmentIgnoreThreshold / devicePixelRatio / sx) || 0; + this._uy = mathAbs$1(segmentIgnoreThreshold / devicePixelRatio / sy) || 0; + } + }; + PathProxy.prototype.setDPR = function (dpr) { + this.dpr = dpr; + }; + PathProxy.prototype.setContext = function (ctx) { + this._ctx = ctx; + }; + PathProxy.prototype.getContext = function () { + return this._ctx; + }; + PathProxy.prototype.beginPath = function () { + this._ctx && this._ctx.beginPath(); + this.reset(); + return this; + }; + PathProxy.prototype.reset = function () { + if (this._saveData) { + this._len = 0; + } + if (this._pathSegLen) { + this._pathSegLen = null; + this._pathLen = 0; + } + this._version++; + }; + PathProxy.prototype.moveTo = function (x, y) { + this._drawPendingPt(); + this.addData(CMD$2.M, x, y); + this._ctx && this._ctx.moveTo(x, y); + this._x0 = x; + this._y0 = y; + this._xi = x; + this._yi = y; + return this; + }; + PathProxy.prototype.lineTo = function (x, y) { + var dx = mathAbs$1(x - this._xi); + var dy = mathAbs$1(y - this._yi); + var exceedUnit = dx > this._ux || dy > this._uy; + this.addData(CMD$2.L, x, y); + if (this._ctx && exceedUnit) { + this._ctx.lineTo(x, y); + } + if (exceedUnit) { + this._xi = x; + this._yi = y; + this._pendingPtDist = 0; + } + else { + var d2 = dx * dx + dy * dy; + if (d2 > this._pendingPtDist) { + this._pendingPtX = x; + this._pendingPtY = y; + this._pendingPtDist = d2; + } + } + return this; + }; + PathProxy.prototype.bezierCurveTo = function (x1, y1, x2, y2, x3, y3) { + this._drawPendingPt(); + this.addData(CMD$2.C, x1, y1, x2, y2, x3, y3); + if (this._ctx) { + this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + } + this._xi = x3; + this._yi = y3; + return this; + }; + PathProxy.prototype.quadraticCurveTo = function (x1, y1, x2, y2) { + this._drawPendingPt(); + this.addData(CMD$2.Q, x1, y1, x2, y2); + if (this._ctx) { + this._ctx.quadraticCurveTo(x1, y1, x2, y2); + } + this._xi = x2; + this._yi = y2; + return this; + }; + PathProxy.prototype.arc = function (cx, cy, r, startAngle, endAngle, anticlockwise) { + this._drawPendingPt(); + tmpAngles[0] = startAngle; + tmpAngles[1] = endAngle; + normalizeArcAngles(tmpAngles, anticlockwise); + startAngle = tmpAngles[0]; + endAngle = tmpAngles[1]; + var delta = endAngle - startAngle; + this.addData(CMD$2.A, cx, cy, r, r, startAngle, delta, 0, anticlockwise ? 0 : 1); + this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise); + this._xi = mathCos$2(endAngle) * r + cx; + this._yi = mathSin$2(endAngle) * r + cy; + return this; + }; + PathProxy.prototype.arcTo = function (x1, y1, x2, y2, radius) { + this._drawPendingPt(); + if (this._ctx) { + this._ctx.arcTo(x1, y1, x2, y2, radius); + } + return this; + }; + PathProxy.prototype.rect = function (x, y, w, h) { + this._drawPendingPt(); + this._ctx && this._ctx.rect(x, y, w, h); + this.addData(CMD$2.R, x, y, w, h); + return this; + }; + PathProxy.prototype.closePath = function () { + this._drawPendingPt(); + this.addData(CMD$2.Z); + var ctx = this._ctx; + var x0 = this._x0; + var y0 = this._y0; + if (ctx) { + ctx.closePath(); + } + this._xi = x0; + this._yi = y0; + return this; + }; + PathProxy.prototype.fill = function (ctx) { + ctx && ctx.fill(); + this.toStatic(); + }; + PathProxy.prototype.stroke = function (ctx) { + ctx && ctx.stroke(); + this.toStatic(); + }; + PathProxy.prototype.len = function () { + return this._len; + }; + PathProxy.prototype.setData = function (data) { + var len = data.length; + if (!(this.data && this.data.length === len) && hasTypedArray) { + this.data = new Float32Array(len); + } + for (var i = 0; i < len; i++) { + this.data[i] = data[i]; + } + this._len = len; + }; + PathProxy.prototype.appendPath = function (path) { + if (!(path instanceof Array)) { + path = [path]; + } + var len = path.length; + var appendSize = 0; + var offset = this._len; + for (var i = 0; i < len; i++) { + appendSize += path[i].len(); + } + if (hasTypedArray && (this.data instanceof Float32Array)) { + this.data = new Float32Array(offset + appendSize); + } + for (var i = 0; i < len; i++) { + var appendPathData = path[i].data; + for (var k = 0; k < appendPathData.length; k++) { + this.data[offset++] = appendPathData[k]; + } + } + this._len = offset; + }; + PathProxy.prototype.addData = function (cmd, a, b, c, d, e, f, g, h) { + if (!this._saveData) { + return; + } + var data = this.data; + if (this._len + arguments.length > data.length) { + this._expandData(); + data = this.data; + } + for (var i = 0; i < arguments.length; i++) { + data[this._len++] = arguments[i]; + } + }; + PathProxy.prototype._drawPendingPt = function () { + if (this._pendingPtDist > 0) { + this._ctx && this._ctx.lineTo(this._pendingPtX, this._pendingPtY); + this._pendingPtDist = 0; + } + }; + PathProxy.prototype._expandData = function () { + if (!(this.data instanceof Array)) { + var newData = []; + for (var i = 0; i < this._len; i++) { + newData[i] = this.data[i]; + } + this.data = newData; + } + }; + PathProxy.prototype.toStatic = function () { + if (!this._saveData) { + return; + } + this._drawPendingPt(); + var data = this.data; + if (data instanceof Array) { + data.length = this._len; + if (hasTypedArray && this._len > 11) { + this.data = new Float32Array(data); + } + } + }; + PathProxy.prototype.getBoundingRect = function () { + min[0] = min[1] = min2[0] = min2[1] = Number.MAX_VALUE; + max[0] = max[1] = max2[0] = max2[1] = -Number.MAX_VALUE; + var data = this.data; + var xi = 0; + var yi = 0; + var x0 = 0; + var y0 = 0; + var i; + for (i = 0; i < this._len;) { + var cmd = data[i++]; + var isFirst = i === 1; + if (isFirst) { + xi = data[i]; + yi = data[i + 1]; + x0 = xi; + y0 = yi; + } + switch (cmd) { + case CMD$2.M: + xi = x0 = data[i++]; + yi = y0 = data[i++]; + min2[0] = x0; + min2[1] = y0; + max2[0] = x0; + max2[1] = y0; + break; + case CMD$2.L: + fromLine(xi, yi, data[i], data[i + 1], min2, max2); + xi = data[i++]; + yi = data[i++]; + break; + case CMD$2.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$2.Q: + fromQuadratic(xi, yi, data[i++], data[i++], data[i], data[i + 1], min2, max2); + xi = data[i++]; + yi = data[i++]; + break; + case CMD$2.A: + 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; + i += 1; + var anticlockwise = !data[i++]; + if (isFirst) { + x0 = mathCos$2(startAngle) * rx + cx; + y0 = mathSin$2(startAngle) * ry + cy; + } + fromArc(cx, cy, rx, ry, startAngle, endAngle, anticlockwise, min2, max2); + xi = mathCos$2(endAngle) * rx + cx; + yi = mathSin$2(endAngle) * ry + cy; + break; + case CMD$2.R: + x0 = xi = data[i++]; + y0 = yi = data[i++]; + var width = data[i++]; + var height = data[i++]; + fromLine(x0, y0, x0 + width, y0 + height, min2, max2); + break; + case CMD$2.Z: + xi = x0; + yi = y0; + break; + } + min$1(min, min, min2); + max$1(max, max, max2); + } + if (i === 0) { + min[0] = min[1] = max[0] = max[1] = 0; + } + return new BoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]); + }; + PathProxy.prototype._calculateLength = function () { + var data = this.data; + var len = this._len; + var ux = this._ux; + var uy = this._uy; + var xi = 0; + var yi = 0; + var x0 = 0; + var y0 = 0; + if (!this._pathSegLen) { + this._pathSegLen = []; + } + var pathSegLen = this._pathSegLen; + var pathTotalLen = 0; + var segCount = 0; + for (var i = 0; i < len;) { + var cmd = data[i++]; + var isFirst = i === 1; + if (isFirst) { + xi = data[i]; + yi = data[i + 1]; + x0 = xi; + y0 = yi; + } + var l = -1; + switch (cmd) { + case CMD$2.M: + xi = x0 = data[i++]; + yi = y0 = data[i++]; + break; + case CMD$2.L: { + var x2 = data[i++]; + var y2 = data[i++]; + var dx = x2 - xi; + var dy = y2 - yi; + if (mathAbs$1(dx) > ux || mathAbs$1(dy) > uy || i === len - 1) { + l = Math.sqrt(dx * dx + dy * dy); + xi = x2; + yi = y2; + } + break; + } + case CMD$2.C: { + var x1 = data[i++]; + var y1 = data[i++]; + var x2 = data[i++]; + var y2 = data[i++]; + var x3 = data[i++]; + var y3 = data[i++]; + l = cubicLength(xi, yi, x1, y1, x2, y2, x3, y3, 10); + xi = x3; + yi = y3; + break; + } + case CMD$2.Q: { + var x1 = data[i++]; + var y1 = data[i++]; + var x2 = data[i++]; + var y2 = data[i++]; + l = quadraticLength(xi, yi, x1, y1, x2, y2, 10); + xi = x2; + yi = y2; + break; + } + case CMD$2.A: + var cx = data[i++]; + var cy = data[i++]; + var rx = data[i++]; + var ry = data[i++]; + var startAngle = data[i++]; + var delta = data[i++]; + var endAngle = delta + startAngle; + i += 1; + if (isFirst) { + x0 = mathCos$2(startAngle) * rx + cx; + y0 = mathSin$2(startAngle) * ry + cy; + } + l = mathMax$3(rx, ry) * mathMin$3(PI2$5, Math.abs(delta)); + xi = mathCos$2(endAngle) * rx + cx; + yi = mathSin$2(endAngle) * ry + cy; + break; + case CMD$2.R: { + x0 = xi = data[i++]; + y0 = yi = data[i++]; + var width = data[i++]; + var height = data[i++]; + l = width * 2 + height * 2; + break; + } + case CMD$2.Z: { + var dx = x0 - xi; + var dy = y0 - yi; + l = Math.sqrt(dx * dx + dy * dy); + xi = x0; + yi = y0; + break; + } + } + if (l >= 0) { + pathSegLen[segCount++] = l; + pathTotalLen += l; + } + } + this._pathLen = pathTotalLen; + return pathTotalLen; + }; + PathProxy.prototype.rebuildPath = function (ctx, percent) { + var d = this.data; + var ux = this._ux; + var uy = this._uy; + var len = this._len; + var x0; + var y0; + var xi; + var yi; + var x; + var y; + var drawPart = percent < 1; + var pathSegLen; + var pathTotalLen; + var accumLength = 0; + var segCount = 0; + var displayedLength; + var pendingPtDist = 0; + var pendingPtX; + var pendingPtY; + if (drawPart) { + if (!this._pathSegLen) { + this._calculateLength(); + } + pathSegLen = this._pathSegLen; + pathTotalLen = this._pathLen; + displayedLength = percent * pathTotalLen; + if (!displayedLength) { + return; + } + } + lo: for (var i = 0; i < len;) { + var cmd = d[i++]; + var isFirst = i === 1; + if (isFirst) { + xi = d[i]; + yi = d[i + 1]; + x0 = xi; + y0 = yi; + } + if (cmd !== CMD$2.L && pendingPtDist > 0) { + ctx.lineTo(pendingPtX, pendingPtY); + pendingPtDist = 0; + } + switch (cmd) { + case CMD$2.M: + x0 = xi = d[i++]; + y0 = yi = d[i++]; + ctx.moveTo(xi, yi); + break; + case CMD$2.L: { + x = d[i++]; + y = d[i++]; + var dx = mathAbs$1(x - xi); + var dy = mathAbs$1(y - yi); + if (dx > ux || dy > uy) { + if (drawPart) { + var l = pathSegLen[segCount++]; + if (accumLength + l > displayedLength) { + var t = (displayedLength - accumLength) / l; + ctx.lineTo(xi * (1 - t) + x * t, yi * (1 - t) + y * t); + break lo; + } + accumLength += l; + } + ctx.lineTo(x, y); + xi = x; + yi = y; + pendingPtDist = 0; + } + else { + var d2 = dx * dx + dy * dy; + if (d2 > pendingPtDist) { + pendingPtX = x; + pendingPtY = y; + pendingPtDist = d2; + } + } + break; + } + case CMD$2.C: { + var x1 = d[i++]; + var y1 = d[i++]; + var x2 = d[i++]; + var y2 = d[i++]; + var x3 = d[i++]; + var y3 = d[i++]; + if (drawPart) { + var l = pathSegLen[segCount++]; + if (accumLength + l > displayedLength) { + var t = (displayedLength - accumLength) / l; + cubicSubdivide(xi, x1, x2, x3, t, tmpOutX); + cubicSubdivide(yi, y1, y2, y3, t, tmpOutY); + ctx.bezierCurveTo(tmpOutX[1], tmpOutY[1], tmpOutX[2], tmpOutY[2], tmpOutX[3], tmpOutY[3]); + break lo; + } + accumLength += l; + } + ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + xi = x3; + yi = y3; + break; + } + case CMD$2.Q: { + var x1 = d[i++]; + var y1 = d[i++]; + var x2 = d[i++]; + var y2 = d[i++]; + if (drawPart) { + var l = pathSegLen[segCount++]; + if (accumLength + l > displayedLength) { + var t = (displayedLength - accumLength) / l; + quadraticSubdivide(xi, x1, x2, t, tmpOutX); + quadraticSubdivide(yi, y1, y2, t, tmpOutY); + ctx.quadraticCurveTo(tmpOutX[1], tmpOutY[1], tmpOutX[2], tmpOutY[2]); + break lo; + } + accumLength += l; + } + ctx.quadraticCurveTo(x1, y1, x2, y2); + xi = x2; + yi = y2; + break; + } + case CMD$2.A: + var cx = d[i++]; + var cy = d[i++]; + var rx = d[i++]; + var ry = d[i++]; + var startAngle = d[i++]; + var delta = d[i++]; + var psi = d[i++]; + var anticlockwise = !d[i++]; + var r = (rx > ry) ? rx : ry; + var isEllipse = mathAbs$1(rx - ry) > 1e-3; + var endAngle = startAngle + delta; + var breakBuild = false; + if (drawPart) { + var l = pathSegLen[segCount++]; + if (accumLength + l > displayedLength) { + endAngle = startAngle + delta * (displayedLength - accumLength) / l; + breakBuild = true; + } + accumLength += l; + } + if (isEllipse && ctx.ellipse) { + ctx.ellipse(cx, cy, rx, ry, psi, startAngle, endAngle, anticlockwise); + } + else { + ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise); + } + if (breakBuild) { + break lo; + } + if (isFirst) { + x0 = mathCos$2(startAngle) * rx + cx; + y0 = mathSin$2(startAngle) * ry + cy; + } + xi = mathCos$2(endAngle) * rx + cx; + yi = mathSin$2(endAngle) * ry + cy; + break; + case CMD$2.R: + x0 = xi = d[i]; + y0 = yi = d[i + 1]; + x = d[i++]; + y = d[i++]; + var width = d[i++]; + var height = d[i++]; + if (drawPart) { + var l = pathSegLen[segCount++]; + if (accumLength + l > displayedLength) { + var d_1 = displayedLength - accumLength; + ctx.moveTo(x, y); + ctx.lineTo(x + mathMin$3(d_1, width), y); + d_1 -= width; + if (d_1 > 0) { + ctx.lineTo(x + width, y + mathMin$3(d_1, height)); + } + d_1 -= height; + if (d_1 > 0) { + ctx.lineTo(x + mathMax$3(width - d_1, 0), y + height); + } + d_1 -= width; + if (d_1 > 0) { + ctx.lineTo(x, y + mathMax$3(height - d_1, 0)); + } + break lo; + } + accumLength += l; + } + ctx.rect(x, y, width, height); + break; + case CMD$2.Z: + if (drawPart) { + var l = pathSegLen[segCount++]; + if (accumLength + l > displayedLength) { + var t = (displayedLength - accumLength) / l; + ctx.lineTo(xi * (1 - t) + x0 * t, yi * (1 - t) + y0 * t); + break lo; + } + accumLength += l; + } + ctx.closePath(); + xi = x0; + yi = y0; + } + } + }; + PathProxy.prototype.clone = function () { + var newProxy = new PathProxy(); + var data = this.data; + newProxy.data = data.slice ? data.slice() + : Array.prototype.slice.call(data); + newProxy._len = this._len; + return newProxy; + }; + PathProxy.CMD = CMD$2; + PathProxy.initDefaultProps = (function () { + var proto = PathProxy.prototype; + proto._saveData = true; + proto._ux = 0; + proto._uy = 0; + proto._pendingPtDist = 0; + proto._version = 0; + })(); + return PathProxy; +}()); + +function containStroke$4(x0, y0, x1, y1, lineWidth, x, y) { + if (lineWidth === 0) { + return false; + } + var _l = lineWidth; + var _a = 0; + var _b = x0; + 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; +} + +function containStroke$3(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) { + if (lineWidth === 0) { + return false; + } + var _l = lineWidth; + 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); + return d <= _l / 2; +} + +function containStroke$2(x0, y0, x1, y1, x2, y2, lineWidth, x, y) { + if (lineWidth === 0) { + return false; + } + var _l = lineWidth; + 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); + return d <= _l / 2; +} + +var PI2$4 = Math.PI * 2; +function normalizeRadian(angle) { + angle %= PI2$4; + if (angle < 0) { + angle += PI2$4; + } + return angle; +} + +var PI2$3 = Math.PI * 2; +function containStroke$1(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$3 < 1e-4) { + 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$3; + } + var angle = Math.atan2(y, x); + if (angle < 0) { + angle += PI2$3; + } + return (angle >= startAngle && angle <= endAngle) + || (angle + PI2$3 >= startAngle && angle + PI2$3 <= endAngle); +} + +function windingLine(x0, y0, x1, y1, x, y) { + if ((y > y0 && y > y1) || (y < y0 && y < y1)) { + return 0; + } + if (y1 === y0) { + return 0; + } + var t = (y - y0) / (y1 - y0); + var dir = y1 < y0 ? 1 : -1; + if (t === 1 || t === 0) { + dir = y1 < y0 ? 0.5 : -0.5; + } + var x_ = t * (x1 - x0) + x0; + return x_ === x ? Infinity : x_ > x ? dir : 0; +} + +var CMD$1 = PathProxy.CMD; +var PI2$2 = Math.PI * 2; +var EPSILON = 1e-4; +function isAroundEqual(a, b) { + return Math.abs(a - b) < EPSILON; +} +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) { + 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_ = void 0; + var y1_ = void 0; + for (var i = 0; i < nRoots; i++) { + var t = roots[i]; + var unit = (t === 0 || t === 1) ? 0.5 : 1; + var x_ = cubicAt(x0, x1, x2, x3, t); + if (x_ < x) { + 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) { + 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++) { + var unit = (roots[i] === 0 || roots[i] === 1) ? 0.5 : 1; + var x_ = quadraticAt(x0, x1, x2, roots[i]); + if (x_ < x) { + continue; + } + if (roots[i] < t) { + w += y_ < y0 ? unit : -unit; + } + else { + w += y2 < y_ ? unit : -unit; + } + } + return w; + } + else { + var unit = (roots[0] === 0 || roots[0] === 1) ? 0.5 : 1; + var x_ = quadraticAt(x0, x1, x2, roots[0]); + if (x_ < x) { + return 0; + } + return y2 < y0 ? unit : -unit; + } + } +} +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 dTheta = Math.abs(startAngle - endAngle); + if (dTheta < 1e-4) { + return 0; + } + if (dTheta >= PI2$2 - 1e-4) { + startAngle = 0; + endAngle = PI2$2; + var dir = anticlockwise ? 1 : -1; + if (x >= roots[0] + cx && x <= roots[1] + cx) { + return dir; + } + else { + return 0; + } + } + if (startAngle > endAngle) { + var tmp_1 = startAngle; + startAngle = endAngle; + endAngle = tmp_1; + } + if (startAngle < 0) { + startAngle += PI2$2; + endAngle += PI2$2; + } + 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$2 + angle; + } + if ((angle >= startAngle && angle <= endAngle) + || (angle + PI2$2 >= startAngle && angle + PI2$2 <= endAngle)) { + if (angle > Math.PI / 2 && angle < Math.PI * 1.5) { + dir = -dir; + } + w += dir; + } + } + } + return w; +} +function containPath(path, lineWidth, isStroke, x, y) { + var data = path.data; + var len = path.len(); + var w = 0; + var xi = 0; + var yi = 0; + var x0 = 0; + var y0 = 0; + var x1; + var y1; + for (var i = 0; i < len;) { + var cmd = data[i++]; + var isFirst = i === 1; + if (cmd === CMD$1.M && i > 1) { + if (!isStroke) { + w += windingLine(xi, yi, x0, y0, x, y); + } + } + if (isFirst) { + xi = data[i]; + yi = data[i + 1]; + x0 = xi; + y0 = yi; + } + switch (cmd) { + case CMD$1.M: + x0 = data[i++]; + y0 = data[i++]; + xi = x0; + yi = y0; + break; + case CMD$1.L: + if (isStroke) { + if (containStroke$4(xi, yi, data[i], data[i + 1], lineWidth, x, y)) { + return true; + } + } + else { + 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$3(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$2(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: + var cx = data[i++]; + var cy = data[i++]; + var rx = data[i++]; + var ry = data[i++]; + var theta = data[i++]; + var dTheta = data[i++]; + i += 1; + var anticlockwise = !!(1 - data[i++]); + x1 = Math.cos(theta) * rx + cx; + y1 = Math.sin(theta) * ry + cy; + if (!isFirst) { + w += windingLine(xi, yi, x1, y1, x, y); + } + else { + x0 = x1; + y0 = y1; + } + var _x = (x - cx) * ry / rx + cx; + if (isStroke) { + if (containStroke$1(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++]; + x1 = x0 + width; + y1 = y0 + height; + if (isStroke) { + if (containStroke$4(x0, y0, x1, y0, lineWidth, x, y) + || containStroke$4(x1, y0, x1, y1, lineWidth, x, y) + || containStroke$4(x1, y1, x0, y1, lineWidth, x, y) + || containStroke$4(x0, y1, x0, y0, lineWidth, x, y)) { + return true; + } + } + else { + 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$4(xi, yi, x0, y0, lineWidth, x, y)) { + return true; + } + } + else { + w += windingLine(xi, yi, x0, y0, x, y); + } + xi = x0; + yi = y0; + break; + } + } + if (!isStroke && !isAroundEqual(yi, y0)) { + w += windingLine(xi, yi, x0, y0, x, y) || 0; + } + return w !== 0; +} +function contain$1(pathProxy, x, y) { + return containPath(pathProxy, 0, false, x, y); +} +function containStroke(pathProxy, lineWidth, x, y) { + return containPath(pathProxy, lineWidth, true, x, y); +} + +var DEFAULT_PATH_STYLE = defaults({ + fill: '#000', + stroke: null, + strokePercent: 1, + fillOpacity: 1, + strokeOpacity: 1, + lineDashOffset: 0, + lineWidth: 1, + lineCap: 'butt', + miterLimit: 10, + strokeNoScale: false, + strokeFirst: false +}, DEFAULT_COMMON_STYLE); +var DEFAULT_PATH_ANIMATION_PROPS = { + style: defaults({ + fill: true, + stroke: true, + strokePercent: true, + fillOpacity: true, + strokeOpacity: true, + lineDashOffset: true, + lineWidth: true, + miterLimit: true + }, DEFAULT_COMMON_ANIMATION_PROPS.style) +}; +var pathCopyParams = TRANSFORMABLE_PROPS.concat(['invisible', + 'culling', 'z', 'z2', 'zlevel', 'parent' +]); +var Path = (function (_super) { + __extends(Path, _super); + function Path(opts) { + return _super.call(this, opts) || this; + } + Path.prototype.update = function () { + var _this = this; + _super.prototype.update.call(this); + var style = this.style; + if (style.decal) { + var decalEl = this._decalEl = this._decalEl || new Path(); + if (decalEl.buildPath === Path.prototype.buildPath) { + decalEl.buildPath = function (ctx) { + _this.buildPath(ctx, _this.shape); + }; + } + decalEl.silent = true; + var decalElStyle = decalEl.style; + for (var key in style) { + if (decalElStyle[key] !== style[key]) { + decalElStyle[key] = style[key]; + } + } + decalElStyle.fill = style.fill ? style.decal : null; + decalElStyle.decal = null; + decalElStyle.shadowColor = null; + style.strokeFirst && (decalElStyle.stroke = null); + for (var i = 0; i < pathCopyParams.length; ++i) { + decalEl[pathCopyParams[i]] = this[pathCopyParams[i]]; + } + decalEl.__dirty |= REDRAW_BIT; + } + else if (this._decalEl) { + this._decalEl = null; + } + }; + Path.prototype.getDecalElement = function () { + return this._decalEl; + }; + Path.prototype._init = function (props) { + var keysArr = keys(props); + this.shape = this.getDefaultShape(); + var defaultStyle = this.getDefaultStyle(); + if (defaultStyle) { + this.useStyle(defaultStyle); + } + for (var i = 0; i < keysArr.length; i++) { + var key = keysArr[i]; + var value = props[key]; + if (key === 'style') { + if (!this.style) { + this.useStyle(value); + } + else { + extend(this.style, value); + } + } + else if (key === 'shape') { + extend(this.shape, value); + } + else { + _super.prototype.attrKV.call(this, key, value); + } + } + if (!this.style) { + this.useStyle({}); + } + }; + Path.prototype.getDefaultStyle = function () { + return null; + }; + Path.prototype.getDefaultShape = function () { + return {}; + }; + Path.prototype.canBeInsideText = function () { + return this.hasFill(); + }; + Path.prototype.getInsideTextFill = function () { + var pathFill = this.style.fill; + if (pathFill !== 'none') { + if (isString(pathFill)) { + var fillLum = lum(pathFill, 0); + if (fillLum > 0.5) { + return DARK_LABEL_COLOR; + } + else if (fillLum > 0.2) { + return LIGHTER_LABEL_COLOR; + } + return LIGHT_LABEL_COLOR; + } + else if (pathFill) { + return LIGHT_LABEL_COLOR; + } + } + return DARK_LABEL_COLOR; + }; + Path.prototype.getInsideTextStroke = function (textFill) { + var pathFill = this.style.fill; + if (isString(pathFill)) { + var zr = this.__zr; + var isDarkMode = !!(zr && zr.isDarkMode()); + var isDarkLabel = lum(textFill, 0) < DARK_MODE_THRESHOLD; + if (isDarkMode === isDarkLabel) { + return pathFill; + } + } + }; + Path.prototype.buildPath = function (ctx, shapeCfg, inBatch) { }; + Path.prototype.pathUpdated = function () { + this.__dirty &= -5; + }; + Path.prototype.getUpdatedPathProxy = function (inBatch) { + !this.path && this.createPathProxy(); + this.path.beginPath(); + this.buildPath(this.path, this.shape, inBatch); + return this.path; + }; + Path.prototype.createPathProxy = function () { + this.path = new PathProxy(false); + }; + Path.prototype.hasStroke = function () { + var style = this.style; + var stroke = style.stroke; + return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0)); + }; + Path.prototype.hasFill = function () { + var style = this.style; + var fill = style.fill; + return fill != null && fill !== 'none'; + }; + Path.prototype.getBoundingRect = function () { + var rect = this._rect; + var style = this.style; + var needsUpdateRect = !rect; + if (needsUpdateRect) { + var firstInvoke = false; + if (!this.path) { + firstInvoke = true; + this.createPathProxy(); + } + var path = this.path; + if (firstInvoke || (this.__dirty & SHAPE_CHANGED_BIT)) { + path.beginPath(); + this.buildPath(path, this.shape, false); + this.pathUpdated(); + } + rect = path.getBoundingRect(); + } + this._rect = rect; + if (this.hasStroke() && this.path && this.path.len() > 0) { + var rectStroke = this._rectStroke || (this._rectStroke = rect.clone()); + if (this.__dirty || needsUpdateRect) { + rectStroke.copy(rect); + var lineScale = style.strokeNoScale ? this.getLineScale() : 1; + var w = style.lineWidth; + if (!this.hasFill()) { + var strokeContainThreshold = this.strokeContainThreshold; + w = Math.max(w, strokeContainThreshold == null ? 4 : strokeContainThreshold); + } + if (lineScale > 1e-10) { + rectStroke.width += w / lineScale; + rectStroke.height += w / lineScale; + rectStroke.x -= w / lineScale / 2; + rectStroke.y -= w / lineScale / 2; + } + } + return rectStroke; + } + return rect; + }; + Path.prototype.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 pathProxy = this.path; + if (this.hasStroke()) { + var lineWidth = style.lineWidth; + var lineScale = style.strokeNoScale ? this.getLineScale() : 1; + if (lineScale > 1e-10) { + if (!this.hasFill()) { + lineWidth = Math.max(lineWidth, this.strokeContainThreshold); + } + if (containStroke(pathProxy, lineWidth / lineScale, x, y)) { + return true; + } + } + } + if (this.hasFill()) { + return contain$1(pathProxy, x, y); + } + } + return false; + }; + Path.prototype.dirtyShape = function () { + this.__dirty |= SHAPE_CHANGED_BIT; + if (this._rect) { + this._rect = null; + } + if (this._decalEl) { + this._decalEl.dirtyShape(); + } + this.markRedraw(); + }; + Path.prototype.dirty = function () { + this.dirtyStyle(); + this.dirtyShape(); + }; + Path.prototype.animateShape = function (loop) { + return this.animate('shape', loop); + }; + Path.prototype.updateDuringAnimation = function (targetKey) { + if (targetKey === 'style') { + this.dirtyStyle(); + } + else if (targetKey === 'shape') { + this.dirtyShape(); + } + else { + this.markRedraw(); + } + }; + Path.prototype.attrKV = function (key, value) { + if (key === 'shape') { + this.setShape(value); + } + else { + _super.prototype.attrKV.call(this, key, value); + } + }; + Path.prototype.setShape = function (keyOrObj, value) { + var shape = this.shape; + if (!shape) { + shape = this.shape = {}; + } + if (typeof keyOrObj === 'string') { + shape[keyOrObj] = value; + } + else { + extend(shape, keyOrObj); + } + this.dirtyShape(); + return this; + }; + Path.prototype.shapeChanged = function () { + return !!(this.__dirty & SHAPE_CHANGED_BIT); + }; + Path.prototype.createStyle = function (obj) { + return createObject(DEFAULT_PATH_STYLE, obj); + }; + Path.prototype._innerSaveToNormal = function (toState) { + _super.prototype._innerSaveToNormal.call(this, toState); + var normalState = this._normalState; + if (toState.shape && !normalState.shape) { + normalState.shape = extend({}, this.shape); + } + }; + Path.prototype._applyStateObj = function (stateName, state, normalState, keepCurrentStates, transition, animationCfg) { + _super.prototype._applyStateObj.call(this, stateName, state, normalState, keepCurrentStates, transition, animationCfg); + var needsRestoreToNormal = !(state && keepCurrentStates); + var targetShape; + if (state && state.shape) { + if (transition) { + if (keepCurrentStates) { + targetShape = state.shape; + } + else { + targetShape = extend({}, normalState.shape); + extend(targetShape, state.shape); + } + } + else { + targetShape = extend({}, keepCurrentStates ? this.shape : normalState.shape); + extend(targetShape, state.shape); + } + } + else if (needsRestoreToNormal) { + targetShape = normalState.shape; + } + if (targetShape) { + if (transition) { + this.shape = extend({}, this.shape); + var targetShapePrimaryProps = {}; + var shapeKeys = keys(targetShape); + for (var i = 0; i < shapeKeys.length; i++) { + var key = shapeKeys[i]; + if (typeof targetShape[key] === 'object') { + this.shape[key] = targetShape[key]; + } + else { + targetShapePrimaryProps[key] = targetShape[key]; + } + } + this._transitionState(stateName, { + shape: targetShapePrimaryProps + }, animationCfg); + } + else { + this.shape = targetShape; + this.dirtyShape(); + } + } + }; + Path.prototype._mergeStates = function (states) { + var mergedState = _super.prototype._mergeStates.call(this, states); + var mergedShape; + for (var i = 0; i < states.length; i++) { + var state = states[i]; + if (state.shape) { + mergedShape = mergedShape || {}; + this._mergeStyle(mergedShape, state.shape); + } + } + if (mergedShape) { + mergedState.shape = mergedShape; + } + return mergedState; + }; + Path.prototype.getAnimationStyleProps = function () { + return DEFAULT_PATH_ANIMATION_PROPS; + }; + Path.prototype.isZeroArea = function () { + return false; + }; + Path.extend = function (defaultProps) { + var Sub = (function (_super) { + __extends(Sub, _super); + function Sub(opts) { + var _this = _super.call(this, opts) || this; + defaultProps.init && defaultProps.init.call(_this, opts); + return _this; + } + Sub.prototype.getDefaultStyle = function () { + return clone$2(defaultProps.style); + }; + Sub.prototype.getDefaultShape = function () { + return clone$2(defaultProps.shape); + }; + return Sub; + }(Path)); + for (var key in defaultProps) { + if (typeof defaultProps[key] === 'function') { + Sub.prototype[key] = defaultProps[key]; + } + } + return Sub; + }; + Path.initDefaultProps = (function () { + var pathProto = Path.prototype; + pathProto.type = 'path'; + pathProto.strokeContainThreshold = 5; + pathProto.segmentIgnoreThreshold = 0; + pathProto.subPixelOptimize = false; + pathProto.autoBatch = false; + pathProto.__dirty = REDRAW_BIT | STYLE_CHANGED_BIT | SHAPE_CHANGED_BIT; + })(); + return Path; +}(Displayable)); + +var DEFAULT_TSPAN_STYLE = defaults({ + strokeFirst: true, + font: DEFAULT_FONT, + x: 0, + y: 0, + textAlign: 'left', + textBaseline: 'top', + miterLimit: 2 +}, DEFAULT_PATH_STYLE); +var TSpan = (function (_super) { + __extends(TSpan, _super); + function TSpan() { + return _super !== null && _super.apply(this, arguments) || this; + } + TSpan.prototype.hasStroke = function () { + var style = this.style; + var stroke = style.stroke; + return stroke != null && stroke !== 'none' && style.lineWidth > 0; + }; + TSpan.prototype.hasFill = function () { + var style = this.style; + var fill = style.fill; + return fill != null && fill !== 'none'; + }; + TSpan.prototype.createStyle = function (obj) { + return createObject(DEFAULT_TSPAN_STYLE, obj); + }; + TSpan.prototype.setBoundingRect = function (rect) { + this._rect = rect; + }; + TSpan.prototype.getBoundingRect = function () { + var style = this.style; + if (!this._rect) { + var text = style.text; + text != null ? (text += '') : (text = ''); + var rect = getBoundingRect(text, style.font, style.textAlign, style.textBaseline); + rect.x += style.x || 0; + rect.y += style.y || 0; + if (this.hasStroke()) { + var w = style.lineWidth; + rect.x -= w / 2; + rect.y -= w / 2; + rect.width += w; + rect.height += w; + } + this._rect = rect; + } + return this._rect; + }; + TSpan.initDefaultProps = (function () { + var tspanProto = TSpan.prototype; + tspanProto.dirtyRectTolerance = 10; + })(); + return TSpan; +}(Displayable)); +TSpan.prototype.type = 'tspan'; + +var DEFAULT_IMAGE_STYLE = defaults({ + x: 0, + y: 0 +}, DEFAULT_COMMON_STYLE); +var DEFAULT_IMAGE_ANIMATION_PROPS = { + style: defaults({ + x: true, + y: true, + width: true, + height: true, + sx: true, + sy: true, + sWidth: true, + sHeight: true + }, DEFAULT_COMMON_ANIMATION_PROPS.style) +}; +function isImageLike(source) { + return !!(source + && typeof source !== 'string' + && source.width && source.height); +} +var ZRImage = (function (_super) { + __extends(ZRImage, _super); + function ZRImage() { + return _super !== null && _super.apply(this, arguments) || this; + } + ZRImage.prototype.createStyle = function (obj) { + return createObject(DEFAULT_IMAGE_STYLE, obj); + }; + ZRImage.prototype._getSize = function (dim) { + var style = this.style; + var size = style[dim]; + if (size != null) { + return size; + } + var imageSource = isImageLike(style.image) + ? style.image : this.__image; + if (!imageSource) { + return 0; + } + var otherDim = dim === 'width' ? 'height' : 'width'; + var otherDimSize = style[otherDim]; + if (otherDimSize == null) { + return imageSource[dim]; + } + else { + return imageSource[dim] / imageSource[otherDim] * otherDimSize; + } + }; + ZRImage.prototype.getWidth = function () { + return this._getSize('width'); + }; + ZRImage.prototype.getHeight = function () { + return this._getSize('height'); + }; + ZRImage.prototype.getAnimationStyleProps = function () { + return DEFAULT_IMAGE_ANIMATION_PROPS; + }; + ZRImage.prototype.getBoundingRect = function () { + var style = this.style; + if (!this._rect) { + this._rect = new BoundingRect(style.x || 0, style.y || 0, this.getWidth(), this.getHeight()); + } + return this._rect; + }; + return ZRImage; +}(Displayable)); +ZRImage.prototype.type = 'image'; + +function buildPath$2(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; + 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 round = Math.round; +function subPixelOptimizeLine$1(outputShape, inputShape, style) { + if (!inputShape) { + return; + } + var x1 = inputShape.x1; + var x2 = inputShape.x2; + var y1 = inputShape.y1; + var y2 = inputShape.y2; + outputShape.x1 = x1; + outputShape.x2 = x2; + outputShape.y1 = y1; + outputShape.y2 = y2; + var lineWidth = style && style.lineWidth; + if (!lineWidth) { + return outputShape; + } + if (round(x1 * 2) === round(x2 * 2)) { + outputShape.x1 = outputShape.x2 = subPixelOptimize$1(x1, lineWidth, true); + } + if (round(y1 * 2) === round(y2 * 2)) { + outputShape.y1 = outputShape.y2 = subPixelOptimize$1(y1, lineWidth, true); + } + return outputShape; +} +function subPixelOptimizeRect$1(outputShape, inputShape, style) { + if (!inputShape) { + return; + } + var originX = inputShape.x; + var originY = inputShape.y; + var originWidth = inputShape.width; + var originHeight = inputShape.height; + outputShape.x = originX; + outputShape.y = originY; + outputShape.width = originWidth; + outputShape.height = originHeight; + var lineWidth = style && style.lineWidth; + if (!lineWidth) { + return outputShape; + } + 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); + return outputShape; +} +function subPixelOptimize$1(position, lineWidth, positiveOrNegative) { + if (!lineWidth) { + return position; + } + var doubledPosition = round(position * 2); + return (doubledPosition + round(lineWidth)) % 2 === 0 + ? doubledPosition / 2 + : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2; +} + +var RectShape = (function () { + function RectShape() { + this.x = 0; + this.y = 0; + this.width = 0; + this.height = 0; + } + return RectShape; +}()); +var subPixelOptimizeOutputShape$1 = {}; +var Rect = (function (_super) { + __extends(Rect, _super); + function Rect(opts) { + return _super.call(this, opts) || this; + } + Rect.prototype.getDefaultShape = function () { + return new RectShape(); + }; + Rect.prototype.buildPath = function (ctx, shape) { + var x; + var y; + var width; + var height; + if (this.subPixelOptimize) { + var optimizedShape = subPixelOptimizeRect$1(subPixelOptimizeOutputShape$1, shape, this.style); + x = optimizedShape.x; + y = optimizedShape.y; + width = optimizedShape.width; + height = optimizedShape.height; + optimizedShape.r = shape.r; + shape = optimizedShape; + } + else { + x = shape.x; + y = shape.y; + width = shape.width; + height = shape.height; + } + if (!shape.r) { + ctx.rect(x, y, width, height); + } + else { + buildPath$2(ctx, shape); + } + }; + Rect.prototype.isZeroArea = function () { + return !this.shape.width || !this.shape.height; + }; + return Rect; +}(Path)); +Rect.prototype.type = 'rect'; + +var DEFAULT_RICH_TEXT_COLOR = { + fill: "#000" +}; +var DEFAULT_STROKE_LINE_WIDTH = 2; +var DEFAULT_TEXT_ANIMATION_PROPS = { + style: defaults({ + fill: true, + stroke: true, + fillOpacity: true, + strokeOpacity: true, + lineWidth: true, + fontSize: true, + lineHeight: true, + width: true, + height: true, + textShadowColor: true, + textShadowBlur: true, + textShadowOffsetX: true, + textShadowOffsetY: true, + backgroundColor: true, + padding: true, + borderColor: true, + borderWidth: true, + borderRadius: true + }, DEFAULT_COMMON_ANIMATION_PROPS.style) +}; +var ZRText = function(_super) { + __extends(ZRText2, _super); + function ZRText2(opts) { + var _this = _super.call(this) || this; + _this.type = "text"; + _this._children = []; + _this._defaultStyle = DEFAULT_RICH_TEXT_COLOR; + _this.attr(opts); + return _this; + } + ZRText2.prototype.childrenRef = function() { + return this._children; + }; + ZRText2.prototype.update = function() { + _super.prototype.update.call(this); + if (this.styleChanged()) { + this._updateSubTexts(); + } + for (var i = 0; i < this._children.length; i++) { + var child = this._children[i]; + child.zlevel = this.zlevel; + child.z = this.z; + child.z2 = this.z2; + child.culling = this.culling; + child.cursor = this.cursor; + child.invisible = this.invisible; + } + }; + ZRText2.prototype.updateTransform = function() { + var innerTransformable = this.innerTransformable; + if (innerTransformable) { + innerTransformable.updateTransform(); + if (innerTransformable.transform) { + this.transform = innerTransformable.transform; + } + } else { + _super.prototype.updateTransform.call(this); + } + }; + ZRText2.prototype.getLocalTransform = function(m) { + var innerTransformable = this.innerTransformable; + return innerTransformable ? innerTransformable.getLocalTransform(m) : _super.prototype.getLocalTransform.call(this, m); + }; + ZRText2.prototype.getComputedTransform = function() { + if (this.__hostTarget) { + this.__hostTarget.getComputedTransform(); + this.__hostTarget.updateInnerText(true); + } + return _super.prototype.getComputedTransform.call(this); + }; + ZRText2.prototype._updateSubTexts = function() { + this._childCursor = 0; + normalizeTextStyle(this.style); + this.style.rich ? this._updateRichTexts() : this._updatePlainTexts(); + this._children.length = this._childCursor; + this.styleUpdated(); + }; + ZRText2.prototype.addSelfToZr = function(zr) { + _super.prototype.addSelfToZr.call(this, zr); + for (var i = 0; i < this._children.length; i++) { + this._children[i].__zr = zr; + } + }; + ZRText2.prototype.removeSelfFromZr = function(zr) { + _super.prototype.removeSelfFromZr.call(this, zr); + for (var i = 0; i < this._children.length; i++) { + this._children[i].__zr = null; + } + }; + ZRText2.prototype.getBoundingRect = function() { + if (this.styleChanged()) { + this._updateSubTexts(); + } + if (!this._rect) { + var tmpRect = new BoundingRect(0, 0, 0, 0); + var children = this._children; + var tmpMat = []; + var rect = null; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var childRect = child.getBoundingRect(); + var transform = child.getLocalTransform(tmpMat); + if (transform) { + tmpRect.copy(childRect); + tmpRect.applyTransform(transform); + rect = rect || tmpRect.clone(); + rect.union(tmpRect); + } else { + rect = rect || childRect.clone(); + rect.union(childRect); + } + } + this._rect = rect || tmpRect; + } + return this._rect; + }; + ZRText2.prototype.setDefaultTextStyle = function(defaultTextStyle) { + this._defaultStyle = defaultTextStyle || DEFAULT_RICH_TEXT_COLOR; + }; + ZRText2.prototype.setTextContent = function(textContent) { + }; + ZRText2.prototype._mergeStyle = function(targetStyle, sourceStyle) { + if (!sourceStyle) { + return targetStyle; + } + var sourceRich = sourceStyle.rich; + var targetRich = targetStyle.rich || sourceRich && {}; + extend(targetStyle, sourceStyle); + if (sourceRich && targetRich) { + this._mergeRich(targetRich, sourceRich); + targetStyle.rich = targetRich; + } else if (targetRich) { + targetStyle.rich = targetRich; + } + return targetStyle; + }; + ZRText2.prototype._mergeRich = function(targetRich, sourceRich) { + var richNames = keys(sourceRich); + for (var i = 0; i < richNames.length; i++) { + var richName = richNames[i]; + targetRich[richName] = targetRich[richName] || {}; + extend(targetRich[richName], sourceRich[richName]); + } + }; + ZRText2.prototype.getAnimationStyleProps = function() { + return DEFAULT_TEXT_ANIMATION_PROPS; + }; + ZRText2.prototype._getOrCreateChild = function(Ctor) { + var child = this._children[this._childCursor]; + if (!child || !(child instanceof Ctor)) { + child = new Ctor(); + } + this._children[this._childCursor++] = child; + child.__zr = this.__zr; + child.parent = this; + return child; + }; + ZRText2.prototype._updatePlainTexts = function() { + var style = this.style; + var textFont = style.font || DEFAULT_FONT; + var textPadding = style.padding; + var text = getStyleText(style); + var contentBlock = parsePlainText(text, style); + var needDrawBg = needDrawBackground(style); + var bgColorDrawn = !!style.backgroundColor; + var outerHeight = contentBlock.outerHeight; + var outerWidth = contentBlock.outerWidth; + var contentWidth = contentBlock.contentWidth; + var textLines = contentBlock.lines; + var lineHeight = contentBlock.lineHeight; + var defaultStyle = this._defaultStyle; + this.isTruncated = !!contentBlock.isTruncated; + var baseX = style.x || 0; + var baseY = style.y || 0; + var textAlign = style.align || defaultStyle.align || "left"; + var verticalAlign = style.verticalAlign || defaultStyle.verticalAlign || "top"; + var textX = baseX; + var textY = adjustTextY(baseY, contentBlock.contentHeight, verticalAlign); + if (needDrawBg || textPadding) { + var boxX = adjustTextX(baseX, outerWidth, textAlign); + var boxY = adjustTextY(baseY, outerHeight, verticalAlign); + needDrawBg && this._renderBackground(style, style, boxX, boxY, outerWidth, outerHeight); + } + textY += lineHeight / 2; + if (textPadding) { + textX = getTextXForPadding(baseX, textAlign, textPadding); + if (verticalAlign === "top") { + textY += textPadding[0]; + } else if (verticalAlign === "bottom") { + textY -= textPadding[2]; + } + } + var defaultLineWidth = 0; + var useDefaultFill = false; + var textFill = getFill("fill" in style ? style.fill : (useDefaultFill = true, defaultStyle.fill)); + var textStroke = getStroke("stroke" in style ? style.stroke : !bgColorDrawn && (!defaultStyle.autoStroke || useDefaultFill) ? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, defaultStyle.stroke) : null); + var hasShadow = style.textShadowBlur > 0; + var fixedBoundingRect = style.width != null && (style.overflow === "truncate" || style.overflow === "break" || style.overflow === "breakAll"); + var calculatedLineHeight = contentBlock.calculatedLineHeight; + for (var i = 0; i < textLines.length; i++) { + var el = this._getOrCreateChild(TSpan); + var subElStyle = el.createStyle(); + el.useStyle(subElStyle); + subElStyle.text = textLines[i]; + subElStyle.x = textX; + subElStyle.y = textY; + { + subElStyle.textAlign = textAlign; + } + subElStyle.textBaseline = "middle"; + subElStyle.opacity = style.opacity; + subElStyle.strokeFirst = true; + if (hasShadow) { + subElStyle.shadowBlur = style.textShadowBlur || 0; + subElStyle.shadowColor = style.textShadowColor || "transparent"; + subElStyle.shadowOffsetX = style.textShadowOffsetX || 0; + subElStyle.shadowOffsetY = style.textShadowOffsetY || 0; + } + subElStyle.stroke = textStroke; + subElStyle.fill = textFill; + if (textStroke) { + subElStyle.lineWidth = style.lineWidth || defaultLineWidth; + subElStyle.lineDash = style.lineDash; + subElStyle.lineDashOffset = style.lineDashOffset || 0; + } + subElStyle.font = textFont; + setSeparateFont(subElStyle, style); + textY += lineHeight; + if (fixedBoundingRect) { + el.setBoundingRect(new BoundingRect(adjustTextX(subElStyle.x, contentWidth, subElStyle.textAlign), adjustTextY(subElStyle.y, calculatedLineHeight, subElStyle.textBaseline), contentWidth, calculatedLineHeight)); + } + } + }; + ZRText2.prototype._updateRichTexts = function() { + var style = this.style; + var text = getStyleText(style); + var contentBlock = parseRichText(text, style); + var contentWidth = contentBlock.width; + var outerWidth = contentBlock.outerWidth; + var outerHeight = contentBlock.outerHeight; + var textPadding = style.padding; + var baseX = style.x || 0; + var baseY = style.y || 0; + var defaultStyle = this._defaultStyle; + var textAlign = style.align || defaultStyle.align; + var verticalAlign = style.verticalAlign || defaultStyle.verticalAlign; + this.isTruncated = !!contentBlock.isTruncated; + var boxX = adjustTextX(baseX, outerWidth, textAlign); + var boxY = adjustTextY(baseY, outerHeight, verticalAlign); + var xLeft = boxX; + var lineTop = boxY; + if (textPadding) { + xLeft += textPadding[3]; + lineTop += textPadding[0]; + } + var xRight = xLeft + contentWidth; + if (needDrawBackground(style)) { + this._renderBackground(style, style, boxX, boxY, outerWidth, outerHeight); + } + var bgColorDrawn = !!style.backgroundColor; + 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 remainedWidth = line.width; + var leftIndex = 0; + var lineXLeft = xLeft; + var lineXRight = xRight; + var rightIndex = tokenCount - 1; + var token = void 0; + while (leftIndex < tokenCount && (token = tokens[leftIndex], !token.align || token.align === "left")) { + this._placeToken(token, style, lineHeight, lineTop, lineXLeft, "left", bgColorDrawn); + remainedWidth -= token.width; + lineXLeft += token.width; + leftIndex++; + } + while (rightIndex >= 0 && (token = tokens[rightIndex], token.align === "right")) { + this._placeToken(token, style, lineHeight, lineTop, lineXRight, "right", bgColorDrawn); + remainedWidth -= token.width; + lineXRight -= token.width; + rightIndex--; + } + lineXLeft += (contentWidth - (lineXLeft - xLeft) - (xRight - lineXRight) - remainedWidth) / 2; + while (leftIndex <= rightIndex) { + token = tokens[leftIndex]; + this._placeToken(token, style, lineHeight, lineTop, lineXLeft + token.width / 2, "center", bgColorDrawn); + lineXLeft += token.width; + leftIndex++; + } + lineTop += lineHeight; + } + }; + ZRText2.prototype._placeToken = function(token, style, lineHeight, lineTop, x, textAlign, parentBgColorDrawn) { + var tokenStyle = style.rich[token.styleName] || {}; + tokenStyle.text = token.text; + var verticalAlign = token.verticalAlign; + var y = lineTop + lineHeight / 2; + if (verticalAlign === "top") { + y = lineTop + token.height / 2; + } else if (verticalAlign === "bottom") { + y = lineTop + lineHeight - token.height / 2; + } + var needDrawBg = !token.isLineHolder && needDrawBackground(tokenStyle); + needDrawBg && this._renderBackground(tokenStyle, style, textAlign === "right" ? x - token.width : textAlign === "center" ? x - token.width / 2 : x, y - token.height / 2, token.width, token.height); + var bgColorDrawn = !!tokenStyle.backgroundColor; + var textPadding = token.textPadding; + if (textPadding) { + x = getTextXForPadding(x, textAlign, textPadding); + y -= token.height / 2 - textPadding[0] - token.innerHeight / 2; + } + var el = this._getOrCreateChild(TSpan); + var subElStyle = el.createStyle(); + el.useStyle(subElStyle); + var defaultStyle = this._defaultStyle; + var useDefaultFill = false; + var defaultLineWidth = 0; + var textFill = getFill("fill" in tokenStyle ? tokenStyle.fill : "fill" in style ? style.fill : (useDefaultFill = true, defaultStyle.fill)); + var textStroke = getStroke("stroke" in tokenStyle ? tokenStyle.stroke : "stroke" in style ? style.stroke : !bgColorDrawn && !parentBgColorDrawn && (!defaultStyle.autoStroke || useDefaultFill) ? (defaultLineWidth = DEFAULT_STROKE_LINE_WIDTH, defaultStyle.stroke) : null); + var hasShadow = tokenStyle.textShadowBlur > 0 || style.textShadowBlur > 0; + subElStyle.text = token.text; + subElStyle.x = x; + subElStyle.y = y; + if (hasShadow) { + subElStyle.shadowBlur = tokenStyle.textShadowBlur || style.textShadowBlur || 0; + subElStyle.shadowColor = tokenStyle.textShadowColor || style.textShadowColor || "transparent"; + subElStyle.shadowOffsetX = tokenStyle.textShadowOffsetX || style.textShadowOffsetX || 0; + subElStyle.shadowOffsetY = tokenStyle.textShadowOffsetY || style.textShadowOffsetY || 0; + } + subElStyle.textAlign = textAlign; + subElStyle.textBaseline = "middle"; + subElStyle.font = token.font || DEFAULT_FONT; + subElStyle.opacity = retrieve3(tokenStyle.opacity, style.opacity, 1); + setSeparateFont(subElStyle, tokenStyle); + if (textStroke) { + subElStyle.lineWidth = retrieve3(tokenStyle.lineWidth, style.lineWidth, defaultLineWidth); + subElStyle.lineDash = retrieve2(tokenStyle.lineDash, style.lineDash); + subElStyle.lineDashOffset = style.lineDashOffset || 0; + subElStyle.stroke = textStroke; + } + if (textFill) { + subElStyle.fill = textFill; + } + var textWidth = token.contentWidth; + var textHeight = token.contentHeight; + el.setBoundingRect(new BoundingRect(adjustTextX(subElStyle.x, textWidth, subElStyle.textAlign), adjustTextY(subElStyle.y, textHeight, subElStyle.textBaseline), textWidth, textHeight)); + }; + ZRText2.prototype._renderBackground = function(style, topStyle, x, y, width, height) { + var textBackgroundColor = style.backgroundColor; + var textBorderWidth = style.borderWidth; + var textBorderColor = style.borderColor; + var isImageBg = textBackgroundColor && textBackgroundColor.image; + var isPlainOrGradientBg = textBackgroundColor && !isImageBg; + var textBorderRadius = style.borderRadius; + var self = this; + var rectEl; + var imgEl; + if (isPlainOrGradientBg || style.lineHeight || textBorderWidth && textBorderColor) { + rectEl = this._getOrCreateChild(Rect); + rectEl.useStyle(rectEl.createStyle()); + rectEl.style.fill = null; + var rectShape = rectEl.shape; + rectShape.x = x; + rectShape.y = y; + rectShape.width = width; + rectShape.height = height; + rectShape.r = textBorderRadius; + rectEl.dirtyShape(); + } + if (isPlainOrGradientBg) { + var rectStyle = rectEl.style; + rectStyle.fill = textBackgroundColor || null; + rectStyle.fillOpacity = retrieve2(style.fillOpacity, 1); + } else if (isImageBg) { + imgEl = this._getOrCreateChild(ZRImage); + imgEl.onload = function() { + self.dirtyStyle(); + }; + var imgStyle = imgEl.style; + imgStyle.image = textBackgroundColor.image; + imgStyle.x = x; + imgStyle.y = y; + imgStyle.width = width; + imgStyle.height = height; + } + if (textBorderWidth && textBorderColor) { + var rectStyle = rectEl.style; + rectStyle.lineWidth = textBorderWidth; + rectStyle.stroke = textBorderColor; + rectStyle.strokeOpacity = retrieve2(style.strokeOpacity, 1); + rectStyle.lineDash = style.borderDash; + rectStyle.lineDashOffset = style.borderDashOffset || 0; + rectEl.strokeContainThreshold = 0; + if (rectEl.hasFill() && rectEl.hasStroke()) { + rectStyle.strokeFirst = true; + rectStyle.lineWidth *= 2; + } + } + var commonStyle = (rectEl || imgEl).style; + commonStyle.shadowBlur = style.shadowBlur || 0; + commonStyle.shadowColor = style.shadowColor || "transparent"; + commonStyle.shadowOffsetX = style.shadowOffsetX || 0; + commonStyle.shadowOffsetY = style.shadowOffsetY || 0; + commonStyle.opacity = retrieve3(style.opacity, topStyle.opacity, 1); + }; + ZRText2.makeFont = function(style) { + var font = ""; + if (hasSeparateFont(style)) { + font = [ + style.fontStyle, + style.fontWeight, + parseFontSize(style.fontSize), + style.fontFamily || "sans-serif" + ].join(" "); + } + return font && trim(font) || style.textFont || style.font; + }; + return ZRText2; +}(Displayable); +var VALID_TEXT_ALIGN = { left: true, right: 1, center: 1 }; +var VALID_TEXT_VERTICAL_ALIGN = { top: 1, bottom: 1, middle: 1 }; +var FONT_PARTS = ["fontStyle", "fontWeight", "fontSize", "fontFamily"]; +function parseFontSize(fontSize) { + if (typeof fontSize === "string" && (fontSize.indexOf("px") !== -1 || fontSize.indexOf("rem") !== -1 || fontSize.indexOf("em") !== -1)) { + return fontSize; + } else if (!isNaN(+fontSize)) { + return fontSize + "px"; + } else { + return DEFAULT_FONT_SIZE + "px"; + } +} +function setSeparateFont(targetStyle, sourceStyle) { + for (var i = 0; i < FONT_PARTS.length; i++) { + var fontProp = FONT_PARTS[i]; + var val = sourceStyle[fontProp]; + if (val != null) { + targetStyle[fontProp] = val; + } + } +} +function hasSeparateFont(style) { + return style.fontSize != null || style.fontFamily || style.fontWeight; +} +function normalizeTextStyle(style) { + normalizeStyle(style); + each$4(style.rich, normalizeStyle); + return style; +} +function normalizeStyle(style) { + if (style) { + style.font = ZRText.makeFont(style); + var textAlign = style.align; + textAlign === "middle" && (textAlign = "center"); + style.align = textAlign == null || VALID_TEXT_ALIGN[textAlign] ? textAlign : "left"; + var verticalAlign = style.verticalAlign; + verticalAlign === "center" && (verticalAlign = "middle"); + style.verticalAlign = verticalAlign == null || VALID_TEXT_VERTICAL_ALIGN[verticalAlign] ? verticalAlign : "top"; + var textPadding = style.padding; + if (textPadding) { + style.padding = normalizeCssArray$1(style.padding); + } + } +} +function getStroke(stroke, lineWidth) { + return stroke == null || lineWidth <= 0 || stroke === "transparent" || stroke === "none" ? null : stroke.image || stroke.colorStops ? "#000" : stroke; +} +function getFill(fill) { + return fill == null || fill === "none" ? null : fill.image || fill.colorStops ? "#000" : fill; +} +function getTextXForPadding(x, textAlign, textPadding) { + return textAlign === "right" ? x - textPadding[1] : textAlign === "center" ? x + textPadding[3] / 2 - textPadding[1] / 2 : x + textPadding[3]; +} +function getStyleText(style) { + var text = style.text; + text != null && (text += ""); + return text; +} +function needDrawBackground(style) { + return !!(style.backgroundColor || style.lineHeight || style.borderWidth && style.borderColor); +} + +var getECData = makeInner(); +var setCommonECData = function (seriesIndex, dataType, dataIdx, el) { + if (el) { + var ecData = getECData(el); + // Add data index and series index for indexing the data by element + // Useful in tooltip + ecData.dataIndex = dataIdx; + ecData.dataType = dataType; + ecData.seriesIndex = seriesIndex; + ecData.ssrType = 'chart'; + // TODO: not store dataIndex on children. + if (el.type === 'group') { + el.traverse(function (child) { + var childECData = getECData(child); + childECData.seriesIndex = seriesIndex; + childECData.dataIndex = dataIdx; + childECData.dataType = dataType; + childECData.ssrType = 'chart'; + }); + } + } +}; + +var _highlightNextDigit = 1; +var _highlightKeyMap = {}; +var getSavedStates = makeInner(); +var getComponentStates = makeInner(); +var HOVER_STATE_NORMAL = 0; +var HOVER_STATE_BLUR = 1; +var HOVER_STATE_EMPHASIS = 2; +var SPECIAL_STATES = ["emphasis", "blur", "select"]; +var DISPLAY_STATES = ["normal", "emphasis", "blur", "select"]; +var Z2_EMPHASIS_LIFT = 10; +var Z2_SELECT_LIFT = 9; +var HIGHLIGHT_ACTION_TYPE = "highlight"; +var DOWNPLAY_ACTION_TYPE = "downplay"; +var SELECT_ACTION_TYPE = "select"; +var UNSELECT_ACTION_TYPE = "unselect"; +var TOGGLE_SELECT_ACTION_TYPE = "toggleSelect"; +function hasFillOrStroke(fillOrStroke) { + return fillOrStroke != null && fillOrStroke !== "none"; +} +function doChangeHoverState(el, stateName, hoverStateEnum) { + if (el.onHoverStateChange && (el.hoverState || 0) !== hoverStateEnum) { + el.onHoverStateChange(stateName); + } + el.hoverState = hoverStateEnum; +} +function singleEnterEmphasis(el) { + doChangeHoverState(el, "emphasis", HOVER_STATE_EMPHASIS); +} +function singleLeaveEmphasis(el) { + if (el.hoverState === HOVER_STATE_EMPHASIS) { + doChangeHoverState(el, "normal", HOVER_STATE_NORMAL); + } +} +function singleEnterBlur(el) { + doChangeHoverState(el, "blur", HOVER_STATE_BLUR); +} +function singleLeaveBlur(el) { + if (el.hoverState === HOVER_STATE_BLUR) { + doChangeHoverState(el, "normal", HOVER_STATE_NORMAL); + } +} +function singleEnterSelect(el) { + el.selected = true; +} +function singleLeaveSelect(el) { + el.selected = false; +} +function updateElementState(el, updater, commonParam) { + updater(el, commonParam); +} +function traverseUpdateState(el, updater, commonParam) { + updateElementState(el, updater, commonParam); + el.isGroup && el.traverse(function(child) { + updateElementState(child, updater, commonParam); + }); +} +function setStatesFlag(el, stateName) { + switch (stateName) { + case "emphasis": + el.hoverState = HOVER_STATE_EMPHASIS; + break; + case "normal": + el.hoverState = HOVER_STATE_NORMAL; + break; + case "blur": + el.hoverState = HOVER_STATE_BLUR; + break; + case "select": + el.selected = true; + } +} +function getFromStateStyle(el, props, toStateName, defaultValue) { + var style = el.style; + var fromState = {}; + for (var i = 0; i < props.length; i++) { + var propName = props[i]; + var val = style[propName]; + fromState[propName] = val == null ? defaultValue && defaultValue[propName] : val; + } + for (var i = 0; i < el.animators.length; i++) { + var animator = el.animators[i]; + if (animator.__fromStateTransition && animator.__fromStateTransition.indexOf(toStateName) < 0 && animator.targetName === "style") { + animator.saveTo(fromState, props); + } + } + return fromState; +} +function createEmphasisDefaultState(el, stateName, targetStates, state) { + var hasSelect = targetStates && indexOf(targetStates, "select") >= 0; + var cloned = false; + if (el instanceof Path) { + var store = getSavedStates(el); + var fromFill = hasSelect ? store.selectFill || store.normalFill : store.normalFill; + var fromStroke = hasSelect ? store.selectStroke || store.normalStroke : store.normalStroke; + if (hasFillOrStroke(fromFill) || hasFillOrStroke(fromStroke)) { + state = state || {}; + var emphasisStyle = state.style || {}; + if (emphasisStyle.fill === "inherit") { + cloned = true; + state = extend({}, state); + emphasisStyle = extend({}, emphasisStyle); + emphasisStyle.fill = fromFill; + } else if (!hasFillOrStroke(emphasisStyle.fill) && hasFillOrStroke(fromFill)) { + cloned = true; + state = extend({}, state); + emphasisStyle = extend({}, emphasisStyle); + emphasisStyle.fill = liftColor(fromFill); + } else if (!hasFillOrStroke(emphasisStyle.stroke) && hasFillOrStroke(fromStroke)) { + if (!cloned) { + state = extend({}, state); + emphasisStyle = extend({}, emphasisStyle); + } + emphasisStyle.stroke = liftColor(fromStroke); + } + state.style = emphasisStyle; + } + } + if (state) { + if (state.z2 == null) { + if (!cloned) { + state = extend({}, state); + } + var z2EmphasisLift = el.z2EmphasisLift; + state.z2 = el.z2 + (z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT); + } + } + return state; +} +function createSelectDefaultState(el, stateName, state) { + if (state) { + if (state.z2 == null) { + state = extend({}, state); + var z2SelectLift = el.z2SelectLift; + state.z2 = el.z2 + (z2SelectLift != null ? z2SelectLift : Z2_SELECT_LIFT); + } + } + return state; +} +function createBlurDefaultState(el, stateName, state) { + var hasBlur = indexOf(el.currentStates, stateName) >= 0; + var currentOpacity = el.style.opacity; + var fromState = !hasBlur ? getFromStateStyle(el, ["opacity"], stateName, { + opacity: 1 + }) : null; + state = state || {}; + var blurStyle = state.style || {}; + if (blurStyle.opacity == null) { + state = extend({}, state); + blurStyle = extend({ + // Already being applied 'emphasis'. DON'T mul opacity multiple times. + opacity: hasBlur ? currentOpacity : fromState.opacity * 0.1 + }, blurStyle); + state.style = blurStyle; + } + return state; +} +function elementStateProxy(stateName, targetStates) { + var state = this.states[stateName]; + if (this.style) { + if (stateName === "emphasis") { + return createEmphasisDefaultState(this, stateName, targetStates, state); + } else if (stateName === "blur") { + return createBlurDefaultState(this, stateName, state); + } else if (stateName === "select") { + return createSelectDefaultState(this, stateName, state); + } + } + return state; +} +function setDefaultStateProxy(el) { + el.stateProxy = elementStateProxy; + var textContent = el.getTextContent(); + var textGuide = el.getTextGuideLine(); + if (textContent) { + textContent.stateProxy = elementStateProxy; + } + if (textGuide) { + textGuide.stateProxy = elementStateProxy; + } +} +function enterEmphasisWhenMouseOver(el, e) { + !shouldSilent(el, e) && !el.__highByOuter && traverseUpdateState(el, singleEnterEmphasis); +} +function leaveEmphasisWhenMouseOut(el, e) { + !shouldSilent(el, e) && !el.__highByOuter && traverseUpdateState(el, singleLeaveEmphasis); +} +function enterEmphasis(el, highlightDigit) { + el.__highByOuter |= 1 << (highlightDigit || 0); + traverseUpdateState(el, singleEnterEmphasis); +} +function leaveEmphasis(el, highlightDigit) { + !(el.__highByOuter &= ~(1 << (highlightDigit || 0))) && traverseUpdateState(el, singleLeaveEmphasis); +} +function enterBlur(el) { + traverseUpdateState(el, singleEnterBlur); +} +function leaveBlur(el) { + traverseUpdateState(el, singleLeaveBlur); +} +function enterSelect(el) { + traverseUpdateState(el, singleEnterSelect); +} +function leaveSelect(el) { + traverseUpdateState(el, singleLeaveSelect); +} +function shouldSilent(el, e) { + return el.__highDownSilentOnTouch && e.zrByTouch; +} +function allLeaveBlur(api) { + var model = api.getModel(); + var leaveBlurredSeries = []; + var allComponentViews = []; + model.eachComponent(function(componentType, componentModel) { + var componentStates = getComponentStates(componentModel); + var isSeries = componentType === "series"; + var view = isSeries ? api.getViewOfSeriesModel(componentModel) : api.getViewOfComponentModel(componentModel); + !isSeries && allComponentViews.push(view); + if (componentStates.isBlured) { + view.group.traverse(function(child) { + singleLeaveBlur(child); + }); + isSeries && leaveBlurredSeries.push(componentModel); + } + componentStates.isBlured = false; + }); + each$4(allComponentViews, function(view) { + if (view && view.toggleBlurSeries) { + view.toggleBlurSeries(leaveBlurredSeries, false, model); + } + }); +} +function blurSeries(targetSeriesIndex, focus, blurScope, api) { + var ecModel = api.getModel(); + blurScope = blurScope || "coordinateSystem"; + function leaveBlurOfIndices(data, dataIndices) { + for (var i = 0; i < dataIndices.length; i++) { + var itemEl = data.getItemGraphicEl(dataIndices[i]); + itemEl && leaveBlur(itemEl); + } + } + if (targetSeriesIndex == null) { + return; + } + if (!focus || focus === "none") { + return; + } + var targetSeriesModel = ecModel.getSeriesByIndex(targetSeriesIndex); + var targetCoordSys = targetSeriesModel.coordinateSystem; + if (targetCoordSys && targetCoordSys.master) { + targetCoordSys = targetCoordSys.master; + } + var blurredSeries = []; + ecModel.eachSeries(function(seriesModel) { + var sameSeries = targetSeriesModel === seriesModel; + var coordSys = seriesModel.coordinateSystem; + if (coordSys && coordSys.master) { + coordSys = coordSys.master; + } + var sameCoordSys = coordSys && targetCoordSys ? coordSys === targetCoordSys : sameSeries; + if (!// Not blur other series if blurScope series + (blurScope === "series" && !sameSeries || blurScope === "coordinateSystem" && !sameCoordSys || focus === "series" && sameSeries)) { + var view = api.getViewOfSeriesModel(seriesModel); + view.group.traverse(function(child) { + if (child.__highByOuter && sameSeries && focus === "self") { + return; + } + singleEnterBlur(child); + }); + if (isArrayLike(focus)) { + leaveBlurOfIndices(seriesModel.getData(), focus); + } else if (isObject$2(focus)) { + var dataTypes = keys(focus); + for (var d = 0; d < dataTypes.length; d++) { + leaveBlurOfIndices(seriesModel.getData(dataTypes[d]), focus[dataTypes[d]]); + } + } + blurredSeries.push(seriesModel); + getComponentStates(seriesModel).isBlured = true; + } + }); + ecModel.eachComponent(function(componentType, componentModel) { + if (componentType === "series") { + return; + } + var view = api.getViewOfComponentModel(componentModel); + if (view && view.toggleBlurSeries) { + view.toggleBlurSeries(blurredSeries, true, ecModel); + } + }); +} +function blurComponent(componentMainType, componentIndex, api) { + if (componentMainType == null || componentIndex == null) { + return; + } + var componentModel = api.getModel().getComponent(componentMainType, componentIndex); + if (!componentModel) { + return; + } + getComponentStates(componentModel).isBlured = true; + var view = api.getViewOfComponentModel(componentModel); + if (!view || !view.focusBlurEnabled) { + return; + } + view.group.traverse(function(child) { + singleEnterBlur(child); + }); +} +function blurSeriesFromHighlightPayload(seriesModel, payload, api) { + var seriesIndex = seriesModel.seriesIndex; + var data = seriesModel.getData(payload.dataType); + if (!data) { + return; + } + var dataIndex = queryDataIndex(data, payload); + dataIndex = (isArray(dataIndex) ? dataIndex[0] : dataIndex) || 0; + var el = data.getItemGraphicEl(dataIndex); + if (!el) { + var count = data.count(); + var current = 0; + while (!el && current < count) { + el = data.getItemGraphicEl(current++); + } + } + if (el) { + var ecData = getECData(el); + blurSeries(seriesIndex, ecData.focus, ecData.blurScope, api); + } else { + var focus_1 = seriesModel.get(["emphasis", "focus"]); + var blurScope = seriesModel.get(["emphasis", "blurScope"]); + if (focus_1 != null) { + blurSeries(seriesIndex, focus_1, blurScope, api); + } + } +} +function findComponentHighDownDispatchers(componentMainType, componentIndex, name, api) { + var ret = { + focusSelf: false, + dispatchers: null + }; + if (componentMainType == null || componentMainType === "series" || componentIndex == null || name == null) { + return ret; + } + var componentModel = api.getModel().getComponent(componentMainType, componentIndex); + if (!componentModel) { + return ret; + } + var view = api.getViewOfComponentModel(componentModel); + if (!view || !view.findHighDownDispatchers) { + return ret; + } + var dispatchers = view.findHighDownDispatchers(name); + var focusSelf; + for (var i = 0; i < dispatchers.length; i++) { + if (getECData(dispatchers[i]).focus === "self") { + focusSelf = true; + break; + } + } + return { + focusSelf, + dispatchers + }; +} +function handleGlobalMouseOverForHighDown(dispatcher, e, api) { + var ecData = getECData(dispatcher); + var _a = findComponentHighDownDispatchers(ecData.componentMainType, ecData.componentIndex, ecData.componentHighDownName, api), dispatchers = _a.dispatchers, focusSelf = _a.focusSelf; + if (dispatchers) { + if (focusSelf) { + blurComponent(ecData.componentMainType, ecData.componentIndex, api); + } + each$4(dispatchers, function(dispatcher2) { + return enterEmphasisWhenMouseOver(dispatcher2, e); + }); + } else { + blurSeries(ecData.seriesIndex, ecData.focus, ecData.blurScope, api); + if (ecData.focus === "self") { + blurComponent(ecData.componentMainType, ecData.componentIndex, api); + } + enterEmphasisWhenMouseOver(dispatcher, e); + } +} +function handleGlobalMouseOutForHighDown(dispatcher, e, api) { + allLeaveBlur(api); + var ecData = getECData(dispatcher); + var dispatchers = findComponentHighDownDispatchers(ecData.componentMainType, ecData.componentIndex, ecData.componentHighDownName, api).dispatchers; + if (dispatchers) { + each$4(dispatchers, function(dispatcher2) { + return leaveEmphasisWhenMouseOut(dispatcher2, e); + }); + } else { + leaveEmphasisWhenMouseOut(dispatcher, e); + } +} +function toggleSelectionFromPayload(seriesModel, payload, api) { + if (!isSelectChangePayload(payload)) { + return; + } + var dataType = payload.dataType; + var data = seriesModel.getData(dataType); + var dataIndex = queryDataIndex(data, payload); + if (!isArray(dataIndex)) { + dataIndex = [dataIndex]; + } + seriesModel[payload.type === TOGGLE_SELECT_ACTION_TYPE ? "toggleSelect" : payload.type === SELECT_ACTION_TYPE ? "select" : "unselect"](dataIndex, dataType); +} +function updateSeriesElementSelection(seriesModel) { + var allData = seriesModel.getAllData(); + each$4(allData, function(_a) { + var data = _a.data, type = _a.type; + data.eachItemGraphicEl(function(el, idx) { + seriesModel.isSelected(idx, type) ? enterSelect(el) : leaveSelect(el); + }); + }); +} +function getAllSelectedIndices(ecModel) { + var ret = []; + ecModel.eachSeries(function(seriesModel) { + var allData = seriesModel.getAllData(); + each$4(allData, function(_a) { + _a.data; var type = _a.type; + var dataIndices = seriesModel.getSelectedDataIndices(); + if (dataIndices.length > 0) { + var item = { + dataIndex: dataIndices, + seriesIndex: seriesModel.seriesIndex + }; + if (type != null) { + item.dataType = type; + } + ret.push(item); + } + }); + }); + return ret; +} +function enableHoverEmphasis(el, focus, blurScope) { + setAsHighDownDispatcher(el, true); + traverseUpdateState(el, setDefaultStateProxy); + enableHoverFocus(el, focus, blurScope); +} +function disableHoverEmphasis(el) { + setAsHighDownDispatcher(el, false); +} +function toggleHoverEmphasis(el, focus, blurScope, isDisabled) { + isDisabled ? disableHoverEmphasis(el) : enableHoverEmphasis(el, focus, blurScope); +} +function enableHoverFocus(el, focus, blurScope) { + var ecData = getECData(el); + if (focus != null) { + ecData.focus = focus; + ecData.blurScope = blurScope; + } else if (ecData.focus) { + ecData.focus = null; + } +} +var OTHER_STATES = ["emphasis", "blur", "select"]; +var defaultStyleGetterMap = { + itemStyle: "getItemStyle", + lineStyle: "getLineStyle", + areaStyle: "getAreaStyle" +}; +function setStatesStylesFromModel(el, itemModel, styleType, getter) { + styleType = styleType || "itemStyle"; + for (var i = 0; i < OTHER_STATES.length; i++) { + var stateName = OTHER_STATES[i]; + var model = itemModel.getModel([stateName, styleType]); + var state = el.ensureState(stateName); + state.style = model[defaultStyleGetterMap[styleType]](); + } +} +function setAsHighDownDispatcher(el, asDispatcher) { + var disable = asDispatcher === false; + var extendedEl = el; + if (el.highDownSilentOnTouch) { + extendedEl.__highDownSilentOnTouch = el.highDownSilentOnTouch; + } + if (!disable || extendedEl.__highDownDispatcher) { + extendedEl.__highByOuter = extendedEl.__highByOuter || 0; + extendedEl.__highDownDispatcher = !disable; + } +} +function isHighDownDispatcher(el) { + return !!(el && el.__highDownDispatcher); +} +function getHighlightDigit(highlightKey) { + var highlightDigit = _highlightKeyMap[highlightKey]; + if (highlightDigit == null && _highlightNextDigit <= 32) { + highlightDigit = _highlightKeyMap[highlightKey] = _highlightNextDigit++; + } + return highlightDigit; +} +function isSelectChangePayload(payload) { + var payloadType = payload.type; + return payloadType === SELECT_ACTION_TYPE || payloadType === UNSELECT_ACTION_TYPE || payloadType === TOGGLE_SELECT_ACTION_TYPE; +} +function isHighDownPayload(payload) { + var payloadType = payload.type; + return payloadType === HIGHLIGHT_ACTION_TYPE || payloadType === DOWNPLAY_ACTION_TYPE; +} +function savePathStates(el) { + var store = getSavedStates(el); + store.normalFill = el.style.fill; + store.normalStroke = el.style.stroke; + var selectState = el.states.select || {}; + store.selectFill = selectState.style && selectState.style.fill || null; + store.selectStroke = selectState.style && selectState.style.stroke || null; +} + +var CMD = PathProxy.CMD; +var points = [[], [], []]; +var mathSqrt$2 = Math.sqrt; +var mathAtan2 = Math.atan2; +function transformPath(path, m) { + if (!m) { + return; + } + var data = path.data; + var len = path.len(); + var cmd; + var nPoint; + var i; + var j; + var k; + var p; + var M = CMD.M; + var C = CMD.C; + var L = CMD.L; + var R = CMD.R; + var A = CMD.A; + var Q = CMD.Q; + for (i = 0, j = 0; i < len;) { + 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$2(m[0] * m[0] + m[1] * m[1]); + var sy = mathSqrt$2(m[2] * m[2] + m[3] * m[3]); + var angle = mathAtan2(-m[1] / sy, m[0] / sx); + data[i] *= sx; + data[i++] += x; + data[i] *= sy; + data[i++] += y; + data[i++] *= sx; + data[i++] *= sy; + data[i++] += angle; + data[i++] += angle; + i += 2; + j = i; + break; + case R: + p[0] = data[i++]; + p[1] = data[i++]; + applyTransform$1(p, p, m); + data[j++] = p[0]; + data[j++] = p[1]; + p[0] += data[i++]; + p[1] += data[i++]; + applyTransform$1(p, p, m); + data[j++] = p[0]; + data[j++] = p[1]; + } + for (k = 0; k < nPoint; k++) { + var p_1 = points[k]; + p_1[0] = data[i++]; + p_1[1] = data[i++]; + applyTransform$1(p_1, p_1, m); + data[j++] = p_1[0]; + data[j++] = p_1[1]; + } + } + path.increaseVersion(); +} + +var mathSqrt$1 = Math.sqrt; +var mathSin$1 = Math.sin; +var mathCos$1 = Math.cos; +var PI$3 = Math.PI; +function vMag(v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1]); +} +function vRatio(u, v) { + return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); +} +function vAngle(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$3 / 180.0); + var xp = mathCos$1(psi) * (x1 - x2) / 2.0 + + mathSin$1(psi) * (y1 - y2) / 2.0; + var yp = -1 * mathSin$1(psi) * (x1 - x2) / 2.0 + + mathCos$1(psi) * (y1 - y2) / 2.0; + var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); + if (lambda > 1) { + rx *= mathSqrt$1(lambda); + ry *= mathSqrt$1(lambda); + } + var f = (fa === fs ? -1 : 1) + * mathSqrt$1((((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$1(psi) * cxp + - mathSin$1(psi) * cyp; + var cy = (y1 + y2) / 2.0 + + mathSin$1(psi) * cxp + + mathCos$1(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$3; + } + if (vRatio(u, v) >= 1) { + dTheta = 0; + } + if (dTheta < 0) { + var n = Math.round(dTheta / PI$3 * 1e6) / 1e6; + dTheta = PI$3 * 2 + (n % 2) * PI$3; + } + path.addData(cmd, cx, cy, rx, ry, theta, dTheta, psi, fs); +} +var commandReg = /([mlvhzcqtsa])([^mlvhzcqtsa]*)/ig; +var numberReg = /-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g; +function createPathProxyFromString(data) { + var path = new PathProxy(); + if (!data) { + return path; + } + var cpx = 0; + var cpy = 0; + var subpathX = cpx; + var subpathY = cpy; + var prevCmd; + var CMD = PathProxy.CMD; + var cmdList = data.match(commandReg); + if (!cmdList) { + return path; + } + for (var l = 0; l < cmdList.length; l++) { + var cmdText = cmdList[l]; + var cmdStr = cmdText.charAt(0); + var cmd = void 0; + 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 = void 0; + var ctlPty = void 0; + var rx = void 0; + var ry = void 0; + var psi = void 0; + var fa = void 0; + var fs = void 0; + var x1 = cpx; + var y1 = cpy; + var len = void 0; + var pathData = void 0; + 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; + len = path.len(); + 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; + len = path.len(); + 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; + len = path.len(); + 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; + len = path.len(); + 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); + cpx = subpathX; + cpy = subpathY; + } + prevCmd = cmd; + } + path.toStatic(); + return path; +} +var SVGPath = (function (_super) { + __extends(SVGPath, _super); + function SVGPath() { + return _super !== null && _super.apply(this, arguments) || this; + } + SVGPath.prototype.applyTransform = function (m) { }; + return SVGPath; +}(Path)); +function isPathProxy(path) { + return path.setData != null; +} +function createPathOptions(str, opts) { + var pathProxy = createPathProxyFromString(str); + var innerOpts = extend({}, opts); + innerOpts.buildPath = function (path) { + if (isPathProxy(path)) { + path.setData(pathProxy.data); + var ctx = path.getContext(); + if (ctx) { + path.rebuildPath(ctx, 1); + } + } + else { + var ctx = path; + pathProxy.rebuildPath(ctx, 1); + } + }; + innerOpts.applyTransform = function (m) { + transformPath(pathProxy, m); + this.dirtyShape(); + }; + return innerOpts; +} +function createFromString(str, opts) { + return new SVGPath(createPathOptions(str, opts)); +} +function extendFromString(str, defaultOpts) { + var innerOpts = createPathOptions(str, defaultOpts); + var Sub = (function (_super) { + __extends(Sub, _super); + function Sub(opts) { + var _this = _super.call(this, opts) || this; + _this.applyTransform = innerOpts.applyTransform; + _this.buildPath = innerOpts.buildPath; + return _this; + } + return Sub; + }(SVGPath)); + return Sub; +} +function mergePath$1(pathEls, opts) { + var pathList = []; + var len = pathEls.length; + for (var i = 0; i < len; i++) { + var pathEl = pathEls[i]; + pathList.push(pathEl.getUpdatedPathProxy(true)); + } + var pathBundle = new Path(opts); + pathBundle.createPathProxy(); + pathBundle.buildPath = function (path) { + if (isPathProxy(path)) { + path.appendPath(pathList); + var ctx = path.getContext(); + if (ctx) { + path.rebuildPath(ctx, 1); + } + } + }; + return pathBundle; +} + +var CircleShape = (function () { + function CircleShape() { + this.cx = 0; + this.cy = 0; + this.r = 0; + } + return CircleShape; +}()); +var Circle = (function (_super) { + __extends(Circle, _super); + function Circle(opts) { + return _super.call(this, opts) || this; + } + Circle.prototype.getDefaultShape = function () { + return new CircleShape(); + }; + Circle.prototype.buildPath = function (ctx, shape) { + ctx.moveTo(shape.cx + shape.r, shape.cy); + ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2); + }; + return Circle; +}(Path)); +Circle.prototype.type = 'circle'; + +var EllipseShape = (function () { + function EllipseShape() { + this.cx = 0; + this.cy = 0; + this.rx = 0; + this.ry = 0; + } + return EllipseShape; +}()); +var Ellipse = (function (_super) { + __extends(Ellipse, _super); + function Ellipse(opts) { + return _super.call(this, opts) || this; + } + Ellipse.prototype.getDefaultShape = function () { + return new EllipseShape(); + }; + Ellipse.prototype.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(); + }; + return Ellipse; +}(Path)); +Ellipse.prototype.type = 'ellipse'; + +var PI$2 = Math.PI; +var PI2$1 = PI$2 * 2; +var mathSin = Math.sin; +var mathCos = Math.cos; +var mathACos = Math.acos; +var mathATan2 = Math.atan2; +var mathAbs = Math.abs; +var mathSqrt = Math.sqrt; +var mathMax$2 = Math.max; +var mathMin$2 = Math.min; +var e = 1e-4; +function intersect(x0, y0, x1, y1, x2, y2, x3, y3) { + var dx10 = x1 - x0; + var dy10 = y1 - y0; + var dx32 = x3 - x2; + var dy32 = y3 - y2; + var t = dy32 * dx10 - dx32 * dy10; + if (t * t < e) { + return; + } + t = (dx32 * (y0 - y2) - dy32 * (x0 - x2)) / t; + return [x0 + t * dx10, y0 + t * dy10]; +} +function computeCornerTangents(x0, y0, x1, y1, radius, cr, clockwise) { + var x01 = x0 - x1; + var y01 = y0 - y1; + var lo = (clockwise ? cr : -cr) / mathSqrt(x01 * x01 + y01 * y01); + var ox = lo * y01; + var oy = -lo * x01; + var x11 = x0 + ox; + var y11 = y0 + oy; + var x10 = x1 + ox; + var y10 = y1 + oy; + var x00 = (x11 + x10) / 2; + var y00 = (y11 + y10) / 2; + var dx = x10 - x11; + var dy = y10 - y11; + var d2 = dx * dx + dy * dy; + var r = radius - cr; + var s = x11 * y10 - x10 * y11; + var d = (dy < 0 ? -1 : 1) * mathSqrt(mathMax$2(0, r * r * d2 - s * s)); + var cx0 = (s * dy - dx * d) / d2; + var cy0 = (-s * dx - dy * d) / d2; + var cx1 = (s * dy + dx * d) / d2; + var cy1 = (-s * dx + dy * d) / d2; + var dx0 = cx0 - x00; + var dy0 = cy0 - y00; + var dx1 = cx1 - x00; + var dy1 = cy1 - y00; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) { + cx0 = cx1; + cy0 = cy1; + } + return { + cx: cx0, + cy: cy0, + x0: -ox, + y0: -oy, + x1: cx0 * (radius / r - 1), + y1: cy0 * (radius / r - 1) + }; +} +function normalizeCornerRadius(cr) { + var arr; + if (isArray(cr)) { + var len = cr.length; + if (!len) { + return cr; + } + if (len === 1) { + arr = [cr[0], cr[0], 0, 0]; + } + else if (len === 2) { + arr = [cr[0], cr[0], cr[1], cr[1]]; + } + else if (len === 3) { + arr = cr.concat(cr[2]); + } + else { + arr = cr; + } + } + else { + arr = [cr, cr, cr, cr]; + } + return arr; +} +function buildPath$1(ctx, shape) { + var _a; + var radius = mathMax$2(shape.r, 0); + var innerRadius = mathMax$2(shape.r0 || 0, 0); + var hasRadius = radius > 0; + var hasInnerRadius = innerRadius > 0; + if (!hasRadius && !hasInnerRadius) { + return; + } + if (!hasRadius) { + radius = innerRadius; + innerRadius = 0; + } + if (innerRadius > radius) { + var tmp = radius; + radius = innerRadius; + innerRadius = tmp; + } + var startAngle = shape.startAngle, endAngle = shape.endAngle; + if (isNaN(startAngle) || isNaN(endAngle)) { + return; + } + var cx = shape.cx, cy = shape.cy; + var clockwise = !!shape.clockwise; + var arc = mathAbs(endAngle - startAngle); + var mod = arc > PI2$1 && arc % PI2$1; + mod > e && (arc = mod); + if (!(radius > e)) { + ctx.moveTo(cx, cy); + } + else if (arc > PI2$1 - e) { + ctx.moveTo(cx + radius * mathCos(startAngle), cy + radius * mathSin(startAngle)); + ctx.arc(cx, cy, radius, startAngle, endAngle, !clockwise); + if (innerRadius > e) { + ctx.moveTo(cx + innerRadius * mathCos(endAngle), cy + innerRadius * mathSin(endAngle)); + ctx.arc(cx, cy, innerRadius, endAngle, startAngle, clockwise); + } + } + else { + var icrStart = void 0; + var icrEnd = void 0; + var ocrStart = void 0; + var ocrEnd = void 0; + var ocrs = void 0; + var ocre = void 0; + var icrs = void 0; + var icre = void 0; + var ocrMax = void 0; + var icrMax = void 0; + var limitedOcrMax = void 0; + var limitedIcrMax = void 0; + var xre = void 0; + var yre = void 0; + var xirs = void 0; + var yirs = void 0; + var xrs = radius * mathCos(startAngle); + var yrs = radius * mathSin(startAngle); + var xire = innerRadius * mathCos(endAngle); + var yire = innerRadius * mathSin(endAngle); + var hasArc = arc > e; + if (hasArc) { + var cornerRadius = shape.cornerRadius; + if (cornerRadius) { + _a = normalizeCornerRadius(cornerRadius), icrStart = _a[0], icrEnd = _a[1], ocrStart = _a[2], ocrEnd = _a[3]; + } + var halfRd = mathAbs(radius - innerRadius) / 2; + ocrs = mathMin$2(halfRd, ocrStart); + ocre = mathMin$2(halfRd, ocrEnd); + icrs = mathMin$2(halfRd, icrStart); + icre = mathMin$2(halfRd, icrEnd); + limitedOcrMax = ocrMax = mathMax$2(ocrs, ocre); + limitedIcrMax = icrMax = mathMax$2(icrs, icre); + if (ocrMax > e || icrMax > e) { + xre = radius * mathCos(endAngle); + yre = radius * mathSin(endAngle); + xirs = innerRadius * mathCos(startAngle); + yirs = innerRadius * mathSin(startAngle); + if (arc < PI$2) { + var it_1 = intersect(xrs, yrs, xirs, yirs, xre, yre, xire, yire); + if (it_1) { + var x0 = xrs - it_1[0]; + var y0 = yrs - it_1[1]; + var x1 = xre - it_1[0]; + var y1 = yre - it_1[1]; + var a = 1 / mathSin(mathACos((x0 * x1 + y0 * y1) / (mathSqrt(x0 * x0 + y0 * y0) * mathSqrt(x1 * x1 + y1 * y1))) / 2); + var b = mathSqrt(it_1[0] * it_1[0] + it_1[1] * it_1[1]); + limitedOcrMax = mathMin$2(ocrMax, (radius - b) / (a + 1)); + limitedIcrMax = mathMin$2(icrMax, (innerRadius - b) / (a - 1)); + } + } + } + } + if (!hasArc) { + ctx.moveTo(cx + xrs, cy + yrs); + } + else if (limitedOcrMax > e) { + var crStart = mathMin$2(ocrStart, limitedOcrMax); + var crEnd = mathMin$2(ocrEnd, limitedOcrMax); + var ct0 = computeCornerTangents(xirs, yirs, xrs, yrs, radius, crStart, clockwise); + var ct1 = computeCornerTangents(xre, yre, xire, yire, radius, crEnd, clockwise); + ctx.moveTo(cx + ct0.cx + ct0.x0, cy + ct0.cy + ct0.y0); + if (limitedOcrMax < ocrMax && crStart === crEnd) { + ctx.arc(cx + ct0.cx, cy + ct0.cy, limitedOcrMax, mathATan2(ct0.y0, ct0.x0), mathATan2(ct1.y0, ct1.x0), !clockwise); + } + else { + crStart > 0 && ctx.arc(cx + ct0.cx, cy + ct0.cy, crStart, mathATan2(ct0.y0, ct0.x0), mathATan2(ct0.y1, ct0.x1), !clockwise); + ctx.arc(cx, cy, radius, mathATan2(ct0.cy + ct0.y1, ct0.cx + ct0.x1), mathATan2(ct1.cy + ct1.y1, ct1.cx + ct1.x1), !clockwise); + crEnd > 0 && ctx.arc(cx + ct1.cx, cy + ct1.cy, crEnd, mathATan2(ct1.y1, ct1.x1), mathATan2(ct1.y0, ct1.x0), !clockwise); + } + } + else { + ctx.moveTo(cx + xrs, cy + yrs); + ctx.arc(cx, cy, radius, startAngle, endAngle, !clockwise); + } + if (!(innerRadius > e) || !hasArc) { + ctx.lineTo(cx + xire, cy + yire); + } + else if (limitedIcrMax > e) { + var crStart = mathMin$2(icrStart, limitedIcrMax); + var crEnd = mathMin$2(icrEnd, limitedIcrMax); + var ct0 = computeCornerTangents(xire, yire, xre, yre, innerRadius, -crEnd, clockwise); + var ct1 = computeCornerTangents(xrs, yrs, xirs, yirs, innerRadius, -crStart, clockwise); + ctx.lineTo(cx + ct0.cx + ct0.x0, cy + ct0.cy + ct0.y0); + if (limitedIcrMax < icrMax && crStart === crEnd) { + ctx.arc(cx + ct0.cx, cy + ct0.cy, limitedIcrMax, mathATan2(ct0.y0, ct0.x0), mathATan2(ct1.y0, ct1.x0), !clockwise); + } + else { + crEnd > 0 && ctx.arc(cx + ct0.cx, cy + ct0.cy, crEnd, mathATan2(ct0.y0, ct0.x0), mathATan2(ct0.y1, ct0.x1), !clockwise); + ctx.arc(cx, cy, innerRadius, mathATan2(ct0.cy + ct0.y1, ct0.cx + ct0.x1), mathATan2(ct1.cy + ct1.y1, ct1.cx + ct1.x1), clockwise); + crStart > 0 && ctx.arc(cx + ct1.cx, cy + ct1.cy, crStart, mathATan2(ct1.y1, ct1.x1), mathATan2(ct1.y0, ct1.x0), !clockwise); + } + } + else { + ctx.lineTo(cx + xire, cy + yire); + ctx.arc(cx, cy, innerRadius, endAngle, startAngle, clockwise); + } + } + ctx.closePath(); +} + +var SectorShape = (function () { + function SectorShape() { + this.cx = 0; + this.cy = 0; + this.r0 = 0; + this.r = 0; + this.startAngle = 0; + this.endAngle = Math.PI * 2; + this.clockwise = true; + this.cornerRadius = 0; + } + return SectorShape; +}()); +var Sector = (function (_super) { + __extends(Sector, _super); + function Sector(opts) { + return _super.call(this, opts) || this; + } + Sector.prototype.getDefaultShape = function () { + return new SectorShape(); + }; + Sector.prototype.buildPath = function (ctx, shape) { + buildPath$1(ctx, shape); + }; + Sector.prototype.isZeroArea = function () { + return this.shape.startAngle === this.shape.endAngle + || this.shape.r === this.shape.r0; + }; + return Sector; +}(Path)); +Sector.prototype.type = 'sector'; + +var RingShape = (function () { + function RingShape() { + this.cx = 0; + this.cy = 0; + this.r = 0; + this.r0 = 0; + } + return RingShape; +}()); +var Ring = (function (_super) { + __extends(Ring, _super); + function Ring(opts) { + return _super.call(this, opts) || this; + } + Ring.prototype.getDefaultShape = function () { + return new RingShape(); + }; + Ring.prototype.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); + }; + return Ring; +}(Path)); +Ring.prototype.type = 'ring'; + +function smoothBezier(points, smooth, isLoop, constraint) { + var cps = []; + var v = []; + var v1 = []; + var v2 = []; + var prevPoint; + var nextPoint; + var min; + var max; + if (constraint) { + min = [Infinity, Infinity]; + max = [-Infinity, -Infinity]; + for (var i = 0, len = points.length; i < len; i++) { + min$1(min, min, points[i]); + max$1(max, max, points[i]); + } + min$1(min, min, constraint[0]); + max$1(max, max, constraint[1]); + } + for (var i = 0, len = points.length; i < len; i++) { + var point = points[i]; + if (isLoop) { + prevPoint = points[i ? i - 1 : len - 1]; + nextPoint = points[(i + 1) % len]; + } + else { + if (i === 0 || i === len - 1) { + cps.push(clone$1(points[i])); + continue; + } + else { + prevPoint = points[i - 1]; + nextPoint = points[i + 1]; + } + } + sub(v, nextPoint, prevPoint); + scale$2(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$2(v1, v, -d0); + scale$2(v2, v, d1); + var cp0 = add([], point, v1); + var cp1 = add([], point, v2); + if (constraint) { + max$1(cp0, cp0, min); + min$1(cp0, cp0, max); + max$1(cp1, cp1, min); + min$1(cp1, cp1, max); + } + cps.push(cp0); + cps.push(cp1); + } + if (isLoop) { + cps.push(cps.shift()); + } + return cps; +} + +function buildPath(ctx, shape, closePath) { + var smooth = shape.smooth; + var points = shape.points; + if (points && points.length >= 2) { + if (smooth) { + 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 { + 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(); + } +} + +var PolygonShape = (function () { + function PolygonShape() { + this.points = null; + this.smooth = 0; + this.smoothConstraint = null; + } + return PolygonShape; +}()); +var Polygon = (function (_super) { + __extends(Polygon, _super); + function Polygon(opts) { + return _super.call(this, opts) || this; + } + Polygon.prototype.getDefaultShape = function () { + return new PolygonShape(); + }; + Polygon.prototype.buildPath = function (ctx, shape) { + buildPath(ctx, shape, true); + }; + return Polygon; +}(Path)); +Polygon.prototype.type = 'polygon'; + +var PolylineShape = (function () { + function PolylineShape() { + this.points = null; + this.percent = 1; + this.smooth = 0; + this.smoothConstraint = null; + } + return PolylineShape; +}()); +var Polyline = (function (_super) { + __extends(Polyline, _super); + function Polyline(opts) { + return _super.call(this, opts) || this; + } + Polyline.prototype.getDefaultStyle = function () { + return { + stroke: '#000', + fill: null + }; + }; + Polyline.prototype.getDefaultShape = function () { + return new PolylineShape(); + }; + Polyline.prototype.buildPath = function (ctx, shape) { + buildPath(ctx, shape, false); + }; + return Polyline; +}(Path)); +Polyline.prototype.type = 'polyline'; + +var subPixelOptimizeOutputShape = {}; +var LineShape = (function () { + function LineShape() { + this.x1 = 0; + this.y1 = 0; + this.x2 = 0; + this.y2 = 0; + this.percent = 1; + } + return LineShape; +}()); +var Line = (function (_super) { + __extends(Line, _super); + function Line(opts) { + return _super.call(this, opts) || this; + } + Line.prototype.getDefaultStyle = function () { + return { + stroke: '#000', + fill: null + }; + }; + Line.prototype.getDefaultShape = function () { + return new LineShape(); + }; + Line.prototype.buildPath = function (ctx, shape) { + var x1; + var y1; + var x2; + var y2; + if (this.subPixelOptimize) { + var optimizedShape = subPixelOptimizeLine$1(subPixelOptimizeOutputShape, shape, this.style); + x1 = optimizedShape.x1; + y1 = optimizedShape.y1; + x2 = optimizedShape.x2; + y2 = optimizedShape.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); + }; + Line.prototype.pointAt = function (p) { + var shape = this.shape; + return [ + shape.x1 * (1 - p) + shape.x2 * p, + shape.y1 * (1 - p) + shape.y2 * p + ]; + }; + return Line; +}(Path)); +Line.prototype.type = 'line'; + +var out = []; +var BezierCurveShape = (function () { + function BezierCurveShape() { + this.x1 = 0; + this.y1 = 0; + this.x2 = 0; + this.y2 = 0; + this.cpx1 = 0; + this.cpy1 = 0; + this.percent = 1; + } + return BezierCurveShape; +}()); +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 = (function (_super) { + __extends(BezierCurve, _super); + function BezierCurve(opts) { + return _super.call(this, opts) || this; + } + BezierCurve.prototype.getDefaultStyle = function () { + return { + stroke: '#000', + fill: null + }; + }; + BezierCurve.prototype.getDefaultShape = function () { + return new BezierCurveShape(); + }; + BezierCurve.prototype.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); + } + }; + BezierCurve.prototype.pointAt = function (t) { + return someVectorAt(this.shape, t, false); + }; + BezierCurve.prototype.tangentAt = function (t) { + var p = someVectorAt(this.shape, t, true); + return normalize$1(p, p); + }; + return BezierCurve; +}(Path)); +BezierCurve.prototype.type = 'bezier-curve'; + +var ArcShape = (function () { + function ArcShape() { + this.cx = 0; + this.cy = 0; + this.r = 0; + this.startAngle = 0; + this.endAngle = Math.PI * 2; + this.clockwise = true; + } + return ArcShape; +}()); +var Arc = (function (_super) { + __extends(Arc, _super); + function Arc(opts) { + return _super.call(this, opts) || this; + } + Arc.prototype.getDefaultStyle = function () { + return { + stroke: '#000', + fill: null + }; + }; + Arc.prototype.getDefaultShape = function () { + return new ArcShape(); + }; + Arc.prototype.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); + }; + return Arc; +}(Path)); +Arc.prototype.type = 'arc'; + +var CompoundPath = (function (_super) { + __extends(CompoundPath, _super); + function CompoundPath() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = 'compound'; + return _this; + } + CompoundPath.prototype._updatePathDirty = function () { + var paths = this.shape.paths; + var dirtyPath = this.shapeChanged(); + for (var i = 0; i < paths.length; i++) { + dirtyPath = dirtyPath || paths[i].shapeChanged(); + } + if (dirtyPath) { + this.dirtyShape(); + } + }; + CompoundPath.prototype.beforeBrush = function () { + this._updatePathDirty(); + var paths = this.shape.paths || []; + var scale = this.getGlobalScale(); + 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); + } + }; + CompoundPath.prototype.buildPath = function (ctx, shape) { + var paths = shape.paths || []; + for (var i = 0; i < paths.length; i++) { + paths[i].buildPath(ctx, paths[i].shape, true); + } + }; + CompoundPath.prototype.afterBrush = function () { + var paths = this.shape.paths || []; + for (var i = 0; i < paths.length; i++) { + paths[i].pathUpdated(); + } + }; + CompoundPath.prototype.getBoundingRect = function () { + this._updatePathDirty.call(this); + return Path.prototype.getBoundingRect.call(this); + }; + return CompoundPath; +}(Path)); + +var Gradient = (function () { + function Gradient(colorStops) { + this.colorStops = colorStops || []; + } + Gradient.prototype.addColorStop = function (offset, color) { + this.colorStops.push({ + offset: offset, + color: color + }); + }; + return Gradient; +}()); + +var LinearGradient = (function (_super) { + __extends(LinearGradient, _super); + function LinearGradient(x, y, x2, y2, colorStops, globalCoord) { + var _this = _super.call(this, colorStops) || this; + _this.x = x == null ? 0 : x; + _this.y = y == null ? 0 : y; + _this.x2 = x2 == null ? 1 : x2; + _this.y2 = y2 == null ? 0 : y2; + _this.type = 'linear'; + _this.global = globalCoord || false; + return _this; + } + return LinearGradient; +}(Gradient)); + +var RadialGradient = (function (_super) { + __extends(RadialGradient, _super); + function RadialGradient(x, y, r, colorStops, globalCoord) { + var _this = _super.call(this, colorStops) || this; + _this.x = x == null ? 0.5 : x; + _this.y = y == null ? 0.5 : y; + _this.r = r == null ? 0.5 : r; + _this.type = 'radial'; + _this.global = globalCoord || false; + return _this; + } + return RadialGradient; +}(Gradient)); + +var extent = [0, 0]; +var extent2 = [0, 0]; +var minTv = new Point(); +var maxTv = new Point(); +var OrientedBoundingRect = (function () { + function OrientedBoundingRect(rect, transform) { + this._corners = []; + this._axes = []; + this._origin = [0, 0]; + for (var i = 0; i < 4; i++) { + this._corners[i] = new Point(); + } + for (var i = 0; i < 2; i++) { + this._axes[i] = new Point(); + } + if (rect) { + this.fromBoundingRect(rect, transform); + } + } + OrientedBoundingRect.prototype.fromBoundingRect = function (rect, transform) { + var corners = this._corners; + var axes = this._axes; + var x = rect.x; + var y = rect.y; + var x2 = x + rect.width; + var y2 = y + rect.height; + corners[0].set(x, y); + corners[1].set(x2, y); + corners[2].set(x2, y2); + corners[3].set(x, y2); + if (transform) { + for (var i = 0; i < 4; i++) { + corners[i].transform(transform); + } + } + Point.sub(axes[0], corners[1], corners[0]); + Point.sub(axes[1], corners[3], corners[0]); + axes[0].normalize(); + axes[1].normalize(); + for (var i = 0; i < 2; i++) { + this._origin[i] = axes[i].dot(corners[0]); + } + }; + OrientedBoundingRect.prototype.intersect = function (other, mtv) { + var overlapped = true; + var noMtv = !mtv; + minTv.set(Infinity, Infinity); + maxTv.set(0, 0); + if (!this._intersectCheckOneSide(this, other, minTv, maxTv, noMtv, 1)) { + overlapped = false; + if (noMtv) { + return overlapped; + } + } + if (!this._intersectCheckOneSide(other, this, minTv, maxTv, noMtv, -1)) { + overlapped = false; + if (noMtv) { + return overlapped; + } + } + if (!noMtv) { + Point.copy(mtv, overlapped ? minTv : maxTv); + } + return overlapped; + }; + OrientedBoundingRect.prototype._intersectCheckOneSide = function (self, other, minTv, maxTv, noMtv, inverse) { + var overlapped = true; + for (var i = 0; i < 2; i++) { + var axis = this._axes[i]; + this._getProjMinMaxOnAxis(i, self._corners, extent); + this._getProjMinMaxOnAxis(i, other._corners, extent2); + if (extent[1] < extent2[0] || extent[0] > extent2[1]) { + overlapped = false; + if (noMtv) { + return overlapped; + } + var dist0 = Math.abs(extent2[0] - extent[1]); + var dist1 = Math.abs(extent[0] - extent2[1]); + if (Math.min(dist0, dist1) > maxTv.len()) { + if (dist0 < dist1) { + Point.scale(maxTv, axis, -dist0 * inverse); + } + else { + Point.scale(maxTv, axis, dist1 * inverse); + } + } + } + else if (minTv) { + var dist0 = Math.abs(extent2[0] - extent[1]); + var dist1 = Math.abs(extent[0] - extent2[1]); + if (Math.min(dist0, dist1) < minTv.len()) { + if (dist0 < dist1) { + Point.scale(minTv, axis, dist0 * inverse); + } + else { + Point.scale(minTv, axis, -dist1 * inverse); + } + } + } + } + return overlapped; + }; + OrientedBoundingRect.prototype._getProjMinMaxOnAxis = function (dim, corners, out) { + var axis = this._axes[dim]; + var origin = this._origin; + var proj = corners[0].dot(axis) + origin[dim]; + var min = proj; + var max = proj; + for (var i = 1; i < corners.length; i++) { + var proj_1 = corners[i].dot(axis) + origin[dim]; + min = Math.min(proj_1, min); + max = Math.max(proj_1, max); + } + out[0] = min; + out[1] = max; + }; + return OrientedBoundingRect; +}()); + +var m = []; +var IncrementalDisplayable = (function (_super) { + __extends(IncrementalDisplayable, _super); + function IncrementalDisplayable() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.notClear = true; + _this.incremental = true; + _this._displayables = []; + _this._temporaryDisplayables = []; + _this._cursor = 0; + return _this; + } + IncrementalDisplayable.prototype.traverse = function (cb, context) { + cb.call(context, this); + }; + IncrementalDisplayable.prototype.useStyle = function () { + this.style = {}; + }; + IncrementalDisplayable.prototype.getCursor = function () { + return this._cursor; + }; + IncrementalDisplayable.prototype.innerAfterBrush = function () { + this._cursor = this._displayables.length; + }; + IncrementalDisplayable.prototype.clearDisplaybles = function () { + this._displayables = []; + this._temporaryDisplayables = []; + this._cursor = 0; + this.markRedraw(); + this.notClear = false; + }; + IncrementalDisplayable.prototype.clearTemporalDisplayables = function () { + this._temporaryDisplayables = []; + }; + IncrementalDisplayable.prototype.addDisplayable = function (displayable, notPersistent) { + if (notPersistent) { + this._temporaryDisplayables.push(displayable); + } + else { + this._displayables.push(displayable); + } + this.markRedraw(); + }; + IncrementalDisplayable.prototype.addDisplayables = function (displayables, notPersistent) { + notPersistent = notPersistent || false; + for (var i = 0; i < displayables.length; i++) { + this.addDisplayable(displayables[i], notPersistent); + } + }; + IncrementalDisplayable.prototype.getDisplayables = function () { + return this._displayables; + }; + IncrementalDisplayable.prototype.getTemporalDisplayables = function () { + return this._temporaryDisplayables; + }; + IncrementalDisplayable.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]); + } + }; + IncrementalDisplayable.prototype.update = function () { + this.updateTransform(); + for (var i = this._cursor; i < this._displayables.length; i++) { + var displayable = this._displayables[i]; + displayable.parent = this; + displayable.update(); + displayable.parent = null; + } + for (var i = 0; i < this._temporaryDisplayables.length; i++) { + var displayable = this._temporaryDisplayables[i]; + displayable.parent = this; + displayable.update(); + displayable.parent = null; + } + }; + IncrementalDisplayable.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; + }; + IncrementalDisplayable.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; + }; + return IncrementalDisplayable; +}(Displayable)); + +// Stored properties for further transition. +var transitionStore = makeInner(); +/** + * Return null if animation is disabled. + */ +function getAnimationConfig(animationType, animatableModel, dataIndex, +// Extra opts can override the option in animatable model. +extraOpts, +// TODO It's only for pictorial bar now. +extraDelayParams) { + var animationPayload; + // Check if there is global animation configuration from dataZoom/resize can override the config in option. + // If animation is enabled. Will use this animation config in payload. + // If animation is disabled. Just ignore it. + if (animatableModel && animatableModel.ecModel) { + var updatePayload = animatableModel.ecModel.getUpdatePayload(); + animationPayload = updatePayload && updatePayload.animation; + } + var animationEnabled = animatableModel && animatableModel.isAnimationEnabled(); + var isUpdate = animationType === 'update'; + if (animationEnabled) { + var duration = void 0; + var easing = void 0; + var delay = void 0; + if (extraOpts) { + duration = retrieve2(extraOpts.duration, 200); + easing = retrieve2(extraOpts.easing, 'cubicOut'); + delay = 0; + } else { + duration = animatableModel.getShallow(isUpdate ? 'animationDurationUpdate' : 'animationDuration'); + easing = animatableModel.getShallow(isUpdate ? 'animationEasingUpdate' : 'animationEasing'); + delay = animatableModel.getShallow(isUpdate ? 'animationDelayUpdate' : 'animationDelay'); + } + // animation from payload has highest priority. + if (animationPayload) { + animationPayload.duration != null && (duration = animationPayload.duration); + animationPayload.easing != null && (easing = animationPayload.easing); + animationPayload.delay != null && (delay = animationPayload.delay); + } + if (isFunction(delay)) { + delay = delay(dataIndex, extraDelayParams); + } + if (isFunction(duration)) { + duration = duration(dataIndex); + } + var config = { + duration: duration || 0, + delay: delay, + easing: easing + }; + return config; + } else { + return null; + } +} +function animateOrSetProps(animationType, el, props, animatableModel, dataIndex, cb, during) { + var isFrom = false; + var removeOpt; + if (isFunction(dataIndex)) { + during = cb; + cb = dataIndex; + dataIndex = null; + } else if (isObject$2(dataIndex)) { + cb = dataIndex.cb; + during = dataIndex.during; + isFrom = dataIndex.isFrom; + removeOpt = dataIndex.removeOpt; + dataIndex = dataIndex.dataIndex; + } + var isRemove = animationType === 'leave'; + if (!isRemove) { + // Must stop the remove animation. + el.stopAnimation('leave'); + } + var animationConfig = getAnimationConfig(animationType, animatableModel, dataIndex, isRemove ? removeOpt || {} : null, animatableModel && animatableModel.getAnimationDelayParams ? animatableModel.getAnimationDelayParams(el, dataIndex) : null); + if (animationConfig && animationConfig.duration > 0) { + var duration = animationConfig.duration; + var animationDelay = animationConfig.delay; + var animationEasing = animationConfig.easing; + var animateConfig = { + duration: duration, + delay: animationDelay || 0, + easing: animationEasing, + done: cb, + force: !!cb || !!during, + // Set to final state in update/init animation. + // So the post processing based on the path shape can be done correctly. + setToFinal: !isRemove, + scope: animationType, + during: during + }; + isFrom ? el.animateFrom(props, animateConfig) : el.animateTo(props, animateConfig); + } else { + el.stopAnimation(); + // If `isFrom`, the props is the "from" props. + !isFrom && el.attr(props); + // Call during at least once. + during && during(1); + cb && cb(); + } +} +/** + * Update graphic element properties with or without animation according to the + * configuration in series. + * + * 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. + * @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$1(el, props, +// TODO: TYPE AnimatableModel +animatableModel, dataIndex, cb, during) { + animateOrSetProps('update', el, props, animatableModel, dataIndex, cb, during); +} +/** + * Init graphic element properties with or without animation according to the + * configuration in series. + * + * 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. + */ +function initProps(el, props, animatableModel, dataIndex, cb, during) { + animateOrSetProps('enter', el, props, animatableModel, dataIndex, cb, during); +} +/** + * If element is removed. + * It can determine if element is having remove animation. + */ +function isElementRemoved(el) { + if (!el.__zr) { + return true; + } + for (var i = 0; i < el.animators.length; i++) { + var animator = el.animators[i]; + if (animator.scope === 'leave') { + return true; + } + } + return false; +} +/** + * Remove graphic element + */ +function removeElement(el, props, animatableModel, dataIndex, cb, during) { + // Don't do remove animation twice. + if (isElementRemoved(el)) { + return; + } + animateOrSetProps('leave', el, props, animatableModel, dataIndex, cb, during); +} +function fadeOutDisplayable(el, animatableModel, dataIndex, done) { + el.removeTextContent(); + el.removeTextGuideLine(); + removeElement(el, { + style: { + opacity: 0 + } + }, animatableModel, dataIndex, done); +} +function removeElementWithFadeOut(el, animatableModel, dataIndex) { + function doRemove() { + el.parent && el.parent.remove(el); + } + // Hide label and labelLine first + // TODO Also use fade out animation? + if (!el.isGroup) { + fadeOutDisplayable(el, animatableModel, dataIndex, doRemove); + } else { + el.traverse(function (disp) { + if (!disp.isGroup) { + // Can invoke doRemove multiple times. + fadeOutDisplayable(disp, animatableModel, dataIndex, doRemove); + } + }); + } +} +/** + * Save old style for style transition in universalTransition module. + * It's used when element will be reused in each render. + * For chart like map, heatmap, which will always create new element. + * We don't need to save this because universalTransition can get old style from the old element + */ +function saveOldStyle(el) { + transitionStore(el).oldStyle = el.style; +} + +var mathMax$1 = Math.max; +var mathMin$1 = Math.min; +var _customShapeMap = {}; +/** + * Extend shape with parameters + */ +function extendShape(opts) { + return Path.extend(opts); +} +var extendPathFromString = extendFromString; +/** + * Extend path + */ +function extendPath(pathData, opts) { + return extendPathFromString(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 name + * @param 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 registers 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 rarely happens. So we don't make more mechanism + * to resolve this issue here. + * + * @param name + * @return 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 pathData + * @param opts + * @param rect + * @param layout 'center' or 'cover' default to be 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 imageUrl image url + * @param opts options + * @param rect constrain rect + * @param layout 'center' or 'cover'. Default to be 'cover' + */ +function makeImage(imageUrl, rect, layout) { + var zrImg = new ZRImage({ + 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 + }; + zrImg.setStyle(centerGraphic(rect, boundingRect)); + } + } + }); + return zrImg; +} +/** + * Get position of centered element in bounding box. + * + * @param rect element local bounding box + * @param boundingRect constraint bounding box + * @return 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 path + * @param 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 + */ +function subPixelOptimizeLine(shape, lineWidth) { + subPixelOptimizeLine$1(shape, shape, { + lineWidth: lineWidth + }); + return shape; +} +/** + * Sub pixel optimize rect for canvas + */ +function subPixelOptimizeRect(param) { + subPixelOptimizeRect$1(param.shape, param.shape, param.style); + return param; +} +/** + * Sub pixel optimize for canvas + * + * @param position Coordinate, such as x, y + * @param lineWidth Should be nonnegative integer. + * @param positiveOrNegative Default false (negative). + * @return Optimized position. + */ +var subPixelOptimize = subPixelOptimize$1; +/** + * Get transform matrix of target (param target), + * in coordinate of its ancestor (param ancestor) + * + * @param target + * @param [ancestor] + */ +function getTransform(target, ancestor) { + var mat = identity([]); + while (target && target !== ancestor) { + mul(mat, target.getLocalTransform(), mat); + target = target.parent; + } + return mat; +} +/** + * Apply transform to an vertex. + * @param target [x, y] + * @param transform Can be: + * + Transform matrix: like [1, 0, 0, 1, 0, 0] + * + {position, rotation, scale}, the same as `zrender/Transformable`. + * @param invert Whether use invert matrix. + * @return [x, y] + */ +function applyTransform(target, transform, invert$1) { + if (transform && !isArrayLike(transform)) { + transform = Transformable.getLocalTransform(transform); + } + if (invert$1) { + transform = invert([], transform); + } + return applyTransform$1([], target, transform); +} +/** + * @param direction 'left' 'right' 'top' 'bottom' + * @param transform Transform matrix: like [1, 0, 0, 1, 0, 0] + * @param invert Whether use invert matrix. + * @return Transformed direction. 'left' 'right' 'top' 'bottom' + */ +function transformDirection(direction, transform, invert) { + // 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(vertex, transform, invert); + return Math.abs(vertex[0]) > Math.abs(vertex[1]) ? vertex[0] > 0 ? 'right' : 'left' : vertex[1] > 0 ? 'bottom' : 'top'; +} +function isNotGroup(el) { + return !el.isGroup; +} +function isPath(el) { + return el.shape != null; +} +/** + * Apply group transition animation from g1 to g2. + * If no animatableModel, no animation. + */ +function groupTransition(g1, g2, animatableModel) { + if (!g1 || !g2) { + return; + } + function getElMap(g) { + var elMap = {}; + g.traverse(function (el) { + if (isNotGroup(el) && el.anid) { + elMap[el.anid] = el; + } + }); + return elMap; + } + function getAnimatableProps(el) { + var obj = { + x: el.x, + y: el.y, + rotation: el.rotation + }; + if (isPath(el)) { + obj.shape = extend({}, el.shape); + } + return obj; + } + var elMap1 = getElMap(g1); + g2.traverse(function (el) { + if (isNotGroup(el) && el.anid) { + var oldEl = elMap1[el.anid]; + if (oldEl) { + var newProp = getAnimatableProps(el); + el.attr(getAnimatableProps(oldEl)); + updateProps$1(el, newProp, animatableModel, getECData(el).dataIndex); + } + } + }); +} +function clipPointsByRect(points, rect) { + // FIXME: This way might be incorrect when graphic clipped by a corner + // and when element has a border. + return map$1(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]; + }); +} +/** + * Return 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 + }; + } +} +function createIcon(iconStr, +// Support 'image://' or 'path://' or direct svg path. +opt, rect) { + var innerOpts = extend({ + rectHover: true + }, opt); + var style = innerOpts.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 ZRImage(innerOpts)) : makePath(iconStr.replace('path://', ''), innerOpts, 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. + */ +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. + */ +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 + // existing `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; +} +function setTooltipConfig(opt) { + var itemTooltipOption = opt.itemTooltipOption; + var componentModel = opt.componentModel; + var itemName = opt.itemName; + var itemTooltipOptionObj = isString(itemTooltipOption) ? { + formatter: itemTooltipOption + } : itemTooltipOption; + var mainType = componentModel.mainType; + var componentIndex = componentModel.componentIndex; + var formatterParams = { + componentType: mainType, + name: itemName, + $vars: ['name'] + }; + formatterParams[mainType + 'Index'] = componentIndex; + var formatterParamsExtra = opt.formatterParamsExtra; + if (formatterParamsExtra) { + each$4(keys(formatterParamsExtra), function (key) { + if (!hasOwn(formatterParams, key)) { + formatterParams[key] = formatterParamsExtra[key]; + formatterParams.$vars.push(key); + } + }); + } + var ecData = getECData(opt.el); + ecData.componentMainType = mainType; + ecData.componentIndex = componentIndex; + ecData.tooltipConfig = { + name: itemName, + option: defaults({ + content: itemName, + encodeHTMLContent: true, + formatterParams: formatterParams + }, itemTooltipOptionObj) + }; +} +function traverseElement(el, cb) { + var stopped; + // TODO + // Polyfill for fixing zrender group traverse don't visit it's root issue. + if (el.isGroup) { + stopped = cb(el); + } + if (!stopped) { + el.traverse(cb); + } +} +function traverseElements(els, cb) { + if (els) { + if (isArray(els)) { + for (var i = 0; i < els.length; i++) { + traverseElement(els[i], cb); + } + } else { + traverseElement(els, cb); + } + } +} +// Register built-in shapes. These shapes might be overwritten +// by users, although we do not recommend that. +registerShape('circle', Circle); +registerShape('ellipse', Ellipse); +registerShape('sector', Sector); +registerShape('ring', Ring); +registerShape('polygon', Polygon); +registerShape('polyline', Polyline); +registerShape('rect', Rect); +registerShape('line', Line); +registerShape('bezierCurve', BezierCurve); +registerShape('arc', Arc); + +const graphic = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ + __proto__: null, + Arc, + BezierCurve, + BoundingRect, + Circle, + CompoundPath, + Ellipse, + Group: Group$2, + Image: ZRImage, + IncrementalDisplayable, + Line, + LinearGradient, + OrientedBoundingRect, + Path, + Point, + Polygon, + Polyline, + RadialGradient, + Rect, + Ring, + Sector, + Text: ZRText, + applyTransform, + clipPointsByRect, + clipRectByRect, + createIcon, + extendPath, + extendShape, + getShapeClass, + getTransform, + groupTransition, + initProps, + isElementRemoved, + lineLineIntersect, + linePolygonIntersect, + makeImage, + makePath, + mergePath, + registerShape, + removeElement, + removeElementWithFadeOut, + resizePath, + setTooltipConfig, + subPixelOptimize, + subPixelOptimizeLine, + subPixelOptimizeRect, + transformDirection, + traverseElements, + updateProps: updateProps$1 +}, Symbol.toStringTag, { value: 'Module' })); + +var EMPTY_OBJ = {}; +function setLabelText(label, labelTexts) { + for (var i = 0; i < SPECIAL_STATES.length; i++) { + var stateName = SPECIAL_STATES[i]; + var text = labelTexts[stateName]; + var state = label.ensureState(stateName); + state.style = state.style || {}; + state.style.text = text; + } + var oldStates = label.currentStates.slice(); + label.clearStates(true); + label.setStyle({ + text: labelTexts.normal + }); + label.useStates(oldStates, true); +} +function getLabelText(opt, stateModels, interpolatedValue) { + var labelFetcher = opt.labelFetcher; + var labelDataIndex = opt.labelDataIndex; + var labelDimIndex = opt.labelDimIndex; + var normalModel = stateModels.normal; + var baseText; + if (labelFetcher) { + baseText = labelFetcher.getFormattedLabel(labelDataIndex, "normal", null, labelDimIndex, normalModel && normalModel.get("formatter"), interpolatedValue != null ? { + interpolatedValue + } : null); + } + if (baseText == null) { + baseText = isFunction(opt.defaultText) ? opt.defaultText(labelDataIndex, opt, interpolatedValue) : opt.defaultText; + } + var statesText = { + normal: baseText + }; + for (var i = 0; i < SPECIAL_STATES.length; i++) { + var stateName = SPECIAL_STATES[i]; + var stateModel = stateModels[stateName]; + statesText[stateName] = retrieve2(labelFetcher ? labelFetcher.getFormattedLabel(labelDataIndex, stateName, null, labelDimIndex, stateModel && stateModel.get("formatter")) : null, baseText); + } + return statesText; +} +function setLabelStyle(targetEl, labelStatesModels, opt, stateSpecified) { + opt = opt || EMPTY_OBJ; + var isSetOnText = targetEl instanceof ZRText; + var needsCreateText = false; + for (var i = 0; i < DISPLAY_STATES.length; i++) { + var stateModel = labelStatesModels[DISPLAY_STATES[i]]; + if (stateModel && stateModel.getShallow("show")) { + needsCreateText = true; + break; + } + } + var textContent = isSetOnText ? targetEl : targetEl.getTextContent(); + if (needsCreateText) { + if (!isSetOnText) { + if (!textContent) { + textContent = new ZRText(); + targetEl.setTextContent(textContent); + } + if (targetEl.stateProxy) { + textContent.stateProxy = targetEl.stateProxy; + } + } + var labelStatesTexts = getLabelText(opt, labelStatesModels); + var normalModel = labelStatesModels.normal; + var showNormal = !!normalModel.getShallow("show"); + var normalStyle = createTextStyle(normalModel, stateSpecified && stateSpecified.normal, opt, false, !isSetOnText); + normalStyle.text = labelStatesTexts.normal; + if (!isSetOnText) { + targetEl.setTextConfig(createTextConfig(normalModel, opt, false)); + } + for (var i = 0; i < SPECIAL_STATES.length; i++) { + var stateName = SPECIAL_STATES[i]; + var stateModel = labelStatesModels[stateName]; + if (stateModel) { + var stateObj = textContent.ensureState(stateName); + var stateShow = !!retrieve2(stateModel.getShallow("show"), showNormal); + if (stateShow !== showNormal) { + stateObj.ignore = !stateShow; + } + stateObj.style = createTextStyle(stateModel, stateSpecified && stateSpecified[stateName], opt, true, !isSetOnText); + stateObj.style.text = labelStatesTexts[stateName]; + if (!isSetOnText) { + var targetElEmphasisState = targetEl.ensureState(stateName); + targetElEmphasisState.textConfig = createTextConfig(stateModel, opt, true); + } + } + } + textContent.silent = !!normalModel.getShallow("silent"); + if (textContent.style.x != null) { + normalStyle.x = textContent.style.x; + } + if (textContent.style.y != null) { + normalStyle.y = textContent.style.y; + } + textContent.ignore = !showNormal; + textContent.useStyle(normalStyle); + textContent.dirty(); + if (opt.enableTextSetter) { + labelInner(textContent).setLabelText = function(interpolatedValue) { + var labelStatesTexts2 = getLabelText(opt, labelStatesModels, interpolatedValue); + setLabelText(textContent, labelStatesTexts2); + }; + } + } else if (textContent) { + textContent.ignore = true; + } + targetEl.dirty(); +} +function getLabelStatesModels(itemModel, labelName) { + labelName = labelName || "label"; + var statesModels = { + normal: itemModel.getModel(labelName) + }; + for (var i = 0; i < SPECIAL_STATES.length; i++) { + var stateName = SPECIAL_STATES[i]; + statesModels[stateName] = itemModel.getModel([stateName, labelName]); + } + return statesModels; +} +function createTextStyle(textStyleModel, specifiedTextStyle, opt, isNotNormal, isAttached) { + var textStyle = {}; + setTextStyleCommon(textStyle, textStyleModel, opt, isNotNormal, isAttached); + specifiedTextStyle && extend(textStyle, specifiedTextStyle); + return textStyle; +} +function createTextConfig(textStyleModel, opt, isNotNormal) { + opt = opt || {}; + var textConfig = {}; + var labelPosition; + var labelRotate = textStyleModel.getShallow("rotate"); + var labelDistance = retrieve2(textStyleModel.getShallow("distance"), isNotNormal ? null : 5); + var labelOffset = textStyleModel.getShallow("offset"); + labelPosition = textStyleModel.getShallow("position") || (isNotNormal ? null : "inside"); + labelPosition === "outside" && (labelPosition = opt.defaultOutsidePosition || "top"); + if (labelPosition != null) { + textConfig.position = labelPosition; + } + if (labelOffset != null) { + textConfig.offset = labelOffset; + } + if (labelRotate != null) { + labelRotate *= Math.PI / 180; + textConfig.rotation = labelRotate; + } + if (labelDistance != null) { + textConfig.distance = labelDistance; + } + textConfig.outsideFill = textStyleModel.get("color") === "inherit" ? opt.inheritColor || null : "auto"; + return textConfig; +} +function setTextStyleCommon(textStyle, textStyleModel, opt, isNotNormal, isAttached) { + opt = opt || EMPTY_OBJ; + var ecModel = textStyleModel.ecModel; + var globalTextStyle = ecModel && ecModel.option.textStyle; + var richItemNames = getRichItemNames(textStyleModel); + var richResult; + if (richItemNames) { + richResult = {}; + for (var name_1 in richItemNames) { + if (richItemNames.hasOwnProperty(name_1)) { + var richTextStyle = textStyleModel.getModel(["rich", name_1]); + setTokenTextStyle(richResult[name_1] = {}, richTextStyle, globalTextStyle, opt, isNotNormal, isAttached, false, true); + } + } + } + if (richResult) { + textStyle.rich = richResult; + } + var overflow = textStyleModel.get("overflow"); + if (overflow) { + textStyle.overflow = overflow; + } + var margin = textStyleModel.get("minMargin"); + if (margin != null) { + textStyle.margin = margin; + } + setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isNotNormal, isAttached, true, false); +} +function getRichItemNames(textStyleModel) { + var richItemNameMap; + while (textStyleModel && textStyleModel !== textStyleModel.ecModel) { + var rich = (textStyleModel.option || EMPTY_OBJ).rich; + if (rich) { + richItemNameMap = richItemNameMap || {}; + var richKeys = keys(rich); + for (var i = 0; i < richKeys.length; i++) { + var richKey = richKeys[i]; + richItemNameMap[richKey] = 1; + } + } + textStyleModel = textStyleModel.parentModel; + } + return richItemNameMap; +} +var TEXT_PROPS_WITH_GLOBAL = ["fontStyle", "fontWeight", "fontSize", "fontFamily", "textShadowColor", "textShadowBlur", "textShadowOffsetX", "textShadowOffsetY"]; +var TEXT_PROPS_SELF = ["align", "lineHeight", "width", "height", "tag", "verticalAlign", "ellipsis"]; +var TEXT_PROPS_BOX = ["padding", "borderWidth", "borderRadius", "borderDashOffset", "backgroundColor", "borderColor", "shadowColor", "shadowBlur", "shadowOffsetX", "shadowOffsetY"]; +function setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isNotNormal, isAttached, isBlock, inRich) { + globalTextStyle = !isNotNormal && globalTextStyle || EMPTY_OBJ; + var inheritColor = opt && opt.inheritColor; + var fillColor = textStyleModel.getShallow("color"); + var strokeColor = textStyleModel.getShallow("textBorderColor"); + var opacity = retrieve2(textStyleModel.getShallow("opacity"), globalTextStyle.opacity); + if (fillColor === "inherit" || fillColor === "auto") { + if (inheritColor) { + fillColor = inheritColor; + } else { + fillColor = null; + } + } + if (strokeColor === "inherit" || strokeColor === "auto") { + if (inheritColor) { + strokeColor = inheritColor; + } else { + strokeColor = null; + } + } + if (!isAttached) { + fillColor = fillColor || globalTextStyle.color; + strokeColor = strokeColor || globalTextStyle.textBorderColor; + } + if (fillColor != null) { + textStyle.fill = fillColor; + } + if (strokeColor != null) { + textStyle.stroke = strokeColor; + } + var textBorderWidth = retrieve2(textStyleModel.getShallow("textBorderWidth"), globalTextStyle.textBorderWidth); + if (textBorderWidth != null) { + textStyle.lineWidth = textBorderWidth; + } + var textBorderType = retrieve2(textStyleModel.getShallow("textBorderType"), globalTextStyle.textBorderType); + if (textBorderType != null) { + textStyle.lineDash = textBorderType; + } + var textBorderDashOffset = retrieve2(textStyleModel.getShallow("textBorderDashOffset"), globalTextStyle.textBorderDashOffset); + if (textBorderDashOffset != null) { + textStyle.lineDashOffset = textBorderDashOffset; + } + if (!isNotNormal && opacity == null && !inRich) { + opacity = opt && opt.defaultOpacity; + } + if (opacity != null) { + textStyle.opacity = opacity; + } + if (!isNotNormal && !isAttached) { + if (textStyle.fill == null && opt.inheritColor) { + textStyle.fill = opt.inheritColor; + } + } + for (var i = 0; i < TEXT_PROPS_WITH_GLOBAL.length; i++) { + var key = TEXT_PROPS_WITH_GLOBAL[i]; + var val = retrieve2(textStyleModel.getShallow(key), globalTextStyle[key]); + if (val != null) { + textStyle[key] = val; + } + } + for (var i = 0; i < TEXT_PROPS_SELF.length; i++) { + var key = TEXT_PROPS_SELF[i]; + var val = textStyleModel.getShallow(key); + if (val != null) { + textStyle[key] = val; + } + } + if (textStyle.verticalAlign == null) { + var baseline = textStyleModel.getShallow("baseline"); + if (baseline != null) { + textStyle.verticalAlign = baseline; + } + } + if (!isBlock || !opt.disableBox) { + for (var i = 0; i < TEXT_PROPS_BOX.length; i++) { + var key = TEXT_PROPS_BOX[i]; + var val = textStyleModel.getShallow(key); + if (val != null) { + textStyle[key] = val; + } + } + var borderType = textStyleModel.getShallow("borderType"); + if (borderType != null) { + textStyle.borderDash = borderType; + } + if ((textStyle.backgroundColor === "auto" || textStyle.backgroundColor === "inherit") && inheritColor) { + textStyle.backgroundColor = inheritColor; + } + if ((textStyle.borderColor === "auto" || textStyle.borderColor === "inherit") && inheritColor) { + textStyle.borderColor = inheritColor; + } + } +} +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(" ")); +} +var labelInner = makeInner(); + +var PATH_COLOR = ['textStyle', 'color']; +var textStyleParams = ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily', 'padding', 'lineHeight', 'rich', 'width', 'height', 'overflow']; +// TODO Performance improvement? +var tmpText = new ZRText(); +var TextStyleMixin = /** @class */function () { + function TextStyleMixin() {} + /** + * Get color property or get color from option.textStyle.color + */ + // TODO Callback + TextStyleMixin.prototype.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} + */ + TextStyleMixin.prototype.getFont = function () { + return getFont({ + fontStyle: this.getShallow('fontStyle'), + fontWeight: this.getShallow('fontWeight'), + fontSize: this.getShallow('fontSize'), + fontFamily: this.getShallow('fontFamily') + }, this.ecModel); + }; + TextStyleMixin.prototype.getTextRect = function (text) { + var style = { + text: text, + verticalAlign: this.getShallow('verticalAlign') || this.getShallow('baseline') + }; + for (var i = 0; i < textStyleParams.length; i++) { + style[textStyleParams[i]] = this.getShallow(textStyleParams[i]); + } + tmpText.useStyle(style); + tmpText.update(); + return tmpText.getBoundingRect(); + }; + return TextStyleMixin; +}(); + +var LINE_STYLE_KEY_MAP = [['lineWidth', 'width'], ['stroke', 'color'], ['opacity'], ['shadowBlur'], ['shadowOffsetX'], ['shadowOffsetY'], ['shadowColor'], ['lineDash', 'type'], ['lineDashOffset', 'dashOffset'], ['lineCap', 'cap'], ['lineJoin', 'join'], ['miterLimit'] +// Option decal is in `DecalObject` but style.decal is in `PatternObject`. +// So do not transfer decal directly. +]; +var getLineStyle = makeStyleMapper(LINE_STYLE_KEY_MAP); +var LineStyleMixin = /** @class */function () { + function LineStyleMixin() {} + LineStyleMixin.prototype.getLineStyle = function (excludes) { + return getLineStyle(this, excludes); + }; + return LineStyleMixin; +}(); + +var ITEM_STYLE_KEY_MAP = [['fill', 'color'], ['stroke', 'borderColor'], ['lineWidth', 'borderWidth'], ['opacity'], ['shadowBlur'], ['shadowOffsetX'], ['shadowOffsetY'], ['shadowColor'], ['lineDash', 'borderType'], ['lineDashOffset', 'borderDashOffset'], ['lineCap', 'borderCap'], ['lineJoin', 'borderJoin'], ['miterLimit', 'borderMiterLimit'] +// Option decal is in `DecalObject` but style.decal is in `PatternObject`. +// So do not transfer decal directly. +]; +var getItemStyle = makeStyleMapper(ITEM_STYLE_KEY_MAP); +var ItemStyleMixin = /** @class */function () { + function ItemStyleMixin() {} + ItemStyleMixin.prototype.getItemStyle = function (excludes, includes) { + return getItemStyle(this, excludes, includes); + }; + return ItemStyleMixin; +}(); + +var Model = /** @class */function () { + function Model(option, parentModel, ecModel) { + this.parentModel = parentModel; + this.ecModel = ecModel; + 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.init = function (option, parentModel, ecModel) { + }; + /** + * Merge the input option to me. + */ + Model.prototype.mergeOption = function (option, ecModel) { + merge(this.option, option, true); + }; + // `path` can be 'a.b.c', so the return value type have to be `ModelOption` + // TODO: TYPE strict key check? + // get(path: string | string[], ignoreParent?: boolean): ModelOption; + Model.prototype.get = function (path, ignoreParent) { + if (path == null) { + return this.option; + } + return this._doGet(this.parsePath(path), !ignoreParent && this.parentModel); + }; + Model.prototype.getShallow = function (key, ignoreParent) { + var option = this.option; + var val = option == null ? option : option[key]; + if (val == null && !ignoreParent) { + var parentModel = this.parentModel; + if (parentModel) { + // FIXME:TS do not know how to make it works + val = parentModel.getShallow(key); + } + } + return val; + }; + // `path` can be 'a.b.c', so the return value type have to be `Model` + // getModel(path: string | string[], parentModel?: Model): Model; + // TODO 'a.b.c' is deprecated + Model.prototype.getModel = function (path, parentModel) { + var hasPath = path != null; + var pathFinal = hasPath ? this.parsePath(path) : null; + var obj = hasPath ? this._doGet(pathFinal) : this.option; + parentModel = parentModel || this.parentModel && this.parentModel.getModel(this.resolveParentPath(pathFinal)); + return new Model(obj, parentModel, this.ecModel); + }; + /** + * If model has option + */ + Model.prototype.isEmpty = function () { + return this.option == null; + }; + Model.prototype.restoreData = function () {}; + // Pending + Model.prototype.clone = function () { + var Ctor = this.constructor; + return new Ctor(clone$2(this.option)); + }; + // setReadOnly(properties): void { + // clazzUtil.setReadOnly(this, properties); + // } + // If path is null/undefined, return null/undefined. + Model.prototype.parsePath = function (path) { + if (typeof path === 'string') { + return path.split('.'); + } + return path; + }; + // Resolve path for parent. Perhaps useful when parent use a different property. + // Default to be a identity resolver. + // Can be modified to a different resolver. + Model.prototype.resolveParentPath = function (path) { + return path; + }; + // FIXME:TS check whether put this method here + Model.prototype.isAnimationEnabled = function () { + if (!env.node && this.option) { + if (this.option.animation != null) { + return !!this.option.animation; + } else if (this.parentModel) { + return this.parentModel.isAnimationEnabled(); + } + } + }; + Model.prototype._doGet = function (pathArr, parentModel) { + var obj = this.option; + if (!pathArr) { + return obj; + } + 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._doGet(this.resolveParentPath(pathArr), parentModel.parentModel); + } + return obj; + }; + return Model; +}(); +// Enable Model.extend. +enableClassExtend(Model); +enableClassCheck(Model); +mixin(Model, LineStyleMixin); +mixin(Model, ItemStyleMixin); +mixin(Model, AreaStyleMixin); +mixin(Model, TextStyleMixin); + +var base = Math.round(Math.random() * 10); +function getUID(type) { + return [type || "", base++].join("_"); +} +function enableSubTypeDefaulter(target) { + var subTypeDefaulters = {}; + target.registerSubTypeDefaulter = function(componentType, defaulter) { + var componentTypeInfo = parseClassType(componentType); + subTypeDefaulters[componentTypeInfo.main] = defaulter; + }; + target.determineSubType = function(componentType, option) { + var type = option.type; + if (!type) { + var componentTypeMain = parseClassType(componentType).main; + if (target.hasSubTypes(componentType) && subTypeDefaulters[componentTypeMain]) { + type = subTypeDefaulters[componentTypeMain](option); + } + } + return type; + }; +} +function enableTopologicalTravel(entity, dependencyGetter) { + entity.topologicalTravel = function(targetNameList, fullNameList, callback, context) { + if (!targetNameList.length) { + return; + } + var result = makeDepndencyGraph(fullNameList); + var graph = result.graph; + var noEntryList = result.noEntryList; + var targetNameSet = {}; + each$4(targetNameList, function(name) { + targetNameSet[name] = true; + }); + while (noEntryList.length) { + var currComponentType = noEntryList.pop(); + var currVertex = graph[currComponentType]; + var isInTargetNameSet = !!targetNameSet[currComponentType]; + if (isInTargetNameSet) { + callback.call(context, currComponentType, currVertex.originalDeps.slice()); + delete targetNameSet[currComponentType]; + } + each$4(currVertex.successor, isInTargetNameSet ? removeEdgeAndAdd : removeEdge); + } + each$4(targetNameSet, function() { + var errMsg = ""; + throw new Error(errMsg); + }); + function removeEdge(succComponentType) { + graph[succComponentType].entryCount--; + if (graph[succComponentType].entryCount === 0) { + noEntryList.push(succComponentType); + } + } + function removeEdgeAndAdd(succComponentType) { + targetNameSet[succComponentType] = true; + removeEdge(succComponentType); + } + }; + function makeDepndencyGraph(fullNameList) { + var graph = {}; + var noEntryList = []; + each$4(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$4(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, + noEntryList + }; + } + function createDependencyGraphItem(graph, name) { + if (!graph[name]) { + graph[name] = { + predecessor: [], + successor: [] + }; + } + return graph[name]; + } + function getAvailableDependencies(originalDeps, fullNameList) { + var availableDeps = []; + each$4(originalDeps, function(dep) { + indexOf(fullNameList, dep) >= 0 && availableDeps.push(dep); + }); + return availableDeps; + } +} +function inheritDefaultOption(superOption, subOption) { + return merge(merge({}, superOption, true), subOption, 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. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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: English. + */ +const langEN = { + time: { + month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + monthAbbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + dayOfWeek: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + dayOfWeekAbbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] + }, + legend: { + selector: { + all: 'All', + inverse: 'Inv' + } + }, + toolbox: { + brush: { + title: { + rect: 'Box Select', + polygon: 'Lasso Select', + lineX: 'Horizontally Select', + lineY: 'Vertically Select', + keep: 'Keep Selections', + clear: 'Clear Selections' + } + }, + dataView: { + title: 'Data View', + lang: ['Data View', 'Close', 'Refresh'] + }, + dataZoom: { + title: { + zoom: 'Zoom', + back: 'Zoom Reset' + } + }, + magicType: { + title: { + line: 'Switch to Line Chart', + bar: 'Switch to Bar Chart', + stack: 'Stack', + tiled: 'Tile' + } + }, + restore: { + title: 'Restore' + }, + saveAsImage: { + title: 'Save as Image', + lang: ['Right Click to Save Image'] + } + }, + series: { + typeNames: { + pie: 'Pie chart', + bar: 'Bar chart', + line: 'Line chart', + scatter: 'Scatter plot', + effectScatter: 'Ripple scatter plot', + radar: 'Radar chart', + tree: 'Tree', + treemap: 'Treemap', + boxplot: 'Boxplot', + candlestick: 'Candlestick', + k: 'K line chart', + heatmap: 'Heat map', + map: 'Map', + parallel: 'Parallel coordinate map', + lines: 'Line graph', + graph: 'Relationship graph', + sankey: 'Sankey diagram', + funnel: 'Funnel chart', + gauge: 'Gauge', + pictorialBar: 'Pictorial bar', + themeRiver: 'Theme River Map', + sunburst: 'Sunburst', + custom: 'Custom chart', + chart: 'Chart' + } + }, + aria: { + general: { + withTitle: 'This is a chart about "{title}"', + withoutTitle: 'This is a chart' + }, + series: { + single: { + prefix: '', + withName: ' with type {seriesType} named {seriesName}.', + withoutName: ' with type {seriesType}.' + }, + multiple: { + prefix: '. It consists of {seriesCount} series count.', + withName: ' The {seriesId} series is a {seriesType} representing {seriesName}.', + withoutName: ' The {seriesId} series is a {seriesType}.', + separator: { + middle: '', + end: '' + } + } + }, + data: { + allData: 'The data is as follows: ', + partialData: 'The first {displayCnt} items are: ', + withName: 'the data for {name} is {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. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +const langZH = { + time: { + month: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + monthAbbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + dayOfWeek: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + dayOfWeekAbbr: ['日', '一', '二', '三', '四', '五', '六'] + }, + 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: '旭日图', + custom: '自定义图表', + chart: '图表' + } + }, + 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: '' + } + } + } +}; + +var LOCALE_ZH = 'ZH'; +var LOCALE_EN = 'EN'; +var DEFAULT_LOCALE = LOCALE_EN; +var localeStorage = {}; +var localeModels = {}; +var SYSTEM_LANG = !env.domSupported ? DEFAULT_LOCALE : function () { + var langStr = (/* eslint-disable-next-line */ + document.documentElement.lang || navigator.language || navigator.browserLanguage || DEFAULT_LOCALE).toUpperCase(); + return langStr.indexOf(LOCALE_ZH) > -1 ? LOCALE_ZH : DEFAULT_LOCALE; +}(); +function registerLocale(locale, localeObj) { + locale = locale.toUpperCase(); + localeModels[locale] = new Model(localeObj); + localeStorage[locale] = localeObj; +} +// export function getLocale(locale: string) { +// return localeStorage[locale]; +// } +function createLocaleObject(locale) { + if (isString(locale)) { + var localeObj = localeStorage[locale.toUpperCase()] || {}; + if (locale === LOCALE_ZH || locale === LOCALE_EN) { + return clone$2(localeObj); + } else { + return merge(clone$2(localeObj), clone$2(localeStorage[DEFAULT_LOCALE]), false); + } + } else { + return merge(clone$2(locale), clone$2(localeStorage[DEFAULT_LOCALE]), false); + } +} +function getLocaleModel(lang) { + return localeModels[lang]; +} +function getDefaultLocaleModel() { + return localeModels[DEFAULT_LOCALE]; +} +// Default locale +registerLocale(LOCALE_EN, langEN); +registerLocale(LOCALE_ZH, langZH); + +var ONE_SECOND = 1000; +var ONE_MINUTE = ONE_SECOND * 60; +var ONE_HOUR = ONE_MINUTE * 60; +var ONE_DAY = ONE_HOUR * 24; +var ONE_YEAR = ONE_DAY * 365; +var defaultLeveledFormatter = { + year: '{yyyy}', + month: '{MMM}', + day: '{d}', + hour: '{HH}:{mm}', + minute: '{HH}:{mm}', + second: '{HH}:{mm}:{ss}', + millisecond: '{HH}:{mm}:{ss} {SSS}', + none: '{yyyy}-{MM}-{dd} {HH}:{mm}:{ss} {SSS}' +}; +var fullDayFormatter = '{yyyy}-{MM}-{dd}'; +var fullLeveledFormatter = { + year: '{yyyy}', + month: '{yyyy}-{MM}', + day: fullDayFormatter, + hour: fullDayFormatter + ' ' + defaultLeveledFormatter.hour, + minute: fullDayFormatter + ' ' + defaultLeveledFormatter.minute, + second: fullDayFormatter + ' ' + defaultLeveledFormatter.second, + millisecond: defaultLeveledFormatter.none +}; +var primaryTimeUnits = ['year', 'month', 'day', 'hour', 'minute', 'second', 'millisecond']; +var timeUnits = ['year', 'half-year', 'quarter', 'month', 'week', 'half-week', 'day', 'half-day', 'quarter-day', 'hour', 'minute', 'second', 'millisecond']; +function pad(str, len) { + str += ''; + return '0000'.substr(0, len - str.length) + str; +} +function getPrimaryTimeUnit(timeUnit) { + switch (timeUnit) { + case 'half-year': + case 'quarter': + return 'month'; + case 'week': + case 'half-week': + return 'day'; + case 'half-day': + case 'quarter-day': + return 'hour'; + default: + // year, minutes, second, milliseconds + return timeUnit; + } +} +function isPrimaryTimeUnit(timeUnit) { + return timeUnit === getPrimaryTimeUnit(timeUnit); +} +function getDefaultFormatPrecisionOfInterval(timeUnit) { + switch (timeUnit) { + case 'year': + case 'month': + return 'day'; + case 'millisecond': + return 'millisecond'; + default: + // Also for day, hour, minute, second + return 'second'; + } +} +function format( +// Note: The result based on `isUTC` are totally different, which can not be just simply +// substituted by the result without `isUTC`. So we make the param `isUTC` mandatory. +time, template, isUTC, lang) { + var date = parseDate(time); + var y = date[fullYearGetterName(isUTC)](); + var M = date[monthGetterName(isUTC)]() + 1; + var q = Math.floor((M - 1) / 3) + 1; + var d = date[dateGetterName(isUTC)](); + var e = date['get' + (isUTC ? 'UTC' : '') + 'Day'](); + var H = date[hoursGetterName(isUTC)](); + var h = (H - 1) % 12 + 1; + var m = date[minutesGetterName(isUTC)](); + var s = date[secondsGetterName(isUTC)](); + var S = date[millisecondsGetterName(isUTC)](); + var a = H >= 12 ? 'pm' : 'am'; + var A = a.toUpperCase(); + var localeModel = lang instanceof Model ? lang : getLocaleModel(lang || SYSTEM_LANG) || getDefaultLocaleModel(); + var timeModel = localeModel.getModel('time'); + var month = timeModel.get('month'); + var monthAbbr = timeModel.get('monthAbbr'); + var dayOfWeek = timeModel.get('dayOfWeek'); + var dayOfWeekAbbr = timeModel.get('dayOfWeekAbbr'); + return (template || '').replace(/{a}/g, a + '').replace(/{A}/g, A + '').replace(/{yyyy}/g, y + '').replace(/{yy}/g, pad(y % 100 + '', 2)).replace(/{Q}/g, q + '').replace(/{MMMM}/g, month[M - 1]).replace(/{MMM}/g, monthAbbr[M - 1]).replace(/{MM}/g, pad(M, 2)).replace(/{M}/g, M + '').replace(/{dd}/g, pad(d, 2)).replace(/{d}/g, d + '').replace(/{eeee}/g, dayOfWeek[e]).replace(/{ee}/g, dayOfWeekAbbr[e]).replace(/{e}/g, e + '').replace(/{HH}/g, pad(H, 2)).replace(/{H}/g, H + '').replace(/{hh}/g, pad(h + '', 2)).replace(/{h}/g, h + '').replace(/{mm}/g, pad(m, 2)).replace(/{m}/g, m + '').replace(/{ss}/g, pad(s, 2)).replace(/{s}/g, s + '').replace(/{SSS}/g, pad(S, 3)).replace(/{S}/g, S + ''); +} +function leveledFormat(tick, idx, formatter, lang, isUTC) { + var template = null; + if (isString(formatter)) { + // Single formatter for all units at all levels + template = formatter; + } else if (isFunction(formatter)) { + // Callback formatter + template = formatter(tick.value, idx, { + level: tick.level + }); + } else { + var defaults$1 = extend({}, defaultLeveledFormatter); + if (tick.level > 0) { + for (var i = 0; i < primaryTimeUnits.length; ++i) { + defaults$1[primaryTimeUnits[i]] = "{primary|" + defaults$1[primaryTimeUnits[i]] + "}"; + } + } + var mergedFormatter = formatter ? formatter.inherit === false ? formatter // Use formatter with bigger units + : defaults(formatter, defaults$1) : defaults$1; + var unit = getUnitFromValue(tick.value, isUTC); + if (mergedFormatter[unit]) { + template = mergedFormatter[unit]; + } else if (mergedFormatter.inherit) { + // Unit formatter is not defined and should inherit from bigger units + var targetId = timeUnits.indexOf(unit); + for (var i = targetId - 1; i >= 0; --i) { + if (mergedFormatter[unit]) { + template = mergedFormatter[unit]; + break; + } + } + template = template || defaults$1.none; + } + if (isArray(template)) { + var levelId = tick.level == null ? 0 : tick.level >= 0 ? tick.level : template.length + tick.level; + levelId = Math.min(levelId, template.length - 1); + template = template[levelId]; + } + } + return format(new Date(tick.value), template, isUTC, lang); +} +function getUnitFromValue(value, isUTC) { + var date = parseDate(value); + var M = date[monthGetterName(isUTC)]() + 1; + var d = date[dateGetterName(isUTC)](); + var h = date[hoursGetterName(isUTC)](); + var m = date[minutesGetterName(isUTC)](); + var s = date[secondsGetterName(isUTC)](); + var S = date[millisecondsGetterName(isUTC)](); + var isSecond = S === 0; + var isMinute = isSecond && s === 0; + var isHour = isMinute && m === 0; + var isDay = isHour && h === 0; + var isMonth = isDay && d === 1; + var isYear = isMonth && M === 1; + if (isYear) { + return 'year'; + } else if (isMonth) { + return 'month'; + } else if (isDay) { + return 'day'; + } else if (isHour) { + return 'hour'; + } else if (isMinute) { + return 'minute'; + } else if (isSecond) { + return 'second'; + } else { + return 'millisecond'; + } +} +function getUnitValue(value, unit, isUTC) { + var date = isNumber(value) ? parseDate(value) : value; + unit = unit || getUnitFromValue(value, isUTC); + switch (unit) { + case 'year': + return date[fullYearGetterName(isUTC)](); + case 'half-year': + return date[monthGetterName(isUTC)]() >= 6 ? 1 : 0; + case 'quarter': + return Math.floor((date[monthGetterName(isUTC)]() + 1) / 4); + case 'month': + return date[monthGetterName(isUTC)](); + case 'day': + return date[dateGetterName(isUTC)](); + case 'half-day': + return date[hoursGetterName(isUTC)]() / 24; + case 'hour': + return date[hoursGetterName(isUTC)](); + case 'minute': + return date[minutesGetterName(isUTC)](); + case 'second': + return date[secondsGetterName(isUTC)](); + case 'millisecond': + return date[millisecondsGetterName(isUTC)](); + } +} +function fullYearGetterName(isUTC) { + return isUTC ? 'getUTCFullYear' : 'getFullYear'; +} +function monthGetterName(isUTC) { + return isUTC ? 'getUTCMonth' : 'getMonth'; +} +function dateGetterName(isUTC) { + return isUTC ? 'getUTCDate' : 'getDate'; +} +function hoursGetterName(isUTC) { + return isUTC ? 'getUTCHours' : 'getHours'; +} +function minutesGetterName(isUTC) { + return isUTC ? 'getUTCMinutes' : 'getMinutes'; +} +function secondsGetterName(isUTC) { + return isUTC ? 'getUTCSeconds' : 'getSeconds'; +} +function millisecondsGetterName(isUTC) { + return isUTC ? 'getUTCMilliseconds' : 'getMilliseconds'; +} +function fullYearSetterName(isUTC) { + return isUTC ? 'setUTCFullYear' : 'setFullYear'; +} +function monthSetterName(isUTC) { + return isUTC ? 'setUTCMonth' : 'setMonth'; +} +function dateSetterName(isUTC) { + return isUTC ? 'setUTCDate' : 'setDate'; +} +function hoursSetterName(isUTC) { + return isUTC ? 'setUTCHours' : 'setHours'; +} +function minutesSetterName(isUTC) { + return isUTC ? 'setUTCMinutes' : 'setMinutes'; +} +function secondsSetterName(isUTC) { + return isUTC ? 'setUTCSeconds' : 'setSeconds'; +} +function millisecondsSetterName(isUTC) { + return isUTC ? 'setUTCMilliseconds' : 'setMilliseconds'; +} + +function addCommas(x) { + if (!isNumeric(x)) { + return isString(x) ? x : "-"; + } + var parts = (x + "").split("."); + return parts[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, "$1,") + (parts.length > 1 ? "." + parts[1] : ""); +} +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 = normalizeCssArray$1; +function makeValueReadable(value, valueType, useUTC) { + var USER_READABLE_DEFUALT_TIME_PATTERN = "{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}"; + function stringToUserReadable(str) { + return str && trim(str) ? str : "-"; + } + function isNumberUserReadable(num) { + return !!(num != null && !isNaN(num) && isFinite(num)); + } + var isTypeTime = valueType === "time"; + var isValueDate = value instanceof Date; + if (isTypeTime || isValueDate) { + var date = isTypeTime ? parseDate(value) : value; + if (!isNaN(+date)) { + return format(date, USER_READABLE_DEFUALT_TIME_PATTERN, useUTC); + } else if (isValueDate) { + return "-"; + } + } + if (valueType === "ordinal") { + return isStringSafe(value) ? stringToUserReadable(value) : isNumber(value) ? isNumberUserReadable(value) ? value + "" : "-" : "-"; + } + var numericResult = numericToNumber(value); + return isNumberUserReadable(numericResult) ? addCommas(numericResult) : isStringSafe(value) ? stringToUserReadable(value) : typeof value === "boolean" ? value + "" : "-"; +} +var TPL_VAR_ALIAS = ["a", "b", "c", "d", "e", "f", "g"]; +var wrapVar = function(varName, seriesIdx) { + return "{" + varName + (seriesIdx == null ? "" : seriesIdx) + "}"; +}; +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; +} +function getTooltipMarker(inOpt, extraCssText) { + var opt = isString(inOpt) ? { + color: inOpt, + extraCssText + } : inOpt || {}; + var color = opt.color; + var type = opt.type; + extraCssText = opt.extraCssText; + var renderMode = opt.renderMode || "html"; + if (!color) { + return ""; + } + if (renderMode === "html") { + return type === "subItem" ? '' : ''; + } else { + var markerId = opt.markerId || "markerX"; + return { + renderMode, + content: "{" + markerId + "|} ", + style: type === "subItem" ? { + width: 4, + height: 4, + borderRadius: 2, + backgroundColor: color + } : { + width: 10, + height: 10, + borderRadius: 5, + backgroundColor: color + } + }; + } +} +function convertToColorString(color, defaultColor) { + defaultColor = defaultColor || "transparent"; + return isString(color) ? color : isObject$2(color) ? color.colorStops && (color.colorStops[0] || {}).color || defaultColor : defaultColor; +} +function windowOpen(link, target) { + if (target === "_blank" || target === "blank") { + var blank = window.open(); + blank.opener = null; + blank.location.href = link; + } else { + window.open(link, target); + } +} + +var each$3 = each$4; +/** + * @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 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; + } + child.x = x; + child.y = y; + child.markRedraw(); + orient === 'horizontal' ? x = nextX + gap : y = nextY + gap; + }); +} +/** + * VBox or HBox layouting + * @param {string} orient + * @param {module:zrender/graphic/Group} group + * @param {number} gap + * @param {number} [width=Infinity] + * @param {number} [height=Infinity] + */ +var box = boxLayout; +/** + * VBox layouting + * @param {module:zrender/graphic/Group} group + * @param {number} gap + * @param {number} [width=Infinity] + * @param {number} [height=Infinity] + */ +curry$1(boxLayout, 'vertical'); +/** + * HBox layouting + * @param {module:zrender/graphic/Group} group + * @param {number} gap + * @param {number} [width=Infinity] + * @param {number} [height=Infinity] + */ +curry$1(boxLayout, 'horizontal'); +/** + * Parse position info. + */ +function getLayoutRect(positionInfo, containerRect, margin) { + margin = normalizeCssArray(margin || 0); + var containerWidth = containerRect.width; + var containerHeight = containerRect.height; + var left = parsePercent(positionInfo.left, containerWidth); + var top = parsePercent(positionInfo.top, containerHeight); + var right = parsePercent(positionInfo.right, containerWidth); + var bottom = parsePercent(positionInfo.bottom, containerHeight); + var width = parsePercent(positionInfo.width, containerWidth); + var height = parsePercent(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; +} +function fetchLayoutMode(ins) { + var layoutMode = ins.layoutMode || ins.constructor.layoutMode; + return isObject$2(layoutMode) ? layoutMode : layoutMode ? { + type: layoutMode + } : null; +} +/** + * Consider Case: + * When default 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 () { + * ... + * let inputPositionParams = layout.getLayoutParams(option); + * this.mergeOption(inputPositionParams); + * }, + * mergeOption: function (newOption) { + * newOption && zrUtil.merge(thisOption, newOption, true); + * layout.mergeLayoutParam(thisOption, newOption); + * } + * }); + * + * @param targetOption + * @param newOption + * @param opt + */ +function mergeLayoutParam(targetOption, newOption, opt) { + var ignoreSize = opt && opt.ignoreSize; + !isArray(ignoreSize) && (ignoreSize = [ignoreSize, ignoreSize]); + var hResult = merge(HV_NAMES[0], 0); + var vResult = merge(HV_NAMES[1], 1); + copy(HV_NAMES[0], targetOption, hResult); + copy(HV_NAMES[1], targetOption, vResult); + function merge(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_1 = names[i]; + if (!hasProp(newParams, name_1) && hasProp(targetOption, name_1)) { + newParams[name_1] = targetOption[name_1]; + 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. + */ +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; +} + +var inner$9 = makeInner(); +var ComponentModel = /** @class */function (_super) { + __extends(ComponentModel, _super); + function ComponentModel(option, parentModel, ecModel) { + var _this = _super.call(this, option, parentModel, ecModel) || this; + _this.uid = getUID('ec_cpt_model'); + return _this; + } + ComponentModel.prototype.init = function (option, parentModel, ecModel) { + this.mergeDefaultAndTheme(option, ecModel); + }; + ComponentModel.prototype.mergeDefaultAndTheme = function (option, ecModel) { + var layoutMode = fetchLayoutMode(this); + 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); + } + }; + ComponentModel.prototype.mergeOption = function (option, ecModel) { + merge(this.option, option, true); + var layoutMode = fetchLayoutMode(this); + if (layoutMode) { + mergeLayoutParam(this.option, option, layoutMode); + } + }; + /** + * Called immediately after `init` or `mergeOption` of this instance called. + */ + ComponentModel.prototype.optionUpdated = function (newCptOption, isInit) {}; + /** + * [How to declare defaultOption]: + * + * (A) If using class declaration in typescript (since echarts 5): + * ```ts + * import {ComponentOption} from '../model/option.js'; + * export interface XxxOption extends ComponentOption { + * aaa: number + * } + * export class XxxModel extends Component { + * static type = 'xxx'; + * static defaultOption: XxxOption = { + * aaa: 123 + * } + * } + * Component.registerClass(XxxModel); + * ``` + * ```ts + * import {inheritDefaultOption} from '../util/component.js'; + * import {XxxModel, XxxOption} from './XxxModel.js'; + * export interface XxxSubOption extends XxxOption { + * bbb: number + * } + * class XxxSubModel extends XxxModel { + * static defaultOption: XxxSubOption = inheritDefaultOption(XxxModel.defaultOption, { + * bbb: 456 + * }) + * fn() { + * let opt = this.getDefaultOption(); + * // opt is {aaa: 123, bbb: 456} + * } + * } + * ``` + * + * (B) If using class extend (previous approach in echarts 3 & 4): + * ```js + * let XxxComponent = Component.extend({ + * defaultOption: { + * xx: 123 + * } + * }) + * ``` + * ```js + * let XxxSubComponent = XxxComponent.extend({ + * defaultOption: { + * yy: 456 + * }, + * fn: function () { + * let opt = this.getDefaultOption(); + * // opt is {xx: 123, yy: 456} + * } + * }) + * ``` + */ + ComponentModel.prototype.getDefaultOption = function () { + var ctor = this.constructor; + // If using class declaration, it is different to travel super class + // in legacy env and auto merge defaultOption. So if using class + // declaration, defaultOption should be merged manually. + if (!isExtendedClass(ctor)) { + // When using ts class, defaultOption must be declared as static. + return ctor.defaultOption; + } + // FIXME: remove this approach? + var fields = inner$9(this); + if (!fields.defaultOption) { + var optList = []; + var clz = ctor; + while (clz) { + var opt = clz.prototype.defaultOption; + opt && optList.push(opt); + clz = clz.superClass; + } + var defaultOption = {}; + for (var i = optList.length - 1; i >= 0; i--) { + defaultOption = merge(defaultOption, optList[i], true); + } + fields.defaultOption = defaultOption; + } + return fields.defaultOption; + }; + /** + * Notice: always force to input param `useDefault` in case that forget to consider it. + * The same behavior as `modelUtil.parseFinder`. + * + * @param useDefault In many cases like series refer axis and axis refer grid, + * If axis index / axis id not specified, use the first target as default. + * In other cases like dataZoom refer axis, if not specified, measn no refer. + */ + ComponentModel.prototype.getReferringComponents = function (mainType, opt) { + var indexKey = mainType + 'Index'; + var idKey = mainType + 'Id'; + return queryReferringComponents(this.ecModel, mainType, { + index: this.get(indexKey, true), + id: this.get(idKey, true) + }, opt); + }; + ComponentModel.prototype.getBoxLayoutParams = function () { + // Consider itself having box layout configs. + var boxLayoutModel = this; + return { + left: boxLayoutModel.get('left'), + top: boxLayoutModel.get('top'), + right: boxLayoutModel.get('right'), + bottom: boxLayoutModel.get('bottom'), + width: boxLayoutModel.get('width'), + height: boxLayoutModel.get('height') + }; + }; + /** + * Get key for zlevel. + * If developers don't configure zlevel. We will assign zlevel to series based on the key. + * For example, lines with trail effect and progressive series will in an individual zlevel. + */ + ComponentModel.prototype.getZLevelKey = function () { + return ''; + }; + ComponentModel.prototype.setZLevel = function (zlevel) { + this.option.zlevel = zlevel; + }; + ComponentModel.protoInitialize = function () { + var proto = ComponentModel.prototype; + proto.type = 'component'; + proto.id = ''; + proto.name = ''; + proto.mainType = ''; + proto.subType = ''; + proto.componentIndex = 0; + }(); + return ComponentModel; +}(Model); +mountExtend(ComponentModel, Model); +enableClassManagement(ComponentModel); +enableSubTypeDefaulter(ComponentModel); +enableTopologicalTravel(ComponentModel, getDependencies); +function getDependencies(componentType) { + var deps = []; + each$4(ComponentModel.getClassesByMainType(componentType), function (clz) { + deps = deps.concat(clz.dependencies || clz.prototype.dependencies || []); + }); + // Ensure main type. + deps = map$1(deps, function (type) { + return parseClassType(type).main; + }); + // Hack dataset for convenience. + if (componentType !== 'dataset' && indexOf(deps, 'dataset') <= 0) { + deps.unshift('dataset'); + } + return deps; +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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') { + /* global navigator */ + platform = navigator.platform || ''; +} +var decalColor = 'rgba(0, 0, 0, 0.2)'; +const globalDefault = { + darkMode: 'auto', + // backgroundColor: 'rgba(0,0,0,0)', + colorBy: 'series', + color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'], + gradientColor: ['#f6efa6', '#d88273', '#bf444c'], + aria: { + decal: { + decals: [{ + color: decalColor, + dashArrayX: [1, 0], + dashArrayY: [2, 5], + symbolSize: 1, + rotation: Math.PI / 6 + }, { + color: decalColor, + symbol: 'circle', + dashArrayX: [[8, 8], [0, 8, 8, 0]], + dashArrayY: [6, 0], + symbolSize: 0.8 + }, { + color: decalColor, + dashArrayX: [1, 0], + dashArrayY: [4, 3], + rotation: -Math.PI / 4 + }, { + color: decalColor, + dashArrayX: [[6, 6], [0, 6, 6, 0]], + dashArrayY: [6, 0] + }, { + color: decalColor, + dashArrayX: [[1, 0], [1, 6]], + dashArrayY: [1, 0, 6, 0], + rotation: Math.PI / 4 + }, { + color: decalColor, + symbol: 'triangle', + dashArrayX: [[9, 9], [0, 9, 9, 0]], + dashArrayY: [7, 2], + symbolSize: 0.75 + }] + } + }, + // 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, + stateAnimation: { + duration: 300, + easing: 'cubicOut' + }, + animation: 'auto', + animationDuration: 1000, + animationDurationUpdate: 500, + animationEasing: 'cubicInOut', + animationEasingUpdate: 'cubicInOut', + 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 +}; + +var VISUAL_DIMENSIONS = createHashMap(['tooltip', 'label', 'itemName', 'itemId', 'itemGroupId', 'itemChildGroupId', 'seriesName']); +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_TYPED_ARRAY = 'typedArray'; +var SOURCE_FORMAT_UNKNOWN = 'unknown'; +var SERIES_LAYOUT_BY_COLUMN = 'column'; +var SERIES_LAYOUT_BY_ROW = 'row'; + +// The result of `guessOrdinal`. +var BE_ORDINAL = { + Must: 1, + Might: 2, + Not: 3 // Other cases +}; +var innerGlobalModel = makeInner(); +/** + * MUST be called before mergeOption of all series. + */ +function resetSourceDefaulter(ecModel) { + // `datasetMap` is used to make default encode. + innerGlobalModel(ecModel).datasetMap = createHashMap(); +} +/** + * [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 | + * + * @return encode Never be `null/undefined`. + */ +function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, source) { + var encode = {}; + var datasetModel = querySeriesUpstreamDatasetModel(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 = innerGlobalModel(ecModel).datasetMap; + var key = datasetModel.uid + '_' + source.seriesLayoutBy; + var baseCategoryDimIndex; + var categoryWayValueDimStart; + coordDimensions = coordDimensions.slice(); + each$4(coordDimensions, function (coordDimInfoLoose, coordDimIdx) { + var coordDimInfo = isObject$2(coordDimInfoLoose) ? coordDimInfoLoose : coordDimensions[coordDimIdx] = { + name: coordDimInfoLoose + }; + if (coordDimInfo.type === 'ordinal' && baseCategoryDimIndex == null) { + baseCategoryDimIndex = coordDimIdx; + categoryWayValueDimStart = getDataDimCountOnCoordDim(coordDimInfo); + } + 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$4(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 multiple series share one dimension + // category axis, series name should better use + // the other dimension 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: ...}, ...]. + * + * @return encode Never be `null/undefined`. + */ +function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) { + var encode = {}; + var datasetModel = querySeriesUpstreamDatasetModel(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$4(dimensionsDefine, function (dim, idx) { + if ((isObject$2(dim) ? dim.name : dim) === 'name') { + potentialNameDimIndex = idx; + } + }); + } + 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 uses itemName in charts. + // So we don't set encodeLabel here. + encode.itemName = [nameDimIndex]; + encode.seriesName = [nameDimIndex]; + } + return encode; +} +/** + * @return If return null/undefined, indicate that should not use datasetModel. + */ +function querySeriesUpstreamDatasetModel(seriesModel) { + // 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 = seriesModel.get('data', true); + if (!thisData) { + return queryReferringComponents(seriesModel.ecModel, 'dataset', { + index: seriesModel.get('datasetIndex', true), + id: seriesModel.get('datasetId', true) + }, SINGLE_REFERRING).models[0]; + } +} +/** + * @return Always return an array event empty. + */ +function queryDatasetUpstreamDatasetModels(datasetModel) { + // Only these attributes declared, we by default reference to `datasetIndex: 0`. + // Otherwise, no reference. + if (!datasetModel.get('transform', true) && !datasetModel.get('fromTransformResult', true)) { + return []; + } + return queryReferringComponents(datasetModel.ecModel, 'dataset', { + index: datasetModel.get('fromDatasetIndex', true), + id: datasetModel.get('fromDatasetId', true) + }, SINGLE_REFERRING).models; +} +/** + * 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? + */ +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$2(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) { + var dataArrayRows = data; + if (seriesLayoutBy === SERIES_LAYOUT_BY_ROW) { + var sample = dataArrayRows[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 < dataArrayRows.length && i < maxLoop; i++) { + var row = dataArrayRows[startIndex + i]; + if (row && (result = detectValue(row[dimIndex])) != null) { + return result; + } + } + } + } else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) { + var dataObjectRows = data; + if (!dimName) { + return BE_ORDINAL.Not; + } + for (var i = 0; i < dataObjectRows.length && i < maxLoop; i++) { + var item = dataObjectRows[i]; + if (item && (result = detectValue(item[dimName])) != null) { + return result; + } + } + } else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) { + var dataKeyedColumns = data; + if (!dimName) { + return BE_ORDINAL.Not; + } + var sample = dataKeyedColumns[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) { + var dataOriginal = data; + for (var i = 0; i < dataOriginal.length && i < maxLoop; i++) { + var item = dataOriginal[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". + // `Number('')` (or any whitespace) is `0`. + if (val != null && Number.isFinite(Number(val)) && val !== '') { + return beStr ? BE_ORDINAL.Might : BE_ORDINAL.Not; + } else if (beStr && val !== '-') { + return BE_ORDINAL.Must; + } + } + return BE_ORDINAL.Not; +} + +var internalOptionCreatorMap = createHashMap(); +function concatInternalOptions(ecModel, mainType, newCmptOptionList) { + var internalOptionCreator = internalOptionCreatorMap.get(mainType); + if (!internalOptionCreator) { + return newCmptOptionList; + } + var internalOptions = internalOptionCreator(ecModel); + if (!internalOptions) { + return newCmptOptionList; + } + return newCmptOptionList.concat(internalOptions); +} + +var innerColor = makeInner(); +makeInner(); +var PaletteMixin = /** @class */function () { + function PaletteMixin() {} + PaletteMixin.prototype.getColorFromPalette = function (name, scope, requestNum) { + var defaultPalette = normalizeToArray(this.get('color', true)); + var layeredPalette = this.get('colorLayer', true); + return getFromPalette(this, innerColor, defaultPalette, layeredPalette, name, scope, requestNum); + }; + PaletteMixin.prototype.clearColorPalette = function () { + clearPalette(this, innerColor); + }; + return PaletteMixin; +}(); +function getNearestPalette(palettes, requestColorNum) { + var paletteNum = palettes.length; + // TODO palettes must be in order + for (var i = 0; i < paletteNum; i++) { + if (palettes[i].length > requestColorNum) { + return palettes[i]; + } + } + return palettes[paletteNum - 1]; +} +/** + * @param name MUST NOT be null/undefined. Otherwise call this function + * twise with the same parameters will get different result. + * @param scope default this. + * @return Can be null/undefined + */ +function getFromPalette(that, inner, defaultPalette, layeredPalette, name, scope, requestNum) { + scope = scope || that; + var scopeFields = inner(scope); + var paletteIdx = scopeFields.paletteIdx || 0; + var paletteNameMap = scopeFields.paletteNameMap = scopeFields.paletteNameMap || {}; + // Use `hasOwnProperty` to avoid conflict with Object.prototype. + if (paletteNameMap.hasOwnProperty(name)) { + return paletteNameMap[name]; + } + var palette = requestNum == null || !layeredPalette ? defaultPalette : getNearestPalette(layeredPalette, requestNum); + // In case can't find in layered color palette. + palette = palette || defaultPalette; + if (!palette || !palette.length) { + return; + } + var pickedPaletteItem = palette[paletteIdx]; + if (name) { + paletteNameMap[name] = pickedPaletteItem; + } + scopeFields.paletteIdx = (paletteIdx + 1) % palette.length; + return pickedPaletteItem; +} +function clearPalette(that, inner) { + inner(that).paletteIdx = 0; + inner(that).paletteNameMap = {}; +} + +var reCreateSeriesIndices; +var assertSeriesInitialized; +var initBase; +var OPTION_INNER_KEY = "\0_ec_inner"; +var OPTION_INNER_VALUE = 1; +var GlobalModel = ( + /** @class */ + function(_super) { + __extends(GlobalModel2, _super); + function GlobalModel2() { + return _super !== null && _super.apply(this, arguments) || this; + } + GlobalModel2.prototype.init = function(option, parentModel, ecModel, theme, locale, optionManager) { + theme = theme || {}; + this.option = null; + this._theme = new Model(theme); + this._locale = new Model(locale); + this._optionManager = optionManager; + }; + GlobalModel2.prototype.setOption = function(option, opts, optionPreprocessorFuncs) { + var innerOpt = normalizeSetOptionInput(opts); + this._optionManager.setOption(option, optionPreprocessorFuncs, innerOpt); + this._resetOption(null, innerOpt); + }; + GlobalModel2.prototype.resetOption = function(type, opt) { + return this._resetOption(type, normalizeSetOptionInput(opt)); + }; + GlobalModel2.prototype._resetOption = function(type, opt) { + var optionChanged = false; + var optionManager = this._optionManager; + if (!type || type === "recreate") { + var baseOption = optionManager.mountOption(type === "recreate"); + if (!this.option || type === "recreate") { + initBase(this, baseOption); + } else { + this.restoreData(); + this._mergeOption(baseOption, opt); + } + optionChanged = true; + } + if (type === "timeline" || type === "media") { + this.restoreData(); + } + if (!type || type === "recreate" || type === "timeline") { + var timelineOption = optionManager.getTimelineOption(this); + if (timelineOption) { + optionChanged = true; + this._mergeOption(timelineOption, opt); + } + } + if (!type || type === "recreate" || type === "media") { + var mediaOptions = optionManager.getMediaOption(this); + if (mediaOptions.length) { + each$4(mediaOptions, function(mediaOption) { + optionChanged = true; + this._mergeOption(mediaOption, opt); + }, this); + } + } + return optionChanged; + }; + GlobalModel2.prototype.mergeOption = function(option) { + this._mergeOption(option, null); + }; + GlobalModel2.prototype._mergeOption = function(newOption, opt) { + var option = this.option; + var componentsMap = this._componentsMap; + var componentsCount = this._componentsCount; + var newCmptTypes = []; + var newCmptTypeMap = createHashMap(); + var replaceMergeMainTypeMap = opt && opt.replaceMergeMainTypeMap; + resetSourceDefaulter(this); + each$4(newOption, function(componentOption, mainType) { + if (componentOption == null) { + return; + } + if (!ComponentModel.hasClass(mainType)) { + option[mainType] = option[mainType] == null ? clone$2(componentOption) : merge(option[mainType], componentOption, true); + } else if (mainType) { + newCmptTypes.push(mainType); + newCmptTypeMap.set(mainType, true); + } + }); + if (replaceMergeMainTypeMap) { + replaceMergeMainTypeMap.each(function(val, mainTypeInReplaceMerge) { + if (ComponentModel.hasClass(mainTypeInReplaceMerge) && !newCmptTypeMap.get(mainTypeInReplaceMerge)) { + newCmptTypes.push(mainTypeInReplaceMerge); + newCmptTypeMap.set(mainTypeInReplaceMerge, true); + } + }); + } + ComponentModel.topologicalTravel(newCmptTypes, ComponentModel.getAllClassMainTypes(), visitComponent, this); + function visitComponent(mainType) { + var newCmptOptionList = concatInternalOptions(this, mainType, normalizeToArray(newOption[mainType])); + var oldCmptList = componentsMap.get(mainType); + var mergeMode = ( + // `!oldCmptList` means init. See the comment in `mappingToExists` + !oldCmptList ? "replaceAll" : replaceMergeMainTypeMap && replaceMergeMainTypeMap.get(mainType) ? "replaceMerge" : "normalMerge" + ); + var mappingResult = mappingToExists(oldCmptList, newCmptOptionList, mergeMode); + setComponentTypeToKeyInfo(mappingResult, mainType, ComponentModel); + option[mainType] = null; + componentsMap.set(mainType, null); + componentsCount.set(mainType, 0); + var optionsByMainType = []; + var cmptsByMainType = []; + var cmptsCountByMainType = 0; + var tooltipExists; + each$4(mappingResult, function(resultItem, index) { + var componentModel = resultItem.existing; + var newCmptOption = resultItem.newOption; + if (!newCmptOption) { + if (componentModel) { + componentModel.mergeOption({}, this); + componentModel.optionUpdated({}, false); + } + } else { + var isSeriesType = mainType === "series"; + var ComponentModelClass = ComponentModel.getClass( + mainType, + resultItem.keyInfo.subType, + !isSeriesType + // Give a more detailed warn later if series don't exists + ); + if (!ComponentModelClass) { + return; + } + if (mainType === "tooltip") { + if (tooltipExists) { + return; + } + tooltipExists = true; + } + if (componentModel && componentModel.constructor === ComponentModelClass) { + componentModel.name = resultItem.keyInfo.name; + componentModel.mergeOption(newCmptOption, this); + componentModel.optionUpdated(newCmptOption, false); + } else { + var extraOpt = extend({ + componentIndex: index + }, resultItem.keyInfo); + componentModel = new ComponentModelClass(newCmptOption, this, this, extraOpt); + extend(componentModel, extraOpt); + if (resultItem.brandNew) { + componentModel.__requireNewView = true; + } + componentModel.init(newCmptOption, this, this); + componentModel.optionUpdated(null, true); + } + } + if (componentModel) { + optionsByMainType.push(componentModel.option); + cmptsByMainType.push(componentModel); + cmptsCountByMainType++; + } else { + optionsByMainType.push(void 0); + cmptsByMainType.push(void 0); + } + }, this); + option[mainType] = optionsByMainType; + componentsMap.set(mainType, cmptsByMainType); + componentsCount.set(mainType, cmptsCountByMainType); + if (mainType === "series") { + reCreateSeriesIndices(this); + } + } + if (!this._seriesIndices) { + reCreateSeriesIndices(this); + } + }; + GlobalModel2.prototype.getOption = function() { + var option = clone$2(this.option); + each$4(option, function(optInMainType, mainType) { + if (ComponentModel.hasClass(mainType)) { + var opts = normalizeToArray(optInMainType); + var realLen = opts.length; + var metNonInner = false; + for (var i = realLen - 1; i >= 0; i--) { + if (opts[i] && !isComponentIdInternal(opts[i])) { + metNonInner = true; + } else { + opts[i] = null; + !metNonInner && realLen--; + } + } + opts.length = realLen; + option[mainType] = opts; + } + }); + delete option[OPTION_INNER_KEY]; + return option; + }; + GlobalModel2.prototype.getTheme = function() { + return this._theme; + }; + GlobalModel2.prototype.getLocaleModel = function() { + return this._locale; + }; + GlobalModel2.prototype.setUpdatePayload = function(payload) { + this._payload = payload; + }; + GlobalModel2.prototype.getUpdatePayload = function() { + return this._payload; + }; + GlobalModel2.prototype.getComponent = function(mainType, idx) { + var list = this._componentsMap.get(mainType); + if (list) { + var cmpt = list[idx || 0]; + if (cmpt) { + return cmpt; + } else if (idx == null) { + for (var i = 0; i < list.length; i++) { + if (list[i]) { + return list[i]; + } + } + } + } + }; + GlobalModel2.prototype.queryComponents = function(condition) { + var mainType = condition.mainType; + if (!mainType) { + return []; + } + var index = condition.index; + var id = condition.id; + var name = condition.name; + var cmpts = this._componentsMap.get(mainType); + if (!cmpts || !cmpts.length) { + return []; + } + var result; + if (index != null) { + result = []; + each$4(normalizeToArray(index), function(idx) { + cmpts[idx] && result.push(cmpts[idx]); + }); + } else if (id != null) { + result = queryByIdOrName("id", id, cmpts); + } else if (name != null) { + result = queryByIdOrName("name", name, cmpts); + } else { + result = filter(cmpts, function(cmpt) { + return !!cmpt; + }); + } + return filterBySubType(result, condition); + }; + GlobalModel2.prototype.findComponents = function(condition) { + var query = condition.query; + var mainType = condition.mainType; + var queryCond = getQueryCond(query); + var result = queryCond ? this.queryComponents(queryCond) : filter(this._componentsMap.get(mainType), function(cmpt) { + return !!cmpt; + }); + 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, + // 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; + } + }; + GlobalModel2.prototype.eachComponent = function(mainType, cb, context) { + var componentsMap = this._componentsMap; + if (isFunction(mainType)) { + var ctxForAll_1 = cb; + var cbForAll_1 = mainType; + componentsMap.each(function(cmpts2, componentType) { + for (var i2 = 0; cmpts2 && i2 < cmpts2.length; i2++) { + var cmpt2 = cmpts2[i2]; + cmpt2 && cbForAll_1.call(ctxForAll_1, componentType, cmpt2, cmpt2.componentIndex); + } + }); + } else { + var cmpts = isString(mainType) ? componentsMap.get(mainType) : isObject$2(mainType) ? this.findComponents(mainType) : null; + for (var i = 0; cmpts && i < cmpts.length; i++) { + var cmpt = cmpts[i]; + cmpt && cb.call(context, cmpt, cmpt.componentIndex); + } + } + }; + GlobalModel2.prototype.getSeriesByName = function(name) { + var nameStr = convertOptionIdName(name, null); + return filter(this._componentsMap.get("series"), function(oneSeries) { + return !!oneSeries && nameStr != null && oneSeries.name === nameStr; + }); + }; + GlobalModel2.prototype.getSeriesByIndex = function(seriesIndex) { + return this._componentsMap.get("series")[seriesIndex]; + }; + GlobalModel2.prototype.getSeriesByType = function(subType) { + return filter(this._componentsMap.get("series"), function(oneSeries) { + return !!oneSeries && oneSeries.subType === subType; + }); + }; + GlobalModel2.prototype.getSeries = function() { + return filter(this._componentsMap.get("series"), function(oneSeries) { + return !!oneSeries; + }); + }; + GlobalModel2.prototype.getSeriesCount = function() { + return this._componentsCount.get("series"); + }; + GlobalModel2.prototype.eachSeries = function(cb, context) { + assertSeriesInitialized(this); + each$4(this._seriesIndices, function(rawSeriesIndex) { + var series = this._componentsMap.get("series")[rawSeriesIndex]; + cb.call(context, series, rawSeriesIndex); + }, this); + }; + GlobalModel2.prototype.eachRawSeries = function(cb, context) { + each$4(this._componentsMap.get("series"), function(series) { + series && cb.call(context, series, series.componentIndex); + }); + }; + GlobalModel2.prototype.eachSeriesByType = function(subType, cb, context) { + assertSeriesInitialized(this); + each$4(this._seriesIndices, function(rawSeriesIndex) { + var series = this._componentsMap.get("series")[rawSeriesIndex]; + if (series.subType === subType) { + cb.call(context, series, rawSeriesIndex); + } + }, this); + }; + GlobalModel2.prototype.eachRawSeriesByType = function(subType, cb, context) { + return each$4(this.getSeriesByType(subType), cb, context); + }; + GlobalModel2.prototype.isSeriesFiltered = function(seriesModel) { + assertSeriesInitialized(this); + return this._seriesIndicesMap.get(seriesModel.componentIndex) == null; + }; + GlobalModel2.prototype.getCurrentSeriesIndices = function() { + return (this._seriesIndices || []).slice(); + }; + GlobalModel2.prototype.filterSeries = function(cb, context) { + assertSeriesInitialized(this); + var newSeriesIndices = []; + each$4(this._seriesIndices, function(seriesRawIdx) { + var series = this._componentsMap.get("series")[seriesRawIdx]; + cb.call(context, series, seriesRawIdx) && newSeriesIndices.push(seriesRawIdx); + }, this); + this._seriesIndices = newSeriesIndices; + this._seriesIndicesMap = createHashMap(newSeriesIndices); + }; + GlobalModel2.prototype.restoreData = function(payload) { + reCreateSeriesIndices(this); + var componentsMap = this._componentsMap; + var componentTypes = []; + componentsMap.each(function(components, componentType) { + if (ComponentModel.hasClass(componentType)) { + componentTypes.push(componentType); + } + }); + ComponentModel.topologicalTravel(componentTypes, ComponentModel.getAllClassMainTypes(), function(componentType) { + each$4(componentsMap.get(componentType), function(component) { + if (component && (componentType !== "series" || !isNotTargetSeries(component, payload))) { + component.restoreData(); + } + }); + }); + }; + GlobalModel2.internalField = function() { + reCreateSeriesIndices = function(ecModel) { + var seriesIndices = ecModel._seriesIndices = []; + each$4(ecModel._componentsMap.get("series"), function(series) { + series && seriesIndices.push(series.componentIndex); + }); + ecModel._seriesIndicesMap = createHashMap(seriesIndices); + }; + assertSeriesInitialized = function(ecModel) { + }; + initBase = function(ecModel, baseOption) { + ecModel.option = {}; + ecModel.option[OPTION_INNER_KEY] = OPTION_INNER_VALUE; + ecModel._componentsMap = createHashMap({ + series: [] + }); + ecModel._componentsCount = createHashMap(); + var airaOption = baseOption.aria; + if (isObject$2(airaOption) && airaOption.enabled == null) { + airaOption.enabled = true; + } + mergeTheme(baseOption, ecModel._theme.option); + merge(baseOption, globalDefault, false); + ecModel._mergeOption(baseOption, null); + }; + }(); + return GlobalModel2; + }(Model) +); +function isNotTargetSeries(seriesModel, payload) { + if (payload) { + var index = payload.seriesIndex; + var id = payload.seriesId; + var name_1 = payload.seriesName; + return index != null && seriesModel.componentIndex !== index || id != null && seriesModel.id !== id || name_1 != null && seriesModel.name !== name_1; + } +} +function mergeTheme(option, theme) { + var notMergeColorLayer = option.color && !option.colorLayer; + each$4(theme, function(themeItem, name) { + if (name === "colorLayer" && notMergeColorLayer) { + return; + } + if (!ComponentModel.hasClass(name)) { + if (typeof themeItem === "object") { + option[name] = !option[name] ? clone$2(themeItem) : merge(option[name], themeItem, false); + } else { + if (option[name] == null) { + option[name] = themeItem; + } + } + } + }); +} +function queryByIdOrName(attr, idOrName, cmpts) { + if (isArray(idOrName)) { + var keyMap_1 = createHashMap(); + each$4(idOrName, function(idOrNameItem) { + if (idOrNameItem != null) { + var idName = convertOptionIdName(idOrNameItem, null); + idName != null && keyMap_1.set(idOrNameItem, true); + } + }); + return filter(cmpts, function(cmpt) { + return cmpt && keyMap_1.get(cmpt[attr]); + }); + } else { + var idName_1 = convertOptionIdName(idOrName, null); + return filter(cmpts, function(cmpt) { + return cmpt && idName_1 != null && cmpt[attr] === idName_1; + }); + } +} +function filterBySubType(components, condition) { + return condition.hasOwnProperty("subType") ? filter(components, function(cmpt) { + return cmpt && cmpt.subType === condition.subType; + }) : components; +} +function normalizeSetOptionInput(opts) { + var replaceMergeMainTypeMap = createHashMap(); + opts && each$4(normalizeToArray(opts.replaceMerge), function(mainType) { + replaceMergeMainTypeMap.set(mainType, true); + }); + return { + replaceMergeMainTypeMap + }; +} +mixin(GlobalModel, PaletteMixin); + +var availableMethods = ['getDom', 'getZr', 'getWidth', 'getHeight', 'getDevicePixelRatio', 'dispatchAction', 'isSSR', 'isDisposed', 'on', 'off', 'getDataURL', 'getConnectedDataURL', +// 'getModel', +'getOption', +// 'getViewOfComponentModel', +// 'getViewOfSeriesModel', +'getId', 'updateLabelLayout']; +var ExtensionAPI = /** @class */function () { + function ExtensionAPI(ecInstance) { + each$4(availableMethods, function (methodName) { + this[methodName] = bind$1(ecInstance[methodName], ecInstance); + }, this); + } + return ExtensionAPI; +}(); + +var coordinateSystemCreators = {}; +var CoordinateSystemManager = /** @class */function () { + function CoordinateSystemManager() { + this._coordinateSystems = []; + } + CoordinateSystemManager.prototype.create = function (ecModel, api) { + var coordinateSystems = []; + each$4(coordinateSystemCreators, function (creator, type) { + var list = creator.create(ecModel, api); + coordinateSystems = coordinateSystems.concat(list || []); + }); + this._coordinateSystems = coordinateSystems; + }; + CoordinateSystemManager.prototype.update = function (ecModel, api) { + each$4(this._coordinateSystems, function (coordSys) { + coordSys.update && coordSys.update(ecModel, api); + }); + }; + CoordinateSystemManager.prototype.getCoordinateSystems = function () { + return this._coordinateSystems.slice(); + }; + CoordinateSystemManager.register = function (type, creator) { + coordinateSystemCreators[type] = creator; + }; + CoordinateSystemManager.get = function (type) { + return coordinateSystemCreators[type]; + }; + return CoordinateSystemManager; +}(); + +var QUERY_REG = /^(min|max)?(.+)$/; +var OptionManager = ( + /** @class */ + function() { + function OptionManager2(api) { + this._timelineOptions = []; + this._mediaList = []; + this._currentMediaIndices = []; + this._api = api; + } + OptionManager2.prototype.setOption = function(rawOption, optionPreprocessorFuncs, opt) { + if (rawOption) { + each$4(normalizeToArray(rawOption.series), function(series) { + series && series.data && isTypedArray(series.data) && setAsPrimitive(series.data); + }); + each$4(normalizeToArray(rawOption.dataset), function(dataset) { + dataset && dataset.source && isTypedArray(dataset.source) && setAsPrimitive(dataset.source); + }); + } + rawOption = clone$2(rawOption); + var optionBackup = this._optionBackup; + var newParsedOption = parseRawOption(rawOption, optionPreprocessorFuncs, !optionBackup); + this._newBaseOption = newParsedOption.baseOption; + if (optionBackup) { + if (newParsedOption.timelineOptions.length) { + optionBackup.timelineOptions = newParsedOption.timelineOptions; + } + if (newParsedOption.mediaList.length) { + optionBackup.mediaList = newParsedOption.mediaList; + } + if (newParsedOption.mediaDefault) { + optionBackup.mediaDefault = newParsedOption.mediaDefault; + } + } else { + this._optionBackup = newParsedOption; + } + }; + OptionManager2.prototype.mountOption = function(isRecreate) { + var optionBackup = this._optionBackup; + this._timelineOptions = optionBackup.timelineOptions; + this._mediaList = optionBackup.mediaList; + this._mediaDefault = optionBackup.mediaDefault; + this._currentMediaIndices = []; + return clone$2(isRecreate ? optionBackup.baseOption : this._newBaseOption); + }; + OptionManager2.prototype.getTimelineOption = function(ecModel) { + var option; + var timelineOptions = this._timelineOptions; + if (timelineOptions.length) { + var timelineModel = ecModel.getComponent("timeline"); + if (timelineModel) { + option = clone$2( + // FIXME:TS as TimelineModel or quivlant interface + timelineOptions[timelineModel.getCurrentIndex()] + ); + } + } + return option; + }; + OptionManager2.prototype.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 = []; + if (!mediaList.length && !mediaDefault) { + return result; + } + for (var i = 0, len = mediaList.length; i < len; i++) { + if (applyMediaQuery(mediaList[i].query, ecWidth, ecHeight)) { + indices.push(i); + } + } + if (!indices.length && mediaDefault) { + indices = [-1]; + } + if (indices.length && !indicesEquals(indices, this._currentMediaIndices)) { + result = map$1(indices, function(index) { + return clone$2(index === -1 ? mediaDefault.option : mediaList[index].option); + }); + } + this._currentMediaIndices = indices; + return result; + }; + return OptionManager2; + }() +); +function parseRawOption(rawOption, optionPreprocessorFuncs, isNew) { + var mediaList = []; + var mediaDefault; + var baseOption; + var declaredBaseOption = rawOption.baseOption; + var timelineOnRoot = rawOption.timeline; + var timelineOptionsOnRoot = rawOption.options; + var mediaOnRoot = rawOption.media; + var hasMedia = !!rawOption.media; + var hasTimeline = !!(timelineOptionsOnRoot || timelineOnRoot || declaredBaseOption && declaredBaseOption.timeline); + if (declaredBaseOption) { + baseOption = declaredBaseOption; + if (!baseOption.timeline) { + baseOption.timeline = timelineOnRoot; + } + } else { + if (hasTimeline || hasMedia) { + rawOption.options = rawOption.media = null; + } + baseOption = rawOption; + } + if (hasMedia) { + if (isArray(mediaOnRoot)) { + each$4(mediaOnRoot, function(singleMedia) { + if (singleMedia && singleMedia.option) { + if (singleMedia.query) { + mediaList.push(singleMedia); + } else if (!mediaDefault) { + mediaDefault = singleMedia; + } + } + }); + } + } + doPreprocess(baseOption); + each$4(timelineOptionsOnRoot, function(option) { + return doPreprocess(option); + }); + each$4(mediaList, function(media) { + return doPreprocess(media.option); + }); + function doPreprocess(option) { + each$4(optionPreprocessorFuncs, function(preProcess) { + preProcess(option, isNew); + }); + } + return { + baseOption, + timelineOptions: timelineOptionsOnRoot || [], + mediaDefault, + mediaList + }; +} +function applyMediaQuery(query, ecWidth, ecHeight) { + var realMap = { + width: ecWidth, + height: ecHeight, + aspectratio: ecWidth / ecHeight + // lower case for convenience. + }; + var applicable = true; + each$4(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)) { + applicable = false; + } + }); + return applicable; +} +function compare(real, expect, operator) { + if (operator === "min") { + return real >= expect; + } else if (operator === "max") { + return real <= expect; + } else { + return real === expect; + } +} +function indicesEquals(indices1, indices2) { + return indices1.join(",") === indices2.join(","); +} + +var each$2 = each$4; +var isObject$1 = isObject$2; +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) { + 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; + if (emphasisOpt.focus) { + opt.emphasis.focus = emphasisOpt.focus; + } + if (emphasisOpt.blurScope) { + opt.emphasis.blurScope = emphasisOpt.blurScope; + } + } + } +} +function removeEC3NormalStatus(opt) { + convertNormalEmphasis(opt, "itemStyle"); + convertNormalEmphasis(opt, "lineStyle"); + convertNormalEmphasis(opt, "areaStyle"); + convertNormalEmphasis(opt, "label"); + convertNormalEmphasis(opt, "labelLine"); + convertNormalEmphasis(opt, "upperLabel"); + convertNormalEmphasis(opt, "edgeLabel"); +} +function compatTextStyle(opt, propName) { + var labelOptSingle = isObject$1(opt) && opt[propName]; + var textStyle = isObject$1(labelOptSingle) && labelOptSingle.textStyle; + if (textStyle) { + for (var i = 0, len = TEXT_STYLE_OPTIONS.length; i < len; i++) { + var textPropName = TEXT_STYLE_OPTIONS[i]; + if (textStyle.hasOwnProperty(textPropName)) { + labelOptSingle[textPropName] = textStyle[textPropName]; + } + } + } +} +function compatEC3CommonStyles(opt) { + if (opt) { + removeEC3NormalStatus(opt); + compatTextStyle(opt, "label"); + opt.emphasis && compatTextStyle(opt.emphasis, "label"); + } +} +function processSeries(seriesOpt) { + if (!isObject$1(seriesOpt)) { + return; + } + compatEC2ItemStyle(seriesOpt); + removeEC3NormalStatus(seriesOpt); + compatTextStyle(seriesOpt, "label"); + compatTextStyle(seriesOpt, "upperLabel"); + compatTextStyle(seriesOpt, "edgeLabel"); + if (seriesOpt.emphasis) { + compatTextStyle(seriesOpt.emphasis, "label"); + compatTextStyle(seriesOpt.emphasis, "upperLabel"); + 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; + 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$4(seriesOpt.categories, function(opt) { + removeEC3NormalStatus(opt); + }); + } + if (data && !isTypedArray(data)) { + for (var i = 0; i < data.length; i++) { + compatEC3CommonStyles(data[i]); + } + } + markPoint = seriesOpt.markPoint; + if (markPoint && markPoint.data) { + var mpData = markPoint.data; + for (var i = 0; i < mpData.length; i++) { + compatEC3CommonStyles(mpData[i]); + } + } + 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]); + } + } + } + if (seriesOpt.type === "gauge") { + compatTextStyle(seriesOpt, "axisLabel"); + compatTextStyle(seriesOpt, "title"); + compatTextStyle(seriesOpt, "detail"); + } else if (seriesOpt.type === "treemap") { + convertNormalEmphasis(seriesOpt.breadcrumb, "itemStyle"); + each$4(seriesOpt.levels, function(opt) { + removeEC3NormalStatus(opt); + }); + } else if (seriesOpt.type === "tree") { + removeEC3NormalStatus(seriesOpt.leaves); + } +} +function toArr(o) { + return isArray(o) ? o : o ? [o] : []; +} +function toObj(o) { + return (isArray(o) ? o[0] : o) || {}; +} +function globalCompatStyle(option, isTheme) { + each$2(toArr(option.series), function(seriesOpt) { + isObject$1(seriesOpt) && processSeries(seriesOpt); + }); + var axes = ["xAxis", "yAxis", "radiusAxis", "angleAxis", "singleAxis", "parallelAxis", "radar"]; + isTheme && axes.push("valueAxis", "categoryAxis", "logAxis", "timeAxis"); + each$2(axes, function(axisName) { + each$2(toArr(option[axisName]), function(axisOpt) { + if (axisOpt) { + compatTextStyle(axisOpt, "axisLabel"); + compatTextStyle(axisOpt.axisPointer, "label"); + } + }); + }); + each$2(toArr(option.parallel), function(parallelOpt) { + var parallelAxisDefault = parallelOpt && parallelOpt.parallelAxisDefault; + compatTextStyle(parallelAxisDefault, "axisLabel"); + compatTextStyle(parallelAxisDefault && parallelAxisDefault.axisPointer, "label"); + }); + each$2(toArr(option.calendar), function(calendarOpt) { + convertNormalEmphasis(calendarOpt, "itemStyle"); + compatTextStyle(calendarOpt, "dayLabel"); + compatTextStyle(calendarOpt, "monthLabel"); + compatTextStyle(calendarOpt, "yearLabel"); + }); + each$2(toArr(option.radar), function(radarOpt) { + compatTextStyle(radarOpt, "name"); + if (radarOpt.name && radarOpt.axisName == null) { + radarOpt.axisName = radarOpt.name; + delete radarOpt.name; + } + if (radarOpt.nameGap != null && radarOpt.axisNameGap == null) { + radarOpt.axisNameGap = radarOpt.nameGap; + delete radarOpt.nameGap; + } + }); + each$2(toArr(option.geo), function(geoOpt) { + if (isObject$1(geoOpt)) { + compatEC3CommonStyles(geoOpt); + each$2(toArr(geoOpt.regions), function(regionObj) { + compatEC3CommonStyles(regionObj); + }); + } + }); + each$2(toArr(option.timeline), function(timelineOpt) { + compatEC3CommonStyles(timelineOpt); + convertNormalEmphasis(timelineOpt, "label"); + convertNormalEmphasis(timelineOpt, "itemStyle"); + convertNormalEmphasis(timelineOpt, "controlStyle", true); + var data = timelineOpt.data; + isArray(data) && each$4(data, function(item) { + if (isObject$2(item)) { + convertNormalEmphasis(item, "label"); + convertNormalEmphasis(item, "itemStyle"); + } + }); + }); + each$2(toArr(option.toolbox), function(toolboxOpt) { + convertNormalEmphasis(toolboxOpt, "iconStyle"); + each$2(toolboxOpt.feature, function(featureOpt) { + convertNormalEmphasis(featureOpt, "iconStyle"); + }); + }); + compatTextStyle(toObj(option.axisPointer), "label"); + compatTextStyle(toObj(option.tooltip).axisPointer, "label"); +} + +function get(opt, path) { + var pathArr = path.split(","); + var obj = opt; + for (var i = 0; i < pathArr.length; i++) { + obj = obj && obj[pathArr[i]]; + if (obj == null) { + break; + } + } + return obj; +} +function set(opt, path, val, overwrite) { + var pathArr = path.split(","); + var obj = opt; + var key; + var i = 0; + for (; i < pathArr.length - 1; i++) { + key = pathArr[i]; + if (obj[key] == null) { + obj[key] = {}; + } + obj = obj[key]; + } + if (obj[pathArr[i]] == null) { + obj[pathArr[i]] = val; + } +} +function compatLayoutProperties(option) { + option && each$4(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 BAR_ITEM_STYLE_MAP = [["borderRadius", "barBorderRadius"], ["borderColor", "barBorderColor"], ["borderWidth", "barBorderWidth"]]; +function compatBarItemStyle(option) { + var itemStyle = option && option.itemStyle; + if (itemStyle) { + for (var i = 0; i < BAR_ITEM_STYLE_MAP.length; i++) { + var oldName = BAR_ITEM_STYLE_MAP[i][1]; + var newName = BAR_ITEM_STYLE_MAP[i][0]; + if (itemStyle[oldName] != null) { + itemStyle[newName] = itemStyle[oldName]; + } + } + } +} +function compatPieLabel(option) { + if (!option) { + return; + } + if (option.alignTo === "edge" && option.margin != null && option.edgeDistance == null) { + option.edgeDistance = option.margin; + } +} +function compatSunburstState(option) { + if (!option) { + return; + } + if (option.downplay && !option.blur) { + option.blur = option.downplay; + } +} +function compatGraphFocus(option) { + if (!option) { + return; + } + if (option.focusNodeAdjacency != null) { + option.emphasis = option.emphasis || {}; + if (option.emphasis.focus == null) { + option.emphasis.focus = "adjacency"; + } + } +} +function traverseTree(data, cb) { + if (data) { + for (var i = 0; i < data.length; i++) { + cb(data[i]); + data[i] && traverseTree(data[i].children, cb); + } + } +} +function globalBackwardCompat(option, isTheme) { + globalCompatStyle(option, isTheme); + option.series = normalizeToArray(option.series); + each$4(option.series, function(seriesOpt) { + if (!isObject$2(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; + } + compatPieLabel(seriesOpt.label); + var data = seriesOpt.data; + if (data && !isTypedArray(data)) { + for (var i = 0; i < data.length; i++) { + compatPieLabel(data[i]); + } + } + if (seriesOpt.hoverOffset != null) { + seriesOpt.emphasis = seriesOpt.emphasis || {}; + if (seriesOpt.emphasis.scaleSize = null) { + seriesOpt.emphasis.scaleSize = seriesOpt.hoverOffset; + } + } + } else if (seriesType === "gauge") { + var pointerColor = get(seriesOpt, "pointer.color"); + pointerColor != null && set(seriesOpt, "itemStyle.color", pointerColor); + } else if (seriesType === "bar") { + compatBarItemStyle(seriesOpt); + compatBarItemStyle(seriesOpt.backgroundStyle); + compatBarItemStyle(seriesOpt.emphasis); + var data = seriesOpt.data; + if (data && !isTypedArray(data)) { + for (var i = 0; i < data.length; i++) { + if (typeof data[i] === "object") { + compatBarItemStyle(data[i]); + compatBarItemStyle(data[i] && data[i].emphasis); + } + } + } + } else if (seriesType === "sunburst") { + var highlightPolicy = seriesOpt.highlightPolicy; + if (highlightPolicy) { + seriesOpt.emphasis = seriesOpt.emphasis || {}; + if (!seriesOpt.emphasis.focus) { + seriesOpt.emphasis.focus = highlightPolicy; + } + } + compatSunburstState(seriesOpt); + traverseTree(seriesOpt.data, compatSunburstState); + } else if (seriesType === "graph" || seriesType === "sankey") { + compatGraphFocus(seriesOpt); + } else if (seriesType === "map") { + if (seriesOpt.mapType && !seriesOpt.map) { + seriesOpt.map = seriesOpt.mapType; + } + if (seriesOpt.mapLocation) { + defaults(seriesOpt, seriesOpt.mapLocation); + } + } + if (seriesOpt.hoverAnimation != null) { + seriesOpt.emphasis = seriesOpt.emphasis || {}; + if (seriesOpt.emphasis && seriesOpt.emphasis.scale == null) { + seriesOpt.emphasis.scale = seriesOpt.hoverAnimation; + } + } + compatLayoutProperties(seriesOpt); + }); + if (option.dataRange) { + option.visualMap = option.dataRange; + } + each$4(COMPATITABLE_COMPONENTS, function(componentName) { + var options = option[componentName]; + if (options) { + if (!isArray(options)) { + options = [options]; + } + each$4(options, function(option2) { + compatLayoutProperties(option2); + }); + } + }); +} + +// (1) [Caution]: the logic is correct based on the premises: +// data processing stage is blocked in stream. +// See +// (2) Only register once when import repeatedly. +// Should be executed after series is filtered and before stack calculation. +function dataStack(ecModel) { + var stackInfoMap = createHashMap(); + ecModel.eachSeries(function (seriesModel) { + var stack = seriesModel.get('stack'); + // Compatible: 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. + // TODO: Type getCalculationInfo return more specific type? + 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$4(stackInfoList, function (targetStackInfo, idxInStack) { + var resultVal = []; + var resultNaN = [NaN, NaN]; + var dims = [targetStackInfo.stackResultDimension, targetStackInfo.stackedOverDimension]; + var targetData = targetStackInfo.data; + var isStackedByIndex = targetStackInfo.isStackedByIndex; + var stackStrategy = targetStackInfo.seriesModel.get('stackStrategy') || 'samesign'; + // Should not write on raw data, because stack series model list changes + // depending on legend selection. + targetData.modify(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 (stackStrategy === 'all' // single stack group + || stackStrategy === 'positive' && val > 0 || stackStrategy === 'negative' && val < 0 || stackStrategy === 'samesign' && sum >= 0 && val > 0 // All positive stack + || stackStrategy === 'samesign' && sum <= 0 && val < 0 // All negative stack + ) { + // The sum has to be very small to be affected by the + // floating arithmetic problem. An incorrect result will probably + // cause axis min/max to be filtered incorrectly. + sum = addSafe(sum, val); + stackedOver = val; + break; + } + } + } + resultVal[0] = sum; + resultVal[1] = stackedOver; + return resultVal; + }); + }); +} + +var SourceImpl = ( + /** @class */ + /* @__PURE__ */ function() { + function SourceImpl2(fields) { + this.data = fields.data || (fields.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS ? {} : []); + this.sourceFormat = fields.sourceFormat || SOURCE_FORMAT_UNKNOWN; + this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN; + this.startIndex = fields.startIndex || 0; + this.dimensionsDetectedCount = fields.dimensionsDetectedCount; + this.metaRawOption = fields.metaRawOption; + var dimensionsDefine = this.dimensionsDefine = fields.dimensionsDefine; + if (dimensionsDefine) { + for (var i = 0; i < dimensionsDefine.length; i++) { + var dim = dimensionsDefine[i]; + if (dim.type == null) { + if (guessOrdinal(this, i) === BE_ORDINAL.Must) { + dim.type = "ordinal"; + } + } + } + } + } + return SourceImpl2; + }() +); +function isSourceInstance(val) { + return val instanceof SourceImpl; +} +function createSource(sourceData, thisMetaRawOption, sourceFormat) { + sourceFormat = sourceFormat || detectSourceFormat(sourceData); + var seriesLayoutBy = thisMetaRawOption.seriesLayoutBy; + var determined = determineSourceDimensions(sourceData, sourceFormat, seriesLayoutBy, thisMetaRawOption.sourceHeader, thisMetaRawOption.dimensions); + var source = new SourceImpl({ + data: sourceData, + sourceFormat, + seriesLayoutBy, + dimensionsDefine: determined.dimensionsDefine, + startIndex: determined.startIndex, + dimensionsDetectedCount: determined.dimensionsDetectedCount, + metaRawOption: clone$2(thisMetaRawOption) + }); + return source; +} +function createSourceFromSeriesDataOption(data) { + return new SourceImpl({ + data, + sourceFormat: isTypedArray(data) ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL + }); +} +function cloneSourceShallow(source) { + return new SourceImpl({ + data: source.data, + sourceFormat: source.sourceFormat, + seriesLayoutBy: source.seriesLayoutBy, + dimensionsDefine: clone$2(source.dimensionsDefine), + startIndex: source.startIndex, + dimensionsDetectedCount: source.dimensionsDetectedCount + }); +} +function detectSourceFormat(data) { + var sourceFormat = SOURCE_FORMAT_UNKNOWN; + if (isTypedArray(data)) { + sourceFormat = SOURCE_FORMAT_TYPED_ARRAY; + } else if (isArray(data)) { + 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) || isTypedArray(item)) { + sourceFormat = SOURCE_FORMAT_ARRAY_ROWS; + break; + } else if (isObject$2(item)) { + sourceFormat = SOURCE_FORMAT_OBJECT_ROWS; + break; + } + } + } else if (isObject$2(data)) { + for (var key in data) { + if (hasOwn(data, key) && isArrayLike(data[key])) { + sourceFormat = SOURCE_FORMAT_KEYED_COLUMNS; + break; + } + } + } + return sourceFormat; +} +function determineSourceDimensions(data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine) { + var dimensionsDetectedCount; + var startIndex; + if (!data) { + return { + dimensionsDefine: normalizeDimensionsOption(dimensionsDefine), + startIndex, + dimensionsDetectedCount + }; + } + if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) { + var dataArrayRows = data; + if (sourceHeader === "auto" || sourceHeader == null) { + arrayRowsTravelFirst(function(val) { + if (val != null && val !== "-") { + if (isString(val)) { + startIndex == null && (startIndex = 1); + } else { + startIndex = 0; + } + } + }, seriesLayoutBy, dataArrayRows, 10); + } else { + startIndex = isNumber(sourceHeader) ? sourceHeader : sourceHeader ? 1 : 0; + } + if (!dimensionsDefine && startIndex === 1) { + dimensionsDefine = []; + arrayRowsTravelFirst(function(val, index) { + dimensionsDefine[index] = val != null ? val + "" : ""; + }, seriesLayoutBy, dataArrayRows, Infinity); + } + dimensionsDetectedCount = dimensionsDefine ? dimensionsDefine.length : seriesLayoutBy === SERIES_LAYOUT_BY_ROW ? dataArrayRows.length : dataArrayRows[0] ? dataArrayRows[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$4(data, function(colArr, key) { + dimensionsDefine.push(key); + }); + } + } else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) { + var value0 = getDataItemValue(data[0]); + dimensionsDetectedCount = isArray(value0) && value0.length || 1; + } else ; + return { + startIndex, + dimensionsDefine: normalizeDimensionsOption(dimensionsDefine), + dimensionsDetectedCount + }; +} +function objectRowsCollectDimensions(data) { + var firstIndex = 0; + var obj; + while (firstIndex < data.length && !(obj = data[firstIndex++])) { + } + if (obj) { + return keys(obj); + } +} +function normalizeDimensionsOption(dimensionsDefine) { + if (!dimensionsDefine) { + return; + } + var nameMap = createHashMap(); + return map$1(dimensionsDefine, function(rawItem, index) { + rawItem = isObject$2(rawItem) ? rawItem : { + name: rawItem + }; + var item = { + name: rawItem.name, + displayName: rawItem.displayName, + type: rawItem.type + }; + if (item.name == null) { + return item; + } + item.name += ""; + 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) { + 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 shouldRetrieveDataByName(source) { + var sourceFormat = source.sourceFormat; + return sourceFormat === SOURCE_FORMAT_OBJECT_ROWS || sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS; +} + +var _a, _b, _c; +var providerMethods; +var mountMethods; +var DefaultDataProvider = ( + /** @class */ + function() { + function DefaultDataProvider2(sourceParam, dimSize) { + var source = !isSourceInstance(sourceParam) ? createSourceFromSeriesDataOption(sourceParam) : sourceParam; + this._source = source; + var data = this._data = source.data; + if (source.sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { + this._offset = 0; + this._dimSize = dimSize; + this._data = data; + } + mountMethods(this, data, source); + } + DefaultDataProvider2.prototype.getSource = function() { + return this._source; + }; + DefaultDataProvider2.prototype.count = function() { + return 0; + }; + DefaultDataProvider2.prototype.getItem = function(idx, out) { + return; + }; + DefaultDataProvider2.prototype.appendData = function(newData) { + }; + DefaultDataProvider2.prototype.clean = function() { + }; + DefaultDataProvider2.protoInitialize = function() { + var proto = DefaultDataProvider2.prototype; + proto.pure = false; + proto.persistent = true; + }(); + DefaultDataProvider2.internalField = function() { + var _a2; + mountMethods = function(provider, data, source) { + var sourceFormat = source.sourceFormat; + var seriesLayoutBy = source.seriesLayoutBy; + var startIndex = source.startIndex; + var dimsDef = source.dimensionsDefine; + var methods = providerMethods[getMethodMapKey(sourceFormat, seriesLayoutBy)]; + extend(provider, methods); + if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) { + provider.getItem = getItemForTypedArray; + provider.count = countForTypedArray; + provider.fillStorage = fillStorageForTypedArray; + } else { + var rawItemGetter = getRawSourceItemGetter(sourceFormat, seriesLayoutBy); + provider.getItem = bind$1(rawItemGetter, null, data, startIndex, dimsDef); + var rawCounter = getRawSourceDataCounter(sourceFormat, seriesLayoutBy); + provider.count = bind$1(rawCounter, null, data, startIndex, dimsDef); + } + }; + var getItemForTypedArray = function(idx, out) { + idx = idx - this._offset; + out = out || []; + var data = this._data; + var dimSize = this._dimSize; + var offset = dimSize * idx; + for (var i = 0; i < dimSize; i++) { + out[i] = data[offset + i]; + } + return out; + }; + var fillStorageForTypedArray = function(start, end, storage, extent) { + var data = this._data; + var dimSize = this._dimSize; + for (var dim = 0; dim < dimSize; dim++) { + var dimExtent = extent[dim]; + var min = dimExtent[0] == null ? Infinity : dimExtent[0]; + var max = dimExtent[1] == null ? -Infinity : dimExtent[1]; + var count = end - start; + var arr = storage[dim]; + for (var i = 0; i < count; i++) { + var val = data[i * dimSize + dim]; + arr[start + i] = val; + val < min && (min = val); + val > max && (max = val); + } + dimExtent[0] = min; + dimExtent[1] = max; + } + }; + var countForTypedArray = function() { + return this._data ? this._data.length / this._dimSize : 0; + }; + providerMethods = (_a2 = {}, _a2[SOURCE_FORMAT_ARRAY_ROWS + "_" + SERIES_LAYOUT_BY_COLUMN] = { + pure: true, + appendData: appendDataSimply + }, _a2[SOURCE_FORMAT_ARRAY_ROWS + "_" + SERIES_LAYOUT_BY_ROW] = { + pure: true, + appendData: function() { + throw new Error('Do not support appendData when set seriesLayoutBy: "row".'); + } + }, _a2[SOURCE_FORMAT_OBJECT_ROWS] = { + pure: true, + appendData: appendDataSimply + }, _a2[SOURCE_FORMAT_KEYED_COLUMNS] = { + pure: true, + appendData: function(newData) { + var data = this._data; + each$4(newData, function(newCol, key) { + var oldCol = data[key] || (data[key] = []); + for (var i = 0; i < (newCol || []).length; i++) { + oldCol.push(newCol[i]); + } + }); + } + }, _a2[SOURCE_FORMAT_ORIGINAL] = { + appendData: appendDataSimply + }, _a2[SOURCE_FORMAT_TYPED_ARRAY] = { + persistent: false, + pure: true, + appendData: function(newData) { + this._data = newData; + }, + // Clean self if data is already used. + clean: function() { + this._offset += this.count(); + this._data = null; + } + }, _a2); + function appendDataSimply(newData) { + for (var i = 0; i < newData.length; i++) { + this._data.push(newData[i]); + } + } + }(); + return DefaultDataProvider2; + }() +); +var getItemSimply = function(rawData, startIndex, dimsDef, idx) { + return rawData[idx]; +}; +var rawSourceItemGetterMap = (_a = {}, _a[SOURCE_FORMAT_ARRAY_ROWS + "_" + SERIES_LAYOUT_BY_COLUMN] = function(rawData, startIndex, dimsDef, idx) { + return rawData[idx + startIndex]; +}, _a[SOURCE_FORMAT_ARRAY_ROWS + "_" + SERIES_LAYOUT_BY_ROW] = function(rawData, startIndex, dimsDef, idx, out) { + idx += startIndex; + var item = out || []; + var data = rawData; + for (var i = 0; i < data.length; i++) { + var row = data[i]; + item[i] = row ? row[idx] : null; + } + return item; +}, _a[SOURCE_FORMAT_OBJECT_ROWS] = getItemSimply, _a[SOURCE_FORMAT_KEYED_COLUMNS] = function(rawData, startIndex, dimsDef, idx, out) { + var item = out || []; + for (var i = 0; i < dimsDef.length; i++) { + var dimName = dimsDef[i].name; + var col = rawData[dimName]; + item[i] = col ? col[idx] : null; + } + return item; +}, _a[SOURCE_FORMAT_ORIGINAL] = getItemSimply, _a); +function getRawSourceItemGetter(sourceFormat, seriesLayoutBy) { + var method = rawSourceItemGetterMap[getMethodMapKey(sourceFormat, seriesLayoutBy)]; + return method; +} +var countSimply = function(rawData, startIndex, dimsDef) { + return rawData.length; +}; +var rawSourceDataCounterMap = (_b = {}, _b[SOURCE_FORMAT_ARRAY_ROWS + "_" + SERIES_LAYOUT_BY_COLUMN] = function(rawData, startIndex, dimsDef) { + return Math.max(0, rawData.length - startIndex); +}, _b[SOURCE_FORMAT_ARRAY_ROWS + "_" + SERIES_LAYOUT_BY_ROW] = function(rawData, startIndex, dimsDef) { + var row = rawData[0]; + return row ? Math.max(0, row.length - startIndex) : 0; +}, _b[SOURCE_FORMAT_OBJECT_ROWS] = countSimply, _b[SOURCE_FORMAT_KEYED_COLUMNS] = function(rawData, startIndex, dimsDef) { + var dimName = dimsDef[0].name; + var col = rawData[dimName]; + return col ? col.length : 0; +}, _b[SOURCE_FORMAT_ORIGINAL] = countSimply, _b); +function getRawSourceDataCounter(sourceFormat, seriesLayoutBy) { + var method = rawSourceDataCounterMap[getMethodMapKey(sourceFormat, seriesLayoutBy)]; + return method; +} +var getRawValueSimply = function(dataItem, dimIndex, property) { + return dataItem[dimIndex]; +}; +var rawSourceValueGetterMap = (_c = {}, _c[SOURCE_FORMAT_ARRAY_ROWS] = getRawValueSimply, _c[SOURCE_FORMAT_OBJECT_ROWS] = function(dataItem, dimIndex, property) { + return dataItem[property]; +}, _c[SOURCE_FORMAT_KEYED_COLUMNS] = getRawValueSimply, _c[SOURCE_FORMAT_ORIGINAL] = function(dataItem, dimIndex, property) { + var value = getDataItemValue(dataItem); + return !(value instanceof Array) ? value : value[dimIndex]; +}, _c[SOURCE_FORMAT_TYPED_ARRAY] = getRawValueSimply, _c); +function getRawSourceValueGetter(sourceFormat) { + var method = rawSourceValueGetterMap[sourceFormat]; + return method; +} +function getMethodMapKey(sourceFormat, seriesLayoutBy) { + return sourceFormat === SOURCE_FORMAT_ARRAY_ROWS ? sourceFormat + "_" + seriesLayoutBy : sourceFormat; +} +function retrieveRawValue(data, dataIndex, dim) { + if (!data) { + return; + } + var dataItem = data.getRawDataItem(dataIndex); + if (dataItem == null) { + return; + } + var store = data.getStore(); + var sourceFormat = store.getSource().sourceFormat; + if (dim != null) { + var dimIndex = data.getDimensionIndex(dim); + var property = store.getDimensionProperty(dimIndex); + return getRawSourceValueGetter(sourceFormat)(dataItem, dimIndex, property); + } else { + var result = dataItem; + if (sourceFormat === SOURCE_FORMAT_ORIGINAL) { + result = getDataItemValue(dataItem); + } + return result; + } +} + +var DIMENSION_LABEL_REG = /\{@(.+?)\}/g; +var DataFormatMixin = ( + /** @class */ + function() { + function DataFormatMixin2() { + } + DataFormatMixin2.prototype.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 style = data.getItemVisual(dataIndex, "style"); + var color = style && style[data.getItemVisual(dataIndex, "drawType") || "fill"]; + var borderColor = style && style.stroke; + var mainType = this.mainType; + var isSeries = mainType === "series"; + var userOutput = data.userOutput && data.userOutput.get(); + 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, + dataIndex: rawDataIndex, + data: itemOpt, + dataType, + value: rawValue, + color, + borderColor, + dimensionNames: userOutput ? userOutput.fullDimensions : null, + encode: userOutput ? userOutput.encode : null, + // Param name list for mapping `a`, `b`, `c`, `d`, `e` + $vars: ["seriesName", "name", "value"] + }; + }; + DataFormatMixin2.prototype.getFormattedLabel = function(dataIndex, status, dataType, labelDimIndex, formatter, extendParams) { + status = status || "normal"; + var data = this.getData(dataType); + var params = this.getDataParams(dataIndex, dataType); + if (extendParams) { + params.value = extendParams.interpolatedValue; + } + if (labelDimIndex != null && isArray(params.value)) { + params.value = params.value[labelDimIndex]; + } + if (!formatter) { + var itemModel = data.getItemModel(dataIndex); + formatter = itemModel.get(status === "normal" ? ["label", "formatter"] : [status, "label", "formatter"]); + } + if (isFunction(formatter)) { + params.status = status; + params.dimensionIndex = labelDimIndex; + return formatter(params); + } else if (isString(formatter)) { + var str = formatTpl(formatter, params); + return str.replace(DIMENSION_LABEL_REG, function(origin, dimStr) { + var len = dimStr.length; + var dimLoose = dimStr; + if (dimLoose.charAt(0) === "[" && dimLoose.charAt(len - 1) === "]") { + dimLoose = +dimLoose.slice(1, len - 1); + } + var val = retrieveRawValue(data, dataIndex, dimLoose); + if (extendParams && isArray(extendParams.interpolatedValue)) { + var dimIndex = data.getDimensionIndex(dimLoose); + if (dimIndex >= 0) { + val = extendParams.interpolatedValue[dimIndex]; + } + } + return val != null ? val + "" : ""; + }); + } + }; + DataFormatMixin2.prototype.getRawValue = function(idx, dataType) { + return retrieveRawValue(this.getData(dataType), idx); + }; + DataFormatMixin2.prototype.formatTooltip = function(dataIndex, multipleSeries, dataType) { + return; + }; + return DataFormatMixin2; + }() +); +function normalizeTooltipFormatResult(result) { + var markupText; + var markupFragment; + if (isObject$2(result)) { + if (result.type) { + markupFragment = result; + } + } else { + markupText = result; + } + return { + text: markupText, + // markers: markers || markersExisting, + frag: markupFragment + }; +} + +function createTask(define) { + return new Task(define); +} +var Task = ( + /** @class */ + function() { + function Task2(define) { + define = define || {}; + this._reset = define.reset; + this._plan = define.plan; + this._count = define.count; + this._onDirty = define.onDirty; + this._dirty = true; + } + Task2.prototype.perform = function(performArgs) { + var upTask = this._upstream; + var skip = performArgs && performArgs.skip; + 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); + } + 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); + return val; + } + var forceFirstProgress; + if (this._dirty || planResult === "reset") { + this._dirty = false; + forceFirstProgress = this._doReset(skip); + } + this._modBy = modBy; + this._modDataCount = modDataCount; + var step = performArgs && performArgs.step; + if (upTask) { + this._dueEnd = upTask._outputDueEnd; + } else { + this._dueEnd = this._count ? this._count(this.context) : Infinity; + } + 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++) { + this._doProgress(progress[i], start, end, modBy, modDataCount); + } + } else { + this._doProgress(progress, start, end, modBy, modDataCount); + } + } + this._dueIndex = end; + var outputDueEnd = this._settedOutputEnd != null ? this._settedOutputEnd : end; + this._outputDueEnd = outputDueEnd; + } else { + this._dueIndex = this._outputDueEnd = this._settedOutputEnd != null ? this._settedOutputEnd : this._dueEnd; + } + return this.unfinished(); + }; + Task2.prototype.dirty = function() { + this._dirty = true; + this._onDirty && this._onDirty(this.context); + }; + Task2.prototype._doProgress = function(progress, start, end, modBy, modDataCount) { + iterator.reset(start, end, modBy, modDataCount); + this._callingProgress = progress; + this._callingProgress({ + start, + end, + count: end - start, + next: iterator.next + }, this.context); + }; + Task2.prototype._doReset = function(skip) { + this._dueIndex = this._outputDueEnd = this._dueEnd = 0; + this._settedOutputEnd = null; + var progress; + var forceFirstProgress; + if (!skip && this._reset) { + progress = this._reset(this.context); + if (progress && progress.progress) { + forceFirstProgress = progress.forceFirstProgress; + progress = progress.progress; + } + if (isArray(progress) && !progress.length) { + progress = null; + } + } + this._progress = progress; + this._modBy = this._modDataCount = null; + var downstream = this._downstream; + downstream && downstream.dirty(); + return forceFirstProgress; + }; + Task2.prototype.unfinished = function() { + return this._progress && this._dueIndex < this._dueEnd; + }; + Task2.prototype.pipe = function(downTask) { + if (this._downstream !== downTask || this._dirty) { + this._downstream = downTask; + downTask._upstream = this; + downTask.dirty(); + } + }; + Task2.prototype.dispose = function() { + if (this._disposed) { + return; + } + this._upstream && (this._upstream._downstream = null); + this._downstream && (this._downstream._upstream = null); + this._dirty = false; + this._disposed = true; + }; + Task2.prototype.getUpstream = function() { + return this._upstream; + }; + Task2.prototype.getDownstream = function() { + return this._downstream; + }; + Task2.prototype.setOutputEnd = function(end) { + this._outputDueEnd = this._settedOutputEnd = end; + }; + return Task2; + }() +); +var iterator = /* @__PURE__ */ 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 : current; + current++; + return result; + } +}(); + +function parseDataValue(value, opt) { + var dimType = opt && opt.type; + if (dimType === "ordinal") { + return value; + } + if (dimType === "time" && !isNumber(value) && value != null && value !== "-") { + value = +parseDate(value); + } + return value == null || value === "" ? NaN : Number(value); +} +createHashMap({ + "number": function(val) { + return parseFloat(val); + }, + "time": function(val) { + return +parseDate(val); + }, + "trim": function(val) { + return isString(val) ? trim(val) : val; + } +}); +var SortOrderComparator = ( + /** @class */ + function() { + function SortOrderComparator2(order, incomparable) { + var isDesc = order === "desc"; + this._resultLT = isDesc ? 1 : -1; + if (incomparable == null) { + incomparable = isDesc ? "min" : "max"; + } + this._incomparable = incomparable === "min" ? -Infinity : Infinity; + } + SortOrderComparator2.prototype.evaluate = function(lval, rval) { + var lvalFloat = isNumber(lval) ? lval : numericToNumber(lval); + var rvalFloat = isNumber(rval) ? rval : numericToNumber(rval); + var lvalNotNumeric = isNaN(lvalFloat); + var rvalNotNumeric = isNaN(rvalFloat); + if (lvalNotNumeric) { + lvalFloat = this._incomparable; + } + if (rvalNotNumeric) { + rvalFloat = this._incomparable; + } + if (lvalNotNumeric && rvalNotNumeric) { + var lvalIsStr = isString(lval); + var rvalIsStr = isString(rval); + if (lvalIsStr) { + lvalFloat = rvalIsStr ? lval : 0; + } + if (rvalIsStr) { + rvalFloat = lvalIsStr ? rval : 0; + } + } + return lvalFloat < rvalFloat ? this._resultLT : lvalFloat > rvalFloat ? -this._resultLT : 0; + }; + return SortOrderComparator2; + }() +); + +var ExternalSource = ( + /** @class */ + function() { + function ExternalSource2() { + } + ExternalSource2.prototype.getRawData = function() { + throw new Error("not supported"); + }; + ExternalSource2.prototype.getRawDataItem = function(dataIndex) { + throw new Error("not supported"); + }; + ExternalSource2.prototype.cloneRawData = function() { + return; + }; + ExternalSource2.prototype.getDimensionInfo = function(dim) { + return; + }; + ExternalSource2.prototype.cloneAllDimensionInfo = function() { + return; + }; + ExternalSource2.prototype.count = function() { + return; + }; + ExternalSource2.prototype.retrieveValue = function(dataIndex, dimIndex) { + return; + }; + ExternalSource2.prototype.retrieveValueFromItem = function(dataItem, dimIndex) { + return; + }; + ExternalSource2.prototype.convertValue = function(rawVal, dimInfo) { + return parseDataValue(rawVal, dimInfo); + }; + return ExternalSource2; + }() +); +function createExternalSource(internalSource, externalTransform) { + var extSource = new ExternalSource(); + var data = internalSource.data; + var sourceFormat = extSource.sourceFormat = internalSource.sourceFormat; + var sourceHeaderCount = internalSource.startIndex; + var errMsg = ""; + if (internalSource.seriesLayoutBy !== SERIES_LAYOUT_BY_COLUMN) { + throwError(errMsg); + } + var dimensions = []; + var dimsByName = {}; + var dimsDef = internalSource.dimensionsDefine; + if (dimsDef) { + each$4(dimsDef, function(dimDef, idx) { + var name = dimDef.name; + var dimDefExt = { + index: idx, + name, + displayName: dimDef.displayName + }; + dimensions.push(dimDefExt); + if (name != null) { + var errMsg_1 = ""; + if (hasOwn(dimsByName, name)) { + throwError(errMsg_1); + } + dimsByName[name] = dimDefExt; + } + }); + } else { + for (var i = 0; i < internalSource.dimensionsDetectedCount || 0; i++) { + dimensions.push({ + index: i + }); + } + } + var rawItemGetter = getRawSourceItemGetter(sourceFormat, SERIES_LAYOUT_BY_COLUMN); + if (externalTransform.__isBuiltIn) { + extSource.getRawDataItem = function(dataIndex) { + return rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex); + }; + extSource.getRawData = bind$1(getRawData, null, internalSource); + } + extSource.cloneRawData = bind$1(cloneRawData, null, internalSource); + var rawCounter = getRawSourceDataCounter(sourceFormat, SERIES_LAYOUT_BY_COLUMN); + extSource.count = bind$1(rawCounter, null, data, sourceHeaderCount, dimensions); + var rawValueGetter = getRawSourceValueGetter(sourceFormat); + extSource.retrieveValue = function(dataIndex, dimIndex) { + var rawItem = rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex); + return retrieveValueFromItem(rawItem, dimIndex); + }; + var retrieveValueFromItem = extSource.retrieveValueFromItem = function(dataItem, dimIndex) { + if (dataItem == null) { + return; + } + var dimDef = dimensions[dimIndex]; + if (dimDef) { + return rawValueGetter(dataItem, dimIndex, dimDef.name); + } + }; + extSource.getDimensionInfo = bind$1(getDimensionInfo, null, dimensions, dimsByName); + extSource.cloneAllDimensionInfo = bind$1(cloneAllDimensionInfo, null, dimensions); + return extSource; +} +function getRawData(upstream) { + var sourceFormat = upstream.sourceFormat; + if (!isSupportedSourceFormat(sourceFormat)) { + var errMsg = ""; + throwError(errMsg); + } + return upstream.data; +} +function cloneRawData(upstream) { + var sourceFormat = upstream.sourceFormat; + var data = upstream.data; + if (!isSupportedSourceFormat(sourceFormat)) { + var errMsg = ""; + throwError(errMsg); + } + if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) { + var result = []; + for (var i = 0, len = data.length; i < len; i++) { + result.push(data[i].slice()); + } + return result; + } else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) { + var result = []; + for (var i = 0, len = data.length; i < len; i++) { + result.push(extend({}, data[i])); + } + return result; + } +} +function getDimensionInfo(dimensions, dimsByName, dim) { + if (dim == null) { + return; + } + if (isNumber(dim) || !isNaN(dim) && !hasOwn(dimsByName, dim)) { + return dimensions[dim]; + } else if (hasOwn(dimsByName, dim)) { + return dimsByName[dim]; + } +} +function cloneAllDimensionInfo(dimensions) { + return clone$2(dimensions); +} +var externalTransformMap = createHashMap(); +function registerExternalTransform(externalTransform) { + externalTransform = clone$2(externalTransform); + var type = externalTransform.type; + var errMsg = ""; + if (!type) { + throwError(errMsg); + } + var typeParsed = type.split(":"); + if (typeParsed.length !== 2) { + throwError(errMsg); + } + var isBuiltIn = false; + if (typeParsed[0] === "echarts") { + type = typeParsed[1]; + isBuiltIn = true; + } + externalTransform.__isBuiltIn = isBuiltIn; + externalTransformMap.set(type, externalTransform); +} +function applyDataTransform(rawTransOption, sourceList, infoForPrint) { + var pipedTransOption = normalizeToArray(rawTransOption); + var pipeLen = pipedTransOption.length; + var errMsg = ""; + if (!pipeLen) { + throwError(errMsg); + } + for (var i = 0, len = pipeLen; i < len; i++) { + var transOption = pipedTransOption[i]; + sourceList = applySingleDataTransform(transOption, sourceList); + if (i !== len - 1) { + sourceList.length = Math.max(sourceList.length, 1); + } + } + return sourceList; +} +function applySingleDataTransform(transOption, upSourceList, infoForPrint, pipeIndex) { + var errMsg = ""; + if (!upSourceList.length) { + throwError(errMsg); + } + if (!isObject$2(transOption)) { + throwError(errMsg); + } + var transType = transOption.type; + var externalTransform = externalTransformMap.get(transType); + if (!externalTransform) { + throwError(errMsg); + } + var extUpSourceList = map$1(upSourceList, function(upSource) { + return createExternalSource(upSource, externalTransform); + }); + var resultList = normalizeToArray(externalTransform.transform({ + upstream: extUpSourceList[0], + upstreamList: extUpSourceList, + config: clone$2(transOption.config) + })); + return map$1(resultList, function(result, resultIndex) { + var errMsg2 = ""; + if (!isObject$2(result)) { + throwError(errMsg2); + } + if (!result.data) { + throwError(errMsg2); + } + var sourceFormat = detectSourceFormat(result.data); + if (!isSupportedSourceFormat(sourceFormat)) { + throwError(errMsg2); + } + var resultMetaRawOption; + var firstUpSource = upSourceList[0]; + if (firstUpSource && resultIndex === 0 && !result.dimensions) { + var startIndex = firstUpSource.startIndex; + if (startIndex) { + result.data = firstUpSource.data.slice(0, startIndex).concat(result.data); + } + resultMetaRawOption = { + seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN, + sourceHeader: startIndex, + dimensions: firstUpSource.metaRawOption.dimensions + }; + } else { + resultMetaRawOption = { + seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN, + sourceHeader: 0, + dimensions: result.dimensions + }; + } + return createSource(result.data, resultMetaRawOption, null); + }); +} +function isSupportedSourceFormat(sourceFormat) { + return sourceFormat === SOURCE_FORMAT_ARRAY_ROWS || sourceFormat === SOURCE_FORMAT_OBJECT_ROWS; +} + +var UNDEFINED = "undefined"; +var CtorUint32Array = typeof Uint32Array === UNDEFINED ? Array : Uint32Array; +var CtorUint16Array = typeof Uint16Array === UNDEFINED ? Array : Uint16Array; +var CtorInt32Array$1 = typeof Int32Array === UNDEFINED ? Array : Int32Array; +var CtorFloat64Array = typeof Float64Array === UNDEFINED ? Array : Float64Array; +var dataCtors = { + "float": CtorFloat64Array, + "int": CtorInt32Array$1, + // Ordinal data type can be string or int + "ordinal": Array, + "number": Array, + "time": CtorFloat64Array +}; +var defaultDimValueGetters; +function getIndicesCtor(rawCount) { + return rawCount > 65535 ? CtorUint32Array : CtorUint16Array; +} +function getInitialExtent() { + return [Infinity, -Infinity]; +} +function cloneChunk(originalChunk) { + var Ctor = originalChunk.constructor; + return Ctor === Array ? originalChunk.slice() : new Ctor(originalChunk); +} +function prepareStore(store, dimIdx, dimType, end, append) { + var DataCtor = dataCtors[dimType || "float"]; + if (append) { + var oldStore = store[dimIdx]; + var oldLen = oldStore && oldStore.length; + if (!(oldLen === end)) { + var newStore = new DataCtor(end); + for (var j = 0; j < oldLen; j++) { + newStore[j] = oldStore[j]; + } + store[dimIdx] = newStore; + } + } else { + store[dimIdx] = new DataCtor(end); + } +} +var DataStore = ( + /** @class */ + function() { + function DataStore2() { + this._chunks = []; + this._rawExtent = []; + this._extent = []; + this._count = 0; + this._rawCount = 0; + this._calcDimNameToIdx = createHashMap(); + } + DataStore2.prototype.initData = function(provider, inputDimensions, dimValueGetter) { + this._provider = provider; + this._chunks = []; + this._indices = null; + this.getRawIndex = this._getRawIdxIdentity; + var source = provider.getSource(); + var defaultGetter = this.defaultDimValueGetter = defaultDimValueGetters[source.sourceFormat]; + this._dimValueGetter = dimValueGetter || defaultGetter; + this._rawExtent = []; + shouldRetrieveDataByName(source); + this._dimensions = map$1(inputDimensions, function(dim) { + return { + // Only pick these two props. Not leak other properties like orderMeta. + type: dim.type, + property: dim.property + }; + }); + this._initDataFromProvider(0, provider.count()); + }; + DataStore2.prototype.getProvider = function() { + return this._provider; + }; + DataStore2.prototype.getSource = function() { + return this._provider.getSource(); + }; + DataStore2.prototype.ensureCalculationDimension = function(dimName, type) { + var calcDimNameToIdx = this._calcDimNameToIdx; + var dimensions = this._dimensions; + var calcDimIdx = calcDimNameToIdx.get(dimName); + if (calcDimIdx != null) { + if (dimensions[calcDimIdx].type === type) { + return calcDimIdx; + } + } else { + calcDimIdx = dimensions.length; + } + dimensions[calcDimIdx] = { + type + }; + calcDimNameToIdx.set(dimName, calcDimIdx); + this._chunks[calcDimIdx] = new dataCtors[type || "float"](this._rawCount); + this._rawExtent[calcDimIdx] = getInitialExtent(); + return calcDimIdx; + }; + DataStore2.prototype.collectOrdinalMeta = function(dimIdx, ordinalMeta) { + var chunk = this._chunks[dimIdx]; + var dim = this._dimensions[dimIdx]; + var rawExtents = this._rawExtent; + var offset = dim.ordinalOffset || 0; + var len = chunk.length; + if (offset === 0) { + rawExtents[dimIdx] = getInitialExtent(); + } + var dimRawExtent = rawExtents[dimIdx]; + for (var i = offset; i < len; i++) { + var val = chunk[i] = ordinalMeta.parseAndCollect(chunk[i]); + if (!isNaN(val)) { + dimRawExtent[0] = Math.min(val, dimRawExtent[0]); + dimRawExtent[1] = Math.max(val, dimRawExtent[1]); + } + } + dim.ordinalMeta = ordinalMeta; + dim.ordinalOffset = len; + dim.type = "ordinal"; + }; + DataStore2.prototype.getOrdinalMeta = function(dimIdx) { + var dimInfo = this._dimensions[dimIdx]; + var ordinalMeta = dimInfo.ordinalMeta; + return ordinalMeta; + }; + DataStore2.prototype.getDimensionProperty = function(dimIndex) { + var item = this._dimensions[dimIndex]; + return item && item.property; + }; + DataStore2.prototype.appendData = function(data) { + var provider = this._provider; + var start = this.count(); + provider.appendData(data); + var end = provider.count(); + if (!provider.persistent) { + end += start; + } + if (start < end) { + this._initDataFromProvider(start, end, true); + } + return [start, end]; + }; + DataStore2.prototype.appendValues = function(values, minFillLen) { + var chunks = this._chunks; + var dimensions = this._dimensions; + var dimLen = dimensions.length; + var rawExtent = this._rawExtent; + var start = this.count(); + var end = start + Math.max(values.length, minFillLen || 0); + for (var i = 0; i < dimLen; i++) { + var dim = dimensions[i]; + prepareStore(chunks, i, dim.type, end, true); + } + var emptyDataItem = []; + for (var idx = start; idx < end; idx++) { + var sourceIdx = idx - start; + for (var dimIdx = 0; dimIdx < dimLen; dimIdx++) { + var dim = dimensions[dimIdx]; + var val = defaultDimValueGetters.arrayRows.call(this, values[sourceIdx] || emptyDataItem, dim.property, sourceIdx, dimIdx); + chunks[dimIdx][idx] = val; + var dimRawExtent = rawExtent[dimIdx]; + val < dimRawExtent[0] && (dimRawExtent[0] = val); + val > dimRawExtent[1] && (dimRawExtent[1] = val); + } + } + this._rawCount = this._count = end; + return { + start, + end + }; + }; + DataStore2.prototype._initDataFromProvider = function(start, end, append) { + var provider = this._provider; + var chunks = this._chunks; + var dimensions = this._dimensions; + var dimLen = dimensions.length; + var rawExtent = this._rawExtent; + var dimNames = map$1(dimensions, function(dim2) { + return dim2.property; + }); + for (var i = 0; i < dimLen; i++) { + var dim = dimensions[i]; + if (!rawExtent[i]) { + rawExtent[i] = getInitialExtent(); + } + prepareStore(chunks, i, dim.type, end, append); + } + if (provider.fillStorage) { + provider.fillStorage(start, end, chunks, rawExtent); + } else { + var dataItem = []; + for (var idx = start; idx < end; idx++) { + dataItem = provider.getItem(idx, dataItem); + for (var dimIdx = 0; dimIdx < dimLen; dimIdx++) { + var dimStorage = chunks[dimIdx]; + var val = this._dimValueGetter(dataItem, dimNames[dimIdx], idx, dimIdx); + dimStorage[idx] = val; + var dimRawExtent = rawExtent[dimIdx]; + val < dimRawExtent[0] && (dimRawExtent[0] = val); + val > dimRawExtent[1] && (dimRawExtent[1] = val); + } + } + } + if (!provider.persistent && provider.clean) { + provider.clean(); + } + this._rawCount = this._count = end; + this._extent = []; + }; + DataStore2.prototype.count = function() { + return this._count; + }; + DataStore2.prototype.get = function(dim, idx) { + if (!(idx >= 0 && idx < this._count)) { + return NaN; + } + var dimStore = this._chunks[dim]; + return dimStore ? dimStore[this.getRawIndex(idx)] : NaN; + }; + DataStore2.prototype.getValues = function(dimensions, idx) { + var values = []; + var dimArr = []; + if (idx == null) { + idx = dimensions; + dimensions = []; + for (var i = 0; i < this._dimensions.length; i++) { + dimArr.push(i); + } + } else { + dimArr = dimensions; + } + for (var i = 0, len = dimArr.length; i < len; i++) { + values.push(this.get(dimArr[i], idx)); + } + return values; + }; + DataStore2.prototype.getByRawIndex = function(dim, rawIdx) { + if (!(rawIdx >= 0 && rawIdx < this._rawCount)) { + return NaN; + } + var dimStore = this._chunks[dim]; + return dimStore ? dimStore[rawIdx] : NaN; + }; + DataStore2.prototype.getSum = function(dim) { + var dimData = this._chunks[dim]; + var sum = 0; + if (dimData) { + for (var i = 0, len = this.count(); i < len; i++) { + var value = this.get(dim, i); + if (!isNaN(value)) { + sum += value; + } + } + } + return sum; + }; + DataStore2.prototype.getMedian = function(dim) { + var dimDataArray = []; + this.each([dim], function(val) { + if (!isNaN(val)) { + dimDataArray.push(val); + } + }); + var sortedDimDataArray = dimDataArray.sort(function(a, b) { + return a - b; + }); + var len = this.count(); + return len === 0 ? 0 : len % 2 === 1 ? sortedDimDataArray[(len - 1) / 2] : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2; + }; + DataStore2.prototype.indexOfRawIndex = function(rawIndex) { + if (rawIndex >= this._rawCount || rawIndex < 0) { + return -1; + } + if (!this._indices) { + return rawIndex; + } + var indices = this._indices; + 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; + }; + DataStore2.prototype.indicesOfNearest = function(dim, value, maxDistance) { + var chunks = this._chunks; + var dimData = chunks[dim]; + var nearestIndices = []; + if (!dimData) { + return nearestIndices; + } + if (maxDistance == null) { + maxDistance = Infinity; + } + var minDist = Infinity; + var minDiff = -1; + var nearestIndicesLen = 0; + for (var i = 0, len = this.count(); i < len; i++) { + var dataIndex = this.getRawIndex(i); + var diff = value - dimData[dataIndex]; + var dist = Math.abs(diff); + if (dist <= maxDistance) { + 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; + }; + DataStore2.prototype.getIndices = function() { + var newIndices; + var indices = this._indices; + if (indices) { + var Ctor = indices.constructor; + var thisCount = this._count; + 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._rawCount); + newIndices = new Ctor(this.count()); + for (var i = 0; i < newIndices.length; i++) { + newIndices[i] = i; + } + } + return newIndices; + }; + DataStore2.prototype.filter = function(dims, cb) { + if (!this._count) { + return this; + } + var newStore = this.clone(); + var count = newStore.count(); + var Ctor = getIndicesCtor(newStore._rawCount); + var newIndices = new Ctor(count); + var value = []; + var dimSize = dims.length; + var offset = 0; + var dim0 = dims[0]; + var chunks = newStore._chunks; + for (var i = 0; i < count; i++) { + var keep = void 0; + var rawIdx = newStore.getRawIndex(i); + if (dimSize === 0) { + keep = cb(i); + } else if (dimSize === 1) { + var val = chunks[dim0][rawIdx]; + keep = cb(val, i); + } else { + var k = 0; + for (; k < dimSize; k++) { + value[k] = chunks[dims[k]][rawIdx]; + } + value[k] = i; + keep = cb.apply(null, value); + } + if (keep) { + newIndices[offset++] = rawIdx; + } + } + if (offset < count) { + newStore._indices = newIndices; + } + newStore._count = offset; + newStore._extent = []; + newStore._updateGetRawIdx(); + return newStore; + }; + DataStore2.prototype.selectRange = function(range) { + var newStore = this.clone(); + var len = newStore._count; + if (!len) { + return this; + } + var dims = keys(range); + var dimSize = dims.length; + if (!dimSize) { + return this; + } + var originalCount = newStore.count(); + var Ctor = getIndicesCtor(newStore._rawCount); + var newIndices = new Ctor(originalCount); + var offset = 0; + var dim0 = dims[0]; + var min = range[dim0][0]; + var max = range[dim0][1]; + var storeArr = newStore._chunks; + var quickFinished = false; + if (!newStore._indices) { + var idx = 0; + if (dimSize === 1) { + var dimStorage = storeArr[dims[0]]; + for (var i = 0; i < len; i++) { + var val = dimStorage[i]; + if (val >= min && val <= max || isNaN(val)) { + newIndices[offset++] = idx; + } + idx++; + } + quickFinished = true; + } else if (dimSize === 2) { + var dimStorage = storeArr[dims[0]]; + var dimStorage2 = storeArr[dims[1]]; + var min2 = range[dims[1]][0]; + var max2 = range[dims[1]][1]; + for (var i = 0; i < len; i++) { + var val = dimStorage[i]; + var val2 = dimStorage2[i]; + 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 = newStore.getRawIndex(i); + var val = storeArr[dims[0]][rawIndex]; + if (val >= min && val <= max || isNaN(val)) { + newIndices[offset++] = rawIndex; + } + } + } else { + for (var i = 0; i < originalCount; i++) { + var keep = true; + var rawIndex = newStore.getRawIndex(i); + for (var k = 0; k < dimSize; k++) { + var dimk = dims[k]; + var val = storeArr[dimk][rawIndex]; + if (val < range[dimk][0] || val > range[dimk][1]) { + keep = false; + } + } + if (keep) { + newIndices[offset++] = newStore.getRawIndex(i); + } + } + } + } + if (offset < originalCount) { + newStore._indices = newIndices; + } + newStore._count = offset; + newStore._extent = []; + newStore._updateGetRawIdx(); + return newStore; + }; + DataStore2.prototype.map = function(dims, cb) { + var target = this.clone(dims); + this._updateDims(target, dims, cb); + return target; + }; + DataStore2.prototype.modify = function(dims, cb) { + this._updateDims(this, dims, cb); + }; + DataStore2.prototype._updateDims = function(target, dims, cb) { + var targetChunks = target._chunks; + var tmpRetValue = []; + var dimSize = dims.length; + var dataCount = target.count(); + var values = []; + var rawExtent = target._rawExtent; + for (var i = 0; i < dims.length; i++) { + rawExtent[dims[i]] = getInitialExtent(); + } + for (var dataIndex = 0; dataIndex < dataCount; dataIndex++) { + var rawIndex = target.getRawIndex(dataIndex); + for (var k = 0; k < dimSize; k++) { + values[k] = targetChunks[dims[k]][rawIndex]; + } + values[dimSize] = dataIndex; + var retValue = cb && cb.apply(null, values); + if (retValue != null) { + if (typeof retValue !== "object") { + tmpRetValue[0] = retValue; + retValue = tmpRetValue; + } + for (var i = 0; i < retValue.length; i++) { + var dim = dims[i]; + var val = retValue[i]; + var rawExtentOnDim = rawExtent[dim]; + var dimStore = targetChunks[dim]; + if (dimStore) { + dimStore[rawIndex] = val; + } + if (val < rawExtentOnDim[0]) { + rawExtentOnDim[0] = val; + } + if (val > rawExtentOnDim[1]) { + rawExtentOnDim[1] = val; + } + } + } + } + }; + DataStore2.prototype.lttbDownSample = function(valueDimension, rate) { + var target = this.clone([valueDimension], true); + var targetStorage = target._chunks; + var dimStore = targetStorage[valueDimension]; + var len = this.count(); + var sampledIndex = 0; + var frameSize = Math.floor(1 / rate); + var currentRawIndex = this.getRawIndex(0); + var maxArea; + var area; + var nextRawIndex; + var newIndices = new (getIndicesCtor(this._rawCount))(Math.min((Math.ceil(len / frameSize) + 2) * 2, len)); + newIndices[sampledIndex++] = currentRawIndex; + for (var i = 1; i < len - 1; i += frameSize) { + var nextFrameStart = Math.min(i + frameSize, len - 1); + var nextFrameEnd = Math.min(i + frameSize * 2, len); + var avgX = (nextFrameEnd + nextFrameStart) / 2; + var avgY = 0; + for (var idx = nextFrameStart; idx < nextFrameEnd; idx++) { + var rawIndex = this.getRawIndex(idx); + var y = dimStore[rawIndex]; + if (isNaN(y)) { + continue; + } + avgY += y; + } + avgY /= nextFrameEnd - nextFrameStart; + var frameStart = i; + var frameEnd = Math.min(i + frameSize, len); + var pointAX = i - 1; + var pointAY = dimStore[currentRawIndex]; + maxArea = -1; + nextRawIndex = frameStart; + var firstNaNIndex = -1; + var countNaN = 0; + for (var idx = frameStart; idx < frameEnd; idx++) { + var rawIndex = this.getRawIndex(idx); + var y = dimStore[rawIndex]; + if (isNaN(y)) { + countNaN++; + if (firstNaNIndex < 0) { + firstNaNIndex = rawIndex; + } + continue; + } + area = Math.abs((pointAX - avgX) * (y - pointAY) - (pointAX - idx) * (avgY - pointAY)); + if (area > maxArea) { + maxArea = area; + nextRawIndex = rawIndex; + } + } + if (countNaN > 0 && countNaN < frameEnd - frameStart) { + newIndices[sampledIndex++] = Math.min(firstNaNIndex, nextRawIndex); + nextRawIndex = Math.max(firstNaNIndex, nextRawIndex); + } + newIndices[sampledIndex++] = nextRawIndex; + currentRawIndex = nextRawIndex; + } + newIndices[sampledIndex++] = this.getRawIndex(len - 1); + target._count = sampledIndex; + target._indices = newIndices; + target.getRawIndex = this._getRawIdx; + return target; + }; + DataStore2.prototype.minmaxDownSample = function(valueDimension, rate) { + var target = this.clone([valueDimension], true); + var targetStorage = target._chunks; + var frameSize = Math.floor(1 / rate); + var dimStore = targetStorage[valueDimension]; + var len = this.count(); + var newIndices = new (getIndicesCtor(this._rawCount))(Math.ceil(len / frameSize) * 2); + var offset = 0; + for (var i = 0; i < len; i += frameSize) { + var minIndex = i; + var minValue = dimStore[this.getRawIndex(minIndex)]; + var maxIndex = i; + var maxValue = dimStore[this.getRawIndex(maxIndex)]; + var thisFrameSize = frameSize; + if (i + frameSize > len) { + thisFrameSize = len - i; + } + for (var k = 0; k < thisFrameSize; k++) { + var rawIndex = this.getRawIndex(i + k); + var value = dimStore[rawIndex]; + if (value < minValue) { + minValue = value; + minIndex = i + k; + } + if (value > maxValue) { + maxValue = value; + maxIndex = i + k; + } + } + var rawMinIndex = this.getRawIndex(minIndex); + var rawMaxIndex = this.getRawIndex(maxIndex); + if (minIndex < maxIndex) { + newIndices[offset++] = rawMinIndex; + newIndices[offset++] = rawMaxIndex; + } else { + newIndices[offset++] = rawMaxIndex; + newIndices[offset++] = rawMinIndex; + } + } + target._count = offset; + target._indices = newIndices; + target._updateGetRawIdx(); + return target; + }; + DataStore2.prototype.downSample = function(dimension, rate, sampleValue, sampleIndex) { + var target = this.clone([dimension], true); + var targetStorage = target._chunks; + var frameValues = []; + var frameSize = Math.floor(1 / rate); + var dimStore = targetStorage[dimension]; + var len = this.count(); + var rawExtentOnDim = target._rawExtent[dimension] = getInitialExtent(); + var newIndices = new (getIndicesCtor(this._rawCount))(Math.ceil(len / frameSize)); + var offset = 0; + for (var i = 0; i < len; i += frameSize) { + if (frameSize > len - i) { + frameSize = len - i; + frameValues.length = frameSize; + } + for (var k = 0; k < frameSize; k++) { + var dataIdx = this.getRawIndex(i + k); + frameValues[k] = dimStore[dataIdx]; + } + var value = sampleValue(frameValues); + var sampleFrameIdx = this.getRawIndex(Math.min(i + sampleIndex(frameValues, value) || 0, len - 1)); + dimStore[sampleFrameIdx] = value; + if (value < rawExtentOnDim[0]) { + rawExtentOnDim[0] = value; + } + if (value > rawExtentOnDim[1]) { + rawExtentOnDim[1] = value; + } + newIndices[offset++] = sampleFrameIdx; + } + target._count = offset; + target._indices = newIndices; + target._updateGetRawIdx(); + return target; + }; + DataStore2.prototype.each = function(dims, cb) { + if (!this._count) { + return; + } + var dimSize = dims.length; + var chunks = this._chunks; + for (var i = 0, len = this.count(); i < len; i++) { + var rawIdx = this.getRawIndex(i); + switch (dimSize) { + case 0: + cb(i); + break; + case 1: + cb(chunks[dims[0]][rawIdx], i); + break; + case 2: + cb(chunks[dims[0]][rawIdx], chunks[dims[1]][rawIdx], i); + break; + default: + var k = 0; + var value = []; + for (; k < dimSize; k++) { + value[k] = chunks[dims[k]][rawIdx]; + } + value[k] = i; + cb.apply(null, value); + } + } + }; + DataStore2.prototype.getDataExtent = function(dim) { + var dimData = this._chunks[dim]; + var initialExtent = getInitialExtent(); + if (!dimData) { + return initialExtent; + } + var currEnd = this.count(); + var useRaw = !this._indices; + 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 rawIdx = this.getRawIndex(i); + var value = dimData[rawIdx]; + value < min && (min = value); + value > max && (max = value); + } + dimExtent = [min, max]; + this._extent[dim] = dimExtent; + return dimExtent; + }; + DataStore2.prototype.getRawDataItem = function(idx) { + var rawIdx = this.getRawIndex(idx); + if (!this._provider.persistent) { + var val = []; + var chunks = this._chunks; + for (var i = 0; i < chunks.length; i++) { + val.push(chunks[i][rawIdx]); + } + return val; + } else { + return this._provider.getItem(rawIdx); + } + }; + DataStore2.prototype.clone = function(clonedDims, ignoreIndices) { + var target = new DataStore2(); + var chunks = this._chunks; + var clonedDimsMap = clonedDims && reduce(clonedDims, function(obj, dimIdx) { + obj[dimIdx] = true; + return obj; + }, {}); + if (clonedDimsMap) { + for (var i = 0; i < chunks.length; i++) { + target._chunks[i] = !clonedDimsMap[i] ? chunks[i] : cloneChunk(chunks[i]); + } + } else { + target._chunks = chunks; + } + this._copyCommonProps(target); + if (!ignoreIndices) { + target._indices = this._cloneIndices(); + } + target._updateGetRawIdx(); + return target; + }; + DataStore2.prototype._copyCommonProps = function(target) { + target._count = this._count; + target._rawCount = this._rawCount; + target._provider = this._provider; + target._dimensions = this._dimensions; + target._extent = clone$2(this._extent); + target._rawExtent = clone$2(this._rawExtent); + }; + DataStore2.prototype._cloneIndices = function() { + if (this._indices) { + var Ctor = this._indices.constructor; + var indices = void 0; + if (Ctor === Array) { + var thisCount = this._indices.length; + indices = new Ctor(thisCount); + for (var i = 0; i < thisCount; i++) { + indices[i] = this._indices[i]; + } + } else { + indices = new Ctor(this._indices); + } + return indices; + } + return null; + }; + DataStore2.prototype._getRawIdxIdentity = function(idx) { + return idx; + }; + DataStore2.prototype._getRawIdx = function(idx) { + if (idx < this._count && idx >= 0) { + return this._indices[idx]; + } + return -1; + }; + DataStore2.prototype._updateGetRawIdx = function() { + this.getRawIndex = this._indices ? this._getRawIdx : this._getRawIdxIdentity; + }; + DataStore2.internalField = function() { + function getDimValueSimply(dataItem, property, dataIndex, dimIndex) { + return parseDataValue(dataItem[dimIndex], this._dimensions[dimIndex]); + } + defaultDimValueGetters = { + arrayRows: getDimValueSimply, + objectRows: function(dataItem, property, dataIndex, dimIndex) { + return parseDataValue(dataItem[property], this._dimensions[dimIndex]); + }, + keyedColumns: getDimValueSimply, + original: function(dataItem, property, dataIndex, dimIndex) { + var value = dataItem && (dataItem.value == null ? dataItem : dataItem.value); + return parseDataValue(value instanceof Array ? value[dimIndex] : value, this._dimensions[dimIndex]); + }, + typedArray: function(dataItem, property, dataIndex, dimIndex) { + return dataItem[dimIndex]; + } + }; + }(); + return DataStore2; + }() +); + +var SourceManager = ( + /** @class */ + function() { + function SourceManager2(sourceHost) { + this._sourceList = []; + this._storeList = []; + this._upstreamSignList = []; + this._versionSignBase = 0; + this._dirty = true; + this._sourceHost = sourceHost; + } + SourceManager2.prototype.dirty = function() { + this._setLocalSource([], []); + this._storeList = []; + this._dirty = true; + }; + SourceManager2.prototype._setLocalSource = function(sourceList, upstreamSignList) { + this._sourceList = sourceList; + this._upstreamSignList = upstreamSignList; + this._versionSignBase++; + if (this._versionSignBase > 9e10) { + this._versionSignBase = 0; + } + }; + SourceManager2.prototype._getVersionSign = function() { + return this._sourceHost.uid + "_" + this._versionSignBase; + }; + SourceManager2.prototype.prepareSource = function() { + if (this._isDirty()) { + this._createSource(); + this._dirty = false; + } + }; + SourceManager2.prototype._createSource = function() { + this._setLocalSource([], []); + var sourceHost = this._sourceHost; + var upSourceMgrList = this._getUpstreamSourceManagers(); + var hasUpstream = !!upSourceMgrList.length; + var resultSourceList; + var upstreamSignList; + if (isSeries(sourceHost)) { + var seriesModel = sourceHost; + var data = void 0; + var sourceFormat = void 0; + var upSource = void 0; + if (hasUpstream) { + var upSourceMgr = upSourceMgrList[0]; + upSourceMgr.prepareSource(); + upSource = upSourceMgr.getSource(); + data = upSource.data; + sourceFormat = upSource.sourceFormat; + upstreamSignList = [upSourceMgr._getVersionSign()]; + } else { + data = seriesModel.get("data", true); + sourceFormat = isTypedArray(data) ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL; + upstreamSignList = []; + } + var newMetaRawOption = this._getSourceMetaRawOption() || {}; + var upMetaRawOption = upSource && upSource.metaRawOption || {}; + var seriesLayoutBy = retrieve2(newMetaRawOption.seriesLayoutBy, upMetaRawOption.seriesLayoutBy) || null; + var sourceHeader = retrieve2(newMetaRawOption.sourceHeader, upMetaRawOption.sourceHeader); + var dimensions = retrieve2(newMetaRawOption.dimensions, upMetaRawOption.dimensions); + var needsCreateSource = seriesLayoutBy !== upMetaRawOption.seriesLayoutBy || !!sourceHeader !== !!upMetaRawOption.sourceHeader || dimensions; + resultSourceList = needsCreateSource ? [createSource(data, { + seriesLayoutBy, + sourceHeader, + dimensions + }, sourceFormat)] : []; + } else { + var datasetModel = sourceHost; + if (hasUpstream) { + var result = this._applyTransform(upSourceMgrList); + resultSourceList = result.sourceList; + upstreamSignList = result.upstreamSignList; + } else { + var sourceData = datasetModel.get("source", true); + resultSourceList = [createSource(sourceData, this._getSourceMetaRawOption(), null)]; + upstreamSignList = []; + } + } + this._setLocalSource(resultSourceList, upstreamSignList); + }; + SourceManager2.prototype._applyTransform = function(upMgrList) { + var datasetModel = this._sourceHost; + var transformOption = datasetModel.get("transform", true); + var fromTransformResult = datasetModel.get("fromTransformResult", true); + if (fromTransformResult != null) { + var errMsg = ""; + if (upMgrList.length !== 1) { + doThrow(errMsg); + } + } + var sourceList; + var upSourceList = []; + var upstreamSignList = []; + each$4(upMgrList, function(upMgr) { + upMgr.prepareSource(); + var upSource = upMgr.getSource(fromTransformResult || 0); + var errMsg2 = ""; + if (fromTransformResult != null && !upSource) { + doThrow(errMsg2); + } + upSourceList.push(upSource); + upstreamSignList.push(upMgr._getVersionSign()); + }); + if (transformOption) { + sourceList = applyDataTransform(transformOption, upSourceList, { + datasetIndex: datasetModel.componentIndex + }); + } else if (fromTransformResult != null) { + sourceList = [cloneSourceShallow(upSourceList[0])]; + } + return { + sourceList, + upstreamSignList + }; + }; + SourceManager2.prototype._isDirty = function() { + if (this._dirty) { + return true; + } + var upSourceMgrList = this._getUpstreamSourceManagers(); + for (var i = 0; i < upSourceMgrList.length; i++) { + var upSrcMgr = upSourceMgrList[i]; + if ( + // Consider the case that there is ancestor diry, call it recursively. + // The performance is probably not an issue because usually the chain is not long. + upSrcMgr._isDirty() || this._upstreamSignList[i] !== upSrcMgr._getVersionSign() + ) { + return true; + } + } + }; + SourceManager2.prototype.getSource = function(sourceIndex) { + sourceIndex = sourceIndex || 0; + var source = this._sourceList[sourceIndex]; + if (!source) { + var upSourceMgrList = this._getUpstreamSourceManagers(); + return upSourceMgrList[0] && upSourceMgrList[0].getSource(sourceIndex); + } + return source; + }; + SourceManager2.prototype.getSharedDataStore = function(seriesDimRequest) { + var schema = seriesDimRequest.makeStoreSchema(); + return this._innerGetDataStore(schema.dimensions, seriesDimRequest.source, schema.hash); + }; + SourceManager2.prototype._innerGetDataStore = function(storeDims, seriesSource, sourceReadKey) { + var sourceIndex = 0; + var storeList = this._storeList; + var cachedStoreMap = storeList[sourceIndex]; + if (!cachedStoreMap) { + cachedStoreMap = storeList[sourceIndex] = {}; + } + var cachedStore = cachedStoreMap[sourceReadKey]; + if (!cachedStore) { + var upSourceMgr = this._getUpstreamSourceManagers()[0]; + if (isSeries(this._sourceHost) && upSourceMgr) { + cachedStore = upSourceMgr._innerGetDataStore(storeDims, seriesSource, sourceReadKey); + } else { + cachedStore = new DataStore(); + cachedStore.initData(new DefaultDataProvider(seriesSource, storeDims.length), storeDims); + } + cachedStoreMap[sourceReadKey] = cachedStore; + } + return cachedStore; + }; + SourceManager2.prototype._getUpstreamSourceManagers = function() { + var sourceHost = this._sourceHost; + if (isSeries(sourceHost)) { + var datasetModel = querySeriesUpstreamDatasetModel(sourceHost); + return !datasetModel ? [] : [datasetModel.getSourceManager()]; + } else { + return map$1(queryDatasetUpstreamDatasetModels(sourceHost), function(datasetModel2) { + return datasetModel2.getSourceManager(); + }); + } + }; + SourceManager2.prototype._getSourceMetaRawOption = function() { + var sourceHost = this._sourceHost; + var seriesLayoutBy; + var sourceHeader; + var dimensions; + if (isSeries(sourceHost)) { + seriesLayoutBy = sourceHost.get("seriesLayoutBy", true); + sourceHeader = sourceHost.get("sourceHeader", true); + dimensions = sourceHost.get("dimensions", true); + } else if (!this._getUpstreamSourceManagers().length) { + var model = sourceHost; + seriesLayoutBy = model.get("seriesLayoutBy", true); + sourceHeader = model.get("sourceHeader", true); + dimensions = model.get("dimensions", true); + } + return { + seriesLayoutBy, + sourceHeader, + dimensions + }; + }; + return SourceManager2; + }() +); +function isSeries(sourceHost) { + return sourceHost.mainType === "series"; +} +function doThrow(errMsg) { + throw new Error(errMsg); +} + +var TOOLTIP_LINE_HEIGHT_CSS = "line-height:1"; +function getTooltipLineHeight(textStyle) { + var lineHeight = textStyle.lineHeight; + if (lineHeight == null) { + return TOOLTIP_LINE_HEIGHT_CSS; + } else { + return "line-height:" + encodeHTML(lineHeight + "") + "px"; + } +} +function getTooltipTextStyle(textStyle, renderMode) { + var nameFontColor = textStyle.color || "#6e7079"; + var nameFontSize = textStyle.fontSize || 12; + var nameFontWeight = textStyle.fontWeight || "400"; + var valueFontColor = textStyle.color || "#464646"; + var valueFontSize = textStyle.fontSize || 14; + var valueFontWeight = textStyle.fontWeight || "900"; + if (renderMode === "html") { + return { + // eslint-disable-next-line max-len + nameStyle: "font-size:" + encodeHTML(nameFontSize + "") + "px;color:" + encodeHTML(nameFontColor) + ";font-weight:" + encodeHTML(nameFontWeight + ""), + // eslint-disable-next-line max-len + valueStyle: "font-size:" + encodeHTML(valueFontSize + "") + "px;color:" + encodeHTML(valueFontColor) + ";font-weight:" + encodeHTML(valueFontWeight + "") + }; + } else { + return { + nameStyle: { + fontSize: nameFontSize, + fill: nameFontColor, + fontWeight: nameFontWeight + }, + valueStyle: { + fontSize: valueFontSize, + fill: valueFontColor, + fontWeight: valueFontWeight + } + }; + } +} +var HTML_GAPS = [0, 10, 20, 30]; +var RICH_TEXT_GAPS = ["", "\n", "\n\n", "\n\n\n"]; +function createTooltipMarkup(type, option) { + option.type = type; + return option; +} +function isSectionFragment(frag) { + return frag.type === "section"; +} +function getBuilder(frag) { + return isSectionFragment(frag) ? buildSection : buildNameValue; +} +function getBlockGapLevel(frag) { + if (isSectionFragment(frag)) { + var gapLevel_1 = 0; + var subBlockLen = frag.blocks.length; + var hasInnerGap_1 = subBlockLen > 1 || subBlockLen > 0 && !frag.noHeader; + each$4(frag.blocks, function(subBlock) { + var subGapLevel = getBlockGapLevel(subBlock); + if (subGapLevel >= gapLevel_1) { + gapLevel_1 = subGapLevel + +(hasInnerGap_1 && // 0 always can not be readable gap level. + (!subGapLevel || isSectionFragment(subBlock) && !subBlock.noHeader)); + } + }); + return gapLevel_1; + } + return 0; +} +function buildSection(ctx, fragment, topMarginForOuterGap, toolTipTextStyle) { + var noHeader = fragment.noHeader; + var gaps = getGap(getBlockGapLevel(fragment)); + var subMarkupTextList = []; + var subBlocks = fragment.blocks || []; + assert(!subBlocks || isArray(subBlocks)); + subBlocks = subBlocks || []; + var orderMode = ctx.orderMode; + if (fragment.sortBlocks && orderMode) { + subBlocks = subBlocks.slice(); + var orderMap = { + valueAsc: "asc", + valueDesc: "desc" + }; + if (hasOwn(orderMap, orderMode)) { + var comparator_1 = new SortOrderComparator(orderMap[orderMode], null); + subBlocks.sort(function(a, b) { + return comparator_1.evaluate(a.sortParam, b.sortParam); + }); + } else if (orderMode === "seriesDesc") { + subBlocks.reverse(); + } + } + each$4(subBlocks, function(subBlock, idx) { + var valueFormatter = fragment.valueFormatter; + var subMarkupText2 = getBuilder(subBlock)( + // Inherit valueFormatter + valueFormatter ? extend(extend({}, ctx), { + valueFormatter + }) : ctx, + subBlock, + idx > 0 ? gaps.html : 0, + toolTipTextStyle + ); + subMarkupText2 != null && subMarkupTextList.push(subMarkupText2); + }); + var subMarkupText = ctx.renderMode === "richText" ? subMarkupTextList.join(gaps.richText) : wrapBlockHTML(toolTipTextStyle, subMarkupTextList.join(""), noHeader ? topMarginForOuterGap : gaps.html); + if (noHeader) { + return subMarkupText; + } + var displayableHeader = makeValueReadable(fragment.header, "ordinal", ctx.useUTC); + var nameStyle = getTooltipTextStyle(toolTipTextStyle, ctx.renderMode).nameStyle; + var tooltipLineHeight = getTooltipLineHeight(toolTipTextStyle); + if (ctx.renderMode === "richText") { + return wrapInlineNameRichText(ctx, displayableHeader, nameStyle) + gaps.richText + subMarkupText; + } else { + return wrapBlockHTML(toolTipTextStyle, '
' + encodeHTML(displayableHeader) + "
" + subMarkupText, topMarginForOuterGap); + } +} +function buildNameValue(ctx, fragment, topMarginForOuterGap, toolTipTextStyle) { + var renderMode = ctx.renderMode; + var noName = fragment.noName; + var noValue = fragment.noValue; + var noMarker = !fragment.markerType; + var name = fragment.name; + var useUTC = ctx.useUTC; + var valueFormatter = fragment.valueFormatter || ctx.valueFormatter || function(value) { + value = isArray(value) ? value : [value]; + return map$1(value, function(val, idx) { + return makeValueReadable(val, isArray(valueTypeOption) ? valueTypeOption[idx] : valueTypeOption, useUTC); + }); + }; + if (noName && noValue) { + return; + } + var markerStr = noMarker ? "" : ctx.markupStyleCreator.makeTooltipMarker(fragment.markerType, fragment.markerColor || "#333", renderMode); + var readableName = noName ? "" : makeValueReadable(name, "ordinal", useUTC); + var valueTypeOption = fragment.valueType; + var readableValueList = noValue ? [] : valueFormatter(fragment.value, fragment.dataIndex); + var valueAlignRight = !noMarker || !noName; + var valueCloseToMarker = !noMarker && noName; + var _a = getTooltipTextStyle(toolTipTextStyle, renderMode), nameStyle = _a.nameStyle, valueStyle = _a.valueStyle; + return renderMode === "richText" ? (noMarker ? "" : markerStr) + (noName ? "" : wrapInlineNameRichText(ctx, readableName, nameStyle)) + (noValue ? "" : wrapInlineValueRichText(ctx, readableValueList, valueAlignRight, valueCloseToMarker, valueStyle)) : wrapBlockHTML(toolTipTextStyle, (noMarker ? "" : markerStr) + (noName ? "" : wrapInlineNameHTML(readableName, !noMarker, nameStyle)) + (noValue ? "" : wrapInlineValueHTML(readableValueList, valueAlignRight, valueCloseToMarker, valueStyle)), topMarginForOuterGap); +} +function buildTooltipMarkup(fragment, markupStyleCreator, renderMode, orderMode, useUTC, toolTipTextStyle) { + if (!fragment) { + return; + } + var builder = getBuilder(fragment); + var ctx = { + useUTC, + renderMode, + orderMode, + markupStyleCreator, + valueFormatter: fragment.valueFormatter + }; + return builder(ctx, fragment, 0, toolTipTextStyle); +} +function getGap(gapLevel) { + return { + html: HTML_GAPS[gapLevel], + richText: RICH_TEXT_GAPS[gapLevel] + }; +} +function wrapBlockHTML(textStyle, encodedContent, topGap) { + var clearfix = '
'; + var marginCSS = "margin: " + topGap + "px 0 0"; + var tooltipLineHeight = getTooltipLineHeight(textStyle); + return '
' + encodedContent + clearfix + "
"; +} +function wrapInlineNameHTML(name, leftHasMarker, style) { + var marginCss = leftHasMarker ? "margin-left:2px" : ""; + return '' + encodeHTML(name) + ""; +} +function wrapInlineValueHTML(valueList, alignRight, valueCloseToMarker, style) { + var paddingStr = valueCloseToMarker ? "10px" : "20px"; + var alignCSS = alignRight ? "float:right;margin-left:" + paddingStr : ""; + valueList = isArray(valueList) ? valueList : [valueList]; + return '' + map$1(valueList, function(value) { + return encodeHTML(value); + }).join("  ") + ""; +} +function wrapInlineNameRichText(ctx, name, style) { + return ctx.markupStyleCreator.wrapRichTextStyle(name, style); +} +function wrapInlineValueRichText(ctx, values, alignRight, valueCloseToMarker, style) { + var styles = [style]; + var paddingLeft = valueCloseToMarker ? 10 : 20; + alignRight && styles.push({ + padding: [0, 0, 0, paddingLeft], + align: "right" + }); + return ctx.markupStyleCreator.wrapRichTextStyle(isArray(values) ? values.join(" ") : values, styles); +} +function retrieveVisualColorForTooltipMarker(series, dataIndex) { + var style = series.getData().getItemVisual(dataIndex, "style"); + var color = style[series.visualDrawType]; + return convertToColorString(color); +} +function getPaddingFromTooltipModel(model, renderMode) { + var padding = model.get("padding"); + return padding != null ? padding : renderMode === "richText" ? [8, 10] : 10; +} +var TooltipMarkupStyleCreator = ( + /** @class */ + function() { + function TooltipMarkupStyleCreator2() { + this.richTextStyles = {}; + this._nextStyleNameId = getRandomIdBase(); + } + TooltipMarkupStyleCreator2.prototype._generateStyleName = function() { + return "__EC_aUTo_" + this._nextStyleNameId++; + }; + TooltipMarkupStyleCreator2.prototype.makeTooltipMarker = function(markerType, colorStr, renderMode) { + var markerId = renderMode === "richText" ? this._generateStyleName() : null; + var marker = getTooltipMarker({ + color: colorStr, + type: markerType, + renderMode, + markerId + }); + if (isString(marker)) { + return marker; + } else { + this.richTextStyles[markerId] = marker.style; + return marker.content; + } + }; + TooltipMarkupStyleCreator2.prototype.wrapRichTextStyle = function(text, styles) { + var finalStl = {}; + if (isArray(styles)) { + each$4(styles, function(stl) { + return extend(finalStl, stl); + }); + } else { + extend(finalStl, styles); + } + var styleName = this._generateStyleName(); + this.richTextStyles[styleName] = finalStl; + return "{" + styleName + "|" + text + "}"; + }; + return TooltipMarkupStyleCreator2; + }() +); + +function defaultSeriesFormatTooltip(opt) { + var series = opt.series; + var dataIndex = opt.dataIndex; + var multipleSeries = opt.multipleSeries; + var data = series.getData(); + var tooltipDims = data.mapDimensionsAll('defaultedTooltip'); + var tooltipDimLen = tooltipDims.length; + var value = series.getRawValue(dataIndex); + var isValueArr = isArray(value); + var markerColor = retrieveVisualColorForTooltipMarker(series, dataIndex); + // Complicated rule for pretty tooltip. + var inlineValue; + var inlineValueType; + var subBlocks; + var sortParam; + if (tooltipDimLen > 1 || isValueArr && !tooltipDimLen) { + var formatArrResult = formatTooltipArrayValue(value, series, dataIndex, tooltipDims, markerColor); + inlineValue = formatArrResult.inlineValues; + inlineValueType = formatArrResult.inlineValueTypes; + subBlocks = formatArrResult.blocks; + // Only support tooltip sort by the first inline value. It's enough in most cases. + sortParam = formatArrResult.inlineValues[0]; + } else if (tooltipDimLen) { + var dimInfo = data.getDimensionInfo(tooltipDims[0]); + sortParam = inlineValue = retrieveRawValue(data, dataIndex, tooltipDims[0]); + inlineValueType = dimInfo.type; + } else { + sortParam = inlineValue = isValueArr ? value[0] : value; + } + // Do not show generated series name. It might not be readable. + var seriesNameSpecified = isNameSpecified(series); + var seriesName = seriesNameSpecified && series.name || ''; + var itemName = data.getName(dataIndex); + var inlineName = multipleSeries ? seriesName : itemName; + return createTooltipMarkup('section', { + header: seriesName, + // When series name is not specified, do not show a header line with only '-'. + // This case always happens in tooltip.trigger: 'item'. + noHeader: multipleSeries || !seriesNameSpecified, + sortParam: sortParam, + blocks: [createTooltipMarkup('nameValue', { + markerType: 'item', + markerColor: markerColor, + // Do not mix display seriesName and itemName in one tooltip, + // which might confuses users. + name: inlineName, + // name dimension might be auto assigned, where the name might + // be not readable. So we check trim here. + noName: !trim(inlineName), + value: inlineValue, + valueType: inlineValueType, + dataIndex: dataIndex + })].concat(subBlocks || []) + }); +} +function formatTooltipArrayValue(value, series, dataIndex, tooltipDims, colorStr) { + // check: category-no-encode-has-axis-data in dataset.html + var data = series.getData(); + var isValueMultipleLine = reduce(value, function (isValueMultipleLine, val, idx) { + var dimItem = data.getDimensionInfo(idx); + return isValueMultipleLine = isValueMultipleLine || dimItem && dimItem.tooltip !== false && dimItem.displayName != null; + }, false); + var inlineValues = []; + var inlineValueTypes = []; + var blocks = []; + tooltipDims.length ? each$4(tooltipDims, function (dim) { + setEachItem(retrieveRawValue(data, dataIndex, dim), dim); + }) + // By default, all dims is used on tooltip. + : each$4(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; + } + if (isValueMultipleLine) { + blocks.push(createTooltipMarkup('nameValue', { + markerType: 'subItem', + markerColor: colorStr, + name: dimInfo.displayName, + value: val, + valueType: dimInfo.type + })); + } else { + inlineValues.push(val); + inlineValueTypes.push(dimInfo.type); + } + } + return { + inlineValues: inlineValues, + inlineValueTypes: inlineValueTypes, + blocks: blocks + }; +} + +var inner$8 = makeInner(); +function getSelectionKey(data, dataIndex) { + return data.getName(dataIndex) || data.getId(dataIndex); +} +var SERIES_UNIVERSAL_TRANSITION_PROP = "__universalTransitionEnabled"; +var SeriesModel = ( + /** @class */ + function(_super) { + __extends(SeriesModel2, _super); + function SeriesModel2() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this._selectedDataIndicesMap = {}; + return _this; + } + SeriesModel2.prototype.init = function(option, parentModel, ecModel) { + this.seriesIndex = this.componentIndex; + this.dataTask = createTask({ + count: dataTaskCount, + reset: dataTaskReset + }); + this.dataTask.context = { + model: this + }; + this.mergeDefaultAndTheme(option, ecModel); + var sourceManager = inner$8(this).sourceManager = new SourceManager(this); + sourceManager.prepareSource(); + var data = this.getInitialData(option, ecModel); + wrapData(data, this); + this.dataTask.context.data = data; + inner$8(this).dataBeforeProcessed = data; + autoSeriesName(this); + this._initSelectedMapFromData(data); + }; + SeriesModel2.prototype.mergeDefaultAndTheme = function(option, ecModel) { + var layoutMode = fetchLayoutMode(this); + var inputPositionParams = layoutMode ? getLayoutParams(option) : {}; + var themeSubType = this.subType; + if (ComponentModel.hasClass(themeSubType)) { + themeSubType += "Series"; + } + merge(option, ecModel.getTheme().get(this.subType)); + merge(option, this.getDefaultOption()); + defaultEmphasis(option, "label", ["show"]); + this.fillDataTextStyle(option.data); + if (layoutMode) { + mergeLayoutParam(option, inputPositionParams, layoutMode); + } + }; + SeriesModel2.prototype.mergeOption = function(newSeriesOption, ecModel) { + newSeriesOption = merge(this.option, newSeriesOption, true); + this.fillDataTextStyle(newSeriesOption.data); + var layoutMode = fetchLayoutMode(this); + if (layoutMode) { + mergeLayoutParam(this.option, newSeriesOption, layoutMode); + } + var sourceManager = inner$8(this).sourceManager; + sourceManager.dirty(); + sourceManager.prepareSource(); + var data = this.getInitialData(newSeriesOption, ecModel); + wrapData(data, this); + this.dataTask.dirty(); + this.dataTask.context.data = data; + inner$8(this).dataBeforeProcessed = data; + autoSeriesName(this); + this._initSelectedMapFromData(data); + }; + SeriesModel2.prototype.fillDataTextStyle = function(data) { + 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); + } + } + } + }; + SeriesModel2.prototype.getInitialData = function(option, ecModel) { + return; + }; + SeriesModel2.prototype.appendData = function(params) { + var data = this.getRawData(); + data.appendData(params.data); + }; + SeriesModel2.prototype.getData = function(dataType) { + var task = getCurrentTask(this); + if (task) { + var data = task.context.data; + return dataType == null || !data.getLinkedData ? data : data.getLinkedData(dataType); + } else { + return inner$8(this).data; + } + }; + SeriesModel2.prototype.getAllData = function() { + var mainData = this.getData(); + return mainData && mainData.getLinkedDataAll ? mainData.getLinkedDataAll() : [{ + data: mainData + }]; + }; + SeriesModel2.prototype.setData = function(data) { + var task = getCurrentTask(this); + if (task) { + var context = task.context; + context.outputData = data; + if (task !== this.dataTask) { + context.data = data; + } + } + inner$8(this).data = data; + }; + SeriesModel2.prototype.getEncode = function() { + var encode = this.get("encode", true); + if (encode) { + return createHashMap(encode); + } + }; + SeriesModel2.prototype.getSourceManager = function() { + return inner$8(this).sourceManager; + }; + SeriesModel2.prototype.getSource = function() { + return this.getSourceManager().getSource(); + }; + SeriesModel2.prototype.getRawData = function() { + return inner$8(this).dataBeforeProcessed; + }; + SeriesModel2.prototype.getColorBy = function() { + var colorBy = this.get("colorBy"); + return colorBy || "series"; + }; + SeriesModel2.prototype.isColorBySeries = function() { + return this.getColorBy() === "series"; + }; + SeriesModel2.prototype.getBaseAxis = function() { + var coordSys = this.coordinateSystem; + return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis(); + }; + SeriesModel2.prototype.formatTooltip = function(dataIndex, multipleSeries, dataType) { + return defaultSeriesFormatTooltip({ + series: this, + dataIndex, + multipleSeries + }); + }; + SeriesModel2.prototype.isAnimationEnabled = function() { + var ecModel = this.ecModel; + if (env.node && !(ecModel && ecModel.ssr)) { + return false; + } + var animationEnabled = this.getShallow("animation"); + if (animationEnabled) { + if (this.getData().count() > this.getShallow("animationThreshold")) { + animationEnabled = false; + } + } + return !!animationEnabled; + }; + SeriesModel2.prototype.restoreData = function() { + this.dataTask.dirty(); + }; + SeriesModel2.prototype.getColorFromPalette = function(name, scope, requestColorNum) { + var ecModel = this.ecModel; + var color = PaletteMixin.prototype.getColorFromPalette.call(this, name, scope, requestColorNum); + if (!color) { + color = ecModel.getColorFromPalette(name, scope, requestColorNum); + } + return color; + }; + SeriesModel2.prototype.coordDimToDataDim = function(coordDim) { + return this.getRawData().mapDimensionsAll(coordDim); + }; + SeriesModel2.prototype.getProgressive = function() { + return this.get("progressive"); + }; + SeriesModel2.prototype.getProgressiveThreshold = function() { + return this.get("progressiveThreshold"); + }; + SeriesModel2.prototype.select = function(innerDataIndices, dataType) { + this._innerSelect(this.getData(dataType), innerDataIndices); + }; + SeriesModel2.prototype.unselect = function(innerDataIndices, dataType) { + var selectedMap = this.option.selectedMap; + if (!selectedMap) { + return; + } + var selectedMode = this.option.selectedMode; + var data = this.getData(dataType); + if (selectedMode === "series" || selectedMap === "all") { + this.option.selectedMap = {}; + this._selectedDataIndicesMap = {}; + return; + } + for (var i = 0; i < innerDataIndices.length; i++) { + var dataIndex = innerDataIndices[i]; + var nameOrId = getSelectionKey(data, dataIndex); + selectedMap[nameOrId] = false; + this._selectedDataIndicesMap[nameOrId] = -1; + } + }; + SeriesModel2.prototype.toggleSelect = function(innerDataIndices, dataType) { + var tmpArr = []; + for (var i = 0; i < innerDataIndices.length; i++) { + tmpArr[0] = innerDataIndices[i]; + this.isSelected(innerDataIndices[i], dataType) ? this.unselect(tmpArr, dataType) : this.select(tmpArr, dataType); + } + }; + SeriesModel2.prototype.getSelectedDataIndices = function() { + if (this.option.selectedMap === "all") { + return [].slice.call(this.getData().getIndices()); + } + var selectedDataIndicesMap = this._selectedDataIndicesMap; + var nameOrIds = keys(selectedDataIndicesMap); + var dataIndices = []; + for (var i = 0; i < nameOrIds.length; i++) { + var dataIndex = selectedDataIndicesMap[nameOrIds[i]]; + if (dataIndex >= 0) { + dataIndices.push(dataIndex); + } + } + return dataIndices; + }; + SeriesModel2.prototype.isSelected = function(dataIndex, dataType) { + var selectedMap = this.option.selectedMap; + if (!selectedMap) { + return false; + } + var data = this.getData(dataType); + return (selectedMap === "all" || selectedMap[getSelectionKey(data, dataIndex)]) && !data.getItemModel(dataIndex).get(["select", "disabled"]); + }; + SeriesModel2.prototype.isUniversalTransitionEnabled = function() { + if (this[SERIES_UNIVERSAL_TRANSITION_PROP]) { + return true; + } + var universalTransitionOpt = this.option.universalTransition; + if (!universalTransitionOpt) { + return false; + } + if (universalTransitionOpt === true) { + return true; + } + return universalTransitionOpt && universalTransitionOpt.enabled; + }; + SeriesModel2.prototype._innerSelect = function(data, innerDataIndices) { + var _a, _b; + var option = this.option; + var selectedMode = option.selectedMode; + var len = innerDataIndices.length; + if (!selectedMode || !len) { + return; + } + if (selectedMode === "series") { + option.selectedMap = "all"; + } else if (selectedMode === "multiple") { + if (!isObject$2(option.selectedMap)) { + option.selectedMap = {}; + } + var selectedMap = option.selectedMap; + for (var i = 0; i < len; i++) { + var dataIndex = innerDataIndices[i]; + var nameOrId = getSelectionKey(data, dataIndex); + selectedMap[nameOrId] = true; + this._selectedDataIndicesMap[nameOrId] = data.getRawIndex(dataIndex); + } + } else if (selectedMode === "single" || selectedMode === true) { + var lastDataIndex = innerDataIndices[len - 1]; + var nameOrId = getSelectionKey(data, lastDataIndex); + option.selectedMap = (_a = {}, _a[nameOrId] = true, _a); + this._selectedDataIndicesMap = (_b = {}, _b[nameOrId] = data.getRawIndex(lastDataIndex), _b); + } + }; + SeriesModel2.prototype._initSelectedMapFromData = function(data) { + if (this.option.selectedMap) { + return; + } + var dataIndices = []; + if (data.hasItemOption) { + data.each(function(idx) { + var rawItem = data.getRawDataItem(idx); + if (rawItem && rawItem.selected) { + dataIndices.push(idx); + } + }); + } + if (dataIndices.length > 0) { + this._innerSelect(data, dataIndices); + } + }; + SeriesModel2.registerClass = function(clz) { + return ComponentModel.registerClass(clz); + }; + SeriesModel2.protoInitialize = function() { + var proto = SeriesModel2.prototype; + proto.type = "series.__base__"; + proto.seriesIndex = 0; + proto.ignoreStyleOnData = false; + proto.hasSymbolVisual = false; + proto.defaultSymbol = "circle"; + proto.visualStyleAccessPath = "itemStyle"; + proto.visualDrawType = "fill"; + }(); + return SeriesModel2; + }(ComponentModel) +); +mixin(SeriesModel, DataFormatMixin); +mixin(SeriesModel, PaletteMixin); +mountExtend(SeriesModel, ComponentModel); +function autoSeriesName(seriesModel) { + var name = seriesModel.name; + if (!isNameSpecified(seriesModel)) { + seriesModel.name = getSeriesAutoName(seriesModel) || name; + } +} +function getSeriesAutoName(seriesModel) { + var data = seriesModel.getRawData(); + var dataDims = data.mapDimensionsAll("seriesName"); + var nameArr = []; + each$4(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) { + if (context.outputData && param.end > context.outputData.count()) { + context.model.getRawData().cloneShallow(context.outputData); + } +} +function wrapData(data, seriesModel) { + each$4(concatArray(data.CHANGABLE_METHODS, data.DOWNSAMPLE_METHODS), function(methodName) { + data.wrapMethod(methodName, curry$1(onDataChange, seriesModel)); + }); +} +function onDataChange(seriesModel, newList) { + var task = getCurrentTask(seriesModel); + if (task) { + task.setOutputEnd((newList || this).count()); + } + return newList; +} +function getCurrentTask(seriesModel) { + var scheduler = (seriesModel.ecModel || {}).scheduler; + var pipeline = scheduler && scheduler.getPipeline(seriesModel.uid); + if (pipeline) { + var task = pipeline.currentTask; + if (task) { + var agentStubMap = task.agentStubMap; + if (agentStubMap) { + task = agentStubMap.get(seriesModel.uid); + } + } + return task; + } +} + +var ComponentView = /** @class */function () { + function ComponentView() { + this.group = new Group$2(); + this.uid = getUID('viewComponent'); + } + ComponentView.prototype.init = function (ecModel, api) {}; + ComponentView.prototype.render = function (model, ecModel, api, payload) {}; + ComponentView.prototype.dispose = function (ecModel, api) {}; + ComponentView.prototype.updateView = function (model, ecModel, api, payload) { + // Do nothing; + }; + ComponentView.prototype.updateLayout = function (model, ecModel, api, payload) { + // Do nothing; + }; + ComponentView.prototype.updateVisual = function (model, ecModel, api, payload) { + // Do nothing; + }; + /** + * Hook for toggle blur target series. + * Can be used in marker for blur or leave blur the markers + */ + ComponentView.prototype.toggleBlurSeries = function (seriesModels, isBlur, ecModel) { + // Do nothing; + }; + /** + * Traverse the new rendered elements. + * + * It will traverse the new added element in progressive rendering. + * And traverse all in normal rendering. + */ + ComponentView.prototype.eachRendered = function (cb) { + var group = this.group; + if (group) { + group.traverse(cb); + } + }; + return ComponentView; +}(); +enableClassExtend(ComponentView); +enableClassManagement(ComponentView); + +/** + * @return {string} If large mode changed, return string 'reset'; + */ +function createRenderPlanner() { + var inner = makeInner(); + return function (seriesModel) { + var fields = inner(seriesModel); + var pipelineContext = seriesModel.pipelineContext; + var originalLarge = !!fields.large; + var originalProgressive = !!fields.progressiveRender; + // FIXME: if the planner works on a filtered series, `pipelineContext` does not + // exists. See #11611 . Probably we need to modify this structure, see the comment + // on `performRawSeries` in `Schedular.js`. + var large = fields.large = !!(pipelineContext && pipelineContext.large); + var progressive = fields.progressiveRender = !!(pipelineContext && pipelineContext.progressiveRender); + return !!(originalLarge !== large || originalProgressive !== progressive) && 'reset'; + }; +} + +var inner$7 = makeInner(); +var renderPlanner = createRenderPlanner(); +var ChartView = ( + /** @class */ + function() { + function ChartView2() { + this.group = new Group$2(); + this.uid = getUID("viewChart"); + this.renderTask = createTask({ + plan: renderTaskPlan, + reset: renderTaskReset + }); + this.renderTask.context = { + view: this + }; + } + ChartView2.prototype.init = function(ecModel, api) { + }; + ChartView2.prototype.render = function(seriesModel, ecModel, api, payload) { + }; + ChartView2.prototype.highlight = function(seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(payload && payload.dataType); + if (!data) { + return; + } + toggleHighlight(data, payload, "emphasis"); + }; + ChartView2.prototype.downplay = function(seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(payload && payload.dataType); + if (!data) { + return; + } + toggleHighlight(data, payload, "normal"); + }; + ChartView2.prototype.remove = function(ecModel, api) { + this.group.removeAll(); + }; + ChartView2.prototype.dispose = function(ecModel, api) { + }; + ChartView2.prototype.updateView = function(seriesModel, ecModel, api, payload) { + this.render(seriesModel, ecModel, api, payload); + }; + ChartView2.prototype.updateLayout = function(seriesModel, ecModel, api, payload) { + this.render(seriesModel, ecModel, api, payload); + }; + ChartView2.prototype.updateVisual = function(seriesModel, ecModel, api, payload) { + this.render(seriesModel, ecModel, api, payload); + }; + ChartView2.prototype.eachRendered = function(cb) { + traverseElements(this.group, cb); + }; + ChartView2.markUpdateMethod = function(payload, methodName) { + inner$7(payload).updateMethod = methodName; + }; + ChartView2.protoInitialize = function() { + var proto = ChartView2.prototype; + proto.type = "chart"; + }(); + return ChartView2; + }() +); +function elSetState(el, state, highlightDigit) { + if (el && isHighDownDispatcher(el)) { + (state === "emphasis" ? enterEmphasis : leaveEmphasis)(el, highlightDigit); + } +} +function toggleHighlight(data, payload, state) { + var dataIndex = queryDataIndex(data, payload); + var highlightDigit = payload && payload.highlightKey != null ? getHighlightDigit(payload.highlightKey) : null; + if (dataIndex != null) { + each$4(normalizeToArray(dataIndex), function(dataIdx) { + elSetState(data.getItemGraphicEl(dataIdx), state, highlightDigit); + }); + } else { + data.eachItemGraphicEl(function(el) { + elSetState(el, state, highlightDigit); + }); + } +} +enableClassExtend(ChartView); +enableClassManagement(ChartView); +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; + var progressiveRender = seriesModel.pipelineContext.progressiveRender; + var view = context.view; + var updateMethod = payload && inner$7(payload).updateMethod; + var methodName = progressiveRender ? "incrementalPrepareRender" : updateMethod && view[updateMethod] ? updateMethod : "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. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 () { + var cbArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + cbArgs[_i] = arguments[_i]; + } + currCall = new Date().getTime(); + scope = this; + args = cbArgs; + 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'); + * }; + * + */ +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 || false) { + 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. + */ +function clear(obj, fnAttr) { + var fn = obj[fnAttr]; + if (fn && fn[ORIGIN_METHOD]) { + // Clear throttle + fn.clear && fn.clear(); + obj[fnAttr] = fn[ORIGIN_METHOD]; + } +} + +var inner$6 = makeInner(); +var defaultStyleMappers = { + itemStyle: makeStyleMapper(ITEM_STYLE_KEY_MAP, true), + lineStyle: makeStyleMapper(LINE_STYLE_KEY_MAP, true) +}; +var defaultColorKey = { + lineStyle: 'stroke', + itemStyle: 'fill' +}; +function getStyleMapper(seriesModel, stylePath) { + var styleMapper = seriesModel.visualStyleMapper || defaultStyleMappers[stylePath]; + if (!styleMapper) { + console.warn("Unknown style type '" + stylePath + "'."); + return defaultStyleMappers.itemStyle; + } + return styleMapper; +} +function getDefaultColorKey(seriesModel, stylePath) { + // return defaultColorKey[stylePath] || + var colorKey = seriesModel.visualDrawType || defaultColorKey[stylePath]; + if (!colorKey) { + console.warn("Unknown style type '" + stylePath + "'."); + return 'fill'; + } + return colorKey; +} +var seriesStyleTask = { + createOnAllSeries: true, + performRawSeries: true, + reset: function (seriesModel, ecModel) { + var data = seriesModel.getData(); + var stylePath = seriesModel.visualStyleAccessPath || 'itemStyle'; + // Set in itemStyle + var styleModel = seriesModel.getModel(stylePath); + var getStyle = getStyleMapper(seriesModel, stylePath); + var globalStyle = getStyle(styleModel); + var decalOption = styleModel.getShallow('decal'); + if (decalOption) { + data.setVisual('decal', decalOption); + decalOption.dirty = true; + } + // TODO + var colorKey = getDefaultColorKey(seriesModel, stylePath); + var color = globalStyle[colorKey]; + // TODO style callback + var colorCallback = isFunction(color) ? color : null; + var hasAutoColor = globalStyle.fill === 'auto' || globalStyle.stroke === 'auto'; + // Get from color palette by default. + if (!globalStyle[colorKey] || colorCallback || hasAutoColor) { + // Note: If some series has color specified (e.g., by itemStyle.color), we DO NOT + // make it effect palette. Because some scenarios users need to make some series + // transparent or as background, which should better not effect the palette. + var colorPalette = seriesModel.getColorFromPalette( + // TODO series count changed. + seriesModel.name, null, ecModel.getSeriesCount()); + if (!globalStyle[colorKey]) { + globalStyle[colorKey] = colorPalette; + data.setVisual('colorFromPalette', true); + } + globalStyle.fill = globalStyle.fill === 'auto' || isFunction(globalStyle.fill) ? colorPalette : globalStyle.fill; + globalStyle.stroke = globalStyle.stroke === 'auto' || isFunction(globalStyle.stroke) ? colorPalette : globalStyle.stroke; + } + data.setVisual('style', globalStyle); + data.setVisual('drawType', colorKey); + // Only visible series has each data be visual encoded + if (!ecModel.isSeriesFiltered(seriesModel) && colorCallback) { + data.setVisual('colorFromPalette', false); + return { + dataEach: function (data, idx) { + var dataParams = seriesModel.getDataParams(idx); + var itemStyle = extend({}, globalStyle); + itemStyle[colorKey] = colorCallback(dataParams); + data.setItemVisual(idx, 'style', itemStyle); + } + }; + } + } +}; +var sharedModel = new Model(); +var dataStyleTask = { + createOnAllSeries: true, + performRawSeries: true, + reset: function (seriesModel, ecModel) { + if (seriesModel.ignoreStyleOnData || ecModel.isSeriesFiltered(seriesModel)) { + return; + } + var data = seriesModel.getData(); + var stylePath = seriesModel.visualStyleAccessPath || 'itemStyle'; + // Set in itemStyle + var getStyle = getStyleMapper(seriesModel, stylePath); + var colorKey = data.getVisual('drawType'); + return { + dataEach: data.hasItemOption ? function (data, idx) { + // Not use getItemModel for performance considuration + var rawItem = data.getRawDataItem(idx); + if (rawItem && rawItem[stylePath]) { + sharedModel.option = rawItem[stylePath]; + var style = getStyle(sharedModel); + var existsStyle = data.ensureUniqueItemVisual(idx, 'style'); + extend(existsStyle, style); + if (sharedModel.option.decal) { + data.setItemVisual(idx, 'decal', sharedModel.option.decal); + sharedModel.option.decal.dirty = true; + } + if (colorKey in style) { + data.setItemVisual(idx, 'colorFromPalette', false); + } + } + } : null + }; + } +}; +// Pick color from palette for the data which has not been set with color yet. +// Note: do not support stream rendering. No such cases yet. +var dataColorPaletteTask = { + performRawSeries: true, + overallReset: function (ecModel) { + // Each type of series uses one scope. + // Pie and funnel are using different scopes. + var paletteScopeGroupByType = createHashMap(); + ecModel.eachSeries(function (seriesModel) { + var colorBy = seriesModel.getColorBy(); + if (seriesModel.isColorBySeries()) { + return; + } + var key = seriesModel.type + '-' + colorBy; + var colorScope = paletteScopeGroupByType.get(key); + if (!colorScope) { + colorScope = {}; + paletteScopeGroupByType.set(key, colorScope); + } + inner$6(seriesModel).scope = colorScope; + }); + ecModel.eachSeries(function (seriesModel) { + if (seriesModel.isColorBySeries() || ecModel.isSeriesFiltered(seriesModel)) { + return; + } + var dataAll = seriesModel.getRawData(); + var idxMap = {}; + var data = seriesModel.getData(); + var colorScope = inner$6(seriesModel).scope; + var stylePath = seriesModel.visualStyleAccessPath || 'itemStyle'; + var colorKey = getDefaultColorKey(seriesModel, stylePath); + data.each(function (idx) { + var rawIdx = data.getRawIndex(idx); + idxMap[rawIdx] = idx; + }); + // Iterate on data before filtered. To make sure color from palette can be + // Consistent when toggling legend. + dataAll.each(function (rawIdx) { + var idx = idxMap[rawIdx]; + var fromPalette = data.getItemVisual(idx, 'colorFromPalette'); + // Get color from palette for each data only when the color is inherited from series color, which is + // also picked from color palette. So following situation is not in the case: + // 1. series.itemStyle.color is set + // 2. color is encoded by visualMap + if (fromPalette) { + var itemStyle = data.ensureUniqueItemVisual(idx, 'style'); + var name_1 = dataAll.getName(rawIdx) || rawIdx + ''; + var dataCount = dataAll.count(); + itemStyle[colorKey] = seriesModel.getColorFromPalette(name_1, colorScope, dataCount); + } + }); + }); + } +}; + +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} + */ +function defaultLoading(api, opts) { + opts = opts || {}; + defaults(opts, { + text: 'loading', + textColor: '#000', + fontSize: 12, + fontWeight: 'normal', + fontStyle: 'normal', + fontFamily: 'sans-serif', + maskColor: 'rgba(255, 255, 255, 0.8)', + showSpinner: true, + color: '#5470c6', + spinnerRadius: 10, + lineWidth: 5, + zlevel: 0 + }); + var group = new Group$2(); + var mask = new Rect({ + style: { + fill: opts.maskColor + }, + zlevel: opts.zlevel, + z: 10000 + }); + group.add(mask); + var textContent = new ZRText({ + style: { + text: opts.text, + fill: opts.textColor, + fontSize: opts.fontSize, + fontWeight: opts.fontWeight, + fontStyle: opts.fontStyle, + fontFamily: opts.fontFamily + }, + zlevel: opts.zlevel, + z: 10001 + }); + var labelRect = new Rect({ + style: { + fill: 'none' + }, + textContent: textContent, + textConfig: { + position: 'right', + distance: 10 + }, + zlevel: opts.zlevel, + z: 10001 + }); + group.add(labelRect); + var arc; + if (opts.showSpinner) { + arc = new Arc({ + shape: { + startAngle: -PI$1 / 2, + endAngle: -PI$1 / 2 + 0.1, + r: opts.spinnerRadius + }, + style: { + stroke: opts.color, + lineCap: 'round', + lineWidth: opts.lineWidth + }, + 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'); + group.add(arc); + } + // Inject resize + group.resize = function () { + var textWidth = textContent.getBoundingRect().width; + var r = opts.showSpinner ? opts.spinnerRadius : 0; + // cx = (containerWidth - arcDiameter - textDistance - textWidth) / 2 + // textDistance needs to be calculated when both animation and text exist + var cx = (api.getWidth() - r * 2 - (opts.showSpinner && textWidth ? 10 : 0) - textWidth) / 2 - (opts.showSpinner && textWidth ? 0 : 5 + textWidth / 2) + // only show the text + + (opts.showSpinner ? 0 : textWidth / 2) + // only show the spinner + + (textWidth ? 0 : r); + var cy = api.getHeight() / 2; + opts.showSpinner && arc.setShape({ + cx: cx, + cy: cy + }); + 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; +} + +var Scheduler = ( + /** @class */ + function() { + function Scheduler2(ecInstance, api, dataProcessorHandlers, visualHandlers) { + this._stageTaskMap = createHashMap(); + this.ecInstance = ecInstance; + this.api = api; + dataProcessorHandlers = this._dataProcessorHandlers = dataProcessorHandlers.slice(); + visualHandlers = this._visualHandlers = visualHandlers.slice(); + this._allHandlers = dataProcessorHandlers.concat(visualHandlers); + } + Scheduler2.prototype.restoreData = function(ecModel, payload) { + ecModel.restoreData(payload); + this._stageTaskMap.each(function(taskRecord) { + var overallTask = taskRecord.overallTask; + overallTask && overallTask.dirty(); + }); + }; + Scheduler2.prototype.getPerformArgs = function(task, isBlock) { + 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, + modBy, + modDataCount + }; + }; + Scheduler2.prototype.getPipeline = function(pipelineId) { + return this._pipelineMap.get(pipelineId); + }; + Scheduler2.prototype.updateStreamModes = function(seriesModel, view) { + var pipeline = this._pipelineMap.get(seriesModel.uid); + var data = seriesModel.getData(); + var dataLen = data.count(); + var progressiveRender = pipeline.progressiveEnabled && view.incrementalPrepareRender && dataLen >= pipeline.threshold; + var large = seriesModel.get("large") && dataLen >= seriesModel.get("largeThreshold"); + var modDataCount = seriesModel.get("progressiveChunkMode") === "mod" ? dataLen : null; + seriesModel.pipelineContext = pipeline.context = { + progressiveRender, + modDataCount, + large + }; + }; + Scheduler2.prototype.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 + }); + scheduler._pipe(seriesModel, seriesModel.dataTask); + }); + }; + Scheduler2.prototype.prepareStageTasks = function() { + var stageTaskMap = this._stageTaskMap; + var ecModel = this.api.getModel(); + var api = this.api; + each$4(this._allHandlers, function(handler) { + var record = stageTaskMap.get(handler.uid) || stageTaskMap.set(handler.uid, {}); + var errMsg = ""; + assert(!(handler.reset && handler.overallReset), errMsg); + handler.reset && this._createSeriesStageTask(handler, record, ecModel, api); + handler.overallReset && this._createOverallStageTask(handler, record, ecModel, api); + }, this); + }; + Scheduler2.prototype.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; + this._pipe(model, renderTask); + }; + Scheduler2.prototype.performDataProcessorTasks = function(ecModel, payload) { + this._performStageTasks(this._dataProcessorHandlers, ecModel, payload, { + block: true + }); + }; + Scheduler2.prototype.performVisualTasks = function(ecModel, payload, opt) { + this._performStageTasks(this._visualHandlers, ecModel, payload, opt); + }; + Scheduler2.prototype._performStageTasks = function(stageHandlers, ecModel, payload, opt) { + opt = opt || {}; + var unfinished = false; + var scheduler = this; + each$4(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_1; + var agentStubMap = overallTask.agentStubMap; + agentStubMap.each(function(stub) { + if (needSetDirty(opt, stub)) { + stub.dirty(); + overallNeedDirty_1 = true; + } + }); + overallNeedDirty_1 && overallTask.dirty(); + scheduler.updatePayload(overallTask, payload); + var performArgs_1 = scheduler.getPerformArgs(overallTask, opt.block); + agentStubMap.each(function(stub) { + stub.perform(performArgs_1); + }); + if (overallTask.perform(performArgs_1)) { + unfinished = true; + } + } 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); + scheduler.updatePayload(task, payload); + if (task.perform(performArgs)) { + unfinished = true; + } + }); + } + }); + function needSetDirty(opt2, task) { + return opt2.setDirty && (!opt2.dirtyMap || opt2.dirtyMap.get(task.__pipeline.id)); + } + this.unfinished = unfinished || this.unfinished; + }; + Scheduler2.prototype.performSeriesTasks = function(ecModel) { + var unfinished; + ecModel.eachSeries(function(seriesModel) { + unfinished = seriesModel.dataTask.perform() || unfinished; + }); + this.unfinished = unfinished || this.unfinished; + }; + Scheduler2.prototype.plan = function() { + this._pipelineMap.each(function(pipeline) { + var task = pipeline.tail; + do { + if (task.__block) { + pipeline.blockIndex = task.__idxInPipeline; + break; + } + task = task.getUpstream(); + } while (task); + }); + }; + Scheduler2.prototype.updatePayload = function(task, payload) { + payload !== "remain" && (task.context.payload = payload); + }; + Scheduler2.prototype._createSeriesStageTask = function(stageHandler, stageHandlerRecord, ecModel, api) { + var scheduler = this; + var oldSeriesTaskMap = stageHandlerRecord.seriesTaskMap; + var newSeriesTaskMap = stageHandlerRecord.seriesTaskMap = createHashMap(); + var seriesType2 = stageHandler.seriesType; + var getTargetSeries = stageHandler.getTargetSeries; + if (stageHandler.createOnAllSeries) { + ecModel.eachRawSeries(create); + } else if (seriesType2) { + ecModel.eachRawSeriesByType(seriesType2, create); + } else if (getTargetSeries) { + getTargetSeries(ecModel, api).each(create); + } + function create(seriesModel) { + var pipelineId = seriesModel.uid; + var task = newSeriesTaskMap.set(pipelineId, oldSeriesTaskMap && oldSeriesTaskMap.get(pipelineId) || createTask({ + plan: seriesTaskPlan, + reset: seriesTaskReset, + count: seriesTaskCount + })); + task.context = { + model: seriesModel, + ecModel, + api, + // PENDING: `useClearVisual` not used? + useClearVisual: stageHandler.isVisual && !stageHandler.isLayout, + plan: stageHandler.plan, + reset: stageHandler.reset, + scheduler + }; + scheduler._pipe(seriesModel, task); + } + }; + Scheduler2.prototype._createOverallStageTask = function(stageHandler, stageHandlerRecord, ecModel, api) { + var scheduler = this; + var overallTask = stageHandlerRecord.overallTask = stageHandlerRecord.overallTask || createTask({ + reset: overallTaskReset + }); + overallTask.context = { + ecModel, + api, + overallReset: stageHandler.overallReset, + scheduler + }; + var oldAgentStubMap = overallTask.agentStubMap; + var newAgentStubMap = overallTask.agentStubMap = createHashMap(); + var seriesType2 = stageHandler.seriesType; + var getTargetSeries = stageHandler.getTargetSeries; + var overallProgress = true; + var shouldOverallTaskDirty = false; + var errMsg = ""; + assert(!stageHandler.createOnAllSeries, errMsg); + if (seriesType2) { + ecModel.eachRawSeriesByType(seriesType2, createStub); + } else if (getTargetSeries) { + getTargetSeries(ecModel, api).each(createStub); + } else { + overallProgress = false; + each$4(ecModel.getSeries(), createStub); + } + function createStub(seriesModel) { + var pipelineId = seriesModel.uid; + var stub = newAgentStubMap.set(pipelineId, oldAgentStubMap && oldAgentStubMap.get(pipelineId) || // When the result of `getTargetSeries` changed, the overallTask + // should be set as dirty and re-performed. + (shouldOverallTaskDirty = true, createTask({ + reset: stubReset, + onDirty: stubOnDirty + }))); + stub.context = { + model: seriesModel, + overallProgress + // FIXME:TS never used, so comment it + // modifyOutputEnd: modifyOutputEnd + }; + stub.agent = overallTask; + stub.__block = overallProgress; + scheduler._pipe(seriesModel, stub); + } + if (shouldOverallTaskDirty) { + overallTask.dirty(); + } + }; + Scheduler2.prototype._pipe = function(seriesModel, task) { + var pipelineId = seriesModel.uid; + var pipeline = this._pipelineMap.get(pipelineId); + !pipeline.head && (pipeline.head = task); + pipeline.tail && pipeline.tail.pipe(task); + pipeline.tail = task; + task.__idxInPipeline = pipeline.count++; + task.__pipeline = pipeline; + }; + Scheduler2.wrapStageHandler = function(stageHandler, visualType) { + if (isFunction(stageHandler)) { + stageHandler = { + overallReset: stageHandler, + seriesType: detectSeriseType(stageHandler) + }; + } + stageHandler.uid = getUID("stageHandler"); + visualType && (stageHandler.visualType = visualType); + return stageHandler; + }; + return Scheduler2; + }() +); +function overallTaskReset(context) { + context.overallReset(context.ecModel, context.api, context.payload); +} +function stubReset(context) { + 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) : null; +} +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$1(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 detectSeriseType(legacyFunc) { + seriesType = null; + try { + 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) { + for (var name_1 in Clz.prototype) { + target[name_1] = 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. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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']; +const 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. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 = '#B9B8CE'; +var backgroundColor = '#100C2A'; +var axisCommon = function () { + return { + axisLine: { + lineStyle: { + color: contrastColor + } + }, + splitLine: { + lineStyle: { + color: '#484753' + } + }, + splitArea: { + areaStyle: { + color: ['rgba(255,255,255,0.02)', 'rgba(255,255,255,0.05)'] + } + }, + minorSplitLine: { + lineStyle: { + color: '#20203B' + } + } + }; +}; +var colorPalette = ['#4992ff', '#7cffb2', '#fddd60', '#ff6e76', '#58d9f9', '#05c091', '#ff8a45', '#8d48e3', '#dd79ff']; +var theme = { + darkMode: true, + color: colorPalette, + backgroundColor: backgroundColor, + axisPointer: { + lineStyle: { + color: '#817f91' + }, + crossStyle: { + color: '#817f91' + }, + label: { + // TODO Contrast of label backgorundColor + color: '#fff' + } + }, + legend: { + textStyle: { + color: contrastColor + }, + pageTextStyle: { + color: contrastColor + } + }, + textStyle: { + color: contrastColor + }, + title: { + textStyle: { + color: '#EEF1FA' + }, + subtextStyle: { + color: '#B9B8CE' + } + }, + toolbox: { + iconStyle: { + borderColor: contrastColor + } + }, + dataZoom: { + borderColor: '#71708A', + textStyle: { + color: contrastColor + }, + brushStyle: { + color: 'rgba(135,163,206,0.3)' + }, + handleStyle: { + color: '#353450', + borderColor: '#C5CBE3' + }, + moveHandleStyle: { + color: '#B0B6C3', + opacity: 0.3 + }, + fillerColor: 'rgba(135,163,206,0.2)', + emphasis: { + handleStyle: { + borderColor: '#91B7F2', + color: '#4D587D' + }, + moveHandleStyle: { + color: '#636D9A', + opacity: 0.7 + } + }, + dataBackground: { + lineStyle: { + color: '#71708A', + width: 1 + }, + areaStyle: { + color: '#71708A' + } + }, + selectedDataBackground: { + lineStyle: { + color: '#87A3CE' + }, + areaStyle: { + color: '#87A3CE' + } + } + }, + visualMap: { + textStyle: { + color: contrastColor + } + }, + timeline: { + lineStyle: { + color: contrastColor + }, + label: { + color: contrastColor + }, + controlStyle: { + color: contrastColor, + borderColor: contrastColor + } + }, + calendar: { + itemStyle: { + color: backgroundColor + }, + dayLabel: { + color: contrastColor + }, + monthLabel: { + color: contrastColor + }, + yearLabel: { + color: contrastColor + } + }, + timeAxis: axisCommon(), + logAxis: axisCommon(), + valueAxis: axisCommon(), + categoryAxis: axisCommon(), + line: { + symbol: 'circle' + }, + graph: { + color: colorPalette + }, + gauge: { + title: { + color: contrastColor + }, + axisLine: { + lineStyle: { + color: [[1, 'rgba(207,212,219,0.2)']] + } + }, + axisLabel: { + color: contrastColor + }, + detail: { + color: '#EEF1FA' + } + }, + candlestick: { + itemStyle: { + color: '#f64e56', + color0: '#54ea92', + borderColor: '#f64e56', + borderColor0: '#54ea92' + // borderColor: '#ca2824', + // borderColor0: '#09a443' + } + } +}; +theme.categoryAxis.splitLine.show = false; + +/** + * 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. + */ +var ECEventProcessor = /** @class */function () { + function ECEventProcessor() {} + ECEventProcessor.prototype.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_1 = ['Index', 'Name', 'Id']; + var dataKeys_1 = { + name: 1, + dataIndex: 1, + dataType: 1 + }; + each$4(query, function (val, key) { + var reserved = false; + for (var i = 0; i < suffixes_1.length; i++) { + var propSuffix = suffixes_1[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_1.hasOwnProperty(key)) { + dataQuery[key] = val; + reserved = true; + } + if (!reserved) { + otherQuery[key] = val; + } + }); + } + return { + cptQuery: cptQuery, + dataQuery: dataQuery, + otherQuery: otherQuery + }; + }; + ECEventProcessor.prototype.filter = function (eventType, query) { + // 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]; + } + }; + ECEventProcessor.prototype.afterTrigger = function () { + // Make sure the eventInfo won't be used in next trigger. + this.eventInfo = null; + }; + return ECEventProcessor; +}(); + +var SYMBOL_PROPS_WITH_CB = ['symbol', 'symbolSize', 'symbolRotate', 'symbolOffset']; +var SYMBOL_PROPS = SYMBOL_PROPS_WITH_CB.concat(['symbolKeepAspect']); +// Encoding visual for all series include which is filtered for legend drawing +var seriesSymbolTask = { + createOnAllSeries: true, + // For legend. + performRawSeries: true, + reset: function (seriesModel, ecModel) { + var data = seriesModel.getData(); + if (seriesModel.legendIcon) { + data.setVisual('legendIcon', seriesModel.legendIcon); + } + if (!seriesModel.hasSymbolVisual) { + return; + } + var symbolOptions = {}; + var symbolOptionsCb = {}; + var hasCallback = false; + for (var i = 0; i < SYMBOL_PROPS_WITH_CB.length; i++) { + var symbolPropName = SYMBOL_PROPS_WITH_CB[i]; + var val = seriesModel.get(symbolPropName); + if (isFunction(val)) { + hasCallback = true; + symbolOptionsCb[symbolPropName] = val; + } else { + symbolOptions[symbolPropName] = val; + } + } + symbolOptions.symbol = symbolOptions.symbol || seriesModel.defaultSymbol; + data.setVisual(extend({ + legendIcon: seriesModel.legendIcon || symbolOptions.symbol, + symbolKeepAspect: seriesModel.get('symbolKeepAspect') + }, symbolOptions)); + // Only visible series has each data be visual encoded + if (ecModel.isSeriesFiltered(seriesModel)) { + return; + } + var symbolPropsCb = keys(symbolOptionsCb); + function dataEach(data, idx) { + var rawValue = seriesModel.getRawValue(idx); + var params = seriesModel.getDataParams(idx); + for (var i = 0; i < symbolPropsCb.length; i++) { + var symbolPropName = symbolPropsCb[i]; + data.setItemVisual(idx, symbolPropName, symbolOptionsCb[symbolPropName](rawValue, params)); + } + } + return { + dataEach: hasCallback ? dataEach : null + }; + } +}; +var dataSymbolTask = { + createOnAllSeries: true, + // For legend. + performRawSeries: true, + reset: function (seriesModel, ecModel) { + if (!seriesModel.hasSymbolVisual) { + return; + } + // Only visible series has each data be visual encoded + if (ecModel.isSeriesFiltered(seriesModel)) { + return; + } + var data = seriesModel.getData(); + function dataEach(data, idx) { + var itemModel = data.getItemModel(idx); + for (var i = 0; i < SYMBOL_PROPS.length; i++) { + var symbolPropName = SYMBOL_PROPS[i]; + var val = itemModel.getShallow(symbolPropName, true); + if (val != null) { + data.setItemVisual(idx, symbolPropName, val); + } + } + } + return { + dataEach: data.hasItemOption ? dataEach : null + }; + } +}; + +function getItemVisualFromData(data, dataIndex, key) { + switch (key) { + case "color": + var style = data.getItemVisual(dataIndex, "style"); + return style[data.getVisual("drawType")]; + case "opacity": + return data.getItemVisual(dataIndex, "style").opacity; + case "symbol": + case "symbolSize": + case "liftZ": + return data.getItemVisual(dataIndex, key); + } +} +function getVisualFromData(data, key) { + switch (key) { + case "color": + var style = data.getVisual("style"); + return style[data.getVisual("drawType")]; + case "opacity": + return data.getVisual("style").opacity; + case "symbol": + case "symbolSize": + case "liftZ": + return data.getVisual(key); + } +} + +function createLegacyDataSelectAction(seriesType, ecRegisterAction) { + function getSeriesIndices(ecModel, payload) { + var seriesIndices = []; + ecModel.eachComponent({ + mainType: "series", + subType: seriesType, + query: payload + }, function(seriesModel) { + seriesIndices.push(seriesModel.seriesIndex); + }); + return seriesIndices; + } + each$4([[seriesType + "ToggleSelect", "toggleSelect"], [seriesType + "Select", "select"], [seriesType + "UnSelect", "unselect"]], function(eventsMap) { + ecRegisterAction(eventsMap[0], function(payload, ecModel, api) { + payload = extend({}, payload); + api.dispatchAction(extend(payload, { + type: eventsMap[1], + seriesIndex: getSeriesIndices(ecModel, payload) + })); + }); + }); +} +function handleSeriesLegacySelectEvents(type, eventPostfix, ecIns, ecModel, payload) { + var legacyEventName = type + eventPostfix; + if (!ecIns.isSilent(legacyEventName)) { + ecModel.eachComponent({ + mainType: "series", + subType: "pie" + }, function(seriesModel) { + var seriesIndex = seriesModel.seriesIndex; + var selectedMap = seriesModel.option.selectedMap; + var selected = payload.selected; + for (var i = 0; i < selected.length; i++) { + if (selected[i].seriesIndex === seriesIndex) { + var data = seriesModel.getData(); + var dataIndex = queryDataIndex(data, payload.fromActionPayload); + ecIns.trigger(legacyEventName, { + type: legacyEventName, + seriesId: seriesModel.id, + name: isArray(dataIndex) ? data.getName(dataIndex[0]) : data.getName(dataIndex), + selected: isString(selectedMap) ? selectedMap : extend({}, selectedMap) + }); + } + } + }); + } +} +function handleLegacySelectEvents(messageCenter, ecIns, api) { + messageCenter.on("selectchanged", function(params) { + var ecModel = api.getModel(); + if (params.isFromClick) { + handleSeriesLegacySelectEvents("map", "selectchanged", ecIns, ecModel, params); + handleSeriesLegacySelectEvents("pie", "selectchanged", ecIns, ecModel, params); + } else if (params.fromAction === "select") { + handleSeriesLegacySelectEvents("map", "selected", ecIns, ecModel, params); + handleSeriesLegacySelectEvents("pie", "selected", ecIns, ecModel, params); + } else if (params.fromAction === "unselect") { + handleSeriesLegacySelectEvents("map", "unselected", ecIns, ecModel, params); + handleSeriesLegacySelectEvents("pie", "unselected", ecIns, ecModel, params); + } + }); +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 findEventDispatcher(target, det, returnFirstMatch) { + var found; + while (target) { + if (det(target)) { + found = target; + if (returnFirstMatch) { + break; + } + } + target = target.__hostTarget || target.parent; + } + return found; +} + +var wmUniqueIndex = Math.round(Math.random() * 9); +var supportDefineProperty = typeof Object.defineProperty === 'function'; +var WeakMap = (function () { + function WeakMap() { + this._id = '__ec_inner_' + wmUniqueIndex++; + } + WeakMap.prototype.get = function (key) { + return this._guard(key)[this._id]; + }; + WeakMap.prototype.set = function (key, value) { + var target = this._guard(key); + if (supportDefineProperty) { + Object.defineProperty(target, this._id, { + value: value, + enumerable: false, + configurable: true + }); + } + else { + target[this._id] = value; + } + return this; + }; + WeakMap.prototype["delete"] = function (key) { + if (this.has(key)) { + delete this._guard(key)[this._id]; + return true; + } + return false; + }; + WeakMap.prototype.has = function (key) { + return !!this._guard(key)[this._id]; + }; + WeakMap.prototype._guard = function (key) { + if (key !== Object(key)) { + throw TypeError('Value of WeakMap is not a non-null object.'); + } + return key; + }; + return WeakMap; +}()); + +/** + * Triangle shape + * @inner + */ +var Triangle = Path.extend({ + 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 = Path.extend({ + 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 = Path.extend({ + 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 = Path.extend({ + 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 constructors + */ +// TODO Use function to build symbol path. +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) { + 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$4(symbolCtors, function (Ctor, name) { + symbolBuildProxies[name] = new Ctor(); +}); +var SymbolClz = Path.extend({ + type: 'symbol', + shape: { + symbolType: '', + x: 0, + y: 0, + width: 0, + height: 0 + }, + calculateTextPosition: function (out, config, rect) { + var res = calculateTextPosition(out, config, rect); + var shape = this.shape; + if (shape && shape.symbolType === 'pin' && config.position === '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; + if (this.__isEmptyBrush) { + symbolStyle.stroke = color; + symbolStyle.fill = innerColor || '#fff'; + // TODO Same width with lineStyle in LineView + symbolStyle.lineWidth = 2; + } else if (this.shape.symbolType === 'line') { + symbolStyle.stroke = color; + } else { + symbolStyle.fill = color; + } + this.markRedraw(); + } +} +/** + * Create a symbol element with given symbol configuration: shape, x, y, width, height, color + */ +function createSymbol(symbolType, x, y, w, h, color, +// whether to keep the ratio of w/h, +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; + // TODO Should deprecate setColor + symbolPath.setColor = symbolPathSetColor; + if (color) { + symbolPath.setColor(color); + } + return symbolPath; +} +function normalizeSymbolSize(symbolSize) { + if (!isArray(symbolSize)) { + symbolSize = [+symbolSize, +symbolSize]; + } + return [symbolSize[0] || 0, symbolSize[1] || 0]; +} +function normalizeSymbolOffset(symbolOffset, symbolSize) { + if (symbolOffset == null) { + return; + } + if (!isArray(symbolOffset)) { + symbolOffset = [symbolOffset, symbolOffset]; + } + return [parsePercent(symbolOffset[0], symbolSize[0]) || 0, parsePercent(retrieve2(symbolOffset[1], symbolOffset[0]), symbolSize[1]) || 0]; +} + +function isSafeNum(num) { + return isFinite(num); +} +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; + } + x = isSafeNum(x) ? x : 0; + x2 = isSafeNum(x2) ? x2 : 1; + y = isSafeNum(y) ? y : 0; + y2 = isSafeNum(y2) ? y2 : 0; + 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; + } + x = isSafeNum(x) ? x : 0.5; + y = isSafeNum(y) ? y : 0.5; + r = r >= 0 && isSafeNum(r) ? r : 0.5; + var canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r); + return canvasGradient; +} +function getCanvasGradient(ctx, obj, rect) { + var canvasGradient = obj.type === 'radial' + ? createRadialGradient(ctx, obj, rect) + : createLinearGradient(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; +} +function isClipPathChanged(clipPaths, prevClipPaths) { + if (clipPaths === prevClipPaths || (!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 parseInt10(val) { + return parseInt(val, 10); +} +function getSize(root, whIdx, 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 stl = document.defaultView.getComputedStyle(root); + return ((root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh])) + - (parseInt10(stl[plt]) || 0) + - (parseInt10(stl[prb]) || 0)) | 0; +} + +function normalizeLineDash(lineType, lineWidth) { + if (!lineType || lineType === 'solid' || !(lineWidth > 0)) { + return null; + } + return lineType === 'dashed' + ? [4 * lineWidth, 2 * lineWidth] + : lineType === 'dotted' + ? [lineWidth] + : isNumber(lineType) + ? [lineType] : isArray(lineType) ? lineType : null; +} +function getLineDash(el) { + var style = el.style; + var lineDash = style.lineDash && style.lineWidth > 0 && normalizeLineDash(style.lineDash, style.lineWidth); + var lineDashOffset = style.lineDashOffset; + if (lineDash) { + var lineScale_1 = (style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1; + if (lineScale_1 && lineScale_1 !== 1) { + lineDash = map$1(lineDash, function (rawVal) { + return rawVal / lineScale_1; + }); + lineDashOffset /= lineScale_1; + } + } + return [lineDash, lineDashOffset]; +} + +var pathProxyForDraw = new PathProxy(true); +function styleHasStroke(style) { + var stroke = style.stroke; + return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0)); +} +function isValidStrokeFillStyle(strokeOrFill) { + return typeof strokeOrFill === 'string' && strokeOrFill !== 'none'; +} +function styleHasFill(style) { + var fill = style.fill; + return fill != null && fill !== 'none'; +} +function doFillPath(ctx, style) { + if (style.fillOpacity != null && style.fillOpacity !== 1) { + var originalGlobalAlpha = ctx.globalAlpha; + ctx.globalAlpha = style.fillOpacity * style.opacity; + ctx.fill(); + ctx.globalAlpha = originalGlobalAlpha; + } + else { + ctx.fill(); + } +} +function doStrokePath(ctx, style) { + if (style.strokeOpacity != null && style.strokeOpacity !== 1) { + var originalGlobalAlpha = ctx.globalAlpha; + ctx.globalAlpha = style.strokeOpacity * style.opacity; + ctx.stroke(); + ctx.globalAlpha = originalGlobalAlpha; + } + else { + ctx.stroke(); + } +} +function createCanvasPattern(ctx, pattern, el) { + var image = createOrUpdateImage(pattern.image, pattern.__image, el); + if (isImageReady(image)) { + var canvasPattern = ctx.createPattern(image, pattern.repeat || 'repeat'); + if (typeof DOMMatrix === 'function' + && canvasPattern + && canvasPattern.setTransform) { + var matrix = new DOMMatrix(); + matrix.translateSelf((pattern.x || 0), (pattern.y || 0)); + matrix.rotateSelf(0, 0, (pattern.rotation || 0) * RADIAN_TO_DEGREE); + matrix.scaleSelf((pattern.scaleX || 1), (pattern.scaleY || 1)); + canvasPattern.setTransform(matrix); + } + return canvasPattern; + } +} +function brushPath(ctx, el, style, inBatch) { + var _a; + var hasStroke = styleHasStroke(style); + var hasFill = styleHasFill(style); + var strokePercent = style.strokePercent; + var strokePart = strokePercent < 1; + var firstDraw = !el.path; + if ((!el.silent || strokePart) && firstDraw) { + el.createPathProxy(); + } + var path = el.path || pathProxyForDraw; + var dirtyFlag = el.__dirty; + if (!inBatch) { + 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; + var fillGradient = void 0; + var strokeGradient = void 0; + var fillPattern = void 0; + var strokePattern = void 0; + var rect = void 0; + if (hasFillGradient || hasStrokeGradient) { + rect = el.getBoundingRect(); + } + if (hasFillGradient) { + fillGradient = dirtyFlag + ? getCanvasGradient(ctx, fill, rect) + : el.__canvasFillGradient; + el.__canvasFillGradient = fillGradient; + } + if (hasStrokeGradient) { + strokeGradient = dirtyFlag + ? getCanvasGradient(ctx, stroke, rect) + : el.__canvasStrokeGradient; + el.__canvasStrokeGradient = strokeGradient; + } + if (hasFillPattern) { + fillPattern = (dirtyFlag || !el.__canvasFillPattern) + ? createCanvasPattern(ctx, fill, el) + : el.__canvasFillPattern; + el.__canvasFillPattern = fillPattern; + } + if (hasStrokePattern) { + strokePattern = (dirtyFlag || !el.__canvasStrokePattern) + ? createCanvasPattern(ctx, stroke, el) + : el.__canvasStrokePattern; + el.__canvasStrokePattern = fillPattern; + } + if (hasFillGradient) { + ctx.fillStyle = fillGradient; + } + else if (hasFillPattern) { + if (fillPattern) { + ctx.fillStyle = fillPattern; + } + else { + hasFill = false; + } + } + if (hasStrokeGradient) { + ctx.strokeStyle = strokeGradient; + } + else if (hasStrokePattern) { + if (strokePattern) { + ctx.strokeStyle = strokePattern; + } + else { + hasStroke = false; + } + } + } + var scale = el.getGlobalScale(); + path.setScale(scale[0], scale[1], el.segmentIgnoreThreshold); + var lineDash; + var lineDashOffset; + if (ctx.setLineDash && style.lineDash) { + _a = getLineDash(el), lineDash = _a[0], lineDashOffset = _a[1]; + } + var needsRebuild = true; + if (firstDraw || (dirtyFlag & SHAPE_CHANGED_BIT)) { + path.setDPR(ctx.dpr); + if (strokePart) { + path.setContext(null); + } + else { + path.setContext(ctx); + needsRebuild = false; + } + path.reset(); + el.buildPath(path, el.shape, inBatch); + path.toStatic(); + el.pathUpdated(); + } + if (needsRebuild) { + path.rebuildPath(ctx, strokePart ? strokePercent : 1); + } + if (lineDash) { + ctx.setLineDash(lineDash); + ctx.lineDashOffset = lineDashOffset; + } + if (!inBatch) { + if (style.strokeFirst) { + if (hasStroke) { + doStrokePath(ctx, style); + } + if (hasFill) { + doFillPath(ctx, style); + } + } + else { + if (hasFill) { + doFillPath(ctx, style); + } + if (hasStroke) { + doStrokePath(ctx, style); + } + } + } + if (lineDash) { + ctx.setLineDash([]); + } +} +function brushImage(ctx, el, style) { + var image = el.__image = createOrUpdateImage(style.image, el.__image, el, el.onload); + if (!image || !isImageReady(image)) { + return; + } + var x = style.x || 0; + var y = style.y || 0; + var width = el.getWidth(); + var height = el.getHeight(); + var aspect = image.width / image.height; + if (width == null && height != null) { + width = height * aspect; + } + else if (height == null && width != null) { + height = width / aspect; + } + else if (width == null && height == null) { + width = image.width; + height = image.height; + } + 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); + } +} +function brushText(ctx, el, style) { + var _a; + var text = style.text; + text != null && (text += ''); + if (text) { + ctx.font = style.font || DEFAULT_FONT; + ctx.textAlign = style.textAlign; + ctx.textBaseline = style.textBaseline; + var lineDash = void 0; + var lineDashOffset = void 0; + if (ctx.setLineDash && style.lineDash) { + _a = getLineDash(el), lineDash = _a[0], lineDashOffset = _a[1]; + } + if (lineDash) { + ctx.setLineDash(lineDash); + ctx.lineDashOffset = lineDashOffset; + } + if (style.strokeFirst) { + if (styleHasStroke(style)) { + ctx.strokeText(text, style.x, style.y); + } + if (styleHasFill(style)) { + ctx.fillText(text, style.x, style.y); + } + } + else { + if (styleHasFill(style)) { + ctx.fillText(text, style.x, style.y); + } + if (styleHasStroke(style)) { + ctx.strokeText(text, style.x, style.y); + } + } + if (lineDash) { + ctx.setLineDash([]); + } + } +} +var SHADOW_NUMBER_PROPS = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY']; +var STROKE_PROPS = [ + ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10] +]; +function bindCommonProps(ctx, style, prevStyle, forceSetAll, scope) { + var styleChanged = false; + if (!forceSetAll) { + prevStyle = prevStyle || {}; + if (style === prevStyle) { + return false; + } + } + if (forceSetAll || style.opacity !== prevStyle.opacity) { + flushPathDrawn(ctx, scope); + styleChanged = true; + var opacity = Math.max(Math.min(style.opacity, 1), 0); + ctx.globalAlpha = isNaN(opacity) ? DEFAULT_COMMON_STYLE.opacity : opacity; + } + if (forceSetAll || style.blend !== prevStyle.blend) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + ctx.globalCompositeOperation = style.blend || DEFAULT_COMMON_STYLE.blend; + } + for (var i = 0; i < SHADOW_NUMBER_PROPS.length; i++) { + var propName = SHADOW_NUMBER_PROPS[i]; + if (forceSetAll || style[propName] !== prevStyle[propName]) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + ctx[propName] = ctx.dpr * (style[propName] || 0); + } + } + if (forceSetAll || style.shadowColor !== prevStyle.shadowColor) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + ctx.shadowColor = style.shadowColor || DEFAULT_COMMON_STYLE.shadowColor; + } + return styleChanged; +} +function bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetAll, scope) { + var style = getStyle(el, scope.inHover); + var prevStyle = forceSetAll + ? null + : (prevEl && getStyle(prevEl, scope.inHover) || {}); + if (style === prevStyle) { + return false; + } + var styleChanged = bindCommonProps(ctx, style, prevStyle, forceSetAll, scope); + if (forceSetAll || style.fill !== prevStyle.fill) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + isValidStrokeFillStyle(style.fill) && (ctx.fillStyle = style.fill); + } + if (forceSetAll || style.stroke !== prevStyle.stroke) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + isValidStrokeFillStyle(style.stroke) && (ctx.strokeStyle = style.stroke); + } + if (forceSetAll || style.opacity !== prevStyle.opacity) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + ctx.globalAlpha = style.opacity == null ? 1 : style.opacity; + } + if (el.hasStroke()) { + var lineWidth = style.lineWidth; + var newLineWidth = lineWidth / ((style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1); + if (ctx.lineWidth !== newLineWidth) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + ctx.lineWidth = newLineWidth; + } + } + for (var i = 0; i < STROKE_PROPS.length; i++) { + var prop = STROKE_PROPS[i]; + var propName = prop[0]; + if (forceSetAll || style[propName] !== prevStyle[propName]) { + if (!styleChanged) { + flushPathDrawn(ctx, scope); + styleChanged = true; + } + ctx[propName] = style[propName] || prop[1]; + } + } + return styleChanged; +} +function bindImageStyle(ctx, el, prevEl, forceSetAll, scope) { + return bindCommonProps(ctx, getStyle(el, scope.inHover), prevEl && getStyle(prevEl, scope.inHover), forceSetAll, scope); +} +function setContextTransform(ctx, el) { + var m = el.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); + } +} +function updateClipStatus(clipPaths, ctx, scope) { + var allClipped = false; + for (var i = 0; i < clipPaths.length; i++) { + var clipPath = clipPaths[i]; + allClipped = allClipped || clipPath.isZeroArea(); + setContextTransform(ctx, clipPath); + ctx.beginPath(); + clipPath.buildPath(ctx, clipPath.shape); + ctx.clip(); + } + scope.allClipped = allClipped; +} +function isTransformChanged(m0, m1) { + if (m0 && m1) { + return m0[0] !== m1[0] + || m0[1] !== m1[1] + || m0[2] !== m1[2] + || m0[3] !== m1[3] + || m0[4] !== m1[4] + || m0[5] !== m1[5]; + } + else if (!m0 && !m1) { + return false; + } + return true; +} +var DRAW_TYPE_PATH = 1; +var DRAW_TYPE_IMAGE = 2; +var DRAW_TYPE_TEXT = 3; +var DRAW_TYPE_INCREMENTAL = 4; +function canPathBatch(style) { + var hasFill = styleHasFill(style); + var hasStroke = styleHasStroke(style); + return !(style.lineDash + || !(+hasFill ^ +hasStroke) + || (hasFill && typeof style.fill !== 'string') + || (hasStroke && typeof style.stroke !== 'string') + || style.strokePercent < 1 + || style.strokeOpacity < 1 + || style.fillOpacity < 1); +} +function flushPathDrawn(ctx, scope) { + scope.batchFill && ctx.fill(); + scope.batchStroke && ctx.stroke(); + scope.batchFill = ''; + scope.batchStroke = ''; +} +function getStyle(el, inHover) { + return inHover ? (el.__hoverStyle || el.style) : el.style; +} +function brushSingle(ctx, el) { + brush(ctx, el, { inHover: false, viewWidth: 0, viewHeight: 0 }, true); +} +function brush(ctx, el, scope, isLast) { + var m = el.transform; + if (!el.shouldBePainted(scope.viewWidth, scope.viewHeight, false, false)) { + el.__dirty &= -2; + el.__isRendered = false; + return; + } + var clipPaths = el.__clipPaths; + var prevElClipPaths = scope.prevElClipPaths; + var forceSetTransform = false; + var forceSetStyle = false; + if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) { + if (prevElClipPaths && prevElClipPaths.length) { + flushPathDrawn(ctx, scope); + ctx.restore(); + forceSetStyle = forceSetTransform = true; + scope.prevElClipPaths = null; + scope.allClipped = false; + scope.prevEl = null; + } + if (clipPaths && clipPaths.length) { + flushPathDrawn(ctx, scope); + ctx.save(); + updateClipStatus(clipPaths, ctx, scope); + forceSetTransform = true; + } + scope.prevElClipPaths = clipPaths; + } + if (scope.allClipped) { + el.__isRendered = false; + return; + } + el.beforeBrush && el.beforeBrush(); + el.innerBeforeBrush(); + var prevEl = scope.prevEl; + if (!prevEl) { + forceSetStyle = forceSetTransform = true; + } + var canBatchPath = el instanceof Path + && el.autoBatch + && canPathBatch(el.style); + if (forceSetTransform || isTransformChanged(m, prevEl.transform)) { + flushPathDrawn(ctx, scope); + setContextTransform(ctx, el); + } + else if (!canBatchPath) { + flushPathDrawn(ctx, scope); + } + var style = getStyle(el, scope.inHover); + if (el instanceof Path) { + if (scope.lastDrawType !== DRAW_TYPE_PATH) { + forceSetStyle = true; + scope.lastDrawType = DRAW_TYPE_PATH; + } + bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope); + if (!canBatchPath || (!scope.batchFill && !scope.batchStroke)) { + ctx.beginPath(); + } + brushPath(ctx, el, style, canBatchPath); + if (canBatchPath) { + scope.batchFill = style.fill || ''; + scope.batchStroke = style.stroke || ''; + } + } + else { + if (el instanceof TSpan) { + if (scope.lastDrawType !== DRAW_TYPE_TEXT) { + forceSetStyle = true; + scope.lastDrawType = DRAW_TYPE_TEXT; + } + bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope); + brushText(ctx, el, style); + } + else if (el instanceof ZRImage) { + if (scope.lastDrawType !== DRAW_TYPE_IMAGE) { + forceSetStyle = true; + scope.lastDrawType = DRAW_TYPE_IMAGE; + } + bindImageStyle(ctx, el, prevEl, forceSetStyle, scope); + brushImage(ctx, el, style); + } + else if (el.getTemporalDisplayables) { + if (scope.lastDrawType !== DRAW_TYPE_INCREMENTAL) { + forceSetStyle = true; + scope.lastDrawType = DRAW_TYPE_INCREMENTAL; + } + brushIncremental(ctx, el, scope); + } + } + if (canBatchPath && isLast) { + flushPathDrawn(ctx, scope); + } + el.innerAfterBrush(); + el.afterBrush && el.afterBrush(); + scope.prevEl = el; + el.__dirty = 0; + el.__isRendered = true; +} +function brushIncremental(ctx, el, scope) { + var displayables = el.getDisplayables(); + var temporalDisplayables = el.getTemporalDisplayables(); + ctx.save(); + var innerScope = { + prevElClipPaths: null, + prevEl: null, + allClipped: false, + viewWidth: scope.viewWidth, + viewHeight: scope.viewHeight, + inHover: scope.inHover + }; + var i; + var len; + for (i = el.getCursor(), len = displayables.length; i < len; i++) { + var displayable = displayables[i]; + displayable.beforeBrush && displayable.beforeBrush(); + displayable.innerBeforeBrush(); + brush(ctx, displayable, innerScope, i === len - 1); + displayable.innerAfterBrush(); + displayable.afterBrush && displayable.afterBrush(); + innerScope.prevEl = displayable; + } + for (var i_1 = 0, len_1 = temporalDisplayables.length; i_1 < len_1; i_1++) { + var displayable = temporalDisplayables[i_1]; + displayable.beforeBrush && displayable.beforeBrush(); + displayable.innerBeforeBrush(); + brush(ctx, displayable, innerScope, i_1 === len_1 - 1); + displayable.innerAfterBrush(); + displayable.afterBrush && displayable.afterBrush(); + innerScope.prevEl = displayable; + } + el.clearTemporalDisplayables(); + el.notClear = true; + ctx.restore(); +} + +var decalMap = new WeakMap(); +var decalCache = new LRU(100); +var decalKeys = ["symbol", "symbolSize", "symbolKeepAspect", "color", "backgroundColor", "dashArrayX", "dashArrayY", "maxTileWidth", "maxTileHeight"]; +function createOrUpdatePatternFromDecal(decalObject, api) { + if (decalObject === "none") { + return null; + } + var dpr = api.getDevicePixelRatio(); + var zr = api.getZr(); + var isSVG = zr.painter.type === "svg"; + if (decalObject.dirty) { + decalMap["delete"](decalObject); + } + var oldPattern = decalMap.get(decalObject); + if (oldPattern) { + return oldPattern; + } + var decalOpt = defaults(decalObject, { + symbol: "rect", + symbolSize: 1, + symbolKeepAspect: true, + color: "rgba(0, 0, 0, 0.2)", + backgroundColor: null, + dashArrayX: 5, + dashArrayY: 5, + rotation: 0, + maxTileWidth: 512, + maxTileHeight: 512 + }); + if (decalOpt.backgroundColor === "none") { + decalOpt.backgroundColor = null; + } + var pattern = { + repeat: "repeat" + }; + setPatternnSource(pattern); + pattern.rotation = decalOpt.rotation; + pattern.scaleX = pattern.scaleY = isSVG ? 1 : 1 / dpr; + decalMap.set(decalObject, pattern); + decalObject.dirty = false; + return pattern; + function setPatternnSource(pattern2) { + var keys = [dpr]; + var isValidKey = true; + for (var i = 0; i < decalKeys.length; ++i) { + var value = decalOpt[decalKeys[i]]; + if (value != null && !isArray(value) && !isString(value) && !isNumber(value) && typeof value !== "boolean") { + isValidKey = false; + break; + } + keys.push(value); + } + var cacheKey; + if (isValidKey) { + cacheKey = keys.join(",") + (isSVG ? "-svg" : ""); + var cache = decalCache.get(cacheKey); + if (cache) { + isSVG ? pattern2.svgElement = cache : pattern2.image = cache; + } + } + var dashArrayX = normalizeDashArrayX(decalOpt.dashArrayX); + var dashArrayY = normalizeDashArrayY(decalOpt.dashArrayY); + var symbolArray = normalizeSymbolArray(decalOpt.symbol); + var lineBlockLengthsX = getLineBlockLengthX(dashArrayX); + var lineBlockLengthY = getLineBlockLengthY(dashArrayY); + var canvas = !isSVG && platformApi.createCanvas(); + var svgRoot = isSVG && { + tag: "g", + attrs: {}, + key: "dcl", + children: [] + }; + var pSize = getPatternSize(); + var ctx; + if (canvas) { + canvas.width = pSize.width * dpr; + canvas.height = pSize.height * dpr; + ctx = canvas.getContext("2d"); + } + brushDecal(); + if (isValidKey) { + decalCache.put(cacheKey, canvas || svgRoot); + } + pattern2.image = canvas; + pattern2.svgElement = svgRoot; + pattern2.svgWidth = pSize.width; + pattern2.svgHeight = pSize.height; + function getPatternSize() { + var width = 1; + for (var i2 = 0, xlen = lineBlockLengthsX.length; i2 < xlen; ++i2) { + width = getLeastCommonMultiple(width, lineBlockLengthsX[i2]); + } + var symbolRepeats = 1; + for (var i2 = 0, xlen = symbolArray.length; i2 < xlen; ++i2) { + symbolRepeats = getLeastCommonMultiple(symbolRepeats, symbolArray[i2].length); + } + width *= symbolRepeats; + var height = lineBlockLengthY * lineBlockLengthsX.length * symbolArray.length; + return { + width: Math.max(1, Math.min(width, decalOpt.maxTileWidth)), + height: Math.max(1, Math.min(height, decalOpt.maxTileHeight)) + }; + } + function brushDecal() { + if (ctx) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + if (decalOpt.backgroundColor) { + ctx.fillStyle = decalOpt.backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + } + var ySum = 0; + for (var i2 = 0; i2 < dashArrayY.length; ++i2) { + ySum += dashArrayY[i2]; + } + if (ySum <= 0) { + return; + } + var y = -lineBlockLengthY; + var yId = 0; + var yIdTotal = 0; + var xId0 = 0; + while (y < pSize.height) { + if (yId % 2 === 0) { + var symbolYId = yIdTotal / 2 % symbolArray.length; + var x = 0; + var xId1 = 0; + var xId1Total = 0; + while (x < pSize.width * 2) { + var xSum = 0; + for (var i2 = 0; i2 < dashArrayX[xId0].length; ++i2) { + xSum += dashArrayX[xId0][i2]; + } + if (xSum <= 0) { + break; + } + if (xId1 % 2 === 0) { + var size = (1 - decalOpt.symbolSize) * 0.5; + var left = x + dashArrayX[xId0][xId1] * size; + var top_1 = y + dashArrayY[yId] * size; + var width = dashArrayX[xId0][xId1] * decalOpt.symbolSize; + var height = dashArrayY[yId] * decalOpt.symbolSize; + var symbolXId = xId1Total / 2 % symbolArray[symbolYId].length; + brushSymbol(left, top_1, width, height, symbolArray[symbolYId][symbolXId]); + } + x += dashArrayX[xId0][xId1]; + ++xId1Total; + ++xId1; + if (xId1 === dashArrayX[xId0].length) { + xId1 = 0; + } + } + ++xId0; + if (xId0 === dashArrayX.length) { + xId0 = 0; + } + } + y += dashArrayY[yId]; + ++yIdTotal; + ++yId; + if (yId === dashArrayY.length) { + yId = 0; + } + } + function brushSymbol(x2, y2, width2, height2, symbolType) { + var scale = isSVG ? 1 : dpr; + var symbol = createSymbol(symbolType, x2 * scale, y2 * scale, width2 * scale, height2 * scale, decalOpt.color, decalOpt.symbolKeepAspect); + if (isSVG) { + var symbolVNode = zr.painter.renderOneToVNode(symbol); + if (symbolVNode) { + svgRoot.children.push(symbolVNode); + } + } else { + brushSingle(ctx, symbol); + } + } + } + } +} +function normalizeSymbolArray(symbol) { + if (!symbol || symbol.length === 0) { + return [["rect"]]; + } + if (isString(symbol)) { + return [[symbol]]; + } + var isAllString = true; + for (var i = 0; i < symbol.length; ++i) { + if (!isString(symbol[i])) { + isAllString = false; + break; + } + } + if (isAllString) { + return normalizeSymbolArray([symbol]); + } + var result = []; + for (var i = 0; i < symbol.length; ++i) { + if (isString(symbol[i])) { + result.push([symbol[i]]); + } else { + result.push(symbol[i]); + } + } + return result; +} +function normalizeDashArrayX(dash) { + if (!dash || dash.length === 0) { + return [[0, 0]]; + } + if (isNumber(dash)) { + var dashValue = Math.ceil(dash); + return [[dashValue, dashValue]]; + } + var isAllNumber = true; + for (var i = 0; i < dash.length; ++i) { + if (!isNumber(dash[i])) { + isAllNumber = false; + break; + } + } + if (isAllNumber) { + return normalizeDashArrayX([dash]); + } + var result = []; + for (var i = 0; i < dash.length; ++i) { + if (isNumber(dash[i])) { + var dashValue = Math.ceil(dash[i]); + result.push([dashValue, dashValue]); + } else { + var dashValue = map$1(dash[i], function(n) { + return Math.ceil(n); + }); + if (dashValue.length % 2 === 1) { + result.push(dashValue.concat(dashValue)); + } else { + result.push(dashValue); + } + } + } + return result; +} +function normalizeDashArrayY(dash) { + if (!dash || typeof dash === "object" && dash.length === 0) { + return [0, 0]; + } + if (isNumber(dash)) { + var dashValue_1 = Math.ceil(dash); + return [dashValue_1, dashValue_1]; + } + var dashValue = map$1(dash, function(n) { + return Math.ceil(n); + }); + return dash.length % 2 ? dashValue.concat(dashValue) : dashValue; +} +function getLineBlockLengthX(dash) { + return map$1(dash, function(line) { + return getLineBlockLengthY(line); + }); +} +function getLineBlockLengthY(dash) { + var blockLength = 0; + for (var i = 0; i < dash.length; ++i) { + blockLength += dash[i]; + } + if (dash.length % 2 === 1) { + return blockLength * 2; + } + return blockLength; +} + +function decalVisual(ecModel, api) { + ecModel.eachRawSeries(function (seriesModel) { + if (ecModel.isSeriesFiltered(seriesModel)) { + return; + } + var data = seriesModel.getData(); + if (data.hasItemVisual()) { + data.each(function (idx) { + var decal = data.getItemVisual(idx, 'decal'); + if (decal) { + var itemStyle = data.ensureUniqueItemVisual(idx, 'style'); + itemStyle.decal = createOrUpdatePatternFromDecal(decal, api); + } + }); + } + var decal = data.getVisual('decal'); + if (decal) { + var style = data.getVisual('style'); + style.decal = createOrUpdatePatternFromDecal(decal, api); + } + }); +} + +var lifecycle = new Eventful(); + +var implsStore = {}; +function registerImpl(name, impl) { + implsStore[name] = impl; +} +function getImpl(name) { + return implsStore[name]; +} + +var TEST_FRAME_REMAIN_TIME = 1; +var PRIORITY_PROCESSOR_SERIES_FILTER = 800; +var PRIORITY_PROCESSOR_DATASTACK = 900; +var PRIORITY_PROCESSOR_FILTER = 1e3; +var PRIORITY_PROCESSOR_DEFAULT = 2e3; +var PRIORITY_PROCESSOR_STATISTIC = 5e3; +var PRIORITY_VISUAL_LAYOUT = 1e3; +var PRIORITY_VISUAL_PROGRESSIVE_LAYOUT = 1100; +var PRIORITY_VISUAL_GLOBAL = 2e3; +var PRIORITY_VISUAL_CHART = 3e3; +var PRIORITY_VISUAL_COMPONENT = 4e3; +var PRIORITY_VISUAL_CHART_DATA_CUSTOM = 4500; +var PRIORITY_VISUAL_POST_CHART_LAYOUT = 4600; +var PRIORITY_VISUAL_BRUSH = 5e3; +var PRIORITY_VISUAL_ARIA = 6e3; +var PRIORITY_VISUAL_DECAL = 7e3; +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, + CHART_ITEM: PRIORITY_VISUAL_CHART_DATA_CUSTOM, + ARIA: PRIORITY_VISUAL_ARIA, + DECAL: PRIORITY_VISUAL_DECAL + } +}; +var IN_MAIN_PROCESS_KEY = "__flagInMainProcess"; +var PENDING_UPDATE = "__pendingUpdate"; +var STATUS_NEEDS_UPDATE_KEY = "__needsUpdateStatus"; +var ACTION_REG = /^[a-zA-Z0-9_]+$/; +var CONNECT_STATUS_KEY = "__connectUpdateStatus"; +var CONNECT_STATUS_PENDING = 0; +var CONNECT_STATUS_UPDATING = 1; +var CONNECT_STATUS_UPDATED = 2; +function createRegisterEventWithLowercaseECharts(method) { + return function() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + if (this.isDisposed()) { + disposedWarning(this.id); + return; + } + return toLowercaseNameAndCallEventful(this, method, args); + }; +} +function createRegisterEventWithLowercaseMessageCenter(method) { + return function() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return toLowercaseNameAndCallEventful(this, method, args); + }; +} +function toLowercaseNameAndCallEventful(host, method, args) { + args[0] = args[0] && args[0].toLowerCase(); + return Eventful.prototype[method].apply(host, args); +} +var MessageCenter = ( + /** @class */ + function(_super) { + __extends(MessageCenter2, _super); + function MessageCenter2() { + return _super !== null && _super.apply(this, arguments) || this; + } + return MessageCenter2; + }(Eventful) +); +var messageCenterProto = MessageCenter.prototype; +messageCenterProto.on = createRegisterEventWithLowercaseMessageCenter("on"); +messageCenterProto.off = createRegisterEventWithLowercaseMessageCenter("off"); +var prepare; +var prepareView; +var updateDirectly; +var updateMethods; +var doConvertPixel; +var updateStreamModes; +var doDispatchAction; +var flushPendingActions; +var triggerUpdatedEvent; +var bindRenderedEvent; +var bindMouseEvent; +var render; +var renderComponents; +var renderSeries; +var createExtensionAPI; +var enableConnect; +var markStatusToUpdate; +var applyChangedStates; +var ECharts = ( + /** @class */ + function(_super) { + __extends(ECharts2, _super); + function ECharts2(dom, theme, opts) { + var _this = _super.call(this, new ECEventProcessor()) || this; + _this._chartsViews = []; + _this._chartsMap = {}; + _this._componentsViews = []; + _this._componentsMap = {}; + _this._pendingActions = []; + opts = opts || {}; + if (isString(theme)) { + theme = themeStorage[theme]; + } + _this._dom = dom; + var defaultRenderer = "canvas"; + var defaultCoarsePointer = "auto"; + var defaultUseDirtyRect = false; + if (opts.ssr) ; + var zr = _this._zr = init$1(dom, { + renderer: opts.renderer || defaultRenderer, + devicePixelRatio: opts.devicePixelRatio, + width: opts.width, + height: opts.height, + ssr: opts.ssr, + useDirtyRect: retrieve2(opts.useDirtyRect, defaultUseDirtyRect), + useCoarsePointer: retrieve2(opts.useCoarsePointer, defaultCoarsePointer), + pointerSize: opts.pointerSize + }); + _this._ssr = opts.ssr; + _this._throttledZrFlush = throttle(bind$1(zr.flush, zr), 17); + theme = clone$2(theme); + theme && globalBackwardCompat(theme, true); + _this._theme = theme; + _this._locale = createLocaleObject(opts.locale || SYSTEM_LANG); + _this._coordSysMgr = new CoordinateSystemManager(); + var api = _this._api = createExtensionAPI(_this); + function prioritySortFunc(a, b) { + return a.__prio - b.__prio; + } + sort(visualFuncs, prioritySortFunc); + sort(dataProcessorFuncs, prioritySortFunc); + _this._scheduler = new Scheduler(_this, api, dataProcessorFuncs, visualFuncs); + _this._messageCenter = new MessageCenter(); + _this._initEvents(); + _this.resize = bind$1(_this.resize, _this); + zr.animation.on("frame", _this._onframe, _this); + bindRenderedEvent(zr, _this); + bindMouseEvent(zr, _this); + setAsPrimitive(_this); + return _this; + } + ECharts2.prototype._onframe = function() { + if (this._disposed) { + return; + } + applyChangedStates(this); + var scheduler = this._scheduler; + if (this[PENDING_UPDATE]) { + var silent = this[PENDING_UPDATE].silent; + this[IN_MAIN_PROCESS_KEY] = true; + try { + prepare(this); + updateMethods.update.call(this, null, this[PENDING_UPDATE].updateParams); + } catch (e) { + this[IN_MAIN_PROCESS_KEY] = false; + this[PENDING_UPDATE] = null; + throw e; + } + this._zr.flush(); + this[IN_MAIN_PROCESS_KEY] = false; + this[PENDING_UPDATE] = null; + flushPendingActions.call(this, silent); + triggerUpdatedEvent.call(this, silent); + } else if (scheduler.unfinished) { + var remainTime = TEST_FRAME_REMAIN_TIME; + var ecModel = this._model; + var api = this._api; + scheduler.unfinished = false; + do { + var startTime = +/* @__PURE__ */ new Date(); + scheduler.performSeriesTasks(ecModel); + scheduler.performDataProcessorTasks(ecModel); + updateStreamModes(this, ecModel); + scheduler.performVisualTasks(ecModel); + renderSeries(this, this._model, api, "remain", {}); + remainTime -= +/* @__PURE__ */ new Date() - startTime; + } while (remainTime > 0 && scheduler.unfinished); + if (!scheduler.unfinished) { + this._zr.flush(); + } + } + }; + ECharts2.prototype.getDom = function() { + return this._dom; + }; + ECharts2.prototype.getId = function() { + return this.id; + }; + ECharts2.prototype.getZr = function() { + return this._zr; + }; + ECharts2.prototype.isSSR = function() { + return this._ssr; + }; + ECharts2.prototype.setOption = function(option, notMerge, lazyUpdate) { + if (this[IN_MAIN_PROCESS_KEY]) { + return; + } + if (this._disposed) { + disposedWarning(this.id); + return; + } + var silent; + var replaceMerge; + var transitionOpt; + if (isObject$2(notMerge)) { + lazyUpdate = notMerge.lazyUpdate; + silent = notMerge.silent; + replaceMerge = notMerge.replaceMerge; + transitionOpt = notMerge.transition; + notMerge = notMerge.notMerge; + } + this[IN_MAIN_PROCESS_KEY] = true; + if (!this._model || notMerge) { + var optionManager = new OptionManager(this._api); + var theme = this._theme; + var ecModel = this._model = new GlobalModel(); + ecModel.scheduler = this._scheduler; + ecModel.ssr = this._ssr; + ecModel.init(null, null, null, theme, this._locale, optionManager); + } + this._model.setOption(option, { + replaceMerge + }, optionPreprocessorFuncs); + var updateParams = { + seriesTransition: transitionOpt, + optionChanged: true + }; + if (lazyUpdate) { + this[PENDING_UPDATE] = { + silent, + updateParams + }; + this[IN_MAIN_PROCESS_KEY] = false; + this.getZr().wakeUp(); + } else { + try { + prepare(this); + updateMethods.update.call(this, null, updateParams); + } catch (e) { + this[PENDING_UPDATE] = null; + this[IN_MAIN_PROCESS_KEY] = false; + throw e; + } + if (!this._ssr) { + this._zr.flush(); + } + this[PENDING_UPDATE] = null; + this[IN_MAIN_PROCESS_KEY] = false; + flushPendingActions.call(this, silent); + triggerUpdatedEvent.call(this, silent); + } + }; + ECharts2.prototype.setTheme = function() { + }; + ECharts2.prototype.getModel = function() { + return this._model; + }; + ECharts2.prototype.getOption = function() { + return this._model && this._model.getOption(); + }; + ECharts2.prototype.getWidth = function() { + return this._zr.getWidth(); + }; + ECharts2.prototype.getHeight = function() { + return this._zr.getHeight(); + }; + ECharts2.prototype.getDevicePixelRatio = function() { + return this._zr.painter.dpr || env.hasGlobalWindow && window.devicePixelRatio || 1; + }; + ECharts2.prototype.getRenderedCanvas = function(opts) { + return this.renderToCanvas(opts); + }; + ECharts2.prototype.renderToCanvas = function(opts) { + opts = opts || {}; + var painter = this._zr.painter; + return painter.getRenderedCanvas({ + backgroundColor: opts.backgroundColor || this._model.get("backgroundColor"), + pixelRatio: opts.pixelRatio || this.getDevicePixelRatio() + }); + }; + ECharts2.prototype.renderToSVGString = function(opts) { + opts = opts || {}; + var painter = this._zr.painter; + return painter.renderToString({ + useViewBox: opts.useViewBox + }); + }; + ECharts2.prototype.getSvgDataURL = function() { + if (!env.svgSupported) { + return; + } + var zr = this._zr; + var list = zr.storage.getDisplayList(); + each$4(list, function(el) { + el.stopAnimation(null, true); + }); + return zr.painter.toDataURL(); + }; + ECharts2.prototype.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$4(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.renderToCanvas(opts).toDataURL("image/" + (opts && opts.type || "png")); + each$4(excludesComponentViews, function(view) { + view.group.ignore = false; + }); + return url; + }; + ECharts2.prototype.getConnectedDataURL = function(opts) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + var isSvg = opts.type === "svg"; + var groupId = this.group; + var mathMin = Math.min; + var mathMax = Math.max; + var MAX_NUMBER = Infinity; + if (connectedGroups[groupId]) { + var left_1 = MAX_NUMBER; + var top_1 = MAX_NUMBER; + var right_1 = -MAX_NUMBER; + var bottom_1 = -MAX_NUMBER; + var canvasList_1 = []; + var dpr_1 = opts && opts.pixelRatio || this.getDevicePixelRatio(); + each$4(instances, function(chart, id) { + if (chart.group === groupId) { + var canvas = isSvg ? chart.getZr().painter.getSvgDom().innerHTML : chart.renderToCanvas(clone$2(opts)); + var boundingRect = chart.getDom().getBoundingClientRect(); + left_1 = mathMin(boundingRect.left, left_1); + top_1 = mathMin(boundingRect.top, top_1); + right_1 = mathMax(boundingRect.right, right_1); + bottom_1 = mathMax(boundingRect.bottom, bottom_1); + canvasList_1.push({ + dom: canvas, + left: boundingRect.left, + top: boundingRect.top + }); + } + }); + left_1 *= dpr_1; + top_1 *= dpr_1; + right_1 *= dpr_1; + bottom_1 *= dpr_1; + var width = right_1 - left_1; + var height = bottom_1 - top_1; + var targetCanvas = platformApi.createCanvas(); + var zr_1 = init$1(targetCanvas, { + renderer: isSvg ? "svg" : "canvas" + }); + zr_1.resize({ + width, + height + }); + if (isSvg) { + var content_1 = ""; + each$4(canvasList_1, function(item) { + var x = item.left - left_1; + var y = item.top - top_1; + content_1 += '' + item.dom + ""; + }); + zr_1.painter.getSvgRoot().innerHTML = content_1; + if (opts.connectedBackgroundColor) { + zr_1.painter.setBackgroundColor(opts.connectedBackgroundColor); + } + zr_1.refreshImmediately(); + return zr_1.painter.toDataURL(); + } else { + if (opts.connectedBackgroundColor) { + zr_1.add(new Rect({ + shape: { + x: 0, + y: 0, + width, + height + }, + style: { + fill: opts.connectedBackgroundColor + } + })); + } + each$4(canvasList_1, function(item) { + var img = new ZRImage({ + style: { + x: item.left * dpr_1 - left_1, + y: item.top * dpr_1 - top_1, + image: item.dom + } + }); + zr_1.add(img); + }); + zr_1.refreshImmediately(); + return targetCanvas.toDataURL("image/" + (opts && opts.type || "png")); + } + } else { + return this.getDataURL(opts); + } + }; + ECharts2.prototype.convertToPixel = function(finder, value) { + return doConvertPixel(this, "convertToPixel", finder, value); + }; + ECharts2.prototype.convertFromPixel = function(finder, value) { + return doConvertPixel(this, "convertFromPixel", finder, value); + }; + ECharts2.prototype.containPixel = function(finder, value) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + var ecModel = this._model; + var result; + var findResult = parseFinder(ecModel, finder); + each$4(findResult, function(models, key) { + key.indexOf("Models") >= 0 && each$4(models, function(model) { + var coordSys = model.coordinateSystem; + if (coordSys && coordSys.containPoint) { + result = result || !!coordSys.containPoint(value); + } else if (key === "seriesModels") { + var view = this._chartsMap[model.__viewId]; + if (view && view.containPoint) { + result = result || view.containPoint(value, model); + } + } else ; + }, this); + }, this); + return !!result; + }; + ECharts2.prototype.getVisual = function(finder, visualType) { + var ecModel = this._model; + var parsedFinder = parseFinder(ecModel, finder, { + defaultMainType: "series" + }); + var seriesModel = parsedFinder.seriesModel; + var data = seriesModel.getData(); + var dataIndexInside = parsedFinder.hasOwnProperty("dataIndexInside") ? parsedFinder.dataIndexInside : parsedFinder.hasOwnProperty("dataIndex") ? data.indexOfRawIndex(parsedFinder.dataIndex) : null; + return dataIndexInside != null ? getItemVisualFromData(data, dataIndexInside, visualType) : getVisualFromData(data, visualType); + }; + ECharts2.prototype.getViewOfComponentModel = function(componentModel) { + return this._componentsMap[componentModel.__viewId]; + }; + ECharts2.prototype.getViewOfSeriesModel = function(seriesModel) { + return this._chartsMap[seriesModel.__viewId]; + }; + ECharts2.prototype._initEvents = function() { + var _this = this; + each$4(MOUSE_EVENT_NAMES, function(eveName) { + var handler = function(e) { + var ecModel = _this.getModel(); + var el = e.target; + var params; + var isGlobalOut = eveName === "globalout"; + if (isGlobalOut) { + params = {}; + } else { + el && findEventDispatcher(el, function(parent) { + var ecData = getECData(parent); + if (ecData && ecData.dataIndex != null) { + var dataModel = ecData.dataModel || ecModel.getSeriesByIndex(ecData.seriesIndex); + params = dataModel && dataModel.getDataParams(ecData.dataIndex, ecData.dataType, el) || {}; + return true; + } else if (ecData.eventData) { + params = extend({}, ecData.eventData); + return true; + } + }, true); + } + if (params) { + var componentType = params.componentType; + var componentIndex = params.componentIndex; + 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]; + params.event = e; + params.type = eveName; + _this._$eventProcessor.eventInfo = { + targetEl: el, + packedEvent: params, + model, + view + }; + _this.trigger(eveName, params); + } + }; + handler.zrEventfulCallAtLast = true; + _this._zr.on(eveName, handler, _this); + }); + each$4(eventActionMap, function(actionType, eventType) { + _this._messageCenter.on(eventType, function(event) { + this.trigger(eventType, event); + }, _this); + }); + each$4(["selectchanged"], function(eventType) { + _this._messageCenter.on(eventType, function(event) { + this.trigger(eventType, event); + }, _this); + }); + handleLegacySelectEvents(this._messageCenter, this, this._api); + }; + ECharts2.prototype.isDisposed = function() { + return this._disposed; + }; + ECharts2.prototype.clear = function() { + if (this._disposed) { + disposedWarning(this.id); + return; + } + this.setOption({ + series: [] + }, true); + }; + ECharts2.prototype.dispose = function() { + if (this._disposed) { + disposedWarning(this.id); + return; + } + this._disposed = true; + var dom = this.getDom(); + if (dom) { + setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, ""); + } + var chart = this; + var api = chart._api; + var ecModel = chart._model; + each$4(chart._componentsViews, function(component) { + component.dispose(ecModel, api); + }); + each$4(chart._chartsViews, function(chart2) { + chart2.dispose(ecModel, api); + }); + chart._zr.dispose(); + chart._dom = chart._model = chart._chartsMap = chart._componentsMap = chart._chartsViews = chart._componentsViews = chart._scheduler = chart._api = chart._zr = chart._throttledZrFlush = chart._theme = chart._coordSysMgr = chart._messageCenter = null; + delete instances[chart.id]; + }; + ECharts2.prototype.resize = function(opts) { + if (this[IN_MAIN_PROCESS_KEY]) { + return; + } + if (this._disposed) { + disposedWarning(this.id); + return; + } + this._zr.resize(opts); + var ecModel = this._model; + this._loadingFX && this._loadingFX.resize(); + if (!ecModel) { + return; + } + var needPrepare = ecModel.resetOption("media"); + var silent = opts && opts.silent; + if (this[PENDING_UPDATE]) { + if (silent == null) { + silent = this[PENDING_UPDATE].silent; + } + needPrepare = true; + this[PENDING_UPDATE] = null; + } + this[IN_MAIN_PROCESS_KEY] = true; + try { + needPrepare && prepare(this); + updateMethods.update.call(this, { + type: "resize", + animation: extend({ + // Disable animation + duration: 0 + }, opts && opts.animation) + }); + } catch (e) { + this[IN_MAIN_PROCESS_KEY] = false; + throw e; + } + this[IN_MAIN_PROCESS_KEY] = false; + flushPendingActions.call(this, silent); + triggerUpdatedEvent.call(this, silent); + }; + ECharts2.prototype.showLoading = function(name, cfg) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + if (isObject$2(name)) { + cfg = name; + name = ""; + } + name = name || "default"; + this.hideLoading(); + if (!loadingEffects[name]) { + return; + } + var el = loadingEffects[name](this._api, cfg); + var zr = this._zr; + this._loadingFX = el; + zr.add(el); + }; + ECharts2.prototype.hideLoading = function() { + if (this._disposed) { + disposedWarning(this.id); + return; + } + this._loadingFX && this._zr.remove(this._loadingFX); + this._loadingFX = null; + }; + ECharts2.prototype.makeActionFromEvent = function(eventObj) { + var payload = extend({}, eventObj); + payload.type = eventActionMap[eventObj.type]; + return payload; + }; + ECharts2.prototype.dispatchAction = function(payload, opt) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + if (!isObject$2(opt)) { + opt = { + silent: !!opt + }; + } + if (!actions[payload.type]) { + return; + } + if (!this._model) { + return; + } + if (this[IN_MAIN_PROCESS_KEY]) { + this._pendingActions.push(payload); + return; + } + var silent = opt.silent; + doDispatchAction.call(this, payload, silent); + var flush = opt.flush; + if (flush) { + this._zr.flush(); + } else if (flush !== false && env.browser.weChat) { + this._throttledZrFlush(); + } + flushPendingActions.call(this, silent); + triggerUpdatedEvent.call(this, silent); + }; + ECharts2.prototype.updateLabelLayout = function() { + lifecycle.trigger("series:layoutlabels", this._model, this._api, { + // Not adding series labels. + // TODO + updatedSeries: [] + }); + }; + ECharts2.prototype.appendData = function(params) { + if (this._disposed) { + disposedWarning(this.id); + return; + } + var seriesIndex = params.seriesIndex; + var ecModel = this.getModel(); + var seriesModel = ecModel.getSeriesByIndex(seriesIndex); + seriesModel.appendData(params); + this._scheduler.unfinished = true; + this.getZr().wakeUp(); + }; + ECharts2.internalField = function() { + prepare = function(ecIns) { + var scheduler = ecIns._scheduler; + scheduler.restorePipelines(ecIns._model); + scheduler.prepareStageTasks(); + prepareView(ecIns, true); + prepareView(ecIns, false); + scheduler.plan(); + }; + prepareView = function(ecIns, isComponent) { + var ecModel = ecIns._model; + var scheduler = ecIns._scheduler; + 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) { + var requireNewView = model.__requireNewView; + model.__requireNewView = false; + var viewId = "_ec_" + model.id + "_" + model.type; + var view2 = !requireNewView && viewMap[viewId]; + if (!view2) { + var classType = parseClassType(model.type); + var Clazz = isComponent ? ComponentView.getClass(classType.main, classType.sub) : ( + // FIXME:TS + // (ChartView as ChartViewConstructor).getClass('series', classType.sub) + // For backward compat, still support a chart type declared as only subType + // like "liquidfill", but recommend "series.liquidfill" + // But need a base class to make a type series. + ChartView.getClass(classType.sub) + ); + view2 = new Clazz(); + view2.init(ecModel, api); + viewMap[viewId] = view2; + viewList.push(view2); + zr.add(view2.group); + } + model.__viewId = view2.__id = viewId; + view2.__alive = true; + view2.__model = model; + view2.group.__ecComponentInfo = { + mainType: model.mainType, + index: model.componentIndex + }; + !isComponent && scheduler.prepareView(view2, 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); + if (viewMap[view.__id] === view) { + delete viewMap[view.__id]; + } + view.__id = view.group.__ecComponentInfo = null; + } else { + i++; + } + } + }; + updateDirectly = function(ecIns, method, payload, mainType, subType) { + var ecModel = ecIns._model; + ecModel.setUpdatePayload(payload); + if (!mainType) { + each$4([].concat(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, + query + }; + subType && (condition.subType = subType); + var excludeSeriesId = payload.excludeSeriesId; + var excludeSeriesIdMap; + if (excludeSeriesId != null) { + excludeSeriesIdMap = createHashMap(); + each$4(normalizeToArray(excludeSeriesId), function(id) { + var modelId = convertOptionIdName(id, null); + if (modelId != null) { + excludeSeriesIdMap.set(modelId, true); + } + }); + } + ecModel && ecModel.eachComponent(condition, function(model) { + var isExcluded = excludeSeriesIdMap && excludeSeriesIdMap.get(model.id) != null; + if (isExcluded) { + return; + } + if (isHighDownPayload(payload)) { + if (model instanceof SeriesModel) { + if (payload.type === HIGHLIGHT_ACTION_TYPE && !payload.notBlur && !model.get(["emphasis", "disabled"])) { + blurSeriesFromHighlightPayload(model, payload, ecIns._api); + } + } else { + var _a = findComponentHighDownDispatchers(model.mainType, model.componentIndex, payload.name, ecIns._api), focusSelf = _a.focusSelf, dispatchers = _a.dispatchers; + if (payload.type === HIGHLIGHT_ACTION_TYPE && focusSelf && !payload.notBlur) { + blurComponent(model.mainType, model.componentIndex, ecIns._api); + } + if (dispatchers) { + each$4(dispatchers, function(dispatcher) { + payload.type === HIGHLIGHT_ACTION_TYPE ? enterEmphasis(dispatcher) : leaveEmphasis(dispatcher); + }); + } + } + } else if (isSelectChangePayload(payload)) { + if (model instanceof SeriesModel) { + toggleSelectionFromPayload(model, payload, ecIns._api); + updateSeriesElementSelection(model); + markStatusToUpdate(ecIns); + } + } + }, ecIns); + ecModel && ecModel.eachComponent(condition, function(model) { + var isExcluded = excludeSeriesIdMap && excludeSeriesIdMap.get(model.id) != null; + if (isExcluded) { + return; + } + 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); + } + }; + updateMethods = { + prepareAndUpdate: function(payload) { + prepare(this); + updateMethods.update.call(this, payload, { + // Needs to mark option changed if newOption is given. + // It's from MagicType. + // TODO If use a separate flag optionChanged in payload? + optionChanged: payload.newOption != null + }); + }, + update: function(payload, updateParams) { + var ecModel = this._model; + var api = this._api; + var zr = this._zr; + var coordSysMgr = this._coordSysMgr; + var scheduler = this._scheduler; + if (!ecModel) { + return; + } + ecModel.setUpdatePayload(payload); + scheduler.restoreData(ecModel, payload); + scheduler.performSeriesTasks(ecModel); + coordSysMgr.create(ecModel, api); + scheduler.performDataProcessorTasks(ecModel, payload); + updateStreamModes(this, ecModel); + coordSysMgr.update(ecModel, api); + clearColorPalette(ecModel); + scheduler.performVisualTasks(ecModel, payload); + render(this, ecModel, api, payload, updateParams); + var backgroundColor = ecModel.get("backgroundColor") || "transparent"; + var darkMode = ecModel.get("darkMode"); + zr.setBackgroundColor(backgroundColor); + if (darkMode != null && darkMode !== "auto") { + zr.setDarkMode(darkMode); + } + lifecycle.trigger("afterupdate", ecModel, api); + }, + updateTransform: function(payload) { + var _this = this; + var ecModel = this._model; + var api = this._api; + if (!ecModel) { + return; + } + ecModel.setUpdatePayload(payload); + var componentDirtyList = []; + ecModel.eachComponent(function(componentType, componentModel) { + if (componentType === "series") { + return; + } + var componentView = _this.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 = _this._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); + this._scheduler.performVisualTasks(ecModel, payload, { + setDirty: true, + dirtyMap: seriesDirtyMap + }); + renderSeries(this, ecModel, api, payload, {}, seriesDirtyMap); + lifecycle.trigger("afterupdate", ecModel, api); + }, + updateView: function(payload) { + var ecModel = this._model; + if (!ecModel) { + return; + } + ecModel.setUpdatePayload(payload); + ChartView.markUpdateMethod(payload, "updateView"); + clearColorPalette(ecModel); + this._scheduler.performVisualTasks(ecModel, payload, { + setDirty: true + }); + render(this, ecModel, this._api, payload, {}); + lifecycle.trigger("afterupdate", ecModel, this._api); + }, + updateVisual: function(payload) { + var _this = this; + var ecModel = this._model; + if (!ecModel) { + return; + } + ecModel.setUpdatePayload(payload); + ecModel.eachSeries(function(seriesModel) { + seriesModel.getData().clearAllVisual(); + }); + ChartView.markUpdateMethod(payload, "updateVisual"); + clearColorPalette(ecModel); + this._scheduler.performVisualTasks(ecModel, payload, { + visualType: "visual", + setDirty: true + }); + ecModel.eachComponent(function(componentType, componentModel) { + if (componentType !== "series") { + var componentView = _this.getViewOfComponentModel(componentModel); + componentView && componentView.__alive && componentView.updateVisual(componentModel, ecModel, _this._api, payload); + } + }); + ecModel.eachSeries(function(seriesModel) { + var chartView = _this._chartsMap[seriesModel.__viewId]; + chartView.updateVisual(seriesModel, ecModel, _this._api, payload); + }); + lifecycle.trigger("afterupdate", ecModel, this._api); + }, + updateLayout: function(payload) { + updateMethods.update.call(this, payload); + } + }; + doConvertPixel = function(ecIns, methodName, finder, value) { + if (ecIns._disposed) { + disposedWarning(ecIns.id); + return; + } + var ecModel = ecIns._model; + var coordSysList = ecIns._coordSysMgr.getCoordinateSystems(); + var result; + var parsedFinder = parseFinder(ecModel, finder); + for (var i = 0; i < coordSysList.length; i++) { + var coordSys = coordSysList[i]; + if (coordSys[methodName] && (result = coordSys[methodName](ecModel, parsedFinder, value)) != null) { + return result; + } + } + }; + updateStreamModes = function(ecIns, ecModel) { + var chartsMap = ecIns._chartsMap; + var scheduler = ecIns._scheduler; + ecModel.eachSeries(function(seriesModel) { + scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]); + }); + }; + doDispatchAction = function(payload, silent) { + var _this = this; + var ecModel = this.getModel(); + var payloadType = payload.type; + var escapeConnect = payload.escapeConnect; + var actionWrap = actions[payloadType]; + var actionInfo = actionWrap.actionInfo; + var cptTypeTmp = (actionInfo.update || "update").split(":"); + var updateMethod = cptTypeTmp.pop(); + var cptType = cptTypeTmp[0] != null && parseClassType(cptTypeTmp[0]); + this[IN_MAIN_PROCESS_KEY] = true; + var payloads = [payload]; + var batched = false; + if (payload.batch) { + batched = true; + payloads = map$1(payload.batch, function(item) { + item = defaults(extend({}, item), payload); + item.batch = null; + return item; + }); + } + var eventObjBatch = []; + var eventObj; + var isSelectChange = isSelectChangePayload(payload); + var isHighDown = isHighDownPayload(payload); + if (isHighDown) { + allLeaveBlur(this._api); + } + each$4(payloads, function(batchItem) { + eventObj = actionWrap.action(batchItem, _this._model, _this._api); + eventObj = eventObj || extend({}, batchItem); + eventObj.type = actionInfo.event || eventObj.type; + eventObjBatch.push(eventObj); + if (isHighDown) { + var _a = preParseFinder(payload), queryOptionMap = _a.queryOptionMap, mainTypeSpecified = _a.mainTypeSpecified; + var componentMainType = mainTypeSpecified ? queryOptionMap.keys()[0] : "series"; + updateDirectly(_this, updateMethod, batchItem, componentMainType); + markStatusToUpdate(_this); + } else if (isSelectChange) { + updateDirectly(_this, updateMethod, batchItem, "series"); + markStatusToUpdate(_this); + } else if (cptType) { + updateDirectly(_this, updateMethod, batchItem, cptType.main, cptType.sub); + } + }); + if (updateMethod !== "none" && !isHighDown && !isSelectChange && !cptType) { + try { + if (this[PENDING_UPDATE]) { + prepare(this); + updateMethods.update.call(this, payload); + this[PENDING_UPDATE] = null; + } else { + updateMethods[updateMethod].call(this, payload); + } + } catch (e) { + this[IN_MAIN_PROCESS_KEY] = false; + throw e; + } + } + if (batched) { + eventObj = { + type: actionInfo.event || payloadType, + escapeConnect, + batch: eventObjBatch + }; + } else { + eventObj = eventObjBatch[0]; + } + this[IN_MAIN_PROCESS_KEY] = false; + if (!silent) { + var messageCenter = this._messageCenter; + messageCenter.trigger(eventObj.type, eventObj); + if (isSelectChange) { + var newObj = { + type: "selectchanged", + escapeConnect, + selected: getAllSelectedIndices(ecModel), + isFromClick: payload.isFromClick || false, + fromAction: payload.type, + fromActionPayload: payload + }; + messageCenter.trigger(newObj.type, newObj); + } + } + }; + flushPendingActions = function(silent) { + var pendingActions = this._pendingActions; + while (pendingActions.length) { + var payload = pendingActions.shift(); + doDispatchAction.call(this, payload, silent); + } + }; + triggerUpdatedEvent = function(silent) { + !silent && this.trigger("updated"); + }; + bindRenderedEvent = function(zr, ecIns) { + zr.on("rendered", function(params) { + ecIns.trigger("rendered", params); + 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[PENDING_UPDATE] && !ecIns._scheduler.unfinished && !ecIns._pendingActions.length + ) { + ecIns.trigger("finished"); + } + }); + }; + bindMouseEvent = function(zr, ecIns) { + zr.on("mouseover", function(e) { + var el = e.target; + var dispatcher = findEventDispatcher(el, isHighDownDispatcher); + if (dispatcher) { + handleGlobalMouseOverForHighDown(dispatcher, e, ecIns._api); + markStatusToUpdate(ecIns); + } + }).on("mouseout", function(e) { + var el = e.target; + var dispatcher = findEventDispatcher(el, isHighDownDispatcher); + if (dispatcher) { + handleGlobalMouseOutForHighDown(dispatcher, e, ecIns._api); + markStatusToUpdate(ecIns); + } + }).on("click", function(e) { + var el = e.target; + var dispatcher = findEventDispatcher(el, function(target) { + return getECData(target).dataIndex != null; + }, true); + if (dispatcher) { + var actionType = dispatcher.selected ? "unselect" : "select"; + var ecData = getECData(dispatcher); + ecIns._api.dispatchAction({ + type: actionType, + dataType: ecData.dataType, + dataIndexInside: ecData.dataIndex, + seriesIndex: ecData.seriesIndex, + isFromClick: true + }); + } + }); + }; + function clearColorPalette(ecModel) { + ecModel.clearColorPalette(); + ecModel.eachSeries(function(seriesModel) { + seriesModel.clearColorPalette(); + }); + } + function allocateZlevels(ecModel) { + var componentZLevels = []; + var seriesZLevels = []; + var hasSeparateZLevel = false; + ecModel.eachComponent(function(componentType, componentModel) { + var zlevel = componentModel.get("zlevel") || 0; + var z = componentModel.get("z") || 0; + var zlevelKey = componentModel.getZLevelKey(); + hasSeparateZLevel = hasSeparateZLevel || !!zlevelKey; + (componentType === "series" ? seriesZLevels : componentZLevels).push({ + zlevel, + z, + idx: componentModel.componentIndex, + type: componentType, + key: zlevelKey + }); + }); + if (hasSeparateZLevel) { + var zLevels = componentZLevels.concat(seriesZLevels); + var lastSeriesZLevel_1; + var lastSeriesKey_1; + sort(zLevels, function(a, b) { + if (a.zlevel === b.zlevel) { + return a.z - b.z; + } + return a.zlevel - b.zlevel; + }); + each$4(zLevels, function(item) { + var componentModel = ecModel.getComponent(item.type, item.idx); + var zlevel = item.zlevel; + var key = item.key; + if (lastSeriesZLevel_1 != null) { + zlevel = Math.max(lastSeriesZLevel_1, zlevel); + } + if (key) { + if (zlevel === lastSeriesZLevel_1 && key !== lastSeriesKey_1) { + zlevel++; + } + lastSeriesKey_1 = key; + } else if (lastSeriesKey_1) { + if (zlevel === lastSeriesZLevel_1) { + zlevel++; + } + lastSeriesKey_1 = ""; + } + lastSeriesZLevel_1 = zlevel; + componentModel.setZLevel(zlevel); + }); + } + } + render = function(ecIns, ecModel, api, payload, updateParams) { + allocateZlevels(ecModel); + renderComponents(ecIns, ecModel, api, payload, updateParams); + each$4(ecIns._chartsViews, function(chart) { + chart.__alive = false; + }); + renderSeries(ecIns, ecModel, api, payload, updateParams); + each$4(ecIns._chartsViews, function(chart) { + if (!chart.__alive) { + chart.remove(ecModel, api); + } + }); + }; + renderComponents = function(ecIns, ecModel, api, payload, updateParams, dirtyList) { + each$4(dirtyList || ecIns._componentsViews, function(componentView) { + var componentModel = componentView.__model; + clearStates(componentModel, componentView); + componentView.render(componentModel, ecModel, api, payload); + updateZ(componentModel, componentView); + updateStates(componentModel, componentView); + }); + }; + renderSeries = function(ecIns, ecModel, api, payload, updateParams, dirtyMap) { + var scheduler = ecIns._scheduler; + updateParams = extend(updateParams || {}, { + updatedSeries: ecModel.getSeries() + }); + lifecycle.trigger("series:beforeupdate", ecModel, api, updateParams); + var unfinished = false; + ecModel.eachSeries(function(seriesModel) { + var chartView = ecIns._chartsMap[seriesModel.__viewId]; + chartView.__alive = true; + var renderTask = chartView.renderTask; + scheduler.updatePayload(renderTask, payload); + clearStates(seriesModel, chartView); + if (dirtyMap && dirtyMap.get(seriesModel.uid)) { + renderTask.dirty(); + } + if (renderTask.perform(scheduler.getPerformArgs(renderTask))) { + unfinished = true; + } + chartView.group.silent = !!seriesModel.get("silent"); + updateBlend(seriesModel, chartView); + updateSeriesElementSelection(seriesModel); + }); + scheduler.unfinished = unfinished || scheduler.unfinished; + lifecycle.trigger("series:layoutlabels", ecModel, api, updateParams); + lifecycle.trigger("series:transition", ecModel, api, updateParams); + ecModel.eachSeries(function(seriesModel) { + var chartView = ecIns._chartsMap[seriesModel.__viewId]; + updateZ(seriesModel, chartView); + updateStates(seriesModel, chartView); + }); + updateHoverLayerStatus(ecIns, ecModel); + lifecycle.trigger("series:afterupdate", ecModel, api, updateParams); + }; + markStatusToUpdate = function(ecIns) { + ecIns[STATUS_NEEDS_UPDATE_KEY] = true; + ecIns.getZr().wakeUp(); + }; + applyChangedStates = function(ecIns) { + if (!ecIns[STATUS_NEEDS_UPDATE_KEY]) { + return; + } + ecIns.getZr().storage.traverse(function(el) { + if (isElementRemoved(el)) { + return; + } + applyElementStates(el); + }); + ecIns[STATUS_NEEDS_UPDATE_KEY] = false; + }; + function applyElementStates(el) { + var newStates = []; + var oldStates = el.currentStates; + for (var i = 0; i < oldStates.length; i++) { + var stateName = oldStates[i]; + if (!(stateName === "emphasis" || stateName === "blur" || stateName === "select")) { + newStates.push(stateName); + } + } + if (el.selected && el.states.select) { + newStates.push("select"); + } + if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) { + newStates.push("emphasis"); + } else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) { + newStates.push("blur"); + } + el.useStates(newStates); + } + function updateHoverLayerStatus(ecIns, ecModel) { + var zr = ecIns._zr; + var storage = zr.storage; + var elCount = 0; + storage.traverse(function(el) { + if (!el.isGroup) { + elCount++; + } + }); + if (elCount > ecModel.get("hoverLayerThreshold") && !env.node && !env.worker) { + ecModel.eachSeries(function(seriesModel) { + if (seriesModel.preventUsingHoverLayer) { + return; + } + var chartView = ecIns._chartsMap[seriesModel.__viewId]; + if (chartView.__alive) { + chartView.eachRendered(function(el) { + if (el.states.emphasis) { + el.states.emphasis.hoverLayer = true; + } + }); + } + }); + } + } + function updateBlend(seriesModel, chartView) { + var blendMode = seriesModel.get("blendMode") || null; + chartView.eachRendered(function(el) { + if (!el.isGroup) { + el.style.blend = blendMode; + } + }); + } + function updateZ(model, view) { + if (model.preventAutoZ) { + return; + } + var z = model.get("z") || 0; + var zlevel = model.get("zlevel") || 0; + view.eachRendered(function(el) { + doUpdateZ(el, z, zlevel, -Infinity); + return true; + }); + } + function doUpdateZ(el, z, zlevel, maxZ2) { + var label = el.getTextContent(); + var labelLine = el.getTextGuideLine(); + var isGroup = el.isGroup; + if (isGroup) { + var children = el.childrenRef(); + for (var i = 0; i < children.length; i++) { + maxZ2 = Math.max(doUpdateZ(children[i], z, zlevel, maxZ2), maxZ2); + } + } else { + el.z = z; + el.zlevel = zlevel; + maxZ2 = Math.max(el.z2, maxZ2); + } + if (label) { + label.z = z; + label.zlevel = zlevel; + isFinite(maxZ2) && (label.z2 = maxZ2 + 2); + } + if (labelLine) { + var textGuideLineConfig = el.textGuideLineConfig; + labelLine.z = z; + labelLine.zlevel = zlevel; + isFinite(maxZ2) && (labelLine.z2 = maxZ2 + (textGuideLineConfig && textGuideLineConfig.showAbove ? 1 : -1)); + } + return maxZ2; + } + function clearStates(model, view) { + view.eachRendered(function(el) { + if (isElementRemoved(el)) { + return; + } + var textContent = el.getTextContent(); + var textGuide = el.getTextGuideLine(); + if (el.stateTransition) { + el.stateTransition = null; + } + if (textContent && textContent.stateTransition) { + textContent.stateTransition = null; + } + if (textGuide && textGuide.stateTransition) { + textGuide.stateTransition = null; + } + if (el.hasState()) { + el.prevStates = el.currentStates; + el.clearStates(); + } else if (el.prevStates) { + el.prevStates = null; + } + }); + } + function updateStates(model, view) { + var stateAnimationModel = model.getModel("stateAnimation"); + var enableAnimation = model.isAnimationEnabled(); + var duration = stateAnimationModel.get("duration"); + var stateTransition = duration > 0 ? { + duration, + delay: stateAnimationModel.get("delay"), + easing: stateAnimationModel.get("easing") + // additive: stateAnimationModel.get('additive') + } : null; + view.eachRendered(function(el) { + if (el.states && el.states.emphasis) { + if (isElementRemoved(el)) { + return; + } + if (el instanceof Path) { + savePathStates(el); + } + if (el.__dirty) { + var prevStates = el.prevStates; + if (prevStates) { + el.useStates(prevStates); + } + } + if (enableAnimation) { + el.stateTransition = stateTransition; + var textContent = el.getTextContent(); + var textGuide = el.getTextGuideLine(); + if (textContent) { + textContent.stateTransition = stateTransition; + } + if (textGuide) { + textGuide.stateTransition = stateTransition; + } + } + if (el.__dirty) { + applyElementStates(el); + } + } + }); + } + createExtensionAPI = function(ecIns) { + return new /** @class */ + (function(_super2) { + __extends(class_1, _super2); + function class_1() { + return _super2 !== null && _super2.apply(this, arguments) || this; + } + class_1.prototype.getCoordinateSystems = function() { + return ecIns._coordSysMgr.getCoordinateSystems(); + }; + class_1.prototype.getComponentByElement = function(el) { + while (el) { + var modelInfo = el.__ecComponentInfo; + if (modelInfo != null) { + return ecIns._model.getComponent(modelInfo.mainType, modelInfo.index); + } + el = el.parent; + } + }; + class_1.prototype.enterEmphasis = function(el, highlightDigit) { + enterEmphasis(el, highlightDigit); + markStatusToUpdate(ecIns); + }; + class_1.prototype.leaveEmphasis = function(el, highlightDigit) { + leaveEmphasis(el, highlightDigit); + markStatusToUpdate(ecIns); + }; + class_1.prototype.enterBlur = function(el) { + enterBlur(el); + markStatusToUpdate(ecIns); + }; + class_1.prototype.leaveBlur = function(el) { + leaveBlur(el); + markStatusToUpdate(ecIns); + }; + class_1.prototype.enterSelect = function(el) { + enterSelect(el); + markStatusToUpdate(ecIns); + }; + class_1.prototype.leaveSelect = function(el) { + leaveSelect(el); + markStatusToUpdate(ecIns); + }; + class_1.prototype.getModel = function() { + return ecIns.getModel(); + }; + class_1.prototype.getViewOfComponentModel = function(componentModel) { + return ecIns.getViewOfComponentModel(componentModel); + }; + class_1.prototype.getViewOfSeriesModel = function(seriesModel) { + return ecIns.getViewOfSeriesModel(seriesModel); + }; + return class_1; + }(ExtensionAPI))(ecIns); + }; + enableConnect = function(chart) { + function updateConnectedChartsStatus(charts, status) { + for (var i = 0; i < charts.length; i++) { + var otherChart = charts[i]; + otherChart[CONNECT_STATUS_KEY] = status; + } + } + each$4(eventActionMap, function(actionType, eventType) { + chart._messageCenter.on(eventType, function(event) { + if (connectedGroups[chart.group] && chart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_PENDING) { + if (event && event.escapeConnect) { + return; + } + var action_1 = chart.makeActionFromEvent(event); + var otherCharts_1 = []; + each$4(instances, function(otherChart) { + if (otherChart !== chart && otherChart.group === chart.group) { + otherCharts_1.push(otherChart); + } + }); + updateConnectedChartsStatus(otherCharts_1, CONNECT_STATUS_PENDING); + each$4(otherCharts_1, function(otherChart) { + if (otherChart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_UPDATING) { + otherChart.dispatchAction(action_1); + } + }); + updateConnectedChartsStatus(otherCharts_1, CONNECT_STATUS_UPDATED); + } + }); + }); + }; + }(); + return ECharts2; + }(Eventful) +); +var echartsProto = ECharts.prototype; +echartsProto.on = createRegisterEventWithLowercaseECharts("on"); +echartsProto.off = createRegisterEventWithLowercaseECharts("off"); +echartsProto.one = function(eventName, cb, ctx) { + var self = this; + function wrapped() { + var args2 = []; + for (var _i = 0; _i < arguments.length; _i++) { + args2[_i] = arguments[_i]; + } + cb && cb.apply && cb.apply(this, args2); + self.off(eventName, wrapped); + } + this.on.call(this, eventName, wrapped, ctx); +}; +var MOUSE_EVENT_NAMES = ["click", "dblclick", "mouseover", "mouseout", "mousemove", "mousedown", "mouseup", "globalout", "contextmenu"]; +function disposedWarning(id) { +} +var actions = {}; +var eventActionMap = {}; +var dataProcessorFuncs = []; +var optionPreprocessorFuncs = []; +var visualFuncs = []; +var themeStorage = {}; +var loadingEffects = {}; +var instances = {}; +var connectedGroups = {}; +var idBase = +/* @__PURE__ */ new Date() - 0; +var DOM_ATTRIBUTE_KEY = "_echarts_instance_"; +function init(dom, theme, opts) { + var isClient = !(opts && opts.ssr); + if (isClient) { + var existInstance = getInstanceByDom(dom); + if (existInstance) { + return existInstance; + } + } + var chart = new ECharts(dom, theme, opts); + chart.id = "ec_" + idBase++; + instances[chart.id] = chart; + isClient && setAttribute(dom, DOM_ATTRIBUTE_KEY, chart.id); + enableConnect(chart); + lifecycle.trigger("afterinit", chart); + return chart; +} +function getInstanceByDom(dom) { + return instances[getAttribute(dom, DOM_ATTRIBUTE_KEY)]; +} +function registerTheme(name, theme) { + themeStorage[name] = theme; +} +function registerPreprocessor(preprocessorFunc) { + if (indexOf(optionPreprocessorFuncs, preprocessorFunc) < 0) { + optionPreprocessorFuncs.push(preprocessorFunc); + } +} +function registerProcessor(priority, processor) { + normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_DEFAULT); +} +function registerPostInit(postInitFunc) { + registerUpdateLifecycle("afterinit", postInitFunc); +} +function registerPostUpdate(postUpdateFunc) { + registerUpdateLifecycle("afterupdate", postUpdateFunc); +} +function registerUpdateLifecycle(name, cb) { + lifecycle.on(name, cb); +} +function registerAction(actionInfo, eventName, action) { + if (isFunction(eventName)) { + action = eventName; + eventName = ""; + } + var actionType = isObject$2(actionInfo) ? actionInfo.type : [actionInfo, actionInfo = { + event: eventName + }][0]; + actionInfo.event = (actionInfo.event || actionType).toLowerCase(); + eventName = actionInfo.event; + if (eventActionMap[eventName]) { + return; + } + assert(ACTION_REG.test(actionType) && ACTION_REG.test(eventName)); + if (!actions[actionType]) { + actions[actionType] = { + action, + actionInfo + }; + } + eventActionMap[eventName] = actionType; +} +function registerCoordinateSystem(type, coordSysCreator) { + CoordinateSystemManager.register(type, coordSysCreator); +} +function registerLayout(priority, layoutTask) { + normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, "layout"); +} +function registerVisual(priority, visualTask) { + normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, "visual"); +} +var registeredTasks = []; +function normalizeRegister(targetList, priority, fn, defaultPriority, visualType) { + if (isFunction(priority) || isObject$2(priority)) { + fn = priority; + priority = defaultPriority; + } + if (indexOf(registeredTasks, fn) >= 0) { + return; + } + registeredTasks.push(fn); + var stageHandler = Scheduler.wrapStageHandler(fn, visualType); + stageHandler.__prio = priority; + stageHandler.__raw = fn; + targetList.push(stageHandler); +} +function registerLoading(name, loadingFx) { + loadingEffects[name] = loadingFx; +} +function registerMap(mapName, geoJson, specialAreas) { + var registerMap2 = getImpl("registerMap"); + registerMap2 && registerMap2(mapName, geoJson, specialAreas); +} +var registerTransform = registerExternalTransform; +registerVisual(PRIORITY_VISUAL_GLOBAL, seriesStyleTask); +registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataStyleTask); +registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataColorPaletteTask); +registerVisual(PRIORITY_VISUAL_GLOBAL, seriesSymbolTask); +registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataSymbolTask); +registerVisual(PRIORITY_VISUAL_DECAL, decalVisual); +registerPreprocessor(globalBackwardCompat); +registerProcessor(PRIORITY_PROCESSOR_DATASTACK, dataStack); +registerLoading("default", defaultLoading); +registerAction({ + type: HIGHLIGHT_ACTION_TYPE, + event: HIGHLIGHT_ACTION_TYPE, + update: HIGHLIGHT_ACTION_TYPE +}, noop); +registerAction({ + type: DOWNPLAY_ACTION_TYPE, + event: DOWNPLAY_ACTION_TYPE, + update: DOWNPLAY_ACTION_TYPE +}, noop); +registerAction({ + type: SELECT_ACTION_TYPE, + event: SELECT_ACTION_TYPE, + update: SELECT_ACTION_TYPE +}, noop); +registerAction({ + type: UNSELECT_ACTION_TYPE, + event: UNSELECT_ACTION_TYPE, + update: UNSELECT_ACTION_TYPE +}, noop); +registerAction({ + type: TOGGLE_SELECT_ACTION_TYPE, + event: TOGGLE_SELECT_ACTION_TYPE, + update: TOGGLE_SELECT_ACTION_TYPE +}, noop); +registerTheme("light", lightTheme); +registerTheme("dark", theme); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 dataIndexMapValueLength(valNumOrArrLengthMoreThan2) { + return valNumOrArrLengthMoreThan2 == null ? 0 : valNumOrArrLengthMoreThan2.length || 1; +} +function defaultKeyGetter(item) { + return item; +} +var DataDiffer = /** @class */function () { + /** + * @param context Can be visited by this.context in callback. + */ + function DataDiffer(oldArr, newArr, oldKeyGetter, newKeyGetter, context, + // By default: 'oneToOne'. + diffMode) { + this._old = oldArr; + this._new = newArr; + this._oldKeyGetter = oldKeyGetter || defaultKeyGetter; + this._newKeyGetter = newKeyGetter || defaultKeyGetter; + // Visible in callback via `this.context`; + this.context = context; + this._diffModeMultiple = diffMode === 'multiple'; + } + /** + * Callback function when add a data + */ + DataDiffer.prototype.add = function (func) { + this._add = func; + return this; + }; + /** + * Callback function when update a data + */ + DataDiffer.prototype.update = function (func) { + this._update = func; + return this; + }; + /** + * Callback function when update a data and only work in `cbMode: 'byKey'`. + */ + DataDiffer.prototype.updateManyToOne = function (func) { + this._updateManyToOne = func; + return this; + }; + /** + * Callback function when update a data and only work in `cbMode: 'byKey'`. + */ + DataDiffer.prototype.updateOneToMany = function (func) { + this._updateOneToMany = func; + return this; + }; + /** + * Callback function when update a data and only work in `cbMode: 'byKey'`. + */ + DataDiffer.prototype.updateManyToMany = function (func) { + this._updateManyToMany = func; + return this; + }; + /** + * Callback function when remove a data + */ + DataDiffer.prototype.remove = function (func) { + this._remove = func; + return this; + }; + DataDiffer.prototype.execute = function () { + this[this._diffModeMultiple ? '_executeMultiple' : '_executeOneToOne'](); + }; + DataDiffer.prototype._executeOneToOne = function () { + var oldArr = this._old; + var newArr = this._new; + var newDataIndexMap = {}; + var oldDataKeyArr = new Array(oldArr.length); + var newDataKeyArr = new Array(newArr.length); + this._initIndexMap(oldArr, null, oldDataKeyArr, '_oldKeyGetter'); + this._initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter'); + for (var i = 0; i < oldArr.length; i++) { + var oldKey = oldDataKeyArr[i]; + var newIdxMapVal = newDataIndexMap[oldKey]; + var newIdxMapValLen = dataIndexMapValueLength(newIdxMapVal); + // idx can never be empty array here. see 'set null' logic below. + if (newIdxMapValLen > 1) { + // 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 newIdx = newIdxMapVal.shift(); + if (newIdxMapVal.length === 1) { + newDataIndexMap[oldKey] = newIdxMapVal[0]; + } + this._update && this._update(newIdx, i); + } else if (newIdxMapValLen === 1) { + newDataIndexMap[oldKey] = null; + this._update && this._update(newIdxMapVal, i); + } else { + this._remove && this._remove(i); + } + } + this._performRestAdd(newDataKeyArr, newDataIndexMap); + }; + /** + * For example, consider the case: + * oldData: [o0, o1, o2, o3, o4, o5, o6, o7], + * newData: [n0, n1, n2, n3, n4, n5, n6, n7, n8], + * Where: + * o0, o1, n0 has key 'a' (many to one) + * o5, n4, n5, n6 has key 'b' (one to many) + * o2, n1 has key 'c' (one to one) + * n2, n3 has key 'd' (add) + * o3, o4 has key 'e' (remove) + * o6, o7, n7, n8 has key 'f' (many to many, treated as add and remove) + * Then: + * (The order of the following directives are not ensured.) + * this._updateManyToOne(n0, [o0, o1]); + * this._updateOneToMany([n4, n5, n6], o5); + * this._update(n1, o2); + * this._remove(o3); + * this._remove(o4); + * this._remove(o6); + * this._remove(o7); + * this._add(n2); + * this._add(n3); + * this._add(n7); + * this._add(n8); + */ + DataDiffer.prototype._executeMultiple = function () { + var oldArr = this._old; + var newArr = this._new; + var oldDataIndexMap = {}; + var newDataIndexMap = {}; + var oldDataKeyArr = []; + var newDataKeyArr = []; + this._initIndexMap(oldArr, oldDataIndexMap, oldDataKeyArr, '_oldKeyGetter'); + this._initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter'); + for (var i = 0; i < oldDataKeyArr.length; i++) { + var oldKey = oldDataKeyArr[i]; + var oldIdxMapVal = oldDataIndexMap[oldKey]; + var newIdxMapVal = newDataIndexMap[oldKey]; + var oldIdxMapValLen = dataIndexMapValueLength(oldIdxMapVal); + var newIdxMapValLen = dataIndexMapValueLength(newIdxMapVal); + if (oldIdxMapValLen > 1 && newIdxMapValLen === 1) { + this._updateManyToOne && this._updateManyToOne(newIdxMapVal, oldIdxMapVal); + newDataIndexMap[oldKey] = null; + } else if (oldIdxMapValLen === 1 && newIdxMapValLen > 1) { + this._updateOneToMany && this._updateOneToMany(newIdxMapVal, oldIdxMapVal); + newDataIndexMap[oldKey] = null; + } else if (oldIdxMapValLen === 1 && newIdxMapValLen === 1) { + this._update && this._update(newIdxMapVal, oldIdxMapVal); + newDataIndexMap[oldKey] = null; + } else if (oldIdxMapValLen > 1 && newIdxMapValLen > 1) { + this._updateManyToMany && this._updateManyToMany(newIdxMapVal, oldIdxMapVal); + newDataIndexMap[oldKey] = null; + } else if (oldIdxMapValLen > 1) { + for (var i_1 = 0; i_1 < oldIdxMapValLen; i_1++) { + this._remove && this._remove(oldIdxMapVal[i_1]); + } + } else { + this._remove && this._remove(oldIdxMapVal); + } + } + this._performRestAdd(newDataKeyArr, newDataIndexMap); + }; + DataDiffer.prototype._performRestAdd = function (newDataKeyArr, newDataIndexMap) { + for (var i = 0; i < newDataKeyArr.length; i++) { + var newKey = newDataKeyArr[i]; + var newIdxMapVal = newDataIndexMap[newKey]; + var idxMapValLen = dataIndexMapValueLength(newIdxMapVal); + if (idxMapValLen > 1) { + for (var j = 0; j < idxMapValLen; j++) { + this._add && this._add(newIdxMapVal[j]); + } + } else if (idxMapValLen === 1) { + this._add && this._add(newIdxMapVal); + } + // Support both `newDataKeyArr` are duplication removed or not removed. + newDataIndexMap[newKey] = null; + } + }; + DataDiffer.prototype._initIndexMap = function (arr, + // Can be null. + map, + // In 'byKey', the output `keyArr` is duplication removed. + // In 'byIndex', the output `keyArr` is not duplication removed and + // its indices are accurately corresponding to `arr`. + keyArr, keyGetterName) { + var cbModeMultiple = this._diffModeMultiple; + for (var i = 0; i < arr.length; i++) { + // Add prefix to avoid conflict with Object.prototype. + var key = '_ec_' + this[keyGetterName](arr[i], i); + if (!cbModeMultiple) { + keyArr[i] = key; + } + if (!map) { + continue; + } + var idxMapVal = map[key]; + var idxMapValLen = dataIndexMapValueLength(idxMapVal); + if (idxMapValLen === 0) { + // Simple optimize: in most cases, one index has one key, + // do not need array. + map[key] = i; + if (cbModeMultiple) { + keyArr.push(key); + } + } else if (idxMapValLen === 1) { + map[key] = [idxMapVal, i]; + } else { + idxMapVal.push(i); + } + } + }; + return DataDiffer; +}(); + +var DimensionUserOuput = ( + /** @class */ + function() { + function DimensionUserOuput2(encode, dimRequest) { + this._encode = encode; + this._schema = dimRequest; + } + DimensionUserOuput2.prototype.get = function() { + return { + // Do not generate full dimension name until fist used. + fullDimensions: this._getFullDimensionNames(), + encode: this._encode + }; + }; + DimensionUserOuput2.prototype._getFullDimensionNames = function() { + if (!this._cachedDimNames) { + this._cachedDimNames = this._schema ? this._schema.makeOutputDimensionNames() : []; + } + return this._cachedDimNames; + }; + return DimensionUserOuput2; + }() +); +function summarizeDimensions(data, schema) { + var summary = {}; + var encode = summary.encode = {}; + var notExtraCoordDimMap = createHashMap(); + var defaultedLabel = []; + var defaultedTooltip = []; + var userOutputEncode = {}; + each$4(data.dimensions, function(dimName) { + var dimItem = data.getDimensionInfo(dimName); + var coordDim = dimItem.coordDim; + if (coordDim) { + var coordDimIndex = dimItem.coordDimIndex; + getOrCreateEncodeArr(encode, coordDim)[coordDimIndex] = dimName; + if (!dimItem.isExtraCoord) { + notExtraCoordDimMap.set(coordDim, 1); + if (mayLabelDimType(dimItem.type)) { + defaultedLabel[0] = dimName; + } + getOrCreateEncodeArr(userOutputEncode, coordDim)[coordDimIndex] = data.getDimensionIndex(dimItem.name); + } + if (dimItem.defaultTooltip) { + defaultedTooltip.push(dimName); + } + } + VISUAL_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]; + encodeFirstDimNotExtra[coordDim] = dimArr[0]; + dataDimsOnCoord = dataDimsOnCoord.concat(dimArr); + }); + summary.dataDimsOnCoord = dataDimsOnCoord; + summary.dataDimIndicesOnCoord = map$1(dataDimsOnCoord, function(dimName) { + return data.getDimensionInfo(dimName).storeDimIndex; + }); + summary.encodeFirstDimNotExtra = encodeFirstDimNotExtra; + var encodeLabel = encode.label; + 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; + summary.userOutput = new DimensionUserOuput(userOutputEncode, schema); + 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) { + return !(dimType === "ordinal" || dimType === "time"); +} + +var SeriesDimensionDefine = /** @class */function () { + /** + * @param opt All of the fields will be shallow copied. + */ + function SeriesDimensionDefine(opt) { + /** + * The format of `otherDims` is: + * ```js + * { + * tooltip?: number + * label?: number + * itemName?: number + * seriesName?: number + * } + * ``` + * + * 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.label` + * label: 1 + * }; + * ``` + * + * This prop should never be `null`/`undefined` after initialized. + */ + this.otherDims = {}; + if (opt != null) { + extend(this, opt); + } + } + return SeriesDimensionDefine; +}(); + +var inner$5 = makeInner(); +var dimTypeShort = { + float: 'f', + int: 'i', + ordinal: 'o', + number: 'n', + time: 't' +}; +/** + * Represents the dimension requirement of a series. + * + * NOTICE: + * When there are too many dimensions in dataset and many series, only the used dimensions + * (i.e., used by coord sys and declared in `series.encode`) are add to `dimensionDefineList`. + * But users may query data by other unused dimension names. + * In this case, users can only query data if and only if they have defined dimension names + * via ec option, so we provide `getDimensionIndexFromSource`, which only query them from + * `source` dimensions. + */ +var SeriesDataSchema = /** @class */function () { + function SeriesDataSchema(opt) { + this.dimensions = opt.dimensions; + this._dimOmitted = opt.dimensionOmitted; + this.source = opt.source; + this._fullDimCount = opt.fullDimensionCount; + this._updateDimOmitted(opt.dimensionOmitted); + } + SeriesDataSchema.prototype.isDimensionOmitted = function () { + return this._dimOmitted; + }; + SeriesDataSchema.prototype._updateDimOmitted = function (dimensionOmitted) { + this._dimOmitted = dimensionOmitted; + if (!dimensionOmitted) { + return; + } + if (!this._dimNameMap) { + this._dimNameMap = ensureSourceDimNameMap(this.source); + } + }; + /** + * @caution Can only be used when `dimensionOmitted: true`. + * + * Get index by user defined dimension name (i.e., not internal generate name). + * That is, get index from `dimensionsDefine`. + * If no `dimensionsDefine`, or no name get, return -1. + */ + SeriesDataSchema.prototype.getSourceDimensionIndex = function (dimName) { + return retrieve2(this._dimNameMap.get(dimName), -1); + }; + /** + * @caution Can only be used when `dimensionOmitted: true`. + * + * Notice: may return `null`/`undefined` if user not specify dimension names. + */ + SeriesDataSchema.prototype.getSourceDimension = function (dimIndex) { + var dimensionsDefine = this.source.dimensionsDefine; + if (dimensionsDefine) { + return dimensionsDefine[dimIndex]; + } + }; + SeriesDataSchema.prototype.makeStoreSchema = function () { + var dimCount = this._fullDimCount; + var willRetrieveDataByName = shouldRetrieveDataByName(this.source); + var makeHashStrict = !shouldOmitUnusedDimensions(dimCount); + // If source don't have dimensions or series don't omit unsed dimensions. + // Generate from seriesDimList directly + var dimHash = ''; + var dims = []; + for (var fullDimIdx = 0, seriesDimIdx = 0; fullDimIdx < dimCount; fullDimIdx++) { + var property = void 0; + var type = void 0; + var ordinalMeta = void 0; + var seriesDimDef = this.dimensions[seriesDimIdx]; + // The list has been sorted by `storeDimIndex` asc. + if (seriesDimDef && seriesDimDef.storeDimIndex === fullDimIdx) { + property = willRetrieveDataByName ? seriesDimDef.name : null; + type = seriesDimDef.type; + ordinalMeta = seriesDimDef.ordinalMeta; + seriesDimIdx++; + } else { + var sourceDimDef = this.getSourceDimension(fullDimIdx); + if (sourceDimDef) { + property = willRetrieveDataByName ? sourceDimDef.name : null; + type = sourceDimDef.type; + } + } + dims.push({ + property: property, + type: type, + ordinalMeta: ordinalMeta + }); + // If retrieving data by index, + // use to determine whether data can be shared. + // (Because in this case there might be no dimension name defined in dataset, but indices always exists). + // (Indices are always 0, 1, 2, ..., so we can ignore them to shorten the hash). + // Otherwise if retrieving data by property name (like `data: [{aa: 123, bb: 765}, ...]`), + // use in hash. + if (willRetrieveDataByName && property != null + // For data stack, we have make sure each series has its own dim on this store. + // So we do not add property to hash to make sure they can share this store. + && (!seriesDimDef || !seriesDimDef.isCalculationCoord)) { + dimHash += makeHashStrict + // Use escape character '`' in case that property name contains '$'. + ? property.replace(/\`/g, '`1').replace(/\$/g, '`2') + // For better performance, when there are large dimensions, tolerant this defects that hardly meet. + : property; + } + dimHash += '$'; + dimHash += dimTypeShort[type] || 'f'; + if (ordinalMeta) { + dimHash += ordinalMeta.uid; + } + dimHash += '$'; + } + // Source from endpoint(usually series) will be read differently + // when seriesLayoutBy or startIndex(which is affected by sourceHeader) are different. + // So we use this three props as key. + var source = this.source; + var hash = [source.seriesLayoutBy, source.startIndex, dimHash].join('$$'); + return { + dimensions: dims, + hash: hash + }; + }; + SeriesDataSchema.prototype.makeOutputDimensionNames = function () { + var result = []; + for (var fullDimIdx = 0, seriesDimIdx = 0; fullDimIdx < this._fullDimCount; fullDimIdx++) { + var name_1 = void 0; + var seriesDimDef = this.dimensions[seriesDimIdx]; + // The list has been sorted by `storeDimIndex` asc. + if (seriesDimDef && seriesDimDef.storeDimIndex === fullDimIdx) { + if (!seriesDimDef.isCalculationCoord) { + name_1 = seriesDimDef.name; + } + seriesDimIdx++; + } else { + var sourceDimDef = this.getSourceDimension(fullDimIdx); + if (sourceDimDef) { + name_1 = sourceDimDef.name; + } + } + result.push(name_1); + } + return result; + }; + SeriesDataSchema.prototype.appendCalculationDimension = function (dimDef) { + this.dimensions.push(dimDef); + dimDef.isCalculationCoord = true; + this._fullDimCount++; + // If append dimension on a data store, consider the store + // might be shared by different series, series dimensions not + // really map to store dimensions. + this._updateDimOmitted(true); + }; + return SeriesDataSchema; +}(); +function isSeriesDataSchema(schema) { + return schema instanceof SeriesDataSchema; +} +function createDimNameMap(dimsDef) { + var dataDimNameMap = createHashMap(); + for (var i = 0; i < (dimsDef || []).length; i++) { + var dimDefItemRaw = dimsDef[i]; + var userDimName = isObject$2(dimDefItemRaw) ? dimDefItemRaw.name : dimDefItemRaw; + if (userDimName != null && dataDimNameMap.get(userDimName) == null) { + dataDimNameMap.set(userDimName, i); + } + } + return dataDimNameMap; +} +function ensureSourceDimNameMap(source) { + var innerSource = inner$5(source); + return innerSource.dimNameMap || (innerSource.dimNameMap = createDimNameMap(source.dimensionsDefine)); +} +function shouldOmitUnusedDimensions(dimCount) { + return dimCount > 30; +} + +var isObject = isObject$2; +var map = map$1; +var CtorInt32Array = typeof Int32Array === "undefined" ? Array : Int32Array; +var ID_PREFIX = "e\0\0"; +var INDEX_NOT_FOUND = -1; +var TRANSFERABLE_PROPERTIES = ["hasItemOption", "_nameList", "_idList", "_invertedIndicesMap", "_dimSummary", "userOutput", "_rawData", "_dimValueGetter", "_nameDimIdx", "_idDimIdx", "_nameRepeatCount"]; +var CLONE_PROPERTIES = ["_approximateExtent"]; +var prepareInvertedIndex; +var getId; +var getIdNameFromStore; +var normalizeDimensions; +var transferProperties; +var cloneListForMapAndSample; +var makeIdFromName; +var SeriesData = ( + /** @class */ + function() { + function SeriesData2(dimensionsInput, hostModel) { + this.type = "list"; + this._dimOmitted = false; + this._nameList = []; + this._idList = []; + this._visual = {}; + this._layout = {}; + this._itemVisuals = []; + this._itemLayouts = []; + this._graphicEls = []; + this._approximateExtent = {}; + this._calculationInfo = {}; + this.hasItemOption = false; + this.TRANSFERABLE_METHODS = ["cloneShallow", "downSample", "minmaxDownSample", "lttbDownSample", "map"]; + this.CHANGABLE_METHODS = ["filterSelf", "selectRange"]; + this.DOWNSAMPLE_METHODS = ["downSample", "minmaxDownSample", "lttbDownSample"]; + var dimensions; + var assignStoreDimIdx = false; + if (isSeriesDataSchema(dimensionsInput)) { + dimensions = dimensionsInput.dimensions; + this._dimOmitted = dimensionsInput.isDimensionOmitted(); + this._schema = dimensionsInput; + } else { + assignStoreDimIdx = true; + dimensions = dimensionsInput; + } + dimensions = dimensions || ["x", "y"]; + var dimensionInfos = {}; + var dimensionNames = []; + var invertedIndicesMap = {}; + var needsHasOwn = false; + var emptyObj = {}; + for (var i = 0; i < dimensions.length; i++) { + var dimInfoInput = dimensions[i]; + var dimensionInfo = isString(dimInfoInput) ? new SeriesDimensionDefine({ + name: dimInfoInput + }) : !(dimInfoInput instanceof SeriesDimensionDefine) ? new SeriesDimensionDefine(dimInfoInput) : dimInfoInput; + var dimensionName = dimensionInfo.name; + dimensionInfo.type = dimensionInfo.type || "float"; + if (!dimensionInfo.coordDim) { + dimensionInfo.coordDim = dimensionName; + dimensionInfo.coordDimIndex = 0; + } + var otherDims = dimensionInfo.otherDims = dimensionInfo.otherDims || {}; + dimensionNames.push(dimensionName); + dimensionInfos[dimensionName] = dimensionInfo; + if (emptyObj[dimensionName] != null) { + needsHasOwn = true; + } + if (dimensionInfo.createInvertedIndices) { + invertedIndicesMap[dimensionName] = []; + } + if (otherDims.itemName === 0) { + this._nameDimIdx = i; + } + if (otherDims.itemId === 0) { + this._idDimIdx = i; + } + if (assignStoreDimIdx) { + dimensionInfo.storeDimIndex = i; + } + } + this.dimensions = dimensionNames; + this._dimInfos = dimensionInfos; + this._initGetDimensionInfo(needsHasOwn); + this.hostModel = hostModel; + this._invertedIndicesMap = invertedIndicesMap; + if (this._dimOmitted) { + var dimIdxToName_1 = this._dimIdxToName = createHashMap(); + each$4(dimensionNames, function(dimName) { + dimIdxToName_1.set(dimensionInfos[dimName].storeDimIndex, dimName); + }); + } + } + SeriesData2.prototype.getDimension = function(dim) { + var dimIdx = this._recognizeDimIndex(dim); + if (dimIdx == null) { + return dim; + } + dimIdx = dim; + if (!this._dimOmitted) { + return this.dimensions[dimIdx]; + } + var dimName = this._dimIdxToName.get(dimIdx); + if (dimName != null) { + return dimName; + } + var sourceDimDef = this._schema.getSourceDimension(dimIdx); + if (sourceDimDef) { + return sourceDimDef.name; + } + }; + SeriesData2.prototype.getDimensionIndex = function(dim) { + var dimIdx = this._recognizeDimIndex(dim); + if (dimIdx != null) { + return dimIdx; + } + if (dim == null) { + return -1; + } + var dimInfo = this._getDimInfo(dim); + return dimInfo ? dimInfo.storeDimIndex : this._dimOmitted ? this._schema.getSourceDimensionIndex(dim) : -1; + }; + SeriesData2.prototype._recognizeDimIndex = function(dim) { + if (isNumber(dim) || dim != null && !isNaN(dim) && !this._getDimInfo(dim) && (!this._dimOmitted || this._schema.getSourceDimensionIndex(dim) < 0)) { + return +dim; + } + }; + SeriesData2.prototype._getStoreDimIndex = function(dim) { + var dimIdx = this.getDimensionIndex(dim); + return dimIdx; + }; + SeriesData2.prototype.getDimensionInfo = function(dim) { + return this._getDimInfo(this.getDimension(dim)); + }; + SeriesData2.prototype._initGetDimensionInfo = function(needsHasOwn) { + var dimensionInfos = this._dimInfos; + this._getDimInfo = needsHasOwn ? function(dimName) { + return dimensionInfos.hasOwnProperty(dimName) ? dimensionInfos[dimName] : void 0; + } : function(dimName) { + return dimensionInfos[dimName]; + }; + }; + SeriesData2.prototype.getDimensionsOnCoord = function() { + return this._dimSummary.dataDimsOnCoord.slice(); + }; + SeriesData2.prototype.mapDimension = function(coordDim, idx) { + var dimensionsSummary = this._dimSummary; + if (idx == null) { + return dimensionsSummary.encodeFirstDimNotExtra[coordDim]; + } + var dims = dimensionsSummary.encode[coordDim]; + return dims ? dims[idx] : null; + }; + SeriesData2.prototype.mapDimensionsAll = function(coordDim) { + var dimensionsSummary = this._dimSummary; + var dims = dimensionsSummary.encode[coordDim]; + return (dims || []).slice(); + }; + SeriesData2.prototype.getStore = function() { + return this._store; + }; + SeriesData2.prototype.initData = function(data, nameList, dimValueGetter) { + var _this = this; + var store; + if (data instanceof DataStore) { + store = data; + } + if (!store) { + var dimensions = this.dimensions; + var provider = isSourceInstance(data) || isArrayLike(data) ? new DefaultDataProvider(data, dimensions.length) : data; + store = new DataStore(); + var dimensionInfos = map(dimensions, function(dimName) { + return { + type: _this._dimInfos[dimName].type, + property: dimName + }; + }); + store.initData(provider, dimensionInfos, dimValueGetter); + } + this._store = store; + this._nameList = (nameList || []).slice(); + this._idList = []; + this._nameRepeatCount = {}; + this._doInit(0, store.count()); + this._dimSummary = summarizeDimensions(this, this._schema); + this.userOutput = this._dimSummary.userOutput; + }; + SeriesData2.prototype.appendData = function(data) { + var range = this._store.appendData(data); + this._doInit(range[0], range[1]); + }; + SeriesData2.prototype.appendValues = function(values, names) { + var _a = this._store.appendValues(values, names && names.length), start = _a.start, end = _a.end; + var shouldMakeIdFromName = this._shouldMakeIdFromName(); + this._updateOrdinalMeta(); + if (names) { + for (var idx = start; idx < end; idx++) { + var sourceIdx = idx - start; + this._nameList[idx] = names[sourceIdx]; + if (shouldMakeIdFromName) { + makeIdFromName(this, idx); + } + } + } + }; + SeriesData2.prototype._updateOrdinalMeta = function() { + var store = this._store; + var dimensions = this.dimensions; + for (var i = 0; i < dimensions.length; i++) { + var dimInfo = this._dimInfos[dimensions[i]]; + if (dimInfo.ordinalMeta) { + store.collectOrdinalMeta(dimInfo.storeDimIndex, dimInfo.ordinalMeta); + } + } + }; + SeriesData2.prototype._shouldMakeIdFromName = function() { + var provider = this._store.getProvider(); + return this._idDimIdx == null && provider.getSource().sourceFormat !== SOURCE_FORMAT_TYPED_ARRAY && !provider.fillStorage; + }; + SeriesData2.prototype._doInit = function(start, end) { + if (start >= end) { + return; + } + var store = this._store; + var provider = store.getProvider(); + this._updateOrdinalMeta(); + var nameList = this._nameList; + var idList = this._idList; + var sourceFormat = provider.getSource().sourceFormat; + var isFormatOriginal = sourceFormat === SOURCE_FORMAT_ORIGINAL; + if (isFormatOriginal && !provider.pure) { + var sharedDataItem = []; + for (var idx = start; idx < end; idx++) { + var dataItem = provider.getItem(idx, sharedDataItem); + if (!this.hasItemOption && isDataItemOption(dataItem)) { + this.hasItemOption = true; + } + if (dataItem) { + var itemName = dataItem.name; + if (nameList[idx] == null && itemName != null) { + nameList[idx] = convertOptionIdName(itemName, null); + } + var itemId = dataItem.id; + if (idList[idx] == null && itemId != null) { + idList[idx] = convertOptionIdName(itemId, null); + } + } + } + } + if (this._shouldMakeIdFromName()) { + for (var idx = start; idx < end; idx++) { + makeIdFromName(this, idx); + } + } + prepareInvertedIndex(this); + }; + SeriesData2.prototype.getApproximateExtent = function(dim) { + return this._approximateExtent[dim] || this._store.getDataExtent(this._getStoreDimIndex(dim)); + }; + SeriesData2.prototype.setApproximateExtent = function(extent, dim) { + dim = this.getDimension(dim); + this._approximateExtent[dim] = extent.slice(); + }; + SeriesData2.prototype.getCalculationInfo = function(key) { + return this._calculationInfo[key]; + }; + SeriesData2.prototype.setCalculationInfo = function(key, value) { + isObject(key) ? extend(this._calculationInfo, key) : this._calculationInfo[key] = value; + }; + SeriesData2.prototype.getName = function(idx) { + var rawIndex = this.getRawIndex(idx); + var name = this._nameList[rawIndex]; + if (name == null && this._nameDimIdx != null) { + name = getIdNameFromStore(this, this._nameDimIdx, rawIndex); + } + if (name == null) { + name = ""; + } + return name; + }; + SeriesData2.prototype._getCategory = function(dimIdx, idx) { + var ordinal = this._store.get(dimIdx, idx); + var ordinalMeta = this._store.getOrdinalMeta(dimIdx); + if (ordinalMeta) { + return ordinalMeta.categories[ordinal]; + } + return ordinal; + }; + SeriesData2.prototype.getId = function(idx) { + return getId(this, this.getRawIndex(idx)); + }; + SeriesData2.prototype.count = function() { + return this._store.count(); + }; + SeriesData2.prototype.get = function(dim, idx) { + var store = this._store; + var dimInfo = this._dimInfos[dim]; + if (dimInfo) { + return store.get(dimInfo.storeDimIndex, idx); + } + }; + SeriesData2.prototype.getByRawIndex = function(dim, rawIdx) { + var store = this._store; + var dimInfo = this._dimInfos[dim]; + if (dimInfo) { + return store.getByRawIndex(dimInfo.storeDimIndex, rawIdx); + } + }; + SeriesData2.prototype.getIndices = function() { + return this._store.getIndices(); + }; + SeriesData2.prototype.getDataExtent = function(dim) { + return this._store.getDataExtent(this._getStoreDimIndex(dim)); + }; + SeriesData2.prototype.getSum = function(dim) { + return this._store.getSum(this._getStoreDimIndex(dim)); + }; + SeriesData2.prototype.getMedian = function(dim) { + return this._store.getMedian(this._getStoreDimIndex(dim)); + }; + SeriesData2.prototype.getValues = function(dimensions, idx) { + var _this = this; + var store = this._store; + return isArray(dimensions) ? store.getValues(map(dimensions, function(dim) { + return _this._getStoreDimIndex(dim); + }), idx) : store.getValues(dimensions); + }; + SeriesData2.prototype.hasValue = function(idx) { + var dataDimIndicesOnCoord = this._dimSummary.dataDimIndicesOnCoord; + for (var i = 0, len = dataDimIndicesOnCoord.length; i < len; i++) { + if (isNaN(this._store.get(dataDimIndicesOnCoord[i], idx))) { + return false; + } + } + return true; + }; + SeriesData2.prototype.indexOfName = function(name) { + for (var i = 0, len = this._store.count(); i < len; i++) { + if (this.getName(i) === name) { + return i; + } + } + return -1; + }; + SeriesData2.prototype.getRawIndex = function(idx) { + return this._store.getRawIndex(idx); + }; + SeriesData2.prototype.indexOfRawIndex = function(rawIndex) { + return this._store.indexOfRawIndex(rawIndex); + }; + SeriesData2.prototype.rawIndexOf = function(dim, value) { + var invertedIndices = dim && this._invertedIndicesMap[dim]; + var rawIndex = invertedIndices && invertedIndices[value]; + if (rawIndex == null || isNaN(rawIndex)) { + return INDEX_NOT_FOUND; + } + return rawIndex; + }; + SeriesData2.prototype.indicesOfNearest = function(dim, value, maxDistance) { + return this._store.indicesOfNearest(this._getStoreDimIndex(dim), value, maxDistance); + }; + SeriesData2.prototype.each = function(dims, cb, ctx) { + if (isFunction(dims)) { + ctx = cb; + cb = dims; + dims = []; + } + var fCtx = ctx || this; + var dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); + this._store.each(dimIndices, fCtx ? bind$1(cb, fCtx) : cb); + }; + SeriesData2.prototype.filterSelf = function(dims, cb, ctx) { + if (isFunction(dims)) { + ctx = cb; + cb = dims; + dims = []; + } + var fCtx = ctx || this; + var dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); + this._store = this._store.filter(dimIndices, fCtx ? bind$1(cb, fCtx) : cb); + return this; + }; + SeriesData2.prototype.selectRange = function(range) { + var _this = this; + var innerRange = {}; + var dims = keys(range); + each$4(dims, function(dim) { + var dimIdx = _this._getStoreDimIndex(dim); + innerRange[dimIdx] = range[dim]; + }); + this._store = this._store.selectRange(innerRange); + return this; + }; + SeriesData2.prototype.mapArray = function(dims, cb, ctx) { + if (isFunction(dims)) { + ctx = cb; + cb = dims; + dims = []; + } + ctx = ctx || this; + var result = []; + this.each(dims, function() { + result.push(cb && cb.apply(this, arguments)); + }, ctx); + return result; + }; + SeriesData2.prototype.map = function(dims, cb, ctx, ctxCompat) { + var fCtx = ctx || ctxCompat || this; + var dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); + var list = cloneListForMapAndSample(this); + list._store = this._store.map(dimIndices, fCtx ? bind$1(cb, fCtx) : cb); + return list; + }; + SeriesData2.prototype.modify = function(dims, cb, ctx, ctxCompat) { + var fCtx = ctx || ctxCompat || this; + var dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); + this._store.modify(dimIndices, fCtx ? bind$1(cb, fCtx) : cb); + }; + SeriesData2.prototype.downSample = function(dimension, rate, sampleValue, sampleIndex) { + var list = cloneListForMapAndSample(this); + list._store = this._store.downSample(this._getStoreDimIndex(dimension), rate, sampleValue, sampleIndex); + return list; + }; + SeriesData2.prototype.minmaxDownSample = function(valueDimension, rate) { + var list = cloneListForMapAndSample(this); + list._store = this._store.minmaxDownSample(this._getStoreDimIndex(valueDimension), rate); + return list; + }; + SeriesData2.prototype.lttbDownSample = function(valueDimension, rate) { + var list = cloneListForMapAndSample(this); + list._store = this._store.lttbDownSample(this._getStoreDimIndex(valueDimension), rate); + return list; + }; + SeriesData2.prototype.getRawDataItem = function(idx) { + return this._store.getRawDataItem(idx); + }; + SeriesData2.prototype.getItemModel = function(idx) { + var hostModel = this.hostModel; + var dataItem = this.getRawDataItem(idx); + return new Model(dataItem, hostModel, hostModel && hostModel.ecModel); + }; + SeriesData2.prototype.diff = function(otherList) { + var thisList = this; + return new DataDiffer(otherList ? otherList.getStore().getIndices() : [], this.getStore().getIndices(), function(idx) { + return getId(otherList, idx); + }, function(idx) { + return getId(thisList, idx); + }); + }; + SeriesData2.prototype.getVisual = function(key) { + var visual = this._visual; + return visual && visual[key]; + }; + SeriesData2.prototype.setVisual = function(kvObj, val) { + this._visual = this._visual || {}; + if (isObject(kvObj)) { + extend(this._visual, kvObj); + } else { + this._visual[kvObj] = val; + } + }; + SeriesData2.prototype.getItemVisual = function(idx, key) { + var itemVisual = this._itemVisuals[idx]; + var val = itemVisual && itemVisual[key]; + if (val == null) { + return this.getVisual(key); + } + return val; + }; + SeriesData2.prototype.hasItemVisual = function() { + return this._itemVisuals.length > 0; + }; + SeriesData2.prototype.ensureUniqueItemVisual = function(idx, key) { + var itemVisuals = this._itemVisuals; + var itemVisual = itemVisuals[idx]; + if (!itemVisual) { + itemVisual = itemVisuals[idx] = {}; + } + var val = itemVisual[key]; + if (val == null) { + val = this.getVisual(key); + if (isArray(val)) { + val = val.slice(); + } else if (isObject(val)) { + val = extend({}, val); + } + itemVisual[key] = val; + } + return val; + }; + SeriesData2.prototype.setItemVisual = function(idx, key, value) { + var itemVisual = this._itemVisuals[idx] || {}; + this._itemVisuals[idx] = itemVisual; + if (isObject(key)) { + extend(itemVisual, key); + } else { + itemVisual[key] = value; + } + }; + SeriesData2.prototype.clearAllVisual = function() { + this._visual = {}; + this._itemVisuals = []; + }; + SeriesData2.prototype.setLayout = function(key, val) { + isObject(key) ? extend(this._layout, key) : this._layout[key] = val; + }; + SeriesData2.prototype.getLayout = function(key) { + return this._layout[key]; + }; + SeriesData2.prototype.getItemLayout = function(idx) { + return this._itemLayouts[idx]; + }; + SeriesData2.prototype.setItemLayout = function(idx, layout, merge) { + this._itemLayouts[idx] = merge ? extend(this._itemLayouts[idx] || {}, layout) : layout; + }; + SeriesData2.prototype.clearItemLayouts = function() { + this._itemLayouts.length = 0; + }; + SeriesData2.prototype.setItemGraphicEl = function(idx, el) { + var seriesIndex = this.hostModel && this.hostModel.seriesIndex; + setCommonECData(seriesIndex, this.dataType, idx, el); + this._graphicEls[idx] = el; + }; + SeriesData2.prototype.getItemGraphicEl = function(idx) { + return this._graphicEls[idx]; + }; + SeriesData2.prototype.eachItemGraphicEl = function(cb, context) { + each$4(this._graphicEls, function(el, idx) { + if (el) { + cb && cb.call(context, el, idx); + } + }); + }; + SeriesData2.prototype.cloneShallow = function(list) { + if (!list) { + list = new SeriesData2(this._schema ? this._schema : map(this.dimensions, this._getDimInfo, this), this.hostModel); + } + transferProperties(list, this); + list._store = this._store; + return list; + }; + SeriesData2.prototype.wrapMethod = function(methodName, injectFunction) { + var originalMethod = this[methodName]; + if (!isFunction(originalMethod)) { + 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))); + }; + }; + SeriesData2.internalField = function() { + prepareInvertedIndex = function(data) { + var invertedIndicesMap = data._invertedIndicesMap; + each$4(invertedIndicesMap, function(invertedIndices, dim) { + var dimInfo = data._dimInfos[dim]; + var ordinalMeta = dimInfo.ordinalMeta; + var store = data._store; + if (ordinalMeta) { + invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array(ordinalMeta.categories.length); + for (var i = 0; i < invertedIndices.length; i++) { + invertedIndices[i] = INDEX_NOT_FOUND; + } + for (var i = 0; i < store.count(); i++) { + invertedIndices[store.get(dimInfo.storeDimIndex, i)] = i; + } + } + }); + }; + getIdNameFromStore = function(data, dimIdx, idx) { + return convertOptionIdName(data._getCategory(dimIdx, idx), null); + }; + getId = function(data, rawIndex) { + var id = data._idList[rawIndex]; + if (id == null && data._idDimIdx != null) { + id = getIdNameFromStore(data, data._idDimIdx, rawIndex); + } + if (id == null) { + id = ID_PREFIX + rawIndex; + } + return id; + }; + normalizeDimensions = function(dimensions) { + if (!isArray(dimensions)) { + dimensions = dimensions != null ? [dimensions] : []; + } + return dimensions; + }; + cloneListForMapAndSample = function(original) { + var list = new SeriesData2(original._schema ? original._schema : map(original.dimensions, original._getDimInfo, original), original.hostModel); + transferProperties(list, original); + return list; + }; + transferProperties = function(target, source) { + each$4(TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []), function(propName) { + if (source.hasOwnProperty(propName)) { + target[propName] = source[propName]; + } + }); + target.__wrappedMethods = source.__wrappedMethods; + each$4(CLONE_PROPERTIES, function(propName) { + target[propName] = clone$2(source[propName]); + }); + target._calculationInfo = extend({}, source._calculationInfo); + }; + makeIdFromName = function(data, idx) { + var nameList = data._nameList; + var idList = data._idList; + var nameDimIdx = data._nameDimIdx; + var idDimIdx = data._idDimIdx; + var name = nameList[idx]; + var id = idList[idx]; + if (name == null && nameDimIdx != null) { + nameList[idx] = name = getIdNameFromStore(data, nameDimIdx, idx); + } + if (id == null && idDimIdx != null) { + idList[idx] = id = getIdNameFromStore(data, idDimIdx, idx); + } + if (id == null && name != null) { + var nameRepeatCount = data._nameRepeatCount; + var nmCnt = nameRepeatCount[name] = (nameRepeatCount[name] || 0) + 1; + id = name; + if (nmCnt > 1) { + id += "__ec__" + nmCnt; + } + idList[idx] = id; + } + }; + }(); + return SeriesData2; + }() +); + +/** + * This method builds the relationship between: + * + "what the coord sys or series requires (see `coordDimensions`)", + * + "what the user defines (in `encode` and `dimensions`, see `opt.dimensionsDefine` and `opt.encodeDefine`)" + * + "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'. + * + * @return The results are always sorted by `storeDimIndex` asc. + */ +function prepareSeriesDataSchema( +// TODO: TYPE completeDimensions type +source, opt) { + if (!isSourceInstance(source)) { + source = createSourceFromSeriesDataOption(source); + } + opt = opt || {}; + var sysDims = opt.coordDimensions || []; + var dimsDef = opt.dimensionsDefine || source.dimensionsDefine || []; + var coordDimNameMap = createHashMap(); + var resultList = []; + var dimCount = getDimCount(source, sysDims, dimsDef, opt.dimensionsCount); + // Try to ignore unused dimensions if sharing a high dimension datastore + // 30 is an experience value. + var omitUnusedDimensions = opt.canOmitUnusedDimensions && shouldOmitUnusedDimensions(dimCount); + var isUsingSourceDimensionsDef = dimsDef === source.dimensionsDefine; + var dataDimNameMap = isUsingSourceDimensionsDef ? ensureSourceDimNameMap(source) : createDimNameMap(dimsDef); + var encodeDef = opt.encodeDefine; + if (!encodeDef && opt.encodeDefaulter) { + encodeDef = opt.encodeDefaulter(source, dimCount); + } + var encodeDefMap = createHashMap(encodeDef); + var indicesMap = new CtorInt32Array$1(dimCount); + for (var i = 0; i < indicesMap.length; i++) { + indicesMap[i] = -1; + } + function getResultItem(dimIdx) { + var idx = indicesMap[dimIdx]; + if (idx < 0) { + var dimDefItemRaw = dimsDef[dimIdx]; + var dimDefItem = isObject$2(dimDefItemRaw) ? dimDefItemRaw : { + name: dimDefItemRaw + }; + var resultItem = new SeriesDimensionDefine(); + var userDimName = dimDefItem.name; + if (userDimName != null && dataDimNameMap.get(userDimName) != null) { + // Only if `series.dimensions` is defined in option + // displayName, will be set, and dimension will be displayed vertically in + // tooltip by default. + resultItem.name = resultItem.displayName = userDimName; + } + dimDefItem.type != null && (resultItem.type = dimDefItem.type); + dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName); + var newIdx = resultList.length; + indicesMap[dimIdx] = newIdx; + resultItem.storeDimIndex = dimIdx; + resultList.push(resultItem); + return resultItem; + } + return resultList[idx]; + } + if (!omitUnusedDimensions) { + for (var i = 0; i < dimCount; i++) { + getResultItem(i); + } + } + // Set `coordDim` and `coordDimIndex` by `encodeDefMap` and normalize `encodeDefMap`. + encodeDefMap.each(function (dataDimsRaw, coordDim) { + var dataDims = normalizeToArray(dataDimsRaw).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) { + encodeDefMap.set(coordDim, false); + return; + } + var validDataDims = encodeDefMap.set(coordDim, []); + each$4(dataDims, function (resultDimIdxOrName, idx) { + // The input resultDimIdx can be dim name or index. + var resultDimIdx = isString(resultDimIdxOrName) ? dataDimNameMap.get(resultDimIdxOrName) : resultDimIdxOrName; + if (resultDimIdx != null && resultDimIdx < dimCount) { + validDataDims[idx] = resultDimIdx; + applyDim(getResultItem(resultDimIdx), coordDim, idx); + } + }); + }); + // Apply templates and default order from `sysDims`. + var availDimIdx = 0; + each$4(sysDims, function (sysDimItemRaw) { + var coordDim; + var sysDimItemDimsDef; + var sysDimItemOtherDims; + var sysDimItem; + if (isString(sysDimItemRaw)) { + coordDim = sysDimItemRaw; + sysDimItem = {}; + } else { + sysDimItem = sysDimItemRaw; + coordDim = sysDimItem.name; + var ordinalMeta = sysDimItem.ordinalMeta; + sysDimItem.ordinalMeta = null; + sysDimItem = extend({}, 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 = encodeDefMap.get(coordDim); + // negative resultDimIdx means no need to mapping. + if (dataDims === false) { + return; + } + dataDims = normalizeToArray(dataDims); + // dimensions provides default dim sequences. + if (!dataDims.length) { + for (var i = 0; i < (sysDimItemDimsDef && sysDimItemDimsDef.length || 1); i++) { + while (availDimIdx < dimCount && getResultItem(availDimIdx).coordDim != null) { + availDimIdx++; + } + availDimIdx < dimCount && dataDims.push(availDimIdx++); + } + } + // Apply templates. + each$4(dataDims, function (resultDimIdx, coordDimIndex) { + var resultItem = getResultItem(resultDimIdx); + // Coordinate system has a higher priority on dim type than source. + if (isUsingSourceDimensionsDef && sysDimItem.type != null) { + resultItem.type = sysDimItem.type; + } + applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex); + if (resultItem.name == null && sysDimItemDimsDef) { + var sysDimItemDimsDefItem = sysDimItemDimsDef[coordDimIndex]; + !isObject$2(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 (VISUAL_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'; + function ifNoNameFillWithCoordName(resultItem) { + if (resultItem.name == null) { + // Duplication will be removed in the next step. + resultItem.name = resultItem.coordDim; + } + } + // Set dim `name` and other `coordDim` and other props. + if (!omitUnusedDimensions) { + for (var resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) { + var resultItem = getResultItem(resultDimIdx); + var coordDim = resultItem.coordDim; + if (coordDim == null) { + // TODO no need to generate coordDim for isExtraCoord? + resultItem.coordDim = genCoordDimName(extra, coordDimNameMap, fromZero); + resultItem.coordDimIndex = 0; + // Series specified generateCoord is using out. + if (!generateCoord || generateCoordCount <= 0) { + resultItem.isExtraCoord = true; + } + generateCoordCount--; + } + ifNoNameFillWithCoordName(resultItem); + if (resultItem.type == null && (guessOrdinal(source, resultDimIdx) === BE_ORDINAL.Must + // Consider the case: + // { + // dataset: {source: [ + // ['2001', 123], + // ['2002', 456], + // ... + // ['The others', 987], + // ]}, + // series: {type: 'pie'} + // } + // The first column should better be treated as a "ordinal" although it + // might not be detected as an "ordinal" by `guessOrdinal`. + || resultItem.isExtraCoord && (resultItem.otherDims.itemName != null || resultItem.otherDims.seriesName != null))) { + resultItem.type = 'ordinal'; + } + } + } else { + each$4(resultList, function (resultItem) { + // PENDING: guessOrdinal or let user specify type: 'ordinal' manually? + ifNoNameFillWithCoordName(resultItem); + }); + // Sort dimensions: there are some rule that use the last dim as label, + // and for some latter travel process easier. + resultList.sort(function (item0, item1) { + return item0.storeDimIndex - item1.storeDimIndex; + }); + } + removeDuplication(resultList); + return new SeriesDataSchema({ + source: source, + dimensions: resultList, + fullDimensionCount: dimCount, + dimensionOmitted: omitUnusedDimensions + }); +} +function removeDuplication(result) { + var duplicationMap = createHashMap(); + for (var i = 0; i < result.length; i++) { + var dim = result[i]; + var dimOriginalName = dim.name; + var count = duplicationMap.get(dimOriginalName) || 0; + if (count > 0) { + // Starts from 0. + dim.name = dimOriginalName + (count - 1); + } + count++; + duplicationMap.set(dimOriginalName, count); + } +} +// ??? 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 calculate bubble size or use visualMap +// on other dimensions besides coordSys needed. +// So, dims that is not used by system, should be shared in data store? +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.dimensionsDetectedCount || 1, sysDims.length, dimsDef.length, optDimCount || 0); + each$4(sysDims, function (sysDimItem) { + var sysDimItemDimsDef; + if (isObject$2(sysDimItem) && (sysDimItemDimsDef = sysDimItem.dimsDef)) { + dimCount = Math.max(dimCount, sysDimItemDimsDef.length); + } + }); + return dimCount; +} +function genCoordDimName(name, map, fromZero) { + if (fromZero || map.hasKey(name)) { + var i = 0; + while (map.hasKey(name + i)) { + i++; + } + name += i; + } + map.set(name, true); + return name; +} + +var CoordSysInfo = ( + /** @class */ + /* @__PURE__ */ function() { + function CoordSysInfo2(coordSysName) { + this.coordSysDims = []; + this.axisMap = createHashMap(); + this.categoryAxisMap = createHashMap(); + this.coordSysName = coordSysName; + } + return CoordSysInfo2; + }() +); +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", SINGLE_REFERRING).models[0]; + var yAxisModel = seriesModel.getReferringComponents("yAxis", SINGLE_REFERRING).models[0]; + 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", SINGLE_REFERRING).models[0]; + 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", SINGLE_REFERRING).models[0]; + var radiusAxisModel = polarModel.findAxisModel("radiusAxis"); + var angleAxisModel = polarModel.findAxisModel("angleAxis"); + 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$4(parallelModel.parallelAxisIndex, function(axisIndex, index) { + var axisModel = ecModel.getComponent("parallelAxis", axisIndex); + var axisDim = coordSysDims[index]; + axisMap.set(axisDim, axisModel); + if (isCategory(axisModel)) { + categoryAxisMap.set(axisDim, axisModel); + if (result.firstCategoryDimIndex == null) { + result.firstCategoryDimIndex = index; + } + } + }); + } +}; +function isCategory(axisModel) { + return axisModel.get("type") === "category"; +} + +/** + * 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 seriesModel + * @param dimensionsInput The same as the input of . + * The input will be modified. + * @param opt + * @param opt.stackedCoordDimension Specify a coord dimension if needed. + * @param opt.byIndex=false + * @return calculationInfo + * { + * stackedDimension: string + * stackedByDimension: string + * isStackedByIndex: boolean + * stackedOverDimension: string + * stackResultDimension: string + * } + */ +function enableDataStack(seriesModel, dimensionsInput, opt) { + opt = opt || {}; + var byIndex = opt.byIndex; + var stackedCoordDimension = opt.stackedCoordDimension; + var dimensionDefineList; + var schema; + var store; + if (isLegacyDimensionsInput(dimensionsInput)) { + dimensionDefineList = dimensionsInput; + } else { + schema = dimensionsInput.schema; + dimensionDefineList = schema.dimensions; + store = dimensionsInput.store; + } + // Compatibal: when `stack` is set as '', do not stack. + var mayStack = !!(seriesModel && seriesModel.get('stack')); + var stackedByDimInfo; + var stackedDimInfo; + var stackResultDimension; + var stackedOverDimension; + each$4(dimensionDefineList, function (dimensionInfo, index) { + if (isString(dimensionInfo)) { + dimensionDefineList[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. + // Also need to use seriesModel.id as postfix because different + // series may share same data store. The stack dimension needs to be distinguished. + stackResultDimension = '__\0ecstackresult_' + seriesModel.id; + stackedOverDimension = '__\0ecstackedover_' + seriesModel.id; + // Create inverted index to fast query index by value. + if (stackedByDimInfo) { + stackedByDimInfo.createInvertedIndices = true; + } + var stackedDimCoordDim_1 = stackedDimInfo.coordDim; + var stackedDimType = stackedDimInfo.type; + var stackedDimCoordIndex_1 = 0; + each$4(dimensionDefineList, function (dimensionInfo) { + if (dimensionInfo.coordDim === stackedDimCoordDim_1) { + stackedDimCoordIndex_1++; + } + }); + var stackedOverDimensionDefine = { + name: stackResultDimension, + coordDim: stackedDimCoordDim_1, + coordDimIndex: stackedDimCoordIndex_1, + type: stackedDimType, + isExtraCoord: true, + isCalculationCoord: true, + storeDimIndex: dimensionDefineList.length + }; + var stackResultDimensionDefine = { + 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_1 + 1, + type: stackedDimType, + isExtraCoord: true, + isCalculationCoord: true, + storeDimIndex: dimensionDefineList.length + 1 + }; + if (schema) { + if (store) { + stackedOverDimensionDefine.storeDimIndex = store.ensureCalculationDimension(stackedOverDimension, stackedDimType); + stackResultDimensionDefine.storeDimIndex = store.ensureCalculationDimension(stackResultDimension, stackedDimType); + } + schema.appendCalculationDimension(stackedOverDimensionDefine); + schema.appendCalculationDimension(stackResultDimensionDefine); + } else { + dimensionDefineList.push(stackedOverDimensionDefine); + dimensionDefineList.push(stackResultDimensionDefine); + } + } + return { + stackedDimension: stackedDimInfo && stackedDimInfo.name, + stackedByDimension: stackedByDimInfo && stackedByDimInfo.name, + isStackedByIndex: byIndex, + stackedOverDimension: stackedOverDimension, + stackResultDimension: stackResultDimension + }; +} +function isLegacyDimensionsInput(dimensionsInput) { + return !isSeriesDataSchema(dimensionsInput.schema); +} +function isDimensionStacked(data, stackedDim) { + // 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'); +} +function getStackedDimension(data, targetDim) { + return isDimensionStacked(data, targetDim) ? data.getCalculationInfo('stackResultDimension') : targetDim; +} + +function getCoordSysDimDefs(seriesModel, coordSysInfo) { + var coordSysName = seriesModel.get('coordinateSystem'); + var registeredCoordSys = CoordinateSystemManager.get(coordSysName); + var coordSysDimDefs; + if (coordSysInfo && coordSysInfo.coordSysDims) { + coordSysDimDefs = map$1(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); + } + return dimInfo; + }); + } + if (!coordSysDimDefs) { + // Get dimensions from registered coordinate system + coordSysDimDefs = registeredCoordSys && (registeredCoordSys.getDimensionsInfo ? registeredCoordSys.getDimensionsInfo() : registeredCoordSys.dimensions.slice()) || ['x', 'y']; + } + return coordSysDimDefs; +} +function injectOrdinalMeta(dimInfoList, createInvertedIndices, coordSysInfo) { + var firstCategoryDimIndex; + var hasNameEncode; + coordSysInfo && each$4(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 (createInvertedIndices) { + dimInfo.createInvertedIndices = true; + } + } + if (dimInfo.otherDims.itemName != null) { + hasNameEncode = true; + } + }); + if (!hasNameEncode && firstCategoryDimIndex != null) { + dimInfoList[firstCategoryDimIndex].otherDims.itemName = 0; + } + return firstCategoryDimIndex; +} +/** + * Caution: there are side effects to `sourceManager` in this method. + * Should better only be called in `Series['getInitialData']`. + */ +function createSeriesData(sourceRaw, seriesModel, opt) { + opt = opt || {}; + var sourceManager = seriesModel.getSourceManager(); + var source; + var isOriginalSource = false; + { + source = sourceManager.getSource(); + // Is series.data. not dataset. + isOriginalSource = source.sourceFormat === SOURCE_FORMAT_ORIGINAL; + } + var coordSysInfo = getCoordSysInfoBySeries(seriesModel); + var coordSysDimDefs = getCoordSysDimDefs(seriesModel, coordSysInfo); + var useEncodeDefaulter = opt.useEncodeDefaulter; + var encodeDefaulter = isFunction(useEncodeDefaulter) ? useEncodeDefaulter : useEncodeDefaulter ? curry$1(makeSeriesEncodeForAxisCoordSys, coordSysDimDefs, seriesModel) : null; + var createDimensionOptions = { + coordDimensions: coordSysDimDefs, + generateCoord: opt.generateCoord, + encodeDefine: seriesModel.getEncode(), + encodeDefaulter: encodeDefaulter, + canOmitUnusedDimensions: !isOriginalSource + }; + var schema = prepareSeriesDataSchema(source, createDimensionOptions); + var firstCategoryDimIndex = injectOrdinalMeta(schema.dimensions, opt.createInvertedIndices, coordSysInfo); + var store = !isOriginalSource ? sourceManager.getSharedDataStore(schema) : null; + var stackCalculationInfo = enableDataStack(seriesModel, { + schema: schema, + store: store + }); + var data = new SeriesData(schema, seriesModel); + data.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; + data.hasItemOption = false; + data.initData( + // Try to reuse the data store in sourceManager if using dataset. + isOriginalSource ? source : store, null, dimValueGetter); + return data; +} +function isNeedCompleteOrdinalData(source) { + if (source.sourceFormat === SOURCE_FORMAT_ORIGINAL) { + var sampleItem = firstDataNotNull(source.data || []); + return !isArray(getDataItemValue(sampleItem)); + } +} +function firstDataNotNull(arr) { + var i = 0; + while (i < arr.length && arr[i] == null) { + i++; + } + return arr[i]; +} + +var Scale = /** @class */function () { + function Scale(setting) { + this._setting = setting || {}; + this._extent = [Infinity, -Infinity]; + } + Scale.prototype.getSetting = function (name) { + return this._setting[name]; + }; + /** + * Set extent from data + */ + 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 + */ + Scale.prototype.unionExtentFromData = function (data, dim) { + this.unionExtent(data.getApproximateExtent(dim)); + }; + /** + * Get extent + * + * Extent is always in increase order. + */ + Scale.prototype.getExtent = function () { + return this._extent.slice(); + }; + /** + * Set extent + */ + Scale.prototype.setExtent = function (start, end) { + var thisExtent = this._extent; + if (!isNaN(start)) { + thisExtent[0] = start; + } + if (!isNaN(end)) { + thisExtent[1] = end; + } + }; + /** + * If value is in extent range + */ + Scale.prototype.isInExtentRange = function (value) { + return this._extent[0] <= value && this._extent[1] >= value; + }; + /** + * 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; + }; + return Scale; +}(); +enableClassManagement(Scale); + +var uidBase = 0; +var OrdinalMeta = /** @class */function () { + function OrdinalMeta(opt) { + this.categories = opt.categories || []; + this._needCollect = opt.needCollect; + this._deduplication = opt.deduplication; + this.uid = ++uidBase; + } + OrdinalMeta.createByAxisModel = function (axisModel) { + var option = axisModel.option; + var data = option.data; + var categories = data && map$1(data, getName); + return new OrdinalMeta({ + categories: categories, + needCollect: !categories, + // deduplication is default in axis. + deduplication: option.dedplication !== false + }); + }; + OrdinalMeta.prototype.getOrdinal = function (category) { + // @ts-ignore + return this._getOrCreateMap().get(category); + }; + /** + * @return The ordinal. If not found, return NaN. + */ + OrdinalMeta.prototype.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 (!isString(category) && !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 = this._getOrCreateMap(); + // @ts-ignore + index = map.get(category); + if (index == null) { + if (needCollect) { + index = this.categories.length; + this.categories[index] = category; + // @ts-ignore + map.set(category, index); + } else { + index = NaN; + } + } + return index; + }; + // Consider big data, do not create map until needed. + OrdinalMeta.prototype._getOrCreateMap = function () { + return this._map || (this._map = createHashMap(this.categories)); + }; + return OrdinalMeta; +}(); +function getName(obj) { + if (isObject$2(obj) && obj.value != null) { + return obj.value; + } else { + return obj + ''; + } +} + +function isIntervalOrLogScale(scale) { + return scale.type === 'interval' || scale.type === 'log'; +} +/** + * @param extent Both extent[0] and extent[1] should be valid number. + * Should be extent[0] < extent[1]. + * @param splitNumber splitNumber should be >= 1. + */ +function intervalScaleNiceTicks(extent, splitNumber, minInterval, maxInterval) { + var result = {}; + var span = extent[1] - extent[0]; + var interval = result.interval = nice(span / splitNumber); + 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 = [round$1(Math.ceil(extent[0] / interval) * interval, precision), round$1(Math.floor(extent[1] / interval) * interval, precision)]; + fixExtent(niceTickExtent, extent); + return result; +} +function increaseInterval(interval) { + var exp10 = Math.pow(10, quantityExponent(interval)); + // Increase interval + var f = interval / exp10; + if (!f) { + f = 1; + } else if (f === 2) { + f = 3; + } else if (f === 3) { + f = 5; + } else { + // f is 1 or 5 + f *= 2; + } + return round$1(f * exp10); +} +/** + * @return interval precision + */ +function getIntervalPrecision(interval) { + // Tow more digital for tick. + return getPrecision(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]; + } +} +function contain(val, extent) { + return val >= extent[0] && val <= extent[1]; +} +function normalize(val, extent) { + if (extent[1] === extent[0]) { + return 0.5; + } + return (val - extent[0]) / (extent[1] - extent[0]); +} +function scale(val, extent) { + return val * (extent[1] - extent[0]) + extent[0]; +} + +var OrdinalScale = /** @class */function (_super) { + __extends(OrdinalScale, _super); + function OrdinalScale(setting) { + var _this = _super.call(this, setting) || this; + _this.type = 'ordinal'; + var ordinalMeta = _this.getSetting('ordinalMeta'); + // Caution: Should not use instanceof, consider ec-extensions using + // import approach to get OrdinalMeta class. + if (!ordinalMeta) { + ordinalMeta = new OrdinalMeta({}); + } + if (isArray(ordinalMeta)) { + ordinalMeta = new OrdinalMeta({ + categories: map$1(ordinalMeta, function (item) { + return isObject$2(item) ? item.value : item; + }) + }); + } + _this._ordinalMeta = ordinalMeta; + _this._extent = _this.getSetting('extent') || [0, ordinalMeta.categories.length - 1]; + return _this; + } + OrdinalScale.prototype.parse = function (val) { + // Caution: Math.round(null) will return `0` rather than `NaN` + if (val == null) { + return NaN; + } + return isString(val) ? this._ordinalMeta.getOrdinal(val) + // val might be float. + : Math.round(val); + }; + OrdinalScale.prototype.contain = function (rank) { + rank = this.parse(rank); + return contain(rank, this._extent) && this._ordinalMeta.categories[rank] != null; + }; + /** + * Normalize given rank or name to linear [0, 1] + * @param val raw ordinal number. + * @return normalized value in [0, 1]. + */ + OrdinalScale.prototype.normalize = function (val) { + val = this._getTickNumber(this.parse(val)); + return normalize(val, this._extent); + }; + /** + * @param val normalized value in [0, 1]. + * @return raw ordinal number. + */ + OrdinalScale.prototype.scale = function (val) { + val = Math.round(scale(val, this._extent)); + return this.getRawOrdinalNumber(val); + }; + OrdinalScale.prototype.getTicks = function () { + var ticks = []; + var extent = this._extent; + var rank = extent[0]; + while (rank <= extent[1]) { + ticks.push({ + value: rank + }); + rank++; + } + return ticks; + }; + OrdinalScale.prototype.getMinorTicks = function (splitNumber) { + // Not support. + return; + }; + /** + * @see `Ordinal['_ordinalNumbersByTick']` + */ + OrdinalScale.prototype.setSortInfo = function (info) { + if (info == null) { + this._ordinalNumbersByTick = this._ticksByOrdinalNumber = null; + return; + } + var infoOrdinalNumbers = info.ordinalNumbers; + var ordinalsByTick = this._ordinalNumbersByTick = []; + var ticksByOrdinal = this._ticksByOrdinalNumber = []; + // Unnecessary support negative tick in `realtimeSort`. + var tickNum = 0; + var allCategoryLen = this._ordinalMeta.categories.length; + for (var len = Math.min(allCategoryLen, infoOrdinalNumbers.length); tickNum < len; ++tickNum) { + var ordinalNumber = infoOrdinalNumbers[tickNum]; + ordinalsByTick[tickNum] = ordinalNumber; + ticksByOrdinal[ordinalNumber] = tickNum; + } + // Handle that `series.data` only covers part of the `axis.category.data`. + var unusedOrdinal = 0; + for (; tickNum < allCategoryLen; ++tickNum) { + while (ticksByOrdinal[unusedOrdinal] != null) { + unusedOrdinal++; + } + ordinalsByTick.push(unusedOrdinal); + ticksByOrdinal[unusedOrdinal] = tickNum; + } + }; + OrdinalScale.prototype._getTickNumber = function (ordinal) { + var ticksByOrdinalNumber = this._ticksByOrdinalNumber; + // also support ordinal out of range of `ordinalMeta.categories.length`, + // where ordinal numbers are used as tick value directly. + return ticksByOrdinalNumber && ordinal >= 0 && ordinal < ticksByOrdinalNumber.length ? ticksByOrdinalNumber[ordinal] : ordinal; + }; + /** + * @usage + * ```js + * const ordinalNumber = ordinalScale.getRawOrdinalNumber(tickVal); + * + * // case0 + * const rawOrdinalValue = axisModel.getCategories()[ordinalNumber]; + * // case1 + * const rawOrdinalValue = this._ordinalMeta.categories[ordinalNumber]; + * // case2 + * const coord = axis.dataToCoord(ordinalNumber); + * ``` + * + * @param {OrdinalNumber} tickNumber index of display + */ + OrdinalScale.prototype.getRawOrdinalNumber = function (tickNumber) { + var ordinalNumbersByTick = this._ordinalNumbersByTick; + // tickNumber may be out of range, e.g., when axis max is larger than `ordinalMeta.categories.length`., + // where ordinal numbers are used as tick value directly. + return ordinalNumbersByTick && tickNumber >= 0 && tickNumber < ordinalNumbersByTick.length ? ordinalNumbersByTick[tickNumber] : tickNumber; + }; + /** + * Get item on tick + */ + OrdinalScale.prototype.getLabel = function (tick) { + if (!this.isBlank()) { + var ordinalNumber = this.getRawOrdinalNumber(tick.value); + var cateogry = this._ordinalMeta.categories[ordinalNumber]; + // Note that if no data, ordinalMeta.categories is an empty array. + // Return empty if it's not exist. + return cateogry == null ? '' : cateogry + ''; + } + }; + OrdinalScale.prototype.count = function () { + return this._extent[1] - this._extent[0] + 1; + }; + OrdinalScale.prototype.unionExtentFromData = function (data, dim) { + this.unionExtent(data.getApproximateExtent(dim)); + }; + /** + * @override + * If value is in extent range + */ + OrdinalScale.prototype.isInExtentRange = function (value) { + value = this._getTickNumber(value); + return this._extent[0] <= value && this._extent[1] >= value; + }; + OrdinalScale.prototype.getOrdinalMeta = function () { + return this._ordinalMeta; + }; + OrdinalScale.prototype.calcNiceTicks = function () {}; + OrdinalScale.prototype.calcNiceExtent = function () {}; + OrdinalScale.type = 'ordinal'; + return OrdinalScale; +}(Scale); +Scale.registerClass(OrdinalScale); + +var roundNumber = round$1; +var IntervalScale = /** @class */function (_super) { + __extends(IntervalScale, _super); + function IntervalScale() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = 'interval'; + // Step is calculated in adjustExtent. + _this._interval = 0; + _this._intervalPrecision = 2; + return _this; + } + IntervalScale.prototype.parse = function (val) { + return val; + }; + IntervalScale.prototype.contain = function (val) { + return contain(val, this._extent); + }; + IntervalScale.prototype.normalize = function (val) { + return normalize(val, this._extent); + }; + IntervalScale.prototype.scale = function (val) { + return scale(val, this._extent); + }; + IntervalScale.prototype.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); + } + }; + IntervalScale.prototype.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 + this.setExtent(extent[0], extent[1]); + }; + IntervalScale.prototype.getInterval = function () { + return this._interval; + }; + IntervalScale.prototype.setInterval = function (interval) { + this._interval = interval; + // Dropped auto calculated niceExtent and use user-set extent. + // We assume user wants to set both interval, min, max to get a better result. + this._niceExtent = this._extent.slice(); + this._intervalPrecision = getIntervalPrecision(interval); + }; + /** + * @param expandToNicedExtent Whether expand the ticks to niced extent. + */ + IntervalScale.prototype.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({ + value: roundNumber(niceTickExtent[0] - interval, intervalPrecision) + }); + } else { + ticks.push({ + value: extent[0] + }); + } + } + var tick = niceTickExtent[0]; + while (tick <= niceTickExtent[1]) { + ticks.push({ + value: tick + }); + // Avoid rounding error + tick = roundNumber(tick + interval, intervalPrecision); + if (tick === ticks[ticks.length - 1].value) { + // 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].value : niceTickExtent[1]; + if (extent[1] > lastNiceTick) { + if (expandToNicedExtent) { + ticks.push({ + value: roundNumber(lastNiceTick + interval, intervalPrecision) + }); + } else { + ticks.push({ + value: extent[1] + }); + } + } + return ticks; + }; + IntervalScale.prototype.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.value - prevTick.value; + var minorInterval = interval / splitNumber; + while (count < splitNumber - 1) { + var minorTick = roundNumber(prevTick.value + (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 opt.precision If 'auto', use nice presision. + * @param opt.pad returns 1.50 but not 1.5 if precision is 2. + */ + IntervalScale.prototype.getLabel = function (data, opt) { + if (data == null) { + return ''; + } + var precision = opt && opt.precision; + if (precision == null) { + precision = getPrecision(data.value) || 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'. + var dataNum = roundNumber(data.value, precision, true); + return addCommas(dataNum); + }; + /** + * @param splitNumber By default `5`. + */ + IntervalScale.prototype.calcNiceTicks = 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; + }; + IntervalScale.prototype.calcNiceExtent = 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 + // Note that extents can be both negative. See #13154 + var expandSize = Math.abs(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.calcNiceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval); + // let 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); + } + }; + IntervalScale.prototype.setNiceExtent = function (min, max) { + this._niceExtent = [min, max]; + }; + IntervalScale.type = 'interval'; + return IntervalScale; +}(Scale); +Scale.registerClass(IntervalScale); + +/* global Float32Array */ +var supportFloat32Array = typeof Float32Array !== 'undefined'; +var Float32ArrayCtor = !supportFloat32Array ? Array : Float32Array; +function createFloat32Array(arg) { + if (isArray(arg)) { + // Return self directly if don't support TypedArray. + return supportFloat32Array ? new Float32Array(arg) : arg; + } + // Else is number + return new Float32ArrayCtor(arg); +} + +var STACK_PREFIX = '__ec_stack_'; +function getSeriesStackId(seriesModel) { + return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex; +} +function getAxisKey(axis) { + return axis.dim + axis.index; +} +function prepareLayoutBarSeries(seriesType, ecModel) { + var seriesModels = []; + ecModel.eachSeriesByType(seriesType, function (seriesModel) { + // Check series coordinate, do layout for cartesian2d only + if (isOnCartesian(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$4(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 dimIdx = data.getDimensionIndex(data.mapDimension(baseAxis.dim)); + var store = data.getStore(); + for (var i = 0, cnt = store.count(); i < cnt; ++i) { + var value = store.get(dimIdx, 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$4(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(seriesModel.get('barWidth'), bandWidth); + var barMaxWidth = parsePercent(seriesModel.get('barMaxWidth'), bandWidth); + var barMinWidth = parsePercent( + // barMinWidth by default is 0.5 / 1 in cartesian. Because in value axis, + // the auto-calculated bar width might be less than 0.5 / 1. + seriesModel.get('barMinWidth') || (isInLargeMode(seriesModel) ? 0.5 : 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$4(seriesInfoList, function (seriesInfo, idx) { + var axisKey = seriesInfo.axisKey; + var bandWidth = seriesInfo.bandWidth; + var columnsOnAxis = columnsMap[axisKey] || { + bandWidth: bandWidth, + remainedWidth: bandWidth, + autoWidthCount: 0, + categoryGap: null, + gap: '20%', + 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$4(columnsMap, function (columnsOnAxis, coordSysName) { + result[coordSysName] = {}; + var stacks = columnsOnAxis.stacks; + var bandWidth = columnsOnAxis.bandWidth; + var categoryGapPercent = columnsOnAxis.categoryGap; + if (categoryGapPercent == null) { + var columnCount = keys(stacks).length; + // More columns in one group + // the spaces between group is smaller. Or the column will be too thin. + categoryGapPercent = Math.max(35 - columnCount * 4, 15) + '%'; + } + var categoryGap = parsePercent(categoryGapPercent, bandWidth); + var barGapPercent = parsePercent(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$4(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 whether 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. Because 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$4(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$4(stacks, function (column, stackId) { + result[coordSysName][stackId] = result[coordSysName][stackId] || { + bandWidth: bandWidth, + offset: offset, + width: column.width + }; + offset += column.width * (1 + barGapPercent); + }); + }); + return result; +} +function retrieveColumnLayout(barWidthAndOffset, axis, seriesModel) { + if (barWidthAndOffset && axis) { + var result = barWidthAndOffset[getAxisKey(axis)]; + return result; + } +} +function isOnCartesian(seriesModel) { + return seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d'; +} +function isInLargeMode(seriesModel) { + return seriesModel.pipelineContext && seriesModel.pipelineContext.large; +} + +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; +}; +var TimeScale = ( + /** @class */ + function(_super) { + __extends(TimeScale2, _super); + function TimeScale2(settings) { + var _this = _super.call(this, settings) || this; + _this.type = "time"; + return _this; + } + TimeScale2.prototype.getLabel = function(tick) { + var useUTC = this.getSetting("useUTC"); + return format(tick.value, fullLeveledFormatter[getDefaultFormatPrecisionOfInterval(getPrimaryTimeUnit(this._minLevelUnit))] || fullLeveledFormatter.second, useUTC, this.getSetting("locale")); + }; + TimeScale2.prototype.getFormattedLabel = function(tick, idx, labelFormatter) { + var isUTC = this.getSetting("useUTC"); + var lang = this.getSetting("locale"); + return leveledFormat(tick, idx, labelFormatter, lang, isUTC); + }; + TimeScale2.prototype.getTicks = function() { + var interval = this._interval; + var extent = this._extent; + var ticks = []; + if (!interval) { + return ticks; + } + ticks.push({ + value: extent[0], + level: 0 + }); + var useUTC = this.getSetting("useUTC"); + var innerTicks = getIntervalTicks(this._minLevelUnit, this._approxInterval, useUTC, extent); + ticks = ticks.concat(innerTicks); + ticks.push({ + value: extent[1], + level: 0 + }); + return ticks; + }; + TimeScale2.prototype.calcNiceExtent = function(opt) { + var extent = this._extent; + if (extent[0] === extent[1]) { + extent[0] -= ONE_DAY; + extent[1] += ONE_DAY; + } + if (extent[1] === -Infinity && extent[0] === Infinity) { + var d = /* @__PURE__ */ new Date(); + extent[1] = +new Date(d.getFullYear(), d.getMonth(), d.getDate()); + extent[0] = extent[1] - ONE_DAY; + } + this.calcNiceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval); + }; + TimeScale2.prototype.calcNiceTicks = function(approxTickNum, minInterval, maxInterval) { + approxTickNum = approxTickNum || 10; + var extent = this._extent; + var span = extent[1] - extent[0]; + this._approxInterval = span / approxTickNum; + if (minInterval != null && this._approxInterval < minInterval) { + this._approxInterval = minInterval; + } + if (maxInterval != null && this._approxInterval > maxInterval) { + this._approxInterval = maxInterval; + } + var scaleIntervalsLen = scaleIntervals.length; + var idx = Math.min(bisect(scaleIntervals, this._approxInterval, 0, scaleIntervalsLen), scaleIntervalsLen - 1); + this._interval = scaleIntervals[idx][1]; + this._minLevelUnit = scaleIntervals[Math.max(idx - 1, 0)][0]; + }; + TimeScale2.prototype.parse = function(val) { + return isNumber(val) ? val : +parseDate(val); + }; + TimeScale2.prototype.contain = function(val) { + return contain(this.parse(val), this._extent); + }; + TimeScale2.prototype.normalize = function(val) { + return normalize(this.parse(val), this._extent); + }; + TimeScale2.prototype.scale = function(val) { + return scale(val, this._extent); + }; + TimeScale2.type = "time"; + return TimeScale2; + }(IntervalScale) +); +var scaleIntervals = [ + // Format interval + ["second", ONE_SECOND], + ["minute", ONE_MINUTE], + ["hour", ONE_HOUR], + ["quarter-day", ONE_HOUR * 6], + ["half-day", ONE_HOUR * 12], + ["day", ONE_DAY * 1.2], + ["half-week", ONE_DAY * 3.5], + ["week", ONE_DAY * 7], + ["month", ONE_DAY * 31], + ["quarter", ONE_DAY * 95], + ["half-year", ONE_YEAR / 2], + ["year", ONE_YEAR] + // 1Y +]; +function isUnitValueSame(unit, valueA, valueB, isUTC) { + var dateA = parseDate(valueA); + var dateB = parseDate(valueB); + var isSame = function(unit2) { + return getUnitValue(dateA, unit2, isUTC) === getUnitValue(dateB, unit2, isUTC); + }; + var isSameYear = function() { + return isSame("year"); + }; + var isSameMonth = function() { + return isSameYear() && isSame("month"); + }; + var isSameDay = function() { + return isSameMonth() && isSame("day"); + }; + var isSameHour = function() { + return isSameDay() && isSame("hour"); + }; + var isSameMinute = function() { + return isSameHour() && isSame("minute"); + }; + var isSameSecond = function() { + return isSameMinute() && isSame("second"); + }; + var isSameMilliSecond = function() { + return isSameSecond() && isSame("millisecond"); + }; + switch (unit) { + case "year": + return isSameYear(); + case "month": + return isSameMonth(); + case "day": + return isSameDay(); + case "hour": + return isSameHour(); + case "minute": + return isSameMinute(); + case "second": + return isSameSecond(); + case "millisecond": + return isSameMilliSecond(); + } +} +function getDateInterval(approxInterval, daysInMonth) { + approxInterval /= ONE_DAY; + return approxInterval > 16 ? 16 : approxInterval > 7.5 ? 7 : approxInterval > 3.5 ? 4 : approxInterval > 1.5 ? 2 : 1; +} +function getMonthInterval(approxInterval) { + var APPROX_ONE_MONTH = 30 * ONE_DAY; + approxInterval /= APPROX_ONE_MONTH; + return approxInterval > 6 ? 6 : approxInterval > 3 ? 3 : approxInterval > 2 ? 2 : 1; +} +function getHourInterval(approxInterval) { + approxInterval /= ONE_HOUR; + return approxInterval > 12 ? 12 : approxInterval > 6 ? 6 : approxInterval > 3.5 ? 4 : approxInterval > 2 ? 2 : 1; +} +function getMinutesAndSecondsInterval(approxInterval, isMinutes) { + approxInterval /= isMinutes ? ONE_MINUTE : ONE_SECOND; + return approxInterval > 30 ? 30 : approxInterval > 20 ? 20 : approxInterval > 15 ? 15 : approxInterval > 10 ? 10 : approxInterval > 5 ? 5 : approxInterval > 2 ? 2 : 1; +} +function getMillisecondsInterval(approxInterval) { + return nice(approxInterval); +} +function getFirstTimestampOfUnit(date, unitName, isUTC) { + var outDate = new Date(date); + switch (getPrimaryTimeUnit(unitName)) { + case "year": + case "month": + outDate[monthSetterName(isUTC)](0); + case "day": + outDate[dateSetterName(isUTC)](1); + case "hour": + outDate[hoursSetterName(isUTC)](0); + case "minute": + outDate[minutesSetterName(isUTC)](0); + case "second": + outDate[secondsSetterName(isUTC)](0); + outDate[millisecondsSetterName(isUTC)](0); + } + return outDate.getTime(); +} +function getIntervalTicks(bottomUnitName, approxInterval, isUTC, extent) { + var safeLimit = 1e4; + var unitNames = timeUnits; + var iter = 0; + function addTicksInSpan(interval, minTimestamp, maxTimestamp, getMethodName, setMethodName, isDate, out) { + var date = new Date(minTimestamp); + var dateTime = minTimestamp; + var d = date[getMethodName](); + while (dateTime < maxTimestamp && dateTime <= extent[1]) { + out.push({ + value: dateTime + }); + d += interval; + date[setMethodName](d); + dateTime = date.getTime(); + } + out.push({ + value: dateTime, + notAdd: true + }); + } + function addLevelTicks(unitName, lastLevelTicks, levelTicks2) { + var newAddedTicks = []; + var isFirstLevel = !lastLevelTicks.length; + if (isUnitValueSame(getPrimaryTimeUnit(unitName), extent[0], extent[1], isUTC)) { + return; + } + if (isFirstLevel) { + lastLevelTicks = [{ + // TODO Optimize. Not include so may ticks. + value: getFirstTimestampOfUnit(new Date(extent[0]), unitName, isUTC) + }, { + value: extent[1] + }]; + } + for (var i2 = 0; i2 < lastLevelTicks.length - 1; i2++) { + var startTick = lastLevelTicks[i2].value; + var endTick = lastLevelTicks[i2 + 1].value; + if (startTick === endTick) { + continue; + } + var interval = void 0; + var getterName = void 0; + var setterName = void 0; + var isDate = false; + switch (unitName) { + case "year": + interval = Math.max(1, Math.round(approxInterval / ONE_DAY / 365)); + getterName = fullYearGetterName(isUTC); + setterName = fullYearSetterName(isUTC); + break; + case "half-year": + case "quarter": + case "month": + interval = getMonthInterval(approxInterval); + getterName = monthGetterName(isUTC); + setterName = monthSetterName(isUTC); + break; + case "week": + case "half-week": + case "day": + interval = getDateInterval(approxInterval); + getterName = dateGetterName(isUTC); + setterName = dateSetterName(isUTC); + isDate = true; + break; + case "half-day": + case "quarter-day": + case "hour": + interval = getHourInterval(approxInterval); + getterName = hoursGetterName(isUTC); + setterName = hoursSetterName(isUTC); + break; + case "minute": + interval = getMinutesAndSecondsInterval(approxInterval, true); + getterName = minutesGetterName(isUTC); + setterName = minutesSetterName(isUTC); + break; + case "second": + interval = getMinutesAndSecondsInterval(approxInterval, false); + getterName = secondsGetterName(isUTC); + setterName = secondsSetterName(isUTC); + break; + case "millisecond": + interval = getMillisecondsInterval(approxInterval); + getterName = millisecondsGetterName(isUTC); + setterName = millisecondsSetterName(isUTC); + break; + } + addTicksInSpan(interval, startTick, endTick, getterName, setterName, isDate, newAddedTicks); + if (unitName === "year" && levelTicks2.length > 1 && i2 === 0) { + levelTicks2.unshift({ + value: levelTicks2[0].value - interval + }); + } + } + for (var i2 = 0; i2 < newAddedTicks.length; i2++) { + levelTicks2.push(newAddedTicks[i2]); + } + return newAddedTicks; + } + var levelsTicks = []; + var currentLevelTicks = []; + var tickCount = 0; + var lastLevelTickCount = 0; + for (var i = 0; i < unitNames.length && iter++ < safeLimit; ++i) { + var primaryTimeUnit = getPrimaryTimeUnit(unitNames[i]); + if (!isPrimaryTimeUnit(unitNames[i])) { + continue; + } + addLevelTicks(unitNames[i], levelsTicks[levelsTicks.length - 1] || [], currentLevelTicks); + var nextPrimaryTimeUnit = unitNames[i + 1] ? getPrimaryTimeUnit(unitNames[i + 1]) : null; + if (primaryTimeUnit !== nextPrimaryTimeUnit) { + if (currentLevelTicks.length) { + lastLevelTickCount = tickCount; + currentLevelTicks.sort(function(a, b) { + return a.value - b.value; + }); + var levelTicksRemoveDuplicated = []; + for (var i_1 = 0; i_1 < currentLevelTicks.length; ++i_1) { + var tickValue = currentLevelTicks[i_1].value; + if (i_1 === 0 || currentLevelTicks[i_1 - 1].value !== tickValue) { + levelTicksRemoveDuplicated.push(currentLevelTicks[i_1]); + if (tickValue >= extent[0] && tickValue <= extent[1]) { + tickCount++; + } + } + } + var targetTickNum = (extent[1] - extent[0]) / approxInterval; + if (tickCount > targetTickNum * 1.5 && lastLevelTickCount > targetTickNum / 1.5) { + break; + } + levelsTicks.push(levelTicksRemoveDuplicated); + if (tickCount > targetTickNum || bottomUnitName === unitNames[i]) { + break; + } + } + currentLevelTicks = []; + } + } + var levelsTicksInExtent = filter(map$1(levelsTicks, function(levelTicks2) { + return filter(levelTicks2, function(tick) { + return tick.value >= extent[0] && tick.value <= extent[1] && !tick.notAdd; + }); + }), function(levelTicks2) { + return levelTicks2.length > 0; + }); + var ticks = []; + var maxLevel = levelsTicksInExtent.length - 1; + for (var i = 0; i < levelsTicksInExtent.length; ++i) { + var levelTicks = levelsTicksInExtent[i]; + for (var k = 0; k < levelTicks.length; ++k) { + ticks.push({ + value: levelTicks[k].value, + level: maxLevel - i + }); + } + } + ticks.sort(function(a, b) { + return a.value - b.value; + }); + var result = []; + for (var i = 0; i < ticks.length; ++i) { + if (i === 0 || ticks[i].value !== ticks[i - 1].value) { + result.push(ticks[i]); + } + } + return result; +} +Scale.registerClass(TimeScale); + +var scaleProto = Scale.prototype; +// FIXME:TS refactor: not good to call it directly with `this`? +var intervalScaleProto = IntervalScale.prototype; +var roundingErrorFix = round$1; +var mathFloor = Math.floor; +var mathCeil = Math.ceil; +var mathPow = Math.pow; +var mathLog$1 = Math.log; +var LogScale = /** @class */function (_super) { + __extends(LogScale, _super); + function LogScale() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = 'log'; + _this.base = 10; + _this._originalScale = new IntervalScale(); + // FIXME:TS actually used by `IntervalScale` + _this._interval = 0; + return _this; + } + /** + * @param Whether expand the ticks to niced extent. + */ + LogScale.prototype.getTicks = function (expandToNicedExtent) { + var originalScale = this._originalScale; + var extent = this._extent; + var originalExtent = originalScale.getExtent(); + var ticks = intervalScaleProto.getTicks.call(this, expandToNicedExtent); + return map$1(ticks, function (tick) { + var val = tick.value; + var powVal = round$1(mathPow(this.base, val)); + // Fix #4158 + powVal = val === extent[0] && this._fixMin ? fixRoundingError(powVal, originalExtent[0]) : powVal; + powVal = val === extent[1] && this._fixMax ? fixRoundingError(powVal, originalExtent[1]) : powVal; + return { + value: powVal + }; + }, this); + }; + LogScale.prototype.setExtent = function (start, end) { + var base = mathLog$1(this.base); + // log(-Infinity) is NaN, so safe guard here + start = mathLog$1(Math.max(0, start)) / base; + end = mathLog$1(Math.max(0, end)) / base; + intervalScaleProto.setExtent.call(this, start, end); + }; + /** + * @return {number} end + */ + LogScale.prototype.getExtent = function () { + var base = this.base; + var extent = scaleProto.getExtent.call(this); + extent[0] = mathPow(base, extent[0]); + extent[1] = mathPow(base, extent[1]); + // Fix #4158 + var originalScale = this._originalScale; + var originalExtent = originalScale.getExtent(); + this._fixMin && (extent[0] = fixRoundingError(extent[0], originalExtent[0])); + this._fixMax && (extent[1] = fixRoundingError(extent[1], originalExtent[1])); + return extent; + }; + LogScale.prototype.unionExtent = function (extent) { + this._originalScale.unionExtent(extent); + var base = this.base; + extent[0] = mathLog$1(extent[0]) / mathLog$1(base); + extent[1] = mathLog$1(extent[1]) / mathLog$1(base); + scaleProto.unionExtent.call(this, extent); + }; + LogScale.prototype.unionExtentFromData = function (data, dim) { + // TODO + // filter value that <= 0 + this.unionExtent(data.getApproximateExtent(dim)); + }; + /** + * Update interval and extent of intervals for nice ticks + * @param approxTickNum default 10 Given approx tick number + */ + LogScale.prototype.calcNiceTicks = 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(extent[0] / interval) * interval), round$1(mathFloor(extent[1] / interval) * interval)]; + this._interval = interval; + this._niceExtent = niceExtent; + }; + LogScale.prototype.calcNiceExtent = function (opt) { + intervalScaleProto.calcNiceExtent.call(this, opt); + this._fixMin = opt.fixMin; + this._fixMax = opt.fixMax; + }; + LogScale.prototype.parse = function (val) { + return val; + }; + LogScale.prototype.contain = function (val) { + val = mathLog$1(val) / mathLog$1(this.base); + return contain(val, this._extent); + }; + LogScale.prototype.normalize = function (val) { + val = mathLog$1(val) / mathLog$1(this.base); + return normalize(val, this._extent); + }; + LogScale.prototype.scale = function (val) { + val = scale(val, this._extent); + return mathPow(this.base, val); + }; + LogScale.type = 'log'; + return LogScale; +}(Scale); +var proto = LogScale.prototype; +proto.getMinorTicks = intervalScaleProto.getMinorTicks; +proto.getLabel = intervalScaleProto.getLabel; +function fixRoundingError(val, originalVal) { + return roundingErrorFix(val, getPrecision(originalVal)); +} +Scale.registerClass(LogScale); + +var ScaleRawExtentInfo = ( + /** @class */ + function() { + function ScaleRawExtentInfo2(scale, model, originalExtent) { + this._prepareParams(scale, model, originalExtent); + } + ScaleRawExtentInfo2.prototype._prepareParams = function(scale, model, dataExtent) { + if (dataExtent[1] < dataExtent[0]) { + dataExtent = [NaN, NaN]; + } + this._dataMin = dataExtent[0]; + this._dataMax = dataExtent[1]; + var isOrdinal = this._isOrdinal = scale.type === "ordinal"; + this._needCrossZero = scale.type === "interval" && model.getNeedCrossZero && model.getNeedCrossZero(); + var axisMinValue = model.get("min", true); + if (axisMinValue == null) { + axisMinValue = model.get("startValue", true); + } + var modelMinRaw = this._modelMinRaw = axisMinValue; + if (isFunction(modelMinRaw)) { + this._modelMinNum = parseAxisModelMinMax(scale, modelMinRaw({ + min: dataExtent[0], + max: dataExtent[1] + })); + } else if (modelMinRaw !== "dataMin") { + this._modelMinNum = parseAxisModelMinMax(scale, modelMinRaw); + } + var modelMaxRaw = this._modelMaxRaw = model.get("max", true); + if (isFunction(modelMaxRaw)) { + this._modelMaxNum = parseAxisModelMinMax(scale, modelMaxRaw({ + min: dataExtent[0], + max: dataExtent[1] + })); + } else if (modelMaxRaw !== "dataMax") { + this._modelMaxNum = parseAxisModelMinMax(scale, modelMaxRaw); + } + if (isOrdinal) { + this._axisDataLen = model.getCategories().length; + } else { + var boundaryGap = model.get("boundaryGap"); + var boundaryGapArr = isArray(boundaryGap) ? boundaryGap : [boundaryGap || 0, boundaryGap || 0]; + if (typeof boundaryGapArr[0] === "boolean" || typeof boundaryGapArr[1] === "boolean") { + this._boundaryGapInner = [0, 0]; + } else { + this._boundaryGapInner = [parsePercent$1(boundaryGapArr[0], 1), parsePercent$1(boundaryGapArr[1], 1)]; + } + } + }; + ScaleRawExtentInfo2.prototype.calculate = function() { + var isOrdinal = this._isOrdinal; + var dataMin = this._dataMin; + var dataMax = this._dataMax; + var axisDataLen = this._axisDataLen; + var boundaryGapInner = this._boundaryGapInner; + var span = !isOrdinal ? dataMax - dataMin || Math.abs(dataMin) : null; + var min = this._modelMinRaw === "dataMin" ? dataMin : this._modelMinNum; + var max = this._modelMaxRaw === "dataMax" ? dataMax : this._modelMaxNum; + var minFixed = min != null; + var maxFixed = max != null; + if (min == null) { + min = isOrdinal ? axisDataLen ? 0 : NaN : dataMin - boundaryGapInner[0] * span; + } + if (max == null) { + max = isOrdinal ? axisDataLen ? axisDataLen - 1 : NaN : dataMax + boundaryGapInner[1] * span; + } + (min == null || !isFinite(min)) && (min = NaN); + (max == null || !isFinite(max)) && (max = NaN); + var isBlank = eqNaN(min) || eqNaN(max) || isOrdinal && !axisDataLen; + if (this._needCrossZero) { + if (min > 0 && max > 0 && !minFixed) { + min = 0; + } + if (min < 0 && max < 0 && !maxFixed) { + max = 0; + } + } + var determinedMin = this._determinedMin; + var determinedMax = this._determinedMax; + if (determinedMin != null) { + min = determinedMin; + minFixed = true; + } + if (determinedMax != null) { + max = determinedMax; + maxFixed = true; + } + return { + min, + max, + minFixed, + maxFixed, + isBlank + }; + }; + ScaleRawExtentInfo2.prototype.modifyDataMinMax = function(minMaxName, val) { + this[DATA_MIN_MAX_ATTR[minMaxName]] = val; + }; + ScaleRawExtentInfo2.prototype.setDeterminedMinMax = function(minMaxName, val) { + var attr = DETERMINED_MIN_MAX_ATTR[minMaxName]; + this[attr] = val; + }; + ScaleRawExtentInfo2.prototype.freeze = function() { + this.frozen = true; + }; + return ScaleRawExtentInfo2; + }() +); +var DETERMINED_MIN_MAX_ATTR = { + min: "_determinedMin", + max: "_determinedMax" +}; +var DATA_MIN_MAX_ATTR = { + min: "_dataMin", + max: "_dataMax" +}; +function ensureScaleRawExtentInfo(scale, model, originalExtent) { + var rawExtentInfo = scale.rawExtentInfo; + if (rawExtentInfo) { + return rawExtentInfo; + } + rawExtentInfo = new ScaleRawExtentInfo(scale, model, originalExtent); + scale.rawExtentInfo = rawExtentInfo; + return rawExtentInfo; +} +function parseAxisModelMinMax(scale, minMax) { + return minMax == null ? null : eqNaN(minMax) ? NaN : scale.parse(minMax); +} + +/** + * Get axis scale extent before niced. + * Item of returned array can only be number (including Infinity and NaN). + * + * Caution: + * Precondition of calling this method: + * The scale extent has been initialized using series data extent via + * `scale.setExtent` or `scale.unionExtentFromData`; + */ +function getScaleExtent(scale, model) { + var scaleType = scale.type; + var rawExtentResult = ensureScaleRawExtentInfo(scale, model, scale.getExtent()).calculate(); + scale.setBlank(rawExtentResult.isBlank); + var min = rawExtentResult.min; + var max = rawExtentResult.max; + // 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_1 = false; + each$4(barSeriesModels, function (seriesModel) { + isBaseAxisAndHasBarSeries_1 = isBaseAxisAndHasBarSeries_1 || seriesModel.getBaseAxis() === model.axis; + }); + if (isBaseAxisAndHasBarSeries_1) { + // Calculate placement of bars on axis. TODO should be decoupled + // with barLayout + 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 { + extent: [min, max], + // "fix" means "fixed", the value should not be + // changed in the subsequent steps. + fixMin: rawExtentResult.minFixed, + fixMax: rawExtentResult.maxFixed + }; +} +function adjustScaleForOverflow(min, max, model, +// Only support cartesian coord yet. +barWidthAndOffset) { + // Get Axis Length + var axisExtent = model.axis.getExtent(); + var axisLength = Math.abs(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$4(barsOnCurrentAxis, function (item) { + minOverflow = Math.min(item.offset, minOverflow); + }); + var maxOverflow = -Infinity; + each$4(barsOnCurrentAxis, function (item) { + maxOverflow = Math.max(item.offset + item.width, maxOverflow); + }); + minOverflow = Math.abs(minOverflow); + maxOverflow = Math.abs(maxOverflow); + var totalOverFlow = minOverflow + maxOverflow; + // Calculate 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 + }; +} +// Precondition of calling this method: +// The scale extent has been initialized using series data extent via +// `scale.setExtent` or `scale.unionExtentFromData`; +function niceScaleExtent(scale, inModel) { + var model = inModel; + var extentInfo = getScaleExtent(scale, model); + var extent = extentInfo.extent; + var splitNumber = model.get('splitNumber'); + if (scale instanceof LogScale) { + scale.base = model.get('logBase'); + } + var scaleType = scale.type; + var interval = model.get('interval'); + var isIntervalOrTime = scaleType === 'interval' || scaleType === 'time'; + scale.setExtent(extent[0], extent[1]); + scale.calcNiceExtent({ + splitNumber: splitNumber, + fixMin: extentInfo.fixMin, + fixMax: extentInfo.fixMax, + minInterval: isIntervalOrTime ? model.get('minInterval') : null, + maxInterval: isIntervalOrTime ? 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 + if (interval != null) { + scale.setInterval && scale.setInterval(interval); + } +} +/** + * @param axisType Default retrieve from model.type + */ +function createScaleByModel(model, axisType) { + axisType = axisType || model.get('type'); + if (axisType) { + switch (axisType) { + // Buildin scale + case 'category': + return new OrdinalScale({ + ordinalMeta: model.getOrdinalMeta ? model.getOrdinalMeta() : model.getCategories(), + extent: [Infinity, -Infinity] + }); + case 'time': + return new TimeScale({ + locale: model.ecModel.getLocaleModel(), + useUTC: model.ecModel.get('useUTC') + }); + default: + // case 'value'/'interval', 'log', or others. + return new (Scale.getClass(axisType) || IntervalScale)(); + } + } +} +/** + * Check if the axis cross 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 axis + * @return Label formatter function. + * param: {number} tickValue, + * param: {number} idx, the index in all ticks. + * If category axis, this param is not required. + * return: {string} label string. + */ +function makeLabelFormatter(axis) { + var labelFormatter = axis.getLabelModel().get('formatter'); + var categoryTickStart = axis.type === 'category' ? axis.scale.getExtent()[0] : null; + if (axis.scale.type === 'time') { + return function (tpl) { + return function (tick, idx) { + return axis.scale.getFormattedLabel(tick, idx, tpl); + }; + }(labelFormatter); + } else if (isString(labelFormatter)) { + return function (tpl) { + return function (tick) { + // For category axis, get raw value; for numeric axis, + // get formatted label like '1,333,444'. + var label = axis.scale.getLabel(tick); + var text = tpl.replace('{value}', label != null ? label : ''); + return text; + }; + }(labelFormatter); + } else if (isFunction(labelFormatter)) { + return function (cb) { + return function (tick, 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 = tick.value - categoryTickStart; + } + return cb(getAxisRawValue(axis, tick), idx, tick.level != null ? { + level: tick.level + } : null); + }; + }(labelFormatter); + } else { + return function (tick) { + return axis.scale.getLabel(tick); + }; + } +} +function getAxisRawValue(axis, tick) { + // 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(tick) : tick.value; +} +/** + * @param axis + * @return 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 realNumberScaleTicks; + var tickCount; + var categoryScaleExtent = scale.getExtent(); + // Optimize for large category data, avoid call `getTicks()`. + if (scale instanceof OrdinalScale) { + 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 tick = realNumberScaleTicks ? realNumberScaleTicks[i] : { + value: categoryScaleExtent[0] + i + }; + var label = labelFormatter(tick, i); + 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 beforeWidth = textRect.width; + var beforeHeight = textRect.height; + var afterWidth = beforeWidth * Math.abs(Math.cos(rotateRadians)) + Math.abs(beforeHeight * Math.sin(rotateRadians)); + var afterHeight = beforeWidth * Math.abs(Math.sin(rotateRadians)) + Math.abs(beforeHeight * Math.cos(rotateRadians)); + var rotatedRect = new BoundingRect(textRect.x, textRect.y, afterWidth, afterHeight); + return rotatedRect; +} +/** + * @param 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 regardless of overlap. + * @param {Object} axis axisModel.axis + */ +function shouldShowAllLabels(axis) { + return axis.type === 'category' && getOptionCategoryInterval(axis.getLabelModel()) === 0; +} +function getDataDimensionsOnAxis(data, axisDim) { + // Remove duplicated dat dimensions caused by `getStackedDimension`. + var dataDimMap = {}; + // Currently `mapDimensionsAll` will contain stack result dimension ('__\0ecstackresult'). + // PENDING: is it reasonable? Do we need to remove the original dim from "coord dim" since + // there has been stacked result dim? + each$4(data.mapDimensionsAll(axisDim), function (dataDim) { + // For example, the extent of the original dimension + // is [0.1, 0.5], the extent of the `stackResultDimension` + // is [7, 9], the final extent should NOT include [0.1, 0.5], + // because there is no graphic corresponding to [0.1, 0.5]. + // See the case in `test/area-stack.html` `main1`, where area line + // stack needs `yAxis` not start from 0. + dataDimMap[getStackedDimension(data, dataDim)] = true; + }); + return keys(dataDimMap); +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var AxisModelCommonMixin = /** @class */function () { + function AxisModelCommonMixin() {} + AxisModelCommonMixin.prototype.getNeedCrossZero = function () { + var option = this.option; + return !option.scale; + }; + /** + * Should be implemented by each axis model if necessary. + * @return coordinate system model + */ + AxisModelCommonMixin.prototype.getCoordSysModel = function () { + return; + }; + return AxisModelCommonMixin; +}(); + +var extensions = []; +var extensionRegisters = { + registerPreprocessor: registerPreprocessor, + registerProcessor: registerProcessor, + registerPostInit: registerPostInit, + registerPostUpdate: registerPostUpdate, + registerUpdateLifecycle: registerUpdateLifecycle, + registerAction: registerAction, + registerCoordinateSystem: registerCoordinateSystem, + registerLayout: registerLayout, + registerVisual: registerVisual, + registerTransform: registerTransform, + registerLoading: registerLoading, + registerMap: registerMap, + registerImpl: registerImpl, + PRIORITY: PRIORITY, + ComponentModel: ComponentModel, + ComponentView: ComponentView, + SeriesModel: SeriesModel, + ChartView: ChartView, + // TODO Use ComponentModel and SeriesModel instead of Constructor + registerComponentModel: function (ComponentModelClass) { + ComponentModel.registerClass(ComponentModelClass); + }, + registerComponentView: function (ComponentViewClass) { + ComponentView.registerClass(ComponentViewClass); + }, + registerSeriesModel: function (SeriesModelClass) { + SeriesModel.registerClass(SeriesModelClass); + }, + registerChartView: function (ChartViewClass) { + ChartView.registerClass(ChartViewClass); + }, + registerSubTypeDefaulter: function (componentType, defaulter) { + ComponentModel.registerSubTypeDefaulter(componentType, defaulter); + }, + registerPainter: function (painterType, PainterCtor) { + registerPainter(painterType, PainterCtor); + } +}; +function use(ext) { + if (isArray(ext)) { + // use([ChartLine, ChartBar]); + each$4(ext, function (singleExt) { + use(singleExt); + }); + return; + } + if (indexOf(extensions, ext) >= 0) { + return; + } + extensions.push(ext); + if (isFunction(ext)) { + ext = { + install: ext + }; + } + ext.install(extensionRegisters); +} + +var inner$4 = makeInner(); +function tickValuesToNumbers(axis, values) { + var nums = map$1(values, function (val) { + return axis.scale.parse(val); + }); + if (axis.type === 'time' && nums.length > 0) { + // Time axis needs duplicate first/last tick (see TimeScale.getTicks()) + // The first and last tick/label don't get drawn + nums.sort(); + nums.unshift(nums[0]); + nums.push(nums[nums.length - 1]); + } + return nums; +} +function createAxisLabels(axis) { + var custom = axis.getLabelModel().get('customValues'); + if (custom) { + var labelFormatter_1 = makeLabelFormatter(axis); + var extent_1 = axis.scale.getExtent(); + var tickNumbers = tickValuesToNumbers(axis, custom); + var ticks = filter(tickNumbers, function (val) { + return val >= extent_1[0] && val <= extent_1[1]; + }); + return { + labels: map$1(ticks, function (numval) { + var tick = { + value: numval + }; + return { + formattedLabel: labelFormatter_1(tick), + rawLabel: axis.scale.getLabel(tick), + tickValue: numval + }; + }) + }; + } + // 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) { + var custom = axis.getTickModel().get('customValues'); + if (custom) { + var extent_2 = axis.scale.getExtent(); + var tickNumbers = tickValuesToNumbers(axis, custom); + return { + ticks: filter(tickNumbers, function (val) { + return val >= extent_2[0] && val <= extent_2[1]; + }) + }; + } + // Only ordinal scale support tick interval + return axis.type === 'category' ? makeCategoryTicks(axis, tickModel) : { + ticks: map$1(axis.scale.getTicks(), function (tick) { + return tick.value; + }) + }; +} +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(optionLabelInterval)) { + labels = makeLabelsByCustomizedCategoryInterval(axis, optionLabelInterval); + } else { + numericLabelInterval = optionLabelInterval === 'auto' ? makeAutoCategoryInterval(axis) : optionLabelInterval; + labels = makeLabelsByNumericCategoryInterval(axis, numericLabelInterval); + } + // Cache to avoid calling interval function repeatedly. + 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(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$1(labelsResult.labels, function (labelItem) { + return labelItem.tickValue; + }); + } else { + tickCategoryInterval = optionTickInterval; + ticks = makeLabelsByNumericCategoryInterval(axis, tickCategoryInterval, true); + } + // Cache to avoid calling interval function repeatedly. + return listCacheSet(ticksCache, optionTickInterval, { + ticks: ticks, + tickCategoryInterval: tickCategoryInterval + }); +} +function makeRealNumberLabels(axis) { + var ticks = axis.scale.getTicks(); + var labelFormatter = makeLabelFormatter(axis); + return { + labels: map$1(ticks, function (tick, idx) { + return { + level: tick.level, + formattedLabel: labelFormatter(tick, idx), + rawLabel: axis.scale.getLabel(tick), + tickValue: tick.value + }; + }) + }; +} +function getListCache(axis, prop) { + // Because key can be a function, and cache size always is small, we use array cache. + return inner$4(axis)[prop] || (inner$4(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$4(axis).autoInterval; + return result != null ? result : inner$4(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({ + value: 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$4(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 hidden labels might not be shown again. + && cache.axisExtent0 === axisExtent[0] && cache.axisExtent1 === 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.axisExtent0 = axisExtent[0]; + cache.axisExtent1 = 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(tickValue) { + var tickObj = { + value: tickValue + }; + result.push(onlyTick ? tickValue : { + formattedLabel: labelFormatter(tickObj), + rawLabel: ordinalScale.getLabel(tickObj), + tickValue: tickValue + }); + } + return result; +} +function makeLabelsByCustomizedCategoryInterval(axis, categoryInterval, onlyTick) { + var ordinalScale = axis.scale; + var labelFormatter = makeLabelFormatter(axis); + var result = []; + each$4(ordinalScale.getTicks(), function (tick) { + var rawLabel = ordinalScale.getLabel(tick); + var tickValue = tick.value; + if (categoryInterval(tick.value, rawLabel)) { + result.push(onlyTick ? tickValue : { + formattedLabel: labelFormatter(tick), + rawLabel: rawLabel, + tickValue: tickValue + }); + } + }); + return result; +} + +var NORMALIZED_EXTENT = [0, 1]; +/** + * Base class of Axis. + */ +var Axis = /** @class */function () { + function Axis(dim, scale, extent) { + this.onBand = false; + this.inverse = false; + this.dim = dim; + this.scale = scale; + this._extent = extent || [0, 0]; + } + /** + * If axis extent contain given coord + */ + Axis.prototype.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 + */ + Axis.prototype.containData = function (data) { + return this.scale.contain(data); + }; + /** + * Get coord extent. + */ + Axis.prototype.getExtent = function () { + return this._extent.slice(); + }; + /** + * Get precision used for formatting + */ + Axis.prototype.getPixelPrecision = function (dataExtent) { + return getPixelPrecision(dataExtent || this.scale.getExtent(), this._extent); + }; + /** + * Set coord extent + */ + Axis.prototype.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 + */ + Axis.prototype.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 + */ + Axis.prototype.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 + */ + Axis.prototype.pointToData = function (point, clamp) { + // Should be implemented in derived class if necessary. + return; + }; + /** + * 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 opt.tickModel default: axis.model.getModel('axisTick') + * @param 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. + */ + Axis.prototype.getTicksCoords = function (opt) { + opt = opt || {}; + var tickModel = opt.tickModel || this.getTickModel(); + var result = createAxisTicks(this, tickModel); + var ticks = result.ticks; + var ticksCoords = map$1(ticks, function (tickVal) { + return { + coord: this.dataToCoord(this.scale.type === 'ordinal' ? this.scale.getRawOrdinalNumber(tickVal) : tickVal), + tickValue: tickVal + }; + }, this); + var alignWithLabel = tickModel.get('alignWithLabel'); + fixOnBandTicksCoords(this, ticksCoords, alignWithLabel, opt.clamp); + return ticksCoords; + }; + Axis.prototype.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$1(minorTicks, function (minorTicksGroup) { + return map$1(minorTicksGroup, function (minorTick) { + return { + coord: this.dataToCoord(minorTick), + tickValue: minorTick + }; + }, this); + }, this); + return minorTicksCoords; + }; + Axis.prototype.getViewLabels = function () { + return createAxisLabels(this).labels; + }; + Axis.prototype.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 overridden to: + * `axisModel.getModel('axisTick', grid3DModel.getModel('axisTick'));` + */ + Axis.prototype.getTickModel = function () { + return this.model.getModel('axisTick'); + }; + /** + * Get width of band + */ + Axis.prototype.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; + }; + /** + * Only be called in category axis. + * Can be overridden, consider other axes like in 3D. + * @return Auto interval for cateogry axis tick and label + */ + Axis.prototype.calculateCategoryInterval = function () { + return calculateCategoryInterval(this); + }; + return Axis; +}(); +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[1], + tickValue: ticksCoords[0].tickValue + }; + } else { + var crossLen = ticksCoords[ticksLen - 1].tickValue - ticksCoords[0].tickValue; + var shift_1 = (ticksCoords[ticksLen - 1].coord - ticksCoords[0].coord) / crossLen; + each$4(ticksCoords, function (ticksItem) { + ticksItem.coord -= shift_1 / 2; + }); + var dataExtent = axis.scale.getExtent(); + diffSize = 1 + dataExtent[1] - ticksCoords[ticksLen - 1].tickValue; + last = { + coord: ticksCoords[ticksLen - 1].coord + shift_1 * diffSize, + tickValue: dataExtent[1] + 1 + }; + 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 unnecessary tick added. + a = round$1(a); + b = round$1(b); + return inverse ? a > b : a < b; + } +} + +function projectPointToLine(x1, y1, x2, y2, x, y, out, limitToEnds) { + var dx = x - x1; + var dy = y - y1; + var dx1 = x2 - x1; + var dy1 = y2 - y1; + var lineLen = Math.sqrt(dx1 * dx1 + dy1 * dy1); + dx1 /= lineLen; + dy1 /= lineLen; + // dot product + var projectedLen = dx * dx1 + dy * dy1; + var t = projectedLen / lineLen; + t *= lineLen; + var ox = out[0] = x1 + t * dx1; + var oy = out[1] = y1 + t * dy1; + return Math.sqrt((ox - x) * (ox - x) + (oy - y) * (oy - y)); +} +// Temporal variable for intermediate usage. +var pt0 = new Point(); +var pt1 = new Point(); +var pt2 = new Point(); +var dir = new Point(); +var dir2 = new Point(); +// Temporal variable for the limitTurnAngle function +var tmpArr = []; +var tmpProjPoint = new Point(); +/** + * Reduce the line segment attached to the label to limit the turn angle between two segments. + * @param linePoints + * @param minTurnAngle Radian of minimum turn angle. 0 - 180 + */ +function limitTurnAngle(linePoints, minTurnAngle) { + if (!(minTurnAngle <= 180 && minTurnAngle > 0)) { + return; + } + minTurnAngle = minTurnAngle / 180 * Math.PI; + // The line points can be + // /pt1----pt2 (label) + // / + // pt0/ + pt0.fromArray(linePoints[0]); + pt1.fromArray(linePoints[1]); + pt2.fromArray(linePoints[2]); + Point.sub(dir, pt0, pt1); + Point.sub(dir2, pt2, pt1); + var len1 = dir.len(); + var len2 = dir2.len(); + if (len1 < 1e-3 || len2 < 1e-3) { + return; + } + dir.scale(1 / len1); + dir2.scale(1 / len2); + var angleCos = dir.dot(dir2); + var minTurnAngleCos = Math.cos(minTurnAngle); + if (minTurnAngleCos < angleCos) { + // Smaller than minTurnAngle + // Calculate project point of pt0 on pt1-pt2 + var d = projectPointToLine(pt1.x, pt1.y, pt2.x, pt2.y, pt0.x, pt0.y, tmpArr); + tmpProjPoint.fromArray(tmpArr); + // Calculate new projected length with limited minTurnAngle and get the new connect point + tmpProjPoint.scaleAndAdd(dir2, d / Math.tan(Math.PI - minTurnAngle)); + // Limit the new calculated connect point between pt1 and pt2. + var t = pt2.x !== pt1.x ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x) : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y); + if (isNaN(t)) { + return; + } + if (t < 0) { + Point.copy(tmpProjPoint, pt1); + } else if (t > 1) { + Point.copy(tmpProjPoint, pt2); + } + tmpProjPoint.toArray(linePoints[1]); + } +} +/** + * Limit the angle of line and the surface + * @param maxSurfaceAngle Radian of minimum turn angle. 0 - 180. 0 is same direction to normal. 180 is opposite + */ +function limitSurfaceAngle(linePoints, surfaceNormal, maxSurfaceAngle) { + if (!(maxSurfaceAngle <= 180 && maxSurfaceAngle > 0)) { + return; + } + maxSurfaceAngle = maxSurfaceAngle / 180 * Math.PI; + pt0.fromArray(linePoints[0]); + pt1.fromArray(linePoints[1]); + pt2.fromArray(linePoints[2]); + Point.sub(dir, pt1, pt0); + Point.sub(dir2, pt2, pt1); + var len1 = dir.len(); + var len2 = dir2.len(); + if (len1 < 1e-3 || len2 < 1e-3) { + return; + } + dir.scale(1 / len1); + dir2.scale(1 / len2); + var angleCos = dir.dot(surfaceNormal); + var maxSurfaceAngleCos = Math.cos(maxSurfaceAngle); + if (angleCos < maxSurfaceAngleCos) { + // Calculate project point of pt0 on pt1-pt2 + var d = projectPointToLine(pt1.x, pt1.y, pt2.x, pt2.y, pt0.x, pt0.y, tmpArr); + tmpProjPoint.fromArray(tmpArr); + var HALF_PI = Math.PI / 2; + var angle2 = Math.acos(dir2.dot(surfaceNormal)); + var newAngle = HALF_PI + angle2 - maxSurfaceAngle; + if (newAngle >= HALF_PI) { + // parallel + Point.copy(tmpProjPoint, pt2); + } else { + // Calculate new projected length with limited minTurnAngle and get the new connect point + tmpProjPoint.scaleAndAdd(dir2, d / Math.tan(Math.PI / 2 - newAngle)); + // Limit the new calculated connect point between pt1 and pt2. + var t = pt2.x !== pt1.x ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x) : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y); + if (isNaN(t)) { + return; + } + if (t < 0) { + Point.copy(tmpProjPoint, pt1); + } else if (t > 1) { + Point.copy(tmpProjPoint, pt2); + } + } + tmpProjPoint.toArray(linePoints[1]); + } +} +function setLabelLineState(labelLine, ignore, stateName, stateModel) { + var isNormal = stateName === 'normal'; + var stateObj = isNormal ? labelLine : labelLine.ensureState(stateName); + // Make sure display. + stateObj.ignore = ignore; + // Set smooth + var smooth = stateModel.get('smooth'); + if (smooth && smooth === true) { + smooth = 0.3; + } + stateObj.shape = stateObj.shape || {}; + if (smooth > 0) { + stateObj.shape.smooth = smooth; + } + var styleObj = stateModel.getModel('lineStyle').getLineStyle(); + isNormal ? labelLine.useStyle(styleObj) : stateObj.style = styleObj; +} +function buildLabelLinePath(path, shape) { + var smooth = shape.smooth; + var points = shape.points; + if (!points) { + return; + } + path.moveTo(points[0][0], points[0][1]); + if (smooth > 0 && points.length >= 3) { + var len1 = dist$1(points[0], points[1]); + var len2 = dist$1(points[1], points[2]); + if (!len1 || !len2) { + path.lineTo(points[1][0], points[1][1]); + path.lineTo(points[2][0], points[2][1]); + return; + } + var moveLen = Math.min(len1, len2) * smooth; + var midPoint0 = lerp$1([], points[1], points[0], moveLen / len1); + var midPoint2 = lerp$1([], points[1], points[2], moveLen / len2); + var midPoint1 = lerp$1([], midPoint0, midPoint2, 0.5); + path.bezierCurveTo(midPoint0[0], midPoint0[1], midPoint0[0], midPoint0[1], midPoint1[0], midPoint1[1]); + path.bezierCurveTo(midPoint2[0], midPoint2[1], midPoint2[0], midPoint2[1], points[2][0], points[2][1]); + } else { + for (var i = 1; i < points.length; i++) { + path.lineTo(points[i][0], points[i][1]); + } + } +} +/** + * Create a label line if necessary and set it's style. + */ +function setLabelLineStyle(targetEl, statesModels, defaultStyle) { + var labelLine = targetEl.getTextGuideLine(); + var label = targetEl.getTextContent(); + if (!label) { + // Not show label line if there is no label. + if (labelLine) { + targetEl.removeTextGuideLine(); + } + return; + } + var normalModel = statesModels.normal; + var showNormal = normalModel.get('show'); + var labelIgnoreNormal = label.ignore; + for (var i = 0; i < DISPLAY_STATES.length; i++) { + var stateName = DISPLAY_STATES[i]; + var stateModel = statesModels[stateName]; + var isNormal = stateName === 'normal'; + if (stateModel) { + var stateShow = stateModel.get('show'); + var isLabelIgnored = isNormal ? labelIgnoreNormal : retrieve2(label.states[stateName] && label.states[stateName].ignore, labelIgnoreNormal); + if (isLabelIgnored // Not show when label is not shown in this state. + || !retrieve2(stateShow, showNormal) // Use normal state by default if not set. + ) { + var stateObj = isNormal ? labelLine : labelLine && labelLine.states[stateName]; + if (stateObj) { + stateObj.ignore = true; + } + if (!!labelLine) { + setLabelLineState(labelLine, true, stateName, stateModel); + } + continue; + } + // Create labelLine if not exists + if (!labelLine) { + labelLine = new Polyline(); + targetEl.setTextGuideLine(labelLine); + // Reset state of normal because it's new created. + // NOTE: NORMAL should always been the first! + if (!isNormal && (labelIgnoreNormal || !showNormal)) { + setLabelLineState(labelLine, true, 'normal', statesModels.normal); + } + // Use same state proxy. + if (targetEl.stateProxy) { + labelLine.stateProxy = targetEl.stateProxy; + } + } + setLabelLineState(labelLine, false, stateName, stateModel); + } + } + if (labelLine) { + defaults(labelLine.style, defaultStyle); + // Not fill. + labelLine.style.fill = null; + var showAbove = normalModel.get('showAbove'); + var labelLineConfig = targetEl.textGuideLineConfig = targetEl.textGuideLineConfig || {}; + labelLineConfig.showAbove = showAbove || false; + // Custom the buildPath. + labelLine.buildPath = buildLabelLinePath; + } +} +function getLabelLineStatesModels(itemModel, labelLineName) { + labelLineName = labelLineName || 'labelLine'; + var statesModels = { + normal: itemModel.getModel(labelLineName) + }; + for (var i = 0; i < SPECIAL_STATES.length; i++) { + var stateName = SPECIAL_STATES[i]; + statesModels[stateName] = itemModel.getModel([stateName, labelLineName]); + } + return statesModels; +} + +function prepareLayoutList(input) { + var list = []; + for (var i = 0; i < input.length; i++) { + var rawItem = input[i]; + if (rawItem.defaultAttr.ignore) { + continue; + } + var label = rawItem.label; + var transform = label.getComputedTransform(); + // NOTE: Get bounding rect after getComputedTransform, or label may not been updated by the host el. + var localRect = label.getBoundingRect(); + var isAxisAligned = !transform || transform[1] < 1e-5 && transform[2] < 1e-5; + var minMargin = label.style.margin || 0; + var globalRect = localRect.clone(); + globalRect.applyTransform(transform); + globalRect.x -= minMargin / 2; + globalRect.y -= minMargin / 2; + globalRect.width += minMargin; + globalRect.height += minMargin; + var obb = isAxisAligned ? new OrientedBoundingRect(localRect, transform) : null; + list.push({ + label: label, + labelLine: rawItem.labelLine, + rect: globalRect, + localRect: localRect, + obb: obb, + priority: rawItem.priority, + defaultAttr: rawItem.defaultAttr, + layoutOption: rawItem.computedLayoutOption, + axisAligned: isAxisAligned, + transform: transform + }); + } + return list; +} +function shiftLayout(list, xyDim, sizeDim, minBound, maxBound, balanceShift) { + var len = list.length; + if (len < 2) { + return; + } + list.sort(function (a, b) { + return a.rect[xyDim] - b.rect[xyDim]; + }); + var lastPos = 0; + var delta; + var adjusted = false; + for (var i = 0; i < len; i++) { + var item = list[i]; + var rect = item.rect; + delta = rect[xyDim] - lastPos; + if (delta < 0) { + // shiftForward(i, len, -delta); + rect[xyDim] -= delta; + item.label[xyDim] -= delta; + adjusted = true; + } + lastPos = rect[xyDim] + rect[sizeDim]; + } + // TODO bleedMargin? + var first = list[0]; + var last = list[len - 1]; + var minGap; + var maxGap; + updateMinMaxGap(); + // If ends exceed two bounds, squeeze at most 80%, then take the gap of two bounds. + minGap < 0 && squeezeGaps(-minGap, 0.8); + maxGap < 0 && squeezeGaps(maxGap, 0.8); + updateMinMaxGap(); + takeBoundsGap(minGap, maxGap, 1); + takeBoundsGap(maxGap, minGap, -1); + // Handle bailout when there is not enough space. + updateMinMaxGap(); + if (minGap < 0) { + squeezeWhenBailout(-minGap); + } + if (maxGap < 0) { + squeezeWhenBailout(maxGap); + } + function updateMinMaxGap() { + minGap = first.rect[xyDim] - minBound; + maxGap = maxBound - last.rect[xyDim] - last.rect[sizeDim]; + } + function takeBoundsGap(gapThisBound, gapOtherBound, moveDir) { + if (gapThisBound < 0) { + // Move from other gap if can. + var moveFromMaxGap = Math.min(gapOtherBound, -gapThisBound); + if (moveFromMaxGap > 0) { + shiftList(moveFromMaxGap * moveDir, 0, len); + var remained = moveFromMaxGap + gapThisBound; + if (remained < 0) { + squeezeGaps(-remained * moveDir, 1); + } + } else { + squeezeGaps(-gapThisBound * moveDir, 1); + } + } + } + function shiftList(delta, start, end) { + if (delta !== 0) { + adjusted = true; + } + for (var i = start; i < end; i++) { + var item = list[i]; + var rect = item.rect; + rect[xyDim] += delta; + item.label[xyDim] += delta; + } + } + // Squeeze gaps if the labels exceed margin. + function squeezeGaps(delta, maxSqeezePercent) { + var gaps = []; + var totalGaps = 0; + for (var i = 1; i < len; i++) { + var prevItemRect = list[i - 1].rect; + var gap = Math.max(list[i].rect[xyDim] - prevItemRect[xyDim] - prevItemRect[sizeDim], 0); + gaps.push(gap); + totalGaps += gap; + } + if (!totalGaps) { + return; + } + var squeezePercent = Math.min(Math.abs(delta) / totalGaps, maxSqeezePercent); + if (delta > 0) { + for (var i = 0; i < len - 1; i++) { + // Distribute the shift delta to all gaps. + var movement = gaps[i] * squeezePercent; + // Forward + shiftList(movement, 0, i + 1); + } + } else { + // Backward + for (var i = len - 1; i > 0; i--) { + // Distribute the shift delta to all gaps. + var movement = gaps[i - 1] * squeezePercent; + shiftList(-movement, i, len); + } + } + } + /** + * Squeeze to allow overlap if there is no more space available. + * Let other overlapping strategy like hideOverlap do the job instead of keep exceeding the bounds. + */ + function squeezeWhenBailout(delta) { + var dir = delta < 0 ? -1 : 1; + delta = Math.abs(delta); + var moveForEachLabel = Math.ceil(delta / (len - 1)); + for (var i = 0; i < len - 1; i++) { + if (dir > 0) { + // Forward + shiftList(moveForEachLabel, 0, i + 1); + } else { + // Backward + shiftList(-moveForEachLabel, len - i - 1, len); + } + delta -= moveForEachLabel; + if (delta <= 0) { + return; + } + } + } + return adjusted; +} +/** + * Adjust labels on y direction to avoid overlap. + */ +function shiftLayoutOnY(list, topBound, bottomBound, +// If average the shifts on all labels and add them to 0 +balanceShift) { + return shiftLayout(list, 'y', 'height', topBound, bottomBound); +} +function hideOverlap(labelList) { + var displayedLabels = []; + // TODO, render overflow visible first, put in the displayedLabels. + labelList.sort(function (a, b) { + return b.priority - a.priority; + }); + var globalRect = new BoundingRect(0, 0, 0, 0); + function hideEl(el) { + if (!el.ignore) { + // Show on emphasis. + var emphasisState = el.ensureState('emphasis'); + if (emphasisState.ignore == null) { + emphasisState.ignore = false; + } + } + el.ignore = true; + } + for (var i = 0; i < labelList.length; i++) { + var labelItem = labelList[i]; + var isAxisAligned = labelItem.axisAligned; + var localRect = labelItem.localRect; + var transform = labelItem.transform; + var label = labelItem.label; + var labelLine = labelItem.labelLine; + globalRect.copy(labelItem.rect); + // Add a threshold because layout may be aligned precisely. + globalRect.width -= 0.1; + globalRect.height -= 0.1; + globalRect.x += 0.05; + globalRect.y += 0.05; + var obb = labelItem.obb; + var overlapped = false; + for (var j = 0; j < displayedLabels.length; j++) { + var existsTextCfg = displayedLabels[j]; + // Fast rejection. + if (!globalRect.intersect(existsTextCfg.rect)) { + continue; + } + if (isAxisAligned && existsTextCfg.axisAligned) { + // Is overlapped + overlapped = true; + break; + } + if (!existsTextCfg.obb) { + // If self is not axis aligned. But other is. + existsTextCfg.obb = new OrientedBoundingRect(existsTextCfg.localRect, existsTextCfg.transform); + } + if (!obb) { + // If self is axis aligned. But other is not. + obb = new OrientedBoundingRect(localRect, transform); + } + if (obb.intersect(existsTextCfg.obb)) { + overlapped = true; + break; + } + } + // TODO Callback to determine if this overlap should be handled? + if (overlapped) { + hideEl(label); + labelLine && hideEl(labelLine); + } else { + label.attr('ignore', labelItem.defaultAttr.ignore); + labelLine && labelLine.attr('ignore', labelItem.defaultAttr.labelGuideIgnore); + displayedLabels.push(labelItem); + } + } +} + +var raf = null; +function requestAnimationFrame (callback) { + if (!raf) { + raf = ( + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function (callback) { + return setTimeout(callback, 16) + } + ).bind(window); + } + return raf(callback) +} + +var caf = null; +function cancelAnimationFrame (id) { + if (!caf) { + caf = ( + window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.mozCancelAnimationFrame || + function (id) { + clearTimeout(id); + } + ).bind(window); + } + + caf(id); +} + +function createStyles (styleText) { + var style = document.createElement('style'); + + if (style.styleSheet) { + style.styleSheet.cssText = styleText; + } else { + style.appendChild(document.createTextNode(styleText)); + } + (document.querySelector('head') || document.body).appendChild(style); + return style +} + +function createElement (tagName, props) { + if ( props === void 0 ) props = {}; + + var elem = document.createElement(tagName); + Object.keys(props).forEach(function (key) { + elem[key] = props[key]; + }); + return elem +} + +function getComputedStyle$1 (elem, prop, pseudo) { + // for older versions of Firefox, `getComputedStyle` required + // the second argument and may return `null` for some elements + // when `display: none` + var computedStyle = window.getComputedStyle(elem, null) || { + display: 'none' + }; + + return computedStyle[prop] +} + +function getRenderInfo (elem) { + if (!document.documentElement.contains(elem)) { + return { + detached: true, + rendered: false + } + } + + var current = elem; + while (current !== document) { + if (getComputedStyle$1(current, 'display') === 'none') { + return { + detached: false, + rendered: false + } + } + current = current.parentNode; + } + + return { + detached: false, + rendered: true + } +} + +var css_248z = ".resize-triggers{visibility:hidden;opacity:0;pointer-events:none}.resize-contract-trigger,.resize-contract-trigger:before,.resize-expand-trigger,.resize-triggers{content:\"\";position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden}.resize-contract-trigger,.resize-expand-trigger{background:#eee;overflow:auto}.resize-contract-trigger:before{width:200%;height:200%}"; + +var total = 0; +var style = null; + +function addListener (elem, callback) { + if (!elem.__resize_mutation_handler__) { + elem.__resize_mutation_handler__ = handleMutation.bind(elem); + } + + var listeners = elem.__resize_listeners__; + + if (!listeners) { + elem.__resize_listeners__ = []; + if (window.ResizeObserver) { + var offsetWidth = elem.offsetWidth; + var offsetHeight = elem.offsetHeight; + var ro = new ResizeObserver(function () { + if (!elem.__resize_observer_triggered__) { + elem.__resize_observer_triggered__ = true; + if (elem.offsetWidth === offsetWidth && elem.offsetHeight === offsetHeight) { + return + } + } + runCallbacks(elem); + }); + + // initially display none won't trigger ResizeObserver callback + var ref = getRenderInfo(elem); + var detached = ref.detached; + var rendered = ref.rendered; + elem.__resize_observer_triggered__ = detached === false && rendered === false; + elem.__resize_observer__ = ro; + ro.observe(elem); + } else if (elem.attachEvent && elem.addEventListener) { + // targeting IE9/10 + elem.__resize_legacy_resize_handler__ = function handleLegacyResize () { + runCallbacks(elem); + }; + elem.attachEvent('onresize', elem.__resize_legacy_resize_handler__); + document.addEventListener('DOMSubtreeModified', elem.__resize_mutation_handler__); + } else { + if (!total) { + style = createStyles(css_248z); + } + initTriggers(elem); + + elem.__resize_rendered__ = getRenderInfo(elem).rendered; + if (window.MutationObserver) { + var mo = new MutationObserver(elem.__resize_mutation_handler__); + mo.observe(document, { + attributes: true, + childList: true, + characterData: true, + subtree: true + }); + elem.__resize_mutation_observer__ = mo; + } + } + } + + elem.__resize_listeners__.push(callback); + total++; +} + +function removeListener (elem, callback) { + var listeners = elem.__resize_listeners__; + if (!listeners) { + return + } + + if (callback) { + listeners.splice(listeners.indexOf(callback), 1); + } + + // no listeners exist, or removing all listeners + if (!listeners.length || !callback) { + // targeting IE9/10 + if (elem.detachEvent && elem.removeEventListener) { + elem.detachEvent('onresize', elem.__resize_legacy_resize_handler__); + document.removeEventListener('DOMSubtreeModified', elem.__resize_mutation_handler__); + return + } + + if (elem.__resize_observer__) { + elem.__resize_observer__.unobserve(elem); + elem.__resize_observer__.disconnect(); + elem.__resize_observer__ = null; + } else { + if (elem.__resize_mutation_observer__) { + elem.__resize_mutation_observer__.disconnect(); + elem.__resize_mutation_observer__ = null; + } + elem.removeEventListener('scroll', handleScroll); + elem.removeChild(elem.__resize_triggers__.triggers); + elem.__resize_triggers__ = null; + } + elem.__resize_listeners__ = null; + } + + if (!--total && style) { + style.parentNode.removeChild(style); + } +} + +function getUpdatedSize (elem) { + var ref = elem.__resize_last__; + var width = ref.width; + var height = ref.height; + var offsetWidth = elem.offsetWidth; + var offsetHeight = elem.offsetHeight; + if (offsetWidth !== width || offsetHeight !== height) { + return { + width: offsetWidth, + height: offsetHeight + } + } + return null +} + +function handleMutation () { + // `this` denotes the scrolling element + var ref = getRenderInfo(this); + var rendered = ref.rendered; + var detached = ref.detached; + if (rendered !== this.__resize_rendered__) { + if (!detached && this.__resize_triggers__) { + resetTriggers(this); + this.addEventListener('scroll', handleScroll, true); + } + this.__resize_rendered__ = rendered; + runCallbacks(this); + } +} + +function handleScroll () { + var this$1$1 = this; + + // `this` denotes the scrolling element + resetTriggers(this); + if (this.__resize_raf__) { + cancelAnimationFrame(this.__resize_raf__); + } + this.__resize_raf__ = requestAnimationFrame(function () { + var updated = getUpdatedSize(this$1$1); + if (updated) { + this$1$1.__resize_last__ = updated; + runCallbacks(this$1$1); + } + }); +} + +function runCallbacks (elem) { + if (!elem || !elem.__resize_listeners__) { + return + } + elem.__resize_listeners__.forEach(function (callback) { + callback.call(elem, elem); + }); +} + +function initTriggers (elem) { + var position = getComputedStyle$1(elem, 'position'); + if (!position || position === 'static') { + elem.style.position = 'relative'; + } + + elem.__resize_old_position__ = position; + elem.__resize_last__ = {}; + + var triggers = createElement('div', { + className: 'resize-triggers' + }); + var expand = createElement('div', { + className: 'resize-expand-trigger' + }); + var expandChild = createElement('div'); + var contract = createElement('div', { + className: 'resize-contract-trigger' + }); + expand.appendChild(expandChild); + triggers.appendChild(expand); + triggers.appendChild(contract); + elem.appendChild(triggers); + + elem.__resize_triggers__ = { + triggers: triggers, + expand: expand, + expandChild: expandChild, + contract: contract + }; + + resetTriggers(elem); + elem.addEventListener('scroll', handleScroll, true); + + elem.__resize_last__ = { + width: elem.offsetWidth, + height: elem.offsetHeight + }; +} + +function resetTriggers (elem) { + var ref = elem.__resize_triggers__; + var expand = ref.expand; + var expandChild = ref.expandChild; + var contract = ref.contract; + + // batch read + var csw = contract.scrollWidth; + var csh = contract.scrollHeight; + var eow = expand.offsetWidth; + var eoh = expand.offsetHeight; + var esw = expand.scrollWidth; + var esh = expand.scrollHeight; + + // batch write + contract.scrollLeft = csw; + contract.scrollTop = csh; + expandChild.style.width = eow + 1 + 'px'; + expandChild.style.height = eoh + 1 + 'px'; + expand.scrollLeft = esw; + expand.scrollTop = esh; +} + +var b=function(){return b=Object.assign||function(e){for(var t,n=1,r=arguments.length;n= maxRepaintRectCount; + } + } + } + for (var i = this.__startIndex; i < this.__endIndex; ++i) { + var el = displayList[i]; + if (el) { + var shouldPaint = el.shouldBePainted(viewWidth, viewHeight, true, true); + var prevRect = el.__isRendered && ((el.__dirty & REDRAW_BIT) || !shouldPaint) + ? el.getPrevPaintRect() + : null; + if (prevRect) { + addRectToMergePool(prevRect); + } + var curRect = shouldPaint && ((el.__dirty & REDRAW_BIT) || !el.__isRendered) + ? el.getPaintRect() + : null; + if (curRect) { + addRectToMergePool(curRect); + } + } + } + for (var i = this.__prevStartIndex; i < this.__prevEndIndex; ++i) { + var el = prevList[i]; + var shouldPaint = el && el.shouldBePainted(viewWidth, viewHeight, true, true); + if (el && (!shouldPaint || !el.__zr) && el.__isRendered) { + var prevRect = el.getPrevPaintRect(); + if (prevRect) { + addRectToMergePool(prevRect); + } + } + } + var hasIntersections; + do { + hasIntersections = false; + for (var i = 0; i < mergedRepaintRects.length;) { + if (mergedRepaintRects[i].isZero()) { + mergedRepaintRects.splice(i, 1); + continue; + } + for (var j = i + 1; j < mergedRepaintRects.length;) { + if (mergedRepaintRects[i].intersect(mergedRepaintRects[j])) { + hasIntersections = true; + mergedRepaintRects[i].union(mergedRepaintRects[j]); + mergedRepaintRects.splice(j, 1); + } + else { + j++; + } + } + i++; + } + } while (hasIntersections); + this._paintRects = mergedRepaintRects; + return mergedRepaintRects; + }; + Layer.prototype.debugGetPaintRects = function () { + return (this._paintRects || []).slice(); + }; + Layer.prototype.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); + } + } + }; + Layer.prototype.clear = function (clearAll, clearColor, repaintRects) { + var dom = this.dom; + var ctx = this.ctx; + var width = dom.width; + var height = dom.height; + clearColor = clearColor || this.clearColor; + var haveMotionBLur = this.motionBlur && !clearAll; + var lastFrameAlpha = this.lastFrameAlpha; + var dpr = this.dpr; + var self = this; + if (haveMotionBLur) { + if (!this.domBack) { + this.createBackBuffer(); + } + this.ctxBack.globalCompositeOperation = 'copy'; + this.ctxBack.drawImage(dom, 0, 0, width / dpr, height / dpr); + } + var domBack = this.domBack; + function doClear(x, y, width, height) { + ctx.clearRect(x, y, width, height); + if (clearColor && clearColor !== 'transparent') { + var clearColorGradientOrPattern = void 0; + if (isGradientObject(clearColor)) { + var shouldCache = clearColor.global || (clearColor.__width === width + && clearColor.__height === height); + clearColorGradientOrPattern = shouldCache + && clearColor.__canvasGradient + || getCanvasGradient(ctx, clearColor, { + x: 0, + y: 0, + width: width, + height: height + }); + clearColor.__canvasGradient = clearColorGradientOrPattern; + clearColor.__width = width; + clearColor.__height = height; + } + else if (isImagePatternObject(clearColor)) { + clearColor.scaleX = clearColor.scaleX || dpr; + clearColor.scaleY = clearColor.scaleY || dpr; + clearColorGradientOrPattern = createCanvasPattern(ctx, clearColor, { + dirty: function () { + self.setUnpainted(); + self.painter.refresh(); + } + }); + } + ctx.save(); + ctx.fillStyle = clearColorGradientOrPattern || clearColor; + ctx.fillRect(x, y, width, height); + ctx.restore(); + } + if (haveMotionBLur) { + ctx.save(); + ctx.globalAlpha = lastFrameAlpha; + ctx.drawImage(domBack, x, y, width, height); + ctx.restore(); + } + } + if (!repaintRects || haveMotionBLur) { + doClear(0, 0, width, height); + } + else if (repaintRects.length) { + each$4(repaintRects, function (rect) { + doClear(rect.x * dpr, rect.y * dpr, rect.width * dpr, rect.height * dpr); + }); + } + }; + return Layer; +}(Eventful)); + +var HOVER_LAYER_ZLEVEL = 1e5; +var CANVAS_ZLEVEL = 314159; +var EL_AFTER_INCREMENTAL_INC = 0.01; +var INCREMENTAL_INC = 1e-3; +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; +} +function createRoot(width, height) { + var domRoot = document.createElement("div"); + domRoot.style.cssText = [ + "position:relative", + "width:" + width + "px", + "height:" + height + "px", + "padding:0", + "margin:0", + "border-width:0" + ].join(";") + ";"; + return domRoot; +} +var CanvasPainter = function() { + function CanvasPainter2(root, storage, opts, id) { + this.type = "canvas"; + this._zlevelList = []; + this._prevDisplayList = []; + this._layers = {}; + this._layerConfig = {}; + this._needsManuallyCompositing = false; + this.type = "canvas"; + var singleCanvas = !root.nodeName || root.nodeName.toUpperCase() === "CANVAS"; + this._opts = opts = extend({}, opts || {}); + this.dpr = opts.devicePixelRatio || devicePixelRatio; + this._singleCanvas = singleCanvas; + this.root = root; + var rootStyle = root.style; + if (rootStyle) { + disableUserSelect(root); + root.innerHTML = ""; + } + this.storage = storage; + var zlevelList = this._zlevelList; + this._prevDisplayList = []; + var layers = this._layers; + if (!singleCanvas) { + this._width = getSize(root, 0, opts); + this._height = getSize(root, 1, opts); + var domRoot = this._domRoot = createRoot(this._width, this._height); + root.appendChild(domRoot); + } else { + var rootCanvas = root; + var width = rootCanvas.width; + var height = rootCanvas.height; + if (opts.width != null) { + width = opts.width; + } + if (opts.height != null) { + height = opts.height; + } + this.dpr = opts.devicePixelRatio || 1; + rootCanvas.width = width * this.dpr; + rootCanvas.height = height * this.dpr; + this._width = width; + this._height = height; + var mainLayer = new Layer(rootCanvas, this, this.dpr); + mainLayer.__builtin__ = true; + mainLayer.initContext(); + layers[CANVAS_ZLEVEL] = mainLayer; + mainLayer.zlevel = CANVAS_ZLEVEL; + zlevelList.push(CANVAS_ZLEVEL); + this._domRoot = root; + } + } + CanvasPainter2.prototype.getType = function() { + return "canvas"; + }; + CanvasPainter2.prototype.isSingleCanvas = function() { + return this._singleCanvas; + }; + CanvasPainter2.prototype.getViewportRoot = function() { + return this._domRoot; + }; + CanvasPainter2.prototype.getViewportRootOffset = function() { + var viewportRoot = this.getViewportRoot(); + if (viewportRoot) { + return { + offsetLeft: viewportRoot.offsetLeft || 0, + offsetTop: viewportRoot.offsetTop || 0 + }; + } + }; + CanvasPainter2.prototype.refresh = function(paintAll) { + var list = this.storage.getDisplayList(true); + var prevList = this._prevDisplayList; + var zlevelList = this._zlevelList; + this._redrawId = Math.random(); + this._paintList(list, prevList, paintAll, this._redrawId); + 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); + } + } + if (this._opts.useDirtyRect) { + this._prevDisplayList = list.slice(); + } + return this; + }; + CanvasPainter2.prototype.refreshHover = function() { + this._paintHoverList(this.storage.getDisplayList(false)); + }; + CanvasPainter2.prototype._paintHoverList = function(list) { + var len = list.length; + var hoverLayer = this._hoverlayer; + hoverLayer && hoverLayer.clear(); + if (!len) { + return; + } + var scope = { + inHover: true, + viewWidth: this._width, + viewHeight: this._height + }; + var ctx; + for (var i = 0; i < len; i++) { + var el = list[i]; + if (el.__inHover) { + if (!hoverLayer) { + hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL); + } + if (!ctx) { + ctx = hoverLayer.ctx; + ctx.save(); + } + brush(ctx, el, scope, i === len - 1); + } + } + if (ctx) { + ctx.restore(); + } + }; + CanvasPainter2.prototype.getHoverLayer = function() { + return this.getLayer(HOVER_LAYER_ZLEVEL); + }; + CanvasPainter2.prototype.paintOne = function(ctx, el) { + brushSingle(ctx, el); + }; + CanvasPainter2.prototype._paintList = function(list, prevList, paintAll, redrawId) { + if (this._redrawId !== redrawId) { + return; + } + paintAll = paintAll || false; + this._updateLayerStatus(list); + var _a = this._doPaintList(list, prevList, paintAll), finished = _a.finished, needsRefreshHover = _a.needsRefreshHover; + if (this._needsManuallyCompositing) { + this._compositeManually(); + } + if (needsRefreshHover) { + this._paintHoverList(list); + } + if (!finished) { + var self_1 = this; + requestAnimationFrame$1(function() { + self_1._paintList(list, prevList, paintAll, redrawId); + }); + } else { + this.eachLayer(function(layer) { + layer.afterBrush && layer.afterBrush(); + }); + } + }; + CanvasPainter2.prototype._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); + this.eachBuiltinLayer(function(layer) { + if (layer.virtual) { + ctx.drawImage(layer.dom, 0, 0, width, height); + } + }); + }; + CanvasPainter2.prototype._doPaintList = function(list, prevList, paintAll) { + var _this = this; + var layerList = []; + var useDirtyRect = this._opts.useDirtyRect; + 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; + var needsRefreshHover = false; + var _loop_1 = function(k2) { + var layer2 = layerList[k2]; + var ctx = layer2.ctx; + var repaintRects = useDirtyRect && layer2.createRepaintRects(list, prevList, this_1._width, this_1._height); + var start = paintAll ? layer2.__startIndex : layer2.__drawIndex; + var useTimer = !paintAll && layer2.incremental && Date.now; + var startTime = useTimer && Date.now(); + var clearColor = layer2.zlevel === this_1._zlevelList[0] ? this_1._backgroundColor : null; + if (layer2.__startIndex === layer2.__endIndex) { + layer2.clear(false, clearColor, repaintRects); + } else if (start === layer2.__startIndex) { + var firstEl = list[start]; + if (!firstEl.incremental || !firstEl.notClear || paintAll) { + layer2.clear(false, clearColor, repaintRects); + } + } + if (start === -1) { + console.error("For some unknown reason. drawIndex is -1"); + start = layer2.__startIndex; + } + var i; + var repaint = function(repaintRect) { + var scope = { + inHover: false, + allClipped: false, + prevEl: null, + viewWidth: _this._width, + viewHeight: _this._height + }; + for (i = start; i < layer2.__endIndex; i++) { + var el = list[i]; + if (el.__inHover) { + needsRefreshHover = true; + } + _this._doPaintEl(el, layer2, useDirtyRect, repaintRect, scope, i === layer2.__endIndex - 1); + if (useTimer) { + var dTime = Date.now() - startTime; + if (dTime > 15) { + break; + } + } + } + if (scope.prevElClipPaths) { + ctx.restore(); + } + }; + if (repaintRects) { + if (repaintRects.length === 0) { + i = layer2.__endIndex; + } else { + var dpr = this_1.dpr; + for (var r = 0; r < repaintRects.length; ++r) { + var rect = repaintRects[r]; + ctx.save(); + ctx.beginPath(); + ctx.rect(rect.x * dpr, rect.y * dpr, rect.width * dpr, rect.height * dpr); + ctx.clip(); + repaint(rect); + ctx.restore(); + } + } + } else { + ctx.save(); + repaint(); + ctx.restore(); + } + layer2.__drawIndex = i; + if (layer2.__drawIndex < layer2.__endIndex) { + finished = false; + } + }; + var this_1 = this; + for (var k = 0; k < layerList.length; k++) { + _loop_1(k); + } + if (env.wxa) { + each$4(this._layers, function(layer2) { + if (layer2 && layer2.ctx && layer2.ctx.draw) { + layer2.ctx.draw(); + } + }); + } + return { + finished, + needsRefreshHover + }; + }; + CanvasPainter2.prototype._doPaintEl = function(el, currentLayer, useDirtyRect, repaintRect, scope, isLast) { + var ctx = currentLayer.ctx; + if (useDirtyRect) { + var paintRect = el.getPaintRect(); + if (!repaintRect || paintRect && paintRect.intersect(repaintRect)) { + brush(ctx, el, scope, isLast); + el.setPrevPaintRect(paintRect); + } + } else { + brush(ctx, el, scope, isLast); + } + }; + CanvasPainter2.prototype.getLayer = function(zlevel, virtual) { + if (this._singleCanvas && !this._needsManuallyCompositing) { + zlevel = CANVAS_ZLEVEL; + } + var layer = this._layers[zlevel]; + if (!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); + } else if (this._layerConfig[zlevel - EL_AFTER_INCREMENTAL_INC]) { + merge(layer, this._layerConfig[zlevel - EL_AFTER_INCREMENTAL_INC], true); + } + if (virtual) { + layer.virtual = virtual; + } + this.insertLayer(zlevel, layer); + layer.initContext(); + } + return layer; + }; + CanvasPainter2.prototype.insertLayer = function(zlevel, layer) { + var layersMap = this._layers; + var zlevelList = this._zlevelList; + var len = zlevelList.length; + var domRoot = this._domRoot; + var prevLayer = null; + var i = -1; + if (layersMap[zlevel]) { + return; + } + if (!isLayerValid(layer)) { + 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; + 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); + } + } + } + layer.painter || (layer.painter = this); + }; + CanvasPainter2.prototype.eachLayer = function(cb, context) { + var zlevelList = this._zlevelList; + for (var i = 0; i < zlevelList.length; i++) { + var z = zlevelList[i]; + cb.call(context, this._layers[z], z); + } + }; + CanvasPainter2.prototype.eachBuiltinLayer = function(cb, context) { + var zlevelList = this._zlevelList; + for (var i = 0; i < zlevelList.length; i++) { + var z = zlevelList[i]; + var layer = this._layers[z]; + if (layer.__builtin__) { + cb.call(context, layer, z); + } + } + }; + CanvasPainter2.prototype.eachOtherLayer = function(cb, context) { + var zlevelList = this._zlevelList; + for (var i = 0; i < zlevelList.length; i++) { + var z = zlevelList[i]; + var layer = this._layers[z]; + if (!layer.__builtin__) { + cb.call(context, layer, z); + } + } + }; + CanvasPainter2.prototype.getLayers = function() { + return this._layers; + }; + CanvasPainter2.prototype._updateLayerStatus = function(list) { + this.eachBuiltinLayer(function(layer2, z) { + layer2.__dirty = layer2.__used = false; + }); + function updatePrevLayer(idx) { + if (prevLayer) { + if (prevLayer.__endIndex !== idx) { + prevLayer.__dirty = true; + } + prevLayer.__endIndex = idx; + } + } + if (this._singleCanvas) { + for (var i_1 = 1; i_1 < list.length; i_1++) { + var el = list[i_1]; + if (el.zlevel !== list[i_1 - 1].zlevel || el.incremental) { + this._needsManuallyCompositing = true; + break; + } + } + } + var prevLayer = null; + var incrementalLayerCount = 0; + var prevZlevel; + var i; + for (i = 0; i < list.length; i++) { + var el = list[i]; + var zlevel = el.zlevel; + var layer = void 0; + if (prevZlevel !== zlevel) { + prevZlevel = zlevel; + incrementalLayerCount = 0; + } + 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("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 { + layer.__drawIndex = -1; + } + updatePrevLayer(i); + prevLayer = layer; + } + if (el.__dirty & REDRAW_BIT && !el.__inHover) { + layer.__dirty = true; + if (layer.incremental && layer.__drawIndex < 0) { + layer.__drawIndex = i; + } + } + } + updatePrevLayer(i); + this.eachBuiltinLayer(function(layer2, z) { + if (!layer2.__used && layer2.getElementCount() > 0) { + layer2.__dirty = true; + layer2.__startIndex = layer2.__endIndex = layer2.__drawIndex = 0; + } + if (layer2.__dirty && layer2.__drawIndex < 0) { + layer2.__drawIndex = layer2.__startIndex; + } + }); + }; + CanvasPainter2.prototype.clear = function() { + this.eachBuiltinLayer(this._clearLayer); + return this; + }; + CanvasPainter2.prototype._clearLayer = function(layer) { + layer.clear(); + }; + CanvasPainter2.prototype.setBackgroundColor = function(backgroundColor) { + this._backgroundColor = backgroundColor; + each$4(this._layers, function(layer) { + layer.setUnpainted(); + }); + }; + CanvasPainter2.prototype.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); + } + } + } + }; + CanvasPainter2.prototype.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); + }; + CanvasPainter2.prototype.resize = function(width, height) { + if (!this._domRoot.style) { + if (width == null || height == null) { + return; + } + this._width = width; + this._height = height; + this.getLayer(CANVAS_ZLEVEL).resize(width, height); + } else { + var domRoot = this._domRoot; + domRoot.style.display = "none"; + var opts = this._opts; + var root = this.root; + width != null && (opts.width = width); + height != null && (opts.height = height); + width = getSize(root, 0, opts); + height = getSize(root, 1, opts); + domRoot.style.display = ""; + 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); + } + } + this.refresh(true); + } + this._width = width; + this._height = height; + } + return this; + }; + CanvasPainter2.prototype.clearLayer = function(zlevel) { + var layer = this._layers[zlevel]; + if (layer) { + layer.clear(); + } + }; + CanvasPainter2.prototype.dispose = function() { + this.root.innerHTML = ""; + this.root = this.storage = this._domRoot = this._layers = null; + }; + CanvasPainter2.prototype.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); + var ctx = imageLayer.ctx; + if (opts.pixelRatio <= this.dpr) { + this.refresh(); + var width_1 = imageLayer.dom.width; + var height_1 = imageLayer.dom.height; + this.eachLayer(function(layer) { + if (layer.__builtin__) { + ctx.drawImage(layer.dom, 0, 0, width_1, height_1); + } else if (layer.renderToCanvas) { + ctx.save(); + layer.renderToCanvas(ctx); + ctx.restore(); + } + }); + } else { + var scope = { + inHover: false, + viewWidth: this._width, + viewHeight: this._height + }; + var displayList = this.storage.getDisplayList(true); + for (var i = 0, len = displayList.length; i < len; i++) { + var el = displayList[i]; + brush(ctx, el, scope, i === len - 1); + } + } + return imageLayer.dom; + }; + CanvasPainter2.prototype.getWidth = function() { + return this._width; + }; + CanvasPainter2.prototype.getHeight = function() { + return this._height; + }; + return CanvasPainter2; +}(); + +function install$a(registers) { + registers.registerPainter('canvas', CanvasPainter); +} + +var LineSeriesModel = ( + /** @class */ + function(_super) { + __extends(LineSeriesModel2, _super); + function LineSeriesModel2() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = LineSeriesModel2.type; + _this.hasSymbolVisual = true; + return _this; + } + LineSeriesModel2.prototype.getInitialData = function(option) { + return createSeriesData(null, this, { + useEncodeDefaulter: true + }); + }; + LineSeriesModel2.prototype.getLegendIcon = function(opt) { + var group = new Group$2(); + var line = createSymbol("line", 0, opt.itemHeight / 2, opt.itemWidth, 0, opt.lineStyle.stroke, false); + group.add(line); + line.setStyle(opt.lineStyle); + var visualType = this.getData().getVisual("symbol"); + var visualRotate = this.getData().getVisual("symbolRotate"); + var symbolType = visualType === "none" ? "circle" : visualType; + var size = opt.itemHeight * 0.8; + var symbol = createSymbol(symbolType, (opt.itemWidth - size) / 2, (opt.itemHeight - size) / 2, size, size, opt.itemStyle.fill); + group.add(symbol); + symbol.setStyle(opt.itemStyle); + var symbolRotate = opt.iconRotate === "inherit" ? visualRotate : opt.iconRotate || 0; + symbol.rotation = symbolRotate * Math.PI / 180; + symbol.setOrigin([opt.itemWidth / 2, opt.itemHeight / 2]); + if (symbolType.indexOf("empty") > -1) { + symbol.style.stroke = symbol.style.fill; + symbol.style.fill = "#fff"; + symbol.style.lineWidth = 2; + } + return group; + }; + LineSeriesModel2.type = "series.line"; + LineSeriesModel2.dependencies = ["grid", "polar"]; + LineSeriesModel2.defaultOption = { + // zlevel: 0, + z: 3, + coordinateSystem: "cartesian2d", + legendHoverLink: true, + clip: true, + label: { + position: "top" + }, + // itemStyle: { + // }, + endLabel: { + show: false, + valueAnimation: true, + distance: 8 + }, + lineStyle: { + width: 2, + type: "solid" + }, + emphasis: { + scale: true + }, + // 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', 'lttb'. + sampling: "none", + animationEasing: "linear", + // Disable progressive + progressive: 0, + hoverLayerThreshold: Infinity, + universalTransition: { + divideShape: "clone" + }, + triggerLineEvent: false + }; + return LineSeriesModel2; + }(SeriesModel) +); + +/** + * @return label string. Not null/undefined + */ +function getDefaultLabel(data, dataIndex) { + var labelDims = data.mapDimensionsAll('defaultedLabel'); + var len = labelDims.length; + // Simple optimization (in lots of cases, label dims length is 1) + if (len === 1) { + var rawVal = retrieveRawValue(data, dataIndex, labelDims[0]); + return rawVal != null ? rawVal + '' : null; + } else if (len) { + var vals = []; + for (var i = 0; i < labelDims.length; i++) { + vals.push(retrieveRawValue(data, dataIndex, labelDims[i])); + } + return vals.join(' '); + } +} +function getDefaultInterpolatedLabel(data, interpolatedValue) { + var labelDims = data.mapDimensionsAll('defaultedLabel'); + if (!isArray(interpolatedValue)) { + return interpolatedValue + ''; + } + var vals = []; + for (var i = 0; i < labelDims.length; i++) { + var dimIndex = data.getDimensionIndex(labelDims[i]); + if (dimIndex >= 0) { + vals.push(interpolatedValue[dimIndex]); + } + } + return vals.join(' '); +} + +var Symbol$1 = /** @class */function (_super) { + __extends(Symbol, _super); + function Symbol(data, idx, seriesScope, opts) { + var _this = _super.call(this) || this; + _this.updateData(data, idx, seriesScope, opts); + return _this; + } + Symbol.prototype._createSymbol = function (symbolType, data, idx, symbolSize, keepAspect) { + // Remove paths created before + this.removeAll(); + // let 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, null, keepAspect); + symbolPath.attr({ + z2: 100, + culling: true, + scaleX: symbolSize[0] / 2, + scaleY: symbolSize[1] / 2 + }); + // Rewrite drift method + symbolPath.drift = driftSymbol; + this._symbolType = symbolType; + this.add(symbolPath); + }; + /** + * Stop animation + * @param {boolean} toLastFrame + */ + Symbol.prototype.stopSymbolAnimation = function (toLastFrame) { + this.childAt(0).stopAnimation(null, toLastFrame); + }; + Symbol.prototype.getSymbolType = function () { + return this._symbolType; + }; + /** + * 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. + */ + Symbol.prototype.getSymbolPath = function () { + return this.childAt(0); + }; + /** + * Highlight symbol + */ + Symbol.prototype.highlight = function () { + enterEmphasis(this.childAt(0)); + }; + /** + * Downplay symbol + */ + Symbol.prototype.downplay = function () { + leaveEmphasis(this.childAt(0)); + }; + /** + * @param {number} zlevel + * @param {number} z + */ + Symbol.prototype.setZ = function (zlevel, z) { + var symbolPath = this.childAt(0); + symbolPath.zlevel = zlevel; + symbolPath.z = z; + }; + Symbol.prototype.setDraggable = function (draggable, hasCursorOption) { + var symbolPath = this.childAt(0); + symbolPath.draggable = draggable; + symbolPath.cursor = !hasCursorOption && draggable ? 'move' : symbolPath.cursor; + }; + /** + * Update symbol properties + */ + Symbol.prototype.updateData = function (data, idx, seriesScope, opts) { + this.silent = false; + var symbolType = data.getItemVisual(idx, 'symbol') || 'circle'; + var seriesModel = data.hostModel; + var symbolSize = Symbol.getSymbolSize(data, idx); + var isInit = symbolType !== this._symbolType; + var disableAnimation = opts && opts.disableAnimation; + if (isInit) { + var keepAspect = data.getItemVisual(idx, 'symbolKeepAspect'); + this._createSymbol(symbolType, data, idx, symbolSize, keepAspect); + } else { + var symbolPath = this.childAt(0); + symbolPath.silent = false; + var target = { + scaleX: symbolSize[0] / 2, + scaleY: symbolSize[1] / 2 + }; + disableAnimation ? symbolPath.attr(target) : updateProps$1(symbolPath, target, seriesModel, idx); + saveOldStyle(symbolPath); + } + this._updateCommon(data, idx, symbolSize, seriesScope, opts); + if (isInit) { + var symbolPath = this.childAt(0); + if (!disableAnimation) { + var target = { + scaleX: this._sizeX, + scaleY: this._sizeY, + style: { + // Always fadeIn. Because it has fadeOut animation when symbol is removed.. + opacity: symbolPath.style.opacity + } + }; + symbolPath.scaleX = symbolPath.scaleY = 0; + symbolPath.style.opacity = 0; + initProps(symbolPath, target, seriesModel, idx); + } + } + if (disableAnimation) { + // Must stop leave transition manually if don't call initProps or updateProps. + this.childAt(0).stopAnimation('leave'); + } + }; + Symbol.prototype._updateCommon = function (data, idx, symbolSize, seriesScope, opts) { + var symbolPath = this.childAt(0); + var seriesModel = data.hostModel; + var emphasisItemStyle; + var blurItemStyle; + var selectItemStyle; + var focus; + var blurScope; + var emphasisDisabled; + var labelStatesModels; + var hoverScale; + var cursorStyle; + if (seriesScope) { + emphasisItemStyle = seriesScope.emphasisItemStyle; + blurItemStyle = seriesScope.blurItemStyle; + selectItemStyle = seriesScope.selectItemStyle; + focus = seriesScope.focus; + blurScope = seriesScope.blurScope; + labelStatesModels = seriesScope.labelStatesModels; + hoverScale = seriesScope.hoverScale; + cursorStyle = seriesScope.cursorStyle; + emphasisDisabled = seriesScope.emphasisDisabled; + } + if (!seriesScope || data.hasItemOption) { + var itemModel = seriesScope && seriesScope.itemModel ? seriesScope.itemModel : data.getItemModel(idx); + var emphasisModel = itemModel.getModel('emphasis'); + emphasisItemStyle = emphasisModel.getModel('itemStyle').getItemStyle(); + selectItemStyle = itemModel.getModel(['select', 'itemStyle']).getItemStyle(); + blurItemStyle = itemModel.getModel(['blur', 'itemStyle']).getItemStyle(); + focus = emphasisModel.get('focus'); + blurScope = emphasisModel.get('blurScope'); + emphasisDisabled = emphasisModel.get('disabled'); + labelStatesModels = getLabelStatesModels(itemModel); + hoverScale = emphasisModel.getShallow('scale'); + cursorStyle = itemModel.getShallow('cursor'); + } + var symbolRotate = data.getItemVisual(idx, 'symbolRotate'); + symbolPath.attr('rotation', (symbolRotate || 0) * Math.PI / 180 || 0); + var symbolOffset = normalizeSymbolOffset(data.getItemVisual(idx, 'symbolOffset'), symbolSize); + if (symbolOffset) { + symbolPath.x = symbolOffset[0]; + symbolPath.y = symbolOffset[1]; + } + cursorStyle && symbolPath.attr('cursor', cursorStyle); + var symbolStyle = data.getItemVisual(idx, 'style'); + var visualColor = symbolStyle.fill; + if (symbolPath instanceof ZRImage) { + var pathStyle = symbolPath.style; + symbolPath.useStyle(extend({ + // TODO other properties like x, y ? + image: pathStyle.image, + x: pathStyle.x, + y: pathStyle.y, + width: pathStyle.width, + height: pathStyle.height + }, symbolStyle)); + } else { + if (symbolPath.__isEmptyBrush) { + // fill and stroke will be swapped if it's empty. + // So we cloned a new style to avoid it affecting the original style in visual storage. + // TODO Better implementation. No empty logic! + symbolPath.useStyle(extend({}, symbolStyle)); + } else { + symbolPath.useStyle(symbolStyle); + } + // Disable decal because symbol scale will been applied on the decal. + symbolPath.style.decal = null; + symbolPath.setColor(visualColor, opts && opts.symbolInnerColor); + symbolPath.style.strokeNoScale = true; + } + var liftZ = data.getItemVisual(idx, 'liftZ'); + var z2Origin = this._z2; + if (liftZ != null) { + if (z2Origin == null) { + this._z2 = symbolPath.z2; + symbolPath.z2 += liftZ; + } + } else if (z2Origin != null) { + symbolPath.z2 = z2Origin; + this._z2 = null; + } + var useNameLabel = opts && opts.useNameLabel; + setLabelStyle(symbolPath, labelStatesModels, { + labelFetcher: seriesModel, + labelDataIndex: idx, + defaultText: getLabelDefaultText, + inheritColor: visualColor, + defaultOpacity: symbolStyle.opacity + }); + // Do not execute util needed. + function getLabelDefaultText(idx) { + return useNameLabel ? data.getName(idx) : getDefaultLabel(data, idx); + } + this._sizeX = symbolSize[0] / 2; + this._sizeY = symbolSize[1] / 2; + var emphasisState = symbolPath.ensureState('emphasis'); + emphasisState.style = emphasisItemStyle; + symbolPath.ensureState('select').style = selectItemStyle; + symbolPath.ensureState('blur').style = blurItemStyle; + // null / undefined / true means to use default strategy. + // 0 / false / negative number / NaN / Infinity means no scale. + var scaleRatio = hoverScale == null || hoverScale === true ? Math.max(1.1, 3 / this._sizeY) + // PENDING: restrict hoverScale > 1? It seems unreasonable to scale down + : isFinite(hoverScale) && hoverScale > 0 ? +hoverScale : 1; + // always set scale to allow resetting + emphasisState.scaleX = this._sizeX * scaleRatio; + emphasisState.scaleY = this._sizeY * scaleRatio; + this.setSymbolScale(1); + toggleHoverEmphasis(this, focus, blurScope, emphasisDisabled); + }; + Symbol.prototype.setSymbolScale = function (scale) { + this.scaleX = this.scaleY = scale; + }; + Symbol.prototype.fadeOut = function (cb, seriesModel, opt) { + var symbolPath = this.childAt(0); + var dataIndex = getECData(this).dataIndex; + var animationOpt = opt && opt.animation; + // Avoid mistaken hover when fading out + this.silent = symbolPath.silent = true; + // Not show text when animating + if (opt && opt.fadeLabel) { + var textContent = symbolPath.getTextContent(); + if (textContent) { + removeElement(textContent, { + style: { + opacity: 0 + } + }, seriesModel, { + dataIndex: dataIndex, + removeOpt: animationOpt, + cb: function () { + symbolPath.removeTextContent(); + } + }); + } + } else { + symbolPath.removeTextContent(); + } + removeElement(symbolPath, { + style: { + opacity: 0 + }, + scaleX: 0, + scaleY: 0 + }, seriesModel, { + dataIndex: dataIndex, + cb: cb, + removeOpt: animationOpt + }); + }; + Symbol.getSymbolSize = function (data, idx) { + return normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize')); + }; + return Symbol; +}(Group$2); +function driftSymbol(dx, dy) { + this.parent.drift(dx, dy); +} + +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'; +} +function normalizeUpdateOpt(opt) { + if (opt != null && !isObject$2(opt)) { + opt = { + isIgnore: opt + }; + } + return opt || {}; +} +function makeSeriesScope(data) { + var seriesModel = data.hostModel; + var emphasisModel = seriesModel.getModel('emphasis'); + return { + emphasisItemStyle: emphasisModel.getModel('itemStyle').getItemStyle(), + blurItemStyle: seriesModel.getModel(['blur', 'itemStyle']).getItemStyle(), + selectItemStyle: seriesModel.getModel(['select', 'itemStyle']).getItemStyle(), + focus: emphasisModel.get('focus'), + blurScope: emphasisModel.get('blurScope'), + emphasisDisabled: emphasisModel.get('disabled'), + hoverScale: emphasisModel.get('scale'), + labelStatesModels: getLabelStatesModels(seriesModel), + cursorStyle: seriesModel.get('cursor') + }; +} +var SymbolDraw = /** @class */function () { + function SymbolDraw(SymbolCtor) { + this.group = new Group$2(); + this._SymbolCtor = SymbolCtor || Symbol$1; + } + /** + * Update symbols draw by new data + */ + SymbolDraw.prototype.updateData = function (data, opt) { + // Remove progressive els. + this._progressiveEls = null; + opt = normalizeUpdateOpt(opt); + var group = this.group; + var seriesModel = data.hostModel; + var oldData = this._data; + var SymbolCtor = this._SymbolCtor; + var disableAnimation = opt.disableAnimation; + var seriesScope = makeSeriesScope(data); + var symbolUpdateOpt = { + disableAnimation: disableAnimation + }; + var getSymbolPoint = opt.getSymbolPoint || function (idx) { + return data.getItemLayout(idx); + }; + // 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 = getSymbolPoint(newIdx); + if (symbolNeedsDraw(data, point, newIdx, opt)) { + var symbolEl = new SymbolCtor(data, newIdx, seriesScope, symbolUpdateOpt); + symbolEl.setPosition(point); + data.setItemGraphicEl(newIdx, symbolEl); + group.add(symbolEl); + } + }).update(function (newIdx, oldIdx) { + var symbolEl = oldData.getItemGraphicEl(oldIdx); + var point = getSymbolPoint(newIdx); + if (!symbolNeedsDraw(data, point, newIdx, opt)) { + group.remove(symbolEl); + return; + } + var newSymbolType = data.getItemVisual(newIdx, 'symbol') || 'circle'; + var oldSymbolType = symbolEl && symbolEl.getSymbolType && symbolEl.getSymbolType(); + if (!symbolEl + // Create a new if symbol type changed. + || oldSymbolType && oldSymbolType !== newSymbolType) { + group.remove(symbolEl); + symbolEl = new SymbolCtor(data, newIdx, seriesScope, symbolUpdateOpt); + symbolEl.setPosition(point); + } else { + symbolEl.updateData(data, newIdx, seriesScope, symbolUpdateOpt); + var target = { + x: point[0], + y: point[1] + }; + disableAnimation ? symbolEl.attr(target) : updateProps$1(symbolEl, target, 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); + }, seriesModel); + }).execute(); + this._getSymbolPoint = getSymbolPoint; + this._data = data; + }; + SymbolDraw.prototype.updateLayout = function () { + var _this = this; + var data = this._data; + if (data) { + // Not use animation + data.eachItemGraphicEl(function (el, idx) { + var point = _this._getSymbolPoint(idx); + el.setPosition(point); + el.markRedraw(); + }); + } + }; + SymbolDraw.prototype.incrementalPrepareUpdate = function (data) { + this._seriesScope = makeSeriesScope(data); + this._data = null; + this.group.removeAll(); + }; + /** + * Update symbols draw by new data + */ + SymbolDraw.prototype.incrementalUpdate = function (taskParams, data, opt) { + // Clear + this._progressiveEls = []; + opt = normalizeUpdateOpt(opt); + function updateIncrementalAndHover(el) { + if (!el.isGroup) { + el.incremental = true; + el.ensureState('emphasis').hoverLayer = 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.setPosition(point); + this.group.add(el); + data.setItemGraphicEl(idx, el); + this._progressiveEls.push(el); + } + } + }; + SymbolDraw.prototype.eachRendered = function (cb) { + traverseElements(this._progressiveEls || this.group, cb); + }; + SymbolDraw.prototype.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); + }, data.hostModel); + }); + } else { + group.removeAll(); + } + }; + return SymbolDraw; +}(); + +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$1(coordSys.dimensions, function (coordDim) { + return data.mapDimension(coordDim); + }); + var stacked = false; + var stackResultDim = data.getCalculationInfo('stackResultDimension'); + if (isDimensionStacked(data, dims[0] /* , dims[1] */)) { + // jshint ignore:line + stacked = true; + dims[0] = stackResultDim; + } + if (isDimensionStacked(data, dims[1] /* , dims[0] */)) { + // jshint ignore:line + stacked = true; + 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]; + } + // If origin is specified as a number, use it as + // valueStart directly + else if (isNumber(valueOrigin) && !isNaN(valueOrigin)) { + valueStart = valueOrigin; + } + // 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); +} + +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; +} +function lineAnimationDiff(oldData, newData, oldStackedOnPoints, newStackedOnPoints, oldCoordSys, newCoordSys, oldValueOrigin, newValueOrigin) { + var diff = diffData(oldData, newData); + // let newIdList = newData.mapArray(newData.getId); + // let 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); + // const oldDataNewCoordInfo = prepareDataCoordInfo(newCoordSys, oldData, newValueOrigin); + var oldPoints = oldData.getLayout('points') || []; + var newPoints = newData.getLayout('points') || []; + for (var i = 0; i < diff.length; i++) { + var diffItem = diff[i]; + var pointAdded = true; + var oldIdx2 = void 0; + var newIdx2 = void 0; + // 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 '=': + oldIdx2 = diffItem.idx * 2; + newIdx2 = diffItem.idx1 * 2; + var currentX = oldPoints[oldIdx2]; + var currentY = oldPoints[oldIdx2 + 1]; + var nextX = newPoints[newIdx2]; + var nextY = newPoints[newIdx2 + 1]; + // If previous data is NaN, use next point directly + if (isNaN(currentX) || isNaN(currentY)) { + currentX = nextX; + currentY = nextY; + } + currPoints.push(currentX, currentY); + nextPoints.push(nextX, nextY); + currStackedPoints.push(oldStackedOnPoints[oldIdx2], oldStackedOnPoints[oldIdx2 + 1]); + nextStackedPoints.push(newStackedOnPoints[newIdx2], newStackedOnPoints[newIdx2 + 1]); + rawIndices.push(newData.getRawIndex(diffItem.idx1)); + break; + case '+': + var newIdx = diffItem.idx; + var newDataDimsForPoint = newDataOldCoordInfo.dataDimsForPoint; + var oldPt = oldCoordSys.dataToPoint([newData.get(newDataDimsForPoint[0], newIdx), newData.get(newDataDimsForPoint[1], newIdx)]); + newIdx2 = newIdx * 2; + currPoints.push(oldPt[0], oldPt[1]); + nextPoints.push(newPoints[newIdx2], newPoints[newIdx2 + 1]); + var stackedOnPoint = getStackedOnPoint(newDataOldCoordInfo, oldCoordSys, newData, newIdx); + currStackedPoints.push(stackedOnPoint[0], stackedOnPoint[1]); + nextStackedPoints.push(newStackedOnPoints[newIdx2], newStackedOnPoints[newIdx2 + 1]); + rawIndices.push(newData.getRawIndex(newIdx)); + break; + case '-': + 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 len = currPoints.length; + var sortedCurrPoints = createFloat32Array(len); + var sortedNextPoints = createFloat32Array(len); + var sortedCurrStackedPoints = createFloat32Array(len); + var sortedNextStackedPoints = createFloat32Array(len); + var sortedStatus = []; + for (var i = 0; i < sortedIndices.length; i++) { + var idx = sortedIndices[i]; + var i2 = i * 2; + var idx2 = idx * 2; + sortedCurrPoints[i2] = currPoints[idx2]; + sortedCurrPoints[i2 + 1] = currPoints[idx2 + 1]; + sortedNextPoints[i2] = nextPoints[idx2]; + sortedNextPoints[i2 + 1] = nextPoints[idx2 + 1]; + sortedCurrStackedPoints[i2] = currStackedPoints[idx2]; + sortedCurrStackedPoints[i2 + 1] = currStackedPoints[idx2 + 1]; + sortedNextStackedPoints[i2] = nextStackedPoints[idx2]; + sortedNextStackedPoints[i2 + 1] = nextStackedPoints[idx2 + 1]; + sortedStatus[i] = status[idx]; + } + return { + current: sortedCurrPoints, + next: sortedNextPoints, + stackedOnCurrent: sortedCurrStackedPoints, + stackedOnNext: sortedNextStackedPoints, + status: sortedStatus + }; +} + +var mathMin = Math.min; +var mathMax = Math.max; +function isPointNull$1(x, y) { + return isNaN(x) || isNaN(y); +} +/** + * 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 drawSegment(ctx, points, start, segLen, allLen, dir, smooth, smoothMonotone, connectNulls) { + var prevX; + var prevY; + var cpx0; + var cpy0; + var cpx1; + var cpy1; + var idx = start; + var k = 0; + for (; k < segLen; k++) { + var x = points[idx * 2]; + var y = points[idx * 2 + 1]; + if (idx >= allLen || idx < 0) { + break; + } + if (isPointNull$1(x, y)) { + if (connectNulls) { + idx += dir; + continue; + } + break; + } + if (idx === start) { + ctx[dir > 0 ? 'moveTo' : 'lineTo'](x, y); + cpx0 = x; + cpy0 = y; + } else { + var dx = x - prevX; + var dy = y - prevY; + // Ignore tiny segment. + if (dx * dx + dy * dy < 0.5) { + idx += dir; + continue; + } + if (smooth > 0) { + var nextIdx = idx + dir; + var nextX = points[nextIdx * 2]; + var nextY = points[nextIdx * 2 + 1]; + // Ignore duplicate point + while (nextX === x && nextY === y && k < segLen) { + k++; + nextIdx += dir; + idx += dir; + nextX = points[nextIdx * 2]; + nextY = points[nextIdx * 2 + 1]; + x = points[idx * 2]; + y = points[idx * 2 + 1]; + dx = x - prevX; + dy = y - prevY; + } + var tmpK = k + 1; + if (connectNulls) { + // Find next point not null + while (isPointNull$1(nextX, nextY) && tmpK < segLen) { + tmpK++; + nextIdx += dir; + nextX = points[nextIdx * 2]; + nextY = points[nextIdx * 2 + 1]; + } + } + var ratioNextSeg = 0.5; + var vx = 0; + var vy = 0; + var nextCpx0 = void 0; + var nextCpy0 = void 0; + // Is last point + if (tmpK >= segLen || isPointNull$1(nextX, nextY)) { + cpx1 = x; + cpy1 = y; + } else { + vx = nextX - prevX; + vy = nextY - prevY; + var dx0 = x - prevX; + var dx1 = nextX - x; + var dy0 = y - prevY; + var dy1 = nextY - y; + var lenPrevSeg = void 0; + var lenNextSeg = void 0; + if (smoothMonotone === 'x') { + lenPrevSeg = Math.abs(dx0); + lenNextSeg = Math.abs(dx1); + var dir_1 = vx > 0 ? 1 : -1; + cpx1 = x - dir_1 * lenPrevSeg * smooth; + cpy1 = y; + nextCpx0 = x + dir_1 * lenNextSeg * smooth; + nextCpy0 = y; + } else if (smoothMonotone === 'y') { + lenPrevSeg = Math.abs(dy0); + lenNextSeg = Math.abs(dy1); + var dir_2 = vy > 0 ? 1 : -1; + cpx1 = x; + cpy1 = y - dir_2 * lenPrevSeg * smooth; + nextCpx0 = x; + nextCpy0 = y + dir_2 * lenNextSeg * smooth; + } else { + lenPrevSeg = Math.sqrt(dx0 * dx0 + dy0 * dy0); + lenNextSeg = Math.sqrt(dx1 * dx1 + dy1 * dy1); + // Use ratio of seg length + ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg); + cpx1 = x - vx * smooth * (1 - ratioNextSeg); + cpy1 = y - vy * smooth * (1 - ratioNextSeg); + // cp0 of next segment + nextCpx0 = x + vx * smooth * ratioNextSeg; + nextCpy0 = y + vy * smooth * ratioNextSeg; + // Smooth constraint between point and next point. + // Avoid exceeding extreme after smoothing. + nextCpx0 = mathMin(nextCpx0, mathMax(nextX, x)); + nextCpy0 = mathMin(nextCpy0, mathMax(nextY, y)); + nextCpx0 = mathMax(nextCpx0, mathMin(nextX, x)); + nextCpy0 = mathMax(nextCpy0, mathMin(nextY, y)); + // Reclaculate cp1 based on the adjusted cp0 of next seg. + vx = nextCpx0 - x; + vy = nextCpy0 - y; + cpx1 = x - vx * lenPrevSeg / lenNextSeg; + cpy1 = y - vy * lenPrevSeg / lenNextSeg; + // Smooth constraint between point and prev point. + // Avoid exceeding extreme after smoothing. + cpx1 = mathMin(cpx1, mathMax(prevX, x)); + cpy1 = mathMin(cpy1, mathMax(prevY, y)); + cpx1 = mathMax(cpx1, mathMin(prevX, x)); + cpy1 = mathMax(cpy1, mathMin(prevY, y)); + // Adjust next cp0 again. + vx = x - cpx1; + vy = y - cpy1; + nextCpx0 = x + vx * lenNextSeg / lenPrevSeg; + nextCpy0 = y + vy * lenNextSeg / lenPrevSeg; + } + } + ctx.bezierCurveTo(cpx0, cpy0, cpx1, cpy1, x, y); + cpx0 = nextCpx0; + cpy0 = nextCpy0; + } else { + ctx.lineTo(x, y); + } + } + prevX = x; + prevY = y; + idx += dir; + } + return k; +} +var ECPolylineShape = /** @class */function () { + function ECPolylineShape() { + this.smooth = 0; + this.smoothConstraint = true; + } + return ECPolylineShape; +}(); +var ECPolyline = /** @class */function (_super) { + __extends(ECPolyline, _super); + function ECPolyline(opts) { + var _this = _super.call(this, opts) || this; + _this.type = 'ec-polyline'; + return _this; + } + ECPolyline.prototype.getDefaultStyle = function () { + return { + stroke: '#000', + fill: null + }; + }; + ECPolyline.prototype.getDefaultShape = function () { + return new ECPolylineShape(); + }; + ECPolyline.prototype.buildPath = function (ctx, shape) { + var points = shape.points; + var i = 0; + var len = points.length / 2; + // const result = getBoundingBox(points, shape.smoothConstraint); + if (shape.connectNulls) { + // Must remove first and last null values avoid draw error in polygon + for (; len > 0; len--) { + if (!isPointNull$1(points[len * 2 - 2], points[len * 2 - 1])) { + break; + } + } + for (; i < len; i++) { + if (!isPointNull$1(points[i * 2], points[i * 2 + 1])) { + break; + } + } + } + while (i < len) { + i += drawSegment(ctx, points, i, len, len, 1, shape.smooth, shape.smoothMonotone, shape.connectNulls) + 1; + } + }; + ECPolyline.prototype.getPointOn = function (xOrY, dim) { + if (!this.path) { + this.createPathProxy(); + this.buildPath(this.path, this.shape); + } + var path = this.path; + var data = path.data; + var CMD = PathProxy.CMD; + var x0; + var y0; + var isDimX = dim === 'x'; + var roots = []; + for (var i = 0; i < data.length;) { + var cmd = data[i++]; + var x = void 0; + var y = void 0; + var x2 = void 0; + var y2 = void 0; + var x3 = void 0; + var y3 = void 0; + var t = void 0; + switch (cmd) { + case CMD.M: + x0 = data[i++]; + y0 = data[i++]; + break; + case CMD.L: + x = data[i++]; + y = data[i++]; + t = isDimX ? (xOrY - x0) / (x - x0) : (xOrY - y0) / (y - y0); + if (t <= 1 && t >= 0) { + var val = isDimX ? (y - y0) * t + y0 : (x - x0) * t + x0; + return isDimX ? [xOrY, val] : [val, xOrY]; + } + x0 = x; + y0 = y; + break; + case CMD.C: + x = data[i++]; + y = data[i++]; + x2 = data[i++]; + y2 = data[i++]; + x3 = data[i++]; + y3 = data[i++]; + var nRoot = isDimX ? cubicRootAt(x0, x, x2, x3, xOrY, roots) : cubicRootAt(y0, y, y2, y3, xOrY, roots); + if (nRoot > 0) { + for (var i_1 = 0; i_1 < nRoot; i_1++) { + var t_1 = roots[i_1]; + if (t_1 <= 1 && t_1 >= 0) { + var val = isDimX ? cubicAt(y0, y, y2, y3, t_1) : cubicAt(x0, x, x2, x3, t_1); + return isDimX ? [xOrY, val] : [val, xOrY]; + } + } + } + x0 = x3; + y0 = y3; + break; + } + } + }; + return ECPolyline; +}(Path); +var ECPolygonShape = /** @class */function (_super) { + __extends(ECPolygonShape, _super); + function ECPolygonShape() { + return _super !== null && _super.apply(this, arguments) || this; + } + return ECPolygonShape; +}(ECPolylineShape); +var ECPolygon = /** @class */function (_super) { + __extends(ECPolygon, _super); + function ECPolygon(opts) { + var _this = _super.call(this, opts) || this; + _this.type = 'ec-polygon'; + return _this; + } + ECPolygon.prototype.getDefaultShape = function () { + return new ECPolygonShape(); + }; + ECPolygon.prototype.buildPath = function (ctx, shape) { + var points = shape.points; + var stackedOnPoints = shape.stackedOnPoints; + var i = 0; + var len = points.length / 2; + var smoothMonotone = shape.smoothMonotone; + if (shape.connectNulls) { + // Must remove first and last null values avoid draw error in polygon + for (; len > 0; len--) { + if (!isPointNull$1(points[len * 2 - 2], points[len * 2 - 1])) { + break; + } + } + for (; i < len; i++) { + if (!isPointNull$1(points[i * 2], points[i * 2 + 1])) { + break; + } + } + } + while (i < len) { + var k = drawSegment(ctx, points, i, len, len, 1, shape.smooth, smoothMonotone, shape.connectNulls); + drawSegment(ctx, stackedOnPoints, i + k - 1, k, len, -1, shape.stackedOnSmooth, smoothMonotone, shape.connectNulls); + i += k + 1; + ctx.closePath(); + } + }; + return ECPolygon; +}(Path); + +function createGridClipPath(cartesian, hasAnimation, seriesModel, done, during) { + var rect = cartesian.getArea(); + var x = rect.x; + var y = rect.y; + var width = rect.width; + var height = rect.height; + var lineWidth = seriesModel.get(['lineStyle', 'width']) || 0; + // 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; + // fix: https://github.com/apache/incubator-echarts/issues/11369 + width = Math.ceil(width); + if (x !== Math.floor(x)) { + x = Math.floor(x); + // if no extra 1px on `width`, it will still be clipped since `x` is floored + width++; + } + var clipPath = new Rect({ + shape: { + x: x, + y: y, + width: width, + height: height + } + }); + if (hasAnimation) { + var baseAxis = cartesian.getBaseAxis(); + var isHorizontal = baseAxis.isHorizontal(); + var isAxisInversed = baseAxis.inverse; + if (isHorizontal) { + if (isAxisInversed) { + clipPath.shape.x += width; + } + clipPath.shape.width = 0; + } else { + if (!isAxisInversed) { + clipPath.shape.y += height; + } + clipPath.shape.height = 0; + } + var duringCb = isFunction(during) ? function (percent) { + during(percent, clipPath); + } : null; + initProps(clipPath, { + shape: { + width: width, + height: height, + x: x, + y: y + } + }, seriesModel, null, done, duringCb); + } + 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 r0 = round$1(sectorArea.r0, 1); + var r = round$1(sectorArea.r, 1); + var clipPath = new Sector({ + shape: { + cx: round$1(polar.cx, 1), + cy: round$1(polar.cy, 1), + r0: r0, + r: r, + startAngle: sectorArea.startAngle, + endAngle: sectorArea.endAngle, + clockwise: sectorArea.clockwise + } + }); + if (hasAnimation) { + var isRadial = polar.getBaseAxis().dim === 'angle'; + if (isRadial) { + clipPath.shape.endAngle = sectorArea.startAngle; + } else { + clipPath.shape.r = r0; + } + initProps(clipPath, { + shape: { + endAngle: sectorArea.endAngle, + r: r + } + }, seriesModel); + } + return clipPath; +} + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 isCoordinateSystemType(coordSys, type) { + return coordSys.type === type; +} + +function isPointsSame(points1, points2) { + if (points1.length !== points2.length) { + return; + } + for (var i = 0; i < points1.length; i++) { + if (points1[i] !== points2[i]) { + return; + } + } + return true; +} +function bboxFromPoints(points) { + var minX = Infinity; + var minY = Infinity; + var maxX = -Infinity; + var maxY = -Infinity; + for (var i = 0; i < points.length; ) { + var x = points[i++]; + var y = points[i++]; + if (!isNaN(x)) { + minX = Math.min(x, minX); + maxX = Math.max(x, maxX); + } + if (!isNaN(y)) { + minY = Math.min(y, minY); + maxY = Math.max(y, maxY); + } + } + return [[minX, minY], [maxX, maxY]]; +} +function getBoundingDiff(points1, points2) { + var _a = bboxFromPoints(points1), min1 = _a[0], max1 = _a[1]; + var _b = bboxFromPoints(points2), min2 = _b[0], max2 = _b[1]; + return Math.max(Math.abs(min1[0] - min2[0]), Math.abs(min1[1] - min2[1]), Math.abs(max1[0] - max2[0]), Math.abs(max1[1] - max2[1])); +} +function getSmooth(smooth) { + return isNumber(smooth) ? smooth : smooth ? 0.5 : 0; +} +function getStackedOnPoints(coordSys, data, dataCoordInfo) { + if (!dataCoordInfo.valueDim) { + return []; + } + var len = data.count(); + var points = createFloat32Array(len * 2); + for (var idx = 0; idx < len; idx++) { + var pt = getStackedOnPoint(dataCoordInfo, coordSys, data, idx); + points[idx * 2] = pt[0]; + points[idx * 2 + 1] = pt[1]; + } + return points; +} +function turnPointsIntoStep(points, basePoints, coordSys, stepTurnAt, connectNulls) { + var baseAxis = coordSys.getBaseAxis(); + var baseIndex = baseAxis.dim === "x" || baseAxis.dim === "radius" ? 0 : 1; + var stepPoints = []; + var i = 0; + var stepPt = []; + var pt = []; + var nextPt = []; + var filteredPoints = []; + if (connectNulls) { + for (i = 0; i < points.length; i += 2) { + var reference = basePoints || points; + if (!isNaN(reference[i]) && !isNaN(reference[i + 1])) { + filteredPoints.push(points[i], points[i + 1]); + } + } + points = filteredPoints; + } + for (i = 0; i < points.length - 2; i += 2) { + nextPt[0] = points[i + 2]; + nextPt[1] = points[i + 3]; + pt[0] = points[i]; + pt[1] = points[i + 1]; + stepPoints.push(pt[0], pt[1]); + switch (stepTurnAt) { + case "end": + stepPt[baseIndex] = nextPt[baseIndex]; + stepPt[1 - baseIndex] = pt[1 - baseIndex]; + stepPoints.push(stepPt[0], stepPt[1]); + break; + case "middle": + 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[0], stepPt[1]); + stepPoints.push(stepPt2[0], stepPt2[1]); + break; + default: + stepPt[baseIndex] = pt[baseIndex]; + stepPt[1 - baseIndex] = nextPt[1 - baseIndex]; + stepPoints.push(stepPt[0], stepPt[1]); + } + } + stepPoints.push(points[i++], points[i++]); + return stepPoints; +} +function clipColorStops(colorStops, maxSize) { + var newColorStops = []; + var len = colorStops.length; + var prevOutOfRangeColorStop; + var prevInRangeColorStop; + function lerpStop(stop0, stop1, clippedCoord) { + var coord0 = stop0.coord; + var p = (clippedCoord - coord0) / (stop1.coord - coord0); + var color = lerp(p, [stop0.color, stop1.color]); + return { + coord: clippedCoord, + color + }; + } + for (var i = 0; i < len; i++) { + var stop_1 = colorStops[i]; + var coord = stop_1.coord; + if (coord < 0) { + prevOutOfRangeColorStop = stop_1; + } else if (coord > maxSize) { + if (prevInRangeColorStop) { + newColorStops.push(lerpStop(prevInRangeColorStop, stop_1, maxSize)); + } else if (prevOutOfRangeColorStop) { + newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop_1, 0), lerpStop(prevOutOfRangeColorStop, stop_1, maxSize)); + } + break; + } else { + if (prevOutOfRangeColorStop) { + newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop_1, 0)); + prevOutOfRangeColorStop = null; + } + newColorStops.push(stop_1); + prevInRangeColorStop = stop_1; + } + } + return newColorStops; +} +function getVisualGradient(data, coordSys, api) { + var visualMetaList = data.getVisual("visualMeta"); + if (!visualMetaList || !visualMetaList.length || !data.count()) { + return; + } + if (coordSys.type !== "cartesian2d") { + return; + } + var coordDim; + var visualMeta; + for (var i = visualMetaList.length - 1; i >= 0; i--) { + var dimInfo = data.getDimensionInfo(visualMetaList[i].dimension); + coordDim = dimInfo && dimInfo.coordDim; + if (coordDim === "x" || coordDim === "y") { + visualMeta = visualMetaList[i]; + break; + } + } + if (!visualMeta) { + return; + } + var axis = coordSys.getAxis(coordDim); + var colorStops = map$1(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 colorStopsInRange = clipColorStops(colorStops, coordDim === "x" ? api.getWidth() : api.getHeight()); + var inRangeStopLen = colorStopsInRange.length; + if (!inRangeStopLen && stopLen) { + return colorStops[0].coord < 0 ? outerColors[1] ? outerColors[1] : colorStops[stopLen - 1].color : outerColors[0] ? outerColors[0] : colorStops[0].color; + } + var tinyExtent = 10; + var minCoord = colorStopsInRange[0].coord - tinyExtent; + var maxCoord = colorStopsInRange[inRangeStopLen - 1].coord + tinyExtent; + var coordSpan = maxCoord - minCoord; + if (coordSpan < 1e-3) { + return "transparent"; + } + each$4(colorStopsInRange, function(stop) { + stop.offset = (stop.coord - minCoord) / coordSpan; + }); + colorStopsInRange.push({ + // NOTE: inRangeStopLen may still be 0 if stoplen is zero. + offset: inRangeStopLen ? colorStopsInRange[inRangeStopLen - 1].offset : 0.5, + color: outerColors[1] || "transparent" + }); + colorStopsInRange.unshift({ + offset: inRangeStopLen ? colorStopsInRange[0].offset : 0.5, + color: outerColors[0] || "transparent" + }); + var gradient = new LinearGradient(0, 0, 0, 0, colorStopsInRange, 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; + } + if (isAuto && canShowAllSymbolForCategory(categoryAxis, data)) { + return; + } + var categoryDataDim = data.mapDimension(categoryAxis.dim); + var labelMap = {}; + each$4(categoryAxis.getViewLabels(), function(labelItem) { + var ordinalNumber = categoryAxis.scale.getRawOrdinalNumber(labelItem.tickValue); + labelMap[ordinalNumber] = 1; + }); + return function(dataIndex) { + return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex)); + }; +} +function canShowAllSymbolForCategory(categoryAxis, data) { + var axisExtent = categoryAxis.getExtent(); + var availSize = Math.abs(axisExtent[1] - axisExtent[0]) / categoryAxis.scale.count(); + isNaN(availSize) && (availSize = 0); + var dataLen = data.count(); + var step = Math.max(1, Math.round(dataLen / 5)); + for (var dataIndex = 0; dataIndex < dataLen; dataIndex += step) { + if (Symbol$1.getSymbolSize( + data, + dataIndex + // Only for cartesian, where `isHorizontal` exists. + )[categoryAxis.isHorizontal() ? 1 : 0] * 1.5 > availSize) { + return false; + } + } + return true; +} +function isPointNull(x, y) { + return isNaN(x) || isNaN(y); +} +function getLastIndexNotNull(points) { + var len = points.length / 2; + for (; len > 0; len--) { + if (!isPointNull(points[len * 2 - 2], points[len * 2 - 1])) { + break; + } + } + return len - 1; +} +function getPointAtIndex(points, idx) { + return [points[idx * 2], points[idx * 2 + 1]]; +} +function getIndexRange(points, xOrY, dim) { + var len = points.length / 2; + var dimIdx = dim === "x" ? 0 : 1; + var a; + var b; + var prevIndex = 0; + var nextIndex = -1; + for (var i = 0; i < len; i++) { + b = points[i * 2 + dimIdx]; + if (isNaN(b) || isNaN(points[i * 2 + 1 - dimIdx])) { + continue; + } + if (i === 0) { + a = b; + continue; + } + if (a <= xOrY && b >= xOrY || a >= xOrY && b <= xOrY) { + nextIndex = i; + break; + } + prevIndex = i; + a = b; + } + return { + range: [prevIndex, nextIndex], + t: (xOrY - a) / (b - a) + }; +} +function anyStateShowEndLabel(seriesModel) { + if (seriesModel.get(["endLabel", "show"])) { + return true; + } + for (var i = 0; i < SPECIAL_STATES.length; i++) { + if (seriesModel.get([SPECIAL_STATES[i], "endLabel", "show"])) { + return true; + } + } + return false; +} +function createLineClipPath(lineView, coordSys, hasAnimation, seriesModel) { + if (isCoordinateSystemType(coordSys, "cartesian2d")) { + var endLabelModel_1 = seriesModel.getModel("endLabel"); + var valueAnimation_1 = endLabelModel_1.get("valueAnimation"); + var data_1 = seriesModel.getData(); + var labelAnimationRecord_1 = { + lastFrameIndex: 0 + }; + var during = anyStateShowEndLabel(seriesModel) ? function(percent, clipRect) { + lineView._endLabelOnDuring(percent, clipRect, data_1, labelAnimationRecord_1, valueAnimation_1, endLabelModel_1, coordSys); + } : null; + var isHorizontal = coordSys.getBaseAxis().isHorizontal(); + var clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel, function() { + var endLabel = lineView._endLabel; + if (endLabel && hasAnimation) { + if (labelAnimationRecord_1.originalX != null) { + endLabel.attr({ + x: labelAnimationRecord_1.originalX, + y: labelAnimationRecord_1.originalY + }); + } + } + }, during); + 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; + } + } + if (during) { + during(1, clipPath); + } + return clipPath; + } else { + return createPolarClipPath(coordSys, hasAnimation, seriesModel); + } +} +function getEndLabelStateSpecified(endLabelModel, coordSys) { + var baseAxis = coordSys.getBaseAxis(); + var isHorizontal = baseAxis.isHorizontal(); + var isBaseInversed = baseAxis.inverse; + var align = isHorizontal ? isBaseInversed ? "right" : "left" : "center"; + var verticalAlign = isHorizontal ? "middle" : isBaseInversed ? "top" : "bottom"; + return { + normal: { + align: endLabelModel.get("align") || align, + verticalAlign: endLabelModel.get("verticalAlign") || verticalAlign + } + }; +} +var LineView = ( + /** @class */ + function(_super) { + __extends(LineView2, _super); + function LineView2() { + return _super !== null && _super.apply(this, arguments) || this; + } + LineView2.prototype.init = function() { + var lineGroup = new Group$2(); + var symbolDraw = new SymbolDraw(); + this.group.add(symbolDraw.group); + this._symbolDraw = symbolDraw; + this._lineGroup = lineGroup; + this._changePolyState = bind$1(this._changePolyState, this); + }; + LineView2.prototype.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.getLayout("points") || []; + 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 = !ecModel.ssr && seriesModel.get("animation"); + var isAreaChart = !areaStyleModel.isEmpty(); + var valueOrigin = areaStyleModel.get("origin"); + var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin); + var stackedOnPoints = isAreaChart && getStackedOnPoints(coordSys, data, dataCoordInfo); + var showSymbol = seriesModel.get("showSymbol"); + var connectNulls = seriesModel.get("connectNulls"); + var isIgnoreFunc = showSymbol && !isCoordSysPolar && getIsIgnoreFunc(seriesModel, data, coordSys); + var oldData = this._data; + oldData && oldData.eachItemGraphicEl(function(el, idx) { + if (el.__temp) { + group.remove(el); + oldData.setItemGraphicEl(idx, null); + } + }); + if (!showSymbol) { + symbolDraw.remove(); + } + group.add(lineGroup); + var step = !isCoordSysPolar ? seriesModel.get("step") : false; + var clipShapeForSymbol; + if (coordSys && coordSys.getArea && seriesModel.get("clip", true)) { + clipShapeForSymbol = coordSys.getArea(); + 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.r += 0.5; + } + } + this._clipShapeForSymbol = clipShapeForSymbol; + var visualColor = getVisualGradient(data, coordSys, api) || data.getVisual("style")[data.getVisual("drawType")]; + if (!(polyline && prevCoordSys.type === coordSys.type && step === this._step)) { + showSymbol && symbolDraw.updateData(data, { + isIgnore: isIgnoreFunc, + clipShape: clipShapeForSymbol, + disableAnimation: true, + getSymbolPoint: function(idx) { + return [points[idx * 2], points[idx * 2 + 1]]; + } + }); + hasAnimation && this._initSymbolLabelAnimation(data, coordSys, clipShapeForSymbol); + if (step) { + if (stackedOnPoints) { + stackedOnPoints = turnPointsIntoStep(stackedOnPoints, points, coordSys, step, connectNulls); + } + points = turnPointsIntoStep(points, null, coordSys, step, connectNulls); + } + polyline = this._newPolyline(points); + if (isAreaChart) { + polygon = this._newPolygon(points, stackedOnPoints); + } else if (polygon) { + lineGroup.remove(polygon); + polygon = this._polygon = null; + } + if (!isCoordSysPolar) { + this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor)); + } + lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel)); + } else { + if (isAreaChart && !polygon) { + polygon = this._newPolygon(points, stackedOnPoints); + } else if (polygon && !isAreaChart) { + lineGroup.remove(polygon); + polygon = this._polygon = null; + } + if (!isCoordSysPolar) { + this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor)); + } + var oldClipPath = lineGroup.getClipPath(); + if (oldClipPath) { + var newClipPath = createLineClipPath(this, coordSys, false, seriesModel); + initProps(oldClipPath, { + shape: newClipPath.shape + }, seriesModel); + } else { + lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel)); + } + showSymbol && symbolDraw.updateData(data, { + isIgnore: isIgnoreFunc, + clipShape: clipShapeForSymbol, + disableAnimation: true, + getSymbolPoint: function(idx) { + return [points[idx * 2], points[idx * 2 + 1]]; + } + }); + if (!isPointsSame(this._stackedOnPoints, stackedOnPoints) || !isPointsSame(this._points, points)) { + if (hasAnimation) { + this._doUpdateAnimation(data, stackedOnPoints, coordSys, api, step, valueOrigin, connectNulls); + } else { + if (step) { + if (stackedOnPoints) { + stackedOnPoints = turnPointsIntoStep(stackedOnPoints, points, coordSys, step, connectNulls); + } + points = turnPointsIntoStep(points, null, coordSys, step, connectNulls); + } + polyline.setShape({ + points + }); + polygon && polygon.setShape({ + points, + stackedOnPoints + }); + } + } + } + var emphasisModel = seriesModel.getModel("emphasis"); + var focus = emphasisModel.get("focus"); + var blurScope = emphasisModel.get("blurScope"); + var emphasisDisabled = emphasisModel.get("disabled"); + polyline.useStyle(defaults( + // Use color in lineStyle first + lineStyleModel.getLineStyle(), + { + fill: "none", + stroke: visualColor, + lineJoin: "bevel" + } + )); + setStatesStylesFromModel(polyline, seriesModel, "lineStyle"); + if (polyline.style.lineWidth > 0 && seriesModel.get(["emphasis", "lineStyle", "width"]) === "bolder") { + var emphasisLineStyle = polyline.getState("emphasis").style; + emphasisLineStyle.lineWidth = +polyline.style.lineWidth + 1; + } + getECData(polyline).seriesIndex = seriesModel.seriesIndex; + toggleHoverEmphasis(polyline, focus, blurScope, emphasisDisabled); + var smooth = getSmooth(seriesModel.get("smooth")); + var smoothMonotone = seriesModel.get("smoothMonotone"); + polyline.setShape({ + smooth, + smoothMonotone, + connectNulls + }); + if (polygon) { + var stackedOnSeries = data.getCalculationInfo("stackedOnSeries"); + var stackedOnSmooth = 0; + polygon.useStyle(defaults(areaStyleModel.getAreaStyle(), { + fill: visualColor, + opacity: 0.7, + lineJoin: "bevel", + decal: data.getVisual("style").decal + })); + if (stackedOnSeries) { + stackedOnSmooth = getSmooth(stackedOnSeries.get("smooth")); + } + polygon.setShape({ + smooth, + stackedOnSmooth, + smoothMonotone, + connectNulls + }); + setStatesStylesFromModel(polygon, seriesModel, "areaStyle"); + getECData(polygon).seriesIndex = seriesModel.seriesIndex; + toggleHoverEmphasis(polygon, focus, blurScope, emphasisDisabled); + } + var changePolyState = this._changePolyState; + data.eachItemGraphicEl(function(el) { + el && (el.onHoverStateChange = changePolyState); + }); + this._polyline.onHoverStateChange = changePolyState; + this._data = data; + this._coordSys = coordSys; + this._stackedOnPoints = stackedOnPoints; + this._points = points; + this._step = step; + this._valueOrigin = valueOrigin; + if (seriesModel.get("triggerLineEvent")) { + this.packEventData(seriesModel, polyline); + polygon && this.packEventData(seriesModel, polygon); + } + }; + LineView2.prototype.packEventData = function(seriesModel, el) { + getECData(el).eventData = { + componentType: "series", + componentSubType: "line", + componentIndex: seriesModel.componentIndex, + seriesIndex: seriesModel.seriesIndex, + seriesName: seriesModel.name, + seriesType: "line" + }; + }; + LineView2.prototype.highlight = function(seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(); + var dataIndex = queryDataIndex(data, payload); + this._changePolyState("emphasis"); + if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) { + var points = data.getLayout("points"); + var symbol = data.getItemGraphicEl(dataIndex); + if (!symbol) { + var x = points[dataIndex * 2]; + var y = points[dataIndex * 2 + 1]; + if (isNaN(x) || isNaN(y)) { + return; + } + if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(x, y)) { + return; + } + var zlevel = seriesModel.get("zlevel") || 0; + var z = seriesModel.get("z") || 0; + symbol = new Symbol$1(data, dataIndex); + symbol.x = x; + symbol.y = y; + symbol.setZ(zlevel, z); + var symbolLabel = symbol.getSymbolPath().getTextContent(); + if (symbolLabel) { + symbolLabel.zlevel = zlevel; + symbolLabel.z = z; + symbolLabel.z2 = this._polyline.z2 + 1; + } + symbol.__temp = true; + data.setItemGraphicEl(dataIndex, symbol); + symbol.stopSymbolAnimation(true); + this.group.add(symbol); + } + symbol.highlight(); + } else { + ChartView.prototype.highlight.call(this, seriesModel, ecModel, api, payload); + } + }; + LineView2.prototype.downplay = function(seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(); + var dataIndex = queryDataIndex(data, payload); + this._changePolyState("normal"); + 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 { + ChartView.prototype.downplay.call(this, seriesModel, ecModel, api, payload); + } + }; + LineView2.prototype._changePolyState = function(toState) { + var polygon = this._polygon; + setStatesFlag(this._polyline, toState); + polygon && setStatesFlag(polygon, toState); + }; + LineView2.prototype._newPolyline = function(points) { + var polyline = this._polyline; + if (polyline) { + this._lineGroup.remove(polyline); + } + polyline = new ECPolyline({ + shape: { + points + }, + segmentIgnoreThreshold: 2, + z2: 10 + }); + this._lineGroup.add(polyline); + this._polyline = polyline; + return polyline; + }; + LineView2.prototype._newPolygon = function(points, stackedOnPoints) { + var polygon = this._polygon; + if (polygon) { + this._lineGroup.remove(polygon); + } + polygon = new ECPolygon({ + shape: { + points, + stackedOnPoints + }, + segmentIgnoreThreshold: 2 + }); + this._lineGroup.add(polygon); + this._polygon = polygon; + return polygon; + }; + LineView2.prototype._initSymbolLabelAnimation = function(data, coordSys, clipShape) { + var isHorizontalOrRadial; + var isCoordSysPolar; + var baseAxis = coordSys.getBaseAxis(); + var isAxisInverse = baseAxis.inverse; + if (coordSys.type === "cartesian2d") { + isHorizontalOrRadial = baseAxis.isHorizontal(); + isCoordSysPolar = false; + } else if (coordSys.type === "polar") { + isHorizontalOrRadial = baseAxis.dim === "angle"; + isCoordSysPolar = true; + } + var seriesModel = data.hostModel; + var seriesDuration = seriesModel.get("animationDuration"); + if (isFunction(seriesDuration)) { + seriesDuration = seriesDuration(null); + } + var seriesDelay = seriesModel.get("animationDelay") || 0; + var seriesDelayValue = isFunction(seriesDelay) ? seriesDelay(null) : seriesDelay; + data.eachItemGraphicEl(function(symbol, idx) { + var el = symbol; + if (el) { + var point = [symbol.x, symbol.y]; + var start = void 0; + var end = void 0; + var current = void 0; + if (clipShape) { + if (isCoordSysPolar) { + var polarClip = clipShape; + var coord = coordSys.pointToCoord(point); + if (isHorizontalOrRadial) { + start = polarClip.startAngle; + end = polarClip.endAngle; + current = -coord[1] / 180 * Math.PI; + } else { + start = polarClip.r0; + end = polarClip.r; + current = coord[0]; + } + } else { + var gridClip = clipShape; + if (isHorizontalOrRadial) { + start = gridClip.x; + end = gridClip.x + gridClip.width; + current = symbol.x; + } else { + start = gridClip.y + gridClip.height; + end = gridClip.y; + current = symbol.y; + } + } + } + var ratio = end === start ? 0 : (current - start) / (end - start); + if (isAxisInverse) { + ratio = 1 - ratio; + } + var delay = isFunction(seriesDelay) ? seriesDelay(idx) : seriesDuration * ratio + seriesDelayValue; + var symbolPath = el.getSymbolPath(); + var text = symbolPath.getTextContent(); + el.attr({ + scaleX: 0, + scaleY: 0 + }); + el.animateTo({ + scaleX: 1, + scaleY: 1 + }, { + duration: 200, + setToFinal: true, + delay + }); + if (text) { + text.animateFrom({ + style: { + opacity: 0 + } + }, { + duration: 300, + delay + }); + } + symbolPath.disableLabelAnimation = true; + } + }); + }; + LineView2.prototype._initOrUpdateEndLabel = function(seriesModel, coordSys, inheritColor) { + var endLabelModel = seriesModel.getModel("endLabel"); + if (anyStateShowEndLabel(seriesModel)) { + var data_2 = seriesModel.getData(); + var polyline = this._polyline; + var points = data_2.getLayout("points"); + if (!points) { + polyline.removeTextContent(); + this._endLabel = null; + return; + } + var endLabel = this._endLabel; + if (!endLabel) { + endLabel = this._endLabel = new ZRText({ + z2: 200 + // should be higher than item symbol + }); + endLabel.ignoreClip = true; + polyline.setTextContent(this._endLabel); + polyline.disableLabelAnimation = true; + } + var dataIndex = getLastIndexNotNull(points); + if (dataIndex >= 0) { + setLabelStyle(polyline, getLabelStatesModels(seriesModel, "endLabel"), { + inheritColor, + labelFetcher: seriesModel, + labelDataIndex: dataIndex, + defaultText: function(dataIndex2, opt, interpolatedValue) { + return interpolatedValue != null ? getDefaultInterpolatedLabel(data_2, interpolatedValue) : getDefaultLabel(data_2, dataIndex2); + }, + enableTextSetter: true + }, getEndLabelStateSpecified(endLabelModel, coordSys)); + polyline.textConfig.position = null; + } + } else if (this._endLabel) { + this._polyline.removeTextContent(); + this._endLabel = null; + } + }; + LineView2.prototype._endLabelOnDuring = function(percent, clipRect, data, animationRecord, valueAnimation, endLabelModel, coordSys) { + var endLabel = this._endLabel; + var polyline = this._polyline; + if (endLabel) { + if (percent < 1 && animationRecord.originalX == null) { + animationRecord.originalX = endLabel.x; + animationRecord.originalY = endLabel.y; + } + var points = data.getLayout("points"); + var seriesModel = data.hostModel; + var connectNulls = seriesModel.get("connectNulls"); + var precision = endLabelModel.get("precision"); + var distance = endLabelModel.get("distance") || 0; + var baseAxis = coordSys.getBaseAxis(); + var isHorizontal = baseAxis.isHorizontal(); + var isBaseInversed = baseAxis.inverse; + var clipShape = clipRect.shape; + var xOrY = isBaseInversed ? isHorizontal ? clipShape.x : clipShape.y + clipShape.height : isHorizontal ? clipShape.x + clipShape.width : clipShape.y; + var distanceX = (isHorizontal ? distance : 0) * (isBaseInversed ? -1 : 1); + var distanceY = (isHorizontal ? 0 : -distance) * (isBaseInversed ? -1 : 1); + var dim = isHorizontal ? "x" : "y"; + var dataIndexRange = getIndexRange(points, xOrY, dim); + var indices = dataIndexRange.range; + var diff = indices[1] - indices[0]; + var value = void 0; + if (diff >= 1) { + if (diff > 1 && !connectNulls) { + var pt = getPointAtIndex(points, indices[0]); + endLabel.attr({ + x: pt[0] + distanceX, + y: pt[1] + distanceY + }); + valueAnimation && (value = seriesModel.getRawValue(indices[0])); + } else { + var pt = polyline.getPointOn(xOrY, dim); + pt && endLabel.attr({ + x: pt[0] + distanceX, + y: pt[1] + distanceY + }); + var startValue = seriesModel.getRawValue(indices[0]); + var endValue = seriesModel.getRawValue(indices[1]); + valueAnimation && (value = interpolateRawValues(data, precision, startValue, endValue, dataIndexRange.t)); + } + animationRecord.lastFrameIndex = indices[0]; + } else { + var idx = percent === 1 || animationRecord.lastFrameIndex > 0 ? indices[0] : 0; + var pt = getPointAtIndex(points, idx); + valueAnimation && (value = seriesModel.getRawValue(idx)); + endLabel.attr({ + x: pt[0] + distanceX, + y: pt[1] + distanceY + }); + } + if (valueAnimation) { + var inner = labelInner(endLabel); + if (typeof inner.setLabelText === "function") { + inner.setLabelText(value); + } + } + } + }; + LineView2.prototype._doUpdateAnimation = function(data, stackedOnPoints, coordSys, api, step, valueOrigin, connectNulls) { + 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); + var current = diff.current; + var stackedOnCurrent = diff.stackedOnCurrent; + var next = diff.next; + var stackedOnNext = diff.stackedOnNext; + if (step) { + stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, diff.current, coordSys, step, connectNulls); + current = turnPointsIntoStep(diff.current, null, coordSys, step, connectNulls); + stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, diff.next, coordSys, step, connectNulls); + next = turnPointsIntoStep(diff.next, null, coordSys, step, connectNulls); + } + if (getBoundingDiff(current, next) > 3e3 || polygon && getBoundingDiff(stackedOnCurrent, stackedOnNext) > 3e3) { + polyline.stopAnimation(); + polyline.setShape({ + points: next + }); + if (polygon) { + polygon.stopAnimation(); + polygon.setShape({ + points: next, + stackedOnPoints: stackedOnNext + }); + } + return; + } + polyline.shape.__points = diff.current; + polyline.shape.points = current; + var target = { + shape: { + points: next + } + }; + if (diff.current !== current) { + target.shape.__points = diff.next; + } + polyline.stopAnimation(); + updateProps$1(polyline, target, seriesModel); + if (polygon) { + polygon.setShape({ + // Reuse the points with polyline. + points: current, + stackedOnPoints: stackedOnCurrent + }); + polygon.stopAnimation(); + updateProps$1(polygon, { + shape: { + stackedOnPoints: stackedOnNext + } + }, seriesModel); + if (polyline.shape.points !== polygon.shape.points) { + polygon.shape.points = polyline.shape.points; + } + } + 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, + ptIdx: i + // Index of points + }); + } + } + } + if (polyline.animators && polyline.animators.length) { + polyline.animators[0].during(function() { + polygon && polygon.dirtyShape(); + var points = polyline.shape.__points; + for (var i2 = 0; i2 < updatedDataInfo.length; i2++) { + var el2 = updatedDataInfo[i2].el; + var offset = updatedDataInfo[i2].ptIdx * 2; + el2.x = points[offset]; + el2.y = points[offset + 1]; + el2.markRedraw(); + } + }); + } + }; + LineView2.prototype.remove = function(ecModel) { + var group = this.group; + var oldData = this._data; + this._lineGroup.removeAll(); + this._symbolDraw.remove(true); + 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._endLabel = this._data = null; + }; + LineView2.type = "line"; + return LineView2; + }(ChartView) +); + +function pointsLayout(seriesType, forceStoreInTypedArray) { + return { + seriesType: seriesType, + plan: createRenderPlanner(), + reset: function (seriesModel) { + var data = seriesModel.getData(); + var coordSys = seriesModel.coordinateSystem; + seriesModel.pipelineContext; + if (!coordSys) { + return; + } + var dims = map$1(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[0] = stackResultDim; + } + if (isDimensionStacked(data, dims[1])) { + dims[1] = stackResultDim; + } + var store = data.getStore(); + var dimIdx0 = data.getDimensionIndex(dims[0]); + var dimIdx1 = data.getDimensionIndex(dims[1]); + return dimLen && { + progress: function (params, data) { + var segCount = params.end - params.start; + var points = createFloat32Array(segCount * dimLen); + var tmpIn = []; + var tmpOut = []; + for (var i = params.start, offset = 0; i < params.end; i++) { + var point = void 0; + if (dimLen === 1) { + var x = store.get(dimIdx0, i); + // NOTE: Make sure the second parameter is null to use default strategy. + point = coordSys.dataToPoint(x, null, tmpOut); + } else { + tmpIn[0] = store.get(dimIdx0, i); + tmpIn[1] = store.get(dimIdx1, i); + // Let coordinate system to handle the NaN data. + point = coordSys.dataToPoint(tmpIn, null, tmpOut); + } + { + points[offset++] = point[0]; + points[offset++] = point[1]; + } + } + data.setLayout('points', points); + } + }; + } + }; +} + +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) { + return Math.round(frame.length / 2); +}; +function dataSample(seriesType) { + return { + seriesType: seriesType, + // FIXME:TS never used, so comment it + // modifyOutputEnd: true, + reset: function (seriesModel, ecModel, api) { + var data = seriesModel.getData(); + var sampling = seriesModel.get('sampling'); + var coordSys = seriesModel.coordinateSystem; + var count = data.count(); + // Only cartesian2d support down sampling. Disable it when there is few data. + if (count > 10 && coordSys.type === 'cartesian2d' && sampling) { + var baseAxis = coordSys.getBaseAxis(); + var valueAxis = coordSys.getOtherAxis(baseAxis); + var extent = baseAxis.getExtent(); + var dpr = api.getDevicePixelRatio(); + // Coordinste system has been resized + var size = Math.abs(extent[1] - extent[0]) * (dpr || 1); + var rate = Math.round(count / size); + if (isFinite(rate) && rate > 1) { + if (sampling === 'lttb') { + seriesModel.setData(data.lttbDownSample(data.mapDimension(valueAxis.dim), 1 / rate)); + } else if (sampling === 'minmax') { + seriesModel.setData(data.minmaxDownSample(data.mapDimension(valueAxis.dim), 1 / rate)); + } + var sampler = void 0; + if (isString(sampling)) { + sampler = samplers[sampling]; + } else if (isFunction(sampling)) { + 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)); + } + } + } + } + }; +} + +function install$9(registers) { + registers.registerChartView(LineView); + registers.registerSeriesModel(LineSeriesModel); + registers.registerLayout(pointsLayout('line')); + registers.registerVisual({ + seriesType: 'line', + reset: function (seriesModel) { + var data = seriesModel.getData(); + // Visual coding for legend + var lineStyle = seriesModel.getModel('lineStyle').getLineStyle(); + if (lineStyle && !lineStyle.stroke) { + // Fill in visual should be palette color if + // has color callback + lineStyle.stroke = data.getVisual('style').fill; + } + data.setVisual('legendLineStyle', lineStyle); + } + }); + // Down sample after filter + registers.registerProcessor(registers.PRIORITY.PROCESSOR.STATISTIC, dataSample('line')); +} + +function getSectorCornerRadius(model, shape, zeroIfNull) { + var cornerRadius = model.get('borderRadius'); + if (cornerRadius == null) { + return zeroIfNull ? { + cornerRadius: 0 + } : null; + } + if (!isArray(cornerRadius)) { + cornerRadius = [cornerRadius, cornerRadius, cornerRadius, cornerRadius]; + } + var dr = Math.abs(shape.r || 0 - shape.r0 || 0); + return { + cornerRadius: map$1(cornerRadius, function (cr) { + return parsePercent$1(cr, dr); + }) + }; +} + +var PI2 = Math.PI * 2; +var RADIAN$1 = Math.PI / 180; +function getViewRect(seriesModel, api) { + return getLayoutRect(seriesModel.getBoxLayoutParams(), { + width: api.getWidth(), + height: api.getHeight() + }); +} +function getBasicPieLayout(seriesModel, api) { + var viewRect = getViewRect(seriesModel, api); + // center can be string or number when coordinateSystem is specified + var center = seriesModel.get('center'); + var radius = seriesModel.get('radius'); + if (!isArray(radius)) { + radius = [0, radius]; + } + var width = parsePercent(viewRect.width, api.getWidth()); + var height = parsePercent(viewRect.height, api.getHeight()); + var size = Math.min(width, height); + var r0 = parsePercent(radius[0], size / 2); + var r = parsePercent(radius[1], size / 2); + var cx; + var cy; + var coordSys = seriesModel.coordinateSystem; + if (coordSys) { + // percentage is not allowed when coordinate system is specified + var point = coordSys.dataToPoint(center); + cx = point[0] || 0; + cy = point[1] || 0; + } else { + if (!isArray(center)) { + center = [center, center]; + } + cx = parsePercent(center[0], width) + viewRect.x; + cy = parsePercent(center[1], height) + viewRect.y; + } + return { + cx: cx, + cy: cy, + r0: r0, + r: r + }; +} +function pieLayout(seriesType, ecModel, api) { + ecModel.eachSeriesByType(seriesType, function (seriesModel) { + var data = seriesModel.getData(); + var valueDim = data.mapDimension('value'); + var viewRect = getViewRect(seriesModel, api); + var _a = getBasicPieLayout(seriesModel, api), + cx = _a.cx, + cy = _a.cy, + r = _a.r, + r0 = _a.r0; + var startAngle = -seriesModel.get('startAngle') * RADIAN$1; + var endAngle = seriesModel.get('endAngle'); + var padAngle = seriesModel.get('padAngle') * RADIAN$1; + endAngle = endAngle === 'auto' ? startAngle - PI2 : -endAngle * RADIAN$1; + var minAngle = seriesModel.get('minAngle') * RADIAN$1; + var minAndPadAngle = minAngle + padAngle; + 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; + var dir = clockwise ? 1 : -1; + var angles = [startAngle, endAngle]; + var halfPadAngle = dir * padAngle / 2; + normalizeArcAngles(angles, !clockwise); + startAngle = angles[0], endAngle = angles[1]; + var layoutData = getSeriesLayoutData(seriesModel); + layoutData.startAngle = startAngle; + layoutData.endAngle = endAngle; + layoutData.clockwise = clockwise; + var angleRange = Math.abs(endAngle - startAngle); + // In the case some sector angle is smaller than minAngle + var restAngle = angleRange; + var valueSumLargerThanMinAngle = 0; + var currentAngle = startAngle; + data.setLayout({ + viewRect: viewRect, + r: r + }); + 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 + }); + return; + } + // FIXME 兼容 2.0 但是 roseType 是 area 的时候才是这样? + if (roseType !== 'area') { + angle = sum === 0 && stillShowZeroSum ? unitRadian : value * unitRadian; + } else { + angle = angleRange / validDataCount; + } + if (angle < minAndPadAngle) { + angle = minAndPadAngle; + restAngle -= minAndPadAngle; + } else { + valueSumLargerThanMinAngle += value; + } + var endAngle = currentAngle + dir * angle; + // calculate display angle + var actualStartAngle = 0; + var actualEndAngle = 0; + if (padAngle > angle) { + actualStartAngle = currentAngle + dir * angle / 2; + actualEndAngle = actualStartAngle; + } else { + actualStartAngle = currentAngle + halfPadAngle; + actualEndAngle = endAngle - halfPadAngle; + } + data.setItemLayout(idx, { + angle: angle, + startAngle: actualStartAngle, + endAngle: actualEndAngle, + clockwise: clockwise, + cx: cx, + cy: cy, + r0: r0, + r: roseType ? linearMap(value, extent, [r0, r]) : r + }); + currentAngle = endAngle; + }); + // Some sector is constrained by minAngle and padAngle + // Rest sectors needs recalculate angle + if (restAngle < PI2 && validDataCount) { + // Average the angle if rest angle is not enough after all angles is + // Constrained by minAngle and padAngle + if (restAngle <= 1e-3) { + var angle_1 = angleRange / validDataCount; + data.each(valueDim, function (value, idx) { + if (!isNaN(value)) { + var layout_1 = data.getItemLayout(idx); + layout_1.angle = angle_1; + var actualStartAngle = 0; + var actualEndAngle = 0; + if (angle_1 < padAngle) { + actualStartAngle = startAngle + dir * (idx + 1 / 2) * angle_1; + actualEndAngle = actualStartAngle; + } else { + actualStartAngle = startAngle + dir * idx * angle_1 + halfPadAngle; + actualEndAngle = startAngle + dir * (idx + 1) * angle_1 - halfPadAngle; + } + layout_1.startAngle = actualStartAngle; + layout_1.endAngle = actualEndAngle; + } + }); + } else { + unitRadian = restAngle / valueSumLargerThanMinAngle; + currentAngle = startAngle; + data.each(valueDim, function (value, idx) { + if (!isNaN(value)) { + var layout_2 = data.getItemLayout(idx); + var angle = layout_2.angle === minAndPadAngle ? minAndPadAngle : value * unitRadian; + var actualStartAngle = 0; + var actualEndAngle = 0; + if (angle < padAngle) { + actualStartAngle = currentAngle + dir * angle / 2; + actualEndAngle = actualStartAngle; + } else { + actualStartAngle = currentAngle + halfPadAngle; + actualEndAngle = currentAngle + dir * angle - halfPadAngle; + } + layout_2.startAngle = actualStartAngle; + layout_2.endAngle = actualEndAngle; + currentAngle += dir * angle; + } + }); + } + } + }); +} +var getSeriesLayoutData = makeInner(); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 dataFilter(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++) { + // @ts-ignore FIXME: LegendModel + if (!legendModels[i].isSelected(name)) { + return false; + } + } + return true; + }); + } + }; +} + +var RADIAN = Math.PI / 180; +function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) { + if (list.length < 2) { + return; + } + function recalculateXOnSemiToAlignOnEllipseCurve(semi) { + var rB = semi.rB; + var rB2 = rB * rB; + for (var i = 0; i < semi.list.length; i++) { + var item = semi.list[i]; + var dy = Math.abs(item.label.y - cy); + // horizontal r is always same with original r because x is not changed. + var rA = r + item.len; + var rA2 = rA * rA; + // Use ellipse implicit function to calculate x + var dx = Math.sqrt(Math.abs((1 - dy * dy / rB2) * rA2)); + var newX = cx + (dx + item.len2) * dir; + var deltaX = newX - item.label.x; + var newTargetWidth = item.targetTextWidth - deltaX * dir; + // text x is changed, so need to recalculate width. + constrainTextWidth(item, newTargetWidth, true); + item.label.x = newX; + } + } + // Adjust X based on the shifted y. Make tight labels aligned on an ellipse curve. + function recalculateX(items) { + // Extremes of + var topSemi = { + list: [], + maxY: 0 + }; + var bottomSemi = { + list: [], + maxY: 0 + }; + for (var i = 0; i < items.length; i++) { + if (items[i].labelAlignTo !== 'none') { + continue; + } + var item = items[i]; + var semi = item.label.y > cy ? bottomSemi : topSemi; + var dy = Math.abs(item.label.y - cy); + if (dy >= semi.maxY) { + var dx = item.label.x - cx - item.len2 * dir; + // horizontal r is always same with original r because x is not changed. + var rA = r + item.len; + // Canculate rB based on the topest / bottemest label. + var rB = Math.abs(dx) < rA ? Math.sqrt(dy * dy / (1 - dx * dx / rA / rA)) : rA; + semi.rB = rB; + semi.maxY = dy; + } + semi.list.push(item); + } + recalculateXOnSemiToAlignOnEllipseCurve(topSemi); + recalculateXOnSemiToAlignOnEllipseCurve(bottomSemi); + } + var len = list.length; + for (var i = 0; i < len; i++) { + if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') { + var dx = list[i].label.x - farthestX; + list[i].linePoints[1][0] += dx; + list[i].label.x = farthestX; + } + } + if (shiftLayoutOnY(list, viewTop, viewTop + viewHeight)) { + recalculateX(list); + } +} +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++) { + var label = labelLayoutList[i].label; + if (isPositionCenter(labelLayoutList[i])) { + continue; + } + if (label.x < cx) { + leftmostX = Math.min(leftmostX, label.x); + leftList.push(labelLayoutList[i]); + } else { + rightmostX = Math.max(rightmostX, label.x); + rightList.push(labelLayoutList[i]); + } + } + for (var i = 0; i < labelLayoutList.length; i++) { + var layout = labelLayoutList[i]; + if (!isPositionCenter(layout) && layout.linePoints) { + if (layout.labelStyleWidth != null) { + continue; + } + var label = layout.label; + var linePoints = layout.linePoints; + var targetTextWidth = void 0; + if (layout.labelAlignTo === 'edge') { + if (label.x < cx) { + targetTextWidth = linePoints[2][0] - layout.labelDistance - viewLeft - layout.edgeDistance; + } else { + targetTextWidth = viewLeft + viewWidth - layout.edgeDistance - linePoints[2][0] - layout.labelDistance; + } + } else if (layout.labelAlignTo === 'labelLine') { + if (label.x < cx) { + targetTextWidth = leftmostX - viewLeft - layout.bleedMargin; + } else { + targetTextWidth = viewLeft + viewWidth - rightmostX - layout.bleedMargin; + } + } else { + if (label.x < cx) { + targetTextWidth = label.x - viewLeft - layout.bleedMargin; + } else { + targetTextWidth = viewLeft + viewWidth - label.x - layout.bleedMargin; + } + } + layout.targetTextWidth = targetTextWidth; + constrainTextWidth(layout, targetTextWidth); + } + } + 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) && layout.linePoints) { + var label = layout.label; + var linePoints = layout.linePoints; + var isAlignToEdge = layout.labelAlignTo === 'edge'; + var padding = label.style.padding; + var paddingH = padding ? padding[1] + padding[3] : 0; + // textRect.width already contains paddingH if bgColor is set + var extraPaddingH = label.style.backgroundColor ? 0 : paddingH; + var realTextWidth = layout.rect.width + extraPaddingH; + var dist = linePoints[1][0] - linePoints[2][0]; + if (isAlignToEdge) { + if (label.x < cx) { + linePoints[2][0] = viewLeft + layout.edgeDistance + realTextWidth + layout.labelDistance; + } else { + linePoints[2][0] = viewLeft + viewWidth - layout.edgeDistance - realTextWidth - layout.labelDistance; + } + } else { + if (label.x < cx) { + linePoints[2][0] = label.x + layout.labelDistance; + } else { + linePoints[2][0] = label.x - layout.labelDistance; + } + linePoints[1][0] = linePoints[2][0] + dist; + } + linePoints[1][1] = linePoints[2][1] = label.y; + } + } +} +/** + * Set max width of each label, and then wrap each label to the max width. + * + * @param layout label layout + * @param availableWidth max width for the label to display + * @param forceRecalculate recaculate the text layout even if the current width + * is smaller than `availableWidth`. This is useful when the text was previously + * wrapped by calling `constrainTextWidth` but now `availableWidth` changed, in + * which case, previous wrapping should be redo. + */ +function constrainTextWidth(layout, availableWidth, forceRecalculate) { + if (forceRecalculate === void 0) { + forceRecalculate = false; + } + if (layout.labelStyleWidth != null) { + // User-defined style.width has the highest priority. + return; + } + var label = layout.label; + var style = label.style; + var textRect = layout.rect; + var bgColor = style.backgroundColor; + var padding = style.padding; + var paddingH = padding ? padding[1] + padding[3] : 0; + var overflow = style.overflow; + // textRect.width already contains paddingH if bgColor is set + var oldOuterWidth = textRect.width + (bgColor ? 0 : paddingH); + if (availableWidth < oldOuterWidth || forceRecalculate) { + var oldHeight = textRect.height; + if (overflow && overflow.match('break')) { + // Temporarily set background to be null to calculate + // the bounding box without background. + label.setStyle('backgroundColor', null); + // Set constraining width + label.setStyle('width', availableWidth - paddingH); + // This is the real bounding box of the text without padding. + var innerRect = label.getBoundingRect(); + label.setStyle('width', Math.ceil(innerRect.width)); + label.setStyle('backgroundColor', bgColor); + } else { + var availableInnerWidth = availableWidth - paddingH; + var newWidth = availableWidth < oldOuterWidth + // Current text is too wide, use `availableWidth` as max width. + ? availableInnerWidth : + // Current available width is enough, but the text may have + // already been wrapped with a smaller available width. + forceRecalculate ? availableInnerWidth > layout.unconstrainedWidth + // Current available is larger than text width, + // so don't constrain width (otherwise it may have + // empty space in the background). + ? null + // Current available is smaller than text width, so + // use the current available width as constraining + // width. + : availableInnerWidth + // Current available width is enough, so no need to + // constrain. + : null; + label.setStyle('width', newWidth); + } + var newRect = label.getBoundingRect(); + textRect.width = newRect.width; + var margin = (label.style.margin || 0) + 2.1; + textRect.height = newRect.height + margin; + textRect.y -= (textRect.height - oldHeight) / 2; + } +} +function isPositionCenter(sectorShape) { + // Not change x for center label + return sectorShape.position === 'center'; +} +function pieLabelLayout(seriesModel) { + var data = seriesModel.getData(); + var labelLayoutList = []; + var cx; + var cy; + var hasLabelRotate = false; + var minShowLabelRadian = (seriesModel.get('minShowLabelAngle') || 0) * RADIAN; + var viewRect = data.getLayout('viewRect'); + var r = data.getLayout('r'); + var viewWidth = viewRect.width; + var viewLeft = viewRect.x; + var viewTop = viewRect.y; + var viewHeight = viewRect.height; + function setNotShow(el) { + el.ignore = true; + } + function isLabelShown(label) { + if (!label.ignore) { + return true; + } + for (var key in label.states) { + if (label.states[key].ignore === false) { + return true; + } + } + return false; + } + data.each(function (idx) { + var sector = data.getItemGraphicEl(idx); + var sectorShape = sector.shape; + var label = sector.getTextContent(); + var labelLine = sector.getTextGuideLine(); + 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 edgeDistance = parsePercent(labelModel.get('edgeDistance'), viewWidth); + var bleedMargin = labelModel.get('bleedMargin'); + var labelLineModel = itemModel.getModel('labelLine'); + var labelLineLen = labelLineModel.get('length'); + labelLineLen = parsePercent(labelLineLen, viewWidth); + var labelLineLen2 = labelLineModel.get('length2'); + labelLineLen2 = parsePercent(labelLineLen2, viewWidth); + if (Math.abs(sectorShape.endAngle - sectorShape.startAngle) < minShowLabelRadian) { + each$4(label.states, setNotShow); + label.ignore = true; + if (labelLine) { + each$4(labelLine.states, setNotShow); + labelLine.ignore = true; + } + return; + } + if (!isLabelShown(label)) { + return; + } + var midAngle = (sectorShape.startAngle + sectorShape.endAngle) / 2; + var nx = Math.cos(midAngle); + var ny = Math.sin(midAngle); + var textX; + var textY; + var linePoints; + var textAlign; + cx = sectorShape.cx; + cy = sectorShape.cy; + var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner'; + if (labelPosition === 'center') { + textX = sectorShape.cx; + textY = sectorShape.cy; + textAlign = 'center'; + } else { + var x1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * nx : sectorShape.r * nx) + cx; + var y1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * ny : sectorShape.r * ny) + cy; + textX = x1 + nx * 3; + textY = y1 + ny * 3; + if (!isLabelInside) { + // For roseType + var x2 = x1 + nx * (labelLineLen + r - sectorShape.r); + var y2 = y1 + ny * (labelLineLen + r - sectorShape.r); + var x3 = x2 + (nx < 0 ? -1 : 1) * labelLineLen2; + var y3 = y2; + if (labelAlignTo === 'edge') { + // Adjust textX because text align of edge is opposite + textX = nx < 0 ? viewLeft + edgeDistance : viewLeft + viewWidth - edgeDistance; + } else { + textX = x3 + (nx < 0 ? -labelDistance : labelDistance); + } + textY = y3; + linePoints = [[x1, y1], [x2, y2], [x3, y3]]; + } + textAlign = isLabelInside ? 'center' : labelAlignTo === 'edge' ? nx > 0 ? 'right' : 'left' : nx > 0 ? 'left' : 'right'; + } + var PI = Math.PI; + var labelRotate = 0; + var rotate = labelModel.get('rotate'); + if (isNumber(rotate)) { + labelRotate = rotate * (PI / 180); + } else if (labelPosition === 'center') { + labelRotate = 0; + } else if (rotate === 'radial' || rotate === true) { + var radialAngle = nx < 0 ? -midAngle + PI : -midAngle; + labelRotate = radialAngle; + } else if (rotate === 'tangential' && labelPosition !== 'outside' && labelPosition !== 'outer') { + var rad = Math.atan2(nx, ny); + if (rad < 0) { + rad = PI * 2 + rad; + } + var isDown = ny > 0; + if (isDown) { + rad = PI + rad; + } + labelRotate = rad - PI; + } + hasLabelRotate = !!labelRotate; + label.x = textX; + label.y = textY; + label.rotation = labelRotate; + label.setStyle({ + verticalAlign: 'middle' + }); + // Not sectorShape the inside label + if (!isLabelInside) { + var textRect = label.getBoundingRect().clone(); + textRect.applyTransform(label.getComputedTransform()); + // Text has a default 1px stroke. Exclude this. + var margin = (label.style.margin || 0) + 2.1; + textRect.y -= margin / 2; + textRect.height += margin; + labelLayoutList.push({ + label: label, + labelLine: labelLine, + position: labelPosition, + len: labelLineLen, + len2: labelLineLen2, + minTurnAngle: labelLineModel.get('minTurnAngle'), + maxSurfaceAngle: labelLineModel.get('maxSurfaceAngle'), + surfaceNormal: new Point(nx, ny), + linePoints: linePoints, + textAlign: textAlign, + labelDistance: labelDistance, + labelAlignTo: labelAlignTo, + edgeDistance: edgeDistance, + bleedMargin: bleedMargin, + rect: textRect, + unconstrainedWidth: textRect.width, + labelStyleWidth: label.style.width + }); + } else { + label.setStyle({ + align: textAlign + }); + var selectState = label.states.select; + if (selectState) { + selectState.x += label.x; + selectState.y += label.y; + } + } + sector.setTextConfig({ + inside: isLabelInside + }); + }); + if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) { + avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop); + } + for (var i = 0; i < labelLayoutList.length; i++) { + var layout = labelLayoutList[i]; + var label = layout.label; + var labelLine = layout.labelLine; + var notShowLabel = isNaN(label.x) || isNaN(label.y); + if (label) { + label.setStyle({ + align: layout.textAlign + }); + if (notShowLabel) { + each$4(label.states, setNotShow); + label.ignore = true; + } + var selectState = label.states.select; + if (selectState) { + selectState.x += label.x; + selectState.y += label.y; + } + } + if (labelLine) { + var linePoints = layout.linePoints; + if (notShowLabel || !linePoints) { + each$4(labelLine.states, setNotShow); + labelLine.ignore = true; + } else { + limitTurnAngle(linePoints, layout.minTurnAngle); + limitSurfaceAngle(linePoints, layout.surfaceNormal, layout.maxSurfaceAngle); + labelLine.setShape({ + points: linePoints + }); + // Set the anchor to the midpoint of sector + label.__hostTarget.textGuideLineConfig = { + anchor: new Point(linePoints[0][0], linePoints[0][1]) + }; + } + } + } +} + +/** + * Piece of pie including Sector, Label, LabelLine + */ +var PiePiece = /** @class */function (_super) { + __extends(PiePiece, _super); + function PiePiece(data, idx, startAngle) { + var _this = _super.call(this) || this; + _this.z2 = 2; + var text = new ZRText(); + _this.setTextContent(text); + _this.updateData(data, idx, startAngle, true); + return _this; + } + PiePiece.prototype.updateData = function (data, idx, startAngle, firstCreate) { + var sector = this; + var seriesModel = data.hostModel; + var itemModel = data.getItemModel(idx); + var emphasisModel = itemModel.getModel('emphasis'); + var layout = data.getItemLayout(idx); + // cornerRadius & innerCornerRadius doesn't exist in the item layout. Use `0` if null value is specified. + // see `setItemLayout` in `pieLayout.ts`. + var sectorShape = extend(getSectorCornerRadius(itemModel.getModel('itemStyle'), layout, true), layout); + // Ignore NaN data. + if (isNaN(sectorShape.startAngle)) { + // Use NaN shape to avoid drawing shape. + sector.setShape(sectorShape); + return; + } + if (firstCreate) { + sector.setShape(sectorShape); + var animationType = seriesModel.getShallow('animationType'); + if (seriesModel.ecModel.ssr) { + // Use scale animation in SSR mode(opacity?) + // Because CSS SVG animation doesn't support very customized shape animation. + initProps(sector, { + scaleX: 0, + scaleY: 0 + }, seriesModel, { + dataIndex: idx, + isFrom: true + }); + sector.originX = sectorShape.cx; + sector.originY = sectorShape.cy; + } else if (animationType === 'scale') { + sector.shape.r = layout.r0; + initProps(sector, { + shape: { + r: layout.r + } + }, seriesModel, idx); + } + // Expansion + else { + if (startAngle != null) { + sector.setShape({ + startAngle: startAngle, + endAngle: startAngle + }); + initProps(sector, { + shape: { + startAngle: layout.startAngle, + endAngle: layout.endAngle + } + }, seriesModel, idx); + } else { + sector.shape.endAngle = layout.startAngle; + updateProps$1(sector, { + shape: { + endAngle: layout.endAngle + } + }, seriesModel, idx); + } + } + } else { + saveOldStyle(sector); + // Transition animation from the old shape + updateProps$1(sector, { + shape: sectorShape + }, seriesModel, idx); + } + sector.useStyle(data.getItemVisual(idx, 'style')); + setStatesStylesFromModel(sector, itemModel); + var midAngle = (layout.startAngle + layout.endAngle) / 2; + var offset = seriesModel.get('selectedOffset'); + var dx = Math.cos(midAngle) * offset; + var dy = Math.sin(midAngle) * offset; + var cursorStyle = itemModel.getShallow('cursor'); + cursorStyle && sector.attr('cursor', cursorStyle); + this._updateLabel(seriesModel, data, idx); + sector.ensureState('emphasis').shape = extend({ + r: layout.r + (emphasisModel.get('scale') ? emphasisModel.get('scaleSize') || 0 : 0) + }, getSectorCornerRadius(emphasisModel.getModel('itemStyle'), layout)); + extend(sector.ensureState('select'), { + x: dx, + y: dy, + shape: getSectorCornerRadius(itemModel.getModel(['select', 'itemStyle']), layout) + }); + extend(sector.ensureState('blur'), { + shape: getSectorCornerRadius(itemModel.getModel(['blur', 'itemStyle']), layout) + }); + var labelLine = sector.getTextGuideLine(); + var labelText = sector.getTextContent(); + labelLine && extend(labelLine.ensureState('select'), { + x: dx, + y: dy + }); + // TODO: needs dx, dy in zrender? + extend(labelText.ensureState('select'), { + x: dx, + y: dy + }); + toggleHoverEmphasis(this, emphasisModel.get('focus'), emphasisModel.get('blurScope'), emphasisModel.get('disabled')); + }; + PiePiece.prototype._updateLabel = function (seriesModel, data, idx) { + var sector = this; + var itemModel = data.getItemModel(idx); + var labelLineModel = itemModel.getModel('labelLine'); + var style = data.getItemVisual(idx, 'style'); + var visualColor = style && style.fill; + var visualOpacity = style && style.opacity; + setLabelStyle(sector, getLabelStatesModels(itemModel), { + labelFetcher: data.hostModel, + labelDataIndex: idx, + inheritColor: visualColor, + defaultOpacity: visualOpacity, + defaultText: seriesModel.getFormattedLabel(idx, 'normal') || data.getName(idx) + }); + var labelText = sector.getTextContent(); + // Set textConfig on sector. + sector.setTextConfig({ + // reset position, rotation + position: null, + rotation: null + }); + // Make sure update style on labelText after setLabelStyle. + // Because setLabelStyle will replace a new style on it. + labelText.attr({ + z2: 10 + }); + var labelPosition = seriesModel.get(['label', 'position']); + if (labelPosition !== 'outside' && labelPosition !== 'outer') { + sector.removeTextGuideLine(); + } else { + var polyline = this.getTextGuideLine(); + if (!polyline) { + polyline = new Polyline(); + this.setTextGuideLine(polyline); + } + // Default use item visual color + setLabelLineStyle(this, getLabelLineStatesModels(itemModel), { + stroke: visualColor, + opacity: retrieve3(labelLineModel.get(['lineStyle', 'opacity']), visualOpacity, 1) + }); + } + }; + return PiePiece; +}(Sector); +// Pie view +var PieView = /** @class */function (_super) { + __extends(PieView, _super); + function PieView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.ignoreLabelLineUpdate = true; + return _this; + } + PieView.prototype.render = function (seriesModel, ecModel, api, payload) { + var data = seriesModel.getData(); + var oldData = this._data; + var group = this.group; + var startAngle; + // First render + if (!oldData && data.count() > 0) { + var shape = data.getItemLayout(0); + for (var s = 1; isNaN(shape && shape.startAngle) && s < data.count(); ++s) { + shape = data.getItemLayout(s); + } + if (shape) { + startAngle = shape.startAngle; + } + } + // remove empty-circle if it exists + if (this._emptyCircleSector) { + group.remove(this._emptyCircleSector); + } + // when all data are filtered, show lightgray empty circle + if (data.count() === 0 && seriesModel.get('showEmptyCircle')) { + var layoutData = getSeriesLayoutData(seriesModel); + var sector = new Sector({ + shape: extend(getBasicPieLayout(seriesModel, api), layoutData) + }); + sector.useStyle(seriesModel.getModel('emptyCircleStyle').getItemStyle()); + this._emptyCircleSector = sector; + group.add(sector); + } + data.diff(oldData).add(function (idx) { + var piePiece = new PiePiece(data, idx, startAngle); + data.setItemGraphicEl(idx, piePiece); + group.add(piePiece); + }).update(function (newIdx, oldIdx) { + var piePiece = oldData.getItemGraphicEl(oldIdx); + piePiece.updateData(data, newIdx, startAngle); + piePiece.off('click'); + group.add(piePiece); + data.setItemGraphicEl(newIdx, piePiece); + }).remove(function (idx) { + var piePiece = oldData.getItemGraphicEl(idx); + removeElementWithFadeOut(piePiece, seriesModel, idx); + }).execute(); + pieLabelLayout(seriesModel); + // Always use initial animation. + if (seriesModel.get('animationTypeUpdate') !== 'expansion') { + this._data = data; + } + }; + PieView.prototype.dispose = function () {}; + PieView.prototype.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; + } + }; + PieView.type = 'pie'; + return PieView; +}(ChartView); + +/** + * [Usage]: + * (1) + * createListSimply(seriesModel, ['value']); + * (2) + * createListSimply(seriesModel, { + * coordDimensions: ['value'], + * dimensionsCount: 5 + * }); + */ +function createSeriesDataSimply(seriesModel, opt, nameList) { + opt = isArray(opt) && { + coordDimensions: opt + } || extend({ + encodeDefine: seriesModel.getEncode() + }, opt); + var source = seriesModel.getSource(); + var dimensions = prepareSeriesDataSchema(source, opt).dimensions; + var list = new SeriesData(dimensions, 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. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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. + */ +var LegendVisualProvider = /** @class */function () { + function LegendVisualProvider( + // Function to get data after filtered. It stores all the encoding info + getDataWithEncodedVisual, + // Function to get raw data before filtered. + getRawData) { + this._getDataWithEncodedVisual = getDataWithEncodedVisual; + this._getRawData = getRawData; + } + LegendVisualProvider.prototype.getAllNames = function () { + var rawData = this._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); + }; + LegendVisualProvider.prototype.containName = function (name) { + var rawData = this._getRawData(); + return rawData.indexOfName(name) >= 0; + }; + LegendVisualProvider.prototype.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 = this._getDataWithEncodedVisual(); + return dataWithEncodedVisual.indexOfName(name); + }; + LegendVisualProvider.prototype.getItemVisual = function (dataIndex, key) { + // Get encoded visual properties from final filtered data. + var dataWithEncodedVisual = this._getDataWithEncodedVisual(); + return dataWithEncodedVisual.getItemVisual(dataIndex, key); + }; + return LegendVisualProvider; +}(); + +var innerData = makeInner(); +var PieSeriesModel = /** @class */function (_super) { + __extends(PieSeriesModel, _super); + function PieSeriesModel() { + return _super !== null && _super.apply(this, arguments) || this; + } + /** + * @overwrite + */ + PieSeriesModel.prototype.init = function (option) { + _super.prototype.init.apply(this, 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$1(this.getData, this), bind$1(this.getRawData, this)); + this._defaultLabelLine(option); + }; + /** + * @overwrite + */ + PieSeriesModel.prototype.mergeOption = function () { + _super.prototype.mergeOption.apply(this, arguments); + }; + /** + * @overwrite + */ + PieSeriesModel.prototype.getInitialData = function () { + return createSeriesDataSimply(this, { + coordDimensions: ['value'], + encodeDefaulter: curry$1(makeSeriesEncodeForNameBased, this) + }); + }; + /** + * @overwrite + */ + PieSeriesModel.prototype.getDataParams = function (dataIndex) { + var data = this.getData(); + // update seats when data is changed + var dataInner = innerData(data); + var seats = dataInner.seats; + if (!seats) { + var valueList_1 = []; + data.each(data.mapDimension('value'), function (value) { + valueList_1.push(value); + }); + seats = dataInner.seats = getPercentSeats(valueList_1, data.hostModel.get('percentPrecision')); + } + var params = _super.prototype.getDataParams.call(this, dataIndex); + // seats may be empty when sum is 0 + params.percent = seats[dataIndex] || 0; + params.$vars.push('percent'); + return params; + }; + PieSeriesModel.prototype._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; + }; + PieSeriesModel.type = 'series.pie'; + PieSeriesModel.defaultOption = { + // zlevel: 0, + z: 2, + legendHoverLink: true, + colorBy: 'data', + // 默认全局居中 + center: ['50%', '50%'], + radius: [0, '75%'], + // 默认顺时针 + clockwise: true, + startAngle: 90, + endAngle: 'auto', + padAngle: 0, + // 最小角度改为0 + minAngle: 0, + // If the angle of a sector less than `minShowLabelAngle`, + // the label will not be displayed. + minShowLabelAngle: 0, + // 选中时扇区偏移量 + selectedOffset: 10, + // 选择模式,默认关闭,可选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: { + // color: 'inherit', + // If rotate around circle + rotate: 0, + show: true, + overflow: 'truncate', + // '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'. + edgeDistance: '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, + minTurnAngle: 90, + maxSurfaceAngle: 90, + lineStyle: { + // color: 各异, + width: 1, + type: 'solid' + } + }, + itemStyle: { + borderWidth: 1, + borderJoin: 'round' + }, + showEmptyCircle: true, + emptyCircleStyle: { + color: 'lightgray', + opacity: 1 + }, + labelLayout: { + // Hide the overlapped label. + hideOverlap: true + }, + emphasis: { + scale: true, + scaleSize: 5 + }, + // If use strategy to avoid label overlapping + avoidLabelOverlap: true, + // Animation type. Valid values: expansion, scale + animationType: 'expansion', + animationDuration: 1000, + // Animation type when update. Valid values: transition, expansion + animationTypeUpdate: 'transition', + animationEasingUpdate: 'cubicInOut', + animationDurationUpdate: 500, + animationEasing: 'cubicInOut' + }; + return PieSeriesModel; +}(SeriesModel); + +function negativeDataFilter(seriesType) { + return { + seriesType: seriesType, + reset: function (seriesModel, ecModel) { + var data = seriesModel.getData(); + data.filterSelf(function (idx) { + // handle negative value condition + var valueDim = data.mapDimension('value'); + var curValue = data.get(valueDim, idx); + if (isNumber(curValue) && !isNaN(curValue) && curValue < 0) { + return false; + } + return true; + }); + } + }; +} + +function install$8(registers) { + registers.registerChartView(PieView); + registers.registerSeriesModel(PieSeriesModel); + createLegacyDataSelectAction('pie', registers.registerAction); + registers.registerLayout(curry$1(pieLayout, 'pie')); + registers.registerProcessor(dataFilter('pie')); + registers.registerProcessor(negativeDataFilter('pie')); +} + +var GridModel = /** @class */function (_super) { + __extends(GridModel, _super); + function GridModel() { + return _super !== null && _super.apply(this, arguments) || this; + } + GridModel.type = 'grid'; + GridModel.dependencies = ['xAxis', 'yAxis']; + GridModel.layoutMode = 'box'; + GridModel.defaultOption = { + show: false, + // zlevel: 0, + z: 0, + left: '10%', + top: 60, + right: '10%', + bottom: 70, + // 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' + }; + return GridModel; +}(ComponentModel); + +var CartesianAxisModel = /** @class */function (_super) { + __extends(CartesianAxisModel, _super); + function CartesianAxisModel() { + return _super !== null && _super.apply(this, arguments) || this; + } + CartesianAxisModel.prototype.getCoordSysModel = function () { + return this.getReferringComponents('grid', SINGLE_REFERRING).models[0]; + }; + CartesianAxisModel.type = 'cartesian2dAxis'; + return CartesianAxisModel; +}(ComponentModel); +mixin(CartesianAxisModel, AxisModelCommonMixin); + +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 default 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: '#6E7079', + 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, + showMinLine: true, + showMaxLine: true, + lineStyle: { + color: ['#E0E6F1'], + width: 1, + type: 'solid' + } + }, + splitArea: { + show: false, + areaStyle: { + color: ['rgba(250,250,250,0.2)', 'rgba(210,219,238,0.2)'] + } + } +}; +var categoryAxis = merge({ + // The gap at both ends of the axis. For categoryAxis, boolean. + boundaryGap: true, + // Set false to faster category collection. + 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); +var valueAxis = merge({ + boundaryGap: [0, 0], + axisLine: { + // Not shown when other axis is categoryAxis in cartesian + show: 'auto' + }, + axisTick: { + // Not shown when other axis is categoryAxis in cartesian + show: 'auto' + }, + // TODO + // min/max: [30, datamin, 60] or [20, datamin] or [datamin, 60] + splitNumber: 5, + 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, + // Length of minor tick + length: 3, + // Line style + lineStyle: { + // Default to be same with axisTick + } + }, + minorSplitLine: { + show: false, + lineStyle: { + color: '#F4F7FD', + width: 1 + } + } +}, defaultOption); +var timeAxis = merge({ + splitNumber: 6, + axisLabel: { + // To eliminate labels that are not nice + showMinLabel: false, + showMaxLabel: false, + rich: { + primary: { + fontWeight: 'bold' + } + } + }, + splitLine: { + show: false + } +}, valueAxis); +var logAxis = defaults({ + logBase: 10 +}, valueAxis); +const axisDefault = { + category: categoryAxis, + value: valueAxis, + time: timeAxis, + log: logAxis +}; + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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_TYPES = { + value: 1, + category: 1, + time: 1, + log: 1 +}; + +/** + * Generate sub axis model class + * @param axisName 'x' 'y' 'radius' 'angle' 'parallel' ... + */ +function axisModelCreator(registers, axisName, BaseAxisModelClass, extraDefaultOption) { + each$4(AXIS_TYPES, function (v, axisType) { + var defaultOption = merge(merge({}, axisDefault[axisType], true), extraDefaultOption, true); + var AxisModel = /** @class */function (_super) { + __extends(AxisModel, _super); + function AxisModel() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = axisName + 'Axis.' + axisType; + return _this; + } + AxisModel.prototype.mergeDefaultAndTheme = function (option, ecModel) { + var layoutMode = fetchLayoutMode(this); + var inputPositionParams = layoutMode ? getLayoutParams(option) : {}; + var themeModel = ecModel.getTheme(); + merge(option, themeModel.get(axisType + 'Axis')); + merge(option, this.getDefaultOption()); + option.type = getAxisType(option); + if (layoutMode) { + mergeLayoutParam(option, inputPositionParams, layoutMode); + } + }; + AxisModel.prototype.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. + */ + AxisModel.prototype.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; + } + }; + AxisModel.prototype.getOrdinalMeta = function () { + return this.__ordinalMeta; + }; + AxisModel.type = axisName + 'Axis.' + axisType; + AxisModel.defaultOption = defaultOption; + return AxisModel; + }(BaseAxisModelClass); + registers.registerComponentModel(AxisModel); + }); + registers.registerSubTypeDefaulter(axisName + 'Axis', getAxisType); +} +function getAxisType(option) { + // Default axis with data is category axis + return option.type || (option.data ? 'category' : 'value'); +} + +var Cartesian = /** @class */function () { + function Cartesian(name) { + this.type = 'cartesian'; + this._dimList = []; + this._axes = {}; + this.name = name || ''; + } + Cartesian.prototype.getAxis = function (dim) { + return this._axes[dim]; + }; + Cartesian.prototype.getAxes = function () { + return map$1(this._dimList, function (dim) { + return this._axes[dim]; + }, this); + }; + Cartesian.prototype.getAxesByScale = function (scaleType) { + scaleType = scaleType.toLowerCase(); + return filter(this.getAxes(), function (axis) { + return axis.scale.type === scaleType; + }); + }; + Cartesian.prototype.addAxis = function (axis) { + var dim = axis.dim; + this._axes[dim] = axis; + this._dimList.push(dim); + }; + return Cartesian; +}(); + +var cartesian2DDimensions = ['x', 'y']; +function canCalculateAffineTransform(scale) { + return scale.type === 'interval' || scale.type === 'time'; +} +var Cartesian2D = /** @class */function (_super) { + __extends(Cartesian2D, _super); + function Cartesian2D() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = 'cartesian2d'; + _this.dimensions = cartesian2DDimensions; + return _this; + } + /** + * Calculate an affine transform matrix if two axes are time or value. + * It's mainly for accelartion on the large time series data. + */ + Cartesian2D.prototype.calcAffineTransform = function () { + this._transform = this._invTransform = null; + var xAxisScale = this.getAxis('x').scale; + var yAxisScale = this.getAxis('y').scale; + if (!canCalculateAffineTransform(xAxisScale) || !canCalculateAffineTransform(yAxisScale)) { + return; + } + var xScaleExtent = xAxisScale.getExtent(); + var yScaleExtent = yAxisScale.getExtent(); + var start = this.dataToPoint([xScaleExtent[0], yScaleExtent[0]]); + var end = this.dataToPoint([xScaleExtent[1], yScaleExtent[1]]); + var xScaleSpan = xScaleExtent[1] - xScaleExtent[0]; + var yScaleSpan = yScaleExtent[1] - yScaleExtent[0]; + if (!xScaleSpan || !yScaleSpan) { + return; + } + // Accelerate data to point calculation on the special large time series data. + var scaleX = (end[0] - start[0]) / xScaleSpan; + var scaleY = (end[1] - start[1]) / yScaleSpan; + var translateX = start[0] - xScaleExtent[0] * scaleX; + var translateY = start[1] - yScaleExtent[0] * scaleY; + var m = this._transform = [scaleX, 0, 0, scaleY, translateX, translateY]; + this._invTransform = invert([], m); + }; + /** + * Base axis will be used on stacking. + */ + Cartesian2D.prototype.getBaseAxis = function () { + return this.getAxesByScale('ordinal')[0] || this.getAxesByScale('time')[0] || this.getAxis('x'); + }; + Cartesian2D.prototype.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])); + }; + Cartesian2D.prototype.containData = function (data) { + return this.getAxis('x').containData(data[0]) && this.getAxis('y').containData(data[1]); + }; + Cartesian2D.prototype.containZone = function (data1, data2) { + var zoneDiag1 = this.dataToPoint(data1); + var zoneDiag2 = this.dataToPoint(data2); + var area = this.getArea(); + var zone = new BoundingRect(zoneDiag1[0], zoneDiag1[1], zoneDiag2[0] - zoneDiag1[0], zoneDiag2[1] - zoneDiag1[1]); + return area.intersect(zone); + }; + Cartesian2D.prototype.dataToPoint = function (data, clamp, out) { + out = out || []; + var xVal = data[0]; + var yVal = data[1]; + // Fast path + if (this._transform + // It's supported that if data is like `[Inifity, 123]`, where only Y pixel calculated. + && xVal != null && isFinite(xVal) && yVal != null && isFinite(yVal)) { + return applyTransform$1(out, data, this._transform); + } + var xAxis = this.getAxis('x'); + var yAxis = this.getAxis('y'); + out[0] = xAxis.toGlobalCoord(xAxis.dataToCoord(xVal, clamp)); + out[1] = yAxis.toGlobalCoord(yAxis.dataToCoord(yVal, clamp)); + return out; + }; + Cartesian2D.prototype.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; + }; + Cartesian2D.prototype.pointToData = function (point, clamp) { + var out = []; + if (this._invTransform) { + return applyTransform$1(out, point, this._invTransform); + } + var xAxis = this.getAxis('x'); + var yAxis = this.getAxis('y'); + out[0] = xAxis.coordToData(xAxis.toLocalCoord(point[0]), clamp); + out[1] = yAxis.coordToData(yAxis.toLocalCoord(point[1]), clamp); + return out; + }; + Cartesian2D.prototype.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. + */ + Cartesian2D.prototype.getArea = function (tolerance) { + tolerance = tolerance || 0; + var xExtent = this.getAxis('x').getGlobalExtent(); + var yExtent = this.getAxis('y').getGlobalExtent(); + var x = Math.min(xExtent[0], xExtent[1]) - tolerance; + var y = Math.min(yExtent[0], yExtent[1]) - tolerance; + var width = Math.max(xExtent[0], xExtent[1]) - x + tolerance; + var height = Math.max(yExtent[0], yExtent[1]) - y + tolerance; + return new BoundingRect(x, y, width, height); + }; + return Cartesian2D; +}(Cartesian); + +var Axis2D = /** @class */function (_super) { + __extends(Axis2D, _super); + function Axis2D(dim, scale, coordExtent, axisType, position) { + var _this = _super.call(this, dim, scale, coordExtent) || this; + /** + * Index of axis, can be used as key + * Injected outside. + */ + _this.index = 0; + _this.type = axisType || 'value'; + _this.position = position || 'bottom'; + return _this; + } + Axis2D.prototype.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.} + */ + Axis2D.prototype.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; + }; + Axis2D.prototype.pointToData = function (point, clamp) { + return this.coordToData(this.toLocalCoord(point[this.dim === 'x' ? 0 : 1]), clamp); + }; + /** + * Set ordinalSortInfo + * @param info new OrdinalSortInfo + */ + Axis2D.prototype.setCategorySortInfo = function (info) { + if (this.type !== 'category') { + return false; + } + this.model.option.categorySortInfo = info; + this.scale.setSortInfo(info); + }; + return Axis2D; +}(Axis); + +function layout(gridModel, axisModel, opt) { + opt = opt || {}; + var grid = gridModel.coordinateSystem; + var axis = axisModel.axis; + var layout2 = {}; + 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]); + } + layout2.position = [axisDim === "y" ? posBound[idx[axisPosition]] : rectBound[0], axisDim === "x" ? posBound[idx[axisPosition]] : rectBound[3]]; + layout2.rotation = Math.PI / 2 * (axisDim === "x" ? 0 : 1); + var dirMap = { + top: -1, + bottom: 1, + left: -1, + right: 1 + }; + layout2.labelDirection = layout2.tickDirection = layout2.nameDirection = dirMap[rawAxisPosition]; + layout2.labelOffset = otherAxisOnZeroOf ? posBound[idx[rawAxisPosition]] - posBound[idx.onZero] : 0; + if (axisModel.get(["axisTick", "inside"])) { + layout2.tickDirection = -layout2.tickDirection; + } + if (retrieve(opt.labelInside, axisModel.get(["axisLabel", "inside"]))) { + layout2.labelDirection = -layout2.labelDirection; + } + var labelRotate = axisModel.get(["axisLabel", "rotate"]); + layout2.labelRotate = axisPosition === "top" ? -labelRotate : labelRotate; + layout2.z2 = 1; + return layout2; +} +function isCartesian2DSeries(seriesModel) { + return seriesModel.get("coordinateSystem") === "cartesian2d"; +} +function findAxisModels(seriesModel) { + var axisModelMap = { + xAxisModel: null, + yAxisModel: null + }; + each$4(axisModelMap, function(v, key) { + var axisType = key.replace(/Model$/, ""); + var axisModel = seriesModel.getReferringComponents(axisType, SINGLE_REFERRING).models[0]; + axisModelMap[key] = axisModel; + }); + return axisModelMap; +} + +var mathLog = Math.log; +function alignScaleTicks(scale, axisModel, alignToScale) { + var intervalScaleProto = IntervalScale.prototype; + var alignToTicks = intervalScaleProto.getTicks.call(alignToScale); + var alignToNicedTicks = intervalScaleProto.getTicks.call(alignToScale, true); + var alignToSplitNumber = alignToTicks.length - 1; + var alignToInterval = intervalScaleProto.getInterval.call(alignToScale); + var scaleExtent = getScaleExtent(scale, axisModel); + var rawExtent = scaleExtent.extent; + var isMinFixed = scaleExtent.fixMin; + var isMaxFixed = scaleExtent.fixMax; + if (scale.type === "log") { + var logBase = mathLog(scale.base); + rawExtent = [mathLog(rawExtent[0]) / logBase, mathLog(rawExtent[1]) / logBase]; + } + scale.setExtent(rawExtent[0], rawExtent[1]); + scale.calcNiceExtent({ + splitNumber: alignToSplitNumber, + fixMin: isMinFixed, + fixMax: isMaxFixed + }); + var extent = intervalScaleProto.getExtent.call(scale); + if (isMinFixed) { + rawExtent[0] = extent[0]; + } + if (isMaxFixed) { + rawExtent[1] = extent[1]; + } + var interval = intervalScaleProto.getInterval.call(scale); + var min = rawExtent[0]; + var max = rawExtent[1]; + if (isMinFixed && isMaxFixed) { + interval = (max - min) / alignToSplitNumber; + } else if (isMinFixed) { + max = rawExtent[0] + interval * alignToSplitNumber; + while (max < rawExtent[1] && isFinite(max) && isFinite(rawExtent[1])) { + interval = increaseInterval(interval); + max = rawExtent[0] + interval * alignToSplitNumber; + } + } else if (isMaxFixed) { + min = rawExtent[1] - interval * alignToSplitNumber; + while (min > rawExtent[0] && isFinite(min) && isFinite(rawExtent[0])) { + interval = increaseInterval(interval); + min = rawExtent[1] - interval * alignToSplitNumber; + } + } else { + var nicedSplitNumber = scale.getTicks().length - 1; + if (nicedSplitNumber > alignToSplitNumber) { + interval = increaseInterval(interval); + } + var range = interval * alignToSplitNumber; + max = Math.ceil(rawExtent[1] / interval) * interval; + min = round$1(max - range); + if (min < 0 && rawExtent[0] >= 0) { + min = 0; + max = round$1(range); + } else if (max > 0 && rawExtent[1] <= 0) { + max = 0; + min = -round$1(range); + } + } + var t0 = (alignToTicks[0].value - alignToNicedTicks[0].value) / alignToInterval; + var t1 = (alignToTicks[alignToSplitNumber].value - alignToNicedTicks[alignToSplitNumber].value) / alignToInterval; + intervalScaleProto.setExtent.call(scale, min + interval * t0, max + interval * t1); + intervalScaleProto.setInterval.call(scale, interval); + if (t0 || t1) { + intervalScaleProto.setNiceExtent.call(scale, min + interval, max - interval); + } +} + +var Grid = ( + /** @class */ + function() { + function Grid2(gridModel, ecModel, api) { + this.type = "grid"; + this._coordsMap = {}; + this._coordsList = []; + this._axesMap = {}; + this._axesList = []; + this.axisPointerEnabled = true; + this.dimensions = cartesian2DDimensions; + this._initCartesian(gridModel, ecModel, api); + this.model = gridModel; + } + Grid2.prototype.getRect = function() { + return this._rect; + }; + Grid2.prototype.update = function(ecModel, api) { + var axesMap = this._axesMap; + this._updateScale(ecModel, this.model); + function updateAxisTicks(axes) { + var alignTo; + var axesIndices = keys(axes); + var len = axesIndices.length; + if (!len) { + return; + } + var axisNeedsAlign = []; + for (var i = len - 1; i >= 0; i--) { + var idx = +axesIndices[i]; + var axis = axes[idx]; + var model = axis.model; + var scale = axis.scale; + if ( + // Only value and log axis without interval support alignTicks. + isIntervalOrLogScale(scale) && model.get("alignTicks") && model.get("interval") == null + ) { + axisNeedsAlign.push(axis); + } else { + niceScaleExtent(scale, model); + if (isIntervalOrLogScale(scale)) { + alignTo = axis; + } + } + } + if (axisNeedsAlign.length) { + if (!alignTo) { + alignTo = axisNeedsAlign.pop(); + niceScaleExtent(alignTo.scale, alignTo.model); + } + each$4(axisNeedsAlign, function(axis2) { + alignScaleTicks(axis2.scale, axis2.model, alignTo.scale); + }); + } + } + updateAxisTicks(axesMap.x); + updateAxisTicks(axesMap.y); + var onZeroRecords = {}; + each$4(axesMap.x, function(xAxis) { + fixAxisOnZero(axesMap, "y", xAxis, onZeroRecords); + }); + each$4(axesMap.y, function(yAxis) { + fixAxisOnZero(axesMap, "x", yAxis, onZeroRecords); + }); + this.resize(this.model, api); + }; + Grid2.prototype.resize = function(gridModel, api, ignoreContainLabel) { + var boxLayoutParams = gridModel.getBoxLayoutParams(); + var isContainLabel = !ignoreContainLabel && gridModel.get("containLabel"); + var gridRect = getLayoutRect(boxLayoutParams, { + width: api.getWidth(), + height: api.getHeight() + }); + this._rect = gridRect; + var axesList = this._axesList; + adjustAxes(); + if (isContainLabel) { + each$4(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(); + } + each$4(this._coordsList, function(coord) { + coord.calcAffineTransform(); + }); + function adjustAxes() { + each$4(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); + }); + } + }; + Grid2.prototype.getAxis = function(dim, axisIndex) { + var axesMapOnDim = this._axesMap[dim]; + if (axesMapOnDim != null) { + return axesMapOnDim[axisIndex || 0]; + } + }; + Grid2.prototype.getAxes = function() { + return this._axesList.slice(); + }; + Grid2.prototype.getCartesian = function(xAxisIndex, yAxisIndex) { + if (xAxisIndex != null && yAxisIndex != null) { + var key = "x" + xAxisIndex + "y" + yAxisIndex; + return this._coordsMap[key]; + } + if (isObject$2(xAxisIndex)) { + yAxisIndex = xAxisIndex.yAxisIndex; + xAxisIndex = xAxisIndex.xAxisIndex; + } + 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]; + } + } + }; + Grid2.prototype.getCartesians = function() { + return this._coordsList.slice(); + }; + Grid2.prototype.convertToPixel = function(ecModel, finder, value) { + var target = this._findConvertTarget(finder); + return target.cartesian ? target.cartesian.dataToPoint(value) : target.axis ? target.axis.toGlobalCoord(target.axis.dataToCoord(value)) : null; + }; + Grid2.prototype.convertFromPixel = function(ecModel, finder, value) { + var target = this._findConvertTarget(finder); + return target.cartesian ? target.cartesian.pointToData(value) : target.axis ? target.axis.coordToData(target.axis.toLocalCoord(value)) : null; + }; + Grid2.prototype._findConvertTarget = function(finder) { + var seriesModel = finder.seriesModel; + var xAxisModel = finder.xAxisModel || seriesModel && seriesModel.getReferringComponents("xAxis", SINGLE_REFERRING).models[0]; + var yAxisModel = finder.yAxisModel || seriesModel && seriesModel.getReferringComponents("yAxis", SINGLE_REFERRING).models[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); + } else if (gridModel) { + var grid = gridModel.coordinateSystem; + if (grid === this) { + cartesian = this._coordsList[0]; + } + } + return { + cartesian, + axis + }; + }; + Grid2.prototype.containPoint = function(point) { + var coord = this._coordsList[0]; + if (coord) { + return coord.containPoint(point); + } + }; + Grid2.prototype._initCartesian = function(gridModel, ecModel, api) { + var _this = this; + var grid = this; + var axisPositionUsed = { + left: false, + right: false, + top: false, + bottom: false + }; + var axesMap = { + x: {}, + y: {} + }; + var axesCount = { + x: 0, + y: 0 + }; + ecModel.eachComponent("xAxis", createAxisCreator("x"), this); + ecModel.eachComponent("yAxis", createAxisCreator("y"), this); + if (!axesCount.x || !axesCount.y) { + this._axesMap = {}; + this._axesList = []; + return; + } + this._axesMap = axesMap; + each$4(axesMap.x, function(xAxis, xAxisIndex) { + each$4(axesMap.y, function(yAxis, yAxisIndex) { + var key = "x" + xAxisIndex + "y" + yAxisIndex; + var cartesian = new Cartesian2D(key); + cartesian.master = _this; + cartesian.model = gridModel; + _this._coordsMap[key] = cartesian; + _this._coordsList.push(cartesian); + cartesian.addAxis(xAxis); + cartesian.addAxis(yAxis); + }); + }); + function createAxisCreator(dimName) { + return function(axisModel, idx) { + if (!isAxisUsedInTheGrid(axisModel, gridModel)) { + return; + } + var axisPosition = axisModel.get("position"); + if (dimName === "x") { + if (axisPosition !== "top" && axisPosition !== "bottom") { + axisPosition = axisPositionUsed.bottom ? "top" : "bottom"; + } + } else { + if (axisPosition !== "left" && axisPosition !== "right") { + axisPosition = axisPositionUsed.left ? "right" : "left"; + } + } + axisPositionUsed[axisPosition] = true; + var axis = new Axis2D(dimName, createScaleByModel(axisModel), [0, 0], axisModel.get("type"), axisPosition); + var isCategory = axis.type === "category"; + axis.onBand = isCategory && axisModel.get("boundaryGap"); + axis.inverse = axisModel.get("inverse"); + axisModel.axis = axis; + axis.model = axisModel; + axis.grid = grid; + axis.index = idx; + grid._axesList.push(axis); + axesMap[dimName][idx] = axis; + axesCount[dimName]++; + }; + } + }; + Grid2.prototype._updateScale = function(ecModel, gridModel) { + each$4(this._axesList, function(axis) { + axis.scale.setExtent(Infinity, -Infinity); + if (axis.type === "category") { + var categorySortInfo = axis.model.get("categorySortInfo"); + axis.scale.setSortInfo(categorySortInfo); + } + }); + ecModel.eachSeries(function(seriesModel) { + if (isCartesian2DSeries(seriesModel)) { + var axesModelMap = findAxisModels(seriesModel); + var xAxisModel = axesModelMap.xAxisModel; + var yAxisModel = axesModelMap.yAxisModel; + if (!isAxisUsedInTheGrid(xAxisModel, gridModel) || !isAxisUsedInTheGrid(yAxisModel, gridModel)) { + return; + } + var cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex); + var data = seriesModel.getData(); + var xAxis = cartesian.getAxis("x"); + var yAxis = cartesian.getAxis("y"); + unionExtent(data, xAxis); + unionExtent(data, yAxis); + } + }, this); + function unionExtent(data, axis) { + each$4(getDataDimensionsOnAxis(data, axis.dim), function(dim) { + axis.scale.unionExtentFromData(data, dim); + }); + } + }; + Grid2.prototype.getTooltipAxes = function(dim) { + var baseAxes = []; + var otherAxes = []; + each$4(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, + otherAxes + }; + }; + Grid2.create = function(ecModel, api) { + var grids = []; + ecModel.eachComponent("grid", function(gridModel, idx) { + var grid = new Grid2(gridModel, ecModel, api); + grid.name = "grid_" + idx; + grid.resize(gridModel, api, true); + gridModel.coordinateSystem = grid; + grids.push(grid); + }); + ecModel.eachSeries(function(seriesModel) { + if (!isCartesian2DSeries(seriesModel)) { + return; + } + var axesModelMap = findAxisModels(seriesModel); + var xAxisModel = axesModelMap.xAxisModel; + var yAxisModel = axesModelMap.yAxisModel; + var gridModel = xAxisModel.getCoordSysModel(); + var grid = gridModel.coordinateSystem; + seriesModel.coordinateSystem = grid.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex); + }); + return grids; + }; + Grid2.dimensions = cartesian2DDimensions; + return Grid2; + }() +); +function isAxisUsedInTheGrid(axisModel, gridModel) { + return axisModel.getCoordSysModel() === gridModel; +} +function fixAxisOnZero(axesMap, otherAxisDim, axis, onZeroRecords) { + axis.getAxesOnZeroOf = function() { + return otherAxisOnZeroOf ? [otherAxisOnZeroOf] : []; + }; + 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 (onZeroAxisIndex != null) { + if (canOnZeroToAxis(otherAxes[onZeroAxisIndex])) { + otherAxisOnZeroOf = otherAxes[onZeroAxisIndex]; + } + } else { + for (var idx in otherAxes) { + if (otherAxes.hasOwnProperty(idx) && canOnZeroToAxis(otherAxes[idx]) && !onZeroRecords[getOnZeroRecordKey(otherAxes[idx])]) { + otherAxisOnZeroOf = otherAxes[idx]; + break; + } + } + } + if (otherAxisOnZeroOf) { + onZeroRecords[getOnZeroRecordKey(otherAxisOnZeroOf)] = true; + } + function getOnZeroRecordKey(axis2) { + return axis2.dim + "_" + axis2.index; + } +} +function canOnZeroToAxis(axis) { + return axis && axis.type !== "category" && axis.type !== "time" && ifAxisCrossZero(axis); +} +function updateAxisTransform(axis, coordBase) { + var axisExtent = axis.getExtent(); + var axisExtentSum = axisExtent[0] + axisExtent[1]; + 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 PI = 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. + */ +var AxisBuilder = /** @class */function () { + function AxisBuilder(axisModel, opt) { + this.group = new Group$2(); + this.opt = opt; + this.axisModel = axisModel; + // Default value + defaults(opt, { + labelOffset: 0, + nameDirection: 1, + tickDirection: 1, + labelDirection: 1, + silent: true, + handleAutoShown: function () { + return true; + } + }); + // FIXME Not use a separate text group? + var transformGroup = new Group$2({ + x: opt.position[0], + y: opt.position[1], + rotation: opt.rotation + }); + // this.group.add(transformGroup); + // this._transformGroup = transformGroup; + transformGroup.updateTransform(); + this._transformGroup = transformGroup; + } + AxisBuilder.prototype.hasBuilder = function (name) { + return !!builders[name]; + }; + AxisBuilder.prototype.add = function (name) { + builders[name](this.opt, this.axisModel, this.group, this._transformGroup); + }; + AxisBuilder.prototype.getGroup = function () { + return this.group; + }; + 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)) { + // Label is inverse parallel with axis line. + textVerticalAlign = direction > 0 ? 'bottom' : 'top'; + textAlign = 'center'; + } else { + textVerticalAlign = 'middle'; + if (rotationDiff > 0 && rotationDiff < PI) { + textAlign = direction > 0 ? 'right' : 'left'; + } else { + textAlign = direction > 0 ? 'left' : 'right'; + } + } + return { + rotation: rotationDiff, + textAlign: textAlign, + textVerticalAlign: textVerticalAlign + }; + }; + AxisBuilder.makeAxisEventDataBase = function (axisModel) { + var eventData = { + componentType: axisModel.mainType, + componentIndex: axisModel.componentIndex + }; + eventData[axisModel.mainType + 'Index'] = axisModel.componentIndex; + return eventData; + }; + 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); + }; + return AxisBuilder; +}(); +var builders = { + axisLine: function (opt, axisModel, group, transformGroup) { + var shown = axisModel.get(['axisLine', 'show']); + if (shown === 'auto' && opt.handleAutoShown) { + shown = opt.handleAutoShown('axisLine'); + } + if (!shown) { + return; + } + var extent = axisModel.axis.getExtent(); + var matrix = transformGroup.transform; + var pt1 = [extent[0], 0]; + var pt2 = [extent[1], 0]; + var inverse = pt1[0] > pt2[0]; + if (matrix) { + applyTransform$1(pt1, pt1, matrix); + applyTransform$1(pt2, pt2, matrix); + } + var lineStyle = extend({ + lineCap: 'round' + }, axisModel.getModel(['axisLine', 'lineStyle']).getLineStyle()); + var line = new Line({ + shape: { + x1: pt1[0], + y1: pt1[1], + x2: pt2[0], + y2: pt2[1] + }, + style: lineStyle, + strokeContainThreshold: opt.strokeContainThreshold || 5, + silent: true, + z2: 1 + }); + subPixelOptimizeLine(line.shape, line.style.lineWidth); + line.anid = 'line'; + group.add(line); + var arrows = axisModel.get(['axisLine', 'symbol']); + if (arrows != null) { + var arrowSize = axisModel.get(['axisLine', 'symbolSize']); + if (isString(arrows)) { + // Use the same arrow for start and end point + arrows = [arrows, arrows]; + } + if (isString(arrowSize) || isNumber(arrowSize)) { + // Use the same size for width and height + arrowSize = [arrowSize, arrowSize]; + } + var arrowOffset = normalizeSymbolOffset(axisModel.get(['axisLine', 'symbolOffset']) || 0, arrowSize); + var symbolWidth_1 = arrowSize[0]; + var symbolHeight_1 = arrowSize[1]; + each$4([{ + 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_1 / 2, -symbolHeight_1 / 2, symbolWidth_1, symbolHeight_1, lineStyle.stroke, true); + // Calculate arrow position with offset + var r = point.r + point.offset; + var pt = inverse ? pt2 : pt1; + symbol.attr({ + rotation: point.rotate, + x: pt[0] + r * Math.cos(opt.rotation), + y: pt[1] - r * Math.sin(opt.rotation), + silent: true, + z2: 11 + }); + group.add(symbol); + } + }); + } + }, + axisTickLabel: function (opt, axisModel, group, transformGroup) { + var ticksEls = buildAxisMajorTicks(group, transformGroup, axisModel, opt); + var labelEls = buildAxisLabel(group, transformGroup, axisModel, opt); + fixMinMaxLabelShow(axisModel, labelEls, ticksEls); + buildAxisMinorTicks(group, transformGroup, axisModel, opt.tickDirection); + // This bit fixes the label overlap issue for the time chart. + // See https://github.com/apache/echarts/issues/14266 for more. + if (axisModel.get(['axisLabel', 'hideOverlap'])) { + var labelList = prepareLayoutList(map$1(labelEls, function (label) { + return { + label: label, + priority: label.z2, + defaultAttr: { + ignore: label.ignore + } + }; + })); + hideOverlap(labelList); + } + }, + axisName: function (opt, axisModel, group, transformGroup) { + 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 = 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, + // Reuse labelOffset. + isNameLocationCenter(nameLocation) ? opt.labelOffset + nameDirection * gap : 0]; + var labelLayout; + var nameRotation = axisModel.get('nameRotate'); + if (nameRotation != null) { + nameRotation = nameRotation * PI / 180; // To radian. + } + var axisNameAvailableWidth; + if (isNameLocationCenter(nameLocation)) { + labelLayout = AxisBuilder.innerTextLayout(opt.rotation, nameRotation != null ? nameRotation : opt.rotation, + // Adapt to axis. + nameDirection); + } else { + labelLayout = endTextLayout(opt.rotation, 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); + var textEl = new ZRText({ + x: pos[0], + y: pos[1], + rotation: labelLayout.rotation, + silent: AxisBuilder.isLabelSilent(axisModel), + style: createTextStyle(textStyleModel, { + text: name, + font: textFont, + overflow: 'truncate', + width: maxWidth, + ellipsis: ellipsis, + fill: textStyleModel.getTextColor() || axisModel.get(['axisLine', 'lineStyle', 'color']), + align: textStyleModel.get('align') || labelLayout.textAlign, + verticalAlign: textStyleModel.get('verticalAlign') || labelLayout.textVerticalAlign + }), + z2: 1 + }); + setTooltipConfig({ + el: textEl, + componentModel: axisModel, + itemName: name + }); + textEl.__fullText = name; + // Id for animation + textEl.anid = 'name'; + if (axisModel.get('triggerEvent')) { + var eventData = AxisBuilder.makeAxisEventDataBase(axisModel); + eventData.targetType = 'axisName'; + eventData.name = name; + getECData(textEl).eventData = eventData; + } + // FIXME + transformGroup.add(textEl); + textEl.updateTransform(); + group.add(textEl); + textEl.decomposeTransform(); + } +}; +function endTextLayout(rotation, textPosition, textRotate, extent) { + var rotationDiff = remRadian(textRotate - rotation); + var textAlign; + var textVerticalAlign; + var inverse = extent[0] > extent[1]; + var onLeft = textPosition === 'start' && !inverse || textPosition !== 'start' && inverse; + if (isRadianAroundZero(rotationDiff - PI / 2)) { + textVerticalAlign = onLeft ? 'bottom' : 'top'; + textAlign = 'center'; + } else if (isRadianAroundZero(rotationDiff - PI * 1.5)) { + textVerticalAlign = onLeft ? 'top' : 'bottom'; + textAlign = 'center'; + } else { + textVerticalAlign = 'middle'; + if (rotationDiff < PI * 1.5 && rotationDiff > PI / 2) { + textAlign = onLeft ? 'left' : 'right'; + } else { + textAlign = onLeft ? 'right' : 'left'; + } + } + return { + rotation: rotationDiff, + textAlign: textAlign, + textVerticalAlign: textVerticalAlign + }; +} +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) { + // 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([], mRotationBack, current.getLocalTransform())); + nextRect.applyTransform(mul([], mRotationBack, next.getLocalTransform())); + return firstRect.intersect(nextRect); +} +function isNameLocationCenter(nameLocation) { + return nameLocation === 'middle' || nameLocation === 'center'; +} +function createTicks(ticksCoords, tickTransform, tickEndCoord, tickLineStyle, anidPrefix) { + 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$1(pt1, pt1, tickTransform); + applyTransform$1(pt2, pt2, tickTransform); + } + // Tick line, Not use group transform to have better line draw + var tickEl = new Line({ + shape: { + x1: pt1[0], + y1: pt1[1], + x2: pt2[0], + y2: pt2[1] + }, + style: tickLineStyle, + z2: 2, + autoBatch: true, + silent: true + }); + subPixelOptimizeLine(tickEl.shape, tickEl.style.lineWidth); + tickEl.anid = anidPrefix + '_' + ticksCoords[i].tickValue; + tickEls.push(tickEl); + } + return tickEls; +} +function buildAxisMajorTicks(group, transformGroup, axisModel, opt) { + var axis = axisModel.axis; + var tickModel = axisModel.getModel('axisTick'); + var shown = tickModel.get('show'); + if (shown === 'auto' && opt.handleAutoShown) { + shown = opt.handleAutoShown('axisTick'); + } + if (!shown || axis.scale.isBlank()) { + return; + } + var lineStyleModel = tickModel.getModel('lineStyle'); + var tickEndCoord = opt.tickDirection * tickModel.get('length'); + var ticksCoords = axis.getTicksCoords(); + var ticksEls = createTicks(ticksCoords, transformGroup.transform, tickEndCoord, defaults(lineStyleModel.getLineStyle(), { + stroke: axisModel.get(['axisLine', 'lineStyle', 'color']) + }), 'ticks'); + for (var i = 0; i < ticksEls.length; i++) { + group.add(ticksEls[i]); + } + return ticksEls; +} +function buildAxisMinorTicks(group, transformGroup, axisModel, tickDirection) { + 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 = 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], transformGroup.transform, tickEndCoord, minorTickLineStyle, 'minorticks_' + i); + for (var k = 0; k < minorTicksEls.length; k++) { + group.add(minorTicksEls[k]); + } + } +} +function buildAxisLabel(group, transformGroup, 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 / 180; + var labelLayout = AxisBuilder.innerTextLayout(opt.rotation, labelRotation, opt.labelDirection); + var rawCategoryData = axisModel.getCategories && axisModel.getCategories(true); + var labelEls = []; + var silent = AxisBuilder.isLabelSilent(axisModel); + var triggerEvent = axisModel.get('triggerEvent'); + each$4(labels, function (labelItem, index) { + var tickValue = axis.scale.type === 'ordinal' ? axis.scale.getRawOrdinalNumber(labelItem.tickValue) : labelItem.tickValue; + var formattedLabel = labelItem.formattedLabel; + var rawLabel = labelItem.rawLabel; + var itemLabelModel = labelModel; + if (rawCategoryData && rawCategoryData[tickValue]) { + var rawCategoryItem = rawCategoryData[tickValue]; + if (isObject$2(rawCategoryItem) && rawCategoryItem.textStyle) { + itemLabelModel = new Model(rawCategoryItem.textStyle, labelModel, axisModel.ecModel); + } + } + var textColor = itemLabelModel.getTextColor() || axisModel.get(['axisLine', 'lineStyle', 'color']); + var tickCoord = axis.dataToCoord(tickValue); + var align = itemLabelModel.getShallow('align', true) || labelLayout.textAlign; + var alignMin = retrieve2(itemLabelModel.getShallow('alignMinLabel', true), align); + var alignMax = retrieve2(itemLabelModel.getShallow('alignMaxLabel', true), align); + var verticalAlign = itemLabelModel.getShallow('verticalAlign', true) || itemLabelModel.getShallow('baseline', true) || labelLayout.textVerticalAlign; + var verticalAlignMin = retrieve2(itemLabelModel.getShallow('verticalAlignMinLabel', true), verticalAlign); + var verticalAlignMax = retrieve2(itemLabelModel.getShallow('verticalAlignMaxLabel', true), verticalAlign); + var textEl = new ZRText({ + x: tickCoord, + y: opt.labelOffset + opt.labelDirection * labelMargin, + rotation: labelLayout.rotation, + silent: silent, + z2: 10 + (labelItem.level || 0), + style: createTextStyle(itemLabelModel, { + text: formattedLabel, + align: index === 0 ? alignMin : index === labels.length - 1 ? alignMax : align, + verticalAlign: index === 0 ? verticalAlignMin : index === labels.length - 1 ? verticalAlignMax : verticalAlign, + fill: isFunction(textColor) ? 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 replace ','. 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 + }) + }); + textEl.anid = 'label_' + tickValue; + setTooltipConfig({ + el: textEl, + componentModel: axisModel, + itemName: formattedLabel, + formatterParamsExtra: { + isTruncated: function () { + return textEl.isTruncated; + }, + value: rawLabel, + tickIndex: index + } + }); + // Pack data for mouse event + if (triggerEvent) { + var eventData = AxisBuilder.makeAxisEventDataBase(axisModel); + eventData.targetType = 'axisLabel'; + eventData.value = rawLabel; + eventData.tickIndex = index; + if (axis.type === 'category') { + eventData.dataIndex = tickValue; + } + getECData(textEl).eventData = eventData; + } + // FIXME + transformGroup.add(textEl); + textEl.updateTransform(); + labelEls.push(textEl); + group.add(textEl); + textEl.decomposeTransform(); + }); + return labelEls; +} + +// 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, + * triggerEmphasis, + * 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$4(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 convenient way to show axisPointer + // for user. So we enable setting tooltip on coordSys model. + var coordSysModel = coordSys.model; + var baseTooltipModel = coordSysModel.getModel('tooltip', globalTooltipModel); + each$4(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$4(tooltipAxes.baseAxes, curry$1(saveTooltipAxisInfo, cross ? 'cross' : true, triggerAxis)); + } + if (cross) { + each$4(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 triggerEmphasis = axisPointerModel.get('triggerEmphasis'); + var axisKey = 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[axisKey] = { + key: axisKey, + axis: axis, + coordSys: coordSys, + axisPointerModel: axisPointerModel, + triggerTooltip: triggerTooltip, + triggerEmphasis: triggerEmphasis, + involveSeries: involveSeries, + snap: snap, + useHandle: isHandleTrigger(axisPointerModel), + seriesModels: [], + linkGroup: null + }; + axesInfoInCoordSys[axisKey] = axisInfo; + result.seriesInvolved = result.seriesInvolved || involveSeries; + var groupIndex = getLinkGroupIndex(linksOption, axis); + if (groupIndex != null) { + var linkGroup = linkGroups[groupIndex] || (linkGroups[groupIndex] = { + axesInfo: {} + }); + linkGroup.axesInfo[axisKey] = axisInfo; + linkGroup.mapper = linksOption[groupIndex].mapper; + axisInfo.linkGroup = linkGroup; + } + } + }); +} +function makeAxisPointerModel(axis, baseTooltipModel, globalAxisPointerModel, ecModel, fromTooltip, triggerTooltip) { + var tooltipAxisPointerModel = baseTooltipModel.getModel('axisPointer'); + var fields = ['type', 'snap', 'lineStyle', 'shadowStyle', 'label', 'animation', 'animationDurationUpdate', 'animationEasingUpdate', 'z']; + var volatileOption = {}; + each$4(fields, function (field) { + volatileOption[field] = clone$2(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; + // Compatible with previous behavior, tooltip axis does not show label by default. + // Only these properties can be overridden 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$4(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(); + } + }); + }); +} +/** + * 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; +} + +var axisPointerClazz = {}; +var AxisView = ( + /** @class */ + function(_super) { + __extends(AxisView2, _super); + function AxisView2() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = AxisView2.type; + return _this; + } + AxisView2.prototype.render = function(axisModel, ecModel, api, payload) { + this.axisPointerClass && fixValue(axisModel); + _super.prototype.render.apply(this, arguments); + this._doUpdateAxisPointerClass(axisModel, api, true); + }; + AxisView2.prototype.updateAxisPointer = function(axisModel, ecModel, api, payload) { + this._doUpdateAxisPointerClass(axisModel, api, false); + }; + AxisView2.prototype.remove = function(ecModel, api) { + var axisPointer = this._axisPointer; + axisPointer && axisPointer.remove(api); + }; + AxisView2.prototype.dispose = function(ecModel, api) { + this._disposeAxisPointer(api); + _super.prototype.dispose.apply(this, arguments); + }; + AxisView2.prototype._doUpdateAxisPointerClass = function(axisModel, api, forceRender) { + var Clazz = AxisView2.getAxisPointerClass(this.axisPointerClass); + if (!Clazz) { + return; + } + var axisPointerModel = getAxisPointerModel(axisModel); + axisPointerModel ? (this._axisPointer || (this._axisPointer = new Clazz())).render(axisModel, axisPointerModel, api, forceRender) : this._disposeAxisPointer(api); + }; + AxisView2.prototype._disposeAxisPointer = function(api) { + this._axisPointer && this._axisPointer.dispose(api); + this._axisPointer = null; + }; + AxisView2.registerAxisPointerClass = function(type, clazz) { + axisPointerClazz[type] = clazz; + }; + AxisView2.getAxisPointerClass = function(type) { + return type && axisPointerClazz[type]; + }; + AxisView2.type = "axis"; + return AxisView2; + }(ComponentView) +); + +var inner$3 = makeInner(); +function rectCoordAxisBuildSplitArea(axisView, axisGroup, axisModel, gridModel) { + var axis = axisModel.axis; + if (axis.scale.isBlank()) { + return; + } + // TODO: TYPE + 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 = inner$3(axisView).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 = void 0; + var y = void 0; + var width = void 0; + var height = void 0; + 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); + 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), + autoBatch: true, + silent: true + })); + colorIndex = (colorIndex + 1) % areaColorsLen; + } + inner$3(axisView).splitAreaColors = newSplitAreaColors; +} +function rectCoordAxisHandleRemove(axisView) { + inner$3(axisView).splitAreaColors = null; +} + +var axisBuilderAttrs = ['axisLine', 'axisTickLabel', 'axisName']; +var selfBuilderAttrs = ['splitArea', 'splitLine', 'minorSplitLine']; +var CartesianAxisView = /** @class */function (_super) { + __extends(CartesianAxisView, _super); + function CartesianAxisView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = CartesianAxisView.type; + _this.axisPointerClass = 'CartesianAxisPointer'; + return _this; + } + /** + * @override + */ + CartesianAxisView.prototype.render = function (axisModel, ecModel, api, payload) { + this.group.removeAll(); + var oldAxisGroup = this._axisGroup; + this._axisGroup = new Group$2(); + this.group.add(this._axisGroup); + if (!axisModel.get('show')) { + return; + } + var gridModel = axisModel.getCoordSysModel(); + var layout$1 = layout(gridModel, axisModel); + var axisBuilder = new AxisBuilder(axisModel, extend({ + handleAutoShown: function (elementType) { + var cartesians = gridModel.coordinateSystem.getCartesians(); + for (var i = 0; i < cartesians.length; i++) { + if (isIntervalOrLogScale(cartesians[i].getOtherAxis(axisModel.axis).scale)) { + // Still show axis tick or axisLine if other axis is value / log + return true; + } + } + // Not show axisTick or axisLine if other axis is category / time + return false; + } + }, layout$1)); + each$4(axisBuilderAttrs, axisBuilder.add, axisBuilder); + this._axisGroup.add(axisBuilder.getGroup()); + each$4(selfBuilderAttrs, function (name) { + if (axisModel.get([name, 'show'])) { + axisElementBuilders[name](this, this._axisGroup, axisModel, gridModel); + } + }, this); + // THIS is a special case for bar racing chart. + // Update the axis label from the natural initial layout to + // sorted layout should has no animation. + var isInitialSortFromBarRacing = payload && payload.type === 'changeAxisOrder' && payload.isInitSort; + if (!isInitialSortFromBarRacing) { + groupTransition(oldAxisGroup, this._axisGroup, axisModel); + } + _super.prototype.render.call(this, axisModel, ecModel, api, payload); + }; + CartesianAxisView.prototype.remove = function () { + rectCoordAxisHandleRemove(this); + }; + CartesianAxisView.type = 'cartesianAxis'; + return CartesianAxisView; +}(AxisView); +var axisElementBuilders = { + splitLine: function (axisView, axisGroup, 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'); + var showMinLine = splitLineModel.get('showMinLine') !== false; + var showMaxLine = splitLineModel.get('showMaxLine') !== false; + 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 (i === 0 && !showMinLine || i === ticksCoords.length - 1 && !showMaxLine) { + continue; + } + var tickValue = ticksCoords[i].tickValue; + 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 line = new Line({ + anid: tickValue != null ? 'line_' + tickValue : null, + autoBatch: true, + shape: { + x1: p1[0], + y1: p1[1], + x2: p2[0], + y2: p2[1] + }, + style: defaults({ + stroke: lineColors[colorIndex] + }, lineStyle), + silent: true + }); + subPixelOptimizeLine(line.shape, lineStyle.lineWidth); + axisGroup.add(line); + } + }, + minorSplitLine: function (axisView, axisGroup, 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; + } + var line = new Line({ + anid: 'minor_line_' + minorTicksCoords[i][k].tickValue, + autoBatch: true, + shape: { + x1: p1[0], + y1: p1[1], + x2: p2[0], + y2: p2[1] + }, + style: lineStyle, + silent: true + }); + subPixelOptimizeLine(line.shape, lineStyle.lineWidth); + axisGroup.add(line); + } + } + }, + splitArea: function (axisView, axisGroup, axisModel, gridModel) { + rectCoordAxisBuildSplitArea(axisView, axisGroup, axisModel, gridModel); + } +}; +var CartesianXAxisView = /** @class */function (_super) { + __extends(CartesianXAxisView, _super); + function CartesianXAxisView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = CartesianXAxisView.type; + return _this; + } + CartesianXAxisView.type = 'xAxis'; + return CartesianXAxisView; +}(CartesianAxisView); +var CartesianYAxisView = /** @class */function (_super) { + __extends(CartesianYAxisView, _super); + function CartesianYAxisView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = CartesianXAxisView.type; + return _this; + } + CartesianYAxisView.type = 'yAxis'; + return CartesianYAxisView; +}(CartesianAxisView); + +// Grid view +var GridView = /** @class */function (_super) { + __extends(GridView, _super); + function GridView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = 'grid'; + return _this; + } + GridView.prototype.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 + })); + } + }; + GridView.type = 'grid'; + return GridView; +}(ComponentView); +var extraOption = { + // gridIndex: 0, + // gridId: '', + offset: 0 +}; +function install$7(registers) { + registers.registerComponentView(GridView); + registers.registerComponentModel(GridModel); + registers.registerCoordinateSystem('cartesian2d', Grid); + axisModelCreator(registers, 'x', CartesianAxisModel, extraOption); + axisModelCreator(registers, 'y', CartesianAxisModel, extraOption); + registers.registerComponentView(CartesianXAxisView); + registers.registerComponentView(CartesianYAxisView); + registers.registerPreprocessor(function (option) { + // Only create grid when need + if (option.xAxis && option.yAxis && !option.grid) { + option.grid = {}; + } + }); +} + +var inner$2 = makeInner(); +var clone = clone$2; +var bind = bind$1; +/** + * Base axis pointer class in 2D. + */ +var BaseAxisPointer = /** @class */function () { + function BaseAxisPointer() { + this._dragging = false; + /** + * In px, arbitrary value. Do not set too small, + * no animation is ok for most cases. + */ + this.animationThreshold = 15; + } + /** + * @implement + */ + BaseAxisPointer.prototype.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 repeatedly 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$2(); + this.createPointerEl(group, elOption, axisModel, axisPointerModel); + this.createLabelEl(group, elOption, axisModel, axisPointerModel); + api.getZr().add(group); + } else { + var doUpdateProps = curry$1(updateProps, axisPointerModel, moveAnimation); + this.updatePointerEl(group, elOption, doUpdateProps); + this.updateLabelEl(group, elOption, doUpdateProps, axisPointerModel); + } + updateMandatoryProps(group, axisPointerModel, true); + this._renderHandle(value); + }; + /** + * @implement + */ + BaseAxisPointer.prototype.remove = function (api) { + this.clear(api); + }; + /** + * @implement + */ + BaseAxisPointer.prototype.dispose = function (api) { + this.clear(api); + }; + /** + * @protected + */ + BaseAxisPointer.prototype.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 + */ + BaseAxisPointer.prototype.makeElOption = function (elOption, value, axisModel, axisPointerModel, api) { + // Should be implemenented by sub-class. + }; + /** + * @protected + */ + BaseAxisPointer.prototype.createPointerEl = function (group, elOption, axisModel, axisPointerModel) { + var pointerOption = elOption.pointer; + if (pointerOption) { + var pointerEl = inner$2(group).pointerEl = new graphic[pointerOption.type](clone(elOption.pointer)); + group.add(pointerEl); + } + }; + /** + * @protected + */ + BaseAxisPointer.prototype.createLabelEl = function (group, elOption, axisModel, axisPointerModel) { + if (elOption.label) { + var labelEl = inner$2(group).labelEl = new ZRText(clone(elOption.label)); + group.add(labelEl); + updateLabelShowHide(labelEl, axisPointerModel); + } + }; + /** + * @protected + */ + BaseAxisPointer.prototype.updatePointerEl = function (group, elOption, updateProps) { + var pointerEl = inner$2(group).pointerEl; + if (pointerEl && elOption.pointer) { + pointerEl.setStyle(elOption.pointer.style); + updateProps(pointerEl, { + shape: elOption.pointer.shape + }); + } + }; + /** + * @protected + */ + BaseAxisPointer.prototype.updateLabelEl = function (group, elOption, updateProps, axisPointerModel) { + var labelEl = inner$2(group).labelEl; + if (labelEl) { + labelEl.setStyle(elOption.label.style); + updateProps(labelEl, { + // Consider text length change in vertical axis, animation should + // be used on shape, otherwise the effect will be weird. + // TODOTODO + // shape: elOption.label.shape, + x: elOption.label.x, + y: elOption.label.y + }); + updateLabelShowHide(labelEl, axisPointerModel); + } + }; + /** + * @private + */ + BaseAxisPointer.prototype._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) { + // For mobile device, prevent screen slider on the button. + stop(e.event); + }, + onmousedown: bind(this._onHandleDragMove, this, 0, 0), + drift: bind(this._onHandleDragMove, this), + ondragend: bind(this._onHandleDragEnd, this) + }); + zr.add(handle); + } + updateMandatoryProps(handle, axisPointerModel, false); + // update style + handle.setStyle(handleModel.getItemStyle(null, ['color', 'borderColor', 'borderWidth', 'opacity', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'])); + // update position + var handleSize = handleModel.get('size'); + if (!isArray(handleSize)) { + handleSize = [handleSize, handleSize]; + } + handle.scaleX = handleSize[0] / 2; + handle.scaleY = handleSize[1] / 2; + createOrUpdate(this, '_doDispatchAxisPointer', handleModel.get('throttle') || 0, 'fixRate'); + this._moveHandleToValue(value, isInit); + }; + BaseAxisPointer.prototype._moveHandleToValue = function (value, isInit) { + updateProps(this._axisPointerModel, !isInit && this._moveAnimation, this._handle, getHandleTransProps(this.getHandleTransform(value, this._axisModel, this._axisPointerModel))); + }; + BaseAxisPointer.prototype._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$2(handle).lastProp = null; + this._doDispatchAxisPointer(); + }; + /** + * Throttled method. + */ + BaseAxisPointer.prototype._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 + }] + }); + }; + BaseAxisPointer.prototype._onHandleDragEnd = function () { + 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' + }); + }; + /** + * @private + */ + BaseAxisPointer.prototype.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; + } + clear(this, '_doDispatchAxisPointer'); + }; + /** + * @protected + */ + BaseAxisPointer.prototype.doClear = function () { + // Implemented by sub-class if necessary. + }; + BaseAxisPointer.prototype.buildLabel = function (xy, wh, xDimIndex) { + xDimIndex = xDimIndex || 0; + return { + x: xy[xDimIndex], + y: xy[1 - xDimIndex], + width: wh[xDimIndex], + height: wh[1 - xDimIndex] + }; + }; + return BaseAxisPointer; +}(); +function updateProps(animationModel, moveAnimation, el, props) { + // Animation optimize. + if (!propsEqual(inner$2(el).lastProp, props)) { + inner$2(el).lastProp = props; + moveAnimation ? updateProps$1(el, props, animationModel) : (el.stopAnimation(), el.attr(props)); + } +} +function propsEqual(lastProps, newProps) { + if (isObject$2(lastProps) && isObject$2(newProps)) { + var equals_1 = true; + each$4(newProps, function (item, key) { + equals_1 = equals_1 && propsEqual(lastProps[key], item); + }); + return !!equals_1; + } else { + return lastProps === newProps; + } +} +function updateLabelShowHide(labelEl, axisPointerModel) { + labelEl[axisPointerModel.get(['label', 'show']) ? 'show' : 'hide'](); +} +function getHandleTransProps(trans) { + return { + x: trans.x || 0, + y: trans.y || 0, + 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; + } + }); +} + +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(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')}, + x: position[0], + y: position[1], + style: createTextStyle(labelModel, { + text: text, + font: font, + fill: labelModel.getTextColor(), + padding: paddings, + backgroundColor: bgColor + }), + // Label 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); +} +function getValueLabel(value, axis, ecModel, seriesDataIndices, opt) { + value = axis.scale.parse(value); + var text = axis.scale.getLabel({ + value: value + }, { + // If `precision` is set, width can be fixed (like '12.00500'), which + // helps to debounce when when moving label. + precision: opt.precision + }); + var formatter = opt.formatter; + if (formatter) { + var params_1 = { + value: getAxisRawValue(axis, { + value: value + }), + axisDimension: axis.dim, + axisIndex: axis.index, + seriesData: [] + }; + each$4(seriesDataIndices, function (idxItem) { + var series = ecModel.getSeriesByIndex(idxItem.seriesIndex); + var dataIndex = idxItem.dataIndexInside; + var dataParams = series && series.getDataParams(dataIndex); + dataParams && params_1.seriesData.push(dataParams); + }); + if (isString(formatter)) { + text = formatter.replace('{value}', text); + } else if (isFunction(formatter)) { + text = formatter(params_1); + } + } + return text; +} +function getTransformedPosition(axis, value, layoutInfo) { + var transform = create(); + rotate(transform, transform, layoutInfo.rotation); + translate(transform, transform, layoutInfo.position); + return applyTransform([axis.dataToCoord(value), (layoutInfo.labelOffset || 0) + (layoutInfo.labelDirection || 1) * (layoutInfo.labelMargin || 0)], transform); +} +function buildCartesianSingleLabelElOption(value, elOption, layoutInfo, axisModel, axisPointerModel, api) { + // @ts-ignore + 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 + }); +} +function makeLineShape(p1, p2, xDimIndex) { + xDimIndex = xDimIndex || 0; + return { + x1: p1[xDimIndex], + y1: p1[1 - xDimIndex], + x2: p2[xDimIndex], + y2: p2[1 - xDimIndex] + }; +} +function makeRectShape(xy, wh, xDimIndex) { + xDimIndex = xDimIndex || 0; + return { + x: xy[xDimIndex], + y: xy[1 - xDimIndex], + width: wh[xDimIndex], + height: wh[1 - xDimIndex] + }; +} + +var CartesianAxisPointer = /** @class */function (_super) { + __extends(CartesianAxisPointer, _super); + function CartesianAxisPointer() { + return _super !== null && _super.apply(this, arguments) || this; + } + /** + * @override + */ + CartesianAxisPointer.prototype.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(grid.model, axisModel); + buildCartesianSingleLabelElOption( + // @ts-ignore + value, elOption, layoutInfo, axisModel, axisPointerModel, api); + }; + /** + * @override + */ + CartesianAxisPointer.prototype.getHandleTransform = function (value, axisModel, axisPointerModel) { + var layoutInfo = layout(axisModel.axis.grid.model, axisModel, { + labelInside: false + }); + // @ts-ignore + layoutInfo.labelMargin = axisPointerModel.get(['handle', 'margin']); + var pos = getTransformedPosition(axisModel.axis, value, layoutInfo); + return { + x: pos[0], + y: pos[1], + rotation: layoutInfo.rotation + (layoutInfo.labelDirection < 0 ? Math.PI : 0) + }; + }; + /** + * @override + */ + CartesianAxisPointer.prototype.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.x, transform.y]; + 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 { + x: currPosition[0], + y: currPosition[1], + rotation: transform.rotation, + cursorPoint: cursorPoint, + tooltipOption: tooltipOptions[dimIndex] + }; + }; + return CartesianAxisPointer; +}(BaseAxisPointer); +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; +} + +var AxisPointerModel = /** @class */function (_super) { + __extends(AxisPointerModel, _super); + function AxisPointerModel() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = AxisPointerModel.type; + return _this; + } + AxisPointerModel.type = 'axisPointer'; + AxisPointerModel.defaultOption = { + // 'auto' means that show when triggered by tooltip or handle. + show: 'auto', + // zlevel: 0, + z: 50, + type: 'line', + // axispointer triggered by tootip determine snap automatically, + // see `modelHelper`. + snap: false, + triggerTooltip: true, + triggerEmphasis: true, + value: null, + status: null, + link: [], + // Do not set 'auto' here, otherwise global animation: false + // will not effect at this axispointer. + animation: null, + animationDurationUpdate: 200, + lineStyle: { + color: '#B9BEC9', + width: 1, + type: 'dashed' + }, + shadowStyle: { + color: 'rgba(210,219,238,0.2)' + }, + label: { + show: true, + formatter: null, + precision: 'auto', + margin: 3, + color: '#fff', + padding: [5, 7, 5, 7], + backgroundColor: 'auto', + borderColor: null, + borderWidth: 0, + borderRadius: 3 + }, + handle: { + show: false, + // eslint-disable-next-line + 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', + 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 + } + }; + return AxisPointerModel; +}(ComponentModel); + +var inner$1 = makeInner(); +var each$1 = each$4; +/** + * @param {string} key + * @param {module:echarts/ExtensionAPI} api + * @param {Function} handler + * param: {string} currTrigger + * param: {Array.} point + */ +function register(key, api, handler) { + if (env.node) { + return; + } + var zr = api.getZr(); + inner$1(zr).records || (inner$1(zr).records = {}); + initGlobalListeners(zr, api); + var record = inner$1(zr).records[key] || (inner$1(zr).records[key] = {}); + record.handler = handler; +} +function initGlobalListeners(zr, api) { + if (inner$1(zr).initialized) { + return; + } + inner$1(zr).initialized = true; + useHandler('click', curry$1(doEnter, 'click')); + useHandler('mousemove', curry$1(doEnter, 'mousemove')); + // useHandler('mouseout', onLeave); + useHandler('globalout', onLeave); + function useHandler(eventType, cb) { + zr.on(eventType, function (e) { + var dis = makeDispatchAction$1(api); + each$1(inner$1(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$1(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 + }; +} +function unregister(key, api) { + if (env.node) { + return; + } + var zr = api.getZr(); + var record = (inner$1(zr).records || {})[key]; + if (record) { + inner$1(zr).records[key] = null; + } +} + +var AxisPointerView = /** @class */function (_super) { + __extends(AxisPointerView, _super); + function AxisPointerView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = AxisPointerView.type; + return _this; + } + AxisPointerView.prototype.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 + }); + } + }); + }; + AxisPointerView.prototype.remove = function (ecModel, api) { + unregister('axisPointer', api); + }; + AxisPointerView.prototype.dispose = function (ecModel, api) { + unregister('axisPointer', api); + }; + AxisPointerView.type = 'axisPointer'; + return AxisPointerView; +}(ComponentView); + +/** + * @param finder contains {seriesIndex, dataIndex, dataIndexInside} + * @param ecModel + * @return {point: [x, y], el: ...} point Will not be null. + */ +function findPointFromSeries(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) { + if (finder.isStacked) { + var baseAxis = coordSys.getBaseAxis(); + var valueAxis = coordSys.getOtherAxis(baseAxis); + var valueAxisDim = valueAxis.dim; + var baseAxisDim = baseAxis.dim; + var baseDataOffset = valueAxisDim === 'x' || valueAxisDim === 'radius' ? 1 : 0; + var baseDim = data.mapDimension(baseAxisDim); + var stackedData = []; + stackedData[baseDataOffset] = data.get(baseDim, dataIndex); + stackedData[1 - baseDataOffset] = data.get(data.getCalculationInfo('stackResultDimension'), dataIndex); + point = coordSys.dataToPoint(stackedData) || []; + } else { + point = coordSys.dataToPoint(data.getValues(map$1(coordSys.dimensions, function (dim) { + return data.mapDimension(dim); + }), dataIndex)) || []; + } + } 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 + }; +} + +var inner = makeInner(); +/** + * Basic logic: check all axis, if they do not demand show/highlight, + * then hide/downplay them. + * + * @return content of event obj for echarts.connect. + */ +function axisTrigger(payload, ecModel, api) { + var currTrigger = payload.currTrigger; + var point = [payload.x, payload.y]; + var finder = payload; + var dispatchAction = payload.dispatchAction || bind$1(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 outputPayload = {}; + var showValueMap = {}; + var dataByCoordSys = { + list: [], + map: {} + }; + var updaters = { + showPointer: curry$1(showPointer, showValueMap), + showTooltip: curry$1(showTooltip, dataByCoordSys) + }; + // Process for triggered axes. + each$4(coordSysAxesInfo.coordSysMap, function (coordSys, coordSysKey) { + // If a point given, it must be contained by the coordinate system. + var coordSysContainsPoint = isIllegalPoint || coordSys.containPoint(point); + each$4(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, outputPayload); + } + }); + }); + // Process for linked axes. + var linkTriggers = {}; + each$4(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$4(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$4(linkTriggers, function (val, tarKey) { + processOnAxis(axesInfo[tarKey], val, updaters, true, outputPayload); + }); + updateModelActually(showValueMap, axesInfo, outputPayload); + dispatchTooltipActually(dataByCoordSys, point, payload, dispatchAction); + dispatchHighDownActually(axesInfo, dispatchAction, api); + return outputPayload; +} +function processOnAxis(axisInfo, newValue, updaters, noSnap, 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 default 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 (!noSnap && axisInfo.snap) { + if (axis.containData(snapToValue) && snapToValue != null) { + newValue = snapToValue; + } + } + updaters.showPointer(axisInfo, newValue, payloadBatch); + // 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$4(axisInfo.seriesModels, function (series, idx) { + var dataDim = series.getData().mapDimensionsAll(dim); + 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$4(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, outputPayload) { + var outputAxesInfo = outputPayload.axesInfo = []; + // Basic logic: If no 'show' required, 'hide' this axisPointer. + each$4(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 + // convenient to fetch payload.seriesIndex and payload.dataIndex + // directly. 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 should 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(zr)[highDownKey] || {}; + var newHighlights = inner(zr)[highDownKey] = {}; + // Update highlight/downplay status according to axisPointer model. + // Build hash map and remove duplicate incidentally. + each$4(axesInfo, function (axisInfo, key) { + var option = axisInfo.axisPointerModel.option; + option.status === 'show' && axisInfo.triggerEmphasis && each$4(option.seriesDataIndices, function (batchItem) { + var key = batchItem.seriesIndex + ' | ' + batchItem.dataIndex; + newHighlights[key] = batchItem; + }); + }); + // Diff. + var toHighlight = []; + var toDownplay = []; + each$4(lastHighlights, function (batchItem, key) { + !newHighlights[key] && toDownplay.push(batchItem); + }); + each$4(newHighlights, function (batchItem, key) { + !lastHighlights[key] && toHighlight.push(batchItem); + }); + toDownplay.length && api.dispatchAction({ + type: 'downplay', + escapeConnect: true, + // Not blur others when highlight in axisPointer. + notBlur: true, + batch: toDownplay + }); + toHighlight.length && api.dispatchAction({ + type: 'highlight', + escapeConnect: true, + // Not blur others when highlight in axisPointer. + notBlur: 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]); +} + +function install$6(registers) { + // CartesianAxisPointer is not supposed to be required here. But consider + // echarts.simple.js and online build tooltip, which only require gridSimple, + // CartesianAxisPointer should be able to required somewhere. + AxisView.registerAxisPointerClass('CartesianAxisPointer', CartesianAxisPointer); + registers.registerComponentModel(AxisPointerModel); + registers.registerComponentView(AxisPointerView); + registers.registerPreprocessor(function (option) { + // Always has a global axisPointerModel for default setting. + if (option) { + (!option.axisPointer || option.axisPointer.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. + registers.registerProcessor(registers.PRIORITY.PROCESSOR.STATISTIC, function (ecModel, api) { + // Build axisPointerModel, mergin tooltip.axisPointer model for each axis. + // allAxesInfo should be updated when setOption performed. + ecModel.getComponent('axisPointer').coordSysAxesInfo = collect(ecModel, api); + }); + // Broadcast to all views. + registers.registerAction({ + type: 'updateAxisPointer', + event: 'updateAxisPointer', + update: ':updateAxisPointer' + }, axisTrigger); +} + +function install$5(registers) { + use(install$7); + use(install$6); +} + +function makeBackground(rect, componentModel) { + var padding = normalizeCssArray(componentModel.get('padding')); + var style = componentModel.getItemStyle(['color', 'opacity']); + style.fill = componentModel.get('backgroundColor'); + 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; +} + +var TooltipModel = /** @class */function (_super) { + __extends(TooltipModel, _super); + function TooltipModel() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = TooltipModel.type; + return _this; + } + TooltipModel.type = 'tooltip'; + TooltipModel.dependencies = ['axisPointer']; + TooltipModel.defaultOption = { + // zlevel: 0, + z: 60, + show: true, + // tooltip main content + showContent: true, + // 'trigger' only works on coordinate system. + // 'item' | 'axis' | 'none' + trigger: 'item', + // 'click' | 'mousemove' | 'none' + triggerOn: 'mousemove|click', + alwaysShowContent: false, + displayMode: 'single', + renderMode: 'auto', + // whether restraint content inside viewRect. + // If renderMode: 'richText', default true. + // If renderMode: 'html', defaut false (for backward compat). + confine: null, + showDelay: 0, + hideDelay: 100, + // Animation transition time, unit is second + transitionDuration: 0.4, + enterable: false, + backgroundColor: '#fff', + // box shadow + shadowBlur: 10, + shadowColor: 'rgba(0, 0, 0, .2)', + shadowOffsetX: 1, + shadowOffsetY: 2, + // tooltip border radius, unit is px, default is 4 + borderRadius: 4, + // tooltip border width, unit is px, default is 0 (no border) + borderWidth: 1, + // Tooltip inside padding, default is 5 for all direction + // Array is allowed to set up, right, bottom, left, same with css + // The default value: See `tooltip/tooltipMarkup.ts#getPaddingFromTooltipModel`. + padding: null, + // Extra css text + extraCssText: '', + // axis indicator, trigger by axis + axisPointer: { + // default is line + // legal values: 'line' | 'shadow' | 'cross' + type: 'line', + // Valid when type is line, appoint tooltip line locate on which line. Optional + // legal values: 'x' | 'y' | 'angle' | 'radius' | 'auto' + // default is 'auto', chose the axis which type is category. + // for multiply y axis, cartesian coord chose x axis, polar chose angle axis + 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: '#666', + fontSize: 14 + } + }; + return TooltipModel; +}(ComponentModel); + +/* global document */ +function shouldTooltipConfine(tooltipModel) { + var confineOption = tooltipModel.get('confine'); + return confineOption != null ? !!confineOption + // In richText mode, the outside part can not be visible. + : tooltipModel.get('renderMode') === 'richText'; +} +function testStyle(styleProps) { + if (!env.domSupported) { + return; + } + var style = document.documentElement.style; + for (var i = 0, len = styleProps.length; i < len; i++) { + if (styleProps[i] in style) { + return styleProps[i]; + } + } +} +var TRANSFORM_VENDOR = testStyle(['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); +var TRANSITION_VENDOR = testStyle(['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); +function toCSSVendorPrefix(styleVendor, styleProp) { + if (!styleVendor) { + return styleProp; + } + styleProp = toCamelCase(styleProp, true); + var idx = styleVendor.indexOf(styleProp); + styleVendor = idx === -1 ? styleProp : "-" + styleVendor.slice(0, idx) + "-" + styleProp; + return styleVendor.toLowerCase(); +} +function getComputedStyle(el, style) { + var stl = el.currentStyle || document.defaultView && document.defaultView.getComputedStyle(el); + return stl ? stl[style] : null; +} + +/* global document, window */ +var CSS_TRANSITION_VENDOR = toCSSVendorPrefix(TRANSITION_VENDOR, 'transition'); +var CSS_TRANSFORM_VENDOR = toCSSVendorPrefix(TRANSFORM_VENDOR, 'transform'); +// eslint-disable-next-line +var gCssText = "position:absolute;display:block;border-style:solid;white-space:nowrap;z-index:9999999;" + (env.transform3dSupported ? 'will-change:transform;' : ''); +function mirrorPos(pos) { + pos = pos === 'left' ? 'right' : pos === 'right' ? 'left' : pos === 'top' ? 'bottom' : 'top'; + return pos; +} +function assembleArrow(tooltipModel, borderColor, arrowPosition) { + if (!isString(arrowPosition) || arrowPosition === 'inside') { + return ''; + } + var backgroundColor = tooltipModel.get('backgroundColor'); + var borderWidth = tooltipModel.get('borderWidth'); + borderColor = convertToColorString(borderColor); + var arrowPos = mirrorPos(arrowPosition); + var arrowSize = Math.max(Math.round(borderWidth) * 1.5, 6); + var positionStyle = ''; + var transformStyle = CSS_TRANSFORM_VENDOR + ':'; + var rotateDeg; + if (indexOf(['left', 'right'], arrowPos) > -1) { + positionStyle += 'top:50%'; + transformStyle += "translateY(-50%) rotate(" + (rotateDeg = arrowPos === 'left' ? -225 : -45) + "deg)"; + } else { + positionStyle += 'left:50%'; + transformStyle += "translateX(-50%) rotate(" + (rotateDeg = arrowPos === 'top' ? 225 : 45) + "deg)"; + } + var rotateRadian = rotateDeg * Math.PI / 180; + var arrowWH = arrowSize + borderWidth; + var rotatedWH = arrowWH * Math.abs(Math.cos(rotateRadian)) + arrowWH * Math.abs(Math.sin(rotateRadian)); + var arrowOffset = Math.round(((rotatedWH - Math.SQRT2 * borderWidth) / 2 + Math.SQRT2 * borderWidth - (rotatedWH - arrowWH) / 2) * 100) / 100; + positionStyle += ";" + arrowPos + ":-" + arrowOffset + "px"; + var borderStyle = borderColor + " solid " + borderWidth + "px;"; + var styleCss = ["position:absolute;width:" + arrowSize + "px;height:" + arrowSize + "px;z-index:-1;", positionStyle + ";" + transformStyle + ";", "border-bottom:" + borderStyle, "border-right:" + borderStyle, "background-color:" + backgroundColor + ";"]; + return "
"; +} +function assembleTransition(duration, onlyFade) { + var transitionCurve = 'cubic-bezier(0.23,1,0.32,1)'; + var transitionOption = " " + duration / 2 + "s " + transitionCurve; + var transitionText = "opacity" + transitionOption + ",visibility" + transitionOption; + if (!onlyFade) { + transitionOption = " " + duration + "s " + transitionCurve; + transitionText += env.transformSupported ? "," + CSS_TRANSFORM_VENDOR + transitionOption : ",left" + transitionOption + ",top" + transitionOption; + } + return CSS_TRANSITION_VENDOR + ':' + transitionText; +} +function assembleTransform(x, y, toString) { + // If using float on style, the final width of the dom might + // keep changing slightly while mouse move. So `toFixed(0)` them. + var x0 = x.toFixed(0) + 'px'; + var y0 = y.toFixed(0) + 'px'; + // not support transform, use `left` and `top` instead. + if (!env.transformSupported) { + return toString ? "top:" + y0 + ";left:" + x0 + ";" : [['top', y0], ['left', x0]]; + } + // support transform + var is3d = env.transform3dSupported; + var translate = "translate" + (is3d ? '3d' : '') + "(" + x0 + "," + y0 + (is3d ? ',0' : '') + ")"; + return toString ? 'top:0;left:0;' + CSS_TRANSFORM_VENDOR + ':' + translate + ';' : [['top', 0], ['left', 0], [TRANSFORM_VENDOR, translate]]; +} +/** + * @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()); + // @ts-ignore, leave it to the tooltip refactor. + var lineHeight = retrieve2(textStyleModel.get('lineHeight'), Math.round(fontSize * 3 / 2)); + fontSize && cssText.push('line-height:' + lineHeight + 'px'); + var shadowColor = textStyleModel.get('textShadowColor'); + var shadowBlur = textStyleModel.get('textShadowBlur') || 0; + var shadowOffsetX = textStyleModel.get('textShadowOffsetX') || 0; + var shadowOffsetY = textStyleModel.get('textShadowOffsetY') || 0; + shadowColor && shadowBlur && cssText.push('text-shadow:' + shadowOffsetX + 'px ' + shadowOffsetY + 'px ' + shadowBlur + 'px ' + shadowColor); + each$4(['decoration', 'align'], function (name) { + var val = textStyleModel.get(name); + val && cssText.push('text-' + name + ':' + val); + }); + return cssText.join(';'); +} +function assembleCssText(tooltipModel, enableTransition, onlyFade) { + var cssText = []; + var transitionDuration = tooltipModel.get('transitionDuration'); + var backgroundColor = tooltipModel.get('backgroundColor'); + var shadowBlur = tooltipModel.get('shadowBlur'); + var shadowColor = tooltipModel.get('shadowColor'); + var shadowOffsetX = tooltipModel.get('shadowOffsetX'); + var shadowOffsetY = tooltipModel.get('shadowOffsetY'); + var textStyleModel = tooltipModel.getModel('textStyle'); + var padding = getPaddingFromTooltipModel(tooltipModel, 'html'); + var boxShadow = shadowOffsetX + "px " + shadowOffsetY + "px " + shadowBlur + "px " + shadowColor; + cssText.push('box-shadow:' + boxShadow); + // Animation transition. Do not animate when transitionDuration is 0. + enableTransition && transitionDuration && cssText.push(assembleTransition(transitionDuration, onlyFade)); + if (backgroundColor) { + cssText.push('background-color:' + backgroundColor); + } + // Border style + each$4(['width', 'color', 'radius'], function (name) { + var borderName = 'border-' + name; + var camelCase = toCamelCase(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(padding).join('px ') + 'px'); + } + return cssText.join(';') + ';'; +} +// If not able to make, do not modify the input `out`. +function makeStyleCoord$1(out, zr, container, zrX, zrY) { + var zrPainter = zr && zr.painter; + if (container) { + var zrViewportRoot = zrPainter && zrPainter.getViewportRoot(); + if (zrViewportRoot) { + // Some APPs might use scale on body, so we support CSS transform here. + transformLocalCoord(out, zrViewportRoot, container, zrX, zrY); + } + } else { + out[0] = zrX; + out[1] = zrY; + // 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 viewportRootOffset = zrPainter && zrPainter.getViewportRootOffset(); + if (viewportRootOffset) { + out[0] += viewportRootOffset.offsetLeft; + out[1] += viewportRootOffset.offsetTop; + } + } + out[2] = out[0] / zr.getWidth(); + out[3] = out[1] / zr.getHeight(); +} +var TooltipHTMLContent = /** @class */function () { + function TooltipHTMLContent(api, opt) { + this._show = false; + this._styleCoord = [0, 0, 0, 0]; + this._enterable = true; + this._alwaysShowContent = false; + this._firstShow = true; + this._longHide = true; + if (env.wxa) { + return null; + } + var el = document.createElement('div'); + // TODO: TYPE + el.domBelongToZr = true; + this.el = el; + var zr = this._zr = api.getZr(); + var appendTo = opt.appendTo; + var container = appendTo && (isString(appendTo) ? document.querySelector(appendTo) : isDom(appendTo) ? appendTo : isFunction(appendTo) && appendTo(api.getDom())); + makeStyleCoord$1(this._styleCoord, zr, container, api.getWidth() / 2, api.getHeight() / 2); + (container || api.getDom()).appendChild(el); + this._api = api; + this._container = container; + // 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; + var zrViewportRoot = zr.painter.getViewportRoot(); + normalizeEvent(zrViewportRoot, e, true); + handler.dispatch('mousemove', e); + } + }; + el.onmouseleave = function () { + // set `_inContent` to `false` before `hideLater` + self._inContent = false; + if (self._enterable) { + if (self._show) { + self.hideLater(self._hideDelay); + } + } + }; + } + /** + * Update when tooltip is rendered + */ + TooltipHTMLContent.prototype.update = function (tooltipModel) { + // FIXME + // Move this logic to ec main? + if (!this._container) { + var container = this._api.getDom(); + var position = getComputedStyle(container, 'position'); + var domStyle = container.style; + if (domStyle.position !== 'absolute' && position !== 'absolute') { + domStyle.position = 'relative'; + } + } + // move tooltip if chart resized + var alwaysShowContent = tooltipModel.get('alwaysShowContent'); + alwaysShowContent && this._moveIfResized(); + // update alwaysShowContent + this._alwaysShowContent = alwaysShowContent; + // update className + this.el.className = tooltipModel.get('className') || ''; + // Hide the tooltip + // PENDING + // this.hide(); + }; + TooltipHTMLContent.prototype.show = function (tooltipModel, nearPointColor) { + clearTimeout(this._hideTimeout); + clearTimeout(this._longHideTimeout); + var el = this.el; + var style = el.style; + var styleCoord = this._styleCoord; + if (!el.innerHTML) { + style.display = 'none'; + } else { + style.cssText = gCssText + assembleCssText(tooltipModel, !this._firstShow, this._longHide) + // initial transform + + assembleTransform(styleCoord[0], styleCoord[1], true) + ("border-color:" + convertToColorString(nearPointColor) + ";") + (tooltipModel.get('extraCssText') || '') + // If mouse occasionally move over the tooltip, a mouseout event will be + // triggered by canvas, and cause some unexpectable result like dragging + // stop, "unfocusAdjacency". Here `pointer-events: none` is used to solve + // it. Although it is not supported by IE8~IE10, fortunately it is a rare + // scenario. + + (";pointer-events:" + (this._enterable ? 'auto' : 'none')); + } + this._show = true; + this._firstShow = false; + this._longHide = false; + }; + TooltipHTMLContent.prototype.setContent = function (content, markers, tooltipModel, borderColor, arrowPosition) { + var el = this.el; + if (content == null) { + el.innerHTML = ''; + return; + } + var arrow = ''; + if (isString(arrowPosition) && tooltipModel.get('trigger') === 'item' && !shouldTooltipConfine(tooltipModel)) { + arrow = assembleArrow(tooltipModel, borderColor, arrowPosition); + } + if (isString(content)) { + el.innerHTML = content + arrow; + } else if (content) { + // Clear previous + el.innerHTML = ''; + if (!isArray(content)) { + content = [content]; + } + for (var i = 0; i < content.length; i++) { + if (isDom(content[i]) && content[i].parentNode !== el) { + el.appendChild(content[i]); + } + } + // no arrow if empty + if (arrow && el.childNodes.length) { + // no need to create a new parent element, but it's not supported by IE 10 and older. + // const arrowEl = document.createRange().createContextualFragment(arrow); + var arrowEl = document.createElement('div'); + arrowEl.innerHTML = arrow; + el.appendChild(arrowEl); + } + } + }; + TooltipHTMLContent.prototype.setEnterable = function (enterable) { + this._enterable = enterable; + }; + TooltipHTMLContent.prototype.getSize = function () { + var el = this.el; + return el ? [el.offsetWidth, el.offsetHeight] : [0, 0]; + }; + TooltipHTMLContent.prototype.moveTo = function (zrX, zrY) { + if (!this.el) { + return; + } + var styleCoord = this._styleCoord; + makeStyleCoord$1(styleCoord, this._zr, this._container, zrX, zrY); + if (styleCoord[0] != null && styleCoord[1] != null) { + var style_1 = this.el.style; + var transforms = assembleTransform(styleCoord[0], styleCoord[1]); + each$4(transforms, function (transform) { + style_1[transform[0]] = transform[1]; + }); + } + }; + /** + * when `alwaysShowContent` is true, + * move the tooltip after chart resized + */ + TooltipHTMLContent.prototype._moveIfResized = function () { + // The ratio of left to width + var ratioX = this._styleCoord[2]; + // The ratio of top to height + var ratioY = this._styleCoord[3]; + this.moveTo(ratioX * this._zr.getWidth(), ratioY * this._zr.getHeight()); + }; + TooltipHTMLContent.prototype.hide = function () { + var _this = this; + var style = this.el.style; + style.visibility = 'hidden'; + style.opacity = '0'; + env.transform3dSupported && (style.willChange = ''); + this._show = false; + this._longHideTimeout = setTimeout(function () { + return _this._longHide = true; + }, 500); + }; + TooltipHTMLContent.prototype.hideLater = function (time) { + if (this._show && !(this._inContent && this._enterable) && !this._alwaysShowContent) { + if (time) { + this._hideDelay = time; + // Set show false to avoid invoke hideLater multiple times + this._show = false; + this._hideTimeout = setTimeout(bind$1(this.hide, this), time); + } else { + this.hide(); + } + } + }; + TooltipHTMLContent.prototype.isShow = function () { + return this._show; + }; + TooltipHTMLContent.prototype.dispose = function () { + clearTimeout(this._hideTimeout); + clearTimeout(this._longHideTimeout); + var parentNode = this.el.parentNode; + parentNode && parentNode.removeChild(this.el); + this.el = this._container = null; + }; + return TooltipHTMLContent; +}(); + +var TooltipRichContent = ( + /** @class */ + function() { + function TooltipRichContent2(api) { + this._show = false; + this._styleCoord = [0, 0, 0, 0]; + this._alwaysShowContent = false; + this._enterable = true; + this._zr = api.getZr(); + makeStyleCoord(this._styleCoord, this._zr, api.getWidth() / 2, api.getHeight() / 2); + } + TooltipRichContent2.prototype.update = function(tooltipModel) { + var alwaysShowContent = tooltipModel.get("alwaysShowContent"); + alwaysShowContent && this._moveIfResized(); + this._alwaysShowContent = alwaysShowContent; + }; + TooltipRichContent2.prototype.show = function() { + if (this._hideTimeout) { + clearTimeout(this._hideTimeout); + } + this.el.show(); + this._show = true; + }; + TooltipRichContent2.prototype.setContent = function(content, markupStyleCreator, tooltipModel, borderColor, arrowPosition) { + var _this = this; + if (isObject$2(content)) { + throwError(""); + } + if (this.el) { + this._zr.remove(this.el); + } + var textStyleModel = tooltipModel.getModel("textStyle"); + this.el = new ZRText({ + style: { + rich: markupStyleCreator.richTextStyles, + text: content, + lineHeight: 22, + borderWidth: 1, + borderColor, + textShadowColor: textStyleModel.get("textShadowColor"), + fill: tooltipModel.get(["textStyle", "color"]), + padding: getPaddingFromTooltipModel(tooltipModel, "richText"), + verticalAlign: "top", + align: "left" + }, + z: tooltipModel.get("z") + }); + each$4(["backgroundColor", "borderRadius", "shadowColor", "shadowBlur", "shadowOffsetX", "shadowOffsetY"], function(propName) { + _this.el.style[propName] = tooltipModel.get(propName); + }); + each$4(["textShadowBlur", "textShadowOffsetX", "textShadowOffsetY"], function(propName) { + _this.el.style[propName] = textStyleModel.get(propName) || 0; + }); + this._zr.add(this.el); + var self = this; + this.el.on("mouseover", function() { + 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; + }); + }; + TooltipRichContent2.prototype.setEnterable = function(enterable) { + this._enterable = enterable; + }; + TooltipRichContent2.prototype.getSize = function() { + var el = this.el; + var bounding = this.el.getBoundingRect(); + var shadowOuterSize = calcShadowOuterSize(el.style); + return [bounding.width + shadowOuterSize.left + shadowOuterSize.right, bounding.height + shadowOuterSize.top + shadowOuterSize.bottom]; + }; + TooltipRichContent2.prototype.moveTo = function(x, y) { + var el = this.el; + if (el) { + var styleCoord = this._styleCoord; + makeStyleCoord(styleCoord, this._zr, x, y); + x = styleCoord[0]; + y = styleCoord[1]; + var style = el.style; + var borderWidth = mathMaxWith0(style.borderWidth || 0); + var shadowOuterSize = calcShadowOuterSize(style); + el.x = x + borderWidth + shadowOuterSize.left; + el.y = y + borderWidth + shadowOuterSize.top; + el.markRedraw(); + } + }; + TooltipRichContent2.prototype._moveIfResized = function() { + var ratioX = this._styleCoord[2]; + var ratioY = this._styleCoord[3]; + this.moveTo(ratioX * this._zr.getWidth(), ratioY * this._zr.getHeight()); + }; + TooltipRichContent2.prototype.hide = function() { + if (this.el) { + this.el.hide(); + } + this._show = false; + }; + TooltipRichContent2.prototype.hideLater = function(time) { + if (this._show && !(this._inContent && this._enterable) && !this._alwaysShowContent) { + if (time) { + this._hideDelay = time; + this._show = false; + this._hideTimeout = setTimeout(bind$1(this.hide, this), time); + } else { + this.hide(); + } + } + }; + TooltipRichContent2.prototype.isShow = function() { + return this._show; + }; + TooltipRichContent2.prototype.dispose = function() { + this._zr.remove(this.el); + }; + return TooltipRichContent2; + }() +); +function mathMaxWith0(val) { + return Math.max(0, val); +} +function calcShadowOuterSize(style) { + var shadowBlur = mathMaxWith0(style.shadowBlur || 0); + var shadowOffsetX = mathMaxWith0(style.shadowOffsetX || 0); + var shadowOffsetY = mathMaxWith0(style.shadowOffsetY || 0); + return { + left: mathMaxWith0(shadowBlur - shadowOffsetX), + right: mathMaxWith0(shadowBlur + shadowOffsetX), + top: mathMaxWith0(shadowBlur - shadowOffsetY), + bottom: mathMaxWith0(shadowBlur + shadowOffsetY) + }; +} +function makeStyleCoord(out, zr, zrX, zrY) { + out[0] = zrX; + out[1] = zrY; + out[2] = out[0] / zr.getWidth(); + out[3] = out[1] / zr.getHeight(); +} + +var proxyRect = new Rect({ + shape: { + x: -1, + y: -1, + width: 2, + height: 2 + } +}); +var TooltipView = /** @class */function (_super) { + __extends(TooltipView, _super); + function TooltipView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = TooltipView.type; + return _this; + } + TooltipView.prototype.init = function (ecModel, api) { + if (env.node || !api.getDom()) { + return; + } + var tooltipModel = ecModel.getComponent('tooltip'); + var renderMode = this._renderMode = getTooltipRenderMode(tooltipModel.get('renderMode')); + this._tooltipContent = renderMode === 'richText' ? new TooltipRichContent(api) : new TooltipHTMLContent(api, { + appendTo: tooltipModel.get('appendToBody', true) ? 'body' : tooltipModel.get('appendTo', true) + }); + }; + TooltipView.prototype.render = function (tooltipModel, ecModel, api) { + if (env.node || !api.getDom()) { + return; + } + // Reset + this.group.removeAll(); + this._tooltipModel = tooltipModel; + this._ecModel = ecModel; + this._api = api; + var tooltipContent = this._tooltipContent; + tooltipContent.update(tooltipModel); + tooltipContent.setEnterable(tooltipModel.get('enterable')); + this._initGlobalListener(); + this._keepShow(); + // PENDING + // `mousemove` event will be triggered very frequently when the mouse moves fast, + // which causes that the `updatePosition` function was also called frequently. + // In Chrome with devtools open and Firefox, tooltip looks laggy and shakes. See #14695 #16101 + // To avoid frequent triggering, + // consider throttling it in 50ms when transition is enabled + if (this._renderMode !== 'richText' && tooltipModel.get('transitionDuration')) { + createOrUpdate(this, '_updatePosition', 50, 'fixRate'); + } else { + clear(this, '_updatePosition'); + } + }; + TooltipView.prototype._initGlobalListener = function () { + var tooltipModel = this._tooltipModel; + var triggerOn = tooltipModel.get('triggerOn'); + register('itemTooltip', this._api, bind$1(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)); + }; + TooltipView.prototype._keepShow = function () { + var tooltipModel = this._tooltipModel; + var ecModel = this._ecModel; + var api = this._api; + var triggerOn = tooltipModel.get('triggerOn'); + // 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. + && triggerOn !== 'none' && triggerOn !== 'click') { + var self_1 = 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_1.manuallyShowTip(tooltipModel, ecModel, api, { + x: self_1._lastX, + y: self_1._lastY, + dataByCoordSys: self_1._lastDataByCoordSys + }); + }); + } + }; + /** + * Show tip manually by + * dispatchAction({ + * type: 'showTip', + * x: 10, + * y: 10 + * }); + * Or + * dispatchAction({ + * type: 'showTip', + * seriesIndex: 0, + * dataIndex or dataIndexInside or name + * }); + * + * TODO Batch + */ + TooltipView.prototype.manuallyShowTip = function (tooltipModel, ecModel, api, payload) { + if (payload.from === this.uid || env.node || !api.getDom()) { + return; + } + var dispatchAction = makeDispatchAction(payload, api); + // Reset ticket + this._ticket = ''; + // When triggered from axisPointer. + var dataByCoordSys = payload.dataByCoordSys; + var cmptRef = findComponentReference(payload, ecModel, api); + if (cmptRef) { + var rect = cmptRef.el.getBoundingRect().clone(); + rect.applyTransform(cmptRef.el.transform); + this._tryShow({ + offsetX: rect.x + rect.width / 2, + offsetY: rect.y + rect.height / 2, + target: cmptRef.el, + position: payload.position, + // When manully trigger, the mouse is not on the el, so we'd better to + // position tooltip on the bottom of the el and display arrow is possible. + positionDefault: 'bottom' + }, dispatchAction); + } else if (payload.tooltip && payload.x != null && payload.y != null) { + var el = proxyRect; + el.x = payload.x; + el.y = payload.y; + el.update(); + getECData(el).tooltipConfig = { + name: null, + option: 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, + dataByCoordSys: 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, + target: pointInfo.el, + position: payload.position, + // When manully trigger, the mouse is not on the el, so we'd better to + // position tooltip on the bottom of the el and display arrow is possible. + positionDefault: 'bottom' + }, 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 + }, dispatchAction); + } + }; + TooltipView.prototype.manuallyHideTip = function (tooltipModel, ecModel, api, payload) { + var tooltipContent = this._tooltipContent; + if (this._tooltipModel) { + tooltipContent.hideLater(this._tooltipModel.get('hideDelay')); + } + this._lastX = this._lastY = this._lastDataByCoordSys = null; + if (payload.from !== this.uid) { + this._hide(makeDispatchAction(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. + TooltipView.prototype._manuallyAxisShowTip = function (tooltipModel, ecModel, api, payload) { + var seriesIndex = payload.seriesIndex; + var dataIndex = payload.dataIndex; + // @ts-ignore + 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 tooltipCascadedModel = buildTooltipModel([data.getItemModel(dataIndex), seriesModel, (seriesModel.coordinateSystem || {}).model], this._tooltipModel); + if (tooltipCascadedModel.get('trigger') !== 'axis') { + return; + } + api.dispatchAction({ + type: 'updateAxisPointer', + seriesIndex: seriesIndex, + dataIndex: dataIndex, + position: payload.position + }); + return true; + }; + TooltipView.prototype._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); + } else if (el) { + var ecData = getECData(el); + if (ecData.ssrType === 'legend') { + // Don't trigger tooltip for legend tooltip item + return; + } + this._lastDataByCoordSys = null; + var seriesDispatcher_1; + var cmptDispatcher_1; + findEventDispatcher(el, function (target) { + // Always show item tooltip if mouse is on the element with dataIndex + if (getECData(target).dataIndex != null) { + seriesDispatcher_1 = target; + return true; + } + // Tooltip provided directly. Like legend. + if (getECData(target).tooltipConfig != null) { + cmptDispatcher_1 = target; + return true; + } + }, true); + if (seriesDispatcher_1) { + this._showSeriesItemTooltip(e, seriesDispatcher_1, dispatchAction); + } else if (cmptDispatcher_1) { + this._showComponentItemTooltip(e, cmptDispatcher_1, dispatchAction); + } else { + this._hide(dispatchAction); + } + } else { + this._lastDataByCoordSys = null; + this._hide(dispatchAction); + } + }; + TooltipView.prototype._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 easier to enter the content + // but tooltip do not move immediately. + var delay = tooltipModel.get('showDelay'); + cb = bind$1(cb, this); + clearTimeout(this._showTimout); + delay > 0 ? this._showTimout = setTimeout(cb, delay) : cb(); + }; + TooltipView.prototype._showAxisTooltip = function (dataByCoordSys, e) { + var ecModel = this._ecModel; + var globalTooltipModel = this._tooltipModel; + var point = [e.offsetX, e.offsetY]; + var singleTooltipModel = buildTooltipModel([e.tooltipOption], globalTooltipModel); + var renderMode = this._renderMode; + var cbParamsList = []; + var articleMarkup = createTooltipMarkup('section', { + blocks: [], + noHeader: true + }); + // Only for legacy: `Serise['formatTooltip']` returns a string. + var markupTextArrLegacy = []; + var markupStyleCreator = new TooltipMarkupStyleCreator(); + each$4(dataByCoordSys, function (itemCoordSys) { + each$4(itemCoordSys.dataByAxis, function (axisItem) { + var axisModel = ecModel.getComponent(axisItem.axisDim + 'Axis', axisItem.axisIndex); + var axisValue = axisItem.value; + if (!axisModel || axisValue == null) { + return; + } + var axisValueLabel = getValueLabel(axisValue, axisModel.axis, ecModel, axisItem.seriesDataIndices, axisItem.valueLabelOpt); + var axisSectionMarkup = createTooltipMarkup('section', { + header: axisValueLabel, + noHeader: !trim(axisValueLabel), + sortBlocks: true, + blocks: [] + }); + articleMarkup.blocks.push(axisSectionMarkup); + each$4(axisItem.seriesDataIndices, function (idxItem) { + var series = ecModel.getSeriesByIndex(idxItem.seriesIndex); + var dataIndex = idxItem.dataIndexInside; + var cbParams = series.getDataParams(dataIndex); + // Can't find data. + if (cbParams.dataIndex < 0) { + return; + } + cbParams.axisDim = axisItem.axisDim; + cbParams.axisIndex = axisItem.axisIndex; + cbParams.axisType = axisItem.axisType; + cbParams.axisId = axisItem.axisId; + cbParams.axisValue = getAxisRawValue(axisModel.axis, { + value: axisValue + }); + cbParams.axisValueLabel = axisValueLabel; + // Pre-create marker style for makers. Users can assemble richText + // text in `formatter` callback and use those markers style. + cbParams.marker = markupStyleCreator.makeTooltipMarker('item', convertToColorString(cbParams.color), renderMode); + var seriesTooltipResult = normalizeTooltipFormatResult(series.formatTooltip(dataIndex, true, null)); + var frag = seriesTooltipResult.frag; + if (frag) { + var valueFormatter = buildTooltipModel([series], globalTooltipModel).get('valueFormatter'); + axisSectionMarkup.blocks.push(valueFormatter ? extend({ + valueFormatter: valueFormatter + }, frag) : frag); + } + if (seriesTooltipResult.text) { + markupTextArrLegacy.push(seriesTooltipResult.text); + } + cbParamsList.push(cbParams); + }); + }); + }); + // In most cases, the second axis is displays upper on the first one. + // So we reverse it to look better. + articleMarkup.blocks.reverse(); + markupTextArrLegacy.reverse(); + var positionExpr = e.position; + var orderMode = singleTooltipModel.get('order'); + var builtMarkupText = buildTooltipMarkup(articleMarkup, markupStyleCreator, renderMode, orderMode, ecModel.get('useUTC'), singleTooltipModel.get('textStyle')); + builtMarkupText && markupTextArrLegacy.unshift(builtMarkupText); + var blockBreak = renderMode === 'richText' ? '\n\n' : '
'; + var allMarkupText = markupTextArrLegacy.join(blockBreak); + this._showOrMove(singleTooltipModel, function () { + if (this._updateContentNotChangedOnAxis(dataByCoordSys, cbParamsList)) { + this._updatePosition(singleTooltipModel, positionExpr, point[0], point[1], this._tooltipContent, cbParamsList); + } else { + this._showTooltipContent(singleTooltipModel, allMarkupText, cbParamsList, Math.random() + '', point[0], point[1], positionExpr, null, markupStyleCreator); + } + }); + // Do not trigger events here, because this branch only be entered + // from dispatchAction. + }; + TooltipView.prototype._showSeriesItemTooltip = function (e, dispatcher, dispatchAction) { + var ecModel = this._ecModel; + var ecData = getECData(dispatcher); + // 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 = ecData.seriesIndex; + var seriesModel = ecModel.getSeriesByIndex(seriesIndex); + // For example, graph link. + var dataModel = ecData.dataModel || seriesModel; + var dataIndex = ecData.dataIndex; + var dataType = ecData.dataType; + var data = dataModel.getData(dataType); + var renderMode = this._renderMode; + var positionDefault = e.positionDefault; + var tooltipModel = buildTooltipModel([data.getItemModel(dataIndex), dataModel, seriesModel && (seriesModel.coordinateSystem || {}).model], this._tooltipModel, positionDefault ? { + position: positionDefault + } : null); + var tooltipTrigger = tooltipModel.get('trigger'); + if (tooltipTrigger != null && tooltipTrigger !== 'item') { + return; + } + var params = dataModel.getDataParams(dataIndex, dataType); + var markupStyleCreator = new TooltipMarkupStyleCreator(); + // Pre-create marker style for makers. Users can assemble richText + // text in `formatter` callback and use those markers style. + params.marker = markupStyleCreator.makeTooltipMarker('item', convertToColorString(params.color), renderMode); + var seriesTooltipResult = normalizeTooltipFormatResult(dataModel.formatTooltip(dataIndex, false, dataType)); + var orderMode = tooltipModel.get('order'); + var valueFormatter = tooltipModel.get('valueFormatter'); + var frag = seriesTooltipResult.frag; + var markupText = frag ? buildTooltipMarkup(valueFormatter ? extend({ + valueFormatter: valueFormatter + }, frag) : frag, markupStyleCreator, renderMode, orderMode, ecModel.get('useUTC'), tooltipModel.get('textStyle')) : seriesTooltipResult.text; + var asyncTicket = 'item_' + dataModel.name + '_' + dataIndex; + this._showOrMove(tooltipModel, function () { + this._showTooltipContent(tooltipModel, markupText, params, asyncTicket, e.offsetX, e.offsetY, e.position, e.target, markupStyleCreator); + }); + // FIXME + // duplicated showtip if manuallyShowTip is called from dispatchAction. + dispatchAction({ + type: 'showTip', + dataIndexInside: dataIndex, + dataIndex: data.getRawIndex(dataIndex), + seriesIndex: seriesIndex, + from: this.uid + }); + }; + TooltipView.prototype._showComponentItemTooltip = function (e, el, dispatchAction) { + var isHTMLRenderMode = this._renderMode === 'html'; + var ecData = getECData(el); + var tooltipConfig = ecData.tooltipConfig; + var tooltipOpt = tooltipConfig.option || {}; + var encodeHTMLContent = tooltipOpt.encodeHTMLContent; + if (isString(tooltipOpt)) { + var content = tooltipOpt; + tooltipOpt = { + content: content, + // Fixed formatter + formatter: content + }; + // when `tooltipConfig.option` is a string rather than an object, + // we can't know if the content needs to be encoded + // for the sake of security, encode it by default. + encodeHTMLContent = true; + } + if (encodeHTMLContent && isHTMLRenderMode && tooltipOpt.content) { + // clone might be unnecessary? + tooltipOpt = clone$2(tooltipOpt); + tooltipOpt.content = encodeHTML(tooltipOpt.content); + } + var tooltipModelCascade = [tooltipOpt]; + var cmpt = this._ecModel.getComponent(ecData.componentMainType, ecData.componentIndex); + if (cmpt) { + tooltipModelCascade.push(cmpt); + } + // In most cases, component tooltip formatter has different params with series tooltip formatter, + // so that they cannot share the same formatter. Since the global tooltip formatter is used for series + // by convention, we do not use it as the default formatter for component. + tooltipModelCascade.push({ + formatter: tooltipOpt.content + }); + var positionDefault = e.positionDefault; + var subTooltipModel = buildTooltipModel(tooltipModelCascade, this._tooltipModel, positionDefault ? { + position: positionDefault + } : null); + var defaultHtml = subTooltipModel.get('content'); + var asyncTicket = Math.random() + ''; + // PENDING: this case do not support richText style yet. + var markupStyleCreator = new TooltipMarkupStyleCreator(); + // Do not check whether `trigger` is 'none' here, because `trigger` + // only works on coordinate system. In fact, we have not found case + // that requires setting `trigger` nothing on component yet. + this._showOrMove(subTooltipModel, function () { + // Use formatterParams from element defined in component + // Avoid users modify it. + var formatterParams = clone$2(subTooltipModel.get('formatterParams') || {}); + this._showTooltipContent(subTooltipModel, defaultHtml, formatterParams, asyncTicket, e.offsetX, e.offsetY, e.position, el, markupStyleCreator); + }); + // If not dispatch showTip, tip may be hide triggered by axis. + dispatchAction({ + type: 'showTip', + from: this.uid + }); + }; + TooltipView.prototype._showTooltipContent = function ( + // Use Model insteadof TooltipModel because this model may be from series or other options. + // Instead of top level tooltip. + tooltipModel, defaultHtml, params, asyncTicket, x, y, positionExpr, el, markupStyleCreator) { + // Reset ticket + this._ticket = ''; + if (!tooltipModel.get('showContent') || !tooltipModel.get('show')) { + return; + } + var tooltipContent = this._tooltipContent; + tooltipContent.setEnterable(tooltipModel.get('enterable')); + var formatter = tooltipModel.get('formatter'); + positionExpr = positionExpr || tooltipModel.get('position'); + var html = defaultHtml; + var nearPoint = this._getNearestPoint([x, y], params, tooltipModel.get('trigger'), tooltipModel.get('borderColor')); + var nearPointColor = nearPoint.color; + if (formatter) { + if (isString(formatter)) { + var useUTC = tooltipModel.ecModel.get('useUTC'); + var params0 = isArray(params) ? params[0] : params; + var isTimeAxis = params0 && params0.axisType && params0.axisType.indexOf('time') >= 0; + html = formatter; + if (isTimeAxis) { + html = format(params0.axisValue, html, useUTC); + } + html = formatTpl(html, params, true); + } else if (isFunction(formatter)) { + var callback = bind$1(function (cbTicket, html) { + if (cbTicket === this._ticket) { + tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr); + this._updatePosition(tooltipModel, positionExpr, x, y, tooltipContent, params, el); + } + }, this); + this._ticket = asyncTicket; + html = formatter(params, asyncTicket, callback); + } else { + html = formatter; + } + } + tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr); + tooltipContent.show(tooltipModel, nearPointColor); + this._updatePosition(tooltipModel, positionExpr, x, y, tooltipContent, params, el); + }; + TooltipView.prototype._getNearestPoint = function (point, tooltipDataParams, trigger, borderColor) { + if (trigger === 'axis' || isArray(tooltipDataParams)) { + return { + color: borderColor || (this._renderMode === 'html' ? '#fff' : 'none') + }; + } + if (!isArray(tooltipDataParams)) { + return { + color: borderColor || tooltipDataParams.color || tooltipDataParams.borderColor + }; + } + }; + TooltipView.prototype._updatePosition = function (tooltipModel, positionExpr, x, + // Mouse x + y, + // Mouse 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 (isFunction(positionExpr)) { + // 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(positionExpr[0], viewWidth); + y = parsePercent(positionExpr[1], viewHeight); + } else if (isObject$2(positionExpr)) { + var boxLayoutPosition = positionExpr; + boxLayoutPosition.width = contentSize[0]; + boxLayoutPosition.height = contentSize[1]; + var layoutRect = getLayoutRect(boxLayoutPosition, { + 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 (isString(positionExpr) && el) { + var pos = calcTooltipPosition(positionExpr, rect, contentSize, tooltipModel.get('borderWidth')); + 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 (shouldTooltipConfine(tooltipModel)) { + 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? + TooltipView.prototype._updateContentNotChangedOnAxis = function (dataByCoordSys, cbParamsList) { + var lastCoordSys = this._lastDataByCoordSys; + var lastCbParamsList = this._cbParamsList; + var contentNotChanged = !!lastCoordSys && lastCoordSys.length === dataByCoordSys.length; + contentNotChanged && each$4(lastCoordSys, function (lastItemCoordSys, indexCoordSys) { + var lastDataByAxis = lastItemCoordSys.dataByAxis || []; + var thisItemCoordSys = dataByCoordSys[indexCoordSys] || {}; + var thisDataByAxis = thisItemCoordSys.dataByAxis || []; + contentNotChanged = contentNotChanged && lastDataByAxis.length === thisDataByAxis.length; + contentNotChanged && each$4(lastDataByAxis, function (lastItem, indexAxis) { + var thisItem = thisDataByAxis[indexAxis] || {}; + var lastIndices = lastItem.seriesDataIndices || []; + var newIndices = thisItem.seriesDataIndices || []; + contentNotChanged = contentNotChanged && lastItem.value === thisItem.value && lastItem.axisType === thisItem.axisType && lastItem.axisId === thisItem.axisId && lastIndices.length === newIndices.length; + contentNotChanged && each$4(lastIndices, function (lastIdxItem, j) { + var newIdxItem = newIndices[j]; + contentNotChanged = contentNotChanged && lastIdxItem.seriesIndex === newIdxItem.seriesIndex && lastIdxItem.dataIndex === newIdxItem.dataIndex; + }); + // check is cbParams data value changed + lastCbParamsList && each$4(lastItem.seriesDataIndices, function (idxItem) { + var seriesIdx = idxItem.seriesIndex; + var cbParams = cbParamsList[seriesIdx]; + var lastCbParams = lastCbParamsList[seriesIdx]; + if (cbParams && lastCbParams && lastCbParams.data !== cbParams.data) { + contentNotChanged = false; + } + }); + }); + }); + this._lastDataByCoordSys = dataByCoordSys; + this._cbParamsList = cbParamsList; + return !!contentNotChanged; + }; + TooltipView.prototype._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 + }); + }; + TooltipView.prototype.dispose = function (ecModel, api) { + if (env.node || !api.getDom()) { + return; + } + clear(this, '_updatePosition'); + this._tooltipContent.dispose(); + unregister('itemTooltip', api); + }; + TooltipView.type = 'tooltip'; + return TooltipView; +}(ComponentView); +/** + * From top to bottom. (the last one should be globalTooltipModel); + */ +function buildTooltipModel(modelCascade, globalTooltipModel, defaultTooltipOption) { + // Last is always tooltip model. + var ecModel = globalTooltipModel.ecModel; + var resultModel; + if (defaultTooltipOption) { + resultModel = new Model(defaultTooltipOption, ecModel, ecModel); + resultModel = new Model(globalTooltipModel.option, resultModel, ecModel); + } else { + resultModel = globalTooltipModel; + } + for (var i = modelCascade.length - 1; i >= 0; i--) { + var tooltipOpt = modelCascade[i]; + if (tooltipOpt) { + if (tooltipOpt instanceof Model) { + tooltipOpt = tooltipOpt.get('tooltip', true); + } + // In each data item tooltip can be simply write: + // { + // value: 10, + // tooltip: 'Something you need to know' + // } + if (isString(tooltipOpt)) { + tooltipOpt = { + formatter: tooltipOpt + }; + } + if (tooltipOpt) { + resultModel = new Model(tooltipOpt, resultModel, ecModel); + } + } + } + return resultModel; +} +function makeDispatchAction(payload, api) { + return payload.dispatchAction || bind$1(api.dispatchAction, api); +} +function refixTooltipPosition(x, y, content, viewWidth, viewHeight, gapH, gapV) { + var size = content.getSize(); + var width = size[0]; + var height = size[1]; + if (gapH != null) { + // Add extra 2 pixels for this case: + // At present the "values" in default tooltip are using CSS `float: right`. + // When the right edge of the tooltip box is on the right side of the + // viewport, the `float` layout might push the "values" to the second line. + if (x + width + gapH + 2 > 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.getSize(); + var width = size[0]; + var height = size[1]; + 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, borderWidth) { + var domWidth = contentSize[0]; + var domHeight = contentSize[1]; + var offset = Math.ceil(Math.SQRT2 * borderWidth) + 8; + 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 - offset; + break; + case 'bottom': + x = rect.x + rectWidth / 2 - domWidth / 2; + y = rect.y + rectHeight + offset; + break; + case 'left': + x = rect.x - domWidth - offset; + y = rect.y + rectHeight / 2 - domHeight / 2; + break; + case 'right': + x = rect.x + rectWidth + offset; + y = rect.y + rectHeight / 2 - domHeight / 2; + } + return [x, y]; +} +function isCenterAlign(align) { + return align === 'center' || align === 'middle'; +} +/** + * Find target component by payload like: + * ```js + * { legendId: 'some_id', name: 'xxx' } + * { toolboxIndex: 1, name: 'xxx' } + * { geoName: 'some_name', name: 'xxx' } + * ``` + * PENDING: at present only + * + * If not found, return null/undefined. + */ +function findComponentReference(payload, ecModel, api) { + var queryOptionMap = preParseFinder(payload).queryOptionMap; + var componentMainType = queryOptionMap.keys()[0]; + if (!componentMainType || componentMainType === 'series') { + return; + } + var queryResult = queryReferringComponents(ecModel, componentMainType, queryOptionMap.get(componentMainType), { + useDefault: false, + enableAll: false, + enableNone: false + }); + var model = queryResult.models[0]; + if (!model) { + return; + } + var view = api.getViewOfComponentModel(model); + var el; + view.group.traverse(function (subEl) { + var tooltipConfig = getECData(subEl).tooltipConfig; + if (tooltipConfig && tooltipConfig.name === payload.name) { + el = subEl; + return true; // stop + } + }); + if (el) { + return { + componentMainType: componentMainType, + componentIndex: model.componentIndex, + el: el + }; + } +} + +function install$4(registers) { + use(install$6); + registers.registerComponentModel(TooltipModel); + registers.registerComponentView(TooltipView); + /** + * @action + * @property {string} type + * @property {number} seriesIndex + * @property {number} dataIndex + * @property {number} [x] + * @property {number} [y] + */ + registers.registerAction({ + type: 'showTip', + event: 'showTip', + update: 'tooltip:manuallyShowTip' + }, noop); + registers.registerAction({ + type: 'hideTip', + event: 'hideTip', + update: 'tooltip:manuallyHideTip' + }, noop); +} + +var TitleModel = /** @class */function (_super) { + __extends(TitleModel, _super); + function TitleModel() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = TitleModel.type; + _this.layoutMode = { + type: 'box', + ignoreSize: true + }; + return _this; + } + TitleModel.type = 'title'; + TitleModel.defaultOption = { + // zlevel: 0, + z: 6, + show: true, + text: '', + target: 'blank', + subtext: '', + subtarget: 'blank', + left: 0, + top: 0, + backgroundColor: 'rgba(0,0,0,0)', + borderColor: '#ccc', + borderWidth: 0, + padding: 5, + itemGap: 10, + textStyle: { + fontSize: 18, + fontWeight: 'bold', + color: '#464646' + }, + subtextStyle: { + fontSize: 12, + color: '#6E7079' + } + }; + return TitleModel; +}(ComponentModel); +// View +var TitleView = /** @class */function (_super) { + __extends(TitleView, _super); + function TitleView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = TitleView.type; + return _this; + } + TitleView.prototype.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 ZRText({ + style: createTextStyle(textStyleModel, { + text: titleModel.get('text'), + fill: textStyleModel.getTextColor() + }, { + disableBox: true + }), + z2: 10 + }); + var textRect = textEl.getBoundingRect(); + var subText = titleModel.get('subtext'); + var subTextEl = new ZRText({ + style: createTextStyle(subtextStyleModel, { + text: subText, + fill: subtextStyleModel.getTextColor(), + y: textRect.height + titleModel.get('itemGap'), + verticalAlign: '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 () { + windowOpen(link, '_' + titleModel.get('target')); + }); + } + if (sublink) { + subTextEl.on('click', function () { + windowOpen(sublink, '_' + titleModel.get('subtarget')); + }); + } + getECData(textEl).eventData = getECData(subTextEl).eventData = triggerEvent ? { + componentType: 'title', + componentIndex: titleModel.componentIndex + } : null; + group.add(textEl); + subText && group.add(subTextEl); + // If no subText, but add subTextEl, there will be an empty line. + 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'); + // @ts-ignore + if (textAlign === 'middle') { + textAlign = 'center'; + } + // Adjust layout by text align + if (textAlign === 'right') { + layoutRect.x += layoutRect.width; + } else if (textAlign === 'center') { + layoutRect.x += layoutRect.width / 2; + } + } + if (!textVerticalAlign) { + textVerticalAlign = titleModel.get('top') || titleModel.get('bottom'); + // @ts-ignore + if (textVerticalAlign === 'center') { + textVerticalAlign = 'middle'; + } + if (textVerticalAlign === 'bottom') { + layoutRect.y += layoutRect.height; + } else if (textVerticalAlign === 'middle') { + layoutRect.y += layoutRect.height / 2; + } + textVerticalAlign = textVerticalAlign || 'top'; + } + group.x = layoutRect.x; + group.y = layoutRect.y; + group.markRedraw(); + var alignStyle = { + align: textAlign, + verticalAlign: 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); + }; + TitleView.type = 'title'; + return TitleView; +}(ComponentView); +function install$3(registers) { + registers.registerComponentModel(TitleModel); + registers.registerComponentView(TitleView); +} + +var getDefaultSelectorOptions = function (ecModel, type) { + if (type === 'all') { + return { + type: 'all', + title: ecModel.getLocaleModel().get(['legend', 'selector', 'all']) + }; + } else if (type === 'inverse') { + return { + type: 'inverse', + title: ecModel.getLocaleModel().get(['legend', 'selector', 'inverse']) + }; + } +}; +var LegendModel = /** @class */function (_super) { + __extends(LegendModel, _super); + function LegendModel() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = LegendModel.type; + _this.layoutMode = { + type: 'box', + // legend.width/height are maxWidth/maxHeight actually, + // whereas real 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 + }; + return _this; + } + LegendModel.prototype.init = function (option, parentModel, ecModel) { + this.mergeDefaultAndTheme(option, ecModel); + option.selected = option.selected || {}; + this._updateSelector(option); + }; + LegendModel.prototype.mergeOption = function (option, ecModel) { + _super.prototype.mergeOption.call(this, option, ecModel); + this._updateSelector(option); + }; + LegendModel.prototype._updateSelector = function (option) { + var selector = option.selector; + var ecModel = this.ecModel; + if (selector === true) { + selector = option.selector = ['all', 'inverse']; + } + if (isArray(selector)) { + each$4(selector, function (item, index) { + isString(item) && (item = { + type: item + }); + selector[index] = merge(item, getDefaultSelectorOptions(ecModel, item.type)); + }); + } + }; + LegendModel.prototype.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_1 = legendData[i].get('name'); + if (this.isSelected(name_1)) { + // Force to unselect others + this.select(name_1); + hasSelected = true; + break; + } + } + // Try select the first if selectedMode is single + !hasSelected && this.select(legendData[0].get('name')); + } + }; + LegendModel.prototype._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 is not specified in option, use availableNames as data, + // which is convenient for user preparing option. + var rawData = this.get('data') || potentialData; + var legendNameMap = createHashMap(); + var legendData = map$1(rawData, function (dataItem) { + // Can be string or number + if (isString(dataItem) || isNumber(dataItem)) { + dataItem = { + name: dataItem + }; + } + if (legendNameMap.get(dataItem.name)) { + // remove legend name duplicate + return null; + } + legendNameMap.set(dataItem.name, true); + return new Model(dataItem, this, this.ecModel); + }, this); + /** + * @type {Array.} + * @private + */ + this._data = filter(legendData, function (item) { + return !!item; + }); + }; + LegendModel.prototype.getData = function () { + return this._data; + }; + LegendModel.prototype.select = function (name) { + var selected = this.option.selected; + var selectedMode = this.get('selectedMode'); + if (selectedMode === 'single') { + var data = this._data; + each$4(data, function (dataItem) { + selected[dataItem.get('name')] = false; + }); + } + selected[name] = true; + }; + LegendModel.prototype.unSelect = function (name) { + if (this.get('selectedMode') !== 'single') { + this.option.selected[name] = false; + } + }; + LegendModel.prototype.toggleSelected = function (name) { + var selected = this.option.selected; + // Default is true + if (!selected.hasOwnProperty(name)) { + selected[name] = true; + } + this[selected[name] ? 'unSelect' : 'select'](name); + }; + LegendModel.prototype.allSelect = function () { + var data = this._data; + var selected = this.option.selected; + each$4(data, function (dataItem) { + selected[dataItem.get('name', true)] = true; + }); + }; + LegendModel.prototype.inverseSelect = function () { + var data = this._data; + var selected = this.option.selected; + each$4(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]; + }); + }; + LegendModel.prototype.isSelected = function (name) { + var selected = this.option.selected; + return !(selected.hasOwnProperty(name) && !selected[name]) && indexOf(this._availableNames, name) >= 0; + }; + LegendModel.prototype.getOrient = function () { + return this.get('orient') === 'vertical' ? { + index: 1, + name: 'vertical' + } : { + index: 0, + name: 'horizontal' + }; + }; + LegendModel.type = 'legend.plain'; + LegendModel.dependencies = ['series']; + LegendModel.defaultOption = { + // zlevel: 0, + z: 4, + show: true, + orient: 'horizontal', + left: 'center', + // right: 'center', + top: 0, + // bottom: null, + align: 'auto', + backgroundColor: 'rgba(0,0,0,0)', + borderColor: '#ccc', + borderRadius: 0, + borderWidth: 0, + padding: 5, + itemGap: 10, + itemWidth: 25, + itemHeight: 14, + symbolRotate: 'inherit', + symbolKeepAspect: true, + inactiveColor: '#ccc', + inactiveBorderColor: '#ccc', + inactiveBorderWidth: 'auto', + itemStyle: { + color: 'inherit', + opacity: 'inherit', + borderColor: 'inherit', + borderWidth: 'auto', + borderCap: 'inherit', + borderJoin: 'inherit', + borderDashOffset: 'inherit', + borderMiterLimit: 'inherit' + }, + lineStyle: { + width: 'auto', + color: 'inherit', + inactiveColor: '#ccc', + inactiveWidth: 2, + opacity: 'inherit', + type: 'inherit', + cap: 'inherit', + join: 'inherit', + dashOffset: 'inherit', + miterLimit: 'inherit' + }, + textStyle: { + color: '#333' + }, + selectedMode: true, + 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' + } + }, + selectorPosition: 'auto', + selectorItemGap: 7, + selectorButtonGap: 10, + tooltip: { + show: false + } + }; + return LegendModel; +}(ComponentModel); + +var curry = curry$1; +var each = each$4; +var Group$1 = Group$2; +var LegendView = ( + /** @class */ + function(_super) { + __extends(LegendView2, _super); + function LegendView2() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = LegendView2.type; + _this.newlineDisabled = false; + return _this; + } + LegendView2.prototype.init = function() { + this.group.add(this._contentGroup = new Group$1()); + this.group.add(this._selectorGroup = new Group$1()); + this._isFirstRender = true; + }; + LegendView2.prototype.getContentGroup = function() { + return this._contentGroup; + }; + LegendView2.prototype.getSelectorGroup = function() { + return this._selectorGroup; + }; + LegendView2.prototype.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); + 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); + var layoutRect = getLayoutRect(defaults({ + width: mainRect.width, + height: mainRect.height + }, positionInfo), viewportSize, padding); + this.group.x = layoutRect.x - mainRect.x; + this.group.y = layoutRect.y - mainRect.y; + this.group.markRedraw(); + this.group.add(this._backgroundEl = makeBackground(mainRect, legendModel)); + }; + LegendView2.prototype.resetInner = function() { + this.getContentGroup().removeAll(); + this._backgroundEl && this.group.remove(this._backgroundEl); + this.getSelectorGroup().removeAll(); + }; + LegendView2.prototype.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(legendModel.getData(), function(legendItemModel, dataIndex) { + var name = legendItemModel.get("name"); + if (!this.newlineDisabled && (name === "" || name === "\n")) { + var g = new Group$1(); + g.newline = true; + contentGroup.add(g); + return; + } + var seriesModel = ecModel.getSeriesByName(name)[0]; + if (legendDrawnMap.get(name)) { + return; + } + if (seriesModel) { + var data = seriesModel.getData(); + var lineVisualStyle = data.getVisual("legendLineStyle") || {}; + var legendIcon = data.getVisual("legendIcon"); + var style = data.getVisual("style"); + var itemGroup = this._createItem(seriesModel, name, dataIndex, legendItemModel, legendModel, itemAlign, lineVisualStyle, style, legendIcon, selectMode, api); + itemGroup.on("click", curry(dispatchSelectAction, name, null, api, excludeSeriesId)).on("mouseover", curry(dispatchHighlightAction, seriesModel.name, null, api, excludeSeriesId)).on("mouseout", curry(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId)); + if (ecModel.ssr) { + itemGroup.eachChild(function(child) { + var ecData = getECData(child); + ecData.seriesIndex = seriesModel.seriesIndex; + ecData.dataIndex = dataIndex; + ecData.ssrType = "legend"; + }); + } + legendDrawnMap.set(name, true); + } else { + ecModel.eachRawSeries(function(seriesModel2) { + if (legendDrawnMap.get(name)) { + return; + } + if (seriesModel2.legendVisualProvider) { + var provider = seriesModel2.legendVisualProvider; + if (!provider.containName(name)) { + return; + } + var idx = provider.indexOfName(name); + var style2 = provider.getItemVisual(idx, "style"); + var legendIcon2 = provider.getItemVisual(idx, "legendIcon"); + var colorArr = parse(style2.fill); + if (colorArr && colorArr[3] === 0) { + colorArr[3] = 0.2; + style2 = extend(extend({}, style2), { + fill: stringify(colorArr, "rgba") + }); + } + var itemGroup2 = this._createItem(seriesModel2, name, dataIndex, legendItemModel, legendModel, itemAlign, {}, style2, legendIcon2, selectMode, api); + itemGroup2.on("click", curry(dispatchSelectAction, null, name, api, excludeSeriesId)).on("mouseover", curry(dispatchHighlightAction, null, name, api, excludeSeriesId)).on("mouseout", curry(dispatchDownplayAction, null, name, api, excludeSeriesId)); + if (ecModel.ssr) { + itemGroup2.eachChild(function(child) { + var ecData = getECData(child); + ecData.seriesIndex = seriesModel2.seriesIndex; + ecData.dataIndex = dataIndex; + ecData.ssrType = "legend"; + }); + } + legendDrawnMap.set(name, true); + } + }, this); + } + }, this); + if (selector) { + this._createSelector(selector, legendModel, api, orient, selectorPosition); + } + }; + LegendView2.prototype._createSelector = function(selector, legendModel, api, orient, selectorPosition) { + var selectorGroup = this.getSelectorGroup(); + each(selector, function createSelectorButton(selectorItem) { + var type = selectorItem.type; + var labelText = new ZRText({ + style: { + x: 0, + y: 0, + align: "center", + verticalAlign: "middle" + }, + onclick: function() { + api.dispatchAction({ + type: type === "all" ? "legendAllSelect" : "legendInverseSelect", + legendId: legendModel.id + }); + } + }); + selectorGroup.add(labelText); + var labelModel = legendModel.getModel("selectorLabel"); + var emphasisLabelModel = legendModel.getModel(["emphasis", "selectorLabel"]); + setLabelStyle(labelText, { + normal: labelModel, + emphasis: emphasisLabelModel + }, { + defaultText: selectorItem.title + }); + enableHoverEmphasis(labelText); + }); + }; + LegendView2.prototype._createItem = function(seriesModel, name, dataIndex, legendItemModel, legendModel, itemAlign, lineVisualStyle, itemVisualStyle, legendIcon, selectMode, api) { + var drawType = seriesModel.visualDrawType; + var itemWidth = legendModel.get("itemWidth"); + var itemHeight = legendModel.get("itemHeight"); + var isSelected = legendModel.isSelected(name); + var iconRotate = legendItemModel.get("symbolRotate"); + var symbolKeepAspect = legendItemModel.get("symbolKeepAspect"); + var legendIconType = legendItemModel.get("icon"); + legendIcon = legendIconType || legendIcon || "roundRect"; + var style = getLegendStyle(legendIcon, legendItemModel, lineVisualStyle, itemVisualStyle, drawType, isSelected, api); + var itemGroup = new Group$1(); + var textStyleModel = legendItemModel.getModel("textStyle"); + if (isFunction(seriesModel.getLegendIcon) && (!legendIconType || legendIconType === "inherit")) { + itemGroup.add(seriesModel.getLegendIcon({ + itemWidth, + itemHeight, + icon: legendIcon, + iconRotate, + itemStyle: style.itemStyle, + lineStyle: style.lineStyle, + symbolKeepAspect + })); + } else { + var rotate = legendIconType === "inherit" && seriesModel.getData().getVisual("symbol") ? iconRotate === "inherit" ? seriesModel.getData().getVisual("symbolRotate") : iconRotate : 0; + itemGroup.add(getDefaultLegendIcon({ + itemWidth, + itemHeight, + icon: legendIcon, + iconRotate: rotate, + itemStyle: style.itemStyle, + symbolKeepAspect + })); + } + var textX = itemAlign === "left" ? itemWidth + 5 : -5; + var textAlign = itemAlign; + var formatter = legendModel.get("formatter"); + var content = name; + if (isString(formatter) && formatter) { + content = formatter.replace("{name}", name != null ? name : ""); + } else if (isFunction(formatter)) { + content = formatter(name); + } + var textColor = isSelected ? textStyleModel.getTextColor() : legendItemModel.get("inactiveColor"); + itemGroup.add(new ZRText({ + style: createTextStyle(textStyleModel, { + text: content, + x: textX, + y: itemHeight / 2, + fill: textColor, + align: textAlign, + verticalAlign: "middle" + }, { + inheritColor: textColor + }) + })); + var hitRect = new Rect({ + shape: itemGroup.getBoundingRect(), + style: { + // Cannot use 'invisible' because SVG SSR will miss the node + fill: "transparent" + } + }); + var tooltipModel = legendItemModel.getModel("tooltip"); + if (tooltipModel.get("show")) { + setTooltipConfig({ + el: hitRect, + componentModel: legendModel, + itemName: name, + itemTooltipOption: tooltipModel.option + }); + } + itemGroup.add(hitRect); + itemGroup.eachChild(function(child) { + child.silent = true; + }); + hitRect.silent = !selectMode; + this.getContentGroup().add(itemGroup); + enableHoverEmphasis(itemGroup); + itemGroup.__legendDataIndex = dataIndex; + return itemGroup; + }; + LegendView2.prototype.layoutInner = function(legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) { + var contentGroup = this.getContentGroup(); + var selectorGroup = this.getSelectorGroup(); + box(legendModel.get("orient"), contentGroup, legendModel.get("itemGap"), maxSize.width, maxSize.height); + var contentRect = contentGroup.getBoundingRect(); + var contentPos = [-contentRect.x, -contentRect.y]; + selectorGroup.markRedraw(); + contentGroup.markRedraw(); + if (selector) { + 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; + } + selectorPos[1 - orientIdx] += contentRect[hw] / 2 - selectorRect[hw] / 2; + selectorGroup.x = selectorPos[0]; + selectorGroup.y = selectorPos[1]; + contentGroup.x = contentPos[0]; + contentGroup.y = contentPos[1]; + 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.x = contentPos[0]; + contentGroup.y = contentPos[1]; + return this.group.getBoundingRect(); + } + }; + LegendView2.prototype.remove = function() { + this.getContentGroup().removeAll(); + this._isFirstRender = true; + }; + LegendView2.type = "legend.plain"; + return LegendView2; + }(ComponentView) +); +function getLegendStyle(iconType, legendItemModel, lineVisualStyle, itemVisualStyle, drawType, isSelected, api) { + function handleCommonProps(style, visualStyle) { + if (style.lineWidth === "auto") { + style.lineWidth = visualStyle.lineWidth > 0 ? 2 : 0; + } + each(style, function(propVal, propName) { + style[propName] === "inherit" && (style[propName] = visualStyle[propName]); + }); + } + var itemStyleModel = legendItemModel.getModel("itemStyle"); + var itemStyle = itemStyleModel.getItemStyle(); + var iconBrushType = iconType.lastIndexOf("empty", 0) === 0 ? "fill" : "stroke"; + var decalStyle = itemStyleModel.getShallow("decal"); + itemStyle.decal = !decalStyle || decalStyle === "inherit" ? itemVisualStyle.decal : createOrUpdatePatternFromDecal(decalStyle, api); + if (itemStyle.fill === "inherit") { + itemStyle.fill = itemVisualStyle[drawType]; + } + if (itemStyle.stroke === "inherit") { + itemStyle.stroke = itemVisualStyle[iconBrushType]; + } + if (itemStyle.opacity === "inherit") { + itemStyle.opacity = (drawType === "fill" ? itemVisualStyle : lineVisualStyle).opacity; + } + handleCommonProps(itemStyle, itemVisualStyle); + var legendLineModel = legendItemModel.getModel("lineStyle"); + var lineStyle = legendLineModel.getLineStyle(); + handleCommonProps(lineStyle, lineVisualStyle); + itemStyle.fill === "auto" && (itemStyle.fill = itemVisualStyle.fill); + itemStyle.stroke === "auto" && (itemStyle.stroke = itemVisualStyle.fill); + lineStyle.stroke === "auto" && (lineStyle.stroke = itemVisualStyle.fill); + if (!isSelected) { + var borderWidth = legendItemModel.get("inactiveBorderWidth"); + var visualHasBorder = itemStyle[iconBrushType]; + itemStyle.lineWidth = borderWidth === "auto" ? itemVisualStyle.lineWidth > 0 && visualHasBorder ? 2 : 0 : itemStyle.lineWidth; + itemStyle.fill = legendItemModel.get("inactiveColor"); + itemStyle.stroke = legendItemModel.get("inactiveBorderColor"); + lineStyle.stroke = legendLineModel.get("inactiveColor"); + lineStyle.lineWidth = legendLineModel.get("inactiveWidth"); + } + return { + itemStyle, + lineStyle + }; +} +function getDefaultLegendIcon(opt) { + var symboType = opt.icon || "roundRect"; + var icon = createSymbol(symboType, 0, 0, opt.itemWidth, opt.itemHeight, opt.itemStyle.fill, opt.symbolKeepAspect); + icon.setStyle(opt.itemStyle); + icon.rotation = (opt.iconRotate || 0) * Math.PI / 180; + icon.setOrigin([opt.itemWidth / 2, opt.itemHeight / 2]); + if (symboType.indexOf("empty") > -1) { + icon.style.stroke = icon.style.fill; + icon.style.fill = "#fff"; + icon.style.lineWidth = 2; + } + return icon; +} +function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) { + dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId); + api.dispatchAction({ + type: "legendToggleSelect", + name: seriesName != null ? seriesName : dataName + }); + dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId); +} +function isUseHoverLayer(api) { + var list = api.getZr().storage.getDisplayList(); + var emphasisState; + var i = 0; + var len = list.length; + while (i < len && !(emphasisState = list[i].states.emphasis)) { + i++; + } + return emphasisState && emphasisState.hoverLayer; +} +function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) { + if (!isUseHoverLayer(api)) { + api.dispatchAction({ + type: "highlight", + seriesName, + name: dataName, + excludeSeriesId + }); + } +} +function dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId) { + if (!isUseHoverLayer(api)) { + api.dispatchAction({ + type: "downplay", + seriesName, + name: dataName, + 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. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 legendFilter(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; + }); + } +} + +function legendSelectActionHandler(methodName, payload, ecModel) { + var isAllSelect = methodName === 'allSelect' || methodName === 'inverseSelect'; + var selectedMap = {}; + var actionLegendIndices = []; + ecModel.eachComponent({ + mainType: 'legend', + query: payload + }, function (legendModel) { + if (isAllSelect) { + legendModel[methodName](); + } else { + legendModel[methodName](payload.name); + } + makeSelectedMap(legendModel, selectedMap); + actionLegendIndices.push(legendModel.componentIndex); + }); + var allSelectedMap = {}; + // make selectedMap from all legend components + ecModel.eachComponent('legend', function (legendModel) { + each$4(selectedMap, function (isSelected, name) { + // 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'](name); + }); + makeSelectedMap(legendModel, allSelectedMap); + }); + // Return the event explicitly + return isAllSelect ? { + selected: allSelectedMap, + // return legendIndex array to tell the developers which legends are allSelect / inverseSelect + legendIndex: actionLegendIndices + } : { + name: payload.name, + selected: allSelectedMap + }; +} +function makeSelectedMap(legendModel, out) { + var selectedMap = out || {}; + each$4(legendModel.getData(), function (model) { + var name = model.get('name'); + // Wrap element + if (name === '\n' || name === '') { + return; + } + var isItemSelected = legendModel.isSelected(name); + if (hasOwn(selectedMap, name)) { + // Unselected if any legend is unselected + selectedMap[name] = selectedMap[name] && isItemSelected; + } else { + selectedMap[name] = isItemSelected; + } + }); + return selectedMap; +} +function installLegendAction(registers) { + /** + * @event legendToggleSelect + * @type {Object} + * @property {string} type 'legendToggleSelect' + * @property {string} [from] + * @property {string} name Series name or data item name + */ + registers.registerAction('legendToggleSelect', 'legendselectchanged', curry$1(legendSelectActionHandler, 'toggleSelected')); + registers.registerAction('legendAllSelect', 'legendselectall', curry$1(legendSelectActionHandler, 'allSelect')); + registers.registerAction('legendInverseSelect', 'legendinverseselect', curry$1(legendSelectActionHandler, 'inverseSelect')); + /** + * @event legendSelect + * @type {Object} + * @property {string} type 'legendSelect' + * @property {string} name Series name or data item name + */ + registers.registerAction('legendSelect', 'legendselected', curry$1(legendSelectActionHandler, 'select')); + /** + * @event legendUnSelect + * @type {Object} + * @property {string} type 'legendUnSelect' + * @property {string} name Series name or data item name + */ + registers.registerAction('legendUnSelect', 'legendunselected', curry$1(legendSelectActionHandler, 'unSelect')); +} + +function install$2(registers) { + registers.registerComponentModel(LegendModel); + registers.registerComponentView(LegendView); + registers.registerProcessor(registers.PRIORITY.PROCESSOR.SERIES_FILTER, legendFilter); + registers.registerSubTypeDefaulter('legend', function () { + return 'plain'; + }); + installLegendAction(registers); +} + +var ScrollableLegendModel = /** @class */function (_super) { + __extends(ScrollableLegendModel, _super); + function ScrollableLegendModel() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = ScrollableLegendModel.type; + return _this; + } + /** + * @param {number} scrollDataIndex + */ + ScrollableLegendModel.prototype.setScrollDataIndex = function (scrollDataIndex) { + this.option.scrollDataIndex = scrollDataIndex; + }; + ScrollableLegendModel.prototype.init = function (option, parentModel, ecModel) { + var inputPositionParams = getLayoutParams(option); + _super.prototype.init.call(this, option, parentModel, ecModel); + mergeAndNormalizeLayoutParams(this, option, inputPositionParams); + }; + /** + * @override + */ + ScrollableLegendModel.prototype.mergeOption = function (option, ecModel) { + _super.prototype.mergeOption.call(this, option, ecModel); + mergeAndNormalizeLayoutParams(this, this.option, option); + }; + ScrollableLegendModel.type = 'legend.scroll'; + ScrollableLegendModel.defaultOption = inheritDefaultOption(LegendModel.defaultOption, { + scrollDataIndex: 0, + pageButtonItemGap: 5, + pageButtonGap: null, + pageButtonPosition: 'end', + pageFormatter: '{current}/{total}', + 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, + pageTextStyle: { + color: '#333' + }, + animationDurationUpdate: 800 + }); + return ScrollableLegendModel; +}(LegendModel); +// Do not `ignoreSize` to enable setting {left: 10, right: 10}. +function mergeAndNormalizeLayoutParams(legendModel, target, raw) { + var orient = legendModel.getOrient(); + var ignoreSize = [1, 1]; + ignoreSize[orient.index] = 0; + mergeLayoutParam(target, raw, { + type: 'box', + ignoreSize: !!ignoreSize + }); +} + +var Group = Group$2; +var WH = ['width', 'height']; +var XY = ['x', 'y']; +var ScrollableLegendView = /** @class */function (_super) { + __extends(ScrollableLegendView, _super); + function ScrollableLegendView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.type = ScrollableLegendView.type; + _this.newlineDisabled = true; + _this._currentIndex = 0; + return _this; + } + ScrollableLegendView.prototype.init = function () { + _super.prototype.init.call(this); + this.group.add(this._containerGroup = new Group()); + this._containerGroup.add(this.getContentGroup()); + this.group.add(this._controllerGroup = new Group()); + }; + /** + * @override + */ + ScrollableLegendView.prototype.resetInner = function () { + _super.prototype.resetInner.call(this); + this._controllerGroup.removeAll(); + this._containerGroup.removeClipPath(); + this._containerGroup.__rectSize = null; + }; + /** + * @override + */ + ScrollableLegendView.prototype.renderInner = function (itemAlign, legendModel, ecModel, api, selector, orient, selectorPosition) { + var self = this; + // Render content items. + _super.prototype.renderInner.call(this, 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); + var pageIconSizeArr = isArray(pageIconSize) ? pageIconSize : [pageIconSize, pageIconSize]; + createPageButton('pagePrev', 0); + var pageTextStyleModel = legendModel.getModel('pageTextStyle'); + controllerGroup.add(new ZRText({ + name: 'pageText', + style: { + // Placeholder to calculate a proper layout. + text: 'xx/xx', + fill: pageTextStyleModel.getTextColor(), + font: pageTextStyleModel.getFont(), + verticalAlign: 'middle', + align: '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$1(self._pageGo, self, pageDataIndexName, legendModel, api) + }, { + x: -pageIconSizeArr[0] / 2, + y: -pageIconSizeArr[1] / 2, + width: pageIconSizeArr[0], + height: pageIconSizeArr[1] + }); + icon.name = name; + controllerGroup.add(icon); + } + }; + /** + * @override + */ + ScrollableLegendView.prototype.layoutInner = function (legendModel, itemAlign, maxSize, isFirstRender, selector, selectorPosition) { + var selectorGroup = this.getSelectorGroup(); + var orientIdx = legendModel.getOrient().index; + var wh = WH[orientIdx]; + var xy = XY[orientIdx]; + var hw = WH[1 - orientIdx]; + var yx = XY[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$2(maxSize); + selector && (processMaxSize[wh] = maxSize[wh] - selectorRect[wh] - selectorButtonGap); + var mainRect = this._layoutContentAndController(legendModel, isFirstRender, processMaxSize, orientIdx, wh, hw, yx, xy); + 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.x = selectorPos[0]; + selectorGroup.y = selectorPos[1]; + selectorGroup.markRedraw(); + } + return mainRect; + }; + ScrollableLegendView.prototype._layoutContentAndController = function (legendModel, isFirstRender, maxSize, orientIdx, wh, hw, yx, xy) { + 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]; + // In case that the inner elements of contentGroup layout do not based on [0, 0] + 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[xy]; + } + // 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.setPosition(contentPos); + containerGroup.setPosition(containerPos); + controllerGroup.setPosition(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$1(contentGroup, { + x: pageInfo.contentPosition[0], + y: pageInfo.contentPosition[1] + }, + // When switch from "show controller" to "not show controller", view should be + // updated immediately without animation, otherwise causes weird effect. + showController ? legendModel : null); + this._updatePageInfoView(legendModel, pageInfo); + return mainRect; + }; + ScrollableLegendView.prototype._pageGo = function (to, legendModel, api) { + var scrollDataIndex = this._getPageInfo(legendModel)[to]; + scrollDataIndex != null && api.dispatchAction({ + type: 'legendScroll', + scrollDataIndex: scrollDataIndex, + legendId: legendModel.id + }); + }; + ScrollableLegendView.prototype._updatePageInfoView = function (legendModel, pageInfo) { + var controllerGroup = this._controllerGroup; + each$4(['pagePrev', 'pageNext'], function (name) { + var key = name + 'DataIndex'; + var canJump = pageInfo[key] != 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 == null ? '' : current + '').replace('{total}', total == null ? '' : total + '') : pageFormatter({ + current: current, + total: total + })); + }; + /** + * 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. + * } + */ + ScrollableLegendView.prototype._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[orientIdx]; + var xy = XY[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.x, contentGroup.y], + 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[xy]; + return { + s: start, + e: start + itemRect[wh], + i: el.__legendDataIndex + }; + } + } + function intersect(itemInfo, winStart) { + return itemInfo.e >= winStart && itemInfo.s <= winStart + containerRectSize; + } + }; + ScrollableLegendView.prototype._findTargetItemIndex = function (targetDataIndex) { + if (!this._showController) { + return 0; + } + var index; + var contentGroup = this.getContentGroup(); + var defaultIndex; + contentGroup.eachChild(function (child, idx) { + var legendDataIdx = child.__legendDataIndex; + // FIXME + // If the given targetDataIndex (from model) is illegal, + // we use defaultIndex. 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; + }; + ScrollableLegendView.type = 'legend.scroll'; + return ScrollableLegendView; +}(LegendView); + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + + +/** + * AUTO-GENERATED FILE. DO NOT MODIFY. + */ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 installScrollableLegendAction(registers) { + /** + * @event legendScroll + * @type {Object} + * @property {string} type 'legendScroll' + * @property {string} scrollDataIndex + */ + registers.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); + }); + }); +} + +function install$1(registers) { + use(install$2); + registers.registerComponentModel(ScrollableLegendModel); + registers.registerComponentView(ScrollableLegendView); + installScrollableLegendAction(registers); +} + +function install(registers) { + use(install$2); + use(install$1); +} + +const {resolveComponent:_resolveComponent,createVNode:_createVNode,openBlock:_openBlock,createElementBlock:_createElementBlock,createCommentVNode:_createCommentVNode,unref:_unref,renderList:_renderList,Fragment:_Fragment,toDisplayString:_toDisplayString,createTextVNode:_createTextVNode,withCtx:_withCtx,createElementVNode:_createElementVNode,createSlots:_createSlots,createBlock:_createBlock} = await importShared('vue'); + + +const _hoisted_1 = { class: "dashboard-widget" }; +const _hoisted_2 = { class: "dashboard-content" }; +const _hoisted_3 = { + key: 0, + class: "d-flex justify-center align-center py-4" +}; +const _hoisted_4 = { key: 1 }; +const _hoisted_5 = { + key: 0, + class: "chart-container" +}; +const _hoisted_6 = { class: "text-caption" }; +const _hoisted_7 = { + key: 0, + class: "d-flex justify-center align-center py-4" +}; +const _hoisted_8 = { key: 1 }; +const _hoisted_9 = { + key: 0, + class: "chart-container" +}; +const _hoisted_10 = { class: "text-caption" }; + +const {ref,computed,onMounted,onUnmounted} = await importShared('vue'); + +// 注册ECharts组件 + +const _sfc_main = { + __name: 'Dashboard', + props: { + config: { + type: Object, + default: () => ({}), + }, + allowRefresh: { + type: Boolean, + default: true, + }, +}, + setup(__props) { + +try { + use([install$a, install$9, install$8, install$5, install$4, install, install$3]); +} catch (e) { + console.warn('ECharts components registration failed', e); +} + +// 接收仪表板配置 +const props = __props; + +// 组件状态 +const loading = ref(true); +const items = ref([]); +const chartData = ref(null); +let refreshTimer = null; + +// 获取状态图标 +function getStatusIcon(status) { + const icons = { + 'success': 'mdi-check-circle', + 'warning': 'mdi-alert', + 'error': 'mdi-alert-circle', + 'info': 'mdi-information', + 'running': 'mdi-play-circle', + 'pending': 'mdi-clock-outline', + 'completed': 'mdi-check-circle-outline', + }; + return icons[status] || 'mdi-help-circle' +} + +// 获取状态颜色 +function getStatusColor(status) { + const colors = { + 'success': 'success', + 'warning': 'warning', + 'error': 'error', + 'info': 'info', + 'running': 'primary', + 'pending': 'secondary', + 'completed': 'success', + }; + return colors[status] || 'grey' +} + +// 图表选项 +const chartOptions = computed(() => { + if (!chartData.value) return {} + + const { type, data } = chartData.value; + + if (type === 'line') { + return { + tooltip: { + trigger: 'axis', + }, + xAxis: { + type: 'category', + data: data.xAxis, + axisLabel: { + color: '#888', + }, + }, + yAxis: { + type: 'value', + axisLabel: { + color: '#888', + }, + }, + series: data.series.map(series => ({ + name: series.name, + type: 'line', + smooth: true, + data: series.data, + areaStyle: { opacity: 0.1 }, + })), + } + } + + if (type === 'pie') { + return { + tooltip: { + trigger: 'item', + formatter: '{a}
{b}: {c} ({d}%)', + }, + series: [ + { + name: data.name, + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: '12', + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: data.items, + }, + ], + } + } + + return {} +}); + +// 获取仪表板数据 +async function fetchDashboardData() { + if (!props.allowRefresh) return + + loading.value = true; + + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 1000)); + + // 随机决定显示饼图或折线图 + const showPie = Math.random() > 0.5; + + if (showPie) { + // 饼图数据 + chartData.value = { + type: 'pie', + data: { + name: '文件分布', + items: [ + { value: Math.floor(Math.random() * 50) + 30, name: '电影' }, + { value: Math.floor(Math.random() * 40) + 20, name: '电视剧' }, + { value: Math.floor(Math.random() * 30) + 10, name: '动漫' }, + { value: Math.floor(Math.random() * 20) + 5, name: '纪录片' }, + ], + }, + }; + } else { + // 折线图数据 + const days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; + chartData.value = { + type: 'line', + data: { + xAxis: days, + series: [ + { + name: '下载量', + data: days.map(() => Math.floor(Math.random() * 10) + 1), + }, + { + name: '完成量', + data: days.map(() => Math.floor(Math.random() * 8) + 1), + }, + ], + }, + }; + } + + // 生成列表数据 + const statuses = ['success', 'warning', 'error', 'info', 'running', 'pending', 'completed']; + items.value = Array.from({ length: 5 }, (_, i) => { + const status = statuses[Math.floor(Math.random() * statuses.length)]; + return { + title: `项目 ${i + 1}`, + subtitle: `上次更新: ${new Date().toLocaleTimeString()}`, + status, + value: Math.floor(Math.random() * 100) + '%', + } + }); + } catch (error) { + console.error('获取仪表板数据失败:', error); + } finally { + loading.value = false; + } +} + +// 设置定时刷新 +function setupRefreshTimer() { + if (props.allowRefresh) { + // 每30秒刷新一次 + refreshTimer = setInterval(() => { + fetchDashboardData(); + }, 30000); + } +} + +// 初始化 +onMounted(() => { + fetchDashboardData(); + setupRefreshTimer(); +}); + +// 清理 +onUnmounted(() => { + if (refreshTimer) { + clearInterval(refreshTimer); + } +}); + +return (_ctx, _cache) => { + const _component_v_progress_circular = _resolveComponent("v-progress-circular"); + const _component_v_icon = _resolveComponent("v-icon"); + const _component_v_avatar = _resolveComponent("v-avatar"); + const _component_v_list_item = _resolveComponent("v-list-item"); + const _component_v_list = _resolveComponent("v-list"); + const _component_v_card_text = _resolveComponent("v-card-text"); + const _component_v_card = _resolveComponent("v-card"); + const _component_v_card_title = _resolveComponent("v-card-title"); + const _component_v_card_subtitle = _resolveComponent("v-card-subtitle"); + const _component_v_card_item = _resolveComponent("v-card-item"); + + return (_openBlock(), _createElementBlock("div", _hoisted_1, [ + (!__props.config?.attrs?.border) + ? (_openBlock(), _createBlock(_component_v_card, { + key: 0, + flat: "" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_card_text, { class: "pa-0" }, { + default: _withCtx(() => [ + _createElementVNode("div", _hoisted_2, [ + (loading.value) + ? (_openBlock(), _createElementBlock("div", _hoisted_3, [ + _createVNode(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ])) + : (_openBlock(), _createElementBlock("div", _hoisted_4, [ + (chartData.value) + ? (_openBlock(), _createElementBlock("div", _hoisted_5, [ + _createVNode(_unref(H), { + class: "chart", + option: chartOptions.value, + autoresize: "" + }, null, 8, ["option"]) + ])) + : _createCommentVNode("", true), + (items.value.length) + ? (_openBlock(), _createBlock(_component_v_list, { + key: 1, + density: "compact", + class: "py-0" + }, { + default: _withCtx(() => [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items.value, (item, index) => { + return (_openBlock(), _createBlock(_component_v_list_item, { + key: index, + title: item.title, + subtitle: item.subtitle + }, _createSlots({ + prepend: _withCtx(() => [ + _createVNode(_component_v_avatar, { + color: getStatusColor(item.status), + size: "small" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { + size: "small", + color: "white" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(getStatusIcon(item.status)), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["color"]) + ]), + _: 2 + }, [ + (item.value) + ? { + name: "append", + fn: _withCtx(() => [ + _createElementVNode("span", _hoisted_6, _toDisplayString(item.value), 1) + ]), + key: "0" + } + : undefined + ]), 1032, ["title", "subtitle"])) + }), 128)) + ]), + _: 1 + })) + : _createCommentVNode("", true) + ])) + ]) + ]), + _: 1 + }) + ]), + _: 1 + })) + : (_openBlock(), _createBlock(_component_v_card, { key: 1 }, { + default: _withCtx(() => [ + _createVNode(_component_v_card_item, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(__props.config?.attrs?.title || '仪表板组件'), 1) + ]), + _: 1 + }), + (__props.config?.attrs?.subtitle) + ? (_openBlock(), _createBlock(_component_v_card_subtitle, { key: 0 }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(__props.config.attrs.subtitle), 1) + ]), + _: 1 + })) + : _createCommentVNode("", true) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, null, { + default: _withCtx(() => [ + (loading.value) + ? (_openBlock(), _createElementBlock("div", _hoisted_7, [ + _createVNode(_component_v_progress_circular, { + indeterminate: "", + color: "primary" + }) + ])) + : (_openBlock(), _createElementBlock("div", _hoisted_8, [ + (chartData.value) + ? (_openBlock(), _createElementBlock("div", _hoisted_9, [ + _createVNode(_unref(H), { + class: "chart", + option: chartOptions.value, + autoresize: "" + }, null, 8, ["option"]) + ])) + : _createCommentVNode("", true), + (items.value.length) + ? (_openBlock(), _createBlock(_component_v_list, { + key: 1, + density: "compact", + class: "rounded pa-0" + }, { + default: _withCtx(() => [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(items.value, (item, index) => { + return (_openBlock(), _createBlock(_component_v_list_item, { + key: index, + title: item.title, + subtitle: item.subtitle + }, _createSlots({ + prepend: _withCtx(() => [ + _createVNode(_component_v_avatar, { + color: getStatusColor(item.status), + size: "small" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { + size: "small", + color: "white" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(getStatusIcon(item.status)), 1) + ]), + _: 2 + }, 1024) + ]), + _: 2 + }, 1032, ["color"]) + ]), + _: 2 + }, [ + (item.value) + ? { + name: "append", + fn: _withCtx(() => [ + _createElementVNode("span", _hoisted_10, _toDisplayString(item.value), 1) + ]), + key: "0" + } + : undefined + ]), 1032, ["title", "subtitle"])) + }), 128)) + ]), + _: 1 + })) + : _createCommentVNode("", true) + ])) + ]), + _: 1 + }) + ]), + _: 1 + })) + ])) +} +} + +}; + +export { _sfc_main as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Bl7XNZ7k.css b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Bl7XNZ7k.css new file mode 100644 index 0000000..e359222 --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-Bl7XNZ7k.css @@ -0,0 +1,48 @@ + +.plugin-page[data-v-d5e502a5] { + max-width: 1200px; + margin: 0 auto; +} + +/* 使卡片等宽并适应移动端 */ +.d-flex.flex-wrap[data-v-d5e502a5] { + gap: 16px; +} +.url-display[data-v-d5e502a5] { + word-break: break-all; + padding: 8px; + background: rgba(0, 0, 0, 0.05); + border-radius: 4px; +} + +/* 移动端堆叠布局 */ +@media (max-width: 768px) { +.d-flex.flex-wrap[data-v-d5e502a5] { + flex-direction: column; +} +} + +/* Add visual distinction between sections */ +.ruleset-section[data-v-d5e502a5] { + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 16px; + background-color: #f5f5f5; +} +.top-section[data-v-d5e502a5] { + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 16px; + background-color: #f9f9f9; +} + +/* Optional: Add different border colors to further distinguish */ +.ruleset-section[data-v-d5e502a5] { + border-left: 4px solid #2196F3; /* Blue accent */ +} +.top-section[data-v-d5e502a5] { + border-left: 4px solid #4CAF50; /* Green accent */ +} +.drag-handle[data-v-d5e502a5] { + cursor: move; +} diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-DlQgf7u6.js b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-DlQgf7u6.js new file mode 100644 index 0000000..594a5fd --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_expose_Page-DlQgf7u6.js @@ -0,0 +1,1118 @@ +import { importShared } from './__federation_fn_import-JrT3xvdd.js'; +import { _ as _export_sfc } from './_plugin-vue_export-helper-pcqpp-6-.js'; + +const {createTextVNode:_createTextVNode,resolveComponent:_resolveComponent,withCtx:_withCtx,createVNode:_createVNode,toDisplayString:_toDisplayString,openBlock:_openBlock,createBlock:_createBlock,createCommentVNode:_createCommentVNode,createElementVNode:_createElementVNode,renderList:_renderList,Fragment:_Fragment,createElementBlock:_createElementBlock,withModifiers:_withModifiers,normalizeClass:_normalizeClass} = await importShared('vue'); + + +const _hoisted_1 = { class: "plugin-page" }; +const _hoisted_2 = { key: 2 }; +const _hoisted_3 = { class: "mb-6" }; +const _hoisted_4 = { class: "d-flex justify-space-between align-center mb-4" }; +const _hoisted_5 = ["onDragstart", "onDragover", "onDrop"]; +const _hoisted_6 = { class: "mb-6" }; +const _hoisted_7 = { class: "d-flex justify-space-between align-center mb-4" }; +const _hoisted_8 = ["onDragstart", "onDragover", "onDrop"]; +const _hoisted_9 = { class: "d-flex justify-space-between mb-2" }; +const _hoisted_10 = { class: "d-flex justify-space-between mb-2" }; +const _hoisted_11 = { class: "d-flex justify-space-between text-caption text-grey" }; +const _hoisted_12 = { class: "d-flex justify-space-between align-center mb-2" }; +const _hoisted_13 = { + key: 0, + class: "url-display" +}; +const _hoisted_14 = ["href"]; +const _hoisted_15 = { + key: 1, + class: "text-grey" +}; + +const {ref,onMounted,computed} = await importShared('vue'); + + + +const _sfc_main = { + __name: 'Page', + props: { + model: { + type: Object, + default: () => { + }, + }, + api: { + type: Object, + default: () => { + }, + }, +}, + emits: ['action', 'switch', 'close'], + setup(__props, { emit: __emit }) { + +const expansionPanels = ref(null); +const snackbar = ref({ + show: false, + message: '', + color: 'success' +}); +const dragItem = ref(null); +// 添加自定义出站状态 +const customOutbounds = ref([]); +const additionalParamOptions = ref([ + {title: '无', value: ''}, + {title: 'no-resolve', value: 'no-resolve'}, + {title: 'src', value: 'src'} +]); +// 新增状态变量 +const subUrl = ref(''); + +function dragStart(event, priority, type = 'top') { + dragItem.value = {priority, type}; + event.dataTransfer.effectAllowed = 'move'; +} + +function dragOver(event, priority, type = 'top') { + event.preventDefault(); + const currentRules = type === 'top' ? rules.value : ruleset_rules.value; + // 高亮当前悬停行 + currentRules.forEach(rule => { + rule._isHovered = (rule.priority === priority); + }); +} + +async function drop(event, targetPriority, type = 'top') { + // 5. 调用 API 提交 + await props.api.put('/plugin/ClashRuleProvider/reorder-rules', { + moved_priority: dragItem.value.priority, + target_priority: targetPriority, + rule_data: dragItem.value, + type: type + }); + await refreshData(); // 失败时恢复数据 + dragItem.value = null; +} + +// 接收初始配置 +const props = __props; + +// 组件状态 +const loading = ref(true); +const error = ref(null); +const rules = ref([]); +const ruleset_rules = ref([]); +const status = ref('running'); +const rulesetPrefix = ref('Custom_'); +const lastUpdated = ref(''); +const updatingSubscription = ref(false); +const subscriptionUrl = ref(''); + +// 规则编辑相关状态 +const ruleDialog = ref(false); +const editingPriority = ref(null); +const editingType = ref('top'); // 记录当前编辑的规则类型('top' 或 'ruleset') +const newRule = ref({ + type: 'DOMAIN-SUFFIX', + payload: '', + action: 'DIRECT', + additional_params: '', + priority: 0 +}); + +// 排序后的规则 +const sortedRules = computed(() => [...rules.value].sort((a, b) => a.priority - b.priority)); +const sortedRulesetRules = computed(() => [...ruleset_rules.value].sort((a, b) => a.priority - b.priority)); +const showAdditionalParams = computed(() => { + return ['IP-CIDR', 'IP-CIDR6', 'IP-ASN', 'GEOIP'].includes(newRule.value.type); +}); +const ruleProviders = ref([]); +const ruleProviderNames = computed(() => Object.keys(ruleProviders.value)); +// 规则类型和动作选项 +const ruleTypes = computed(() => { + const allTypes = [ + 'DOMAIN', 'DOMAIN-SUFFIX', 'DOMAIN-KEYWORD', 'DOMAIN-REGEX', 'GEOSITE', + 'IP-CIDR', 'IP-CIDR6', 'IP-SUFFIX', 'IP-ASN', 'GEOIP', + 'SRC-GEOIP', 'SRC-IP-ASN', 'SRC-IP-CIDR', 'SRC-IP-SUFFIX', + 'DST-PORT', 'SRC-PORT', 'IN-PORT', 'IN-TYPE', 'IN-USER', 'IN-NAME', + 'PROCESS-PATH', 'PROCESS-PATH-REGEX', 'PROCESS-NAME', 'PROCESS-NAME-REGEX', + 'UID', 'NETWORK', 'DSCP', 'RULE-SET', 'AND', 'OR', 'NOT', 'SUB-RULE', 'MATCH' + ]; + + // 如果是 ruleset 规则,过滤掉 SUB-RULE 和 RULE-SET + if (editingType.value === 'ruleset') { + return allTypes.filter(type => !['SUB-RULE', 'RULE-SET'].includes(type)); + } + + return allTypes; +}); +// 修改actions为计算属性,合并内置动作和自定义出站 +const actions = computed(() => [ + 'DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE', + ...customOutbounds.value.map(outbound => outbound.name) +]); +const subscriptionInfo = ref({ + download: 0, + upload: 0, + total: 0, + expire: 0, + last_update: 0, + used_percentage: 0, + rule_size: 0 +}); +// 自定义事件,用于通知主应用刷新数据 +const emit = __emit; + +// 格式化字节为易读单位(如 1.5 GB) +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +// 在工具函数中添加时间戳转换 +function formatTimestamp(timestamp) { + if (!timestamp) return 'N/A'; + const date = new Date(timestamp * 1000); // 注意:JS时间戳是毫秒,需乘以1000 + return date.toLocaleString(); // 或使用其他格式如 date.toISOString().split('T')[0] +} + +// 更新过期时间颜色判断(基于时间戳) +function getExpireColor(timestamp) { + if (!timestamp) return 'grey'; + const secondsLeft = timestamp - Math.floor(Date.now() / 1000); + const daysLeft = secondsLeft / 86400; + return daysLeft < 7 ? 'error' : daysLeft < 30 ? 'warning' : 'success'; +} + +// 复制功能 +function copyToClipboard(text) { + navigator.clipboard.writeText(text).then(() => { + snackbar.value = { + show: true, + message: '已复制到剪贴板', + color: 'success' + }; + }).catch(() => { + snackbar.value = { + show: true, + message: '复制失败', + color: 'error' + }; + }); +} + +// 计算已用流量百分比 +function calculatePercentage(download, total) { + return total > 0 ? Math.round((download / total) * 100) : 0; +} + +// 根据流量百分比获取颜色 +function getUsageColor(percentage) { + return percentage > 90 ? 'error' : percentage > 70 ? 'warning' : 'success'; +} + +// 获取动作对应的颜色 +function getActionColor(action) { + const colors = { + 'DIRECT': 'success', + 'REJECT': 'error', + 'REJECT-DROP': 'error', + 'PASS': 'warning', + 'COMPATIBLE': 'info' + }; + return colors[action] || 'primary' +} + +function isSystemRule(rule) { + return rule.payload?.startsWith(rulesetPrefix.value); +} + +// 打开添加规则对话框 +function openAddRuleDialog(type = 'top') { + editingPriority.value = null; + editingType.value = type; + const currentRules = type === 'top' ? sortedRules.value : sortedRulesetRules.value; + const nextPriority = currentRules.length > 0 + ? Math.max(...currentRules.map(r => r.priority)) + 1 + : 0; + + newRule.value = { + type: 'DOMAIN-SUFFIX', + payload: '', + action: 'DIRECT', + additional_params: '', + priority: nextPriority + }; + + ruleDialog.value = true; +} + +// 编辑规则 +function editRule(priority, type = 'top') { + editingType.value = type; // 记录当前编辑的类型 + const currentRules = type === 'top' ? sortedRules.value : sortedRulesetRules.value; + const rule = currentRules.find(r => r.priority === priority); + + if (rule) { + editingPriority.value = priority; + newRule.value = { + type: rule.type, + payload: rule.payload, + action: rule.action, + additional_params: rule.additional_params?.join(', ') || '', + priority: rule.priority + }; + ruleDialog.value = true; + } +} + +// 保存规则 +async function saveRule() { + try { + const requestData = { + type: editingType.value, // "top" 或 "ruleset" + rule_data: { + ...newRule.value, + additional_params: newRule.value.additional_params + ? newRule.value.additional_params.split(',').map(param => param.trim()).filter(param => param) + : [] + } + }; + + const method = editingPriority.value === null ? 'post' : 'put'; + await props.api[method]('/plugin/ClashRuleProvider/rule', requestData); + + ruleDialog.value = false; + await refreshData(); + + // 显示成功提示 + snackbar.value = { + show: true, + message: editingPriority.value === null ? '规则添加成功' : '规则更新成功', + color: 'success' + }; + } catch (err) { + console.error('保存规则失败:', err); + error.value = err.message || '保存规则失败'; + snackbar.value = { + show: true, + message: '保存规则失败: ' + (err.message || '未知错误'), + color: 'error' + }; + } +} + +// 删除规则 +async function deleteRule(priority, type = 'top') { + try { + await props.api.delete('/plugin/ClashRuleProvider/rule', { + data: { + type: type, // 规则类型 + priority: priority // 要删除的规则优先级 + } + }); + await refreshData(); + } catch (err) { + error.value = err.message || '删除规则失败'; + } +} + + +// 更新订阅 +async function updateSubscription() { + if (!subscriptionUrl.value) { + error.value = '请先输入订阅URL'; + return + } + + updatingSubscription.value = true; + try { + await props.api.put('plugin/ClashRuleProvider/subscription', { + url: subscriptionUrl.value + }); + // 显示成功提示 + snackbar.value = { + show: true, + message: '订阅更新成功', + color: 'success' + }; + await refreshData(); + } catch (err) { + console.error('更新订阅失败:', err); + error.value = err.message; + } finally { + updatingSubscription.value = false; + } +} + + +// 获取和刷新数据 +async function refreshData() { + loading.value = true; + error.value = null; + const wasPanelOpen = expansionPanels.value === (0); // 检查订阅面板是否展开 + try { + const state = await props.api.get('/plugin/ClashRuleProvider/status'); + status.value = state?.data?.state ? 'running' : 'disabled'; + subUrl.value = state?.data?.sub_url || ''; // 从API获取订阅URL + // 更新订阅信息 + if (state?.data?.subscription_info) { + subscriptionInfo.value = { + ...state.data.subscription_info, + used_percentage: calculatePercentage( + state.data.subscription_info.download, + state.data.subscription_info.total + ), + rule_size: state?.data?.clash?.rule_size + }; + } + rulesetPrefix.value = state?.data?.ruleset_prefix || 'Custom_'; + // 直接从响应中获取规则数组 + const response = await props.api.get('/plugin/ClashRuleProvider/rules?rule_type=top'); + rules.value = response?.data.rules || []; + + const response_ruleset = await props.api.get('/plugin/ClashRuleProvider/rules?rule_type=ruleset'); + ruleset_rules.value = response_ruleset?.data.rules || []; + + // 获取订阅信息 + const subscription = await props.api.get('/plugin/ClashRuleProvider/subscription'); + subscriptionUrl.value = subscription?.data.url; + // 获取自定义出站 + const outboundsResponse = await props.api.get('/plugin/ClashRuleProvider/clash_outbound'); + customOutbounds.value = outboundsResponse?.data.outbound || []; + + const providersResponse = await props.api.get('/plugin/ClashRuleProvider/rule_providers'); + ruleProviders.value = providersResponse?.data || {}; + + lastUpdated.value = new Date().toLocaleString(); + // 刷新后恢复面板状态 + if (wasPanelOpen && !(expansionPanels.value === 0)) { + expansionPanels.value = 0; + } + } catch (err) { + console.error('获取数据失败:', err); + error.value = err.message || '获取数据失败'; + status.value = 'error'; + } finally { + loading.value = false; + emit('action'); + } +} + +// 通知主应用切换到配置页面 +function notifySwitch() { + emit('switch'); +} + +// 通知主应用关闭组件 +function notifyClose() { + emit('close'); +} + +// 组件挂载时加载数据 +onMounted(() => { + refreshData(); +}); + +return (_ctx, _cache) => { + const _component_v_card_title = _resolveComponent("v-card-title"); + const _component_v_icon = _resolveComponent("v-icon"); + const _component_v_btn = _resolveComponent("v-btn"); + const _component_v_card_item = _resolveComponent("v-card-item"); + const _component_v_alert = _resolveComponent("v-alert"); + const _component_v_skeleton_loader = _resolveComponent("v-skeleton-loader"); + const _component_v_chip = _resolveComponent("v-chip"); + const _component_v_table = _resolveComponent("v-table"); + const _component_v_tooltip = _resolveComponent("v-tooltip"); + const _component_v_expansion_panel_title = _resolveComponent("v-expansion-panel-title"); + const _component_v_text_field = _resolveComponent("v-text-field"); + const _component_v_progress_linear = _resolveComponent("v-progress-linear"); + const _component_v_card_text = _resolveComponent("v-card-text"); + const _component_v_card = _resolveComponent("v-card"); + const _component_v_expansion_panel_text = _resolveComponent("v-expansion-panel-text"); + const _component_v_expansion_panel = _resolveComponent("v-expansion-panel"); + const _component_v_expansion_panels = _resolveComponent("v-expansion-panels"); + const _component_v_col = _resolveComponent("v-col"); + const _component_v_row = _resolveComponent("v-row"); + const _component_v_spacer = _resolveComponent("v-spacer"); + const _component_v_card_actions = _resolveComponent("v-card-actions"); + const _component_v_snackbar = _resolveComponent("v-snackbar"); + const _component_v_select = _resolveComponent("v-select"); + const _component_v_dialog = _resolveComponent("v-dialog"); + + return (_openBlock(), _createElementBlock("div", _hoisted_1, [ + _createVNode(_component_v_card, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_item, null, { + append: _withCtx(() => [ + _createVNode(_component_v_btn, { + icon: "", + color: "primary", + variant: "text", + onClick: notifyClose + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[15] || (_cache[15] = [ + _createTextVNode("mdi-close") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => _cache[14] || (_cache[14] = [ + _createTextVNode("Clash Rule Provider") + ])), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, null, { + default: _withCtx(() => [ + (error.value) + ? (_openBlock(), _createBlock(_component_v_alert, { + key: 0, + type: "error", + class: "mb-4" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(error.value), 1) + ]), + _: 1 + })) + : _createCommentVNode("", true), + (loading.value) + ? (_openBlock(), _createBlock(_component_v_skeleton_loader, { + key: 1, + type: "card" + })) + : (_openBlock(), _createElementBlock("div", _hoisted_2, [ + _createElementVNode("div", _hoisted_3, [ + _createElementVNode("div", _hoisted_4, [ + _cache[18] || (_cache[18] = _createElementVNode("div", { class: "text-h6" }, "规则集规则", -1)), + _createVNode(_component_v_btn, { + color: "primary", + onClick: _cache[0] || (_cache[0] = $event => (openAddRuleDialog('ruleset'))) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[16] || (_cache[16] = [ + _createTextVNode("mdi-plus") + ])), + _: 1 + }), + _cache[17] || (_cache[17] = _createTextVNode(" 添加规则 ")) + ]), + _: 1 + }) + ]), + _createVNode(_component_v_table, { + density: "compact", + hover: "" + }, { + default: _withCtx(() => [ + _cache[22] || (_cache[22] = _createElementVNode("thead", null, [ + _createElementVNode("tr", null, [ + _createElementVNode("th", null, "优先级"), + _createElementVNode("th", null, "类型"), + _createElementVNode("th", null, "内容"), + _createElementVNode("th", null, "出站"), + _createElementVNode("th", null, "规则集"), + _createElementVNode("th", null, "操作") + ]) + ], -1)), + _createElementVNode("tbody", null, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(sortedRulesetRules.value, (rule) => { + return (_openBlock(), _createElementBlock("tr", { + key: rule.priority, + class: _normalizeClass({ 'bg-blue-lighten-5': rule._isHovered }), + draggable: "true", + onDragstart: $event => (dragStart($event, rule.priority, 'ruleset')), + onDragover: _withModifiers($event => (dragOver($event, rule.priority, 'ruleset')), ["prevent"]), + onDrop: $event => (drop($event, rule.priority, 'ruleset')) + }, [ + _createElementVNode("td", null, [ + _createVNode(_component_v_icon, { class: "drag-handle" }, { + default: _withCtx(() => _cache[19] || (_cache[19] = [ + _createTextVNode("mdi-drag") + ])), + _: 1 + }), + _createTextVNode(" " + _toDisplayString(rule.priority), 1) + ]), + _createElementVNode("td", null, _toDisplayString(rule.type), 1), + _createElementVNode("td", null, _toDisplayString(rule.payload), 1), + _createElementVNode("td", null, [ + _createVNode(_component_v_chip, { + color: getActionColor(rule.action), + size: "small" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(rule.action), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + _createElementVNode("td", null, _toDisplayString(rulesetPrefix.value) + _toDisplayString(rule.action), 1), + _createElementVNode("td", null, [ + _createVNode(_component_v_btn, { + icon: "", + size: "small", + color: "primary", + variant: "text", + onClick: $event => (editRule(rule.priority, 'ruleset')) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[20] || (_cache[20] = [ + _createTextVNode("mdi-pencil") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]), + _createVNode(_component_v_btn, { + icon: "", + size: "small", + color: "error", + variant: "text", + onClick: $event => (deleteRule(rule.priority, 'ruleset')) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[21] || (_cache[21] = [ + _createTextVNode("mdi-delete") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick"]) + ]) + ], 42, _hoisted_5)) + }), 128)) + ]) + ]), + _: 1 + }), + _cache[23] || (_cache[23] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *对规则集中规则的修改可以在Clash中立即生效。 ", -1)) + ]), + _createElementVNode("div", _hoisted_6, [ + _createElementVNode("div", _hoisted_7, [ + _cache[26] || (_cache[26] = _createElementVNode("div", { class: "text-h6" }, "置顶规则", -1)), + _createVNode(_component_v_btn, { + color: "primary", + onClick: _cache[1] || (_cache[1] = $event => (openAddRuleDialog('top'))) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[24] || (_cache[24] = [ + _createTextVNode("mdi-plus") + ])), + _: 1 + }), + _cache[25] || (_cache[25] = _createTextVNode(" 添加规则 ")) + ]), + _: 1 + }) + ]), + _createVNode(_component_v_table, { + density: "compact", + hover: "" + }, { + default: _withCtx(() => [ + _cache[31] || (_cache[31] = _createElementVNode("thead", null, [ + _createElementVNode("tr", null, [ + _createElementVNode("th", null, "优先级"), + _createElementVNode("th", null, "类型"), + _createElementVNode("th", null, "内容"), + _createElementVNode("th", null, "出站"), + _createElementVNode("th", null, "操作") + ]) + ], -1)), + _createElementVNode("tbody", null, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(sortedRules.value, (rule) => { + return (_openBlock(), _createElementBlock("tr", { + key: rule.priority, + class: _normalizeClass({ 'bg-blue-lighten-5': rule._isHovered }), + draggable: "true", + onDragstart: $event => (dragStart($event, rule.priority, 'top')), + onDragover: _withModifiers($event => (dragOver($event, rule.priority, 'top')), ["prevent"]), + onDrop: $event => (drop($event, rule.priority, 'top')) + }, [ + _createElementVNode("td", null, [ + _createVNode(_component_v_icon, { class: "drag-handle" }, { + default: _withCtx(() => _cache[27] || (_cache[27] = [ + _createTextVNode("mdi-drag") + ])), + _: 1 + }), + _createTextVNode(" " + _toDisplayString(rule.priority), 1) + ]), + _createElementVNode("td", null, _toDisplayString(rule.type), 1), + _createElementVNode("td", null, _toDisplayString(rule.payload), 1), + _createElementVNode("td", null, [ + _createVNode(_component_v_chip, { + color: getActionColor(rule.action), + size: "small" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(rule.action), 1) + ]), + _: 2 + }, 1032, ["color"]) + ]), + _createElementVNode("td", null, [ + _createVNode(_component_v_btn, { + icon: "", + size: "small", + color: "primary", + variant: "text", + onClick: $event => (editRule(rule.priority, 'top')), + disabled: isSystemRule(rule) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[28] || (_cache[28] = [ + _createTextVNode("mdi-pencil") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick", "disabled"]), + _createVNode(_component_v_btn, { + icon: "", + size: "small", + color: "error", + variant: "text", + onClick: $event => (deleteRule(rule.priority, 'top')), + disabled: isSystemRule(rule) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[29] || (_cache[29] = [ + _createTextVNode("mdi-delete") + ])), + _: 1 + }) + ]), + _: 2 + }, 1032, ["onClick", "disabled"]), + (isSystemRule(rule)) + ? (_openBlock(), _createBlock(_component_v_tooltip, { + key: 0, + activator: "parent", + location: "top" + }, { + default: _withCtx(() => _cache[30] || (_cache[30] = [ + _createTextVNode(" 根据规则集自动添加 ") + ])), + _: 1 + })) + : _createCommentVNode("", true) + ]) + ], 42, _hoisted_8)) + }), 128)) + ]) + ]), + _: 1 + }), + _cache[32] || (_cache[32] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *置顶规则用于管理来自规则集的匹配规则,这些规则会动态更新。 ", -1)), + _cache[33] || (_cache[33] = _createElementVNode("div", { class: "text-caption text-grey mt-2" }, " *对置顶规则的修改只有Clash更新配置后才会生效。 ", -1)) + ]), + _createVNode(_component_v_expansion_panels, { + modelValue: expansionPanels.value, + "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ((expansionPanels).value = $event)) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_expansion_panel, null, { + default: _withCtx(() => [ + _createVNode(_component_v_expansion_panel_title, null, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[34] || (_cache[34] = [ + _createTextVNode("mdi-cloud-download") + ])), + _: 1 + }), + _cache[35] || (_cache[35] = _createElementVNode("span", null, "订阅管理", -1)), + (subscriptionInfo.value.last_update) + ? (_openBlock(), _createBlock(_component_v_chip, { + key: 0, + size: "small", + color: "light-blue", + class: "ml-2" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(formatTimestamp(subscriptionInfo.value.last_update)), 1) + ]), + _: 1 + })) + : _createCommentVNode("", true), + (subscriptionInfo.value.expire) + ? (_openBlock(), _createBlock(_component_v_chip, { + key: 1, + size: "small", + color: getExpireColor(subscriptionInfo.value.expire), + class: "ml-2" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(formatTimestamp(subscriptionInfo.value.expire)), 1) + ]), + _: 1 + }, 8, ["color"])) + : _createCommentVNode("", true) + ]), + _: 1 + }), + _createVNode(_component_v_expansion_panel_text, null, { + default: _withCtx(() => [ + _createVNode(_component_v_text_field, { + modelValue: subscriptionUrl.value, + "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ((subscriptionUrl).value = $event)), + label: "订阅URL", + placeholder: "https://example.com/clash-rules.txt", + class: "mb-4", + readonly: "", + loading: loading.value + }, null, 8, ["modelValue", "loading"]), + _createVNode(_component_v_card, { + variant: "outlined", + class: "mb-4" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_card_text, null, { + default: _withCtx(() => [ + _createElementVNode("div", _hoisted_9, [ + _cache[36] || (_cache[36] = _createElementVNode("span", null, "已用流量:", -1)), + _createElementVNode("strong", null, _toDisplayString(formatBytes(subscriptionInfo.value.download + subscriptionInfo.value.upload)), 1) + ]), + _createElementVNode("div", _hoisted_10, [ + _cache[37] || (_cache[37] = _createElementVNode("span", null, "剩余流量:", -1)), + _createElementVNode("strong", null, _toDisplayString(formatBytes(subscriptionInfo.value.total - subscriptionInfo.value.download)), 1) + ]), + _createVNode(_component_v_progress_linear, { + "model-value": subscriptionInfo.value.used_percentage, + color: getUsageColor(subscriptionInfo.value.used_percentage), + height: "10", + class: "mb-2" + }, null, 8, ["model-value", "color"]), + _createElementVNode("div", _hoisted_11, [ + _createElementVNode("span", null, "下载:" + _toDisplayString(formatBytes(subscriptionInfo.value.download)), 1), + _createElementVNode("span", null, "上传:" + _toDisplayString(formatBytes(subscriptionInfo.value.upload)), 1), + _createElementVNode("span", null, "总量:" + _toDisplayString(formatBytes(subscriptionInfo.value.total)), 1) + ]) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "primary", + onClick: updateSubscription, + loading: updatingSubscription.value + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[38] || (_cache[38] = [ + _createTextVNode("mdi-cloud-sync") + ])), + _: 1 + }), + _cache[39] || (_cache[39] = _createTextVNode(" 更新订阅 ")) + ]), + _: 1 + }, 8, ["loading"]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]), + _createVNode(_component_v_row, null, { + default: _withCtx(() => [ + _createVNode(_component_v_col, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card, { class: "flex-grow-1" }, { + default: _withCtx(() => [ + _createVNode(_component_v_card_text, { class: "text-subtitle-2" }, { + default: _withCtx(() => [ + _cache[45] || (_cache[45] = _createElementVNode("div", { class: "text-h6 mb-2" }, "状态信息", -1)), + _createElementVNode("div", null, [ + _cache[40] || (_cache[40] = _createElementVNode("strong", null, "状态: ", -1)), + _createVNode(_component_v_chip, { + size: "small", + color: status.value === 'running' ? 'success' : 'warning' + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(status.value), 1) + ]), + _: 1 + }, 8, ["color"]) + ]), + _createElementVNode("div", null, [ + _cache[41] || (_cache[41] = _createElementVNode("strong", null, "订阅配置规则数量:", -1)), + _createTextVNode(" " + _toDisplayString(subscriptionInfo.value.rule_size), 1) + ]), + _createElementVNode("div", null, [ + _cache[42] || (_cache[42] = _createElementVNode("strong", null, "置顶规则数量:", -1)), + _createTextVNode(" " + _toDisplayString(sortedRules.value.length), 1) + ]), + _createElementVNode("div", null, [ + _cache[43] || (_cache[43] = _createElementVNode("strong", null, "规则集规则数量:", -1)), + _createTextVNode(" " + _toDisplayString(sortedRulesetRules.value.length), 1) + ]), + _createElementVNode("div", null, [ + _cache[44] || (_cache[44] = _createElementVNode("strong", null, "最后更新:", -1)), + _createTextVNode(" " + _toDisplayString(lastUpdated.value), 1) + ]) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_col, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card, { class: "flex-grow-1" }, { + default: _withCtx(() => [ + _createVNode(_component_v_card_text, { class: "text-subtitle-2" }, { + default: _withCtx(() => [ + _createElementVNode("div", _hoisted_12, [ + _cache[48] || (_cache[48] = _createElementVNode("div", { class: "text-h6" }, "订阅链接", -1)), + (subUrl.value) + ? (_openBlock(), _createBlock(_component_v_btn, { + key: 0, + icon: "", + size: "small", + variant: "text", + color: "primary", + onClick: _cache[4] || (_cache[4] = $event => (copyToClipboard(subUrl.value))) + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, null, { + default: _withCtx(() => _cache[46] || (_cache[46] = [ + _createTextVNode("mdi-content-copy") + ])), + _: 1 + }), + _createVNode(_component_v_tooltip, { + activator: "parent", + location: "top" + }, { + default: _withCtx(() => _cache[47] || (_cache[47] = [ + _createTextVNode("复制链接") + ])), + _: 1 + }) + ]), + _: 1 + })) + : _createCommentVNode("", true) + ]), + (subUrl.value) + ? (_openBlock(), _createElementBlock("div", _hoisted_13, [ + _createElementVNode("a", { + href: subUrl.value, + target: "_blank", + class: "text-primary" + }, _toDisplayString(subUrl.value), 9, _hoisted_14) + ])) + : (_openBlock(), _createElementBlock("div", _hoisted_15, "未配置订阅URL")) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ])) + ]), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_btn, { + color: "primary", + onClick: refreshData, + loading: loading.value + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[49] || (_cache[49] = [ + _createTextVNode("mdi-refresh") + ])), + _: 1 + }), + _cache[50] || (_cache[50] = _createTextVNode(" 刷新数据 ")) + ]), + _: 1 + }, 8, ["loading"]), + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "primary", + onClick: notifySwitch + }, { + default: _withCtx(() => [ + _createVNode(_component_v_icon, { left: "" }, { + default: _withCtx(() => _cache[51] || (_cache[51] = [ + _createTextVNode("mdi-cog") + ])), + _: 1 + }), + _cache[52] || (_cache[52] = _createTextVNode(" 配置 ")) + ]), + _: 1 + }) + ]), + _: 1 + }), + _createVNode(_component_v_snackbar, { + modelValue: snackbar.value.show, + "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ((snackbar.value.show) = $event)), + color: snackbar.value.color, + location: "bottom", + class: "mb-2" + }, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(snackbar.value.message), 1) + ]), + _: 1 + }, 8, ["modelValue", "color"]) + ]), + _: 1 + }), + _createVNode(_component_v_dialog, { + modelValue: ruleDialog.value, + "onUpdate:modelValue": _cache[13] || (_cache[13] = $event => ((ruleDialog).value = $event)), + "max-width": "600" + }, { + default: _withCtx(() => [ + _createVNode(_component_v_card, null, { + default: _withCtx(() => [ + _createVNode(_component_v_card_title, null, { + default: _withCtx(() => [ + _createTextVNode(_toDisplayString(editingPriority.value === null ? '添加规则' : '编辑规则'), 1) + ]), + _: 1 + }), + _createVNode(_component_v_card_text, null, { + default: _withCtx(() => [ + _createVNode(_component_v_select, { + modelValue: newRule.value.type, + "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ((newRule.value.type) = $event)), + items: ruleTypes.value, + label: "规则类型", + required: "", + class: "mb-4" + }, null, 8, ["modelValue", "items"]), + (newRule.value.type !== 'RULE-SET') + ? (_openBlock(), _createBlock(_component_v_text_field, { + key: 0, + modelValue: newRule.value.payload, + "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ((newRule.value.payload) = $event)), + label: "内容", + required: "", + class: "mb-4" + }, null, 8, ["modelValue"])) + : (_openBlock(), _createBlock(_component_v_select, { + key: 1, + modelValue: newRule.value.payload, + "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => ((newRule.value.payload) = $event)), + items: ruleProviderNames.value, + label: "选择规则集", + required: "", + class: "mb-4" + }, null, 8, ["modelValue", "items"])), + _createVNode(_component_v_select, { + modelValue: newRule.value.action, + "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => ((newRule.value.action) = $event)), + items: actions.value, + label: "出站", + required: "", + class: "mb-4" + }, null, 8, ["modelValue", "items"]), + (showAdditionalParams.value) + ? (_openBlock(), _createBlock(_component_v_select, { + key: 2, + modelValue: newRule.value.additional_params, + "onUpdate:modelValue": _cache[10] || (_cache[10] = $event => ((newRule.value.additional_params) = $event)), + label: "附加参数", + items: additionalParamOptions.value, + clearable: "", + hint: "可选参数", + "persistent-hint": "", + class: "mb-4" + }, null, 8, ["modelValue", "items"])) + : _createCommentVNode("", true), + (editingPriority.value !== null) + ? (_openBlock(), _createBlock(_component_v_text_field, { + key: 3, + modelValue: newRule.value.priority, + "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => ((newRule.value.priority) = $event)), + modelModifiers: { number: true }, + type: "number", + label: "优先级", + hint: "数字越小优先级越高", + "persistent-hint": "" + }, null, 8, ["modelValue"])) + : _createCommentVNode("", true) + ]), + _: 1 + }), + _createVNode(_component_v_card_actions, null, { + default: _withCtx(() => [ + _createVNode(_component_v_spacer), + _createVNode(_component_v_btn, { + color: "secondary", + onClick: _cache[12] || (_cache[12] = $event => (ruleDialog.value = false)) + }, { + default: _withCtx(() => _cache[53] || (_cache[53] = [ + _createTextVNode("取消") + ])), + _: 1 + }), + _createVNode(_component_v_btn, { + color: "primary", + onClick: saveRule + }, { + default: _withCtx(() => _cache[54] || (_cache[54] = [ + _createTextVNode("保存") + ])), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }) + ]), + _: 1 + }, 8, ["modelValue"]) + ])) +} +} + +}; +const PageComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-d5e502a5"]]); + +export { PageComponent as default }; diff --git a/plugins.v2/clashruleprovider/dist/assets/__federation_fn_import-JrT3xvdd.js b/plugins.v2/clashruleprovider/dist/assets/__federation_fn_import-JrT3xvdd.js new file mode 100644 index 0000000..ed562f5 --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/__federation_fn_import-JrT3xvdd.js @@ -0,0 +1,418 @@ +const buildIdentifier = "[0-9A-Za-z-]+"; +const build = `(?:\\+(${buildIdentifier}(?:\\.${buildIdentifier})*))`; +const numericIdentifier = "0|[1-9]\\d*"; +const numericIdentifierLoose = "[0-9]+"; +const nonNumericIdentifier = "\\d*[a-zA-Z-][a-zA-Z0-9-]*"; +const preReleaseIdentifierLoose = `(?:${numericIdentifierLoose}|${nonNumericIdentifier})`; +const preReleaseLoose = `(?:-?(${preReleaseIdentifierLoose}(?:\\.${preReleaseIdentifierLoose})*))`; +const preReleaseIdentifier = `(?:${numericIdentifier}|${nonNumericIdentifier})`; +const preRelease = `(?:-(${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*))`; +const xRangeIdentifier = `${numericIdentifier}|x|X|\\*`; +const xRangePlain = `[v=\\s]*(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:${preRelease})?${build}?)?)?`; +const hyphenRange = `^\\s*(${xRangePlain})\\s+-\\s+(${xRangePlain})\\s*$`; +const mainVersionLoose = `(${numericIdentifierLoose})\\.(${numericIdentifierLoose})\\.(${numericIdentifierLoose})`; +const loosePlain = `[v=\\s]*${mainVersionLoose}${preReleaseLoose}?${build}?`; +const gtlt = "((?:<|>)?=?)"; +const comparatorTrim = `(\\s*)${gtlt}\\s*(${loosePlain}|${xRangePlain})`; +const loneTilde = "(?:~>?)"; +const tildeTrim = `(\\s*)${loneTilde}\\s+`; +const loneCaret = "(?:\\^)"; +const caretTrim = `(\\s*)${loneCaret}\\s+`; +const star = "(<|>)?=?\\s*\\*"; +const caret = `^${loneCaret}${xRangePlain}$`; +const mainVersion = `(${numericIdentifier})\\.(${numericIdentifier})\\.(${numericIdentifier})`; +const fullPlain = `v?${mainVersion}${preRelease}?${build}?`; +const tilde = `^${loneTilde}${xRangePlain}$`; +const xRange = `^${gtlt}\\s*${xRangePlain}$`; +const comparator = `^${gtlt}\\s*(${fullPlain})$|^$`; +const gte0 = "^\\s*>=\\s*0.0.0\\s*$"; +function parseRegex(source) { + return new RegExp(source); +} +function isXVersion(version) { + return !version || version.toLowerCase() === "x" || version === "*"; +} +function pipe(...fns) { + return (x) => { + return fns.reduce((v, f) => f(v), x); + }; +} +function extractComparator(comparatorString) { + return comparatorString.match(parseRegex(comparator)); +} +function combineVersion(major, minor, patch, preRelease2) { + const mainVersion2 = `${major}.${minor}.${patch}`; + if (preRelease2) { + return `${mainVersion2}-${preRelease2}`; + } + return mainVersion2; +} +function parseHyphen(range) { + return range.replace( + parseRegex(hyphenRange), + (_range, from, fromMajor, fromMinor, fromPatch, _fromPreRelease, _fromBuild, to, toMajor, toMinor, toPatch, toPreRelease) => { + if (isXVersion(fromMajor)) { + from = ""; + } else if (isXVersion(fromMinor)) { + from = `>=${fromMajor}.0.0`; + } else if (isXVersion(fromPatch)) { + from = `>=${fromMajor}.${fromMinor}.0`; + } else { + from = `>=${from}`; + } + if (isXVersion(toMajor)) { + to = ""; + } else if (isXVersion(toMinor)) { + to = `<${+toMajor + 1}.0.0-0`; + } else if (isXVersion(toPatch)) { + to = `<${toMajor}.${+toMinor + 1}.0-0`; + } else if (toPreRelease) { + to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}`; + } else { + to = `<=${to}`; + } + return `${from} ${to}`.trim(); + } + ); +} +function parseComparatorTrim(range) { + return range.replace(parseRegex(comparatorTrim), "$1$2$3"); +} +function parseTildeTrim(range) { + return range.replace(parseRegex(tildeTrim), "$1~"); +} +function parseCaretTrim(range) { + return range.replace(parseRegex(caretTrim), "$1^"); +} +function parseCarets(range) { + return range.trim().split(/\s+/).map((rangeVersion) => { + return rangeVersion.replace( + parseRegex(caret), + (_, major, minor, patch, preRelease2) => { + if (isXVersion(major)) { + return ""; + } else if (isXVersion(minor)) { + return `>=${major}.0.0 <${+major + 1}.0.0-0`; + } else if (isXVersion(patch)) { + if (major === "0") { + return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`; + } else { + return `>=${major}.${minor}.0 <${+major + 1}.0.0-0`; + } + } else if (preRelease2) { + if (major === "0") { + if (minor === "0") { + return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${minor}.${+patch + 1}-0`; + } else { + return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`; + } + } else { + return `>=${major}.${minor}.${patch}-${preRelease2} <${+major + 1}.0.0-0`; + } + } else { + if (major === "0") { + if (minor === "0") { + return `>=${major}.${minor}.${patch} <${major}.${minor}.${+patch + 1}-0`; + } else { + return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`; + } + } + return `>=${major}.${minor}.${patch} <${+major + 1}.0.0-0`; + } + } + ); + }).join(" "); +} +function parseTildes(range) { + return range.trim().split(/\s+/).map((rangeVersion) => { + return rangeVersion.replace( + parseRegex(tilde), + (_, major, minor, patch, preRelease2) => { + if (isXVersion(major)) { + return ""; + } else if (isXVersion(minor)) { + return `>=${major}.0.0 <${+major + 1}.0.0-0`; + } else if (isXVersion(patch)) { + return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`; + } else if (preRelease2) { + return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`; + } + return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`; + } + ); + }).join(" "); +} +function parseXRanges(range) { + return range.split(/\s+/).map((rangeVersion) => { + return rangeVersion.trim().replace( + parseRegex(xRange), + (ret, gtlt2, major, minor, patch, preRelease2) => { + const isXMajor = isXVersion(major); + const isXMinor = isXMajor || isXVersion(minor); + const isXPatch = isXMinor || isXVersion(patch); + if (gtlt2 === "=" && isXPatch) { + gtlt2 = ""; + } + preRelease2 = ""; + if (isXMajor) { + if (gtlt2 === ">" || gtlt2 === "<") { + return "<0.0.0-0"; + } else { + return "*"; + } + } else if (gtlt2 && isXPatch) { + if (isXMinor) { + minor = 0; + } + patch = 0; + if (gtlt2 === ">") { + gtlt2 = ">="; + if (isXMinor) { + major = +major + 1; + minor = 0; + patch = 0; + } else { + minor = +minor + 1; + patch = 0; + } + } else if (gtlt2 === "<=") { + gtlt2 = "<"; + if (isXMinor) { + major = +major + 1; + } else { + minor = +minor + 1; + } + } + if (gtlt2 === "<") { + preRelease2 = "-0"; + } + return `${gtlt2 + major}.${minor}.${patch}${preRelease2}`; + } else if (isXMinor) { + return `>=${major}.0.0${preRelease2} <${+major + 1}.0.0-0`; + } else if (isXPatch) { + return `>=${major}.${minor}.0${preRelease2} <${major}.${+minor + 1}.0-0`; + } + return ret; + } + ); + }).join(" "); +} +function parseStar(range) { + return range.trim().replace(parseRegex(star), ""); +} +function parseGTE0(comparatorString) { + return comparatorString.trim().replace(parseRegex(gte0), ""); +} +function compareAtom(rangeAtom, versionAtom) { + rangeAtom = +rangeAtom || rangeAtom; + versionAtom = +versionAtom || versionAtom; + if (rangeAtom > versionAtom) { + return 1; + } + if (rangeAtom === versionAtom) { + return 0; + } + return -1; +} +function comparePreRelease(rangeAtom, versionAtom) { + const { preRelease: rangePreRelease } = rangeAtom; + const { preRelease: versionPreRelease } = versionAtom; + if (rangePreRelease === void 0 && !!versionPreRelease) { + return 1; + } + if (!!rangePreRelease && versionPreRelease === void 0) { + return -1; + } + if (rangePreRelease === void 0 && versionPreRelease === void 0) { + return 0; + } + for (let i = 0, n = rangePreRelease.length; i <= n; i++) { + const rangeElement = rangePreRelease[i]; + const versionElement = versionPreRelease[i]; + if (rangeElement === versionElement) { + continue; + } + if (rangeElement === void 0 && versionElement === void 0) { + return 0; + } + if (!rangeElement) { + return 1; + } + if (!versionElement) { + return -1; + } + return compareAtom(rangeElement, versionElement); + } + return 0; +} +function compareVersion(rangeAtom, versionAtom) { + return compareAtom(rangeAtom.major, versionAtom.major) || compareAtom(rangeAtom.minor, versionAtom.minor) || compareAtom(rangeAtom.patch, versionAtom.patch) || comparePreRelease(rangeAtom, versionAtom); +} +function eq(rangeAtom, versionAtom) { + return rangeAtom.version === versionAtom.version; +} +function compare(rangeAtom, versionAtom) { + switch (rangeAtom.operator) { + case "": + case "=": + return eq(rangeAtom, versionAtom); + case ">": + return compareVersion(rangeAtom, versionAtom) < 0; + case ">=": + return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0; + case "<": + return compareVersion(rangeAtom, versionAtom) > 0; + case "<=": + return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0; + case void 0: { + return true; + } + default: + return false; + } +} +function parseComparatorString(range) { + return pipe( + parseCarets, + parseTildes, + parseXRanges, + parseStar + )(range); +} +function parseRange(range) { + return pipe( + parseHyphen, + parseComparatorTrim, + parseTildeTrim, + parseCaretTrim + )(range.trim()).split(/\s+/).join(" "); +} +function satisfy(version, range) { + if (!version) { + return false; + } + const parsedRange = parseRange(range); + const parsedComparator = parsedRange.split(" ").map((rangeVersion) => parseComparatorString(rangeVersion)).join(" "); + const comparators = parsedComparator.split(/\s+/).map((comparator2) => parseGTE0(comparator2)); + const extractedVersion = extractComparator(version); + if (!extractedVersion) { + return false; + } + const [ + , + versionOperator, + , + versionMajor, + versionMinor, + versionPatch, + versionPreRelease + ] = extractedVersion; + const versionAtom = { + version: combineVersion( + versionMajor, + versionMinor, + versionPatch, + versionPreRelease + ), + major: versionMajor, + minor: versionMinor, + patch: versionPatch, + preRelease: versionPreRelease == null ? void 0 : versionPreRelease.split(".") + }; + for (const comparator2 of comparators) { + const extractedComparator = extractComparator(comparator2); + if (!extractedComparator) { + return false; + } + const [ + , + rangeOperator, + , + rangeMajor, + rangeMinor, + rangePatch, + rangePreRelease + ] = extractedComparator; + const rangeAtom = { + operator: rangeOperator, + version: combineVersion( + rangeMajor, + rangeMinor, + rangePatch, + rangePreRelease + ), + major: rangeMajor, + minor: rangeMinor, + patch: rangePatch, + preRelease: rangePreRelease == null ? void 0 : rangePreRelease.split(".") + }; + if (!compare(rangeAtom, versionAtom)) { + return false; + } + } + return true; +} + +// eslint-disable-next-line no-undef +const moduleMap = {}; +const moduleCache = Object.create(null); +async function importShared(name, shareScope = 'default') { + return moduleCache[name] + ? new Promise((r) => r(moduleCache[name])) + : (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name) +} +async function getSharedFromRuntime(name, shareScope) { + let module = null; + if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { + const versionObj = globalThis.__federation_shared__[shareScope][name]; + const requiredVersion = moduleMap[name]?.requiredVersion; + const hasRequiredVersion = !!requiredVersion; + if (hasRequiredVersion) { + const versionKey = Object.keys(versionObj).find((version) => + satisfy(version, requiredVersion) + ); + if (versionKey) { + const versionValue = versionObj[versionKey]; + module = await (await versionValue.get())(); + } else { + console.log( + `provider support ${name}(${versionKey}) is not satisfied requiredVersion(\${moduleMap[name].requiredVersion})` + ); + } + } else { + const versionKey = Object.keys(versionObj)[0]; + const versionValue = versionObj[versionKey]; + module = await (await versionValue.get())(); + } + } + if (module) { + return flattenModule(module, name) + } +} +async function getSharedFromLocal(name) { + if (moduleMap[name]?.import) { + let module = await (await moduleMap[name].get())(); + return flattenModule(module, name) + } else { + console.error( + `consumer config import=false,so cant use callback shared module` + ); + } +} +function flattenModule(module, name) { + // use a shared module which export default a function will getting error 'TypeError: xxx is not a function' + if (typeof module.default === 'function') { + Object.keys(module).forEach((key) => { + if (key !== 'default') { + module.default[key] = module[key]; + } + }); + moduleCache[name] = module.default; + return module.default + } + if (module.default) module = Object.assign({}, module.default, module); + moduleCache[name] = module; + return module +} + +export { importShared, getSharedFromLocal as importSharedLocal, getSharedFromRuntime as importSharedRuntime }; diff --git a/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js b/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js new file mode 100644 index 0000000..3da658f --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js @@ -0,0 +1,9 @@ +const _export_sfc = (sfc, props) => { + const target = sfc.__vccOpts || sfc; + for (const [key, val] of props) { + target[key] = val; + } + return target; +}; + +export { _export_sfc as _ }; diff --git a/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js b/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js new file mode 100644 index 0000000..015521f --- /dev/null +++ b/plugins.v2/clashruleprovider/dist/assets/remoteEntry.js @@ -0,0 +1,87 @@ +const currentImports = {}; + const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); + let moduleMap = { +"./Page":()=>{ + dynamicLoadingCss(["__federation_expose_Page-Bl7XNZ7k.css"], false, './Page'); + return __federation_import('./__federation_expose_Page-DlQgf7u6.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, +"./Config":()=>{ + dynamicLoadingCss(["__federation_expose_Config-DXzIavcD.css"], false, './Config'); + return __federation_import('./__federation_expose_Config-C3BpNVeC.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)}, +"./Dashboard":()=>{ + dynamicLoadingCss([], false, './Dashboard'); + return __federation_import('./__federation_expose_Dashboard-BkyO-3pr.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},}; + const seen = {}; + const dynamicLoadingCss = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { + const metaUrl = import.meta.url; + if (typeof metaUrl === 'undefined') { + console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".'); + return; + } + + const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('remoteEntry.js')); + const base = '/'; + 'assets'; + + cssFilePaths.forEach(cssPath => { + let href = ''; + const baseUrl = base || curUrl; + if (baseUrl) { + const trimmer = { + trailing: (path) => (path.endsWith('/') ? path.slice(0, -1) : path), + leading: (path) => (path.startsWith('/') ? path.slice(1) : path) + }; + const isAbsoluteUrl = (url) => url.startsWith('http') || url.startsWith('//'); + + const cleanBaseUrl = trimmer.trailing(baseUrl); + const cleanCssPath = trimmer.leading(cssPath); + const cleanCurUrl = trimmer.trailing(curUrl); + + if (isAbsoluteUrl(baseUrl)) { + href = [cleanBaseUrl, cleanCssPath].filter(Boolean).join('/'); + } else { + if (cleanCurUrl.includes(cleanBaseUrl)) { + href = [cleanCurUrl, cleanCssPath].filter(Boolean).join('/'); + } else { + href = [cleanCurUrl + cleanBaseUrl, cleanCssPath].filter(Boolean).join('/'); + } + } + } else { + href = cssPath; + } + + if (dontAppendStylesToHead) { + const key = 'css__ClashRuleProvider__' + exposeItemName; + window[key] = window[key] || []; + window[key].push(href); + return; + } + + if (href in seen) return; + seen[href] = true; + + const element = document.createElement('link'); + element.rel = 'stylesheet'; + element.href = href; + document.head.appendChild(element); + }); + }; + async function __federation_import(name) { + currentImports[name] ??= import(name); + return currentImports[name] + } const get =(module) => { + if(!moduleMap[module]) throw new Error('Can not find remote module ' + module) + return moduleMap[module](); + }; + const init =(shareScope) => { + globalThis.__federation_shared__= globalThis.__federation_shared__|| {}; + Object.entries(shareScope).forEach(([key, value]) => { + for (const [versionKey, versionValue] of Object.entries(value)) { + const scope = versionValue.scope || 'default'; + globalThis.__federation_shared__[scope] = globalThis.__federation_shared__[scope] || {}; + const shared= globalThis.__federation_shared__[scope]; + (shared[key] = shared[key]||{})[versionKey] = versionValue; + } + }); + }; + +export { dynamicLoadingCss, get, init }; diff --git a/plugins.v2/cleaninvalidseed/__init__.py b/plugins.v2/cleaninvalidseed/__init__.py index 8f2c820..6a054cb 100644 --- a/plugins.v2/cleaninvalidseed/__init__.py +++ b/plugins.v2/cleaninvalidseed/__init__.py @@ -1,24 +1,22 @@ -import glob -import os import shutil -import time from datetime import datetime, timedelta from pathlib import Path +from typing import Any, List, Dict, Tuple, Optional import pytz from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger -from app.utils.string import StringUtils -from app.schemas.types import EventType -from app.schemas import ServiceInfo -from app.core.event import eventmanager, Event from app.core.config import settings -from app.plugins import _PluginBase -from typing import Any, List, Dict, Tuple, Optional -from app.log import logger -from app.schemas import NotificationType +from app.core.event import eventmanager, Event from app.helper.downloader import DownloaderHelper +from app.log import logger +from app.plugins import _PluginBase +from app.schemas import NotificationType +from app.schemas import ServiceInfo +from app.schemas.types import EventType +from app.utils.string import StringUtils + class CleanInvalidSeed(_PluginBase): # 插件名称 @@ -56,6 +54,7 @@ class CleanInvalidSeed(_PluginBase): _exclude_categories = "" _exclude_labels = "" _more_logs = False + _downloaders = [] # 定时器 _scheduler: Optional[BackgroundScheduler] = None _error_msg = [ @@ -67,7 +66,7 @@ class CleanInvalidSeed(_PluginBase): _custom_error_msg = "" def init_plugin(self, config: dict = None): - self.downloader_helper = DownloaderHelper() + # 停止现有任务 self.stop_service() @@ -97,8 +96,7 @@ class CleanInvalidSeed(_PluginBase): self._scheduler.add_job( func=self.clean_invalid_seed, trigger="date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) - + timedelta(seconds=3), + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), name="清理无效种子", ) # 关闭一次性开关 @@ -137,7 +135,7 @@ class CleanInvalidSeed(_PluginBase): ) @property - def service_info(self) -> Optional[ServiceInfo]: + def service_info(self) -> Optional[Dict[str, ServiceInfo]]: """ 服务信息 """ @@ -145,7 +143,7 @@ class CleanInvalidSeed(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - services = self.downloader_helper.get_services(name_filters=self._downloaders) + services = DownloaderHelper().get_services(name_filters=self._downloaders) if not services: logger.warning("获取下载器实例失败,请检查配置") @@ -166,11 +164,12 @@ class CleanInvalidSeed(_PluginBase): return active_services - def check_is_qb(self, service_info) -> bool: + @staticmethod + def check_is_qb(service_info) -> bool: """ 检查下载器类型是否为 qbittorrent 或 transmission """ - if self.downloader_helper.is_downloader(service_type="qbittorrent", service=service_info): + if DownloaderHelper().is_downloader(service_type="qbittorrent", service=service_info): return True return False @@ -225,11 +224,11 @@ class CleanInvalidSeed(_PluginBase): event_data = event.event_data if event_data: if not ( - event_data.get("action") == "detect_invalid_torrents" - or event_data.get("action") == "delete_invalid_torrents" - or event_data.get("action") == "detect_invalid_files" - or event_data.get("action") == "delete_invalid_files" - or event_data.get("action") == "toggle_notify_all" + event_data.get("action") == "detect_invalid_torrents" + or event_data.get("action") == "delete_invalid_torrents" + or event_data.get("action") == "detect_invalid_files" + or event_data.get("action") == "delete_invalid_files" + or event_data.get("action") == "toggle_notify_all" ): return self.post_message( @@ -344,7 +343,7 @@ class CleanInvalidSeed(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue logger.info(f"开始清理 {downloader_name} 无效做种...") all_torrents = self.get_all_torrents(service) @@ -376,13 +375,14 @@ class CleanInvalidSeed(_PluginBase): is_tracker_working = True if not ( - (tracker.get("status") == 4) and (tracker.get("msg") in error_msgs) + (tracker.get("status") == 4) and (tracker.get("msg") in error_msgs) ): is_invalid = False working_tracker_set.add(tracker_domian) if self._more_logs: - logger.info(f"处理 [{torrent.name}] tracker [{tracker_domian}]: 分类: [{torrent.category}], 标签: [{torrent.tags}], 状态: [{tracker.get('status')}], msg: [{tracker.get('msg')}], is_invalid: [{is_invalid}], is_working: [{is_tracker_working}]") + logger.info( + f"处理 [{torrent.name}] tracker [{tracker_domian}]: 分类: [{torrent.category}], 标签: [{torrent.tags}], 状态: [{tracker.get('status')}], msg: [{tracker.get('msg')}], is_invalid: [{is_invalid}], is_working: [{is_tracker_working}]") if is_invalid: temp_invalid_torrents.append(torrent) elif not is_tracker_working: @@ -433,25 +433,30 @@ class CleanInvalidSeed(_PluginBase): if not is_excluded: if self._label_only: # 仅标记 - downloader_obj.set_torrents_tag(ids=torrent.get("hash"), tags=[self._label if self._label != "" else "无效做种"]) + downloader_obj.set_torrents_tag(ids=torrent.get("hash"), tags=[ + self._label if self._label != "" else "无效做种"]) else: # 只删除种子不删除文件,以防其它站点辅种 downloader_obj.delete_torrents(False, torrent.get("hash")) # 标记已处理种子信息 deleted_torrent_tuple_list.append( - ( - torrent.name, - torrent.category, - torrent.tags, - torrent.size, - tracker_domian, - tracker.msg, - ) + ( + torrent.name, + torrent.category, + torrent.tags, + torrent.size, + tracker_domian, + tracker.msg, ) + ) break invalid_msg = f"检测到{len(invalid_torrent_tuple_list)}个失效做种\n" tracker_not_working_msg = f"检测到{len(tracker_not_working_torrents)}个tracker未工作做种,请检查种子状态\n" + exclude_categories_msg = "" + exclude_labels_msg = "" + deleted_msg = "" + if self._label_only or self._delete_invalid_torrents: if self._label_only: deleted_msg = f"标记了{len(deleted_torrent_tuple_list)}个失效种子\n" @@ -513,34 +518,34 @@ class CleanInvalidSeed(_PluginBase): logger.info(exclude_labels_msg) # 通知 if self._notify: - invalid_msg = invalid_msg.replace("_", "\_") + invalid_msg = invalid_msg.replace("_", "\\_") self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=invalid_msg, ) if self._notify_all: - tracker_not_working_msg = tracker_not_working_msg.replace("_", "\_") + tracker_not_working_msg = tracker_not_working_msg.replace("_", "\\_") self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=tracker_not_working_msg, ) if self._label_only or self._delete_invalid_torrents: - deleted_msg = deleted_msg.replace("_", "\_") + deleted_msg = deleted_msg.replace("_", "\\_") self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=deleted_msg, ) if self._notify_all: - exclude_categories_msg = exclude_categories_msg.replace("_", "\_") + exclude_categories_msg = exclude_categories_msg.replace("_", "\\_") self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=exclude_categories_msg, ) - exclude_labels_msg = exclude_labels_msg.replace("_", "\_") + exclude_labels_msg = exclude_labels_msg.replace("_", "\\_") self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", @@ -559,7 +564,7 @@ class CleanInvalidSeed(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue all_torrents += self.get_all_torrents(service) @@ -638,7 +643,7 @@ class CleanInvalidSeed(_PluginBase): message += f"***已删除无效源文件,释放{StringUtils.str_filesize(total_size)}空间!***\n" logger.info(message) if self._notify: - message = message.replace("_", "\_") + message = message.replace("_", "\\_") self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", @@ -646,7 +651,8 @@ class CleanInvalidSeed(_PluginBase): ) logger.info("检测无效源文件任务结束") - def get_size(self, path: Path): + @staticmethod + def get_size(path: Path): total_size = 0 if path.is_file(): return path.stat().st_size @@ -801,7 +807,7 @@ class CleanInvalidSeed(_PluginBase): 'model': 'downloaders', 'label': '请选择下载器', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] @@ -813,7 +819,7 @@ class CleanInvalidSeed(_PluginBase): "content": [ { "component": "VCol", - "props": { "cols": 12, "md": 6 }, + "props": {"cols": 12, "md": 6}, "content": [ { "component": "VTextField", @@ -826,7 +832,7 @@ class CleanInvalidSeed(_PluginBase): }, { "component": "VCol", - "props": { "cols": 12, "md": 6 }, + "props": {"cols": 12, "md": 6}, "content": [ { "component": "VTextField", diff --git a/plugins.v2/crossseed/__init__.py b/plugins.v2/crossseed/__init__.py index 6ccc869..3301350 100644 --- a/plugins.v2/crossseed/__init__.py +++ b/plugins.v2/crossseed/__init__.py @@ -194,10 +194,6 @@ class CrossSeed(_PluginBase): # 私有属性 _scheduler = None cross_helper = None - sites = None - siteoper = None - torrent = None - downloader_helper = None # 开关 _enabled = False _cron = None @@ -233,10 +229,7 @@ class CrossSeed(_PluginBase): cached = 0 def init_plugin(self, config: dict = None): - self.sites = SitesHelper() - self.siteoper = SiteOper() - self.torrent = TorrentHelper() - self.downloader_helper = DownloaderHelper() + # 读取配置 if config: self._enabled = config.get("enabled") @@ -257,7 +250,7 @@ class CrossSeed(_PluginBase): self._success_caches = [] if self._clearcache else config.get("success_caches") or [] # 过滤掉已删除的站点 - inner_site_list = self.siteoper.list_order_by_pri() + inner_site_list = SiteOper().list_order_by_pri() all_sites = [(site.id, site.name) for site in inner_site_list] + [ (site.get("id"), site.get("name")) for site in self.__custom_sites() ] @@ -363,7 +356,7 @@ class CrossSeed(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - services = self.downloader_helper.get_services(name_filters=self._downloaders) + services = DownloaderHelper().get_services(name_filters=self._downloaders) if not services: logger.warning("获取下载器实例失败,请检查配置") return None @@ -445,7 +438,7 @@ class CrossSeed(_PluginBase): # 站点的可选项 site_options = ([{"title": site.name, "value": site.id} - for site in self.siteoper.list_order_by_pri()] + for site in SiteOper().list_order_by_pri()] + [{"title": site.get("name"), "value": site.get("id")} for site in customSites]) # 测试版本,只支持青蛙 @@ -557,7 +550,7 @@ class CrossSeed(_PluginBase): 'model': 'downloaders', 'label': '辅种下载器', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] @@ -852,7 +845,7 @@ class CrossSeed(_PluginBase): if not torrent_info.site_name: # 尝试通过域名获取站点信息 tracker_domain = StringUtils.get_url_domain(tracker) - site_info = self.sites.get_indexer(tracker_domain) + site_info = SitesHelper().get_indexer(tracker_domain) if site_info: torrent_info.site_name = site_info.get("name") @@ -983,7 +976,7 @@ class CrossSeed(_PluginBase): chunk_size = 100 for site_config in self._site_cs_infos: # 检查站点是否已经停用 - db_site = self.siteoper.get(site_config.id) + db_site = SiteOper().get(site_config.id) if db_site and not db_site.is_active: logger.info(f"站点{site_config.name}已停用,跳过辅种") continue @@ -1045,7 +1038,8 @@ class CrossSeed(_PluginBase): logger.info(f"下载器 {service.name} 辅种完成") - def __download(self, service: ServiceInfo, content: Union[bytes, str], + @staticmethod + def __download(service: ServiceInfo, content: Union[bytes, str], save_path: str) -> Optional[str]: """ 添加下载任务 @@ -1055,9 +1049,9 @@ class CrossSeed(_PluginBase): tag = StringUtils.generate_random_str(10) state = service.instance.add_torrent(content=content, - download_dir=save_path, - is_paused=True, - tag=["已整理", "辅种", tag]) + download_dir=save_path, + is_paused=True, + tag=["已整理", "辅种", tag]) if not state: return None else: @@ -1070,9 +1064,9 @@ class CrossSeed(_PluginBase): elif service.type == "transmission": # 添加任务 torrent = service.instance.add_torrent(content=content, - download_dir=save_path, - is_paused=True, - labels=["已整理", "辅种"]) + download_dir=save_path, + is_paused=True, + labels=["已整理", "辅种"]) if not torrent: return None else: @@ -1099,7 +1093,7 @@ class CrossSeed(_PluginBase): torrent_url = site_config.get_torrent_url(tor.torrent_id) # 下载种子文件 - _, content, _, _, error_msg = self.torrent.download_torrent( + _, content, _, _, error_msg = TorrentHelper().download_torrent( url=torrent_url, cookie=site_config.cookie, ua=site_config.ua or settings.USER_AGENT, diff --git a/plugins.v2/doubanrank/__init__.py b/plugins.v2/doubanrank/__init__.py index 8459ac7..94484d2 100644 --- a/plugins.v2/doubanrank/__init__.py +++ b/plugins.v2/doubanrank/__init__.py @@ -45,9 +45,6 @@ class DoubanRank(_PluginBase): # 退出事件 _event = Event() # 私有属性 - downloadchain: DownloadChain = None - subscribechain: SubscribeChain = None - mediachain: MediaChain = None _scheduler = None _douban_address = { 'movie-ustop': 'https://rsshub.app/douban/movie/ustop', @@ -70,9 +67,6 @@ class DoubanRank(_PluginBase): _proxy = False def init_plugin(self, config: dict = None): - self.downloadchain = DownloadChain() - self.subscribechain = SubscribeChain() - self.mediachain = MediaChain() if config: self._enabled = config.get("enabled") @@ -574,9 +568,10 @@ class DoubanRank(_PluginBase): if douban_id: # 识别豆瓣信息 if settings.RECOGNIZE_SOURCE == "themoviedb": - tmdbinfo = self.mediachain.get_tmdbinfo_by_doubanid(doubanid=douban_id, mtype=meta.type) + tmdbinfo = MediaChain().get_tmdbinfo_by_doubanid(doubanid=douban_id, mtype=meta.type) if not tmdbinfo: - logger.warn(f'未能通过豆瓣ID {douban_id} 获取到TMDB信息,标题:{title},豆瓣ID:{douban_id}') + logger.warn( + f'未能通过豆瓣ID {douban_id} 获取到TMDB信息,标题:{title},豆瓣ID:{douban_id}') continue mediainfo = self.chain.recognize_media(meta=meta, tmdbid=tmdbinfo.get("id")) if not mediainfo: @@ -598,22 +593,23 @@ class DoubanRank(_PluginBase): logger.info(f'{mediainfo.title_year} 评分不符合要求') continue # 查询缺失的媒体信息 - exist_flag, _ = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) + exist_flag, _ = DownloadChain().get_no_exists_info(meta=meta, mediainfo=mediainfo) if exist_flag: logger.info(f'{mediainfo.title_year} 媒体库中已存在') continue # 判断用户是否已经添加订阅 - if self.subscribechain.exists(mediainfo=mediainfo, meta=meta): + subscribechain = SubscribeChain() + if subscribechain.exists(mediainfo=mediainfo, meta=meta): logger.info(f'{mediainfo.title_year} 订阅已存在') continue # 添加订阅 - self.subscribechain.add(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=meta.begin_season, - exist_ok=True, - username="豆瓣榜单") + subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + exist_ok=True, + username="豆瓣榜单") # 存储历史记录 history.append({ "title": title, diff --git a/plugins.v2/doubansync/__init__.py b/plugins.v2/doubansync/__init__.py index 25b70a7..9ce4b9f 100644 --- a/plugins.v2/doubansync/__init__.py +++ b/plugins.v2/doubansync/__init__.py @@ -9,6 +9,7 @@ from apscheduler.triggers.cron import CronTrigger from app import schemas from app.chain.media import MediaChain +from app.db.subscribe_oper import SubscribeOper from app.db.user_oper import UserOper from app.schemas.types import MediaType, EventType, SystemConfigKey @@ -50,12 +51,6 @@ class DoubanSync(_PluginBase): _interests_url: str = "https://www.douban.com/feed/people/%s/interests" _scheduler: Optional[BackgroundScheduler] = None _cache_path: Optional[Path] = None - rsshelper = None - downloadchain = None - searchchain = None - subscribechain = None - mediachain = None - useroper = None # 配置属性 _enabled: bool = False @@ -69,12 +64,6 @@ class DoubanSync(_PluginBase): _search_download = False def init_plugin(self, config: dict = None): - self.rsshelper = RssHelper() - self.downloadchain = DownloadChain() - self.searchchain = SearchChain() - self.subscribechain = SubscribeChain() - self.mediachain = MediaChain() - self.useroper = UserOper() # 停止现有任务 self.stop_service() @@ -337,7 +326,7 @@ class DoubanSync(_PluginBase): } } ] - } + } ] }, { @@ -546,12 +535,13 @@ class DoubanSync(_PluginBase): except Exception as e: logger.error("退出插件失败:%s" % str(e)) - def __get_username_by_douban(self, user_id: str) -> Optional[str]: + @staticmethod + def __get_username_by_douban(user_id: str) -> Optional[str]: """ 根据豆瓣ID获取用户名 """ try: - return self.useroper.get_name(douban_userid=user_id) + return UserOper().get_name(douban_userid=user_id) except Exception as err: logger.warn(f'{err}, 需要 MoviePilot v2.2.6+ 版本') return None @@ -579,23 +569,28 @@ class DoubanSync(_PluginBase): logger.info(f"开始同步用户 {user_id} 的豆瓣想看数据 ...") url = self._interests_url % user_id if version == "v2": - results = self.rsshelper.parse(url, headers={ + results = RssHelper().parse(url, headers={ "User-Agent": settings.USER_AGENT }) else: - results = self.rsshelper.parse(url) + results = RssHelper().parse(url) if not results: logger.warn(f"未获取到用户 {user_id} 豆瓣RSS数据:{url}") continue else: logger.info(f"获取到用户 {user_id} 豆瓣RSS数据:{len(results)}") # 解析数据 + mediachain = MediaChain() + downloadchain = DownloadChain() + subscribechain = SubscribeChain() + searchchain = SearchChain() + subscribeoper = SubscribeOper() for result in results: try: dtype = result.get("title", "")[:2] title = result.get("title", "")[2:] # 增加豆瓣昵称,数据来源自app.helper.rss.py - nickname = result.get("nickname","") + nickname = result.get("nickname", "") if nickname: nickname = f"[{nickname}]" if dtype not in ["想看"]: @@ -620,7 +615,7 @@ class DoubanSync(_PluginBase): douban_info = self.chain.douban_info(doubanid=douban_id) meta.type = MediaType.MOVIE if douban_info.get("type") == "movie" else MediaType.TV if settings.RECOGNIZE_SOURCE == "themoviedb": - tmdbinfo = self.mediachain.get_tmdbinfo_by_doubanid(doubanid=douban_id, mtype=meta.type) + tmdbinfo = mediachain.get_tmdbinfo_by_doubanid(doubanid=douban_id, mtype=meta.type) if not tmdbinfo: logger.warn(f'未能通过豆瓣ID {douban_id} 获取到TMDB信息,标题:{title},豆瓣ID:{douban_id}') continue @@ -634,7 +629,7 @@ class DoubanSync(_PluginBase): logger.warn(f'豆瓣ID {douban_id} 未识别到媒体信息') continue # 查询缺失的媒体信息 - exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) + exist_flag, no_exists = downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) if exist_flag: logger.info(f'{mediainfo.title_year} 媒体库中已存在') action = "exist" @@ -643,9 +638,10 @@ class DoubanSync(_PluginBase): real_name = self.__get_username_by_douban(user_id) if self._search_download: # 先搜索资源 - logger.info(f'媒体库中不存在或不完整,开启搜索下载,开始搜索 {mediainfo.title_year} 的资源...') - # 按订阅优先级规则组搜索过滤,站点为设置的订阅站点 - filter_results = self.searchchain.process( + logger.info( + f'媒体库中不存在或不完整,开启搜索下载,开始搜索 {mediainfo.title_year} 的资源...') + # 按订阅优先级规则组搜索过滤,站点为设置的订阅站点 + filter_results = searchchain.process( mediainfo=mediainfo, no_exists=no_exists, sites=self.systemconfig.get(SystemConfigKey.RssSites), @@ -656,7 +652,7 @@ class DoubanSync(_PluginBase): action = "download" if mediainfo.type == MediaType.MOVIE: # 电影类型调用单次下载 - download_id = self.downloadchain.download_single( + download_id = downloadchain.download_single( context=filter_results[0], username=real_name or f"豆瓣{nickname}想看" ) @@ -666,7 +662,7 @@ class DoubanSync(_PluginBase): action = "subscribe" else: # 电视剧类型调用批量下载 - downloaded_list, no_exists = self.downloadchain.batch_download( + downloaded_list, no_exists = downloadchain.batch_download( contexts=filter_results, no_exists=no_exists, username=real_name or f"豆瓣{nickname}想看" @@ -678,13 +674,13 @@ class DoubanSync(_PluginBase): # 更新订阅信息 logger.info(f'根据缺失剧集更新订阅信息 {mediainfo.title_year} ...') - subscribe = self.subscribechain.subscribeoper.get(sub_id) + subscribe = subscribeoper.get(sub_id) if subscribe: - self.subscribechain.finish_subscribe_or_not(subscribe=subscribe, - meta=meta, - mediainfo=mediainfo, - downloads=downloaded_list, - lefts=no_exists) + subscribechain.finish_subscribe_or_not(subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + downloads=downloaded_list, + lefts=no_exists) else: logger.info(f'未找到符合条件资源,添加订阅 {mediainfo.title_year} ...') @@ -714,8 +710,9 @@ class DoubanSync(_PluginBase): # 缓存只清理一次 self._clearflag = False - def add_subscribe(self, mediainfo, meta, nickname, real_name): - return self.subscribechain.add( + @staticmethod + def add_subscribe(mediainfo, meta, nickname, real_name): + return SubscribeChain().add( title=mediainfo.title, year=mediainfo.year, mtype=mediainfo.type, diff --git a/plugins.v2/downloadsitetag/__init__.py b/plugins.v2/downloadsitetag/__init__.py index 26b8275..c9e2603 100644 --- a/plugins.v2/downloadsitetag/__init__.py +++ b/plugins.v2/downloadsitetag/__init__.py @@ -45,9 +45,6 @@ class DownloadSiteTag(_PluginBase): # 退出事件 _event = threading.Event() # 私有属性 - downloadhistory_oper = None - sites_helper = None - downloader_helper = None _scheduler = None _enabled = False _onlyonce = False @@ -64,9 +61,6 @@ class DownloadSiteTag(_PluginBase): _downloaders = None def init_plugin(self, config: dict = None): - self.downloadhistory_oper = DownloadHistoryOper() - self.downloader_helper = DownloaderHelper() - self.sites_helper = SitesHelper() # 读取配置 if config: self._enabled = config.get("enabled") @@ -113,7 +107,7 @@ class DownloadSiteTag(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - services = self.downloader_helper.get_services(name_filters=self._downloaders) + services = DownloaderHelper().get_services(name_filters=self._downloaders) if not services: logger.warning("获取下载器实例失败,请检查配置") return None @@ -205,7 +199,7 @@ class DownloadSiteTag(_PluginBase): # 记录处理的种子, 供辅种(无下载历史)使用 dispose_history = {} # 所有站点索引 - indexers = [indexer.get("name") for indexer in self.sites_helper.get_indexers()] + indexers = [indexer.get("name") for indexer in SitesHelper().get_indexers()] # JackettIndexers索引器支持多个站点, 如果不存在历史记录, 则通过tracker会再次附加其他站点名称 indexers.append("JackettIndexers") indexers = set(indexers) @@ -230,6 +224,8 @@ class DownloadSiteTag(_PluginBase): # 按添加时间进行排序, 时间靠前的按大小和名称加入处理历史, 判定为原始种子, 其他为辅种 torrents = self._torrents_sort(torrents=torrents, dl_type=service.type) logger.info(f"{self.LOG_TAG}下载器 {downloader} 分析种子信息中 ...") + downloadhis = DownloadHistoryOper() + siteshelper = SitesHelper() for torrent in torrents: try: if self._event.is_set(): @@ -246,7 +242,7 @@ class DownloadSiteTag(_PluginBase): torrent_tags = self._get_label(torrent=torrent, dl_type=service.type) torrent_cat = self._get_category(torrent=torrent, dl_type=service.type) # 提取种子hash对应的下载历史 - history: DownloadHistory = self.downloadhistory_oper.get_by_hash(_hash) + history: DownloadHistory = downloadhis.get_by_hash(_hash) if not history: # 如果找到已处理种子的历史, 表明当前种子是辅种, 否则创建一个空DownloadHistory if _key and _key in dispose_history: @@ -273,7 +269,7 @@ class DownloadSiteTag(_PluginBase): break else: domain = StringUtils.get_url_domain(tracker) - site_info = self.sites_helper.get_indexer(domain) + site_info = siteshelper.get_indexer(domain) if site_info: history.torrent_site = site_info.get("name") break @@ -652,7 +648,7 @@ class DownloadSiteTag(_PluginBase): 'model': 'downloaders', 'label': '下载器', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] @@ -856,4 +852,4 @@ class DownloadSiteTag(_PluginBase): self._event.clear() self._scheduler = None except Exception as e: - print(str(e)) \ No newline at end of file + print(str(e)) diff --git a/plugins.v2/historytov2/__init__.py b/plugins.v2/historytov2/__init__.py index e163abe..16ce8a7 100644 --- a/plugins.v2/historytov2/__init__.py +++ b/plugins.v2/historytov2/__init__.py @@ -30,7 +30,6 @@ class HistoryToV2(_PluginBase): auth_level = 1 # 私有属性 - historyoper = None _enabled = False _host = None _username = None diff --git a/plugins.v2/imdbsource/__init__.py b/plugins.v2/imdbsource/__init__.py new file mode 100644 index 0000000..f55e148 --- /dev/null +++ b/plugins.v2/imdbsource/__init__.py @@ -0,0 +1,1211 @@ +from datetime import datetime +from typing import Optional, Any, List, Dict, Tuple + +from app import schemas +from app.core.config import settings +from app.core.event import eventmanager, Event +from app.plugins import _PluginBase +from app.plugins.imdbsource.imdb_helper import ImdbHelper +from app.schemas import DiscoverSourceEventData, MediaRecognizeConvertEventData, RecommendSourceEventData +from app.schemas.types import ChainEventType, MediaType +from app.utils.http import RequestUtils + + +class ImdbSource(_PluginBase): + # 插件名称 + plugin_name = "IMDb源" + # 插件描述 + plugin_desc = "让探索和推荐支持IMDb数据源。" + # 插件图标 + plugin_icon = ("https://raw.githubusercontent.com/jxxghp/" + "MoviePilot-Plugins/refs/heads/main/icons/IMDb_IOS-OSX_App.png") + # 插件版本 + plugin_version = "1.3.1" + # 插件作者 + plugin_author = "wumode" + # 作者主页 + author_url = "https://github.com/wumode" + # 插件配置项ID前缀 + plugin_config_prefix = "imdbsource_" + # 加载顺序 + plugin_order = 22 + # 可使用的用户级别 + auth_level = 1 + + # 私有属性 + _enabled = False + _proxy = False + + _imdb_helper = None + _cache = {"discover": [], "trending": [], "trending_in_anime": [], "trending_in_sitcom": [], + "trending_in_documentary": [], "imdb_top_250": []} + + def init_plugin(self, config: dict = None): + if config: + self._enabled = config.get("enabled") + self._proxy = config.get("proxy") + self._imdb_helper = ImdbHelper(proxies=settings.PROXY if self._proxy else None) + if "media-amazon.com" not in settings.SECURITY_IMAGE_DOMAINS: + settings.SECURITY_IMAGE_DOMAINS.append("media-amazon.com") + if "media-imdb.com" not in settings.SECURITY_IMAGE_DOMAINS: + settings.SECURITY_IMAGE_DOMAINS.append("media-imdb.com") + + def get_state(self) -> bool: + return self._enabled + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + """ + 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 + """ + return [ + { + "component": "VForm", + "content": [ + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 4}, + "content": [ + { + "component": "VSwitch", + "props": { + "model": "enabled", + "label": "启用插件", + }, + } + ], + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'proxy', + 'label': '使用代理服务器', + } + } + ] + } + ], + } + ], + } + ], { + "enabled": False, + "proxy": False + } + + def get_page(self) -> List[dict]: + pass + + def stop_service(self): + """ + 退出插件 + """ + pass + + def get_module(self) -> Dict[str, Any]: + """ + 获取插件模块声明,用于胁持系统模块实现(方法名:方法实现) + { + "id1": self.xxx1, + "id2": self.xxx2, + } + """ + pass + + @staticmethod + def __movie_to_media(movie_info: dict) -> schemas.MediaInfo: + title = "" + if movie_info.get("titleText"): + title = movie_info.get("titleText", {}).get("text", "") + release_year = 0 + if movie_info.get("releaseYear"): + release_year = movie_info.get("releaseYear", {}).get("year") + poster_path = None + if movie_info.get("primaryImage"): + primary_image = movie_info.get("primaryImage").get("url") + if primary_image: + poster_path = primary_image.replace('@._V1', '@._V1_QL75_UY414_CR6,0,280,414_') + vote_average = 0 + if movie_info.get("ratingsSummary"): + vote_average = movie_info.get("ratingsSummary").get("aggregateRating") + runtime = 0 + if movie_info.get("runtime"): + runtime = movie_info.get("runtime").get("seconds") + overview = '' + if movie_info.get("plot"): + overview = movie_info.get("plot").get("plotText").get("plainText") + return schemas.MediaInfo( + type="电影", + title=title, + year=release_year, + title_year=f"{title} ({release_year})", + mediaid_prefix="imdb", + media_id=str(movie_info.get("id")), + poster_path=poster_path, + vote_average=vote_average, + runtime=runtime, + overview=overview, + imdb_id=movie_info.get("id") + ) + + @staticmethod + def __series_to_media(series_info: dict) -> schemas.MediaInfo: + title = "" + if series_info.get("titleText"): + title = series_info.get("titleText", {}).get("text", "") + release_year = 0 + if series_info.get("releaseYear"): + release_year = series_info.get("releaseYear", {}).get("year") + poster_path = None + if series_info.get("primaryImage"): + primary_image = series_info.get("primaryImage").get("url") + if primary_image: + poster_path = primary_image.replace('@._V1', '@._V1_QL75_UY414_CR6,0,280,414_') + vote_average = 0 + if series_info.get("ratingsSummary"): + vote_average = series_info.get("ratingsSummary").get("aggregateRating") + runtime = 0 + if series_info.get("runtime"): + runtime = series_info.get("runtime").get("seconds") + overview = '' + if series_info.get("plot"): + if series_info.get("plot").get("plotText"): + overview = series_info.get("plot").get("plotText").get("plainText") + release_date_str = '0000-00-00' + if series_info.get("releaseDate"): + release_date = series_info.get('releaseDate') + release_date_str = f"{release_date.get('year')}-{release_date.get('month')}-{release_date.get('day')}" + return schemas.MediaInfo( + type="电视剧", + title=title, + year=release_year, + title_year=f"{title} ({release_year})", + mediaid_prefix="imdb", + media_id=str(series_info.get("id")), + release_date=release_date_str, + poster_path=poster_path, + vote_average=vote_average, + runtime=runtime, + overview=overview, + imdb_id=series_info.get("id") + ) + + @staticmethod + def title_id_to_mtype(title_id: str) -> MediaType: + if title_id in ["tvSeries", "tvMiniSeries", "tvShort", "tvEpisode"]: + return MediaType.TV + elif title_id in ["movie", "tvMovie"]: + return MediaType.MOVIE + return MediaType.UNKNOWN + + def trending_in_documentary(self, apikey: str, page: int = 1, count: int = 30) -> List[schemas.MediaInfo]: + if apikey != settings.API_TOKEN: + return [] + if not self._imdb_helper: + return [] + title_types = ("tvSeries", "tvMiniSeries", "tvShort", 'movie') + first_page = False + if page == 1: + first_page = True + self._cache["trending_in_documentary"] = [] # 清空缓存 + results = [] + if len(self._cache["trending_in_documentary"]) >= count: + results = self._cache["trending_in_documentary"][:count] + self._cache["trending_in_documentary"] = self._cache["trending_in_documentary"][count:] + else: + results.extend(self._cache["trending_in_documentary"]) + remaining = count - len(results) + self._cache["trending_in_documentary"] = [] # 清空缓存 + data = self._imdb_helper.advanced_title_search(first_page=first_page, + title_types=title_types, + sort_by="POPULARITY", + sort_order="ASC", + interests=("Documentary",) + ) + if not data: + new_results = [] + else: + new_results = data.get("edges") + if new_results: + results.extend(new_results[:remaining]) + self._cache["trending_in_documentary"] = new_results[remaining:] + res = [] + for item in results: + title_type_id = item.get('node').get("title").get("titleType", {}).get("id") + mtype = self.title_id_to_mtype(title_type_id) + if mtype == MediaType.MOVIE: + res.append(self.__movie_to_media(item.get('node').get("title"))) + elif mtype == MediaType.TV: + res.append(self.__series_to_media(item.get('node').get("title"))) + return res + + def imdb_top_250(self, apikey: str, page: int = 1, count: int = 30) -> List[schemas.MediaInfo]: + if apikey != settings.API_TOKEN: + return [] + if not self._imdb_helper: + return [] + title_types = ("movie",) + first_page = False + if page == 1: + first_page = True + self._cache["imdb_top_250"] = [] # 清空缓存 + results = [] + if len(self._cache["imdb_top_250"]) >= count: + results = self._cache["imdb_top_250"][:count] + self._cache["imdb_top_250"] = self._cache["imdb_top_250"][count:] + else: + results.extend(self._cache["imdb_top_250"]) + remaining = count - len(results) + self._cache["imdb_top_250"] = [] # 清空缓存 + data = self._imdb_helper.advanced_title_search(first_page=first_page, + title_types=title_types, + sort_by="USER_RATING", + sort_order="DESC", + ranked=("TOP_RATED_MOVIES-250",) + ) + if not data: + new_results = [] + else: + new_results = data.get("edges") + if new_results: + results.extend(new_results[:remaining]) + self._cache["imdb_top_250"] = new_results[remaining:] + res = [] + for item in results: + title_type_id = item.get('node').get("title").get("titleType", {}).get("id") + mtype = self.title_id_to_mtype(title_type_id) + if mtype == MediaType.MOVIE: + res.append(self.__movie_to_media(item.get('node').get("title"))) + return res + + def trending_in_sitcom(self, apikey: str, page: int = 1, count: int = 30) -> List[schemas.MediaInfo]: + if apikey != settings.API_TOKEN: + return [] + if not self._imdb_helper: + return [] + title_types = ("tvSeries", "tvMiniSeries", "tvShort") + first_page = False + if page == 1: + first_page = True + self._cache["trending_in_sitcom"] = [] # 清空缓存 + results = [] + if len(self._cache["trending_in_sitcom"]) >= count: + results = self._cache["trending_in_sitcom"][:count] + self._cache["trending_in_sitcom"] = self._cache["trending_in_sitcom"][count:] + else: + results.extend(self._cache["trending_in_sitcom"]) + remaining = count - len(results) + self._cache["trending_in_sitcom"] = [] # 清空缓存 + data = self._imdb_helper.advanced_title_search(first_page=first_page, + title_types=title_types, + sort_by="POPULARITY", + sort_order="ASC", + interests=("Sitcom",) + ) + if not data: + new_results = [] + else: + new_results = data.get("edges") + if new_results: + results.extend(new_results[:remaining]) + self._cache["trending_in_sitcom"] = new_results[remaining:] + res = [] + for item in results: + title_type_id = item.get('node').get("title").get("titleType", {}).get("id") + mtype = self.title_id_to_mtype(title_type_id) + if mtype == MediaType.TV: + res.append(self.__series_to_media(item.get('node').get("title"))) + return res + + def trending_in_anime(self, apikey: str, page: int = 1, count: int = 30) -> List[schemas.MediaInfo]: + if apikey != settings.API_TOKEN: + return [] + if not self._imdb_helper: + return [] + title_types = ("tvSeries", "tvMiniSeries", "tvShort", 'movie') + first_page = False + if page == 1: + first_page = True + self._cache["trending_in_anime"] = [] # 清空缓存 + results = [] + if len(self._cache["trending_in_anime"]) >= count: + results = self._cache["trending_in_anime"][:count] + self._cache["trending_in_anime"] = self._cache["trending_in_anime"][count:] + else: + results.extend(self._cache["trending_in_anime"]) + remaining = count - len(results) + self._cache["trending_in_anime"] = [] # 清空缓存 + data = self._imdb_helper.advanced_title_search(first_page=first_page, + title_types=title_types, + sort_by="POPULARITY", + sort_order="ASC", + interests=("Anime",) + ) + if not data: + new_results = [] + else: + new_results = data.get("edges") + if new_results: + results.extend(new_results[:remaining]) + self._cache["trending_in_anime"] = new_results[remaining:] + res = [] + for item in results: + title_type_id = item.get('node').get("title").get("titleType", {}).get("id") + mtype = self.title_id_to_mtype(title_type_id) + if mtype == MediaType.MOVIE: + res.append(self.__movie_to_media(item.get('node').get("title"))) + elif mtype == MediaType.TV: + res.append(self.__series_to_media(item.get('node').get("title"))) + return res + + def imdb_trending(self, apikey: str, page: int = 1, count: int = 30) -> List[schemas.MediaInfo]: + if apikey != settings.API_TOKEN: + return [] + if not self._imdb_helper: + return [] + title_types = ("tvSeries", "tvMiniSeries", "tvShort", 'movie') + first_page = False + if page == 1: + first_page = True + self._cache["discover"] = [] # 清空缓存 + results = [] + if len(self._cache["discover"]) >= count: + results = self._cache["discover"][:count] + self._cache["discover"] = self._cache["discover"][count:] + else: + results.extend(self._cache["discover"]) + remaining = count - len(results) + self._cache["discover"] = [] # 清空缓存 + data = self._imdb_helper.advanced_title_search(first_page=first_page, + title_types=title_types, + sort_by="POPULARITY", + sort_order="ASC", + ) + if not data: + new_results = [] + else: + new_results = data.get("edges") + if new_results: + results.extend(new_results[:remaining]) + self._cache["discover"] = new_results[remaining:] + res = [] + for item in results: + title_type_id = item.get('node').get("title").get("titleType", {}).get("id") + mtype = self.title_id_to_mtype(title_type_id) + if mtype == MediaType.MOVIE: + res.append(self.__movie_to_media(item.get('node').get("title"))) + elif mtype == MediaType.TV: + res.append(self.__series_to_media(item.get('node').get("title"))) + return res + + def imdb_discover(self, apikey: str, mtype: str = "series", + country: str = None, + lang: str = None, + genre: str = None, + sort_by: str = 'POPULARITY', + sort_order: str = 'ASC', + using_rating: bool = False, + user_rating: str = None, + year: str = None, + award: str = None, + ranked_list: str = None, + page: int = 1, count: int = 30) -> List[schemas.MediaInfo]: + + if apikey != settings.API_TOKEN: + return [] + if not self._imdb_helper: + return [] + title_type = ("tvSeries", "tvMiniSeries", "tvShort") + if mtype == 'movies': + title_type = ("movie",) + if user_rating and using_rating: + user_rating = float(user_rating) + else: + user_rating = None + genres = (genre,) if genre else None + countries = (country,) if country else None + languages = (lang,) if lang else None + release_date_start = None + release_date_end = None + if year: + if year == "2025": + release_date_start = "2025-01-01" + elif year == "2024": + release_date_start = "2024-01-01" + release_date_end = "2024-12-31" + elif year == "2023": + release_date_start = "2023-01-01" + release_date_end = "2023-12-31" + elif year == "2022": + release_date_start = "2022-01-01" + release_date_end = "2022-12-31" + elif year == "2021": + release_date_start = "2021-01-01" + release_date_end = "2021-12-31" + elif year == "2020": + release_date_start = "2020-01-01" + release_date_end = "2020-12-31" + elif year == "2020s": + release_date_start = "2020-01-01" + release_date_end = "2029-12-31" + elif year == "2010s": + release_date_start = "2010-01-01" + release_date_end = "2019-12-31" + elif year == "2000s": + release_date_start = "2000-01-01" + release_date_end = "2009-12-31" + elif year == "1990s": + release_date_start = "1990-01-01" + release_date_end = "1999-12-31" + elif year == "1980s": + release_date_start = "1980-01-01" + release_date_end = "1989-12-31" + elif year == "1970s": + release_date_start = "1970-01-01" + release_date_end = "1979-12-31" + if not release_date_end: + release_date_end = datetime.now().date().strftime("%Y-%m-%d") + awards = (award,) if award else None + ranked_lists = (ranked_list,) if ranked_list else None + first_page = False + if page == 1: + first_page = True + self._cache["discover"] = [] # 清空缓存 + results = [] + if len(self._cache["discover"]) >= count: + results = self._cache["discover"][:count] + self._cache["discover"] = self._cache["discover"][count:] + else: + results.extend(self._cache["discover"]) + remaining = count - len(results) + self._cache["discover"] = [] # 清空缓存 + data = self._imdb_helper.advanced_title_search(first_page=first_page, + title_types=title_type, + genres=genres, + sort_by=sort_by, + sort_order=sort_order, + rating_min=user_rating, + countries=countries, + languages=languages, + release_date_end=release_date_end, + release_date_start=release_date_start, + award_constraint=awards, + ranked=ranked_lists) + if not data: + new_results = [] + else: + new_results = data.get("edges") + if new_results: + results.extend(new_results[:remaining]) + self._cache["discover"] = new_results[remaining:] + res = [] + if mtype == "movies": + for movie in results: + movie_info = movie.get('node').get("title") + res.append(self.__movie_to_media(movie_info)) + + else: + for tv in results: + tv_info = tv.get('node').get('title') + res.append(self.__series_to_media(tv_info)) + + return res + + def get_api(self) -> List[Dict[str, Any]]: + """ + 获取插件API + [{ + "path": "/xx", + "endpoint": self.xxx, + "methods": ["GET", "POST"], + "summary": "API说明" + }] + """ + return [ + { + "path": "/imdb_discover", + "endpoint": self.imdb_discover, + "methods": ["GET"], + "summary": "IMDb探索数据源", + "description": "获取 IMDb探索 数据", + }, + { + "path": "/imdb_trending", + "endpoint": self.imdb_trending, + "methods": ["GET"], + "summary": "IMDb Trending", + "description": "获取 IMDb Trending 数据", + }, + { + "path": "/trending_in_anime", + "endpoint": self.trending_in_anime, + "methods": ["GET"], + "summary": "IMDb Trending in Anime", + "description": "获取 IMDb Trending in Anime 数据", + }, + { + "path": "/trending_in_sitcom", + "endpoint": self.trending_in_sitcom, + "methods": ["GET"], + "summary": "IMDb Trending in Sitcom", + "description": "获取 IMDb Trending in Sitcom 数据", + }, + { + "path": "/imdb_top_250", + "endpoint": self.imdb_top_250, + "methods": ["GET"], + "summary": "IMDb Top 250 Movies", + "description": "获取 IMDb Top 250 Movies 数据", + }, + { + "path": "/trending_in_documentary", + "endpoint": self.trending_in_documentary, + "methods": ["GET"], + "summary": "IMDb Trending in Documentary", + "description": "获取 IMDb Trending in Documentary 数据", + } + ] + + @staticmethod + def imdb_filter_ui() -> List[dict]: + """ + IMDb过滤参数UI配置 + """ + # 国家字典 + country_dict = { + "US": "美国", + "CN": "中国", + "JP": "日本", + "KR": "韩国", + "IN": "印度", + "FR": "法国", + "DE": "德国", + "IT": "意大利", + "ES": "西班牙", + "UK": "英国", + "AU": "澳大利亚", + "CA": "加拿大", + "RU": "俄罗斯", + "BR": "巴西", + "MX": "墨西哥", + "AR": "阿根廷" + } + + cuntry_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in country_dict.items() + ] + + # 原始语种字典 + lang_dict = { + "en": "英语", + "zh": "中文", + "ja": "日语", + "ko": "韩语", + "fr": "法语", + "de": "德语", + "it": "意大利语", + "es": "西班牙语", + "pt": "葡萄牙语", + "ru": "俄语" + } + + lang_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in lang_dict.items() + ] + + # 风格字典 + genre_dict = { + "Action": "动作", + "Adventure": "冒险", + "Animation": "动画", + "Biography": "传记", + "Comedy": "喜剧", + "Crime": "犯罪", + "Documentary": "纪录片", + "Drama": "剧情", + "Family": "家庭", + "Fantasy": "奇幻", + "Game-Show": "游戏节目", + "History": "历史", + "Horror": "恐怖", + "Music": "音乐", + "Musical": "歌舞", + "Mystery": "悬疑", + "News": "新闻", + "Reality-TV": "真人秀", + "Romance": "爱情", + "Sci-Fi": "科幻", + "Short": "短片", + "Sport": "体育", + "Talk-Show": "脱口秀", + "Thriller": "惊悚", + "War": "战争", + "Western": "西部片" + } + + genre_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in genre_dict.items() + ] + + # 排序字典 + sort_dict = { + "POPULARITY": "人气", + "USER_RATING": "评分", + "RELEASE_DATE": "发布日期", + "TITLE_REGIONAL": "A-Z" + } + + sort_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in sort_dict.items() + ] + + sort_order_dict = { + "ASC": "升序", + "DESC": "降序", + } + + sort_order_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in sort_order_dict.items() + ] + + year_dict = { + "2025": "2025", + "2024": "2024", + "2023": "2023", + "2022": "2022", + "2021": "2021", + "2020": "2020", + "2020s": "2020s", + "2010s": "2010s", + "2000s": "2000s", + "1990s": "1990s", + "1980s": "1980s", + "1970s": "1970s", + } + + year_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in year_dict.items() + ] + + award_dict = { + "ev0000003-Winning": "奥斯卡奖", + "ev0000223-Winning": "艾美奖", + "ev0000292-Winning": "金球奖", + "ev0000003-Nominated": "奥斯卡提名", + "ev0000223-Nominated": "艾美奖提名", + "ev0000292-Nominated": "金球奖提名", + "ev0000003-bestPicture-Winning": "最佳影片", + "ev0000003-bestPicture-Nominated": "最佳影片提名", + "ev0000003-bestDirector-Winning": "最佳导演", + "ev0000003-bestDirector-Nominated": "最佳导演提名", + "ev0000558-Winning": "金酸莓奖", + "ev0000558-Nominated": "金酸莓奖提名" + } + + award_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in award_dict.items() + ] + + ranked_list_dict = { + "TOP_RATED_MOVIES-100": "IMDb Top 100", + "TOP_RATED_MOVIES-250": "IMDb Top 250", + "TOP_RATED_MOVIES-1000": "IMDb Top 1000", + "LOWEST_RATED_MOVIES-100": "IMDb Bottom 100", + "LOWEST_RATED_MOVIES-250": "IMDb Bottom 250", + "LOWEST_RATED_MOVIES-1000": "IMDb Bottom 1000", + } + + ranked_list_ui = [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": key + }, + "text": value + } for key, value in ranked_list_dict.items() + ] + + return [ + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "类型" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "mtype" + }, + "content": [ + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": "series" + }, + "text": "电视剧" + }, + { + "component": "VChip", + "props": { + "filter": True, + "tile": True, + "value": "movies" + }, + "text": "电影" + } + ] + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "风格" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "genre" + }, + "content": genre_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "国家" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "country" + }, + "content": cuntry_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "语言" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "lang" + }, + "content": lang_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "年份" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "year" + }, + "content": year_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "奖项" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "award" + }, + "content": award_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center", + "show": "{{mtype == 'movies'}}" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "排名" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "ranked_list" + }, + "content": ranked_list_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "排序依据" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "sort_by" + }, + "content": sort_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "排序方式" + } + ] + }, + { + "component": "VChipGroup", + "props": { + "model": "sort_order" + }, + "content": sort_order_ui + } + ] + }, + { + "component": "div", + "props": { + "class": "flex justify-start items-center" + }, + "content": [ + { + "component": "div", + "props": { + "class": "mr-5" + }, + "content": [ + { + "component": "VLabel", + "text": "评分" + } + ] + }, + { + "component": "VSwitch", + "props": { + "model": "using_rating", + "label": "启用", + }, + }, + { + "component": "VDivider", + "props": { + "class": "my-3" + } + }, + { + "component": "VSlider", + "props": { + "v-model": "user_rating", + "thumb-label": True, + "max": "10", + "min": "1", + "step": "1", + "hide-details": True, + } + } + ] + } + ] + + @eventmanager.register(ChainEventType.DiscoverSource) + def discover_source(self, event: Event): + """ + 监听识别事件 + """ + if not self._enabled: + return + event_data: DiscoverSourceEventData = event.event_data + imdb_source = schemas.DiscoverMediaSource( + name="IMDb", + mediaid_prefix="imdb", + api_path=f"plugin/ImdbSource/imdb_discover?apikey={settings.API_TOKEN}", + filter_params={ + "mtype": "series", + "company": None, + "contentRating": None, + "country": None, + "genre": None, + "lang": None, + "sort_by": "POPULARITY", + "sort_order": "ASC", + "status": None, + "year": None, + "user_rating": 1, + "using_rating": False, + "award": None, + "ranked_list": None + }, + depends={ + "ranked_list": ["mtype"] + }, + filter_ui=self.imdb_filter_ui() + ) + if not event_data.extra_sources: + event_data.extra_sources = [imdb_source] + else: + event_data.extra_sources.append(imdb_source) + + @eventmanager.register(ChainEventType.MediaRecognizeConvert) + def media_recognize_covert(self, event: Event) -> Optional[dict]: + if not self._enabled: + return + event_data: MediaRecognizeConvertEventData = event.event_data + if not event_data: + return + api_key = settings.TMDB_API_KEY + if event_data.convert_type != "themoviedb" or not api_key: + return + if not event_data.mediaid.startswith("imdb"): + return + imdb_id = event_data.mediaid[5:] + api_url = f"https://{settings.TMDB_API_DOMAIN}/3/find/{imdb_id}?api_key={api_key}&external_source=imdb_id" + ret = RequestUtils(accept_type="application/json").get_res(api_url) + if ret: + data = ret.json() + all_results = [] + for result_type in ["movie_results", "tv_results"]: + if data.get(result_type): + all_results.extend(data[result_type]) + if not all_results: + return # 无匹配结果 + # 按 popularity 降序排序,取最高人气的条目 + most_popular_item = max(all_results, key=lambda x: x.get("popularity", -1)) + event_data.media_dict["id"] = most_popular_item.get("id") + + @eventmanager.register(ChainEventType.RecommendSource) + def recommend_source(self, event: Event): + if not self._enabled: + return + event_data: RecommendSourceEventData = event.event_data + if not event_data: + return + imdb_trending: schemas.RecommendMediaSource = schemas.RecommendMediaSource( + name="IMDb Trending", + api_path=f"plugin/ImdbSource/imdb_trending?apikey={settings.API_TOKEN}", + type='Rankings' + ) + trending_in_anime: schemas.RecommendMediaSource = schemas.RecommendMediaSource( + name="IMDb Trending in Anime", + api_path=f"plugin/ImdbSource/trending_in_anime?apikey={settings.API_TOKEN}", + type='Anime' + ) + trending_in_sitcom: schemas.RecommendMediaSource = schemas.RecommendMediaSource( + name="IMDb Trending in Sitcom", + api_path=f"plugin/ImdbSource/trending_in_sitcom?apikey={settings.API_TOKEN}", + type='TV Shows' + ) + + imdb_top_250: schemas.RecommendMediaSource = schemas.RecommendMediaSource( + name="IMDb Top 250 Movies", + api_path=f"plugin/ImdbSource/imdb_top_250?apikey={settings.API_TOKEN}", + type='Movies' + ) + imdb_documentary: schemas.RecommendMediaSource = schemas.RecommendMediaSource( + name="IMDb Trending in Documentary", + api_path=f"plugin/ImdbSource/trending_in_documentary?apikey={settings.API_TOKEN}", + type='Rankings' + ) + trending_source = [imdb_trending, trending_in_anime, trending_in_sitcom, imdb_top_250, imdb_documentary] + if not event_data.extra_sources: + event_data.extra_sources = trending_source + else: + event_data.extra_sources.extend(trending_source) diff --git a/plugins.v2/imdbsource/imdb_helper.py b/plugins.v2/imdbsource/imdb_helper.py new file mode 100644 index 0000000..12c1c09 --- /dev/null +++ b/plugins.v2/imdbsource/imdb_helper.py @@ -0,0 +1,785 @@ +import re +from typing import Optional, Any, Dict, List, Tuple +from io import StringIO +from collections import OrderedDict +from dataclasses import dataclass + +import graphene +import requests +from requests_html import HTMLSession +import ijson +import json +import base64 + +from app.log import logger +from app.utils.http import RequestUtils +from app.utils.string import StringUtils +from app.utils.common import retry +from app.schemas.types import MediaType +from app.core.cache import cached + + +@dataclass(frozen=True) +class SearchParams: + title_types: Optional[Tuple[str, ...]] = None + genres: Optional[Tuple[str, ...]] = None + sort_by: str = 'POPULARITY' + sort_order: str = 'ASC' + rating_min: Optional[float] = None + rating_max: Optional[float] = None + countries: Optional[Tuple[str, ...]] = None + languages: Optional[Tuple[str, ...]] = None + release_date_end: Optional[str] = None + release_date_start: Optional[str] = None + award_constraint: Optional[Tuple[str, ...]] = None + ranked: Optional[Tuple[str, ...]] = None + interests: Optional[Tuple[str, ...]] = None + + +class SearchState: + def __init__(self, pageinfo: dict, total: int): + self.pageinfo = pageinfo + self.total = total + + +class ImdbHelper: + _query_by_id = """query queryWithVariables($id: ID!) { + title(id: $id) { + id + type + is_adult + primary_title + original_title + start_year + end_year + runtime_minutes + plot + rating { + aggregate_rating + votes_count + } + genres + posters { + url + width + height + } + certificates { + country { + code + name + } + rating + } + spoken_languages { + code + name + } + origin_countries { + code + name + } + critic_review { + score + review_count + } + directors: credits(first: 5, categories: ["director"]) { + name { + id + display_name + avatars { + url + width + height + } + } + } + writers: credits(first: 5, categories: ["writer"]) { + name { + id + display_name + avatars { + url + width + height + } + } + } + casts: credits(first: 5, categories: ["actor", "actress"]) { + name { + id + display_name + avatars { + url + width + height + } + } + characters + } + } +}""" + _endpoint = "https://graph.imdbapi.dev/v1" + _search_endpoint = "https://v3.sg.media-imdb.com/suggestion/x/%s.json?includeVideos=0" + _official_endpoint = "https://caching.graphql.imdb.com/" + _hash_update_url = ("https://raw.githubusercontent.com/wumode/MoviePilot-Plugins/" + "refs/heads/imdbsource_assets/plugins.v2/imdbsource/imdb_hash.json") + _qid_map = { + MediaType.TV: ["tvSeries", "tvMiniSeries", "tvShort", "tvEpisode"], + MediaType.MOVIE: ["movie"] + } + + _imdb_headers = { + "Accept": "application/json, text/plain, */*", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome" + "/84.0.4147.105 Safari/537.36", + "Referer": "https://www.imdb.com/", + } + all_title_types = ["tvSeries", "tvMiniSeries", "movie", "tvMovie", "musicVideo", "tvShort", "short", + "tvEpisode", "tvSpecial", "videoGame"] + interest_id = { + "Anime": "in0000027", + "Superhero": "in0000008", + "Sitcom": "in0000044", + "Coming-of-Age": "in0000073", + "Slasher Horror": "in0000115", + "Raunchy Comedy": "in0000041", + "Documentary": "in0000060" + } + + def __init__(self, proxies=None): + self._proxies = proxies + self._session = HTMLSession() + self._req_utils = RequestUtils(headers=self._imdb_headers, session=self._session, timeout=10, proxies=proxies) + self._imdb_req = RequestUtils(accept_type="application/json", + content_type="application/json", + headers=self._imdb_headers, + timeout=10, + proxies=proxies, + session=requests.Session()) + self._imdb_api_hash = {"AdvancedTitleSearch": None, "TitleAkasPaginated": None} + self._search_states = OrderedDict() + self._max_states = 30 + + def imdbid(self, imdbid: str) -> Optional[Dict]: + params = {"operationName": "queryWithVariables", "query": self._query_by_id, "variables": {"id": imdbid}} + ret = RequestUtils( + accept_type="application/json", content_type="application/json" + ).post_res(f"{self._endpoint}", json=params) + if not ret: + return None + data = ret.json() + if "errors" in data: + logger.error(f"Imdb query ({imdbid}) errors {data.get('errors')}") + logger.error(f"{params}") + return None + info = data.get("data").get("title", None) + return info + + @cached(maxsize=1000, ttl=3600) + def __episodes_by_season(self, imdbid: str, build_id: str, season: str) -> Optional[Dict]: + if not build_id or not season: + return None + prefix = "pageProps.contentData.section" + url = (f"https://www.imdb.com/_next/data/{build_id}" + f"/en-US/title/{imdbid}/episodes.json?season={season}&ref_=ttep&tconst={imdbid}") + response = self._req_utils.get_res(url) + if not response or response.status_code != 200: + return + json_content = response.text + try: + section = next(ijson.items(json_content, prefix)) + except StopIteration: + logger.warn(f"No data found at prefix: {prefix}") + return None + except (ijson.JSONError, ValueError) as e: + logger.warn(f"JSON parsing error: {e}") + return None + except TypeError as e: + logger.warn(f"Invalid input type: {e}") + return None + return section + + @cached(maxsize=1000, ttl=3600) + def __episodes(self, imdbid: str) -> Optional[Dict]: + prefix = "props.pageProps.contentData.section" + url = f"https://www.imdb.com/title/{imdbid}/episodes/" + + response = self._req_utils.get_res(url) + if not response or response.status_code != 200: + return + script_content = response.html.xpath('//script[@id="__NEXT_DATA__"]/text()') + if len(script_content) == 0: + return None + json_content = script_content[0] + # 直接定位到目标路径提取 items + try: + section = next(ijson.items(json_content, prefix)) + except StopIteration: + logger.warn(f"No data found at prefix: {prefix}") + return None + except (ijson.JSONError, ValueError) as e: + logger.warn(f"JSON parsing error: {e}") + return None + except TypeError as e: + logger.warn(f"Invalid input type: {e}") + return None + total_seasons = [] + for s in section.get("seasons"): + if s.get("value") and s.get("value") not in total_seasons: + total_seasons.append(s.get("value")) + build_id = next(ijson.items(json_content, 'buildId')) + current_season = section.get('currentSeason') or '1' + total_seasons.remove(current_season) + for season in total_seasons: + section_next = self.__episodes_by_season(imdbid, build_id=build_id, season=season) + if section_next: + section["episodes"]["items"].extend(section_next.get("episodes", {}).get("items", [])) + section["episodes"]["total"] += section_next.get("episodes", {}).get("total", 0) + return section + + @retry(Exception, logger=logger) + @cached(maxsize=32, ttl=1800) + def __request(self, params: Dict, sha256) -> Optional[Dict]: + params["extensions"] = {"persistedQuery": {"sha256Hash": sha256, "version": 1}} + ret = self._imdb_req.post_res(f"{self._official_endpoint}", json=params, raise_exception=True) + if not ret: + return None + data = ret.json() + if "errors" in data: + logger.error(f"Imdb query errors") + return None + return data.get("data") + + @cached(maxsize=1, ttl=30 * 24 * 3600) + def __get_hash(self) -> Optional[dict]: + """ + 根据IMDb hash使用 + """ + headers = { + "Accept": "text/html", + } + res = RequestUtils(headers=headers).get_res( + self._hash_update_url, + proxies=self._proxies + ) + if not res: + logger.error("获取IMDb hash") + return None + return res.json() + + def __update_hash(self): + imdb_hash = self.__get_hash() + if imdb_hash: + self._imdb_api_hash["AdvancedTitleSearch"] = imdb_hash.get("AdvancedTitleSearch") + self._imdb_api_hash["TitleAkasPaginated"] = imdb_hash.get("TitleAkasPaginated") + + @staticmethod + def __award_to_constraint(award: str) -> Optional[Dict]: + pattern = r'^(ev\d+)(?:-(best\w+))?-(Winning|Nominated)$' + match = re.match(pattern, award) + constraint = {} + if match: + ev_id = match.group(1) # 第一部分:evXXXXXXXX + best = match.group(2) # 第二部分:bestXX(可选) + status = match.group(3) # 第三部分:Winning/Nominated + constraint["eventId"] = ev_id + if status == "Winning": + constraint["winnerFilter"] = "WINNER_ONLY" + if best: + constraint["searchAwardCategoryId"] = best + return constraint + else: + return None + + @staticmethod + def __ranked_list_to_constraint(ranked: str) -> Optional[Dict]: + """ + "TOP_RATED_MOVIES-100": "IMDb Top 100", + "TOP_RATED_MOVIES-250": "IMDb Top 250", + "TOP_RATED_MOVIES-1000": "IMDb Top 1000", + "LOWEST_RATED_MOVIES-100": "IMDb Bottom 100", + "LOWEST_RATED_MOVIES-250": "IMDb Bottom 250", + "LOWEST_RATED_MOVIES-1000": "IMDb Bottom 1000" + """ + pattern = r'^(TOP_RATED_MOVIES|LOWEST_RATED_MOVIES)-(\d+)$' + match = re.match(pattern, ranked) + if match: + ranked_title_list_type = match.group(1) + rank_range = int(match.group(2)) + constraint = {"rankRange": {"max": rank_range}, "rankedTitleListType": ranked_title_list_type} + return constraint + return None + + def advanced_title_search(self, + first_page: bool = True, + title_types: Optional[Tuple[str, ...]] = None, + genres: Optional[Tuple[str, ...]] = None, + sort_by: str = 'POPULARITY', + sort_order: str = 'ASC', + rating_min: Optional[float] = None, + rating_max: Optional[float] = None, + countries: Optional[Tuple[str, ...]] = None, + languages: Optional[Tuple[str, ...]] = None, + release_date_end: Optional[str] = None, + release_date_start: Optional[str] = None, + award_constraint: Optional[Tuple[str, ...]] = None, + ranked: Optional[Tuple[str, ...]] = None, + interests: Optional[Tuple[str, ...]] = None): + # 创建参数对象 + params = SearchParams( + title_types=title_types, + genres=genres, + sort_by=sort_by, + sort_order=sort_order, + rating_min=rating_min, + rating_max=rating_max, + countries=countries, + languages=languages, + release_date_end=release_date_end, + release_date_start=release_date_start, + award_constraint=award_constraint, + ranked=ranked, + interests=interests + ) + sha256 = 'be358d7b41add9fd174461f4c8c673dfee5e2a88744e2d5dc037362a96e2b4e4' + self.__update_hash() + if self._imdb_api_hash.get("AdvancedTitleSearch"): + sha256 = self._imdb_api_hash["AdvancedTitleSearch"] + # 获取或创建搜索状态 + last_cursor = None + if not first_page and params in self._search_states: + search_state: SearchState = self._search_states.pop(params) # 移除并获取 + self._search_states[params] = search_state + # 不是第一页且已有状态 - 使用上次的结果 + if not search_state.pageinfo.get("hasNextPage"): + return {'pageInfo': {'endCursor': None, 'hasNextPage': False, 'hasPreviousPage': True, + 'startCursor': None}, + 'edges': [], 'total': search_state.total, 'genres': [], 'keywords': [], + 'titleTypes': [], 'jobCategories': []} + if search_state.pageinfo.get('endCursor'): + last_cursor = search_state.pageinfo.get('endCursor') + # 这里实现基于上次结果的逻辑 + else: + # 重新搜索 + first_page = True + else: + first_page = True + result = self.__advanced_title_search(params, sha256, first_page, last_cursor) + if result: + page_info = result.get("pageInfo", {}) + total = result.get("total", 0) + search_state = SearchState(page_info, total) + self._search_states[params] = search_state + if len(self._search_states) > self._max_states: + self._search_states.popitem(last=False) # 移除最旧的条目 + return result + + def __advanced_title_search(self, + params: SearchParams, + sha256: str, + first_page: bool = True, + last_cursor: Optional[str] = None, + ) -> Optional[Dict]: + + variables = {"first": 50, + "locale": "en-US", + "sortBy": params.sort_by, + "sortOrder": params.sort_order, + } + if params.title_types: + title_type_ids = [] + for title_type in params.title_types: + if title_type in self.all_title_types: + title_type_ids.append(title_type) + if len(title_type_ids): + variables["titleTypeConstraint"] = {"anyTitleTypeIds": params.title_types, + "excludeTitleTypeIds": []} + if params.genres: + variables["genreConstraint"] = {"allGenreIds": params.genres, "excludeGenreIds": []} + if params.countries: + variables["originCountryConstraint"] = {"allCountries": params.countries} + if params.languages: + variables["languageConstraint"] = {"anyPrimaryLanguages": params.languages} + if params.rating_min or params.rating_max: + rating_min = params.rating_min if params.rating_min else 1 + rating_min = max(rating_min, 1) + rating_max = params.rating_max if params.rating_max else 10 + rating_max = min(rating_max, 10) + variables["userRatingsConstraint"] = {"aggregateRatingRange": {"max": rating_max, "min": rating_min}} + if params.release_date_start or params.release_date_end: + release_dict = {} + if params.release_date_start: + release_dict["start"] = params.release_date_start + if params.release_date_end: + release_dict["end"] = params.release_date_end + variables["releaseDateConstraint"] = {"releaseDateRange": release_dict} + if params.award_constraint: + constraints = [] + for award in params.award_constraint: + c = self.__award_to_constraint(award) + if c: + constraints.append(c) + variables["awardConstraint"] = {"allEventNominations": constraints} + if params.ranked: + constraints = [] + for r in params.ranked: + c = self.__ranked_list_to_constraint(r) + if c: + constraints.append(c) + variables["rankedTitleListConstraint"] = {"allRankedTitleLists": constraints, + "excludeRankedTitleLists": []} + if params.interests: + constraints = [] + for interest in params.interests: + in_id = self.interest_id.get(interest) + if in_id: + constraints.append(in_id) + variables["interestConstraint"] = {"allInterestIds": constraints, "excludeInterestIds": []} + if not first_page and last_cursor: + variables["after"] = last_cursor + + params = {"operationName": "AdvancedTitleSearch", + "variables": variables} + data = self.__request(params, sha256) + if not data: + return None + return data.get("advancedTitleSearch") + + def __known_as(self, imdbid: str, + sha256='48d4f7bfa73230fb550147bd4704d8050080e65fe2ad576da6276cac2330e446') -> Optional[List]: + """ + 获取电影和电视别名 + :param imdbid: IMBd id + :return: 别名列表 + """ + self.__update_hash() + if self._imdb_api_hash.get("TitleAkasPaginated"): + sha256 = self._imdb_api_hash["TitleAkasPaginated"] + params = {"operationName": "TitleAkasPaginated", + "variables": {"const": imdbid, "first": 50, "locale": "en-US", "originalTitleText": False}} + data = self.__request(params=params, sha256=sha256) + if not data: + return None + if not data.get("data", {}).get("title", {}).get("akas", {}).get("total"): + return None + akas = [] + for edge in data["data"]["title"]["akas"]["edges"]: + title = edge.get("node", {}).get("displayableProperty", {}).get("value", {}).get("plainText") + if not title: + continue + country = edge.get("node", {}).get("country", {}) + language = edge.get("node", {}).get("language", {}) + akas.append({"title": title, "country": country, "language": language}) + return akas + + def __search_on_imdb(self, term, mtype, release_year=None): + params = f"{term}" + if release_year is not None: + params += f" {release_year}" + ret = RequestUtils( + accept_type="application/json", + ).get_res(f"{self._search_endpoint % params}") + if not ret: + return None + data = ret.json() + if "d" not in data: + return None + result = [d for d in data["d"] if d.get("qid") in self._qid_map.get(mtype)] + return result + + def search_tvs(self, title: str, year: str = None) -> List[dict]: + if not title: + return [] + if year: + tvs = self.__search_on_imdb(title, MediaType.TV, year) or [] + else: + tvs = self.__search_on_imdb(title, MediaType.TV, ) or [] + ret_infos = [] + for tv in tvs: + # if title in tv.get("l"): + # if self.__compare_names(title, [tv.get("l")]): + # tv['media_type'] = MediaType.TV + ret_infos.append(tv) + return ret_infos + + def search_movies(self, title: str, year: str = None) -> List[dict]: + if not title: + return [] + if year: + movies = self.__search_on_imdb(title, MediaType.MOVIE, year) or [] + else: + movies = self.__search_on_imdb(title, MediaType.MOVIE) or [] + ret_infos = [] + for movie in movies: + # if title in movie.get("l"): + # if self.__compare_names(title, [movie.get("l")]): + # movie['media_type'] = MediaType.MOVIE + ret_infos.append(movie) + return ret_infos + + @staticmethod + def __compare_names(file_name: str, tmdb_names: list) -> bool: + """ + 比较文件名是否匹配,忽略大小写和特殊字符 + :param file_name: 识别的文件名或者种子名 + :param tmdb_names: TMDB返回的译名 + :return: True or False + """ + if not file_name or not tmdb_names: + return False + if not isinstance(tmdb_names, list): + tmdb_names = [tmdb_names] + file_name = StringUtils.clear(file_name).upper() + for tmdb_name in tmdb_names: + tmdb_name = StringUtils.clear(tmdb_name).strip().upper() + if file_name == tmdb_name: + return True + return False + + def __search_movie_by_name(self, name: str, year: str) -> Optional[dict]: + """ + 根据名称查询电影IMDB匹配 + :param name: 识别的文件名或种子名 + :param year: 电影上映日期 + :return: 匹配的媒体信息 + """ + movies = self.search_movies(name, year=year) + if (movies is None) or (len(movies) == 0): + logger.debug(f"{name} 未找到相关电影信息!") + return {} + movies = sorted( + movies, + key=lambda x: str(x.get("y") or '0000'), + reverse=True + ) + for movie in movies: + movie_year = f"{movie.get('y')}" + if year and movie_year != year: + # 年份不匹配 + continue + # 匹配标题、原标题 + movie_info = self.imdbid(movie.get("id")) + if not movie_info: + continue + if self.__compare_names(name, [movie_info.get("primary_title")]): + return movie_info + if movie_info.get("original_title") and self.__compare_names(name, [movie_info.get("original_title")]): + return movie_info + akas = self.__known_as(movie.get("id")) + if not akas: + continue + akas_names = [item.get("title") for item in akas] + if self.__compare_names(name, akas_names): + return movie_info + return {} + + def __search_tv_by_name(self, name: str, year: str) -> Optional[dict]: + """ + 根据名称查询电视剧IMDB匹配 + :param name: 识别的文件名或者种子名 + :param year: 电视剧的首播年份 + :return: 匹配的媒体信息 + """ + tvs = self.search_tvs(name, year=year) + if (tvs is None) or (len(tvs) == 0): + logger.debug(f"{name} 未找到相关电影信息!") + return {} + tvs = sorted( + tvs, + key=lambda x: str(x.get("y") or '0000'), + reverse=True + ) + for tv in tvs: + tv_year = f"{tv.get('y')}" + if year and tv_year != year: + # 年份不匹配 + continue + # 匹配标题、原标题 + tv_info = self.imdbid(tv.get("id")) + if not tv_info: + continue + if self.__compare_names(name, [tv_info.get("primary_title")]): + return tv_info + if tv_info.get("original_title") and self.__compare_names(name, [tv_info.get("original_title")]): + return tv_info + akas = self.__known_as(tv.get("id")) + if not akas: + continue + akas_names = [item.get("title") for item in akas] + if self.__compare_names(name, akas_names): + return tv_info + return {} + + def __search_tv_by_season(self, name: str, season_year: str, season_number: int) -> Optional[dict]: + """ + 根据电视剧的名称和季的年份及序号匹配IMDB + :param name: 识别的文件名或者种子名 + :param season_year: 季的年份 + :param season_number: 季序号 + :return: 匹配的媒体信息 + """ + + def __season_match(_tv_info: dict, _season_year: str) -> bool: + tv_extra_info = self.__episodes(_tv_info.get("id")) + if not tv_extra_info: + return False + release_year = [] + for item in tv_extra_info["episodes"]["items"]: + if item.get("season") == season_number: + release_year.append(item.get("releaseDate").get("year") or item.get("releaseYear")) + first_release_year = min(release_year) if release_year else tv_extra_info["currentYear"] + if first_release_year == _season_year: + _tv_info["seasons"] = tv_extra_info["seasons"] + _tv_info["episodes"] = tv_extra_info["episodes"] + return True + + tvs = self.search_tvs(title=name) + if (tvs is None) or (len(tvs) == 0): + logger.debug("%s 未找到季%s相关信息!" % (name, season_number)) + return {} + tvs = sorted( + tvs, + key=lambda x: str(x.get('y') or '0000'), + reverse=True + ) + for tv in tvs: + tv_info = self.imdbid(tv.get("id")) + if not tv_info: + continue + tv_year = f"{tv.get('y')}" if tv.get('y') else None + if (self.__compare_names(name, [tv_info.get('primary_title')]) + or (tv_info.get('original_title') and self.__compare_names(name, [tv_info.get('original_title')]))) \ + and (tv_year == str(season_year)): + return tv_info + akas = self.__known_as(tv.get("id")) + if not akas: + continue + akas_names = [item.get("title") for item in akas] + if not self.__compare_names(name, akas_names): + continue + if __season_match(_tv_info=tv_info, _season_year=season_year): + return tv_info + + def get_info(self, + mtype: MediaType, + imdbid: str) -> dict: + """ + 给定IMDB号,查询一条媒体信息 + :param mtype: 类型:电影、电视剧,为空时都查(此时用不上年份) + :param imdbid: IMDB的ID + """ + # 查询TMDB详情 + if mtype == MediaType.MOVIE: + imdb_info = self.imdbid(imdbid) + if imdb_info: + imdb_info['media_type'] = MediaType.MOVIE + elif mtype == MediaType.TV: + imdb_info = self.imdbid(imdbid) + if imdb_info: + imdb_info['media_type'] = MediaType.TV + tv_extra_info = self.__episodes(imdbid) + imdb_info["seasons"] = tv_extra_info["seasons"] + imdb_info["episodes"] = tv_extra_info["episodes"] + else: + imdb_info = None + logger.warn(f"IMDb id:{imdbid} 未查询到媒体信息") + return imdb_info + + def match_multi(self, name: str) -> Optional[dict]: + """ + 根据名称同时查询电影和电视剧,没有类型也没有年份时使用 + :param name: 识别的文件名或种子名 + :return: 匹配的媒体信息 + """ + + multis = self.search_tvs(name) + self.search_movies(name) + ret_info = {} + if len(multis) == 0: + logger.debug(f"{name} 未找到相关媒体息!") + return {} + else: + multis = sorted( + multis, + key=lambda x: ("1" if x.get("media_type") == MediaType.MOVIE else "0") + str(x.get('y') or '0000'), + reverse=True + ) + media_t = MediaType.UNKNOWN + for multi in multis: + media_info = self.imdbid(multi.get("id")) + if not media_info: + continue + if multi.get("media_type") == MediaType.MOVIE: + if self.__compare_names(name, media_info.get('primary_title')) \ + or self.__compare_names(name, multi.get('primary_title')): + ret_info = media_info + media_t = MediaType.MOVIE + break + elif multi.get("media_type") == MediaType.TV: + if self.__compare_names(name, media_info.get('primary_title')) \ + or self.__compare_names(name, multi.get('primary_title')): + ret_info = media_info + media_t = MediaType.TV + break + if ret_info and not isinstance(ret_info.get("media_type"), MediaType): + ret_info['media_type'] = media_t + return ret_info + + def match(self, name: str, + mtype: MediaType, + year: Optional[str] = None, + season_year: Optional[str] = None, + season_number: Optional[int] = None, + group_seasons: Optional[List[dict]] = None) -> Optional[dict]: + """ + 搜索imdb中的媒体信息,匹配返回一条尽可能正确的信息 + :param name: 检索的名称 + :param mtype: 类型:电影、电视剧 + :param year: 年份,如要是季集需要是首播年份(first_air_date) + :param season_year: 当前季集年份 + :param season_number: 季集,整数 + :param group_seasons: 集数组信息 + :return: TMDB的INFO,同时会将mtype赋值到media_type中 + """ + if not name: + return None + info = {} + if mtype != MediaType.TV: + year_range = [year] + if year: + year_range.append(str(int(year) + 1)) + year_range.append(str(int(year) - 1)) + for year in year_range: + logger.debug( + f"正在识别{mtype.value}:{name}, 年份={year} ...") + info = self.__search_movie_by_name(name, year) + if info: + info['media_type'] = MediaType.MOVIE + break + else: + # 有当前季和当前季集年份,使用精确匹配 + if season_year and season_number: + logger.debug( + f"正在识别{mtype.value}:{name}, 季集={season_number}, 季集年份={season_year} ...") + info = self.__search_tv_by_season(name, + season_year, + season_number) + if not info: + year_range = [year] + if year: + year_range.append(str(int(year) + 1)) + year_range.append(str(int(year) - 1)) + for year in year_range: + logger.debug( + f"正在识别{mtype.value}:{name}, 年份={year} ...") + info = self.__search_tv_by_name(name, year) + if info: + break + if info: + info['media_type'] = MediaType.TV + if not info.get("seasons"): + tv_extra_info = self.__episodes(info.get('id')) + if tv_extra_info: + info["seasons"] = tv_extra_info["seasons"] + info["episodes"] = tv_extra_info["episodes"] + return info diff --git a/plugins.v2/imdbsource/requirements.txt b/plugins.v2/imdbsource/requirements.txt new file mode 100644 index 0000000..86d7fe2 --- /dev/null +++ b/plugins.v2/imdbsource/requirements.txt @@ -0,0 +1,3 @@ +graphene~=3.4.3 +ijson~=3.4.0 +requests-html~=0.10.0 \ No newline at end of file diff --git a/plugins.v2/iyuuautoseed/__init__.py b/plugins.v2/iyuuautoseed/__init__.py index a9d9766..c64cde1 100644 --- a/plugins.v2/iyuuautoseed/__init__.py +++ b/plugins.v2/iyuuautoseed/__init__.py @@ -48,10 +48,6 @@ class IYUUAutoSeed(_PluginBase): # 私有属性 _scheduler = None iyuu_helper = None - downloader_helper = None - sites_helper = None - site_oper = None - torrent_helper = None # 开关 _enabled = False _cron = None @@ -72,6 +68,7 @@ class IYUUAutoSeed(_PluginBase): _addhosttotag = False _size = None _clearcache = False + _auto_start = False # 退出事件 _event = Event() # 种子链接xpaths @@ -99,10 +96,7 @@ class IYUUAutoSeed(_PluginBase): cached = 0 def init_plugin(self, config: dict = None): - self.sites_helper = SitesHelper() - self.site_oper = SiteOper() - self.torrent_helper = TorrentHelper() - self.downloader_helper = DownloaderHelper() + # 读取配置 if config: self._enabled = config.get("enabled") @@ -128,8 +122,8 @@ class IYUUAutoSeed(_PluginBase): self._success_caches = [] if self._clearcache else config.get("success_caches") or [] # 过滤掉已删除的站点 - all_sites = [site.id for site in self.site_oper.list_order_by_pri()] + [site.get("id") for site in - self.__custom_sites()] + all_sites = [site.id for site in SiteOper().list_order_by_pri()] + [site.get("id") for site in + self.__custom_sites()] self._sites = [site_id for site_id in all_sites if site_id in self._sites] self.__update_config() @@ -171,7 +165,7 @@ class IYUUAutoSeed(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - services = self.downloader_helper.get_services(name_filters=self._downloaders) + services = DownloaderHelper().get_services(name_filters=self._downloaders) if not services: logger.warning("获取下载器实例失败,请检查配置") return None @@ -198,7 +192,7 @@ class IYUUAutoSeed(_PluginBase): logger.debug("尚未配置主辅分离下载器,辅种不分离") return None - service = self.downloader_helper.get_service(name=self._auto_downloader) + service = DownloaderHelper().get_service(name=self._auto_downloader) if not service: logger.warning("获取主辅分离下载器实例失败,请检查配置") return None @@ -248,7 +242,7 @@ class IYUUAutoSeed(_PluginBase): # 站点的可选项 site_options = ([{"title": site.name, "value": site.id} - for site in self.site_oper.list_order_by_pri()] + for site in SiteOper().list_order_by_pri()] + [{"title": site.get("name"), "value": site.get("id")} for site in customSites]) return [ @@ -381,7 +375,7 @@ class IYUUAutoSeed(_PluginBase): 'model': 'downloaders', 'label': '下载器', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] @@ -401,7 +395,7 @@ class IYUUAutoSeed(_PluginBase): 'model': 'auto_downloader', 'label': '主辅分离', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] @@ -1048,7 +1042,8 @@ class IYUUAutoSeed(_PluginBase): # 查询站点 site_domain = StringUtils.get_url_domain(site_url) # 站点信息 - site_info = self.sites_helper.get_indexer(site_domain) + sites_helper = SitesHelper() + site_info = sites_helper.get_indexer(site_domain) if not site_info or not site_info.get('url'): logger.debug(f"没有维护种子对应的站点:{site_url}") return False @@ -1064,7 +1059,7 @@ class IYUUAutoSeed(_PluginBase): self.exist += 1 return False # 站点流控 - check, checkmsg = self.sites_helper.check(site_domain) + check, checkmsg = sites_helper.check(site_domain) if check: logger.warn(checkmsg) self.fail += 1 @@ -1086,7 +1081,7 @@ class IYUUAutoSeed(_PluginBase): else: torrent_url += "?https=1" # 下载种子文件 - _, content, _, _, error_msg = self.torrent_helper.download_torrent( + _, content, _, _, error_msg = TorrentHelper().download_torrent( url=torrent_url, cookie=site_info.get("cookie"), ua=site_info.get("ua") or settings.USER_AGENT, diff --git a/plugins.v2/libraryscraper/__init__.py b/plugins.v2/libraryscraper/__init__.py index 0ef398c..8364440 100644 --- a/plugins.v2/libraryscraper/__init__.py +++ b/plugins.v2/libraryscraper/__init__.py @@ -40,8 +40,6 @@ class LibraryScraper(_PluginBase): user_level = 1 # 私有属性 - transferhis = None - mediachain = None _scheduler = None _scraper = None # 限速开关 @@ -55,7 +53,7 @@ class LibraryScraper(_PluginBase): _event = Event() def init_plugin(self, config: dict = None): - self.mediachain = MediaChain() + # 读取配置 if config: self._enabled = config.get("enabled") @@ -70,7 +68,6 @@ class LibraryScraper(_PluginBase): # 启动定时任务 & 立即运行一次 if self._enabled or self._onlyonce: - self.transferhis = TransferHistoryOper() if self._onlyonce: logger.info(f"媒体库刮削服务,立即运行一次") @@ -401,14 +398,14 @@ class LibraryScraper(_PluginBase): # 如果未开启新增已入库媒体是否跟随TMDB信息变化则根据tmdbid查询之前的title if not settings.SCRAP_FOLLOW_TMDB: - transfer_history = self.transferhis.get_by_type_tmdbid(tmdbid=mediainfo.tmdb_id, - mtype=mediainfo.type.value) + transfer_history = TransferHistoryOper().get_by_type_tmdbid(tmdbid=mediainfo.tmdb_id, + mtype=mediainfo.type.value) if transfer_history: mediainfo.title = transfer_history.title # 获取图片 self.chain.obtain_images(mediainfo) # 刮削 - self.mediachain.scrape_metadata( + MediaChain().scrape_metadata( fileitem=schemas.FileItem( storage="local", type="dir", diff --git a/plugins.v2/mediaservermsg/__init__.py b/plugins.v2/mediaservermsg/__init__.py index 8196ede..e4f38ac 100644 --- a/plugins.v2/mediaservermsg/__init__.py +++ b/plugins.v2/mediaservermsg/__init__.py @@ -31,7 +31,6 @@ class MediaServerMsg(_PluginBase): auth_level = 1 # 私有属性 - mediaserver_helper = None _enabled = False _add_play_link = False _mediaservers = None @@ -59,7 +58,7 @@ class MediaServerMsg(_PluginBase): } def init_plugin(self, config: dict = None): - self.mediaserver_helper = MediaServerHelper() + if config: self._enabled = config.get("enabled") self._types = config.get("types") or [] @@ -74,7 +73,7 @@ class MediaServerMsg(_PluginBase): logger.warning("尚未配置媒体服务器,请检查配置") return None - services = self.mediaserver_helper.get_services(type_filter=type_filter, name_filters=self._mediaservers) + services = MediaServerHelper().get_services(type_filter=type_filter, name_filters=self._mediaservers) if not services: logger.warning("获取媒体服务器实例失败,请检查配置") return None @@ -181,7 +180,7 @@ class MediaServerMsg(_PluginBase): 'model': 'mediaservers', 'label': '媒体服务器', 'items': [{"title": config.name, "value": config.name} - for config in self.mediaserver_helper.get_configs().values()] + for config in MediaServerHelper().get_configs().values()] } } ] @@ -342,7 +341,7 @@ class MediaServerMsg(_PluginBase): if service: play_link = service.instance.get_play_url(event_info.item_id) elif event_info.channel: - services = self.mediaserver_helper.get_services(type_filter=event_info.channel) + services = MediaServerHelper().get_services(type_filter=event_info.channel) for service in services.values(): play_link = service.instance.get_play_url(event_info.item_id) if play_link: diff --git a/plugins.v2/mediaserverrefresh/__init__.py b/plugins.v2/mediaserverrefresh/__init__.py index 87dc621..67f2fd3 100644 --- a/plugins.v2/mediaserverrefresh/__init__.py +++ b/plugins.v2/mediaserverrefresh/__init__.py @@ -32,13 +32,12 @@ class MediaServerRefresh(_PluginBase): auth_level = 1 # 私有属性 - mediaserver_helper = None _enabled = False _delay = 0 _mediaservers = None def init_plugin(self, config: dict = None): - self.mediaserver_helper = MediaServerHelper() + if config: self._enabled = config.get("enabled") self._delay = config.get("delay") or 0 @@ -53,7 +52,7 @@ class MediaServerRefresh(_PluginBase): logger.warning("尚未配置媒体服务器,请检查配置") return None - services = self.mediaserver_helper.get_services(name_filters=self._mediaservers) + services = MediaServerHelper().get_services(name_filters=self._mediaservers) if not services: logger.warning("获取媒体服务器实例失败,请检查配置") return None @@ -128,7 +127,7 @@ class MediaServerRefresh(_PluginBase): 'model': 'mediaservers', 'label': '媒体服务器', 'items': [{"title": config.name, "value": config.name} - for config in self.mediaserver_helper.get_configs().values()] + for config in MediaServerHelper().get_configs().values()] } } ] diff --git a/plugins.v2/moviepilotupdatenotify/__init__.py b/plugins.v2/moviepilotupdatenotify/__init__.py index 05866a3..3125c44 100644 --- a/plugins.v2/moviepilotupdatenotify/__init__.py +++ b/plugins.v2/moviepilotupdatenotify/__init__.py @@ -1,5 +1,6 @@ import datetime import re +from typing import Any, List, Dict, Tuple, Optional import pytz from apscheduler.schedulers.background import BackgroundScheduler @@ -7,12 +8,11 @@ from apscheduler.triggers.cron import CronTrigger from app.chain.system import SystemChain from app.core.config import settings -from app.plugins import _PluginBase -from typing import Any, List, Dict, Tuple, Optional +from app.helper.system import SystemHelper from app.log import logger +from app.plugins import _PluginBase from app.schemas import NotificationType from app.utils.http import RequestUtils -from app.utils.system import SystemUtils class MoviePilotUpdateNotify(_PluginBase): @@ -23,7 +23,7 @@ class MoviePilotUpdateNotify(_PluginBase): # 插件图标 plugin_icon = "Moviepilot_A.png" # 插件版本 - plugin_version = "2.1" + plugin_version = "2.2" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -70,7 +70,7 @@ class MoviePilotUpdateNotify(_PluginBase): # 自动重启 if (server_update or front_update) and self._restart: logger.info("开始执行自动重启…") - SystemUtils.restart() + SystemHelper.restart() def __check_server_update(self): """ @@ -165,7 +165,7 @@ class MoviePilotUpdateNotify(_PluginBase): logger.error("无法获取版本信息,请检查网络连接或GitHub API请求。") return None - def __get_backend_latest(self) -> Tuple[str, str, str]: + def __get_backend_latest(self) -> Tuple[Optional[str], Optional[str], Optional[str]]: """ 获取最新版本 """ diff --git a/plugins.v2/personmeta/__init__.py b/plugins.v2/personmeta/__init__.py index 4be5e26..d538d28 100644 --- a/plugins.v2/personmeta/__init__.py +++ b/plugins.v2/personmeta/__init__.py @@ -55,9 +55,6 @@ class PersonMeta(_PluginBase): # 私有属性 _scheduler = None - tmdbchain = None - mschain = None - mediaserver_helper = None _enabled = False _onlyonce = False _cron = None @@ -67,9 +64,7 @@ class PersonMeta(_PluginBase): _mediaservers = [] def init_plugin(self, config: dict = None): - self.tmdbchain = TmdbChain() - self.mschain = MediaServerChain() - self.mediaserver_helper = MediaServerHelper() + if config: self._enabled = config.get("enabled") self._onlyonce = config.get("onlyonce") @@ -266,7 +261,7 @@ class PersonMeta(_PluginBase): 'model': 'mediaservers', 'label': '媒体服务器', 'items': [{"title": config.name, "value": config.name} - for config in self.mediaserver_helper.get_configs().values()] + for config in MediaServerHelper().get_configs().values()] } } ] @@ -316,7 +311,7 @@ class PersonMeta(_PluginBase): logger.warning("尚未配置媒体服务器,请检查配置") return None - services = self.mediaserver_helper.get_services(type_filter=type_filter, name_filters=self._mediaservers) + services = MediaServerHelper().get_services(type_filter=type_filter, name_filters=self._mediaservers) if not services: logger.warning("获取媒体服务器实例失败,请检查配置") return None @@ -355,7 +350,7 @@ class PersonMeta(_PluginBase): logger.warn(f"{mediainfo.title_year} 在媒体库中不存在") return # 查询条目详情 - iteminfo = self.mschain.iteminfo(server=existsinfo.server, item_id=existsinfo.itemid) + iteminfo = MediaServerChain().iteminfo(server=existsinfo.server, item_id=existsinfo.itemid) if not iteminfo: logger.warn(f"{mediainfo.title_year} 条目详情获取失败") return @@ -371,12 +366,13 @@ class PersonMeta(_PluginBase): service_infos = self.service_infos() if not service_infos: return + mediaserverchain = MediaServerChain() for server, service in service_infos.items(): # 扫描所有媒体库 logger.info(f"开始刮削服务器 {server} 的演员信息 ...") - for library in self.mschain.librarys(server): + for library in mediaserverchain.librarys(server): logger.info(f"开始刮削媒体库 {library.name} 的演员信息 ...") - for item in self.mschain.items(server, library.id): + for item in mediaserverchain.items(server, library.id): if not item: continue if not item.item_id: @@ -577,7 +573,7 @@ class PersonMeta(_PluginBase): # 从TMDB信息中更新人物信息 person_tmdbid, person_imdbid = __get_peopleid(personinfo) if person_tmdbid: - person_detail = self.tmdbchain.person_detail(int(person_tmdbid)) + person_detail = TmdbChain().person_detail(int(person_tmdbid)) if person_detail: cn_name = self.__get_chinese_name(person_detail) # 图片优先从TMDB获取 diff --git a/plugins.v2/playletcategory/__init__.py b/plugins.v2/playletcategory/__init__.py index 109d4e0..2fd85e0 100644 --- a/plugins.v2/playletcategory/__init__.py +++ b/plugins.v2/playletcategory/__init__.py @@ -1,8 +1,8 @@ import random -import time import shutil import subprocess import threading +import time from pathlib import Path from typing import Any, List, Dict, Tuple @@ -12,7 +12,6 @@ from app.core.event import eventmanager, Event from app.log import logger from app.plugins import _PluginBase from app.schemas import TransferInfo -from app.schemas.file import FileItem from app.schemas.types import EventType, MediaType, NotificationType from app.utils.system import SystemUtils @@ -320,9 +319,7 @@ class PlayletCategory(_PluginBase): try: # 相对路径 relative_path = file.relative_to(target_path) - logger.debug(f"relative_path:{to_path}") to_path = new_path / relative_path - logger.debug(f"to_path:{to_path}") shutil.move(file, to_path) except Exception as e: logger.error(f"移动文件失败:{e}") diff --git a/plugins.v2/qbcommand/__init__.py b/plugins.v2/qbcommand/__init__.py index a6c79d6..b08c649 100644 --- a/plugins.v2/qbcommand/__init__.py +++ b/plugins.v2/qbcommand/__init__.py @@ -41,8 +41,6 @@ class QbCommand(_PluginBase): auth_level = 1 # 私有属性 - _sites = None - _siteoper = None _qb = None _enabled: bool = False _notify: bool = False @@ -62,10 +60,10 @@ class QbCommand(_PluginBase): _multi_level_root_domain = ["edu.cn", "com.cn", "net.cn", "org.cn"] _scheduler = None _exclude_dirs = "" + _downloaders = [] + def init_plugin(self, config: dict = None): - self._sites = SitesHelper() - self._siteoper = SiteOper() - self.downloader_helper = DownloaderHelper() + # 停止现有任务 self.stop_service() # 读取配置 @@ -87,7 +85,7 @@ class QbCommand(_PluginBase): self._op_site_ids = config.get("op_site_ids") or [] self._downloaders = config.get("downloaders") # 查询所有站点 - all_sites = [site for site in self._sites.get_indexers() if not site.get("public")] + self.__custom_sites() + all_sites = [site for site in SitesHelper().get_indexers() if not site.get("public")] + self.__custom_sites() # 过滤掉没有选中的站点 self._op_sites = [site for site in all_sites if site.get("id") in self._op_site_ids] self._exclude_dirs = config.get("exclude_dirs") or "" @@ -101,8 +99,7 @@ class QbCommand(_PluginBase): self._scheduler.add_job( self.pause_torrent, "date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) - + timedelta(seconds=3), + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), ) elif self._only_resume_once: self._scheduler = BackgroundScheduler(timezone=settings.TZ) @@ -110,8 +107,7 @@ class QbCommand(_PluginBase): self._scheduler.add_job( self.resume_torrent, "date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) - + timedelta(seconds=3), + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), ) self._only_resume_once = False @@ -136,9 +132,9 @@ class QbCommand(_PluginBase): self._scheduler.start() if ( - self._only_pause_upload - or self._only_pause_download - or self._only_pause_checking + self._only_pause_upload + or self._only_pause_download + or self._only_pause_checking ): if self._only_pause_upload: self._scheduler = BackgroundScheduler(timezone=settings.TZ) @@ -146,8 +142,7 @@ class QbCommand(_PluginBase): self._scheduler.add_job( self.pause_torrent, "date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) - + timedelta(seconds=3), + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), kwargs={ 'type': self.TorrentType.UPLOADING } @@ -158,8 +153,7 @@ class QbCommand(_PluginBase): self._scheduler.add_job( self.pause_torrent, "date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) - + timedelta(seconds=3), + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), kwargs={ 'type': self.TorrentType.DOWNLOADING } @@ -170,8 +164,7 @@ class QbCommand(_PluginBase): self._scheduler.add_job( self.pause_torrent, "date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) - + timedelta(seconds=3), + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), kwargs={ 'type': self.TorrentType.CHECKING } @@ -201,7 +194,7 @@ class QbCommand(_PluginBase): self.set_limit(self._upload_limit, self._download_limit) @property - def service_info(self) -> Optional[ServiceInfo]: + def service_info(self) -> Optional[Dict[str, ServiceInfo]]: """ 服务信息 """ @@ -209,7 +202,7 @@ class QbCommand(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - services = self.downloader_helper.get_services(name_filters=self._downloaders) + services = DownloaderHelper().get_services(name_filters=self._downloaders) if not services: logger.warning("获取下载器实例失败,请检查配置") @@ -230,15 +223,17 @@ class QbCommand(_PluginBase): return active_services - def check_is_qb(self, service_info) -> bool: + @staticmethod + def check_is_qb(service_info) -> bool: """ 检查下载器类型是否为 qbittorrent 或 transmission """ - if self.downloader_helper.is_downloader(service_type="qbittorrent", service=service_info): + if DownloaderHelper().is_downloader(service_type="qbittorrent", service=service_info): return True - elif self.downloader_helper.is_downloader(service_type="transmission", service=service_info): + elif DownloaderHelper().is_downloader(service_type="transmission", service=service_info): return False return False + def get_state(self) -> bool: return self._enabled @@ -409,9 +404,9 @@ class QbCommand(_PluginBase): if torrent.state_enum.is_uploading and not torrent.state_enum.is_paused: uploading_torrents.append(torrent.get("hash")) elif ( - torrent.state_enum.is_downloading - and not torrent.state_enum.is_paused - and not torrent.state_enum.is_checking + torrent.state_enum.is_downloading + and not torrent.state_enum.is_paused + and not torrent.state_enum.is_checking ): downloading_torrents.append(torrent.get("hash")) elif torrent.state_enum.is_checking: @@ -476,7 +471,7 @@ class QbCommand(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue all_torrents = self.get_all_torrents(service) hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = ( @@ -498,12 +493,12 @@ class QbCommand(_PluginBase): mtype=NotificationType.SiteMessage, title=f"【下载器{downloader_name}暂停任务启动】", text=f"种子总数: {len(all_torrents)} \n" - f"做种数量: {len(hash_uploading)}\n" - f"下载数量: {len(hash_downloading)}\n" - f"检查数量: {len(hash_checking)}\n" - f"暂停数量: {len(hash_paused)}\n" - f"错误数量: {len(hash_error)}\n" - f"暂停操作中请稍等...\n", + f"做种数量: {len(hash_uploading)}\n" + f"下载数量: {len(hash_downloading)}\n" + f"检查数量: {len(hash_checking)}\n" + f"暂停数量: {len(hash_paused)}\n" + f"错误数量: {len(hash_error)}\n" + f"暂停操作中请稍等...\n", ) pause_torrents = self.filter_pause_torrents(all_torrents) hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = ( @@ -551,11 +546,11 @@ class QbCommand(_PluginBase): mtype=NotificationType.SiteMessage, title=f"【下载器{downloader_name}暂停任务完成】", text=f"种子总数: {len(all_torrents)} \n" - f"做种数量: {len(hash_uploading)}\n" - f"下载数量: {len(hash_downloading)}\n" - f"检查数量: {len(hash_checking)}\n" - f"暂停数量: {len(hash_paused)}\n" - f"错误数量: {len(hash_error)}\n", + f"做种数量: {len(hash_uploading)}\n" + f"下载数量: {len(hash_downloading)}\n" + f"检查数量: {len(hash_checking)}\n" + f"暂停数量: {len(hash_paused)}\n" + f"错误数量: {len(hash_error)}\n", ) def __is_excluded(self, file_path) -> bool: @@ -566,6 +561,7 @@ class QbCommand(_PluginBase): if exclude_dir and exclude_dir in str(file_path): return True return False + def filter_pause_torrents(self, all_torrents): torrents = [] for torrent in all_torrents: @@ -592,7 +588,7 @@ class QbCommand(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue all_torrents = self.get_all_torrents(service) hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = ( @@ -613,12 +609,12 @@ class QbCommand(_PluginBase): mtype=NotificationType.SiteMessage, title=f"【下载器{downloader_name}开始任务启动】", text=f"种子总数: {len(all_torrents)} \n" - f"做种数量: {len(hash_uploading)}\n" - f"下载数量: {len(hash_downloading)}\n" - f"检查数量: {len(hash_checking)}\n" - f"暂停数量: {len(hash_paused)}\n" - f"错误数量: {len(hash_error)}\n" - f"开始操作中请稍等...\n", + f"做种数量: {len(hash_uploading)}\n" + f"下载数量: {len(hash_downloading)}\n" + f"检查数量: {len(hash_checking)}\n" + f"暂停数量: {len(hash_paused)}\n" + f"错误数量: {len(hash_error)}\n" + f"开始操作中请稍等...\n", ) resume_torrents = self.filter_resume_torrents(all_torrents) @@ -655,11 +651,11 @@ class QbCommand(_PluginBase): mtype=NotificationType.SiteMessage, title=f"【下载器{downloader_name}开始任务完成】", text=f"种子总数: {len(all_torrents)} \n" - f"做种数量: {len(hash_uploading)}\n" - f"下载数量: {len(hash_downloading)}\n" - f"检查数量: {len(hash_checking)}\n" - f"暂停数量: {len(hash_paused)}\n" - f"错误数量: {len(hash_error)}\n", + f"做种数量: {len(hash_uploading)}\n" + f"下载数量: {len(hash_downloading)}\n" + f"检查数量: {len(hash_checking)}\n" + f"暂停数量: {len(hash_paused)}\n" + f"错误数量: {len(hash_error)}\n", ) def filter_resume_torrents(self, all_torrents): @@ -714,7 +710,7 @@ class QbCommand(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue all_torrents = self.get_all_torrents(service) hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = ( @@ -734,11 +730,11 @@ class QbCommand(_PluginBase): mtype=NotificationType.SiteMessage, title=f"【下载器{downloader_name}任务状态】", text=f"种子总数: {len(all_torrents)} \n" - f"做种数量: {len(hash_uploading)}\n" - f"下载数量: {len(hash_downloading)}\n" - f"检查数量: {len(hash_checking)}\n" - f"暂停数量: {len(hash_paused)}\n" - f"错误数量: {len(hash_error)}\n" + f"做种数量: {len(hash_uploading)}\n" + f"下载数量: {len(hash_downloading)}\n" + f"检查数量: {len(hash_checking)}\n" + f"暂停数量: {len(hash_paused)}\n" + f"错误数量: {len(hash_error)}\n" ) @eventmanager.register(EventType.PluginAction) @@ -766,10 +762,10 @@ class QbCommand(_PluginBase): return True if ( - not upload_limit - or not upload_limit.isdigit() - or not download_limit - or not download_limit.isdigit() + not upload_limit + or not upload_limit.isdigit() + or not download_limit + or not download_limit.isdigit() ): self.post_message( mtype=NotificationType.SiteMessage, @@ -783,7 +779,7 @@ class QbCommand(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue flag = flag and downloader_obj.set_speed_limit( download_limit=int(download_limit), upload_limit=int(upload_limit) @@ -806,7 +802,7 @@ class QbCommand(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue download_limit_current_val, _ = downloader_obj.get_speed_limit() flag = flag and downloader_obj.set_speed_limit( @@ -831,7 +827,7 @@ class QbCommand(_PluginBase): downloader_name = service.name downloader_obj = service.instance if not downloader_obj: - logger.error(f"{self.LOG_TAG} 获取下载器失败 {downloader_name}") + logger.error(f"获取下载器失败 {downloader_name}") continue _, upload_limit_current_val = downloader_obj.get_speed_limit() flag = flag and downloader_obj.set_speed_limit( @@ -856,7 +852,7 @@ class QbCommand(_PluginBase): elif flag is None and self._enabled and self._enable_upload_limit: flag = self.set_upload_limit(upload_limit) - if flag == True: + if flag is True: logger.info(f"设置QB限速成功") if self._notify: if upload_limit == 0: @@ -872,7 +868,7 @@ class QbCommand(_PluginBase): title=f"【QB远程操作】", text=text, ) - elif flag == False: + elif flag is False: logger.error(f"QB设置限速失败") if self._notify: self.post_message( @@ -881,7 +877,8 @@ class QbCommand(_PluginBase): text=f"设置QB限速失败", ) - def get_torrent_tracker(self, torrent): + @staticmethod + def get_torrent_tracker(torrent): """ qb解析 tracker :return: tracker url @@ -937,11 +934,11 @@ class QbCommand(_PluginBase): customSites = self.__custom_sites() site_options = [ - {"title": site.name, "value": site.id} - for site in self._siteoper.list_order_by_pri() - ] + [ - {"title": site.get("name"), "value": site.get("id")} for site in customSites - ] + {"title": site.name, "value": site.id} + for site in SiteOper().list_order_by_pri() + ] + [ + {"title": site.get("name"), "value": site.get("id")} for site in customSites + ] return [ { "component": "VForm", @@ -1021,7 +1018,7 @@ class QbCommand(_PluginBase): 'model': 'downloaders', 'label': '下载器', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] diff --git a/plugins.v2/rsssubscribe/__init__.py b/plugins.v2/rsssubscribe/__init__.py index 9af08e9..f2eb689 100644 --- a/plugins.v2/rsssubscribe/__init__.py +++ b/plugins.v2/rsssubscribe/__init__.py @@ -11,7 +11,6 @@ from apscheduler.triggers.cron import CronTrigger from app import schemas from app.chain.download import DownloadChain -from app.chain.search import SearchChain from app.chain.subscribe import SubscribeChain from app.core.config import settings from app.core.context import MediaInfo, TorrentInfo, Context @@ -48,10 +47,6 @@ class RssSubscribe(_PluginBase): # 私有变量 _scheduler: Optional[BackgroundScheduler] = None _cache_path: Optional[Path] = None - rsshelper = None - downloadchain = None - searchchain = None - subscribechain = None # 配置属性 _enabled: bool = False @@ -70,10 +65,6 @@ class RssSubscribe(_PluginBase): _size_range: str = "" def init_plugin(self, config: dict = None): - self.rsshelper = RssHelper() - self.downloadchain = DownloadChain() - self.searchchain = SearchChain() - self.subscribechain = SubscribeChain() # 停止现有任务 self.stop_service() @@ -618,12 +609,14 @@ class RssSubscribe(_PluginBase): history = [] else: history: List[dict] = self.get_data('history') or [] + downloadchain = DownloadChain() + subscribechain = SubscribeChain() for url in self._address.split("\n"): # 处理每一个RSS链接 if not url: continue logger.info(f"开始刷新RSS:{url} ...") - results = self.rsshelper.parse(url, proxy=self._proxy) + results = RssHelper().parse(url, proxy=self._proxy) if not results: logger.error(f"未获取到RSS数据:{url}") return @@ -704,7 +697,7 @@ class RssSubscribe(_PluginBase): # 下载或订阅 if self._action == "download": # 添加下载 - result = self.downloadchain.download_single( + result = downloadchain.download_single( context=Context( meta_info=meta, media_info=mediainfo, @@ -718,18 +711,18 @@ class RssSubscribe(_PluginBase): continue else: # 检查是否在订阅中 - subflag = self.subscribechain.exists(mediainfo=mediainfo, meta=meta) + subflag = subscribechain.exists(mediainfo=mediainfo, meta=meta) if subflag: logger.info(f'{mediainfo.title_year} {meta.season} 正在订阅中') continue # 添加订阅 - self.subscribechain.add(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=meta.begin_season, - exist_ok=True, - username="RSS订阅") + subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + exist_ok=True, + username="RSS订阅") # 存储历史记录 history.append({ "title": f"{mediainfo.title} {meta.season}", @@ -772,4 +765,4 @@ class RssSubscribe(_PluginBase): """ 检查字符串是否表示单个数字或数字范围(如'5', '5.5', '5-10' 或 '5.5-10.2') """ - return bool(re.match(r"^\d+(\.\d+)?(-\d+(\.\d+)?)?$", value)) \ No newline at end of file + return bool(re.match(r"^\d+(\.\d+)?(-\d+(\.\d+)?)?$", value)) diff --git a/plugins.v2/sitestatistic/__init__.py b/plugins.v2/sitestatistic/__init__.py index 5056427..93f4aef 100644 --- a/plugins.v2/sitestatistic/__init__.py +++ b/plugins.v2/sitestatistic/__init__.py @@ -1,11 +1,10 @@ +import gc import warnings -from collections import defaultdict from datetime import datetime, timedelta from threading import Lock from typing import Optional, Any, List, Dict, Tuple import pytz -from app.helper.sites import SitesHelper from apscheduler.schedulers.background import BackgroundScheduler from app import schemas @@ -14,6 +13,7 @@ from app.core.config import settings from app.core.event import eventmanager, Event from app.db.models.siteuserdata import SiteUserData from app.db.site_oper import SiteOper +from app.helper.sites import SitesHelper from app.log import logger from app.plugins import _PluginBase from app.schemas.types import EventType, NotificationType @@ -32,7 +32,7 @@ class SiteStatistic(_PluginBase): # 插件图标 plugin_icon = "statistic.png" # 插件版本 - plugin_version = "1.6" + plugin_version = "1.7.1" # 插件作者 plugin_author = "lightolly,jxxghp" # 作者主页 @@ -45,9 +45,6 @@ class SiteStatistic(_PluginBase): auth_level = 2 # 配置属性 - siteoper = None - siteshelper = None - sitechain = None _enabled: bool = False _onlyonce: bool = False _dashboard_type: str = "today" @@ -55,9 +52,6 @@ class SiteStatistic(_PluginBase): _scheduler = None def init_plugin(self, config: dict = None): - self.siteoper = SiteOper() - self.siteshelper = SitesHelper() - self.sitechain = SiteChain() # 停止现有任务 self.stop_service() @@ -72,7 +66,7 @@ class SiteStatistic(_PluginBase): if self._onlyonce: config["onlyonce"] = False self._scheduler = BackgroundScheduler(timezone=settings.TZ) - self._scheduler.add_job(self.sitechain.refresh_userdatas, "date", + self._scheduler.add_job(SiteChain().refresh_userdatas, "date", run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), name="站点数据统计服务") self._scheduler.print_jobs() @@ -263,68 +257,55 @@ class SiteStatistic(_PluginBase): self.post_message(mtype=NotificationType.SiteMessage, title="站点数据统计", text="\n".join(sorted_messages)) - def __get_data(self) -> Tuple[str, List[SiteUserData], List[SiteUserData]]: + @staticmethod + def __get_data() -> Tuple[str, List[SiteUserData], List[SiteUserData]]: """ 获取最近一次统计的日期、最近一次统计的站点数据、上一次的站点数据 如果上一次某个站点数据缺失,则 fallback 到该站点之前最近有数据的日期 """ - # 获取所有原始数据 - raw_data_list: List[SiteUserData] = self.siteoper.get_userdata() - if not raw_data_list: + # 优化:只获取最近的站点数据,而不是所有历史数据 + latest_data: List[SiteUserData] = SiteOper().get_userdata_latest() + if not latest_data: return "", [], [] - # 每个日期、每个站点只保留最后一条数据 - data_list = list({f"{data.updated_day}_{data.name}": data for data in raw_data_list}.values()) + # 获取最新日期(用于显示) + latest_day = max(data.updated_day for data in latest_data) + + # 按上传量降序排序 + latest_data.sort(key=lambda x: x.upload or 0, reverse=True) - # 按日期倒序排序 - data_list.sort(key=lambda x: x.updated_day, reverse=True) - - # 按日期分组数据 - data_by_day = defaultdict(list) - for data in data_list: - data_by_day[data.updated_day].append(data) - - # 获取最近一次统计的日期 - latest_day = data_list[0].updated_day - - # 筛选最近一次统计的数据(可能为空) - latest_data = [data for data in data_list if data.updated_day == latest_day] - # 最近一次统计按上传量降序排序 - latest_data.sort(key=lambda x: x.upload, reverse=True) - - # 获取所有日期倒序排序后的列表 - sorted_dates = sorted(data_by_day.keys(), reverse=True) - - # 计算前一天的日期字符串(相对于最近一次日期) - previous_day_str = (datetime.strptime(latest_day, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d") - # 获取前一天的站点数据 - previous_day_sites = data_by_day.get(previous_day_str, []) - # 构建前一天站点到数据的映射 - previous_by_site = {data.name: data for data in previous_day_sites} - - # 准备查找早于前一天的日期列表,用于 fallback - fallback_dates = [d for d in sorted_dates if d < previous_day_str] - - # 按站点细化进行上一次数据的 fallback 处理 + # 为每个站点查找对应的前一天数据 previous_data = [] for current_site in latest_data: site_name = current_site.name - # 优先尝试获取前一天的同一站点数据 + current_day = current_site.updated_day + + # 计算该站点的前一天日期 + previous_day_str = (datetime.strptime(current_day, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d") + + # 获取前一天的数据 + previous_data_list = SiteOper().get_userdata_by_date(previous_day_str) + previous_by_site = {data.name: data for data in previous_data_list} site_prev = previous_by_site.get(site_name) - - # 如果前一天没有该站点的数据,则进行逐日回退查找 - if site_prev is None or site_prev.err_msg: - for d in fallback_dates: - # 在每个候选日期中查找对应站点数据 - candidate = next((x for x in data_by_day[d] if x.name == site_name), None) - if candidate: + + # 如果前一天没有该站点数据,尝试查找更早的数据 + if not site_prev or site_prev.err_msg: + # 最多回溯7天,避免查询过多历史数据 + for i in range(2, 8): + fallback_date = (datetime.strptime(current_day, "%Y-%m-%d") - timedelta(days=i)).strftime("%Y-%m-%d") + fallback_data_list = SiteOper().get_userdata_by_date(fallback_date) + fallback_by_site = {data.name: data for data in fallback_data_list} + candidate = fallback_by_site.get(site_name) + if candidate and not candidate.err_msg: site_prev = candidate break - # 如果找到了上一次的数据,加入结果列表 if site_prev: previous_data.append(site_prev) + # 清理垃圾 + gc.collect() + return latest_day, latest_data, previous_data @staticmethod @@ -851,152 +832,80 @@ class SiteStatistic(_PluginBase): dashboard='all' ) - # 站点数据明细 - site_trs = [ - { - 'component': 'tr', - 'props': { - 'class': 'text-sm' - }, - 'content': [ - { - 'component': 'td', - 'props': { - 'class': 'whitespace-nowrap break-keep text-high-emphasis' - }, - 'text': data.name - }, - { - 'component': 'td', - 'text': data.username - }, - { - 'component': 'td', - 'text': data.user_level - }, - { - 'component': 'td', - 'props': { - 'class': 'text-success' - }, - 'text': StringUtils.str_filesize(data.upload) - }, - { - 'component': 'td', - 'props': { - 'class': 'text-error' - }, - 'text': StringUtils.str_filesize(data.download) - }, - { - 'component': 'td', - 'text': data.ratio - }, - { - 'component': 'td', - 'text': format_bonus(data.bonus or 0) - }, - { - 'component': 'td', - 'text': data.seeding - }, - { - 'component': 'td', - 'text': StringUtils.str_filesize(data.seeding_size) - } - ] - } for data in stattistic_data + # 优化:使用更轻量级的方式构建站点数据明细,避免创建过多嵌套对象 + # 先准备表头 + table_headers = [ + {'text': '站点', 'class': 'text-start ps-4'}, + {'text': '用户名', 'class': 'text-start ps-4'}, + {'text': '用户等级', 'class': 'text-start ps-4'}, + {'text': '上传量', 'class': 'text-start ps-4'}, + {'text': '下载量', 'class': 'text-start ps-4'}, + {'text': '分享率', 'class': 'text-start ps-4'}, + {'text': '魔力值', 'class': 'text-start ps-4'}, + {'text': '做种数', 'class': 'text-start ps-4'}, + {'text': '做种体积', 'class': 'text-start ps-4'} ] + # 构建表头行 + header_row = { + 'component': 'thead', + 'content': [ + { + 'component': 'th', + 'props': {'class': header['class']}, + 'text': header['text'] + } for header in table_headers + ] + } + + # 构建数据行,避免在列表推导式中创建复杂嵌套 + table_rows = [] + for data in stattistic_data: + # 预先计算所有需要的值 + row_data = [ + {'text': data.name, 'class': 'whitespace-nowrap break-keep text-high-emphasis'}, + {'text': data.username, 'class': ''}, + {'text': data.user_level, 'class': ''}, + {'text': StringUtils.str_filesize(data.upload), 'class': 'text-success'}, + {'text': StringUtils.str_filesize(data.download), 'class': 'text-error'}, + {'text': data.ratio, 'class': ''}, + {'text': format_bonus(data.bonus or 0), 'class': ''}, + {'text': data.seeding, 'class': ''}, + {'text': StringUtils.str_filesize(data.seeding_size), 'class': ''} + ] + + # 构建单行配置 + row_content = [] + for cell_data in row_data: + cell = {'component': 'td', 'text': cell_data['text']} + if cell_data['class']: + cell['props'] = {'class': cell_data['class']} + row_content.append(cell) + + table_rows.append({ + 'component': 'tr', + 'props': {'class': 'text-sm'}, + 'content': row_content + }) + # 拼装页面 - return [ + page = [ { 'component': 'VRow', 'content': site_totals + [ # 各站点数据明细 { 'component': 'VCol', - 'props': { - 'cols': 12, - }, + 'props': {'cols': 12}, 'content': [ { 'component': 'VTable', - 'props': { - 'hover': True - }, + 'props': {'hover': True}, 'content': [ - { - 'component': 'thead', - 'content': [ - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '站点' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '用户名' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '用户等级' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '上传量' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '下载量' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '分享率' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '魔力值' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '做种数' - }, - { - 'component': 'th', - 'props': { - 'class': 'text-start ps-4' - }, - 'text': '做种体积' - } - ] - }, + header_row, { 'component': 'tbody', - 'content': site_trs + 'content': table_rows } ] } @@ -1006,16 +915,22 @@ class SiteStatistic(_PluginBase): } ] + # 清理垃圾 + gc.collect() + + return page + def stop_service(self): pass - def refresh_by_domain(self, domain: str, apikey: str) -> schemas.Response: + @staticmethod + def refresh_by_domain(domain: str, apikey: str) -> schemas.Response: """ 刷新一个站点数据,可由API调用 """ if apikey != settings.API_TOKEN: return schemas.Response(success=False, message="API密钥错误") - site_info = self.siteshelper.get_indexer(domain) + site_info = SitesHelper().get_indexer(domain) if site_info: site_data = SiteChain().refresh_userdata(site=site_info) if site_data: diff --git a/plugins.v2/speedlimiter/__init__.py b/plugins.v2/speedlimiter/__init__.py index 3f01708..23e52b2 100644 --- a/plugins.v2/speedlimiter/__init__.py +++ b/plugins.v2/speedlimiter/__init__.py @@ -32,8 +32,6 @@ class SpeedLimiter(_PluginBase): auth_level = 1 # 私有属性 - downloader_helper = None - mediaserver_helper = None _scheduler = None _enabled: bool = False _notify: bool = False @@ -54,8 +52,7 @@ class SpeedLimiter(_PluginBase): _exclude_path = "" def init_plugin(self, config: dict = None): - self.downloader_helper = DownloaderHelper() - self.mediaserver_helper = MediaServerHelper() + # 读取配置 if config: self._enabled = config.get("enabled") @@ -183,7 +180,7 @@ class SpeedLimiter(_PluginBase): 'model': 'downloader', 'label': '下载器', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] @@ -402,7 +399,7 @@ class SpeedLimiter(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - services = self.downloader_helper.get_services(name_filters=self._downloader) + services = DownloaderHelper().get_services(name_filters=self._downloader) if not services: logger.warning("获取下载器实例失败,请检查配置") return None @@ -442,7 +439,7 @@ class SpeedLimiter(_PluginBase): return # 当前播放的总比特率 total_bit_rate = 0 - media_servers = self.mediaserver_helper.get_services() + media_servers = MediaServerHelper().get_services() if not media_servers: return # 查询所有媒体服务器状态 diff --git a/plugins.v2/subscribeclear/__init__.py b/plugins.v2/subscribeclear/__init__.py index 082468c..5ef0685 100644 --- a/plugins.v2/subscribeclear/__init__.py +++ b/plugins.v2/subscribeclear/__init__.py @@ -33,10 +33,9 @@ class SubscribeClear(_PluginBase): # 私有属性 _titles = [] _episodes = [] - downloader_helper = None def init_plugin(self, config: dict = None): - self.downloader_helper = DownloaderHelper() + if config: self._titles = config.get("titles") or [] self._episodes = config.get("episodes") or [] @@ -48,11 +47,10 @@ class SubscribeClear(_PluginBase): def clear_history(self, titles: List[str], episodes: List[str]): logger.info(f"清除下载历史记录:{titles} {episodes}") - data = self.get_data() - down_oper = DownloadHistoryOper() - downloader_history ={} + data = self.get_download_data() + downloader_history = {} for d in data: - if d.title in titles or d.id in episodes: + if d.title in titles or d.id in episodes: tmp = downloader_history.get(d.downloader) if not tmp: tmp = [] @@ -70,7 +68,7 @@ class SubscribeClear(_PluginBase): history_torrents = {} for t in torrents: logger.info(f"种子信息: {t}") - history_torrents[t.hash]=t + history_torrents[t.hash] = t for h in history: # 判断当前历史记录的hash是否在未找到的hash列表中 if h.download_hash not in history_torrents.keys(): @@ -79,43 +77,39 @@ class SubscribeClear(_PluginBase): else: # 从下载器删除种子 self.delete_download_history(h, history_torrents[h.download_hash]) - - - - def delete_data(self, history: DownloadHistory): + @staticmethod + def delete_data(history: DownloadHistory): """ 从订阅记录中删除该信息 """ try: down_oper = DownloadHistoryOper() down_oper.delete_history(history.id) - logger.info(f"删除下载历史记录:{history.id} {history.title} {history.seasons} {history.episodes} {history.download_hash}") + logger.info( + f"删除下载历史记录:{history.id} {history.title} {history.seasons} {history.episodes} {history.download_hash}") return True except Exception as e: logger.error(f"删除下载历史记录失败:{str(e)}") return False - - - def delete_download_history(self,history: DownloadHistory, torrent: Any): + def delete_download_history(self, history: DownloadHistory, torrent: Any): downloader_name = history.downloader downloader_obj = self.__get_downloader(downloader_name) - logger.info(f"删除种子信息:{history.id} {history.title} {history.seasons} {history.episodes} {history.download_hash}") + logger.info( + f"删除种子信息:{history.id} {history.title} {history.seasons} {history.episodes} {history.download_hash}") hashs = [history.download_hash] # 处理辅种 torrents, error = downloader_obj.get_torrents() - if error : + if error: logger.error(f"获取辅种信息失败: {error}") else: for t in torrents: if t.name == torrent.name and t.size == torrent.size: hashs.append(t.hash) - downloader_obj.delete_torrents(delete_file=True,ids=hashs) + downloader_obj.delete_torrents(delete_file=True, ids=hashs) self.delete_data(history) - - def get_state(self) -> bool: return True @@ -141,17 +135,17 @@ class SubscribeClear(_PluginBase): def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: # 获取下载历史数据 - histories = self.get_data() - + histories = self.get_download_data() + # 构造标题和剧集列表 titles = [] episode_options = [] - + for history in histories: # 标题列表 if history.title not in titles: titles.append(history.title) - + # 剧集列表 episode_str = history.title if history.seasons: @@ -160,7 +154,6 @@ class SubscribeClear(_PluginBase): episode_str += f" {history.episodes}" episode_options.append({"title": episode_str, "value": history.id}) - # 将列表转换为选择框选项格式 title_options = [{"title": t, "value": t} for t in titles] @@ -189,9 +182,9 @@ class SubscribeClear(_PluginBase): } ] } - + episode_select = { - 'component': 'VRow', + 'component': 'VRow', 'content': [ { 'component': 'VCol', @@ -220,15 +213,16 @@ class SubscribeClear(_PluginBase): 'content': [ title_select, episode_select - ] + ] } ], { "titles": [], "episodes": [] } - def get_data(self) -> List[DownloadHistory]: - down_oper = DownloadHistoryOper() + @staticmethod + def get_download_data() -> List[DownloadHistory]: + down_oper = DownloadHistoryOper() downs = [] page = 1 while True: @@ -241,7 +235,7 @@ class SubscribeClear(_PluginBase): def get_page(self) -> List[dict]: items = [] - for down in self.get_data(): + for down in self.get_download_data(): items.append({ 'component': 'tr', 'content': [ @@ -255,7 +249,7 @@ class SubscribeClear(_PluginBase): }, { 'component': 'td', - 'text':down.seasons + " " + down.episodes + 'text': down.seasons + " " + down.episodes }, { 'component': 'td', @@ -300,7 +294,7 @@ class SubscribeClear(_PluginBase): }, 'text': '名称' }, - { + { 'component': 'th', 'props': { 'class': 'text-start ps-4' @@ -330,14 +324,6 @@ class SubscribeClear(_PluginBase): } ] - - @staticmethod - def get_api(self) -> List[Dict[str, Any]]: - """ - 注册API - """ - pass - def stop_service(self): """ 退出插件 @@ -349,7 +335,7 @@ class SubscribeClear(_PluginBase): """ 服务信息 """ - services = self.downloader_helper.get_services(type_filter="qbittorrent") + services = DownloaderHelper().get_services(type_filter="qbittorrent") if not services: logger.warning("获取下载器实例失败,请检查配置") return None diff --git a/plugins.v2/synccookiecloud/__init__.py b/plugins.v2/synccookiecloud/__init__.py index 81205d6..7072b41 100644 --- a/plugins.v2/synccookiecloud/__init__.py +++ b/plugins.v2/synccookiecloud/__init__.py @@ -40,11 +40,9 @@ class SyncCookieCloud(_PluginBase): _enabled: bool = False _onlyonce: bool = False _cron: str = "" - siteoper = None _scheduler: Optional[BackgroundScheduler] = None def init_plugin(self, config: dict = None): - self.siteoper = SiteOper() # 停止现有任务 self.stop_service() @@ -92,7 +90,7 @@ class SyncCookieCloud(_PluginBase): 同步站点cookie到cookiecloud """ # 获取所有站点 - sites = self.siteoper.list_order_by_pri() + sites = SiteOper().list_order_by_pri() if not sites: return diff --git a/plugins.v2/tobypasstrackers/__init__.py b/plugins.v2/tobypasstrackers/__init__.py index 16d8bec..c87f64f 100644 --- a/plugins.v2/tobypasstrackers/__init__.py +++ b/plugins.v2/tobypasstrackers/__init__.py @@ -1,26 +1,24 @@ -from typing import Any, List, Dict, Tuple, Optional -from datetime import datetime, timedelta -import ipaddress -import socket -import base64 -import json import asyncio +import base64 +import ipaddress +import json +import socket +from datetime import datetime, timedelta +from typing import Any, List, Dict, Tuple, Optional -from apscheduler.schedulers.background import BackgroundScheduler -from fastapi import Response -from apscheduler.triggers.cron import CronTrigger import pytz +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.cron import CronTrigger +from fastapi import Response -from app.chain.site import SiteChain from app.core.config import settings -from app.core.event import EventManager, eventmanager +from app.core.event import eventmanager from app.db.site_oper import SiteOper -from app.helper.sites import SitesHelper from app.log import logger from app.plugins import _PluginBase -from app.utils.http import RequestUtils -from app.schemas.types import EventType, NotificationType from app.plugins.tobypasstrackers.dns_helper import DnsHelper +from app.schemas.types import EventType, NotificationType +from app.utils.http import RequestUtils class ToBypassTrackers(_PluginBase): @@ -43,13 +41,6 @@ class ToBypassTrackers(_PluginBase): # 可使用的用户级别 auth_level = 2 - # 私有属性 - sites: SitesHelper = None - site_chain: SiteChain = None - siteoper: SiteOper = None - - # 事件管理器 - event: EventManager = None # 定时器 _scheduler: Optional[BackgroundScheduler] = None # 开关 @@ -67,13 +58,12 @@ class ToBypassTrackers(_PluginBase): _dns_input: str = "" ipv6_txt: str = "" ipv4_txt: str = "" + trackers: Dict[str, List[str]] = {} def init_plugin(self, config: dict = None): - self.sites = SitesHelper() - # self.event = EventManager() - self.site_chain = SiteChain() + self.stop_service() - self.siteoper = SiteOper() + self.trackers = {} self.ipv6_txt = self.get_data("ipv6_txt") if self.get_data("ipv6_txt") else "" self.ipv4_txt = self.get_data("ipv4_txt") if self.get_data("ipv4_txt") else "" @@ -98,7 +88,7 @@ class ToBypassTrackers(_PluginBase): self._china_ipv6_route = config.get("china_ipv6_route") self._china_ip_route = config.get("china_ip_route") # 过滤掉已删除的站点 - all_sites = [site.id for site in self.siteoper.list_order_by_pri()] + all_sites = [site.id for site in SiteOper().list_order_by_pri()] self._bypassed_sites = [site_id for site_id in all_sites if site_id in self._bypassed_sites] self.__update_config() if self._enabled or self._onlyonce: @@ -160,8 +150,7 @@ class ToBypassTrackers(_PluginBase): def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: site_options = ([{"title": site.name, "value": site.id} - for site in self.siteoper.list_order_by_pri()] - ) + for site in SiteOper().list_order_by_pri()]) return [ { 'component': 'VForm', @@ -629,7 +618,7 @@ class ToBypassTrackers(_PluginBase): chnroute_lists = res.text[:-1].split('\n') for ipr in chnroute_lists: ip_list.append(ipr) - do_sites = {site.domain: site.name for site in self.siteoper.list_order_by_pri() if + do_sites = {site.domain: site.name for site in SiteOper().list_order_by_pri() if site.id in self._bypassed_sites} domain_name_map = {} for site in do_sites: diff --git a/plugins.v2/tobypasstrackers/dns_helper.py b/plugins.v2/tobypasstrackers/dns_helper.py index d25a3db..0a4916a 100644 --- a/plugins.v2/tobypasstrackers/dns_helper.py +++ b/plugins.v2/tobypasstrackers/dns_helper.py @@ -1,7 +1,6 @@ import re from typing import Optional, List, Callable -import aioquic import dns.asyncresolver import dns.resolver @@ -74,7 +73,6 @@ class DnsHelper: 使用 UDP 异步方式解析域名 :param domain: 域名 - :param port: DNS服务器端口(默认53) :param dns_type: 记录类型,如 A、AAAA :return: IP地址列表 或 None """ diff --git a/plugins.v2/torrentremover/__init__.py b/plugins.v2/torrentremover/__init__.py index ddae9d9..6865342 100644 --- a/plugins.v2/torrentremover/__init__.py +++ b/plugins.v2/torrentremover/__init__.py @@ -39,7 +39,6 @@ class TorrentRemover(_PluginBase): auth_level = 2 # 私有属性 - downloader_helper = None _event = threading.Event() _scheduler = None _enabled = False @@ -63,7 +62,7 @@ class TorrentRemover(_PluginBase): _torrentcategorys = None def init_plugin(self, config: dict = None): - self.downloader_helper = DownloaderHelper() + if config: self._enabled = config.get("enabled") self._onlyonce = config.get("onlyonce") @@ -257,7 +256,7 @@ class TorrentRemover(_PluginBase): 'model': 'downloaders', 'label': '下载器', 'items': [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] } } ] @@ -593,7 +592,7 @@ class TorrentRemover(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - services = self.downloader_helper.get_services(name_filters=self._downloaders) + services = DownloaderHelper().get_services(name_filters=self._downloaders) if not services: logger.warning("获取下载器实例失败,请检查配置") return None diff --git a/plugins.v2/torrenttransfer/__init__.py b/plugins.v2/torrenttransfer/__init__.py index 77db83b..430b6bf 100644 --- a/plugins.v2/torrenttransfer/__init__.py +++ b/plugins.v2/torrenttransfer/__init__.py @@ -12,7 +12,6 @@ from qbittorrentapi import TorrentDictionary from app.core.config import settings from app.helper.downloader import DownloaderHelper -from app.helper.torrent import TorrentHelper from app.log import logger from app.modules.qbittorrent import Qbittorrent from app.modules.transmission import Transmission @@ -43,8 +42,7 @@ class TorrentTransfer(_PluginBase): # 私有属性 _scheduler = None - torrent_helper = None - downloader_helper = None + # 开关 _enabled = False _cron = None @@ -76,8 +74,7 @@ class TorrentTransfer(_PluginBase): _torrent_tags = [] def init_plugin(self, config: dict = None): - self.torrent_helper = TorrentHelper() - self.downloader_helper = DownloaderHelper() + # 读取配置 if config: self._enabled = config.get("enabled") @@ -136,7 +133,8 @@ class TorrentTransfer(_PluginBase): self._scheduler.print_jobs() self._scheduler.start() - def service_info(self, name: str) -> Optional[ServiceInfo]: + @staticmethod + def service_info(name: str) -> Optional[ServiceInfo]: """ 服务信息 """ @@ -144,7 +142,7 @@ class TorrentTransfer(_PluginBase): logger.warning("尚未配置下载器,请检查配置") return None - service = self.downloader_helper.get_service(name) + service = DownloaderHelper().get_service(name) if not service or not service.instance: logger.warning(f"获取下载器 {name} 实例失败,请检查配置") return None @@ -197,7 +195,7 @@ class TorrentTransfer(_PluginBase): 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 """ downloader_options = [{"title": config.name, "value": config.name} - for config in self.downloader_helper.get_configs().values()] + for config in DownloaderHelper().get_configs().values()] return [ { 'component': 'VForm', @@ -622,7 +620,8 @@ class TorrentTransfer(_PluginBase): return downloader = service.instance from_service = self.service_info(self._fromdownloader) - if self.downloader_helper.is_downloader("qbittorrent", service=service): + downloader_helper = DownloaderHelper() + if downloader_helper.is_downloader("qbittorrent", service=service): # 生成随机Tag tag = StringUtils.generate_random_str(10) if self._remainoldtag: @@ -651,7 +650,7 @@ class TorrentTransfer(_PluginBase): logger.error(f"{downloader} 下载任务添加成功,但获取任务信息失败!") return None return torrent_hash - elif self.downloader_helper.is_downloader("transmission", service=service): + elif downloader_helper.is_downloader("transmission", service=service): # 添加任务 if self._remainoldtag: # 获取种子标签 @@ -780,6 +779,7 @@ class TorrentTransfer(_PluginBase): # 删除重复数 del_dup = 0 + downloader_helper = DownloaderHelper() for torrent_item in trans_torrents: # 检查种子文件是否存在 torrent_file = Path(self._fromtorrentpath) / f"{torrent_item.get('hash')}.torrent" @@ -795,7 +795,7 @@ class TorrentTransfer(_PluginBase): # 删除重复的源种子,不能删除文件! if self._deleteduplicate: logger.info(f"删除重复的源下载器任务(不含文件):{torrent_item.get('hash')} ...") - to_downloader.delete_torrents(delete_file=False, ids=[torrent_item.get('hash')]) + from_downloader.delete_torrents(delete_file=False, ids=[torrent_item.get('hash')]) del_dup += 1 else: logger.info(f"{torrent_item.get('hash')} 已在目的下载器中,跳过 ...") @@ -814,7 +814,7 @@ class TorrentTransfer(_PluginBase): continue # 如果源下载器是QB检查是否有Tracker,没有的话额外获取 - if self.downloader_helper.is_downloader("qbittorrent", service=from_service): + if downloader_helper.is_downloader("qbittorrent", service=from_service): # 读取种子内容、解析种子文件 content = torrent_file.read_bytes() if not content: @@ -878,7 +878,7 @@ class TorrentTransfer(_PluginBase): logger.info(f"成功添加转移做种任务,种子文件:{torrent_file}") # TR会自动校验,QB需要手动校验 - if self.downloader_helper.is_downloader("qbittorrent", service=to_service): + if downloader_helper.is_downloader("qbittorrent", service=to_service): if self._skipverify: if self._autostart: logger.info(f"{download_id} 跳过校验,开启自动开始,注意观察种子的完整性") diff --git a/plugins/autosubv2/README.md b/plugins/autosubv2/README.md index 603d212..783207a 100644 --- a/plugins/autosubv2/README.md +++ b/plugins/autosubv2/README.md @@ -13,76 +13,117 @@ - 支持批量翻译以提高效率 - 支持使用滑动窗口配置上下文提高翻译连贯性 - 支持多种字幕提取语言偏好设置 +- 支持监听媒体入库事件自动执行字幕生成 +- 支持手动触发字幕生成任务 +- 支持任务队列机制,确保并发安全 +- 支持任务状态列表展示(等待中 / 进行中 / 已完成 / 失败) ## 配置说明 ### 基础配置 -| 配置项 | 说明 | 默认值 | -|--------|------|--------| -| 立即运行一次 | 保存配置后是否立即执行一次任务 | 否 | -| 本地字幕提取策略 | 设置字幕提取的优先级策略 | 优先原音字幕 | -| 翻译为中文 | 是否在需要时使用大模型将字幕翻译成中文 | 是 | -| 发送通知 | 是否发送任务执行通知 | 否 | +| 配置项 | 说明 | 默认值 | +|----------|------------------------|--------| +| 启用插件 | 是否启用插件 | 否 | +| 清除历史记录 | 清除已完成的任务记录(完成、跳过或失败) | 否 | +| 媒体入库自动执行 | 监听到媒体入库事件后自动执行字幕生成 | 是 | +| 手动执行一次 | 保存配置后立即执行一次任务 | 否 | +| 发送通知 | 是否发送任务执行通知 | 否 | +| 文件大小(MB) | 最小处理的视频文件大小,小于该值的文件不处理 | 10 | +| 字幕源语言偏好 | 设置字幕提取的优先级策略 | 优先原音字幕 | +| 翻译为中文 | 是否使用大模型将字幕翻译成中文 | 是 | -### ASR配置 +### ASR配置(语音识别) -| 配置项 | 说明 | 默认值 | -|--------|------|--------| -| 允许从音轨提取字幕 | 是否允许从视频音轨中提取字幕 | 是 | -| ASR引擎 | 语音识别引擎 | faster-whisper | -| 模型 | 使用的模型大小 | base | -| 使用代理下载模型 | 是否使用代理下载模型 | 是 | +| 配置项 | 说明 | 默认值 | +|---------------------|------------------|------| +| 允许从音轨提取字幕 | 是否允许从视频音轨中提取字幕 | 是 | +| faster-whisper 模型选择 | 使用的 Whisper 模型大小 | base | +| 使用代理下载模型 | 是否使用代理下载模型 | 是 | -### 翻译配置 +### 翻译接口配置 -| 配置项 | 说明 | 默认值 | -|--------|------|--------| -| 启用批量翻译 | 是否启用批量翻译以提高效率 | 是 | -| 每批翻译行数 | 每批处理的字幕行数 | 20 | -| 上下文窗口大小 | 翻译时考虑的上下文行数 | 5 | -| llm请求重试次数 | 翻译失败时的重试次数 | 3 | +> 可选使用 ChatGPT 插件配置 或 自定义 OpenAI 接口参数 -### 其他配置 +| 配置项 | 说明 | 默认值 | +|------------------|--------------------------------------|-------------------------| +| 复用ChatGPT插件配置 | 是否直接使用系统中已配置的 ChatGPT 插件参数 | 否 | +| 使用代理服务器 | 是否通过 MP 配置的代理访问 OpenAI 接口 | 否 | +| 兼容模式 | 是否启用兼容模式(绕过 `/v1` 路径拼接) | 否 | +| OpenAI API URL | 自定义 OpenAI 接口地址 | https://api.openai.com | +| API 密钥 | OpenAI 的 API Key | 无 | +| 自定义模型 | 使用的 LLM 模型名称(如 gpt-3.5-turbo) | gpt-3.5-turbo | -| 配置项 | 说明 | 默认值 | -|--------|------|--------| -| 媒体路径 | 要处理的媒体文件或文件夹绝对路径,每行一个 | 空 | -| 文件大小(MB) | 最小处理文件大小 | 10 | +### 翻译参数配置 +| 配置项 | 说明 | 默认值 | +|---------------|------------------------------------|-----| +| 启用批量翻译 | 是否启用批量翻译以提高效率 | 是 | +| 每批翻译行数 | 每批处理的字幕行数 | 10 | +| 上下文窗口大小 | 翻译时考虑的上下文行数 | 5 | +| LLM请求重试次数 | 翻译失败时的重试次数 | 3 | +| 翻译英文时合并整句 | 对英文字幕先合并单词再翻译,提升翻译质量 | 否 | + +### 手动运行配置 + +| 配置项 | 说明 | 默认值 | +|------|-----------------------|-----| +| 媒体路径 | 要处理的媒体文件或文件夹绝对路径,每行一个 | 空 | ## 字幕提取策略说明 + 字幕提取优先级:外挂字幕 > 内嵌字幕 > 音轨识别 字幕提取策略的选择主要取决于视频源语言和大模型的翻译能力。对于包含多语言字幕的非英语视频,建议根据以下原则选择策略: 1. 仅英文字幕 - - 仅使用英文字幕作为翻译源 - - 当视频无英文字幕时,使用ASR提取 - - 适用于大模型仅支持中英互译的场景 + - 仅使用英文字幕作为翻译源 + - 当视频无英文字幕时,使用ASR提取 + - 适用于大模型仅支持中英互译的场景 2. 优先英文字幕 - - 优先使用英文字幕作为翻译源 - - 无英文字幕时,使用其他语言字幕 - - 当所有字幕都不存在时,使用ASR提取 - - 适用于大模型在英译中任务上表现更好的场景 + - 优先使用英文字幕作为翻译源 + - 无英文字幕时,使用其他语言字幕 + - 当所有字幕都不存在时,使用ASR提取 + - 适用于大模型在英译中任务上表现更好的场景 3. 优先原音字幕 - - 优先使用视频原始语言的字幕 - - 无原音字幕时,使用英文字幕 - - 当所有字幕都不存在时,使用ASR提取 - - 适用于大模型支持多语言翻译且翻译质量较好的场景 + - 优先使用视频原始语言的字幕 + - 无原音字幕时,使用英文字幕 + - 当所有字幕都不存在时,使用ASR提取 + - 适用于大模型支持多语言翻译且翻译质量较好的场景 + +## 翻译方式说明 + +插件支持两种方式调用大模型进行翻译: + +1. **复用 ChatGPT 插件配置** + - 开启“复用ChatGPT插件配置”后,自动使用系统中维护的 ChatGPT 插件参数 + - 包括 API Key、API URL、是否使用代理等 + - 适合已有 ChatGPT 插件的用户快速部署 + +2. **自定义 OpenAI 接口参数** + - 关闭“复用ChatGPT插件配置”后,可独立配置: + - API 地址(支持反代) + - API Key + - 使用的模型 + - 是否使用代理 + - 是否启用兼容模式(避免 `/v1` 路径冲突) + + +--- ## 注意事项 -1. 翻译功能依赖OpenAI插件配置,使用前请确保已正确配置 -2. 首次使用音轨识别功能时,会自动从HuggingFace下载模型。开启"使用代理下载模型"选项会使用MP配置的代理。 -3. 媒体路径支持单个文件或文件夹的绝对路径。选择文件夹时会递归处理其中的所有视频文件,外挂字幕将从媒体文件同级目录中查找 -4. 批量翻译通过一次处理多行字幕来减少API调用次数,提高效率。如果翻译结果与原文行数不匹配,系统会自动降级为逐行翻译 -5. 上下文窗口大小和批量翻译行数需要根据大模型的推理能力来调整。当模型能力不足时,过大的批量或上下文窗口可能会影响翻译质量 +1. 翻译功能依赖大模型配置,使用前请确保已正确配置 OpenAI Key 或 ChatGPT 插件。 +2. 首次使用音轨识别功能时,会自动从 HuggingFace 下载模型。开启"使用代理下载模型"选项会使用 MP 配置的代理。 +3. 媒体路径支持单个文件或文件夹的绝对路径。选择文件夹时会递归处理其中的所有视频文件,外挂字幕将从媒体文件同级目录中查找。 +4. 批量翻译通过一次处理多行字幕来减少 API 调用次数,提高效率。如果翻译结果与原文行数不匹配,系统会自动降级为逐行翻译。 +5. 上下文窗口大小和批量翻译行数需要根据大模型的推理能力来调整。当模型能力不足时,过大的批量或上下文窗口可能会影响翻译质量。 6. 翻译后的中文字幕会打上“机翻”标签。 +7. 插件运行时会启动一个后台线程用于消费任务队列,插件关闭时会清空队列并终止当前任务。 ## todo -- 监听媒体入库事件自动调用字幕生成 -- 任务完成后调用媒体库刷新 -- 历史任务管理与展示 \ No newline at end of file + +- 工作流/API接口 +- 任务完成后调用媒体库刷新 \ No newline at end of file diff --git a/plugins/autosubv2/__init__.py b/plugins/autosubv2/__init__.py index d636195..464170c 100644 --- a/plugins/autosubv2/__init__.py +++ b/plugins/autosubv2/__init__.py @@ -1,7 +1,5 @@ import copy import os -import re -import subprocess import tempfile import time import traceback @@ -11,27 +9,53 @@ from typing import Tuple, Dict, Any, List from threading import Event import iso639 import psutil -import pytz import srt -from apscheduler.schedulers.background import BackgroundScheduler from lxml import etree - +from dataclasses import dataclass +from enum import Enum +import queue +import threading +from uuid import uuid4 from app.core.config import settings +from app.core.context import MediaInfo +from app.core.event import eventmanager, Event as MPEvent +from app.schemas import TransferInfo +from app.schemas.types import NotificationType, EventType from app.log import logger from app.plugins import _PluginBase from app.utils.system import SystemUtils from plugins.autosubv2.ffmpeg import Ffmpeg -from plugins.autosubv2.translate.openai import OpenAi -from app.schemas.types import NotificationType +from plugins.autosubv2.translate.openai_translate import OpenAi -# todo -# 监听入库事件,自动调用翻译 - class UserInterruptException(Exception): """用户中断当前任务的异常""" pass + +class TaskSource(Enum): + MANUAL = "manual" + EVENT = "event" + + +class TaskStatus(Enum): + PENDING = "pending" + IN_PROGRESS = "in_progress" + COMPLETED = "completed" + IGNORED = "ignored" + FAILED = "failed" + + +@dataclass +class TaskItem: + task_id: str + video_file: str + source: TaskSource + add_time: datetime + status: TaskStatus = TaskStatus.PENDING + complete_time: datetime = None + + class AutoSubv2(_PluginBase): # 插件名称 plugin_name = "AI字幕自动生成(v2)" @@ -42,7 +66,7 @@ class AutoSubv2(_PluginBase): # 主题色 plugin_color = "#2C4F7E" # 插件版本 - plugin_version = "1.2" + plugin_version = "2.3" # 插件作者 plugin_author = "TimoYoung" # 作者主页 @@ -54,202 +78,268 @@ class AutoSubv2(_PluginBase): # 可使用的用户级别 auth_level = 2 - # 退出事件 - _event = Event() # 私有属性 + _tasks: Dict[str, TaskItem] = None + _task_queue = None + _consumer_thread = None + _current_processing_task = None _running = False - # 语句结束符 - _end_token = ['.', '!', '?', '。', '!', '?', '。"', '!"', '?"', '."', '!"', '?"'] - _noisy_token = [('(', ')'), ('[', ']'), ('{', '}'), ('【', '】'), ('♪', '♪'), ('♫', '♫'), ('♪♪', '♪♪')] - - def __init__(self): - super().__init__() - # ChatGPT - self.openai = None - self._openai_key = None - self._openai_url = None - self._openai_proxy = None - self._openai_model = None - self._scheduler = None - self.process_count = None - self.fail_count = None - self.success_count = None - self.skip_count = None - self.faster_whisper_model_path = None - self.faster_whisper_model = None - self.asr_engine = None - self.send_notify = None - self.additional_args = None - self.enable_asr = None - self.translate_zh = None - self.whisper_model = None - self.whisper_main = None - self.file_size = None - self.enable_batch = None - self.batch_size = None - self.context_window = None - self.max_retries = None - self._proxy = None - self._translate_preference = None + _event = Event() + _enabled = None + _clear_history = None + _listen_transfer_event = None + _send_notify = None + _translate_preference = None + _run_now = None + _path_list = None + _file_size = None + _translate_zh = None + _openai = None + _enable_batch = None + _batch_size = None + _context_window = None + _max_retries = None + _enable_merge = None + _enable_asr = None + _huggingface_proxy = None + _faster_whisper_model_path = None + _faster_whisper_model = None def init_plugin(self, config=None): - - self.process_count = 0 - self.skip_count = 0 - self.fail_count = 0 - self.success_count = 0 - # 如果没有配置信息, 则不处理 if not config: return - - self.translate_zh = config.get('translate_zh', False) - if self.translate_zh: - chatgpt = self.get_config("ChatGPT") - if not chatgpt: - logger.error(f"翻译依赖于ChatGPT,请先维护ChatGPT插件") - return - self._openai_key = chatgpt and chatgpt.get("openai_key") - self._openai_url = chatgpt and chatgpt.get("openai_url") - self._openai_proxy = chatgpt and chatgpt.get("proxy") - self._openai_model = chatgpt and chatgpt.get("model") - if not self._openai_key: - logger.error(f"翻译依赖于ChatGPT,请先维护openai_key") - return - self.openai = OpenAi(api_key=self._openai_key, api_url=self._openai_url, - proxy=settings.PROXY if self._openai_proxy else None, - model=self._openai_model) - - path_list = list(set(config.get('path_list').split('\n'))) - self.file_size = int(config.get('file_size')) if config.get('file_size') else 10 - self.whisper_main = config.get('whisper_main') - self.whisper_model = config.get('whisper_model') - self.enable_asr = config.get('enable_asr', True) - self.enable_batch = config.get('enable_batch', True) - self.batch_size = int(config.get('batch_size')) if config.get('batch_size') else 20 - self.context_window = int(config.get('context_window')) if config.get('context_window') else 5 - self.max_retries = int(config.get('max_retries')) if config.get('max_retries') else 3 - self.additional_args = config.get('additional_args', '-t 4 -p 1') - self.send_notify = config.get('send_notify', False) - self.asr_engine = config.get('asr_engine', 'faster_whisper') - self.faster_whisper_model = config.get('faster_whisper_model', 'base') - self.faster_whisper_model_path = config.get('faster_whisper_model_path', - self.get_data_path() / "faster-whisper-models") - self._proxy = config.get('proxy', False) - self._translate_preference = config.get('translate_preference', 'origin_first') - run_now = config.get('run_now') - self.stop_service() - - if not run_now: - return - - config['run_now'] = False - self.update_config(config) - # 如果没有配置信息, 则不处理 - if not path_list or not self.file_size: - logger.warn(f"配置信息不完整,不进行处理") - return - - # asr 配置检查 - if self.enable_asr and not self.__check_asr(): - return - - if self._running: - logger.warn(f"上一次任务还未完成,不进行处理") - return - - if run_now: - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - logger.info("AI字幕自动生成任务,立即运行一次") - self._scheduler.add_job(func=self._do_autosub, kwargs={'path_list': path_list}, trigger='date', - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), - name="AI字幕自动生成") - - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() - - def _do_autosub(self, path_list: str): - # 依次处理每个目录 - try: - self._running = True - self.success_count = self.skip_count = self.fail_count = self.process_count = 0 - for path in path_list: - if self._event.is_set(): - logger.info(f"字幕生成服务停止") + self._tasks = self.load_tasks() + self._enabled = config.get('enabled', False) + self._clear_history = config.get('clear_history', False) + self._listen_transfer_event = config.get('listen_transfer_event', True) + self._run_now = config.get('run_now') + if self._run_now: + self._path_list = list(set(config.get('path_list').split('\n'))) + self._send_notify = config.get('send_notify', False) + self._file_size = int(config.get('file_size')) if config.get('file_size') else 10 + # 字幕生成设置 + self._translate_preference = config.get('translate_preference', 'english_first') + self._enable_asr = config.get('enable_asr', True) + if self._enable_asr: + self._faster_whisper_model = config.get('faster_whisper_model', 'base') + self._faster_whisper_model_path = config.get('faster_whisper_model_path', + self.get_data_path() / "faster-whisper-models") + self._huggingface_proxy = config.get('proxy', True) + self._translate_zh = config.get('translate_zh', False) + if self._translate_zh: + use_chatgpt = config.get('use_chatgpt', True) + if use_chatgpt: + chatgpt = self.get_config("ChatGPT") + if not chatgpt: + logger.error(f"翻译依赖于ChatGPT,请先维护ChatGPT插件") return - logger.info(f"开始处理目录/文件:{path} ...") - # 如果目录不存在, 则不处理 - if not os.path.exists(path): - logger.warn(f"目录/文件不存在,不进行处理") - continue + openai_key_str = chatgpt and chatgpt.get("openai_key") + openai_url = chatgpt and chatgpt.get("openai_url") + openai_proxy = chatgpt and chatgpt.get("proxy") + openai_model = chatgpt and chatgpt.get("model") + compatible = chatgpt and chatgpt.get("compatible") + if not openai_key_str: + logger.error(f"请先在ChatGPT插件中维护openai_key") + return + openai_key = [key.strip() for key in openai_key_str.split(',') if key.strip()][0] + else: + openai_key = config.get('openai_key') + if not openai_key: + logger.error(f"翻译依赖于OpenAI,请先维护openai_key") + return + openai_url = config.get('openai_url', "https://api.openai.com") + openai_proxy = config.get('openai_proxy', False) + openai_model = config.get('openai_model', "gpt-3.5-turbo") + compatible = config.get('compatible', False) + self._openai = OpenAi(api_key=openai_key, api_url=openai_url, + proxy=settings.PROXY if openai_proxy else None, + model=openai_model, compatible=bool(compatible)) + self._enable_batch = config.get('enable_batch', True) + self._batch_size = int(config.get('batch_size')) if config.get('batch_size') else 10 + self._context_window = int(config.get('context_window')) if config.get('context_window') else 5 + self._max_retries = int(config.get('max_retries')) if config.get('max_retries') else 3 + self._enable_merge = config.get('enable_merge', False) - # 如果目录不是绝对路径, 则不处理 - if not os.path.isabs(path): - logger.warn(f"目录/文件不是绝对路径,不进行处理") - continue + if self._clear_history: + config['clear_history'] = False + self.update_config(config) + self.clear_tasks() + if self._enabled: + logger.info("AI生成字幕服务已启动") + # asr 配置检查 + if self._enable_asr and not self.__check_asr(): + return - if os.path.isdir(path): - # 处理目录 - self.__process_folder_subtitle(path) - elif os.path.splitext(path)[-1].lower() in settings.RMT_MEDIAEXT: - # 处理单个视频文件 - self.__process_file_subtitle(path) - # 如果目录不是文件夹, 则不处理 - else: - logger.warn(f"目录不是文件夹或视频文件,不进行处理") - continue + if not self._running: + self._task_queue = queue.Queue() + self._consumer_thread = threading.Thread(target=self._consume_tasks, daemon=True) + self._consumer_thread.start() + logger.info("任务队列和消费者线程已启动") + self._running = True - except Exception as e: - logger.error(f"处理异常: {e}") - logger.error(traceback.format_exc()) - finally: - logger.info(f"处理完成: " - f"成功{self.success_count} / 跳过{self.skip_count} / 失败{self.fail_count} / 共{self.process_count}") - self._running = False + if self._run_now: + config['run_now'] = False + self.update_config(config) + logger.info("立即运行一次") + self._run_at_once(path_list=self._path_list) + else: + self.stop_service() + + def load_tasks(self) -> Dict[str, TaskItem]: + raw_tasks = self.get_data("tasks") or {} + tasks = {} + for task_id, task_dict in raw_tasks.items(): + try: + task = TaskItem( + task_id=task_dict["task_id"], + video_file=task_dict["video_file"], + source=TaskSource(task_dict["source"]), + add_time=datetime.fromisoformat(task_dict["add_time"]), + status=TaskStatus(task_dict["status"]), + complete_time=datetime.fromisoformat(task_dict["complete_time"]) + if task_dict.get("complete_time") else None, + ) + tasks[task_id] = task + except Exception as e: + logger.error(f"恢复任务失败:{e}") + return tasks + + @staticmethod + def _serialize_task(task: TaskItem) -> dict: + return { + "task_id": task.task_id, + "video_file": task.video_file, + "source": task.source.value, + "add_time": task.add_time.isoformat() if task.add_time else None, + "status": task.status.value, + "complete_time": task.complete_time.isoformat() if task.complete_time else None, + } + + def save_tasks(self): + tasks_dict = {task_id: self._serialize_task(task) for task_id, task in self._tasks.items()} + self.save_data("tasks", tasks_dict) + + def add_task(self, video_file: str, source: TaskSource): + """ + 添加新任务到队列和任务列表中,若任务已存在则跳过。 + :param video_file: 视频文件路径 + :param source: 任务来源(手动/事件) + """ + task = TaskItem( + task_id=str(uuid4()), + video_file=video_file, + source=source, + add_time=datetime.now() + ) + + if self.__is_duplicate_task(task.video_file): + logger.info(f"任务已存在,跳过添加:{video_file}") + return False + + self._task_queue.put(task) + self._tasks[task.task_id] = task + self.save_tasks() + logger.info(f"加入任务队列: {video_file}") + return True + + def clear_tasks(self): + self._tasks = {task_id: task for task_id, task in self._tasks.items() if task.status in [ + TaskStatus.PENDING, TaskStatus.IN_PROGRESS + ]} + self.save_tasks() + logger.info("插件历史任务已清除") + + def __is_duplicate_task(self, video_file: str) -> bool: + with self._task_queue.mutex: + for task in self._task_queue.queue: + if task.video_file == video_file: + return True + # 还要检查当前正在处理的任务(即可能不在队列中,但正在被消费) + if self._consumer_thread and self._current_processing_task and self._current_processing_task.video_file == video_file: + return True + return False + + def _consume_tasks(self): + while not self._event.is_set(): + try: + task = self._task_queue.get(timeout=1) + if task is None: + continue + self._current_processing_task = task + logger.info(f"开始处理任务 {task.task_id}: {task.video_file}") + task.status = TaskStatus.IN_PROGRESS + self._tasks[task.task_id] = task + self.save_tasks() + task.status = self.__process_autosub(task.video_file) + task.complete_time = datetime.now() + self._tasks[task.task_id] = task + self.save_tasks() + self._task_queue.task_done() + self._current_processing_task = None + except queue.Empty: + continue + except Exception as e: + logger.error(f"消费任务时发生异常: {e}") + logger.error(traceback.format_exc()) + self._current_processing_task = None + logger.info("消费线程已退出") + + # 监听媒体入库事件,每个事件触发一次自动字幕任务 + @eventmanager.register(EventType.TransferComplete) + def on_transfer_complete(self, event: MPEvent): + """监听媒体入库事件""" + if not self._listen_transfer_event: + return + item = event.event_data + item_media: MediaInfo = item.get("mediainfo") + logger.info(f"监听到媒体入库事件:{item_media.title}") + origin_lang = item_media.original_language + prefer_langs = ['zh', 'chi', 'zh-CN', 'chs', 'zhs', 'zh-Hans', 'zhong', 'simp', 'cn'] + if origin_lang in prefer_langs: + logger.info(f"媒体原始语言为中文,跳过处理") + return + + item_transfer: TransferInfo = item.get("transferinfo") + item_file_list = item_transfer.file_list_new + + for file_path in item_file_list: + if os.path.splitext(file_path)[-1].lower() in settings.RMT_MEDIAEXT: + self.add_task(file_path, TaskSource.EVENT) + + def _run_at_once(self, path_list: List[str]): + # 依次处理每个目录 + for path in path_list: + if not os.path.exists(path) or not os.path.isabs(path): + logger.warn(f"目录/文件无效,不进行处理:{path}") + continue + if os.path.isdir(path): + for video_file in self.__get_library_files(path): + self.add_task(video_file, TaskSource.MANUAL) + elif os.path.splitext(path)[-1].lower() in settings.RMT_MEDIAEXT: + self.add_task(path, TaskSource.MANUAL) def __check_asr(self): - if self.asr_engine == 'whisper.cpp': - if not self.whisper_main or not self.whisper_model: - logger.warn(f"配置信息不完整,不进行处理") - return False - if not os.path.exists(self.whisper_main): - logger.warn(f"whisper.cpp主程序不存在,不进行处理") - return False - if not os.path.exists(self.whisper_model): - logger.warn(f"whisper.cpp模型文件不存在,不进行处理") - return False - # 校验扩展参数是否包含异常字符 - if self.additional_args and re.search(r'[;|&]', self.additional_args): - logger.warn(f"扩展参数包含异常字符,不进行处理") - return False - elif self.asr_engine == 'faster-whisper': - if not self.faster_whisper_model_path or not self.faster_whisper_model: - logger.warn(f"配置信息不完整,不进行处理") - return False - if not os.path.exists(self.faster_whisper_model_path): - logger.info(f"创建faster-whisper模型目录:{self.faster_whisper_model_path}") - os.mkdir(self.faster_whisper_model_path) - try: - from faster_whisper import WhisperModel, download_model - except ImportError: - logger.warn(f"faster-whisper 未安装,不进行处理") - return False - return True - else: - logger.warn(f"未配置asr引擎,不进行处理") + if not self._faster_whisper_model_path or not self._faster_whisper_model: + logger.warn(f"faster-whisper配置信息不完整,不进行处理") + return False + if not os.path.exists(self._faster_whisper_model_path): + logger.info(f"创建faster-whisper模型目录:{self._faster_whisper_model_path}") + os.mkdir(self._faster_whisper_model_path) + try: + from faster_whisper import WhisperModel, download_model + except ImportError: + logger.warn(f"faster-whisper 未安装,不进行处理") return False return True - def __process_file_subtitle(self, video_file): + def __process_autosub(self, video_file) -> TaskStatus: if not video_file: - return + return TaskStatus.FAILED # 如果文件大小小于指定大小, 则不处理 - if os.path.getsize(video_file) < self.file_size: - return + if os.path.getsize(video_file) < self._file_size * 1024 * 1024: + return TaskStatus.IGNORED - self.process_count += 1 start_time = time.time() file_path, file_ext = os.path.splitext(video_file) file_name = os.path.basename(video_file) @@ -259,64 +349,42 @@ class AutoSubv2(_PluginBase): # 判断目的字幕(和内嵌)是否已存在 if self.__target_subtitle_exists(video_file): logger.warn(f"字幕文件已经存在,不进行处理") - self.skip_count += 1 - return + return TaskStatus.IGNORED # 生成字幕 - ret, lang, gen_sub_path = self.__generate_subtitle(video_file, file_path, self.enable_asr) + ret, lang, gen_sub_path = self.__generate_subtitle(video_file, file_path, self._enable_asr) if not ret: - message = f" 媒体: {file_name}\n " - if not self.enable_asr: - message += "内嵌&外挂字幕不存在,不进行翻译" - self.skip_count += 1 - else: - message += "生成字幕失败,跳过后续处理" - self.fail_count += 1 - - if self.send_notify: + message = f" 媒体: {file_name}\n 生成字幕失败,跳过后续处理" + if self._send_notify: self.post_message(mtype=NotificationType.Plugin, title="【自动字幕生成】", text=message) - return + return TaskStatus.FAILED - if self.translate_zh: + if self._translate_zh: # 翻译字幕 logger.info(f"开始翻译字幕为中文 ...") - # self.__translate_zh_subtitle(lang, f"{file_path}.{lang}.srt", f"{file_path}.zh.机翻.srt") self.__translate_zh_subtitle(lang, gen_sub_path, f"{file_path}.zh.机翻.srt") logger.info(f"翻译字幕完成:{file_name}.zh.机翻.srt") end_time = time.time() message = f" 媒体: {file_name}\n 处理完成\n 字幕原始语言: {lang}\n " - if self.translate_zh: + if self._translate_zh: message += f"字幕翻译语言: zh\n " message += f"耗时:{round(end_time - start_time, 2)}秒" logger.info(f"自动字幕生成 处理完成:{message}") - if self.send_notify: + if self._send_notify: self.post_message(mtype=NotificationType.Plugin, title="【自动字幕生成】", text=message) - self.success_count += 1 + return TaskStatus.COMPLETED except UserInterruptException: logger.info(f"用户中断当前任务:{video_file}") - self.fail_count += 1 + return TaskStatus.FAILED except Exception as e: logger.error(f"自动字幕生成 处理异常:{e}") end_time = time.time() message = f" 媒体: {file_name}\n 处理失败\n 耗时:{round(end_time - start_time, 2)}秒" - if self.send_notify: + if self._send_notify: self.post_message(mtype=NotificationType.Plugin, title="【自动字幕生成】", text=message) # 打印调用栈 logger.error(traceback.format_exc()) - self.fail_count += 1 - - def __process_folder_subtitle(self, path): - """ - 处理目录字幕 - :param path: - :return: - """ - # 获取目录媒体文件列表 - for video_file in self.__get_library_files(path): - if self._event.is_set(): - logger.info(f"{video_file}处理中止") - return - self.__process_file_subtitle(video_file) + return TaskStatus.FAILED def __do_speech_recognition(self, audio_lang, audio_file): """ @@ -326,80 +394,64 @@ class AutoSubv2(_PluginBase): :return: """ lang = audio_lang - if self.asr_engine == 'whisper.cpp': - command = [self.whisper_main] + self.additional_args.split() - command += ['-l', lang, '-m', self.whisper_model, '-osrt', '-of', audio_file, audio_file] - ret = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if ret.returncode == 0: - if lang == 'auto': - # 从output中获取语言 "whisper_full_with_state: auto-detected language: en (p = 0.973642)" - output = ret.stdout.decode('utf-8') if ret.stdout else "" - lang = re.search(r"auto-detected language: (\w+)", output) - if lang and lang.group(1): - lang = lang.group(1) - else: - lang = "en" - return True, lang - elif self.asr_engine == 'faster-whisper': - try: - from faster_whisper import WhisperModel, download_model - # 设置缓存目录, 防止缓存同目录出现 cross-device 错误 - cache_dir = os.path.join(self.faster_whisper_model_path, "cache") - if not os.path.exists(cache_dir): - os.mkdir(cache_dir) - os.environ["HF_HUB_CACHE"] = cache_dir - if self._proxy: - os.environ["HTTP_PROXY"] = settings.PROXY['http'] - os.environ["HTTPS_PROXY"] = settings.PROXY['https'] - model = WhisperModel( - download_model(self.faster_whisper_model, local_files_only=False, cache_dir=cache_dir), - device="cpu", compute_type="int8", cpu_threads=psutil.cpu_count(logical=False)) - segments, info = model.transcribe(audio_file, - language=lang if lang != 'auto' else None, - word_timestamps=True, - vad_filter=True, - temperature=0, - beam_size=5) - logger.info("Detected language '%s' with probability %f" % (info.language, info.language_probability)) + try: + from faster_whisper import WhisperModel, download_model + # 设置缓存目录, 防止缓存同目录出现 cross-device 错误 + cache_dir = os.path.join(self._faster_whisper_model_path, "cache") + if not os.path.exists(cache_dir): + os.mkdir(cache_dir) + os.environ["HF_HUB_CACHE"] = cache_dir + if self._huggingface_proxy: + os.environ["HTTP_PROXY"] = settings.PROXY['http'] + os.environ["HTTPS_PROXY"] = settings.PROXY['https'] + model = WhisperModel( + download_model(self._faster_whisper_model, local_files_only=False, cache_dir=cache_dir), + device="cpu", compute_type="int8", cpu_threads=psutil.cpu_count(logical=False)) + segments, info = model.transcribe(audio_file, + language=lang if lang != 'auto' else None, + word_timestamps=True, + vad_filter=True, + temperature=0, + beam_size=5) + logger.info("Detected language '%s' with probability %f" % (info.language, info.language_probability)) - if lang == 'auto': - lang = info.language + if lang == 'auto': + lang = info.language - subs = [] - if lang in ['en', 'eng']: - # 英文先生成单词级别字幕,再合并 - idx = 0 - for segment in segments: - if self._event.is_set(): - logger.info(f"whisper音轨转录服务停止") - raise UserInterruptException(f"用户中断当前任务") - for word in segment.words: - idx += 1 - subs.append(srt.Subtitle(index=idx, - start=timedelta(seconds=word.start), - end=timedelta(seconds=word.end), - content=word.word)) - subs = self.__merge_srt(subs) - else: - for i, segment in enumerate(segments): - if self._event.is_set(): - logger.info(f"whisper音轨转录服务停止") - raise UserInterruptException(f"用户中断当前任务") - subs.append(srt.Subtitle(index=i, - start=timedelta(seconds=segment.start), - end=timedelta(seconds=segment.end), - content=segment.text)) - self.__save_srt(f"{audio_file}.srt", subs) - logger.info(f"音轨转字幕完成") - return True, lang - except ImportError: - logger.warn(f"faster-whisper 未安装,不进行处理") - return False, None - except Exception as e: - traceback.print_exc() - logger.error(f"faster-whisper 处理异常:{e}") - return False, None - return False, None + subs = [] + if lang in ['en', 'eng']: + # 英文先生成单词级别字幕,再合并 + idx = 0 + for segment in segments: + if self._event.is_set(): + logger.info(f"whisper音轨转录服务停止") + raise UserInterruptException(f"用户中断当前任务") + for word in segment.words: + idx += 1 + subs.append(srt.Subtitle(index=idx, + start=timedelta(seconds=word.start), + end=timedelta(seconds=word.end), + content=word.word)) + subs = self.__merge_srt(subs) + else: + for i, segment in enumerate(segments): + if self._event.is_set(): + logger.info(f"whisper音轨转录服务停止") + raise UserInterruptException(f"用户中断当前任务") + subs.append(srt.Subtitle(index=i, + start=timedelta(seconds=segment.start), + end=timedelta(seconds=segment.end), + content=segment.text)) + self.__save_srt(f"{audio_file}.srt", subs) + logger.info(f"音轨转字幕完成") + return True, lang + except ImportError: + logger.warn(f"faster-whisper 未安装,不进行处理") + return False, None + except Exception as e: + traceback.print_exc() + logger.error(f"faster-whisper 处理异常:{e}") + return False, None def __generate_subtitle(self, video_file, subtitle_file, enable_asr=True): """ @@ -573,7 +625,7 @@ class AutoSubv2(_PluginBase): # 合并字幕 merged_subtitle = [] sentence_end = True - + end_tokens = ['.', '!', '?', '。', '!', '?', '。"', '!"', '?"', '."', '!"', '?"'] for index, item in enumerate(subtitle_data): # 当前字幕先将多行合并为一行,再去除首尾空格 content = item.content.replace('\n', ' ').strip() @@ -598,7 +650,7 @@ class AutoSubv2(_PluginBase): merged_subtitle[-1].end = item.end # 如果当前字幕内容以标志符结尾,则设置语句已经终结 - if content.endswith(tuple(self._end_token)): + if content.endswith(tuple(end_tokens)): sentence_end = True # 如果上句字幕超过一定长度,则设置语句已经终结 elif len(merged_subtitle[-1].content) > 80: @@ -608,7 +660,8 @@ class AutoSubv2(_PluginBase): return merged_subtitle - def __get_video_prefer_audio(self, video_meta, prefer_lang=None): + @staticmethod + def __get_video_prefer_audio(video_meta, prefer_lang=None): """ 获取视频的首选音轨,如果有多音轨, 优先指定语言音轨,否则获取默认音轨 :param video_meta @@ -643,7 +696,8 @@ class AutoSubv2(_PluginBase): logger.info(f"选中音轨信息:{audio_index}, {audio_lang}") return True, audio_index, audio_lang - def __get_video_prefer_subtitle(self, video_meta, prefer_lang=None, strict=False, srt=True): + @staticmethod + def __get_video_prefer_subtitle(video_meta, prefer_lang=None, strict=False, only_srt=True): """ 获取视频的首选字幕。优先级:1.字幕为偏好语言 2.默认字幕 3.第一个字幕 :param video_meta: 视频元数据 @@ -694,7 +748,7 @@ class AutoSubv2(_PluginBase): if stream.get('disposition', {}).get('forced'): continue # image-based 字幕,跳过 - if srt and ( + if only_srt and ( 'width' in stream or stream.get('codec_name') in image_based_subtitle_codecs ): @@ -724,18 +778,21 @@ class AutoSubv2(_PluginBase): logger.debug(f"命中内嵌字幕信息:{subtitle_index}, {subtitle_lang}, score:{subtitle_score}") return True, subtitle_index, subtitle_lang - def __is_noisy_subtitle(self, content): + @staticmethod + def __is_noisy_subtitle(content): """ 判断是否为背景音等字幕 :param content: :return: """ - return any(content.startswith(t[0]) and content.endswith(t[1]) for t in self._noisy_token) + noisy_tokens = [('(', ')'), ('[', ']'), ('{', '}'), ('【', '】'), ('♪', '♪'), ('♫', '♫'), ('♪♪', '♪♪')] + return any(content.startswith(t[0]) and content.endswith(t[1]) for t in noisy_tokens) def __get_context(self, all_subs: list, target_indices: List[int], is_batch: bool) -> str: """通用上下文获取方法""" - min_idx = max(0, min(target_indices) - self.context_window) - max_idx = min(len(all_subs) - 1, max(target_indices) + self.context_window) if is_batch else min(target_indices) + min_idx = max(0, min(target_indices) - self._context_window) + max_idx = min(len(all_subs) - 1, max(target_indices) + self._context_window) if is_batch else min( + target_indices) context = [] for idx in range(min_idx, max_idx + 1): @@ -747,18 +804,23 @@ class AutoSubv2(_PluginBase): def __process_items(self, all_subs: list, items: list) -> list: """统一处理入口(支持批量和单条)""" - if self.enable_batch and len(items) > 1: + if self._enable_batch and len(items) > 1: return self.__process_batch(all_subs, items) return [self.__process_single(all_subs, item) for item in items] + def __translate_to_zh(self, text: str, context: str = None) -> str: + if self._event.is_set(): + raise UserInterruptException(f"用户中断当前任务") + return self._openai.translate_to_zh(text, context) + def __process_batch(self, all_subs: list, batch: list) -> list: """批量处理逻辑""" indices = [all_subs.index(item) for item in batch] - context = self.__get_context(all_subs, indices, is_batch=True) if self.context_window > 0 else None + context = self.__get_context(all_subs, indices, is_batch=True) if self._context_window > 0 else None batch_text = '\n'.join([item.content for item in batch]) try: - ret, result = self.openai.translate_to_zh(batch_text, context) + ret, result = self.__translate_to_zh(batch_text, context) if not ret: raise Exception(result) @@ -777,10 +839,10 @@ class AutoSubv2(_PluginBase): def __process_single(self, all_subs: List[srt.Subtitle], item: srt.Subtitle) -> srt.Subtitle: """单条处理逻辑""" - for _ in range(self.max_retries): + for _ in range(self._max_retries): idx = all_subs.index(item) - context = self.__get_context(all_subs, [idx], is_batch=False) if self.context_window > 0 else None - success, trans = self.openai.translate_to_zh(item.content, context) + context = self.__get_context(all_subs, [idx], is_batch=False) if self._context_window > 0 else None + success, trans = self.__translate_to_zh(item.content, context) if success: item.content = f"{trans}\n{item.content}" @@ -795,7 +857,7 @@ class AutoSubv2(_PluginBase): def __translate_zh_subtitle(self, source_lang: str, source_subtitle: str, dest_subtitle: str): self._stats = {'total': 0, 'batch_success': 0, 'batch_fail': 0, 'line_fallback': 0} subs = self.__load_srt(source_subtitle) - if source_lang in ["en", "eng"]: + if source_lang in ["en", "eng"] and self._enable_merge: valid_subs = self.__merge_srt(subs) logger.info(f"英文字幕合并:合并前字幕数: {len(subs)},合并后字幕数: {len(valid_subs)}") else: @@ -805,12 +867,9 @@ class AutoSubv2(_PluginBase): current_batch = [] for item in valid_subs: - if self._event.is_set(): - logger.info(f"字幕{source_subtitle}翻译停止") - raise UserInterruptException(f"用户中断当前任务") current_batch.append(item) - if len(current_batch) >= self.batch_size: + if len(current_batch) >= self._batch_size: processed += self.__process_items(valid_subs, current_batch) current_batch = [] logger.info(f"进度: {len(processed)}/{len(valid_subs)}") @@ -917,7 +976,7 @@ class AutoSubv2(_PluginBase): :param video_file: :return: """ - if self.translate_zh: + if self._translate_zh: prefer_langs = ['zh', 'chi', 'zh-CN', 'chs', 'zhs', 'zh-Hans', 'zhong', 'simp', 'cn'] strict = True else: @@ -939,7 +998,7 @@ class AutoSubv2(_PluginBase): if not video_meta: return False ret, subtitle_index, subtitle_lang = self.__get_video_prefer_subtitle(video_meta, prefer_lang=prefer_langs, - srt=False) + only_srt=False) if ret and subtitle_lang in prefer_langs: return True @@ -958,69 +1017,40 @@ class AutoSubv2(_PluginBase): 'content': [ { 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3 - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ { 'component': 'VSwitch', 'props': { - 'model': 'run_now', - 'label': '立即运行一次', + 'model': 'enabled', + 'label': '启用插件', + 'color': 'primary' } } ] }, { 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3 - }, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'translate_preference', - 'label': '本地字幕提取策略', - 'items': [ - {'title': '仅英文字幕', 'value': 'english_only'}, - {'title': '优先英文字幕', 'value': 'english_first'}, - {'title': '优先原音字幕', 'value': 'origin_first'} - ] - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3 - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ { 'component': 'VSwitch', 'props': { - 'model': 'translate_zh', - 'label': '翻译为中文', + 'model': 'clear_history', + 'label': '清理历史记录', } } ] }, { 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3 - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ { 'component': 'VSwitch', 'props': { 'model': 'send_notify', - 'label': '发送通知', + 'label': '发送通知' } } ] @@ -1032,185 +1062,49 @@ class AutoSubv2(_PluginBase): 'content': [ { 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3 - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ { 'component': 'VSwitch', 'props': { - 'model': 'enable_asr', - 'label': '允许从音轨提取字幕', + 'model': 'listen_transfer_event', + 'label': '媒体入库自动执行', + 'hint': '监听媒体入库事件,自动执行字幕生成' } } ] }, { 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3, - 'v-show': 'enable_asr' - }, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'asr_engine', - 'label': 'ASR引擎', - 'items': [ - {'title': 'faster-whisper', 'value': 'faster-whisper'} - ] - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3, - 'v-show': 'enable_asr' - }, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'faster_whisper_model', - 'label': '模型', - 'items': [ - {'title': 'tiny', 'value': 'tiny'}, - {'title': 'tiny.en', 'value': 'tiny.en'}, - {'title': 'base', 'value': 'base'}, - {'title': 'base.en', 'value': 'base.en'}, - {'title': 'small', 'value': 'small'}, - {'title': 'small.en', 'value': 'small.en'}, - {'title': 'medium', 'value': 'medium'}, - {'title': 'large-v1', 'value': 'large-v1'}, - {'title': 'large-v2', 'value': 'large-v2'}, - {'title': 'large-v3', 'value': 'large-v3'}, - {'title': 'distil-small.en', 'value': 'distil-small.en'}, - {'title': 'distil-medium.en', 'value': 'distil-medium.en'}, - {'title': 'distil-large-v2.en', 'value': 'distil-large-v2'}, - {'title': 'distil-large-v3.en', 'value': 'distil-large-v3'}, - ] - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3, - 'v-show': 'enable_asr' - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ { 'component': 'VSwitch', 'props': { - 'model': 'proxy', - 'label': '使用代理下载模型', + 'model': 'run_now', + 'label': '手动执行一次', + 'color': 'secondary' } } ] - }, - ] - }, - { - 'component': 'VRow', - 'content': [ - - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3, - 'v-show': 'translate_zh' - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'enable_batch', - 'label': '启用批量翻译', - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3, - 'v-show': 'translate_zh' - }, - 'content': [ - { - 'component': 'VTextField', - 'props': { - 'model': 'batch_size', - 'label': '每批翻译行数', - 'placeholder': '20' - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3, - 'v-show': 'translate_zh' - }, - 'content': [ - { - 'component': 'VTextField', - 'props': { - 'model': 'context_window', - 'label': '上下文窗口大小', - 'placeholder': '5' - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 3, - 'v-show': 'translate_zh' - }, - 'content': [ - { - 'component': 'VTextField', - 'props': { - 'model': 'max_retries', - 'label': 'llm请求重试次数', - 'placeholder': '3' - } - } - ] - }, + } ] }, { 'component': 'VRow', + 'props': {'v-show': 'run_now'}, 'content': [ { 'component': 'VCol', - 'props': { - 'cols': 12 - }, + 'props': {'cols': 12, 'md': 12}, 'content': [ { 'component': 'VTextarea', 'props': { 'model': 'path_list', 'label': '媒体路径', - 'rows': 5, - 'placeholder': '媒体文件或文件夹绝对路径(如为文件夹会遍历其中所有媒体文件),每行一个路径,请确保路径正确' + 'rows': 3, + 'placeholder': '绝对路径,每行一个。支持文件和文件夹' } } ] @@ -1222,37 +1116,47 @@ class AutoSubv2(_PluginBase): 'content': [ { 'component': 'VCol', - 'props': { - 'cols': 12, - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ { 'component': 'VTextField', 'props': { 'model': 'file_size', - 'label': '文件大小(MB)', - 'placeholder': '单位 MB, 大于该大小的文件才会进行字幕生成' + 'label': '触发字幕生成的视频文件不小于(MB)', + 'placeholder': '默认10' } } ] - } - ] - }, - { - 'component': 'VRow', - 'content': [ + }, { 'component': 'VCol', - 'props': { - 'cols': 12, - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ { - 'component': 'VAlert', + 'component': 'VSelect', 'props': { - 'type': 'info', - 'variant': 'tonal', - 'text': '翻译依赖 OpenAi 插件配置' + 'model': 'translate_preference', + 'label': '字幕源语言偏好', + 'hint': '小语种视频存在多语言字幕/音轨时,优先选择哪种语言用于翻译', + 'items': [ + {'title': '仅英文', 'value': 'english_only'}, + {'title': '英文优先', 'value': 'english_first'}, + {'title': '原音优先', 'value': 'origin_first'} + ] + } + } + ] + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'translate_zh', + 'label': '翻译成中文', + 'hint': '使用大模型翻译成中文字幕' } } ] @@ -1264,11 +1168,294 @@ class AutoSubv2(_PluginBase): 'content': [ { 'component': 'VCol', - 'props': { - 'cols': 12, - }, + 'props': {'cols': 12, 'md': 4}, 'content': [ - { + { + 'component': 'VSwitch', + 'props': { + 'model': 'enable_asr', + 'label': '允许从音轨生成字幕' + } + } + ] + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4, 'v-show': 'enable_asr'}, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'faster_whisper_model', + 'label': 'faster-whisper模型选择', + 'items': ['tiny', 'base', 'small', 'medium', + 'large-v3', + {'title': 'large-v3-turbo', + 'value': 'deepdml/faster-whisper-large-v3-turbo-ct2'}, + ] + } + } + ] + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4, 'v-show': 'enable_asr'}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'proxy', + 'hint': '需配置MP环境变量PROXY_HOST', + 'label': '使用代理下载模型' + } + } + ] + } + ] + }, + { + 'component': 'VExpansionPanels', + 'props': {'variant': 'accordion', 'multiple': True}, + 'content': [ + { + 'component': 'VExpansionPanel', + 'props': {'v-show': 'translate_zh'}, + 'content': [ + { + 'component': 'VExpansionPanelTitle', + 'text': '大模型接口设置' + }, + { + 'component': 'VExpansionPanelText', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'use_chatgpt', + 'label': '复用ChatGPT插件配置' + } + } + ] + }, + { + 'component': 'VTextField', + 'props': { + 'model': 'use_chatgpt_trigger', + 'class': 'd-none', + 'text': 'trigger', + 'change': 'use_chatgpt_trigger = use_chatgpt ? 1 : 0' + } + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4, + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'openai_proxy', + 'label': '使用代理服务器', + 'v-show': '!use_chatgpt', + 'v-if': '!use_chatgpt' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4, + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'compatible', + 'label': '兼容模式', + 'v-show': '!use_chatgpt' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'openai_url', + 'label': 'OpenAI API Url', + 'placeholder': 'https://api.openai.com', + 'v-show': '!use_chatgpt' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'openai_key', + 'label': 'API密钥', + 'placeholder': 'sk-xxx', + 'v-show': '!use_chatgpt' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'openai_model', + 'label': '自定义模型', + 'placeholder': 'gpt-3.5-turbo', + 'v-show': '!use_chatgpt' + } + } + ] + } + ] + } + ] + } + ] + }, + { + 'component': 'VExpansionPanel', + 'props': {'v-show': 'translate_zh'}, + 'content': [ + { + 'component': 'VExpansionPanelTitle', + 'text': '翻译参数设置' + }, + { + 'component': 'VExpansionPanelText', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4}, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'context_window', + 'label': '上下文窗口大小', + 'placeholder': '5' + } + } + ] + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4}, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'max_retries', + 'label': 'llm请求重试次数', + 'placeholder': '3' + } + } + ] + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enable_merge', + 'label': '翻译英文时合并整句' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enable_batch', + 'label': '启用批量翻译' + } + } + ] + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 4, 'v-show': 'enable_batch'}, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'batch_size', + 'label': '每批翻译行数', + 'placeholder': '10' + } + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 12}, + 'content': [ + { 'component': 'VAlert', 'props': { 'type': 'success', @@ -1276,24 +1463,24 @@ class AutoSubv2(_PluginBase): }, 'content': [ { - 'component': 'span', - 'text': '详细说明参考:' - }, - { - 'component': 'a', - 'props': { - 'href': 'https://github.com/TimoYoung/MoviePilot-Plugins/blob/main/plugins/autosubv2/README.md', - 'target': '_blank' + 'component': 'span', + 'text': '详细说明参考:' }, - 'content': [ - { - 'component': 'u', - 'text': 'README' - } - ] - }] - } - + { + 'component': 'a', + 'props': { + 'href': 'https://github.com/jxxghp/MoviePilot-Plugins/blob/main/plugins/autosubv2/README.md', + 'target': '_blank' + }, + 'content': [ + { + 'component': 'u', + 'text': 'README' + } + ] + } + ] + } ] } ] @@ -1301,27 +1488,141 @@ class AutoSubv2(_PluginBase): ] } ], { - "run_now": False, + "enabled": False, + "clear_history": False, "send_notify": False, - "translate_zh": True, - "enable_asr": True, - "asr_engine": "faster-whisper", - "faster_whisper_model": "base", - "proxy": True, - "translate_preference": "origin_first", - "enable_batch": True, - "batch_size": 20, - "context_window": 5, - "max_retries": 3, + "listen_transfer_event": True, + "run_now": False, "path_list": "", "file_size": "10", + "translate_preference": "english_first", + "translate_zh": False, + "enable_asr": True, + "faster_whisper_model": "base", + "proxy": True, + "use_chatgpt": True, + "use_chatgpt_trigger": 0, + "openai_proxy": False, + "compatible": False, + "openai_url": "https://api.openai.com", + "openai_key": None, + "openai_model": "gpt-3.5-turbo", + "context_window": 5, + "max_retries": 3, + "enable_merge": False, + "enable_batch": True, + "batch_size": 10, } def get_api(self) -> List[Dict[str, Any]]: pass def get_page(self) -> List[dict]: - pass + # 加载任务并按添加时间倒序排列 + tasks: Dict[str, TaskItem] = self.load_tasks() + sorted_tasks = sorted( + tasks.items(), + key=lambda x: x[1].add_time, + reverse=True + ) + + status_classes = { + TaskStatus.PENDING: "text-info", + TaskStatus.IN_PROGRESS: "text-warning", + TaskStatus.COMPLETED: "text-success", + TaskStatus.IGNORED: "text-muted", + TaskStatus.FAILED: "text-error" + } + + rows = [] + for task_id, task in sorted_tasks: + source_label = { + TaskSource.MANUAL: "手动添加", + TaskSource.EVENT: "入库触发" + }.get(task.source, task.source) + + status_text = { + TaskStatus.PENDING: "等待中", + TaskStatus.IN_PROGRESS: "处理中", + TaskStatus.COMPLETED: "已完成", + TaskStatus.IGNORED: "已忽略", + TaskStatus.FAILED: "失败" + }.get(task.status, task.status) + + status_class = status_classes.get(task.status, "") + + add_time_str = task.add_time.strftime("%Y-%m-%d %H:%M:%S") + complete_time_str = ( + task.complete_time.strftime("%Y-%m-%d %H:%M:%S") + if task.complete_time else "-" + ) + + rows.append({ + "component": "tr", + "props": {"class": "text-sm"}, + "content": [ + {"component": "td", "text": add_time_str}, + {"component": "td", "text": task.video_file}, + {"component": "td", "text": source_label}, + {"component": "td", "text": complete_time_str}, + { + "component": "td", + "props": {"class": status_class}, + "text": status_text + }, + ], + }) + + return [ + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VTable", + "props": {"hover": True}, + "content": [ + { + "component": "thead", + "content": [ + { + "component": "th", + "props": {"class": "text-start ps-4"}, + "text": "添加时间" + }, + { + "component": "th", + "props": {"class": "text-start ps-4"}, + "text": "视频文件" + }, + { + "component": "th", + "props": {"class": "text-start ps-4"}, + "text": "来源" + }, + { + "component": "th", + "props": {"class": "text-start ps-4"}, + "text": "完成时间" + }, + { + "component": "th", + "props": {"class": "text-start ps-4"}, + "text": "状态" + }, + ] + }, + {"component": "tbody", "content": rows} + ] + } + ] + } + ] + } + ] @staticmethod def get_command() -> List[Dict[str, Any]]: @@ -1339,7 +1640,23 @@ class AutoSubv2(_PluginBase): """ if self._running: self._event.set() - self._running = False - self._scheduler.shutdown() - self._event.clear() - logger.info(f"停止自动字幕生成服务") + if self._consumer_thread and self._consumer_thread.is_alive(): + logger.info("正在停止当前任务...") + # self._consumer_thread.join(timeout=3) + self._consumer_thread.join() + + if self._task_queue: + while not self._task_queue.empty(): + self._task_queue.get_nowait() + self._task_queue.task_done() + logger.info("任务队列已清空") + if self._tasks is not None: + for task_id in list(self._tasks.keys()): + task = self._tasks[task_id] + if task.status == TaskStatus.PENDING or task.status == TaskStatus.IN_PROGRESS: + task.status = TaskStatus.FAILED + task.complete_time = datetime.now() + self.save_tasks() # 持久化更新后的任务列表 + self._running = False + self._event.clear() + logger.info(f"自动字幕生成服务已停止") diff --git a/plugins/autosubv2/translate/openai.py b/plugins/autosubv2/translate/openai_translate.py similarity index 96% rename from plugins/autosubv2/translate/openai.py rename to plugins/autosubv2/translate/openai_translate.py index 9654a94..dae55a1 100644 --- a/plugins/autosubv2/translate/openai.py +++ b/plugins/autosubv2/translate/openai_translate.py @@ -12,10 +12,11 @@ class OpenAi: _api_url: str = None _model: str = "gpt-3.5-turbo" - def __init__(self, api_key: str = None, api_url: str = None, proxy: dict = None, model: str = None): + def __init__(self, api_key: str = None, api_url: str = None, proxy: dict = None, model: str = None, + compatible: bool = False): self._api_key = api_key self._api_url = api_url - openai.api_base = self._api_url + "/v1" + openai.api_base = self._api_url if compatible else self._api_url + "/v1" openai.api_key = self._api_key if proxy and proxy.get("https"): openai.proxy = proxy.get("https") diff --git a/plugins/bangumicoll/__init__.py b/plugins/bangumicoll/__init__.py index 86227dc..4b224be 100644 --- a/plugins/bangumicoll/__init__.py +++ b/plugins/bangumicoll/__init__.py @@ -37,7 +37,7 @@ class BangumiColl(_PluginBase): # 插件图标 plugin_icon = "bangumi_b.png" # 插件版本 - plugin_version = "1.5.6" + plugin_version = "1.5.7" # 插件作者 plugin_author = "Attente" # 作者主页 @@ -67,12 +67,6 @@ class BangumiColl(_PluginBase): _group_select_order: list = [] def init_plugin(self, config: dict = None): - self.downloadchain = DownloadChain() - self.siteoper = SiteOper() - self.subscribechain = SubscribeChain() - self.subscribehelper = SubscribeHelper() - self.subscribeoper = SubscribeOper() - self.tmdbapi = TmdbApi() # 停止现有任务 self.stop_service() @@ -86,21 +80,21 @@ class BangumiColl(_PluginBase): if config: # 遍历配置中的键并设置相应的属性 for key in ( - "enabled", - "total_change", - "cron", - "notify", - "onlyonce", - "uid", - "collection_type", - "save_path", - "sites", - "match_groups", - "group_select_order", + "enabled", + "total_change", + "cron", + "notify", + "onlyonce", + "uid", + "collection_type", + "save_path", + "sites", + "match_groups", + "group_select_order", ): setattr(self, f"_{key}", config.get(key, getattr(self, f"_{key}"))) # 获得所有站点 - site_ids = {site.id for site in self.siteoper.list_order_by_pri()} + site_ids = {site.id for site in SiteOper().list_order_by_pri()} # 过滤已删除的站点 self._sites = [site_id for site_id in self._sites if site_id in site_ids] # 更新配置 @@ -113,8 +107,7 @@ class BangumiColl(_PluginBase): self._scheduler.add_job( func=self.bangumi_coll, trigger='date', - run_date=datetime.datetime.now(tz=pytz.timezone(settings.TZ)) - + datetime.timedelta(seconds=3), + run_date=datetime.datetime.now(tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3), ) self._scheduler.start() @@ -141,14 +134,335 @@ class BangumiColl(_PluginBase): ) def get_form(self): - from .page_components import form # 列出所有站点 sites_options = [ {"title": site.name, "value": site.id} - for site in self.siteoper.list_order_by_pri() + for site in SiteOper().list_order_by_pri() ] - return form(sites_options, self._is_v2) + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 3}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + }, + } + ], + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 3}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'notify', + 'label': '自动取消订阅并通知', + }, + } + ], + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 3}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'total_change', + 'label': '固定总集数', + }, + } + ], + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 3}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'onlyonce', + 'label': '立即运行一次', + }, + } + ], + }, + ], + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 8, 'md': 4}, + 'content': [ + { + # 'component': 'VTextField', # 组件替换为VCronField + 'component': 'VCronField', + 'props': { + 'model': 'cron', + 'label': '执行周期', + 'placeholder': '5位cron表达式,留空自动', + }, + } + ], + }, + { + 'component': 'VCol', + 'props': {'cols': 8, 'md': 4}, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'uid', + 'label': 'UID/用户名', + 'placeholder': '设置了用户名填写用户名,否则填写UID', + }, + }, + ], + }, + { + 'component': 'VCol', + 'props': {'cols': 8, 'md': 4}, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'collection_type', + 'label': '收藏类型', + 'chips': True, + 'multiple': True, + 'items': [ + {'title': '在看', 'value': 3}, + {'title': '想看', 'value': 1}, + ], + }, + } + ], + }, + ], + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'success', + 'variant': 'tonal', + 'text': True + }, + 'content': [ + { + 'component': 'div', + 'props': { + 'innerHTML': '提示:剧集组优先级越靠前优先级越高。'} + } + ] + } + ] + }, + ], + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 8, 'md': 4}, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'match_groups', + 'disabled': not self._is_v2, + 'label': '剧集组填充(实验性)', + } + } + ] + }, + { + 'component': 'VCol', + 'props': {'cols': 8}, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'group_select_order', + 'label': '剧集组优先级', + 'disabled': not self._is_v2, + 'chips': True, + 'multiple': True, + 'clearable': True, + 'items': [ + {"title": "初始播出日期", "value": 1}, + {"title": "绝对", "value": 2}, + {"title": "DVD", "value": 3}, + {"title": "数字", "value": 4}, + {"title": "故事线", "value": 5}, + {"title": "制片", "value": 6}, + {"title": "电视", "value": 7}, + ], + }, + } + ] + }, + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 6}, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'save_path', + 'label': '保存目录', + 'placeholder': '留空自动', + }, + } + ], + }, + { + 'component': 'VCol', + 'props': {'cols': 12, 'md': 6}, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'sites', + 'label': '选择站点', + 'chips': True, + 'multiple': True, + 'clearable': True, + 'items': sites_options, + }, + } + ], + }, + ], + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': True + }, + 'content': [ + { + 'component': 'div', + 'props': { + 'innerHTML': '注意: 该插件仅会将公开的收藏添加到订阅。'} + } + ] + } + ] + }, + ], + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': True + }, + 'content': [ + { + 'component': 'div', + 'props': { + 'innerHTML': '注意: 开启自动取消订阅并通知后,已添加的订阅在下一次执行时若不在已选择的收藏类型中,将会被取消订阅。'} + } + ] + } + ] + }, + ], + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': True + }, + 'content': [ + { + 'component': 'div', + 'props': { + 'innerHTML': '注意: 开启固定总集数后,从Bangumi API获取到总集数将不会因订阅元数据更新改变。' + } + } + ] + } + ] + }, + ], + }, + ], + }, + ], { + "enabled": False, + "total_change": False, + "notify": False, + "onlyonce": False, + "cron": "", + "uid": "", + "collection_type": [3], + "save_path": "", + "sites": [], + "match_groups": False, + "group_select_order": [], + } def get_service(self) -> List[Dict[str, Any]]: """ @@ -194,11 +508,11 @@ class BangumiColl(_PluginBase): def get_command(self): return [ { - "cmd": "/bangumi_coll", - "event": EventType.PluginAction, - "desc": "Bangumi收藏订阅", - "category": "", - "data": {"action": "bangumi_coll"} + "cmd": "/bangumi_coll", + "event": EventType.PluginAction, + "desc": "Bangumi收藏订阅", + "category": "", + "data": {"action": "bangumi_coll"} } ] @@ -218,21 +532,21 @@ class BangumiColl(_PluginBase): return self.post_message(channel=event_data.get("channel"), - title=f"开始添加用户: {self._uid} 的收藏 ...", - userid=event_data.get("user")) + title=f"开始添加用户: {self._uid} 的收藏 ...", + userid=event_data.get("user")) # 运行任务 msg = self.bangumi_coll() self.post_message(channel=event_data.get("channel"), - title="添加完成" if not msg else msg, - userid=event_data.get("user")) + title="添加完成" if not msg else msg, + userid=event_data.get("user")) def bangumi_coll(self) -> str: """订阅Bangumi用户收藏""" if not self._uid: logger.error("未设置UID") return "未设置UID" - + msg = "" try: res = self.get_bgm_res(addr="UserCollections", id=self._uid) items = self.parse_collection_items(res) @@ -263,9 +577,8 @@ class BangumiColl(_PluginBase): "tags": [tag.get('name') for tag in item['subject'].get('tags', [{}])] } for item in data - if item.get("type") in self._collection_type and item['subject'].get('date')\ # 只添加未来30天内放送的条目 - and self.is_date_in_range(item['subject'].get('date'), threshold_days=30)[0] + if item.get("type") in self._collection_type and item['subject'].get('date') and self.is_date_in_range(item['subject'].get('date'), threshold_days=30)[0] } def manage_subscriptions(self, items: Dict[int, Dict[str, Any]]): @@ -273,7 +586,7 @@ class BangumiColl(_PluginBase): # 查询订阅 db_sub = { i.bangumiid: i.id - for i in self.subscribechain.subscribeoper.list() + for i in SubscribeOper().list() if i.bangumiid } # bangumi 条目 @@ -308,6 +621,7 @@ class BangumiColl(_PluginBase): """添加订阅""" fail_items = {} + subscribeoper = SubscribeOper() for subid, item in items.items(): if item.get("name_cn"): meta = MetaInfo(item.get("name_cn")) @@ -332,9 +646,9 @@ class BangumiColl(_PluginBase): meta.en_name = meta.title if (mediainfo := self.chain.recognize_media( - meta=meta, - mtype=mtype, - cache=False + meta=meta, + mtype=mtype, + cache=False )) or any( getattr(meta, attr) == meta.org_string for attr in ('cn_name', 'en_name') @@ -349,9 +663,10 @@ class BangumiColl(_PluginBase): mediainfo.bangumi_id = subid # 根据发行日期判断是不是续作 if mediainfo.type == MediaType.TV \ - and not self.is_date_in_range(sub_air_date, mediainfo.release_date)[0]: + and not self.is_date_in_range(sub_air_date, mediainfo.release_date)[0]: # 识别剧集组标志 group_flag: bool = True + season_info = [] if "OVA" in item.get("tags"): # 季0 处理 if tmdb_info := self.chain.tmdb_info(mediainfo.tmdb_id, mediainfo.type, 0): @@ -359,12 +674,13 @@ class BangumiColl(_PluginBase): if self.is_date_in_range(sub_air_date, info.get("air_date"), 2)[0]: mediainfo.season = 0 meta.begin_episode = info.get("episode_number") - else: # 信息不完整, 跳过条目 + else: # 信息不完整, 跳过条目 continue else: # 过滤信息不完整和第0季 - season_info = [info for info in mediainfo.season_info if info.get("season_number") and info.get("air_date") and info.get("episode_count")] + season_info = [info for info in mediainfo.season_info if + info.get("season_number") and info.get("air_date") and info.get("episode_count")] # 获取 bangumi 信息 meta = self.get_eps(meta, subid) # 先通过season_info处理三季及以上的情况, tmdb存在第二季也不能保证不会被合并 @@ -399,16 +715,16 @@ class BangumiColl(_PluginBase): season_list = [] for info in mediainfo.season_info: if info.get("season_number") == 0: - season_list.append((len(season_info)+1, len(mediainfo.seasons[1])+info.get("episode_count"))) + season_list.append( + (len(season_info) + 1, len(mediainfo.seasons[1]) + info.get("episode_count"))) season_list.append((len(season_info), len(mediainfo.seasons[1]))) # 预匹配剧集组 candidate_groups = ( group for group in mediainfo.episode_groups - if any( - group.get("group_count") == s[0] and - group.get("episode_count") == s[1] - for s in season_list - ) + if any(group.get("group_count") == s[0] and + group.get("episode_count") == s[1] + for s in season_list + ) ) for group in candidate_groups: @@ -419,9 +735,10 @@ class BangumiColl(_PluginBase): else: mediainfo = self._match_group(air_date, meta, mediainfo) # 非续作 - elif mediainfo.type == MediaType.TV: mediainfo.season = 1 + elif mediainfo.type == MediaType.TV: + mediainfo.season = 1 # 检查本地媒体 - exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) + exist_flag, no_exists = DownloadChain().get_no_exists_info(meta=meta, mediainfo=mediainfo) if exist_flag: # 添加到排除 self.update_data(key="exclude", value=subid) @@ -432,19 +749,19 @@ class BangumiColl(_PluginBase): self.update_data(key="exclude", value=subid) logger.info(f'{mediainfo.title_year} 媒体库中已存在 第 {mediainfo.season} 季') continue - sid = self.subscribeoper.list_by_tmdbid( + sid = subscribeoper.list_by_tmdbid( mediainfo.tmdb_id, mediainfo.season ) if sid: logger.info(f"{mediainfo.title_year} 正在订阅中") if len(sid) == 1: - self.subscribeoper.update( + subscribeoper.update( sid=sid[0].id, payload={"bangumiid": subid} ) logger.info(f"{mediainfo.title_year} Bangumi条目id更新成功") continue # 添加订阅 - sid, msg = self.subscribechain.add(**self.prepare_add_args(meta, mediainfo)) + sid, msg = SubscribeChain().add(**self.prepare_add_args(meta, mediainfo)) if not sid: fail_items[subid] = f"{item.get('name_cn') or item.get('name')} {msg}" @@ -454,6 +771,7 @@ class BangumiColl(_PluginBase): """ 将tmdb多季合并的季信息进行拆分 """ + season_data = {} if tmdb_info := self.chain.tmdb_info(mediainfo.tmdb_id, mediainfo.type, season): season = 1 air_date = tmdb_info.get("air_date") @@ -505,8 +823,8 @@ class BangumiColl(_PluginBase): episode_count = group.get("episode_count", 0) if ( - group_count >= total_season - and episode_count >= begin_ep + group_count >= total_season + and episode_count >= begin_ep ): logger.info( f"{mediainfo.title_year} 正在匹配 剧集组: " @@ -514,8 +832,8 @@ class BangumiColl(_PluginBase): f"共 {group_count} 季 {episode_count} 集") if season_num := self.get_group_season( - group.get("id"), air_date, mediainfo - ): + group.get("id"), air_date, mediainfo + ): mediainfo.episode_group = group.get("id") mediainfo.season = season_num return mediainfo @@ -529,7 +847,7 @@ class BangumiColl(_PluginBase): :param mediainfo: MediaInfo :return: 季号 """ - if group_seasons := self.tmdbapi.get_tv_group_seasons(group_id): + if group_seasons := TmdbApi().get_tv_group_seasons(group_id): for group_season in group_seasons: if self.is_date_in_range(air_date, group_season.get("episodes")[0].get("air_date"))[0]: logger.info(f"{mediainfo.title_year} 剧集组: {group_id} 第{group_season.get('order')}季 ") @@ -564,9 +882,9 @@ class BangumiColl(_PluginBase): total_episode = len(mediainfo.seasons.get(mediainfo.season or 1) or []) if ( - meta.begin_season - and mediainfo.season != meta.begin_season - or total_episode != meta.total_episode + meta.begin_season + and mediainfo.season != meta.begin_season + or total_episode != meta.total_episode ): meta = self.get_eps(meta, mediainfo.bangumi_id) total_ep: int = meta.end_episode if meta.end_episode else total_episode @@ -597,7 +915,7 @@ class BangumiColl(_PluginBase): """更新媒体季信息""" best_info = None min_days = float('inf') - + for info in season_info: result, days = self.is_date_in_range(air_date, info.get("air_date")) if result: @@ -628,11 +946,12 @@ class BangumiColl(_PluginBase): # 移除订阅 def delete_subscribe(self, del_items: dict[int, int]): """删除订阅""" + subscribeoper = SubscribeOper() for subscribe_id in del_items.keys(): try: - if subscribe := self.subscribeoper.get(subscribe_id): - self.subscribeoper.delete(subscribe_id) - self.subscribehelper.sub_done_async( + if subscribe := subscribeoper.get(subscribe_id): + subscribeoper.delete(subscribe_id) + SubscribeHelper().sub_done_async( {"tmdbid": subscribe.tmdbid, "doubanid": subscribe.doubanid} ) self.post_message( @@ -711,4 +1030,3 @@ class BangumiColl(_PluginBase): except Exception as e: logger.error(f"获取订阅历史失败: {str(e)}") return set() - diff --git a/plugins/bangumicoll/page_components.py b/plugins/bangumicoll/page_components.py deleted file mode 100644 index 05fc70c..0000000 --- a/plugins/bangumicoll/page_components.py +++ /dev/null @@ -1,355 +0,0 @@ -from bs4 import BeautifulSoup - - -def form(sites_options: list[dict], is_v2: bool = True) -> list: - return [ - { - 'component': 'VForm', - 'content': [ - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': {'cols': 12, 'md': 3}, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'enabled', - 'label': '启用插件', - }, - } - ], - }, - { - 'component': 'VCol', - 'props': {'cols': 12, 'md': 3}, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'notify', - 'label': '自动取消订阅并通知', - }, - } - ], - }, - { - 'component': 'VCol', - 'props': {'cols': 12, 'md': 3}, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'total_change', - 'label': '不更新元数据', - }, - } - ], - }, - { - 'component': 'VCol', - 'props': {'cols': 12, 'md': 3}, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'onlyonce', - 'label': '立即运行一次', - }, - } - ], - }, - ], - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': {'cols': 8, 'md': 4}, - 'content': [ - { - # 'component': 'VTextField', # 组件替换为VCronField - 'component': 'VCronField', - 'props': { - 'model': 'cron', - 'label': '执行周期', - 'placeholder': '5位cron表达式,留空自动', - }, - } - ], - }, - { - 'component': 'VCol', - 'props': {'cols': 8, 'md': 4}, - 'content': [ - { - 'component': 'VTextField', - 'props': { - 'model': 'uid', - 'label': 'UID/用户名', - 'placeholder': '设置了用户名填写用户名,否则填写UID', - }, - }, - ], - }, - { - 'component': 'VCol', - 'props': {'cols': 8, 'md': 4}, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'collection_type', - 'label': '收藏类型', - 'chips': True, - 'multiple': True, - 'items': [ - {'title': '在看', 'value': 3}, - {'title': '想看', 'value': 1}, - ], - }, - } - ], - }, - ], - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - }, - 'content': [ - { - 'component': 'VAlert', - 'props': { - 'type': 'info', - 'variant': 'tonal', - }, - 'content': parse_html( - '

提示: 剧集组优先级越靠前优先级越高。

' - ), - }, - ], - }, - ], - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': {'cols': 8, 'md': 4}, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'match_groups', - 'disabled': not is_v2, - 'label': '剧集组填充(实验性)', - } - } - ] - }, - { - 'component': 'VCol', - 'props': {'cols': 8}, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'group_select_order', - 'label': '剧集组优先级', - 'disabled': not is_v2, - 'chips': True, - 'multiple': True, - 'clearable': True, - 'items': [ - {"title": "初始播出日期", "value": 1}, - {"title": "绝对", "value": 2}, - {"title": "DVD", "value": 3}, - {"title": "数字", "value": 4}, - {"title": "故事线", "value": 5}, - {"title": "制片", "value": 6}, - {"title": "电视", "value": 7}, - ], - }, - } - ] - }, - ] - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': {'cols': 12, 'md': 6}, - 'content': [ - { - 'component': 'VTextField', - 'props': { - 'model': 'save_path', - 'label': '保存目录', - 'placeholder': '留空自动', - }, - } - ], - }, - { - 'component': 'VCol', - 'props': {'cols': 12, 'md': 6}, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'sites', - 'label': '选择站点', - 'chips': True, - 'multiple': True, - 'clearable': True, - 'items': sites_options, - }, - } - ], - }, - ], - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - }, - 'content': [ - { - 'component': 'VAlert', - 'props': { - 'type': 'info', - 'variant': 'tonal', - }, - 'content': parse_html( - '

注意: 该插件仅会将公开的收藏添加到订阅

' - ), - } - ], - } - ], - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - }, - 'content': [ - { - 'component': 'VAlert', - 'props': { - 'type': 'info', - 'variant': 'tonal', - }, - 'content': parse_html( - '

注意: 开启自动取消订阅并通知后,已添加的订阅在下一次执行时若不在已选择的收藏类型中,将会被取消订阅。

' - ), - } - ], - } - ], - }, - ], - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - }, - 'content': [ - { - 'component': 'VAlert', - 'props': { - 'type': 'info', - 'variant': 'tonal', - }, - 'content': parse_html( - '

注意: 开启不更新元数据后,从Bangumi API获取到总集数将不会因订阅元数据更新改变。

' - ), - }, - ], - }, - ], - }, - ], { - "enabled": False, - "total_change": False, - "notify": False, - "onlyonce": False, - "cron": "", - "uid": "", - "collection_type": [3], - "save_path": "", - "sites": [], - "match_groups": False, - "group_select_order": [], - } - - -def parse_html(html_string: str) -> list: - soup = BeautifulSoup(html_string, 'html.parser') - result: list = [] - - # 定义需要直接转为文本的标签 - inline_text_tags = {'strong', 'u', 'em', 'b', 'i'} - - def process_element(element: BeautifulSoup): - # 处理纯文本节点 - if element.name is None: - text = element.strip() - return text if text else "" - - # 处理HTML标签 - component = element.name - props = {attr: element[attr] for attr in element.attrs} - content = [] - - # 递归处理子元素 - for child in element.children: - child_content = process_element(child) - if isinstance(child_content, str): - content.append({'component': 'span', 'text': child_content}) - elif child_content: # 只有在child_content不为空时添加 - content.append(child_content) - - # 构建标签对象 - tag_data = { - 'component': component, - 'props': props, - 'content': content if component not in inline_text_tags else [], - } - - if content and component in inline_text_tags: - tag_data['text'] = ' '.join( - item['text'] for item in content if 'text' in item - ) - - return tag_data - - # 遍历所有子元素 - for element in soup.children: - element_content = process_element(element) - if element_content: # 只增加非空内容 - result.append(element_content) - - return result diff --git a/plugins/contractcheck/__init__.py b/plugins/contractcheck/__init__.py index 00b98a1..e50a9d1 100644 --- a/plugins/contractcheck/__init__.py +++ b/plugins/contractcheck/__init__.py @@ -39,7 +39,7 @@ class ContractCheck(_PluginBase): # 插件图标 plugin_icon = "contract.png" # 插件版本 - plugin_version = "1.4" + plugin_version = "1.4.1" # 插件作者 plugin_author = "DzAvril" # 作者主页 @@ -53,13 +53,13 @@ class ContractCheck(_PluginBase): class ContractInfo: def __init__( - self, - site_name: str = "", - official: bool = False, - size: int = 0, - num: int = 0, - duration: int = 0, - date: datetime = datetime.now(), + self, + site_name: str = "", + official: bool = False, + size: int = 0, + num: int = 0, + duration: int = 0, + date: datetime = datetime.now(), ): self.site_name: str = site_name self.official: bool = official @@ -69,8 +69,6 @@ class ContractCheck(_PluginBase): self.date: datetime = date # 私有属性 - sites = None - siteoper = None statistic_sites: list = [] contract_infos: list[ContractInfo] = [] _scheduler: Optional[BackgroundScheduler] = None @@ -87,8 +85,7 @@ class ContractCheck(_PluginBase): _dashboard_type: str = "brief" def init_plugin(self, config: dict = None): - self.sites = SitesHelper() - self.siteoper = SiteOper() + # 停止现有任务 self.stop_service() # 配置 @@ -123,8 +120,7 @@ class ContractCheck(_PluginBase): self._scheduler.add_job( self.refresh_all_site_data, "date", - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) - + timedelta(seconds=3), + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), ) # 关闭一次性开关 self._onlyonce = False @@ -162,7 +158,7 @@ class ContractCheck(_PluginBase): self.statistic_sites.append(site_id) def _get_site_id(self, name): - all_sites = [site for site in self.siteoper.list_order_by_pri()] + [ + all_sites = [site for site in SiteOper().list_order_by_pri()] + [ site for site in self.__custom_sites() ] for site in all_sites: @@ -505,9 +501,7 @@ class ContractCheck(_PluginBase): } ] - def get_dashboard( - self, - ) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + def get_dashboard(self, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: """ 获取插件仪表盘页面,需要返回:1、仪表板col配置字典;2、全局配置(自动刷新等);3、仪表板页面元素配置json(含数据) 1、col配置参考: @@ -783,7 +777,7 @@ class ContractCheck(_PluginBase): i = html_text.find("window.location") if i == -1: return None - tmp_url = url + html_text[i : html_text.find(";")].replace( + tmp_url = url + html_text[i: html_text.find(";")].replace( '"', "" ).replace("+", "").replace(" ", "").replace( "window.location=", "" @@ -793,8 +787,8 @@ class ContractCheck(_PluginBase): ).get_res(url=tmp_url) if res and res.status_code == 200: if ( - "charset=utf-8" in res.text - or "charset=UTF-8" in res.text + "charset=utf-8" in res.text + or "charset=UTF-8" in res.text ): res.encoding = "UTF-8" else: @@ -819,7 +813,7 @@ class ContractCheck(_PluginBase): ).get_res(url=url + "/index.php") if res and res.status_code == 200: if re.search( - r"charset=\"?utf-8\"?", res.text, re.IGNORECASE + r"charset=\"?utf-8\"?", res.text, re.IGNORECASE ): res.encoding = "utf-8" else: @@ -975,7 +969,7 @@ class ContractCheck(_PluginBase): """ 多线程刷新站点下载上传量,默认间隔6小时 """ - if not self.sites.get_indexers(): + if not SitesHelper().get_indexers(): return logger.info("开始刷新站点数据 ...") @@ -983,8 +977,8 @@ class ContractCheck(_PluginBase): with lock: all_sites = [ - site for site in self.sites.get_indexers() if not site.get("public") - ] + self.__custom_sites() + site for site in SitesHelper().get_indexers() if not site.get("public") + ] + self.__custom_sites() # 没有指定站点,默认使用全部站点 if not self.statistic_sites: refresh_sites = all_sites diff --git a/plugins/contractcheck/siteuserinfo/__init__.py b/plugins/contractcheck/siteuserinfo/__init__.py index 07b4fed..eec076d 100644 --- a/plugins/contractcheck/siteuserinfo/__init__.py +++ b/plugins/contractcheck/siteuserinfo/__init__.py @@ -70,6 +70,7 @@ class ISiteUserInfo(metaclass=ABCMeta): "听听歌": ["TTG", "WiKi", "DoA", "NGB", "ARiN"], "馒头": ["MTeam", "MTeamTV"], "朋友": ["FRDS"], + "猪猪": ["PigoHD","PigoWeb","PigoNF"] } # 错误信息 diff --git a/plugins/customindexer/__init__.py b/plugins/customindexer/__init__.py index c542311..465c45b 100644 --- a/plugins/customindexer/__init__.py +++ b/plugins/customindexer/__init__.py @@ -28,12 +28,11 @@ class CustomIndexer(_PluginBase): auth_level = 2 # 私有属性 - siteshelper = None _enabled = False _confstr = "" def init_plugin(self, config: dict = None): - self.siteshelper = SitesHelper() + # 读取配置 if config: self._enabled = config.get("enabled") @@ -49,7 +48,7 @@ class CustomIndexer(_PluginBase): if not domain or not jsonstr: continue jsonstr = base64.b64decode(jsonstr).decode('utf-8') - self.siteshelper.add_indexer(domain, json.loads(jsonstr)) + SitesHelper().add_indexer(domain, json.loads(jsonstr)) except Exception as err: logger.error(f"自定义索引站点配置错误:{err}") self.systemmessage.put(f"自定义索引站点配置错误:{err}", title="自定义索引站点") diff --git a/plugins/downloadingmsg/__init__.py b/plugins/downloadingmsg/__init__.py index 130d787..171f8ce 100644 --- a/plugins/downloadingmsg/__init__.py +++ b/plugins/downloadingmsg/__init__.py @@ -37,10 +37,8 @@ class DownloadingMsg(_PluginBase): _seconds = None _type = None _adminuser = None - _downloadhis = None def init_plugin(self, config: dict = None): - self._downloadhis = DownloadHistoryOper() # 停止现有任务 self.stop_service() @@ -71,8 +69,9 @@ class DownloadingMsg(_PluginBase): if self._type == "user" or self._type == "both": user_torrents = {} # 根据正在下载种子hash获取下载历史 + _downloadhis = DownloadHistoryOper() for torrent in torrents: - downloadhis = self._downloadhis.get_by_hash(download_hash=torrent.hash) + downloadhis = _downloadhis.get_by_hash(download_hash=torrent.hash) if not downloadhis: logger.warn(f"种子 {torrent.hash} 未获取到MoviePilot下载历史,无法推送下载进度") continue @@ -115,13 +114,14 @@ class DownloadingMsg(_PluginBase): messages = [] index = 1 channel_value = None + _downloadhis = DownloadHistoryOper() for torrent in torrents: year = None name = None se = None ep = None # 先查询下载记录,没有再识别 - downloadhis = self._downloadhis.get_by_hash(download_hash=torrent.hash) + downloadhis = _downloadhis.get_by_hash(download_hash=torrent.hash) if downloadhis: name = downloadhis.title year = downloadhis.year diff --git a/plugins/episodegroupmeta/__init__.py b/plugins/episodegroupmeta/__init__.py index 20c7b6c..f15bd07 100644 --- a/plugins/episodegroupmeta/__init__.py +++ b/plugins/episodegroupmeta/__init__.py @@ -23,6 +23,7 @@ from app.utils.common import retry from app.utils.http import RequestUtils from app.db.models import PluginData + class ExistMediaInfo(BaseModel): # 季, 集 groupep: Optional[Dict[int, list]] = {} @@ -522,7 +523,7 @@ class EpisodeGroupMeta(_PluginBase): } } ] - + return [ { 'component': 'VRow', @@ -628,7 +629,7 @@ class EpisodeGroupMeta(_PluginBase): time.sleep(int(self._delay)) # 开始处理 if self.start_rt(mediainfo=mediainfo, episode_groups=episode_groups): - # 处理完成时, 属于自动匹配的, 发送通知 + # 处理完成时, 属于自动匹配的, 发送通知 if self._notify and mediainfo_dict: self.post_message( mtype=schemas.NotificationType.Manual, @@ -673,8 +674,10 @@ class EpisodeGroupMeta(_PluginBase): self.log_warn(f"{mediainfo.title_year} 在媒体库 {server} 中没有数据") continue else: - self.log_info(f"{mediainfo.title_year} 在媒体库 {existsinfo.server} 中找到了这些季集:{existsinfo.groupep}") - _bool = self.__start_rt_mediaserver(mediainfo=mediainfo, existsinfo=existsinfo, episode_groups=episode_groups, group_id=group_id) + self.log_info( + f"{mediainfo.title_year} 在媒体库 {existsinfo.server} 中找到了这些季集:{existsinfo.groupep}") + _bool = self.__start_rt_mediaserver(mediainfo=mediainfo, existsinfo=existsinfo, + episode_groups=episode_groups, group_id=group_id) relust_bool = relust_bool or _bool else: # v2版本 遍历所有媒体服务器的方式 @@ -696,8 +699,11 @@ class EpisodeGroupMeta(_PluginBase): self.log_warn(f"{mediainfo.title_year} 在 ({info.type}){name} 媒体服务器中没有数据") continue else: - self.log_info(f"{mediainfo.title_year} 在媒体库 ({existsinfo.server_type}){existsinfo.server} 中找到了这些季集:{existsinfo.groupep}") - _bool = self.__start_rt_mediaserver(mediainfo=mediainfo, existsinfo=existsinfo, episode_groups=episode_groups, group_id=group_id, mediaserver_instance=info.instance) + self.log_info( + f"{mediainfo.title_year} 在媒体库 ({existsinfo.server_type}){existsinfo.server} 中找到了这些季集:{existsinfo.groupep}") + _bool = self.__start_rt_mediaserver(mediainfo=mediainfo, existsinfo=existsinfo, + episode_groups=episode_groups, group_id=group_id, + mediaserver_instance=info.instance) relust_bool = relust_bool or _bool return relust_bool @@ -762,7 +768,8 @@ class EpisodeGroupMeta(_PluginBase): ep_num = ep[_index] for _id in _ids: # 获取媒体服务器媒体项 - iteminfo = self.get_iteminfo(server_type=existsinfo.server_type, itemid=_id, mediaserver_instance=mediaserver_instance) + iteminfo = self.get_iteminfo(server_type=existsinfo.server_type, itemid=_id, + mediaserver_instance=mediaserver_instance) if not iteminfo: self.log_info(f"未找到媒体项 - itemid: {_id}, 第 {order} 季, 第 {ep_num} 集") continue @@ -771,7 +778,8 @@ class EpisodeGroupMeta(_PluginBase): if iteminfo.get("LockData") or ( "Name" in iteminfo.get("LockedFields", []) and "Overview" in iteminfo.get("LockedFields", [])): - self.log_warn(f"已锁定媒体项 - itemid: {_id}, 第 {order} 季, 第 {ep_num} 集, 如果需要刮削请打开设置中的“锁定的剧集也刮削”选项") + self.log_warn( + f"已锁定媒体项 - itemid: {_id}, 第 {order} 季, 第 {ep_num} 集, 如果需要刮削请打开设置中的“锁定的剧集也刮削”选项") continue # 替换项目数据 episode = episodes[ep_num - 1] @@ -789,7 +797,8 @@ class EpisodeGroupMeta(_PluginBase): self.__append_to_list(new_dict["LockedFields"], "Name") self.__append_to_list(new_dict["LockedFields"], "Overview") # 更新数据 - self.set_iteminfo(server_type=existsinfo.server_type, itemid=_id, iteminfo=new_dict, mediaserver_instance=mediaserver_instance) + self.set_iteminfo(server_type=existsinfo.server_type, itemid=_id, iteminfo=new_dict, + mediaserver_instance=mediaserver_instance) # still_path 图片 if episode.get("still_path"): self.set_item_image(server_type=existsinfo.server_type, itemid=_id, @@ -812,7 +821,8 @@ class EpisodeGroupMeta(_PluginBase): if item not in list: list.append(item) - def __media_exists(self, mediainfo: schemas.MediaInfo, server: str, server_type: str, mediaserver_instance: Any = None) -> ExistMediaInfo: + def __media_exists(self, mediainfo: schemas.MediaInfo, server: str, server_type: str, + mediaserver_instance: Any = None) -> ExistMediaInfo: """ 根据媒体信息,返回是否存在于指定媒体服务器中,剧集列表与剧集ID列表 :param mediainfo: 媒体信息 @@ -825,14 +835,14 @@ class EpisodeGroupMeta(_PluginBase): try: instance = mediaserver_instance or self.emby res = instance.get_data(("[HOST]emby/Items?" - "IncludeItemTypes=Series" - "&Fields=ProductionYear" - "&StartIndex=0" - "&Recursive=true" - "&SearchTerm=%s" - "&Limit=10" - "&IncludeSearchTypes=false" - "&api_key=[APIKEY]") % mediainfo.title) + "IncludeItemTypes=Series" + "&Fields=ProductionYear" + "&StartIndex=0" + "&Recursive=true" + "&SearchTerm=%s" + "&Limit=10" + "&IncludeSearchTypes=false" + "&api_key=[APIKEY]") % mediainfo.title) res_items = res.json().get("Items") if res_items: for res_item in res_items: @@ -893,9 +903,9 @@ class EpisodeGroupMeta(_PluginBase): try: instance = mediaserver_instance or self.jellyfin res = instance.get_data(url=f"[HOST]Users/[USER]/Items?api_key=[APIKEY]" - f"&searchTerm={mediainfo.title}" - f"&IncludeItemTypes=Series" - f"&Limit=10&Recursive=true") + f"&searchTerm={mediainfo.title}" + f"&IncludeItemTypes=Series" + f"&Limit=10&Recursive=true") res_items = res.json().get("Items") if res_items: for res_item in res_items: @@ -958,14 +968,14 @@ class EpisodeGroupMeta(_PluginBase): return None # 根据标题和年份模糊搜索,该结果不够准确 videos = _plex.library.search(title=mediainfo.title, - year=mediainfo.year, - libtype="show") + year=mediainfo.year, + libtype="show") if (not videos and mediainfo.original_title and str(mediainfo.original_title) != str(mediainfo.title)): videos = _plex.library.search(title=mediainfo.original_title, - year=mediainfo.year, - libtype="show") + year=mediainfo.year, + libtype="show") if not videos: return None if isinstance(videos, list): @@ -1323,7 +1333,7 @@ class EpisodeGroupMeta(_PluginBase): self.jellyfin = Jellyfin() return None - services = self.mediaserver_helper.get_services(type_filter=type_filter)#, name_filters=self._mediaservers) + services = self.mediaserver_helper.get_services(type_filter=type_filter) #, name_filters=self._mediaservers) if not services: self.log_warn("获取媒体服务器实例失败,请检查配置") return None diff --git a/plugins/neodbsync/__init__.py b/plugins/neodbsync/__init__.py index bece8a9..9154e21 100644 --- a/plugins/neodbsync/__init__.py +++ b/plugins/neodbsync/__init__.py @@ -9,7 +9,6 @@ from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from app.chain.download import DownloadChain -from app.chain.search import SearchChain from app.chain.subscribe import SubscribeChain from app.core.config import settings from app.core.event import Event @@ -49,9 +48,6 @@ class NeoDBSync(_PluginBase): _scheduler: Optional[BackgroundScheduler] = None _cache_path: Optional[Path] = None - downloadchain = None - searchchain = None - subscribechain = None # 配置属性 _enabled: bool = False @@ -64,9 +60,6 @@ class NeoDBSync(_PluginBase): _tokens: str = "" def init_plugin(self, config: dict = None): - self.downloadchain = DownloadChain() - self.searchchain = SearchChain() - self.subscribechain = SubscribeChain() # 停止现有任务 self.stop_service() @@ -510,6 +503,8 @@ class NeoDBSync(_PluginBase): logger.info(f"用户 {username} 没有想看数据") continue # 遍历该用户的所有想看条目 + downloadchain = DownloadChain() + subscribechain = SubscribeChain() for result in results: try: # Take the url as the unique identifier. For example: /movie/2fEdnxYWozPayayizQmk5M @@ -539,19 +534,19 @@ class NeoDBSync(_PluginBase): logger.warn(f'未识别到媒体信息,标题:{title}') continue # 查询缺失的媒体信息 - exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) + exist_flag, no_exists = downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) if exist_flag: logger.info(f'{mediainfo.title_year} 媒体库中已存在') else: # 添加订阅 logger.info(f'{mediainfo.title_year} 媒体库中不存在或不完整,添加订阅 ...') - self.subscribechain.add(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=meta.begin_season, - exist_ok=True, - username="NeoDB 想看") + subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + exist_ok=True, + username="NeoDB 想看") action = "subscribe" # 存储历史记录 history.append({ diff --git a/plugins/ntfymsg/__init__.py b/plugins/ntfymsg/__init__.py index c005233..12af771 100644 --- a/plugins/ntfymsg/__init__.py +++ b/plugins/ntfymsg/__init__.py @@ -1,3 +1,4 @@ +import base64 import json import requests @@ -15,10 +16,20 @@ class NtfyClient: headers = { "Title": title.encode(encoding='utf-8'), "Markdown": "true" if format_as_markdown else "false", + "Icon": "https://movie-pilot.org/images/logo.png", } + if self._token: + headers["Authorization"] = "Bearer " + self._token + elif self._user and self._password: + authStr = self._user + ":" + self._password + headers["Authorization"] = "Basic " + base64.b64encode(authStr.encode('utf-8')).decode('utf-8') + + if self._actions: + headers["Actions"] = self._actions.encode('utf-8') + response = json.loads( - requests.post(url=self.url, data=message.encode(encoding='utf-8'), headers=headers, auth=self._auth).text + requests.post(url=self.url, data=message.encode(encoding='utf-8'), headers=headers).text ) return response @@ -28,11 +39,16 @@ class NtfyClient: server: str = "https://ntfy.sh", user: str = "", password: str = "", + token: str = "", + actions: str = "", ): self._server = server self._topic = topic self.__set_url(server, topic) - self._auth = (user, password) + self._user = user + self._password = password + self._token = token + self._actions = actions def __set_url(self, server, topic): self.url = server.strip("/") + "/" + topic @@ -46,7 +62,7 @@ class NtfyMsg(_PluginBase): # 插件图标 plugin_icon = "Ntfy_A.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "lethargicScribe" # 作者主页 @@ -64,6 +80,8 @@ class NtfyMsg(_PluginBase): _topic = None _user = None _password = None + _token = None + _actions = None _msgtypes = [] def init_plugin(self, config: dict = None): @@ -74,6 +92,8 @@ class NtfyMsg(_PluginBase): self._topic = config.get("topic") self._user = config.get("user") self._password = config.get("password") + self._token = config.get("token") + self._actions = config.get("actions") def get_state(self) -> bool: return self._enabled and (True if self._server and self._topic else False) @@ -194,6 +214,45 @@ class NtfyMsg(_PluginBase): } ] }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'token', + 'label': '访问令牌', + 'placeholder': 'ntfytoken', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'actions', + 'label': '用户动作', + 'placeholder': 'ntfyactions', + } + } + ] + } + ] + }, { 'component': 'VRow', 'content': [ @@ -217,6 +276,48 @@ class NtfyMsg(_PluginBase): } ] }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': '用户或Token创建参考:https://docs.ntfy.sh/config/#access-control' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': '用户动作创建参考:https://docs.ntfy.sh/publish/?h=action#using-a-header' + } + } + ] + } + ] + }, ] } ], { @@ -226,6 +327,8 @@ class NtfyMsg(_PluginBase): 'topic': 'MoviePilot', 'user': '', 'password': '', + 'token': '', + 'actions': '', } def get_page(self) -> List[dict]: @@ -266,7 +369,11 @@ class NtfyMsg(_PluginBase): try: if not self._server or not self._topic: return False, "参数未配置" - ntfy = NtfyClient(server=self._server, topic=self._topic, user=self._user, password=self._password) + ntfy = NtfyClient( + server=self._server, topic=self._topic, + user=self._user, password=self._password, + token=self._token, actions=self._actions + ) ntfy.send(title=title, message=text, format_as_markdown=True) except Exception as msg_e: diff --git a/plugins/removelink/__init__.py b/plugins/removelink/__init__.py index f34419b..173f2a8 100644 --- a/plugins/removelink/__init__.py +++ b/plugins/removelink/__init__.py @@ -139,14 +139,12 @@ class RemoveLink(_PluginBase): _delete_scrap_infos = False _delete_torrents = False _delete_history = False - _transferhistory = None _observer = [] # 监控目录的文件列表 state_set: Dict[str, int] = {} def init_plugin(self, config: dict = None): logger.info(f"Hello, RemoveLink! config {config}") - self._transferhistory = TransferHistoryOper() if config: self._enabled = config.get("enabled") self._notify = config.get("notify") @@ -491,10 +489,11 @@ class RemoveLink(_PluginBase): if not self._delete_history: return # 查找历史记录 - transfer_history = self._transferhistory.get_by_src(path) + _transferhistory = TransferHistoryOper() + transfer_history = _transferhistory.get_by_src(path) if transfer_history: # 删除历史记录 - self._transferhistory.delete(transfer_history.id) + _transferhistory.delete(transfer_history.id) logger.info(f"删除历史记录:{transfer_history.id}") def delete_empty_folders(self, path): diff --git a/plugins/siterefresh/__init__.py b/plugins/siterefresh/__init__.py index 48ccfe6..4292c5e 100644 --- a/plugins/siterefresh/__init__.py +++ b/plugins/siterefresh/__init__.py @@ -29,9 +29,6 @@ class SiteRefresh(_PluginBase): # 可使用的用户级别 auth_level = 2 - # 私有属性 - siteoper: SiteOper = None - # 配置属性 _enabled: bool = False _notify: bool = False @@ -42,7 +39,7 @@ class SiteRefresh(_PluginBase): _siteconf: list = [] def init_plugin(self, config: dict = None): - self.siteoper = SiteOper() + # 配置 if config: self._enabled = config.get("enabled") @@ -70,7 +67,7 @@ class SiteRefresh(_PluginBase): logger.error(f"未获取到site_id") return - site = self.siteoper.get(site_id) + site = SiteOper().get(site_id) if not site: logger.error(f"未获取到site_id {site_id} 对应的站点数据") return diff --git a/plugins/tmdbwallpaper/__init__.py b/plugins/tmdbwallpaper/__init__.py index 86f3e06..7a6ac13 100644 --- a/plugins/tmdbwallpaper/__init__.py +++ b/plugins/tmdbwallpaper/__init__.py @@ -1,16 +1,16 @@ from datetime import datetime, timedelta from pathlib import Path from typing import Any, List, Dict, Tuple +from urllib.parse import urlparse import pytz from apscheduler.schedulers.background import BackgroundScheduler -from app.chain.tmdb import TmdbChain from app.core.config import settings +from app.helper.wallpaper import WallpaperHelper from app.log import logger from app.plugins import _PluginBase from app.utils.http import RequestUtils -from app.utils.web import WebUtils class TmdbWallpaper(_PluginBase): @@ -21,7 +21,7 @@ class TmdbWallpaper(_PluginBase): # 插件图标 plugin_icon = "Macos_Sierra.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.4" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -237,13 +237,19 @@ class TmdbWallpaper(_PluginBase): if not self._savepath: return - if settings.WALLPAPER == "tmdb": - urls = TmdbChain().get_trending_wallpapers() or [] - for url in urls: + urls = WallpaperHelper().get_wallpapers(10) or [] + for url in urls: + if settings.WALLPAPER == "tmdb": filename = url.split("/")[-1] - __save_file(url, filename) - else: - url = WebUtils.get_bing_wallpaper() - if url: - filename = f"{datetime.now().strftime('%Y%m%d')}.jpg" - __save_file(url, filename) + elif settings.WALLPAPER == "bing": + # https://cn.bing.com/th?id=OHR.EchinaceaButterfly_EN-US8404044892_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp + # 解析url参数,获取id的值 + url_params = urlparse(url) + filename = url_params.query.split("id=")[-1] + else: + # 其他壁纸类型,直接使用url的文件名 + filename = url.split("/")[-1] + # 没有后缀的文件名,添加.jpg后缀 + if not filename.endswith(".jpg"): + filename += ".jpg" + __save_file(url, filename) diff --git a/plugins/trackereditor/__init__.py b/plugins/trackereditor/__init__.py index a12e427..a1a225f 100644 --- a/plugins/trackereditor/__init__.py +++ b/plugins/trackereditor/__init__.py @@ -19,7 +19,7 @@ class TrackerEditor(_PluginBase): # 插件图标 plugin_icon = "trackereditor_A.png" # 插件版本 - plugin_version = "1.8" + plugin_version = "1.9" # 插件作者 plugin_author = "honue" # 作者主页 @@ -91,7 +91,7 @@ class TrackerEditor(_PluginBase): new_url = tracker.url.replace(target_domain, tracker_dict[target_domain]) logger.info(f"{original_url[:30]}... 替换为 {new_url[:30]}...") torrent.edit_tracker(orig_url=original_url, new_url=new_url) - torrent_update_cnt += 1 + torrent_update_cnt += 1 elif self._downloader_type == "transmission": self._downloader = Transmission(self._host, self._port, self._username, self._password)