mirror of
https://gitee.com/antv/g6.git
synced 2024-12-01 19:28:39 +08:00
fix: ci and remove issuehunt (#5277)
* fix: remove issue hunt * chore: remote pnpm-lock file, and fix lint * chore: update action * chore: fix ci * chore: remove coverall
This commit is contained in:
parent
2d02bdc0de
commit
9ff0fd9944
@ -13,7 +13,6 @@ module.exports = {
|
||||
'consistent-return': 0,
|
||||
'lines-between-class-members': 0,
|
||||
'class-methods-use-this': 0,
|
||||
'lines-between-class-members': 0,
|
||||
'no-multi-assign': 0,
|
||||
'no-continue': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
|
5
.github/FUNDING.yml
vendored
5
.github/FUNDING.yml
vendored
@ -1,5 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
open_collective: antvis
|
||||
issuehunt: antvis/G6
|
117
.github/ISSUE_TEMPLATE/issue_hunt.yml
vendored
117
.github/ISSUE_TEMPLATE/issue_hunt.yml
vendored
@ -1,117 +0,0 @@
|
||||
name: 'Issue Hunt Program'
|
||||
description: Create a report to help us improve
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: issue_hunt_program
|
||||
attributes:
|
||||
label: Issue Hunt Program (optional)
|
||||
description: |
|
||||
The Issue Hunt Program is an incentive program proposed by AntV to encourage community contributors to participate in the AntV program. For more information, you can refer to the [Issue Hunt Program](https://github.com/antvis/G6/blob/master/ISSUEHUNT.en-US.md).
|
||||
Issues submitted through the Issue Hunt Program are treated the same as regular issues. Regular issues can also be included in the Issue Hunt Program, but they need to be reviewed by the AntV team.
|
||||
options:
|
||||
- label: This issue will participate in the Issue Hunt Program
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: issue_hunt_difficulty
|
||||
attributes:
|
||||
label: Issue Difficulty
|
||||
options:
|
||||
- Low
|
||||
- Medium
|
||||
- High
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: bounty_description
|
||||
attributes:
|
||||
label: Bounty
|
||||
description: |
|
||||
If you are not a program maintainer and are willing to offer a bounty for this issue, you can provide a bounty description here and provide the bounty amount on [IssueHunt](https://oss.issuehunt.io/r/antvis/G6/issues) after submitting the issue.
|
||||
The platform may charge a certain fee, which is unrelated to AntV.
|
||||
placeholder: |
|
||||
Example: I will pay $100 if you can resolve this issue within 24 hours.
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for reporting an issue :pray:.
|
||||
|
||||
This issue tracker is for reporting bugs found in G6 (https://github.com/antvis/G6).
|
||||
If you have a question about how to achieve something and are struggling, please post a question
|
||||
inside of G6's Discussion's tab: https://github.com/antvis/G6/discussions
|
||||
|
||||
Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already:
|
||||
- G6's Issue's tab: https://github.com/antvis/G6/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc
|
||||
- G6's closed issues tab: https://github.com/antvis/G6/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed
|
||||
- G6 Discussion's tab: https://github.com/antvis/G6/discussions
|
||||
|
||||
The more information you fill in, the better the community can help you.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Provide a clear and concise description of the challenge you are running into.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: link
|
||||
attributes:
|
||||
label: Your Example Website or App
|
||||
description: |
|
||||
Which website or app were you using when the bug happened?
|
||||
Note:
|
||||
- Your bug will may get fixed much faster if we can run your code and it doesn't have dependencies other than the G6 npm package.
|
||||
- To create a shareable code example you can use Stackblitz (https://stackblitz.com/) or CodeSandbox (https://codesandbox.io/s/new). Please no localhost URLs.
|
||||
- Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve.
|
||||
placeholder: |
|
||||
e.g. Stackblitz, Code Sandbox app url
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to Reproduce the Bug or Issue
|
||||
description: Describe the steps we have to take to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: Provide a clear and concise description of what you expected to happen.
|
||||
placeholder: |
|
||||
As a user, I expected ___ behavior but i am seeing ___
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots_or_videos
|
||||
attributes:
|
||||
label: Screenshots or Videos
|
||||
description: |
|
||||
If applicable, add screenshots or a video to help explain your problem.
|
||||
For more information on the supported file image/file types and the file size limits, please refer
|
||||
to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files
|
||||
placeholder: |
|
||||
You can drag your video or image files inside of this editor ↓
|
||||
- type: textarea
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
value: |
|
||||
- OS: [e.g. macOS, Windows, Linux]
|
||||
- Browser: [e.g. Chrome, Safari, Firefox]
|
||||
- Version: [e.g. 91.1]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
101
.github/ISSUE_TEMPLATE/issue_hunt_chinese.yml
vendored
101
.github/ISSUE_TEMPLATE/issue_hunt_chinese.yml
vendored
@ -1,101 +0,0 @@
|
||||
name: 'Issue Hunt 计划'
|
||||
description: 创建一个新的 issue,如果你的 issue 不符合规范,它将会被自动关闭。
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: issue_hunt_program
|
||||
attributes:
|
||||
label: Issue Hunt 计划(可选)
|
||||
description: |
|
||||
Issue Hunt 计划是 AntV 为了鼓励社区贡献者参与到 AntV 项目中来,提出的一个激励计划。如果想了解更多,可以查看 [Issue Hunt 计划](https://github.com/antvis/G6/blob/master/ISSUEHUNT.md)
|
||||
通过 Issue Hunt 计划提出的 issue 与普通 issue 无异。普通 issue 也可加入 Issue Hunt 计划,但需要经过 AntV 团队审核。
|
||||
options:
|
||||
- label: 我同意将这个 Issue 参与 Issue Hunt 计划
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: issue_hunt_difficulty
|
||||
attributes:
|
||||
label: Issue 难度
|
||||
options:
|
||||
- 低难度
|
||||
- 中等难度
|
||||
- 高难度
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: bounty_description
|
||||
attributes:
|
||||
label: 悬赏
|
||||
description: |
|
||||
如果您非项目维护者,并愿意对该 issue 悬赏,可以在此处提供悬赏描述,并在提交 Issue 后在 [IssueHunt](https://oss.issuehunt.io/r/antvis/G6/issues) 提供悬赏金额。
|
||||
平台可能会收取一定的手续费,这与 AntV 无关。
|
||||
placeholder: |
|
||||
例如:如果您能够在 24 小时内解决此问题,我将支付 $100。
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
在提交新 issue 之前,先通过以下链接查看有没有类似的 bug 或者建议:
|
||||
- [G6 Issues](https://github.com/antvis/G6/issues)
|
||||
- [G6 Closed Issues](https://github.com/antvis/G6/issues?q=is%3Aissue+is%3Aclosed)
|
||||
- [G6 Discussions](https://github.com/antvis/G6/discussions)
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 简洁清晰地描述你遇到的问题。
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: link
|
||||
attributes:
|
||||
label: 重现链接
|
||||
description: |
|
||||
可以使用 CodeSandbox(https://codesandbox.io/s/new) 或者 StackBlitz(https://stackblitz.com/) 重现你的问题。
|
||||
placeholder: |
|
||||
示例: CodeSandBox 或者 StackBlitz URL
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: 重现步骤
|
||||
description: 简洁清晰的重现步骤能够帮助我们更迅速地定位问题所在。
|
||||
placeholder: |
|
||||
1.进入页面...
|
||||
2.点击....
|
||||
3.查看错误....
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 预期行为
|
||||
description: 描述你期望的结果以及实际的结果。
|
||||
placeholder: |
|
||||
我期望看到...,但我看到了...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
value: |
|
||||
- 操作系统: [macOS, Windows, Linux, React Native ...]
|
||||
- 网页浏览器: [Google Chrome, Safari, Firefox]
|
||||
- 版本: [例如 91.1]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots_or_videos
|
||||
attributes:
|
||||
label: 屏幕截图或视频(可选)
|
||||
description: 可以添加屏幕截图或视频帮助你解释问题。
|
||||
placeholder: |
|
||||
可以将你的图片或者视频拖拽到此处↓
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 补充说明(可选)
|
||||
description: 比如:遇到这个 bug 的业务场景、上下文。
|
53
.github/workflows/build.yml
vendored
Normal file
53
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
node_modules/
|
||||
packages/g6/node_modules/
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Run CI
|
||||
run: |
|
||||
# pnpm run lint
|
||||
# pnpm run test
|
||||
pnpm run build
|
||||
|
||||
# - name: coverall
|
||||
# if: success()
|
||||
# uses: coverallsapp/github-action@master
|
||||
# with:
|
||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
45
.github/workflows/issue_hunt_replay.yml
vendored
45
.github/workflows/issue_hunt_replay.yml
vendored
@ -1,45 +0,0 @@
|
||||
name: Auto Reply to Issue Hunt Program
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
auto-reply:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Comment on Issue
|
||||
if: ${{ github.event.label.name == 'Reward/悬赏' }}
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
请复制以下模板,填写相关信息,然后删除模板中的注释。
|
||||
Please copy the following template, fill in the relevant information, and then delete the comments in the template.
|
||||
```template
|
||||
|
||||
## Issue 认领 / Issue Claim
|
||||
|
||||
**贡献者/Contributor**
|
||||
|
||||
> 例如:张三, 李四
|
||||
> For example: John Smith, Sarah Johnson
|
||||
|
||||
**预计完成时间/Estimated Completion Date**
|
||||
|
||||
> 例如:2023-06-06
|
||||
> For example: May 30, 2023
|
||||
|
||||
**联系方式/Contact Information**
|
||||
|
||||
> 例如:contact@email.com
|
||||
> For example: contact@email.com
|
||||
|
||||
**其他信息/Additional Information**
|
||||
|
||||
> 例如:这个 issue 比预期的要复杂,我希望能获得更多的奖励
|
||||
> For example: This issue is more complex than expected, and I hope to receive more reward.
|
||||
|
||||
```
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ es
|
||||
|
||||
# lock
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
@ -1,19 +0,0 @@
|
||||
# 不要修改该文件, 会自动生成, 详见 http://gitlab.alibaba-inc.com/node/ci
|
||||
# 不要使用 ci 生成,直接按照 gitlab 文档自己写吧!
|
||||
before_script:
|
||||
- export PATH=$PWD/node_modules/.bin:$PWD/.node/bin:$PATH
|
||||
- time enclose install tnpm:tnpm
|
||||
- tnpm -v
|
||||
stages:
|
||||
- test
|
||||
|
||||
# job1
|
||||
test:
|
||||
image: reg.docker.alibaba-inc.com/dockerlab/node-ci:3.2.0
|
||||
stage: test
|
||||
script:
|
||||
- env -u CI tnpm i --silent --internal-oss-cache --install-node=10
|
||||
# - tnpm run ci ci 环境问题导致 electron 安装失败暂时跑不了
|
||||
- tnpm run lint
|
||||
tags:
|
||||
- swarm
|
30
.travis.yml
30
.travis.yml
@ -1,30 +0,0 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- '12'
|
||||
|
||||
env:
|
||||
- NODE_ENV=test
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- xvfb
|
||||
- libgconf-2-4
|
||||
|
||||
install:
|
||||
- export DISPLAY=':99.0'
|
||||
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
|
||||
- yarn
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ "$TEST_TYPE" = test ]; then
|
||||
yarn ci && bash <(curl -s https://codecov.io/bash)
|
||||
else
|
||||
yarn $TEST_TYPE
|
||||
fi
|
||||
env:
|
||||
matrix:
|
||||
- TEST_TYPE=lint
|
||||
- TEST_TYPE=test
|
@ -1,75 +0,0 @@
|
||||
# Issue Hunt Program
|
||||
|
||||
## What is the Issue Hunt Program?
|
||||
|
||||
The Issue Hunt Program is an incentive program proposed by AntV to encourage community contributors to participate in the AntV project.
|
||||
|
||||
## How to Participate in Issue Hunt?
|
||||
|
||||
1. Find an issue that interests you. (issue with 'Reward/悬赏' tag)
|
||||
2. Contribute code to the issue.
|
||||
3. Earn rewards.
|
||||
|
||||
## How to Find Issues You Like?
|
||||
|
||||
- Find issues that interest you on [Issue Hunt](https://oss.issuehunt.io/r/antvis/G6/issues)
|
||||
- Find issues you like on [GitHub](https://github.com/antvis/G6/issues?q=is%3Aopen+is%3Aissue+label%3AReward%2F%E6%82%AC%E8%B5%8F)
|
||||
|
||||
## Set Rewards for Issues
|
||||
|
||||
Both project maintainers and community contributors can set rewards for issues.
|
||||
|
||||
If you are a project maintainer, you can set the reward amount for an issue by using a label. For example:
|
||||
|
||||
`Reward/悬赏`, `$10` means the reward amount for this issue is $10.
|
||||
|
||||
If you are a community contributor, you can reward the issue **you proposed** or the issue **you want to be resolved** on [Issue Hunt](https://oss.issuehunt.io/r/antvis/G6/issues).
|
||||
|
||||
> ⚠️ Issue Hunt platform may charge a certain fee.。
|
||||
|
||||
## Reward Amounts
|
||||
|
||||
The reward amounts for project maintainers are divided into three tiers:
|
||||
|
||||
- **Low Difficulty**:`$0.5` - `$5`
|
||||
- Simple question replies, documentation modifications, issue resolutions, new feature development
|
||||
- Workload within 2 hours (Evaluated by the project maintainer)
|
||||
- **Medium Difficulty**:`$5` - `$10`
|
||||
- Requires some code modifications
|
||||
- Workload within 2 day
|
||||
- **High Difficulty**:`$10` - `$50` (no upper limit in principle)
|
||||
- Requires extensive code modifications
|
||||
- Fixes critical and urgent issues
|
||||
- Complex new feature development
|
||||
- Workload within 2 week
|
||||
|
||||
## Reward Distribution
|
||||
|
||||
If a reward is initiated through the Issue Hunt platform, Issue Hunt will distribute the reward amount to the contributor's account within 7 days after the issue is closed.
|
||||
|
||||
If a reward is initiated by a project maintainer through GitHub, we will distribute rewards to closed issues based on the [Milestone](https://github.com/antvis/G6/milestones) schedule. Prior to distribution, we will contact the contributor to confirm the reward amount and distribution method.
|
||||
|
||||
## Issue Claiming
|
||||
|
||||
To avoid multiple people claiming the same issue simultaneously, you can leave a comment below an issue marked with `Reward/悬赏`. The project maintainer will reply within 24 hours to confirm if you can claim the issue and add the `Claimed/已认领` label to the issue.
|
||||
|
||||
Your claiming comment should include the following information (copy and edit the template to reply):
|
||||
|
||||
```template
|
||||
|
||||
## Issue Claiming
|
||||
|
||||
**Contributors**
|
||||
> For example: John Smith, Sarah Johnson
|
||||
|
||||
**Estimated Completion Date**
|
||||
> For example: May 30, 2023
|
||||
|
||||
**Contact Information**
|
||||
> For example: contact@email.com
|
||||
|
||||
**Additional Information**
|
||||
> For example: This issue is more complex than expected, and I hope to receive more reward.
|
||||
|
||||
|
||||
```
|
74
ISSUEHUNT.md
74
ISSUEHUNT.md
@ -1,74 +0,0 @@
|
||||
# Issue Hunt 计划
|
||||
|
||||
## 什么是 Issue Hunt 计划?
|
||||
|
||||
Issue Hunt 计划是 AntV 为了鼓励社区贡献者参与到 AntV 项目中来,提出的一个激励计划。
|
||||
|
||||
## 如何参与 Issue Hunt?
|
||||
|
||||
1. 找到你感兴趣的 issue (带有 `Reward/悬赏` 标签的 issue)
|
||||
2. 为 issue 贡献代码
|
||||
3. 获得奖励
|
||||
|
||||
## 如何找到你喜欢的 Issues?
|
||||
|
||||
1. 从 [Issue Hunt](https://oss.issuehunt.io/r/antvis/G6/issues) 上找到你感兴趣的 issues
|
||||
2. 从 [GitHub](https://github.com/antvis/G6/issues?q=is%3Aopen+is%3Aissue+label%3AReward%2F%E6%82%AC%E8%B5%8F) 上找到你喜欢的 issues
|
||||
|
||||
## 为 Issues 设置悬赏
|
||||
|
||||
项目维护者和社区人员都可以为 issue 设置悬赏。
|
||||
|
||||
如果你是项目维护者,你可以在 Issue 上以 Label 的形式设置奖励金额,例如:
|
||||
|
||||
`Reward/悬赏`, `$10` 表示这个 Issue 的奖励金额为 10 美元。
|
||||
|
||||
如果你是社区工作者,你可以到[Issue Hunt](https://oss.issuehunt.io/r/antvis/G6/issues)上为**你提出的**/**你希望尽快解决的** issue 进行悬赏。
|
||||
|
||||
> ⚠️ Issue Hunt 平台可能会收取一定的手续费。
|
||||
|
||||
## 激励金额
|
||||
|
||||
项目维护者的悬赏金额分为三档:
|
||||
|
||||
- **低难度**:`$0.5` - `$5`
|
||||
- 简单的问题答复、文档修改、问题解决、新特性开发
|
||||
- 工作量在 2 小时以内(由项目维护者评估)
|
||||
- **中等难度**:`$5` - `$10`
|
||||
- 需要一定的代码修改
|
||||
- 工作量在 2 天以内
|
||||
- **高难度**:`$10` - `$50` (原则上不设上限)
|
||||
- 需要大量的代码修改
|
||||
- 严重且紧急的问题修复
|
||||
- 复杂的新特性开发
|
||||
- 工作量在 2 周以内
|
||||
|
||||
## 激励发放
|
||||
|
||||
如果是通过 Issue Hunt 平台发起的悬赏,Issue Hunt 会在 issue 被关闭后 7 天内将奖励金额发放到贡献者的账户上。
|
||||
|
||||
如果是项目维护者通过 GitHub 发起的悬赏,我们会根据 [Milestone](https://github.com/antvis/G6/milestones) 的周期对关闭的 issues 进行奖励发放。发放前我们会联系贡献者确认奖励金额及发放方式。
|
||||
|
||||
## 项目认领
|
||||
|
||||
为了避免多人同时认领同一个 issue,你可以在被标记了 `Reward/悬赏` 的 issue 下方留言,项目维护者会在 24 小时内回复你是否可以认领该 issue,并在 issue 上添加 `Claimed/已认领` 标签。
|
||||
|
||||
你的认领留言应包含以下信息(请复制后编辑回复):
|
||||
|
||||
```template
|
||||
|
||||
## Issue 认领
|
||||
|
||||
**贡献者**
|
||||
> 例如:张三, 李四
|
||||
|
||||
**预计完成时间**
|
||||
> 例如:2023-06-06
|
||||
|
||||
**联系方式**
|
||||
> 例如:contact@email.com
|
||||
|
||||
**其他信息**
|
||||
> 例如:这个 issue 比预期的要复杂,我希望能获得更多的奖励
|
||||
|
||||
```
|
14
README.md
14
README.md
@ -6,7 +6,13 @@
|
||||
|
||||
![](https://user-images.githubusercontent.com/6113694/45008751-ea465300-b036-11e8-8e2a-166cbb338ce2.png)
|
||||
|
||||
[![travis-ci](https://img.shields.io/travis/antvis/g6/master.svg)](https://travis-ci.org/antvis/g6) [![codecov](https://codecov.io/gh/antvis/G6/branch/master/graph/badge.svg)](https://codecov.io/gh/antvis/G6) ![typescript](https://img.shields.io/badge/language-typescript-red.svg) ![MIT](https://img.shields.io/badge/license-MIT-000000.svg) [![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6) [![NPM downloads](http://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6) [![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g6.svg)](http://isitmaintained.com/project/antvis/g6 'Percentage of issues still open')
|
||||
[![build](https://github.com/antvis/G6/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/antvis/G6/actions/)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/antvis/G6/badge.svg?branch=master)](https://coveralls.io/github/antvis/G6?branch=master)
|
||||
![typescript](https://img.shields.io/badge/language-typescript-red.svg)
|
||||
![MIT](https://img.shields.io/badge/license-MIT-000000.svg)
|
||||
[![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6)
|
||||
[![NPM downloads](http://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6)
|
||||
[![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g6.svg)](http://isitmaintained.com/project/antvis/g6)
|
||||
|
||||
## What is G6
|
||||
|
||||
@ -143,12 +149,6 @@ For React project integration, we have an independent product recommendation: [G
|
||||
|
||||
At present, Graphin has good practices in business graph analysis projects. For details, see [《Who uses Graphin》](https://github.com/antvis/Graphin/issues/212)
|
||||
|
||||
## Online Analysis Tool:G6VP
|
||||
|
||||
If you have a piece of relational data (graph data) and want to quickly visualize it online and analyze it efficiently, then we recommend using the official [G6VP](https://github.com/antvis/G6VP), which supports local File JSON, Excel, and graph data sources such as TuGraph, Neo4J, and GraphScope are also supported. With 60+ built-in analysis assets, graph analysis can be as simple as building blocks. The platform also provides one-click export of SDK, which can be quickly integrated into the business system
|
||||
|
||||
![G6VP Image](https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GOVySaZ1iHYAAAAAAAAAAAAADmJ7AQ/original)
|
||||
|
||||
## G6 Communication Group
|
||||
|
||||
Welcome to join the **G6 Communication Group**. We also welcome the github issues.
|
||||
|
@ -6,7 +6,13 @@
|
||||
|
||||
![](https://user-images.githubusercontent.com/6113694/45008751-ea465300-b036-11e8-8e2a-166cbb338ce2.png)
|
||||
|
||||
[![travis-ci](https://img.shields.io/travis/antvis/g6.svg)](https://travis-ci.org/antvis/g6) [![codecov](https://codecov.io/gh/antvis/G6/branch/master/graph/badge.svg)](https://codecov.io/gh/antvis/G6) ![typescript](https://img.shields.io/badge/language-typescript-red.svg) ![MIT](https://img.shields.io/badge/license-MIT-000000.svg) [![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6) [![NPM downloads](http://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6) [![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g6.svg)](http://isitmaintained.com/project/antvis/g6 'Percentage of issues still open')
|
||||
[![build](https://github.com/antvis/G6/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/antvis/G6/actions/)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/antvis/G6/badge.svg?branch=master)](https://coveralls.io/github/antvis/G6?branch=master)
|
||||
![typescript](https://img.shields.io/badge/language-typescript-red.svg)
|
||||
![MIT](https://img.shields.io/badge/license-MIT-000000.svg)
|
||||
[![npm package](https://img.shields.io/npm/v/@antv/g6.svg)](https://www.npmjs.com/package/@antv/g6)
|
||||
[![NPM downloads](http://img.shields.io/npm/dm/@antv/g6.svg)](https://npmjs.org/package/@antv/g6)
|
||||
[![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g6.svg)](http://isitmaintained.com/project/antvis/g6)
|
||||
|
||||
## 什么是 G6
|
||||
|
||||
@ -145,12 +151,6 @@ DEBUG_MODE=1 npm test -- --watch ./tests/unit/algorithm/find-path-spec
|
||||
|
||||
目前 Graphin 在商业图分析项目中均有良好的实践,具体查看[《谁在使用 Graphin》](https://github.com/antvis/Graphin/issues/212)
|
||||
|
||||
## 在线分析工具 G6VP
|
||||
|
||||
如果你有一份关系数据(图数据),想要快速在线进行可视化,并能够高效分析,那么我们推荐使用官方出品的 [G6VP](https://github.com/antvis/G6VP),它支持本地文件 JSON,Excel,也支持 TuGraph,Neo4J,GraphScope 等图数据源,内置了 60+ 的分析资产,图分析可以像搭积木一样简单。平台还提供一键导出 SDK,快速集成到业务系统中,大大降 低初始研发门槛 与 后续维护成本。
|
||||
|
||||
![G6VP Image](https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GOVySaZ1iHYAAAAAAAAAAAAADmJ7AQ/original)
|
||||
|
||||
## G6 图可视化交流群
|
||||
|
||||
欢迎各界 G6 使用者、图可视化爱好者加入 **G6 图可视化交流群** 及 **G6 图可视化交流二群**(钉钉群,使用钉钉扫一扫加入)讨论与交流。Graphin 的使用者,爱好者请加入 **Graphin's Group Chat**
|
||||
|
32
package.json
32
package.json
@ -44,34 +44,34 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.7.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"husky": "^4.2.5",
|
||||
"lint-staged": "^10.2.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^6.15.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"husky": "^4.3.8",
|
||||
"lint-staged": "^10.5.4",
|
||||
"monaco-editor": "0.29.1",
|
||||
"monaco-editor-webpack-plugin": "5.0.0",
|
||||
"normalize-url": "^7.0.3",
|
||||
"normalize-url": "^7.2.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.1.2",
|
||||
"pretty-quick": "^3.0.2",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"react-monaco-editor": "0.40.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-airbnb": "^5.11.2",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-eslint-rules": "^5.4.0",
|
||||
"typescript": "^5.3.2"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@umijs/fabric": "^2.3.1",
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@types/react": "^16.14.54",
|
||||
"@types/react-dom": "^16.9.24",
|
||||
"@umijs/fabric": "^2.14.1",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-unicorn": "^27.0.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"react-scripts": "3.1.2"
|
||||
|
@ -61,7 +61,7 @@ export default {
|
||||
|
||||
unbind(graph: IAbstractGraph) {
|
||||
const { events } = this;
|
||||
let draggable = graph.get('canvas').get('draggable');
|
||||
const draggable = graph.get('canvas').get('draggable');
|
||||
if (
|
||||
this.type === 'drag-canvas' ||
|
||||
this.type === 'brush-select' ||
|
||||
|
@ -346,7 +346,7 @@ const singleNode: ShapeOptions = {
|
||||
name,
|
||||
});
|
||||
} else {
|
||||
let { width: w, height: h } = icon;
|
||||
const { width: w, height: h } = icon;
|
||||
group['shapeMap'][name] = group.addShape('image', {
|
||||
attrs: {
|
||||
...icon,
|
||||
|
@ -115,7 +115,7 @@ export default class ViewController {
|
||||
realRatio = maxZoom;
|
||||
console.warn('fitview failed, ratio out of range, ratio: %f', ratio, 'graph maxzoom has been used instead');
|
||||
}
|
||||
let zoomedMatrix = transform(translatedMatrix, [
|
||||
const zoomedMatrix = transform(translatedMatrix, [
|
||||
['t', -viewCenter.x, -viewCenter.y],
|
||||
['s', realRatio, realRatio],
|
||||
['t', viewCenter.x, viewCenter.y],
|
||||
@ -425,7 +425,7 @@ export default class ViewController {
|
||||
const startMatrix = group.getMatrix() || [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
||||
group.resetMatrix();
|
||||
|
||||
let bbox: BBox = {
|
||||
const bbox: BBox = {
|
||||
x: 0, y: 0,
|
||||
minX: Number.MAX_SAFE_INTEGER, minY: Number.MAX_SAFE_INTEGER,
|
||||
maxX: Number.MIN_SAFE_INTEGER, maxY: Number.MIN_SAFE_INTEGER,
|
||||
|
@ -2718,8 +2718,8 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs
|
||||
edges.forEach(edge => {
|
||||
const { isVEdge, size = 1 } = edge.getModel();
|
||||
if (edge.isVisible() && !isVEdge) return;
|
||||
let source = edge.getSource();
|
||||
let target = edge.getTarget();
|
||||
const source = edge.getSource();
|
||||
const target = edge.getTarget();
|
||||
let otherEnd = null;
|
||||
let otherEndIsSource;
|
||||
if (source.getModel().id === comboModel.id ||
|
||||
@ -2827,10 +2827,10 @@ export default abstract class AbstractGraph extends EventEmitter implements IAbs
|
||||
const addedVEdgeMap = {};
|
||||
edges.forEach(edge => {
|
||||
if (edge.isVisible() && !edge.getModel().isVEdge) return;
|
||||
let source = edge.getSource();
|
||||
let target = edge.getTarget();
|
||||
let sourceId = source.get('id');
|
||||
let targetId = target.get('id');
|
||||
const source = edge.getSource();
|
||||
const target = edge.getTarget();
|
||||
const sourceId = source.get('id');
|
||||
const targetId = target.get('id');
|
||||
let otherEnd = null;
|
||||
let otherEndIsSource;
|
||||
if (sourceId === comboModel.id ||
|
||||
|
@ -265,7 +265,7 @@ export default class Node extends Item implements INode {
|
||||
))
|
||||
) return 'bbox|label';
|
||||
|
||||
let updateLabel = keys.includes('label') || keys.includes('labelCfg');
|
||||
const updateLabel = keys.includes('label') || keys.includes('labelCfg');
|
||||
|
||||
return updateLabel ? 'style|label' : 'style';
|
||||
}
|
||||
|
@ -83,8 +83,8 @@ export const getLoopCfgs = (cfg: EdgeData): EdgeData => {
|
||||
let startPoint = [cfg.startPoint.x, cfg.startPoint.y];
|
||||
let endPoint = [cfg.endPoint.x, cfg.endPoint.y];
|
||||
|
||||
let halfOfHeight = bbox.height / 2;
|
||||
let halfOfWidth = bbox.width / 2;
|
||||
const halfOfHeight = bbox.height / 2;
|
||||
const halfOfWidth = bbox.width / 2;
|
||||
let rstart = halfOfHeight;
|
||||
let rend = halfOfHeight;
|
||||
|
||||
|
@ -841,9 +841,9 @@ export const lerp = (start: number, end: number, alpha: number): number => {
|
||||
* @returns {number[]}
|
||||
*/
|
||||
export const lerpArray = (start: number[], end: number[], alpha: number): number[] => {
|
||||
var len = Math.min(start.length, end.length);
|
||||
const len = Math.min(start.length, end.length);
|
||||
const out = new Array(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
out[i] = lerp(start[i], end[i], alpha);
|
||||
}
|
||||
return out;
|
||||
|
@ -336,6 +336,88 @@ export const getNeighborPoints = (
|
||||
});
|
||||
return filterConnectPoints(neighbors);
|
||||
};
|
||||
|
||||
/**
|
||||
* sorted array ascendly
|
||||
* add new item to proper index when calling add
|
||||
*/
|
||||
export class SortedArray {
|
||||
public arr: {
|
||||
id: string;
|
||||
value: number;
|
||||
}[] = [];
|
||||
private map: {
|
||||
[id: string]: boolean;
|
||||
} = {};
|
||||
constructor() {
|
||||
this.arr = [];
|
||||
this.map = {};
|
||||
}
|
||||
private _innerAdd(item, length) {
|
||||
const idxRange = [0, length - 1];
|
||||
while (idxRange[1] - idxRange[0] > 1) {
|
||||
const midIdx = Math.floor((idxRange[0] + idxRange[1]) / 2);
|
||||
if (this.arr[midIdx].value > item.value) {
|
||||
idxRange[1] = midIdx;
|
||||
} else if (this.arr[midIdx].value < item.value) {
|
||||
idxRange[0] = midIdx;
|
||||
} else {
|
||||
this.arr.splice(midIdx, 0, item);
|
||||
this.map[item.id] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.arr.splice(idxRange[1], 0, item);
|
||||
this.map[item.id] = true;
|
||||
}
|
||||
public add(item) {
|
||||
// 已经存在,先移除
|
||||
delete this.map[item.id];
|
||||
|
||||
const length = this.arr.length;
|
||||
if (!length) {
|
||||
this.arr.push(item);
|
||||
this.map[item.id] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 比最后一个大,加入尾部
|
||||
if (this.arr[length - 1].value < item.value) {
|
||||
this.arr.push(item);
|
||||
this.map[item.id] = true;
|
||||
return;
|
||||
}
|
||||
this._innerAdd(item, length);
|
||||
}
|
||||
// only remove from the map to avoid cost
|
||||
// clear the invalid (not in the map) item when calling minId(true)
|
||||
public remove(id) {
|
||||
if (!this.map[id]) return;
|
||||
delete this.map[id];
|
||||
}
|
||||
private _clearAndGetMinId() {
|
||||
let res;
|
||||
for (let i = this.arr.length - 1; i >= 0; i--) {
|
||||
if (this.map[this.arr[i].id]) res = this.arr[i].id;
|
||||
else this.arr.splice(i, 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
private _findFirstId() {
|
||||
while (this.arr.length) {
|
||||
const first = this.arr.shift();
|
||||
if (this.map[first.id]) return first.id;
|
||||
}
|
||||
}
|
||||
public minId(clear) {
|
||||
if (clear) {
|
||||
return this._clearAndGetMinId();
|
||||
} else {
|
||||
return this._findFirstId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const pathFinder = (
|
||||
points: PolyPoint[],
|
||||
start: PolyPoint,
|
||||
@ -348,7 +430,7 @@ export const pathFinder = (
|
||||
// A-Star Algorithm
|
||||
const closedSet = [];
|
||||
const openSet = {
|
||||
[start.id]: start
|
||||
[start.id]: start,
|
||||
};
|
||||
const cameFrom: {
|
||||
[key: string]: any;
|
||||
@ -367,8 +449,8 @@ export const pathFinder = (
|
||||
const sortedOpenSet = new SortedArray();
|
||||
sortedOpenSet.add({
|
||||
id: start.id,
|
||||
value: fScore[start.id]
|
||||
})
|
||||
value: fScore[start.id],
|
||||
});
|
||||
|
||||
const pointById: {
|
||||
[key: string]: PolyPoint;
|
||||
@ -381,12 +463,12 @@ export const pathFinder = (
|
||||
let current;
|
||||
while (Object.keys(openSet).length) {
|
||||
const minId = sortedOpenSet.minId(false);
|
||||
if (minId) {
|
||||
current = openSet[minId];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (minId) {
|
||||
current = openSet[minId];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// 若 openSet 中 fScore 最小的点就是终点
|
||||
if (current === goal) {
|
||||
// ending condition
|
||||
@ -405,31 +487,31 @@ export const pathFinder = (
|
||||
if (closedSet.indexOf(neighbor) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const neighborId = neighbor.id;
|
||||
if (!openSet[neighborId]) {
|
||||
openSet[neighborId] = neighbor;
|
||||
}
|
||||
|
||||
|
||||
const tentativeGScore = fScore[current.id] + distance(current, neighbor); // + distance(neighbor, goal);
|
||||
if (gScore[neighborId] && tentativeGScore >= gScore[neighborId]) {
|
||||
sortedOpenSet.add({
|
||||
id: neighborId,
|
||||
value: fScore[neighborId]
|
||||
})
|
||||
value: fScore[neighborId],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
cameFrom[neighborId] = current.id;
|
||||
gScore[neighborId] = tentativeGScore;
|
||||
fScore[neighborId] =
|
||||
gScore[neighborId] + heuristicCostEstimate(neighbor, goal, start, os, ot);
|
||||
sortedOpenSet.add({
|
||||
id: neighborId,
|
||||
value: fScore[neighborId]
|
||||
})
|
||||
value: fScore[neighborId],
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
iterateNeighbors(neighborPoints);
|
||||
}
|
||||
|
||||
@ -512,11 +594,11 @@ export const getPolylinePoints = (
|
||||
maxX: sKeyShapeBBox.maxX + sx,
|
||||
minY: sKeyShapeBBox.minY + sy,
|
||||
maxY: sKeyShapeBBox.maxY + sy,
|
||||
}
|
||||
};
|
||||
sBBox.centerX = (sBBox.minX + sBBox.maxX) / 2;
|
||||
sBBox.centerY = (sBBox.minY + sBBox.maxY) / 2;
|
||||
} else {
|
||||
sBBox = getBBoxFromPoint(start) as PBBox
|
||||
sBBox = getBBoxFromPoint(start) as PBBox;
|
||||
}
|
||||
} else {
|
||||
sBBox = sNode && sNode.getBBox();
|
||||
@ -537,11 +619,11 @@ export const getPolylinePoints = (
|
||||
maxX: tKeyShapeBBox.maxX + tx,
|
||||
minY: tKeyShapeBBox.minY + ty,
|
||||
maxY: tKeyShapeBBox.maxY + ty,
|
||||
}
|
||||
};
|
||||
tBBox.centerX = (tBBox.minX + tBBox.maxX) / 2;
|
||||
tBBox.centerY = (tBBox.minY + tBBox.maxY) / 2;
|
||||
} else {
|
||||
tBBox = getBBoxFromPoint(end) as PBBox
|
||||
tBBox = getBBoxFromPoint(end) as PBBox;
|
||||
}
|
||||
} else {
|
||||
tBBox = tNode && tNode.getBBox();
|
||||
@ -614,15 +696,15 @@ export const getPolylinePoints = (
|
||||
/**
|
||||
* 去除连续同 x 不同 y 的中间点;去除连续同 y 不同 x 的中间点
|
||||
* @param points 坐标集合 { x: number, y: number, id: string }[]
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const removeRedundantPoint = (points) => {
|
||||
if (!points?.length) return points;
|
||||
const beginPoint = points[points.length - 1];
|
||||
const current = {
|
||||
x: beginPoint.x,
|
||||
y: beginPoint.y
|
||||
}
|
||||
y: beginPoint.y,
|
||||
};
|
||||
let continueSameX = [beginPoint];
|
||||
let continueSameY = [beginPoint];
|
||||
for (let i = points.length - 2; i >= 0; i--) {
|
||||
@ -651,86 +733,4 @@ export const removeRedundantPoint = (points) => {
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* sorted array ascendly
|
||||
* add new item to proper index when calling add
|
||||
*/
|
||||
export class SortedArray {
|
||||
public arr: {
|
||||
id: string,
|
||||
value: number
|
||||
}[] = [];
|
||||
private map: {
|
||||
[id: string]: boolean
|
||||
} = {};
|
||||
constructor() {
|
||||
this.arr = [];
|
||||
this.map = {};
|
||||
}
|
||||
private _innerAdd(item, length) {
|
||||
const idxRange = [0, length - 1];
|
||||
while (idxRange[1] - idxRange[0] > 1) {
|
||||
const midIdx = Math.floor((idxRange[0] + idxRange[1]) / 2);
|
||||
if (this.arr[midIdx].value > item.value) {
|
||||
idxRange[1] = midIdx;
|
||||
} else if (this.arr[midIdx].value < item.value) {
|
||||
idxRange[0] = midIdx;
|
||||
} else {
|
||||
this.arr.splice(midIdx, 0, item);
|
||||
this.map[item.id] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.arr.splice(idxRange[1], 0, item);
|
||||
this.map[item.id] = true;
|
||||
}
|
||||
public add(item) {
|
||||
// 已经存在,先移除
|
||||
delete this.map[item.id];
|
||||
|
||||
const length = this.arr.length;
|
||||
if (!length) {
|
||||
this.arr.push(item);
|
||||
this.map[item.id] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 比最后一个大,加入尾部
|
||||
if (this.arr[length - 1].value < item.value) {
|
||||
this.arr.push(item);
|
||||
this.map[item.id] = true;
|
||||
return;
|
||||
}
|
||||
this._innerAdd(item, length);
|
||||
|
||||
}
|
||||
// only remove from the map to avoid cost
|
||||
// clear the invalid (not in the map) item when calling minId(true)
|
||||
public remove(id) {
|
||||
if (!this.map[id]) return;
|
||||
delete this.map[id];
|
||||
}
|
||||
private _clearAndGetMinId(){
|
||||
let res;
|
||||
for (let i = this.arr.length - 1; i >= 0; i--) {
|
||||
if (this.map[this.arr[i].id]) res = this.arr[i].id;
|
||||
else this.arr.splice(i, 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
private _findFirstId() {
|
||||
while (this.arr.length) {
|
||||
const first = this.arr.shift();
|
||||
if (this.map[first.id]) return first.id;
|
||||
}
|
||||
}
|
||||
public minId(clear) {
|
||||
if (clear) {
|
||||
return this._clearAndGetMinId();
|
||||
} else {
|
||||
return this._findFirstId();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
NodeConfig,
|
||||
BaseGlobal as Global,
|
||||
UpdateType,
|
||||
Util
|
||||
Util,
|
||||
} from '@antv/g6-core';
|
||||
import { deepMix } from '@antv/util';
|
||||
|
||||
@ -14,37 +14,256 @@ const { defaultSubjectColors } = Util;
|
||||
const FAN_NAME_PREFIX = 'fan-shape-';
|
||||
|
||||
type DonutAttrs = {
|
||||
[propKey: string]: number
|
||||
[propKey: string]: number;
|
||||
};
|
||||
|
||||
type DonutColorMap = {
|
||||
[propKey: string]: string
|
||||
[propKey: string]: string;
|
||||
};
|
||||
|
||||
interface DonutNodeConfig extends NodeConfig {
|
||||
// values for fan shapes on the donut
|
||||
donutAttrs?: DonutAttrs,
|
||||
donutAttrs?: DonutAttrs;
|
||||
// assign the color for a fan, propKey corresponds to the keys in donutAttrs. If not assigned, default palette will be used
|
||||
donutColorMap?: DonutColorMap
|
||||
donutColorMap?: DonutColorMap;
|
||||
}
|
||||
|
||||
type FanValue = {
|
||||
key: string; // key of the fan, came from the key of corresponding property of donutAttrs
|
||||
value: number; // format number value of the single fan
|
||||
color: string; // color from corresponding position of donutColorMap
|
||||
}
|
||||
};
|
||||
|
||||
type FanConfig = {
|
||||
arcR: number; // the radius of the fan
|
||||
arcBegin: [number, number]; // the beginning position of the arc
|
||||
beginAngle: number; // the beginning angle of the arc
|
||||
config: FanValue, // value and color of the fan
|
||||
config: FanValue; // value and color of the fan
|
||||
fanIndex: number; // the index of the fan at the donut fans array
|
||||
lineWidth: number; // the line width for the arc path
|
||||
totalValue: number; // the total value of the donut configs
|
||||
drawWhole?: boolean; // whether draw a arc with radius 2*PI to represent a circle
|
||||
updateShape?: IShape; // the shape to be updated, if not assgined, draw a new fan shape
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate the total value and format single value for each fan
|
||||
* @param donutAttrs
|
||||
* @param donutColorMap
|
||||
* @returns
|
||||
*/
|
||||
const getDonutConfig = (
|
||||
donutAttrs: DonutAttrs,
|
||||
donutColorMap: DonutColorMap,
|
||||
): {
|
||||
totalValue: number;
|
||||
configs: FanValue[];
|
||||
} => {
|
||||
let totalValue = 0;
|
||||
const configs = [];
|
||||
Object.keys(donutAttrs).forEach((name) => {
|
||||
const value = +donutAttrs[name];
|
||||
if (isNaN(value)) return;
|
||||
configs.push({
|
||||
key: name,
|
||||
value,
|
||||
color: donutColorMap[name],
|
||||
});
|
||||
totalValue += value;
|
||||
});
|
||||
return { totalValue, configs };
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate the lineWidth and radius for fan shapes according to the keyShape's radius
|
||||
* @param keyShape
|
||||
* @returns
|
||||
*/
|
||||
const getDonutSize = (
|
||||
keyShape: IShape,
|
||||
): {
|
||||
lineWidth: number;
|
||||
arcR: number;
|
||||
} => {
|
||||
const keyShapeR = keyShape.attr('r');
|
||||
const innerR = 0.6 * keyShapeR; // 甜甜圈的内环半径
|
||||
const arcR = (keyShapeR + innerR) / 2; // 内环半径与外环半径的平均值
|
||||
const lineWidth = keyShapeR - innerR;
|
||||
return { lineWidth, arcR };
|
||||
};
|
||||
|
||||
/**
|
||||
* draws one fan shape and returns the next position and angle
|
||||
* @param group
|
||||
* @param fanConfig
|
||||
* @returns
|
||||
*/
|
||||
const drawFan = (
|
||||
group: IGroup,
|
||||
fanConfig: FanConfig,
|
||||
): {
|
||||
beginAngle: number; // next begin iangle
|
||||
arcBegin: [number, number]; // next begin position
|
||||
shape: IShape | undefined; // shape added by this function
|
||||
shouldEnd: boolean; // finish fans drawing
|
||||
} => {
|
||||
const {
|
||||
arcR,
|
||||
arcBegin,
|
||||
beginAngle,
|
||||
config,
|
||||
fanIndex,
|
||||
lineWidth,
|
||||
totalValue,
|
||||
drawWhole = false,
|
||||
updateShape = undefined,
|
||||
} = fanConfig;
|
||||
const percent = config.value / totalValue;
|
||||
if (percent < 0.001) {
|
||||
// too small to add a fan
|
||||
return {
|
||||
beginAngle,
|
||||
arcBegin,
|
||||
shape: undefined,
|
||||
shouldEnd: false,
|
||||
};
|
||||
}
|
||||
let arcEnd, endAngle, isBig;
|
||||
// draw a path represents the whole circle, or the percentage is close to 1
|
||||
if (drawWhole || percent > 0.999) {
|
||||
arcEnd = [arcR, 0.0001]; // [arcR * cos(2 * PI), -arcR * sin(2 * PI)]
|
||||
isBig = 1;
|
||||
} else {
|
||||
const angle = percent * Math.PI * 2;
|
||||
endAngle = beginAngle + angle;
|
||||
arcEnd = [arcR * Math.cos(endAngle), -arcR * Math.sin(endAngle)];
|
||||
isBig = angle > Math.PI ? 1 : 0;
|
||||
}
|
||||
const style = {
|
||||
path: [
|
||||
['M', arcBegin[0], arcBegin[1]],
|
||||
['A', arcR, arcR, 0, isBig, 0, arcEnd[0], arcEnd[1]],
|
||||
],
|
||||
stroke:
|
||||
config.color ||
|
||||
updateShape?.attr('stroke') ||
|
||||
defaultSubjectColors[fanIndex % defaultSubjectColors.length],
|
||||
lineWidth,
|
||||
};
|
||||
if (updateShape) {
|
||||
// update
|
||||
updateShape.attr(style);
|
||||
} else {
|
||||
// draw
|
||||
group['shapeMap'][`${FAN_NAME_PREFIX}${fanIndex}`] = group.addShape('path', {
|
||||
attrs: style,
|
||||
name: `${FAN_NAME_PREFIX}${fanIndex}`,
|
||||
draggable: true,
|
||||
});
|
||||
}
|
||||
return {
|
||||
beginAngle: endAngle,
|
||||
arcBegin: arcEnd,
|
||||
shape: group['shapeMap'][`${FAN_NAME_PREFIX}${fanIndex}`],
|
||||
shouldEnd: drawWhole || percent > 0.999,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* draws the fan shapes
|
||||
* @param cfg
|
||||
* @param group
|
||||
* @param keyShape
|
||||
* @returns
|
||||
*/
|
||||
const drawFans = (cfg: DonutNodeConfig, group: IGroup, keyShape: IShape) => {
|
||||
const { donutAttrs = {}, donutColorMap = {} } = cfg;
|
||||
const attrNum = Object.keys(donutAttrs).length;
|
||||
if (donutAttrs && attrNum > 1) {
|
||||
const { configs, totalValue } = getDonutConfig(donutAttrs, donutColorMap);
|
||||
if (totalValue) {
|
||||
const { lineWidth, arcR } = getDonutSize(keyShape);
|
||||
let arcBegin: [number, number] = [arcR, 0];
|
||||
let beginAngle = 0;
|
||||
if (attrNum === 1) {
|
||||
// draw a path represents a circle
|
||||
drawFan(group, {
|
||||
arcR,
|
||||
arcBegin,
|
||||
beginAngle,
|
||||
config: configs[0],
|
||||
fanIndex: 0,
|
||||
lineWidth,
|
||||
totalValue,
|
||||
drawWhole: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < configs.length; i++) {
|
||||
const result = drawFan(group, {
|
||||
arcR,
|
||||
arcBegin,
|
||||
beginAngle,
|
||||
config: configs[i],
|
||||
fanIndex: i,
|
||||
lineWidth,
|
||||
totalValue,
|
||||
});
|
||||
if (result.shouldEnd) return;
|
||||
arcBegin = result.arcBegin;
|
||||
beginAngle = result.beginAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* utilizes the existing fan shapes, update them with new configs
|
||||
* removes the redundent fan shapes
|
||||
* or adds more fan shapes
|
||||
* @param cfg
|
||||
* @param item
|
||||
* @param keyShape
|
||||
*/
|
||||
const updateFans = (cfg: DonutNodeConfig, item: Item, keyShape: IShape) => {
|
||||
const { donutAttrs, donutColorMap = {} } = cfg;
|
||||
const visitMap = {};
|
||||
const group = item.getContainer();
|
||||
if (donutAttrs) {
|
||||
const { configs, totalValue } = getDonutConfig(donutAttrs, donutColorMap);
|
||||
if (totalValue) {
|
||||
const { lineWidth, arcR } = getDonutSize(keyShape);
|
||||
let arcBegin: [number, number] = [arcR, 0];
|
||||
let beginAngle = 0;
|
||||
for (let i = 0; i < configs.length; i++) {
|
||||
const shapeName = `${FAN_NAME_PREFIX}${i}`;
|
||||
const result = drawFan(group, {
|
||||
arcR,
|
||||
arcBegin,
|
||||
beginAngle,
|
||||
config: configs[i],
|
||||
fanIndex: i,
|
||||
lineWidth,
|
||||
totalValue,
|
||||
drawWhole: configs.length === 1,
|
||||
updateShape: group['shapeMap'][shapeName],
|
||||
});
|
||||
if (result.shape) visitMap[shapeName] = true;
|
||||
if (result.shouldEnd) break;
|
||||
arcBegin = result.arcBegin;
|
||||
beginAngle = result.beginAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove the old shapes which are not visited, including the situation taht donutAttrs is empty
|
||||
const fanKeys = Object.keys(group['shapeMap']).filter((key) => key.includes(FAN_NAME_PREFIX));
|
||||
fanKeys.forEach((key) => {
|
||||
if (!visitMap[key]) {
|
||||
group['shapeMap'][key].remove(true);
|
||||
delete group['shapeMap'][key];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 饼图节点
|
||||
registerNode(
|
||||
@ -64,7 +283,7 @@ registerNode(
|
||||
style: {
|
||||
fill: Global.nodeLabel.style.fill,
|
||||
fontSize: Global.nodeLabel.style.fontSize,
|
||||
fontFamily: Global.windowFontFamily
|
||||
fontFamily: Global.windowFontFamily,
|
||||
},
|
||||
},
|
||||
// 节点上左右上下四个方向上的链接circle配置
|
||||
@ -96,14 +315,14 @@ registerNode(
|
||||
// 文本位置
|
||||
labelPosition: 'center',
|
||||
drawShape(cfg: DonutNodeConfig, group: IGroup): IShape {
|
||||
const { icon: defaultIcon = {} } = this.mergeStyle || this.getOptions(cfg) as NodeConfig;
|
||||
const { icon: defaultIcon = {} } = this.mergeStyle || (this.getOptions(cfg) as NodeConfig);
|
||||
const style = this.getShapeStyle!(cfg);
|
||||
const icon = deepMix({}, defaultIcon, cfg.icon);
|
||||
const keyShape: IShape = group.addShape('circle', {
|
||||
attrs: style,
|
||||
className: `${this.type}-keyShape`,
|
||||
draggable: true,
|
||||
name: `${this.type}-keyShape`
|
||||
name: `${this.type}-keyShape`,
|
||||
});
|
||||
group['shapeMap'][`${this.type}-keyShape`] = keyShape;
|
||||
|
||||
@ -146,7 +365,13 @@ registerNode(
|
||||
|
||||
return keyShape;
|
||||
},
|
||||
updateShape(cfg: DonutNodeConfig, item: Item, keyShapeStyle: object, hasIcon: boolean, updateType: UpdateType) {
|
||||
updateShape(
|
||||
cfg: DonutNodeConfig,
|
||||
item: Item,
|
||||
keyShapeStyle: object,
|
||||
hasIcon: boolean,
|
||||
updateType: UpdateType,
|
||||
) {
|
||||
// here cfg is merged configure including old model and new configs
|
||||
const keyShape = item.get('keyShape');
|
||||
keyShape.attr({
|
||||
@ -166,205 +391,3 @@ registerNode(
|
||||
},
|
||||
'circle',
|
||||
);
|
||||
|
||||
/**
|
||||
* draws the fan shapes
|
||||
* @param cfg
|
||||
* @param group
|
||||
* @param keyShape
|
||||
* @returns
|
||||
*/
|
||||
const drawFans = (cfg: DonutNodeConfig, group: IGroup, keyShape: IShape) => {
|
||||
const { donutAttrs = {}, donutColorMap = {} } = cfg;
|
||||
const attrNum = Object.keys(donutAttrs).length;
|
||||
if (donutAttrs && attrNum > 1) {
|
||||
const { configs, totalValue } = getDonutConfig(donutAttrs, donutColorMap);
|
||||
if (totalValue) {
|
||||
const { lineWidth, arcR } = getDonutSize(keyShape);
|
||||
let arcBegin: [number, number] = [arcR, 0];
|
||||
let beginAngle = 0;
|
||||
if (attrNum === 1) {
|
||||
// draw a path represents a circle
|
||||
drawFan(group, {
|
||||
arcR,
|
||||
arcBegin,
|
||||
beginAngle,
|
||||
config: configs[0],
|
||||
fanIndex: 0,
|
||||
lineWidth,
|
||||
totalValue,
|
||||
drawWhole: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < configs.length; i++) {
|
||||
const result = drawFan(group, {
|
||||
arcR,
|
||||
arcBegin,
|
||||
beginAngle,
|
||||
config: configs[i],
|
||||
fanIndex: i,
|
||||
lineWidth,
|
||||
totalValue
|
||||
});
|
||||
if (result.shouldEnd) return;
|
||||
arcBegin = result.arcBegin;
|
||||
beginAngle = result.beginAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* draws one fan shape and returns the next position and angle
|
||||
* @param group
|
||||
* @param fanConfig
|
||||
* @returns
|
||||
*/
|
||||
const drawFan = (group: IGroup, fanConfig: FanConfig): {
|
||||
beginAngle: number, // next begin iangle
|
||||
arcBegin: [number, number], // next begin position
|
||||
shape: IShape | undefined, // shape added by this function
|
||||
shouldEnd: boolean, // finish fans drawing
|
||||
} => {
|
||||
const { arcR, arcBegin, beginAngle, config, fanIndex, lineWidth, totalValue, drawWhole = false, updateShape = undefined } = fanConfig;
|
||||
const percent = config.value / totalValue;
|
||||
if (percent < 0.001) {
|
||||
// too small to add a fan
|
||||
return {
|
||||
beginAngle,
|
||||
arcBegin,
|
||||
shape: undefined,
|
||||
shouldEnd: false
|
||||
}
|
||||
}
|
||||
let arcEnd, endAngle, isBig;
|
||||
// draw a path represents the whole circle, or the percentage is close to 1
|
||||
if (drawWhole || percent > 0.999) {
|
||||
arcEnd = [arcR, 0.0001]; // [arcR * cos(2 * PI), -arcR * sin(2 * PI)]
|
||||
isBig = 1;
|
||||
} else {
|
||||
const angle = percent * Math.PI * 2;
|
||||
endAngle = beginAngle + angle;
|
||||
arcEnd = [
|
||||
arcR * Math.cos(endAngle),
|
||||
-arcR * Math.sin(endAngle),
|
||||
];
|
||||
isBig = angle > Math.PI ? 1 : 0;
|
||||
}
|
||||
const style = {
|
||||
path: [
|
||||
['M', arcBegin[0], arcBegin[1]],
|
||||
['A', arcR, arcR, 0, isBig, 0, arcEnd[0], arcEnd[1]]
|
||||
],
|
||||
stroke: config.color || updateShape?.attr('stroke') || defaultSubjectColors[fanIndex % defaultSubjectColors.length],
|
||||
lineWidth,
|
||||
};
|
||||
if (updateShape) {
|
||||
// update
|
||||
updateShape.attr(style)
|
||||
} else {
|
||||
// draw
|
||||
group['shapeMap'][`${FAN_NAME_PREFIX}${fanIndex}`] = group.addShape('path', {
|
||||
attrs: style,
|
||||
name: `${FAN_NAME_PREFIX}${fanIndex}`,
|
||||
draggable: true,
|
||||
});
|
||||
}
|
||||
return {
|
||||
beginAngle: endAngle,
|
||||
arcBegin: arcEnd,
|
||||
shape: group['shapeMap'][`${FAN_NAME_PREFIX}${fanIndex}`],
|
||||
shouldEnd: drawWhole || percent > 0.999
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* utilizes the existing fan shapes, update them with new configs
|
||||
* removes the redundent fan shapes
|
||||
* or adds more fan shapes
|
||||
* @param cfg
|
||||
* @param item
|
||||
* @param keyShape
|
||||
*/
|
||||
const updateFans = (cfg: DonutNodeConfig, item: Item, keyShape: IShape) => {
|
||||
const { donutAttrs, donutColorMap = {} } = cfg;
|
||||
const visitMap = {};
|
||||
const group = item.getContainer();
|
||||
if (donutAttrs) {
|
||||
const { configs, totalValue } = getDonutConfig(donutAttrs, donutColorMap);
|
||||
if (totalValue) {
|
||||
const { lineWidth, arcR } = getDonutSize(keyShape);
|
||||
let arcBegin: [number, number] = [arcR, 0];
|
||||
let beginAngle = 0;
|
||||
for (let i = 0; i < configs.length; i++) {
|
||||
const shapeName = `${FAN_NAME_PREFIX}${i}`;
|
||||
const result = drawFan(group, {
|
||||
arcR,
|
||||
arcBegin,
|
||||
beginAngle,
|
||||
config: configs[i],
|
||||
fanIndex: i,
|
||||
lineWidth,
|
||||
totalValue,
|
||||
drawWhole: configs.length === 1,
|
||||
updateShape: group['shapeMap'][shapeName]
|
||||
});
|
||||
if (result.shape) visitMap[shapeName] = true;
|
||||
if (result.shouldEnd) break;
|
||||
arcBegin = result.arcBegin;
|
||||
beginAngle = result.beginAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove the old shapes which are not visited, including the situation taht donutAttrs is empty
|
||||
const fanKeys = Object.keys(group['shapeMap']).filter(key => key.includes(FAN_NAME_PREFIX));
|
||||
fanKeys.forEach(key => {
|
||||
if (!visitMap[key]) {
|
||||
group['shapeMap'][key].remove(true);
|
||||
delete group['shapeMap'][key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calculate the total value and format single value for each fan
|
||||
* @param donutAttrs
|
||||
* @param donutColorMap
|
||||
* @returns
|
||||
*/
|
||||
const getDonutConfig = (donutAttrs: DonutAttrs, donutColorMap: DonutColorMap): {
|
||||
totalValue: number,
|
||||
configs: FanValue[]
|
||||
} => {
|
||||
let totalValue = 0;
|
||||
const configs = [];
|
||||
Object.keys(donutAttrs).forEach((name) => {
|
||||
const value = (+donutAttrs[name]);
|
||||
if (isNaN(value)) return;
|
||||
configs.push({
|
||||
key: name,
|
||||
value,
|
||||
color: donutColorMap[name],
|
||||
});
|
||||
totalValue += value;
|
||||
});
|
||||
return { totalValue, configs };
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate the lineWidth and radius for fan shapes according to the keyShape's radius
|
||||
* @param keyShape
|
||||
* @returns
|
||||
*/
|
||||
const getDonutSize = (keyShape: IShape): {
|
||||
lineWidth: number,
|
||||
arcR: number
|
||||
} => {
|
||||
const keyShapeR = keyShape.attr('r');
|
||||
const innerR = 0.6 * keyShapeR; // 甜甜圈的内环半径
|
||||
const arcR = (keyShapeR + innerR) / 2; // 内环半径与外环半径的平均值
|
||||
const lineWidth = keyShapeR - innerR;
|
||||
return { lineWidth, arcR };
|
||||
}
|
@ -52,6 +52,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.18",
|
||||
"@umijs/fabric": "^2.0.0",
|
||||
"event-simulate": "^1.0.1",
|
||||
"father": "^2.30.0",
|
||||
"jest": "^26.6.3",
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { AbstractShape, AnimateCfg } from '@antv/g-canvas';
|
||||
import { AnimateCfg } from '@antv/g-canvas';
|
||||
import { animations } from './animateFunc';
|
||||
|
||||
export type AnimationConfig = AnimateCfg & { animate: keyof typeof animations };
|
||||
|
||||
export const animateShapeWithConfig = (
|
||||
shape: AbstractShape,
|
||||
shape: any,
|
||||
config?: Partial<AnimationConfig>,
|
||||
initMatrix?: number[],
|
||||
) => {
|
||||
|
46848
pnpm-lock.yaml
46848
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user