diff --git a/.changeset/config.json b/.changeset/config.json index f38b47ab5..d71842701 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -5,7 +5,7 @@ "fixed": [], "linked": [], "access": "restricted", - "baseBranch": "main", + "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [] } diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5ca524058..142939199 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x] + node-version: [latest] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..1c8b3960b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,36 @@ +# 当具有 publish 标签的 PR 被合并时,自动发布新版本 +# Automatically publish a new version when a PR with the publish label is merged +name: Auto Publish +on: + pull_request: + types: [closed] + branches: + - v2 + +jobs: + publish: + runs-on: ubuntu-latest + if: contains(github.event.pull_request.labels.*.name, 'publish') && github.event.pull_request.merged == true + + steps: + - uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install pnpm and dependencies + uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: true + + - name: Build + run: npm run build + + - name: Publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: pnpm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} & pnpm run publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..893f15e6d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,51 @@ +name: Changesets +on: + push: + branches: + - master +env: + CI: true + PNPM_CACHE_FOLDER: .pnpm-store + +jobs: + version: + timeout-minutes: 15 + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [latest] + steps: + - name: checkout code repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: install pnpm + run: npm i pnpm@latest -g + - name: Setup npmrc + run: | + cat << EOF > "$HOME/.npmrc" + email=${NPM_EMAIL} + //registry.npmjs.org/:_authToken=$NPM_TOKEN + EOF + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_EMAIL: ${{ secrets.NPM_EMAIL }} + - name: setup pnpm config + run: pnpm config set store-dir $PNPM_CACHE_FOLDER + - name: install dependencies + run: pnpm install + - name: create publish versions + uses: changesets/action@v1 + with: + version: pnpm ci:version + commit: 'chore: release versions' + title: 'chore: release versions' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: publish to npm + run: pnpm run release diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 898a5b684..35df7d3ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: timeout-minutes: 15 strategy: matrix: - node-version: [14.x] + node-version: [latest] steps: - name: checkout code repository uses: actions/checkout@v3 @@ -30,6 +30,16 @@ jobs: run: pnpm install - name: test run: pnpm test + - name: Run Playwright tests + run: | + pnpm exec playwright install chromium + pnpm exec playwright test --workers=10 + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 # - name: Coveralls # uses: coverallsapp/github-action@master # with: diff --git a/.github/workflows/v2-site.yml b/.github/workflows/v2-site.yml new file mode 100644 index 000000000..364204f63 --- /dev/null +++ b/.github/workflows/v2-site.yml @@ -0,0 +1,69 @@ +name: V2 Site + +on: + pull_request: + branches: + - v2 # default branch +env: + CI: true + PNPM_STORE: .pnpm-store + +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + matrix: + node-version: [latest] + steps: + - name: checkout code repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Switch to site branch + run: git checkout -b gh-pages + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: install pnpm + run: npm i pnpm@latest -g + - name: Setup npmrc + run: | + cat << EOF > "$HOME/.npmrc" + email=${NPM_EMAIL} + //registry.npmjs.org/:_authToken=$NPM_TOKEN + EOF + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_EMAIL: ${{ secrets.NPM_EMAIL }} + - name: setup pnpm config + run: pnpm config set store-dir $PNPM_STORE + - name: install dependencies + run: pnpm install + - name: build site + run: | + pnpm run build:lib + pnpm run build:site + - name: copy and delete files + run: | + find . -maxdepth 1 -type f -exec rm -f {} \; + cp -r site/dist/* ./ + rm -rf site + rm -rf node_modules + rm -rf packages + rm -rf scripts + rm -rf public + rm -rf config + rm -rf template + rm -rf .changeset + rm -rf .github + rm -rf $PNPM_STORE + - name: commit changes + run: | + git config --local user.email "954055752@qq.com" + git config --local user.name "lxfu1" + git add . + git commit -m "chore: update site" + - name: push to gh-pages branch + run: git push origin gh-pages -f diff --git a/.gitignore b/.gitignore index 33f664b2b..602f421bd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ node_modules /npm-debug.log* /yarn-error.log /yarn.lock -/pnpm-lock.yaml +pnpm-lock.yaml /package-lock.json /pnpm-debug.log /**/pnpm-debug.log @@ -31,6 +31,12 @@ logs #ide .idea/ .eslintcache +.vscode/** # temp temp-gallery.md + +# Tools +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/.npmrc b/.npmrc index 62b8985c7..00e134fbe 100644 --- a/.npmrc +++ b/.npmrc @@ -4,4 +4,5 @@ public-hoist-pattern[]=d3-* public-hoist-pattern[]=@types/react public-hoist-pattern[]=@types/react-dom public-hoist-pattern[]=npm-run-all -public-hoist-pattern[]=rimraf \ No newline at end of file +public-hoist-pattern[]=rimraf +public-hoist-pattern[]=@antv/* \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 8af90c5d6..58db32539 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,5 @@ **/*.svg **/*.html -*.md package.json .umi .umi-production @@ -11,4 +10,4 @@ dist node_modules *.ejs gatsby-browser.js -*.min.js \ No newline at end of file +*.min.js diff --git a/.prettierrc.js b/.prettierrc.js index f30f82663..305cdad3a 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -3,4 +3,5 @@ const fabric = require('@umijs/fabric'); module.exports = { ...fabric.prettier, printWidth: 120, + plugins: [require.resolve('prettier-plugin-organize-imports'), require.resolve('prettier-plugin-packagejson')], }; diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2f7b59c..bdbc24379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,393 +1,3 @@ -## 1.4.2 +## 2.0.0-alpah.0 -`2022-07-19` - -- 🐞 [Critical security vulnerabilities immer and minimist](https://github.com/ant-design/ant-design-charts/issues/1289) -## 1.4.1 - -`2022-07-13` - -- 🐞 修复 umd 不可用 - -## 1.4.0 - -`2022-07-12` - -- 🔥 文件目录命名风格统一 -- `@ant-design/maps` Heatmap 重命名为 GeographicHeatmap -- 支持自定义 anchor -- 🆕 OrganizationGraph 支持自定义 anchor -- 🐞 修复 scaleToolbarPanelProps 为空时出错 -- 🐞 修复流程图保存数据失败 - -## 1.3.6 - -`2022-03-22` - -- 🆕 Flowchart 阅读态完善,支持取消键盘事件和画布事件 -- 🆕 Graph 支持自定义布局 -- 🐞 修复 TS 类型错误 -## 1.3.5 - -`2022-01-25` - -- 🐞 修复 umd 命名错误问题 [1120](https://github.com/ant-design/ant-design-charts/issues/1120) -- 🐞 修复流程图无法异步加载数据 [1117](https://github.com/ant-design/ant-design-charts/issues/1117) -- 🐞 修复关系图 tooltipCfg.container 配置报错 - -## 1.3.4 - -`2022-01-07` - -- 🆕 官网示例更新 -- 🐞 修复 interaction tooltip 导致页面崩溃 -- 🐞 修复 OrganizationGraph 接口和实现不一致 -## 1.3.3 - -`2021-12-24` - -- 🆕 新增 Venn 图 -- 🆕 主包导出 flowchart 样式文件 -- 🆕 新增 PR 巡检 -- 🐞 修复官网示例 - -## 1.3.2 - -`2021-12-07` - -- 🐞 修复 peerDependencies - -## 1.3.1 - -`2021-11-26` - -- 🆕 CDN 引入变量重命名 - - window.charts -> window.Charts - - window.plots -> window.Plots - - window.maps -> window.Maps - - window.graphs -> window.Graphs - -## 1.3.0 - -`2021-11-25` - -- 🆕 新增流程图 [Flowchart](https://charts.ant.design/zh/examples/flowchart/basic#basic) - -流程图除 `react`、`react-dom` 外,还依赖 `antd`、`@ant-design/icons`、`lodash`,使用时确保已经安装,同时记得引入样式文件 `import "@ant-design/flowchart/dist/index.css";` - -```ts -"peerDependencies": { - "@ant-design/icons": "^4.6.0", - "antd": "^4.6.3", - "lodash": "^4.17.20", - "react": ">=16.8.4", - "react-dom": ">=16.8.4" - } -``` - -```tsx -import { Flowchart } from '@ant-design/charts'; -import "@ant-design/flowchart/dist/index.css"; -``` - -- 🆕 新增地理可视化 [Maps](https://charts.ant.design/zh/examples/map-area/division#chinese-provinces) -- 🆕 新增 CirclePacking -- 🆕 Ant Design Charts 完成拆包,推荐使用子包 - - 统计图表:@ant-design/plots - - 流程图:@ant-design/flowchart - - 地理可视化:@ant-design/maps - - 关系图:@ant-design/graphs - - -## 1.2.4 - -`2021-07-27` - -- 🆕 DagreGraph 新增 card 类型。 -- 🐞 修复 FlowAnalysisGraph 类型错误。 -- 🐞 修复 autoFit 布局偏移。 - -## 1.2.3 - -`2021-07-20` - -### New Features - -- 新增资金流向图(FundFlowGraph): 支持节自定义;展开收起;边、节点格式化配置等。 - -- FlowAnalysisGraph 、DecompositionTreeGraph 新增 badge 配置 - -- 关系图支持默认节点类型 - -- `indicator-card` 新增 `LR` 布局 - -### Bug fixes - -- 修复 FlowAnalysisGraph 、DecompositionTreeGraph 空数据报错 - -## 1.2.2 - -`2021-07-20` - -- 🐞 旧 Graph 节点注册 -- 🐞 修复 FlowAnalysisGraph 空数据异常 - -## 1.2.1 - -`2021-07-20` - -- 🆕 FlowAnalysisGraph layout 新增 `follow` -- 🆕 Graphs ArrowCfg 新增 size -- 🆕 Graphs 默认值&文档优化 - -## 1.2.0 - -> 图组件全新 API ,新增&优化多个图组件,新增交互、节点自定义等。调整目录结构,类型引入定义更简单。 - -### 新功能 - -> 新增节点、边交互状态,来源去向图、组织架构图、指标拆解树支持自定义节点,节点、边个性化设置,更灵活。 - -#### 来源去向图 - -> FlowAnalysisGraph - -| ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626337308273-1869606d-da26-470b-99ac-7dce4a324eb6.png#clientId=uc4615b79-7491-4&from=paste&height=396&id=ufe21e8a7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=396&originWidth=754&originalType=binary&ratio=1&size=103139&status=done&style=none&taskId=u8c9cb519-5085-4006-a5cc-7a15448f60a&width=754) | ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626335599778-010ac67d-982c-4fca-b240-d53ef55ef35a.png#clientId=ua9a3d129-b5b7-4&from=paste&height=448&id=u2b24628b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=448&originWidth=931&originalType=binary&ratio=1&size=89087&status=done&style=none&taskId=uc41711ad-8b1a-45d9-bacc-acab2f49541&width=931) | -| --- | --- | - -#### 组织架构图 - -> OrganizationGraph - -| ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626336025336-fe4de176-5cf4-490a-b222-dac7039bf628.png#clientId=ua9a3d129-b5b7-4&from=paste&height=506&id=u3f49c5ea&margin=%5Bobject%20Object%5D&name=image.png&originHeight=506&originWidth=923&originalType=binary&ratio=1&size=125664&status=done&style=none&taskId=uee2b4d05-5a34-41ab-824a-bd0b2af281f&width=923) | ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626336047983-fe2acb21-072f-486f-b0d8-3bb1498d52f1.png#clientId=ua9a3d129-b5b7-4&from=paste&height=372&id=u2cda8a8f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=372&originWidth=866&originalType=binary&ratio=1&size=92687&status=done&style=none&taskId=u6ab558b7-7d5a-43c3-b025-819b815da49&width=866) | -| --- | --- | - -​
- -#### 指标拆解树 - -> DecompositionTreeGraph - -| ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626336487396-701f2b89-4d96-4b43-aa2c-0a98e4ead468.png#clientId=ua9a3d129-b5b7-4&from=paste&height=340&id=ue6de2099&margin=%5Bobject%20Object%5D&name=image.png&originHeight=340&originWidth=721&originalType=binary&ratio=1&size=95783&status=done&style=none&taskId=u0babaee1-4b72-4114-8a53-5643b8d543c&width=721) | ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626336513538-be5d3195-b73f-4ed8-87d1-4e68834d7e71.png#clientId=ua9a3d129-b5b7-4&from=paste&height=361&id=u651a2cae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=361&originWidth=692&originalType=binary&ratio=1&size=74657&status=done&style=none&taskId=ua5a066c3-d3d8-4f01-bf7c-a2bc4c30f0a&width=692) | -| --- | --- | - -#### 辐射树图 - -> RadialTreeGraph - -| ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626336586223-5d573bc5-01da-43d8-b9cc-250e56cbe79f.png#clientId=ua9a3d129-b5b7-4&from=paste&height=213&id=ub106f57d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=525&originWidth=620&originalType=binary&ratio=1&size=157766&status=done&style=none&taskId=ufcc3ccab-ea60-42b4-98ba-10b0ca74d6c&width=252) | ![image.png](https://cdn.nlark.com/yuque/0/2021/png/278352/1626336604503-8982ef00-7d01-4a60-8132-4c78f2ffdb0a.png#clientId=ua9a3d129-b5b7-4&from=paste&height=225&id=u4541d37d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=513&originWidth=547&originalType=binary&ratio=1&size=133385&status=done&style=none&taskId=u468c94a6-439f-4b6b-b83d-78287c4251a&width=240) | -| --- | --- | - -​
- -### 不兼容改动 - -> 调整目录结构带来不兼容 - -#### 类型引入 - -旧: - -```typescript -import { Line } from '@ant-design/charts'; -import { LineConfig } from '@ant-design/charts/es/line'; -``` - -新: - -```typescript -import { Line, LineConfig } from '@ant-design/charts'; -``` - -#### 按需引入 - -旧: - -```typescript -import Line from '@ant-design/charts/es/line'; -``` - -新: - -```typescript -import Line from '@ant-design/charts/es/plots/line'; -``` - -#### 官网示例少了 - -由于目前 API 做了升级,还没来得及升级的图表暂时不在官网透出,但任然可以正常使用,也可以在官网访问。
只需手动输入网址即可。 - -```typescript -// 组件名为中划线格式 -https://charts.ant.design/zh-CN/demos/ + 组件名 -// OrganizationalGraph -https://charts.ant.design/zh-CN/demos/organizational-graph -``` - -## 1.1.20 - -`2021-06-29` - -- 🐞 修复 useGraph props 丢失,导致更新失败。 - -## 1.1.19 - -`2021-06-28` - -- 🆕 OrganizationalGraph 新增 [Title](https://charts.ant.design/demos/organizational-graph#set-title)。 - -## 1.1.18 - -`2021-06-24` - -- 🐞 修复 onReady & onEvent 类型错误。 -- ContainerOptions 统一为 ContainerConfig,并从 src/hooks/useChart 动到 src/interface 。 - -## 1.1.17 - -`2021-06-17` - -- 🐞 修复 graph autoFit 不生效。 -- 🆕 新增 Violin 文档。 - -## 1.1.16 - -`2021-06-15` - -- 🆕 新增 [Facet](https://charts.ant.design/demos/facet)。 -- 🆕 新增 Violin 组件,文档暂未透出。 - -## 1.1.15 - -`2021-06-14` - -- 🐞 所有图表 ts 类型统一。 - - ContainerProps to ContainerConfig. - - RelationGraph to CommonConfig. - - IndentedTreeProps to IndentedTreeGraphConfig. - - OrganizationTreeProps to OrganizationalGraphConfig. - -## 1.1.14 - -`2021-06-14` - -- 🆕 新增 [RadialGraph](https://charts.ant.design/demos/radial-graph#base)。 -- 🆕 IndentedTreeGraph 新增 markerPosition 功能,配合布局使用。 -- 🐞 修复 OrganizationalGraph 不支持线文本配置。 - -## 1.1.13 - -`2021-06-11` - -- 🆕 新增 OrganizationalGraph。OrganizationTreeGraph 的升级版,OrganizationTreeGraph 后续不在官网透出。 -- 🐞 修复 OrganizationTreeGraph changeData 布局出错。 - -## 1.1.12 - -`2021-06-10` - -- 🐞 大小写问题 - -## 1.1.11 - -`2021-06-09` - -- 🐞 合理化文件路径。 -- 🆕 IndentedTreeGraph 新增 animate 配置。 - -## 1.1.10 - -`2021-06-07` - -- 💄 CDN 使用方式 charts_g6 改名为 graphs - -## 1.1.9 - -`2021-06-04` - -- 🐞 修复引用类型,数据不更新。 - -## 1.1.8 - -`2021-06-04` - -- 🐞 修复 IndentedTreeGraph ts 类型出错。 - -## 1.1.5 - -`2021-06-01` - -- 🆕 新增 IndentedTreeGraph, 功能类似 IndentedTree ,IndentedTree 不再官网继续透出,功能保留。 -- 🆕 关系图内置 `grahpId` , 默认支持一个页面使用多个关系图。 -- 🆕 关系图新增 `loading`、`onReady`、`loadingTemplate` 等 props. - -## 1.1.4 - -`2021-05-14` - -- 🔥 已经废弃 MultiView ,更名为 Mix ,MultiView 继续保留,但不在文档透出。 - -## 1.1.3 - -`2021-04-28` - -- 🔥 默认支持按需加载 [#475](https://github.com/ant-design/ant-design-charts/issues/475) - -## 1.1.2 - -`2021-04-12` - -- 🐞 修复更新逻辑,config immutable 避免底层修改 config 后出现重复更新。 - -## 1.1.1 - -- remname: history -> CHANGELOG -- fix: graph layout and changeData error - -## 1.1.0 - -- 文档更新 [#545](https://github.com/ant-design/ant-design-charts/pull/545) -- feat: 新增弦图(Chord) [#545](https://github.com/ant-design/ant-design-charts/pull/545) -- fix: fund flow graph with large slope edges [#540](https://github.com/ant-design/ant-design-charts/pull/540) -- fix: 兼容 React17 [#542](https://github.com/ant-design/ant-design-charts/pull/542) -- 导出 G2Plot 相关 function [#545](https://github.com/ant-design/ant-design-charts/pull/545) - - flow - - measureTextWidth - - adaptors - -## 1.0.1 - -- 新增瀑布图 - -## 1.0.0 - -- 底层依赖架构全新升级 -- 新增全量 API -- 持续迭代 - -## 0.9.6 - -- 新增 onlyChangeData props 用于控制 changeData 。 - -## 0.9.5 - -- tooltip 添加 ReactNode 支持。 -- 提供额外 API : downloadImage()、toDataURL() 。 -- 新增 memoData props 用于控制 rerender 。 - -## 0.9.4 - -新增图表 - -- column-line -- dual-line -- groupedColumn-line -- stackedColumn-line - -删除图表 - -- OverlappedComboPlot - -## 0.9.2 - -修改 props 属性名,chartStyle => style。 +`2023-08-31` diff --git a/CONTRIBUTING.zh-CN.md b/CONTRIBUTING.zh-CN.md index 79ddeb3e5..c997ea148 100644 --- a/CONTRIBUTING.zh-CN.md +++ b/CONTRIBUTING.zh-CN.md @@ -10,12 +10,17 @@ ```bash # clone 代码 -$ git clone https://github.com/ant-design/ant-design-charts.git +$ git clone -b v2 https://github.com/ant-design/ant-design-charts.git $ cd ./ant-design-charts # 依赖安装,由于项目使用了 pnpm 做多包管理,如果没有安装 pnpm,请先[安装pnpm](https://pnpm.io/installation#using-npm),并配置对应的 [store-dir](https://pnpm.io/configuring) $ pnpm i # 先创建开发分支开发,分支名应该有含义,避免使用 update、tmp 之类的 $ git checkout -b branch-name +# 启动本地官网 +$ pnpm build:lib & pnpm start +# 监听要改动的包,eg plots +$ cd ./packages/plots +$ pnpm start # 开发完成后跑下测试是否通过,必要时需要新增或修改测试用例 $ pnpm test # 测试通过后,提交代码,message 见下面的规范 @@ -40,55 +45,10 @@ $ git push origin branch-name ```ts - packages - charts - - flowchart - - graphs - - maps - plots + - util ``` -以 graphs 为例,`cd ./packages/graphs`, 修改 scripts 里面的 `test:live` 文件路径修改为对应的测试文件, 并执行 `pnpm test:live` 即可,由于 afterEach 会移除对应的 DOM,测试时记得注释掉。 - -```tsx -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { render } from '../../src/utils'; -import { FileTreeGraph } from '../../src'; -import { FileData } from '../data'; - -describe('File Tree', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - // document.body.removeChild(container); - // container = null; - }); - it('render', () => { - let chartRef = undefined; - const chartProps = { - data: FileData, - onReady: (graph) => { - chartRef = graph; - }, - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect( - chartRef - .findById('0-1') - .get('group') - .getChildren() - .filter((item) => item.cfg.name === 'text-shape').length, - ).toBe(1); - }); -}); -``` - - ### 代码风格 你的代码风格必须通过 eslint,你可以运行 `$ pnpm lint -r` 本地测试。 @@ -190,4 +150,4 @@ Ant Design Charts 基于 [semver](http://semver.org/lang/zh-CN/) 语义化版本 - 避免提交重复的 issue,在提交之前搜索现有的 issue。 - 在标签、标题或内容中体现明确的意图。 -随后 AntV 负责人会确认 issue 意图,更新合适的标签,关联 milestone,指派开发者。 \ No newline at end of file +随后 AntV 负责人会确认 issue 意图,更新合适的标签,关联 milestone,指派开发者。 diff --git a/README.md b/README.md index 8478a81a3..a0dee6daf 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,19 @@
-A React chart library, based on [G2Plot](https://github.com/antvis/G2Plot), [G6](https://github.com/antvis/G6), [XFlow](https://github.com/antvis/XFlow), [L7Plot](https://github.com/antvis/L7Plot). +A React chart library, based on [G2](https://github.com/antvis/G2), [G6](https://github.com/antvis/G6), [X6](https://github.com/antvis/X6), [L7](https://github.com/antvis/L7). -![build](https://github.com/ant-design/ant-design-charts/workflows/build/badge.svg) ![npm](https://img.shields.io/npm/v/@ant-design/charts) ![npm](https://img.shields.io/npm/dm/@ant-design/charts) [![GitHub stars](https://img.shields.io/github/stars/ant-design/ant-design-charts)](https://github.com/ant-design/ant-design-charts/stargazers) [![npm License](https://img.shields.io/npm/l/@ant-design/charts.svg)](https://www.npmjs.com/package/@ant-design/charts) +

- Website • - Quick Start • - Gallery • - FAQ • - Blog + Website • + Quick Start • + Examples • + FAQ

@@ -25,16 +24,6 @@ A React chart library, based on [G2Plot](https://github.com/antvis/G2Plot), [G6] ### Statistical charts -### Flowchart - - -### Maps - - -### Relation Graphs - - - ## ✨ Features - Easy to use @@ -70,51 +59,13 @@ const Page: React.FC = () => { { year: '1999', value: 13 }, ]; - let chart; - const props = { - config: { - data, - width: 800, - height: 400, - autoFit: false, - xField: 'year', - yField: 'value', - point: { - size: 5, - shape: 'diamond', - }, - label: { - style: { - fill: '#aaa', - }, - }, - onReady: (chartInstance) => chart = chartInstance - } - }; - - - // Export Image - const downloadImage = () => { - chart?.downloadImage(); - }; - - // Get chart base64 string - const toDataURL = () => { - console.log(chart?.toDataURL()); + data, + xField: 'year', + yField: 'value', }; - return ( -
- - - -
- ); + return }; export default Page; ``` @@ -124,20 +75,6 @@ Preview -## 📜 Document & API - -See chart API for details. Common props: - -| Property | Description | Type | defaultValue | -| :--- | :--- | :--- | :--- | -| onReady | chart loaded callback | (chart)=> void | - | -| onEvent | chart events | (chart, event)=> void | - | -| loading | loading status | boolean | - | -| loadingTemplate | loading template | React.ReactElement | - | -| errorTemplate | custom error template | (e: Error) => React.ReactNode | - | -| className | container class | string | - | -| style | container style | React.CSSProperties | - | - ## Development Clone locally: @@ -146,8 +83,7 @@ See chart API for details. Common props: $ git clone git@github.com:ant-design/ant-design-charts.git $ cd ant-design-charts $ pnpm install -$ pnpm build:lib -$ pnpm start +$ pnpm build:lib & pnpm start ``` ## 🤝 How to Contribute diff --git a/config/jest.js b/config/jest.js index ad191ed22..f374d04ef 100644 --- a/config/jest.js +++ b/config/jest.js @@ -8,7 +8,6 @@ const BaseJestConfig = { '\\.(less|css)$': 'jest-less-loader', }, moduleNameMapper: { - '^lodash-es$': 'lodash', '^.+\\.(css|less)$': 'identity-obj-proxy', }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], diff --git a/config/webpack.js b/config/webpack.js index 68cbec048..6a6f79b32 100644 --- a/config/webpack.js +++ b/config/webpack.js @@ -15,8 +15,10 @@ const getWebpackConfig = (name, library) => { }, output: { filename: '[name].min.js', - library: library, - libraryTarget: 'umd', + library: { + name: library, + type: 'umd', + }, path: resolveApp('dist/'), }, resolve: { @@ -38,6 +40,12 @@ const getWebpackConfig = (name, library) => { commonjs: 'react-dom', amd: 'react-dom', }, + lodash: { + root: '_', + commonjs2: 'lodash', + commonjs: 'lodash', + amd: 'lodash', + }, }, module: { rules: [ @@ -137,8 +145,8 @@ const getWebpackConfig = (name, library) => { }, }, optimization: { - runtimeChunk: 'single', - moduleIds: 'deterministic', + // runtimeChunk: true, + // moduleIds: 'deterministic', }, }; }; diff --git a/mako.config.json b/mako.config.json new file mode 100644 index 000000000..c9c854339 --- /dev/null +++ b/mako.config.json @@ -0,0 +1,10 @@ +{ + "optimization": { + "skipModules": false, + "concatenateModules": false + }, + "moduleIdStrategy": "hashed", + "experimental": { + "magicComment": true + } +} diff --git a/package.json b/package.json index b1f4018e6..27334f83e 100644 --- a/package.json +++ b/package.json @@ -2,70 +2,83 @@ "name": "charts", "private": true, "scripts": { - "start": "pnpm -r --stream --filter=./site run start", + "start": "pnpm run build:lib && pnpm -r --stream --filter=./site run start", "test": "pnpm -r --stream --filter=./packages/* run test", + "test:e2e": "pnpm exec playwright test", "lint": "pnpm -r --stream --filter=./packages/* run lint", - "build": "pnpm -r --stream --filter=!./packages/site run build", - "build:lib": "pnpm -r --stream --filter=!./packages/site run build:lib", + "build": "pnpm -r --stream --filter=!./site run build", + "build:site": "pnpm -r --stream --filter=./site run build", + "build:lib": "pnpm -r --stream --filter=!./site run build:lib", + "dev:graphs": "cd packages/graphs && pnpm run dev", "profile": "webpack --config webpack.config.js --mode production --profile --json > stats.json", "prettier": "prettier --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"", "ci:version": "pnpm changeset version", - "add:changelog": "pnpm changeset" + "add:changelog": "pnpm changeset", + "before:release": "pnpm run build", + "release": "pnpm run before:release && pnpm publish --no-git-checks -r --registry=https://registry.npmjs.org --filter @ant-design/*", + "release:alpha": "pnpm run before:release && pnpm publish --tag alpha --no-git-checks -r --filter @ant-design/*", + "release:beta": "pnpm run before:release && pnpm publish --tag beta --no-git-checks -r --filter @ant-design/*" }, "devDependencies": { - "@antv/data-set": "^0.11.8", - "@babel/core": "^7.11.6", + "@babel/core": "^7.26.0", "@babel/polyfill": "^7.12.1", - "@babel/preset-env": "^7.13.9", - "@babel/preset-typescript": "^7.10.4", - "@changesets/cli": "^2.24.4", - "@swc/jest": "^0.2.23", - "@testing-library/jest-dom": "^5.5.0", + "@babel/preset-env": "^7.26.0", + "@babel/preset-typescript": "^7.26.0", + "@changesets/cli": "^2.27.9", + "@playwright/test": "^1.49.1", + "@swc/jest": "^0.2.37", + "@testing-library/jest-dom": "^5.17.0", "@testing-library/react-hooks": "^7.0.2", - "@types/enzyme": "^3.10.5", - "@types/node": "^14.0.10", - "@types/webpack-env": "^1.14.1", - "@umijs/fabric": "^2.0.7", - "babel-loader": "^8.2.3", - "babel-plugin-named-asset-import": "^0.3.7", + "@types/enzyme": "^3.10.18", + "@types/node": "^14.18.63", + "@types/webpack-env": "^1.18.5", + "@umijs/fabric": "^2.14.1", + "babel-loader": "^8.4.1", + "babel-plugin-named-asset-import": "^0.3.8", "babel-polyfill": "^6.26.0", - "babel-preset-react-app": "^10.0.0", + "babel-preset-react-app": "^10.0.1", "babel-types": "^6.26.0", - "chalk": "^4.1.0", - "css-loader": "^6.4.0", - "ejs": "^3.0.2", + "chalk": "^4.1.2", + "cross-env": "^7.0.3", + "css-loader": "^6.11.0", + "ejs": "^3.1.10", "enzyme": "^3.11.0", - "enzyme-to-json": "^3.4.4", + "enzyme-to-json": "^3.6.2", "eslint": "^7.32.0", "identity-obj-proxy": "^3.0.0", - "jest": "^26.0.1", - "jest-canvas-mock": "^2.4.0", + "jest": "^26.6.3", + "jest-canvas-mock": "^2.5.2", "jest-electron": "^0.1.12", - "jest-extended": "^1.2.0", + "jest-extended": "^1.2.1", "jest-less-loader": "^0.1.2", - "less": "^4.1.2", - "less-loader": "^10.1.0", - "lodash": "^4.17.20", - "np": "*", + "less": "^4.2.0", + "less-loader": "^10.2.0", + "np": "^10.0.7", "npm-run-all": "^4.1.5", - "prettier": "^2.0.2", - "pretty-quick": "^3.0.1", + "prettier": "^2.8.8", + "prettier-plugin-organize-imports": "^4.1.0", + "prettier-plugin-packagejson": "^2.5.3", + "pretty-quick": "^3.3.1", "react-dev-utils": "^12.0.1", - "style-loader": "^3.3.0", - "typescript": "^4.0.3", - "vfile-reporter": "^7.0.2", - "webpack": "^5.58.2", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.0", - "webpack-dev-server": "^4.3.1", - "whatwg-fetch": "^3.0.0", + "rimraf": "^3.0.2", + "style-loader": "^3.3.4", + "ts-loader": "^9.5.1", + "typescript": "^5.6.3", + "vfile-reporter": "^7.0.5", + "webpack": "^5.96.1", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.1.0", + "whatwg-fetch": "^3.6.20", "yorkie": "^2.0.0" }, "pnpm": { "overrides": { "@typescript-eslint/eslint-plugin": "^4.1.1", "@typescript-eslint/parser": "^4.1.1", - "monaco-editor": "0.21.3" + "tslib": "2.6.1", + "react-dom": "^18.0.1", + "react": "^18.0.1" } }, "gitHooks": { @@ -74,11 +87,13 @@ "sideEffects": false, "license": "MIT", "dependencies": { + "conventional-changelog-cli": "^5.0.0", "less-plugin-npm-import": "^2.1.0", "remark-frontmatter": "^4.0.1", - "remark-parse": "^10.0.1", - "remark-stringify": "^10.0.2", - "to-vfile": "^7.2.3", + "remark-parse": "^10.0.2", + "remark-stringify": "^10.0.3", + "to-vfile": "^7.2.4", "unified": "^10.1.2" - } + }, + "repository": "https://github.com/ant-design/ant-design-charts.git" } diff --git a/packages/charts/CHANGELOG.md b/packages/charts/CHANGELOG.md new file mode 100644 index 000000000..7d36a6d79 --- /dev/null +++ b/packages/charts/CHANGELOG.md @@ -0,0 +1,55 @@ +## 2.2.6 + +`2024-12-20` + +- 🔥 [Graphs] 适配 G6 最新版本 + +## 2.2.3 + +`2024-11-11` + +- 🔥 已接入 8 类流程图组件 + +## 2.2.1 + +`2024-08-26` + +- 🐞 [🧐[问题]ESM packages (lodash-es) need to be imported. ](https://github.com/ant-design/ant-design-charts/issues/2489) + +## 2.1.2 + +`2024-07-24` + +- 🔥 Support SSR + +## 2.1.1 + +`2024-05-06` + +- 🐞 修复低版本 webpack 不支持的 `export * as` 语法 + +## 2.1.0 + +`2024-04-19` + +- 🐞 修复低版本 webpack 不支持的 `export * as` 语法 +- 🔥 暴露图表 ref 上层可通过 ref 直接获取图表实例,eg: `` + +## 2.0.3 + +`2024-04-16` + +- 🔥 底层依赖升级 +- 🔥 plots 功能完善 + +## 2.0.3 + +`2023-12-18` + +- 🐞 修复 plots 依赖版本问题 + +## 2.0.0 + +`2023-11-22` + +- 🔥 2.0 正式版本发布,目前已完成统计图表的升级,关系图、地理可视化、流程图等会在后续次版本更新,[next 版本官网](https://ant-design-charts-next.antgroup.com/) diff --git a/packages/charts/README.md b/packages/charts/README.md index ab3a3cd81..dea8be701 100644 --- a/packages/charts/README.md +++ b/packages/charts/README.md @@ -2,7 +2,7 @@
-A React chart library, based on [G2Plot](https://github.com/antvis/G2Plot), [G6](https://github.com/antvis/G6), [X6](https://github.com/antvis/X6), [L7Plot](https://github.com/antvis/L7Plot). +A React chart library, based on [G2](https://github.com/antvis/G2), [G6](https://github.com/antvis/G6), [X6](https://github.com/antvis/X6), [L7](https://github.com/antvis/L7). ![build](https://github.com/ant-design/ant-design-charts/workflows/build/badge.svg) ![npm](https://img.shields.io/npm/v/@ant-design/charts) @@ -15,7 +15,6 @@ A React chart library, based on [G2Plot](https://github.com/antvis/G2Plot), [G6] Quick StartGalleryFAQ • - Blog

@@ -72,45 +71,11 @@ const Page: React.FC = () => { const config = { data, - width: 800, - height: 400, - autoFit: false, xField: 'year', yField: 'value', - point: { - size: 5, - shape: 'diamond', - }, - label: { - style: { - fill: '#aaa', - }, - }, }; - let chart; - - // Export Image - const downloadImage = () => { - chart?.downloadImage(); - }; - - // Get chart base64 string - const toDataURL = () => { - console.log(chart?.toDataURL()); - }; - - return ( -
- - - (chart = chartInstance)} /> -
- ); + return ; }; export default Page; ``` @@ -120,21 +85,6 @@ Preview -## 📜 Document & API - -See chart API for details. Common props: - -| Property | Description | Type | defaultValue | -| :--- | :--- | :--- | :--- | -| onReady | chart loaded callback | (chart)=> void | - | -| onEvent | chart events | (chart, event)=> void | - | -| loading | loading status | boolean | - | -| loadingTemplate | loading template | React.ReactElement | - | -| errorTemplate | custom error template | (e: Error) => React.ReactNode | - | -| className | container class | string | - | -| style | container style | React.CSSProperties | - | - - ## 🤝 How to Contribute Your contributions are always welcome! Please Do have a look at the [issues](https://github.com/ant-design/ant-design-charts/issues) first. diff --git a/packages/charts/package.json b/packages/charts/package.json index 953850fac..8893b34e0 100644 --- a/packages/charts/package.json +++ b/packages/charts/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "@ant-design/charts", - "version": "1.4.2", + "version": "2.2.6", "description": "AntV upper level visual component library", "bugs": { "url": "https://github.com/ant-design/ant-design-charts/issues" @@ -20,7 +20,7 @@ "/dist" ], "scripts": { - "build": "run-s clean lib dist copy", + "build": "run-s clean lib dist", "ci": "pnpm run build && pnpm run test:coverage", "clean": "rimraf lib es dist", "dist": "webpack --config webpack.config.js --mode production", @@ -28,25 +28,23 @@ "lib": "run-p lib:*", "lib:cjs": "tsc -p tsconfig.json --target ES5 --module commonjs --outDir lib", "lib:es": "tsc -p tsconfig.json --target ES5 --module ESNext --outDir es", - "copy": "node ./scripts/copy.js" + "prepublishOnly": "pnpm run build", + "tag": "node ./scripts/tag.mjs" }, "dependencies": { - "@ant-design/flowchart": "workspace:*", - "@ant-design/graphs": "workspace:*", - "@ant-design/maps": "workspace:*", - "@ant-design/plots": "workspace:*" + "@ant-design/graphs": "workspace:^", + "@ant-design/plots": "workspace:^", + "lodash": "^4.17.21", + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "peerDependencies": { - "@ant-design/icons": "^4.6.0", - "antd": "^4.6.3", - "lodash": "^4.17.20", "react": ">=16.8.4", "react-dom": ">=16.8.4" }, "sideEffects": false, "license": "MIT", "devDependencies": { - "npm-run-all": "^4.1.5", - "rimraf": "^3.0.2" + "open": "^10.1.0" } } diff --git a/packages/charts/scripts/copy.js b/packages/charts/scripts/copy.js deleted file mode 100644 index 50d77a593..000000000 --- a/packages/charts/scripts/copy.js +++ /dev/null @@ -1,4 +0,0 @@ -const shell = require('shelljs'); - -// 复制本地 g2plot.min.js -shell.exec(`cp ../flowchart/dist/index.css ./dist`); diff --git a/packages/charts/scripts/tag.mjs b/packages/charts/scripts/tag.mjs new file mode 100644 index 000000000..ee8e0d4ec --- /dev/null +++ b/packages/charts/scripts/tag.mjs @@ -0,0 +1,22 @@ +import chalk from 'chalk'; +import { execFileSync } from 'child_process'; +import { readFileSync } from 'fs'; +import open from 'open'; +import readline from 'readline'; + +const pkg = JSON.parse(readFileSync('./package.json', 'utf-8')); +const version = pkg.version; +const repository = pkg.repository.url; + +console.log(chalk.yellow('The tag will be created with the version: '), chalk.bold(chalk.green(version))); + +const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + +rl.question('Do you want to continue? (y/n): ', (answer) => { + if (answer === 'y') { + execFileSync('git', ['tag', version]); + execFileSync('git', ['push', 'origin', version]); + open(`${repository}/releases/new`); + } + rl.close(); +}); diff --git a/packages/charts/src/index.ts b/packages/charts/src/index.ts index a1bf6a389..347e7cbb9 100644 --- a/packages/charts/src/index.ts +++ b/packages/charts/src/index.ts @@ -1,6 +1,2 @@ -export * from '@ant-design/plots'; -export * from '@ant-design/flowchart'; export * from '@ant-design/graphs'; -export * from '@ant-design/maps'; - -export const version = '1.4.2'; +export * from '@ant-design/plots'; diff --git a/packages/flowchart/CHANGELOG.md b/packages/flowchart/CHANGELOG.md deleted file mode 100644 index 7282fac7d..000000000 --- a/packages/flowchart/CHANGELOG.md +++ /dev/null @@ -1,31 +0,0 @@ -## 1.2.2 - -`2023-01-12` - -- 🐞 修复 React18 页面卡死 -- 🔥 新增 `onDelNode、onCopy、onPaste` 方法 - -## 1.2.1 - -`2022-08-23` - -- 🐞 修复流程图 `canvasProps.config` 配置无效 - -## 1.2.0 - -`2022-08-16` - -- 🔥 新增 canvasProps.showPortsOnNodeSelected 属性,支持选中节点时连线 -- 🔥 新增 canvasProps.edgeConfig 属性,支持默认连线配置 - -## 1.1.9 - -`2022-07-26` - -- 🐞 [新建边无反应](https://github.com/ant-design/ant-design-charts/issues/1455) - -## 1.1.8 - -`2022-07-19` - -- 🐞 [Critical security vulnerabilities immer](https://github.com/ant-design/ant-design-charts/issues/1289) diff --git a/packages/flowchart/README.md b/packages/flowchart/README.md deleted file mode 100644 index 66c214483..000000000 --- a/packages/flowchart/README.md +++ /dev/null @@ -1,173 +0,0 @@ -# @ant-design/flowchart - -
- -Flowchart solutions, based on [XFlow](https://github.com/antvis/xflow). - -![npm](https://img.shields.io/npm/v/@ant-design/charts) ![npm](https://img.shields.io/npm/dm/@ant-design/flowchart) [![GitHub stars](https://img.shields.io/github/stars/ant-design/ant-design-charts)](https://github.com/ant-design/ant-design-charts/stargazers) [![npm License](https://img.shields.io/npm/l/@ant-design/charts.svg)](https://www.npmjs.com/package/@ant-design/charts) - -

- Website • - Quick Start • - Gallery • - FAQ • - Blog -

- -
- -## Case - -
- - -
- -## ✨ Features - -- Easy to use -- TypeScript - -## 📦 Installation - -```bash | pure -$ npm install @ant-design/flowchart -``` - -## 🔨 Usage - -```tsx | pure -import React from 'react'; -import { Flowchart } from '@ant-design/flowchart'; -import 'antd/dist/antd.css'; -import '@ant-design/flowchart/dist/index.css'; - -const DemoFlowchart = () => { - return ( -
- { - console.log(d, JSON.stringify(d)); - }} - toolbarPanelProps={{ - position: { - top: 0, - left: 0, - right: 0, - }, - }} - scaleToolbarPanelProps={{ - layout: 'horizontal', - position: { - right: 0, - top: -40, - }, - style: { - width: 150, - height: 39, - left: 'auto', - background: 'transparent', - }, - }} - canvasProps={{ - position: { - top: 40, - left: 0, - right: 0, - bottom: 0, - }, - }} - nodePanelProps={{ - position: { width: 160, top: 40, bottom: 0, left: 0 }, - }} - detailPanelProps={{ - position: { width: 200, top: 40, bottom: 0, right: 0 }, - }} - /> -
- ); -}; -export default DemoFlowchart; -``` - -Preview - - - -## 📜 Document & API - -```ts -export interface FlowchartProps extends FlowchartContainerProps { - /** 默认数据 */ - data?: Datum; - - /** 画布是否自动居中 */ - isAutoCenter?: boolean; - - /** 节点面板配置 */ - nodePanelProps?: NodePanelProps; - - /** 画布主要区域配置 */ - canvasProps?: CanvasProps; - - /** - * @title 画布状态 - * @description scan 会禁用一些画布事件,例如连线、鼠标右键等 - * @default "edit" - */ - mode?: 'edit' | 'scan'; - - /** toolbar */ - toolbarPanelProps?: ToolbarPanelProps; - - /** keyBinding */ - keyBindingProps?: false | KeybindingConfig; - - /** scale toolbar */ - scaleToolbarPanelProps?: ScaleToolbarPanelProps; - - /** form editor */ - detailPanelProps?: DetailPanelProps; - - /** 右键菜单配置 */ - contextMenuPanelProps?: ContextMenuPanelProps; - - /** popover */ - popoverProps?: PopoverProps; - - /** onReady */ - onReady?: (graph: IFlowchartGraph, app: IApplication) => void; - - /** 点击回调,仅支持 save-graph-data */ - onSave?: (data: Datum) => void; - - /** 新增节点时回调 */ - onAddNode?: (node: NsGraph.INodeConfig) => void; - - /** 新增边时回调 */ - onAddEdge?: (edge: NsGraph.IEdgeConfig) => void; - - /** xflow app 销毁前的回调 */ - onDestroy?: IAppDestroy; - - /** xflow app 初始化后的回调 */ - onConfigReady?: IAppConfigReady; - - /** 节点或边更新数据时调用 */ - onConfigChange?: (params: { data: Datum; type: string; config?: NsGraph.INodeConfig | NsGraph.IEdgeConfig }) => void; -} -``` - -## 🤝 How to Contribute - -Your contributions are always welcome! Please Do have a look at the [issues](https://github.com/ant-design/ant-design-charts/issues) first. - -## 📧 Contact us - -DingTalk group number: `44788198 `. - - - -## License - -MIT diff --git a/packages/flowchart/jest.config.js b/packages/flowchart/jest.config.js deleted file mode 100644 index 6f8dc6a64..000000000 --- a/packages/flowchart/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const { BaseJestConfig, OnlineConfig } = require('../../config/jest'); -module.exports = { - ...BaseJestConfig, - ...(process.env.DEBUG_MODE === '1' ? OnlineConfig : {}), - moduleNameMapper: { - '^lodash-es$': 'lodash', - '^.+\\.(css|less)$': 'identity-obj-proxy', - '@antv/xflow': '/node_modules/@antv/xflow/dist/index.umd.js', - }, -}; diff --git a/packages/flowchart/jest.setup.js b/packages/flowchart/jest.setup.js deleted file mode 100644 index 172d187cc..000000000 --- a/packages/flowchart/jest.setup.js +++ /dev/null @@ -1,15 +0,0 @@ -require('../../config/setup'); -if (!global._babelPolyfill) { - require('babel-polyfill'); - if (process.env.DEBUG_MODE) { - const createLink = (src) => { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.className = 'dynamic-link'; - link.href = src; - document.getElementsByTagName('head')[0].appendChild(link); - }; - createLink('https://unpkg.com/antd@4.21.1/dist/antd.css'); - createLink('https://unpkg.com/@ant-design/flowchart@1.2.0/dist/index.css'); - } -} diff --git a/packages/flowchart/package.json b/packages/flowchart/package.json deleted file mode 100644 index 839c290ee..000000000 --- a/packages/flowchart/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "@ant-design/flowchart", - "version": "1.2.2", - "description": "Flowchart", - "bugs": { - "url": "https://github.com/ant-design/ant-design-charts/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/ant-design/ant-design-charts.git" - }, - "main": "lib/index.js", - "unpkg": "dist/flowchart.min.js", - "module": "es/index.js", - "types": "es/index.d.ts", - "files": [ - "/lib", - "/es", - "/dist" - ], - "scripts": { - "start": "pnpm lib:es --w", - "build": "run-s clean lib dist less", - "build:lib": "pnpm build", - "ci": "pnpm run build && pnpm run test:coverage", - "clean": "rimraf lib es dist", - "dist": "webpack --config webpack.config.js --mode production", - "profile": "webpack --config webpack.config.js --mode production --profile --json > stats.json", - "lib": "run-p lib:*", - "lib:cjs": "tsc -p tsconfig.prod.json --target ES5 --module commonjs --outDir lib", - "lib:es": "tsc -p tsconfig.prod.json --target ES5 --module ESNext --outDir es", - "less": "run-p less:*", - "less:build": "node ./scripts/less.js", - "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --fix --format=pretty ./src && pnpm run lint:prettier", - "lint:prettier": "pnpm run prettier && git diff && prettier --version && prettier --check \"src/**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto", - "prettier": "prettier --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"", - "test": "jest", - "test:live": "cross-env DEBUG_MODE=1 jest --watch ./tests/graph/normal-spec.tsx --no-cache" - }, - "dependencies": { - "@antv/layout": "^0.1.17", - "@antv/x6": "^1.25.0", - "@antv/x6-react-components": "^1.1.13", - "@antv/x6-react-shape": "^1.4.5", - "@antv/xflow": "^1.0.53", - "react-color": "2.17.3", - "react-use": "17.3.1" - }, - "peerDependencies": { - "@ant-design/icons": "^4.6.0", - "antd": "^4.6.3", - "lodash": "^4.17.20", - "react": ">=16.8.4", - "react-dom": ">=16.8.4" - }, - "sideEffects": false, - "license": "MIT", - "devDependencies": { - "@types/react": "^18.0.0", - "antd": "^4.16.13", - "lodash": "^4.17.20", - "cross-env": "^7.0.3", - "npm-run-all": "^4.1.5", - "rimraf": "^3.0.2", - "@types/jest": "^26.0.0" - } -} diff --git a/packages/flowchart/scripts/less.js b/packages/flowchart/scripts/less.js deleted file mode 100644 index c8e982f9d..000000000 --- a/packages/flowchart/scripts/less.js +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const fse = require('fs-extra'); -const cp = require('child_process'); - -const cwd = process.cwd(); -const es = path.join(cwd, 'es'); -const lib = path.join(cwd, 'lib'); -const dist = path.join(cwd, 'dist'); -const src = path.join(cwd, 'src'); -const styleEntry = path.join(cwd, 'src/style'); - -function compile(source, target) { - try { - let cmd = '../../node_modules/.bin/lessc'; - if (os.type() === 'Windows_NT') { - cmd = path.join(cwd, '../../node_modules/.bin/lessc.cmd'); - } - - cp.execFileSync(cmd, [ - // https://www.npmjs.com/package/less-plugin-npm-import - '--npm-import=prefix=~', - //https://lesscss.org/usage/#command-line-usage-relative-urls - '--rewrite-urls=all', - '--js', - source, - target, - ]); - } catch (error) { - console.log(error, source, target); - } -} - -let lessFiles = []; - -// Copy less files -function readdir(dir) { - if (fs.existsSync(dir)) { - const files = fs.readdirSync(dir); - files.forEach((file) => { - const sub = path.join(dir, file); - const stat = fs.statSync(sub); - if (stat && stat.isDirectory()) { - readdir(sub); - } else { - const ext = path.extname(file); - if (ext === '.less' || ext === '.css') { - console.log('find less file, compiling', file); - const less = path.relative(src, sub); - lessFiles.push({ - file: file, - absolutePath: sub, - relativePath: path.relative(styleEntry, sub), - }); - const name = less.substr(0, less.length - ext.length); - // copy less - fse.copySync(sub, path.join(es, less)); - fse.copySync(sub, path.join(lib, less)); - // compile less to css and compile - compile(sub, path.join(es, `${name}.css`)); - compile(sub, path.join(lib, `${name}.css`)); - } - } - }); - } -} - -// create dist for styles -function ensureDistIsCreated() { - const dirs = [es, lib]; - dirs.forEach((dir) => { - const dirPath = path.join(dir, 'style'); - if (!fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath); - console.log('dir is created:', dirPath); - } - }); -} - -// Build components in one file: lib/style/components.less -function rollup(files) { - console.log('Generate "style/components.less"'); - let content = ''; - files.forEach((file) => { - if (fs.existsSync(file.absolutePath)) { - content += `@import "${file.relativePath}";\n`; - } - }); - const source = path.join(es, 'style', 'components.less'); - fs.writeFileSync(source, content); - fs.writeFileSync(path.join(lib, 'style', 'components.less'), content); - compile(source, path.join(es, 'style', 'components.css')); - compile(source, path.join(lib, 'style', 'components.css')); - compile(source, path.join(dist, 'index.css')); -} - -fs.readdir(src, (err, files) => { - lessFiles = []; - files.forEach((file) => { - const dirPath = path.join(src, file); - const stat = fs.statSync(dirPath); - if (stat.isDirectory()) { - readdir(path.join(src, file)); - } - }); - // create dist for styles - ensureDistIsCreated(); - // Build components in one file: lib/style/components.less - rollup(lessFiles); -}); diff --git a/packages/flowchart/src/components/menu/index.ts b/packages/flowchart/src/components/menu/index.ts deleted file mode 100644 index 49c5d8846..000000000 --- a/packages/flowchart/src/components/menu/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { NsNodeCmd, NsEdgeCmd, IMenuOptions, NsGraph } from '@antv/xflow'; -import { createCtxMenuConfig, MenuItemType } from '@antv/xflow'; -import { IconStore, XFlowNodeCommands, XFlowEdgeCommands } from '@antv/xflow'; -import { DeleteOutlined, EditOutlined, StopOutlined } from '@ant-design/icons'; - -/** menuitem 配置 */ -export namespace NsMenuItemConfig { - /** 注册菜单依赖的icon */ - IconStore.set('DeleteOutlined', DeleteOutlined); - IconStore.set('EditOutlined', EditOutlined); - IconStore.set('StopOutlined', StopOutlined); - - export const DELETE_EDGE: IMenuOptions = { - id: XFlowEdgeCommands.DEL_EDGE.id, - label: '删除边', - iconName: 'DeleteOutlined', - onClick: async ({ target, commandService }) => { - if (target.data) { - commandService.executeCommand(XFlowEdgeCommands.DEL_EDGE.id, { - edgeConfig: target.data as NsGraph.IEdgeConfig, - }); - } - }, - }; - - export const DELETE_NODE = (onDelNode): IMenuOptions => { - return { - id: XFlowNodeCommands.DEL_NODE.id, - label: '删除节点', - iconName: 'DeleteOutlined', - onClick: async ({ target, commandService }) => { - if (target.data && target?.data?.id) { - commandService.executeCommand(XFlowNodeCommands.DEL_NODE.id, { - nodeConfig: { id: target?.data?.id }, - }); - if (typeof onDelNode === 'function') onDelNode(target.data); - } - }, - }; - }; - - export const EMPTY_MENU: IMenuOptions = { - id: 'EMPTY_MENU_ITEM', - label: '暂无可用', - isEnabled: false, - iconName: 'DeleteOutlined', - }; - - export const SEPARATOR: IMenuOptions = { - id: 'separator', - type: MenuItemType.Separator, - }; -} - -export const useMenuConfig: Function = createCtxMenuConfig((config, proxy) => { - const { showOfficial = true, submenu, onDelNode } = proxy.getValue(); - config.setMenuModelService(async (target, model) => { - if (!target) { - return; - } - const { type } = target; - - switch (type) { - /** 节点菜单 */ - case 'node' /* */: - model.setValue({ - id: 'root', - type: MenuItemType.Root, - submenu: (showOfficial ? [NsMenuItemConfig.DELETE_NODE(onDelNode)] : []).concat( - submenu ? submenu({ ...config, menuType: 'node' }) : [], - ), - }); - break; - /** 边菜单 */ - case 'edge': - model.setValue({ - id: 'root', - type: MenuItemType.Root, - submenu: (showOfficial ? [NsMenuItemConfig.DELETE_EDGE] : []).concat( - submenu ? submenu({ ...config, menuType: 'edge' }) : [], - ), - }); - break; - /** 画布菜单 */ - case 'blank': - model.setValue({ - id: 'root', - type: MenuItemType.Root, - submenu: (showOfficial ? [NsMenuItemConfig.EMPTY_MENU] : []).concat( - submenu ? submenu({ ...config, menuType: 'blank' }) : [], - ), - }); - break; - /** 默认菜单 */ - default: - model.setValue({ - id: 'root', - type: MenuItemType.Root, - submenu: (showOfficial ? [NsMenuItemConfig.EMPTY_MENU] : []).concat( - submenu ? submenu({ ...config, menuType: 'blank' }) : [], - ), - }); - break; - } - }); -}); diff --git a/packages/flowchart/src/components/toolbar/constants.ts b/packages/flowchart/src/components/toolbar/constants.ts deleted file mode 100644 index 77c020e82..000000000 --- a/packages/flowchart/src/components/toolbar/constants.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 全量 Command,用户通过 name 指定,支持配置式和命令式 - * enum Command {Undo, Redo, SaveGraphData,frontNode,backNode} - */ - -/** undo 操作 */ -const UNDO_CMD = 'undo-cmd'; -/** redo 操作 */ -const REDO_CMD = 'redo-cmd'; -/** 保存 */ -const SAVE_GRAPH_DATA = 'save-graph-data'; -/** 置前 */ -const FRONT_NODE = 'front-node'; -/** 置后 */ -const BACK_NODE = 'back-node'; - -const MULTI_SELECT = 'graph-toggle-multi-select'; - -const ADD_GROUP = 'add-group'; - -const DEL_GROUP = 'del-group'; - -const COPY = 'graph-copy-selection'; - -const PASTE = 'graph-paste-selection'; - -export const CommandPool = { - UNDO_CMD, - REDO_CMD, - SAVE_GRAPH_DATA, - FRONT_NODE, - BACK_NODE, - MULTI_SELECT, - ADD_GROUP, - DEL_GROUP, - COPY, - PASTE, -}; diff --git a/packages/flowchart/src/components/toolbar/index.tsx b/packages/flowchart/src/components/toolbar/index.tsx deleted file mode 100644 index f18e6cf1d..000000000 --- a/packages/flowchart/src/components/toolbar/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { CanvasToolbar as XFlowToolbarPanel } from '@antv/xflow'; -import { useToolbarConfig } from './util'; -import { FlowchartProps } from '../../interface'; - -export const ToolbarPanel: React.FC = (props) => { - const { - layout = 'horizontal', - position = { top: 0, left: 240, right: 240, bottom: 0 }, - show = true, - className, - style, - } = props; - - const toolbarConfig = useToolbarConfig(props); - if (!show) { - return null; - } - return ( - - ); -}; - -export default ToolbarPanel; diff --git a/packages/flowchart/src/components/toolbar/util.ts b/packages/flowchart/src/components/toolbar/util.ts deleted file mode 100644 index 16d701e34..000000000 --- a/packages/flowchart/src/components/toolbar/util.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { - createToolbarConfig, - IModelService, - IToolbarItemOptions, - NsGroupCmd, - uuidv4, - XFlowGroupCommands, - XFlowNodeCommands, - XFlowGraphCommands, - NsGraphCmd, - NsNodeCmd, - IconStore, - MODELS, -} from '@antv/xflow'; -import { getProps, Log, getGraphHistory, getGraphInstance } from '../../util'; -import { - UngroupOutlined, - SaveOutlined, - GroupOutlined, - GatewayOutlined, - UndoOutlined, - RedoOutlined, - VerticalAlignTopOutlined, - VerticalAlignBottomOutlined, - CopyOutlined, - SnippetsOutlined, -} from '@ant-design/icons'; -import { CommandPool } from './constants'; -import { CommandItem, FlowchartProps } from '../../interface'; - -export namespace TOOLBAR_ITEMS { - export const BACK_NODE = XFlowNodeCommands.BACK_NODE.id; - export const FRONT_NODE = XFlowNodeCommands.FRONT_NODE.id; - export const SAVE_GRAPH_DATA = XFlowGraphCommands.SAVE_GRAPH_DATA.id; - export const REDO_CMD = `${XFlowGraphCommands.REDO_CMD.id}`; - export const UNDO_CMD = `${XFlowGraphCommands.UNDO_CMD.id}`; - export const MULTI_SELECT = `${XFlowGraphCommands.GRAPH_TOGGLE_MULTI_SELECT.id}`; - export const ADD_GROUP = `${XFlowGroupCommands.ADD_GROUP.id}`; - export const DEL_GROUP = `${XFlowGroupCommands.DEL_GROUP.id}`; - export const COPY = `${XFlowGraphCommands.GRAPH_COPY.id}`; - export const PASTE = `${XFlowGraphCommands.GRAPH_PASTE.id}`; -} - -namespace NSToolbarConfig { - /** toolbar依赖的状态 */ - export interface IToolbarState { - isMultiSelctionActive: boolean; - isGroupSelected: boolean; - isNodeSelected: boolean; - isUndoable: boolean; - isRedoable: boolean; - } - - export const getDependencies = async (modelService: IModelService) => { - return [ - await MODELS.SELECTED_NODES.getModel(modelService), - await MODELS.GRAPH_ENABLE_MULTI_SELECT.getModel(modelService), - ]; - }; - - /** toolbar依赖的状态 */ - export const getToolbarState = async (modelService: IModelService) => { - // isMultiSelctionActive - const { isEnable: isMultiSelctionActive } = await MODELS.GRAPH_ENABLE_MULTI_SELECT.useValue(modelService); - // isGroupSelected - const isGroupSelected = await MODELS.IS_GROUP_SELECTED.useValue(modelService); - // isNormalNodesSelected: node不能是GroupNode - const isNormalNodesSelected = await MODELS.IS_NORMAL_NODES_SELECTED.useValue(modelService); - // undo redo - const isUndoable = await MODELS.COMMAND_UNDOABLE.useValue(modelService); - const isRedoable = await MODELS.COMMAND_REDOABLE.useValue(modelService); - - return { - isUndoable, - isRedoable, - isNodeSelected: isNormalNodesSelected, - isGroupSelected, - isMultiSelctionActive, - } as NSToolbarConfig.IToolbarState; - }; - - export const getToolbarItems = async ( - state: IToolbarState, - getIconConfig: any, - commands: CommandItem[], - flowchartId: string, - ) => { - const toolbarGroup: IToolbarItemOptions[] = []; - const history = getGraphHistory(flowchartId); - const graph = getGraphInstance(flowchartId); - const selectedCells = graph.getSelectedCells(); - - /** 撤销 */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.UNDO_CMD), - id: TOOLBAR_ITEMS.UNDO_CMD, - isEnabled: history.canUndo(), - onClick: async () => { - history.undo(); - }, - }); - - /** 重做 */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.REDO_CMD), - id: TOOLBAR_ITEMS.REDO_CMD, - isEnabled: history.canRedo(), - onClick: async () => { - history.redo(); - }, - }); - - /** FRONT_NODE */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.FRONT_NODE), - id: TOOLBAR_ITEMS.FRONT_NODE, - isEnabled: state.isNodeSelected, - onClick: async ({ commandService, modelService }) => { - const node = await MODELS.SELECTED_NODE.useValue(modelService); - commandService.executeCommand(TOOLBAR_ITEMS.FRONT_NODE, { - nodeId: node?.id, - }); - }, - }); - - /** BACK_NODE */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.BACK_NODE), - id: TOOLBAR_ITEMS.BACK_NODE, - isEnabled: state.isNodeSelected, - onClick: async ({ commandService, modelService }) => { - const node = await MODELS.SELECTED_NODE.useValue(modelService); - commandService.executeCommand(TOOLBAR_ITEMS.BACK_NODE, { - nodeId: node?.id, - }); - }, - }); - - /** 开启框选 */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.MULTI_SELECT), - id: TOOLBAR_ITEMS.MULTI_SELECT, - active: state.isMultiSelctionActive, - onClick: async ({ commandService }) => { - commandService.executeCommand(TOOLBAR_ITEMS.MULTI_SELECT, {}); - }, - }); - - /** 新建群组 */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.ADD_GROUP), - id: TOOLBAR_ITEMS.ADD_GROUP, - isEnabled: state.isNodeSelected, - onClick: async ({ commandService, modelService }) => { - const cells = await MODELS.SELECTED_CELLS.useValue(modelService); - const groupChildren = cells.map((cell) => cell.id); - commandService.executeCommand(TOOLBAR_ITEMS.ADD_GROUP, { - nodeConfig: { - id: uuidv4(), - renderKey: 'GROUP_NODE_RENDER_ID', // xflow 需要导出 - groupChildren, - groupCollapsedSize: { width: 200, height: 40 }, - label: '新建群组', - }, - }); - }, - }); - - /** 解散群组 */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.DEL_GROUP), - id: TOOLBAR_ITEMS.DEL_GROUP, - isEnabled: state.isGroupSelected, - onClick: async ({ commandService, modelService }) => { - const cell = await MODELS.SELECTED_NODE.useValue(modelService); - const nodeConfig = cell.getData(); - commandService.executeCommand(XFlowGroupCommands.DEL_GROUP.id, { - nodeConfig: nodeConfig, - }); - }, - }); - - /** copy */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.COPY), - id: TOOLBAR_ITEMS.COPY, - isEnabled: !!selectedCells?.length, - onClick: async ({ commandService }) => { - commandService.executeCommand(XFlowGraphCommands.GRAPH_COPY.id, {}); - }, - }); - - /** paste */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.PASTE), - id: CommandPool.PASTE, - isEnabled: true, - onClick: async ({ commandService }) => { - commandService.executeCommand(XFlowGraphCommands.GRAPH_PASTE.id, {}); - }, - }); - - /** 保存数据 */ - toolbarGroup.push({ - ...getIconConfig(CommandPool.SAVE_GRAPH_DATA), - id: TOOLBAR_ITEMS.SAVE_GRAPH_DATA, - onClick: async ({ commandService }) => { - commandService.executeCommand(TOOLBAR_ITEMS.SAVE_GRAPH_DATA, { - saveGraphDataService: (meta, graphData) => { - const onSave = getProps(flowchartId, 'onSave'); - if (onSave) { - return onSave(graphData); - } - }, - }); - }, - }); - return [ - { - name: 'graphData', - items: toolbarGroup - .filter((item) => !!item?.iconName) - .sort((pre: IToolbarItemOptions & { command: string }, next: IToolbarItemOptions & { command: string }) => { - return ( - commands.findIndex((item: CommandItem) => item.command === pre.command) - - commands.findIndex((item: CommandItem) => item.command === next.command) - ); - }), - }, - ]; - }; -} - -/** 注册icon 类型 */ -const registerIcon = () => { - IconStore.set('SaveOutlined', SaveOutlined); - IconStore.set('UndoOutlined', UndoOutlined); - IconStore.set('RedoOutlined', RedoOutlined); - IconStore.set('VerticalAlignTopOutlined', VerticalAlignTopOutlined); - IconStore.set('VerticalAlignBottomOutlined', VerticalAlignBottomOutlined); - IconStore.set('GatewayOutlined', GatewayOutlined); - IconStore.set('GroupOutlined', GroupOutlined); - IconStore.set('UngroupOutlined', UngroupOutlined); - IconStore.set('CopyOutlined', CopyOutlined); - IconStore.set('SnippetsOutlined', SnippetsOutlined); -}; - -export const useToolbarConfig: Function = createToolbarConfig( - (toolbarConfig, proxy) => { - const { flowchartId } = proxy.getValue(); - const toolbarPanelProps = getProps(flowchartId, 'toolbarPanelProps') ?? {}; - registerIcon(); - - let { - commands = [ - { - command: CommandPool.REDO_CMD, - tooltip: '重做', - iconName: 'RedoOutlined', - }, - { - command: CommandPool.UNDO_CMD, - tooltip: '撤销', - iconName: 'UndoOutlined', - }, - { - command: CommandPool.FRONT_NODE, - tooltip: '置前', - iconName: 'VerticalAlignTopOutlined', - }, - { - command: CommandPool.BACK_NODE, - tooltip: '置后', - iconName: 'VerticalAlignBottomOutlined', - }, - { - command: CommandPool.MULTI_SELECT, - tooltip: '开启框选', - iconName: 'GatewayOutlined', - }, - { - command: CommandPool.ADD_GROUP, - tooltip: '新建群组', - iconName: 'GroupOutlined', - }, - { - command: CommandPool.DEL_GROUP, - tooltip: '解散群组', - iconName: 'UngroupOutlined', - }, - { - command: CommandPool.COPY, - tooltip: '复制', - iconName: 'CopyOutlined', - }, - { - command: CommandPool.PASTE, - tooltip: '粘贴', - iconName: 'SnippetsOutlined', - }, - { - command: CommandPool.SAVE_GRAPH_DATA, - tooltip: '保存', - iconName: 'SaveOutlined', - }, - ] as CommandItem[], - } = toolbarPanelProps; - - const getIconConfig = (commandName: string) => { - if (!Object.values(CommandPool).includes(commandName)) { - Log.warn(`unknown command: ${commandName}`); - return {}; - } - /** 暂时不支持自定义 icon,感觉使用上并不方便,后续再考虑接入 */ - return commands.find((item: CommandItem) => item.command === commandName); - }; - - /** 生产 toolbar item */ - toolbarConfig.setToolbarModelService(async (toolbarModel, modelService, toDispose) => { - const updateToolbarModel = async () => { - const state = await NSToolbarConfig.getToolbarState(modelService); - const toolbarItems = await NSToolbarConfig.getToolbarItems(state, getIconConfig, commands, flowchartId); - - toolbarModel.setValue((toolbar) => { - toolbar.mainGroups = toolbarItems; - }); - }; - const models = await NSToolbarConfig.getDependencies(modelService); - const subscriptions = models.map((model) => { - return model.watch(async () => { - updateToolbarModel(); - }); - }); - toDispose.pushAll(subscriptions); - }); - }, -); diff --git a/packages/flowchart/src/context.ts b/packages/flowchart/src/context.ts deleted file mode 100644 index c8d62e916..000000000 --- a/packages/flowchart/src/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -const AppContext: any = createContext({}); - -export default AppContext; diff --git a/packages/flowchart/src/graph/appendUtils.ts b/packages/flowchart/src/graph/appendUtils.ts deleted file mode 100644 index 96bc6ac6e..000000000 --- a/packages/flowchart/src/graph/appendUtils.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** 向 graph 原型上挂在一系列方法 */ -import { IApplication } from '@antv/xflow'; -import { getGraphData, excLoadData } from '../util'; -import { IFlowchartGraph as IGraph } from '../interface'; - -export const appendUtils = (graph: IGraph, app: IApplication) => { - const x6Graph = graph; - /** 更新节点指定数据 - * @param {id} string 节点 id - * @param {key} string 需要更新的字段 - * @param {data} object 更新内容 - */ - const updateNodeKeyById = (id: string, key: string, data: object) => { - const currentNode = x6Graph.getCellById(id); - if (currentNode) { - x6Graph.getCellById(id).prop(key, { - ...currentNode[key], - ...data, - }); - } - }; - - x6Graph.updateNodeKeyById = updateNodeKeyById; - x6Graph.getGraphData = getGraphData; - x6Graph.loadData = (data) => { - excLoadData(app, data); - }; - - return x6Graph; -}; diff --git a/packages/flowchart/src/graph/constants.ts b/packages/flowchart/src/graph/constants.ts deleted file mode 100644 index f8129022a..000000000 --- a/packages/flowchart/src/graph/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const DEFAULT_SCALE_TOOLBAR_PROPS = { - layout: 'horizontal', - position: { - right: 0, - top: -140, - }, - style: { - width: 150, - background: 'transparent', - left: 'auto', - }, -}; diff --git a/packages/flowchart/src/graph/index.less b/packages/flowchart/src/graph/index.less deleted file mode 100644 index 73b0fc389..000000000 --- a/packages/flowchart/src/graph/index.less +++ /dev/null @@ -1,70 +0,0 @@ -@import (inline) '../../node_modules/@antv/xflow/dist/index.css'; -@primaryColor: #3572f9; -@light-border: ~'1px solid #d9d9d9'; - -.xflow-app-workspace { - overflow: hidden; - border: 1px solid #d9d9d9; - - .xflow-workspace-panel { - z-index: 1; - background: #fff; - } - - svg { - overflow: visible !important; - } - - .x6-edge { - stroke-width: 1px; - &.x6-edge-selected { - path:nth-child(2) { - stroke: @primaryColor; - } - } - &:hover { - path:nth-child(2) { - stroke: @primaryColor; - } - } - &.hoverHighlight { - path:nth-child(2) { - stroke: @primaryColor; - } - } - } - .x6-port { - > circle { - stroke: #69c0ff; - } - } - .x6-widget-transform { - box-sizing: border-box !important; - margin: 0; - padding: 0; - border: 2px solid #3572f9; - box-shadow: 0 4px 4px 0 #dbe6ff; - - > div { - width: 8px; - height: 8px; - background-color: #fff; - border: 1px solid #3572f9; - } - } - - .xflow-json-form .ant-tabs-content-holder { - padding: 0 !important; - } - - .ant-collapse-header { - display: flex; - flex-wrap: nowrap; - align-items: flex-start; - padding: 12px 16px; - color: rgba(0, 0, 0, 0.85); - line-height: 1.5715; - cursor: pointer; - transition: all 0.3s, visibility 0s; - } -} diff --git a/packages/flowchart/src/graph/index.tsx b/packages/flowchart/src/graph/index.tsx deleted file mode 100644 index 1946cbff9..000000000 --- a/packages/flowchart/src/graph/index.tsx +++ /dev/null @@ -1,130 +0,0 @@ -// @ts-nocheck -import React, { useRef, useEffect, useCallback } from 'react'; -import { - XFlow, - FlowchartCanvas, - CanvasContextMenu, - KeyBindings, - uuidv4, - FlowchartNodePanel, - FlowchartFormPanel, - CanvasScaleToolbar, - FlowchartExtension, - // CanvasMiniMap, -} from '@antv/xflow'; -import { ToolbarPanel } from '../components/toolbar'; -import { useMenuConfig } from '../components/menu'; -import { setProps, setInstance, excLoadData } from '../util'; -import { useCmdConfig, useKeybindingConfig } from './service'; -import { FlowchartProps, IFlowchartGraph as IGraph } from '../interface'; -import AppContext from '../context'; -import { appendUtils } from './appendUtils'; -import { DEFAULT_SCALE_TOOLBAR_PROPS } from './constants'; - -const Flowchart: React.FC = (props) => { - const { - className, - style, - detailPanelProps, - toolbarPanelProps, - nodePanelProps = {}, - scaleToolbarPanelProps = {}, - contextMenuPanelProps = {}, - canvasProps = {}, - keyBindingProps, - // miniMapProps = {}, - onAddNode, - onDelNode, - onCopy, - onPaste, - onAddEdge, - onConfigChange, - onDestroy, - onConfigReady, - isAutoCenter, - data, - mode, - onReady, - } = props as any; - const uuidv4Ref = useRef(uuidv4()); - const container = useRef(); - setProps(props, uuidv4Ref.current, container); - const { - position = { top: 40, left: 240, right: 240, bottom: 0 }, - showPortsOnNodeSelected, - edgeConfig = {}, - } = canvasProps; - // const { position: miniMapPosition = { bottom: 12, right: 12 }, show: showMinimMap = true } = miniMapProps; - const graphRef = useRef(); - const menuConfig = useMenuConfig({ ...contextMenuPanelProps, onDelNode }); - const commandConfig = useCmdConfig({ - flowchartId: uuidv4Ref.current, - }); // 需要 getProps - const keybindingConfig = keyBindingProps || useKeybindingConfig({ onDelNode, onCopy, onPaste }); - const { show = true } = scaleToolbarPanelProps; - const { show: showMenu = true } = contextMenuPanelProps; - const loadData = useCallback( - async (app) => { - if (data) { - excLoadData(app, data); - } - }, - [data], - ); - - useEffect(() => { - return () => { - graphRef.current?.dispose(); - }; - }, []); - - return ( - -
- { - const X6Graph = (await app.getGraphInstance()) as any; - setInstance(X6Graph, app, uuidv4Ref.current); - X6Graph.flowchartId = uuidv4Ref.current; - graphRef.current = X6Graph; - loadData(app); - onReady?.(appendUtils(X6Graph, app), app); - }} - > - - - - - {show && } - {showMenu && } - {/* {showMinimMap && } */} - - - {keyBindingProps !== false && } - -
-
- ); -}; - -export default Flowchart; diff --git a/packages/flowchart/src/graph/service/cmd.ts b/packages/flowchart/src/graph/service/cmd.ts deleted file mode 100644 index d5825621b..000000000 --- a/packages/flowchart/src/graph/service/cmd.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createCmdConfig, DisposableCollection, uuidv4 } from '@antv/xflow'; - -export const useCmdConfig: Function = createCmdConfig((config, proxy) => { - // 设置hook - config.setRegisterHookFn((hooks) => { - const list = [ - hooks.addNode.registerHook({ - name: 'get node config from backend api', - handler: async (args) => { - args.nodeConfig = { - ...args.nodeConfig, - id: args.nodeConfig.id || `node-${uuidv4()}`, - zIndex: args.nodeConfig.zIndex || 10, - /** 移除 _copied */ - label: args.nodeConfig.label?.replace?.(/\_copied/g, ''), - }; - }, - }), - ]; - const toDispose = new DisposableCollection(); - toDispose.pushAll(list); - return toDispose; - }); -}); diff --git a/packages/flowchart/src/graph/service/index.ts b/packages/flowchart/src/graph/service/index.ts deleted file mode 100644 index 0dfb95acc..000000000 --- a/packages/flowchart/src/graph/service/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useCmdConfig } from './cmd'; -export { useKeybindingConfig } from './keyBinding'; diff --git a/packages/flowchart/src/graph/service/keyBinding.ts b/packages/flowchart/src/graph/service/keyBinding.ts deleted file mode 100644 index 7938c3c23..000000000 --- a/packages/flowchart/src/graph/service/keyBinding.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - createKeybindingConfig, - XFlowNodeCommands, - XFlowEdgeCommands, - NsNodeCmd, - MODELS, - NsGraphCmd, - XFlowGraphCommands, -} from '@antv/xflow'; - -export const useKeybindingConfig = createKeybindingConfig((config, proxy) => { - const { onDelNode, onCopy, onPaste } = proxy.getValue(); - const emitBindEvents = async (modelService, callback) => { - const cells = await MODELS.SELECTED_CELLS.useValue(modelService); - if (typeof callback === 'function') callback(cells.map((item) => item.data)); - }; - config.setKeybindingFunc((regsitry) => { - return regsitry.registerKeybinding([ - { - id: 'delete node or edge', - keybinding: ['delete', 'backspace'], - callback: async function (item, modelService, cmd, e) { - const cells = await MODELS.SELECTED_CELLS.useValue(modelService); - // 先删除edges - await Promise.all( - cells.map((cell) => { - if (cell.isEdge()) { - return cmd.executeCommand(XFlowEdgeCommands.DEL_EDGE.id, { - edgeConfig: { ...cell.getData(), id: cell.id }, - }); - } - return null; - }), - ); - // 先删除nodes - await Promise.all( - cells.map((cell) => { - if (cell.isNode()) { - return cmd.executeCommand(XFlowNodeCommands.DEL_NODE.id, { - nodeConfig: { - ...cell.getData(), - id: cell.id, - }, - }); - } - return null; - }), - ); - emitBindEvents(modelService, onDelNode); - }, - }, - { - id: 'copy', - keybinding: ['command+c', 'ctrl+c'], - callback: async function (item, modelService, cmd, e) { - e.preventDefault(); - cmd.executeCommand(XFlowGraphCommands.GRAPH_COPY.id, {}); - emitBindEvents(modelService, onCopy); - }, - }, - { - id: 'paste', - keybinding: ['command+v', 'ctrl+v'], - callback: async function (item, modelService, cmd, e) { - e.preventDefault(); - cmd.executeCommand(XFlowGraphCommands.GRAPH_PASTE.id, {}); - emitBindEvents(modelService, onPaste); - }, - }, - ]); - }); -}); diff --git a/packages/flowchart/src/index.ts b/packages/flowchart/src/index.ts deleted file mode 100644 index ce8eda548..000000000 --- a/packages/flowchart/src/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - XFlowNodeCommands, - XFlowGraphCommands, - IconStore, - WorkspacePanel, - usePanelContext, - FormItemWrapper, - XFlowAppProvider, - useXFlowApp, - XFlowEdgeCommands, - createKeybindingConfig, - FlowchartFormPanel as FormPanel, - FlowchartFormWrapper as FormWrapper, - EditorPanels, - FlowchartService, -} from '@antv/xflow'; -// 临时方案 -import Flowchart from './graph'; - -export { ToolbarPanel } from './components/toolbar'; -const { NodeService, EdgeService, GroupService, CanvasService } = FlowchartService; -export { - Flowchart, - WorkspacePanel, - XFlowNodeCommands, - XFlowGraphCommands, - usePanelContext, - FormItemWrapper, - IconStore, - XFlowAppProvider, - useXFlowApp, - XFlowEdgeCommands, - createKeybindingConfig, - FormPanel, - FormWrapper, - EditorPanels, - NodeService, - EdgeService, - GroupService, - CanvasService, -}; - -export * from './interface'; diff --git a/packages/flowchart/src/interface.ts b/packages/flowchart/src/interface.ts deleted file mode 100644 index 64c0a44d5..000000000 --- a/packages/flowchart/src/interface.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { - IGraphCommandService, - IModelService, - NsGraph, - IPosition, - NsJsonSchemaForm, - IToolbarLayout, - IAppDestroy, - IAppConfigReady, - IApplication, - KeybindingConfig, - IFlowchartGraphProps, - IFlowchartNodePanelProps, -} from '@antv/xflow'; -import { Cell, Graph } from '@antv/x6'; -import { PopoverProps as AntDPopoverConfig } from 'antd/es/popover'; - -export interface FlowchartContainerProps { - style?: React.CSSProperties; - className?: string; - loading?: boolean; - loadingTemplate?: React.ReactElement; - errorTemplate?: (e: Error) => React.ReactNode; -} - -type Datum = { - nodes?: unknown[]; - edges?: unknown[]; -}; - -export interface CustomNode { - /** 节点名称,唯一 */ - name: string; - /** 节点 React 组件 */ - component: NsGraph.INodeRender; - /** popover 组件 */ - popover?: React.Component | React.FC; - /** 默认标签 */ - label?: string; - /** 默认宽度 */ - width?: number; - /** 默认高度 */ - height?: number; - /** 连接锚点配置,默认上下左右四个 */ - ports?: NsGraph.INodeConfig['ports']; -} - -export interface RegisterNode { - title?: string; - nodes: CustomNode[]; -} - -export interface BaseProps { - style?: React.CSSProperties; - className?: string; - /** 是否展示 */ - show?: boolean; - /** 节点位置 */ - position?: IPosition; -} - -export type NodePanelProps = IFlowchartNodePanelProps; - -export type CanvasProps = Omit; - -export type Command = - | 'undo-cmd' - | 'redo-cmd' - | 'front-node' - | 'back-node' - | 'save-graph-data' - | 'multi-select' - | 'add-group' - | 'del-group' - | 'graph-toggle-multi-select' - | 'graph-copy-selection' - | 'graph-paste-selection'; - -export type CommandItem = { - /** 命令 */ - command: Command; - /** 名称 */ - text?: string; - /** tooltip */ - tooltip?: string; - /** iconName */ - iconName?: string; -}; -export interface ToolbarPanelProps extends BaseProps { - commands?: CommandItem[]; - layout?: IToolbarLayout; - readonly flowchartId?: string; -} - -export interface ScaleToolbarPanelProps extends BaseProps { - layout?: IToolbarLayout; -} - -export type MenuItem = { - id: string; - label: string; - render: () => JSX.Element; -}; - -export type MenuType = 'node' | 'edge' | 'blank'; - -export type ContextMenuPanelProps = Pick & { - /** 是否展示内置功能 */ - showOfficial?: boolean; - /** 自定义menu */ - submenu?: (cfg: { menuType: MenuType }) => MenuItem[]; -}; - -export interface DetailPanelProps extends BaseProps { - controlMapService?: (editorMap: NsJsonSchemaForm.IControlMap) => NsJsonSchemaForm.IControlMap; - formSchemaService?: (args: { - cell: Cell; - targetType: NsJsonSchemaForm.TargetType; - targetData: NsJsonSchemaForm.TargetData; - modelService: IModelService; - commandService: IGraphCommandService; - }) => Promise; - prefixClz?: string; - header?: React.ReactNode; - footer?: React.ReactNode; -} - -export interface GraphEvents { - /** 节点点击事件 */ - handleNodeClick?: (node: NsGraph.INodeConfig) => void; - /** 边点击事件 */ - handleEdgeClick?: (edge: NsGraph.IEdgeConfig) => void; -} - -export interface PopoverProps extends Omit { - title?: (data: NsGraph.INodeConfig) => React.ReactNode; - content?: (data: NsGraph.INodeConfig) => React.ReactNode; -} - -export interface IFlowchartGraph extends Graph { - getGraphData?: (flowchartId: string) => Promise; - updateNodeKeyById?: (id: string, key: string, data: object) => void; - loadData?: (data: Datum) => void; - readonly flowchartId?: string; -} - -export interface IGraphConfig {} - -// Flowchart 通用配置 -export interface FlowchartProps extends FlowchartContainerProps { - /** 默认数据 */ - data?: Datum; - /** 主题 */ - theme?: 'light' | 'dark'; - /** 画布的配置 */ - // graphConfig?: GraphConfig; - /** 画布是否自动居中 */ - isAutoCenter?: boolean; - /** 节点面板配置 */ - nodePanelProps?: NodePanelProps; - /** 画布主要区域配置 */ - canvasProps?: CanvasProps; - /** - * @title 画布状态 - * @description scan 会禁用一些画布事件,例如连线、鼠标右键等 - * @default "edit" - */ - mode?: 'edit' | 'scan'; - /** toolbar */ - toolbarPanelProps?: ToolbarPanelProps; - /** keyBinding */ - keyBindingProps?: false | KeybindingConfig; - /** scale toolbar */ - scaleToolbarPanelProps?: ScaleToolbarPanelProps; - /** mini map */ - // miniMapProps?: MiniMapProps; - /** form editor */ - detailPanelProps?: DetailPanelProps; - /** 右键菜单配置 */ - contextMenuPanelProps?: ContextMenuPanelProps; - /** popover */ - popoverProps?: PopoverProps; - /** onReady */ - onReady?: (graph: IFlowchartGraph, app: IApplication) => void; - /** 点击回调,仅支持 save-graph-data */ - onSave?: (data: Datum) => void; - /** 新增节点时回调 */ - onAddNode?: (node: NsGraph.INodeConfig) => void; - /** 删除节点时回调 */ - onDelNode?: (node: NsGraph.INodeConfig | NsGraph.INodeConfig[]) => void; - /** 复制节点时回调 */ - onCopy?: (node: NsGraph.INodeConfig[]) => void; - /** 粘贴节点时回调 */ - onPaste?: (node: NsGraph.INodeConfig[]) => void; - /** 新增边时回调 */ - onAddEdge?: (edge: NsGraph.IEdgeConfig) => void; - /** xflow app 销毁前的回调 */ - onDestroy?: IAppDestroy; - /** xflow app 初始化后的回调 */ - onConfigReady?: IAppConfigReady; - /** 节点或边更新数据时调用 */ - onConfigChange?: (params: { data: Datum; type: string; config?: NsGraph.INodeConfig | NsGraph.IEdgeConfig }) => void; -} - -export { NsGraph }; diff --git a/packages/flowchart/src/util/global.ts b/packages/flowchart/src/util/global.ts deleted file mode 100644 index 41bfc0d26..000000000 --- a/packages/flowchart/src/util/global.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { MutableRefObject } from 'react'; -import { IApplication } from '@antv/xflow'; -import { FlowchartProps, IFlowchartGraph as IGraph } from '../interface'; - -// 解决 xflow hooks 获取不到上层配置 -interface IGlobalProps { - [key: string]: { - config: object; - container?: MutableRefObject; - }; -} -export const globalProps: IGlobalProps = {}; - -/** 设置全局状态 */ -export const setProps = (props: FlowchartProps, flowchartId: string, container?: MutableRefObject) => { - globalProps[flowchartId] = { - config: props, - container, - }; -}; - -const graphInstance = new Map(); -const appInstance = new Map(); - -export const setInstance = (x6graph: IGraph, app: IApplication, flowchartId: string) => { - graphInstance.set(`${flowchartId}-x6graph`, x6graph); - appInstance.set(`${flowchartId}-app`, app); -}; - -export const getGraphInstance = (flowchartId: string) => { - return graphInstance.get(`${flowchartId}-x6graph`) as IGraph; -}; - -export const getAppInstance = (flowchartId: string) => { - return appInstance.get(`${flowchartId}-app`); -}; - -/** 获取全局状态 */ -export const getProps = (flowchartId: string, key: string) => { - return globalProps[flowchartId]?.config?.[key]; -}; -export const getContainer = (flowchartId: string, type = 'container') => { - return globalProps[flowchartId]?.[type]?.current; -}; diff --git a/packages/flowchart/src/util/index.ts b/packages/flowchart/src/util/index.ts deleted file mode 100644 index b54ddf104..000000000 --- a/packages/flowchart/src/util/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './global'; -export * from './util'; diff --git a/packages/flowchart/src/util/util.ts b/packages/flowchart/src/util/util.ts deleted file mode 100644 index a4b3c88da..000000000 --- a/packages/flowchart/src/util/util.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { getProps, getGraphInstance, getAppInstance } from './global'; -import { XFlowGraphCommands, NsGraphCmd } from '@antv/xflow'; - -export const Log = window.console; - -export const getGraphData = async (flowchartId: string) => { - const app = getAppInstance(flowchartId); - let data; - await app.executeCommand(XFlowGraphCommands.SAVE_GRAPH_DATA.id, { - saveGraphDataService: async (graphMeta, graphData) => { - data = graphData; - }, - } as NsGraphCmd.SaveGraphData.IArgs); - return data; -}; - -export const excLoadData = async (app, data) => { - if (!data?.nodes?.length) { - return; - } - const res = await app.executeCommand(XFlowGraphCommands.LOAD_DATA.id, { - loadDataService: async () => { - return data; - }, - } as NsGraphCmd.GraphLoadData.IArgs); - const { graphData } = res?.contextProvider()?.getResult(); - /** 3. 画布内容渲染 */ - await app.executeCommand(XFlowGraphCommands.GRAPH_RENDER.id, { - graphData, - }); -}; - -export const getFlowchartId = (e) => { - let currentNode = e?.e?.currentTarget; - if (!currentNode) { - return document.getElementsByClassName('xflow-canvas-container')[0]?.getAttribute('data-flowchart-id'); - } - let containter = null; - while (!containter) { - const current = currentNode.getElementsByClassName('xflow-canvas-container'); - if (current?.length > 0) { - containter = current; - } - currentNode = currentNode.parentNode; - } - return containter[0]?.getAttribute('data-flowchart-id'); -}; - -/** - * 防抖函数 - * @param func 执行函数 - * @param delay 延迟时间 ms - * @param immediate 是否立即执行 - */ -export const debounce = (func: Function, delay: number, immediate: boolean = false): Function => { - let timer: number | undefined; - - return function (this: unknown, ...args: any[]) { - let that = this; - if (immediate) { - func.apply(that, args); - immediate = false; - return; - } - clearTimeout(timer); - timer = window.setTimeout(() => { - func.apply(that, args); - }, delay); - }; -}; - -export const getGraphHistory = (flowchartId: string) => { - return getGraphInstance(flowchartId).history; -}; - -/** 更新配置时通知上传执行保存 */ -export const onConfigChange = debounce( - (config, flowchartId) => { - const configChange = getProps(flowchartId, 'onConfigChange'); - if (!configChange || typeof configChange !== 'function') { - return; - } - return configChange({ - data: getGraphData(flowchartId), - ...config, - }); - }, - 300, - true, -); diff --git a/packages/flowchart/tests/graph/normal-spec.tsx b/packages/flowchart/tests/graph/normal-spec.tsx deleted file mode 100644 index ae8702363..000000000 --- a/packages/flowchart/tests/graph/normal-spec.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { act } from 'react-dom/test-utils'; -import { Flowchart } from '../../src'; - -describe('Flowchart render', () => { - let container; - const DATA = {}; - beforeEach(() => { - container = document.createElement('div'); - container.className = 'container'; - container.style.height = '600px'; - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('chart render', () => { - const NODE_LINK = { - id: 'NODE_LINK', - label: 'NODE_LINK', - render: (ev) => { - return ( - <> - - 自定义渲染 - -

{ - console.log(ev); - }} - > - click -

- - ); - }, - }; - - act(() => { - ReactDOM.render( - { - console.log(d); - }} - onDelNode={(data) => { - console.log(data); - }} - toolbarPanelProps={{ - position: { - top: 0, - left: 0, - right: 0, - }, - }} - contextMenuPanelProps={{ - // showOfficial: false, - // @ts-ignore - submenu: (cfg) => { - const { menuType } = cfg; - if (menuType === 'node') { - return [NODE_LINK]; - } - return []; - }, - }} - scaleToolbarPanelProps={{ - layout: 'horizontal', - position: { - right: 0, - top: -40, - }, - // style: { - // background: 'transparent', - // }, - }} - onAddNode={() => { - console.log('onAddNode'); - }} - onAddEdge={() => { - console.log('onAddEdge'); - }} - onConfigChange={() => { - console.log('onConfigChange'); - }} - canvasProps={{ - showPortsOnNodeSelected: true, - config: { - connecting: { - router: '', - }, - }, - edgeConfig: { - attrs: { - line: { - stroke: 'red', - strokeDasharray: '', - }, - }, - }, - position: { - top: 40, - left: 0, - right: 0, - bottom: 0, - }, - }} - nodePanelProps={{ - position: { width: 160, top: 40, bottom: 0, left: 0 }, - }} - detailPanelProps={{ - position: { width: 200, top: 40, bottom: 0, right: 0 }, - }} - onReady={(graph, app) => { - // graph.loadData(DATA); - }} - />, - container, - ); - }); - const flowchartContainer = document.getElementsByClassName('xflow-canvas-container')[0]; - expect(flowchartContainer).not.toBeUndefined(); - }); -}); diff --git a/packages/flowchart/tsconfig.json b/packages/flowchart/tsconfig.json deleted file mode 100644 index 1452f0cf4..000000000 --- a/packages/flowchart/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "paths": { - "@antv/xflow-extension": ["node_modules/@antv/xflow-extension/"], - "@antv/xflow-core": ["node_modules/@antv/xflow-core/"] - } - }, - "include": ["src", "tests"] -} diff --git a/packages/flowchart/typings/global/index.d.ts b/packages/flowchart/typings/global/index.d.ts deleted file mode 100644 index e4d598cc1..000000000 --- a/packages/flowchart/typings/global/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.less'; -declare module '*.module.less'; -declare module '*.png'; -declare module '*.svg'; diff --git a/packages/flowchart/webpack.config.js b/packages/flowchart/webpack.config.js deleted file mode 100644 index 5a4f05dd0..000000000 --- a/packages/flowchart/webpack.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const { getWebpackConfig } = require('../../config/webpack'); - -module.exports = getWebpackConfig('flowchart', 'Flowchart'); diff --git a/packages/graphs/CHANGELOG.md b/packages/graphs/CHANGELOG.md deleted file mode 100644 index f8b989529..000000000 --- a/packages/graphs/CHANGELOG.md +++ /dev/null @@ -1,142 +0,0 @@ -## 1.4.0 - -`2023-02-20` - -- 🐞 修复 FlowAnalysisGraph edges `A->B && B -> A` 连线丢失问题 - -## 1.3.9 - -`2023-02-16` - -- 🐞 修复 FlowAnalysisGraph 数据成环时导致的 loop - -## 1.3.7 - -`2023-02-08` - -- 🐞 修复 [render-graph setFlowTag 会导致nodes和edges异常](https://github.com/ant-design/ant-design-charts/issues/1801) - -## 1.3.6 - -## 1.3.5 - -`2023-01-29` - -- 🔥 新增 `FlowAnalysisGraph level` 配置 -- 内置 [markerCfg.collapsed](https://github.com/ant-design/ant-design-charts/pull/1775/commits/2a37066cc436752d8726e008127d2f9bc5eb1fa6) 逻辑 -- 🐞 [Avoid duplicated graph](https://github.com/ant-design/ant-design-charts/pull/1749) when useGraph hook being called twice on dev build with StrictMode turned on - -## 1.3.4 - -`2023-01-16` - -- 🐞 `set ellipsis` 对非中文字符长度计算不准 - -## 1.3.3 - -- 新增 `marker:click` 事件 -- 🔥 新增 `FileTreeGraph` - - -## 1.3.3-beta.3 - -- 🔥 `FileTreeGraph` 新增 `nodeCfg.lineStyle` 配置 - -`2022-11-10` -## 1.3.3-beta.0 - -`2022-10-17` - -- 🔥 新增 `FileTreeGraph` - - -## 1.3.2 - -`2022-10-17` - -- 🔥 Graphs 支持 React18 - -## 1.3.2-beta.1 - -## 1.3.2-beta.0 - -`2022-10-13` - -- 🔥 MarkerCfg 支持 Array -- 🔥 ToolbarCfg customContent 透出 graph 实例 - -## 1.3.1 - -`2022-09-27` - -- 🐞 dist-tag `1.3.0` -- 🐞 修复 `getChildren` `syncData` 等类型错误 - -## 1.3.0 - -`2022-09-19` - -- 🔥 新增 `MindMapGraph` -- 🐞 修复 `getChildrenData` 数据异常报错 - -## 1.2.8 - -`2022-09-05` - -- 🐞 修复节点配置 level 后无法区分 markerCfg.show 状态 - -```ts -const level = 2; -const chartProps = { - data: TreeData, - level, - markerCfg: (cfg) => { - return { - position: 'right', - show: cfg.children?.length, - collapsed: cfg.depth >= level - 1, - }; - }, -}; -``` - -## 1.2.7 - -`2022-08-25` - -- 🔥 nodeCfg 新增 `percent` 配置 - - - -## 1.2.6 - -`2022-08-23` - -- 🐞 修复 `edgeCfg` 透传错误 - -## 1.2.5 - -`2022-08-22` - -- 🔥 `RadialGraph` 升级,可以动态拓展 -- 🔥 新增 `fetchLoading` 配置,可配合 `asyncData` `getChildren` 等配置做自定义 loading -- 🐞 完善 `layout` 定义 - -## 1.2.4 - -`2022-08-19` - -- 🔥 新增 `menuCfg` 配置 -- 🐞 [关系图透传节点配置,支持 image 等节点](https://github.com/ant-design/ant-design-charts/issues/1489) - -## 1.2.3 - -`2022-07-22` - -- 🐞 [指标拆解图 fitCenter 设置为 false 时, 树从头节点重新展开](https://github.com/ant-design/ant-design-charts/issues/1441) - -## 1.2.2 - -`2022-07-21` - -- 🔥 [来源去向图新增一个异步加载功能](https://github.com/ant-design/ant-design-charts/issues/1437) diff --git a/packages/graphs/README.md b/packages/graphs/README.md index 6f26a6576..71349e463 100644 --- a/packages/graphs/README.md +++ b/packages/graphs/README.md @@ -1,284 +1 @@ # @ant-design/graphs - -
- -A React relation graphs component library, based on [G6](https://github.com/antvis/G6). - -![npm](https://img.shields.io/npm/v/@ant-design/graphs) ![npm](https://img.shields.io/npm/dm/@ant-design/graphs) [![GitHub stars](https://img.shields.io/github/stars/ant-design/ant-design-charts)](https://github.com/ant-design/ant-design-charts/stargazers) [![npm License](https://img.shields.io/npm/l/@ant-design/charts.svg)](https://www.npmjs.com/package/@ant-design/charts) - -

- Website • - Quick Start • - Gallery • - FAQ • - Blog -

- -
- -## Case - -
- - - -
-
- - - -
- -## ✨ Features - -- Easy to use -- TypeScript - -## 📦 Installation - -```bash | pure -$ npm install @ant-design/graphs -``` - -## 🔨 Usage - -```tsx | pure -import React from 'react'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - const config = { - data, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - nodeCfg: { - title: { - style: (cfg) => { - return { - fill: cfg?.value?.title === '青年' ? 'yellow' : '#fff', - }; - }, - }, - items: { - containerStyle: { - fill: '#fff', - }, - style: (cfg, group, type) => { - const styles = { - value: { - fill: '#52c41a', - }, - text: { - fill: '#aaa', - }, - icon: { - width: 10, - height: 10, - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - style: { - radius: [2, 2, 2, 2], - }, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: children?.length, - }; - }, - }; - - return ; -}; - -export default DemoDecompositionTreeGraph; -``` - -## 📜 Document & API - -See chart API for details. Common props: - -````ts -// Graph 通用配置 -export interface CommonConfig extends GraphContainerConfig { - data: Datum; - /** 是否缩放节点大小自适应容器 */ - autoFit?: boolean; - - /** 是否将图平移到中心位置 */ - fitCenter?: boolean; - - width?: number; - height?: number; - pixelRatio?: number; - - /** 不同组件 layout 有差别,参考对应组件文档 */ - layout?: any; - - /** 边配置 */ - edgeCfg?: EdgeCfg; - - /** 节点配置 */ - nodeCfg?: NodeCfg; - - /** marker 配置 */ - markerCfg?: IMarkerCfg; - - /** 迷你地 */ - minimapCfg?: MiniMapConfig; - - /** 交互组件 */ - toolbarCfg?: ToolbarCfg; - - /** 提示 */ - tooltipCfg?: TooltipCfg; - - /** 交互行为 */ - behaviors?: string[]; - - /** 是否开启动画 */ - animate?: boolean; - - /** - * @title 是否自定义布局 - * @description 开启后,layout 失效,使用 data 里面的 x/y 进行数据布局 - * @example - * ```ts - * { - * id: '-3', - * x: 100, - * y: 100, - * value: { - * title: '来源页面A', - * items: [ - * { - * text: '曝光PV', - * value: '10.30万', - * icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - * }, - * ], - * }, - * }, - * ``` - * @default false - */ - customLayout?: boolean; - - /** 图表渲染完成回调 */ - onReady?: (graph: IGraph) => void; -} -```` - -## 🤝 How to Contribute - -Your contributions are always welcome! Please Do have a look at the [issues](https://github.com/ant-design/ant-design-charts/issues) first. - -## 📧 Contact us - -DingTalk group number: `44788198 `. - - - -## License - -MIT diff --git a/packages/graphs/jest.config.js b/packages/graphs/jest.config.js index fa82ad77e..f0ce7b7ad 100644 --- a/packages/graphs/jest.config.js +++ b/packages/graphs/jest.config.js @@ -3,6 +3,8 @@ module.exports = { ...BaseJestConfig, ...(process.env.DEBUG_MODE === '1' ? OnlineConfig : {}), moduleNameMapper: { - 'd3-((?!linear)\\S*)': `/../../node_modules/d3-$1/dist/d3-$1.min.js`, + '^d3-((?!linear)\\S*)': `/../../node_modules/d3-$1/dist/d3-$1.min.js`, + '^lodash$': 'lodash', + '^@ant-design/charts-util': `/../../packages/util/src`, }, }; diff --git a/packages/graphs/jest.setup.js b/packages/graphs/jest.setup.js index 52173dcfa..0bfed5f3b 100644 --- a/packages/graphs/jest.setup.js +++ b/packages/graphs/jest.setup.js @@ -1 +1,2 @@ require('../../config/setup'); +require('whatwg-fetch'); diff --git a/packages/graphs/package.json b/packages/graphs/package.json index 6b278c879..6117c72e6 100644 --- a/packages/graphs/package.json +++ b/packages/graphs/package.json @@ -1,7 +1,17 @@ { "name": "@ant-design/graphs", - "version": "1.4.0", - "description": "Relation graph", + "version": "2.0.4", + "description": "A React graph library based on Graphin", + "keywords": [ + "antv", + "g6", + "graph", + "graph analysis", + "graph editor", + "graph visualization", + "relational data", + "react" + ], "bugs": { "url": "https://github.com/ant-design/ant-design-charts/issues" }, @@ -9,55 +19,59 @@ "type": "git", "url": "git+https://github.com/ant-design/ant-design-charts.git" }, + "license": "MIT", "main": "lib/index.js", "unpkg": "dist/graphs.min.js", "module": "es/index.js", "types": "es/index.d.ts", "files": [ - "/lib", - "/es", - "/dist" + "lib", + "dist", + "es", + "src" ], "scripts": { - "start": "pnpm lib:es --w", - "build": "run-s clean lib dist", - "build:lib": "run-s clean lib", - "ci": "pnpm run build && pnpm run test:coverage", + "build": "run-p build:*", + "build:esm": "rimraf ./es && tsc --module ESNext --outDir es -p tsconfig.prod.json", + "build:lib": "rimraf ./lib && tsc --module commonjs --outDir lib -p tsconfig.prod.json", + "build:umd": "rimraf ./dist && webpack --config webpack.config.js --mode production", + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", + "ci": "run-s lint build test", "clean": "rimraf lib es dist", - "dist": "webpack --config webpack.config.js --mode production", - "profile": "webpack --config webpack.config.js --mode production --profile --json > stats.json", - "lib": "run-p lib:*", - "lib:cjs": "tsc -p tsconfig.prod.json --target ES5 --module commonjs --outDir lib", - "lib:es": "tsc -p tsconfig.prod.json --target ES5 --module ESNext --outDir es", - "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --fix --format=pretty ./src && pnpm run lint:prettier", - "lint:prettier": "pnpm run prettier && git diff && prettier --version && prettier --check \"src/**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto", + "dev": "vite", + "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --fix --format=pretty ./src && npm run lint:prettier", + "lint:prettier": "npm run prettier && git diff && prettier --version && prettier --check \"src/**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto", + "prepublishOnly": "pnpm run build", "prettier": "prettier --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"", - "test": "jest", - "test:live": "cross-env DEBUG_MODE=1 jest --watch ./tests/graphs/flow-loop-edges-spec.tsx --no-cache" + "profile": "webpack --config webpack.config.js --mode production --profile --json > stats.json", + "start": "npm run build:esm --w", + "test": "jest" }, "dependencies": { - "@antv/dom-util": "^2.0.4", - "@antv/g6": "^4.2.4", - "@antv/layout": "^0.1.17", - "@antv/util": "^2.0.9", - "insert-css": "^2.0.0", - "react-content-loader": "^5.0.4" + "@ant-design/charts-util": "0.0.1-alpha.7", + "@antv/g6": "^5.0.38", + "@antv/g6-extension-react": "^0.1.13", + "@antv/graphin": "^3.0.4", + "lodash": "^4.17.21", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "styled-components": "^6.1.13" }, "devDependencies": { - "@types/jest": "^26.0.0", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", - "cross-env": "^7.0.3", - "npm-run-all": "^4.1.5", - "rimraf": "^3.0.2" + "@antv/algorithm": "^0.1.26", + "@types/jest": "^26.0.24", + "@types/lodash": "^4.17.13", + "antd": "^5.22.0", + "react-router-dom": "^6.28.0", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "vite": "^5.4.11" }, "peerDependencies": { "react": ">=16.8.4", "react-dom": ">=16.8.4" }, "publishConfig": { - "access": "public" - }, - "sideEffects": false, - "license": "MIT" + "access": "public", + "registry": "https://registry.npmjs.org/" + } } diff --git a/packages/graphs/src/components/conversion-dagre-graph/behaviors/custom-click.ts b/packages/graphs/src/components/conversion-dagre-graph/behaviors/custom-click.ts deleted file mode 100644 index edd5e8dbe..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/behaviors/custom-click.ts +++ /dev/null @@ -1,68 +0,0 @@ -import G6 from '@antv/g6'; -import type { G6GraphEvent } from '@antv/g6'; -import { setItemState, paintOnce, resetBaseStates } from '../utils'; -import { ITEM_STATE } from '../types'; - -G6.registerBehavior('conv-click', { - getDefaultCfg() { - return { - multiple: false, // 是否支持多选 - }; - }, - getEvents() { - return { - 'node:click': 'onNodeClick', - 'canvas:click': 'onCanvasClick', - 'edge:click': 'onEdgeClick', - }; - }, - handleNodeDetailInfoClick(event: G6GraphEvent) { - const shape = event.target; - // click节点详情icon时emit事件,用于节点详情的弹窗的展示和隐藏 - if (shape.get('name') === 'node-detail-info-icon') { - this.graph.emit('node:detailinfoclick', { - ...event, - type: 'node:detailinfoclick', - name: 'node:detailinfoclick', - }); - } else { - this.graph.emit('node:otherareaclick', { - ...event, - type: 'node:otherareaclick', - name: 'node:otherareaclick', - }); - } - }, - onNodeClick(event: G6GraphEvent) { - this.handleNodeDetailInfoClick(event); - const { item: currentNode } = event; - const { graph } = this; - // 获取之前的选中状态 - const isSelected = currentNode.hasState(ITEM_STATE.Selected); - paintOnce(graph, () => { - resetBaseStates(graph); - // 设置最新选中状态 - setItemState(graph, currentNode, ITEM_STATE.Selected, !isSelected); - // 重绘节点相关的边,暂时不做 - // refreshRelatedEdges(currentNode); - }); - }, - onEdgeClick(event: G6GraphEvent) { - const { item: currentEdge } = event; - const { graph } = this; - // 获取之前的选中状态 - const isSelected = currentEdge.hasState(ITEM_STATE.Selected); - paintOnce(graph, () => { - resetBaseStates(graph); - // 设置最新选中状态 - setItemState(graph, currentEdge, ITEM_STATE.Selected, !isSelected); - }); - }, - onCanvasClick() { - const { graph } = this; - // 点击画布,重置元素状态为初始值 - paintOnce(graph, () => { - resetBaseStates(graph); - }); - }, -}); diff --git a/packages/graphs/src/components/conversion-dagre-graph/behaviors/custom-hover.ts b/packages/graphs/src/components/conversion-dagre-graph/behaviors/custom-hover.ts deleted file mode 100644 index afc33fbd1..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/behaviors/custom-hover.ts +++ /dev/null @@ -1,128 +0,0 @@ -import G6 from '@antv/g6'; -import type { G6GraphEvent } from '@antv/g6'; -import { setItemState, paintOnce, getInOutPathEdges, clearActiveStates } from '../utils'; -import { ITEM_STATE } from '../types'; - -G6.registerBehavior('conv-hover', { - getDefaultCfg() { - return { - highlightData: { - nodes: [], - edges: [], - // 触发高亮的节点id - nodeId: null, - }, - }; - }, - getEvents() { - return { - 'node:mouseenter': 'onNodeMouseEnter', - 'node:mouseleave': 'onNodeMouseLeave', - 'node:mousemove': 'onNodeMouseMove', - 'edge:mouseenter': 'onEdgeMouseEnter', - 'edge:mouseleave': 'onEdgeMouseLeave', - }; - }, - handleNodeNameTooltip(event: G6GraphEvent) { - const shape = event.target; - // hover节点名称时emit事件,用于节点名称的tooltip的展示和隐藏 - if (shape.get('name') === 'node-name') { - this.graph.emit('node:nametooltipshow', { - ...event, - type: 'node:nametooltipshow', - name: 'node:nametooltipshow', - }); - } else { - this.graph.emit('node:nametooltiphide', { - ...event, - type: 'node:nametooltiphide', - name: 'node:nametooltiphide', - }); - } - }, - onNodeMouseEnter(event: G6GraphEvent) { - this.handleNodeNameTooltip(event); - const currentNode = event.item; - const model = currentNode.getModel(); - if (!currentNode || currentNode.destroyed || model.disabled) return; - - const states = currentNode.getStates(); - if (states.length) return; - - if (!this.highlightData.nodeId) { - this.highlightData = { - nodes: this.graph.getNodes().map((node) => node.getModel()), - edges: this.graph.getEdges().map((edge) => edge.getModel()), - }; - } - - // 高亮节点相关链路 - this.highlightPath(currentNode, this.highlightData.edges); - }, - onNodeMouseLeave(event: G6GraphEvent) { - // 清空所有元素的激活状态 - paintOnce(this.graph, () => { - clearActiveStates(this.graph); - }); - this.highlightData = { - nodes: [], - edges: [], - nodeId: null, - }; - }, - onNodeMouseMove(event: G6GraphEvent) { - this.handleNodeNameTooltip(event); - }, - onEdgeMouseEnter(event: G6GraphEvent) { - const currentEdge = event.item; - const model = currentEdge.getModel(); - - if (!currentEdge || currentEdge.destroyed || model.disabled) return; - - const states = currentEdge.getStates(); - if (states.length) return; - paintOnce(this.graph, () => { - setItemState(this.graph, currentEdge, ITEM_STATE.Active, true); - // 提升边层级 - currentEdge.toFront(); - }); - }, - onEdgeMouseLeave(event: G6GraphEvent) { - const edge = event.item; - setItemState(this.graph, edge, ITEM_STATE.Active, false); - }, - // 高亮节点相关的链路 - highlightPath(currentNode, queriedEdges = []) { - const currentNodeId = currentNode.getModel().id; - const { nodeId: lastNodeId, edges: lastHighlightEdges } = this.highlightData; - // 要高亮节点与上一次高亮节点一样,同时相关路径也一样,则不再触发高亮操作 - if (lastNodeId === currentNodeId && queriedEdges === lastHighlightEdges) { - return; - } - const { relativeOutEdges, relativeInEdges } = getInOutPathEdges(currentNodeId, queriedEdges); - const relativeNodeIds = new Set(); - relativeOutEdges.forEach((edge) => { - relativeNodeIds.add(edge.source); - relativeNodeIds.add(edge.target); - }); - const relativeEdgeIds = relativeOutEdges.map((edge) => edge.id); - - paintOnce(this.graph, () => { - relativeEdgeIds.forEach((edgeId) => { - const edge = this.graph.findById(edgeId); - setItemState(this.graph, edge, ITEM_STATE.Active, true); - // 置顶 - edge.toFront(); - }); - setItemState(this.graph, currentNode, ITEM_STATE.Active, true); - // 提升节点层级 - currentNode.toFront(); - }); - - this.highlightData = { - nodeId: currentNodeId, - edges: relativeOutEdges, - nodes: relativeNodeIds, - }; - }, -}); diff --git a/packages/graphs/src/components/conversion-dagre-graph/behaviors/index.ts b/packages/graphs/src/components/conversion-dagre-graph/behaviors/index.ts deleted file mode 100644 index b976391b0..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/behaviors/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Graph } from '@antv/g6'; -import './custom-hover'; -import './custom-click'; - -const modes = { - default: ['conv-hover', 'conv-click'], -}; - -export default (graph: Graph) => { - Object.keys(modes).forEach((key) => { - graph.addBehaviors(modes[key], key); - }); -}; diff --git a/packages/graphs/src/components/conversion-dagre-graph/constants/defaultConfig.ts b/packages/graphs/src/components/conversion-dagre-graph/constants/defaultConfig.ts deleted file mode 100644 index d85c364e7..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/constants/defaultConfig.ts +++ /dev/null @@ -1,37 +0,0 @@ -// 默认布局配置 -export const DEFAULT_LAYOUT_OPTIONS = { - rankdir: 'TB', - align: undefined, // 节点居中对齐 - // align: 'UL', // 节点居中对齐在设置layer的情况下有bug,暂时使用这个 - nodesep: 20, - ranksep: 75, -}; - -// 默认节点配置 -export const DEFAULT_NODE = { - type: 'conv-node', - anchorPoints: [ - [0.5, 0], - [0.5, 1], - [0, 0.5], - [1, 0.5], - ], -}; - -// 默认边配置 -export const DEFAULT_EDGE = { - type: 'conv-cubic-vertical', - sourceAnchor: 1, - targetAnchor: 0, - style: { - stroke: '#B8C3D9', - lineWidth: 1, - }, -}; - -// 默认画布mode -export const DEFAULT_MODE = [ - 'drag-canvas', // 拖动画布 - 'zoom-canvas', // 缩放画布 - 'drag-node', // 拖动节点 -]; diff --git a/packages/graphs/src/components/conversion-dagre-graph/constants/index.ts b/packages/graphs/src/components/conversion-dagre-graph/constants/index.ts deleted file mode 100644 index 5b27ddc81..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/constants/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './defaultConfig'; -export * from './states'; diff --git a/packages/graphs/src/components/conversion-dagre-graph/constants/states.ts b/packages/graphs/src/components/conversion-dagre-graph/constants/states.ts deleted file mode 100644 index e62679029..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/constants/states.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ITEM_STATE } from '../types'; - -export const BASE_STATES: ITEM_STATE[] = [ITEM_STATE.Active, ITEM_STATE.Selected]; - -export const SIZE_IMPACT_STATES: ITEM_STATE[] = [ITEM_STATE.Active, ITEM_STATE.Selected]; diff --git a/packages/graphs/src/components/conversion-dagre-graph/edges/baseEdge.ts b/packages/graphs/src/components/conversion-dagre-graph/edges/baseEdge.ts deleted file mode 100644 index 7892c7f20..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/edges/baseEdge.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { setItemStateStyle, getArrowConfig } from '../utils'; - -export default { - draw(cfg, group, path) { - const { - custom: { ratio }, - style: { dashed, stroke, endArrow = true, sizeMapping = true }, - } = cfg; - let borderWidth = 0; - // 如果开启了大小映射,则根据比率映射边框宽度 - if (sizeMapping) { - borderWidth = !isNaN(ratio) && ratio !== Infinity ? Math.max(2, Math.min(72, 72 * ratio)) : 2; - } - - // 添加边框 - const keyShape = group?.addShape('path', { - attrs: { - stroke, - opacity: 0.2, - path, - lineWidth: borderWidth, - }, - name: 'path-border-shape', - }); - - // 路径主线 - group?.addShape('path', { - attrs: { - stroke, - path, - endArrow: endArrow ? getArrowConfig(stroke) : null, - lineDash: dashed ? [3, 3] : [], - }, - name: 'path-shape', - }); - return keyShape; - }, - afterDraw(cfg, group) { - const { - label, - style: { labelFill }, - } = cfg; - const shape = group?.get('children')[0]; - // 获取路径图形的中点坐标 - const midPoint = shape.getPoint(0.5); - if (label) { - // 绘制label - const labelShape = group?.addShape('text', { - attrs: { - text: label, - x: midPoint.x, - y: midPoint.y, - fill: labelFill, - opacity: 0.85, - fontFamily: 'Roboto-Regular', - fontSize: 12, - textAlign: 'center', - textBaseline: 'middle', - }, - name: 'label-shape', - }); - const labelBbox = labelShape?.getBBox(); - const padding = 5; // 四周的留白 - - // 绘制label背景框 - group?.addShape('rect', { - attrs: { - width: labelBbox.width + padding * 2, - height: labelBbox.height + padding * 2, - x: midPoint.x - labelBbox.width / 2 - padding, - y: midPoint.y - labelBbox.height / 2 - padding, - fill: '#fff', - }, - name: 'label-bg-shape', - labelRelated: true, - }); - - // 边名称labelShape上移一个层级 - if (labelShape) { - labelShape.toFront(); - } - } - }, - setState(name, value, edge) { - // 设置状态样式 - setItemStateStyle(edge, 'edge'); - }, -}; diff --git a/packages/graphs/src/components/conversion-dagre-graph/edges/convCubicHorizontal.ts b/packages/graphs/src/components/conversion-dagre-graph/edges/convCubicHorizontal.ts deleted file mode 100644 index 719270f8a..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/edges/convCubicHorizontal.ts +++ /dev/null @@ -1,31 +0,0 @@ -import G6 from '@antv/g6'; -import type { EdgeConfig } from '@antv/g6'; -import baseEdge from './baseEdge'; - -// 自定义转化边 -export const registerConvCubicHorizontal = () => { - G6.registerEdge('conv-cubic-horizontal', { - ...baseEdge, - draw(cfg: EdgeConfig, group) { - const { startPoint, endPoint } = cfg; - const hgap = Math.abs(endPoint.y - startPoint.y); - // 水平三阶贝塞尔曲线,两端留4px的汇总直线 - const path = [ - ['M', startPoint.x, startPoint.y], - ['L', startPoint.x + 4, startPoint.y], - [ - 'C', - startPoint.x < endPoint.x ? startPoint.x + hgap / 2 : startPoint.x - hgap / 2, - startPoint.y, - startPoint.x < endPoint.x ? endPoint.x - hgap / 2 : endPoint.x + hgap / 2, - endPoint.y, - endPoint.x - 15, - endPoint.y, - ], - ['L', endPoint.x, endPoint.y], - ]; - - return baseEdge.draw(cfg, group, path); - }, - }); -} diff --git a/packages/graphs/src/components/conversion-dagre-graph/edges/convCubicVertical.ts b/packages/graphs/src/components/conversion-dagre-graph/edges/convCubicVertical.ts deleted file mode 100644 index 306b3348f..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/edges/convCubicVertical.ts +++ /dev/null @@ -1,32 +0,0 @@ -import G6 from '@antv/g6'; -import type { EdgeConfig } from '@antv/g6'; -import { getArrowConfig } from '../utils'; -import baseEdge from './baseEdge'; - -// 自定义转化边 -export const registerConvCubicVertical = () => { - G6.registerEdge('conv-cubic-vertical', { - ...baseEdge, - draw(cfg: EdgeConfig, group) { - const { startPoint, endPoint } = cfg; - const hgap = Math.abs(endPoint.y - startPoint.y); - // 垂直三阶贝塞尔曲线,两端留4px的汇总直线 - const path = [ - ['M', startPoint.x, startPoint.y], - ['L', startPoint.x, startPoint.y + 4], - [ - 'C', - startPoint.x, - startPoint.y < endPoint.y ? startPoint.y + hgap / 2 : startPoint.y - hgap / 2, - endPoint.x, - startPoint.y < endPoint.y ? endPoint.y - hgap / 2 : endPoint.y + hgap / 2, - endPoint.x, - endPoint.y - 15, - ], - ['L', endPoint.x, endPoint.y], - ]; - - return baseEdge.draw(cfg, group, path); - }, - }); -} diff --git a/packages/graphs/src/components/conversion-dagre-graph/edges/convLine.ts b/packages/graphs/src/components/conversion-dagre-graph/edges/convLine.ts deleted file mode 100644 index 3182e34e6..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/edges/convLine.ts +++ /dev/null @@ -1,20 +0,0 @@ -import G6 from '@antv/g6'; -import type { EdgeConfig } from '@antv/g6'; -import baseEdge from './baseEdge'; - - -export const registerConvLine = () => { - // 自定义转化边 - G6.registerEdge('conv-line', { - ...baseEdge, - draw(cfg: EdgeConfig, group) { - const { startPoint, endPoint } = cfg; - // 直线,线条样式、箭头自定义 - const path = [ - ['M', startPoint.x, startPoint.y], - ['L', endPoint.x, endPoint.y], - ]; - return baseEdge.draw(cfg, group, path); - }, - }); -} diff --git a/packages/graphs/src/components/conversion-dagre-graph/edges/index.ts b/packages/graphs/src/components/conversion-dagre-graph/edges/index.ts deleted file mode 100644 index 5d0498975..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/edges/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { registerConvCubicVertical } from './convCubicVertical'; -import { registerConvCubicHorizontal } from './convCubicHorizontal'; -import { registerConvLine } from './convLine'; - -export const resigterEdges = () => { - registerConvCubicVertical(); - registerConvCubicHorizontal(); - registerConvLine(); -}; \ No newline at end of file diff --git a/packages/graphs/src/components/conversion-dagre-graph/index.tsx b/packages/graphs/src/components/conversion-dagre-graph/index.tsx deleted file mode 100644 index 46a1fefa6..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/index.tsx +++ /dev/null @@ -1,308 +0,0 @@ -import React, { Component } from 'react'; -import G6 from '@antv/g6'; -import type { Graph as G6Graph } from '@antv/g6'; -import { isEqual } from 'lodash'; -import { DEFAULT_LAYOUT_OPTIONS, DEFAULT_NODE, DEFAULT_EDGE, DEFAULT_MODE } from './constants'; -import type { Props } from './types'; -import { ITEM_STATE } from './types'; -import { transformOriginData, drawLayerName, updateEdgeAnchorAndType } from './utils'; -import registerBehavior from './behaviors'; -import { resigterNodes } from './nodes'; -import { resigterEdges } from './edges'; -import { resigterLayout } from './layout'; - -export default class ConversionDagreGraph extends Component { - private container: HTMLElement | null; - private graph: G6Graph | null; - private shouldCacheZoomAndTranslate: boolean; - private cacheData; - private resizeObserver: any; - - static defaultProps = { - data: null, - layerOrder: [], - segmLayer: '', - ratioMethod: 'both', - layout: { - rankdir: 'TB', // 默认从上到下 - }, - }; - - constructor(props: Props) { - super(props); - this.container = null; - this.graph = null; - this.shouldCacheZoomAndTranslate = false; - const { data, layerOrder, segmLayer, ratioMethod } = props; - this.state = { - graphData: transformOriginData(data, layerOrder, segmLayer, ratioMethod), - }; - } - - componentDidMount() { - // 先做图相关的注册:如节点、边、布局 - this.registerGraphRelative(); - // 创建主图 - this.newGraph(); - // 绘制节点和边 - this.renderGraph(); - } - - static getDerivedStateFromProps(props, state) { - const { data, layerOrder, segmLayer, ratioMethod } = props; - // props数据 -> G6渲染图数据 - const graphData = transformOriginData(data, layerOrder, segmLayer, ratioMethod); - return { - graphData, - }; - } - - componentDidUpdate(prevProps) { - const { layerOrder, layout, data } = this.props; - // 缓存当前视口状态 - this.handleCacheGraph(); - const { graphData } = this.state; - const prevGraphData = transformOriginData( - prevProps.data, - prevProps.layerOrder, - prevProps.segmLayer, - prevProps.ratioMethod, - ); - this.shouldCacheZoomAndTranslate = !this.getGraphShallowDiff(data, prevProps.data); - if (isEqual(prevGraphData, graphData) && !isEqual(layout, prevProps.layout)) { - // 仅更新布局 - this.graph.updateLayout(this.getLayoutParams(!isEqual(layout, prevProps.layout))); - return; - } - // 数据变化或者层级顺序发生变化 - if (!isEqual(prevGraphData, graphData) || !isEqual(layerOrder, prevProps.layerOrder)) { - // 更新布局 - this.graph.updateLayout(this.getLayoutParams(!isEqual(layerOrder, prevProps.layerOrder))); - // 重新绘制节点和边 - this.renderGraph(); - } - } - - shouldComponentUpdate(nextProps, nextState) { - const { graphData } = this.state; - const nextGraphData = transformOriginData( - nextProps.data, - nextProps.layerOrder, - nextProps.segmLayer, - nextProps.ratioMethod, - ); - if (!isEqual(nextGraphData, graphData) || !isEqual(nextProps.layout, this.props.layout)) { - return true; - } - return false; - } - - componentWillUnmount() { - // 移除事件监听 - this.removeEventListener(); - } - - // 图相关的注册 - registerGraphRelative = () => { - // 注册节点 - resigterNodes(); - // 注册边 - resigterEdges(); - // 注册布局 - resigterLayout(); - }; - - // 浅比较图数据: 只比较图结构有没有变化 - getGraphShallowDiff = (data = { nodes: [], edges: [] }, prevData = { nodes: [], edges: [] }) => { - const { nodes, edges } = data; - const { nodes: prevNodes, edges: prevEdges } = prevData; - if (nodes.length !== prevNodes.length || edges.length !== prevEdges.length) { - return true; - } - const noNodeChange = nodes.every((node) => prevNodes.find((prevNode) => prevNode.id === node.id)); - const noEdgeChange = edges.every((edge) => prevEdges.find((prevEdge) => prevEdge.id === edge.id)); - return !(noNodeChange && noEdgeChange); - }; - - // 创建主图 - newGraph = () => { - if (this.graph) { - this.removeEventListener(); - this.graph.destroy(); - } - const width = this.container?.scrollWidth; - const height = this.container?.scrollHeight; - const graph = new G6.Graph({ - container: this.container, - width, - height, - modes: { - default: DEFAULT_MODE, - }, - defaultNode: DEFAULT_NODE, - defaultEdge: DEFAULT_EDGE, - layout: this.getLayoutParams(), - minZoom: 0.001, - }); - this.graph = graph; - const { onReady } = this.props; - if (onReady) { - onReady(graph); - } - // 注册自定义behavior - registerBehavior(this.graph); - this.addEventListener(); - }; - - // 绘制节点和边 - renderGraph = () => { - const { graphData } = this.state; - this.graph.data(graphData); - this.graph.render(); - }; - - // 添加事件监听 - addEventListener = () => { - // 布局完成 - this.graph.on('afterlayout', this.handleAfterLayout); - }; - - // 移除事件监听 - removeEventListener = () => { - if (this.graph && !this.graph?.destroyed) { - // 移除布局完成监听 - this.graph.off('afterlayout', this.handleAfterLayout); - // 移除container resize监听 - this.resizeObserver.disconnect(); - } - }; - - // 获取布局参数 - getLayoutParams = (forceLayout?: boolean) => { - const { layout, data } = this.props; - // 是否每个节点都有位置信息 - const hasPosition = data?.nodes?.every((node) => node.x !== undefined && node.y !== undefined); - // 如果每个节点都有位置信息,则走自定义preset布局 - if (!forceLayout && hasPosition) { - return { - type: 'conv-preset', - }; - } - return { - ...DEFAULT_LAYOUT_OPTIONS, - ranksep: layout.rankdir === 'TB' ? 75 : 150, - ...layout, - type: 'dagre', - }; - }; - - // 布局完成回调 - handleAfterLayout = () => { - const { - layerOrder, - layout: { rankdir }, - } = this.props; - if (!layerOrder?.length) { - // 画布内容自适应视口大小 - this.graph.fitView(20); - return; - } - // 更新边对应的类型、起点和终点的连接点 - updateEdgeAnchorAndType(this.graph, layerOrder, rankdir); - // 绘制层级名称 - drawLayerName(this.graph, layerOrder, rankdir); - if (this.shouldCacheZoomAndTranslate) { - // 设置画布缩放比、位移、选中元素 - this.setCacheGraph(); - } else { - // 画布内容自适应视口大小 - this.graph.fitView(20); - // 首次非空图数据,缓存当前视口状态 - if (this.state.graphData?.nodes?.length) { - this.shouldCacheZoomAndTranslate = true; - this.handleCacheGraph(); - } - } - }; - - // 缓存画布缩放比、画布位移、选中元素 - handleCacheGraph = () => { - if (!this.graph || this.graph.destroyed) { - return; - } - const width = this.graph.get('width'); - const height = this.graph.get('height'); - // 记录保存时的视口中心点对应的canvas坐标 - const centerPoint = this.graph.getCanvasByPoint(width / 2, height / 2); - // 选中的节点 - const selectedNodes = this.graph - .getNodes() - .filter((node) => node.hasState(ITEM_STATE.Selected)) - .map((node) => node.get('id')); - // 选中的边 - const selectedEdges = this.graph - .getEdges() - .filter((edge) => edge.hasState(ITEM_STATE.Selected)) - .map((edge) => edge.get('id')); - // 缓存视口数据 - this.cacheData = { - zoom: this.graph.getZoom(), // 缩放比 - centerPoint, // 视口中心对应的canvas坐标 - selectedNodes, // 选中的节点 - selectedEdges, // 选中的边 - }; - }; - - // 设置画布缩放比、位移、选中的节点 - setCacheGraph = () => { - if (!this.cacheData) { - return; - } - const { - selectedNodes, - selectedEdges, - zoom, - centerPoint: { x, y }, - } = this.cacheData; - const width = this.graph.get('width'); - const height = this.graph.get('height'); - this.graph.zoomTo(zoom); - // 获取视口中心点对应的canvas坐标 - const newCenterPoint = this.graph.getCanvasByPoint(width / 2, height / 2); - // 做视口中心点的平移 - this.graph.translate(x - newCenterPoint.x, y - newCenterPoint.y); - // 还原选中的节点状态 - selectedNodes.forEach((selectedNode) => { - this.graph.setItemState(selectedNode, ITEM_STATE.Selected, true); - }); - // 还原选中的边状态 - selectedEdges.forEach((selectedEdge) => { - this.graph.setItemState(selectedEdge, ITEM_STATE.Selected, true); - }); - }; - - handleMouseDown = (event) => { - //阻止外层拖动影响 - event.preventDefault(); - }; - - render(): React.ReactNode { - const { className, style: propsStyle = {} } = this.props; - const style = { - width: '100%', - height: '100%', - ...propsStyle, - }; - - return ( -
{ - this.container = container; - }} - /> - ); - } -} diff --git a/packages/graphs/src/components/conversion-dagre-graph/layout/conv-preset.ts b/packages/graphs/src/components/conversion-dagre-graph/layout/conv-preset.ts deleted file mode 100644 index df0af05d8..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/layout/conv-preset.ts +++ /dev/null @@ -1,11 +0,0 @@ -import G6 from '@antv/g6'; - -// 自定义preset布局,如果节点中都有位置信息时走这里 -export const registerConvPreset = () => { - G6.registerLayout('conv-preset', { - // 执行布局 - execute: function execute() { - // 执行一次空布局,目的是为了走布局流程触发afterlayout - }, - }); -} diff --git a/packages/graphs/src/components/conversion-dagre-graph/layout/index.ts b/packages/graphs/src/components/conversion-dagre-graph/layout/index.ts deleted file mode 100644 index 2f90178b7..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/layout/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { registerConvPreset } from './conv-preset'; - -export const resigterLayout = () => { - registerConvPreset(); -}; \ No newline at end of file diff --git a/packages/graphs/src/components/conversion-dagre-graph/nodes/convNode.ts b/packages/graphs/src/components/conversion-dagre-graph/nodes/convNode.ts deleted file mode 100644 index 1b7e35475..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/nodes/convNode.ts +++ /dev/null @@ -1,171 +0,0 @@ -import G6 from '@antv/g6'; -import type { NodeConfig } from '@antv/g6'; -import { setItemStateStyle } from '../utils'; - -export const registerConvNode = () => { - // 自定义转化节点 - G6.registerNode('conv-node', { - options: {}, - draw(cfg: NodeConfig, group) { - const { - custom = {} as any, - style: { stroke, textColor }, - size = 150, - } = cfg; - const label = cfg.label as string; - const { measure = {}, relatedMeasures = [] } = custom; - const showFormattedValue = - measure.formattedValue !== undefined && measure.formattedValue !== null; - // 最外层描边(光晕) - const keyShape = group.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: size as number, - height: 122, - lineWidth: 0, // 默认不描边 - stroke, - opacity: 0.3, - radius: 4, - }, - name: 'node-key-shape', - draggable: true, - }); - - const keyShapeBbox = keyShape.getBBox(); - - group.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: keyShapeBbox.width, - height: keyShapeBbox.height, - stroke: stroke, - fill: '#fff', - radius: 4, - }, - name: 'node-inner-border-shape', - draggable: true, - }); - - group.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: 4, - height: keyShapeBbox.height, - fill: stroke, - radius: [4, 0, 0, 4], - }, - name: 'node-left-border-shape', - draggable: true, - }); - - const showNameMore = label.length > 10; - // 节点名称 - group?.addShape('text', { - attrs: { - text: showNameMore ? `${label.slice(0, 11)}...` : label, - x: 12, - y: 12, - fontFamily: 'PingFangSC', - fontSize: 12, - fontWeight: 600, - lineHeight: 17, - textBaseline: 'top', - fill: textColor, - opacity: 0.85, - }, - name: 'node-name', - }); - - // 主指标名称 - group?.addShape('text', { - attrs: { - text: measure.name || '', - x: 12, - y: 37, - fontFamily: 'PingFangSC', - fontSize: 12, - fontWeight: 600, - lineHeight: 17, - textBaseline: 'top', - fill: textColor, - opacity: 0.85, - }, - name: 'node-measure-name', - draggable: true, - }); - - // 主指标值 - const measureShape = group?.addShape('text', { - attrs: { - text: (showFormattedValue ? measure.formattedValue : measure.value) || '', - x: 12, - y: 58, - fontFamily: 'Roboto-Medium', - fontSize: 20, - fontWeight: 600, - lineHeight: 28, - textBaseline: 'top', - fill: textColor, - opacity: 0.85, - }, - name: 'node-measure-value', - draggable: true, - }); - - if (showFormattedValue) { - const measureShapeBbox = measureShape.getBBox(); - // 主指标单位 - group?.addShape('text', { - attrs: { - text: measure.formattedUnit || '', - x: 12 + measureShapeBbox.width + 4, - y: 56, // 这个字体要比主指标字体矮一点 - fontFamily: 'PingFangSC', - fontSize: 20, - fontWeight: 500, - lineHeight: 28, - textBaseline: 'top', - fill: textColor, - opacity: 0.85, - }, - name: 'node-measure-unit', - draggable: true, - }); - } - - // 相关指标信息 - relatedMeasures.forEach((relatedMeasure, index) => { - const showFormattedValue = relatedMeasure.formattedValue !== undefined; - const relatedMeasureText = `${relatedMeasure.name} ${ - showFormattedValue ? relatedMeasure.formattedValue : relatedMeasure.value - }${showFormattedValue && relatedMeasure.formattedUnit}`; - group?.addShape('text', { - attrs: { - text: relatedMeasureText, - x: 12, - y: 95, - fontFamily: 'PingFangSC', - fontSize: 12, - fontWeight: 400, - lineHeight: 17, - textBaseline: 'top', - fill: textColor, - opacity: 0.45, - }, - name: `node-releted-measure-${index}`, - draggable: true, - }); - }); - - return keyShape; - }, - - setState(name, value, node) { - // 设置状态样式 - setItemStateStyle(node, 'node'); - }, - }); -} \ No newline at end of file diff --git a/packages/graphs/src/components/conversion-dagre-graph/nodes/index.ts b/packages/graphs/src/components/conversion-dagre-graph/nodes/index.ts deleted file mode 100644 index b3d83a6b7..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/nodes/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { registerConvNode } from './convNode'; - -export const resigterNodes = () => { - registerConvNode(); -}; \ No newline at end of file diff --git a/packages/graphs/src/components/conversion-dagre-graph/types/graph.ts b/packages/graphs/src/components/conversion-dagre-graph/types/graph.ts deleted file mode 100644 index ea8e57944..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/types/graph.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { measureItem, PlainObject, OriginNode, OriginEdge } from './propsAndState'; - -// 边类型 -export enum EdgeType { - CONV_LINE = 'conv-line', - CONV_CUBIC_VERTICAL = 'conv-cubic-vertical', - CONV_CUBIC_HORIZONTAL = 'conv-cubic-horizontal', -} - -// 元素状态 -export enum ITEM_STATE { - Active = 'active', - Default = 'default', - Selected = 'selected', -} - -// 节点 -export interface GraphNode { - id: string; // 节点id - label: string; // 节点名称 - layer: number; // 节点所属层级 - custom: { - layerName: string; // 节点所属层级名称 - measure: measureItem; // 节点主指标 - relatedMeasures?: measureItem[]; // 节点相关指标 - compareMeasures?: measureItem[]; // 同环比指标 - [key: string]: any; - }; - style: PlainObject; // 样式 - data: OriginNode; // 节点原始数据 - x?: number; // 节点x坐标 - y?: number; // 节点y坐标 -} - -// 边 -export interface GraphEdge { - id: string; // 边id - source: string; // 边起点id - target: string; // 边终点id - label: string; // 边名称 - style: PlainObject; // 样式 - custom: { - ratio: number; // 边比率 - showRatio: string; // 边比率(展示) - [key: string]: any; - }; - - data: OriginEdge; // 边原始数据 -} - -// 转化流程图数据 -export interface ConvGraphData { - nodes: GraphNode[]; - edges: GraphEdge[]; -} diff --git a/packages/graphs/src/components/conversion-dagre-graph/types/index.ts b/packages/graphs/src/components/conversion-dagre-graph/types/index.ts deleted file mode 100644 index de831696d..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './graph'; -export * from './propsAndState'; diff --git a/packages/graphs/src/components/conversion-dagre-graph/types/propsAndState.ts b/packages/graphs/src/components/conversion-dagre-graph/types/propsAndState.ts deleted file mode 100644 index 00aa34b8e..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/types/propsAndState.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { IGraph } from '@antv/g6'; -export type PlainObject = Record; - -// 单个指标 -export interface measureItem { - name: string; // 指标名称 - value: number; // 指标数值 - formattedValue?: number; // 指标格式化后的值 - formattedUnit?: string; // 指标格式化后的单位 -} - -// 原始节点数据 -export interface OriginNode { - id: string; // 节点id - name: string; // 节点名称 - layerName: string; // 节点所属层级名称 - layer?: number; // 节点所属层级 - measure: measureItem; // 节点主指标 - relatedMeasures?: measureItem[]; // 节点相关指标 - compareMeasures?: measureItem[]; // 同环比指标 - x?: number; // 节点x坐标 - y?: number; // 节点y坐标 - style?: PlainObject; // 样式 -} - -// 原始边数据 -export interface OriginEdge { - id: string; // 边id - source: string; // 边起点id - target: string; // 边终点id - measure: measureItem; // 边主指标 - name?: string; // 边名称 - ratio?: number; // 边比率 - style?: PlainObject; // 样式 -} - -export type Rankdir = 'TB' | 'LR'; - -export interface LayoutParams { - rankdir: Rankdir; - [key: string]: any; -} - -// 层级顺序 -export type LayerOrder = string[]; - -// 边比率计算方式 -export type RatioMethod = 'proportion' | 'splitFlow' | 'both'; // 占比、分流、占比和分流 - -// 原始图数据 -export interface OriginData { - nodes: OriginNode[]; - edges: OriginEdge[]; -} - -// 组件props -export interface Props { - data: OriginData; // 图数据 - layerOrder?: LayerOrder; // 层级顺序 - segmLayer?: string; // 分段层级 - ratioMethod?: RatioMethod; // 比率计算方式 - layout?: LayoutParams; // 布局配置 - className?: string; // class类名 - style?: PlainObject; // 样式配置 - onReady?: (graph: IGraph) => void; -} diff --git a/packages/graphs/src/components/conversion-dagre-graph/utils/edge.ts b/packages/graphs/src/components/conversion-dagre-graph/utils/edge.ts deleted file mode 100644 index 388b59769..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/utils/edge.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { Graph, EdgeConfig } from '@antv/g6'; -import type { Rankdir } from '../types'; -import { EdgeType } from '../types'; -import type { LayerOrder } from '../types'; - -// 获取边的类型,起点和终点的anchorPoint索引值 -export const getEdgeAnchorAndType = ( - graph: Graph, - edge: EdgeConfig, - layerOrder: LayerOrder, - rankdir: Rankdir, -) => { - const nodes = graph.getNodes().map((node) => node.getModel()); - const sourceNode = nodes.find((node) => node.id === edge.source); - const targetNode = nodes.find((node) => node.id === edge.target); - const sourceLayer = layerOrder.findIndex( - (item) => item === (sourceNode?.custom as any)?.layerName, - ); - const targetLayer = layerOrder.findIndex( - (item) => item === (targetNode?.custom as any)?.layerName, - ); - - let sourceAnchor; - let targetAnchor; - let type; - // 同层级节点之间的边, - if (sourceLayer !== -1 && sourceLayer === targetLayer) { - type = EdgeType.CONV_LINE; - // 起始点的连接点根据起始点的位置顺序来 - if (rankdir === 'LR') { - sourceAnchor = sourceNode.y < targetNode.y ? 1 : 0; - targetAnchor = sourceNode.y < targetNode.y ? 0 : 1; - } else { - sourceAnchor = sourceNode.x < targetNode.x ? 3 : 2; - targetAnchor = sourceNode.x < targetNode.x ? 2 : 3; - } - } else { - if (rankdir === 'LR') { - type = EdgeType.CONV_CUBIC_HORIZONTAL; - sourceAnchor = sourceNode.x < targetNode.x ? 3 : 2; - targetAnchor = sourceNode.x < targetNode.x ? 2 : 3; - } else { - type = EdgeType.CONV_CUBIC_VERTICAL; - sourceAnchor = sourceNode.y < targetNode.y ? 1 : 0; - targetAnchor = sourceNode.y < targetNode.y ? 0 : 1; - } - } - - return { - type, - sourceAnchor, - targetAnchor, - }; -}; - -// 更新边对应的类型、起点和终点的连接点 -export const updateEdgeAnchorAndType = (graph: Graph, layerOrder: LayerOrder, rankdir: Rankdir) => { - graph.getEdges().forEach((edge) => { - const edgeModel = edge.getModel(); - const { type, sourceAnchor, targetAnchor } = getEdgeAnchorAndType( - graph, - edgeModel, - layerOrder, - rankdir, - ); - graph.updateItem(edgeModel.id, { - type, - sourceAnchor, - targetAnchor, - }); - }); -}; - -// 获取自定义箭头 -export const getArrowConfig = (color: string = '#B8C3D9') => { - return { - path: 'M 14,-4 C 14,-4,11,0,14,4 L 4,0 L 14,-4 Z', - d: 2, - fill: color, - stroke: color, - lineWidth: 1, - lineDash: [], - }; -}; diff --git a/packages/graphs/src/components/conversion-dagre-graph/utils/graph.ts b/packages/graphs/src/components/conversion-dagre-graph/utils/graph.ts deleted file mode 100644 index 08238ad97..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/utils/graph.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { OriginEdge } from '../types'; - -// 获取节点的所有出/入路径的边集合 -export const getInOutPathEdges = (id: string, edges: OriginEdge[]) => { - const relativeInEdges = []; - const relativeOutEdges = []; - const queriedNodeIds = new Set([id]); - - // 查询节点的一度入度边 + 出度边 - const queryOneDegreeEdges = (inNodeIds, outNodeIds) => { - const inNodeIdSet = new Set(); - const outNodeIdSet = new Set(); - - edges?.forEach((edge) => { - // 查找入边 - if (inNodeIds.has(edge.target)) { - relativeInEdges.push(edge); - if (!queriedNodeIds.has(edge.source)) { - inNodeIdSet.add(edge.source); - } - } else if (outNodeIds.has(edge.source)) { - // 查找出边 - relativeOutEdges.push(edge); - if (!queriedNodeIds.has(edge.target)) { - outNodeIdSet.add(edge.target); - } - } - }); - - if (inNodeIdSet.size > 0 || outNodeIdSet.size > 0) { - queryOneDegreeEdges(inNodeIdSet, outNodeIdSet); - } - }; - - queryOneDegreeEdges(new Set([id]), new Set([id])); - return { - relativeInEdges, - relativeOutEdges, - }; -}; diff --git a/packages/graphs/src/components/conversion-dagre-graph/utils/index.ts b/packages/graphs/src/components/conversion-dagre-graph/utils/index.ts deleted file mode 100644 index c22696d4d..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/utils/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './transform'; -export * from './layer'; -export * from './edge'; -export * from './state'; -export * from './graph'; -export * from './text'; diff --git a/packages/graphs/src/components/conversion-dagre-graph/utils/layer.ts b/packages/graphs/src/components/conversion-dagre-graph/utils/layer.ts deleted file mode 100644 index 72c04f4e5..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/utils/layer.ts +++ /dev/null @@ -1,152 +0,0 @@ -import type { Graph } from '@antv/g6'; -import type { Rankdir } from '../types'; -import type { LayerOrder } from '../types'; - -// 在画布上绘制层级名称 -export const drawLayerName = (graph: Graph, layerOrder: LayerOrder, rankdir: Rankdir) => { - const nodes = graph.getNodes().map((node) => node.getModel()); - if (!graph || !Array.isArray(layerOrder) || !layerOrder?.length) { - return; - } - - const group = graph.getGroup(); - // 删除已有的层级名称容器分组 - removeLayerNameGroup(graph); - // 添加层级名称容器的分组 - const layerNameContainerGroup = group.addGroup({ id: 'layer-name-container-group' }); - - // 过滤得到节点中实际存在的有效层级 - const effectiveLayer = layerOrder.filter((layerName) => - nodes.find((node) => (node.custom as any)?.layerName === layerName), - ); - // 存储所有层级名称的x: 需遍历所有节点找出最左侧的节点的x, 实现层级名称纵向对齐 - let minX = Infinity; - let minY = Infinity; - // 存储所有层级名称的x: 同层级节点x值的分布最多的值 - const xInfo = []; - // 存储所有层级名称的y: 同层级节点y值的分布最多的值 - const yInfo = []; - effectiveLayer.forEach((layerName) => { - const countYInfo = {}; - const countXInfo = {}; - nodes.forEach((node) => { - if ((node.custom as any).layerName === layerName) { - // 找到所有节点中最左侧的节点的x坐标 - if (node.x < minX) { - minX = node.x; - } - // 找到所有节点中最顶部的节点的y坐标 - if (node.y < minY) { - minY = node.y; - } - const xKey = String(node.x); - const yKey = String(node.y); - // 记录所属同层级节点的x、y值分布(考虑节点拖动保存情况) - if (Object.keys(countXInfo).indexOf(String(xKey)) === -1) { - countXInfo[xKey] = 1; - } else { - countXInfo[xKey] += 1; - } - if (Object.keys(countYInfo).indexOf(String(yKey)) === -1) { - countYInfo[yKey] = 1; - } else { - countYInfo[yKey] += 1; - } - } - }); - // 找出同层级节点中最多分布的x值(考虑节点拖动保存情况) - const xInfoKeys = Object.keys(countXInfo); - let mostX = xInfoKeys[0]; - xInfoKeys.forEach((key) => { - if (countXInfo[key] > countXInfo[mostX]) { - mostX = key; - } - }); - - xInfo.push(Number(mostX)); - - // 找出同层级节点中最多分布的y值(考虑节点拖动保存情况) - const yInfoKeys = Object.keys(countYInfo); - let mostY = yInfoKeys[0]; - yInfoKeys.forEach((key) => { - if (countYInfo[key] > countYInfo[mostY]) { - mostY = key; - } - }); - yInfo.push(Number(mostY)); - }); - - // 在画布中绘制有效层级 - effectiveLayer.forEach((layerName, index) => { - if (isNaN(xInfo[index]) || isNaN(yInfo[index])) { - return; - } - // 层级名称换行处理:先暂时处理每隔4个字符换行一次,待G6升级g-canvas 1.x以后使用wordWrap代替 - let text = ''; - const textArray = layerName.split(''); - textArray.forEach((item, index) => { - text += index > 0 && index < textArray.length - 1 && index % 4 === 3 ? `${item}\n` : item; - }); - // 添加层级名称的分组 - const layerNameGroup = layerNameContainerGroup.addGroup({ id: `layer-name-group-${index}` }); - if (rankdir === 'TB') { - layerNameGroup.setMatrix([1, 0, 0, 0, 1, 0, minX - 102, yInfo[index], 1]); - } else { - layerNameGroup.setMatrix([1, 0, 0, 0, 1, 0, xInfo[index] + 32, minY - 102, 1]); - } - - // 监听层级名称分组的拖动: 一期暂不支持,后续和节点拖动保存位置一起做 - // layerNameGroup.on('drag', e => { - // const { pointX, pointY } = e; - // layerNameGroup.setMatrix([1, 0, 0, 0, 1, 0, pointX, pointY, 1]); - // }); - - // 向层级名称的分组中添加 层级名称图形 - const layerNameShape = layerNameGroup.addShape('text', { - attrs: { - text, - x: 0, - y: 0, - fontFamily: 'PingFangSC', - fontSize: 16, - fontWeight: 400, - fill: 'rgb(3,34,98)', - opacity: 0.65, - lineHeight: 18, - textBaseline: 'top', - textAlign: 'center', - // wordWrap: true, // g-canvas 1.x以上版本才支持 - // wordWrapWidth: 48, - }, - name: `layer-name-${layerName}`, - // draggable: true, // 一期暂不支持,后续和节点拖动保存位置一起做 - }); - - const layerNameBbox = layerNameShape.getBBox(); - - // 向层级名称的分组中添加 层级名称背景图形 - layerNameGroup.addShape('rect', { - attrs: { - x: -34, // 留白2 - y: -2, - width: 68, - height: layerNameBbox?.height + 4, - fill: '#B8C7E6', - opacity: 0.12, - }, - name: `layer-name-${layerName}`, - // draggable: true, // 一期暂不支持,后续和节点拖动保存位置一起做 - }); - - layerNameShape.toFront(); - }); -}; - -// 删除已有的层级名称分组 -export const removeLayerNameGroup = (graph: Graph) => { - const group = graph.getGroup(); - const layerNameGroup = group.findById('layer-name-container-group'); - if (layerNameGroup) { - group.removeChild(layerNameGroup); - } -}; diff --git a/packages/graphs/src/components/conversion-dagre-graph/utils/state.ts b/packages/graphs/src/components/conversion-dagre-graph/utils/state.ts deleted file mode 100644 index 143487623..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/utils/state.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { ITEM_STATE } from '../types'; -import { getArrowConfig } from './edge'; -import type { Item, INode, Graph, IEdge } from '@antv/g6'; -import { BASE_STATES, SIZE_IMPACT_STATES } from '../constants'; - -// 获取节点状态样式 -export const getNodeStateStyles = (state) => { - const stateStyles = { - [ITEM_STATE.Selected]: { - 'node-key-shape': { - lineWidth: 14, - }, - 'node-inner-border-shape': { - lineWidth: 2, - }, - }, - [ITEM_STATE.Active]: { - 'node-key-shape': { - lineWidth: 6, - }, - 'node-inner-border-shape': { - lineWidth: 1, - }, - }, - [ITEM_STATE.Default]: { - 'node-key-shape': { - lineWidth: 0, - }, - 'node-inner-border-shape': { - lineWidth: 1, - }, - }, - }; - return { - ...stateStyles[state], - }; -}; - -// 获取边line的状态样式 -export const getEdgeStateStyles = (state, edge) => { - const { highlightColor, stroke, labelFill } = edge.style; - const stateStyles = { - [ITEM_STATE.Selected]: { - 'path-shape': { - stroke: highlightColor, - endArrow: getArrowConfig(highlightColor), - }, - 'label-shape': { - fill: highlightColor, - }, - }, - [ITEM_STATE.Active]: { - 'path-shape': { - stroke: highlightColor, - endArrow: getArrowConfig(highlightColor), - }, - 'label-shape': { - fill: highlightColor, - }, - }, - [ITEM_STATE.Default]: { - 'path-shape': { - stroke, - endArrow: getArrowConfig(stroke), - }, - 'label-shape': { - fill: labelFill, - }, - }, - }; - return { - ...stateStyles[state], - }; -}; - -// 设置元素状态样式 -export const setItemStateStyle = (item, type) => { - // 获取元素的数据模型 - const model = item.getModel(); - const states = item.getStates(); - // 获取元素状态 - const state = states[states.length - 1] || ITEM_STATE.Default; - // 获取元素在该state下的各shape的style - const style = type === 'node' ? getNodeStateStyles(state) : getEdgeStateStyles(state, model); - // 获取元素的所有shape - const shapes = item.getContainer()?.get('children'); - // 更新各shape的style - shapes?.forEach((shape) => { - const shapeName = shape.get('name'); - if (style[shapeName]) { - shape.attr(style[shapeName]); - } - }); -}; - -// 一次绘制 -export const paintOnce = (graph: Graph, fn: Function) => { - const autoPaint = graph.get('autoPaint'); - graph.setAutoPaint(false); - fn(); - graph.paint(); - graph.setAutoPaint(autoPaint); -}; - -// 清空元素状态 -export const clearItemStates = ( - graph: Graph, - graphItem: Item, - states: ITEM_STATE[], - enablePaint: boolean = false, -) => { - function fn() { - states.forEach((state) => { - if (graphItem?.hasState(state)) { - graph.setItemState(graphItem, state, false); - // item.setState(state, false); - // 对尺寸 style 有影响的 state 才需要 refresh ,重新计算位置和边界 - // if (Size_IMPACT_STATES.includes(state)) { - graphItem.refresh(); - // } - } - }); - } - if (enablePaint) { - paintOnce(graph, () => { - fn(); - }); - } else { - fn(); - } -}; - -// 清空指定元素集合的状态 -export const clearItemsStates = ( - graph: Graph, - items: Item[], - clearStates: ITEM_STATE[], - enablePaint: boolean = false, -) => { - function fn() { - items.forEach((graphItem) => { - try { - clearItemStates(graph, graphItem, clearStates, false); - } catch (error) { - console.log('error :>> ', graphItem, error); - } - }); - } - if (enablePaint) { - paintOnce(graph, () => { - fn(); - }); - } else { - fn(); - } -}; - -// 设置元素状态 -export const setItemState = ( - graph: Graph, - graphItem: Item, - state: ITEM_STATE, - value: boolean | string, - enablePaint: boolean = false, -) => { - function fn() { - if (!graphItem || graphItem.destroyed) { - return; - } - graph.setItemState(graphItem, state, value); - // 对style有影响,重新计算位置和边界 - if (!graphItem.getKeyShape()?.get('destroyed') && SIZE_IMPACT_STATES.includes(state)) { - graphItem.refresh(); - } - } - if (enablePaint) { - paintOnce(graph, () => { - fn(); - }); - } else { - fn(); - } -}; - -// 重绘节点相关的边 -export const refreshRelatedEdges = (node: INode) => { - node.getEdges().forEach((edge) => { - if (!edge.getKeyShape()?.get('destroyed') && edge.get('keyShape')) { - edge.refresh(); - } - }); -}; - -// 重置所有节点的状态到默认认态 -export const resetNodeStates = (graph: Graph, enablePaint: boolean = false) => { - // 由于状态的变化,可能会造成 style 的变化,特别是大小的变化,因此在重置了 node 之后,还需要 refresh 关联的边,否则会造成边的终点连接不到节点边界 - const relatedEdges = new Set() as Set; - - const resetNodes = () => { - BASE_STATES.forEach((state) => { - const stateNodes = graph.findAllByState('node', state) as INode[]; - stateNodes.forEach((node) => { - clearItemStates(graph, node, [state], false); - if (SIZE_IMPACT_STATES.includes(state)) { - node.getEdges().forEach((edge) => relatedEdges.add(edge)); - } - }); - }); - Array.from(relatedEdges).forEach((edge) => { - edge.refresh(); - }); - }; - - if (enablePaint) { - paintOnce(graph, resetNodes); - } else { - resetNodes(); - } -}; - -// 重置所有边的状态到默认状态 -export const resetEdgeStates = (graph: Graph, enablePaint: boolean = false) => { - clearItemsStates(graph, graph.getEdges(), BASE_STATES, enablePaint); -}; - -// 重置为默认状态 -export const resetBaseStates = (graph: Graph, enablePaint: boolean = false) => { - resetNodeStates(graph, enablePaint); - resetEdgeStates(graph, enablePaint); -}; - -// 清空激活状态 -export const clearActiveStates = (graph: Graph, enablePaint: boolean = false) => { - clearItemsStates(graph, graph.getNodes(), [ITEM_STATE.Active], enablePaint); - clearItemsStates(graph, graph.getEdges(), [ITEM_STATE.Active], enablePaint); -}; diff --git a/packages/graphs/src/components/conversion-dagre-graph/utils/text.ts b/packages/graphs/src/components/conversion-dagre-graph/utils/text.ts deleted file mode 100644 index 60fbbcd26..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/utils/text.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { OriginNode } from '../types'; - -// 获取文本的字节长度 -export const getNodeStrSize = (node: OriginNode): number => { - const { measure: { formattedValue = '', value = '', formattedUnit = '' } = {} } = node || {}; - const nodeStr = - ((formattedValue !== undefined && formattedValue !== null - ? `${formattedValue}${formattedUnit}` - : value) as string) || ''; - return nodeStr.replace(/[^\x00-\xff]/g, '00').length; -}; -// 获取文本最长的节点 -export const getMaxSizeNode = (nodes: OriginNode[] = []): OriginNode => - nodes.reduce((prevNode, node) => { - const prevSize = getNodeStrSize(prevNode); - const nodeSize = getNodeStrSize(node); - return prevSize > nodeSize ? prevNode : node; - }); - -// 获取字符串实际渲染的宽度 -export const getWordsWidth = (text: string = '', font: string): number => { - if (text === '') { - return 0; - } - let canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - context.font = font; - const { width } = context.measureText(text); - canvas = null; - return width; -}; - -// 获取最大节点宽度 -export const getNodeMaxSize = (nodes: OriginNode[] = []): number => { - if (!nodes?.length) { - return 150; - } - const maxSizeNode = getMaxSizeNode(nodes); - const { measure: { formattedValue = '', value = '', formattedUnit = '' } = {} } = maxSizeNode; - // 是否展示formatValue - const isShowFormatValue = formattedValue !== undefined && formattedValue !== null; - const displayValue = isShowFormatValue ? formattedValue : value; - const displayValueWidth = getWordsWidth(`${displayValue}`, '600 20px Roboto-Medium'); - // 单位不为空时才计算单位宽度 unit的paddingLeft 4 - const unitWidth = - isShowFormatValue && formattedUnit - ? getWordsWidth(`${formattedUnit}`, '600 20px PingFangSC') + 4 - : 0; - // 节点大小 value + unit + 左右padding - const nodeSize = displayValueWidth + unitWidth + 24; - // 节点最小宽度 150 - return Math.max(nodeSize, 150); -}; diff --git a/packages/graphs/src/components/conversion-dagre-graph/utils/transform.ts b/packages/graphs/src/components/conversion-dagre-graph/utils/transform.ts deleted file mode 100644 index 1682e63d6..000000000 --- a/packages/graphs/src/components/conversion-dagre-graph/utils/transform.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { cloneDeep, uniqBy } from 'lodash'; -import { getNodeMaxSize } from './text'; -import type { - OriginData, - LayerOrder, - OriginNode, - GraphNode, - GraphEdge, - ConvGraphData, - RatioMethod, -} from '../types'; - -// 数据转换:原始节点数据 -> G6渲染节点数据 -const transformOriginNodes = ( - originNodes: OriginNode[], - layerOrder: LayerOrder, - size: number, -): GraphNode[] => { - // 过滤得到节点中实际存在的有效层级 - const effectiveLayer = - (Array.isArray(layerOrder) && - layerOrder.filter((layerName) => originNodes.find((node) => node.layerName === layerName))) || - []; - return originNodes.map((originNode) => { - const layer = effectiveLayer?.findIndex((item) => item === originNode.layerName) + 1; // dagre布局指定层级有bug,layer暂时从1开始 - return { - id: originNode.id, // id - label: originNode.name || '', // 名称 - layer: layer !== -1 ? layer : undefined, - style: { - stroke: '#B8C3D9', - textColor: '#000', - ...(originNode.style || {}), - }, - size, - custom: { - layerName: originNode.layerName, // 所属层级名称 - measure: originNode.measure, // 主指标 - relatedMeasures: originNode.relatedMeasures, // 相关指标 - compareMeasures: originNode.compareMeasures, // 同环比指标 - }, - data: cloneDeep(originNode), - x: originNode.x, - y: originNode.y, - }; - }); -}; - -// 数据转换:原始边数据 -> G6渲染边数据 -const transformOriginEdges = ( - data: OriginData, - layerOrder: string[], - segmLayer: string, - ratioMethod: RatioMethod, -) : GraphEdge[] => { - const { nodes: originNodes = [], edges: originEdges = [] } = data; - - return originEdges - .map((originEdge) => { - const sourceNode = originNodes.find((node) => node.id === originEdge.source); - const targetNode = originNodes.find((node) => node.id === originEdge.target); - if (!sourceNode || !targetNode) { - return undefined; - } - - // 自动计算边的比率和名称 - let autoRatio = 0; - let name = ''; - switch (ratioMethod) { - case 'both': - // 包含占比和分流 - // 找出分段层级的层级值 - const segmLayerValue = layerOrder.findIndex((item) => item === segmLayer); - // 找出终点的层级值 - const targetLayerValue = layerOrder.findIndex((item) => item === targetNode?.layerName); - // 同层级的边、分段层级的入边都是占比边, 比率 = 边上指标 / 终点的主指标 - if (!segmLayer || targetLayerValue <= segmLayerValue) { - autoRatio = originEdge?.measure?.value / targetNode?.measure?.value; - name = '占比'; - } else { - // 分段层级的出边都是分流边, 比率 = 边上指标 / 起点的主指标 - autoRatio = originEdge?.measure?.value / sourceNode?.measure?.value; - name = '分流'; - } - break; - case 'splitFlow': - // 均是分流:比率 = 边上指标 / 起点的主指标 - autoRatio = originEdge?.measure?.value / sourceNode?.measure?.value; - name = '分流'; - break; - case 'proportion': - // 均是占比:比率 = 边上指标 / 终点的主指标 - autoRatio = originEdge?.measure?.value / targetNode?.measure?.value; - name = '占比'; - break; - default: - // 不展示转化比率 - autoRatio = undefined; - name = ''; - break; - } - - const ratio = originEdge.ratio || autoRatio; // 优先使用edge数据里的ratio - const formatRatio = - !isNaN(ratio) && ratio !== Infinity - ? Number((ratio * 100).toString().match(/^\d+(?:\.\d{0,2})?/)) - : '-'; // 比率最多保留2位小数 - const showRatio = `${formatRatio}%`; - const label = originEdge.name || name; // 优先使用edge数据里的name - - return { - id: originEdge.id, - source: originEdge.source, - target: originEdge.target, - label: autoRatio !== undefined ? `${label} ${showRatio}` : '', // 名称 比率%, 不开启转化指标的时候不展示 - style: { - stroke: '#B8C3D9', - labelFill: '#000', - highlightColor: '#3572F9', - ...(originEdge.style || {}), - }, - custom: { - ratio, - formatRatio, - showRatio, - sourceNode, - targetNode, - label, - }, - data: cloneDeep(originEdge), - }; - }) - .filter((edge) => !!edge); -}; - -// 数据转换:原始数据 -> G6渲染数据 -export const transformOriginData = ( - originData: OriginData, - layerOrder: LayerOrder = [], - segmLayer: string, - ratioMethod: RatioMethod, -): ConvGraphData => { - const { nodes: originNodes = [] } = originData; - // 节点去重 - const uniqNodes = uniqBy(originNodes, 'id'); - // 获取所有节点中最大的宽作为节点size - const size = getNodeMaxSize(uniqNodes); - const nodes = transformOriginNodes(uniqNodes, layerOrder, size); - const edges = transformOriginEdges(originData, layerOrder, segmLayer, ratioMethod); - return { - nodes, - edges, - }; -}; diff --git a/packages/graphs/src/components/decomposition-tree-graph/events.ts b/packages/graphs/src/components/decomposition-tree-graph/events.ts deleted file mode 100644 index 062e21711..000000000 --- a/packages/graphs/src/components/decomposition-tree-graph/events.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { TreeGraphData } from '@antv/g6'; -import { ITreeGraph, IG6GraphEvent, INode, NodeConfig, Datum, FetchLoading } from '../../interface'; -import { prefix, MARKER_CLICK } from '../../constants'; -import { DecompositionTreeGraphConfig } from '../decomposition-tree-graph'; -import { createFetchLoading, closeFetchLoading, getChildrenData, setLevelData } from '../../utils'; - -// 展开&折叠事件 -export const bindEvents = (params: { - graph: ITreeGraph; - level?: number; - getChildren?: DecompositionTreeGraphConfig['nodeCfg']['getChildren']; - fetchLoading?: FetchLoading; -}) => { - const { graph, level, getChildren, fetchLoading } = params; - const onClick = async (e: IG6GraphEvent) => { - const item = e.item as INode; - const model = item.getModel(); - if (e.target.get('name')?.startsWith('collapse-icon')) { - const { collapsed, g_currentPath, children = [], g_parentId, g_level, id } = item.getModel(); - let appendChildren = - level && - !(children as Array).length && - getChildrenData(graph.get('eventData').getData(), g_currentPath as string); - - if (getChildren && !(children as Array)?.length && !appendChildren?.length) { - createFetchLoading(model as NodeConfig, fetchLoading); - let appendChildrenData = await getChildren(item.getModel() as NodeConfig); - if (appendChildrenData) { - appendChildrenData = appendChildrenData.map((t, index) => { - return { - [`${prefix}_level`]: (g_level as number) + 1, - [`${prefix}_parentId`]: `${g_parentId}-${id}`, - [`${prefix}_currentPath`]: `${g_currentPath}-${index}`, - ...t, - }; - }); - setLevelData(graph, appendChildrenData, g_currentPath as string); - } - appendChildren = appendChildrenData; - closeFetchLoading(); - } - - if (appendChildren?.length > 0) { - model.children = appendChildren; - graph.updateChild(model as TreeGraphData, model.id); - graph.updateItem(item, { - collapsed: false, - }); - graph.refreshItem(item); - graph.emit(MARKER_CLICK, e, { - type: 'fetch', - collapsed: true, - }); - } else { - graph.updateItem(item, { - collapsed: !collapsed, - }); - graph.layout(); - graph.emit(MARKER_CLICK, e, { - type: 'collapse', - collapsed: !!collapsed, - }); - } - } - }; - graph.on('node:click', (e: IG6GraphEvent) => { - onClick(e); - }); - graph.on('node:touchstart', (e: IG6GraphEvent) => { - onClick(e); - }); -}; diff --git a/packages/graphs/src/components/decomposition-tree-graph/index.tsx b/packages/graphs/src/components/decomposition-tree-graph/index.tsx deleted file mode 100644 index d454f2994..000000000 --- a/packages/graphs/src/components/decomposition-tree-graph/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; -import { defaultFlowGraphAnchorPoints, defaultNodeSize, defaultNodeStyle, defaultStateStyles } from '../../constants'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { CompactBoxLayout } from '../../layout'; -import { - CommonConfig, - IGraph, - IGroup, - NodeCfg, - NodeConfig, - Shape, - ShapeCfg, - G6TreeGraphData, - FetchLoading, -} from '../../interface'; -import { ChartLoading } from '../../utils'; -import { registerIndicatorGeometries } from '../flow-analysis-graph/customItem'; -import { bindEvents } from './events'; - -export interface DecompositionTreeGraphConfig - extends Omit, 'data' | 'nodeCfg'>, - FetchLoading { - data: G6TreeGraphData; - /** 展开层级,默认 100 */ - level?: number; - nodeCfg?: NodeCfg & { - /** 点击展开时异步获取数据 */ - getChildren?: (nodeCfg: NodeConfig) => Promise; - }; -} - -registerIndicatorGeometries(); - -const defaultLayout = { - type: 'compactBox', - direction: 'LR', - getId: (d: any) => { - return d.id; - }, - getHeight: () => { - return 60; - }, - getWidth: () => { - return 16; - }, - getVGap: () => { - return 16; - }, - getHGap: () => { - return 100; - }, -}; - -const defaultProps = { - nodeCfg: { - type: 'indicator-card', - size: defaultNodeSize, - style: defaultNodeStyle, - anchorPoints: defaultFlowGraphAnchorPoints, - padding: 6, - layout: 'bundled', - nodeStateStyles: defaultStateStyles, - label: { - style: (cfg: Shape | ShapeCfg, group: IGroup | IGraph | undefined, type: string | undefined) => { - const styles = { - icon: { - width: 10, - height: 10, - }, - value: { - fill: '#000', - }, - text: { - fill: '#aaa', - }, - }; - return type ? styles[type] : {}; - }, - }, - }, - edgeCfg: { - type: 'cubic-horizontal', - endArrow: { - type: 'vee', - }, - edgeStateStyles: defaultStateStyles, - }, - behaviors: ['zoom-canvas', 'drag-canvas'], - layout: defaultLayout, - animate: true, - autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, - level: 100, -}; - -const DecompositionTreeGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('TreeGraph', rest, { - name: 'DecompositionTreeGraph', - bindEvents, - }); - - return ( - - {loading && } -
- - ); -}; - -export default DecompositionTreeGraph; diff --git a/packages/graphs/src/components/dendrogram/index.tsx b/packages/graphs/src/components/dendrogram/index.tsx new file mode 100644 index 000000000..197f367e0 --- /dev/null +++ b/packages/graphs/src/components/dendrogram/index.tsx @@ -0,0 +1,39 @@ +import type { Graph } from '@antv/g6'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { formatTreeData } from '../../core/utils/data'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS, getDendrogramOptions } from './options'; +import type { DendrogramOptions } from './types'; + +export const Dendrogram: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const options = useMemo(() => { + const { data, defaultExpandLevel, direction = 'horizontal', compact = false, ...restProps } = props; + + return mergeOptions( + COMMON_OPTIONS, + DEFAULT_OPTIONS, + { data: formatTreeData(data, defaultExpandLevel) }, + getDendrogramOptions({ direction, compact }), + restProps, + ); + }, [props]); + + return ( + + {children} + + ); +}); + +export type { DendrogramOptions }; diff --git a/packages/graphs/src/components/dendrogram/options.tsx b/packages/graphs/src/components/dendrogram/options.tsx new file mode 100644 index 000000000..7d90bd6a3 --- /dev/null +++ b/packages/graphs/src/components/dendrogram/options.tsx @@ -0,0 +1,62 @@ +import { isEmpty } from 'lodash'; +import type { GraphOptions } from '../../types'; +import type { DendrogramOptions } from './types'; + +export const DEFAULT_OPTIONS: GraphOptions = { + node: { + type: 'circle', + style: { + labelText: (d) => d.id, + }, + }, +}; + +export const getDendrogramOptions = ({ + direction, + compact, +}: Pick): GraphOptions => { + const isLeafNode = (d) => isEmpty(d.children); + + const layoutType = compact ? 'compact-box' : 'dendrogram'; + if (direction === 'vertical') { + return { + node: { + style: { + labelBackground: true, + labelPlacement: 'right', + labelTransform: (d) => (isLeafNode(d) ? 'rotate(90deg) translate(18px)' : 'translate(18px)'), + ports: [{ placement: 'top' }, { placement: 'bottom' }], + }, + }, + edge: { type: 'cubic-vertical' }, + layout: { type: layoutType, direction: 'TB', nodeSep: 40, rankSep: 140, getVGap: () => 80, getHGap: () => 20 }, + }; + } else if (direction === 'horizontal') { + return { + node: { + style: { + labelBackground: true, + labelPlacement: (d) => (isLeafNode(d) ? 'right' : 'left'), + ports: [{ placement: 'left' }, { placement: 'right' }], + }, + }, + edge: { type: 'cubic-horizontal' }, + layout: { type: layoutType, direction: 'LR', nodeSep: 40, rankSep: 200, getVGap: () => 5, getHGap: () => 100 }, + }; + } else { + return { + node: { style: { labelBackground: true } }, + edge: { type: 'cubic-radial' }, + layout: { + type: layoutType, + direction: 'RL', + radial: true, + nodeSep: 40, + rankSep: 200, + getVGap: () => 30, + getHGap: () => 60, + }, + transforms: (prev) => [...prev, 'place-radial-labels'], + }; + } +}; diff --git a/packages/graphs/src/components/dendrogram/types.ts b/packages/graphs/src/components/dendrogram/types.ts new file mode 100644 index 000000000..6a29677b6 --- /dev/null +++ b/packages/graphs/src/components/dendrogram/types.ts @@ -0,0 +1,26 @@ +import type { GraphData, TreeData } from '@antv/g6'; +import type { GraphOptions } from '../../types'; + +export interface DendrogramOptions extends Omit { + /** + * The data. + */ + data?: GraphData | TreeData; + /** + * The default expand level. If not set, all nodes will be expanded. + */ + defaultExpandLevel?: number; + /** + * The direction of the dendrogram. + * - `'vertical'`: vertical direction (top to bottom). + * - `'horizontal'`: horizontal direction (left to right). + * - `'radial'`: radial direction (clockwise). + * @default 'horizontal' + */ + direction?: 'vertical' | 'horizontal' | 'radial'; + /** + * Whether to compact the layout. + * @default false + */ + compact?: boolean; +} diff --git a/packages/graphs/src/components/file-tree-graph/customItem.ts b/packages/graphs/src/components/file-tree-graph/customItem.ts deleted file mode 100644 index 3d4dd89ef..000000000 --- a/packages/graphs/src/components/file-tree-graph/customItem.ts +++ /dev/null @@ -1,794 +0,0 @@ -import G6, { IGroup, ModelConfig } from '@antv/g6'; -import { CardItems, CardNodeCfg, EdgeCfg, NodeCfg } from '../../interface'; -import { - getCssPadding, - getStyle, - createFetchLoading, - closeFetchLoading, - getMarkerPosition, - getGlobalInstance, - getChildrenData, - pushAsyncEvent, -} from '../../utils'; - -const { Util } = G6; - -// file tree -export const registerFileTreeGeometries = () => { - const defaultLineWidth = 2; - const defaultTextStyle = { - fill: 'rgba(0,0,0,.65)', - textAlign: 'middle', - fontSize: 14, - fontFamily: 'PingFangSC-Regular', - cursor: 'pointer', - textBaseline: 'middle', - }; - const defaultStroke = '#40a9ff'; - const markerSize = 12; - const defaultMarkerCfg = { - width: markerSize, - height: markerSize, - radius: markerSize / 2, - stroke: '#999', - fill: '#fff', - cursor: 'pointer', - r: markerSize / 2, - lineWidth: 1, - }; - G6.registerNode('file-tree-node', { - options: { - style: { - fill: '#e8f7ff', - }, - stateStyles: { - hover: { - fillOpacity: 0.6, - }, - selected: {}, - }, - }, - addLabel(group: IGroup, label: string, x: number, y: number) { - return group.addShape('text', { - attrs: { - text: label, - x: x * 2, - y, - textAlign: 'left', - textBaseline: 'top', - fontFamily: 'PingFangSC-Regular', - }, - cursor: 'pointer', - name: 'name-text-shape', - }); - }, - addCollapse(group, props) { - const { collapsed, markerCfg, model, size } = props; - const { style } = markerCfg; - const [x, y] = [0, size[1]]; - // 子类数量 icon,绘制圆点在节点正下方 - const markerStyle = getStyle(style, model, group, 'collapse'); - const config = { ...defaultMarkerCfg, ...markerStyle }; - const childCountGroup = group.addGroup({ - name: 'collapse-group', - }); - const rectX = x - config.width / 2; - const rectY = y - config.height / 2; - childCountGroup.addShape('rect', { - attrs: { - lineWidth: defaultLineWidth, - x: rectX, - y: rectY, - ...config, - }, - name: 'collapse-icon-circle', - }); - childCountGroup.addShape('path', { - attrs: { - path: collapsed - ? [ - ['M', rectX + config.width / 4, y - config.height / 4], - ['L', rectX + (config.width * 3) / 4, y], - ['L', rectX + config.width / 4, y + config.height / 4], - ] - : [ - ['M', rectX + config.width / 4, y - config.height / 4], - ['L', rectX + config.width / 2, y + config.height / 4], - ['L', rectX + (config.width * 3) / 4, y - config.height / 4], - ], - ...config, - }, - name: 'collapse-icon-arrow', - capture: false, - }); - - if (collapsed) { - childCountGroup.show(); - } else { - childCountGroup.hide(); - } - }, - addMarker(group, props) { - const { markerCfg, model, size, startX = 0, startY = 0 } = props; - const { style, position = 'right', show } = markerCfg; - if (!show) return; - // 子类数量 icon,绘制圆点在节点正下方 - const markerStyle = getStyle(style, model, group); - const config = { ...defaultMarkerCfg, ...markerStyle }; - const { x, y } = getMarkerPosition(position, size); - // 增加子节点 icon - const markerIcon = group.addShape('marker', { - attrs: { - x: x + startX, - y: y + startY, - symbol: G6.Marker.expand, - ...config, - }, - name: 'icon-add-child', - }); - markerIcon.hide(); - return markerIcon; - }, - addHoverBack(group, props) { - const { x, y, width, height, style } = props; - group.addShape('rect', { - attrs: { - x, - y, - width, - height, - radius: 4, - cursor: 'pointer', - ...style, - }, - // capture: false, - name: 'main-shape', - draggable: true, - }); - }, - addName(group, props) { - const { label, x = 0, y, style } = props; - return group.addShape('text', { - attrs: { - text: label, - x, - y, - ...defaultTextStyle, - ...style, - }, - name: 'text-shape', - draggable: true, - }); - }, - addBottomLine(group, props) { - const { x, y, width, stroke, lineWidth } = props; - return group.addShape('path', { - attrs: { - path: [ - ['M', x - 1, y], - ['L', width, y], - ], - stroke, - lineWidth, - }, - name: 'node-path-shape', - }); - }, - draw(model, group) { - const { collapsed, depth, value, markerCfg, children = [] } = model; - const nodeCfg = model.nodeCfg as CardNodeCfg; - const { style, label, padding = 0, customContent, lineStyle } = nodeCfg; - const bottomLineStyle = getStyle(lineStyle, model, group); - const fileName = (value as CardItems).text; - let size = nodeCfg?.size || []; - if (typeof size === 'number') size = [size, size]; - const cardPadding = getCssPadding(padding); - // 是否为根节点 - const rootNode = depth === 0; - // 子节点数量 - const childCount = (children as any[]).length || 0; - - const height = size[1] || 28; - const x = 0; - const y = 0; - - let text; // 名称文本 - if (customContent) { - text = customContent(value as any, group, { - startX: x + cardPadding[3], - startY: height / 2, - width: size[0], - }); - } else { - text = this.addName(group, { - label: fileName, - x: x + cardPadding[3], - y: height / 2, - style: getStyle(label.style, model, group), - }); - } - const textWidth = group.getBBox().width; - const width = Math.max(size[0], textWidth + cardPadding[1] + cardPadding[3]); - if (!size.length) size = [width, height]; - - const cardStyle = getStyle(style, model, group); - - const keyShapeAttrs = { - x, - y, - width, - height, - radius: 12, - ...cardStyle, - }; - const keyShape = group.addShape('rect', { - attrs: keyShapeAttrs, - name: 'root-rect-shape', - }); - - // 底部横线 - !rootNode && - this.addBottomLine(group, { - stroke: defaultStroke, - lineWidth: defaultLineWidth, - x, - width, - y: y + height, - ...bottomLineStyle, - }); - let callbackMarkerCfg = markerCfg; - const graph = getGlobalInstance(model._graphId as string); - if (typeof markerCfg === 'function') { - callbackMarkerCfg = markerCfg( - { - ...model, - children: getChildrenData(graph?.get('eventData').getData(), model.g_currentPath as string), - }, - group, - ); - } - - if (childCount && !rootNode) - this.addCollapse(group, { - collapsed, - markerCfg: callbackMarkerCfg, - model, - size: [width, height], - }); - const markerIcon = this.addMarker(group, { - collapsed, - markerCfg: callbackMarkerCfg, - model, - size: [width, height], - startX: text.getBBox().x - cardPadding[3], - startY: keyShape.getBBox().y, - }); - const bbox = group.getBBox(); - const { minX, minY, maxX, maxY } = bbox; - const backContainer = group.addShape('path', { - attrs: { - path: childCount - ? [ - ['M', minX, minY], - ['L', maxX, minY], - ['L', maxX, maxY], - ['L', minX + 20, maxY], - ['L', minX + 20, maxY + 20], - ['L', minX, maxY + 20], - ['Z'], - ] - : [['M', minX, minY], ['L', maxX, minY], ['L', maxX, maxY], ['L', minX, maxY], ['Z']], - fill: '#fff', - opacity: 0, - }, - draggable: true, - }); - text?.toFront(); - backContainer.toBack(); - if (rootNode) { - pushAsyncEvent(model._graphId as string, () => { - const { width: keyShapeWith, maxX } = keyShape.getBBox(); - markerIcon?.attr({ - x: x + keyShapeWith / 2, - }); - text?.attr({ - x: x + cardPadding[3] - keyShapeWith / 2, - }); - keyShape.attr({ - x: x - keyShapeWith / 2, - }); - }); - } - - return keyShape; - }, - setState(name, value, node) { - if (['closest', 'selected', 'hover'].includes(name)) { - const model = node.getModel(); - const { edgeCfg, nodeCfg, depth, markerCfg } = model; - if (depth === 0) return; - const { edgeStateStyles, style: edgeStyle } = edgeCfg as EdgeCfg; - const { - nodeStateStyles, - label: { style: lableStyle }, - style, - lineStyle, - } = nodeCfg as NodeCfg; - // closest 使用 hover 样式 - const _name = name === 'closest' ? 'hover' : name; - const group = node.getContainer(); - const rootShape = group.find((child) => child.get('name') === 'root-rect-shape'); - const markerShapes = []; - const getMarkers = (shapes) => { - shapes.forEach((shape) => { - const name = shape.get('name'); - if (name?.startsWith('collapse-icon') || name === 'icon-add-child') { - markerShapes.push(shape); - } - if (shape.getChildren?.().length) getMarkers(shape.getChildren()); - }); - }; - getMarkers(group.getChildren()); - const textShape = group.find((child) => child.get('name') === 'text-shape'); - const pathShape = group.find((child) => child.get('name') === 'node-path-shape'); - const selected = node.hasState('selected'); - if (value) { - const baseStyle = nodeStateStyles[_name]; - rootShape?.attr({ - ...(nodeStateStyles[_name]?.rect || baseStyle), - }); - markerShapes.forEach((markerShape) => { - markerShape?.attr({ - ...(nodeStateStyles[_name]?.marker || baseStyle), - }); - }); - textShape?.attr({ - ...(nodeStateStyles[_name]?.text || baseStyle), - }); - pathShape?.attr({ - ...edgeStateStyles[_name], - }); - } else { - if (selected) { - const baseStyle = nodeStateStyles['selected']; - rootShape?.attr({ - ...(nodeStateStyles['selected']?.rect || baseStyle), - }); - markerShapes.forEach((markerShape) => { - markerShape?.attr({ - ...(nodeStateStyles['selected']?.marker || baseStyle), - }); - }); - textShape?.attr({ - ...(nodeStateStyles['selected']?.text || baseStyle), - }); - pathShape?.attr({ - ...edgeStateStyles['selected'], - }); - } else { - const cardStyle = getStyle(style, model, node); - const textStyle = getStyle(lableStyle, model, node); - const pathStyle = getStyle(lineStyle, model, node); - const markStyle = getStyle(markerCfg, model, node); - rootShape?.attr(cardStyle); - textShape?.attr(textStyle); - pathShape?.attr(pathStyle); - markerShapes.forEach((markerShape) => { - markerShape?.attr(markStyle?.style); - }); - } - } - } - }, - }); - - G6.registerEdge( - 'file-tree-edge', - { - getControlPoints: (cfg) => { - const startPoint = cfg.startPoint; - const endPoint = cfg.endPoint; - return [ - startPoint, - { - x: startPoint.x, - y: endPoint.y, - }, - endPoint, - ]; - }, - update: undefined, - }, - 'polyline', - ); -}; - -export const registerFileTreeBehaviors = (): void => { - G6.registerBehavior('wheel-scroll', { - getDefaultCfg() { - return { - direction: 'y', - zoomKey: 'ctrl', - sensitivity: 3, - // wheel-scroll 可滚动的扩展范围,默认为 0,即最多可以滚动一屏的位置 - // 当设置的值大于 0 时,即滚动可以超过一屏 - // 当设置的值小于 0 时,相当于缩小了可滚动范围 - // 具体实例可参考:https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*IFfoS67_HssAAAAAAAAAAAAAARQnAQ - scalableRange: -64, - }; - }, - - getEvents() { - if (!this.zoomKey || ['shift', 'ctrl', 'alt', 'control'].indexOf(this.zoomKey) === -1) this.zoomKey = 'ctrl'; - return { - wheel: 'onWheel', - }; - }, - - onWheel(ev) { - const graph = this.graph; - let keyDown = ev[`${this.zoomKey}Key`]; - if (this.zoomKey === 'control') keyDown = ev.ctrlKey; - if (keyDown) { - const sensitivity = this.get('sensitivity'); - const canvas = graph.get('canvas'); - const point = canvas.getPointByClient(ev.clientX, ev.clientY); - let ratio = graph.getZoom(); - if (ev.wheelDelta > 0) { - ratio *= 1 + 0.01 * sensitivity; - } else { - ratio *= 1 - 0.01 * sensitivity; - } - graph.zoomTo(ratio, { - x: point.x, - y: point.y, - }); - graph.emit('wheelzoom', ev); - } else { - let dx = ev.deltaX || ev.movementX; - let dy = ev.deltaY || ev.movementY; - if (!dy && navigator.userAgent.indexOf('Firefox') > -1) dy = (-ev.wheelDelta * 125) / 3; - - const width = this.graph.get('width'); - const height = this.graph.get('height'); - const graphCanvasBBox = this.graph.get('group').getCanvasBBox(); - - let expandWidth = this.scalableRange; - let expandHeight = this.scalableRange; - // 若 scalableRange 是 0~1 的小数,则作为比例考虑 - if (expandWidth < 1 && expandWidth > -1) { - expandWidth = width * expandWidth; - expandHeight = height * expandHeight; - } - - const { minX, maxX, minY, maxY } = graphCanvasBBox; - - if (dx > 0) { - if (maxX < -expandWidth) { - dx = 0; - } else if (maxX - dx < -expandWidth) { - dx = maxX + expandWidth; - } - } else if (dx < 0) { - if (minX > width + expandWidth) { - dx = 0; - } else if (minX - dx > width + expandWidth) { - dx = minX - (width + expandWidth); - } - } - - if (dy > 0) { - if (maxY < -expandHeight) { - dy = 0; - } else if (maxY - dy < -expandHeight) { - dy = maxY + expandHeight; - } - } else if (dy < 0) { - if (minY > height + expandHeight) { - dy = 0; - } else if (minY - dy > height + expandHeight) { - dy = minY - (height + expandHeight); - } - } - - if (this.get('direction') === 'x') { - dy = 0; - } else if (this.get('direction') === 'y') { - dx = 0; - } - - graph.translate(-dx, -dy); - } - ev.preventDefault(); - }, - }); - G6.registerBehavior('drag-branch', { - getEvents: function getEvents() { - return { - 'node:dragstart': 'dragstart', - 'node:drag': 'drag', - 'node:dragend': 'dragend', - 'node:dragenter': 'dragenter', - 'node:dragleave': 'dragleave', - }; - }, - dragstart: function dragstart(e) { - this.set('foundNode', undefined); - this.origin = { - x: e.x, - y: e.y, - }; - this.target = e.item; - const graph = this.get('graph'); - // 未配置 shouldBegin 时 默认为 true - if (this.shouldBegin && !this.shouldBegin(graph.findDataById(this.target?.getID()))) { - this.began = false; - return; - } - this.began = true; - }, - dragenter: function dragenter(e) { - if (!this.began) { - return; - } - const graph = this.get('graph'); - const foundNode = e.item; - if (foundNode) graph.setItemState(foundNode, 'closest', true); - this.set('foundNode', foundNode); - }, - dragleave: function dragleave(e) { - if (!this.began) { - return; - } - const graph = this.get('graph'); - const foundNode = this.get('foundNode'); - if (foundNode) graph.setItemState(foundNode, 'closest', false); - this.set('foundNode', foundNode); - }, - drag: function drag(e) { - if (!this.began) { - return; - } - // move the delegate - this.updateDelegate(e); - }, - dragend: function dragend(e) { - const graph = this.get('graph'); - const foundNode = this.get('foundNode'); - if (foundNode) { - graph.setItemState(foundNode, 'closest', false); - } - if (!this.began) { - return; - } - this.began = false; - const { item } = e; - const id = item.getID(); - const data = graph.findDataById(id); - - // remove delegate - if (this.delegateRect) { - this.delegateRect.remove(); - this.delegateRect = null; - } - - if (!foundNode) { - graph.emit('afterdragbranch', { - success: false, - message: 'Failed. No node close to the dragged node.', - branch: data, - }); - return; - } - - const foundNodeId = foundNode.getID(); - - let oriParentData; - Util.traverseTree(graph.get('data'), (d) => { - if (oriParentData) return false; - if (d.children?.filter((child) => child.id === id)?.length) { - oriParentData = d; - } - return true; - }); - - // 未配置 shouldEnd,则默认为 true - if (this.shouldEnd && !this.shouldEnd(data, graph.findDataById(foundNodeId), oriParentData)) { - return; - } - - // if the foundNode is a descent of the dragged node, return - let isDescent = false; - - Util.traverseTree(data, (d) => { - if (d.id === foundNodeId) isDescent = true; - }); - if (isDescent) { - const newParentData = graph.findDataById(foundNodeId); - graph.emit('afterdragbranch', { - success: false, - message: 'Failed. The target node is a descendant of the dragged node.', - newParentData, - branch: data, - }); - return; - } - - const newParentData = graph.findDataById(foundNodeId); - // 触发外部对数据的改变 - graph.emit('afterdragbranch', { success: true, message: 'Success.', newParentData, oriParentData, branch: data }); - graph.removeChild(data.id); - setTimeout(() => { - let newChildren = newParentData.children; - if (newChildren) newChildren.push(data); - else newChildren = [data]; - // 更新正在被操作的子树颜色 - Util.traverseTree(data, (d) => { - d.branchColor = newParentData.branchColor; - }); - graph.updateChildren(newChildren, newParentData.id); - }, 600); - }, - updateDelegate(e) { - const { graph } = this; - if (!this.delegateRect) { - // 拖动多个 - const parent = graph.get('group'); - const attrs = { - fill: '#F3F9FF', - fillOpacity: 0.5, - stroke: '#1890FF', - strokeOpacity: 0.9, - lineDash: [5, 5], - }; - - const { x: cx, y: cy, width, height, minX, minY } = this.calculationGroupPosition(e); - this.originPoint = { x: cx, y: cy, width, height, minX, minY }; - // model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标 - this.delegateRect = parent.addShape('rect', { - attrs: { - width, - height, - x: cx, - y: cy, - ...attrs, - }, - name: 'rect-delegate-shape', - }); - this.delegateRect.set('capture', false); - } else { - const clientX = e.x - this.origin.x + this.originPoint.minX; - const clientY = e.y - this.origin.y + this.originPoint.minY; - this.delegateRect.attr({ - x: clientX, - y: clientY, - }); - } - }, - calculationGroupPosition(evt) { - let node = this.target; - if (!node) { - node = evt.item; - } - - const bbox = node.getBBox(); - const { minX, minY, maxX, maxY } = bbox; - - return { - x: Math.floor(minX), - y: Math.floor(minY), - width: Math.ceil(maxX) - Math.floor(minX), - height: Math.ceil(maxY) - Math.floor(minY), - minX, - minY, - }; - }, - }); - - G6.registerBehavior('click-node', { - getEvents() { - return { - 'node:click': 'onNodeClick', - 'canvas:click': 'onCanvasClick', - }; - }, - async onNodeClick(e) { - const { item } = e; - const shapeName = e.target.get('name'); - const model = item.getModel(); - const { graph } = this; - // 点击增加节点 - if (shapeName === 'icon-add-child') { - const data = graph.findDataById(item.getID()); - if (!data.children) data.children = []; - this.graph.emit('add:children', e); - const { id, value } = e.item.getModel(); - const { getChildren, fetchLoading } = graph.get('extraPlugin'); - if (getChildren) { - createFetchLoading(model, fetchLoading); - const appendChildren = await getChildren({ - id, - value, - }); - data.children.push(...appendChildren); - // @ts-ignore - graph.updateChildren(data.children, data.id); - closeFetchLoading(); - } - return; - } - - // 选中节点 - graph.getNodes().forEach((node) => { - graph.setItemState(node, 'selected', false); - }); - graph.setItemState(item, 'selected', true); - - return; - }, - onCanvasClick(e) { - this.graph.getNodes().forEach((node) => { - this.graph.setItemState(node, 'selected', false); - }); - }, - }); - - G6.registerBehavior('hover-node', { - getEvents() { - return { - 'node:mouseover': 'onNodeMouseOver', - 'node:mouseleave': 'onNodeMouseLeave', - 'node:mouseenter': 'onNodeMouseEnter', - }; - }, - onNodeMouseEnter(e) { - const { item } = e; - if (!item || item.get('destroyed')) return; - item.toFront(); - const model = item.getModel(); - const { collapsed, depth } = model; - const rootNode = depth === 0 || model.isRoot; - const group = item.getContainer(); - - if (rootNode) return; - - // 控制子节点个数标记 - if (!collapsed) { - group.find((e) => e.get('name') === 'collapse-group')?.show(); - } - this.graph.setItemState(item, 'hover', true); - }, - onNodeMouseOver(e) { - this.graph.emit('tooltip: show', e); - // expand 状态下,若 hover 到子节点个数标记,填充背景+显示收起 icon - const { item } = e; - const group = item.getContainer(); - const model = item.getModel(); - if (!model.collapsed) { - const childCountGroup = group.find((e) => e.get('name') === 'collapse-group'); - if (childCountGroup) { - childCountGroup.show(); - childCountGroup.find((e) => e.get('name') === 'collapse-icon-arrow')?.show(); - } - } - - group.find((e) => e.get('name') === 'icon-add-child')?.show(); - }, - onNodeMouseLeave(e) { - const { item } = e; - const model = item.getModel(); - const group = item.getContainer(); - const { collapsed } = model; - if (!collapsed) { - const childCountGroup = group.find((e) => e.get('name') === 'collapse-group'); - if (childCountGroup) { - childCountGroup.hide(); - childCountGroup.find((e) => e.get('name') === 'collapse-icon-arrow')?.hide(); - } - } - - group.find((e) => e.get('name') === 'icon-add-child')?.hide(); - this.graph.emit('tooltip: hide', e); - }, - }); -}; diff --git a/packages/graphs/src/components/file-tree-graph/events.ts b/packages/graphs/src/components/file-tree-graph/events.ts deleted file mode 100644 index 062e21711..000000000 --- a/packages/graphs/src/components/file-tree-graph/events.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { TreeGraphData } from '@antv/g6'; -import { ITreeGraph, IG6GraphEvent, INode, NodeConfig, Datum, FetchLoading } from '../../interface'; -import { prefix, MARKER_CLICK } from '../../constants'; -import { DecompositionTreeGraphConfig } from '../decomposition-tree-graph'; -import { createFetchLoading, closeFetchLoading, getChildrenData, setLevelData } from '../../utils'; - -// 展开&折叠事件 -export const bindEvents = (params: { - graph: ITreeGraph; - level?: number; - getChildren?: DecompositionTreeGraphConfig['nodeCfg']['getChildren']; - fetchLoading?: FetchLoading; -}) => { - const { graph, level, getChildren, fetchLoading } = params; - const onClick = async (e: IG6GraphEvent) => { - const item = e.item as INode; - const model = item.getModel(); - if (e.target.get('name')?.startsWith('collapse-icon')) { - const { collapsed, g_currentPath, children = [], g_parentId, g_level, id } = item.getModel(); - let appendChildren = - level && - !(children as Array).length && - getChildrenData(graph.get('eventData').getData(), g_currentPath as string); - - if (getChildren && !(children as Array)?.length && !appendChildren?.length) { - createFetchLoading(model as NodeConfig, fetchLoading); - let appendChildrenData = await getChildren(item.getModel() as NodeConfig); - if (appendChildrenData) { - appendChildrenData = appendChildrenData.map((t, index) => { - return { - [`${prefix}_level`]: (g_level as number) + 1, - [`${prefix}_parentId`]: `${g_parentId}-${id}`, - [`${prefix}_currentPath`]: `${g_currentPath}-${index}`, - ...t, - }; - }); - setLevelData(graph, appendChildrenData, g_currentPath as string); - } - appendChildren = appendChildrenData; - closeFetchLoading(); - } - - if (appendChildren?.length > 0) { - model.children = appendChildren; - graph.updateChild(model as TreeGraphData, model.id); - graph.updateItem(item, { - collapsed: false, - }); - graph.refreshItem(item); - graph.emit(MARKER_CLICK, e, { - type: 'fetch', - collapsed: true, - }); - } else { - graph.updateItem(item, { - collapsed: !collapsed, - }); - graph.layout(); - graph.emit(MARKER_CLICK, e, { - type: 'collapse', - collapsed: !!collapsed, - }); - } - } - }; - graph.on('node:click', (e: IG6GraphEvent) => { - onClick(e); - }); - graph.on('node:touchstart', (e: IG6GraphEvent) => { - onClick(e); - }); -}; diff --git a/packages/graphs/src/components/file-tree-graph/index.tsx b/packages/graphs/src/components/file-tree-graph/index.tsx deleted file mode 100644 index 798127435..000000000 --- a/packages/graphs/src/components/file-tree-graph/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React from 'react'; -import { stateColor } from '../../constants'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { CompactBoxLayout } from '../../layout'; -import { - CommonConfig, - NodeCfg, - NodeConfig, - G6TreeGraphData, - FetchLoading, - MarkerCfg, - IGraph, - IGroup, - CardNodeCfg, - IShapeStyle, -} from '../../interface'; -import { ChartLoading } from '../../utils'; -import { bindEvents } from './events'; -import { registerFileTreeGeometries, registerFileTreeBehaviors } from './customItem'; - -export type FileTreeMarkerCfg = Pick; -export interface FileTreeGraphConfig - extends Omit, 'data' | 'nodeCfg' | 'markerCfg'>, - FetchLoading { - data: G6TreeGraphData; - /** 展开层级,默认 100 */ - level?: number; - markerCfg?: FileTreeMarkerCfg | ((cfg: CardNodeCfg, graph: IGraph | IGroup) => FileTreeMarkerCfg); - nodeCfg?: NodeCfg & { - /** 点击展开时异步获取数据 */ - getChildren?: (nodeCfg: NodeConfig) => Promise; - /** 装饰线 */ - lineStyle?: (nodeCfg: NodeConfig) => IShapeStyle; - }; -} - -registerFileTreeGeometries(); -registerFileTreeBehaviors(); -const PEM = 18; -const defaultLayout = { - type: 'indented', - direction: 'LR', - isHorizontal: true, - indent: 40, - getHeight: (d) => { - if (d.isRoot) { - return 30; - } - if (d.collapsed && d.children?.length) { - return 36; - } - return 22; - }, - getVGap: () => { - return 10; - }, - getWidth: (d) => { - const label = d.value?.text || ' '; - return d.width || label.split('').length * PEM; // FIXME DO NOT get width like this - }, -}; - -const defaultProps = { - nodeCfg: { - type: 'file-tree-node', - size: [120, 28], - anchorPoints: [ - [0, 1], - [1, 0.5], - ], - padding: [6, 12], - nodeStateStyles: { - hover: { - rect: { - fill: '#eee', - }, - text: { - fill: '#38404C', - }, - marker: { - stroke: stateColor, - }, - }, - selected: { - rect: { - fill: '#EFF2FF', - }, - text: { - fill: '#2F54EB', - }, - }, - }, - label: { - style: (cfg) => { - if (cfg.depth === 0) return { fill: '#fff' }; - return { - fill: 'rgba(0,0,0,.65)', - }; - }, - }, - style: (model) => ({ - fill: model.depth === 0 ? '#525964' : 'transparent', - }), - lineStyle: { - lineWidth: 2, - stroke: '#ccc', - }, - }, - edgeCfg: { - type: 'file-tree-edge', - style: { - lineWidth: 2, - radius: 16, - }, - edgeStateStyles: { - hover: { - stroke: stateColor, - }, - selected: { - stroke: stateColor, - }, - }, - }, - behaviors: ['drag-canvas', 'wheel-scroll', 'hover-node', 'click-node', 'drag-branch'], - layout: defaultLayout, - animate: true, - // autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, - markerCfg: { - show: true, - }, -}; - -const FileTreeGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('TreeGraph', rest, { - name: 'FileTreeGraph', - bindEvents, - }); - - return ( - - {loading && } -
- - ); -}; - -export default FileTreeGraph; diff --git a/packages/graphs/src/components/fishbone/index.tsx b/packages/graphs/src/components/fishbone/index.tsx new file mode 100644 index 000000000..be22f32f8 --- /dev/null +++ b/packages/graphs/src/components/fishbone/index.tsx @@ -0,0 +1,41 @@ +import type { Graph } from '@antv/g6'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { formatTreeData } from '../../core/utils/data'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS, getFishboneOptions } from './options'; +import type { FishboneOptions } from './types'; + +export const Fishbone: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const { data, defaultExpandLevel, type = 'cause', labelField, ...restProps } = props; + + const options = useMemo( + () => + mergeOptions( + COMMON_OPTIONS, + DEFAULT_OPTIONS, + { data: formatTreeData(data) }, + getFishboneOptions({ type, labelField }), + restProps, + ), + [props], + ); + + return ( + + {children} + + ); +}); + +export type { FishboneOptions }; diff --git a/packages/graphs/src/components/fishbone/options.tsx b/packages/graphs/src/components/fishbone/options.tsx new file mode 100644 index 000000000..653dea34c --- /dev/null +++ b/packages/graphs/src/components/fishbone/options.tsx @@ -0,0 +1,92 @@ +import type { ID, NodeData, SingleLayoutOptions, Size } from '@antv/g6'; +import { get } from 'lodash'; +import { formatLabel } from '../../core/utils/label'; +import { measureTextSize } from '../../core/utils/measure-text'; +import type { GraphOptions } from '../../types'; +import type { FishboneOptions } from './types'; + +export const DEFAULT_OPTIONS: GraphOptions = { + node: { + style: { + size: 10, + labelPlacement: 'center', + }, + }, + edge: { + type: 'polyline', + }, + layout: { + type: 'fishbone', + hGap: 40, + vGap: 60, + }, + animation: false, +}; + +const FONT_FAMILY = 'system-ui, sans-serif'; + +const getNodeSize = (id: ID, depth: number): Size => { + if (depth === 0) return measureTextSize(id, [80, 48], { fontSize: 24, fontWeight: 600, fontFamily: FONT_FAMILY }); + if (depth === 1) return measureTextSize(id, [80, 30], { fontSize: 18, fontFamily: FONT_FAMILY }); + return [2, 30]; +}; + +const getNodeFill = (node: NodeData): string => { + const depth = node.depth as number; + if (depth === 0) return '#EFF0F0'; + if (depth === 1) return (node.style?.color as string) || '#EFF0F0'; + return 'transparent'; +}; + +export function getFishboneOptions({ type, labelField }: Pick): GraphOptions { + const options: GraphOptions = { + node: { + type: 'rect', + style: { + fill: (d) => getNodeFill(d), + labelFill: (d) => (d.depth === 1 ? '#fff' : '#262626'), + labelFillOpacity: 1, + labelFontSize: (d) => (d.depth === 0 ? 24 : d.depth === 1 ? 18 : 16), + labelFontWeight: (d) => (d.depth === 0 ? 600 : 400), + labelLineHeight: (d) => (d.depth === 0 ? 26 : d.depth === 1 ? 20 : 18), + labelText: (d) => formatLabel(d, labelField), + radius: 8, + size: (d) => getNodeSize(d.id, d.depth!), + }, + }, + edge: { + type: 'polyline', + style: { + lineWidth: 3, + stroke: function (data) { + const target = this.getNodeData(data.target); + return get(target, 'style.color', '#99ADD1') as string; + }, + }, + }, + transforms: (prev) => [ + ...prev, + { + type: 'assign-color-by-branch', + key: 'assign-color-by-branch', + }, + { + type: 'arrange-edge-z-index', + key: 'arrange-edge-z-index', + }, + ], + }; + + options.layout ||= {} as SingleLayoutOptions; + if (type === 'decision') { + // @ts-ignore + options.node.style.labelPlacement = (d) => (d.depth === 0 || d.depth === 1 ? 'center' : 'right'); + Object.assign(options.layout!, { direction: 'LR' }); + } else if (type === 'cause') { + // @ts-ignore + options.node.style.labelPlacement = (d) => (d.depth === 0 || d.depth === 1 ? 'center' : 'left'); + Object.assign(options.layout!, { direction: 'RL' }); + } + + return options; +} diff --git a/packages/graphs/src/components/fishbone/types.ts b/packages/graphs/src/components/fishbone/types.ts new file mode 100644 index 000000000..0501bcb8f --- /dev/null +++ b/packages/graphs/src/components/fishbone/types.ts @@ -0,0 +1,27 @@ +import type { GraphData, NodeData, TreeData } from '@antv/g6'; +import type { GraphOptions } from '../../types'; + +export interface FishboneOptions extends Omit { + /** + * The data. + */ + data?: GraphData | TreeData; + /** + * The default expand level. If not set, all nodes will be expanded. + */ + defaultExpandLevel?: number; + /** + * The type of the fishbone diagram. + * - `'decision'`: direction from left to right. + * - `'cause'`: direction from right to left. + * @default 'cause' + */ + type?: 'decision' | 'cause'; + /** + * Selects a field from the data to use as the label for the node. + * If a string is provided, it will select the field as `data[labelField]`. + * If a function is provided, it will call the function with the data and use the returned value. + * @default (data) => data.id + */ + labelField?: string | ((node: NodeData) => string); +} diff --git a/packages/graphs/src/components/flow-analysis-graph/customItem.ts b/packages/graphs/src/components/flow-analysis-graph/customItem.ts deleted file mode 100644 index 8475a2b8e..000000000 --- a/packages/graphs/src/components/flow-analysis-graph/customItem.ts +++ /dev/null @@ -1,698 +0,0 @@ -import G6, { IGroup, Node } from '@antv/g6'; -import { clone, deepMix, each, isBoolean, isPlainObject, mix } from '@antv/util'; -import { defaultCardStyle, defaultLabelStyle, defaultLineLabelStyle, defaultMargin, prefix } from '../../constants'; -import { CardItems, CardNodeCfg, EdgeCfg, EdgeConfig, IPoint, IShape, CardItem } from '../../interface'; -import { - cloneBesidesImg, - createMarker, - getArrowCfg, - getCssPadding, - getStatusBBox, - getStatusCfg, - getStyle, - setEllipsis, - getChildrenData, -} from '../../utils'; -import { getGlobalInstance } from '../../utils/global'; - -const getPathInfo = ( - cfg: EdgeConfig< - | string - | { - text?: string; - subText?: string; - } - >, -) => { - const { edgeCfg } = cfg; - const startPoint = cfg.startPoint as IPoint; - const endPoint = cfg.endPoint as IPoint; - const { x: startX, y: startY } = startPoint; - const { x: endX, y: endY } = endPoint; - const yDiff = endY - startY; - const useControlPoint = Math.abs(yDiff) > 0; - let line1EndPoint: IPoint; - let line2StartPoint: IPoint; - let controlPoint: IPoint; - let path: Array>; - if (Math.abs(yDiff) <= 5) { - line2StartPoint = { - x: startX + 20, - y: endY, - }; - path = [ - ['M', startX, startY], - ['L', endX, endY], - ]; - } else { - const slope = useControlPoint ? Math.min(500 / Math.abs(yDiff), 20) : 0; - const cpOffset = slope > 15 ? 0 : 16; - const offset = yDiff < 0 ? cpOffset : -cpOffset; - - line1EndPoint = { - x: startX + slope, - y: endY + offset, - }; - line2StartPoint = { - x: line1EndPoint.x + cpOffset, - y: endY, - }; - // 控制点坐标 - controlPoint = { - x: ((line1EndPoint.x - startX) * (endY - startY)) / (line1EndPoint.y - startY) + startX, - y: endY, - }; - path = [ - ['M', startX, startY], - ['L', line1EndPoint.x, line1EndPoint.y], - ['Q', controlPoint.x, controlPoint.y, line2StartPoint.x, line2StartPoint.y], - ['L', endX, endY], - ]; - } - - const { startArrow: startArrowCfg, endArrow: endArrowCfg } = edgeCfg as EdgeCfg; - const startArrow = getArrowCfg(startArrowCfg, cfg); - const endArrow = getArrowCfg(endArrowCfg, cfg); - - return { - startArrow, - endArrow, - path, - line2StartPoint, - endY, - }; -}; - -const getPathText = (value: EdgeCfg['type']) => { - let text; - let subText; - if (value instanceof Object) { - text = value.text; - subText = value.subText; - } else { - text = value; - } - return { text, subText }; -}; - -// 通用指标卡 -export const registerIndicatorGeometries = () => { - const defaultTitleLabelStyle = { - fill: '#fff', - fontSize: 12, - }; - const defaultTitleRectStyle = { - fill: '#40a9ff', - radius: [2, 2, 0, 0], - }; - const defaultIconStyle = { - width: 12, - height: 12, - }; - const ARROWS = ['startArrow', 'endArrow']; - const SHAPE_DEFAULT_ATTRS = { - lineWidth: 1, - stroke: undefined, - fill: undefined, - lineAppendWidth: 1, - opacity: undefined, - strokeOpacity: undefined, - fillOpacity: undefined, - x: 0, - y: 0, - r: 10, - width: 20, - height: 20, - shadowColor: undefined, - shadowBlur: 0, - shadowOffsetX: 0, - shadowOffsetY: 0, - }; - const PATH_SHAPE_DEFAULT_ATTRS = { - lineWidth: 1, - stroke: '#000', - lineDash: undefined, - startArrow: false, - endArrow: false, - opacity: undefined, - strokeOpacity: undefined, - fillOpacity: undefined, - shadowColor: undefined, - shadowBlur: 0, - shadowOffsetX: 0, - shadowOffsetY: 0, - }; - const SHAPES_DEFAULT_ATTRS = { - edge: PATH_SHAPE_DEFAULT_ATTRS, - node: SHAPE_DEFAULT_ATTRS, - }; - // 注册节点 - G6.registerNode( - 'indicator-card', - { - // @ts-ignore - draw: (cfg: CardNodeCfg | undefined = {}, group: IGroup | undefined) => { - const { value = {}, nodeCfg, markerCfg, _draggable: draggable } = cfg; - const { - title: titleCfg, - items: itemsCfg, - label = {}, - style, - padding = 0, - badge, - percent, - autoWidth, - customContent, - } = nodeCfg as CardNodeCfg; - const appendPadding = getStatusBBox(badge); - const { style: labelStyle } = label; - const cardPadding = getCssPadding(padding); - const paddingArray = cardPadding.map((item: number, index: number) => item + appendPadding[index]); - const { style: titleStyle, containerStyle: titleContainerStyle, autoEllipsis = true } = titleCfg ?? {}; - const { - style: itemStyle, - containerStyle: itemContainerStyle, - layout, - itemSpacing = 4, - sort, - padding: itemPadding = [6, 0, 0], - } = itemsCfg ?? {}; - const itemPaddingArray = getCssPadding(itemPadding); - const { title, items, percent: percentValue } = value as CardItem; - let size = cfg?.size || [100, 30]; - if (typeof size === 'number') size = [size, size]; - let height = 0; // 统计容器总高度,动态设置 - const shapeWidth = size[0]; - const contentWidth = shapeWidth - paddingArray[1] - paddingArray[3]; - // card box - const cardStyle = getStyle(style, cfg, group); - const shape = group!.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: size[0], - height: size[1], - ...defaultCardStyle, - ...cardStyle, - }, - name: 'main-box', - draggable, - }); - - // node title - let titleTextShape; - let itemShape; - let titleShape; - if (title) { - // title rect - titleShape = group!.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: size[0], - height: 0, - ...defaultTitleRectStyle, - ...getStyle(titleContainerStyle, cfg, group), - }, - name: 'title-rect', - draggable, - }); - const textStyle = { - ...defaultTitleLabelStyle, - ...getStyle(titleStyle, cfg, group), - }; - titleTextShape = group!.addShape('text', { - attrs: { - x: paddingArray[3], - y: paddingArray[0], - textBaseline: 'top', - text: autoEllipsis && !autoWidth ? setEllipsis(title, textStyle, contentWidth) : title, - ...textStyle, - }, - name: 'title', - draggable, - }); - const { height: titleHeight } = titleTextShape ? titleTextShape.getBBox() : { height: size[1] / 2 }; - - titleShape?.attr('height', titleHeight + paddingArray[0] + paddingArray[2]); - height += titleShape.getBBox().height; - } - - if (items) { - if (!titleShape) { - height += paddingArray[0]; - } - itemShape = group!.addShape('rect', { - attrs: { - x: paddingArray[3], - y: height, - width: contentWidth, - height: 0, - ...getStyle(itemContainerStyle, cfg, group), - }, - name: 'item-box', - draggable, - }); - height += itemPaddingArray[0]; - const itemContentWidth = contentWidth - itemPaddingArray[1] - itemPaddingArray[3]; - const isArray = Array.isArray(items); - const createRowItems = ( - item: CardItems, - contentWidth: number, - startX: number, - index: number | string = 0, - ): number[] => { - const rowHeight: number[] = []; - let valueShapeWidth = 0; - const keys = sort ? Object.keys(item) : ['text', 'value', 'icon']; - keys.forEach((key: string, keyIndex: number) => { - let x; - const isIcon = key.startsWith('icon'); - // sort 直接均分,简单化 - if (sort || layout === 'flex') { - x = (keyIndex * contentWidth) / keys.length; - } else if (layout === 'follow') { - x = valueShapeWidth; - } else { - // layout === 'bundled' - // 直接均分,icon 紧随 value - x = key === 'text' ? 0 : contentWidth / 2; - x += isIcon ? valueShapeWidth : 0; - } - - const keyShape = group!.addShape(isIcon ? 'image' : 'text', { - attrs: { - textBaseline: 'top', - x: startX + x, - y: height, - text: item[key], - img: item[key], - ...(isIcon ? defaultIconStyle : defaultLabelStyle), - ...getStyle(itemStyle || labelStyle, cfg, group, key), - }, - name: `${key}-${index}-${keyIndex}`, - draggable, - }); - if (key === 'value' || layout === 'follow') { - valueShapeWidth += keyShape.getBBox().width; - valueShapeWidth += layout === 'follow' ? itemSpacing : 0; - } - rowHeight.push(keyShape.getBBox().height); - }); - return rowHeight; - }; - const createItems = (item: CardItems, index: number = 0) => { - const itemsHeight: number[] = []; - if (customContent) { - itemsHeight.push( - customContent(item, group, { - startX: paddingArray[3] + itemPaddingArray[3], - startY: height, - width: itemContentWidth, - }) ?? 0, - ); - } else { - itemsHeight.push(...createRowItems(item, itemContentWidth, paddingArray[3] + itemPaddingArray[3], index)); - } - height += Math.max(...itemsHeight); - if (isArray && index !== items.length - 1) { - height += defaultMargin; - } - }; - - if (Array.isArray(items)) { - items.forEach((item, index) => { - createItems(item, index); - }); - } else { - createItems(items); - } - } - - const titleHeight = titleShape?.getBBox().height || 0; - itemShape?.attr('height', Math.max(height - titleHeight + itemPaddingArray[2], size[1])); - const itemHeight = itemShape?.getBBox().height || 0; - const shapeHeight = items - ? (titleHeight || paddingArray[0]) + itemHeight + paddingArray[2] - : titleHeight + itemHeight; - shape?.attr('height', shapeHeight); - let outerMaxX = shapeWidth; - if (autoWidth) { - const shapeMaxX = Math.max.apply( - null, - group?.getChildren()?.map((childrenShape) => { - return childrenShape.getBBox().maxX || 0; - }) as number[], - ); - outerMaxX = Math.max(shapeWidth, shapeMaxX + paddingArray[1]); - titleShape?.attr('width', outerMaxX); - shape?.attr('width', outerMaxX); - itemShape?.attr('width', shapeMaxX - paddingArray[1]); - } - - if (badge) { - const statusConfig = getStatusCfg(badge, [size[0], shapeHeight]); - group!.addShape('rect', { - attrs: { - fill: '#40a9ff', - ...statusConfig, - ...getStyle(badge.style, cfg, group), - }, - name: 'status-rect', - draggable, - }); - } - if (percent && percentValue > 0) { - const { - size: percentSize = 4, - position = 'bottom', - style: percentStyle = { - fill: '#40a9ff', - }, - backgroundStyle = { - fill: 'rgba(0,0,0,.1)', - radius: [0, 0, 2, 2], - }, - } = percent; - const statusConfig = getStatusCfg( - { - position, - size: [outerMaxX, percentSize], - }, - [outerMaxX, shapeHeight], - ); - - group!.addShape('rect', { - attrs: { - ...statusConfig, - ...getStyle(backgroundStyle, cfg, group), - }, - name: 'percent-rect-background', - draggable, - }); - group!.addShape('rect', { - attrs: { - fill: '#40a9ff', - ...statusConfig, - width: Math.min(1, percentValue) * statusConfig.width, - ...getStyle(percentStyle, cfg, group), - }, - name: 'percent-rect', - draggable, - }); - } - // collapse marker - if (markerCfg) { - const graph = getGlobalInstance(cfg._graphId); - const { collapsed: stateCollapsed } = group?.get('item')?.getModel() ?? {}; - const { width: shapeWidth, height: shapeHeight } = shape.getBBox(); - let markerCfgArray = []; - if (typeof markerCfg === 'function') { - const callbackMarkerCfg = markerCfg( - { - ...cfg, - children: getChildrenData(graph?.get('eventData').getData(), cfg.g_currentPath as string), - }, - group, - ); - markerCfgArray = callbackMarkerCfg instanceof Array ? callbackMarkerCfg : [callbackMarkerCfg]; - } else { - markerCfgArray = markerCfg instanceof Array ? markerCfg : [markerCfg]; - } - const getCollapsed = () => { - if (isBoolean(stateCollapsed)) return stateCollapsed; - if (cfg._graphId.startsWith('FlowAnalysisGraph')) return !(cfg[`${prefix}_children`] as string[])?.length; - return !(cfg.children as string[])?.length; - }; - markerCfgArray.forEach((mc) => { - const { show, position = 'right', collapsed: inCollapsed, style: markerStyle } = mc; - const collapsed = inCollapsed ?? getCollapsed(); - createMarker( - { - show, - position, - collapsed, // 优先使用内部状态 - style: markerStyle, - }, - group, - [shapeWidth, shapeHeight], - markerCfgArray.length > 1, - ); - shape.attr('defaultCollapsed', collapsed); - }); - } - - return shape; - }, - /** - * 更新节点,包含文本 - * @override - * @param {Object} cfg 节点的配置项 - * @param {Node} node 节点 - */ - // @ts-ignore - update: undefined, - // @ts-ignore - setState(name: string, value: boolean, item: Node) { - const shape: IShape = item.get('keyShape'); - if (!shape || shape.destroyed) return; - - const type = item.getType(); - - const stateName = isBoolean(value) ? name : `${name}:${value}`; - const itemStateStyle = item.getStateStyle(stateName); - // const originStyle = item.getOriginStyle(); - - // 不允许设置一个不存在的状态 - if (!itemStateStyle) { - return; - } - - // 要设置或取消的状态的样式 - // 当没有 state 状态时,默认使用 model.stateStyles 中的样式 - const styles = Object.assign({}, itemStateStyle); - - const group = item.getContainer(); - - // 从图元素现有的样式中删除本次要取消的 states 中存在的属性值。使用对象检索更快 - const keptAttrs: any = { x: 1, y: 1, cx: 1, cy: 1 }; - - if (value) { - // style 为要设置的状态的样式 - for (const key in styles) { - const style = styles[key]; - if (isPlainObject(style) && !ARROWS.includes(key)) { - const subShape = group.find((element) => element.get('name') === key); - if (subShape) { - subShape.attr(style); - } - } else { - // 非纯对象,则认为是设置到 keyShape 上面的 - shape.attr({ - [key]: style, - }); - } - } - } else { - // 所有生效的 state 的样式 - const enableStatesStyle = cloneBesidesImg(item.getCurrentStatesStyle()); - - const model = item.getModel(); - // 原始样式 - const originStyle = mix({}, model.style, cloneBesidesImg(item.getOriginStyle())); - - const keyShapeName = shape.get('name'); - - // cloning shape.attr(), keys.forEach to avoid cloning the img attr, which leads to maximum clone heap #2383 - // const keyShapeStyles = clone(shape.attr()) - const shapeAttrs = shape.attr(); - const keyShapeStyles = {}; - Object.keys(shapeAttrs).forEach((key) => { - if (key === 'img') return; - const attr = shapeAttrs[key]; - if (attr && typeof attr === 'object') { - keyShapeStyles[key] = clone(attr); - } else { - keyShapeStyles[key] = attr; - } - }); - - // 已有样式 - 要取消的状态的样式 - const filtetDisableStatesStyle: any = {}; - - // styles 为要取消的状态的样式 - for (const p in styles) { - const style = styles[p]; - if (isPlainObject(style) && !ARROWS.includes(p)) { - const subShape = group.find((element) => element.get('name') === p); - if (subShape) { - const subShapeStyles = clone(subShape.attr()); - each(style, (v, key) => { - if (p === keyShapeName && keyShapeStyles[key] && !keptAttrs[key]) { - delete keyShapeStyles[key]; - const value = originStyle[p][key] || SHAPES_DEFAULT_ATTRS[type][key]; - shape.attr(key, value); - } else if (subShapeStyles[key] || subShapeStyles[key] === 0) { - delete subShapeStyles[key]; - const value = originStyle[p][key] || SHAPES_DEFAULT_ATTRS[type][key]; - subShape.attr(key, value); - } - }); - filtetDisableStatesStyle[p] = subShapeStyles; - } - } else { - if (keyShapeStyles[p] && !keptAttrs[p]) { - delete keyShapeStyles[p]; - const value = - originStyle[p] || - (originStyle[keyShapeName] ? originStyle[keyShapeName][p] : undefined) || - SHAPES_DEFAULT_ATTRS[type][p]; - shape.attr(p, value); - } - } - } - - // 从图元素现有的样式中删除本次要取消的 states 中存在的属性值后, - // 如果 keyShape 有 name 属性,则 filtetDisableStatesStyle 的格式为 { keyShapeName: {} } - // 否则为普通对象 - if (!keyShapeName) { - mix(filtetDisableStatesStyle, keyShapeStyles); - } else { - filtetDisableStatesStyle[keyShapeName] = keyShapeStyles; - } - for (const key in enableStatesStyle) { - if (keptAttrs[key]) continue; - const enableStyle = enableStatesStyle[key]; - if (!isPlainObject(enableStyle) || ARROWS.includes(key)) { - // 把样式属性merge到keyShape中 - if (!keyShapeName) { - mix(originStyle, { - [key]: enableStyle, - }); - } else { - mix(originStyle[keyShapeName], { - [key]: enableStyle, - }); - delete originStyle[key]; - } - delete enableStatesStyle[key]; - } - } - - const originstyles = {}; - deepMix(originstyles, originStyle, filtetDisableStatesStyle, enableStatesStyle); - let keyShapeSetted = false; - - for (const originKey in originstyles) { - const style = originstyles[originKey]; - if (isPlainObject(style) && !ARROWS.includes(originKey)) { - const subShape = group.find((element) => element.get('name') === originKey); - if (subShape) { - if (originKey === keyShapeName) { - keyShapeSetted = true; - } - if (originKey !== 'collapse-icon') subShape.attr(style); - } - } else if (!keyShapeSetted) { - const value = style || SHAPES_DEFAULT_ATTRS[type][originKey]; - shape.attr({ - [originKey]: value, - }); - } - } - } - }, - }, - 'single-node', - ); - // 注册边 - G6.registerEdge( - 'labels-line', - { - // @ts-ignore - draw: function draw(cfg: ItemModelConfig | undefined = {}, group: IGroup | undefined) { - const { edgeCfg, value } = cfg; - const { text, subText } = getPathText(value); - const { style: edgeStyle, label: labelCfg } = edgeCfg as EdgeCfg; - const { startArrow, endArrow, path, line2StartPoint, endY } = getPathInfo(cfg); - const { style: labelStyle, margin = 4 } = labelCfg ?? {}; - - const line = group!.addShape('path', { - attrs: { - path, - stroke: '#ccc', - startArrow, - endArrow, - ...(typeof edgeStyle === 'function' ? edgeStyle(cfg, group) : edgeStyle), - }, - name: 'path-shape', - }); - - const createItem = (itemText: string, key: string, attrs: object) => { - group!.addShape('text', { - attrs: { - text: itemText, - x: line2StartPoint.x, - ...attrs, - }, - name: `line-text-${key}`, - }); - }; - if (text) { - const textStyle = { ...defaultLineLabelStyle, ...getStyle(labelStyle, cfg, group, 'text') }; - const offsetY = subText ? Number((`${textStyle.fontSize}` || '12').replace(/px/g, '')) / 2 : 0; - createItem(text, 'text', { - y: endY - offsetY - margin / 2, - ...textStyle, - }); - } - if (subText) { - const textStyle = { ...defaultLineLabelStyle, ...getStyle(labelStyle, cfg, group, 'subText') }; - const offsetY = Number((`${textStyle.fontSize}` || '12').replace(/px/g, '')) / 2; - createItem(text, 'subText', { - y: endY + offsetY + margin / 2, - ...textStyle, - }); - } - return line; - }, - // @ts-ignore - update: (cfg: ItemModelConfig, edge) => { - const { edgeCfg, value } = cfg; - const { text, subText } = getPathText(value); - const group = edge.getContainer(); - const getShape = (shapeName: string) => { - return group.get('children').find((item: Node) => item.get('name') === shapeName); - }; - const { startArrow, endArrow, path, line2StartPoint, endY } = getPathInfo(cfg); - const { style: edgeStyle, label: labelCfg } = edgeCfg as EdgeCfg; - const { style: labelStyle, margin = 4 } = labelCfg ?? {}; - - // path - const pathShape = getShape('path-shape'); - pathShape?.attr({ - path, - stroke: '#ccc', - startArrow, - endArrow, - ...(typeof edgeStyle === 'function' ? edgeStyle(cfg, group) : edgeStyle), - }); - // path text - const texts = ['text', 'subText']; - const hasSubText = !!getShape(`line-text-subText`); - texts.forEach((key: string) => { - const textShape = getShape(`line-text-${key}`); - const textStyle = { ...defaultLineLabelStyle, ...getStyle(labelStyle, cfg, group, key) }; - const offsetY = hasSubText ? Number((`${textStyle.fontSize}` || '12').replace(/px/g, '')) / 2 : 0; - textShape?.attr({ - x: line2StartPoint.x, - y: key === 'text' ? endY - offsetY - margin / 2 : endY + offsetY + margin / 2, - text: key === 'text' ? text : subText, - ...textStyle, - }); - }); - }, - }, - 'single-edge', - ); -}; diff --git a/packages/graphs/src/components/flow-analysis-graph/events.ts b/packages/graphs/src/components/flow-analysis-graph/events.ts deleted file mode 100644 index c6adf0cc9..000000000 --- a/packages/graphs/src/components/flow-analysis-graph/events.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { NodeData, IGraph, IG6GraphEvent, INode, NodeConfig, FetchLoading, FlowGraphDatum } from '../../interface'; -import { MARKER_CLICK, prefix } from '../../constants'; -import { FlowAnalysisGraphConfig } from '../../components/flow-analysis-graph'; -import { createFetchLoading, closeFetchLoading, isType, EventData } from '../../utils'; - -/** - * 流向图展开收起 - */ -type CollapsedNode = NodeData & { collapsedLevel: number; g_level: number }; - -export const bindEvents = (params: { - graph: IGraph; - level: number; - asyncData: FlowAnalysisGraphConfig['nodeCfg']['asyncData']; - fetchLoading?: FetchLoading; -}) => { - const { graph, level, asyncData, fetchLoading } = params; - const changeData = (data, eventData?: FlowGraphDatum) => { - if (eventData) graph.set('eventData', new EventData(eventData)); - graph.changeData(data); - if (graph.get('fitCenter')) { - graph.fitCenter(); - } - }; - const onClick = async (e: IG6GraphEvent) => { - const controlData: { edges: any[]; nodes: any[] } = graph.get('eventData').getData(); - if (e.target.get('name')?.startsWith('collapse-icon')) { - const item = e.item as INode; - const model = item.getModel() as any; - let { id: nodeId, collapsed } = model; - - if (!isType(collapsed, 'Boolean')) { - // @ts-ignore - collapsed = item._cfg.group - .getChildren() - .find((t) => t.get('name') === 'main-box') - ?.attr('defaultCollapsed'); - } - const { edges: fullEdges = [], nodes: fullNodes } = controlData ?? {}; - const updateItems: INode[] = []; - const updateIds: string[] = []; - let allTargets: string[] = []; - const getLinkedId = (currentId: string) => { - fullEdges.forEach((edge) => { - const { source, target } = edge; - if (source === currentId && !allTargets.includes(target)) { - allTargets.push(target); - getLinkedId(target); - } - }); - }; - getLinkedId(nodeId as string); - // 避免成环的情况 - allTargets = allTargets.filter((t) => t !== nodeId); - if (!collapsed) { - // collapse - graph.findAll('node', (node) => allTargets.includes(node.get('id'))).forEach((node) => graph.hideItem(node)); - fullNodes.forEach((node: CollapsedNode) => { - const { collapsedLevel = 0, id } = node; - if (allTargets.includes(id)) { - node.collapsedLevel = collapsedLevel + 1; - } - }); - } else { - const { nodes: currentNodes, edges: currentEdges } = graph.get('data'); - if (allTargets.length) { - // 已经展开过 - if (graph.findById(allTargets[0])) { - allTargets.forEach((id) => { - const n = graph.findById(id); - if (n) { - const { collapsedLevel } = n.getModel(); - if (!collapsedLevel || collapsedLevel < 2) graph.showItem(n); - } - }); - fullNodes.forEach((node: CollapsedNode) => { - const { collapsedLevel = 0, id } = node; - if (allTargets.includes(id)) { - node.collapsedLevel = collapsedLevel - 1; - } - }); - } else { - // 从全量 data 中取 - let concatNodes; - if (level) { - concatNodes = fullNodes.filter((n) => { - return allTargets.includes(n.id) && n[`${prefix}_level`] === model[`${prefix}_level`] + 1; - }); - allTargets = concatNodes.map((n) => n.id); - } - - const currentData = { - nodes: currentNodes - .map((n) => { - if (n.id === nodeId) n[`${prefix}_children`] = allTargets; - return n; - }) - .concat(concatNodes), - edges: currentEdges.concat(fullEdges.filter((e) => e.source === nodeId)), - }; - changeData(currentData); - } - } else if (asyncData) { - createFetchLoading(item.getModel() as NodeConfig, fetchLoading); - const { nodes: asnycNodes, edges: asyncEdges } = await asyncData(item.getModel() as NodeConfig); - // modify current node collapsed status - graph.updateItem(item, { - collapsed: false, - }); - graph.refreshItem(item); - closeFetchLoading(); - const getDataByEvent = (nodes, edges) => { - return { - nodes: nodes - .map((n) => { - if (n.id === nodeId) n[`${prefix}_children`] = asnycNodes.map((n) => n.id); - return n; - }) - .concat(asnycNodes), - edges: edges.concat( - asyncEdges?.length ? asyncEdges : asnycNodes.map((t) => ({ source: nodeId, target: t.id })), - ), - }; - }; - changeData(getDataByEvent(currentNodes, currentEdges), getDataByEvent(fullNodes, fullEdges)); - graph.emit(MARKER_CLICK, e, { - type: 'fetch', - collapsed: true, - }); - return; - } - } - const { target: updateNodeTarget } = fullEdges.find((edge) => edge.source === nodeId); - fullEdges.forEach((edge) => { - const { source, target } = edge; - if (target === updateNodeTarget) { - updateIds.push(source); - } - }); - Array.from(new Set(updateIds)).forEach((id: string) => { - updateItems.push(graph.find('node', (node) => node.get('id') === id) as INode); - }); - updateItems.forEach((nodeItem) => { - graph.updateItem(nodeItem, { - collapsed: !collapsed, - }); - graph.refreshItem(nodeItem); - }); - graph.emit(MARKER_CLICK, e, { - type: 'collapse', - collapsed: !!collapsed, - }); - } - }; - graph.on('node:click', (e: IG6GraphEvent) => { - onClick(e); - }); -}; diff --git a/packages/graphs/src/components/flow-analysis-graph/index.tsx b/packages/graphs/src/components/flow-analysis-graph/index.tsx deleted file mode 100644 index 5e2078d01..000000000 --- a/packages/graphs/src/components/flow-analysis-graph/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import { DagreLayout } from '@antv/layout'; -import { defaultFlowGraphAnchorPoints, defaultNodeSize, defaultNodeStyle, defaultStateStyles } from '../../constants'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { ChartLoading } from '../../utils'; -import { bindEvents } from './events'; -import { - CardItems, - CommonConfig, - FlowGraphEdgeData, - NodeCfg, - NodeData, - NodeConfig, - FetchLoading, -} from '../../interface'; -import { registerIndicatorGeometries } from './customItem'; - -export type FlowAnalysisNodeData = NodeData<{ - title?: string; - items?: CardItems[]; -}>; - -export interface FlowAnalysisGraphConfig extends Omit, 'data' | 'nodeCfg'>, FetchLoading { - data: { - nodes: FlowAnalysisNodeData[]; - edges: FlowGraphEdgeData[]; - }; - /** 展开层级,默认 100 */ - level?: number; - nodeCfg?: NodeCfg & { - /** 点击展开时异步获取数据 */ - asyncData?: (nodeCfg: NodeConfig) => Promise<{ - nodes: FlowAnalysisNodeData[]; - edges?: FlowGraphEdgeData[]; - }>; - }; -} - -registerIndicatorGeometries(); - -const defaultLayout = { - type: 'dagre', - rankdir: 'LR', - center: [0, 0], - nodesepFunc: () => 1, - ranksepFunc: () => 1, -}; - -const defaultProps = { - nodeCfg: { - type: 'indicator-card', - size: defaultNodeSize, - style: defaultNodeStyle, - anchorPoints: defaultFlowGraphAnchorPoints, - padding: 6, - layout: 'bundled', - nodeStateStyles: defaultStateStyles, - }, - edgeCfg: { - type: 'cubic-horizontal', - edgeStateStyles: defaultStateStyles, - }, - behaviors: ['zoom-canvas', 'drag-canvas'], - layout: defaultLayout, - animate: true, - markerPosition: 'right' as 'right', - autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, - level: 100, -}; - -const FlowAnalysisGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('Graph', rest, { name: 'FlowAnalysisGraph', bindEvents }); - - return ( - - {loading && } -
- - ); -}; - -export default FlowAnalysisGraph; diff --git a/packages/graphs/src/components/flow-direction-graph/index.tsx b/packages/graphs/src/components/flow-direction-graph/index.tsx new file mode 100644 index 000000000..ee52af6ce --- /dev/null +++ b/packages/graphs/src/components/flow-direction-graph/index.tsx @@ -0,0 +1,32 @@ +import type { Graph } from '@antv/g6'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS, getFlowDirectionGraphOptions } from './options'; +import type { FlowDirectionGraphOptions } from './types'; + +export const FlowDirectionGraph: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const { labelField, ...restProps } = props; + const options = useMemo( + () => mergeOptions(COMMON_OPTIONS, DEFAULT_OPTIONS, getFlowDirectionGraphOptions({ labelField }), restProps), + [props], + ); + + return ( + + {children} + + ); +}); + +export type { FlowDirectionGraphOptions }; diff --git a/packages/graphs/src/components/flow-direction-graph/options.tsx b/packages/graphs/src/components/flow-direction-graph/options.tsx new file mode 100644 index 000000000..959f9ae51 --- /dev/null +++ b/packages/graphs/src/components/flow-direction-graph/options.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { RCNode } from '../../core/base'; +import { formatLabel } from '../../core/utils/label'; +import type { GraphOptions } from '../../types'; +import type { FlowDirectionGraphOptions } from './types'; + +const { TextNode } = RCNode; + +export const DEFAULT_OPTIONS: GraphOptions = { + node: { + type: 'react', + state: { + active: { + halo: false, + }, + selected: { + halo: false, + }, + }, + }, + edge: { + type: 'cubic-horizontal', + style: { + strokeOpacity: 0.5, + }, + state: { + active: { + strokeOpacity: 1, + }, + }, + }, + layout: { + type: 'dagre', + rankdir: 'LR', + animation: false, + }, + transforms: ['translate-react-node-origin'], +}; + +export const getFlowDirectionGraphOptions = ({ + labelField, +}: Pick): FlowDirectionGraphOptions => { + const options: FlowDirectionGraphOptions = { + node: { + style: { + component: (data) => { + const label = formatLabel(data, labelField); + return ; + }, + size: [100, 40], + ports: [{ placement: 'left' }, { placement: 'right' }], + }, + }, + }; + + return options; +}; diff --git a/packages/graphs/src/components/flow-direction-graph/types.ts b/packages/graphs/src/components/flow-direction-graph/types.ts new file mode 100644 index 000000000..a0e5f871c --- /dev/null +++ b/packages/graphs/src/components/flow-direction-graph/types.ts @@ -0,0 +1,12 @@ +import type { NodeData } from '@antv/g6'; +import type { GraphOptions } from '../../types'; + +export interface FlowDirectionGraphOptions extends GraphOptions { + /** + * Selects a field from the data to use as the label for the node. + * If a string is provided, it will select the field as `data[labelField]`. + * If a function is provided, it will call the function with the data and use the returned value. + * @default (data) => data.id + */ + labelField?: string | ((node: NodeData) => string); +} diff --git a/packages/graphs/src/components/flow-graph/index.tsx b/packages/graphs/src/components/flow-graph/index.tsx new file mode 100644 index 000000000..30f96b9d2 --- /dev/null +++ b/packages/graphs/src/components/flow-graph/index.tsx @@ -0,0 +1,31 @@ +import type { Graph } from '@antv/g6'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS, getFlowGraphOptions } from './options'; +import type { FlowGraphOptions } from './types'; + +export const FlowGraph: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const options = useMemo(() => { + const { direction = 'horizontal', labelField, ...restProps } = props; + return mergeOptions(COMMON_OPTIONS, DEFAULT_OPTIONS, getFlowGraphOptions({ direction, labelField }), restProps); + }, [props]); + + return ( + + {children} + + ); +}); + +export type { FlowGraphOptions }; diff --git a/packages/graphs/src/components/flow-graph/options.tsx b/packages/graphs/src/components/flow-graph/options.tsx new file mode 100644 index 000000000..0f78a0dc0 --- /dev/null +++ b/packages/graphs/src/components/flow-graph/options.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { RCNode } from '../../core/base'; +import { formatLabel } from '../../core/utils/label'; +import type { FlowGraphOptions } from './types'; + +const { TextNode } = RCNode; + +export const DEFAULT_OPTIONS: FlowGraphOptions = { + node: { + type: 'react', + state: { + active: { + halo: false, + }, + selected: { + halo: false, + }, + }, + }, + edge: { + type: 'polyline', + style: { + lineWidth: 2, + endArrow: true, + radius: 8, + router: { + type: 'orth', + }, + }, + }, + layout: { + type: 'dagre', + animation: false, + }, + transforms: ['translate-react-node-origin'], +}; + +export const getFlowGraphOptions = ({ + direction, + labelField, +}: Pick): FlowGraphOptions => { + const options: FlowGraphOptions = { + node: { + style: { + component: (data) => { + const label = formatLabel(data, labelField); + return ; + }, + size: [100, 40], + ports: + direction === 'vertical' + ? [{ placement: 'top' }, { placement: 'bottom' }] + : [{ placement: 'left' }, { placement: 'right' }], + }, + }, + layout: { + type: 'dagre', + rankdir: direction === 'vertical' ? 'TB' : 'LR', + }, + }; + + return options; +}; diff --git a/packages/graphs/src/components/flow-graph/types.ts b/packages/graphs/src/components/flow-graph/types.ts new file mode 100644 index 000000000..48b80a188 --- /dev/null +++ b/packages/graphs/src/components/flow-graph/types.ts @@ -0,0 +1,17 @@ +import type { NodeData } from '@antv/g6'; +import type { GraphOptions } from '../../types'; + +export interface FlowGraphOptions extends GraphOptions { + /** + * The direction of the FlowGraph. + * @default 'horizontal' + */ + direction?: 'horizontal' | 'vertical'; + /** + * Selects a field from the data to use as the label for the node. + * If a string is provided, it will select the field as `data[labelField]`. + * If a function is provided, it will call the function with the data and use the returned value. + * @default (data) => data.id + */ + labelField?: string | ((node: NodeData) => string); +} diff --git a/packages/graphs/src/components/fund-flow-graph/customItem.ts b/packages/graphs/src/components/fund-flow-graph/customItem.ts deleted file mode 100644 index d1d47af9a..000000000 --- a/packages/graphs/src/components/fund-flow-graph/customItem.ts +++ /dev/null @@ -1,277 +0,0 @@ -import G6, { IGroup, Node } from '@antv/g6'; -import { defaultMargin, defaultLabelStyle } from '../../constants'; -import { getStyle, getCssPadding, getSize, getArrowCfg, createMarker } from '../../utils'; -import { CardNodeCfg, CardItems, ModelConfig, IPoint, EdgeCfg } from '../../interface'; -import { edgeType } from './index'; - -interface ItemModelConfig extends ModelConfig { - value: edgeType; -} - -const getPathInfo = (cfg: ItemModelConfig) => { - const { edgeCfg } = cfg; - const startPoint = cfg.startPoint as IPoint; - const endPoint = cfg.endPoint as IPoint; - const { x: startX, y: startY } = startPoint; - const { x: endX, y: endY } = endPoint; - const Ydiff = endY - startY; - const slope = Ydiff !== 0 ? Math.min(500 / Math.abs(Ydiff), 20) : 0; - const cpOffset = slope > 15 ? 0 : 16; - const offset = Ydiff < 0 ? cpOffset : -cpOffset; - - const line1EndPoint = { - x: startX + slope, - y: endY + offset, - }; - const line2StartPoint = { - x: line1EndPoint.x + cpOffset, - y: endY, - }; - - // 控制点坐标 - const controlPoint = { - x: ((line1EndPoint.x - startX) * (endY - startY)) / (line1EndPoint.y - startY) + startX, - y: endY, - }; - - let path = [ - ['M', startX, startY], - ['L', line1EndPoint.x, line1EndPoint.y], - ['Q', controlPoint.x, controlPoint.y, line2StartPoint.x, line2StartPoint.y], - ['L', endX, endY], - ]; - - if (Math.abs(Ydiff) <= 5) { - path = [ - ['M', startX, startY], - ['L', endX, endY], - ]; - } - const { startArrow: startArrowCfg, endArrow: endArrowCfg } = edgeCfg as EdgeCfg; - - const startArrow = getArrowCfg(startArrowCfg, cfg); - const endArrow = getArrowCfg(endArrowCfg, cfg); - - return { - startArrow, - endArrow, - path, - line2StartPoint, - endY, - }; -}; - -const getPathText = (value: edgeType) => { - let text; - let subText; - if (value instanceof Object) { - text = value.text; - subText = value.subText; - } else { - text = value; - } - return { text, subText }; -}; - -// 资金流向图 -export const registerFundFlowItems = () => { - // 注册节点 - G6.registerNode( - 'fund-card', - { - // @ts-ignore - draw: (cfg: CardNodeCfg | undefined = {}, group: IGroup | undefined) => { - const { value = {}, nodeCfg, markerCfg } = cfg; - const { label = {}, style, padding = 0, customContent } = nodeCfg as CardNodeCfg; - const { style: labelStyle } = label; - const paddingArray = getCssPadding(padding); - const size = getSize(cfg.size); - let height = 0; // 统计容器总高度,动态设置 - const shapeWidth = size[0]; - const contentWidth = shapeWidth - paddingArray[1] - paddingArray[3]; - const contentHeight = size[1] - paddingArray[0] - paddingArray[2]; - // card box - const cardStyle = getStyle(style, cfg, group); - const shape = group!.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: size[0], - height: size[1], - radius: size[1] / 2, - fill: '#fff', - stroke: '#40a9ff', - ...cardStyle, - }, - name: 'main-box', - draggable: true, - }); - - if (value) { - height += paddingArray[0]; - const createRowItems = (item: CardItems, contentWidth: number, startX: number): number[] => { - const { text, icon } = item; - let textShape; - let iconShape; - if (icon) { - iconShape = group!.addShape('image', { - attrs: { - x: startX, - y: height, - img: icon, - width: contentHeight, - height: contentHeight, - ...getStyle(labelStyle, cfg, group, 'icon'), - }, - name: 'fund-icon', - }); - } - textShape = group?.addShape('text', { - attrs: { - textBaseline: 'middle', - textAlign: iconShape ? 'start' : 'center', - x: startX + (iconShape ? iconShape?.getBBox().width + defaultMargin : contentWidth / 2), - y: paddingArray[0] + contentHeight / 2, - text, - ...defaultLabelStyle, - ...getStyle(labelStyle, cfg, group, 'text'), - }, - name: `fund-text`, - }); - return [textShape?.getBBox().height ?? 0, iconShape?.getBBox().height ?? 0]; - }; - const createItems = (item: CardItems) => { - const itemsHeight: number[] = []; - if (customContent) { - itemsHeight.push( - customContent(item, group, { - startX: paddingArray[3], - startY: height, - width: contentWidth, - }) ?? 0, - ); - } else { - itemsHeight.push(...createRowItems(item, contentWidth, paddingArray[3])); - } - height += Math.max(...itemsHeight); - }; - - createItems(value as CardItems); - } - shape?.attr('height', Math.max(size[1], height + paddingArray[2])); - - // collapse marker - if (markerCfg) { - const { collapsed: stateCollapsed } = group?.get('item')?.getModel() ?? {}; - const { width: shapeWidth, height: shapeHeight } = shape.getBBox(); - const { - show, - position = 'right', - collapsed, - style: markerStyle, - } = typeof markerCfg === 'function' ? markerCfg(cfg, group) : markerCfg; - createMarker( - { - show, - position, - collapsed: stateCollapsed ?? collapsed, // 优先使用内部状态 - style: markerStyle, - }, - group, - [shapeWidth, shapeHeight], - ); - shape.attr('defaultCollapsed', collapsed); - } - - return shape; - }, - /** - * 更新节点,包含文本 - * @override - * @param {Object} cfg 节点的配置项 - * @param {Node} node 节点 - */ - update: undefined, - }, - 'single-node', - ); - - // 注册边 - G6.registerEdge( - 'fund-line', - { - // @ts-ignore - draw: function draw(cfg: ItemModelConfig | undefined = {}, group: IGroup | undefined) { - const { edgeCfg, value } = cfg; - const { text, subText } = getPathText(value); - const { style: edgeStyle, label: labelCfg } = edgeCfg as EdgeCfg; - - const { startArrow, endArrow, path, line2StartPoint, endY } = getPathInfo(cfg); - const { style: labelStyle } = labelCfg ?? {}; - const line = group!.addShape('path', { - attrs: { - path, - stroke: '#ccc', - startArrow, - endArrow, - ...(typeof edgeStyle === 'function' ? edgeStyle(cfg, group) : edgeStyle), - }, - name: 'path-shape', - }); - - const createItem = (itemText: string, key: string) => { - group!.addShape('text', { - attrs: { - text: itemText, - x: line2StartPoint.x, - y: key === 'text' ? endY - 4 : endY + 16, - ...defaultLabelStyle, - ...getStyle(labelStyle, cfg, group, key), - }, - name: `line-text-${key}`, - }); - }; - text && createItem(text, 'text'); - subText && createItem(subText, 'subText'); - - return line; - }, - // @ts-ignore - update: (cfg: ItemModelConfig, edge) => { - const { edgeCfg, value } = cfg; - const { text, subText } = getPathText(value); - const group = edge.getContainer(); - const getShape = (shapeName: string) => { - return group.get('children').find((item: Node) => item.get('name') === shapeName); - }; - // const { startArrow, endArrow } = getPathInfo(cfg); - const { startArrow, endArrow, path, line2StartPoint, endY } = getPathInfo(cfg); - const { style: edgeStyle, label: labelCfg } = edgeCfg as EdgeCfg; - const { style: labelStyle } = labelCfg ?? {}; - - // path - const pathShape = getShape('path-shape'); - pathShape?.attr({ - path, - stroke: '#ccc', - startArrow, - endArrow, - ...(typeof edgeStyle === 'function' ? edgeStyle(cfg, group) : edgeStyle), - }); - // path text - const texts = ['text', 'subText']; - texts.forEach((key: string) => { - const textShape = getShape(`line-text-${key}`); - textShape?.attr({ - x: line2StartPoint.x, - y: key === 'text' ? endY - 4 : endY + 16, - text: key === 'text' ? text : subText, - ...defaultLabelStyle, - ...getStyle(labelStyle, cfg, group, key), - }); - }); - }, - }, - 'single-edge', - ); -}; diff --git a/packages/graphs/src/components/fund-flow-graph/index.tsx b/packages/graphs/src/components/fund-flow-graph/index.tsx deleted file mode 100644 index 25262facb..000000000 --- a/packages/graphs/src/components/fund-flow-graph/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import { DagreLayout } from '@antv/layout'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { ChartLoading } from '../../utils'; -import { defaultFlowGraphAnchorPoints, defaultNodeSize, defaultStateStyles, defaultNodeStyle } from '../../constants'; -import { registerFundFlowItems } from './customItem'; -import { bindEvents } from '../flow-analysis-graph/events'; -import { CommonConfig, EdgeData, NodeData } from '../../interface'; - -export type edgeType = - | string - | { - text?: string; - subText?: string; - }; - -export type FundFlowEdgeData = EdgeData; - -export type FundFlowNodeData = NodeData<{ - text?: string; - icon?: string; -}>; -export interface FundFlowGraphConfig extends Omit, 'data'> { - data: { - nodes: FundFlowNodeData[]; - edges: FundFlowEdgeData[]; - }; -} - -registerFundFlowItems(); - -const defaultLayout = { - type: 'dagre', - rankdir: 'LR', - nodesep: 30, - ranksep: 50, -}; - -const defaultProps = { - nodeCfg: { - type: 'fund-card', - size: defaultNodeSize, - style: defaultNodeStyle, - anchorPoints: defaultFlowGraphAnchorPoints, - nodeStateStyles: defaultStateStyles, - padding: 6, - }, - edgeCfg: { - type: 'fund-line', - edgeStateStyles: defaultStateStyles, - style: { - stroke: '#40a9ff', - }, - endArrow: { - fill: '#40a9ff', - }, - }, - behaviors: ['zoom-canvas', 'drag-canvas'], - layout: defaultLayout, - animate: true, - autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, -}; - -const FundFlowGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('Graph', rest, { name: 'FundFlowGraph', bindEvents }); - - return ( - - {loading && } -
- - ); -}; - -export default FundFlowGraph; diff --git a/packages/graphs/src/components/indented-tree/index.tsx b/packages/graphs/src/components/indented-tree/index.tsx new file mode 100644 index 000000000..8778df21a --- /dev/null +++ b/packages/graphs/src/components/indented-tree/index.tsx @@ -0,0 +1,50 @@ +import type { Graph } from '@antv/g6'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { formatTreeData } from '../../core/utils/data'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS, getIndentedTreeOptions } from './options'; +import type { IndentedTreeOptions } from './types'; + +export const IndentedTree: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const { + data, + defaultExpandLevel, + type = 'default', + nodeMinWidth, + nodeMaxWidth, + direction = 'right', + labelField, + ...restProps + } = props; + + const options = useMemo( + () => + mergeOptions( + COMMON_OPTIONS, + DEFAULT_OPTIONS, + { data: formatTreeData(data, defaultExpandLevel) }, + getIndentedTreeOptions({ type, nodeMinWidth, nodeMaxWidth, direction, labelField }), + restProps, + ), + [props], + ); + + return ( + + {children} + + ); +}); + +export type { IndentedTreeOptions }; diff --git a/packages/graphs/src/components/indented-tree/options.tsx b/packages/graphs/src/components/indented-tree/options.tsx new file mode 100644 index 000000000..6afe73458 --- /dev/null +++ b/packages/graphs/src/components/indented-tree/options.tsx @@ -0,0 +1,254 @@ +import type { Graph, NodeData, SingleLayoutOptions } from '@antv/g6'; +import { get } from 'lodash'; +import React from 'react'; +import { CollapseExpandIcon, RCNode, TextNodeProps } from '../../core/base'; +import { formatLabel } from '../../core/utils/label'; +import { measureTextSize } from '../../core/utils/measure-text'; +import { getNodeSide } from '../../core/utils/node'; +import { getBoxedTextNodeStyle, getLinearTextNodeStyle } from '../../core/utils/tree'; +import type { GraphOptions } from '../../types'; +import type { IndentedTreeOptions } from './types'; + +const { ArrowCountIcon } = CollapseExpandIcon; +const { TextNode } = RCNode; + +export const DEFAULT_OPTIONS: GraphOptions = { + node: { + type: 'react', + state: { + active: { + halo: false, + }, + selected: { + halo: false, + }, + }, + }, + edge: { + type: 'indented', + style: { + lineWidth: 3, + }, + }, + transforms: (prev) => [ + ...prev, + { + type: 'collapse-expand-react-node', + key: 'collapse-expand-react-node', + enable: false, + trigger: 'icon', + iconRender: function (isCollapsed: boolean, data: NodeData) { + return ; + }, + }, + ], + layout: { + type: 'indented', + direction: 'LR', + indent: (node) => getIndent(node, 20), + getVGap: () => 14, + }, + animation: { + duration: 500, + }, +}; + +const getIndent = (node, preset) => { + if (node.depth === 0) return 0; + + let totalWidth = preset; + let currentNode = node.parent; + + while (currentNode) { + totalWidth += currentNode.width / 2; + currentNode = currentNode.parent; + } + + return totalWidth / node.depth; +}; + +const getNodeTextAlign = (graph: Graph, data: NodeData) => { + const side = getNodeSide(graph, data); + return side === 'left' ? 'right' : side === 'center' ? 'center' : 'left'; +}; + +export const getIndentedTreeOptions = ({ + type, + nodeMinWidth, + nodeMaxWidth, + direction, + labelField, +}: Pick): GraphOptions => { + let options: GraphOptions = {}; + const minWidth = nodeMinWidth || 0; + const maxWidth = nodeMaxWidth || 300; + + if (type === 'boxed') { + options = { + node: { + style: { + component: function (data: NodeData) { + const depth = data.depth; + const color = data.style?.color as string; + const label = formatLabel(data, labelField); + const { font } = getBoxedTextNodeStyle(label, minWidth, maxWidth, depth); + const props: TextNodeProps = { + type: depth === 0 || depth === 1 ? 'filled' : 'outlined', + text: label, + color: depth === 0 ? '#f1f4f5' : color, + maxWidth, + font, + style: { + textAlign: getNodeTextAlign(this as unknown as Graph, data), + ...(depth === 0 ? { color: '#252525' } : {}), + }, + }; + return ; + }, + size: (data: NodeData) => { + const label = formatLabel(data, labelField); + return getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + }, + }, + }, + edge: { + style: { + stroke: function (data) { + const source = this.getNodeData(data.source); + return get(source, 'style.color', '#99ADD1') as string; + }, + radius: 16, + }, + }, + transforms: (prev) => [ + ...prev, + { + type: 'assign-color-by-branch', + key: 'assign-color-by-branch', + }, + ], + layout: { + type: 'indented', + getWidth: (data) => { + const label = formatLabel(data, labelField); + const [width] = getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return width; + }, + getHeight: (data) => { + const label = formatLabel(data, labelField); + const [, height] = getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return height; + }, + }, + }; + } else if (type === 'linear') { + options = { + node: { + style: { + component: function (data: NodeData) { + const depth = data.depth; + const color = data.style?.color as string; + const label = formatLabel(data, labelField); + const { font } = getLinearTextNodeStyle(label, minWidth, maxWidth, depth); + const props = { text: label, color, maxWidth, font } as TextNodeProps; + Object.assign( + props, + depth === 0 + ? { type: 'filled', color: '#f1f4f5', style: { color: '#252525' } } + : { + type: 'underlined', + style: { textAlign: getNodeTextAlign(this as unknown as Graph, data) }, + }, + ); + return ; + }, + size: (data: NodeData) => { + const label = formatLabel(data, labelField); + return getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + }, + ports: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + return side === 'left' + ? [{ placement: 'bottom' }, { placement: 'bottom-right' }] + : side === 'center' + ? [{ placement: 'bottom' }] + : [{ placement: 'bottom' }, { placement: 'bottom-left' }]; + }, + }, + }, + edge: { + style: { + stroke: function (data) { + const target = this.getNodeData(data.target); + return get(target, 'style.color', '#99ADD1') as string; + }, + radius: 24, + }, + }, + layout: { + type: 'indented', + getWidth: (data) => { + const label = formatLabel(data, labelField); + const [width] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return width; + }, + getHeight: (data) => { + const label = formatLabel(data, labelField); + const [, height] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return height; + }, + getVGap: () => 12, + }, + transforms: (prev) => [ + ...prev, + { + type: 'assign-color-by-branch', + key: 'assign-color-by-branch', + }, + { + type: 'arrange-edge-z-index', + key: 'arrange-edge-z-index', + }, + ], + }; + } else { + const PADDING = [24, 16]; + + options = { + node: { + style: { + component: (data) => { + const label = formatLabel(data, labelField); + return ; + }, + size: (data) => { + const label = formatLabel(data, labelField); + return measureTextSize(label, PADDING); + }, + }, + }, + layout: { + type: 'indented', + getWidth: (data) => { + const label = formatLabel(data, labelField); + const [width] = measureTextSize(label, PADDING); + return width; + }, + getHeight: (data) => { + const label = formatLabel(data, labelField); + const [, height] = measureTextSize(label, PADDING); + return height; + }, + }, + }; + } + + if (direction) { + options.layout ||= {} as SingleLayoutOptions; + Object.assign(options.layout as SingleLayoutOptions, { + direction: direction === 'alternate' ? 'H' : direction === 'left' ? 'RL' : 'LR', + }); + } + + return options; +}; diff --git a/packages/graphs/src/components/indented-tree/types.ts b/packages/graphs/src/components/indented-tree/types.ts new file mode 100644 index 000000000..55d818a06 --- /dev/null +++ b/packages/graphs/src/components/indented-tree/types.ts @@ -0,0 +1,40 @@ +import type { GraphData, NodeData, TreeData } from '@antv/g6'; +import type { GraphOptions } from '../../types'; + +export interface IndentedTreeOptions extends Omit { + /** + * The data. + */ + data?: GraphData | TreeData; + /** + * The default expand level. If not set, all nodes will be expanded. + */ + defaultExpandLevel?: number; + /** + * The type of the mind map + * @default 'default' + */ + type?: 'default' | 'linear' | 'boxed'; + /** + * The direction of the mind map. + * @default 'right' + */ + direction?: 'left' | 'right' | 'alternate'; + /** + * The minimum width of the tree nodes. If the text of a tree node is too short, it will be centered. + * @default 0 + */ + nodeMinWidth?: number; + /** + * The maximum width of the tree nodes. If the text of a tree node is too long, it will be wrapped. + * @default 300 + */ + nodeMaxWidth?: number; + /** + * Selects a field from the data to use as the label for the node. + * If a string is provided, it will select the field as `data[labelField]`. + * If a function is provided, it will call the function with the data and use the returned value. + * @default (data) => data.id + */ + labelField?: string | ((data: NodeData) => string); +} diff --git a/packages/graphs/src/components/index.ts b/packages/graphs/src/components/index.ts new file mode 100644 index 000000000..075b19029 --- /dev/null +++ b/packages/graphs/src/components/index.ts @@ -0,0 +1,8 @@ +export { Dendrogram, type DendrogramOptions } from './dendrogram'; +export { Fishbone, type FishboneOptions } from './fishbone'; +export { FlowDirectionGraph, type FlowDirectionGraphOptions } from './flow-direction-graph'; +export { FlowGraph, type FlowGraphOptions } from './flow-graph'; +export { IndentedTree, type IndentedTreeOptions } from './indented-tree'; +export { MindMap, type MindMapOptions } from './mind-map'; +export { NetworkGraph, type NetworkGraphOptions } from './network-graph'; +export { OrganizationChart, type OrganizationChartOptions } from './organization-chart'; diff --git a/packages/graphs/src/components/mind-map-graph/index.tsx b/packages/graphs/src/components/mind-map-graph/index.tsx deleted file mode 100644 index 85e7a6751..000000000 --- a/packages/graphs/src/components/mind-map-graph/index.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from 'react'; -import { defaultFlowGraphAnchorPoints, defaultNodeSize, defaultNodeStyle, defaultStateStyles } from '../../constants'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { CompactBoxLayout } from '../../layout'; -import { - CommonConfig, - IGraph, - IGroup, - NodeCfg, - NodeConfig, - Shape, - ShapeCfg, - G6TreeGraphData, - FetchLoading, -} from '../../interface'; -import { ChartLoading } from '../../utils'; -import { bindEvents } from '../file-tree-graph/events'; -import { registerIndicatorGeometries } from '../flow-analysis-graph/customItem'; - -export interface MindMapGraphConfig extends Omit, 'data' | 'nodeCfg'>, FetchLoading { - data: G6TreeGraphData; - /** 展开层级,默认 100 */ - level?: number; - nodeCfg?: NodeCfg & { - /** 点击展开时异步获取数据 */ - getChildren?: (nodeCfg: NodeConfig) => Promise; - }; -} - -registerIndicatorGeometries(); - -const defaultLayout = { - type: 'mindmap', - direction: 'H', - getId: (d: any) => { - return d.id; - }, - getHeight: () => { - return 60; - }, - getWidth: () => { - return 16; - }, - getVGap: () => { - return 16; - }, - getHGap: () => { - return 100; - }, -}; - -const defaultProps = { - nodeCfg: { - type: 'indicator-card', - size: defaultNodeSize, - style: defaultNodeStyle, - anchorPoints: defaultFlowGraphAnchorPoints, - padding: 6, - layout: 'bundled', - nodeStateStyles: defaultStateStyles, - label: { - style: (cfg: Shape | ShapeCfg, group: IGroup | IGraph | undefined, type: string | undefined) => { - const styles = { - icon: { - width: 10, - height: 10, - }, - value: { - fill: '#000', - }, - text: { - fill: '#aaa', - }, - }; - return type ? styles[type] : {}; - }, - }, - }, - edgeCfg: { - type: 'cubic-horizontal', - endArrow: { - type: 'vee', - }, - edgeStateStyles: defaultStateStyles, - }, - behaviors: ['zoom-canvas', 'drag-canvas'], - layout: defaultLayout, - animate: true, - autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, - level: 100, -}; - -const MindMapGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('TreeGraph', rest, { - name: 'MindMapGraph', - bindEvents, - }); - - return ( - - {loading && } -
- - ); -}; - -export default MindMapGraph; diff --git a/packages/graphs/src/components/mind-map/index.tsx b/packages/graphs/src/components/mind-map/index.tsx new file mode 100644 index 000000000..a094da672 --- /dev/null +++ b/packages/graphs/src/components/mind-map/index.tsx @@ -0,0 +1,48 @@ +import type { Graph } from '@antv/g6'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { formatTreeData } from '../../core/utils/data'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS, getMindMapOptions } from './options'; +import type { MindMapOptions } from './types'; + +export const MindMap: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const options = useMemo(() => { + const { + data, + type = 'default', + nodeMinWidth, + nodeMaxWidth, + direction = 'alternate', + labelField, + defaultExpandLevel, + ...restProps + } = props; + const options = mergeOptions( + COMMON_OPTIONS, + DEFAULT_OPTIONS, + { data: formatTreeData(data, defaultExpandLevel) }, + getMindMapOptions({ type, nodeMinWidth, nodeMaxWidth, direction, labelField }), + restProps, + ); + return options; + }, [props]); + + return ( + + {children} + + ); +}); + +export type { MindMapOptions }; diff --git a/packages/graphs/src/components/mind-map/options.tsx b/packages/graphs/src/components/mind-map/options.tsx new file mode 100644 index 000000000..15ecebf63 --- /dev/null +++ b/packages/graphs/src/components/mind-map/options.tsx @@ -0,0 +1,256 @@ +import type { Graph, NodeData, SingleLayoutOptions } from '@antv/g6'; +import { get } from 'lodash'; +import React from 'react'; +import type { TextNodeProps } from '../../core/base'; +import { CollapseExpandIcon, RCNode } from '../../core/base'; +import { formatLabel } from '../../core/utils/label'; +import { measureTextSize } from '../../core/utils/measure-text'; +import { getNodeSide } from '../../core/utils/node'; +import { getBoxedTextNodeStyle, getLinearTextNodeStyle } from '../../core/utils/tree'; +import type { GraphOptions } from '../../types'; +import type { MindMapOptions } from './types'; + +const { ArrowCountIcon } = CollapseExpandIcon; +const { TextNode } = RCNode; + +export const DEFAULT_OPTIONS: GraphOptions = { + node: { + type: 'react', + state: { + active: { + halo: false, + }, + selected: { + halo: false, + }, + }, + }, + edge: { + type: 'cubic-horizontal', + style: { + lineWidth: 3, + }, + }, + transforms: (prev) => [ + ...prev, + { + type: 'collapse-expand-react-node', + key: 'collapse-expand-react-node', + enable: false, + trigger: 'icon', + iconRender: function (isCollapsed: boolean, data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + return ( + + ); + }, + iconPlacement: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + return side === 'left' ? 'left' : 'right'; + }, + }, + ], + layout: { + type: 'mindmap', + direction: 'H', + preLayout: false, + getWidth: () => 120, + getHGap: () => 64, + }, + animation: { + duration: 500, + }, +}; + +export function getMindMapOptions({ + type, + direction, + nodeMinWidth, + nodeMaxWidth, + labelField, +}: Pick): GraphOptions { + let options: GraphOptions = {}; + + if (type === 'boxed') { + const minWidth = nodeMinWidth || 120; + const maxWidth = nodeMaxWidth || 300; + + options = { + node: { + style: { + component: (data: NodeData) => { + const depth = data.depth; + const color = data.style?.color; + const label = formatLabel(data, labelField); + const { font } = getBoxedTextNodeStyle(label, minWidth, maxWidth, depth); + const props = { text: label, color, maxWidth, font } as TextNodeProps; + Object.assign( + props, + depth === 0 + ? { type: 'filled', color: '#f1f4f5', style: { color: '#252525' } } + : depth === 1 + ? { type: 'filled' } + : { type: 'outlined' }, + ); + return ; + }, + size: (data: NodeData) => { + const label = formatLabel(data, labelField); + return getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + }, + dx: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + const label = formatLabel(data, labelField); + const [width] = getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return side === 'left' ? -width : side === 'center' ? -width / 2 : 0; + }, + ports: [{ placement: 'left' }, { placement: 'right' }], + }, + }, + edge: { + style: { + stroke: function (data) { + const source = this.getNodeData(data.source); + return get(source, 'style.color', '#99ADD1') as string; + }, + }, + }, + transforms: (prev) => [...prev, { type: 'assign-color-by-branch', key: 'assign-color-by-branch' }], + layout: { + type: 'mindmap', + getHeight: (data) => { + const label = formatLabel(data, labelField); + const [, height] = getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return height; + }, + getVGap: () => 14, + }, + }; + } else if (type === 'linear') { + const minWidth = nodeMinWidth || 0; + const maxWidth = nodeMaxWidth || 300; + + options = { + node: { + style: { + component: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + const depth = data.depth; + const color = data.style?.color; + const label = formatLabel(data, labelField); + const { font } = getLinearTextNodeStyle(label, minWidth, maxWidth, depth); + const props = { text: label, color, maxWidth, font } as TextNodeProps; + Object.assign( + props, + depth === 0 + ? { type: 'filled', color: '#f1f4f5', style: { color: '#252525' } } + : { + type: 'underlined', + style: side === 'left' ? { textAlign: 'right' } : side === 'center' ? { textAlign: 'center' } : {}, + }, + ); + return ; + }, + size: (data: NodeData) => { + const label = formatLabel(data, labelField); + return getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + }, + dx: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + const label = formatLabel(data, labelField); + const [width] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return side === 'left' ? -width : side === 'center' ? -width / 2 : 0; + }, + dy: function (data: NodeData) { + const label = formatLabel(data, labelField); + const [, height] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return height / 2; + }, + ports: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + return side === 'center' + ? [{ placement: 'left' }, { placement: 'right' }] + : [{ placement: 'left-bottom' }, { placement: 'right-bottom' }]; + }, + }, + }, + edge: { + style: { + stroke: function (data) { + const target = this.getNodeData(data.target); + return get(target, 'style.color', '#99ADD1') as string; + }, + }, + }, + layout: { + type: 'mindmap', + getHeight: (data) => { + const label = formatLabel(data, labelField); + const [, height] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return height; + }, + getVGap: () => 12, + }, + transforms: (prev) => [ + ...prev.filter((t) => (t as any).key !== 'collapse-expand-react-node'), + { + type: 'assign-color-by-branch', + key: 'assign-color-by-branch', + }, + { + ...(prev.find((t) => (t as any).key === 'collapse-expand-react-node') as any), + iconOffsetY: (data) => { + if (data.depth === 0) return 0; + const label = formatLabel(data, labelField); + const [, height] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; + return height / 2; + }, + }, + ], + }; + } else { + const PADDING = [24, 16]; + options = { + node: { + style: { + component: (data) => { + const label = formatLabel(data, labelField); + return ; + }, + size: (data) => { + const label = formatLabel(data, labelField); + return measureTextSize(label, PADDING); + }, + dx: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + const label = formatLabel(data, labelField); + const [width] = measureTextSize(label, PADDING); + return side === 'left' ? -width : side === 'center' ? -width / 2 : 0; + }, + ports: [{ placement: 'left' }, { placement: 'right' }], + }, + }, + layout: { + type: 'mindmap', + getHeight: (data) => { + const label = formatLabel(data, labelField); + const [, height] = measureTextSize(label, PADDING); + return height; + }, + }, + }; + } + + if (direction) { + options.layout ||= {} as SingleLayoutOptions; + (options.layout as SingleLayoutOptions).direction = + direction === 'alternate' ? 'H' : direction === 'left' ? 'RL' : 'LR'; + } + + return options; +} diff --git a/packages/graphs/src/components/mind-map/types.ts b/packages/graphs/src/components/mind-map/types.ts new file mode 100644 index 000000000..d6c87712f --- /dev/null +++ b/packages/graphs/src/components/mind-map/types.ts @@ -0,0 +1,40 @@ +import type { GraphData, NodeData, TreeData } from '@antv/g6'; +import type { GraphOptions } from '../../types'; + +export interface MindMapOptions extends Omit { + /** + * The data of the mind map. + */ + data?: GraphData | TreeData; + /** + * The default expand level of the mind map. If not set, all nodes will be expanded. + */ + defaultExpandLevel?: number; + /** + * The type of the mind map + * @default 'default' + */ + type?: 'default' | 'linear' | 'boxed'; + /** + * The direction of the mind map. + * @default 'alternate' + */ + direction?: 'left' | 'right' | 'alternate'; + /** + * The minimum width of the node. + * @default 0(default) 120(boxed) + */ + nodeMinWidth?: number; + /** + * The maximum width of the node. + * @default 300 + */ + nodeMaxWidth?: number; + /** + * Selects a field from the data to use as the label for the node. + * If a string is provided, it will select the field as `data[labelField]`. + * If a function is provided, it will call the function with the data and use the returned value. + * @default (data) => data.id + */ + labelField?: string | ((data: NodeData) => string); +} diff --git a/packages/graphs/src/components/network-graph/index.tsx b/packages/graphs/src/components/network-graph/index.tsx new file mode 100644 index 000000000..e7033b767 --- /dev/null +++ b/packages/graphs/src/components/network-graph/index.tsx @@ -0,0 +1,28 @@ +import type { Graph } from '@antv/g6'; +import React, { + forwardRef, + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS } from './options'; +import type { NetworkGraphOptions } from './types'; + +export const NetworkGraph: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const options = useMemo(() => mergeOptions(COMMON_OPTIONS, DEFAULT_OPTIONS, props), [props]); + + return ( + + {children} + + ); +}); + +export type { NetworkGraphOptions }; diff --git a/packages/graphs/src/components/network-graph/options.tsx b/packages/graphs/src/components/network-graph/options.tsx new file mode 100644 index 000000000..d8fc3d977 --- /dev/null +++ b/packages/graphs/src/components/network-graph/options.tsx @@ -0,0 +1,23 @@ +import type { NetworkGraphOptions } from './types'; + +const SIZE = 32; + +export const DEFAULT_OPTIONS: NetworkGraphOptions = { + node: { + type: 'circle', + }, + layout: { + type: 'd3-force', + link: { + distance: SIZE * 4, + }, + collide: { + radius: SIZE, + }, + manyBody: { + strength: -SIZE * 22, + }, + x: {}, + y: {}, + }, +}; diff --git a/packages/graphs/src/components/network-graph/types.ts b/packages/graphs/src/components/network-graph/types.ts new file mode 100644 index 000000000..d5e0ee9dc --- /dev/null +++ b/packages/graphs/src/components/network-graph/types.ts @@ -0,0 +1,3 @@ +import type { GraphOptions } from '../../types'; + +export interface NetworkGraphOptions extends GraphOptions {} diff --git a/packages/graphs/src/components/organization-chart/index.tsx b/packages/graphs/src/components/organization-chart/index.tsx new file mode 100644 index 000000000..24e957470 --- /dev/null +++ b/packages/graphs/src/components/organization-chart/index.tsx @@ -0,0 +1,37 @@ +import type { Graph } from '@antv/g6'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; +import { BaseGraph } from '../../core/base-graph'; +import { COMMON_OPTIONS } from '../../core/constants'; +import { mergeOptions } from '../../core/utils/options'; +import { DEFAULT_OPTIONS, getOrganizationChartOptions } from './options'; +import type { OrganizationChartOptions } from './types'; + +export const OrganizationChart: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const options = useMemo(() => { + const { direction = 'vertical', labelField, ...restProps } = props; + const options = mergeOptions( + COMMON_OPTIONS, + DEFAULT_OPTIONS, + getOrganizationChartOptions({ direction, labelField }), + restProps, + ); + return options; + }, [props]); + + return ( + + {children} + + ); +}); + +export type { OrganizationChartOptions }; diff --git a/packages/graphs/src/components/organization-chart/options.tsx b/packages/graphs/src/components/organization-chart/options.tsx new file mode 100644 index 000000000..5e0dbf824 --- /dev/null +++ b/packages/graphs/src/components/organization-chart/options.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { RCNode } from '../../core/base'; +import { formatLabel } from '../../core/utils/label'; +import type { GraphOptions } from '../../types'; +import { OrganizationChartOptions } from './types'; + +const { TextNode } = RCNode; + +export const DEFAULT_OPTIONS: GraphOptions = { + node: { + type: 'react', + state: { + active: { + halo: false, + }, + selected: { + halo: false, + }, + }, + }, + edge: { + type: 'polyline', + style: { + lineWidth: 2, + router: { + type: 'orth', + }, + }, + }, + layout: { + type: 'dagre', + animation: false, + }, + transforms: ['translate-react-node-origin'], +}; + +export function getOrganizationChartOptions({ + direction, + labelField, +}: Pick): GraphOptions { + const options: GraphOptions = { + node: { + style: { + component: (data) => { + const label = formatLabel(data, labelField); + return ; + }, + size: [100, 40], + ports: + direction === 'vertical' + ? [{ placement: 'top' }, { placement: 'bottom' }] + : [{ placement: 'left' }, { placement: 'right' }], + }, + }, + transforms: (prev) => [ + ...prev, + { + type: 'collapse-expand-react-node', + key: 'collapse-expand-react-node', + iconPlacement: direction === 'vertical' ? 'bottom' : 'right', + enable: false, + refreshLayout: true, + }, + ], + layout: { + type: 'dagre', + rankdir: direction === 'vertical' ? 'TB' : 'LR', + }, + }; + + return options; +} diff --git a/packages/graphs/src/components/organization-chart/types.ts b/packages/graphs/src/components/organization-chart/types.ts new file mode 100644 index 000000000..1ca4104ed --- /dev/null +++ b/packages/graphs/src/components/organization-chart/types.ts @@ -0,0 +1,17 @@ +import type { NodeData } from '@antv/g6'; +import type { GraphOptions } from '../../types'; + +export interface OrganizationChartOptions extends GraphOptions { + /** + * The direction of the organization chart. + * @default 'vertical' + */ + direction?: 'vertical' | 'horizontal'; + /** + * Selects a field from the data to use as the label for the node. + * If a string is provided, it will select the field as `data[labelField]`. + * If a function is provided, it will call the function with the data and use the returned value. + * @default (data) => data.id + */ + labelField?: string | ((data: NodeData) => string); +} diff --git a/packages/graphs/src/components/organization-graph/customItem.ts b/packages/graphs/src/components/organization-graph/customItem.ts deleted file mode 100644 index a01788250..000000000 --- a/packages/graphs/src/components/organization-graph/customItem.ts +++ /dev/null @@ -1,180 +0,0 @@ -import G6, { IGroup, LabelStyle } from '@antv/g6'; -import { defaultMargin, defaultLabelStyle, defaultCardStyle } from '../../constants'; -import { getStyle, getCssPadding, createMarker } from '../../utils'; -import { CardNodeCfg, OrgItem } from '../../interface'; - -// 组织架构图 -export const registerOrganizationCardNode = () => { - const defaultIconStyle = { - width: 12, - height: 12, - }; - G6.registerNode( - 'organization-card', - { - draw(cfg: Omit | undefined = {}, group: IGroup | undefined) { - const { value: originValue = {}, nodeCfg, markerCfg, _draggable: draggable } = cfg; - const value = { ...(originValue as OrgItem & { text?: string; value?: string }) }; - let isOld = false; - /** 兼容历史数据 */ - if (value.text) { - isOld = true; - value.name = value.text as string; - } - if (value.value) { - isOld = true; - value.title = value.value as string; - } - const { style, padding = 0, label = {}, autoWidth, customContent } = nodeCfg as CardNodeCfg; - const { style: labelStyle } = label; - const paddingArray = getCssPadding(padding); - let size = (cfg?.size || [100, 30]) as number[]; - if (typeof size === 'number') size = [size, size]; - let height = 0; // 统计容器总高度,动态设置,宽度不做调整 - const contentWidth = size[0] - paddingArray[1] - paddingArray[3]; - // card box - const cardStyle = getStyle(style, cfg, group); - const shape = group!.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: size[0], - height: size[1], - ...defaultCardStyle, - ...cardStyle, - }, - name: 'main-box', - draggable, - }); - - if (value) { - // 兼容历史数据 - const getKey = (key) => { - if (isOld) { - const keys = { - name: 'text', - title: 'value', - }; - return keys[key]; - } - return key; - }; - height += paddingArray[0]; - const createRowItems = ( - item: OrgItem, - contentWidth: number, - startX: number, - index: number | string = 0, - ): number[] => { - let iconWidth = 0; - const rowHeight: number[] = []; - const keys = ['icon', 'name', 'title']; - const getXY = (type: string, layoutCfg: LabelStyle) => { - const { fontSize = 12 } = layoutCfg; - let x = 0; - let y = 0; - const offsetX = autoWidth - ? iconWidth - ? iconWidth + paddingArray[3] - : iconWidth - : (contentWidth + iconWidth) / 2; - switch (type) { - case 'icon': - x = startX; - y = height; - break; - case 'name': - x = startX + offsetX; - y = item.title ? paddingArray[0] : (size[1] - fontSize) / 2; - break; - case 'title': - x = startX + offsetX; - y = item.name ? paddingArray[0] + rowHeight[1] + defaultMargin : (size[1] - fontSize) / 2; - break; - default: - break; - } - return { x, y }; - }; - keys.forEach((key: string, keyIndex: number) => { - const isIcon = key.startsWith('icon'); - const shapeStyle = getStyle(labelStyle, cfg, group, getKey(key)); - if (key === 'icon' && item[key]) { - iconWidth = shapeStyle.width || 32; - } - const keyShape = group!.addShape(isIcon ? 'image' : 'text', { - attrs: { - textBaseline: 'top', - textAlign: autoWidth ? 'start' : 'center', - ...getXY(key, shapeStyle), - text: item[key], - img: item[key], - ...(isIcon ? defaultIconStyle : defaultLabelStyle), - ...shapeStyle, - }, - name: `${key}-${index}-${keyIndex}`, - draggable, - }); - rowHeight.push(keyShape.getBBox().height); - }); - return rowHeight; - }; - const createItems = (item: OrgItem, index: number = 0) => { - const itemsHeight: number[] = []; - if (customContent) { - itemsHeight.push( - customContent(item, group, { - startX: paddingArray[3], - startY: height, - width: contentWidth, - }) ?? 0, - ); - } else { - itemsHeight.push(...createRowItems(item, contentWidth, paddingArray[3], index)); - } - height += Math.max(...itemsHeight); - }; - - createItems(value); - } - - shape?.attr('height', Math.max(height + paddingArray[2], size[1])); - if (autoWidth) { - const maxX = Math.max( - size[0], - ...(group?.getChildren()?.map((childrenShape) => { - return (childrenShape.getBBox().maxX || 0) + paddingArray[1]; - }) as number[]), - ); - shape?.attr('width', maxX); - } - // collapse marker - if (markerCfg) { - const { collapsed: stateCollapsed } = group?.get('item')?.getModel() ?? {}; - const { width: shapeWidth, height: shapeHeight } = shape.getBBox(); - const { - show, - position = 'right', - collapsed, - style: markerStyle, - } = typeof markerCfg === 'function' ? markerCfg(cfg, group) : markerCfg; - createMarker( - { - show, - position, - collapsed: stateCollapsed ?? collapsed, // 优先使用内部状态 - style: markerStyle, - }, - group, - [shapeWidth, shapeHeight], - ); - shape.attr('defaultCollapsed', collapsed); - } - - return shape; - }, - update: undefined, - }, - 'single-node', - ); -}; diff --git a/packages/graphs/src/components/organization-graph/index.tsx b/packages/graphs/src/components/organization-graph/index.tsx deleted file mode 100644 index 7e094007f..000000000 --- a/packages/graphs/src/components/organization-graph/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; -import { ChartLoading } from '../../utils'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { defaultStateStyles } from '../../constants'; -import { bindEvents } from '../file-tree-graph/events'; -import { registerOrganizationCardNode } from './customItem'; -import { IGroup, CommonConfig, ShapeCfg, Shape, NodeConfig, IGraph, NodeData, OrgItem } from '../../interface'; - -export type OrganizationGraphData = NodeData; -export interface OrganizationGraphConfig extends Omit { - data: OrganizationGraphData; -} - -registerOrganizationCardNode(); - -const defaultNodeStyle = { - fill: '#91d5ff', - stroke: '#40a9ff', - radius: 2, -}; - -const defaultLayout = { - type: 'compactBox', - direction: 'TB', - getId: function getId(d: NodeConfig) { - return d.id; - }, - getHeight: function getHeight() { - return 16; - }, - getWidth: function getWidth() { - return 16; - }, - getVGap: function getVGap() { - return 40; - }, - getHGap: function getHGap() { - return 70; - }, -}; -const defaultProps = { - nodeCfg: { - type: 'organization-card', - size: [100, 44], - style: defaultNodeStyle, - padding: 6, - anchorPoints: [ - [0.5, 0], - [0.5, 1], - ], - nodeStateStyles: defaultStateStyles, - label: { - style: (cfg: Shape | ShapeCfg, group: IGroup | IGraph | undefined, type: string | undefined) => { - const styles = { - icon: { - width: 32, - height: 32, - }, - title: { - fill: '#fff', - }, - name: { - fill: '#000', - }, - }; - return type ? styles[type] : {}; - }, - }, - }, - edgeCfg: { - type: 'polyline', - endArrow: { - type: 'triangle', - fill: '#91d5ff', - }, - edgeStateStyles: defaultStateStyles, - style: { - stroke: '#91d5ff', - }, - }, - behaviors: ['zoom-canvas', 'drag-canvas'], - layout: defaultLayout, - animate: true, - markerPosition: 'right' as 'right', - autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, -}; - -const OrganizationGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('TreeGraph', rest, { name: 'OrganizationGraph', bindEvents }); - - return ( - - {loading && } -
- - ); -}; - -export default OrganizationGraph; diff --git a/packages/graphs/src/components/radial-graph/events.ts b/packages/graphs/src/components/radial-graph/events.ts deleted file mode 100644 index 62f9e76b1..000000000 --- a/packages/graphs/src/components/radial-graph/events.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { GraphData } from '@antv/g6'; -import { findIndex } from '@antv/util'; -import { RadialLayout } from '@antv/layout'; -import { ITreeGraph, IG6GraphEvent, INode, NodeConfig, IEdge, FetchLoading } from '../../interface'; -import { radialSectorLayout } from '../../layout'; -import { createFetchLoading, closeFetchLoading } from '../../utils'; - -/** sector layout */ -export const bindRadialExplore = (params: { - graph: ITreeGraph; - asyncData: (nodeCfg: NodeConfig) => GraphData; - layout?: RadialLayout; - fetchLoading?: FetchLoading; -}) => { - const { graph, asyncData, layout: layoutCfg, fetchLoading } = params; - const onDblClick = async (e: IG6GraphEvent) => { - const item = e.item as INode; - const itemModel = item.getModel(); - createFetchLoading(itemModel as NodeConfig, fetchLoading); - const newData = await asyncData(item.getModel() as NodeConfig); - closeFetchLoading(); - const nodes = graph.getNodes(); - const edges = graph.getEdges(); - const { x, y } = itemModel; - const centerNodeId = graph.get('centerNode'); - const centerNode = centerNodeId ? graph.findById(centerNodeId) : nodes[0]; - const { x: centerX, y: centerY } = centerNode.getModel(); - // the max degree about foces(clicked) node in the original data - const pureNodes = newData.nodes.filter( - (item) => findIndex(nodes, (t: INode) => t.getModel().id === item.id) === -1, - ); - const pureEdges = newData.edges.filter( - (item) => - findIndex(edges, (t: IEdge) => { - const { source, target } = t.getModel(); - return source === item.source && target === item.target; - }) === -1, - ); - - // for graph.changeData() - const allNodeModels: GraphData['nodes'] = []; - const allEdgeModels: GraphData['edges'] = []; - pureNodes.forEach((nodeModel) => { - // set the initial positions of the new nodes to the focus(clicked) node - nodeModel.x = itemModel.x; - nodeModel.y = itemModel.y; - graph.addItem('node', nodeModel); - }); - - // add new edges to graph - pureEdges.forEach((em, i) => { - graph.addItem('edge', em); - }); - - edges.forEach((e: IEdge) => { - allEdgeModels.push(e.getModel()); - }); - nodes.forEach((n: INode) => { - allNodeModels.push(n.getModel() as NodeConfig); - }); - // 这里使用了引用类型 - radialSectorLayout({ - center: [centerX, centerY], - eventNodePosition: [x, y], - nodes: nodes.map((n) => n.getModel() as NodeConfig), - layoutNodes: pureNodes, - options: layoutCfg as any, - }); - graph.positionsAnimate(); - graph.data({ - nodes: allNodeModels, - edges: allEdgeModels, - }); - }; - graph.on('node:dblclick', (e: IG6GraphEvent) => { - onDblClick(e); - }); -}; diff --git a/packages/graphs/src/components/radial-graph/index.tsx b/packages/graphs/src/components/radial-graph/index.tsx deleted file mode 100644 index 071730945..000000000 --- a/packages/graphs/src/components/radial-graph/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import { RadialLayout } from '@antv/layout'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { ChartLoading } from '../../utils'; -import { CommonConfig, GraphData, NodeCfg, NodeConfig, FetchLoading } from '../../interface'; -import { defaultFlowGraphAnchorPoints, defaultStateStyles, defaultNodeStyle } from '../../constants'; -import { bindRadialExplore } from './events'; - -export interface RadialGraphConfig extends Omit>, 'data'>, FetchLoading { - data: GraphData; - nodeCfg?: NodeCfg & { - /** 点击展开时异步获取数据 */ - asyncData?: (nodeCfg: NodeConfig) => Promise; - }; -} - -const defaultLayout = { - type: 'radial', - unitRadius: 50, - preventOverlap: true, - maxPreventOverlapIteration: 100, -}; - -const defaultProps = { - nodeCfg: { - type: 'circle', - size: 10, - anchorPoints: defaultFlowGraphAnchorPoints, - linkCenter: true, - nodeStateStyles: defaultStateStyles, - style: defaultNodeStyle, - }, - edgeCfg: { - type: 'line', - edgeStateStyles: defaultStateStyles, - }, - behaviors: ['zoom-canvas', 'drag-canvas'], - layout: defaultLayout, - animate: true, - markerPosition: 'right' as 'right', - autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, -}; - -const RadialGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('Graph', rest, { name: 'RadialGraph', bindEvents: bindRadialExplore }); - - return ( - - {loading && } -
- - ); -}; - -export default RadialGraph; diff --git a/packages/graphs/src/components/radial-tree-graph/index.tsx b/packages/graphs/src/components/radial-tree-graph/index.tsx deleted file mode 100644 index 45347e3fa..000000000 --- a/packages/graphs/src/components/radial-tree-graph/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { DendrogramLayout } from '../../layout'; -import ErrorBoundary from '../../errorBoundary'; -import useGraph from '../../hooks/useGraphs'; -import useProps from '../../hooks/useProps'; -import { ChartLoading } from '../../utils'; - -import { CommonConfig, TreeGraphData } from '../../interface'; -import { defaultFlowGraphAnchorPoints, defaultStateStyles, defaultNodeStyle } from '../../constants'; - -export interface RadialTreeGraphConfig extends Omit, 'data'> { - data: TreeGraphData; -} - -const defaultLayout = { - type: 'dendrogram', - direction: 'LR', - nodeSep: 20, - rankSep: 100, - radial: true, -}; - -const defaultProps = { - nodeCfg: { - type: 'circle', - size: 30, - anchorPoints: defaultFlowGraphAnchorPoints, - linkCenter: true, - nodeStateStyles: defaultStateStyles, - style: defaultNodeStyle, - }, - edgeCfg: { - type: 'line', - edgeStateStyles: defaultStateStyles, - }, - behaviors: ['zoom-canvas', 'drag-canvas'], - layout: defaultLayout, - animate: true, - markerPosition: 'right' as 'right', - autoFit: true, - fitCenter: true, - style: { - position: 'relative' as React.CSSProperties['position'], - height: 'inherit', - backgroundColor: '#fff', - }, -}; - -const RadialTreeGraph: React.FC = (props) => { - const { uProps } = useProps(props, defaultProps); - const { className, style, loading, loadingTemplate, errorTemplate, ...rest } = uProps; - const { container } = useGraph('TreeGraph', rest, { name: 'RadialTreeGraph' }); - - return ( - - {loading && } -
- - ); -}; - -export default RadialTreeGraph; diff --git a/packages/graphs/src/constants/event.ts b/packages/graphs/src/constants/event.ts deleted file mode 100644 index 3d1948daf..000000000 --- a/packages/graphs/src/constants/event.ts +++ /dev/null @@ -1 +0,0 @@ -export const MARKER_CLICK = 'marker:click'; diff --git a/packages/graphs/src/constants/index.ts b/packages/graphs/src/constants/index.ts deleted file mode 100644 index a828cac3d..000000000 --- a/packages/graphs/src/constants/index.ts +++ /dev/null @@ -1,73 +0,0 @@ -export { MARKER_CLICK } from './event'; -export const stateColor = '#1890ff'; -// 默认交互状态 -export const defaultStateStyles = { - hover: { - stroke: stateColor, - lineWidth: 2, - }, -}; - -// card 默认节点大小 -export const defaultNodeSize = [120, 40]; - -// 默认节点样式 -export const defaultNodeStyle = { - stroke: '#40a9ff', -}; - -// 默认 anchor 连接点 -export const defaultFlowGraphAnchorPoints = [ - [0, 0.5], - [1, 0.5], -]; - -// card body|footer 默认样式 -export const defaultLabelStyle = { - fill: '#000', - fontSize: 12, -}; -export const defaultLineLabelStyle = { - fill: '#000', - fontSize: 12, - textAlign: 'start', - textBaseline: 'middle', -}; -// 缩略图默认配置 -export const defaultMinimapCfg = { - show: false, - size: [150, 100], - type: 'keyShape', -}; - -// card 默认样式 -export const defaultCardStyle = { - fill: '#fff', - stroke: '#40a9ff', - radius: 2, -}; - -// card 内部 padding | margin | 行间距 -export const defaultMargin = 6; - -// 打标前缀 -export const prefix = 'g'; - -// status bar 的默认宽度 -export const defaultStatusBarWidth = 4; - -export const defaultToolbarStyle = { - position: 'absolute', - right: '12px', - top: '12px', - display: 'flex', - flexDirection: 'column', - padding: '6px', - borderRadius: '2px', - fontSize: '24px', - textAlign: 'center', - lineHeight: '24px', - color: 'rgba(0,0,0,.65)', - backgroundColor: '#fff', - boxShadow: '0 0 3px #ccc', -} as React.CSSProperties; diff --git a/packages/graphs/src/core/base-graph.tsx b/packages/graphs/src/core/base-graph.tsx new file mode 100644 index 000000000..1296f4763 --- /dev/null +++ b/packages/graphs/src/core/base-graph.tsx @@ -0,0 +1,45 @@ +import { ChartLoading, ErrorBoundary } from '@ant-design/charts-util'; +import type { GraphOptions as G6GraphOptions, Graph } from '@antv/g6'; +import { Graphin } from '@antv/graphin'; +import { isEmpty } from 'lodash'; +import React, { + ForwardRefExoticComponent, + PropsWithChildren, + PropsWithoutRef, + RefAttributes, + forwardRef, + useImperativeHandle, + useRef, +} from 'react'; +import type { GraphOptions } from '../types'; + +export const BaseGraph: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes +> = forwardRef>(({ children, ...props }, ref) => { + const { containerStyle, className, onInit, onReady, onDestroy, errorTemplate, loading, loadingTemplate, ...options } = + props; + const graphRef = useRef(null); + + useImperativeHandle(ref, () => graphRef.current!); + + return ( + + {loading && } + {!isEmpty(options.data) && ( + { + graphRef.current = ref; + }} + className={className} + style={containerStyle} + options={options as G6GraphOptions} + onInit={onInit} + onReady={onReady} + onDestroy={onDestroy} + > + {children} + + )} + + ); +}); diff --git a/packages/graphs/src/core/base/collapse-expand-icon/arrow-count-icon.tsx b/packages/graphs/src/core/base/collapse-expand-icon/arrow-count-icon.tsx new file mode 100644 index 000000000..fe155cf9f --- /dev/null +++ b/packages/graphs/src/core/base/collapse-expand-icon/arrow-count-icon.tsx @@ -0,0 +1,125 @@ +import type { Graph, NodeData } from '@antv/g6'; +import React, { FC } from 'react'; +import styled from 'styled-components'; + +interface ArrowCountIconProps extends Pick, 'className' | 'style'> { + /** + * The placement of the icon + */ + placement?: 'top' | 'right' | 'bottom' | 'left'; + /** + * G6 Graph instance + */ + graph: Graph; + /** + * Node data + */ + data: NodeData; + /** + * Whether the node is collapsed + */ + isCollapsed: boolean; + /** + * Determines the count to show when the node is collapsed + */ + countType?: 'descendant' | 'children'; +} + +const StyledWrapper = styled.div<{ + $color: string; + $isCollapsed: boolean; + $placement: 'top' | 'right' | 'bottom' | 'left'; +}>` + ${({ $placement }) => { + switch ($placement) { + case 'top': + return 'transform: translate(-50%, -100%); flex-direction: column-reverse;'; + case 'right': + return 'transform: translate(0, -50%);'; + case 'left': + return 'transform: translate(-100%, -50%); flex-direction: row-reverse;'; + default: + return 'transform: translate(-50%, 0); flex-direction: column;'; + } + }} + + .arrow-count-icon-bar { + ${({ $placement }) => { + const isVertical = $placement === 'top' || $placement === 'bottom'; + return isVertical ? 'width: 3px; height: 8px; margin: 0 7px;' : 'width: 8px; height: 3px; margin: 7px 0;'; + }} + background-color: ${({ $color }) => $color}; + } + + .arrow-count-icon-circle { + width: 16px; + height: 16px; + color: #fff; + font-weight: 600; + font-size: 10px; + line-height: ${({ $isCollapsed }) => ($isCollapsed ? '16px' : '14px')}; + text-align: center; + background-color: ${({ $color }) => $color}; + border-radius: 50%; + } + + .arrow-count-icon-circle-arrow { + width: 16px; + height: 16px; + transform: ${({ $isCollapsed, $placement }) => { + if ($isCollapsed) return 'none'; + switch ($placement) { + case 'top': + return 'translateY(1px) rotate(-90deg)'; + case 'right': + return 'translateX(-1px) rotate(0deg)'; + case 'left': + return 'translateX(1px) rotate(180deg)'; + default: + return 'translateY(-1px) rotate(90deg)'; + } + }}; + } + + display: ${({ $isCollapsed }) => ($isCollapsed ? 'flex' : 'none')}; + + .collapsible-node-wrapper:hover & { + display: flex; + } +`; + +export const ArrowCountIcon: FC = (props) => { + const { className, style, graph, data, isCollapsed, countType = 'descendant', placement = 'bottom' } = props; + const color = (graph.getNodeData(data.id).style?.color as string) || '#99ADD1'; + const count = (countType === 'descendant' ? graph.getDescendantsData(data.id) : graph.getChildrenData(data.id)) + .length; + + return ( + +
+
+ {isCollapsed ? ( + count + ) : ( +
+ + + +
+ )} +
+ + ); +}; diff --git a/packages/graphs/src/core/base/collapse-expand-icon/index.tsx b/packages/graphs/src/core/base/collapse-expand-icon/index.tsx new file mode 100644 index 000000000..02ffd7d30 --- /dev/null +++ b/packages/graphs/src/core/base/collapse-expand-icon/index.tsx @@ -0,0 +1,2 @@ +export { ArrowCountIcon } from './arrow-count-icon'; +export { PlusMinusIcon } from './plus-minus-icon'; diff --git a/packages/graphs/src/core/base/collapse-expand-icon/plus-minus-icon.tsx b/packages/graphs/src/core/base/collapse-expand-icon/plus-minus-icon.tsx new file mode 100644 index 000000000..76ffa21ed --- /dev/null +++ b/packages/graphs/src/core/base/collapse-expand-icon/plus-minus-icon.tsx @@ -0,0 +1,33 @@ +import React, { FC } from 'react'; +import styled from 'styled-components'; + +interface PlusMinusIconProps extends Pick, 'className' | 'style'> { + /** + * Whether the node is collapsed + */ + isCollapsed: boolean; +} + +const StyledWrapper = styled.div` + height: 16px; + width: 16px; + transform: translate(-50%, -50%); + background: #fff; + border-radius: 50%; + border: 2px solid #99add1; + color: #99add1; + font-weight: 800; + line-height: 14px; + text-align: center; + box-sizing: content-box; +`; + +export const PlusMinusIcon: FC = (props) => { + const { isCollapsed, style, className } = props; + + return ( + + {isCollapsed ? '+' : '-'} + + ); +}; diff --git a/packages/graphs/src/core/base/index.ts b/packages/graphs/src/core/base/index.ts new file mode 100644 index 000000000..6a9682509 --- /dev/null +++ b/packages/graphs/src/core/base/index.ts @@ -0,0 +1,5 @@ +import * as CollapseExpandIcon from './collapse-expand-icon'; +import * as RCNode from './node'; + +export type { OrganizationChartNodeProps, TextNodeProps } from './node'; +export { CollapseExpandIcon, RCNode }; diff --git a/packages/graphs/src/core/base/node/index.tsx b/packages/graphs/src/core/base/node/index.tsx new file mode 100644 index 000000000..42e2b3b3d --- /dev/null +++ b/packages/graphs/src/core/base/node/index.tsx @@ -0,0 +1,2 @@ +export { OrganizationChartNode, type OrganizationChartNodeProps } from './organization-chart-node'; +export { TextNode, type TextNodeProps } from './text-node'; diff --git a/packages/graphs/src/core/base/node/organization-chart-node.tsx b/packages/graphs/src/core/base/node/organization-chart-node.tsx new file mode 100644 index 000000000..85e808ece --- /dev/null +++ b/packages/graphs/src/core/base/node/organization-chart-node.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import styled, { css } from 'styled-components'; + +export interface OrganizationChartNodeProps extends Pick, 'className' | 'style'> { + /** + * Name of the person + */ + name: string; + /** + * Position of the person + */ + position: string; + /** + * Working status of the person + */ + status?: string; + /** + * Whether the node is hovered + */ + isActive?: boolean; +} + +const StyledWrapper = styled.div<{ $color?: string; $isActive?: boolean }>` + height: inherit; + width: inherit; + border-radius: 8px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.1); + position: relative; + border: none; + background-color: #fff; + box-sizing: content-box; + + ${(props) => + props.$isActive && + css` + transform: translate(-3px, -3px); + border: 2px solid #1783ff; + `} + + .org-chart-node-line { + width: 100%; + height: 6px; + background-color: ${(props) => props.$color}; + border-radius: 8px 8px 0 0; + } + + .org-chart-node-content { + height: calc(100% - 6px); + margin: 0 16px 3px; + display: flex; + align-items: center; + + &-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 16px; + background-color: ${(props) => props.$color}; + font-weight: 600; + font-size: 18px; + text-align: center; + line-height: 40px; + color: #fff; + } + + &-detail { + width: calc(100% - 56px); + } + + &-name { + color: #242424; + font-weight: 600; + font-size: 18px; + margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &-post { + color: #616161; + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } +`; + +export const OrganizationChartNode: React.FC = (props) => { + const { name, position, status = 'online', isActive, className, style } = props; + + const colorMap = { + online: '#1783FF', + busy: '#00C9C9', + offline: '#F08F56', + }; + + return ( + +
+
+
{name.slice(0, 1)}
+
+
{name}
+ {position &&
{position}
} +
+
+
+ ); +}; diff --git a/packages/graphs/src/core/base/node/text-node.tsx b/packages/graphs/src/core/base/node/text-node.tsx new file mode 100644 index 000000000..757182462 --- /dev/null +++ b/packages/graphs/src/core/base/node/text-node.tsx @@ -0,0 +1,142 @@ +import { measureTextWidth } from '@ant-design/charts-util'; +import React, { FC } from 'react'; +import styled, { css } from 'styled-components'; +import { darkenHexColor, hexToRgba } from '../../utils/color'; + +const StyledWrapper = styled.div<{ + $type: TextNodeProps['type']; + $color: string; + $borderWidth: number; + $isActive: boolean; + $isSelected: boolean; +}>` + position: relative; + display: flex; + align-items: center; + justify-content: center; + overflow-wrap: anywhere; + line-height: 1.5em; + text-align: center; + height: inherit; + width: inherit; + box-sizing: content-box; + font-size: 14px; + + ${({ $type, $color, $borderWidth }) => { + switch ($type) { + case 'normal': + return ` + color: ${$color}; + `; + case 'filled': + return css` + color: #fff; + background-color: ${$color}; + border-radius: 8px; + `; + case 'outlined': + return css` + height: calc(100% - 2 * ${$borderWidth}px); + width: calc(100% - 2 * ${$borderWidth}px); + color: ${$color}; + background-color: #fff; + border: ${$borderWidth}px solid ${$color}; + border-radius: 8px; + `; + case 'underlined': + return css` + height: calc(100% - ${$borderWidth}px / 2); + width: inherit; + border-bottom: ${$borderWidth}px solid ${$color}; + background-color: #fff; + color: ${$color}; + `; + } + }} + + ${({ $isActive, $isSelected, $borderWidth, $color }) => + ($isActive || $isSelected) && + css` + height: calc(100% - 2 * ${$borderWidth}px); + width: calc(100% - 2 * ${$borderWidth}px); + border: ${$borderWidth}px solid ${darkenHexColor($color, 100)}; + ${$isSelected && `box-shadow: 0 0 0 2px ${hexToRgba($color, 0.1)};`} + `} +`; + +export interface TextNodeProps extends Pick, 'className' | 'style'> { + /** + * Node type + * @default 'normal' + */ + type?: 'normal' | 'filled' | 'outlined' | 'underlined'; + /** + * Node text + */ + text?: string; + /** + * Node color + * @default '#1783ff' + */ + color?: string; + /** + * Node max width. If the text width is greater than the max width, the text will be wrapped. + * @default Infinity + */ + maxWidth?: number; + /** + * Node border width + * @default 2 + */ + borderWidth?: number; + /** + * Node font style (fontWeight, fontSize, fontFamily, fontStyle, fontVariant) + */ + font?: { + fontFamily?: string | undefined; + fontSize?: number | string | undefined; + fontStyle?: number | string | undefined; + fontVariant?: number | string | undefined; + fontWeight?: number | string | undefined; + }; + /** + * Whether the node is active + * @default false + */ + isActive?: boolean; + /** + * Whether the node is selected + * @default false + */ + isSelected?: boolean; +} + +export const TextNode: FC = (props) => { + const { + className, + style = {}, + type = 'normal', + text = '', + font, + color = '#1783ff', + borderWidth = 3, + maxWidth = Infinity, + isActive = false, + isSelected = false, + } = props; + const isMultiLine = measureTextWidth(text, font) > maxWidth; + + return ( + +
{text}
+
+ ); +}; diff --git a/packages/graphs/src/core/behaviors/hover-activate-chain.ts b/packages/graphs/src/core/behaviors/hover-activate-chain.ts new file mode 100644 index 000000000..b49dbe794 --- /dev/null +++ b/packages/graphs/src/core/behaviors/hover-activate-chain.ts @@ -0,0 +1,43 @@ +import type { EdgeDirection, ID } from '@antv/g6'; +import { HoverActivate, idOf } from '@antv/g6'; + +/** + * Behavior to activate the hovered element and its chain (including nodes and edges). + */ +export class HoverActivateChain extends HoverActivate { + protected getActiveIds(event) { + const { model, graph } = this.context; + const targetId = event.target.id; + const targetType = graph.getElementType(targetId); + + const ids = [targetId]; + if (targetType === 'edge') { + const edge = model.getEdgeDatum(targetId); + this.collectChainNodes(edge.source, 'in', ids); + this.collectChainNodes(edge.target, 'out', ids); + } else if (targetType === 'node') { + this.collectChainNodes(targetId, 'both', ids); + } + + graph.frontElement(ids); + + return ids; + } + + private collectChainNodes(nodeId: ID, direction: EdgeDirection, ids: ID[]) { + const { model } = this.context; + const edges = model.getRelatedEdgesData(nodeId, direction); + + edges.forEach((edge) => { + if (!ids.includes(idOf(edge))) ids.push(idOf(edge)); + if (!ids.includes(edge.source)) { + ids.push(edge.source); + this.collectChainNodes(edge.source, 'in', ids); + } + if (!ids.includes(edge.target)) { + ids.push(edge.target); + this.collectChainNodes(edge.target, 'out', ids); + } + }); + } +} diff --git a/packages/graphs/src/core/behaviors/hover-activate-neighbors.ts b/packages/graphs/src/core/behaviors/hover-activate-neighbors.ts new file mode 100644 index 000000000..a3d669487 --- /dev/null +++ b/packages/graphs/src/core/behaviors/hover-activate-neighbors.ts @@ -0,0 +1,21 @@ +import { HoverActivate, idOf } from '@antv/g6'; + +export class HoverActivateNeighbors extends HoverActivate { + getActiveIds(event) { + const { model, graph } = this.context; + const targetId = event.target.id; + const targetType = graph.getElementType(targetId); + + const ids = [targetId]; + if (targetType === 'edge') { + const edge = model.getEdgeDatum(targetId); + ids.push(edge.source, edge.target); + } else if (targetType === 'node') { + ids.push(...model.getRelatedEdgesData(targetId).map(idOf)); + } + + graph.frontElement(ids); + + return ids; + } +} diff --git a/packages/graphs/src/core/behaviors/index.ts b/packages/graphs/src/core/behaviors/index.ts new file mode 100644 index 000000000..817a8153d --- /dev/null +++ b/packages/graphs/src/core/behaviors/index.ts @@ -0,0 +1,2 @@ +export { HoverActivateChain } from './hover-activate-chain'; +export { HoverActivateNeighbors } from './hover-activate-neighbors'; diff --git a/packages/graphs/src/core/constants/index.ts b/packages/graphs/src/core/constants/index.ts new file mode 100644 index 000000000..1d9707146 --- /dev/null +++ b/packages/graphs/src/core/constants/index.ts @@ -0,0 +1 @@ +export { COMMON_OPTIONS } from './options'; diff --git a/packages/graphs/src/core/constants/options.ts b/packages/graphs/src/core/constants/options.ts new file mode 100644 index 000000000..fc8fd2239 --- /dev/null +++ b/packages/graphs/src/core/constants/options.ts @@ -0,0 +1,15 @@ +import type { GraphOptions } from '../../types'; + +export const COMMON_OPTIONS: GraphOptions = { + autoResize: true, + behaviors: [ + { + key: 'zoom-canvas', + type: 'zoom-canvas', + }, + { + key: 'drag-canvas', + type: 'drag-canvas', + }, + ], +}; diff --git a/packages/graphs/src/core/edges/indented-edge.ts b/packages/graphs/src/core/edges/indented-edge.ts new file mode 100644 index 000000000..655bef043 --- /dev/null +++ b/packages/graphs/src/core/edges/indented-edge.ts @@ -0,0 +1,11 @@ +import type { Point, PolylineStyleProps } from '@antv/g6'; +import { Polyline } from '@antv/g6'; + +export class IndentedEdge extends Polyline { + getControlPoints(attributes: Required): Point[] { + const [sourcePoint, targetPoint] = this.getEndpoints(attributes, false); + const [sx] = sourcePoint; + const [, ty] = targetPoint; + return [[sx, ty]]; + } +} diff --git a/packages/graphs/src/core/edges/index.ts b/packages/graphs/src/core/edges/index.ts new file mode 100644 index 000000000..ee5829fb6 --- /dev/null +++ b/packages/graphs/src/core/edges/index.ts @@ -0,0 +1 @@ +export { IndentedEdge } from './indented-edge'; diff --git a/packages/graphs/src/core/hoc/index.ts b/packages/graphs/src/core/hoc/index.ts new file mode 100644 index 000000000..00e1a9ea4 --- /dev/null +++ b/packages/graphs/src/core/hoc/index.ts @@ -0,0 +1 @@ +export { withCollapsibleNode } from './with-collapsible-node'; diff --git a/packages/graphs/src/core/hoc/with-collapsible-node.tsx b/packages/graphs/src/core/hoc/with-collapsible-node.tsx new file mode 100644 index 000000000..a3d30b535 --- /dev/null +++ b/packages/graphs/src/core/hoc/with-collapsible-node.tsx @@ -0,0 +1,110 @@ +import type { CardinalPlacement, Graph, NodeData } from '@antv/g6'; +import { idOf } from '@antv/g6'; +import { get, isEmpty } from 'lodash'; +import React, { useEffect, useRef, useState } from 'react'; +import styled, { css } from 'styled-components'; +import type { CollapseExpandReactNodeOptions } from '../transform'; + +const StyledWrapper = styled.div` + position: relative; + height: inherit; + width: inherit; +`; + +const StyledIconWrapper = styled.div<{ $placement: CardinalPlacement; $offsetX: number; $offsetY: number }>` + position: absolute; + z-index: 10; + + &:hover { + cursor: pointer; + } + + ${({ $placement, $offsetX, $offsetY }) => { + const positions = { + top: `left: calc(50% + ${$offsetX}px); top: ${$offsetY}px;`, + bottom: `left: calc(50% + ${$offsetX}px); top: calc(100% + ${$offsetY}px);`, + right: `left: calc(100% + ${$offsetX}px); top: calc(50% + ${$offsetY}px);`, + left: `left: ${$offsetX}px; top: calc(50% + ${$offsetY}px);`, + }; + + return css` + ${positions[$placement]} + `; + }} +`; + +interface CollapsibleNodeProps extends CollapseExpandReactNodeOptions { + /** + * Node data + */ + data: NodeData; + /** + * G6 Graph instance + */ + graph: Graph; +} + +export const withCollapsibleNode = (NodeComponent: React.FC) => { + return (props: CollapsibleNodeProps) => { + const { + data, + graph, + trigger, + iconRender, + iconPlacement, + iconOffsetX, + iconOffsetY, + iconClassName, + iconStyle, + refreshLayout, + } = props as Required; + const [isCollapsed, setIsCollapsed] = useState(get(data, 'style.collapsed', false) as boolean); + const wrapperRef = useRef(null); + const iconRef = useRef(null); + + const isIconShown = trigger === 'icon' && !isEmpty(data.children); + + const handleClickCollapse = async (e) => { + e.stopPropagation(); + + const toggleExpandCollapse = isCollapsed ? 'expandElement' : 'collapseElement'; + await graph[toggleExpandCollapse](idOf(data)); + setIsCollapsed((prev) => !prev); + + if (refreshLayout) { + await graph.layout(); + } + }; + + useEffect(() => { + const target = trigger === 'icon' ? iconRef.current : trigger === 'node' ? wrapperRef.current : trigger; + + target?.addEventListener('click', handleClickCollapse); + return () => { + target?.removeEventListener('click', handleClickCollapse); + }; + }, [trigger, isCollapsed]); + + const computeCallbackStyle = (callableStyle: Function | number | string) => { + return typeof callableStyle === 'function' ? callableStyle.call(graph, data) : callableStyle; + }; + + return ( + + {isIconShown && ( + + {iconRender?.call(graph, isCollapsed, data)} + + )} + {NodeComponent.call(graph, data)} + + ); + }; +}; diff --git a/packages/graphs/src/core/registry/build-in.ts b/packages/graphs/src/core/registry/build-in.ts new file mode 100644 index 000000000..f2bc62835 --- /dev/null +++ b/packages/graphs/src/core/registry/build-in.ts @@ -0,0 +1,30 @@ +import { ReactNode } from '@antv/g6-extension-react'; +import { HoverActivateChain, HoverActivateNeighbors } from '../behaviors'; +import { IndentedEdge } from '../edges'; +import { + ArrangeEdgeZIndex, + AssignColorByBranch, + CollapseExpandReactNode, + MapEdgeLineWidth, + TranslateReactNodeOrigin, +} from '../transform'; + +export const BUILT_IN_EXTENSIONS = { + node: { + react: ReactNode, + }, + edge: { + indented: IndentedEdge, + }, + behavior: { + 'hover-activate-neighbors': HoverActivateNeighbors, + 'hover-activate-chain': HoverActivateChain, + }, + transform: { + 'translate-react-node-origin': TranslateReactNodeOrigin, + 'collapse-expand-react-node': CollapseExpandReactNode, + 'assign-color-by-branch': AssignColorByBranch, + 'map-edge-line-width': MapEdgeLineWidth, + 'arrange-edge-z-index': ArrangeEdgeZIndex, + }, +}; diff --git a/packages/graphs/src/core/registry/register.ts b/packages/graphs/src/core/registry/register.ts new file mode 100644 index 000000000..29636706c --- /dev/null +++ b/packages/graphs/src/core/registry/register.ts @@ -0,0 +1,11 @@ +import type { ExtensionCategory } from '@antv/g6'; +import { register } from '@antv/g6'; +import { BUILT_IN_EXTENSIONS } from './build-in'; + +export function registerBuiltInExtensions() { + Object.entries(BUILT_IN_EXTENSIONS).forEach(([category, extensions]) => { + Object.entries(extensions).forEach(([type, extension]) => { + register(category as ExtensionCategory, type, extension); + }); + }); +} diff --git a/packages/graphs/src/core/transform/arrange-edge-z-index.ts b/packages/graphs/src/core/transform/arrange-edge-z-index.ts new file mode 100644 index 000000000..13a6e859c --- /dev/null +++ b/packages/graphs/src/core/transform/arrange-edge-z-index.ts @@ -0,0 +1,24 @@ +import type { DrawData } from '@antv/g6'; +import { BaseTransform } from '@antv/g6'; + +/** + * ArrangeEdgeZIndex transform, specifically for indented tree layout + */ +export class ArrangeEdgeZIndex extends BaseTransform { + public beforeDraw(input: DrawData): DrawData { + const { model } = this.context; + const { nodes, edges } = model.getData(); + + const oneLevelNodes = nodes.filter((node) => node.depth === 1); + const oneLevelNodeIds = oneLevelNodes.map((node) => node.id); + + edges.forEach((edge) => { + if (oneLevelNodeIds.includes(edge.target)) { + edge.style ||= {}; + edge.style.zIndex = oneLevelNodes.length - oneLevelNodes.findIndex((node) => node.id === edge.target); + } + }); + + return input; + } +} diff --git a/packages/graphs/src/core/transform/assign-color-by-branch.ts b/packages/graphs/src/core/transform/assign-color-by-branch.ts new file mode 100644 index 000000000..a2de1bcd6 --- /dev/null +++ b/packages/graphs/src/core/transform/assign-color-by-branch.ts @@ -0,0 +1,47 @@ +import type { BaseTransformOptions, CategoricalPalette, DrawData, RuntimeContext } from '@antv/g6'; +import { BaseTransform } from '@antv/g6'; + +export interface AssignColorByBranchOptions extends BaseTransformOptions { + colors?: CategoricalPalette; +} + +export class AssignColorByBranch extends BaseTransform { + static defaultOptions: Partial = { + colors: [ + '#1783FF', + '#F08F56', + '#D580FF', + '#00C9C9', + '#7863FF', + '#DB9D0D', + '#60C42D', + '#FF80CA', + '#2491B3', + '#17C76F', + ], + }; + + constructor(context: RuntimeContext, options: AssignColorByBranchOptions) { + super(context, Object.assign({}, AssignColorByBranch.defaultOptions, options)); + } + + beforeDraw(input: DrawData): DrawData { + const nodes = this.context.model.getNodeData(); + + if (nodes.length === 0) return input; + + let colorIndex = 0; + const dfs = (nodeId: string, color?: string) => { + const node = nodes.find((datum) => datum.id == nodeId); + if (!node) return; + + node.style ||= {}; + node.style.color = color || this.options.colors[colorIndex++ % this.options.colors.length]; + node.children?.forEach((childId) => dfs(childId, node.style?.color as string)); + }; + + nodes.filter((node) => node.depth === 1).forEach((rootNode) => dfs(rootNode.id)); + + return input; + } +} diff --git a/packages/graphs/src/core/transform/collapse-expand-react-node.tsx b/packages/graphs/src/core/transform/collapse-expand-react-node.tsx new file mode 100644 index 000000000..3339c4658 --- /dev/null +++ b/packages/graphs/src/core/transform/collapse-expand-react-node.tsx @@ -0,0 +1,119 @@ +import type { BaseTransformOptions, CardinalPlacement, Graph, NodeData, RuntimeContext } from '@antv/g6'; +import { BaseTransform, idOf } from '@antv/g6'; +import { get, has, set } from 'lodash'; +import React from 'react'; +import { CollapseExpandIcon } from '../base'; +import { withCollapsibleNode } from '../hoc'; +import { getNeighborNodeIds } from '../utils/data'; + +const { PlusMinusIcon } = CollapseExpandIcon; + +export interface CollapseExpandReactNodeOptions extends BaseTransformOptions { + /** + * 是否启用收起/展开功能 + * @default true + */ + enable?: boolean | ((data: NodeData) => boolean); + /** + * 点击指定元素,触发节点收起/展开 + * - 'icon': 点击内置图标 + * - 'node': 点击节点 + * - HTMLElement: 自定义元素 + * @default 'icon' + */ + trigger?: 'icon' | 'node' | HTMLElement; + /** + * 收起/展开指定方向上的邻居节点 + * - 'in': 前驱节点 + * - 'out': 后继节点 + * - 'both': 前驱和后继节点 + * @default 'out' + */ + direction?: 'in' | 'out' | 'both'; + iconType?: 'plus-minus' | 'arrow-count'; + /** + * 渲染函数,用于自定义收起/展开图标 + * @param isCollapsed - 当前节点是否已收起 + * @returns 自定义图标 + */ + iconRender?: (this: Graph, isCollapsed: boolean, data: NodeData) => React.ReactNode; + /** + * 图标相对于节点的位置 + * @default 'bottom' + */ + iconPlacement?: CardinalPlacement | ((this: Graph, data: NodeData) => CardinalPlacement); + /** + * 图标相对于节点的水平偏移量 + * @default 0 + */ + iconOffsetX?: number | ((this: Graph, data: NodeData) => number); + /** + * 图标相对于节点的垂直偏移量 + * @default 0 + */ + iconOffsetY?: number | ((this: Graph, data: NodeData) => number); + /** + * 指定图标的 CSS 类名 + */ + iconClassName?: string; + /** + * 指定图标的内联样式 + */ + iconStyle?: React.CSSProperties; + /** + * 每次收起/展开节点后,是否刷新布局 + */ + refreshLayout?: boolean; +} + +export class CollapseExpandReactNode extends BaseTransform { + static defaultOptions: Partial = { + enable: true, + trigger: 'icon', + direction: 'out', + iconRender: (isCollapsed) => , + iconPlacement: 'bottom', + iconOffsetX: 0, + iconOffsetY: 0, + iconClassName: '', + iconStyle: {}, + refreshLayout: false, + }; + + constructor(context: RuntimeContext, options: CollapseExpandReactNodeOptions) { + super(context, Object.assign({}, CollapseExpandReactNode.defaultOptions, options)); + } + + public afterLayout() { + const { graph, element, model } = this.context; + const { nodes = [], edges = [] } = graph.getData(); + const { enable, ...options } = this.options; + + nodes.forEach((datum) => { + const nodeId = idOf(datum); + + const node = element!.getElement(nodeId); + if (!node || (datum.children && datum.children.length > 0)) return; + + const children = getNeighborNodeIds(nodeId, edges, this.options.direction); + if (children.length === 0) return; + + model.updateNodeData([{ id: nodeId, children }]); + }); + + const nodeMapper = graph.getOptions().node!; + + if (has(nodeMapper, 'style.component')) { + const Component = get(nodeMapper, 'style.component') as React.FC; + set(nodeMapper, 'style.component', (data: NodeData) => { + if (!(typeof enable === 'function' ? enable(data) : enable)) return Component.call(graph, data); + + const CollapsibleNode = withCollapsibleNode(Component); + return ; + }); + } + + graph.setNode(nodeMapper); + graph.draw(); + } +} diff --git a/packages/graphs/src/core/transform/index.ts b/packages/graphs/src/core/transform/index.ts new file mode 100644 index 000000000..a1e0f4535 --- /dev/null +++ b/packages/graphs/src/core/transform/index.ts @@ -0,0 +1,8 @@ +export { ArrangeEdgeZIndex } from './arrange-edge-z-index'; +export { AssignColorByBranch } from './assign-color-by-branch'; +export type { AssignColorByBranchOptions } from './assign-color-by-branch'; +export { CollapseExpandReactNode } from './collapse-expand-react-node'; +export type { CollapseExpandReactNodeOptions } from './collapse-expand-react-node'; +export { MapEdgeLineWidth } from './map-edge-line-width'; +export type { MapEdgeLineWidthOptions } from './map-edge-line-width'; +export { TranslateReactNodeOrigin } from './translate-react-node-origin'; diff --git a/packages/graphs/src/core/transform/map-edge-line-width.ts b/packages/graphs/src/core/transform/map-edge-line-width.ts new file mode 100644 index 000000000..a1a6b9910 --- /dev/null +++ b/packages/graphs/src/core/transform/map-edge-line-width.ts @@ -0,0 +1,108 @@ +import type { BaseTransformOptions, DrawData, EdgeData, Graph, ID, RuntimeContext } from '@antv/g6'; +import { BaseTransform, idOf } from '@antv/g6'; +import { linear, log, pow, sqrt } from '../utils/scale'; + +export interface MapEdgeLineWidthOptions extends BaseTransformOptions { + /** + * 数值 + */ + value: number | ((this: Graph, data: EdgeData) => number); + /** + * 最小值 + */ + minValue?: number | ((data: EdgeData, edges: Record) => number); + /** + * 最大值 + */ + maxValue?: number | ((data: EdgeData, edges: Record) => number); + /** + * 最小线宽 + */ + minLineWidth?: number | ((data: EdgeData) => number); + /** + * 最大线宽 + */ + maxLineWidth?: number | ((data: EdgeData) => number); + /** + * 插值函数,用于将数值映射到线宽 + * - `'linear'`:线性插值函数,将一个值从一个范围线性映射到另一个范围,常用于处理中心性值的差异较小的情况 + * - `'log'`:对数插值函数,将一个值从一个范围对数映射到另一个范围,常用于处理中心性值的差异较大的情况 + * - `'pow'`:幂律插值函数,将一个值从一个范围幂律映射到另一个范围,常用于处理中心性值的差异较大的情况 + * - `'sqrt'`:平方根插值函数,将一个值从一个范围平方根映射到另一个范围,常用于处理中心性值的差异较大的情况 + * - 自定义插值函数:`(value: number, domain: [number, number], range: [number, number]) => number`,其中 `value` 为需要映射的值,`domain` 为输入值的范围,`range` 为输出值的范围 + */ + scale?: + | 'linear' + | 'log' + | 'pow' + | 'sqrt' + | ((value: number, domain: [number, number], range: [number, number]) => number); +} + +export class MapEdgeLineWidth extends BaseTransform { + static defaultOptions: Partial = { + minValue: (edge, edges) => Math.min(...Object.values(edges)), + maxValue: (edge, edges) => Math.max(...Object.values(edges)), + minLineWidth: 1, + maxLineWidth: 10, + scale: 'linear', + }; + + constructor(context: RuntimeContext, options: MapEdgeLineWidthOptions) { + super(context, Object.assign({}, MapEdgeLineWidth.defaultOptions, options)); + } + + beforeDraw(input: DrawData): DrawData { + const edges = this.context.model.getEdgeData(); + + const { maxValue, minValue, maxLineWidth, minLineWidth, scale, value } = this.options; + + const values = edges.reduce((acc, edge) => { + acc[idOf(edge)] = typeof value === 'function' ? value.call(this.context.graph, edge) : value; + return acc; + }, {} as Record); + + edges.forEach((edge) => { + const lineWidth = this.assignLineWidthByValue( + values[idOf(edge)] || 0, + typeof minValue === 'function' ? minValue(edge, values) : minValue, + typeof maxValue === 'function' ? maxValue(edge, values) : maxValue, + typeof minLineWidth === 'function' ? minLineWidth(edge) : minLineWidth, + typeof maxLineWidth === 'function' ? maxLineWidth(edge) : maxLineWidth, + scale, + ); + edge.style ||= {}; + edge.style.lineWidth = lineWidth; + }); + return input; + } + + private assignLineWidthByValue = ( + value: number, + minValue: number, + maxValue: number, + minLineWidth: number, + maxLineWidth: number, + scale: MapEdgeLineWidthOptions['scale'], + ): number => { + const domain: [number, number] = [minValue, maxValue]; + const range: [number, number] = [minLineWidth, maxLineWidth]; + + if (value < minValue || value > maxValue || minValue === maxValue) return range[0]; + + if (typeof scale === 'function') return scale(value, domain, range); + + switch (scale) { + case 'linear': + return linear(value, domain, range); + case 'log': + return log(value, domain, range); + case 'pow': + return pow(value, domain, range, 2); + case 'sqrt': + return sqrt(value, domain, range); + default: + return range[0]; + } + }; +} diff --git a/packages/graphs/src/core/transform/translate-react-node-origin.ts b/packages/graphs/src/core/transform/translate-react-node-origin.ts new file mode 100644 index 000000000..63d033701 --- /dev/null +++ b/packages/graphs/src/core/transform/translate-react-node-origin.ts @@ -0,0 +1,28 @@ +import type { DrawData, Size } from '@antv/g6'; +import { BaseTransform, parseSize } from '@antv/g6'; + +/** + * HTML 元素的默认原点位置在左上角,而 G6 的默认原点位置在中心,所以需要调整 dx 和 dy + */ +export class TranslateReactNodeOrigin extends BaseTransform { + public beforeDraw(input: DrawData): DrawData { + const { graph, element } = this.context; + + const { + add: { nodes: nodesToAdd }, + update: { nodes: nodesToUpdate }, + } = input; + + [...nodesToAdd.values(), ...nodesToUpdate.values()].forEach((datum) => { + // @ts-expect-error private method invoke + element!.computeElementDefaultStyle('node', { graph, datum }); + const style = element!.getDefaultStyle(datum.id); + const [width, height] = parseSize(style.size as Size); + if (!datum.style) datum.style = {}; + datum.style.dx = -width / 2; + datum.style.dy = -height / 2; + }); + + return input; + } +} diff --git a/packages/graphs/src/core/utils/color.ts b/packages/graphs/src/core/utils/color.ts new file mode 100644 index 000000000..b83bdc08a --- /dev/null +++ b/packages/graphs/src/core/utils/color.ts @@ -0,0 +1,37 @@ +export function hexToRgba(hex, opacity) { + let r = 0, + g = 0, + b = 0; + if (hex.length === 4) { + r = parseInt(hex[1] + hex[1], 16); + g = parseInt(hex[2] + hex[2], 16); + b = parseInt(hex[3] + hex[3], 16); + } else if (hex.length === 7) { + r = parseInt(hex[1] + hex[2], 16); + g = parseInt(hex[3] + hex[4], 16); + b = parseInt(hex[5] + hex[6], 16); + } + return `rgba(${r}, ${g}, ${b}, ${opacity})`; +} + +export function darkenHexColor(hex: string, amount: number): string { + let r = 0, + g = 0, + b = 0; + if (hex.length === 4) { + r = parseInt(hex[1] + hex[1], 16); + g = parseInt(hex[2] + hex[2], 16); + b = parseInt(hex[3] + hex[3], 16); + } else if (hex.length === 7) { + r = parseInt(hex[1] + hex[2], 16); + g = parseInt(hex[3] + hex[4], 16); + b = parseInt(hex[5] + hex[6], 16); + } + + r = Math.max(0, r - amount); + g = Math.max(0, g - amount); + b = Math.max(0, b - amount); + + const toHex = (c: number) => c.toString(16).padStart(2, '0'); + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; +} diff --git a/packages/graphs/src/core/utils/data.tsx b/packages/graphs/src/core/utils/data.tsx new file mode 100644 index 000000000..c2b86132d --- /dev/null +++ b/packages/graphs/src/core/utils/data.tsx @@ -0,0 +1,144 @@ +import type { EdgeData, EdgeDirection, GraphData, ID, NodeData, TreeData } from '@antv/g6'; +import { treeToGraphData } from '@antv/g6'; + +/** + * 获取邻居节点 + * @param nodeId - 节点 ID + * @param edges - 边数据 + * @param direction - 边的方向 + * @returns 邻居节点 ID + */ +export const getNeighborNodeIds = (nodeId: ID, edges: EdgeData[], direction: EdgeDirection): ID[] => { + const getSuccessorNodeIds = (reverse = false): ID[] => { + const [source, target] = reverse ? ['target', 'source'] : ['source', 'target']; + return edges.filter((edge) => edge[source] === nodeId).map((edge) => edge[target]) as ID[]; + }; + + if (direction === 'out') return getSuccessorNodeIds(); + if (direction === 'in') return getSuccessorNodeIds(true); + return getSuccessorNodeIds().concat(getSuccessorNodeIds(true)); +}; + +const EMPTY_GRAPH_DATA: GraphData = { nodes: [], edges: [] }; + +/** + * 检查给定的数据是否是有效的树图结构 + * @param data - 数据 + * @returns 如果数据是有效的树图结构,则返回 true;否则返回 false + */ +export function isTreeData(data: any): data is TreeData { + if (typeof data !== 'object' || data === null) return false; + + if (!('id' in data)) return false; + + if ('children' in data) { + if (!Array.isArray(data.children)) return false; + + for (const child of data.children) { + if (!isTreeData(child)) return false; + } + } + + return true; +} + +/** + * 检查给定的数据是否是有效的图结构 + * @param data - 数据 + * @returns 如果数据是有效的图结构,则返回 true;否则返回 false + */ +export function isGraphData(data: GraphData | TreeData): data is GraphData { + if (typeof data !== 'object' || data === null) return false; + + if (!Object.keys(data).every((key) => ['nodes', 'edges', 'combos'].includes(key))) { + return false; + } + const { nodes = [], edges = [], combos = [] } = data; + + if (!Array.isArray(nodes) || !Array.isArray(edges) || !Array.isArray(combos)) { + return false; + } + + const nodeIds = new Set(nodes.map((node) => node.id)); + + if (!nodes.every((node) => typeof node === 'object' && node !== null && 'id' in node)) { + return false; + } + + if (!edges.every((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target))) { + return false; + } + + return true; +} + +/** + * 将图数据转换为树图数据 + * @param data - 图数据 + * @returns 树图数据 + */ +export function graphData2TreeData(data: GraphData): TreeData | undefined { + if (!isGraphData(data)) { + return; + } + + const { nodes = [], edges = [] } = data; + + const nodeMap = Object.fromEntries(nodes.map((node) => [node.id, node])); + const indegree = Object.fromEntries(nodes.map((node) => [node.id, 0])); + const adjList = Object.fromEntries(nodes.map((node) => [node.id, [] as ID[]])); + + for (const { source, target } of edges) { + adjList[source].push(target); + indegree[target] = (indegree[target] || 0) + 1; + } + + const roots = Object.entries(indegree) + .filter(([_, deg]) => deg === 0) + .map(([id]) => id); + if (roots.length !== 1) { + return; + } + + const buildTree = (nodeId: ID): TreeData => { + const node = nodeMap[nodeId]; + return { + ...node, + children: adjList[nodeId].map(buildTree), + }; + }; + + return buildTree(roots[0]); +} + +/** + * 将树图数据转换为图数据 + * @param data - 树图数据 + * @param defaultExpandLevel - 默认展开层级。若不传入,则所有节点均展开 + * @returns 图数据 + */ +export function treeData2GraphData(data: TreeData, defaultExpandLevel?: number): GraphData { + if (!isTreeData(data)) return EMPTY_GRAPH_DATA; + + return treeToGraphData(data, { + getNodeData: (datum: TreeData, depth: number) => { + datum.depth = depth; + datum.style ||= {}; + if (defaultExpandLevel) { + datum.style.collapsed = depth >= defaultExpandLevel; + } + if (!datum.children) return datum as unknown as NodeData; + const { children, ...restDatum } = datum; + return { ...restDatum, children: children.map((child) => child.id) }; + }, + }); +} + +export function formatTreeData(data?: GraphData | TreeData, defaultExpandLevel?: number): GraphData { + if (!data) return EMPTY_GRAPH_DATA; + + const treeData = isGraphData(data) ? graphData2TreeData(data) : data; + if (!treeData) return EMPTY_GRAPH_DATA; + + return treeData2GraphData(treeData, defaultExpandLevel); +} diff --git a/packages/graphs/src/core/utils/label.ts b/packages/graphs/src/core/utils/label.ts new file mode 100644 index 000000000..996831e9a --- /dev/null +++ b/packages/graphs/src/core/utils/label.ts @@ -0,0 +1,11 @@ +import type { NodeData } from '@antv/g6'; +import { get } from 'lodash'; + +export function formatLabel(datum: NodeData, labelField?: string | ((datum: NodeData) => string)): string { + const label = labelField + ? typeof labelField === 'function' + ? labelField(datum) + : get(datum, `data.${labelField}`, datum.id) + : datum.id; + return String(label); +} diff --git a/packages/graphs/src/core/utils/measure-text.ts b/packages/graphs/src/core/utils/measure-text.ts new file mode 100644 index 000000000..c352e66da --- /dev/null +++ b/packages/graphs/src/core/utils/measure-text.ts @@ -0,0 +1,29 @@ +import { measureTextHeight, measureTextWidth } from '@ant-design/charts-util'; + +/** + * 计算文本尺寸 + * @param text - 文本内容 + * @param offset - 水平和垂直偏移量,默认为 [0, 0],用于调整文本节点的大小 + * @param font - 字体样式 + * @param minWidth - 最小宽度,默认为 0 + * @param maxWith - 最大宽度,默认为 Infinity;超出部分会被自动换行 + * @returns 文本尺寸(包括宽度和高度) + */ +export function measureTextSize( + text: string, + offset = [0, 0], + font: any = { fontSize: 16, fontFamily: 'PingFang SC' }, + minWidth = 0, + maxWith = Infinity, +): [number, number] { + const height = measureTextHeight(text, font); + const width = measureTextWidth(text, font) + 4; + + const lineNumber = maxWith === Infinity ? 1 : Math.ceil(width / maxWith); + const [offsetWidth, offsetHeight] = offset; + + return [ + Math.max(minWidth, Math.min(maxWith, width)) + offsetWidth, + offsetHeight + height + height * 1.5 * (lineNumber - 1), + ]; +} diff --git a/packages/graphs/src/core/utils/node.tsx b/packages/graphs/src/core/utils/node.tsx new file mode 100644 index 000000000..71dcb0a58 --- /dev/null +++ b/packages/graphs/src/core/utils/node.tsx @@ -0,0 +1,41 @@ +import type { Graph, NodeData } from '@antv/g6'; +import { idOf, positionOf } from '@antv/g6'; +import { memoize } from 'lodash'; + +/** + * Get the side of the node relative to the reference node + * @param nodeData - Node data + * @param parentData - Reference node data + * @returns The side of the node relative to the reference node + */ +export const getRelativeSide = memoize( + (nodeData: NodeData, refNodeData?: NodeData): 'center' | 'left' | 'right' => { + if (!refNodeData) return 'center'; + + const nodePositionX = positionOf(nodeData)[0]; + const refNodePositionX = positionOf(refNodeData)[0]; + return refNodePositionX > nodePositionX ? 'left' : 'right'; + }, + (nodeData, refNodeData) => + refNodeData ? [positionOf(nodeData), positionOf(refNodeData)].flat().join('-') : 'center', +); + +/** + * Get the side of the node relative to the parent node + * @param graph - Graph instance + * @param data - Node data + * @returns The side of the node relative to the parent node + */ +export const getNodeSide = (graph: Graph, data: NodeData) => { + const parentData = graph.getParentData(idOf(data), 'tree'); + return getRelativeSide(data, parentData); +}; + +/** + * Whether the node is a leaf node + * @param nodeData - node data + * @returns Whether the node is a leaf node + */ +export const isLeafNode = (nodeData: NodeData): boolean => { + return !nodeData.children || nodeData.children.length === 0; +}; diff --git a/packages/graphs/src/core/utils/options.ts b/packages/graphs/src/core/utils/options.ts new file mode 100644 index 000000000..5433d8668 --- /dev/null +++ b/packages/graphs/src/core/utils/options.ts @@ -0,0 +1,44 @@ +import { isPlainObject } from 'lodash'; +import type { GraphOptions, ParsedGraphOptions } from '../../types'; + +/** + * 合并多个图配置项,优先级从左到右递增 + * @param options 图配置项列表 + * @returns 最后用于渲染的配置项 + */ +export function mergeOptions(...options: GraphOptions[]): ParsedGraphOptions { + if (options.length === 0) return {} as ParsedGraphOptions; + + const merged = { ...options[0] }; + + for (let i = 1; i < options.length; i++) { + const currentOptions = options[i]; + + for (const key in currentOptions) { + if (currentOptions.hasOwnProperty(key)) { + const currValue = currentOptions[key]; + const prevValue = merged[key]; + + if (['component', 'data'].includes(key)) { + merged[key] = currValue; + } else if (typeof currValue === 'function') { + if (['plugins', 'behaviors', 'transforms'].includes(key)) { + merged[key] = currValue(prevValue || []); + } else { + merged[key] = function (datum) { + const value = currValue.call(this, datum); + if (isPlainObject(value)) return mergeOptions(prevValue, value); + return value; + }; + } + } else if (isPlainObject(currValue) && isPlainObject(prevValue)) { + merged[key] = mergeOptions(prevValue, currValue); + } else { + merged[key] = currValue; + } + } + } + } + + return merged as ParsedGraphOptions; +} diff --git a/packages/graphs/src/core/utils/scale.ts b/packages/graphs/src/core/utils/scale.ts new file mode 100644 index 000000000..77fc49423 --- /dev/null +++ b/packages/graphs/src/core/utils/scale.ts @@ -0,0 +1,62 @@ +/** + * 将一个值从一个范围线性映射到另一个范围 + * @param value - 需要映射的值 + * @param domain - 输入值的范围 [最小值, 最大值] + * @param range - 输出值的范围 [最小值, 最大值] + * @returns 映射后的值 + */ +export const linear = (value: number, domain: [number, number], range: [number, number]) => { + const [d0, d1] = domain; + const [r0, r1] = range; + + if (d1 === d0) return r0; + + const ratio = (value - d0) / (d1 - d0); + return r0 + ratio * (r1 - r0); +}; + +/** + * 将一个值从一个范围对数映射到另一个范围 + * @param value - 需要映射的值 + * @param domain - 输入值的范围 [最小值, 最大值] + * @param range - 输出值的范围 [最小值, 最大值] + * @returns 映射后的值 + */ +export const log = (value: number, domain: [number, number], range: [number, number]) => { + const [d0, d1] = domain; + const [r0, r1] = range; + + const ratio = Math.log(value - d0 + 1) / Math.log(d1 - d0 + 1); + return r0 + ratio * (r1 - r0); +}; + +/** + * 将一个值从一个范围幂映射到另一个范围 + * @param value - 需要映射的值 + * @param domain - 输入值的范围 [最小值, 最大值] + * @param range - 输出值的范围 [最小值, 最大值] + * @param exponent - 幂指数 + * @returns 映射后的值 + */ +export const pow = (value: number, domain: [number, number], range: [number, number], exponent: number = 2): number => { + const [d0, d1] = domain; + const [r0, r1] = range; + + const ratio = Math.pow((value - d0) / (d1 - d0), exponent); + return r0 + ratio * (r1 - r0); +}; + +/** + * 将一个值从一个范围平方根映射到另一个范围 + * @param value - 需要映射的值 + * @param domain - 输入值的范围 [最小值, 最大值] + * @param range - 输出值的范围 [最小值, 最大值] + * @returns 映射后的值 + */ +export const sqrt = (value: number, domain: [number, number], range: [number, number]) => { + const [d0, d1] = domain; + const [r0, r1] = range; + + const ratio = Math.sqrt((value - d0) / (d1 - d0)); + return r0 + ratio * (r1 - r0); +}; diff --git a/packages/graphs/src/core/utils/tree.ts b/packages/graphs/src/core/utils/tree.ts new file mode 100644 index 000000000..fa546cdc4 --- /dev/null +++ b/packages/graphs/src/core/utils/tree.ts @@ -0,0 +1,22 @@ +import { memoize } from 'lodash'; +import { measureTextSize } from './measure-text'; + +export const getLinearTextNodeStyle = memoize((text: string, minWidth: number, maxWidth: number, depth: number = 0) => { + const font = { + fontWeight: depth === 0 ? 600 : 400, + fontSize: depth === 0 ? 24 : 16, + }; + const offset = depth === 0 ? [64, 30] : [12, 12]; + const size = measureTextSize(text, offset, font, minWidth, maxWidth); + return { font, size }; +}); + +export const getBoxedTextNodeStyle = memoize((text: string, minWidth: number, maxWidth: number, depth: number = 0) => { + const font = { + fontWeight: depth === 0 || depth === 1 ? 600 : 400, + fontSize: depth === 0 ? 24 : 16, + }; + const offset = depth === 0 ? [64, 30] : [36, 24]; + const size = measureTextSize(text, offset, font, minWidth, maxWidth); + return { font, size }; +}); diff --git a/packages/graphs/src/errorBoundary/index.tsx b/packages/graphs/src/errorBoundary/index.tsx deleted file mode 100644 index 318fb852a..000000000 --- a/packages/graphs/src/errorBoundary/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { Fragment } from 'react'; - -class ErrorBoundary extends React.Component { - static getDerivedStateFromError(error: Error) { - return { hasError: true, error }; - } - - static getDerivedStateFromProps(nextProps: any, state: any) { - if (state.children !== nextProps.children) { - return { - children: nextProps.children, - hasError: false, - error: undefined, - }; - } - return null; - } - - state: { - hasError: boolean; - error?: Error; - } = { - hasError: false, - }; - - renderError = (e: Error) => { - const { errorTemplate } = this.props; - switch (e) { - default: - if (typeof errorTemplate === 'function') { - return errorTemplate(e); - } - return errorTemplate ? errorTemplate :
组件出错了,请核查后重试: {e.message}
; - } - }; - - render() { - if (this.state.hasError) { - return this.renderError(this.state.error!); - } - return {this.props.children}; - } -} - -export default ErrorBoundary; diff --git a/packages/graphs/src/hooks/useFullscreen.ts b/packages/graphs/src/hooks/useFullscreen.ts deleted file mode 100644 index f60a3d3c5..000000000 --- a/packages/graphs/src/hooks/useFullscreen.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Log } from '../utils'; - -interface FunUseFullscreen { - (el?: HTMLElement | null): [boolean, () => void]; -} - -const useFullscreen: FunUseFullscreen = (el) => { - const [fullscreen, setFullscreen] = useState(false); - const handleFullScreenChange = () => { - // if exit fullscreen - if (!document.fullscreenElement) { - setFullscreen(false); - } - }; - const enterFullscreen = () => { - if (el && el.requestFullscreen) { - el.requestFullscreen() - .then(() => { - setFullscreen(true); - }) - .catch((err) => { - Log.error('requestFullscreen error: ', err); - }); - } - }; - const exitFullscreen = () => { - if (document.exitFullscreen) { - document - .exitFullscreen() - .then(() => { - setFullscreen(false); - }) - .catch((err) => { - Log.error('exitFullscreen error: ', err); - }); - } - }; - - const toggleFullscreen = () => { - // 切换是否全屏 - if (!el) { - Log.error('need dom'); - return; - } - if (!fullscreen) { - enterFullscreen(); - } else { - exitFullscreen(); - } - }; - - useEffect(() => { - // 用户按Esc键退出全屏 或者 退出全屏都会触发这个事件 - document.addEventListener('fullscreenchange', handleFullScreenChange, false); - return () => { - document.removeEventListener('fullscreenchange', handleFullScreenChange); - }; - }, []); - - return [fullscreen, toggleFullscreen]; -}; - -export default useFullscreen; diff --git a/packages/graphs/src/hooks/useGraphs.ts b/packages/graphs/src/hooks/useGraphs.ts deleted file mode 100644 index bc9a2ba18..000000000 --- a/packages/graphs/src/hooks/useGraphs.ts +++ /dev/null @@ -1,438 +0,0 @@ -import G6, { IEdge, INode, ModeType } from '@antv/g6'; -import { isEqual, isObject, isString, omit } from '@antv/util'; -import { useEffect, useRef } from 'react'; -import { ArrowConfig, CardNodeCfg, CommonConfig, EdgeConfig, NodeConfig, StateStyles } from '../interface'; -import { - deepClone, - getAnchorPoints, - getArrowCfg, - getCommonCfg, - getGraphId, - getGraphSize, - getMarkerPosition, - renderGraph, - getCenterNode, - bindStateEvents, - runAsyncEvent, - getRenderData, -} from '../utils'; -import { processMinimap, processTooltip, processMenu, processToolbar } from '../plugins'; -import { setGlobalInstance } from '../utils/global'; - -export default function useGraph( - graphClass: string, - config: any, - extra: { name?: string; bindEvents?: Function } = {}, -) { - const container = useRef(null); - const graphRef = useRef(); - const graphOptions = useRef(); - // data 单独处理,会被 G6 修改 - const graphData = useRef(); - - const { - data, - width, - height, - layout, - minimapCfg, - behaviors, - fitCenter, - nodeCfg, - edgeCfg, - markerCfg, - level, - toolbarCfg, - tooltipCfg, - menuCfg, - customLayout, - } = config; - const graph = graphRef.current; - /** 隐藏孤立边 */ - const setEdgesState = (edges: IEdge[]) => { - edges.forEach((edge: IEdge) => { - const { source, target } = edge.getModel(); - const sourceVisible = graph?.findById(source as string)?.get('visible'); - const targetVisible = graph?.findById(target as string)?.get('visible'); - if (sourceVisible === false || targetVisible === false) { - edge.changeVisibility(false); - } - }); - }; - - const changeData = () => { - if (!graph) return; - let originData = data; - let tagData = originData; - if (level) [originData, tagData] = getRenderData(data, level); - graph.changeData(originData); - graph.get('eventData')?.setData(tagData); - setEdgesState(graph.getEdges()); - if (fitCenter) { - graph.fitCenter(); - } - runAsyncEvent(graph.get('id')); - }; - - const updateLayout = () => { - graph?.updateLayout(layout); - if (fitCenter) { - graph?.fitCenter(); - } - }; - - const updateNodes = () => { - if (!graph) { - return; - } - const { type: nodeType, anchorPoints: nodeAnchorPoints, style: nodeStyle, title: nodeLabelCfg } = nodeCfg ?? {}; - - graph.getNodes().forEach((node: INode) => { - const anchorPoints = getAnchorPoints(nodeAnchorPoints, node.getModel() as NodeConfig); - graph!.updateItem(node, { - nodeCfg, - markerCfg, - type: nodeType, - style: nodeStyle, - anchorPoints, - labelCfg: nodeLabelCfg, - }); - }); - runAsyncEvent(graph.get('id')); - }; - - const updateEdges = () => { - if (!graph) { - return; - } - const { - type: edgeType, - style: edgeStyle, - startArrow: startArrowCfg, - endArrow: endArrowCfg, - label: labelCfg, - } = edgeCfg ?? {}; - graph.getEdges().forEach((edge: IEdge) => { - // 资金流向图&来源去向图 - if (['fund-line', 'labels-line'].includes(edgeType)) { - graph!.updateItem(edge, { - edgeCfg, - }); - } else { - const edgeCfgModel = edge.getModel(); - const startArrow = getArrowCfg(startArrowCfg, edgeCfgModel); - const endArrow = getArrowCfg(endArrowCfg, edgeCfgModel); - const { style, content } = labelCfg ?? {}; - - graph!.updateItem(edge, { - type: edgeType, - label: getCommonCfg(content, edgeCfgModel, graph), - labelCfg: { - style: getCommonCfg(style, edgeCfgModel, graph), - }, - style: { - stroke: '#ccc', - startArrow, - endArrow, - ...(typeof edgeStyle === 'function' ? edgeStyle(edgeCfgModel, graph) : edgeStyle), - }, - }); - } - }); - }; - - // 目前仅支持更新位置 - const updateMarker = () => { - if (!graph) { - return; - } - graph.getNodes().forEach((node: INode) => { - const { position = 'right' } = - typeof markerCfg === 'function' ? markerCfg(node.getModel(), node.get('group')) : markerCfg; - const { width, height } = node.getBBox(); - const markerShape = node - .get('group') - .get('children') - .find((item: INode) => item.get('name') === 'collapse-icon'); - if (markerShape) { - markerShape?.attr({ - ...getMarkerPosition(position, [width, height]), - }); - } - }); - }; - - const getEdgeStateStyles = (edgeStateStyles: StateStyles | undefined) => { - const { name = '' } = extra; - if (name !== 'FundFlowGraph') { - return edgeStateStyles; - } - if (!edgeStateStyles) { - return; - } - const { hover = {} } = edgeStateStyles; - const { endArrow, startArrow } = hover; - if (!endArrow && !startArrow) { - return edgeStateStyles; - } - return { - hover: { - ...hover, - endArrow: endArrow ? getArrowCfg(endArrow as ArrowConfig) : false, - startArrow: startArrow ? getArrowCfg(startArrow as ArrowConfig) : false, - }, - }; - }; - - useEffect(() => { - if (graph && !graph.destroyed) { - if (isEqual(data, graphData.current)) { - return; - } - if (extra.name === 'RadialGraph' && !graphData.current.nodes?.length) { - const centerNode = getCenterNode(data); - graph.set('centerNode', centerNode); - graph.updateLayout({ - ...layout, - focusNode: centerNode, - }); - } else { - changeData(); - } - graphData.current = deepClone(data); - } - }, [data]); - - useEffect(() => { - if (graph && !graph.destroyed) { - if (isEqual(config, graphOptions.current)) { - return; - } - if (!customLayout && !isEqual(layout, graphOptions.current?.layout)) { - updateLayout(); - } - if (!isEqual(minimapCfg, graphOptions.current?.minimapCfg)) { - processMinimap(minimapCfg, graph); - } - if (!isEqual(nodeCfg, graphOptions.current?.nodeCfg)) { - updateNodes(); - } - if (!isEqual(edgeCfg, graphOptions.current?.edgeCfg)) { - updateEdges(); - } - if (!isEqual(markerCfg, graphOptions.current?.markerCfg)) { - updateMarker(); - } - graphOptions.current = config; - } - }, [config]); - - useEffect(() => { - if (graph && !graph.destroyed) { - const graphSize = getGraphSize(width, height, container); - graph.changeSize(graphSize[0], graphSize[1]); - } - }, [container, width, height]); - - useEffect(() => { - if (graph && !graph.destroyed) { - const { default: defaultMode } = graph.get('modes'); - const removingBehaviors: string[] = []; - defaultMode.forEach((be: string | ModeType) => { - if (isObject(be)) { - removingBehaviors.push(be.type); - } else if (isString(be)) { - removingBehaviors.push(be); - } - }); - graph.removeBehaviors(removingBehaviors, 'default'); - graph.addBehaviors(behaviors, 'default'); - } - }, [behaviors]); - - useEffect(() => { - if (container.current && graphClass && !graphRef.current) { - const { name = '', bindEvents } = extra; - const graphSize = getGraphSize(width, height, container); - const { - nodeCfg, - edgeCfg, - behaviors = [], - layout, - animate, - autoFit, - fitCenter, - onReady, - customLayout, - fetchLoading, - } = config; - - const { - type: nodeType, - anchorPoints: nodeAnchorPoints, - nodeStateStyles, - style: nodeStyle, - title: nodeLabelCfg, - linkCenter, - getChildren, - asyncData, - } = nodeCfg ?? {}; - - const { - type: edgeType, - style: edgeStyle, - startArrow: startArrowCfg, - endArrow: endArrowCfg, - label: labelCfg, - edgeStateStyles, - } = edgeCfg ?? {}; - - graphRef.current = new G6[graphClass]({ - container: container.current as any, - width: graphSize[0], - height: graphSize[1], - animate, - linkCenter, - modes: { - default: behaviors, - }, - defaultNode: { - ...nodeCfg, - nodeCfg, - }, - defaultEdge: { - ...omit(edgeCfg, ['label']), - edgeCfg, - labelCfg: labelCfg?.style, - }, - nodeStateStyles, - edgeStateStyles: getEdgeStateStyles(edgeStateStyles), - layout: customLayout ? undefined : layout, - fitView: autoFit, - fitCenter, - extraPlugin: { - getChildren, - fetchLoading, - }, - }); - const graphId = getGraphId(graphRef.current, name); - const graph = graphRef.current; - graph.set('id', graphId); - setGlobalInstance(graphId, graph); - - const customNode = ['fund-card', 'indicator-card', 'file-tree-node', 'organization-card']; - const getLabel = (value: { [key: string]: string } | string): string => { - // 辐射树图 - if (isString(value)) { - return value; - } - if (name === 'FundFlowGraph') { - return value?.text; - } - return value?.title; - }; - // defaultNode 默认只能绑定 plainObject,针对 Function 类型需要通过该模式绑定 - graph.node((node: NodeConfig) => { - if (customNode.includes(nodeType)) { - const anchorPoints = getAnchorPoints(nodeAnchorPoints, node); - node.markerCfg = markerCfg; - node.edgeCfg = edgeCfg; - return { - anchorPoints, - _draggable: behaviors.includes('drag-node'), - _graphId: graphId, - }; - } - const { style } = (nodeLabelCfg ?? {}) as CardNodeCfg; - return { - _graphId: graphId, - label: getLabel(node.value), - labelCfg: { - style: getCommonCfg(style, node, graph), - }, - style: { - stroke: '#ccc', - ...(typeof nodeStyle === 'function' ? nodeStyle(node, graph) : nodeStyle), - }, - }; - }); - - const getEdgeLabel = (edge: EdgeConfig) => { - const { content } = labelCfg ?? {}; - - if (['DecompositionTreeGraph', 'OrganizationGraph', 'RadialTreeGraph'].includes(name)) { - return getCommonCfg(content, edge, graph); - } - if (['FundFlowGraph', 'FlowAnalysisGraph'].includes(name)) { - const { value } = edge; - // @ts-ignore - return typeof value === 'object' ? value?.text : value; - } - return edge.value; - }; - if (!['fund-line', 'labels-line'].includes(edgeType)) { - graph.edge((edge: EdgeConfig) => { - const startArrow = getArrowCfg(startArrowCfg, edge); - const endArrow = getArrowCfg(endArrowCfg, edge); - const { style } = labelCfg ?? {}; - return { - label: getEdgeLabel(edge), - labelCfg: { - style: getCommonCfg(style, edge, graph), - }, - style: { - stroke: '#ccc', - startArrow, - endArrow, - ...(typeof edgeStyle === 'function' ? edgeStyle(edge, graph) : edgeStyle), - }, - }; - }); - } - - bindStateEvents(graph, config); - // 绑定节点辐射事件 - if (name === 'RadialGraph') { - const centerNode = getCenterNode(data); - graph.set('centerNode', centerNode); - } - // 绑定事件 - if (typeof bindEvents === 'function') { - bindEvents({ - graph, - level, - asyncData, - getChildren, - fetchLoading, - layout, - }); - } - renderGraph(graph, data, level); - fitCenter && graph.fitCenter(); - onReady && onReady(graph); - graphData.current = deepClone(data); - } - }, []); - - useEffect(() => { - if (graphRef.current) { - const _graph = graphRef.current; - processMinimap(minimapCfg, _graph); - processTooltip(tooltipCfg, _graph); - processMenu(menuCfg, _graph); - processToolbar(toolbarCfg, _graph, container.current); - } - }, [graphRef, toolbarCfg, tooltipCfg, menuCfg]); - - useEffect(() => { - return () => { - if (graph?.current && !graph.current.destroyed) { - graph.current.destroy(); - } - }; - }, []); - - return { - container, - }; -} diff --git a/packages/graphs/src/hooks/useProps.ts b/packages/graphs/src/hooks/useProps.ts deleted file mode 100644 index c7ead61c3..000000000 --- a/packages/graphs/src/hooks/useProps.ts +++ /dev/null @@ -1,42 +0,0 @@ -// 合并 defaultProps -import { useCallback } from 'react'; -import { getType, deepClone } from '../utils'; -import { CommonConfig } from '../interface'; - -type SpecialKey = 'level'; - -export default function useProps( - props: CommonConfig, - defaultProps: Partial, -): { - uProps: Partial & { [key in SpecialKey]?: number }; -} { - const cloneProps = deepClone(props); - - const mergeProps = useCallback( - (p: Partial, defaultProps: Partial) => { - const config = { - ...defaultProps, - }; - const propsKeys = Object.keys(p); - propsKeys.forEach((key: string) => { - if (getType(p[key]) === 'Object') { - config[key] = { - ...defaultProps[key], - ...p[key], - }; - } else { - config[key] = p[key]; - } - }); - return config; - }, - [props, defaultProps], - ); - - const uProps = mergeProps(cloneProps, defaultProps); - - return { - uProps, - }; -} diff --git a/packages/graphs/src/index.ts b/packages/graphs/src/index.ts index 0b10ce70e..2779370a8 100644 --- a/packages/graphs/src/index.ts +++ b/packages/graphs/src/index.ts @@ -1,43 +1,30 @@ -import G6 from '@antv/g6'; -import OrganizationGraph from './components/organization-graph'; -import type { OrganizationGraphConfig } from './components/organization-graph'; -import RadialTreeGraph from './components/radial-tree-graph'; -import type { RadialTreeGraphConfig } from './components/radial-tree-graph'; -import FlowAnalysisGraph from './components/flow-analysis-graph'; -import type { FlowAnalysisGraphConfig } from './components/flow-analysis-graph'; -import DecompositionTreeGraph from './components/decomposition-tree-graph'; -import type { DecompositionTreeGraphConfig } from './components/decomposition-tree-graph'; -import FundFlowGraph from './components/fund-flow-graph'; -import type { FundFlowGraphConfig } from './components/fund-flow-graph'; -import RadialGraph from './components/radial-graph'; -import type { RadialGraphConfig } from './components/radial-graph'; -import MindMapGraph from './components/mind-map-graph'; -import type { MindMapGraphConfig } from './components/mind-map-graph'; -import FileTreeGraph from './components/file-tree-graph'; -import type { FileTreeGraphConfig } from './components/file-tree-graph'; -import ConversionDagreGraph from './components/conversion-dagre-graph'; +import * as G6 from '@antv/g6'; +import './preset'; export { - FlowAnalysisGraph, - RadialTreeGraph, - DecompositionTreeGraph, - OrganizationGraph, - FundFlowGraph, - RadialGraph, - MindMapGraph, - FileTreeGraph, - ConversionDagreGraph, - G6, -}; -export * from './interface'; -export * from './layout'; + Dendrogram, + Fishbone, + FlowDirectionGraph, + FlowGraph, + IndentedTree, + MindMap, + NetworkGraph, + OrganizationChart, +} from './components'; export type { - OrganizationGraphConfig, - RadialTreeGraphConfig, - FlowAnalysisGraphConfig, - DecompositionTreeGraphConfig, - FundFlowGraphConfig, - RadialGraphConfig, - MindMapGraphConfig, - FileTreeGraphConfig, -}; + DendrogramOptions, + FishboneOptions, + FlowDirectionGraphOptions, + FlowGraphOptions, + IndentedTreeOptions, + MindMapOptions, + NetworkGraphOptions, + OrganizationChartOptions, +} from './components'; +export { CollapseExpandIcon, RCNode } from './core/base'; +export type { OrganizationChartNodeProps, TextNodeProps } from './core/base/node'; +export { measureTextSize } from './core/utils/measure-text'; +export { getNodeSide } from './core/utils/node'; +export { mergeOptions } from './core/utils/options'; +export type { GraphOptions } from './types'; +export { G6 }; diff --git a/packages/graphs/src/interface.ts b/packages/graphs/src/interface.ts deleted file mode 100644 index a84d71e26..000000000 --- a/packages/graphs/src/interface.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { - ArrowConfig as G6ArrowConfig, - Edge, - EdgeConfig as G6EdgeConfig, - Graph, - IEdge, - IG6GraphEvent, - IGraph, - ITreeGraph, - IGroup, - INode, - IPoint, - IShape, - LabelStyle, - ModelConfig, - Node, - NodeConfig as G6NodeConfig, - ShapeStyle, - StateStyles, - TreeGraphData as G6TreeGraphData, - GraphData, - Item, - TreeGraph, -} from '@antv/g6'; - -import { MenuConfig } from './plugins'; - -export interface GraphContainerConfig { - style?: React.CSSProperties; - className?: string; - loading?: boolean; - loadingTemplate?: React.ReactElement; - errorTemplate?: React.ReactNode | ((e: Error) => React.ReactNode); -} -export interface NodeConfig extends G6NodeConfig { - value?: any; -} - -export interface EdgeConfig extends G6EdgeConfig { - value?: T; -} - -export interface MiniMapConfig { - show?: boolean; - viewportClassName?: string; - type?: 'default' | 'keyShape' | 'delegate'; - size?: number[]; - delegateStyle?: ShapeStyle; - refresh?: boolean; - padding?: number; -} -export type Shape = Edge | Node; -export type ShapeCfg = EdgeConfig | NodeConfig; -export type IShapeStyle = - | ShapeStyle - | ((edge: Shape | ShapeCfg, graph: IGraph | IGroup | undefined, name?: string) => ShapeStyle); -export type ILabelStyle = - | LabelStyle - | ((edge: Shape | ShapeCfg, graph: IGraph | IGroup | undefined, name?: string) => LabelStyle); - -export interface FetchLoading { - /** 异步请求时的加载动画,仅在配置了异步请求时生效 */ - fetchLoading?: React.ReactNode | ((item: NodeConfig) => React.ReactNode); -} - -export interface ArrowConfig extends G6ArrowConfig { - /** 是否展示 */ - show?: boolean; - /** 箭头类型 */ - type?: string; - /** 箭头大小 */ - size?: number; -} - -export type IArrowConfig = false | ArrowConfig | ((edge: Shape | ShapeCfg | undefined) => ArrowConfig); - -// 通用节点配置 -export interface NodeCfg extends Omit { - /** 节点类型 */ - type?: string; - /** 节点大小 */ - size?: number | number[]; - /** 节点锚点 */ - anchorPoints?: number[][] | ((node: NodeConfig) => number[][]); - /** 节点样式 */ - style?: IShapeStyle; - /** 节点 label 样式 */ - label?: { - style?: ILabelStyle; - }; - /** 节点状态 */ - nodeStateStyles?: StateStyles; - /** 箭头 是否指向节点中心 */ - linkCenter?: boolean; -} -// 通用边配置 -export interface EdgeCfg { - /** 边类型 */ - type?: string | { text: string; subText?: string }; - /** 边 label 配置 */ - label?: { - /** 仅在树图里面生效 */ - content?: string | ((edge: EdgeCfg) => string); - style?: ILabelStyle; - margin?: number; - }; - /** 起始箭头 */ - startArrow?: IArrowConfig; - /** 结束箭头 */ - endArrow?: IArrowConfig; - /** 边样式 */ - style?: IShapeStyle; - /** 边状态 */ - edgeStateStyles?: StateStyles; -} - -export interface CustomCfg { - /** 横向绘制起始位置 */ - startX?: number; - /** 纵向绘制起始位置 */ - startY?: number; - /** 容器宽度 */ - width?: number; -} - -export interface BadgeCfg { - /** 标记位置 */ - position?: 'left' | 'top' | 'right' | 'bottom'; - /** 标记大小 */ - size?: number | number[]; - /** 标记样式 */ - style?: IShapeStyle; -} - -export interface PercentCfg extends Omit { - /** 标记高度 */ - size?: number; - /** 标记位置 */ - position?: 'top' | 'bottom'; - /** 占比背景色 */ - backgroundStyle?: IShapeStyle; -} - -type PluginContainer = { - /** tooltip css 类名 */ - className?: string; - /** tooltip 容器样式 */ - style?: React.CSSProperties; - /** tooltip 容器,默认和 canvas 使用同一父容器 */ - container?: HTMLDivElement | string | null; - /** 自定义模板 */ - customContent?: (item: T) => React.ReactElement; -}; - -type ToolbarCfgCustomContent = { - zoomIn: () => void; - zoomOut: () => void; - toggleFullscreen: () => void; - fullscreen: boolean; - graph: IGraph; -}; - -export interface ToolbarCfg extends Omit, 'container'> { - /** 是否展示 */ - show?: boolean; - /** 缩放因子 */ - zoomFactor?: number; - /** - * @title renderIcon,自定义渲染 - * @deprecated - */ - renderIcon?: ({ - zoomIn, - zoomOut, - toggleFullscreen, - fullscreen, - graph, - }: ToolbarCfgCustomContent) => React.ReactElement; -} - -// 通用 card 配置 -export interface CardNodeCfg extends NodeCfg { - /** graph id */ - readonly _graphId?: string; - /** graph data */ - readonly graphData?: any; - title?: { - /** title 容器样式 */ - containerStyle?: IShapeStyle; - /** title 样式 */ - style?: ILabelStyle; - /** 是否自动隐藏 */ - autoEllipsis?: boolean; - }; - items?: { - /** items 容器样式 */ - containerStyle?: IShapeStyle; - /** item 样式 */ - style?: ILabelStyle; - /** - * item 布局方式 - * - flex: text、value、icon 均分容器宽度 - * - bundled: text、(value、icon) 均分容器宽度(sort: true 时无效) - * - follow: 从左到右依次排放 - * - */ - layout?: 'bundled' | 'flex' | 'follow'; - /** - * 内容横向间距 - * layout: 'follow' 时生效 - */ - itemSpacing?: number; - /** 是否根据 item 顺序绘制 */ - sort?: boolean; - /** item 容器填充 */ - padding?: number | number[]; - }; - /** card 容器填充 */ - padding?: number | number[]; - /** 节点标记 */ - badge?: BadgeCfg; - /** 占比标记 */ - percent?: PercentCfg; - /** 是否自动调节节点宽度 */ - autoWidth?: boolean; - /** 自定义节点 */ - customContent?: (item: CardItems | OrgItem, group: IGroup | undefined, cfg: CustomCfg) => number; -} - -// 卡片配置 -export interface CardItems { - text: string | number; - value?: string | number; - icon?: string; -} -/** 组织架构图 */ -export type OrgItem = { - name: string; - title?: string; - icon?: string; -}; -export interface NodeData { - id: string; - value: T; - children?: NodeData[]; -} - -export interface EdgeData { - source: string; - target: string; - value?: T; -} -export type MarkerPosition = 'left' | 'right' | 'top' | 'bottom'; -export interface MarkerCfg { - /** 是否展示 */ - show?: boolean; - /** 是否折叠态,推荐使用内置状态 */ - collapsed?: boolean; - position?: MarkerPosition; - style?: ShapeStyle; -} - -export interface TooltipCfg extends PluginContainer { - show?: boolean; - offsetX?: number; - offsetY?: number; - /** 是否展示 */ - shouldBegin?: (evt?: IG6GraphEvent) => boolean; - /** item 类型 ['node','edge'] */ - itemTypes?: string[]; - /** 触发方式 */ - trigger?: 'mouseenter' | 'click'; - /** 固定位置 */ - fixToNode?: [number, number] | undefined; -} - -export type IMarkerCfg = - | MarkerCfg - | MarkerCfg[] - | ((cfg: CardNodeCfg, graph: IGraph | IGroup | undefined) => MarkerCfg | MarkerCfg[]); - -export type Datum = Record; - -// Graph 通用配置 -export interface CommonConfig extends GraphContainerConfig { - data: Datum; - /** 是否缩放节点大小自适应容器 */ - autoFit?: boolean; - /** 是否将图平移到中心位置 */ - fitCenter?: boolean; - width?: number; - height?: number; - pixelRatio?: number; - /** 不同组件 layout 有差别,参考对应组件文档 */ - layout?: T; - /** 边配置 */ - edgeCfg?: EdgeCfg; - /** 节点配置 */ - nodeCfg?: NodeCfg; - /** marker 配置 */ - markerCfg?: IMarkerCfg; - /** 迷你地 */ - minimapCfg?: MiniMapConfig; - /** 交互组件 */ - toolbarCfg?: ToolbarCfg; - /** 提示 */ - tooltipCfg?: TooltipCfg; - /** 右键菜单 */ - menuCfg?: MenuConfig; - /** 交互行为 */ - behaviors?: Array; - /** 是否开启动画 */ - animate?: boolean; - /** - * @title 是否自定义布局 - * @description 开启后,layout 失效,使用 data 里面的 x/y 进行数据布局 - * @example - * ```ts - * { - * id: '-3', - * x: 100, - * y: 100, - * value: { - * title: '来源页面A', - * items: [ - * { - * text: '曝光PV', - * value: '10.30万', - * icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - * }, - * ], - * }, - * }, - * ``` - * @default false - */ - customLayout?: boolean; - /** 图表渲染完成回调 */ - onReady?: (graph: IGraph) => void; -} -export type CardItem = { - title?: string; - items?: CardItems[]; - /** 归一化百分比,仅在 `nodeCfg.percent` 配置时生效 */ - percent?: number; -}; - -export type TreeGraphData = NodeData; -// 流向图节点数据 -export type FlowGraphNodeData = NodeData; -export type FlowGraphEdgeData = EdgeData; - -// 流向图数据 -export interface FlowGraphDatum { - nodes: FlowGraphNodeData[]; - edges: FlowGraphEdgeData[]; -} - -export { - StateStyles, - ShapeStyle, - Node, - Edge, - IGraph, - ITreeGraph, - ModelConfig, - IG6GraphEvent, - IGroup, - LabelStyle, - INode, - IEdge, - Graph, - IPoint, - IShape, - G6TreeGraphData, - GraphData, - Item, - TreeGraph, -}; diff --git a/packages/graphs/src/layout/index.ts b/packages/graphs/src/layout/index.ts deleted file mode 100644 index 8927db7de..000000000 --- a/packages/graphs/src/layout/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -export { radialSectorLayout } from './radial-sector'; -export type { IRadialSectorLayout } from './radial-sector'; - -export type CompactBoxLayout = { - /** 布局方向 */ - direction?: 'LR' | 'RL' | 'TB' | 'BT' | 'H' | 'V'; - /** 节点高度 */ - getHeight?: number | (() => number); - /** 节点宽度 */ - getWidth?: number | (() => number); - /** 每个节点的垂直间隙 */ - getVGap?: number | (() => number); - /** 每个节点的水平间隙 */ - getHGap?: number | (() => number); - /** 是否按照辐射状布局。若 radial 为 true,建议 direction 设置为 'LR' 或 'RL' */ - radial?: boolean; - [key: string]: unknown; -}; -export type DendrogramLayout = { - /** 布局方向 */ - direction?: 'LR' | 'RL' | 'TB' | 'BT' | 'H' | 'V'; - /** 节点间距 */ - nodeSep?: number; - /** 层与层之间的间距 */ - rankSep?: number; - /** 是否按照辐射状布局。若 radial 为 true,建议 direction 设置为 'LR' 或 'RL' */ - radial?: boolean; - [key: string]: unknown; -}; diff --git a/packages/graphs/src/layout/radial-sector.ts b/packages/graphs/src/layout/radial-sector.ts deleted file mode 100644 index 0b25ac301..000000000 --- a/packages/graphs/src/layout/radial-sector.ts +++ /dev/null @@ -1,106 +0,0 @@ -type INode = { - id: string; - x?: number; - y?: number; - layer?: number; - [key: string]: unknown; -}; - -export type IRadialSectorLayout = { - /** 布局中心 [x,y] */ - center: [number, number]; - /** 事件节点坐标 */ - eventNodePosition: [number, number]; - /** 画布当前节点信息,可通过 graph.getNodes().map(n => n.getModel()) 获取 */ - nodes: INode[]; - /** 布局节点,拓展时的新节点,会和当前画布节点做去重处理 */ - layoutNodes: INode[]; - options?: { - /** 圈层半径 */ - unitRadius: number; - /** 节点直径 */ - nodeSize: number; - /** 节点间距 */ - nodeSpacing: number; - }; -}; - -export const radialSectorLayout = (params: IRadialSectorLayout): INode[] => { - const { center, eventNodePosition, nodes: allNodes, layoutNodes, options = {} } = params; - const { unitRadius = 80, nodeSize = 20, nodeSpacing = 10 } = options as IRadialSectorLayout['options']; - - if (!layoutNodes.length) layoutNodes; - - // 过滤已经在画布上的节点 - const pureLayoutNodes = layoutNodes.filter((node) => { - return ( - allNodes.findIndex((n) => { - const { id } = n; - return id === node.id; - }) !== -1 - ); - }); - if (!pureLayoutNodes.length) return layoutNodes; - - const getDistance = (point1: Partial, point2: Partial) => { - const dx = point1.x - point2.x; - const dy = point1.y - point2.y; - return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); - }; - - // 节点裁剪 - const [centerX, centerY] = center; - const [ex, ey] = eventNodePosition; - const diffX = ex - centerX; - const diffY = ey - centerY; - const allNodePositions: INode[] = []; - allNodes.forEach((n) => { - const { id, x, y } = n; - allNodePositions.push({ - id, - x, - y, - layer: Math.round(getDistance({ x, y }, { x: centerX, y: centerY })) / unitRadius, - }); - }); - - const currentRadius = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2)); - const degree = Math.atan2(diffY, diffX); - let minRadius = currentRadius + unitRadius; - - let pureNodePositions: Partial[] = []; - const getNodesPosition = (nodes: INode[], r: number) => { - const degreeStep = 2 * Math.asin((nodeSize + nodeSpacing) / 2 / r); - pureNodePositions = []; - const l = nodes.length - 1; - nodes.forEach((n, i) => { - n.x = centerX + r * Math.cos(degree + (-l / 2 + i) * degreeStep); - n.y = centerY + r * Math.sin(degree + (-l / 2 + i) * degreeStep); - pureNodePositions.push({ x: n.x as number, y: n.y as number }); - }); - }; - - const checkOverlap = (nodesPosition: INode[], pureNodesPosition: Partial[]) => { - let hasOverlap = false; - const checkLayer = Math.round(minRadius / unitRadius); - const loopNodes = nodesPosition.filter((n) => n.layer === checkLayer); - for (let i = 0; i < loopNodes.length; i++) { - const n = loopNodes[i]; - // 因为是同心圆布局,最先相交的应该是收尾节点 - if ( - getDistance(pureNodesPosition[0], n) < nodeSize || - getDistance(pureNodesPosition[pureNodesPosition.length - 1], n) < nodeSize - ) { - hasOverlap = true; - break; - } - } - return hasOverlap; - }; - getNodesPosition(pureLayoutNodes, minRadius); - while (checkOverlap(allNodePositions, pureNodePositions)) { - minRadius += unitRadius; - getNodesPosition(pureLayoutNodes, minRadius); - } - return layoutNodes; -}; diff --git a/packages/graphs/src/plugins/components/base.ts b/packages/graphs/src/plugins/components/base.ts deleted file mode 100644 index 70d1b0ca3..000000000 --- a/packages/graphs/src/plugins/components/base.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { wrapBehavior, each, deepMix } from '@antv/util'; -import { IAbstractGraph as IGraph, IG6GraphEvent } from '@antv/g6'; - -export interface IPluginBaseConfig { - container?: HTMLDivElement | string | null; - className?: string; - graph?: IGraph; - [key: string]: any; -} - -interface EventMapType { - [key: string]: any; -} - -export default abstract class PluginBase { - private _events: EventMapType; - - public _cfgs: IPluginBaseConfig; - - public destroyed: boolean; - - /** - * 插件基类的构造函数 - * @param cfgs 插件的配置项 - */ - constructor(cfgs?: IPluginBaseConfig) { - this._cfgs = deepMix(this.getDefaultCfgs(), cfgs); - this._events = {}; - this.destroyed = false; - } - - /** - * 获取默认的插件配置 - */ - public getDefaultCfgs() { - return {}; - } - - /** - * 初始化插件 - * @param graph IGraph 实例 - */ - public initPlugin(graph: IGraph) { - const self = this; - self.set('graph', graph); - const events = self.getEvents(); - - const bindEvents: EventMapType = {}; - - each(events, (v, k) => { - const event = wrapBehavior(self, v) as (e: IG6GraphEvent) => void; - bindEvents[k] = event; - graph.on(k, event); - }); - this._events = bindEvents; - - this.init(); - } - - /** - * 初始化方法,供子类实现 - */ - public abstract init(); - - /** - * 获取插件中的事件和事件处理方法,供子类实现 - */ - public getEvents() { - return {}; - } - - /** - * 获取配置项中的某个值 - * @param key 键值 - */ - public get(key: string) { - return this._cfgs?.[key]; - } - - /** - * 将指定的值存储到 cfgs 中 - * @param key 键值 - * @param val 设置的值 - */ - public set(key: string, val: any) { - this._cfgs[key] = val; - } - - /** - * 销毁方法,供子类复写 - */ - public destroy() {} - - /** - * 销毁插件 - */ - public destroyPlugin() { - this.destroy(); - const graph = this.get('graph'); - const events = this._events; - each(events, (v, k) => { - graph.off(k, v); - }); - (this._events as EventMapType | null) = null; - (this._cfgs as IPluginBaseConfig | null) = null; - this.destroyed = true; - } -} diff --git a/packages/graphs/src/plugins/components/menu/index.ts b/packages/graphs/src/plugins/components/menu/index.ts deleted file mode 100644 index 8fa9054a9..000000000 --- a/packages/graphs/src/plugins/components/menu/index.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { modifyCSS, createDom } from '@antv/dom-util'; -import React from 'react'; -import { isString } from '@antv/util'; -import insertCss from 'insert-css'; -import { IAbstractGraph as IGraph, IG6GraphEvent, Item } from '@antv/g6'; -import Base, { IPluginBaseConfig } from '../base'; -import { render } from '../../../utils'; - -typeof document !== 'undefined' && - insertCss(` - .g6-component-contextmenu { - border: 1px solid #e2e2e2; - border-radius: 4px; - font-size: 12px; - color: #545454; - background-color: rgba(255, 255, 255, 0.9); - padding: 10px 8px; - box-shadow: rgb(174, 174, 174) 0px 0px 10px; - } - .g6-contextmenu-ul { - padding: 0; - margin: 0; - list-style: none; - } - -`); - -export interface MenuConfig extends IPluginBaseConfig { - show?: boolean; - handleMenuClick?: (target: HTMLElement, item: Item) => void; - customContent?: (evt?: IG6GraphEvent) => React.ReactNode; - // offsetX 与 offsetY 需要加上父容器的 padding - offsetX?: number; - offsetY?: number; - shouldBegin?: (evt?: IG6GraphEvent) => boolean; - // 允许出现 tooltip 的 item 类型 - itemTypes?: string[]; - trigger?: 'click' | 'contextmenu'; -} - -export default class Menu extends Base { - constructor(config?: MenuConfig) { - super(config); - } - - public getDefaultCfgs(): MenuConfig { - return { - offsetX: 6, - offsetY: 6, - handleMenuClick: undefined, - // 指定菜单内容,function(e) {...} - customContent: (e) => { - return ` -
    -
  • 菜单项1
  • -
  • 菜单项2
  • -
- `; - }, - shouldBegin: (e) => { - return true; - }, - // 菜单隐藏事件 - onHide() { - return true; - }, - itemTypes: ['node', 'edge', 'combo'], - trigger: 'contextmenu', - }; - } - - // class-methods-use-this - public getEvents() { - if (this.get('trigger') === 'click') { - return { - click: 'onMenuShow', - touchend: 'onMenuShow', - }; - } - return { - contextmenu: 'onMenuShow', - }; - } - - public init() { - const className = this.get('className'); - const menu = createDom(`
`); - modifyCSS(menu, { top: '0px', position: 'absolute', visibility: 'hidden' }); - let container: HTMLDivElement | null = this.get('container'); - if (!container) { - container = this.get('graph').get('container'); - } - if (isString(container)) { - container = document.getElementById(container) as HTMLDivElement; - } - - container.appendChild(menu); - - this.set('menu', menu); - } - - protected onMenuShow(e: IG6GraphEvent) { - const self = this; - e.preventDefault(); - - const itemTypes = this.get('itemTypes'); - if (!e.item) { - if (itemTypes.indexOf('canvas') === -1) { - self.onMenuHide(); - return; - } - } else { - if (e.item && e.item.getType && itemTypes.indexOf(e.item.getType()) === -1) { - self.onMenuHide(); - return; - } - } - - const shouldBegin = this.get('shouldBegin'); - if (!shouldBegin(e)) return; - - const menuDom = this.get('menu'); - const customContent = this.get('customContent'); - const graph: IGraph = this.get('graph'); - const menu = customContent(e, graph); - render(menu, menuDom); - // 清除之前监听的事件 - this.removeMenuEventListener(); - - const handleMenuClick = this.get('handleMenuClick'); - if (handleMenuClick) { - const handleMenuClickWrapper = (evt) => { - handleMenuClick(evt.target, e.item, graph); - }; - this.set('handleMenuClickWrapper', handleMenuClickWrapper); - menuDom.addEventListener('click', handleMenuClickWrapper); - } - - const width: number = graph.get('width'); - const height: number = graph.get('height'); - - const bbox = menuDom.getBoundingClientRect(); - - const offsetX = this.get('offsetX') || 0; - const offsetY = this.get('offsetY') || 0; - - const graphTop = graph.getContainer().offsetTop; - const graphLeft = graph.getContainer().offsetLeft; - - let x = e.canvasX + graphLeft + offsetX; - let y = e.canvasY + graphTop + offsetY; - - // when the menu is (part of) out of the canvas - if (x + bbox.width > width) { - x = e.canvasX - bbox.width - offsetX + graphLeft; - } - if (y + bbox.height > height) { - y = e.canvasY - bbox.height - offsetY + graphTop; - } - - modifyCSS(menuDom, { - top: `${y}px`, - left: `${x}px`, - visibility: 'visible', - }); - - // 左键单击会触发 body 上监听的 click 事件,导致菜单展示出来后又立即被隐藏了,需要过滤掉 - let triggeredByFirstClick = this.get('trigger') === 'click'; - const handler = (evt) => { - if (triggeredByFirstClick) { - triggeredByFirstClick = false; - return; - } - self.onMenuHide(); - }; - - // 如果在页面中其他任意地方进行click, 隐去菜单 - document.body.addEventListener('click', handler); - this.set('handler', handler); - } - - private removeMenuEventListener() { - const handleMenuClickWrapper = this.get('handleMenuClickWrapper'); - const handler = this.get('handler'); - - if (handleMenuClickWrapper) { - const menuDom = this.get('menu'); - menuDom.removeEventListener('click', handleMenuClickWrapper); - this.set('handleMenuClickWrapper', null); - } - if (handler) { - document.body.removeEventListener('click', handler); - } - } - - private onMenuHide() { - const menuDom = this.get('menu'); - if (menuDom) { - modifyCSS(menuDom, { visibility: 'hidden' }); - } - - // 隐藏菜单后需要移除事件监听 - this.removeMenuEventListener(); - } - - public destroy() { - const menu = this.get('menu'); - this.removeMenuEventListener(); - - if (menu) { - let container: HTMLDivElement | null = this.get('container'); - if (!container) { - container = this.get('graph').get('container'); - } - if (isString(container)) { - container = document.getElementById(container) as HTMLDivElement; - } - container.removeChild(menu); - } - } -} diff --git a/packages/graphs/src/plugins/components/toolbar/index.tsx b/packages/graphs/src/plugins/components/toolbar/index.tsx deleted file mode 100644 index fb95999ac..000000000 --- a/packages/graphs/src/plugins/components/toolbar/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React, { useEffect, useRef, Fragment } from 'react'; -import useFullscreen from '../../../hooks/useFullscreen'; -import { Graph, ToolbarCfg } from '../../../interface'; - -export interface IToolbar { - toolbarCfg: ToolbarCfg; - graph: Graph; - container: HTMLDivElement | null; -} - -export const Toolbar: React.FC = ({ toolbarCfg, container, graph }) => { - const useGraph = useRef(); - const width = useRef(); - const height = useRef(); - const zoom = useRef(1); - const { zoomFactor = 0.25, renderIcon, customContent } = toolbarCfg; - const [fullscreen, toggleFullscreen] = useFullscreen(container); - // 获取当全屏时的窗口大小 - const getWindow = () => { - return [window.outerWidth, window.outerHeight]; - }; - // 切换全屏时保存 graph 尺寸 - const toggleWidth = (f: boolean) => { - const size = f ? getWindow() : [width.current, height.current]; - useGraph.current?.changeSize(size[0] as number, size[1] as number); - }; - - // 获取缩放中心 - const getCenter = () => { - if (!container) { - return { - x: 0, - y: 0, - }; - } - return { - x: container.clientWidth / 2, - y: container.clientHeight / 2, - }; - }; - - // in 放大 - const zoomIn = () => { - useGraph.current?.zoom(Math.min(zoom.current + zoomFactor, 5), getCenter()); - }; - - // out 缩小 - const zoomOut = () => { - useGraph.current?.zoom(Math.max(zoom.current - zoomFactor, 0.25), getCenter()); - }; - - useEffect(() => { - if (graph) { - useGraph.current = graph; - width.current = container?.clientWidth; - height.current = container?.clientHeight; - } - }, [graph]); - - const setToggleFullscreen = () => { - toggleFullscreen(); - toggleWidth(!document.fullscreenElement); - }; - - const customRender = customContent || renderIcon; - - if (customRender) { - return customRender({ - zoomIn, - zoomOut, - toggleFullscreen: setToggleFullscreen, - fullscreen, - graph, - }); - } - - return ( - - {!fullscreen ? ( - - ☐ - - ) : ( - - ⚄ - - )} - - + - - - - - - - ); -}; diff --git a/packages/graphs/src/plugins/index.ts b/packages/graphs/src/plugins/index.ts deleted file mode 100644 index 3d1ed84b3..000000000 --- a/packages/graphs/src/plugins/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { processMinimap } from './minimap'; -export { processTooltip } from './tooltip'; -export { processMenu } from './menu'; -export { processToolbar } from './toolbar'; -export type { MenuConfig } from './components/menu'; diff --git a/packages/graphs/src/plugins/menu.ts b/packages/graphs/src/plugins/menu.ts deleted file mode 100644 index aa2073273..000000000 --- a/packages/graphs/src/plugins/menu.ts +++ /dev/null @@ -1,26 +0,0 @@ -import G6 from '@antv/g6'; -import Menu from './components/menu'; -import type { MenuConfig } from './components/menu'; -import { Graph } from '../interface'; - -export const processMenu = (cfg: MenuConfig = {}, graph: Graph): void => { - if (!graph || graph.destroyed) { - return; - } - // 兼容旧逻辑 - const [pluginMenu] = graph.get('plugins').filter((plugin) => plugin.get('name') === 'menu'); - if (pluginMenu) { - graph.removePlugin(pluginMenu); - } - if (cfg.show || cfg.show === undefined) { - const menu = new Menu({ - offsetX: 16 + 10, - offsetY: 0, - itemTypes: ['node'], - ...cfg, - name: 'menu', - }); - - graph.addPlugin(menu as any); - } -}; diff --git a/packages/graphs/src/plugins/minimap.ts b/packages/graphs/src/plugins/minimap.ts deleted file mode 100644 index 1eeeb36c7..000000000 --- a/packages/graphs/src/plugins/minimap.ts +++ /dev/null @@ -1,22 +0,0 @@ -import G6 from '@antv/g6'; -import { defaultMinimapCfg } from '../constants'; -import { MiniMapConfig, Graph, TreeGraph } from '../interface'; - -export const processMinimap = (cfg: MiniMapConfig | undefined = {}, graph: Graph | TreeGraph): void => { - if (!graph || graph.destroyed) { - return; - } - const [pluginMinimap] = graph.get('plugins').filter((plugin) => plugin.get('name') === 'minimap'); - if (pluginMinimap) { - graph.removePlugin(pluginMinimap); - } - if (cfg.show) { - const curMminimapCfg = Object.assign(defaultMinimapCfg, cfg); - const minimap = new G6.Minimap({ - ...curMminimapCfg, - name: 'minimap', - }); - - graph.addPlugin(minimap); - } -}; diff --git a/packages/graphs/src/plugins/toolbar.tsx b/packages/graphs/src/plugins/toolbar.tsx deleted file mode 100644 index 24e1e3ec7..000000000 --- a/packages/graphs/src/plugins/toolbar.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { defaultToolbarStyle } from '../constants'; -import { ToolbarCfg, Graph } from '../interface'; -import { Toolbar } from './components/toolbar'; -import { createNode } from '../utils'; - -export const processToolbar = (cfg: ToolbarCfg = {}, graph: Graph, container: HTMLDivElement): void => { - const { show, className, style, ...rest } = cfg; - const graphId = graph?.get('id'); - if (!graph || graph.destroyed) { - return; - } - const toolbarId = `${graphId}-toolbar`; - const exist = document.querySelector(`#${toolbarId}`); - if (exist) { - exist.parentNode?.removeChild(exist); - } - if (show) { - const mountPoint = createNode( - , - { - className: className ?? 'charts-toolbar', - id: toolbarId, - }, - { - ...defaultToolbarStyle, - ...style, - }, - ); - // @ts-ignore - container.appendChild(mountPoint); - } -}; diff --git a/packages/graphs/src/plugins/tooltip.ts b/packages/graphs/src/plugins/tooltip.ts deleted file mode 100644 index 19df56fc3..000000000 --- a/packages/graphs/src/plugins/tooltip.ts +++ /dev/null @@ -1,34 +0,0 @@ -import G6, { Tooltip } from '@antv/g6'; -import { isFunction } from '@antv/util'; -import { TooltipCfg, Graph } from '../interface'; -import { createNode } from '../utils'; - -export const processTooltip = (cfg: TooltipCfg = {}, graph: Graph): void => { - if (!graph || graph.destroyed) { - return; - } - const [pluginTooltip] = graph.get('plugins').filter((plugin) => plugin.get('name') === 'tooltip'); - if (pluginTooltip) { - graph.removePlugin(pluginTooltip); - } - // 兼容旧数据 - if (cfg.show || (cfg.show === undefined && Object.keys(cfg).length > 0)) { - const { customContent, ...rest } = cfg; - const tooltip = new G6.Tooltip({ - offsetX: 10, - offsetY: 20, - itemTypes: ['node'], - ...rest, - getContent(e) { - return isFunction(customContent) - ? createNode(customContent(e.item.getModel()), { - className: 'g6-tooltip', - }) - : ''; - }, - name: 'tooltip', - }); - - graph.addPlugin(tooltip); - } -}; diff --git a/packages/graphs/src/preset.ts b/packages/graphs/src/preset.ts new file mode 100644 index 000000000..b65997092 --- /dev/null +++ b/packages/graphs/src/preset.ts @@ -0,0 +1,3 @@ +import { registerBuiltInExtensions } from './core/registry/register'; + +registerBuiltInExtensions(); diff --git a/packages/graphs/src/types.ts b/packages/graphs/src/types.ts new file mode 100644 index 000000000..a19848fae --- /dev/null +++ b/packages/graphs/src/types.ts @@ -0,0 +1,32 @@ +import type { ContainerConfig } from '@ant-design/charts-util'; +import type { BehaviorOptions, GraphOptions as G6GraphOptions, Graph, PluginOptions } from '@antv/g6'; +import type { TransformOptions } from '@antv/g6/lib/spec'; + +export interface GraphOptions extends ContainerConfig, Omit { + /** + * 当图初始化完成后(即 new Graph() 之后)执行回调 + */ + onInit?: (graph: Graph) => void; + /** + * 当图渲染完成后(即 graph.render() 之后)执行回调 + */ + onReady?: (graph: Graph) => void; + /** + * 当图销毁后(即 graph.destroy() 之后)执行回调 + */ + onDestroy?: () => void; + /** + * 交互 + */ + behaviors?: BehaviorOptions | ((this: Graph, behaviors: BehaviorOptions) => BehaviorOptions); + /** + * 画布插件 + */ + plugins?: PluginOptions | ((this: Graph, plugins: PluginOptions) => PluginOptions); + /** + * 数据转换器 + */ + transforms?: TransformOptions | ((this: Graph, transforms: TransformOptions) => TransformOptions); +} + +export type ParsedGraphOptions = Required; diff --git a/packages/graphs/src/utils/async-events.ts b/packages/graphs/src/utils/async-events.ts deleted file mode 100644 index 966f3672f..000000000 --- a/packages/graphs/src/utils/async-events.ts +++ /dev/null @@ -1,19 +0,0 @@ -const EventMap = new Map(); - -export const pushAsyncEvent = (eventId: string, event: Function) => { - let events = EventMap.get(eventId); - if (!events) { - events = [event]; - } else { - events.push(event); - } - EventMap.set(eventId, events); -}; - -export const runAsyncEvent = (eventId: string) => { - let events = EventMap.get(eventId) || []; - events.forEach(function (event) { - if (typeof event === 'function') event.apply(null, arguments); - }); - EventMap.set(eventId, []); -}; diff --git a/packages/graphs/src/utils/chart-loading.tsx b/packages/graphs/src/utils/chart-loading.tsx deleted file mode 100644 index 1c2030f49..000000000 --- a/packages/graphs/src/utils/chart-loading.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import ContentLoader from 'react-content-loader'; - -interface Props { - loadingTemplate?: React.ReactElement; -} - -export const ChartLoading = ({ loadingTemplate }: Props) => { - const renderLoading = () => { - if (loadingTemplate) { - return loadingTemplate; - } - return ( - - - - - - - - - - - - - ); - }; - - return ( -
- {renderLoading()} -
- ); -}; diff --git a/packages/graphs/src/utils/clone-besides-img.ts b/packages/graphs/src/utils/clone-besides-img.ts deleted file mode 100644 index fe3e7a7b3..000000000 --- a/packages/graphs/src/utils/clone-besides-img.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isObject, isString, clone } from '@antv/util'; - -export const cloneBesidesImg = (obj: any) => { - const clonedObj = {}; - Object.keys(obj).forEach((key1) => { - const obj2 = obj[key1]; - if (isObject(obj2)) { - const clonedObj2 = {}; - Object.keys(obj2).forEach((key2) => { - const v = obj2[key2]; - if (key2 === 'img' && !isString(v)) return; - clonedObj2[key2] = clone(v); - }); - clonedObj[key1] = clonedObj2; - } else { - clonedObj[key1] = clone(obj2); - } - }); - return clonedObj; -}; diff --git a/packages/graphs/src/utils/close-fetch-loading.ts b/packages/graphs/src/utils/close-fetch-loading.ts deleted file mode 100644 index be66de681..000000000 --- a/packages/graphs/src/utils/close-fetch-loading.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { prefix } from '../constants'; -/** 关闭加载动画 */ -export const closeFetchLoading = () => { - const hideContainer = document.getElementsByClassName(`${prefix}-antd-loading`); - Array.from(hideContainer).forEach((el) => { - document.body.removeChild(el); - }); -}; diff --git a/packages/graphs/src/utils/count-by.ts b/packages/graphs/src/utils/count-by.ts deleted file mode 100644 index ccdd88a98..000000000 --- a/packages/graphs/src/utils/count-by.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { reduce } from '@antv/util'; -/** - * From lodash - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The corresponding value of - * each key is the number of times the key was returned by `iteratee`. The - * iteratee is invoked with one argument: (value). - * - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * const users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'betty', 'active': true }, - * { 'user': 'fred', 'active': false } - * ] - * - * countBy(users, value => value.active); - * // => { 'true': 2, 'false': 1 } - */ -export const countBy = (collection: Array, iteratee?: Function) => { - return reduce( - collection, - (result, value, key) => { - key = iteratee ? iteratee(value) : value; - if (result.hasOwnProperty(key)) { - ++result[key]; - } else { - result[key] = 1; - } - return result; - }, - {}, - ); -}; diff --git a/packages/graphs/src/utils/create-fetch-loading.ts b/packages/graphs/src/utils/create-fetch-loading.ts deleted file mode 100644 index c1a0d6072..000000000 --- a/packages/graphs/src/utils/create-fetch-loading.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { isFunction } from '@antv/util'; -import { NodeConfig, FetchLoading } from '../interface'; -import { prefix } from '../constants'; -import { createNode } from './create-node'; -import { setStyles } from './set-styles'; - -/** 开启加载动画, 不支持同时存在多个 */ -export const createFetchLoading = (node: NodeConfig, fetchLoading: FetchLoading) => { - const loadingClassName = `${prefix}-antd-loading`; - if (fetchLoading) { - const loadingTemplate = isFunction(fetchLoading) ? fetchLoading(node) : fetchLoading; - document.body.appendChild( - createNode(loadingTemplate, { - className: loadingClassName, - }), - ); - } else { - const container = document.createElement('div'); - container.className = loadingClassName; - const styles = { - position: 'fixed' as 'fixed', - left: '0', - top: '0', - width: '100vw', - height: '100vh', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - background: 'rgba(0,0,0, 0.25)', - color: '#fff', - fontSize: '16px', - zIndex: 999, - }; - setStyles(container, styles); - const span = document.createElement('span'); - span.innerText = 'loading...'; - container.appendChild(span); - document.body.appendChild(container); - } -}; diff --git a/packages/graphs/src/utils/create-marker.ts b/packages/graphs/src/utils/create-marker.ts deleted file mode 100644 index 4535eb00e..000000000 --- a/packages/graphs/src/utils/create-marker.ts +++ /dev/null @@ -1,25 +0,0 @@ -import G6 from '@antv/g6'; -import { IGroup, MarkerCfg } from '../interface'; -import { defaultCardStyle } from '../constants'; -import { getMarkerPosition } from './get-marker-position'; - -export const createMarker = (cfg: MarkerCfg, group: IGroup | undefined, size: number[], suffix?: boolean) => { - const { show, position, collapsed, style } = cfg; - if (show) { - group!.addShape('marker', { - attrs: { - ...getMarkerPosition(position, size), - r: 6, - cursor: 'pointer', - symbol: collapsed ? G6.Marker.expand : G6.Marker.collapse, - stroke: defaultCardStyle.stroke, - lineWidth: 1, - fill: '#fff', - ...style, - }, - defaultCollapsed: false, - name: suffix ? `collapse-icon-${position}` : 'collapse-icon', - position: position, - }); - } -}; diff --git a/packages/graphs/src/utils/create-node.ts b/packages/graphs/src/utils/create-node.ts deleted file mode 100644 index cae2ff867..000000000 --- a/packages/graphs/src/utils/create-node.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { render } from './render'; -import { setStyles } from './set-styles'; - -export const createNode = (children: React.ReactNode, attrs: object = {}, styles?: React.CSSProperties) => { - const mountPoint = document.createElement('div'); - Object.keys(attrs).forEach((key) => { - mountPoint[key] = attrs[key]; - }); - if (styles) { - setStyles(mountPoint, styles); - } - render(children as React.ReactElement, mountPoint); - return mountPoint; -}; diff --git a/packages/graphs/src/utils/deep-clone.ts b/packages/graphs/src/utils/deep-clone.ts deleted file mode 100644 index 3124affcb..000000000 --- a/packages/graphs/src/utils/deep-clone.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 深克隆 - * @param source 要深克隆的目标对象 - */ -export const deepClone = (source: Object | undefined) => { - if (!source || typeof source !== 'object') { - return source; - } - - let target; - if (Array.isArray(source)) { - target = source.map((item) => deepClone(item)); - } else if (source instanceof HTMLElement) { - target = source; - } else { - target = {}; - Object.keys(source).forEach((key) => { - return (target[key] = deepClone(source[key])); - }); - } - - return target; -}; diff --git a/packages/graphs/src/utils/event-data.ts b/packages/graphs/src/utils/event-data.ts deleted file mode 100644 index e4a9ff880..000000000 --- a/packages/graphs/src/utils/event-data.ts +++ /dev/null @@ -1,13 +0,0 @@ -type Datum = any; -export default class EventData { - data: Datum; - constructor(data?: Datum) { - data && this.setData(data); - } - getData(): Datum { - return this.data; - } - setData(data: Datum) { - this.data = data; - } -} diff --git a/packages/graphs/src/utils/get-anchor-points.ts b/packages/graphs/src/utils/get-anchor-points.ts deleted file mode 100644 index df1d26604..000000000 --- a/packages/graphs/src/utils/get-anchor-points.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defaultFlowGraphAnchorPoints } from '../constants'; -import { NodeConfig } from '../interface'; -/** 支持自定义 anchor */ -export const getAnchorPoints = (anchorPoints: ((node: NodeConfig) => number[][]) | number[][], node?: NodeConfig) => { - if (typeof anchorPoints === 'function' && node) { - return anchorPoints(node) || {}; - } - if (Array.isArray(anchorPoints)) { - return anchorPoints; - } - return defaultFlowGraphAnchorPoints; -}; diff --git a/packages/graphs/src/utils/get-arrow-cfg.ts b/packages/graphs/src/utils/get-arrow-cfg.ts deleted file mode 100644 index 5719fe721..000000000 --- a/packages/graphs/src/utils/get-arrow-cfg.ts +++ /dev/null @@ -1,29 +0,0 @@ -import G6 from '@antv/g6'; -import { IArrowConfig, EdgeConfig } from '../interface'; - -// 默认箭头样式 -export const getArrowCfg = ( - arrowCfg: IArrowConfig | undefined, - edge?: EdgeConfig< - | string - | { - text?: string; - subText?: string; - } - >, -) => { - if (!arrowCfg) { - return; - } - if (typeof arrowCfg === 'object' && arrowCfg?.show === false) { - return; - } - const cfg = typeof arrowCfg === 'function' ? arrowCfg(edge) : arrowCfg; - const { type = 'vee', d = 0, size = 10 } = cfg; - return { - path: G6.Arrow[type](size, size, d), - fill: '#ccc', - d, - ...cfg, - }; -}; diff --git a/packages/graphs/src/utils/get-center-node.ts b/packages/graphs/src/utils/get-center-node.ts deleted file mode 100644 index c9d2cc385..000000000 --- a/packages/graphs/src/utils/get-center-node.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { countBy } from './count-by'; -import { GraphData } from '../interface'; - -export const getCenterNode = (data: GraphData) => { - const { nodes, edges } = data; - if (nodes.length === 1) { - return nodes[0].id; - } - const linkCount: string[] = []; - edges.forEach((item) => { - linkCount.push(item.source); - }); - const timesObj = countBy(linkCount); - let maxTimes = 0; - let maxTimeKey = ''; - for (let k in timesObj) { - if (timesObj.hasOwnProperty(k) && timesObj[k] > maxTimes) { - maxTimes = timesObj[k]; - maxTimeKey = k; - } - } - return maxTimeKey; -}; diff --git a/packages/graphs/src/utils/get-children-data.ts b/packages/graphs/src/utils/get-children-data.ts deleted file mode 100644 index 7fa4155d7..000000000 --- a/packages/graphs/src/utils/get-children-data.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Datum } from '../interface'; - -/** - * get children - * 获取相关路径下的一级节点 - */ -export const getChildrenData = (data: Datum, currentPath: string = ''): Datum => { - // 打标时已经做了编码,这直接取值即可 - const path = currentPath.split('-'); - path.shift(); // 根节点没有 path - let current = data; - path.forEach((childrenIndex: string) => { - if (!current) return; - current = current.children[Number(childrenIndex)]; - }); - if (!current?.children) { - return []; - } - return current.children.map((item: Datum) => ({ - ...item, - children: [], - })); -}; diff --git a/packages/graphs/src/utils/get-common-cfg.ts b/packages/graphs/src/utils/get-common-cfg.ts deleted file mode 100644 index 97252e282..000000000 --- a/packages/graphs/src/utils/get-common-cfg.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { IGraph, IGroup, EdgeConfig, NodeConfig, TreeGraph } from '../interface'; - -// 统一处理 config,支持回调 -export const getCommonCfg = ( - cfg: unknown, - item: - | EdgeConfig< - | string - | { - text?: string; - subText?: string; - } - > - | NodeConfig - | undefined, - graph?: IGraph | IGroup | TreeGraph | undefined, -) => { - if (typeof cfg === 'function') { - return cfg(item, graph); - } - return cfg; -}; diff --git a/packages/graphs/src/utils/get-css-padding.ts b/packages/graphs/src/utils/get-css-padding.ts deleted file mode 100644 index 1d869aaf3..000000000 --- a/packages/graphs/src/utils/get-css-padding.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * padding | margin 按 CSS 规则转换 - */ -export const getCssPadding = (padding: number | number[]) => { - if (typeof padding === 'number') { - return [padding, padding, padding, padding]; - } - let result: number[] = []; - switch (padding.length) { - case 1: - result = [padding[0], padding[0], padding[0], padding[0]]; - break; - case 2: - result = [padding[0], padding[1], padding[0], padding[1]]; - break; - case 3: - result = [padding[0], padding[1], padding[2], padding[1]]; - break; - case 4: - result = padding; - break; - default: - break; - } - return result; -}; diff --git a/packages/graphs/src/utils/get-flow-level-data.ts b/packages/graphs/src/utils/get-flow-level-data.ts deleted file mode 100644 index c4aa6c71f..000000000 --- a/packages/graphs/src/utils/get-flow-level-data.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { prefix } from '../constants'; -import { FlowGraphDatum } from '../interface'; - -/** - * 根据 level 获取相关数据 - */ -export const getFlowLevelData = (data: FlowGraphDatum, level: number): FlowGraphDatum => { - const { nodes, edges } = data; - if (level <= 0) { - return data; - } - - const levelNodes = nodes.filter((item) => !item.hasOwnProperty(`${prefix}_level`) || item[`${prefix}_level`] < level); - - return { - nodes: levelNodes, - edges: edges.filter((item) => { - const { source, target } = item; - return levelNodes.filter((n) => [source, target].includes(n.id)).length >= 2; - }), - }; -}; diff --git a/packages/graphs/src/utils/get-graph-id.ts b/packages/graphs/src/utils/get-graph-id.ts deleted file mode 100644 index 770a6b030..000000000 --- a/packages/graphs/src/utils/get-graph-id.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { uuid } from './uuid'; -// 同一页面存在多 graph 时需要指定 graphId -export const getGraphId = (graph: { current?: string }, name: string) => { - if (graph.current) { - return graph.current; - } - graph.current = `${name}-graph-${uuid()}`; - return graph.current; -}; diff --git a/packages/graphs/src/utils/get-graph-size.ts b/packages/graphs/src/utils/get-graph-size.ts deleted file mode 100644 index bdf18ea05..000000000 --- a/packages/graphs/src/utils/get-graph-size.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Log } from './log'; - -export const getGraphSize = ( - width: number | undefined, - height: number | undefined, - container: React.RefObject, -): number[] => { - let CANVAS_WIDTH; - let CANVAS_HEIGHT; - if (container && container.current) { - CANVAS_WIDTH = container.current.offsetWidth; - CANVAS_HEIGHT = container.current.offsetHeight || 500; - } - if ((!width && !CANVAS_WIDTH) || (!height && !CANVAS_HEIGHT)) { - Log.warn('请为 Graph 指定 width 与 height!否则将使用默认值 500 * 500'); - return [500, 500]; - } - - return [width || CANVAS_WIDTH || 500, height || CANVAS_HEIGHT || 500]; -}; diff --git a/packages/graphs/src/utils/get-marker-position.ts b/packages/graphs/src/utils/get-marker-position.ts deleted file mode 100644 index f7b4f821f..000000000 --- a/packages/graphs/src/utils/get-marker-position.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const getMarkerPosition = (direction: string = 'right', size: number[]) => { - const [width, height] = size; - let x = 0; - let y = 0; - switch (direction) { - case 'top': - x = width / 2; - y = 0; - break; - case 'right': - x = width; - y = height / 2; - break; - case 'bottom': - x = width / 2; - y = height; - break; - case 'left': - x = 0; - y = height / 2; - break; - } - - return { x, y }; -}; diff --git a/packages/graphs/src/utils/get-parent-children.ts b/packages/graphs/src/utils/get-parent-children.ts deleted file mode 100644 index 74cda0c95..000000000 --- a/packages/graphs/src/utils/get-parent-children.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Datum } from '../interface'; -/** - * 将查询到的节点挂载到当前图数据上 - */ -export const setParentChildren = (parendData: Datum, currentPath: string, children: Datum[]): Datum => { - const path = currentPath.split('-'); - path.shift(); - let current = parendData; - path.forEach((childrenIndex: string) => { - current = current.children[Number(childrenIndex)]; - }); - current.children = children; - return parendData; -}; diff --git a/packages/graphs/src/utils/get-size.ts b/packages/graphs/src/utils/get-size.ts deleted file mode 100644 index 32fcc3776..000000000 --- a/packages/graphs/src/utils/get-size.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defaultNodeSize } from '../constants'; - -export const getSize = (size: number | number[] | undefined) => { - if (Array.isArray(size)) { - return size; - } - return size ? [size, size] : defaultNodeSize; -}; diff --git a/packages/graphs/src/utils/get-status-bbox.ts b/packages/graphs/src/utils/get-status-bbox.ts deleted file mode 100644 index febc6dc5e..000000000 --- a/packages/graphs/src/utils/get-status-bbox.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CardNodeCfg } from '../interface'; -import { getSize } from './get-size'; -import { defaultStatusBarWidth } from '../constants'; - -/** - * card status 对布局的影响,直接加到 padding 中 - */ -export const getStatusBBox = (cfg: CardNodeCfg['badge'] | undefined) => { - if (!cfg) { - return [0, 0, 0, 0]; - } - const { size = [], position = 'left' } = cfg; - const [width, height] = getSize(size); - const appendPadding: number[] = []; - switch (position) { - case 'top': - appendPadding.push(height ?? defaultStatusBarWidth, 0, 0, 0); - break; - case 'right': - appendPadding.push(0, width ?? defaultStatusBarWidth, 0, 0); - break; - case 'bottom': - appendPadding.push(0, 0, height ?? defaultStatusBarWidth, 0); - break; - case 'left': - appendPadding.push(0, 0, 0, width ?? defaultStatusBarWidth); - break; - } - return appendPadding; -}; diff --git a/packages/graphs/src/utils/get-status-cfg.ts b/packages/graphs/src/utils/get-status-cfg.ts deleted file mode 100644 index b8fd383f5..000000000 --- a/packages/graphs/src/utils/get-status-cfg.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { CardNodeCfg } from '../interface'; -import { getSize } from './get-size'; -import { defaultStatusBarWidth } from '../constants'; -import { isNumber } from '@antv/util'; - -export const getStatusCfg = (cfg: CardNodeCfg['badge'], cardSize: [number, number]) => { - const { size = [], position = 'left' } = cfg ?? {}; - const [width, height] = getSize(size); - const [cardWidth, cardHeight] = cardSize; - let x = 0; - let y = 0; - let w = 0; - let h = 0; - switch (position) { - case 'top': - x = 0; - y = 0; - w = width ?? cardWidth; - h = height ?? defaultStatusBarWidth; - break; - case 'left': - x = 0; - y = 0; - w = width ?? defaultStatusBarWidth; - h = height ?? cardHeight; - break; - case 'right': - x = cardWidth - (isNumber(width) ? width : defaultStatusBarWidth); - y = 0; - w = width ?? defaultStatusBarWidth; - h = height ?? cardHeight; - break; - case 'bottom': - x = 0; - y = cardHeight - (isNumber(height) ? height : defaultStatusBarWidth); - w = width ?? cardWidth; - h = height ?? defaultStatusBarWidth; - break; - } - - return { - x, - y, - width: w, - height: h, - }; -}; diff --git a/packages/graphs/src/utils/get-style.ts b/packages/graphs/src/utils/get-style.ts deleted file mode 100644 index 82f6f4e3c..000000000 --- a/packages/graphs/src/utils/get-style.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CardNodeCfg, ModelConfig, IGroup, Item } from '../interface'; - -// 统一处理 config,支持回调 -export const getStyle = ( - source: unknown, - cfg: CardNodeCfg | ModelConfig, - item?: IGroup | Item, - current?: string | number, -) => { - if (typeof source === 'function') { - return source(cfg, item, current) || {}; - } - return source || {}; -}; diff --git a/packages/graphs/src/utils/get-tree-level-data.ts b/packages/graphs/src/utils/get-tree-level-data.ts deleted file mode 100644 index 1e47535f4..000000000 --- a/packages/graphs/src/utils/get-tree-level-data.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Datum } from '../interface'; - -/** - * 根据 level 获取相关数据 - */ -export const getTreeLevelData = (data: Datum, level: number): Datum => { - const { children = [], g_level = 0 } = data; - if (level <= 0) { - return data; - } - return { - ...data, - children: - g_level + 1 < level - ? children?.map((item: Datum) => { - return getTreeLevelData(item, level); - }) - : [], - }; -}; diff --git a/packages/graphs/src/utils/get-type.ts b/packages/graphs/src/utils/get-type.ts deleted file mode 100644 index 65543bdb5..000000000 --- a/packages/graphs/src/utils/get-type.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const getType = (n: Object) => { - return Object.prototype.toString.call(n).slice(8, -1); -}; diff --git a/packages/graphs/src/utils/global.ts b/packages/graphs/src/utils/global.ts deleted file mode 100644 index ff64efe82..000000000 --- a/packages/graphs/src/utils/global.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Graph } from '@antv/g6'; - -export const globalInstance = {}; - -export const setGlobalInstance = (instanceId: string, graph: Graph) => { - globalInstance[instanceId] = graph; -}; -export const getGlobalInstance = (instanceId: string) => { - return globalInstance[instanceId]; -}; diff --git a/packages/graphs/src/utils/index.ts b/packages/graphs/src/utils/index.ts deleted file mode 100644 index 2e666b5aa..000000000 --- a/packages/graphs/src/utils/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -export { cloneBesidesImg } from './clone-besides-img'; -export { createMarker } from './create-marker'; -export { getArrowCfg } from './get-arrow-cfg'; -export { getCssPadding } from './get-css-padding'; -export { getStatusBBox } from './get-status-bbox'; -export { getStatusCfg } from './get-status-cfg'; -export { setEllipsis } from './set-ellipsis'; -export { getChildrenData } from './get-children-data'; -export { getStyle } from './get-style'; -export { bindStateEvents } from './state-events'; -export { deepClone } from './deep-clone'; -export { getAnchorPoints } from './get-anchor-points'; -export { getCommonCfg } from './get-common-cfg'; -export { getGraphId } from './get-graph-id'; -export { getGraphSize } from './get-graph-size'; -export { getTreeLevelData } from './get-tree-level-data'; -export { getFlowLevelData } from './get-flow-level-data'; -export { getMarkerPosition } from './get-marker-position'; -export { renderGraph, getRenderData } from './render-graph'; -export { setTreeTag } from './set-tree-tag'; -export { setFlowTag } from './set-flow-tag'; -export { getCenterNode } from './get-center-node'; -export { getSize } from './get-size'; -export { setStyles } from './set-styles'; -export { Log } from './log'; -export { getType } from './get-type'; -export { countBy } from './count-by'; -export { isType } from './is-type'; -export { render, unmount } from './render'; -export { createFetchLoading } from './create-fetch-loading'; -export { closeFetchLoading } from './close-fetch-loading'; -export { getGlobalInstance, setGlobalInstance } from './global'; -export { pushAsyncEvent, runAsyncEvent } from './async-events'; -export { createNode } from './create-node'; -export { ChartLoading } from './chart-loading'; -import EventData from './event-data'; -export { setLevelData } from './set-level-data'; -export { EventData }; diff --git a/packages/graphs/src/utils/is-type.ts b/packages/graphs/src/utils/is-type.ts deleted file mode 100644 index 3c1bce65a..000000000 --- a/packages/graphs/src/utils/is-type.ts +++ /dev/null @@ -1,5 +0,0 @@ -// 类型检测 -export const isType = (value: any, type: string): boolean => { - const { toString } = {}; - return toString.call(value) === `[object ${type}]`; -}; diff --git a/packages/graphs/src/utils/log.ts b/packages/graphs/src/utils/log.ts deleted file mode 100644 index 5681f6914..000000000 --- a/packages/graphs/src/utils/log.ts +++ /dev/null @@ -1 +0,0 @@ -export const Log = window.console; diff --git a/packages/graphs/src/utils/render-graph.ts b/packages/graphs/src/utils/render-graph.ts deleted file mode 100644 index 114b9a27d..000000000 --- a/packages/graphs/src/utils/render-graph.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { IGraph, FlowGraphDatum, TreeGraphData } from '../interface'; -import EventData from './event-data'; -import { deepClone } from './deep-clone'; -import { setTreeTag, setFlowTag, getFlowLevelData, getTreeLevelData } from './'; -import { runAsyncEvent } from './async-events'; - -const isFlowData = (data) => data?.nodes instanceof Array && data?.edges instanceof Array; - -export const getRenderData = (data: FlowGraphDatum | TreeGraphData, level?: number) => { - let originData = deepClone(data); - let tagData = originData; - if (level) { - if (isFlowData(data)) { - tagData = setFlowTag(data as FlowGraphDatum, level); - originData = getFlowLevelData(tagData, level); - } else { - // is tree data - tagData = setTreeTag(data as TreeGraphData); - originData = getTreeLevelData(tagData, level); - } - } - return [originData, tagData]; -}; - -export const renderGraph = (graph: IGraph, data: any, level?: number) => { - const [originData, tagData] = getRenderData(data, level); - graph.data(originData); - graph.set('eventData', new EventData(tagData)); - graph.render(); - // 关闭局部刷新,各种 bug - graph.get('canvas').set('localRefresh', false); - runAsyncEvent(graph.get('id')); -}; diff --git a/packages/graphs/src/utils/set-ellipsis.ts b/packages/graphs/src/utils/set-ellipsis.ts deleted file mode 100644 index 8e67e08bb..000000000 --- a/packages/graphs/src/utils/set-ellipsis.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** 超出省略,不够精准 */ -export const setEllipsis = (text: string, fontStyle: { [key: string]: unknown }, contentWidth: number = 120) => { - const { fontSize = 12, fontWeight = 'normal', fontFamily = 'Arial, sans-serif' } = fontStyle; - const canvas = document.querySelector('canvas'); - const ctx = canvas.getContext('2d'); - ctx.font = `${fontSize}px ${fontWeight} ${fontFamily}`; - let currentText = text; - let flag = false; - ctx.fillText(currentText, 0, 0); - for (let i = text.length - 1; i > 0; i--) { - const { width } = ctx.measureText(currentText); - if (width <= contentWidth) { - if (!flag) return currentText; - // 中文结尾删除最后一位、非中文结尾删除最后2位 - const reg = /[\u4e00-\u9fa5]/; - const lastStr = currentText.substring(currentText.length - 1, currentText.length); - return currentText.substring(0, currentText.length - (reg.test(lastStr) ? 1 : 2)) + '...'; - } else { - flag = true; - currentText = currentText.substring(0, currentText.length - 1); - } - } -}; diff --git a/packages/graphs/src/utils/set-flow-tag.ts b/packages/graphs/src/utils/set-flow-tag.ts deleted file mode 100644 index 9aa16e312..000000000 --- a/packages/graphs/src/utils/set-flow-tag.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { prefix } from '../constants'; -import { Log } from './log'; -import { FlowGraphDatum, FlowGraphNodeData } from '../interface'; - -/** - * 对数据进行打标,加上 level - */ -export const setFlowTag = (data: FlowGraphDatum, limitLevel: number = 100) => { - let resNodes = []; - const { nodes = [], edges = [] } = data; - let levelNodes = []; - const getTarget = (source: string) => { - return edges.filter((item) => { - const used = resNodes.findIndex((t) => t.id === item.target); - if (used !== -1 && item.source === source) { - const loop = getSource(item.source); - Log.warn( - `The data [${item.source},${item.target},${loop[0]?.source}] is in a loop, please check the rationality.`, - ); - } - return used === -1 && item.source === source; - }); - }; - const getSource = (target: string) => { - return edges.filter((item) => item.target === target); - }; - edges.forEach((item) => { - const { source } = item; - if (!edges.find((item) => item.target === source)) levelNodes.push(source); - }); - let level = 0; - - const appendInfo = (item: FlowGraphNodeData) => ({ - [`${prefix}_level`]: level, - [`${prefix}_parent`]: getSource(item.id).map((n) => n.source), - [`${prefix}_children`]: level < limitLevel - 1 ? getTarget(item.id).map((n) => n.target) : [], - }); - - if (!levelNodes.length) { - return { - edges, - nodes: nodes.map((item) => ({ - ...item, - ...appendInfo(item), - })), - }; - } - while (levelNodes.length) { - const currentLevelNodes = nodes.filter((item) => levelNodes.includes(item.id)); - resNodes = resNodes.concat( - currentLevelNodes.map((item) => ({ - ...item, - ...appendInfo(item), - })), - ); - const nextLevelNodes = []; - levelNodes.forEach((source) => { - nextLevelNodes.push(...getTarget(source).map((item) => item.target)); - }); - levelNodes = nextLevelNodes; - level += 1; - } - - return { nodes: resNodes, edges }; -}; diff --git a/packages/graphs/src/utils/set-level-data.ts b/packages/graphs/src/utils/set-level-data.ts deleted file mode 100644 index 474c3e67d..000000000 --- a/packages/graphs/src/utils/set-level-data.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IGraph, Datum } from '../interface'; - -/** - * 挂载异步数据到全局 data - */ -export const setLevelData = (graph: IGraph, data: Datum, currentPath: string) => { - const currentData = graph.get('eventData').getData(); - // 打标时已经做了编码,这直接取值即可 - const path = currentPath.split('-'); - path.shift(); // 根节点没有 path - let current = currentData; - path.forEach((childrenIndex: string) => { - current = current.children[Number(childrenIndex)]; - }); - current.children = data; -}; diff --git a/packages/graphs/src/utils/set-styles.ts b/packages/graphs/src/utils/set-styles.ts deleted file mode 100644 index cf12b9fb8..000000000 --- a/packages/graphs/src/utils/set-styles.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const setStyles = (container: HTMLDivElement, style: React.CSSProperties = {}) => { - const keys = Object.keys(style); - keys.forEach((key: string) => { - container.style[key] = style[key]; - }); -}; diff --git a/packages/graphs/src/utils/set-tree-tag.ts b/packages/graphs/src/utils/set-tree-tag.ts deleted file mode 100644 index 512e68295..000000000 --- a/packages/graphs/src/utils/set-tree-tag.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { prefix } from '../constants'; -import { TreeGraphData } from '../interface'; - -/** - * 对数据进行打标,加上 level 和 parentId - */ -export const setTreeTag = (data: TreeGraphData, level = 0, parentId = '', path: string = '') => { - const { id, children = [] } = data; - return { - [`${prefix}_level`]: level, - [`${prefix}_parentId`]: parentId, - [`${prefix}_currentPath`]: path, - ...data, - children: children?.map((item: TreeGraphData, index: number) => { - return setTreeTag(item, level + 1, parentId ? `${parentId}-${id}` : id, `${path}-${index}`); - }), - }; -}; diff --git a/packages/graphs/src/utils/state-events.ts b/packages/graphs/src/utils/state-events.ts deleted file mode 100644 index 0ef90db97..000000000 --- a/packages/graphs/src/utils/state-events.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { CommonConfig, IGraph, INode, IEdge } from '../interface'; - -// 交互 -export const bindStateEvents = (graph: IGraph, cfg?: Partial | undefined) => { - const { nodeCfg = {}, edgeCfg = {} } = cfg ?? {}; - const { nodeStateStyles } = nodeCfg; - const { edgeStateStyles } = edgeCfg; - /** - * 存储交互状态 - * id: [[endActive, endDefalut], [startActive, startDefalut]] - */ - const statusCache = {}; - - const updateArrowFill = (item: IEdge, endArrowFill: string, stratArrowFill: string) => { - graph.updateItem(item, { - style: { - endArrow: !!endArrowFill && { - fill: endArrowFill, - }, - startArrow: !!stratArrowFill && { - fill: stratArrowFill, - }, - }, - }); - }; - - const setState = (item: INode | IEdge, name: string, status: boolean) => { - status ? item.toFront() : item.toBack(); - const { endArrow, startArrow } = item.getModel().style ?? {}; - - if (endArrow || startArrow) { - if (!statusCache[item.getID()]) { - // @ts-ignore - const { fill: endArrowFill } = endArrow ?? {}; - // @ts-ignore - const { fill: startArrowFill } = startArrow ?? {}; - const hoverStatus = item.getModel().style?.[name]?.stroke; - statusCache[item.getID()] = [ - [hoverStatus ?? endArrowFill, endArrowFill], - [hoverStatus ?? startArrowFill, startArrowFill], - ]; - } - const fill = statusCache[item.getID()]; - updateArrowFill(item as IEdge, endArrow && fill[0][status ? 0 : 1], startArrow && fill[1][status ? 0 : 1]); - } - graph.setItemState(item, name, status); - }; - const getRelationItems = (currentItem: INode | IEdge, name: string, status: boolean, type: string) => { - const relationItems = - type === 'node' - ? graph.findAll('edge', (edge: IEdge) => edge.getSource() === currentItem || edge.getTarget() === currentItem) - : graph.findAll( - 'node', - (node: INode) => - (currentItem as IEdge).getSource().get('id') === node.get('id') || - (currentItem as IEdge).getTarget().get('id') === node.get('id'), - ); - const highlightItems = [currentItem].concat(relationItems); - - highlightItems.forEach((item) => { - setState(item, name, status); - }); - }; - if (nodeStateStyles) { - graph.on('node:mouseenter', (evt) => { - const item = evt.item as INode; - getRelationItems(item, 'hover', true, 'node'); - }); - graph.on('node:mouseleave', (evt) => { - const item = evt.item as INode; - getRelationItems(item, 'hover', false, 'node'); - }); - } - if (edgeStateStyles) { - graph.on('edge:mouseenter', (evt) => { - const item = evt.item as INode; - getRelationItems(item, 'hover', true, 'edge'); - }); - graph.on('edge:mouseleave', (evt) => { - const item = evt.item as INode; - getRelationItems(item, 'hover', false, 'edge'); - }); - } -}; diff --git a/packages/graphs/tests/data/conv-dagre.ts b/packages/graphs/tests/data/conv-dagre.ts deleted file mode 100644 index 781710e85..000000000 --- a/packages/graphs/tests/data/conv-dagre.ts +++ /dev/null @@ -1,451 +0,0 @@ -export const ConvDagreData = { - nodes: [ - { - id: 'node-0', - name: '页面-0', - layerName: '层级0', - measure: { - name: 'DAU', - value: 17500000000, - formattedValue: 175, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 1000000, - formattedValue: 100, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#F5A623', - }, - }, - { - id: 'node-1', - name: '页面1', - layerName: '层级0', - measure: { - name: 'DAU', - value: 5500000000, - formattedValue: 55, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 500000, - formattedValue: 50, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#F5A623', - }, - }, - { - id: 'node-2', - name: '页面2', - layerName: '层级0', - measure: { - name: 'DAU', - value: 5000000000, - formattedValue: 50, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 100000, - formattedValue: 10, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#F5A623', - }, - }, - { - id: 'node-3', - name: '页面3', - layerName: '层级0', - measure: { - name: 'DAU', - value: 900000000, - formattedValue: 9, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 50000, - formattedValue: 5, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#F5A623', - }, - }, - { - id: 'node-4', - name: '页面4', - layerName: '层级0', - measure: { - name: 'DAU', - value: 5700000000, - formattedValue: 57, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 60000, - formattedValue: 6, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#F5A623', - }, - }, - { - id: 'node-5', - name: '页面5', - layerName: '层级1', - measure: { - name: 'DAU', - value: 24000000000, - formattedValue: 240, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 1600000, - formattedValue: 160, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#4A90E2', - }, - }, - { - id: 'node-6', - name: '页面6', - layerName: '层级1', - measure: { - name: 'DAU', - value: 6600000000, - formattedValue: 66, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 110000, - formattedValue: 11, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#4A90E2', - }, - }, - { - id: 'node-7', - name: '页面7', - layerName: '层级2', - measure: { - name: 'DAU', - value: 5000000000, - formattedValue: 50, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 500000, - formattedValue: 50, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#7ED321', - }, - }, - { - id: 'node-8', - name: '页面8', - layerName: '层级2', - measure: { - name: 'DAU', - value: 5000000000, - formattedValue: 50, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 500000, - formattedValue: 50, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#7ED321', - }, - }, - { - id: 'node-9', - name: '页面9', - layerName: '层级2', - measure: { - name: 'DAU', - value: 900000, - formattedValue: 90, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 500000, - formattedValue: 50, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#7ED321', - }, - }, - { - id: 'node-10', - name: '页面10', - layerName: '层级2', - measure: { - name: 'DAU', - value: 10000000000, - formattedValue: 100, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 100000, - formattedValue: 10, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#7ED321', - }, - }, - { - id: 'node-11', - name: '页面11', - layerName: '层级2', - measure: { - name: 'DAU', - value: 1000000000, - formattedValue: 10, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 90000, - formattedValue: 9, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#7ED321', - }, - }, - { - id: 'node-12', - name: '页面12', - layerName: '层级2', - measure: { - name: 'DAU', - value: 600000000, - formattedValue: 6, - formattedUnit: '万', - }, - relatedMeasures: [ - { - name: 'MAU', - value: 10000, - formattedValue: 1, - formattedUnit: '万', - }, - ], - compareMeasures: [], - style: { - stroke: '#7ED321', - }, - }, - ], - edges: [ - { - id: 'edge-0', - source: 'node-0', - target: 'node-5', - measure: { - name: 'DAU', - value: 17500000000, - formattedValue: 175, - formattedUnit: '万', - }, - }, - { - id: 'edge-1', - source: 'node-1', - target: 'node-5', - measure: { - name: 'DAU', - value: 5500000000, - formattedValue: 55, - formattedUnit: '万', - }, - }, - { - id: 'edge-2', - source: 'node-2', - target: 'node-5', - measure: { - name: 'DAU', - value: 1000000000, - formattedValue: 10, - formattedUnit: '万', - }, - }, - { - id: 'edge-3', - source: 'node-3', - target: 'node-6', - measure: { - name: 'DAU', - value: 900000000, - formattedValue: 9, - formattedUnit: '万', - }, - }, - { - id: 'edge-4', - source: 'node-5', - target: 'node-7', - measure: { - name: 'DAU', - value: 5000000000, - formattedValue: 50, - formattedUnit: '万', - }, - }, - { - id: 'edge-5', - source: 'node-5', - target: 'node-8', - measure: { - name: 'DAU', - value: 5000000000, - formattedValue: 50, - formattedUnit: '万', - }, - }, - { - id: 'edge-6', - source: 'node-5', - target: 'node-9', - measure: { - name: 'DAU', - value: 9000000000, - formattedValue: 90, - formattedUnit: '万', - }, - }, - { - id: 'edge-7', - source: 'node-5', - target: 'node-10', - measure: { - name: 'DAU', - value: 5000000000, - formattedValue: 50, - formattedUnit: '万', - }, - }, - { - id: 'edge-8', - source: 'node-6', - target: 'node-11', - measure: { - name: 'DAU', - value: 1000000000, - formattedValue: 10, - formattedUnit: '万', - }, - }, - { - id: 'edge-9', - source: 'node-4', - target: 'node-6', - measure: { - name: 'DAU', - value: 5700000000, - formattedValue: 57, - formattedUnit: '万', - }, - }, - { - id: 'edge-10', - source: 'node-5', - target: 'node-6', - measure: { - name: 'DAU', - value: 2000000000, - formattedValue: 20, - formattedUnit: '万', - }, - style: { - dashed: true, - }, - }, - { - id: 'edge-11', - source: 'node-6', - target: 'node-12', - measure: { - name: 'DAU', - value: 600000000, - formattedValue: 6, - formattedUnit: '万', - }, - }, - { - id: 'edge-12', - source: 'node-6', - target: 'node-10', - measure: { - name: 'DAU', - value: 5000000000, - formattedValue: 50, - formattedUnit: '万', - }, - }, - ], -}; diff --git a/packages/graphs/tests/data/file-tree.ts b/packages/graphs/tests/data/file-tree.ts deleted file mode 100644 index ad4314047..000000000 --- a/packages/graphs/tests/data/file-tree.ts +++ /dev/null @@ -1,139 +0,0 @@ -export const FileData = { - value: { text: 'Modeling Methods' }, - id: '0', - isRoot: true, - children: [ - { - value: { text: 'Classification' }, - id: '0-1', - children: [ - { - value: { text: 'Logistic regression' }, - id: '0-1-1', - }, - { - value: { text: 'Linear discriminant analysis' }, - id: '0-1-2', - }, - { - value: { text: 'Rules' }, - id: '0-1-3', - }, - { - value: { text: 'Decision trees' }, - id: '0-1-4', - }, - { - value: { text: 'Naive Bayes' }, - id: '0-1-5', - }, - { - value: { text: 'K nearest neighbor' }, - id: '0-1-6', - }, - { - value: { text: 'Probabilistic neural network' }, - id: '0-1-7', - }, - { - value: { text: 'Support vector machine' }, - id: '0-1-8', - }, - ], - }, - { - value: { text: 'Consensus' }, - id: '0-2', - children: [ - { - value: { text: 'Models diversity' }, - id: '0-2-1', - children: [ - { - value: { text: 'Different initializations' }, - id: '0-2-1-1', - }, - { - value: { text: 'Different parameter choices' }, - id: '0-2-1-2', - }, - { - value: { text: 'Different architectures' }, - id: '0-2-1-3', - }, - { - value: { text: 'Different modeling methods' }, - id: '0-2-1-4', - }, - { - value: { text: 'Different training sets' }, - id: '0-2-1-5', - }, - { - value: { text: 'Different feature sets' }, - id: '0-2-1-6', - }, - ], - }, - { - value: { text: 'Methods' }, - id: '0-2-2', - children: [ - { - value: { text: 'Classifier selection' }, - id: '0-2-2-1', - }, - { - value: { text: 'Classifier fusion' }, - id: '0-2-2-2', - }, - ], - }, - { - value: { text: 'Common' }, - id: '0-2-3', - children: [ - { - value: { text: 'Bagging' }, - id: '0-2-3-1', - }, - { - value: { text: 'Boosting' }, - id: '0-2-3-2', - }, - { - value: { text: 'AdaBoost' }, - id: '0-2-3-3', - }, - ], - }, - ], - }, - { - value: { text: 'Regression' }, - id: '0-3', - children: [ - { - value: { text: 'Multiple linear regression' }, - id: '0-3-1', - }, - { - value: { text: 'Partial least squares' }, - id: '0-3-2', - }, - { - value: { text: 'Multi-layer feedforward neural network' }, - id: '0-3-3', - }, - { - value: { text: 'General regression neural network' }, - id: '0-3-4', - }, - { - value: { text: 'Support vector regression' }, - id: '0-3-5', - }, - ], - }, - ], -}; diff --git a/packages/graphs/tests/data/flow-loop.ts b/packages/graphs/tests/data/flow-loop.ts deleted file mode 100644 index edb56bbcf..000000000 --- a/packages/graphs/tests/data/flow-loop.ts +++ /dev/null @@ -1,290 +0,0 @@ -export const FlowLoopData = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - value: '来源A', - }, - { - source: '-2', - target: '0', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - value: '来源B', - }, - { - source: '-1', - target: '0', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - value: '来源C', - }, - { - source: '0', - target: '-1', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - value: '来源C', - }, - { - source: '0', - target: '1', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '1', - target: '-3', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '0', - target: '2', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '0', - target: '3', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '0', - target: '4', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '0', - target: '5', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '2', - target: '6', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '3', - target: '7', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - { - source: '4', - target: '8', - sourceAnchor: 1, - // 该边连入 target 点的第 0 个 anchorPoint, - targetAnchor: 0, - }, - ], -}; - -export const FlowLoopDoubleData = { - nodes: [ - { - id: '4', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - - { - id: '6', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '4', - target: '5', - }, - { - source: '5', - target: '6', - }, - { - source: '6', - target: '4', - }, - ], -}; diff --git a/packages/graphs/tests/data/flow.ts b/packages/graphs/tests/data/flow.ts deleted file mode 100644 index b67925415..000000000 --- a/packages/graphs/tests/data/flow.ts +++ /dev/null @@ -1,189 +0,0 @@ -export const FlowData = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], -}; diff --git a/packages/graphs/tests/data/index.ts b/packages/graphs/tests/data/index.ts deleted file mode 100644 index 407165de3..000000000 --- a/packages/graphs/tests/data/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { RadialData } from './radial'; -export { TreeData, NoTitleTreeData } from './tree'; -export { FlowData } from './flow'; -export { FileData } from './file-tree'; -export { RadialTreeData } from './radial-tree'; -export { ConvDagreData } from './conv-dagre'; -export { ORG_DATA } from './org'; -export { FlowLoopData, FlowLoopDoubleData } from './flow-loop'; diff --git a/packages/graphs/tests/data/org.ts b/packages/graphs/tests/data/org.ts deleted file mode 100644 index 725a4a3ef..000000000 --- a/packages/graphs/tests/data/org.ts +++ /dev/null @@ -1,105 +0,0 @@ -export const ORG_DATA = { - id: 'root', - value: { - name: 'Joel Alan', - title: 'CEO', - // 建议使用 bae64 数据 - icon: 'https://avatars.githubusercontent.com/u/31396322?v=4', - }, - children: [ - { - id: 'joel', - value: { - name: 'Joel Alan', - }, - children: [ - { - id: 'c1', - value: { - name: 'c1', - }, - children: [ - { - id: 'c1-1', - value: { - name: 'c1-1', - }, - }, - { - id: 'c1-2', - value: { - name: 'c1-2', - }, - children: [ - { - id: 'c1-2-1', - value: { - name: 'c1-2-1', - }, - }, - { - id: 'c1-2-2', - value: { - name: 'c1-2-2', - }, - }, - ], - }, - ], - }, - { - id: 'c2', - value: { - name: 'c2', - }, - }, - { - id: 'c3', - value: { - name: 'c3', - }, - children: [ - { - id: 'c3-1', - value: { - name: 'c3-1', - }, - }, - { - id: 'c3-2', - value: { - name: 'c3-2', - }, - children: [ - { - id: 'c3-2-1', - value: { - name: 'c3-2-1', - }, - }, - { - id: 'c3-2-2', - value: { - name: 'c3-2-2', - }, - }, - { - id: 'c3-2-3', - value: { - name: 'c3-2-3', - }, - }, - ], - }, - { - id: 'c3-3', - value: { - name: 'c3-3', - }, - }, - ], - }, - ], - }, - ], -}; diff --git a/packages/graphs/tests/data/radial-tree.ts b/packages/graphs/tests/data/radial-tree.ts deleted file mode 100644 index 8e80525dd..000000000 --- a/packages/graphs/tests/data/radial-tree.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const RadialTreeData = { - id: 'Modeling Methods', - children: [ - { - id: 'Classification', - children: [ - { id: 'Logistic regression', value: 'Logistic regression' }, - { id: 'Linear discriminant analysis', value: 'Linear discriminant analysis' }, - { id: 'Rules', value: 'Rules' }, - { id: 'Decision trees', value: 'Decision trees' }, - { id: 'Naive Bayes', value: 'Naive Bayes' }, - { id: 'K nearest neighbor', value: 'K nearest neighbor' }, - { id: 'Probabilistic neural network', value: 'Probabilistic neural network' }, - { id: 'Support vector machine', value: 'Support vector machine' }, - ], - value: 'Classification', - }, - ], - value: 'Modeling Methods', -}; diff --git a/packages/graphs/tests/data/radial.ts b/packages/graphs/tests/data/radial.ts deleted file mode 100644 index c955711c8..000000000 --- a/packages/graphs/tests/data/radial.ts +++ /dev/null @@ -1,82 +0,0 @@ -export const RadialData = { - nodes: [ - { - id: '0', - label: '0', - }, - { - id: '1', - label: '1', - }, - { - id: '2', - label: '2', - }, - { - id: '3', - label: '3', - }, - { - id: '4', - label: '4', - }, - { - id: '5', - label: '5', - }, - { - id: '6', - label: '6', - }, - { - id: '7', - label: '7', - }, - { - id: '8', - label: '8', - }, - { - id: '9', - label: '9', - }, - ], - edges: [ - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '0', - target: '6', - }, - { - source: '0', - target: '7', - }, - { - source: '0', - target: '8', - }, - { - source: '0', - target: '9', - }, - ], -}; diff --git a/packages/graphs/tests/data/tree.ts b/packages/graphs/tests/data/tree.ts deleted file mode 100644 index 59e70125f..000000000 --- a/packages/graphs/tests/data/tree.ts +++ /dev/null @@ -1,219 +0,0 @@ -export const TreeData = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南华北verylong1234567', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - markerCfg: { - collapsed: false, - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北华南真的很大很多很差', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - children: [ - { - id: 'A21', - value: { - title: '陕西', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: 'A22', - value: { - title: '青海', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }, - ], -}; - -export const NoTitleTreeData = { - id: 'A0', - value: { - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - items: [ - { - text: '1152万', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - items: [ - { - text: '1152万', - }, - ], - }, - }, - { - id: 'A12', - value: { - items: [ - { - text: '1152万', - }, - ], - }, - }, - { - id: 'A13', - value: { - items: [ - { - text: '1152万', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - items: [ - { - text: '595万', - }, - ], - }, - children: [ - { - id: 'A21', - value: { - items: [ - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: 'A22', - value: { - items: [ - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }, - ], -}; diff --git a/packages/graphs/tests/datasets/algorithm-category.json b/packages/graphs/tests/datasets/algorithm-category.json new file mode 100644 index 000000000..a98d0fbab --- /dev/null +++ b/packages/graphs/tests/datasets/algorithm-category.json @@ -0,0 +1,52 @@ +{ + "id": "Modeling Methods", + "children": [ + { + "id": "Classification", + "children": [ + { "id": "Logistic regression" }, + { "id": "Linear discriminant analysis" }, + { "id": "Rules" }, + { "id": "Decision trees" }, + { "id": "Naive Bayes" }, + { "id": "K nearest neighbor" }, + { "id": "Probabilistic neural network" }, + { "id": "Support vector machine" } + ] + }, + { + "id": "Consensus", + "children": [ + { + "id": "Models diversity", + "children": [ + { "id": "Different initializations" }, + { "id": "Different parameter choices" }, + { "id": "Different architectures" }, + { "id": "Different modeling methods" }, + { "id": "Different training sets" }, + { "id": "Different feature sets" } + ] + }, + { + "id": "Methods", + "children": [{ "id": "Classifier selection" }, { "id": "Classifier fusion" }] + }, + { + "id": "Common", + "children": [{ "id": "Bagging" }, { "id": "Boosting" }, { "id": "AdaBoost" }] + } + ] + }, + { + "id": "Regression", + "children": [ + { "id": "Multiple linear regression" }, + { "id": "Partial least squares" }, + { "id": "Multi-layer feedforward neural network" }, + { "id": "General regression neural network" }, + { "id": "Support vector regression" } + ] + } + ] +} diff --git a/packages/graphs/tests/datasets/language-tree.json b/packages/graphs/tests/datasets/language-tree.json new file mode 100644 index 000000000..e90ded5e3 --- /dev/null +++ b/packages/graphs/tests/datasets/language-tree.json @@ -0,0 +1,956 @@ +{ + "nodes": [ + { + "id": "Proto Indo-European", + "children": [ + "Balto-Slavic", + "Germanic", + "Celtic", + "Italic", + "Hellenic", + "Anatolian", + "Indo-Iranian", + "Tocharian", + "Phrygian", + "Armenian", + "Albanian", + "Thracian" + ] + }, + { + "id": "Balto-Slavic", + "children": ["Baltic", "Slavic"] + }, + { + "id": "Baltic", + "children": ["Old Prussian", "Lithuanian", "Latvian"] + }, + { + "id": "Old Prussian" + }, + { + "id": "Lithuanian" + }, + { + "id": "Latvian" + }, + { + "id": "Slavic", + "children": ["East Slavic", "West Slavic", "South Slavic"] + }, + { + "id": "East Slavic", + "children": ["Bulgarian", "Old Church Slavonic", "Macedonian", "Serbo-Croatian", "Slovene"] + }, + { + "id": "Bulgarian" + }, + { + "id": "Old Church Slavonic" + }, + { + "id": "Macedonian" + }, + { + "id": "Serbo-Croatian" + }, + { + "id": "Slovene" + }, + { + "id": "West Slavic", + "children": ["Polish", "Slovak", "Czech", "Wendish"] + }, + { + "id": "Polish" + }, + { + "id": "Slovak" + }, + { + "id": "Czech" + }, + { + "id": "Wendish" + }, + { + "id": "South Slavic", + "children": ["Russian", "Ukrainian", "Belarusian", "Rusyn"] + }, + { + "id": "Russian" + }, + { + "id": "Ukrainian" + }, + { + "id": "Belarusian" + }, + { + "id": "Rusyn" + }, + { + "id": "Germanic", + "children": ["North Germanic", "West Germanic", "East Germanic"] + }, + { + "id": "North Germanic", + "children": ["Old Norse", "Old Swedish", "Old Danish"] + }, + { + "id": "Old Norse", + "children": ["Old Icelandic", "Old Norwegian", "Faroese"] + }, + { + "id": "Old Icelandic", + "children": ["Icelandic"] + }, + { + "id": "Icelandic" + }, + { + "id": "Old Norwegian", + "children": ["Middle Norwegian"] + }, + { + "id": "Middle Norwegian", + "children": ["Norwegian"] + }, + { + "id": "Norwegian" + }, + { + "id": "Faroese" + }, + { + "id": "Old Swedish", + "children": ["Middle Swedish"] + }, + { + "id": "Middle Swedish", + "children": ["Swedish"] + }, + { + "id": "Swedish" + }, + { + "id": "Old Danish", + "children": ["Middle Danish"] + }, + { + "id": "Middle Danish", + "children": ["Danish"] + }, + { + "id": "Danish" + }, + { + "id": "West Germanic", + "children": ["Old English", "Old Frisian", "Old Dutch", "Old Low German", "Old High German"] + }, + { + "id": "Old English", + "children": ["Middle English"] + }, + { + "id": "Middle English", + "children": ["English"] + }, + { + "id": "English" + }, + { + "id": "Old Frisian", + "children": ["Frisian"] + }, + { + "id": "Frisian" + }, + { + "id": "Old Dutch", + "children": ["Middle Dutch"] + }, + { + "id": "Middle Dutch", + "children": ["Hollandic", "Flemish", "Dutch", "Limburgish", "Brabantian", "Rhinelandic"] + }, + { + "id": "Hollandic" + }, + { + "id": "Flemish" + }, + { + "id": "Dutch" + }, + { + "id": "Limburgish" + }, + { + "id": "Brabantian" + }, + { + "id": "Rhinelandic" + }, + { + "id": "Old Low German", + "children": ["Middle Low German"] + }, + { + "id": "Middle Low German", + "children": ["Low German"] + }, + { + "id": "Low German" + }, + { + "id": "Old High German", + "children": ["Middle High German"] + }, + { + "id": "Middle High German", + "children": ["(High) German", "Yiddish"] + }, + { + "id": "(High) German" + }, + { + "id": "Yiddish" + }, + { + "id": "East Germanic", + "children": ["Gothic"] + }, + { + "id": "Gothic" + }, + { + "id": "Celtic", + "children": ["Brythonic", "Goidelic"] + }, + { + "id": "Brythonic", + "children": ["Welsh", "Breton", "Cornish", "Cuymbric"] + }, + { + "id": "Welsh" + }, + { + "id": "Breton" + }, + { + "id": "Cornish" + }, + { + "id": "Cuymbric" + }, + { + "id": "Goidelic", + "children": ["Modern Irish", "Scottish Gaelic", "Manx"] + }, + { + "id": "Modern Irish" + }, + { + "id": "Scottish Gaelic" + }, + { + "id": "Manx" + }, + { + "id": "Italic", + "children": ["Osco-Umbrian", "Latino-Faliscan"] + }, + { + "id": "Osco-Umbrian", + "children": ["Umbrian", "Oscan"] + }, + { + "id": "Umbrian" + }, + { + "id": "Oscan" + }, + { + "id": "Latino-Faliscan", + "children": ["Latin", "Faliscan"] + }, + { + "id": "Latin", + "children": [ + "Portugese", + "Spanish", + "French", + "Romanian", + "Italian", + "Catalan", + "Franco-Provençal", + "Rhaeto-Romance" + ] + }, + { + "id": "Portugese" + }, + { + "id": "Spanish" + }, + { + "id": "French" + }, + { + "id": "Romanian" + }, + { + "id": "Italian" + }, + { + "id": "Catalan" + }, + { + "id": "Franco-Provençal" + }, + { + "id": "Rhaeto-Romance" + }, + { + "id": "Faliscan" + }, + { + "id": "Hellenic", + "children": ["Greek"] + }, + { + "id": "Greek" + }, + { + "id": "Anatolian", + "children": ["Hittite", "Palaic", "Luwic", "Lydian"] + }, + { + "id": "Hittite" + }, + { + "id": "Palaic" + }, + { + "id": "Luwic" + }, + { + "id": "Lydian" + }, + { + "id": "Indo-Iranian", + "children": ["Dardic", "Indic", "Iranian"] + }, + { + "id": "Dardic", + "children": ["Dard"] + }, + { + "id": "Dard" + }, + { + "id": "Indic", + "children": ["Sanskrit"] + }, + { + "id": "Sanskrit", + "children": [ + "Sindhi", + "Romani", + "Urdu", + "Hindi", + "Bihari", + "Assamese", + "Bengali", + "Marathi", + "Gujarati", + "Punjabi", + "Sinhalese" + ] + }, + { + "id": "Sindhi" + }, + { + "id": "Romani" + }, + { + "id": "Urdu" + }, + { + "id": "Hindi" + }, + { + "id": "Bihari" + }, + { + "id": "Assamese" + }, + { + "id": "Bengali" + }, + { + "id": "Marathi" + }, + { + "id": "Gujarati" + }, + { + "id": "Punjabi" + }, + { + "id": "Sinhalese" + }, + { + "id": "Iranian", + "children": ["Old Persian", "Balochi", "Kurdish", "Pashto", "Sogdian"] + }, + { + "id": "Old Persian", + "children": ["Middle Persian", "Pahlavi"] + }, + { + "id": "Middle Persian", + "children": ["Persian"] + }, + { + "id": "Persian" + }, + { + "id": "Pahlavi" + }, + { + "id": "Balochi" + }, + { + "id": "Kurdish" + }, + { + "id": "Pashto" + }, + { + "id": "Sogdian" + }, + { + "id": "Tocharian", + "children": ["Tocharian A", "Tocharian B"] + }, + { + "id": "Tocharian A" + }, + { + "id": "Tocharian B" + }, + { + "id": "Phrygian" + }, + { + "id": "Armenian" + }, + { + "id": "Albanian" + }, + { + "id": "Thracian" + } + ], + "edges": [ + { + "source": "Proto Indo-European", + "target": "Balto-Slavic" + }, + { + "source": "Proto Indo-European", + "target": "Germanic" + }, + { + "source": "Proto Indo-European", + "target": "Celtic" + }, + { + "source": "Proto Indo-European", + "target": "Italic" + }, + { + "source": "Proto Indo-European", + "target": "Hellenic" + }, + { + "source": "Proto Indo-European", + "target": "Anatolian" + }, + { + "source": "Proto Indo-European", + "target": "Indo-Iranian" + }, + { + "source": "Proto Indo-European", + "target": "Tocharian" + }, + { + "source": "Proto Indo-European", + "target": "Phrygian" + }, + { + "source": "Proto Indo-European", + "target": "Armenian" + }, + { + "source": "Proto Indo-European", + "target": "Albanian" + }, + { + "source": "Proto Indo-European", + "target": "Thracian" + }, + { + "source": "Balto-Slavic", + "target": "Baltic" + }, + { + "source": "Balto-Slavic", + "target": "Slavic" + }, + { + "source": "Baltic", + "target": "Old Prussian" + }, + { + "source": "Baltic", + "target": "Lithuanian" + }, + { + "source": "Baltic", + "target": "Latvian" + }, + { + "source": "Slavic", + "target": "East Slavic" + }, + { + "source": "Slavic", + "target": "West Slavic" + }, + { + "source": "Slavic", + "target": "South Slavic" + }, + { + "source": "East Slavic", + "target": "Bulgarian" + }, + { + "source": "East Slavic", + "target": "Old Church Slavonic" + }, + { + "source": "East Slavic", + "target": "Macedonian" + }, + { + "source": "East Slavic", + "target": "Serbo-Croatian" + }, + { + "source": "East Slavic", + "target": "Slovene" + }, + { + "source": "West Slavic", + "target": "Polish" + }, + { + "source": "West Slavic", + "target": "Slovak" + }, + { + "source": "West Slavic", + "target": "Czech" + }, + { + "source": "West Slavic", + "target": "Wendish" + }, + { + "source": "South Slavic", + "target": "Russian" + }, + { + "source": "South Slavic", + "target": "Ukrainian" + }, + { + "source": "South Slavic", + "target": "Belarusian" + }, + { + "source": "South Slavic", + "target": "Rusyn" + }, + { + "source": "Germanic", + "target": "North Germanic" + }, + { + "source": "Germanic", + "target": "West Germanic" + }, + { + "source": "Germanic", + "target": "East Germanic" + }, + { + "source": "North Germanic", + "target": "Old Norse" + }, + { + "source": "North Germanic", + "target": "Old Swedish" + }, + { + "source": "North Germanic", + "target": "Old Danish" + }, + { + "source": "Old Norse", + "target": "Old Icelandic" + }, + { + "source": "Old Norse", + "target": "Old Norwegian" + }, + { + "source": "Old Norse", + "target": "Faroese" + }, + { + "source": "Old Icelandic", + "target": "Icelandic" + }, + { + "source": "Old Norwegian", + "target": "Middle Norwegian" + }, + { + "source": "Middle Norwegian", + "target": "Norwegian" + }, + { + "source": "Old Swedish", + "target": "Middle Swedish" + }, + { + "source": "Middle Swedish", + "target": "Swedish" + }, + { + "source": "Old Danish", + "target": "Middle Danish" + }, + { + "source": "Middle Danish", + "target": "Danish" + }, + { + "source": "West Germanic", + "target": "Old English" + }, + { + "source": "West Germanic", + "target": "Old Frisian" + }, + { + "source": "West Germanic", + "target": "Old Dutch" + }, + { + "source": "West Germanic", + "target": "Old Low German" + }, + { + "source": "West Germanic", + "target": "Old High German" + }, + { + "source": "Old English", + "target": "Middle English" + }, + { + "source": "Middle English", + "target": "English" + }, + { + "source": "Old Frisian", + "target": "Frisian" + }, + { + "source": "Old Dutch", + "target": "Middle Dutch" + }, + { + "source": "Middle Dutch", + "target": "Hollandic" + }, + { + "source": "Middle Dutch", + "target": "Flemish" + }, + { + "source": "Middle Dutch", + "target": "Dutch" + }, + { + "source": "Middle Dutch", + "target": "Limburgish" + }, + { + "source": "Middle Dutch", + "target": "Brabantian" + }, + { + "source": "Middle Dutch", + "target": "Rhinelandic" + }, + { + "source": "Old Low German", + "target": "Middle Low German" + }, + { + "source": "Middle Low German", + "target": "Low German" + }, + { + "source": "Old High German", + "target": "Middle High German" + }, + { + "source": "Middle High German", + "target": "(High) German" + }, + { + "source": "Middle High German", + "target": "Yiddish" + }, + { + "source": "East Germanic", + "target": "Gothic" + }, + { + "source": "Celtic", + "target": "Brythonic" + }, + { + "source": "Celtic", + "target": "Goidelic" + }, + { + "source": "Brythonic", + "target": "Welsh" + }, + { + "source": "Brythonic", + "target": "Breton" + }, + { + "source": "Brythonic", + "target": "Cornish" + }, + { + "source": "Brythonic", + "target": "Cuymbric" + }, + { + "source": "Goidelic", + "target": "Modern Irish" + }, + { + "source": "Goidelic", + "target": "Scottish Gaelic" + }, + { + "source": "Goidelic", + "target": "Manx" + }, + { + "source": "Italic", + "target": "Osco-Umbrian" + }, + { + "source": "Italic", + "target": "Latino-Faliscan" + }, + { + "source": "Osco-Umbrian", + "target": "Umbrian" + }, + { + "source": "Osco-Umbrian", + "target": "Oscan" + }, + { + "source": "Latino-Faliscan", + "target": "Latin" + }, + { + "source": "Latino-Faliscan", + "target": "Faliscan" + }, + { + "source": "Latin", + "target": "Portugese" + }, + { + "source": "Latin", + "target": "Spanish" + }, + { + "source": "Latin", + "target": "French" + }, + { + "source": "Latin", + "target": "Romanian" + }, + { + "source": "Latin", + "target": "Italian" + }, + { + "source": "Latin", + "target": "Catalan" + }, + { + "source": "Latin", + "target": "Franco-Provençal" + }, + { + "source": "Latin", + "target": "Rhaeto-Romance" + }, + { + "source": "Hellenic", + "target": "Greek" + }, + { + "source": "Anatolian", + "target": "Hittite" + }, + { + "source": "Anatolian", + "target": "Palaic" + }, + { + "source": "Anatolian", + "target": "Luwic" + }, + { + "source": "Anatolian", + "target": "Lydian" + }, + { + "source": "Indo-Iranian", + "target": "Dardic" + }, + { + "source": "Indo-Iranian", + "target": "Indic" + }, + { + "source": "Indo-Iranian", + "target": "Iranian" + }, + { + "source": "Dardic", + "target": "Dard" + }, + { + "source": "Indic", + "target": "Sanskrit" + }, + { + "source": "Sanskrit", + "target": "Sindhi" + }, + { + "source": "Sanskrit", + "target": "Romani" + }, + { + "source": "Sanskrit", + "target": "Urdu" + }, + { + "source": "Sanskrit", + "target": "Hindi" + }, + { + "source": "Sanskrit", + "target": "Bihari" + }, + { + "source": "Sanskrit", + "target": "Assamese" + }, + { + "source": "Sanskrit", + "target": "Bengali" + }, + { + "source": "Sanskrit", + "target": "Marathi" + }, + { + "source": "Sanskrit", + "target": "Gujarati" + }, + { + "source": "Sanskrit", + "target": "Punjabi" + }, + { + "source": "Sanskrit", + "target": "Sinhalese" + }, + { + "source": "Iranian", + "target": "Old Persian" + }, + { + "source": "Iranian", + "target": "Balochi" + }, + { + "source": "Iranian", + "target": "Kurdish" + }, + { + "source": "Iranian", + "target": "Pashto" + }, + { + "source": "Iranian", + "target": "Sogdian" + }, + { + "source": "Old Persian", + "target": "Middle Persian" + }, + { + "source": "Old Persian", + "target": "Pahlavi" + }, + { + "source": "Middle Persian", + "target": "Persian" + }, + { + "source": "Tocharian", + "target": "Tocharian A" + }, + { + "source": "Tocharian", + "target": "Tocharian B" + } + ] +} diff --git a/packages/graphs/tests/datasets/mind-mapping.json b/packages/graphs/tests/datasets/mind-mapping.json new file mode 100644 index 000000000..9d8652a32 --- /dev/null +++ b/packages/graphs/tests/datasets/mind-mapping.json @@ -0,0 +1,25 @@ +{ + "id": "Mind Mapping", + "children": [ + { + "id": "Benefits", + "children": [{ "id": "Overview" }, { "id": "Easy to memorize" }, { "id": "Simple, fast & fun" }] + }, + { + "id": "Collaboration", + "children": [{ "id": "Teamwork" }, { "id": "Sharing" }, { "id": "Colleagues" }] + }, + { + "id": "Productivity", + "children": [{ "id": "More efficient" }, { "id": "Intuitive" }] + }, + { + "id": "Planning", + "children": [{ "id": "Projects" }, { "id": "Goals" }, { "id": "Strategies" }] + }, + { + "id": "Creativity", + "children": [{ "id": "Ideas" }, { "id": "Innovation" }, { "id": "Thoughts" }] + } + ] +} diff --git a/packages/graphs/tests/datasets/product-launch.json b/packages/graphs/tests/datasets/product-launch.json new file mode 100644 index 000000000..c58bfff9c --- /dev/null +++ b/packages/graphs/tests/datasets/product-launch.json @@ -0,0 +1,56 @@ +{ + "nodes": [ + { + "id": "start", + "data": { "name": "流程开始" } + }, + { + "id": "submit-agreement", + "data": { "name": "提交协议", "elapsed_time": "11秒" } + }, + { + "id": "contract-review", + "data": { + "name": "合约审核", + "elapsed_time": "1.63分", + "status": "running", + "children": [ + { "name": "风控审核", "elapsed_time": "39秒" }, + { "name": "财务审核", "elapsed_time": "0秒" }, + { "name": "法务审核", "elapsed_time": "0秒" }, + { "name": "销售初评", "elapsed_time": "0秒" }, + { "name": "主管审核", "elapsed_time": "0秒" }, + { "name": "销售终审", "elapsed_time": "0秒" } + ] + } + }, + { + "id": "merchant-confirmation", + "data": { "name": "商户确认", "elapsed_time": "0秒" } + }, + { + "id": "end", + "data": { "name": "流程结束" } + } + ], + "edges": [ + { + "source": "start", + "target": "submit-agreement" + }, + { + "source": "submit-agreement", + "target": "contract-review", + "data": { "elapsed_time": "11秒" } + }, + { + "source": "contract-review", + "target": "merchant-confirmation", + "data": { "elapsed_time": "0秒" } + }, + { + "source": "merchant-confirmation", + "target": "end" + } + ] +} diff --git a/packages/graphs/tests/datasets/task-scheduling.json b/packages/graphs/tests/datasets/task-scheduling.json new file mode 100644 index 000000000..6cf8476d4 --- /dev/null +++ b/packages/graphs/tests/datasets/task-scheduling.json @@ -0,0 +1,40 @@ +{ + "nodes": [ + { "id": "source", "data": { "type": "source", "name": "集群 OB" } }, + { "id": "store-1", "data": { "type": "store", "name": "Store-11.124.115.16-9000:mysql2ob_default", "delay": "4" } }, + { "id": "store-2", "data": { "type": "store", "name": "Store-11.124.115.16-9000:mysql2ob_default", "delay": "4" } }, + { "id": "store-3", "data": { "type": "store", "name": "Store-11.124.115.16-9000:mysql2ob_default", "delay": "4" } }, + { "id": "store-4", "data": { "type": "store", "name": "Store-11.124.115.16-9000:mysql2ob_default", "delay": "4" } }, + { + "id": "writer-1", + "data": { "type": "writer", "name": "Writer-11.124.115.16-9000:mysql2ob_default", "delay": "4" } + }, + { + "id": "writer-2", + "data": { "type": "writer", "name": "Writer-11.124.115.16-9000:mysql2ob_default", "delay": "4" } + }, + { + "id": "writer-3", + "data": { "type": "writer", "name": "Writer-11.124.115.16-9000:mysql2ob_default", "delay": "11" } + }, + { + "id": "writer-4", + "data": { "type": "writer", "name": "Writer-11.124.115.16-9000:mysql2ob_default", "delay": "4" } + }, + { "id": "target", "data": { "type": "target", "name": "Platform" } } + ], + "edges": [ + { "source": "source", "target": "store-1" }, + { "source": "store-1", "target": "writer-1" }, + { "source": "writer-1", "target": "target" }, + { "source": "source", "target": "store-2" }, + { "source": "store-2", "target": "writer-2" }, + { "source": "writer-2", "target": "target" }, + { "source": "source", "target": "store-3" }, + { "source": "store-3", "target": "writer-3" }, + { "source": "writer-3", "target": "target" }, + { "source": "source", "target": "store-4" }, + { "source": "store-4", "target": "writer-4" }, + { "source": "writer-4", "target": "target" } + ] +} diff --git a/packages/graphs/tests/datasets/user-flow.json b/packages/graphs/tests/datasets/user-flow.json new file mode 100644 index 000000000..dc8c1b1ea --- /dev/null +++ b/packages/graphs/tests/datasets/user-flow.json @@ -0,0 +1,437 @@ +{ + "nodes": [ + { + "id": "node-0", + "name": "页面-0", + "layerName": "层级0", + "measure": { + "name": "DAU", + "value": 17500000000, + "formattedValue": 175, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 1000000, + "formattedValue": 100, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#F5A623" + } + }, + { + "id": "node-1", + "name": "页面1", + "layerName": "层级0", + "measure": { + "name": "DAU", + "value": 5500000000, + "formattedValue": 55, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 500000, + "formattedValue": 50, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#F5A623" + } + }, + { + "id": "node-2", + "name": "页面2", + "layerName": "层级0", + "measure": { + "name": "DAU", + "value": 1000000000, + "formattedValue": 10, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 100000, + "formattedValue": 10, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#F5A623" + } + }, + { + "id": "node-3", + "name": "页面3", + "layerName": "层级0", + "measure": { + "name": "DAU", + "value": 900000000, + "formattedValue": 9, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 50000, + "formattedValue": 5, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#F5A623" + } + }, + { + "id": "node-4", + "name": "页面4", + "layerName": "层级0", + "measure": { + "name": "DAU", + "value": 5700000000, + "formattedValue": 57, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 60000, + "formattedValue": 6, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#F5A623" + } + }, + { + "id": "node-5", + "name": "页面5", + "layerName": "层级1", + "measure": { + "name": "DAU", + "value": 24000000000, + "formattedValue": 240, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 1600000, + "formattedValue": 160, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#4A90E2" + } + }, + { + "id": "node-6", + "name": "页面6", + "layerName": "层级1", + "measure": { + "name": "DAU", + "value": 6600000000, + "formattedValue": 66, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 110000, + "formattedValue": 11, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#4A90E2" + } + }, + { + "id": "node-7", + "name": "页面7", + "layerName": "层级2", + "measure": { + "name": "DAU", + "value": 5000000000, + "formattedValue": 50, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 500000, + "formattedValue": 50, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#7ED321" + } + }, + { + "id": "node-8", + "name": "页面8", + "layerName": "层级2", + "measure": { + "name": "DAU", + "value": 5000000000, + "formattedValue": 50, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 500000, + "formattedValue": 50, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#7ED321" + } + }, + { + "id": "node-9", + "name": "页面9", + "layerName": "层级2", + "measure": { + "name": "DAU", + "value": 90000000000, + "formattedValue": 90, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 500000, + "formattedValue": 50, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#7ED321" + } + }, + { + "id": "node-10", + "name": "页面10", + "layerName": "层级2", + "measure": { + "name": "DAU", + "value": 100000000000, + "formattedValue": 100, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 100000, + "formattedValue": 10, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#7ED321" + } + }, + { + "id": "node-11", + "name": "页面11", + "layerName": "层级2", + "measure": { + "name": "DAU", + "value": 1000000000, + "formattedValue": 10, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 90000, + "formattedValue": 9, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#7ED321" + } + }, + { + "id": "node-12", + "name": "页面12", + "layerName": "层级2", + "measure": { + "name": "DAU", + "value": 600000000, + "formattedValue": 6, + "formattedUnit": "万" + }, + "relatedMeasures": [ + { + "name": "MAU", + "value": 10000, + "formattedValue": 1, + "formattedUnit": "万" + } + ], + "compareMeasures": [], + "style": { + "stroke": "#7ED321" + } + } + ], + "edges": [ + { + "id": "edge-0", + "source": "node-0", + "target": "node-5", + "measure": { + "name": "DAU", + "value": 17500000000, + "formattedValue": 175, + "formattedUnit": "万" + } + }, + { + "id": "edge-1", + "source": "node-1", + "target": "node-5", + "measure": { + "name": "DAU", + "value": 5500000000, + "formattedValue": 55, + "formattedUnit": "万" + } + }, + { + "id": "edge-2", + "source": "node-2", + "target": "node-5", + "measure": { + "name": "DAU", + "value": 1000000000, + "formattedValue": 10, + "formattedUnit": "万" + } + }, + { + "id": "edge-3", + "source": "node-3", + "target": "node-6", + "measure": { + "name": "DAU", + "value": 900000000, + "formattedValue": 9, + "formattedUnit": "万" + } + }, + { + "id": "edge-4", + "source": "node-5", + "target": "node-7", + "measure": { + "name": "DAU", + "value": 5000000000, + "formattedValue": 50, + "formattedUnit": "万" + } + }, + { + "id": "edge-5", + "source": "node-5", + "target": "node-8", + "measure": { + "name": "DAU", + "value": 5000000000, + "formattedValue": 50, + "formattedUnit": "万" + } + }, + { + "id": "edge-6", + "source": "node-5", + "target": "node-9", + "measure": { + "name": "DAU", + "value": 9000000000, + "formattedValue": 90, + "formattedUnit": "万" + } + }, + { + "id": "edge-7", + "source": "node-5", + "target": "node-10", + "measure": { + "name": "DAU", + "value": 5000000000, + "formattedValue": 50, + "formattedUnit": "万" + } + }, + { + "id": "edge-8", + "source": "node-6", + "target": "node-11", + "measure": { + "name": "DAU", + "value": 1000000000, + "formattedValue": 10, + "formattedUnit": "万" + } + }, + { + "id": "edge-9", + "source": "node-4", + "target": "node-6", + "measure": { + "name": "DAU", + "value": 5700000000, + "formattedValue": 57, + "formattedUnit": "万" + } + }, + { + "id": "edge-11", + "source": "node-6", + "target": "node-12", + "measure": { + "name": "DAU", + "value": 600000000, + "formattedValue": 6, + "formattedUnit": "万" + } + }, + { + "id": "edge-12", + "source": "node-6", + "target": "node-10", + "measure": { + "name": "DAU", + "value": 5000000000, + "formattedValue": 50, + "formattedUnit": "万" + } + } + ] +} diff --git a/packages/graphs/tests/demos/dendrogram.tsx b/packages/graphs/tests/demos/dendrogram.tsx new file mode 100644 index 000000000..baf953f4d --- /dev/null +++ b/packages/graphs/tests/demos/dendrogram.tsx @@ -0,0 +1,13 @@ +import { Dendrogram as DendrogramComponent, DendrogramOptions } from '@ant-design/graphs'; +import React from 'react'; +import data from '../datasets/algorithm-category.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +export const Dendrogram = () => { + const options = useGraphOptions({ + autoFit: 'view', + data, + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/fishbone.tsx b/packages/graphs/tests/demos/fishbone.tsx new file mode 100644 index 000000000..e0eea9cfc --- /dev/null +++ b/packages/graphs/tests/demos/fishbone.tsx @@ -0,0 +1,62 @@ +import { Fishbone as ADCFishbone } from '@ant-design/graphs'; +import React from 'react'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +const data = { + id: 'Product Profitability Below Expectations', + children: [ + { + id: 'Problem Description', + children: [ + { id: 'Brand Sales Volume' }, + { id: 'Market Capacity' }, + { id: 'Brand Market Share' }, + { id: 'Total Contribution Margin' }, + ], + }, + { + id: 'Brand Positioning', + children: [{ id: 'Packaging' }, { id: 'Brand Name' }, { id: 'Selling Price' }, { id: 'Product Specifications' }], + }, + { + id: 'Distribution Channels', + children: [{ id: 'Region' }, { id: 'Channel' }, { id: 'Customer Type' }, { id: 'Sales Personnel Coverage' }], + }, + { + id: 'Market Awareness', + children: [ + { id: 'Regional Weighting' }, + { id: 'Media Mix' }, + { id: 'Advertising Investment' }, + { id: 'Quality Perception' }, + ], + }, + { + id: 'Trial Purchase', + children: [ + { id: 'In-store Display' }, + { id: 'Promotion Type' }, + { id: 'Timing of Promotion' }, + { id: 'Supply Assurance' }, + ], + }, + { + id: 'Repeat Purchase', + children: [ + { id: 'Consumer Profile' }, + { id: 'Usage Occasion' }, + { id: 'Frequency of Use' }, + { id: 'Returns Due to Product Issues' }, + ], + }, + ], +}; + +export const Fishbone = () => { + const options = useGraphOptions({ + autoFit: 'view', + data + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/flow-graph-product-launch.tsx b/packages/graphs/tests/demos/flow-graph-product-launch.tsx new file mode 100644 index 000000000..0c100a41e --- /dev/null +++ b/packages/graphs/tests/demos/flow-graph-product-launch.tsx @@ -0,0 +1,199 @@ +import { FlowGraph as FlowGraphComponent, type FlowGraphOptions, type G6 } from '@ant-design/graphs'; +import { isBoolean } from 'lodash'; +import React, { FC } from 'react'; +import styled from 'styled-components'; +import data from '../datasets/product-launch.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +interface StepData { + name: string; + status?: string; + elapsed_time?: string; +} + +interface NodeData extends G6.NodeData { + data: StepData & { + children?: StepData[]; + [key: string]: unknown; + }; +} + +interface EdgeData extends G6.EdgeData { + data: { + elapsed_time?: string; + [key: string]: unknown; + }; +} + +const StyledStepCardWrapper = styled.div` + height: 58px; + width: 120px; + background: #ecf2fe; + border-radius: 4px; + box-sizing: border-box; + padding: 6px 12px; + font-size: 10px; + font-weight: 500; + color: #252525; + display: flex; + flex-direction: column; + justify-content: center; + + .elapsed-time { + margin-top: 8px; + + &-title { + color: #aaa; + font-size: 8px; + } + } +`; + +const StyledStepGroupCardWrapper = styled.div<{ $isCollapsed: boolean }>` + width: inherit; + height: inherit; + border-radius: 4px; + box-sizing: border-box; + border: 1px solid #eee; + + .header { + height: 32px; + line-height: 32px; + background-color: #3875f7; + color: #fff; + border-radius: ${({ $isCollapsed }) => ($isCollapsed ? '4px' : '4px 4px 0 0')}; + display: flex; + font-size: 10px; + padding: 0 12px; + gap: 2px; + + &-content { + flex: 1; + display: flex; + justify-content: space-between; + + .elapsed-time { + display: flex; + gap: 2px; + font-size: 9px; + + &-title { + color: #acc7fb; + } + } + } + + &-extra { + cursor: pointer; + width: fit-content; + color: #acc7fb; + } + } + + .step-card-group { + display: flex; + gap: 8px; + flex-direction: column; + align-items: center; + padding: 16px 0; + } +`; + +const StepCard: FC = ({ name, elapsed_time }) => { + return ( + +
{name}
+ {elapsed_time && ( +
+
80分位耗时
+
{elapsed_time}
+
+ )} +
+ ); +}; + +const StepGroupCard: FC void }> = ( + props, +) => { + const { name, elapsed_time, children, isCollapsed, toggleCollapse } = props; + return ( + +
+
+
{name}
+ {elapsed_time && ( +
+
80分位耗时
+
{elapsed_time}
+
+ )} +
+
+ {isCollapsed ? '展开' : '收起'} +
+
+ {!isCollapsed && ( +
+ {children?.map((child, index) => ( + + ))} +
+ )} +
+ ); +}; + +function isGroupCollapsed(data: NodeData) { + return isBoolean(data.style?.collapsed) ? data.style?.collapsed : data.data.status === 'finished'; +} + +function isSingleStep(data: NodeData) { + return !data.data.children; +} + +export const FlowGraphProductLaunch = () => { + const options = useGraphOptions({ + autoFit: 'view', + data, + node: { + style: { + component: function (data: NodeData) { + if (isSingleStep(data)) return ; + const toggleCollapse = async () => { + const graph = this as unknown as G6.Graph; + graph.updateNodeData([{ id: data.id, style: { collapsed: !isGroupCollapsed(data) } }]); + await graph.render(); + }; + return ; + }, + // @ts-ignore + size: (data: NodeData) => { + if (isSingleStep(data)) return [120, 58]; + const GAP = 8; + const height = isGroupCollapsed(data) ? 32 : 56 + (58 + GAP) * (data.data?.children?.length || 0); + return [200, height]; + }, + }, + }, + edge: { + style: { + lineWidth: 1, + labelBackground: true, + labelBackgroundOpacity: 1, + labelFill: '#aaa', + labelFontSize: 8, + labelFontWeight: 500, + // @ts-ignore + labelText: (data: EdgeData) => (data.data?.elapsed_time ? `80分位耗时\n${data.data.elapsed_time}` : ''), + }, + }, + layout: { + type: 'dagre', + nodeSize: (data: NodeData) => (isSingleStep(data) ? 160 : 400), + animation: false, + }, + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/flow-graph-task-scheduling.tsx b/packages/graphs/tests/demos/flow-graph-task-scheduling.tsx new file mode 100644 index 000000000..9f1125a87 --- /dev/null +++ b/packages/graphs/tests/demos/flow-graph-task-scheduling.tsx @@ -0,0 +1,150 @@ +import { FlowGraph as FlowGraphComponent, FlowGraphOptions } from '@ant-design/graphs'; +import { Typography } from 'antd'; +import React from 'react'; +import styled from 'styled-components'; +import { hexToRgba } from '../../src/core/utils/color'; +import data from '../datasets/task-scheduling.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +const { Text } = Typography; + +const StyleEndNodeWrapper = styled.div` + width: inherit; + height: inherit; + box-sizing: border-box; + border: 1px solid #f1f5fe; + border-radius: 4px; + display: flex; + font-size: 12px; + + .end-node-type { + width: 40px; + background: #f1f5fe; + color: #808692; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + } + + .end-node-name { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + } +} +`; + +const StyleTaskNodeWrapper = styled.div<{ $color: string; $isActive?: boolean }>` + width: inherit; + height: inherit; + border-radius: 4px; + color: #fff; + align-content: center; + box-sizing: border-box; + border: 1px solid ${(props) => hexToRgba(props.$color, props.$isActive ? 1 : 0.5)}; + display: flex; + flex-direction: column; + + .task-node-title { + height: 50%; + background-color: ${(props) => props.$color}; + } + + .task-node-name { + font-size: 12px; + padding-left: 6px; + color: #fff; + } + + .task-node-delay { + font-size: 12px; + padding-left: 6px; + color: #252525; + flex: 1; + align-content: center; + } +} +`; + +const EndNode: React.FC<{ + data: { + type: string; + name: string; + }; + isActive?: boolean; +}> = (props) => { + const { + data: { type, name }, + isActive, + } = props; + return ( + +
{type === 'source' ? '来源' : '结束'}
+
{name}
+
+ ); +}; + +const TaskNode: React.FC<{ + data: { + type: string; + name: string; + delay: string; + }; + isActive?: boolean; +}> = (props) => { + const { + data: { type, name, delay }, + isActive, + } = props; + + const color = type === 'store' ? '#1783FF' : Number(delay) < 10 ? '#52c41a' : '#ff4d4f'; + + return ( + +
+ + {name} + +
+ +
{`delay: ${delay}min`}
+
+ ); +}; + +export const FlowGraphTaskScheduling = () => { + const options = useGraphOptions({ + autoFit: 'view', + data, + node: { + style: { + component: (d) => { + const isActive = d.states?.includes('active'); + return ['source', 'target'].includes(d.data.type) ? ( + + ) : ( + + ); + }, + size: (d: any) => (['source', 'target'].includes(d.data.type) ? [120, 40] : [194, 58]), + }, + }, + edge: { + style: { + lineWidth: 1, + }, + state: { + active: { + stroke: '#1890ff', + halo: false, + }, + }, + }, + behaviors: (prev) => [...prev, 'hover-activate-chain'], + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/flow-graph.tsx b/packages/graphs/tests/demos/flow-graph.tsx new file mode 100644 index 000000000..8b92edb2f --- /dev/null +++ b/packages/graphs/tests/demos/flow-graph.tsx @@ -0,0 +1,25 @@ +import { FlowGraph as FlowGraphComponent, RCNode, type FlowGraphOptions } from '@ant-design/graphs'; +import type { NodeData } from '@antv/g6'; +import React from 'react'; +import data from '../datasets/task-scheduling.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +const { TextNode } = RCNode; + +export const FlowGraph = () => { + const options = useGraphOptions({ + autoFit: 'view', + data, + node: { + style: { + component: (d: NodeData) => { + const isActive = d.states?.includes('active'); + return ; + }, + }, + }, + behaviors: (prev) => [...prev, 'hover-activate-chain'], + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/hooks/useQueryOptions.ts b/packages/graphs/tests/demos/hooks/useQueryOptions.ts new file mode 100644 index 000000000..b95424dd3 --- /dev/null +++ b/packages/graphs/tests/demos/hooks/useQueryOptions.ts @@ -0,0 +1,33 @@ +import { GraphOptions, mergeOptions } from '@ant-design/graphs'; +import { useSearchParams } from 'react-router-dom'; + +export const useGraphOptions = >(options: T): T => { + const [params] = useSearchParams(); + const queryParams = Object.fromEntries(params) as any; + + Object.keys(queryParams).forEach((key) => { + if (queryParams[key] === 'true') { + queryParams[key] = true; + } + if (queryParams[key] === 'false') { + queryParams[key] = false; + } + }); + + queryParams.devicePixelRatio = 4; + + queryParams.transforms = () => { + return (transforms) => { + return [ + ...transforms.filter((transform: any) => transform.key !== 'collapse-expand-react-node'), + { + ...transforms.find((transform: any) => transform.key === 'collapse-expand-react-node'), + enable: queryParams.collapseExpand, + ...(queryParams.collapseExpandTrigger && { trigger: queryParams.collapseExpandTrigger }), + }, + ]; + }; + }; + + return mergeOptions(options, queryParams) as any; +}; diff --git a/packages/graphs/tests/demos/indented-tree.tsx b/packages/graphs/tests/demos/indented-tree.tsx new file mode 100644 index 000000000..60cc6f72c --- /dev/null +++ b/packages/graphs/tests/demos/indented-tree.tsx @@ -0,0 +1,15 @@ +import type { IndentedTreeOptions } from '@ant-design/graphs'; +import { IndentedTree as IndentedTreeComponent } from '@ant-design/graphs'; +import React from 'react'; +import data from '../datasets/algorithm-category.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +export const IndentedTree = () => { + const options = useGraphOptions({ + autoFit: 'view', + data, + animation: false, + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/index.tsx b/packages/graphs/tests/demos/index.tsx new file mode 100644 index 000000000..9b549a879 --- /dev/null +++ b/packages/graphs/tests/demos/index.tsx @@ -0,0 +1,13 @@ +export { Dendrogram } from './dendrogram'; +export { Fishbone } from './fishbone'; +export { FlowGraph } from './flow-graph'; +export { FlowGraphProductLaunch } from './flow-graph-product-launch'; +export { FlowGraphTaskScheduling } from './flow-graph-task-scheduling'; +export { IndentedTree } from './indented-tree'; +export { MindMap } from './mind-map'; +export { MindMapCustom } from './mind-map-custom'; +export { NetworkGraph } from './network-graph'; +export { OrganizationChart } from './organization-chart'; +export { OrganizationChart2 } from './organization-chart2'; +export { UserFlowDirectionDefault } from './user-flow-direction-default'; +export { UserFlowDirectionGraph } from './user-flow-direction-graph'; diff --git a/packages/graphs/tests/demos/mind-map-custom.tsx b/packages/graphs/tests/demos/mind-map-custom.tsx new file mode 100644 index 000000000..e176021ff --- /dev/null +++ b/packages/graphs/tests/demos/mind-map-custom.tsx @@ -0,0 +1,30 @@ +import type { MindMapOptions } from '@ant-design/graphs'; +import { getNodeSide, measureTextSize, MindMap as MindMapComponent, RCNode } from '@ant-design/graphs'; +import { Graph, type NodeData } from '@antv/g6'; +import React from 'react'; +import data from '../datasets/algorithm-category.json'; + +const { TextNode } = RCNode; + +export const MindMapCustom = () => { + const options: MindMapOptions = { + autoFit: 'view', + data, + node: { + style: { + component: (data) => { + return ; + }, + size: (data) => measureTextSize(data.id, [24, 16]), + dx: function (data: NodeData) { + const side = getNodeSide(this as unknown as Graph, data); + const size = measureTextSize(data.id, [24, 16]); + return side === 'left' ? -size[0] : side === 'center' ? -size[0] / 2 : 0; + }, + }, + }, + animation: false, + }; + + return ; +}; diff --git a/packages/graphs/tests/demos/mind-map.tsx b/packages/graphs/tests/demos/mind-map.tsx new file mode 100644 index 000000000..ba976da73 --- /dev/null +++ b/packages/graphs/tests/demos/mind-map.tsx @@ -0,0 +1,15 @@ +import type { MindMapOptions } from '@ant-design/graphs'; +import { MindMap as MindMapComponent } from '@ant-design/graphs'; +import React from 'react'; +import data from '../datasets/algorithm-category.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +export const MindMap = () => { + const options = useGraphOptions({ + autoFit: 'view', + data, + animation: false, + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/network-graph.tsx b/packages/graphs/tests/demos/network-graph.tsx new file mode 100644 index 000000000..aad6e2b85 --- /dev/null +++ b/packages/graphs/tests/demos/network-graph.tsx @@ -0,0 +1,49 @@ +import { NetworkGraph as NetworkGraphComponent } from '@ant-design/graphs'; +import { labelPropagation } from '@antv/algorithm'; +import React from 'react'; +import data from '../datasets/language-tree.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +export const NetworkGraph = () => { + const options = useGraphOptions({ + autoFit: 'view', + data: { + ...data, + nodes: labelPropagation(data).clusters.flatMap((cluster) => cluster.nodes), + }, + node: { + style: { + labelText: (d) => d.id, + labelBackground: true, + iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', + }, + state: { + active: { + halo: false, + }, + }, + palette: { + field: (d) => d.clusterId as string, + }, + }, + edge: { + state: { + active: { + halo: false, + }, + }, + }, + behaviors: (prev) => [ + ...prev, + { + key: 'hover-activate', + type: 'hover-activate', + degree: 1, + inactiveState: 'inactive', + }, + ], + animation: false, + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/organization-chart.tsx b/packages/graphs/tests/demos/organization-chart.tsx new file mode 100644 index 000000000..a5f0ea804 --- /dev/null +++ b/packages/graphs/tests/demos/organization-chart.tsx @@ -0,0 +1,21 @@ +import { OrganizationChart as OrganizationChartComponent, type OrganizationChartOptions } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +export const OrganizationChart = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/organization-chart.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = useGraphOptions({ + autoFit: 'view', + data, + transforms: [], + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/organization-chart2.tsx b/packages/graphs/tests/demos/organization-chart2.tsx new file mode 100644 index 000000000..f11aeeeb6 --- /dev/null +++ b/packages/graphs/tests/demos/organization-chart2.tsx @@ -0,0 +1,56 @@ +import { + OrganizationChart as OrganizationChartComponent, + RCNode, + type OrganizationChartOptions, +} from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +const { OrganizationChartNode } = RCNode; + +export const OrganizationChart2 = () => { + const [data, setData] = useState(undefined); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + fetch('https://assets.antv.antgroup.com/g6/organization-chart.json') + .then((res) => res.json()) + .then((data) => { + setData(data); + setLoading(false); + }); + }, []); + + const options = useGraphOptions({ + data, + autoFit: 'view', + node: { + style: { + component: (d) => { + const { name, position, status } = d.data || {}; + const isActive = d.states?.includes('active'); + return ; + }, + size: [240, 80], + }, + }, + edge: { + style: { + radius: 16, + lineWidth: 2, + endArrow: true, + }, + }, + behaviors: (prev) => [...prev, 'hover-activate-neighbors'], + transforms: (prev) => [ + ...prev.filter((t) => (t as any).type !== 'collapse-expand-react-node'), + { + ...(prev.find((t) => (t as any).type === 'collapse-expand-react-node') as any), + enable: true, + }, + ], + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/user-flow-direction-default.tsx b/packages/graphs/tests/demos/user-flow-direction-default.tsx new file mode 100644 index 000000000..fb5bc05dc --- /dev/null +++ b/packages/graphs/tests/demos/user-flow-direction-default.tsx @@ -0,0 +1,13 @@ +import { FlowDirectionGraph } from '@ant-design/graphs'; +import React from 'react'; +import data from '../datasets/user-flow.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +export const UserFlowDirectionDefault = () => { + const options = useGraphOptions({ + autoFit: 'view', + data, + }); + + return ; +}; diff --git a/packages/graphs/tests/demos/user-flow-direction-graph.tsx b/packages/graphs/tests/demos/user-flow-direction-graph.tsx new file mode 100644 index 000000000..94decd3a9 --- /dev/null +++ b/packages/graphs/tests/demos/user-flow-direction-graph.tsx @@ -0,0 +1,109 @@ +import { FlowDirectionGraph } from '@ant-design/graphs'; +import { Flex } from 'antd'; +import React from 'react'; +import styled from 'styled-components'; +import data from '../datasets/user-flow.json'; +import { useGraphOptions } from './hooks/useQueryOptions'; + +const transformData = (data) => { + const REF_NODE_IDS = ['node-5', 'node-6']; + const findNodeById = (id) => data.nodes.find((node) => node.id === id); + data.edges.forEach((edge) => { + edge.data ||= {}; + const isSplit = REF_NODE_IDS.includes(edge.source); + edge.data.type = isSplit ? 'split' : 'proportion'; + edge.data.ratio = edge.measure.value / findNodeById(isSplit ? edge.source : edge.target).measure.value; + }); + return data; +}; + +/** + * 用户路径分析图,展示了用户从不同来源页面进入活动页面后的转化路径 + */ +export const UserFlowDirectionGraph = () => { + const options = useGraphOptions({ + autoFit: 'view', + data: transformData(data), + node: { + style: { + component: (data) => , + size: [160, 90], + }, + }, + edge: { + style: { + stroke: (d) => + d.data!.type === 'split' ? 'l(0) 0:#F04864 0.5:#7EC2F3 1:#1890FF' : 'l(0) 0:#1890FF 0.5:#7EC2F3 1:#F04864', + labelText: (d) => { + const { type, ratio } = d.data as { type: string; ratio: number }; + const text = type === 'split' ? '分流' : '占比'; + return `${text} ${(Number(ratio) * 100).toFixed(2)}%`; + }, + labelBackground: true, + }, + }, + transforms: (prev) => [ + ...prev, + { + type: 'map-edge-line-width', + key: 'map-edge-line-width', + value: (d) => d.data.ratio, + minValue: 0, + maxValue: 1, + minLineWidth: 1, + maxLineWidth: 32, + }, + ], + layout: { + type: 'antv-dagre', + nodesep: 16, + ranksep: 100, + }, + }); + + return ; +}; + +const StyledWrapper = styled.div` + width: calc(100% - 32px); + height: calc(100% - 32px); + background-color: #f6f7f9; + border-radius: 8px; + padding: 16px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.1); + + .user-flow-node-name { + font-size: 16px; + font-weight: bold; + color: #252525; + margin-bottom: 8px; + } + + .user-flow-node-metric { + font-size: 12px; + color: #666666; + + &-value { + font-weight: bold; + } + } +`; + +const UserFlowNode = ({ data }) => { + const metrics = [ + { name: 'DAU', value: data.measure.formattedValue + data.measure.formattedUnit }, + { name: 'MAU', value: data.relatedMeasures[0].formattedValue + data.relatedMeasures[0].formattedUnit }, + ]; + + return ( + +
{data.name}
+ {metrics.map((metric) => ( + +
{metric.name}
+
{metric.value}
+
+ ))} +
+ ); +}; diff --git a/packages/graphs/tests/graphs/anchor-callback-spec.tsx b/packages/graphs/tests/graphs/anchor-callback-spec.tsx deleted file mode 100644 index 80a2d678e..000000000 --- a/packages/graphs/tests/graphs/anchor-callback-spec.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; - -describe('Type NodeData', () => { - let container; - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], - }; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('Render data', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const chartProps = { - data, - nodeCfg: { - size: [140, 25] as number[], - anchorPoints: (node) => { - return [ - [0, 0.5], - [1, 1], - ]; - }, - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: 2 as number, - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right' as 'right', - show: !!edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'] as string[], - } as const; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - const { anchorPoints } = chartRef.findById('-3')?.getModel(); - expect(anchorPoints).toEqual([ - [0, 0.5], - [1, 1], - ]); - }); -}); diff --git a/packages/graphs/tests/graphs/chore-plugin-spec.tsx b/packages/graphs/tests/graphs/chore-plugin-spec.tsx deleted file mode 100644 index df51009df..000000000 --- a/packages/graphs/tests/graphs/chore-plugin-spec.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { RadialTreeGraph } from '../../src'; -import { RadialTreeData } from '../data'; - -describe('Chore plugin', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - container.className = 'container'; - container.style.height = '600px'; - document.body.appendChild(container); - }); - afterAll(() => { - const containers = document.getElementsByClassName('container'); - Array.from(containers).forEach((el) => { - document.body.removeChild(el); - }); - container = null; - }); - - it('plugin', () => { - const config = { - data: RadialTreeData, - nodeConfig: { - type: 'image', - img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*w5uESbSe430AAAAAAAAAAABkARQnAQ', - }, - menuCfg: { - customContent: (e) => { - return ( -
- -
- ); - }, - }, - tooltipCfg: { - show: true, - customContent: (e) => { - return 'custom content'; - }, - }, - minimapCfg: { - show: true, - }, - toolbarCfg: { - show: true, - }, - } as any; - act(() => { - render(, container); - }); - expect(document.querySelector('.g6-component-meu')).not.toBeUndefined(); - expect(document.querySelector('.charts-toolbar')).not.toBeUndefined(); - expect(document.querySelector('.g6-minimap-viewport')).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/conversion-dagre-graph-spec.tsx b/packages/graphs/tests/graphs/conversion-dagre-graph-spec.tsx deleted file mode 100644 index 8469200db..000000000 --- a/packages/graphs/tests/graphs/conversion-dagre-graph-spec.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-nocheck -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { ConversionDagreGraph } from '../../src'; -import { ConvDagreData } from '../data'; - -describe('Type NodeData', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - container.style.height = '520px'; - document.body.appendChild(container); - }); - afterEach(() => { - // document.body.removeChild(container); - // container = null; - }); - it('Render data', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - - const onChangeData = (params) => { - // 业务回调 - }; - - const chartProps = { - data: ConvDagreData, - layerOrder: ['层级0', '层级1', '层级2'], - segmLayer: '层级1', - ratioMethod: 'both', - onChangeData, - }; - - act(() => { - render(, container); - }); - - expect(chartRef).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/edge-cfg-label-spec.tsx b/packages/graphs/tests/graphs/edge-cfg-label-spec.tsx deleted file mode 100644 index a5bdd51c9..000000000 --- a/packages/graphs/tests/graphs/edge-cfg-label-spec.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; -import { FlowData } from '../data'; - -describe('Edge config', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('label', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - - const chartProps = { - data: FlowData, - nodeCfg: { - size: [140, 25], - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: 2, - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = FlowData; - return { - position: 'right' as const, - show: true, - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(chartRef.cfg.defaultEdge.labelCfg).toEqual({ - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }); - }); -}); diff --git a/packages/graphs/tests/graphs/error-boundary-spec.tsx b/packages/graphs/tests/graphs/error-boundary-spec.tsx deleted file mode 100644 index 2deded346..000000000 --- a/packages/graphs/tests/graphs/error-boundary-spec.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { RadialTreeGraph } from '../../src'; - -describe('Area render', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it.skip('error template with ReactNode', () => { - const props = { - loading: true, - // An object of type loadingTemplate is only used to trigger a boundary error - loadingTemplate: { - triggleError: true, - }, - errorTemplate: custom error, - }; - const chartProps = { - data: { - id: 'Modeling Methods', - value: 'Methods', - children: [], - }, - autoFit: false, - nodeCfg: { - type: 'image', - img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mpPvTKdP7cIAAAAAAAAAAABkARQnAQ', - }, - width: 200, - height: 160, - } as any; - act(() => { - render(, container); - }); - expect(document.querySelector('#error').innerHTML).toBe('custom error'); - }); - - it.skip('error template with callback', () => { - const props = { - loading: true, - // An object of type loadingTemplate is only used to trigger a boundary error - loadingTemplate: { - triggleError: true, - }, - errorTemplate: () => custom error with callback, - }; - const chartProps = { - data: { - id: 'Modeling Methods', - value: 'Methods', - children: [], - }, - autoFit: false, - nodeCfg: { - type: 'image', - img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mpPvTKdP7cIAAAAAAAAAAABkARQnAQ', - }, - width: 200, - height: 160, - } as any; - act(() => { - render(, container); - }); - expect(document.querySelector('#error-callback').innerHTML).toBe('custom error with callback'); - }); -}); diff --git a/packages/graphs/tests/graphs/fetch-loading-spec.tsx b/packages/graphs/tests/graphs/fetch-loading-spec.tsx deleted file mode 100644 index 9c4e589c4..000000000 --- a/packages/graphs/tests/graphs/fetch-loading-spec.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useRef } from 'react'; -import { renderHook } from '@testing-library/react-hooks/server'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { RadialGraph } from '../../src'; -import { RadialData } from '../data'; -const refs = renderHook(() => useRef()) as any; - -describe('RadialGraph contentmenu', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - container.className = 'container'; - container.style.height = '600px'; - document.body.appendChild(container); - }); - afterAll(() => { - const containers = document.getElementsByClassName('container'); - Array.from(containers).forEach((el) => { - document.body.removeChild(el); - }); - container = null; - }); - - it('explore', () => { - const fetchData = (node): Promise => { - return new Promise((resolve, reject) => { - const data = new Array(Math.ceil(Math.random() * 10) + 2).fill('').map((_, i) => i + 1); - setTimeout(() => { - resolve({ - nodes: [ - { - ...node, - }, - ].concat( - data.map((i) => { - return { - id: `${node.id}-${i}`, - label: `e${i}`, - }; - }), - ), - edges: data.map((i) => { - return { - source: node.id, - target: `${node.id}-${i}`, - }; - }), - }); - }, 1000); - }); - }; - - const asyncData = async (node) => { - return await fetchData(node); - }; - - const config = { - data: RadialData, - autoFit: false, - layout: { - unitRadius: 80, - /** 节点直径 */ - nodeSize: 20, - /** 节点间距 */ - nodeSpacing: 10, - }, - nodeCfg: { - asyncData, - size: 20, - style: { - fill: '#fff', - stroke: 'yellow', - }, - labelCfg: { - style: { - fontSize: 5, - fill: 'red', - }, - }, - }, - menuCfg: { - customContent: (e) => { - return ( -
- -
- ); - }, - }, - edgeCfg: { - style: { - lineWidth: 1, - }, - endArrow: { - d: 10, - size: 2, - }, - }, - fetchLoading:
custom loading
, - onReady: (graph) => { - refs.current = graph; - }, - }; - act(() => { - render(, container); - }); - const menuContainer = document.querySelector('.g6-component-meu'); - expect(menuContainer).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/file-tree-spec.tsx b/packages/graphs/tests/graphs/file-tree-spec.tsx deleted file mode 100644 index fff4d3859..000000000 --- a/packages/graphs/tests/graphs/file-tree-spec.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FileTreeGraph } from '../../src'; -import { FileData } from '../data'; - -describe('File Tree', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('render', () => { - let chartRef = undefined; - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString(), - value: { - text: '异步节点' + Math.random().toString(), - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async (): Promise => { - return await fetchData(); - }; - const chartProps = { - data: FileData, - nodeCfg: { - // customContent: (item, group, cfg) => { - // const { startX, startY, width } = cfg; - // const { text } = item; - // const customGroup = group.addGroup({ - // name: 'custom-group', - // }); - // customGroup.addShape('text', { - // attrs: { - // textBaseline: 'middle', - // textAlign: 'start', - // x: text === 'Modeling Methods' ? startX - width / 2 : startX, - // y: startY, - // text, - // fill: 'red', - // }, - // // group 内唯一字段 - // name: `text-shape`, - // }); - // return customGroup; - // }, - }, - edgeCfg: { - style: (cfg) => { - if (cfg.id === '0') { - return { - stroke: 'transparent', - }; - } - return { - stroke: '#ccc', - radius: 6, - }; - }, - }, - markerCfg: (cfg) => { - return { - position: 'right' as 'right', - // show: false, - style: { - fill: '#fff', - stroke: '#999', - }, - }; - }, - - onReady: (graph) => { - chartRef = graph; - graph.on('add:children', async (e) => { - // const data = graph.findDataById(e.item.getID()); - // if (!data.children) data.children = []; - // const { id, value } = e.item.getModel(); - // const appendChildren = await getChildren(); - // data.children.push(...appendChildren); - // graph.updateChildren(data.children, data.id); - }); - }, - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect( - chartRef - .findById('0-1') - .get('group') - .getChildren() - .filter((item) => item.cfg.name === 'text-shape').length, - ).toBe(1); - }); -}); diff --git a/packages/graphs/tests/graphs/flow-analysis-sync-spec.tsx b/packages/graphs/tests/graphs/flow-analysis-sync-spec.tsx deleted file mode 100644 index c9ac31202..000000000 --- a/packages/graphs/tests/graphs/flow-analysis-sync-spec.tsx +++ /dev/null @@ -1,155 +0,0 @@ -// @ts-nocheck -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; -import { FlowData } from '../data'; - -describe('Type NodeData', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('Render data', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve({ - nodes: [ - { - id: Math.random().toString(), - value: { - title: '来源页面99', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: Math.random().toString(), - value: { - title: '来源页面999', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - edges: [], - }); - }, 1000); - }); - }; - const asyncData = async () => { - return await fetchData(); - }; - - const chartProps = { - data: FlowData, - fitCenter: false, - nodeCfg: { - asyncData, - size: [140, 25], - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = FlowData; - return { - position: 'right', - show: true, - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - const { _graphId } = chartRef.findById('-3')?.getModel(); - expect(_graphId).toBeDefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/flow-level-spec.tsx b/packages/graphs/tests/graphs/flow-level-spec.tsx deleted file mode 100644 index e96b1e95d..000000000 --- a/packages/graphs/tests/graphs/flow-level-spec.tsx +++ /dev/null @@ -1,156 +0,0 @@ -// @ts-nocheck -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; -import { FlowData } from '../data'; - -describe('Flow level props', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('Render level', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve({ - nodes: [ - { - id: Math.random().toString(), - value: { - title: '来源页面99', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: Math.random().toString(), - value: { - title: '来源页面999', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - edges: [], - }); - }, 1000); - }); - }; - const asyncData = async () => { - return await fetchData(); - }; - - const level = 2; - - const chartProps = { - data: FlowData, - autoFit: false, - fitCenter: false, - level, - nodeCfg: { - asyncData, - size: [140, 25], - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: { - position: 'right', - show: true, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - const { _graphId } = chartRef.findById('-3')?.getModel(); - expect(_graphId).toBeDefined(); - expect(chartRef.get('data').nodes.length).toBe(4); - }); -}); diff --git a/packages/graphs/tests/graphs/flow-loop-edges-spec.tsx b/packages/graphs/tests/graphs/flow-loop-edges-spec.tsx deleted file mode 100644 index 0d1adf353..000000000 --- a/packages/graphs/tests/graphs/flow-loop-edges-spec.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// @ts-nocheck -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; -import { FlowLoopData, FlowLoopDoubleData } from '../data'; - -describe('Flow loop edges', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('Render loop', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const chartProps = { - data: FlowLoopData, - nodeCfg: { - size: [140, 25], - }, - markerCfg: { - position: 'right', - show: true, - }, - edgeCfg: { - endArrow: {}, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(chartRef.get('data').nodes.length).toBe(FlowLoopData.nodes.length); - expect(chartRef.get('data').edges.length).toBe(FlowLoopData.edges.length); - }); - it('Render Double loop', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const chartProps = { - data: FlowLoopDoubleData, - nodeCfg: { - size: [140, 25], - }, - markerCfg: { - position: 'right', - show: true, - }, - edgeCfg: { - endArrow: {}, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(chartRef.get('data').nodes.length).toBe(FlowLoopDoubleData.nodes.length); - expect(chartRef.get('data').edges.length).toBe(FlowLoopDoubleData.edges.length); - }); -}); diff --git a/packages/graphs/tests/graphs/fund-line-spec.tsx b/packages/graphs/tests/graphs/fund-line-spec.tsx deleted file mode 100644 index 812f9d7af..000000000 --- a/packages/graphs/tests/graphs/fund-line-spec.tsx +++ /dev/null @@ -1,351 +0,0 @@ -// @ts-nocheck -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; - -describe('Labels Line', () => { - let container; - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - // value: '来源A', - value: { - text: '来源A', - subText: 'subText', - }, - }, - { - source: '-2', - target: '0', - value: { - text: '来源B', - subText: 'subText', - }, - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - value: { - text: '来源A', - subText: 'subText', - }, - }, - { - source: '0', - target: '2', - value: { - text: '来源A', - subText: 'subText', - }, - }, - { - source: '0', - target: '3', - value: '哈哈', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - value: '哈哈哈', - }, - { - source: '4', - target: '8', - value: { - text: '来源A', - subText: 'subText', - }, - }, - ], - }; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('Render label', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const chartProps = { - data, - nodeCfg: { - size: [140, 25], - anchorPoints: (node) => { - return [ - [0, 0.5], - [1, 0.5], - ]; - }, - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - autoFit: false, - edgeCfg: { - type: 'labels-line', - endArrow: true, - // startArrow: true, - label: { - style: { - fill: '#aaa', - fontSize: 24, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - // lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - layout: { - // rankdir: 'TB', - ranksepFunc: () => 40, - nodesepFunc: () => 20, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right', - // position: 'bottom', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - const { anchorPoints } = chartRef.findById('-3')?.getModel(); - expect(anchorPoints).toEqual([ - [0, 0.5], - [1, 0.5], - ]); - }); -}); diff --git a/packages/graphs/tests/graphs/get-children-types-spec.tsx b/packages/graphs/tests/graphs/get-children-types-spec.tsx deleted file mode 100644 index e446f25b7..000000000 --- a/packages/graphs/tests/graphs/get-children-types-spec.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; -import { TreeData } from '../data'; - -describe('Get children types', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('types', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString(), - value: { - title: '异步节点' + Math.random().toString(), - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '50%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async (): Promise => { - return await fetchData(); - }; - const level = 2; - const chartProps = { - data: TreeData, - level, - nodeCfg: { - getChildren, - }, - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/level-fn-spec.tsx b/packages/graphs/tests/graphs/level-fn-spec.tsx deleted file mode 100644 index 07cd01c28..000000000 --- a/packages/graphs/tests/graphs/level-fn-spec.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; -import { TreeData } from '../data'; - -describe('Level', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('show', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString(), - value: { - title: '异步节点' + Math.random().toString(), - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '50%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async (): Promise => { - const asyncData = await fetchData(); - return asyncData; - }; - const level = 2; - const chartProps = { - data: TreeData, - level, - nodeCfg: { - getChildren, - }, - markerCfg: (cfg) => { - return { - position: 'right' as 'right', - show: cfg.children?.length, - collapsed: cfg.depth >= level - 1, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect( - chartRef - .findById('A1') - .get('group') - .getChildren() - .filter((item) => item.cfg.name === 'collapse-icon').length, - ).toBe(1); - expect( - chartRef - .findById('A2') - .get('group') - .getChildren() - .filter((item) => item.cfg.name === 'collapse-icon').length, - ).toBe(1); - }); -}); diff --git a/packages/graphs/tests/graphs/marker-cfg-array-spec.tsx b/packages/graphs/tests/graphs/marker-cfg-array-spec.tsx deleted file mode 100644 index ae7a7f35d..000000000 --- a/packages/graphs/tests/graphs/marker-cfg-array-spec.tsx +++ /dev/null @@ -1,114 +0,0 @@ -// @ts-nocheck -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; -import { FlowData } from '../data'; - -describe('Type NodeData', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('Render data', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve({ - nodes: [ - { - id: Math.random().toString(), - value: { - title: '来源页面99', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: Math.random().toString(), - value: { - title: '来源页面999', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - edges: [], - }); - }, 1000); - }); - }; - const asyncData = async () => { - return await fetchData(); - }; - - const chartProps = { - data: FlowData, - fitCenter: false, - nodeCfg: { - asyncData, - size: [140, 25], - }, - markerCfg: (cfg) => { - const { id } = cfg; - const { edges } = FlowData; - if (id === '0') { - return [ - { - position: 'left', - show: true, - collapsed: !edges.find((item) => item.source === cfg.id), - }, - { - position: 'right', - show: true, - collapsed: !edges.find((item) => item.source === cfg.id), - }, - ]; - } - return { - position: 'right', - show: true, - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - const { _graphId } = chartRef.findById('-3')?.getModel(); - expect(_graphId).toBeDefined(); - expect( - chartRef - .findById('0') - .get('group') - .getChildren() - .filter((item) => item.cfg.name.startsWith('collapse-icon')).length, - ).toBe(2); - }); -}); diff --git a/packages/graphs/tests/graphs/menu-spec.tsx b/packages/graphs/tests/graphs/menu-spec.tsx deleted file mode 100644 index 1db4cf28b..000000000 --- a/packages/graphs/tests/graphs/menu-spec.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { RadialTreeGraph } from '../../src'; - -describe('RadialTreeGraph contentmenu', () => { - let container; - const data = { - id: 'Modeling Methods', - children: [ - { - id: 'Classification', - children: [ - { id: 'Logistic regression', value: 'Logistic regression' }, - { id: 'Linear discriminant analysis', value: 'Linear discriminant analysis' }, - { id: 'Rules', value: 'Rules' }, - { id: 'Decision trees', value: 'Decision trees' }, - { id: 'Naive Bayes', value: 'Naive Bayes' }, - { id: 'K nearest neighbor', value: 'K nearest neighbor' }, - { id: 'Probabilistic neural network', value: 'Probabilistic neural network' }, - { id: 'Support vector machine', value: 'Support vector machine' }, - ], - value: 'Classification', - }, - ], - value: 'Modeling Methods', - }; - - beforeEach(() => { - container = document.createElement('div'); - container.className = 'container'; - container.style.height = '600px'; - document.body.appendChild(container); - }); - afterAll(() => { - const containers = document.getElementsByClassName('container'); - Array.from(containers).forEach((el) => { - document.body.removeChild(el); - }); - container = null; - }); - - it('menu', () => { - const config = { - data, - nodeConfig: { - type: 'image', - img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*w5uESbSe430AAAAAAAAAAABkARQnAQ', - }, - menuCfg: { - customContent: (e) => { - return ( -
- -
- ); - }, - }, - } as any; - act(() => { - render(, container); - }); - const menuContainer = document.querySelector('.g6-component-meu'); - expect(menuContainer).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/mind-map-spec.tsx b/packages/graphs/tests/graphs/mind-map-spec.tsx deleted file mode 100644 index bd773b4f0..000000000 --- a/packages/graphs/tests/graphs/mind-map-spec.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { MindMapGraph } from '../../src'; -import { NoTitleTreeData } from '../data'; - -describe('Mind Map', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('render', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString(), - value: { - items: [ - { - text: '占比', - value: '50%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async (): Promise => { - const asyncData = await fetchData(); - return asyncData; - }; - const level = [-2, -1, 0, 1, 2]; - const levelTexts = level.map((l) => { - if (l < 0) return `${Math.abs(l)}层上游`; - if (l > 0) return `${Math.abs(l)}层下游`; - return `主节点`; - }); - const containerWidth = 800; - const width = 120; - const LevelFC = () => ( - - {levelTexts.map((l) => ( -
- - {l} - -
- ))} -
- ); - - const nodeSize = [width, 40]; - const chartProps = { - data: NoTitleTreeData, - autoFit: false, - width: containerWidth, - // level: 5, - layout: { - getHeight: () => { - return 40; - }, - getWidth: () => { - return nodeSize[0]; - }, - getVGap: () => { - return 16; - }, - getHGap: () => { - return (containerWidth / level.length - width) / 2; - }, - }, - // level, - nodeCfg: { - getChildren, - size: nodeSize, - padding: 4, - style: { - stroke: '#5AD8A6', - }, - items: { - padding: 0, - }, - customContent: (item, group, cfg) => { - const { startX, startY, width } = cfg; - const { text, value, icon, trend } = item; - const tagWidth = 28; - const tagHeight = 16; - group?.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: nodeSize[0], - height: nodeSize[1] + 8, - fill: '#5AD8A6', - fillOpacity: 0.1, - }, - // group 内唯一字段 - name: `container-${Math.random()}`, - }); - group?.addShape('rect', { - attrs: { - x: startX, - y: startY, - width: tagWidth, - height: tagHeight, - fill: '#47c796', - }, - // group 内唯一字段 - name: `tag-${Math.random()}`, - }); - group?.addShape('text', { - attrs: { - textBaseline: 'middle', - textAlign: 'center', - x: startX + tagWidth / 2, - y: startY + tagHeight / 2, - text: '人群', - fill: '#fff', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - group?.addShape('text', { - attrs: { - textBaseline: 'middle', - textAlign: 'start', - x: startX + tagWidth + 4, - y: startY + tagHeight / 2, - text: '人群服务名称', - fill: 'rgba(0,0,0,.65)', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - const textMargin = 10; - const sense = group?.addShape('text', { - attrs: { - textBaseline: 'top', - textAlign: 'start', - x: startX, - y: startY + tagHeight + textMargin, - text: '所属场景:', - fill: 'rgba(0,0,0,.45)', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - group?.addShape('text', { - attrs: { - textBaseline: 'top', - textAlign: 'start', - x: sense.getBBox().maxX, - y: startY + tagHeight + textMargin, - text: '这是场景名称', - fill: 'rgba(0,0,0,.45)', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - // 行高 - return 14; - }, - }, - markerCfg: (cfg) => { - const { children = [], id } = cfg; - if (id === 'A0') { - return [ - { - position: 'left' as const, - show: !!children?.length, - }, - { - position: 'right' as const, - show: !!children?.length, - }, - ]; - } - return { - position: cfg.x > 0 ? 'right' : 'left', - show: true, - } as const; - }, - }; - - act(() => { - render( -
-
{LevelFC()}
- -
, - container, - ); - }); - expect(chartRef).not.toBeUndefined(); - expect( - chartRef - .findById('A0') - .get('group') - .getChildren() - .filter((item) => item.cfg.name.startsWith('collapse-icon')).length, - ).toBe(2); - }); -}); diff --git a/packages/graphs/tests/graphs/node-explore-spec.tsx b/packages/graphs/tests/graphs/node-explore-spec.tsx deleted file mode 100644 index 4ddb991ba..000000000 --- a/packages/graphs/tests/graphs/node-explore-spec.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React, { useRef } from 'react'; -import { renderHook } from '@testing-library/react-hooks/server'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { RadialGraph } from '../../src'; -import { RadialData } from '../data'; -const refs = renderHook(() => useRef()) as any; - -describe('RadialGraph contentmenu', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - container.className = 'container'; - container.style.height = '1400px'; - container.style.height = '600px'; - document.body.appendChild(container); - }); - afterAll(() => { - const containers = document.getElementsByClassName('container'); - Array.from(containers).forEach((el) => { - document.body.removeChild(el); - }); - container = null; - }); - - it('explore', () => { - const fetchData = (node) => { - return new Promise((resolve, reject) => { - const data = new Array(Math.ceil(Math.random() * 10) + 2).fill('').map((_, i) => i + 1); - setTimeout(() => { - resolve({ - nodes: [ - { - ...node, - }, - ].concat( - data.map((i) => { - return { - id: `${node.id}-${i}`, - label: `${node.label}-${i}`, - }; - }), - ), - edges: data.map((i) => { - return { - source: node.id, - target: `${node.id}-${i}`, - }; - }), - }); - }, 1000); - }); - }; - - const asyncData = async (node) => { - return await fetchData(node); - }; - - const config = { - data: RadialData, - autoFit: false, - layout: { - unitRadius: 80, - /** 节点直径 */ - nodeSize: 20, - /** 节点间距 */ - nodeSpacing: 10, - }, - nodeCfg: { - asyncData, - size: 20, - style: { - fill: '#6CE8DC', - stroke: '#6CE8DC', - }, - labelCfg: { - style: { - fontSize: 5, - fill: '#000', - }, - }, - }, - menuCfg: { - customContent: (e) => { - return ( -
- -
- ); - }, - }, - edgeCfg: { - style: { - lineWidth: 1, - }, - endArrow: { - d: 10, - size: 2, - }, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - onReady: (graph) => { - refs.current = graph; - // @ts-ignore - window.g = graph; - }, - } as any; - act(() => { - render(, container); - }); - const menuContainer = document.querySelector('.g6-component-meu'); - expect(menuContainer).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/percent-badge-auto-width-spec.tsx b/packages/graphs/tests/graphs/percent-badge-auto-width-spec.tsx deleted file mode 100644 index 003b69c89..000000000 --- a/packages/graphs/tests/graphs/percent-badge-auto-width-spec.tsx +++ /dev/null @@ -1,157 +0,0 @@ -// @ts-nocheck -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; - -describe('Percent badge with auto width', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('percent', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const stroke = '#EA2F97'; - const config = { - data: { - id: 'total', - value: { title: '首屏体感均值', items: [{ text: '总耗时:2059ms' }] }, - children: [ - { id: 'beforeappstart', value: { title: '资源加载耗时', items: [{ text: '耗时:806ms' }], percent: '0.39' } }, - { - id: 'beforerequest', - value: { - title: '接口请求前串行逻辑耗时(可能是获取定位等)', - items: [{ text: '耗时:54ms' }], - percent: '0.03', - }, - }, - { - id: 'c2crequestcost', - value: { - title: '端到端耗时', - items: [{ text: '耗时:2121ms' }, { text: '预加载节省耗时:1180ms' }], - percent: '0.46', - }, - children: [ - { - id: 'queryTradeBuy)', - value: { - title: '服务总耗时', - items: [{ text: 'queryTradeBuy' }, { text: '耗时:1613ms' }], - percent: '0.76', - }, - children: [ - { - id: 'FzHotelTradeService#queryHotelInitBizInfo4Render', - value: { - title: '获取行业信息(商品、卖家、买家等)', - items: [{ text: 'FzHotelTradeService#queryHotelInitBizInfo4Render' }, { text: '耗时:593ms' }], - percent: '0.37', - }, - }, - { - id: 'PayingMemberService#getMemberCardItem', - value: { - title: '查询菲住联盟会员卡信息', - items: [{ text: 'PayingMemberService#getMemberCardItem' }, { text: '耗时:4ms' }], - percent: '0.00', - }, - }, - { - id: 'ConfirmOrderPageService#render', - value: { - title: '调用业务中台fliggy-buy2获取优惠/资金等信息', - items: [{ text: 'ConfirmOrderPageService#render' }, { text: '耗时:450ms' }], - percent: '0.28', - }, - }, - { - id: 'FzHotelTradeBuyService#enrichHotelRender4Buy', - value: { - title: '获取行业组件信息', - items: [{ text: 'FzHotelTradeBuyService#enrichHotelRender4Buy' }, { text: '耗时:19ms' }], - percent: '0.01', - }, - }, - { - id: 'TempStoreService#store', - value: { - title: '保存奥创组件信息', - items: [{ text: 'TempStoreService#store' }, { text: '耗时:12ms' }], - percent: '0.01', - }, - }, - { - id: 'FfaInterflowRegisterService#getRegisterInfo4Trade', - value: { - title: '获取会员注册信息', - items: [{ text: 'FfaInterflowRegisterService#getRegisterInfo4Trade' }, { text: '耗时:48ms' }], - percent: '0.03', - }, - }, - { - id: 'GeneralRightService#getRightInfo', - value: { - title: '获取会员权益信息', - items: [{ text: 'GeneralRightService#getRightInfo' }, { text: '耗时:40ms' }], - percent: '0.03', - }, - }, - { - id: 'InsSceneService#recommend', - value: { - title: '保险推荐', - items: [{ text: 'InsSceneService#recommend' }, { text: '耗时:241ms' }], - percent: '0.15', - }, - }, - ], - }, - { - id: 'netWork', - value: { title: '网络开销(http传输+mtop层)', items: [{ text: '耗时:508ms' }], percent: '0.24' }, - }, - ], - }, - { id: 'rendercost', value: { title: '渲染耗时', items: [{ text: '耗时:330ms' }], percent: '0.16' } }, - ], - }, - autoFit: true, - behaviors: [], - nodeCfg: { - style: { stroke: '#40a9ff' }, - percent: { size: 5, position: 'bottom' }, - autoWidth: true, - title: { autoEllipsis: true }, - items: {}, - nodeStateStyles: { hover: { stroke: '#1890ff', lineWidth: 2 } }, - }, - edgeCfg: { endArrow: { fill: '#40a9ff' } }, - }; - - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect( - chartRef - .getNodes()[0] - .get('group') - .getChildren() - .filter((item) => item.cfg.name === 'percent-rect-background'), - ).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/percent-badge-spec.tsx b/packages/graphs/tests/graphs/percent-badge-spec.tsx deleted file mode 100644 index 3629c5c98..000000000 --- a/packages/graphs/tests/graphs/percent-badge-spec.tsx +++ /dev/null @@ -1,231 +0,0 @@ -// @ts-nocheck -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; - -describe('Percent badge', () => { - let container; - const data = { - id: 'A0', - value: { - title: '全年降本增收', - items: [ - { - text: '3031万', - value: '80%', - }, - ], - percent: 0.8, - }, - children: [ - { - id: 'A1', - value: { - title: '降本增收项目1', - percent: 0.7, - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '70%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '降本增收项目1-1', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '降本增收项目1-2', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '降本增收项目1-3', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '降本增收项目2', - percent: 0.3, - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('percent', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const stroke = '#EA2F97'; - const chartProps = { - data, - fitCenter: false, - nodeCfg: { - size: [140, 25], - percent: { - position: 'bottom', - size: 4, - style: (arg) => { - return { - radius: [0, 0, 0, 2], - fill: arg.value.percent > 0.3 ? stroke : '#1f8fff', - }; - }, - // backgroundStyle: { - // // fill: 'red', - // }, - }, - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: (arg) => { - return { - fill: '#fff', - radius: 2, - stroke: arg.value.percent > 0.3 ? stroke : '#1f8fff', - }; - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - return { - stroke: '#518AD3', - strokeOpacity: 0.5, - }; - }, - endArrow: { - fill: '#518AD3', - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - return { - position: 'right', - show: cfg.children?.length, - style: (arg) => { - return { - stroke: arg.value.percent > 0.3 ? stroke : '#1f8fff', - }; - }, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect( - chartRef - .getNodes()[0] - .get('group') - .getChildren() - .filter((item) => item.cfg.name === 'percent-rect-background'), - ).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/graphs/tooltip-spec.tsx b/packages/graphs/tests/graphs/tooltip-spec.tsx deleted file mode 100644 index 45d44d9df..000000000 --- a/packages/graphs/tests/graphs/tooltip-spec.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; - -describe('DecompositionTreeGraph tooltip', () => { - let container; - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }; - beforeEach(() => { - container = document.createElement('div'); - container.className = 'container'; - container.style.height = '600px'; - document.body.appendChild(container); - }); - beforeAll(() => { - jest.useFakeTimers(); - }); - afterAll(() => { - const containers = document.getElementsByClassName('container'); - Array.from(containers).forEach((el) => { - document.body.removeChild(el); - }); - container = null; - }); - - it.skip('tooltip', () => { - let refs; - const after1000ms = (callback: Function) => { - setTimeout(() => { - callback(); - }, 1000); - }; - jest.spyOn(global, 'setTimeout'); - const config = { - data, - nodeConfig: { - type: 'rect', - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: children?.length, - }; - }, - tooltipCfg: { - container: document.getElementsByTagName('body')[0], - shouldBegin: (e) => { - return true; - }, - customContent: (e) => { - return 'custom content'; - }, - }, - onReady: (graph) => { - refs = graph; - }, - } as any; - act(() => { - render(, container); - }); - const tooltipContainer = document.querySelector('.g6-component-tooltip'); - expect(tooltipContainer).not.toBeUndefined(); - const node = refs.getNodes()[0]; - refs.emit('node:mouseenter', { item: node }); - after1000ms(() => { - const tooltipContent = document.querySelector('.g6-tooltip'); - expect(tooltipContent.innerHTML).toBe('custom content'); - }); - }); -}); diff --git a/packages/graphs/tests/graphs/tree-level-spec.tsx b/packages/graphs/tests/graphs/tree-level-spec.tsx deleted file mode 100644 index ae34fd7ad..000000000 --- a/packages/graphs/tests/graphs/tree-level-spec.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useRef } from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; -import { TreeData } from '../data'; - -describe('Tree level', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - it('render level', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString(), - value: { - title: '异步节点' + Math.random().toString(), - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '50%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async (): Promise => { - return await fetchData(); - }; - const level = 2; - const chartProps = { - data: TreeData, - autoFit: false, - level, - nodeCfg: { - getChildren, - }, - markerCfg: { - show: true, - position: 'right' as const, - }, - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(chartRef.findById('A1').getModel().children.length).toBe(0); - }); -}); diff --git a/packages/graphs/tests/index.html b/packages/graphs/tests/index.html new file mode 100644 index 000000000..5f1dfdd57 --- /dev/null +++ b/packages/graphs/tests/index.html @@ -0,0 +1,19 @@ + + + + + + @ant-design/graphs + + + +
+ + + diff --git a/packages/graphs/tests/issues/1077-spec.tsx b/packages/graphs/tests/issues/1077-spec.tsx deleted file mode 100644 index 5ba3688a2..000000000 --- a/packages/graphs/tests/issues/1077-spec.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; - -describe('Custom layout', () => { - let container; - const data = { - nodes: [ - { - id: '1', - x: 20, - y: 100, - value: { - title: 'A', - items: [ - { - text: 'text', - }, - ], - }, - }, - { - id: '2', - x: 20, - y: 200, - value: { - title: 'B', - items: [ - { - text: 'text ', - }, - ], - }, - }, - ], - edges: [ - { - source: '1', - target: '2', - }, - ], - }; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('Layout with custom position', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const chartProps = { - data, - customLayout: true, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - const node = chartRef.findById('2'); - const { x, y } = node.getModel(); - expect(x).toBe(20); - expect(y).toBe(200); - }); -}); diff --git a/packages/graphs/tests/issues/1286-spec.tsx b/packages/graphs/tests/issues/1286-spec.tsx deleted file mode 100644 index d0a6db2ac..000000000 --- a/packages/graphs/tests/issues/1286-spec.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { OrganizationGraph } from '../../src'; - -describe('Type NodeData', () => { - let container; - const data = { - id: 'root', - value: { - name: 'Joel Alan', - title: 'CEO', - // 建议使用 bae64 数据 - icon: 'https://avatars.githubusercontent.com/u/31396322?v=4', - }, - children: [ - { - id: 'joel', - value: { - name: 'Joel Alan', - }, - children: [ - { - id: 'c1', - value: { - name: 'c1', - }, - children: [ - { - id: 'c1-1', - value: { - name: 'c1-1', - }, - }, - { - id: 'c1-2', - value: { - name: 'c1-2', - }, - children: [ - { - id: 'c1-2-1', - value: { - name: 'c1-2-1', - }, - }, - { - id: 'c1-2-2', - value: { - name: 'c1-2-2', - }, - }, - ], - }, - ], - }, - { - id: 'c2', - value: { - name: 'c2', - }, - }, - { - id: 'c3', - value: { - name: 'c3', - }, - children: [ - { - id: 'c3-1', - value: { - name: 'c3-1', - }, - }, - { - id: 'c3-2', - value: { - name: 'c3-2', - }, - children: [ - { - id: 'c3-2-1', - value: { - name: 'c3-2-1', - }, - }, - { - id: 'c3-2-2', - value: { - name: 'c3-2-2', - }, - }, - { - id: 'c3-2-3', - value: { - name: 'c3-2-3', - }, - }, - ], - }, - { - id: 'c3-3', - value: { - name: 'c3-3', - }, - }, - ], - }, - ], - }, - ], - }; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - // document.body.removeChild(container); - // container = null; - }); - - it('Render data', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const chartProps = { - data, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - // const node = chartRef.findById('2'); - // const { x, y } = node.getModel(); - expect(1).toBe(1); - }); -}); diff --git a/packages/graphs/tests/issues/1291-spec.tsx b/packages/graphs/tests/issues/1291-spec.tsx deleted file mode 100644 index 287c32c65..000000000 --- a/packages/graphs/tests/issues/1291-spec.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { OrganizationGraph } from '../../src'; -import { ORG_DATA } from '../data'; - -describe('Content can be drag', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('render', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (graph) => { - chartRef = graph; - }, - }; - const config = { - height: 600, - data: ORG_DATA, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(chartRef.findById('root').getModel()._draggable).toBeTruthy(); - }); -}); diff --git a/packages/graphs/tests/issues/1338-spec.tsx b/packages/graphs/tests/issues/1338-spec.tsx deleted file mode 100644 index bff797790..000000000 --- a/packages/graphs/tests/issues/1338-spec.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; - -describe('Auto width', () => { - let container; - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV(很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长)', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], - }; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('Render', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const chartProps = { - data, - nodeCfg: { - size: [140, 25], - autoWidth: true, - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: 2, - }, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - const node = chartRef.findById('1'); - const { maxX, minX } = node.getBBox(); - const { size } = node.getModel(); - expect(maxX - minX).toBeGreaterThan(size[0]); - }); -}); diff --git a/packages/graphs/tests/issues/1441-spec.tsx b/packages/graphs/tests/issues/1441-spec.tsx deleted file mode 100644 index a39df9cda..000000000 --- a/packages/graphs/tests/issues/1441-spec.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; - -describe('Fit center', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('Render', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A132', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString().substr(2, 10), - value: { - title: '异步节点' + Math.random().toString(), - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '50%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async () => { - const asyncData = await fetchData(); - return asyncData; - }; - - const config = { - data, - autoFit: false, - nodeCfg: { - getChildren, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: true, - collapsed: !children?.length, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/issues/1489-spec.tsx b/packages/graphs/tests/issues/1489-spec.tsx deleted file mode 100644 index 1310931f6..000000000 --- a/packages/graphs/tests/issues/1489-spec.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { RadialTreeGraph } from '../../src'; - -describe('Image node', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('Render', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const data = { - id: 'Modeling Methods', - children: [ - { - id: 'Classification', - type: 'image', - img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*w5uESbSe430AAAAAAAAAAABkARQnAQ', - children: [ - { id: 'Logistic regression', value: 'Logistic regression' }, - { id: 'Linear discriminant analysis', value: 'Linear discriminant analysis' }, - { id: 'Rules', value: 'Rules' }, - { id: 'Decision trees', value: 'Decision trees' }, - { id: 'Naive Bayes', value: 'Naive Bayes' }, - { id: 'K nearest neighbor', value: 'K nearest neighbor' }, - { id: 'Probabilistic neural network', value: 'Probabilistic neural network' }, - { id: 'Support vector machine', value: 'Support vector machine' }, - ], - value: 'Classification', - }, - { - id: 'Consensus', - children: [ - { - id: 'Models diversity', - children: [ - { id: 'Different initializations', value: 'Different initializations' }, - { id: 'Different parameter choices', value: 'Different parameter choices' }, - { id: 'Different architectures', value: 'Different architectures' }, - { id: 'Different modeling methods', value: 'Different modeling methods' }, - { id: 'Different training sets', value: 'Different training sets' }, - { id: 'Different feature sets', value: 'Different feature sets' }, - ], - value: 'Models diversity', - }, - { - id: 'Methods', - children: [ - { id: 'Classifier selection', value: 'Classifier selection' }, - { id: 'Classifier fusion', value: 'Classifier fusion' }, - ], - value: 'Methods', - }, - { - id: 'Common', - children: [ - { id: 'Bagging', value: 'Bagging' }, - { id: 'Boosting', value: 'Boosting' }, - { id: 'AdaBoost', value: 'AdaBoost' }, - ], - value: 'Common', - }, - ], - value: 'Consensus', - }, - { - id: 'Regression', - children: [ - { id: 'Multiple linear regression', value: 'Multiple linear regression' }, - { id: 'Partial least squares', value: 'Partial least squares' }, - { - id: 'Multi-layer feedforward neural network', - value: 'Multi-layer feedforward neural network', - }, - { id: 'General regression neural network', value: 'General regression neural network' }, - { id: 'Support vector regression', value: 'Support vector regression' }, - ], - value: 'Regression', - }, - ], - value: 'Modeling Methods', - }; - - const config = { - data, - autoFit: false, - nodeCfg: { - type: 'image', - img: 'https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*mpPvTKdP7cIAAAAAAAAAAABkARQnAQ', - }, - layout: { - type: 'compactBox', - direction: 'RL', - getId: function getId(d) { - return d.id; - }, - getHeight: () => { - return 26; - }, - getWidth: () => { - return 26; - }, - getVGap: () => { - return 20; - }, - getHGap: () => { - return 30; - }, - radial: true, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/issues/1577-spec.tsx b/packages/graphs/tests/issues/1577-spec.tsx deleted file mode 100644 index 718299049..000000000 --- a/packages/graphs/tests/issues/1577-spec.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; -import { FlowData } from '../data'; - -describe('Image node', () => { - let container; - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('Render', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - - const config = { - data: FlowData, - level: 2, - nodeCfg: { - size: [140, 25], - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = FlowData; - return { - position: 'right', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/issues/1614-spec.tsx b/packages/graphs/tests/issues/1614-spec.tsx deleted file mode 100644 index 1bdff966e..000000000 --- a/packages/graphs/tests/issues/1614-spec.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph } from '../../src'; -import { TreeData } from '../data'; - -describe('React 18', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('render', () => { - let chartRef = undefined; - - const props = { - className: 'container', - onReady: (ref) => { - chartRef = ref; - }, - }; - const spyWarn = jest.spyOn(console, 'warn'); - - const config = { - data: TreeData, - autoFit: false, - width: 300, - height: 300, - tooltipCfg: { - customContent: () => { - return
tooltip
; - }, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(spyWarn).toBeCalledTimes(0); - }); -}); diff --git a/packages/graphs/tests/issues/1752-spec.tsx b/packages/graphs/tests/issues/1752-spec.tsx deleted file mode 100644 index 9b3c8d8fa..000000000 --- a/packages/graphs/tests/issues/1752-spec.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph, FlowAnalysisGraph } from '../../src'; -import { TreeData, FlowData } from '../data'; - -describe('Marker Events', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('render DecompositionTreeGraph', () => { - let chartRef = undefined; - let ext; - const props = { - className: 'container', - onReady: (graph) => { - chartRef = graph; - graph.on('marker:click', (evt, extra) => { - ext = extra; - }); - graph.emit('marker:click', '', { - type: 'collapse', - collapsed: true, - }); - }, - }; - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString(), - value: { - title: '异步节点' + Math.random().toString(), - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '50%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async (): Promise => { - return await fetchData(); - }; - const config = { - data: TreeData, - height: 600, - nodeCfg: { - getChildren, - }, - tooltipCfg: { - customContent: () => { - return
tooltip
; - }, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: true, - collapsed: !children?.length, - }; - }, - - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(ext).toEqual({ - type: 'collapse', - collapsed: true, - }); - }); - - it('render FlowAnalysisGraph', () => { - let chartRef = undefined; - - const props = { - className: 'container', - onReady: (graph) => { - chartRef = graph; - graph.on('marker:click', (evt, ext) => { - console.log(ext); - }); - }, - }; - const spyWarn = jest.spyOn(console, 'warn'); - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve({ - nodes: [ - { - id: Math.random().toString(), - value: { - title: '来源页面99', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: Math.random().toString(), - value: { - title: '来源页面999', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - edges: [], - }); - }, 1000); - }); - }; - const asyncData = async () => { - return await fetchData(); - }; - const config = { - data: FlowData, - height: 600, - tooltipCfg: { - customContent: () => { - return
tooltip
; - }, - }, - nodeCfg: { - asyncData, - }, - markerCfg: (cfg) => { - const { edges } = FlowData; - return { - position: 'right', - // show: edges.find((item) => item.source === cfg.id), - show: true, - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - }); -}); diff --git a/packages/graphs/tests/issues/1764-spec.tsx b/packages/graphs/tests/issues/1764-spec.tsx deleted file mode 100644 index c7b199168..000000000 --- a/packages/graphs/tests/issues/1764-spec.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { DecompositionTreeGraph, FlowAnalysisGraph } from '../../src'; -import { TreeData } from '../data'; - -describe('Set Ellipsis', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it.skip('render title', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (graph) => { - chartRef = graph; - }, - }; - const config = { - data: TreeData, - height: 600, - nodeCfg: { - title: { - style: { - fontSize: 12, - }, - }, - }, - tooltipCfg: { - customContent: () => { - return
tooltip
; - }, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: true, - collapsed: !children?.length, - }; - }, - - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect( - chartRef - .findById('A1') - .get('group') - .getChildren() - .filter((group) => group.get('name') === 'title')[0] - .attr('text'), - ).toBe('华南华北verylong...'); - expect( - chartRef - .findById('A2') - .get('group') - .getChildren() - .filter((group) => group.get('name') === 'title')[0] - .attr('text'), - ).toBe('华北华南真的很大...'); - }); -}); diff --git a/packages/graphs/tests/issues/1801-spec.tsx b/packages/graphs/tests/issues/1801-spec.tsx deleted file mode 100644 index 5cdf05560..000000000 --- a/packages/graphs/tests/issues/1801-spec.tsx +++ /dev/null @@ -1,387 +0,0 @@ -import React from 'react'; -import { render } from '../../src/utils'; -import { act } from 'react-dom/test-utils'; -import { FlowAnalysisGraph } from '../../src'; - -describe('Get Flow Data', () => { - let container; - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - it('render', () => { - let chartRef = undefined; - const props = { - className: 'container', - onReady: (graph) => { - chartRef = graph; - }, - }; - const config = { - height: 600, - data: { - nodes: [ - { - id: 'a', - value: { - id: 'a', - index: 0, - app_name: 'a', - biz_name: '交通', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'aaaaa', - percent: 0, - }, - }, - { - id: 'b', - value: { - id: 'b', - index: 1, - app_name: 'b', - biz_name: '基础服务', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'bbbbb', - percent: 0, - }, - }, - { - id: 'c', - value: { - id: 'c', - index: 2, - app_name: 'c', - biz_name: '自驾出行', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'ccccc', - percent: 0, - }, - }, - { - id: 'd', - value: { - id: 'd', - index: 3, - app_name: 'd', - biz_name: '导航', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'ddddd', - percent: 0, - }, - }, - { - id: 'e', - value: { - id: 'e', - index: 4, - app_name: 'e', - biz_name: '', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'eeeee', - percent: 0, - }, - }, - { - id: 'f', - value: { - id: 'f', - index: 5, - app_name: 'f', - biz_name: '应用服务', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'fffff', - percent: 0, - }, - }, - { - id: 'g', - value: { - id: 'g', - index: 6, - app_name: 'g', - biz_name: '导航', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'ggggg', - percent: 0, - }, - }, - { - id: 'h', - value: { - id: 'h', - index: 7, - app_name: 'h', - biz_name: '应用服务', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'hhhhh', - percent: 0, - }, - }, - { - id: 'i', - value: { - id: 'i', - index: 8, - app_name: 'i', - biz_name: '应用服务', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'iiiii', - percent: 0, - }, - }, - { - id: 'j', - value: { - id: 'j', - index: 9, - app_name: 'j', - biz_name: '应用服务', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'jjjjj', - percent: 0, - }, - }, - { - id: 'k', - value: { - id: 'k', - index: 10, - app_name: 'k', - biz_name: '导航', - score: 0, - items: [], - changes: [], - is_alarm_app: true, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'kkkkk', - percent: 0, - }, - }, - { - id: 'l', - value: { - id: 'l', - index: 11, - app_name: 'l', - biz_name: '', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'lllll', - percent: 0, - }, - }, - { - id: 'm', - value: { - id: 'm', - index: 12, - app_name: 'm', - biz_name: '接入层', - score: 0, - items: [], - changes: [], - is_alarm_app: false, - is_root_app: false, - threshold: 0, - metrics: [], - title: 'mmmmm', - percent: 0, - }, - }, - ], - edges: [ - { - source: 'a', - target: 'k', - }, - { - source: 'c', - target: 'k', - }, - { - source: 'd', - target: 'k', - }, - { - source: 'e', - target: 'k', - }, - { - source: 'f', - target: 'k', - }, - { - source: 'g', - target: 'k', - }, - { - source: 'i', - target: 'k', - }, - { - source: 'j', - target: 'k', - }, - { - source: 'h', - target: 'f', - }, - { - source: 'b', - target: 'i', - }, - { - source: 'm', - target: 'b', - }, - { - source: 'l', - target: 'm', - }, - ], - // app_name: 'k', - }, - autoFit: false, - fitCenter: true, - minimapCfg: { - show: true, - refresh: true, - size: [100, 50], - className: 'graph-minimap', - type: 'delegate', - delegateStyle: { - fill: '#91caff', - }, - }, - toolbarCfg: { - show: true, - }, - nodeCfg: { - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 14, - }, - autoEllipsis: true, - }, - size: [220, 40], - percent: { - position: 'bottom', - size: 4, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - anchorPoints: [ - [0.5, 0], - [0.5, 1], - ], - }, - edgeCfg: { - type: 'polyline', - endArrow: true, - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 0.5, - }, - }, - edgeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - }, - layout: { - rankdir: 'TB', - }, - behaviors: ['drag-canvas', 'zoom-canvas'], - } as any; - act(() => { - render(, container); - }); - expect(chartRef).not.toBeUndefined(); - expect(chartRef.get('data').edges.length).toBe(12); - }); -}); diff --git a/packages/graphs/tests/main.tsx b/packages/graphs/tests/main.tsx new file mode 100644 index 000000000..108e3b42c --- /dev/null +++ b/packages/graphs/tests/main.tsx @@ -0,0 +1,42 @@ +import { Alert, Flex, Select } from 'antd'; +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { Outlet, RouterProvider, createBrowserRouter, useMatch, useNavigate } from 'react-router-dom'; +import * as demos from './demos'; + +const App = () => { + const navigate = useNavigate(); + const match = useMatch('/*'); + return ( + + -
- ); -}; - -const RenameService = (props) => { - return ( - - {(config, plugin) => } - - ); -}; - -const CanvasService = (props) => { - return

主画布

; -}; - -export const controlMapService = (controlMap) => { - controlMap.set('rename-service', RenameService); - controlMap.set('canvas-service', CanvasService); - return controlMap; -}; - -const formSchemaService = async (args) => { - const { targetType } = args; - const isGroup = args.targetData?.isGroup; - const nodeSchema = { - tabs: [ - { - name: '设置', - groups: [ - { - name: 'groupName', - controls: [ - { - label: '节点名', - name: '自定义form', - shape: 'rename-service', - placeholder: '节点名称', - }, - ], - }, - ], - }, - ], - }; - - if (isGroup) { - // TODO - } - - if (targetType === 'node') { - return nodeSchema; - } - - if (targetType === 'edge') { - // TODO - } - - return { - tabs: [ - { - name: '设置', - groups: [ - { - name: 'groupName', - controls: [ - { - label: '', - name: 'canvas-service', - shape: 'canvas-service', - }, - ], - }, - ], - }, - ], - }; -}; - -const DemoFlowchart = () => { - return ( -
- { - console.log(d); - }} - toolbarPanelProps={{ - position: { - top: 0, - left: 0, - right: 0, - }, - }} - scaleToolbarPanelProps={{ - layout: 'horizontal', - position: { - right: 0, - top: -40, - }, - style: { - width: 150, - height: 39, - left: 'auto', - background: 'transparent', - }, - }} - canvasProps={{ - position: { - top: 40, - left: 0, - right: 0, - bottom: 0, - }, - }} - nodePanelProps={{ - position: { width: 160, top: 40, bottom: 0, left: 0 }, - }} - detailPanelProps={{ - position: { width: 200, top: 40, bottom: 0, right: 0 }, - controlMapService, - formSchemaService, - }} - /> -
- ); -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/flowchart/basic/demo/custom-node.js b/site/examples/flowchart/basic/demo/custom-node.js deleted file mode 100644 index a9309178d..000000000 --- a/site/examples/flowchart/basic/demo/custom-node.js +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Flowchart } from '@ant-design/flowchart'; - -/** - * 样式文件引入,实际项目中不要这么用,可以考虑在对应的less\sass文件中引入 - * eg: - * style.less - * @import (inline) '../../node_modules/antd/dist/antd.css'; - * @import (inline) '../../node_modules/@ant-design/flowchart/dist/index.css'; - * demo.tsx - * import './style.less' - */ -const createLink = (src) => { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.className = 'dynamic-link'; - link.href = src; - document.getElementsByTagName('head')[0].appendChild(link); -}; -createLink('https://unpkg.com/antd@4.24.3/dist/antd.css'); -createLink('https://unpkg.com/@ant-design/flowchart@1.2.1/dist/index.css'); - -const IndicatorNode = (props) => { - const { size = { width: 120, height: 50 }, data } = props; - const { width, height } = size; - const { label = '自定义节点', stroke = '#ccc', fill = '#fff', fontFill, fontSize } = data; - - return ( -
-
{label}
-
- ); -}; - -const DemoFlowchart = () => { - return ( -
- { - console.log(d); - }} - toolbarPanelProps={{ - position: { - top: 0, - left: 0, - right: 0, - }, - }} - scaleToolbarPanelProps={{ - layout: 'horizontal', - position: { - right: 0, - top: -40, - }, - style: { - width: 150, - height: 39, - left: 'auto', - background: 'transparent', - }, - }} - canvasProps={{ - position: { - top: 40, - left: 0, - right: 0, - bottom: 0, - }, - }} - nodePanelProps={{ - position: { width: 160, top: 40, bottom: 0, left: 0 }, - defaultActiveKey: ['custom'], // ['custom', 'official'] - registerNode: { - title: '指标节点', - nodes: [ - { - component: IndicatorNode, - popover: () =>
指标节点
, - name: 'custom-node-indicator', - width: 120, - height: 50, - label: '自定义节点', - }, - ], - }, - }} - detailPanelProps={{ - position: { width: 200, top: 40, bottom: 0, right: 0 }, - }} - /> -
- ); -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/flowchart/basic/demo/meta.json b/site/examples/flowchart/basic/demo/meta.json deleted file mode 100644 index 5f6ab8e63..000000000 --- a/site/examples/flowchart/basic/demo/meta.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "基础流程图", - "en": "Basic flowchart" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/ABozcNxHpq/a2219011-3e9b-47d2-82a3-376fc779a065.png" - }, - { - "filename": "custom-node.js", - "title": { - "zh": "自定义节点", - "en": "Custom node" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/4o%26HrctHA3/bcbfb761-4fbb-4bc9-8875-8e71853f3253.png" - }, - { - "filename": "custom-form.js", - "title": { - "zh": "自定义表单", - "en": "Custom form" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/zJ7Rye47U6/c5108c8c-d8f6-43bc-b18c-c93f8de7ac31.png" - }, - { - "filename": "read.js", - "title": { - "zh": "阅读态", - "en": "Reading state" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/EMItQJlo%24%24/42341287-3b55-4bcd-b62c-4d2e69298a0b.png" - }, - { - "filename": "complex-form.js", - "title": { - "zh": "自定义表单-结合内部组件", - "en": "Custom form with internal components" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/gDwaA%24IRr5/cdbe1478-ba99-44c7-b9f3-fc30c13698a1.png" - } - ] -} diff --git a/site/examples/flowchart/basic/demo/read.js b/site/examples/flowchart/basic/demo/read.js deleted file mode 100644 index 206ec6c85..000000000 --- a/site/examples/flowchart/basic/demo/read.js +++ /dev/null @@ -1,807 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Flowchart } from '@ant-design/flowchart'; - -/** - * 样式文件引入,实际项目中不要这么用,可以考虑在对应的less\sass文件中引入 - * eg: - * style.less - * @import (inline) '../../node_modules/antd/dist/antd.css'; - * @import (inline) '../../node_modules/@ant-design/flowchart/dist/index.css'; - * demo.tsx - * import './style.less' - */ -const createLink = (src) => { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.className = 'dynamic-link'; - link.href = src; - document.getElementsByTagName('head')[0].appendChild(link); -}; -createLink('https://unpkg.com/antd@4.24.3/dist/antd.css'); -createLink('https://unpkg.com/@ant-design/flowchart@1.2.1/dist/index.css'); - -const DATA = { - nodes: [ - { - id: 'node-63cd90e9-090b-4a52-b003-084fe8512d37', - parentId: '', - renderKey: 'Terminal', - name: 'Terminal', - label: '开始', - width: 100, - height: 40, - ports: { - items: [ - { group: 'top', id: '3c0f5d34-3ab9-40a6-bfd8-bfe736fd8b59' }, - { group: 'right', id: '1911685f-a894-4e63-ace4-db386ae97bad' }, - { group: 'bottom', id: '25aed5b5-ad0e-4638-8775-de00294097f1' }, - { group: 'left', id: '9989c947-1b39-4635-b9aa-bb5e6ad9351e' }, - ], - groups: { - top: { - position: { name: 'top' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - right: { - position: { name: 'right' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - bottom: { - position: { name: 'bottom' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - left: { - position: { name: 'left' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - }, - }, - isLeaf: true, - x: 580, - y: 80, - zIndex: 10, - stroke: '#417505', - }, - { - id: 'node-915545b7-7723-4ccc-8970-3309da79a0d5', - parentId: '', - renderKey: 'Process', - name: 'Process', - label: '步骤1', - width: 100, - height: 40, - ports: { - items: [ - { group: 'top', id: '7643ce5d-4e4c-4776-affd-6f2ca0335dcc' }, - { group: 'right', id: 'fea7703c-2d37-46f0-b310-0955864644ba' }, - { group: 'bottom', id: '1fa43052-ace7-44ff-b64b-82fdf3a48298' }, - { group: 'left', id: '497cc3ac-68f4-4c50-b99e-1c8fa7a7c457' }, - ], - groups: { - top: { - position: { name: 'top' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - right: { - position: { name: 'right' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - bottom: { - position: { name: 'bottom' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - left: { - position: { name: 'left' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - }, - }, - isLeaf: true, - x: 580, - y: 180, - zIndex: 10, - stroke: '#000000', - fill: '#7ed321', - }, - { - id: 'node-79008d10-2f11-459c-888b-032ae29b8952', - parentId: '', - renderKey: 'Decision', - name: 'Decision', - label: '条件P', - width: 100, - height: 60, - ports: { - items: [ - { group: 'top', id: 'a90ca41d-3c7d-46d3-9a09-0a68e5923822' }, - { group: 'right', id: '5a5874a5-39ba-432a-b595-ff043912c57f' }, - { group: 'bottom', id: 'd007cc53-7925-4f82-87ac-673bd96404e2' }, - { group: 'left', id: 'd872380e-a3ba-4147-9a77-3a7b0d1c2f45' }, - ], - groups: { - top: { - position: { name: 'top' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - right: { - position: { name: 'right' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - bottom: { - position: { name: 'bottom' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - left: { - position: { name: 'left' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - }, - }, - isLeaf: true, - x: 580, - y: 278, - zIndex: 10, - fill: '#f8e71c', - stroke: '#000000', - }, - { - id: 'node-c657806d-dd71-4a89-b4ec-fc5fad51b843', - parentId: '', - renderKey: 'Process', - name: 'Process', - label: '步骤2', - width: 100, - height: 40, - ports: { - items: [ - { group: 'top', id: 'ec0aa7a6-40fe-4082-94e0-d98742ab062f' }, - { group: 'right', id: '79e56de6-b111-4b23-a89f-7dc244c0b02e' }, - { group: 'bottom', id: '8a32bebe-4fe8-4482-a40d-0a1ae7537246' }, - { group: 'left', id: 'ed13ee18-2198-4002-9bb2-89bc2e28ee72' }, - ], - groups: { - top: { - position: { name: 'top' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - right: { - position: { name: 'right' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - bottom: { - position: { name: 'bottom' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - left: { - position: { name: 'left' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - }, - }, - isLeaf: true, - x: 580, - y: 402, - zIndex: 10, - stroke: '#50e3c2', - }, - { - id: 'node-f6ccc339-9a05-4bf1-ad25-bfb956ff9388', - parentId: '', - renderKey: 'Terminal', - name: 'Terminal', - label: '结束', - width: 100, - height: 40, - ports: { - items: [ - { group: 'top', id: '481bf3a3-e4fc-40fc-be77-766a8ecb9360' }, - { group: 'right', id: 'ffc158ad-8ad5-4e33-a68a-50e2f1eb4794' }, - { group: 'bottom', id: 'efad4b89-681a-4c4a-b30a-6868cc2219a3' }, - { group: 'left', id: '588ceaae-2f21-4040-9325-ccae4d44484c' }, - ], - groups: { - top: { - position: { name: 'top' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - right: { - position: { name: 'right' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - bottom: { - position: { name: 'bottom' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - left: { - position: { name: 'left' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - }, - }, - isLeaf: true, - x: 580, - y: 499, - zIndex: 10, - stroke: '#bd10e0', - }, - { - id: 'node-e07e6834-7d15-4b3c-9b40-8f234fca363e', - parentId: '', - renderKey: 'Process', - name: 'Process', - label: '步骤3', - width: 100, - height: 40, - ports: { - items: [ - { group: 'top', id: '273b21fc-7b54-4819-83ca-3b0547976d5d' }, - { group: 'right', id: '42fce1fe-d257-4a08-8ac0-5804403e0ed0' }, - { group: 'bottom', id: '29465bdc-72ef-445d-9dc3-ac833c855658' }, - { group: 'left', id: '281f8f42-b772-4507-a56b-718f8ecc2a9b' }, - ], - groups: { - top: { - position: { name: 'top' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - right: { - position: { name: 'right' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - bottom: { - position: { name: 'bottom' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - left: { - position: { name: 'left' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - }, - }, - isLeaf: true, - x: 810, - y: 288, - zIndex: 10, - stroke: '#000000', - fill: '#7ed321', - group: '5f8e6625-9a79-4d0e-9ae3-023421a10c60', - isCollapsed: false, - }, - { - id: 'node-3f701e75-5116-4a62-8717-1fe8f71c920a', - parentId: '', - renderKey: 'Database', - name: 'Database', - label: '步骤4', - width: 100, - height: 40, - ports: { - items: [ - { group: 'top', id: '6ef1c94f-4083-4bd0-b35a-f68c6744f374' }, - { group: 'right', id: 'add1041d-6c42-4d9a-866b-1a2d16d74461' }, - { group: 'bottom', id: 'e2e4400d-b102-4685-a288-df626d54efa3' }, - { group: 'left', id: '85c0f07c-e618-4734-b541-1676eefa4cf0' }, - ], - groups: { - top: { - position: { name: 'top' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - right: { - position: { name: 'right' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - bottom: { - position: { name: 'bottom' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - left: { - position: { name: 'left' }, - attrs: { - circle: { - r: 4, - magnet: true, - stroke: '#31d0c6', - strokeWidth: 2, - fill: '#fff', - style: { visibility: 'hidden' }, - }, - }, - zIndex: 10, - }, - }, - }, - isLeaf: true, - x: 811, - y: 374, - zIndex: 10, - stroke: '#bd10e0', - group: '5f8e6625-9a79-4d0e-9ae3-023421a10c60', - isCollapsed: false, - }, - { - id: '5f8e6625-9a79-4d0e-9ae3-023421a10c60', - renderKey: 'GROUP_NODE_RENDER_ID', - groupChildren: ['node-e07e6834-7d15-4b3c-9b40-8f234fca363e', 'node-3f701e75-5116-4a62-8717-1fe8f71c920a'], - groupCollapsedSize: { width: 200, height: 40 }, - label: '异常处理', - zIndex: 10, - width: 170, - height: 190, - groupChildrenSize: { width: 182, height: 192 }, - x: 770, - y: 252, - isGroup: true, - stroke: '#f5a623', - }, - ], - edges: [ - { - id: '[object Object]:25aed5b5-ad0e-4638-8775-de00294097f1-[object Object]:7643ce5d-4e4c-4776-affd-6f2ca0335dcc', - targetPortId: '7643ce5d-4e4c-4776-affd-6f2ca0335dcc', - sourcePortId: '25aed5b5-ad0e-4638-8775-de00294097f1', - source: { cell: 'node-63cd90e9-090b-4a52-b003-084fe8512d37', port: '25aed5b5-ad0e-4638-8775-de00294097f1' }, - target: { cell: 'node-915545b7-7723-4ccc-8970-3309da79a0d5', port: '7643ce5d-4e4c-4776-affd-6f2ca0335dcc' }, - zIndex: 1, - attrs: { - line: { - stroke: '#A2B1C3', - targetMarker: { name: 'block', width: 12, height: 8 }, - strokeDasharray: '5 5', - strokeWidth: 1, - }, - }, - data: { - targetPortId: '7643ce5d-4e4c-4776-affd-6f2ca0335dcc', - sourcePortId: '25aed5b5-ad0e-4638-8775-de00294097f1', - source: 'node-63cd90e9-090b-4a52-b003-084fe8512d37', - target: 'node-915545b7-7723-4ccc-8970-3309da79a0d5', - }, - }, - { - id: '[object Object]:1fa43052-ace7-44ff-b64b-82fdf3a48298-[object Object]:a90ca41d-3c7d-46d3-9a09-0a68e5923822', - targetPortId: 'a90ca41d-3c7d-46d3-9a09-0a68e5923822', - sourcePortId: '1fa43052-ace7-44ff-b64b-82fdf3a48298', - source: { cell: 'node-915545b7-7723-4ccc-8970-3309da79a0d5', port: '1fa43052-ace7-44ff-b64b-82fdf3a48298' }, - target: { cell: 'node-79008d10-2f11-459c-888b-032ae29b8952', port: 'a90ca41d-3c7d-46d3-9a09-0a68e5923822' }, - zIndex: 1, - attrs: { - line: { - stroke: '#A2B1C3', - targetMarker: { name: 'block', width: 12, height: 8 }, - strokeDasharray: '5 5', - strokeWidth: 1, - }, - }, - data: { - targetPortId: 'a90ca41d-3c7d-46d3-9a09-0a68e5923822', - sourcePortId: '1fa43052-ace7-44ff-b64b-82fdf3a48298', - source: 'node-915545b7-7723-4ccc-8970-3309da79a0d5', - target: 'node-79008d10-2f11-459c-888b-032ae29b8952', - }, - }, - { - id: '[object Object]:d007cc53-7925-4f82-87ac-673bd96404e2-[object Object]:ec0aa7a6-40fe-4082-94e0-d98742ab062f', - targetPortId: 'ec0aa7a6-40fe-4082-94e0-d98742ab062f', - sourcePortId: 'd007cc53-7925-4f82-87ac-673bd96404e2', - source: { cell: 'node-79008d10-2f11-459c-888b-032ae29b8952', port: 'd007cc53-7925-4f82-87ac-673bd96404e2' }, - target: { cell: 'node-c657806d-dd71-4a89-b4ec-fc5fad51b843', port: 'ec0aa7a6-40fe-4082-94e0-d98742ab062f' }, - zIndex: 1, - attrs: { - line: { - stroke: '#50e3c2', - targetMarker: { name: 'block', width: 12, height: 8 }, - strokeDasharray: '5 5', - strokeWidth: 1, - label: '是', - }, - }, - data: { - targetPortId: 'ec0aa7a6-40fe-4082-94e0-d98742ab062f', - sourcePortId: 'd007cc53-7925-4f82-87ac-673bd96404e2', - source: 'node-79008d10-2f11-459c-888b-032ae29b8952', - target: 'node-c657806d-dd71-4a89-b4ec-fc5fad51b843', - }, - stroke: '#50e3c2', - label: '是', - }, - { - id: '[object Object]:8a32bebe-4fe8-4482-a40d-0a1ae7537246-[object Object]:481bf3a3-e4fc-40fc-be77-766a8ecb9360', - targetPortId: '481bf3a3-e4fc-40fc-be77-766a8ecb9360', - sourcePortId: '8a32bebe-4fe8-4482-a40d-0a1ae7537246', - source: { cell: 'node-c657806d-dd71-4a89-b4ec-fc5fad51b843', port: '8a32bebe-4fe8-4482-a40d-0a1ae7537246' }, - target: { cell: 'node-f6ccc339-9a05-4bf1-ad25-bfb956ff9388', port: '481bf3a3-e4fc-40fc-be77-766a8ecb9360' }, - zIndex: 1, - attrs: { - line: { - stroke: '#bd10e0', - targetMarker: { name: 'block', width: 12, height: 8 }, - strokeDasharray: '5 5', - strokeWidth: 1, - }, - }, - data: { - targetPortId: '481bf3a3-e4fc-40fc-be77-766a8ecb9360', - sourcePortId: '8a32bebe-4fe8-4482-a40d-0a1ae7537246', - source: 'node-c657806d-dd71-4a89-b4ec-fc5fad51b843', - target: 'node-f6ccc339-9a05-4bf1-ad25-bfb956ff9388', - }, - stroke: '#bd10e0', - }, - { - id: '[object Object]:5a5874a5-39ba-432a-b595-ff043912c57f-[object Object]:281f8f42-b772-4507-a56b-718f8ecc2a9b', - targetPortId: '281f8f42-b772-4507-a56b-718f8ecc2a9b', - sourcePortId: '5a5874a5-39ba-432a-b595-ff043912c57f', - source: { cell: 'node-79008d10-2f11-459c-888b-032ae29b8952', port: '5a5874a5-39ba-432a-b595-ff043912c57f' }, - target: { cell: 'node-e07e6834-7d15-4b3c-9b40-8f234fca363e', port: '281f8f42-b772-4507-a56b-718f8ecc2a9b' }, - zIndex: 1, - attrs: { - line: { - stroke: '#f5a623', - targetMarker: { name: 'block', width: 12, height: 8 }, - strokeDasharray: '5 5', - strokeWidth: 1, - label: '否', - }, - }, - data: { - targetPortId: '281f8f42-b772-4507-a56b-718f8ecc2a9b', - sourcePortId: '5a5874a5-39ba-432a-b595-ff043912c57f', - source: 'node-79008d10-2f11-459c-888b-032ae29b8952', - target: 'node-e07e6834-7d15-4b3c-9b40-8f234fca363e', - }, - label: '否', - stroke: '#f5a623', - }, - { - id: '[object Object]:29465bdc-72ef-445d-9dc3-ac833c855658-[object Object]:6ef1c94f-4083-4bd0-b35a-f68c6744f374', - targetPortId: '6ef1c94f-4083-4bd0-b35a-f68c6744f374', - sourcePortId: '29465bdc-72ef-445d-9dc3-ac833c855658', - source: { cell: 'node-e07e6834-7d15-4b3c-9b40-8f234fca363e', port: '29465bdc-72ef-445d-9dc3-ac833c855658' }, - target: { cell: 'node-3f701e75-5116-4a62-8717-1fe8f71c920a', port: '6ef1c94f-4083-4bd0-b35a-f68c6744f374' }, - zIndex: 1, - attrs: { - line: { - stroke: '#A2B1C3', - targetMarker: { name: 'block', width: 12, height: 8 }, - strokeDasharray: '5 5', - strokeWidth: 1, - }, - }, - data: { - targetPortId: '6ef1c94f-4083-4bd0-b35a-f68c6744f374', - sourcePortId: '29465bdc-72ef-445d-9dc3-ac833c855658', - source: 'node-e07e6834-7d15-4b3c-9b40-8f234fca363e', - target: 'node-3f701e75-5116-4a62-8717-1fe8f71c920a', - }, - }, - { - id: '[object Object]:e2e4400d-b102-4685-a288-df626d54efa3-[object Object]:ffc158ad-8ad5-4e33-a68a-50e2f1eb4794', - targetPortId: 'ffc158ad-8ad5-4e33-a68a-50e2f1eb4794', - sourcePortId: 'e2e4400d-b102-4685-a288-df626d54efa3', - source: { cell: 'node-3f701e75-5116-4a62-8717-1fe8f71c920a', port: 'e2e4400d-b102-4685-a288-df626d54efa3' }, - target: { cell: 'node-f6ccc339-9a05-4bf1-ad25-bfb956ff9388', port: 'ffc158ad-8ad5-4e33-a68a-50e2f1eb4794' }, - zIndex: 1, - attrs: { - line: { - stroke: '#A2B1C3', - targetMarker: { name: 'block', width: 12, height: 8 }, - strokeDasharray: '5 5', - strokeWidth: 1, - }, - }, - data: { - targetPortId: 'ffc158ad-8ad5-4e33-a68a-50e2f1eb4794', - sourcePortId: 'e2e4400d-b102-4685-a288-df626d54efa3', - source: 'node-3f701e75-5116-4a62-8717-1fe8f71c920a', - target: 'node-f6ccc339-9a05-4bf1-ad25-bfb956ff9388', - }, - }, - ], -}; - -const DemoFlowchart = () => { - return ( -
- -
- ); -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/flowchart/basic/index.en.md b/site/examples/flowchart/basic/index.en.md deleted file mode 100644 index 7a112c6b8..000000000 --- a/site/examples/flowchart/basic/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Flowchart -order: 0 ---- diff --git a/site/examples/flowchart/basic/index.zh.md b/site/examples/flowchart/basic/index.zh.md deleted file mode 100644 index ef985ca9d..000000000 --- a/site/examples/flowchart/basic/index.zh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 流程图 -order: 0 ---- - diff --git a/site/examples/map-advanced-plot/muti-layers/API.en.md b/site/examples/map-advanced-plot/muti-layers/API.en.md deleted file mode 100644 index 496de5e5e..000000000 --- a/site/examples/map-advanced-plot/muti-layers/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-advanced-plot/muti-layers/API.zh.md b/site/examples/map-advanced-plot/muti-layers/API.zh.md deleted file mode 100644 index 3d593c389..000000000 --- a/site/examples/map-advanced-plot/muti-layers/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-advanced-plot/muti-layers/demo/meta.json b/site/examples/map-advanced-plot/muti-layers/demo/meta.json deleted file mode 100644 index f02abb5bc..000000000 --- a/site/examples/map-advanced-plot/muti-layers/demo/meta.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "wind-field.js", - "title": { - "zh": "风场图", - "en": "Wind field map" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/wuc%26qDZ5Ov/4b425019-66be-4ccc-84b9-c314ed6b3313.png" - } - ] -} diff --git a/site/examples/map-advanced-plot/muti-layers/demo/wind-field.js b/site/examples/map-advanced-plot/muti-layers/demo/wind-field.js deleted file mode 100644 index 7937f0cc9..000000000 --- a/site/examples/map-advanced-plot/muti-layers/demo/wind-field.js +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { L7PlotMap } from '@ant-design/maps'; - -const DemoL7PlotMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/bmw-prod/7455fead-1dc0-458d-b91a-fb4cf99e701e.txt') - .then((response) => response.text()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - if (!data.length) { - return null; - } - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [60, 40.7128], - zoom: 2, - }, - layers: [ - { - type: 'arcLayer', - source: { - data: data, - parser: { - type: 'csv', - x: 'lng1', - y: 'lat1', - x1: 'lng2', - y1: 'lat2', - }, - }, - shape: 'arc', - size: 0.5, - color: '#6495ED', - style: { - opacity: 0.8, - }, - animate: { - duration: 4, - interval: 0.2, - trailLength: 0.6, - }, - }, - ], - zoom: { - position: 'bottomright', - }, - scale: { - position: 'bottomright', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-advanced-plot/muti-layers/index.en.md b/site/examples/map-advanced-plot/muti-layers/index.en.md deleted file mode 100644 index 4e0cd4299..000000000 --- a/site/examples/map-advanced-plot/muti-layers/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Muti Layers -order: 0 ---- diff --git a/site/examples/map-advanced-plot/muti-layers/index.zh.md b/site/examples/map-advanced-plot/muti-layers/index.zh.md deleted file mode 100644 index ea3ae24b1..000000000 --- a/site/examples/map-advanced-plot/muti-layers/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 多图层 -order: 0 ---- diff --git a/site/examples/map-area/division/API.en.md b/site/examples/map-area/division/API.en.md deleted file mode 100644 index d7366a808..000000000 --- a/site/examples/map-area/division/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-area/division/API.zh.md b/site/examples/map-area/division/API.zh.md deleted file mode 100644 index a16845937..000000000 --- a/site/examples/map-area/division/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-area/division/demo/europe-pop-est.js b/site/examples/map-area/division/demo/europe-pop-est.js deleted file mode 100644 index 3d135ce42..000000000 --- a/site/examples/map-area/division/demo/europe-pop-est.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { AreaMap } from '@ant-design/maps'; - -const DemoAreaMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/EIXm%24lEPD%24/europe.geo.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'blank', - center: [120.19382669582967, 30.258134], - zoom: 3, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - autoFit: true, - color: { - field: 'pop_est', - value: ['rgb(239,243,255)', 'rgb(189,215,231)', 'rgb(107,174,214)', 'rgb(49,130,189)', 'rgb(8,81,156)'], - scale: { - type: 'quantile', - }, - }, - style: { - opacity: 1, - stroke: 'rgb(93,112,146)', - lineWidth: 0.6, - lineOpacity: 1, - }, - state: { - active: true, - }, - label: { - visible: true, - field: 'name', - style: { - fill: '#000', - opacity: 0.8, - fontSize: 10, - stroke: '#fff', - strokeWidth: 1.5, - textAllowOverlap: false, - padding: [5, 5], - }, - }, - tooltip: { - items: ['name', 'name_zh', 'pop_est'], - }, - zoom: { - position: 'bottomright', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-area/division/demo/meta.json b/site/examples/map-area/division/demo/meta.json deleted file mode 100644 index 6b334230b..000000000 --- a/site/examples/map-area/division/demo/meta.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "europe-pop-est.js", - "title": { - "zh": "2019 欧洲总人口数", - "en": "2019 Europe pop-est" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/lxHiE3f%24jB/2ab86960-79ee-477d-a656-8f86d71de072.png" - }, - { - "filename": "us-states-density.js", - "title": { - "zh": "美国人口密度", - "en": "US State density" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/IsrqSMt3SL/f9238dd6-fd88-4200-a624-3c27a8ae677e.png" - } - ] -} diff --git a/site/examples/map-area/division/demo/us-states-density.js b/site/examples/map-area/division/demo/us-states-density.js deleted file mode 100644 index d0c77dd16..000000000 --- a/site/examples/map-area/division/demo/us-states-density.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { AreaMap } from '@ant-design/maps'; - -const DemoAreaMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/d36ad90e-3902-4742-b8a2-d93f7e5dafa2.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'blank', - center: [120.19382669582967, 30.258134], - zoom: 3, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - autoFit: true, - color: { - field: 'density', - value: ['#fee5d9', '#fcae91', '#fb6a4a', '#de2d26', '#a50f15'], - scale: { - type: 'quantile', - }, - }, - style: { - opacity: 1, - stroke: '#fff', - lineWidth: 0.6, - lineOpacity: 1, - }, - state: { - active: true, - }, - label: { - visible: true, - field: 'name', - style: { - fill: '#000', - opacity: 0.8, - fontSize: 10, - stroke: '#fff', - strokeWidth: 1.5, - textAllowOverlap: false, - padding: [8, 8], - }, - }, - tooltip: { - items: ['name', 'density'], - }, - zoom: { - position: 'bottomright', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-area/division/index.en.md b/site/examples/map-area/division/index.en.md deleted file mode 100644 index e6e1db201..000000000 --- a/site/examples/map-area/division/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Division Fill -order: 0 ---- diff --git a/site/examples/map-area/division/index.zh.md b/site/examples/map-area/division/index.zh.md deleted file mode 100644 index 8c3eb8685..000000000 --- a/site/examples/map-area/division/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 区域填充 -order: 0 ---- diff --git a/site/examples/map-area/interactive/API.en.md b/site/examples/map-area/interactive/API.en.md deleted file mode 100644 index d7366a808..000000000 --- a/site/examples/map-area/interactive/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-area/interactive/API.zh.md b/site/examples/map-area/interactive/API.zh.md deleted file mode 100644 index a16845937..000000000 --- a/site/examples/map-area/interactive/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-area/interactive/demo/meta.json b/site/examples/map-area/interactive/demo/meta.json deleted file mode 100644 index d41bef6ee..000000000 --- a/site/examples/map-area/interactive/demo/meta.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "single-choice.js", - "title": { - "zh": "单选", - "en": "Single choice" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/Ths%26mCNgFa/2c4d5660-5946-49e8-8917-cdbeb37be073.png" - }, - { - "filename": "multiple-choice.js", - "title": { - "zh": "多选", - "en": "Multiple choice" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/wiunBM1p0N/4d58f32a-d2d3-40dc-824d-13e8207a4789.png" - } - ] -} diff --git a/site/examples/map-area/interactive/demo/multiple-choice.js b/site/examples/map-area/interactive/demo/multiple-choice.js deleted file mode 100644 index 4cce597b1..000000000 --- a/site/examples/map-area/interactive/demo/multiple-choice.js +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { AreaMap } from '@ant-design/maps'; - -const DemoAreaMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/1d27c363-af3a-469e-ab5b-7a7e1ce4f311.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'blank', - center: [120.19382669582967, 30.258134], - zoom: 3, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - autoFit: true, - color: { - field: 'unit_price', - value: [ - '#1A4397', - '#2555B7', - '#3165D1', - '#467BE8', - '#6296FE', - '#7EA6F9', - '#98B7F7', - '#BDD0F8', - '#DDE6F7', - '#F2F5FC', - ].reverse(), - scale: { - type: 'quantile', - }, - }, - style: { - opacity: 1, - stroke: '#fff', - lineWidth: 0.8, - lineOpacity: 1, - }, - state: { - active: true, - select: { - stroke: 'yellow', - lineWidth: 1.5, - lineOpacity: 0.8, - }, - }, - enabledMultiSelect: true, - label: { - visible: true, - field: 'name', - style: { - fill: 'black', - opacity: 0.5, - fontSize: 12, - spacing: 1, - padding: [15, 15], - }, - }, - tooltip: { - items: ['name', 'code'], - }, - zoom: { - position: 'bottomright', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-area/interactive/demo/single-choice.js b/site/examples/map-area/interactive/demo/single-choice.js deleted file mode 100644 index 066909eed..000000000 --- a/site/examples/map-area/interactive/demo/single-choice.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { AreaMap } from '@ant-design/maps'; - -const DemoAreaMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/d36ad90e-3902-4742-b8a2-d93f7e5dafa2.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const color = [ - 'rgb(255,255,217)', - 'rgb(237,248,177)', - 'rgb(199,233,180)', - 'rgb(127,205,187)', - 'rgb(65,182,196)', - 'rgb(29,145,192)', - 'rgb(34,94,168)', - 'rgb(12,44,132)', - ]; - const config = { - map: { - type: 'mapbox', - style: 'blank', - center: [120.19382669582967, 30.258134], - zoom: 3, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - autoFit: true, - color: { - field: 'density', - value: color, - scale: { - type: 'quantile', - }, - }, - style: { - opacity: 1, - stroke: 'rgb(93,112,146)', - lineType: 'dash', - lineDash: [2, 2], - lineWidth: 0.6, - lineOpacity: 1, - }, - state: { - active: true, - select: true, - }, - tooltip: { - items: ['name', 'density'], - }, - zoom: { - position: 'bottomright', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-area/interactive/index.en.md b/site/examples/map-area/interactive/index.en.md deleted file mode 100644 index 38e4bb6ba..000000000 --- a/site/examples/map-area/interactive/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Map Interaction -order: 1 ---- diff --git a/site/examples/map-area/interactive/index.zh.md b/site/examples/map-area/interactive/index.zh.md deleted file mode 100644 index 2bbf8302b..000000000 --- a/site/examples/map-area/interactive/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 区域交互 -order: 1 ---- diff --git a/site/examples/map-dot/bobble/API.en.md b/site/examples/map-dot/bobble/API.en.md deleted file mode 100644 index 6fceadad4..000000000 --- a/site/examples/map-dot/bobble/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/bobble/API.zh.md b/site/examples/map-dot/bobble/API.zh.md deleted file mode 100644 index 12e1658a9..000000000 --- a/site/examples/map-dot/bobble/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/bobble/demo/air-temperature.js b/site/examples/map-dot/bobble/demo/air-temperature.js deleted file mode 100644 index d62e3fea9..000000000 --- a/site/examples/map-dot/bobble/demo/air-temperature.js +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/rmsportal/oVTMqfzuuRFKiDwhPSFL.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [102.447303, 37.753574], - zoom: 5, - pitch: 0, - }, - source: { - data: data.list, - parser: { - type: 'json', - x: 'j', - y: 'w', - }, - }, - color: { - field: 't', - value: [ - '#03071e', - '#370617', - '#6a040f', - '#9d0208', - '#d00000', - '#dc2f02', - '#e85d04', - '#f48c06', - '#faa307', - '#ffba08', - ].reverse(), - scale: { - type: 'quantize', - }, - }, - size: { - field: 't', - value: [2, 18], - }, - style: { - opacity: 0.5, - strokeWidth: 0, - }, - state: { - active: { - color: '#FFF684', - }, - }, - autoFit: true, - label: { - visible: false, - // 是否显示标签图层 - field: 't', - style: { - fill: '#fff', - opacity: 0.6, - fontSize: 12, - textAnchor: 'top', - // 文本相对锚点的位置 center|left|right|top|bottom|top-left - textOffset: [0, 20], - // 文本相对锚点的偏移量 [水平, 垂直] - spacing: 1, - // 字符间距 - padding: [5, 5], - // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近 - stroke: '#ffffff', - // 描边颜色 - strokeWidth: 0.3, - // 描边宽度 - strokeOpacity: 1.0, - }, - }, - tooltip: { - items: ['s', 't'], - }, - zoom: { - position: 'bottomright', - }, - scale: { - position: 'bottomright', - }, - layerMenu: { - position: 'topright', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/bobble/demo/animate.js b/site/examples/map-dot/bobble/demo/animate.js deleted file mode 100644 index c19a66605..000000000 --- a/site/examples/map-dot/bobble/demo/animate.js +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/xZqmXatMnc/quanguojiaotongshijianxiangyingzhishu.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [102.447303, 37.753574], - zoom: 2, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - color: '#4cfd47', - size: 20, - animate: true, - state: { - active: true, - }, - autoFit: true, - label: { - field: 'cityName', - style: { - fill: '#fff', - fontSize: 12, - textAnchor: 'top', - textOffset: [0, 20], - }, - }, - zoom: { - position: 'bottomright', - }, - layerMenu: { - position: 'topright', - }, - tooltip: { - items: [ - { - field: 'cityName', - alias: '名称', - }, - ], - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/bobble/demo/distribution-cities.js b/site/examples/map-dot/bobble/demo/distribution-cities.js deleted file mode 100644 index 13d30ddc4..000000000 --- a/site/examples/map-dot/bobble/demo/distribution-cities.js +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [list, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/g5hIthhKlr/quanguoshixianweizhi.json') - .then((response) => response.json()) - .then(({ list }) => setData(list)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - - const config = { - map: { - type: 'mapbox', - style: 'dark', - zoom: 5, - center: [107.4976, 32.1697], - pitch: 45, - }, - source: { - data: list, - parser: { - type: 'json', - coordinates: 'lnglat', - }, - }, - color: '#47aff7', - size: { - field: 'style', - value: ({ style }) => { - if (style == 0) { - return 8; - } else if (style == 1) { - return 4; - } else { - return 2; - } - }, - }, - style: { - opacity: 0.8, - stroke: '#c3faff', - strokeWidth: 1, - }, - state: { - active: { - color: '#FFF684', - }, - }, - zoom: { - position: 'bottomright', - }, - tooltip: { - items: ['name'], - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/bobble/demo/earthquake-level.js b/site/examples/map-dot/bobble/demo/earthquake-level.js deleted file mode 100644 index 741db7a2b..000000000 --- a/site/examples/map-dot/bobble/demo/earthquake-level.js +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/m5r7MFHt8U/wenchuandizhenshuju.json') - .then((response) => response.json()) - .then(({ data }) => setData(data)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [103.447303, 31.753574], - zoom: 7, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'json', - x: 'lng', - y: 'lat', - }, - }, - color: { - field: 'mag', - value: ['#82cf9c', '#10b3b0', '#2033ab'], - scale: { - type: 'quantize', - }, - }, - size: { - field: 'mag', - value: ({ mag }) => (mag - 4.3) * 10, - }, - style: { - opacity: 0.8, - strokeWidth: 0, - }, - state: { - active: { - color: '#FFF684', - }, - }, - autoFit: true, - zoom: { - position: 'topright', - }, - scale: { - position: 'bottomright', - }, - tooltip: { - items: ['title', 'mag', 'depth'], - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/bobble/demo/meta.json b/site/examples/map-dot/bobble/demo/meta.json deleted file mode 100644 index 426d9194e..000000000 --- a/site/examples/map-dot/bobble/demo/meta.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "air-temperature.js", - "title": { - "zh": "国内气温气泡", - "en": "Air bubble map of China" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/gozHK9LNFl/d37bc490-722b-4b73-a627-3c261b0401d2.png" - }, - { - "filename": "distribution-cities.js", - "title": { - "zh": "全国城市与区县分布", - "en": "Distribution of cities, districts and counties in China" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/RGpOaNlkdd/1c099806-c02a-4b03-8dfe-788e982b822b.png" - }, - { - "filename": "earthquake-level.js", - "title": { - "zh": "地震等级", - "en": "Earthquake level" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/JVdKQHfpZC/d3287038-6329-4bdd-8471-c830d5daf52f.png" - }, - { - "filename": "animate.js", - "title": { - "zh": "全国交通事件气泡动画", - "en": "National traffic event bubble animation" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/dTdD4KQGvH/06e323e4-92ce-40d9-a58f-837adbce5f26.png" - } - ] -} diff --git a/site/examples/map-dot/bobble/index.en.md b/site/examples/map-dot/bobble/index.en.md deleted file mode 100644 index 35cbb4d43..000000000 --- a/site/examples/map-dot/bobble/index.en.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Bubble Map Map -order: 0 ---- - -Refers to a point layer that can be located on a map with a dot symbol of the same shape, size and a fixed ratio to the value it represents, and is used to express the distribution characteristics of discrete phenomena. diff --git a/site/examples/map-dot/bobble/index.zh.md b/site/examples/map-dot/bobble/index.zh.md deleted file mode 100644 index d4a102601..000000000 --- a/site/examples/map-dot/bobble/index.zh.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 地图气泡图 -order: 0 ---- - -指地图上可用一个形状相同、大小和与其代表的数值成固定比率的圆点符号来定位的点图层,用于表达离散现象分布特征的地图。 diff --git a/site/examples/map-dot/dot-density/API.en.md b/site/examples/map-dot/dot-density/API.en.md deleted file mode 100644 index ef451b413..000000000 --- a/site/examples/map-dot/dot-density/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/dot-density/API.zh.md b/site/examples/map-dot/dot-density/API.zh.md deleted file mode 100644 index 5fca5d49c..000000000 --- a/site/examples/map-dot/dot-density/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/dot-density/demo/beijing-traffic.js b/site/examples/map-dot/dot-density/demo/beijing-traffic.js deleted file mode 100644 index 5bb4e9955..000000000 --- a/site/examples/map-dot/dot-density/demo/beijing-traffic.js +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/8Ps2h%24qgmk/traffic_110000.csv') - .then((response) => response.text()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - if (!data.length) { - return null; - } - const colors = ['#c57f34', '#cbfddf', '#edea70', '#8cc9f1', '#2c7bb6']; - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [116.417463, 40.015175], - pitch: 0, - zoom: 9, - }, - source: { - data: data, - parser: { - type: 'csv', - y: 'lat', - x: 'lng', - }, - }, - shape: 'dot', - color: { - field: 'type', - value: ({ type }) => { - switch (parseInt(type)) { - case 3: - return colors[0]; - - case 4: - return colors[1]; - - case 41: - return colors[2]; - - case 5: - return colors[3]; - - default: - return colors[4]; - } - }, - }, - size: 0.5, - style: { - opacity: 1, - }, - autoFit: true, - zoom: { - position: 'bottomright', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/dot-density/demo/cuisine-nationwide.js b/site/examples/map-dot/dot-density/demo/cuisine-nationwide.js deleted file mode 100644 index c465f8e43..000000000 --- a/site/examples/map-dot/dot-density/demo/cuisine-nationwide.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/fZreT5RyVT/6wanquanguoyuecaidefenbu.geojson') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [105.425968, 35.882505], - pitch: 0, - zoom: 4, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - shape: 'dot', - color: '#3C1FA8', - size: 0.5, - style: { - opacity: 1, - }, - autoFit: true, - zoom: { - position: 'bottomright', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/dot-density/demo/meta.json b/site/examples/map-dot/dot-density/demo/meta.json deleted file mode 100644 index dc86e38ca..000000000 --- a/site/examples/map-dot/dot-density/demo/meta.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "cuisine-nationwide.js", - "title": { - "zh": "6万点位全国粤菜分布", - "en": "Distribution of 60000 Cantonese dishes in China" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/RO8qymvsqT/447d85e6-7b2f-4344-b3c5-265db3cf4633.png" - }, - { - "filename": "beijing-traffic.js", - "title": { - "zh": "10万辆北京公共交通车辆", - "en": "100000 Beijing public transport vehicles" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/zkZZmhfMei/ac9d0fb0-283a-46c1-9b6a-c9eeea288bc8.png" - }, - { - "filename": "shanghai-traffic.js", - "title": { - "zh": "164万辆上海市交通车辆", - "en": "1.64 million traffic vehicles in Shanghai" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/t5BQDH5Jp8/dbcddc7e-5ff9-4d3b-af9b-2d86f40b626a.png" - } - ] -} diff --git a/site/examples/map-dot/dot-density/demo/shanghai-traffic.js b/site/examples/map-dot/dot-density/demo/shanghai-traffic.js deleted file mode 100644 index 6f18946ca..000000000 --- a/site/examples/map-dot/dot-density/demo/shanghai-traffic.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/rmsportal/BElVQFEFvpAKzddxFZxJ.txt') - .then((response) => response.text()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - if (!data.length) { - return null; - } - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [121.417463, 31.215175], - pitch: 0, - zoom: 11, - }, - source: { - data: data, - parser: { - type: 'csv', - y: 'lat', - x: 'lng', - }, - }, - shape: 'dot', - color: '#080298', - size: 0.5, - style: { - opacity: 1, - }, - zoom: { - position: 'bottomright', - }, - scale: { - position: 'bottomleft', - }, - layerMenu: { - position: 'topright', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/dot-density/index.en.md b/site/examples/map-dot/dot-density/index.en.md deleted file mode 100644 index c2a0ace28..000000000 --- a/site/examples/map-dot/dot-density/index.en.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Dot Density -order: 1 ---- - -Also known as a Point Map, Dot Distribution Map. - -Dot Maps are a way of detecting spatial patterns or the distribution of data over a geographical region, by placing equally sized points over a geographical region. - -Dot Maps are ideal for seeing how things are distributed over a geographical region and can reveal patterns when the points cluster on the map. Dot Maps are easy to grasp and are better at giving an overview of the data, but are not great for retrieving exact values. diff --git a/site/examples/map-dot/dot-density/index.zh.md b/site/examples/map-dot/dot-density/index.zh.md deleted file mode 100644 index 9bda71001..000000000 --- a/site/examples/map-dot/dot-density/index.zh.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: 点密度图 -order: 1 ---- - -也称为「点分布图」或「点密度图」。 - -点密度地图在地理区域上放置相等大小的圆点,旨在检测该地域上的空间布局或数据分布。 - -点密度地图非常适合用来查看物件在某地域内的分布状况和模式,而且容易掌握,能提供数据概览,可是在检索精确数值方面表现则不太理想。 diff --git a/site/examples/map-dot/icon/API.en.md b/site/examples/map-dot/icon/API.en.md deleted file mode 100644 index 6fceadad4..000000000 --- a/site/examples/map-dot/icon/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/icon/API.zh.md b/site/examples/map-dot/icon/API.zh.md deleted file mode 100644 index 12e1658a9..000000000 --- a/site/examples/map-dot/icon/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/icon/demo/bank.js b/site/examples/map-dot/icon/demo/bank.js deleted file mode 100644 index 1132d3194..000000000 --- a/site/examples/map-dot/icon/demo/bank.js +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap, registerImages } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState({ - list: [], - }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/h%26vOn55UIF/yinhangwangdian.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const images = [ - { - id: '160104', - image: 'https://gw.alipayobjects.com/zos/antfincdn/tWx6gaMr9P/zhongguoyinhang.png', - }, - { - id: '160139', - image: 'https://gw.alipayobjects.com/zos/antfincdn/KDjael3M3h/youzhengyinhang.png', - }, - { - id: '160105', - image: 'https://gw.alipayobjects.com/zos/antfincdn/Cxwxb%265wn7/gongshangyinhang.png', - }, - { - id: '160106', - image: 'https://gw.alipayobjects.com/zos/basement_prod/7aa1f460-9f9f-499f-afdf-13424aa26bbf.svg', - }, - { - id: '160107', - image: 'https://gw.alipayobjects.com/zos/antfincdn/hITtoj%2672C/nongyeyinhang.png', - }, - { - id: '160108', - image: 'https://gw.alipayobjects.com/zos/antfincdn/KHWJyfcPJu/jiaotongyinhang.png', - }, - { - id: '160109', - image: 'https://gw.alipayobjects.com/zos/antfincdn/%247VfhYcrfu/zhaoshangyinhang.png', - }, - { - id: '160111', - image: 'https://gw.alipayobjects.com/zos/antfincdn/pgo8%261emOy/guangdayinhang.png', - }, - ]; - registerImages(images); - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [116.473168, 39.993015], - zoom: 15, - pitch: 0, - }, - source: { - data: data.list, - parser: { - type: 'json', - coordinates: 'location', - }, - }, - color: '#fff', - shape: { - field: 'typecode', - value: ({ typecode }) => typecode, - }, - size: 10, - tooltip: { - items: ['name', 'address', 'tel'], - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/icon/demo/meta.json b/site/examples/map-dot/icon/demo/meta.json deleted file mode 100644 index 7b3291579..000000000 --- a/site/examples/map-dot/icon/demo/meta.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "bank.js", - "title": { - "zh": "银行网点", - "en": "Bank outlets" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/zWvhGhbuTY/08a0857b-814b-4165-b787-b375d73a5b6c.png" - }, - { - "filename": "poi.js", - "title": { - "zh": "POI 图标", - "en": "POI Icon" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/cfT2Jb1Fi2/83261ea9-3ccc-4684-a47a-721343c0279b.png" - } - ] -} diff --git a/site/examples/map-dot/icon/demo/poi.js b/site/examples/map-dot/icon/demo/poi.js deleted file mode 100644 index e06bad1f0..000000000 --- a/site/examples/map-dot/icon/demo/poi.js +++ /dev/null @@ -1,62 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap, registerImages } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/893d1d5f-11d9-45f3-8322-ee9140d288ae.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const images = [ - { - id: '01', - image: 'https://gw.alipayobjects.com/zos/basement_prod/604b5e7f-309e-40db-b95b-4fac746c5153.svg', - }, - { - id: '02', - image: 'https://gw.alipayobjects.com/zos/basement_prod/30580bc9-506f-4438-8c1a-744e082054ec.svg', - }, - { - id: '03', - image: 'https://gw.alipayobjects.com/zos/basement_prod/7aa1f460-9f9f-499f-afdf-13424aa26bbf.svg', - }, - ]; - registerImages(images); - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [121.409765, 31.256735], - zoom: 14.5, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'json', - x: 'longitude', - y: 'latitude', - }, - }, - color: '#fff', - shape: { - field: 'name', - value: ['01', '02', '03'], - }, - size: 20, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/icon/index.en.md b/site/examples/map-dot/icon/index.en.md deleted file mode 100644 index 8613c85d9..000000000 --- a/site/examples/map-dot/icon/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Icon Map -order: 1 ---- diff --git a/site/examples/map-dot/icon/index.zh.md b/site/examples/map-dot/icon/index.zh.md deleted file mode 100644 index a5c35c5b8..000000000 --- a/site/examples/map-dot/icon/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 图标图 -order: 1 ---- diff --git a/site/examples/map-dot/map-scatter/API.en.md b/site/examples/map-dot/map-scatter/API.en.md deleted file mode 100644 index 6fceadad4..000000000 --- a/site/examples/map-dot/map-scatter/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/map-scatter/API.zh.md b/site/examples/map-dot/map-scatter/API.zh.md deleted file mode 100644 index 12e1658a9..000000000 --- a/site/examples/map-dot/map-scatter/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-dot/map-scatter/demo/classified-scatter.js b/site/examples/map-dot/map-scatter/demo/classified-scatter.js deleted file mode 100644 index f49407cc7..000000000 --- a/site/examples/map-dot/map-scatter/demo/classified-scatter.js +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/6c4bb5f2-850b-419d-afc4-e46032fc9f94.csv') - .then((response) => response.text()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - if (!data.length) { - return null; - } - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [-121.24357, 37.58264], - pitch: 0, - zoom: 6.45, - }, - source: { - data: data, - parser: { - type: 'csv', - x: 'Longitude', - y: 'Latitude', - }, - }, - color: { - field: 'Magnitude', - value: [ - '#0A3663', - '#1558AC', - '#3771D9', - '#4D89E5', - '#64A5D3', - '#72BED6', - '#83CED6', - '#A6E1E0', - '#B8EFE2', - '#D7F9F0', - ], - }, - size: 3, - style: { - opacity: 0.8, - strokeWidth: 0, - }, - state: { - active: { - color: '#FFF684', - }, - }, - label: { - visible: false, - field: 'Magnitude', - style: { - fill: '#fff', - fontSize: 12, - textAnchor: 'top', - textOffset: [0, 20], - padding: [10, 10], - }, - }, - tooltip: { - items: ['Magnitude'], - }, - zoom: { - position: 'bottomright', - }, - layerMenu: { - position: 'topright', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/map-scatter/demo/distribution-cities.js b/site/examples/map-dot/map-scatter/demo/distribution-cities.js deleted file mode 100644 index 7ebfd4bb4..000000000 --- a/site/examples/map-dot/map-scatter/demo/distribution-cities.js +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { DotMap } from '@ant-design/maps'; - -const DemoDotMap = () => { - const [list, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/g5hIthhKlr/quanguoshixianweizhi.json') - .then((response) => response.json()) - .then(({ list }) => setData(list)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'dark', - zoom: 5, - center: [107.4976, 32.1697], - pitch: 0, - }, - source: { - data: list, - parser: { - type: 'json', - coordinates: 'lnglat', - }, - }, - size: 4, - color: { - field: 'style', - value: ({ style }) => { - if (style == 0) { - return '#14B4C9'; - } else if (style == 1) { - return '#3771D9'; - } else { - return '#B8EFE2'; - } - }, - }, - style: { - opacity: 0.8, - strokeWidth: 0, - }, - state: { - active: { - color: '#FFF684', - }, - }, - label: { - visible: false, - field: 'name', - style: { - fill: '#fff', - fontSize: 12, - textAnchor: 'top', - textOffset: [0, 20], - padding: [10, 10], - }, - }, - tooltip: { - items: ['name'], - }, - zoom: { - position: 'bottomright', - }, - layerMenu: { - position: 'topright', - }, - legend: { - type: 'category', - position: 'bottomleft', - items: [ - { - color: '#14B4C9', - value: '地级市', - }, - { - color: '#3771D9', - value: '县城市', - }, - { - color: '#B8EFE2', - value: '区县', - }, - ], - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-dot/map-scatter/demo/meta.json b/site/examples/map-dot/map-scatter/demo/meta.json deleted file mode 100644 index 818abd4e4..000000000 --- a/site/examples/map-dot/map-scatter/demo/meta.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "distribution-cities.js", - "title": { - "zh": "全国城市与区县分布", - "en": "Distribution of cities, districts and counties in China" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/TKxay5%26lpP/c01e799b-85f2-4dd1-8126-4c5d6845c522.png" - }, - { - "filename": "classified-scatter.js", - "title": { - "zh": "分类散点", - "en": "Classified Scatter Map" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/u31zxgii2R/d3fe9e5f-feb6-4b32-8237-4d79ae99a906.png" - } - ] -} diff --git a/site/examples/map-dot/map-scatter/index.en.md b/site/examples/map-dot/map-scatter/index.en.md deleted file mode 100644 index 9e39a4e2f..000000000 --- a/site/examples/map-dot/map-scatter/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Scatter Map -order: 0 ---- diff --git a/site/examples/map-dot/map-scatter/index.zh.md b/site/examples/map-dot/map-scatter/index.zh.md deleted file mode 100644 index 6db174451..000000000 --- a/site/examples/map-dot/map-scatter/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 地图散点图 -order: 0 ---- diff --git a/site/examples/map-heat/grid/API.en.md b/site/examples/map-heat/grid/API.en.md deleted file mode 100644 index 3371990ad..000000000 --- a/site/examples/map-heat/grid/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-heat/grid/API.zh.md b/site/examples/map-heat/grid/API.zh.md deleted file mode 100644 index 347643127..000000000 --- a/site/examples/map-heat/grid/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-heat/grid/demo/grid2d.js b/site/examples/map-heat/grid/demo/grid2d.js deleted file mode 100644 index 5a1cd0167..000000000 --- a/site/examples/map-heat/grid/demo/grid2d.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { GridMap } from '@ant-design/maps'; - -const DemoGridMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/aBQAMIpvPL/qingdao_500m.csv') - .then((response) => response.text()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - if (!data.length) { - return null; - } - const config = { - map: { - type: 'mapbox', - style: 'dark', - pitch: 0, - zoom: 8.6, - center: [120.198254, 36.265551], - }, - source: { - data: data, - parser: { - type: 'csv', - x: 'lng', - y: 'lat', - }, - aggregation: { - radius: 1000, - field: 'count', - method: 'sum', - }, - }, - shape: 'square', - color: { - field: 'count', - value: ['#0868AC', '#43A2CA', '#43A2CA', '#7BCCC4', '#BAE4BC', '#F0F9E8', '#F0F9E8'], - }, - style: { - coverage: 0.9, - angle: 0, - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/grid/demo/grid3d.js b/site/examples/map-heat/grid/demo/grid3d.js deleted file mode 100644 index 18e56e84b..000000000 --- a/site/examples/map-heat/grid/demo/grid3d.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { GridMap } from '@ant-design/maps'; - -const DemoGridMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv') - .then((response) => response.text()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - if (!data.length) { - return null; - } - const config = { - map: { - type: 'mapbox', - style: 'dark', - pitch: 48, - center: [109.054293, 29.246265], - zoom: 6, - }, - source: { - data: data, - parser: { - type: 'csv', - x: 'lng', - y: 'lat', - }, - aggregation: { - radius: 20000, - field: 'v', - method: 'sum', - }, - }, - shape: 'squareColumn', - size: { - field: 'count', - value: ({ count }) => { - return count * 200; - }, - }, - color: { - field: 'count', - value: [ - '#8C1EB2', - '#8C1EB2', - '#DA05AA', - '#F0051A', - '#FF2A3C', - '#FF4818', - '#FF4818', - '#FF8B18', - '#F77B00', - '#ED9909', - '#ECC357', - '#EDE59C', - ].reverse(), - }, - style: { - coverage: 0.9, - angle: 0, - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/grid/demo/meta.json b/site/examples/map-heat/grid/demo/meta.json deleted file mode 100644 index f430df417..000000000 --- a/site/examples/map-heat/grid/demo/meta.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "grid2d.js", - "title": { - "zh": "网格热力 2D", - "en": "Grid thermal 2D" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/fKPFt8cFk4/a091a42a-2411-4011-9dc7-ef7d39197f8d.png" - }, - { - "filename": "grid3d.js", - "title": { - "zh": "网格热力 3D", - "en": "Grid thermal 3D" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/XPitK0Ftor/9dcf6d62-ffc1-4207-9bb6-1f52fbc93002.png" - } - ] -} diff --git a/site/examples/map-heat/grid/index.en.md b/site/examples/map-heat/grid/index.en.md deleted file mode 100644 index bef82c2c6..000000000 --- a/site/examples/map-heat/grid/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Grid -order: 1 ---- diff --git a/site/examples/map-heat/grid/index.zh.md b/site/examples/map-heat/grid/index.zh.md deleted file mode 100644 index 88976ffe1..000000000 --- a/site/examples/map-heat/grid/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 网格聚合图 -order: 1 ---- diff --git a/site/examples/map-heat/heatmap/API.en.md b/site/examples/map-heat/heatmap/API.en.md deleted file mode 100644 index d05edf7cd..000000000 --- a/site/examples/map-heat/heatmap/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-heat/heatmap/API.zh.md b/site/examples/map-heat/heatmap/API.zh.md deleted file mode 100644 index 7680e5da4..000000000 --- a/site/examples/map-heat/heatmap/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-heat/heatmap/demo/global-2d.js b/site/examples/map-heat/heatmap/demo/global-2d.js deleted file mode 100644 index 0dec4db83..000000000 --- a/site/examples/map-heat/heatmap/demo/global-2d.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { HeatMap } from '@ant-design/maps'; - -const DemoHeatMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [127.5671666579043, 7.445038892195569], - zoom: 2.632456779444394, - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - size: { - field: 'mag', - }, - style: { - intensity: 3, - radius: 20, - opacity: 1, - colorsRamp: [ - { - color: '#206C7C', - position: 0, - }, - { - color: '#2EA9A1 ', - position: 0.2, - }, - { - color: '#91EABC', - position: 0.4, - }, - { - color: '#FFF598', - position: 0.6, - }, - { - color: '#F7B74A', - position: 0.8, - }, - { - color: '#FF4818', - position: 1, - }, - ], - }, - zoom: { - position: 'bottomright', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/heatmap/demo/global-3d.js b/site/examples/map-heat/heatmap/demo/global-3d.js deleted file mode 100644 index 113bc0b20..000000000 --- a/site/examples/map-heat/heatmap/demo/global-3d.js +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { HeatMap } from '@ant-design/maps'; - -const DemoHeatMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [127.5671666579043, 7.445038892195569], - zoom: 2.632456779444394, - pitch: 45, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - shape: 'heatmap3D', - size: { - field: 'mag', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/heatmap/demo/housing-transaction.js b/site/examples/map-heat/heatmap/demo/housing-transaction.js deleted file mode 100644 index f106aee15..000000000 --- a/site/examples/map-heat/heatmap/demo/housing-transaction.js +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { HeatMap } from '@ant-design/maps'; - -const DemoHeatMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/S2Pb%26549sG/20210723023614.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'dark', - zoom: 11.7, - center: [120.19660949707033, 30.234747338474293], - pitch: 0, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - size: { - field: 'count', - value: [0, 1], - }, - style: { - intensity: 2, - radius: 15, - opacity: 1, - colorsRamp: [ - { - color: 'rgba(33,102,172,0.0)', - position: 0, - }, - { - color: 'rgb(103,169,207)', - position: 0.2, - }, - { - color: 'rgb(209,229,240)', - position: 0.4, - }, - { - color: 'rgb(253,219,199)', - position: 0.6, - }, - { - color: 'rgb(239,138,98)', - position: 0.8, - }, - { - color: 'rgb(178,24,43,1.0)', - position: 1, - }, - ], - }, - zoom: { - position: 'bottomright', - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/heatmap/demo/meta.json b/site/examples/map-heat/heatmap/demo/meta.json deleted file mode 100644 index 28f12b7b6..000000000 --- a/site/examples/map-heat/heatmap/demo/meta.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "housing-transaction.js", - "title": { - "zh": "杭州房屋交易量", - "en": "Hangzhou housing transaction volume" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/qgKi3OxQVE/4553729d-c2ce-490b-aebb-ea948bef7f2e.png" - }, - { - "filename": "traffic-accident.js", - "title": { - "zh": "全国交通事故增长率", - "en": "National traffic accident growth rate" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/HRChB6ZKXz/343063e1-afbc-4fb1-a5d7-0383be3d28b3.png" - }, - { - "filename": "global-2d.js", - "title": { - "zh": "全球热力", - "en": "Global heat map" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/svqQmzIKVw/2977de7d-6362-42f1-b731-05e734b6bfe0.png" - }, - { - "filename": "global-3d.js", - "title": { - "zh": "全球热力 3D", - "en": "Global 3D heat map" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/5osUdJv%24Nq/51c6693d-e00a-4c32-adb2-90b95359d4f2.png" - } - ] -} diff --git a/site/examples/map-heat/heatmap/demo/traffic-accident.js b/site/examples/map-heat/heatmap/demo/traffic-accident.js deleted file mode 100644 index 7e8283166..000000000 --- a/site/examples/map-heat/heatmap/demo/traffic-accident.js +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { HeatMap } from '@ant-design/maps'; - -const DemoHeatMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/OOSGL1vhp3/20200726024229.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'dark', - center: [127.5671666579043, 7.445038892195569], - zoom: 2.632456779444394, - pitch: 45, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - }, - shape: 'heatmap3D', - size: { - field: 'avg', - value: ({ avg }) => avg / 100, - }, - legend: { - position: 'bottomleft', - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/heatmap/index.en.md b/site/examples/map-heat/heatmap/index.en.md deleted file mode 100644 index 02e7f2b1a..000000000 --- a/site/examples/map-heat/heatmap/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Heatmap -order: 0 ---- diff --git a/site/examples/map-heat/heatmap/index.zh.md b/site/examples/map-heat/heatmap/index.zh.md deleted file mode 100644 index a7e81e475..000000000 --- a/site/examples/map-heat/heatmap/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 点热力图 -order: 0 ---- diff --git a/site/examples/map-heat/hexbin/API.en.md b/site/examples/map-heat/hexbin/API.en.md deleted file mode 100644 index 7c6a2093d..000000000 --- a/site/examples/map-heat/hexbin/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-heat/hexbin/API.zh.md b/site/examples/map-heat/hexbin/API.zh.md deleted file mode 100644 index ee399ba5f..000000000 --- a/site/examples/map-heat/hexbin/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/map-heat/hexbin/demo/hexbin3d.js b/site/examples/map-heat/hexbin/demo/hexbin3d.js deleted file mode 100644 index fec0ebd4c..000000000 --- a/site/examples/map-heat/hexbin/demo/hexbin3d.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { HexbinMap } from '@ant-design/maps'; - -const DemoHexbinMap = () => { - const [data, setData] = useState([]); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/basement_prod/a1a8158d-6fe3-424b-8e50-694ccf61c4d7.csv') - .then((response) => response.text()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - if (!data.length) { - return null; - } - const config = { - map: { - type: 'mapbox', - style: 'dark', - pitch: 43, - center: [120.13383079335335, 29.9], - zoom: 8.2, - }, - source: { - data: data, - parser: { - type: 'csv', - x: 'lng', - y: 'lat', - }, - aggregation: { - field: 'v', - radius: 2500, - method: 'sum', - }, - }, - shape: 'hexagonColumn', - size: { - field: 'sum', - value: ({ sum }) => { - return sum * 200; - }, - }, - color: { - field: 'sum', - value: [ - '#094D4A', - '#146968', - '#1D7F7E', - '#289899', - '#34B6B7', - '#4AC5AF', - '#5FD3A6', - '#7BE39E', - '#A1EDB8', - '#C3F9CC', - '#DEFAC0', - '#ECFFB1', - ], - }, - style: { - coverage: 0.8, - angle: 0, - opacity: 1.0, - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/hexbin/demo/meta.json b/site/examples/map-heat/hexbin/demo/meta.json deleted file mode 100644 index 12173939e..000000000 --- a/site/examples/map-heat/hexbin/demo/meta.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": { - "zh": "分类", - "en": "Category" - }, - "demos": [ - { - "filename": "traffic-delay.js", - "title": { - "zh": "杭州交通高峰期路口延误指数", - "en": "Intersection delay index in traffic peak in Hangzhou" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/7W1VDrMpy%24/63f536b0-244a-41e9-9698-c3249a2108a7.png" - }, - { - "filename": "hexbin3d.js", - "title": { - "zh": "蜂窝热力 3D", - "en": "Hexbin thermal 3D" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/fA50QqeP%24T/a5031ad0-7786-4183-bf23-e66dbdf52fe5.png" - } - ] -} diff --git a/site/examples/map-heat/hexbin/demo/traffic-delay.js b/site/examples/map-heat/hexbin/demo/traffic-delay.js deleted file mode 100644 index 34c46dcb1..000000000 --- a/site/examples/map-heat/hexbin/demo/traffic-delay.js +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import ReactDOM from 'react-dom'; -import { HexbinMap } from '@ant-design/maps'; - -const DemoHexbinMap = () => { - const [data, setData] = useState({ type: 'FeatureCollection', features: [] }); - - useEffect(() => { - asyncFetch(); - }, []); - - const asyncFetch = () => { - fetch('https://gw.alipayobjects.com/os/antfincdn/Ml2DwikvFC/20210726100731.json') - .then((response) => response.json()) - .then((json) => setData(json)) - .catch((error) => { - console.log('fetch data failed', error); - }); - }; - const config = { - map: { - type: 'mapbox', - style: 'dark', - pitch: 43, - center: [120.13383079335335, 29.9], - zoom: 8.2, - }, - source: { - data: data, - parser: { - type: 'geojson', - }, - aggregation: { - radius: 1200, - field: 'rank', - method: 'sum', - }, - }, - shape: 'hexagonColumn', - size: { - field: 'sum', - value: ({ sum }) => { - return sum * 10; - }, - }, - color: { - field: 'sum', - value: ['#0553A1', '#0B79B0', '#10B3B0', '#7CCF98', '#DCE872'], - }, - style: { - coverage: 0.8, - angle: 0, - opacity: 1.0, - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/map-heat/hexbin/index.en.md b/site/examples/map-heat/hexbin/index.en.md deleted file mode 100644 index 2e8409c66..000000000 --- a/site/examples/map-heat/hexbin/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Hexbin -order: 2 ---- diff --git a/site/examples/map-heat/hexbin/index.zh.md b/site/examples/map-heat/hexbin/index.zh.md deleted file mode 100644 index 14347e1a4..000000000 --- a/site/examples/map-heat/hexbin/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 蜂窝聚合图 -order: 2 ---- diff --git a/site/examples/relation-graph/decomposition-tree-graph/API.en.md b/site/examples/relation-graph/decomposition-tree-graph/API.en.md deleted file mode 100644 index c9663778f..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/decomposition-tree-graph/API.zh.md b/site/examples/relation-graph/decomposition-tree-graph/API.zh.md deleted file mode 100644 index 77765267c..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/async-load.js b/site/examples/relation-graph/decomposition-tree-graph/demo/async-load.js deleted file mode 100644 index accb89b30..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/async-load.js +++ /dev/null @@ -1,147 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - const fetchData = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve( - [1, 2].map(() => ({ - id: 'A2' + Math.random().toString(), - value: { - title: '异步节点' + Math.random().toString(), - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '50%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - })), - ); - }, 1000); - }); - }; - - const getChildren = async () => { - const asyncData = await fetchData(); - return asyncData; - }; - - const config = { - data, - autoFit: false, - nodeCfg: { - getChildren, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: true, - collapsed: !children?.length, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/auto-width.js b/site/examples/relation-graph/decomposition-tree-graph/demo/auto-width.js deleted file mode 100644 index 4aeb67108..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/auto-width.js +++ /dev/null @@ -1,117 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比-非常非常非常非常非常长', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - const config = { - data, - autoFit: false, - nodeCfg: { - autoWidth: true, - items: { - layout: 'follow', - }, - }, - layout: { - getWidth: () => { - return 60; - }, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/basic.js b/site/examples/relation-graph/decomposition-tree-graph/demo/basic.js deleted file mode 100644 index 163ca8438..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/basic.js +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - const config = { - data, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: children?.length, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/layout.js b/site/examples/relation-graph/decomposition-tree-graph/demo/layout.js deleted file mode 100644 index 16cd4d041..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/layout.js +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - const config = { - data, - layout: { - type: 'indented', - direction: 'LR', - dropCap: false, - indent: 250, - getHeight: () => { - return 60; - }, - getWidth: () => { - return 100; - }, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: children?.length, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/line-style.js b/site/examples/relation-graph/decomposition-tree-graph/demo/line-style.js deleted file mode 100644 index 7bc3df5b1..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/line-style.js +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - const config = { - data, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - nodeCfg: { - items: { - style: (cfg, group, type) => { - const styles = { - value: { - fill: '#52c41a', - }, - text: { - fill: '#aaa', - }, - icon: { - width: 10, - height: 10, - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - style: { - stroke: '#40a9ff', - }, - }, - edgeCfg: { - endArrow: { - fill: '#40a9ff', - }, - style: (item, graph) => { - /** - * graph.findById(item.target).getModel() - * item.source: 获取 source 数据 - * item.target: 获取 target 数据 - */ - // console.log(graph.findById(item.source).getModel()); - return { - stroke: '#40a9ff', - lineWidth: 1, - strokeOpacity: 0.5, - }; - }, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: children?.length, - }; - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/meta.json b/site/examples/relation-graph/decomposition-tree-graph/demo/meta.json deleted file mode 100644 index e2c5c6161..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/meta.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "指标拆解图", - "en": "Basic" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/cOaXmA8nSM/ea4bd786-2442-41a0-acb6-fa1d9dfb20f2.png" - }, - { - "filename": "layout.js", - "title": { - "zh": "指标拆解图-调整布局", - "en": "Layout" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/vcSjpldUrc/569cf7e4-0b01-41d7-b71f-fdb71645c520.png" - }, - { - "filename": "percent.js", - "title": { - "zh": "指标拆解图-节点占比", - "en": "Percent" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/9UCy2n8WPu/fe8e1b07-efde-4ba8-9e83-97986f668faf.png" - }, - { - "filename": "style.js", - "title": { - "zh": "指标拆解图-节点样式", - "en": "Node style" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/IJhgY4ZtHf/4ca28c72-d60c-42ca-b562-1cec9489f409.png" - }, - { - "filename": "line-style.js", - "title": { - "zh": "指标拆解图-边样式", - "en": "Line style" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/sizWh4nARJ/615012d8-825d-462c-a20f-d1e51a2aed15.png" - }, - { - "filename": "no-stroke.js", - "title": { - "zh": "指标拆解图-无边框", - "en": "No stroke" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/eVNaLR7coY/54584a5f-3233-4881-8006-aa7b71678994.png" - }, - { - "filename": "async-load.js", - "title": { - "zh": "指标拆解图-异步请求", - "en": "Asynchronous request" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/oTq0fFxMRL/993abc47-28f7-4489-b3d8-3a04f9fbb10b.png" - }, - { - "filename": "auto-width.js", - "title": { - "zh": "指标拆解图-自适应", - "en": "Auto width" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/d21RW%24hqns/e390cf8f-375f-48da-8eee-b01d7c3184a9.png" - } - ] -} diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/no-stroke.js b/site/examples/relation-graph/decomposition-tree-graph/demo/no-stroke.js deleted file mode 100644 index 3acace017..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/no-stroke.js +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - const config = { - data, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - nodeCfg: { - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - }, - }, - items: { - containerStyle: { - fill: '#fff', - }, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 10, - height: 10, - }, - value: { - fill: '#52c41a', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - style: { - stroke: 'transparent', - }, - nodeStateStyles: false, - }, - edgeCfg: { - endArrow: { - show: false, - }, - style: (item, graph) => { - /** - * graph.findById(item.target).getModel() - * item.source: 获取 source 数据 - * item.target: 获取 target 数据 - */ - // console.log(graph.findById(item.source).getModel()); - return { - stroke: '#40a9ff', - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: false, - }, - }; - // @ts-ignore - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/percent.js b/site/examples/relation-graph/decomposition-tree-graph/demo/percent.js deleted file mode 100644 index f2b6fc06a..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/percent.js +++ /dev/null @@ -1,199 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '全年降本增收', - items: [ - { - text: '3031万', - value: '80%', - }, - ], - percent: 0.8, - }, - children: [ - { - id: 'A1', - value: { - title: '降本增收项目1', - percent: 0.7, - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '70%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '降本增收项目1-1', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '降本增收项目1-2', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '降本增收项目1-3', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '降本增收项目2', - percent: 0.3, - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - const stroke = '#EA2F97'; - const config = { - data, - nodeCfg: { - size: [140, 25], - percent: { - position: 'bottom', - size: 4, - style: (arg) => { - return { - radius: [0, 0, 0, 2], - fill: arg.value.percent > 0.3 ? stroke : '#1f8fff', - }; - }, - }, - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: (arg) => { - return { - fill: '#fff', - radius: 2, - stroke: arg.value.percent > 0.3 ? stroke : '#1f8fff', - }; - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - return { - stroke: '#518AD3', - strokeOpacity: 0.5, - }; - }, - endArrow: { - fill: '#518AD3', - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - return { - position: 'right', - show: cfg.children?.length, - style: (arg) => { - return { - stroke: arg.value.percent > 0.3 ? stroke : '#1f8fff', - }; - }, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/demo/style.js b/site/examples/relation-graph/decomposition-tree-graph/demo/style.js deleted file mode 100644 index 5d8f2b451..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/demo/style.js +++ /dev/null @@ -1,150 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { DecompositionTreeGraph } from '@ant-design/graphs'; - -const DemoDecompositionTreeGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '1152万', - }, - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '595万', - }, - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - ], - }; - - const config = { - data, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - nodeCfg: { - title: { - style: (cfg) => { - return { - fill: cfg?.value?.title === '青年' ? 'yellow' : '#fff', - }; - }, - }, - items: { - containerStyle: { - fill: '#fff', - }, - style: (cfg, group, type) => { - const styles = { - value: { - fill: '#52c41a', - }, - text: { - fill: '#aaa', - }, - icon: { - width: 10, - height: 10, - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - style: { - radius: [2, 2, 2, 2], - }, - }, - markerCfg: (cfg) => { - const { children } = cfg; - return { - show: children?.length, - }; - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/decomposition-tree-graph/index.en.md b/site/examples/relation-graph/decomposition-tree-graph/index.en.md deleted file mode 100644 index 04953fe18..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Decomposition Tree Graph -order: 0 ---- diff --git a/site/examples/relation-graph/decomposition-tree-graph/index.zh.md b/site/examples/relation-graph/decomposition-tree-graph/index.zh.md deleted file mode 100644 index 1f4ea0914..000000000 --- a/site/examples/relation-graph/decomposition-tree-graph/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 指标拆解图 -order: 0 ---- diff --git a/site/examples/relation-graph/flow-analysis-graph/API.en.md b/site/examples/relation-graph/flow-analysis-graph/API.en.md deleted file mode 100644 index 594bd1869..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/flow-analysis-graph/API.zh.md b/site/examples/relation-graph/flow-analysis-graph/API.zh.md deleted file mode 100644 index 594bd1869..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/flow-analysis-graph/demo/basic.js b/site/examples/relation-graph/flow-analysis-graph/demo/basic.js deleted file mode 100644 index e6807b757..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/demo/basic.js +++ /dev/null @@ -1,288 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { FlowAnalysisGraph } from '@ant-design/graphs'; - -const DemoFlowAnalysisGraph = () => { - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], - }; - const config = { - data, - nodeCfg: { - size: [140, 25], - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/flow-analysis-graph/demo/custom.js b/site/examples/relation-graph/flow-analysis-graph/demo/custom.js deleted file mode 100644 index 001681738..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/demo/custom.js +++ /dev/null @@ -1,221 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { FlowAnalysisGraph } from '@ant-design/graphs'; - -const DemoFlowAnalysisGraph = () => { - const data = { - nodes: [ - { - id: '0', - value: { - title: 'spmd1', - items: [ - { - text: '曝光UV', - value: '1000万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - trend: '45.9%', - }, - { - text: '点击UV', - value: '10万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - trend: '1.9%', - }, - ], - }, - }, - { - id: '1', - value: { - title: '开通营销页1', - items: [ - { - text: '访问UV', - value: '1000万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - trend: '45.9%', - }, - ], - }, - }, - { - id: '2', - value: { - title: '开通营销页2', - items: [ - { - text: '访问UV', - value: '1000万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - trend: '45.9%', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面1', - items: [ - { - text: '访问UV', - value: '1000万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - trend: '45.9%', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面2', - items: [ - { - text: '访问UV', - value: '1000万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - trend: '45.9%', - }, - ], - }, - }, - ], - edges: [ - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '1', - target: '3', - }, - { - source: '2', - target: '4', - }, - ], - }; - - const config = { - data, - nodeCfg: { - size: [180, 30], - items: { - padding: 6, - containerStyle: { - fill: '#fff', - }, - }, - customContent: (item, group, cfg) => { - const { startX, startY, width } = cfg; - const { text, value, icon, trend } = item; - text && - group?.addShape('text', { - attrs: { - textBaseline: 'top', - x: startX, - y: startY, - text, - fill: '#aaa', - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - value && - group?.addShape('text', { - attrs: { - textBaseline: 'top', - x: startX + 60, - y: startY, - text: value, - fill: '#000', - }, - name: `value-${Math.random()}`, - }); - icon && - group?.addShape('image', { - attrs: { - x: startX + 100, - y: startY, - width: 8, - height: 10, - img: icon, - }, - name: `image-${Math.random()}`, - }); - trend && - group?.addShape('text', { - attrs: { - textBaseline: 'top', - x: startX + 110, - y: startY, - text: trend, - fill: '#f00', - }, - name: `value-${Math.random()}`, - }); - - // 行高 - return 14; - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 0.5, - }, - }, - edgeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - layout: { - ranksepFunc: () => 30, - nodesepFunc: () => 30, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/flow-analysis-graph/demo/layout.js b/site/examples/relation-graph/flow-analysis-graph/demo/layout.js deleted file mode 100644 index 55f9af1fc..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/demo/layout.js +++ /dev/null @@ -1,234 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { FlowAnalysisGraph } from '@ant-design/graphs'; - -const DemoFlowAnalysisGraph = () => { - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], - }; - const config = { - data, - layout: { - rankdir: 'TB', - ranksepFunc: () => 20, - }, - nodeCfg: { - anchorPoints: [ - [0.5, 0], - [0.5, 1], - ], - }, - edgeCfg: { - type: 'polyline', - endArrow: true, - }, - markerCfg: (cfg) => { - return { - position: 'bottom', - show: data.edges.filter((item) => item.source === cfg.id)?.length, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/flow-analysis-graph/demo/line-style.js b/site/examples/relation-graph/flow-analysis-graph/demo/line-style.js deleted file mode 100644 index e6807b757..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/demo/line-style.js +++ /dev/null @@ -1,288 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { FlowAnalysisGraph } from '@ant-design/graphs'; - -const DemoFlowAnalysisGraph = () => { - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], - }; - const config = { - data, - nodeCfg: { - size: [140, 25], - items: { - containerStyle: { - fill: '#fff', - }, - padding: 6, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: Math.random() * 10 + 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/flow-analysis-graph/demo/meta.json b/site/examples/relation-graph/flow-analysis-graph/demo/meta.json deleted file mode 100644 index 9c76b182e..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/demo/meta.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "来源去向图", - "en": "Basic" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/SM9hvYbqYB/1caef0d3-78cf-4f7d-aaca-59a361cae2ec.png" - }, - { - "filename": "layout.js", - "title": { - "zh": "来源去向图-调整布局", - "en": "Layout" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/%26%26CzCKJS%26v/3dabb996-b6eb-4be9-b5ce-71f58bb7a238.png" - }, - { - "filename": "line-style.js", - "title": { - "zh": "来源去向图-边样式", - "en": "Line style" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/RBLyS%24za4m/ab3d4511-87d4-4d96-989b-97704c283556.png" - }, - { - "filename": "state.js", - "title": { - "zh": "来源去向图-状态", - "en": "State" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/vaMmDDzbmV/fc466a8f-0526-4226-af7d-a4445b00df26.png" - }, - { - "filename": "type.js", - "title": { - "zh": "来源去向图-节点类型", - "en": "Node type" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/FZ8uhqbOHj/4b5198e2-5466-46e9-8869-fca9015ccbf6.png" - }, - { - "filename": "custom.js", - "title": { - "zh": "来源去向图-自定义节点", - "en": "Custom node" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/x%24v02l2Gk1/b41b1807-2dba-4f4c-af46-192930eb7501.png" - } - ] -} diff --git a/site/examples/relation-graph/flow-analysis-graph/demo/state.js b/site/examples/relation-graph/flow-analysis-graph/demo/state.js deleted file mode 100644 index e59b8f0e3..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/demo/state.js +++ /dev/null @@ -1,299 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { FlowAnalysisGraph } from '@ant-design/graphs'; - -const DemoFlowAnalysisGraph = () => { - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], - }; - const config = { - data, - nodeCfg: { - size: [140, 25], - badge: { - style: (cfg) => { - const ids = ['-3', '-2', '-1']; - const fill = ids.includes(cfg.id) ? '#c86bdd' : '#5ae859'; - return { - fill, - radius: [2, 0, 0, 2], - }; - }, - }, - items: { - padding: 6, - containerStyle: { - fill: '#fff', - }, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - lineWidth: 2, - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/flow-analysis-graph/demo/type.js b/site/examples/relation-graph/flow-analysis-graph/demo/type.js deleted file mode 100644 index e59b8f0e3..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/demo/type.js +++ /dev/null @@ -1,299 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { FlowAnalysisGraph } from '@ant-design/graphs'; - -const DemoFlowAnalysisGraph = () => { - const data = { - nodes: [ - { - id: '-3', - value: { - title: '来源页面A', - items: [ - { - text: '曝光PV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-2', - value: { - title: '来源页面B', - items: [ - { - text: '点击UV', - value: '10.30万', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - }, - { - id: '-1', - value: { - title: '来源页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '0', - value: { - title: '活动页面', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '1', - value: { - title: '去向页面A', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '2', - value: { - title: '去向页面B', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '3', - value: { - title: '去向页面C', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '4', - value: { - title: '去向页面D', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '5', - value: { - title: '去向页面E', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '6', - value: { - title: '去向页面F', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '7', - value: { - title: '去向页面G', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - { - id: '8', - value: { - title: '去向页面H', - items: [ - { - text: '访问页面UV', - }, - ], - }, - }, - ], - edges: [ - { - source: '-3', - target: '0', - value: '来源A', - }, - { - source: '-2', - target: '0', - value: '来源B', - }, - { - source: '-1', - target: '0', - value: '来源C', - }, - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '2', - target: '6', - }, - { - source: '3', - target: '7', - }, - { - source: '4', - target: '8', - }, - ], - }; - const config = { - data, - nodeCfg: { - size: [140, 25], - badge: { - style: (cfg) => { - const ids = ['-3', '-2', '-1']; - const fill = ids.includes(cfg.id) ? '#c86bdd' : '#5ae859'; - return { - fill, - radius: [2, 0, 0, 2], - }; - }, - }, - items: { - padding: 6, - containerStyle: { - fill: '#fff', - }, - style: (cfg, group, type) => { - const styles = { - icon: { - width: 12, - height: 12, - }, - value: { - fill: '#f00', - }, - text: { - fill: '#aaa', - }, - }; - return styles[type]; - }, - }, - nodeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - }, - }, - title: { - containerStyle: { - fill: 'transparent', - }, - style: { - fill: '#000', - fontSize: 12, - }, - }, - style: { - fill: '#E6EAF1', - stroke: '#B2BED5', - radius: [2, 2, 2, 2], - }, - }, - edgeCfg: { - label: { - style: { - fill: '#aaa', - fontSize: 12, - fillOpacity: 1, - }, - }, - style: (edge) => { - const stroke = edge.target === '0' ? '#c86bdd' : '#5ae859'; - return { - stroke, - lineWidth: 1, - strokeOpacity: 0.5, - }; - }, - edgeStateStyles: { - hover: { - lineWidth: 2, - strokeOpacity: 1, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/flow-analysis-graph/index.en.md b/site/examples/relation-graph/flow-analysis-graph/index.en.md deleted file mode 100644 index 4b38637d9..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Flow Analysis Graph -order: 1 ---- diff --git a/site/examples/relation-graph/flow-analysis-graph/index.zh.md b/site/examples/relation-graph/flow-analysis-graph/index.zh.md deleted file mode 100644 index cfc2ce4c0..000000000 --- a/site/examples/relation-graph/flow-analysis-graph/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 来源去向图 -order: 1 ---- diff --git a/site/examples/relation-graph/fund-flow-graph/API.en.md b/site/examples/relation-graph/fund-flow-graph/API.en.md deleted file mode 100644 index 718c4c032..000000000 --- a/site/examples/relation-graph/fund-flow-graph/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/fund-flow-graph/API.zh.md b/site/examples/relation-graph/fund-flow-graph/API.zh.md deleted file mode 100644 index 8ef851055..000000000 --- a/site/examples/relation-graph/fund-flow-graph/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/fund-flow-graph/demo/basic.js b/site/examples/relation-graph/fund-flow-graph/demo/basic.js deleted file mode 100644 index 3468fb883..000000000 --- a/site/examples/relation-graph/fund-flow-graph/demo/basic.js +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import ReactDOM from 'react-dom'; -import { FundFlowGraph } from '@ant-design/graphs'; - -const DemoFundFlowGraph = () => { - const data = { - nodes: [ - { - id: '1', - value: { - text: 'Company1', - // 建议使用 bae64 数据 - icon: 'https://gw.alipayobjects.com/zos/antfincdn/28B4PgocL4/bbd3e7ef-6b5e-4034-893d-1b5073ad9aa4.png', - }, - }, - { - id: '2', - value: { text: 'Company2' }, - }, - { - id: '3', - value: { text: 'Company3' }, - }, - { - id: '4', - value: { text: 'Company4' }, - }, - { - id: '5', - value: { text: 'Company5' }, - }, - { - id: '6', - value: { text: 'Company6' }, - }, - { - id: '7', - value: { text: 'Company7' }, - }, - { - id: '8', - value: { text: 'Company8' }, - }, - { - id: '9', - value: { text: 'Company9' }, - }, - ], - edges: [ - { - source: '1', - target: '2', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '1', - target: '3', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '2', - target: '5', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '5', - target: '6', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '3', - target: '4', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '4', - target: '7', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '1', - target: '8', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '1', - target: '9', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - ], - }; - const config = { - data, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/fund-flow-graph/demo/meta.json b/site/examples/relation-graph/fund-flow-graph/demo/meta.json deleted file mode 100644 index 4f115e0dc..000000000 --- a/site/examples/relation-graph/fund-flow-graph/demo/meta.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "资金流向图", - "en": "Basic" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/qQk2NkG%26En/8a4e97f4-23a0-4aba-b2a6-a71343b7a3f8.png" - }, - { - "filename": "style.js", - "title": { - "zh": "资金流向图-格式化设置", - "en": "Style" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/p7h4IUyVXM/680af564-b119-4e38-9626-bb9917424ec7.png" - } - ] -} diff --git a/site/examples/relation-graph/fund-flow-graph/demo/style.js b/site/examples/relation-graph/fund-flow-graph/demo/style.js deleted file mode 100644 index c003477b1..000000000 --- a/site/examples/relation-graph/fund-flow-graph/demo/style.js +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import ReactDOM from 'react-dom'; -import { FundFlowGraph } from '@ant-design/graphs'; - -const DemoFundFlowGraph = () => { - const data = { - nodes: [ - { - id: '1', - value: { - text: 'Company1', - // 建议使用 bae64 数据 - icon: 'https://gw.alipayobjects.com/zos/antfincdn/28B4PgocL4/bbd3e7ef-6b5e-4034-893d-1b5073ad9aa4.png', - }, - }, - { - id: '2', - value: { text: 'Company2' }, - }, - { - id: '3', - value: { text: 'Company3' }, - }, - { - id: '4', - value: { text: 'Company4' }, - }, - { - id: '5', - value: { text: 'Company5' }, - }, - { - id: '6', - value: { text: 'Company6' }, - }, - { - id: '7', - value: { text: 'Company7' }, - }, - { - id: '8', - value: { text: 'Company8' }, - }, - { - id: '9', - value: { text: 'Company9' }, - }, - ], - edges: [ - { - source: '1', - target: '2', - value: { text: '100,000 Yuan', subText: '2019-08-03', extraKey: 'A' }, - }, - { - source: '1', - target: '3', - value: { text: '100,000 Yuan', subText: '2019-08-03', extraKey: 'B' }, - }, - { - source: '2', - target: '5', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '5', - target: '6', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '3', - target: '4', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '4', - target: '7', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '1', - target: '8', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - { - source: '1', - target: '9', - value: { text: '100,000 Yuan', subText: '2019-08-03' }, - }, - ], - }; - const colorMap = { - A: '#FFAA15', - B: '#72CC4A', - }; - const config = { - data, - edgeCfg: { - // type: 'line', - endArrow: (edge) => { - const { value } = edge; - return { - fill: colorMap[value.extraKey] || '#40a9ff', - }; - }, - style: (edge) => { - const { value } = edge; - return { - stroke: colorMap[value.extraKey] || '#40a9ff', - }; - }, - edgeStateStyles: { - hover: { - stroke: '#1890ff', - lineWidth: 2, - endArrow: { - fill: '#1890ff', - }, - }, - }, - }, - markerCfg: (cfg) => { - const { edges } = data; - return { - position: 'right', - show: edges.find((item) => item.source === cfg.id), - collapsed: !edges.find((item) => item.source === cfg.id), - }; - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/fund-flow-graph/index.en.md b/site/examples/relation-graph/fund-flow-graph/index.en.md deleted file mode 100644 index 77e92773e..000000000 --- a/site/examples/relation-graph/fund-flow-graph/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fund Flow Graph -order: 2 ---- diff --git a/site/examples/relation-graph/fund-flow-graph/index.zh.md b/site/examples/relation-graph/fund-flow-graph/index.zh.md deleted file mode 100644 index ffce08c52..000000000 --- a/site/examples/relation-graph/fund-flow-graph/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 资金流向图 -order: 2 ---- diff --git a/site/examples/relation-graph/mind-map-graph/API.en.md b/site/examples/relation-graph/mind-map-graph/API.en.md deleted file mode 100644 index f75476884..000000000 --- a/site/examples/relation-graph/mind-map-graph/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/mind-map-graph/API.zh.md b/site/examples/relation-graph/mind-map-graph/API.zh.md deleted file mode 100644 index d68916f48..000000000 --- a/site/examples/relation-graph/mind-map-graph/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/mind-map-graph/demo/basic.js b/site/examples/relation-graph/mind-map-graph/demo/basic.js deleted file mode 100644 index 060b87e7b..000000000 --- a/site/examples/relation-graph/mind-map-graph/demo/basic.js +++ /dev/null @@ -1,279 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { MindMapGraph } from '@ant-design/graphs'; - -const MindMapGraphGraph = () => { - const data = { - id: 'A0', - value: { - title: '订单金额', - items: [ - { - text: '3031万', - }, - ], - }, - children: [ - { - id: 'A1', - value: { - title: '华南', - items: [ - { - text: '占比', - value: '30%', - }, - ], - }, - children: [ - { - id: 'A11', - value: { - title: '广东', - items: [ - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A12', - value: { - title: '广西', - items: [ - { - text: '占比', - value: '30%', - }, - ], - }, - }, - { - id: 'A13', - value: { - title: '海南', - items: [ - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - { - id: 'A2', - value: { - title: '华北', - items: [ - { - text: '占比', - value: '30%', - icon: 'https://gw.alipayobjects.com/zos/antfincdn/iFh9X011qd/7797962c-04b6-4d67-9143-e9d05f9778bf.png', - }, - ], - }, - children: [ - { - id: 'A2-1', - value: { - title: '华北', - items: [ - { - text: '占比', - value: '30%', - }, - ], - }, - }, - ], - }, - ], - }; - - const level = [-2, -1, 0, 1, 2]; - const levelTexts = level.map((l) => { - if (l < 0) return `${Math.abs(l)}层上游`; - if (l > 0) return `${Math.abs(l)}层下游`; - return `主节点`; - }); - const containerWidth = 800; - const width = 120; - const LevelFC = () => ( - - {levelTexts.map((l) => ( -
- - {l} - -
- ))} -
- ); - - const nodeSize = [width, 40]; - const config = { - data: data, - autoFit: false, - width: containerWidth, - // level: 5, - layout: { - getHeight: () => { - return 40; - }, - getWidth: () => { - return nodeSize[0]; - }, - getVGap: () => { - return 16; - }, - getHGap: () => { - return (containerWidth / level.length - width) / 2; - }, - }, - // level, - nodeCfg: { - size: nodeSize, - padding: 4, - style: { - stroke: '#5AD8A6', - }, - items: { - padding: [4, 0], - }, - customContent: (item, group, cfg) => { - const { startX, startY, width } = cfg; - const { text, value, icon, trend } = item; - const tagWidth = 28; - const tagHeight = 16; - group?.addShape('rect', { - attrs: { - x: 0, - y: 0, - width: nodeSize[0], - height: nodeSize[1] + 8, - fillOpacity: 0.1, - }, - // group 内唯一字段 - name: `container-${Math.random()}`, - }); - group?.addShape('rect', { - attrs: { - x: startX, - y: startY, - width: tagWidth, - height: tagHeight, - fill: '#47c796', - }, - // group 内唯一字段 - name: `tag-${Math.random()}`, - }); - group?.addShape('text', { - attrs: { - textBaseline: 'middle', - textAlign: 'center', - x: startX + tagWidth / 2, - y: startY + tagHeight / 2, - text: '人群', - fill: '#fff', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - group?.addShape('text', { - attrs: { - textBaseline: 'middle', - textAlign: 'start', - x: startX + tagWidth + 4, - y: startY + tagHeight / 2, - text: '人群服务名称', - fill: 'rgba(0,0,0,.65)', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - const textMargin = 10; - const sense = group?.addShape('text', { - attrs: { - textBaseline: 'top', - textAlign: 'start', - x: startX, - y: startY + tagHeight + textMargin, - text: '所属场景:', - fill: 'rgba(0,0,0,.45)', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - group?.addShape('text', { - attrs: { - textBaseline: 'top', - textAlign: 'start', - x: sense.getBBox().maxX, - y: startY + tagHeight + textMargin, - text: '这是场景名称', - fill: 'rgba(0,0,0,.45)', - fontSize: 10, - }, - // group 内唯一字段 - name: `text-${Math.random()}`, - }); - }, - }, - markerCfg: (cfg) => { - const { children = [], id } = cfg; - if (id === 'A0') { - return [ - { - position: 'left', - show: !!children?.length, - collapsed: !children?.length, - }, - { - position: 'right', - show: !!children?.length, - collapsed: !children?.length, - }, - ]; - } - return { - position: 'right', - show: !!children?.length, - collapsed: !children?.length, - }; - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ( -
-
{LevelFC()}
- -
- ); -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/mind-map-graph/demo/meta.json b/site/examples/relation-graph/mind-map-graph/demo/meta.json deleted file mode 100644 index 62edf01d2..000000000 --- a/site/examples/relation-graph/mind-map-graph/demo/meta.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "脑图", - "en": "Basic" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/%24cS0yQQN%24/c69ba993-ca99-4ba1-a5d8-5ba824b83d95.png" - } - ] -} diff --git a/site/examples/relation-graph/mind-map-graph/index.en.md b/site/examples/relation-graph/mind-map-graph/index.en.md deleted file mode 100644 index 5a892d065..000000000 --- a/site/examples/relation-graph/mind-map-graph/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: MindMap Graph -order: 0 ---- diff --git a/site/examples/relation-graph/mind-map-graph/index.zh.md b/site/examples/relation-graph/mind-map-graph/index.zh.md deleted file mode 100644 index 7734d8494..000000000 --- a/site/examples/relation-graph/mind-map-graph/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 脑图 -order: 4 ---- diff --git a/site/examples/relation-graph/organization-graph/API.en.md b/site/examples/relation-graph/organization-graph/API.en.md deleted file mode 100644 index 4e754e489..000000000 --- a/site/examples/relation-graph/organization-graph/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/organization-graph/API.zh.md b/site/examples/relation-graph/organization-graph/API.zh.md deleted file mode 100644 index 0b4207193..000000000 --- a/site/examples/relation-graph/organization-graph/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/organization-graph/demo/basic.js b/site/examples/relation-graph/organization-graph/demo/basic.js deleted file mode 100644 index 8a3a1ae9f..000000000 --- a/site/examples/relation-graph/organization-graph/demo/basic.js +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { OrganizationGraph } from '@ant-design/graphs'; - -const DemoOrganizationGraph = () => { - const data = { - id: 'root', - value: { - name: '股东会', - }, - children: [ - { - id: 'joel', - value: { - name: 'Joel Alan', - }, - children: [ - { - id: 'c1', - value: { - name: 'c1', - }, - children: [ - { - id: 'c1-1', - value: { - name: 'c1-1', - }, - }, - { - id: 'c1-2', - value: { - name: 'c1-2', - }, - children: [ - { - id: 'c1-2-1', - value: { - name: 'c1-2-1', - }, - }, - { - id: 'c1-2-2', - value: { - name: 'c1-2-2', - }, - }, - ], - }, - ], - }, - { - id: 'c2', - value: { - name: 'c2', - }, - }, - { - id: 'c3', - value: { - name: 'c3', - }, - children: [ - { - id: 'c3-1', - value: { - name: 'c3-1', - }, - }, - { - id: 'c3-2', - value: { - name: 'c3-2', - }, - children: [ - { - id: 'c3-2-1', - value: { - name: 'c3-2-1', - }, - }, - { - id: 'c3-2-2', - value: { - name: 'c3-2-2', - }, - }, - { - id: 'c3-2-3', - value: { - name: 'c3-2-3', - }, - }, - ], - }, - { - id: 'c3-3', - value: { - name: 'c3-3', - }, - }, - ], - }, - ], - }, - ], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/organization-graph/demo/custom.js b/site/examples/relation-graph/organization-graph/demo/custom.js deleted file mode 100644 index a7ba1abcf..000000000 --- a/site/examples/relation-graph/organization-graph/demo/custom.js +++ /dev/null @@ -1,320 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { OrganizationGraph } from '@ant-design/graphs'; - -const DemoOrganizationGraph = () => { - const data = { - id: 'root', - value: { - name: '赵某某', - }, - children: [ - { - id: 'jug', - value: { - name: '审判长', - level: 2, - }, - children: [ - { - id: 'joel', - value: { - name: '一审', - level: 1, - }, - children: [ - { - id: 'c1', - value: { - name: '原告', - level: 2, - }, - children: [ - { - id: 'c1-1', - value: { - name: '中纺原料xx有限公司', - }, - children: [ - { - id: 'c1-1-1', - value: { - name: '法定代表人', - }, - children: [ - { - id: 'c1-1-1-1', - value: { - name: '刘某某', - }, - }, - ], - }, - ], - }, - { - id: 'c1-2', - value: { - name: '委托诉讼代理人', - }, - children: [ - { - id: 'c1-2-1', - value: { - name: '北京xx律师事务所', - }, - }, - { - id: 'c1-2-2', - value: { - name: '赵某某', - }, - }, - ], - }, - ], - }, - { - id: 'c2', - value: { - name: '被告1', - level: 2, - }, - children: [ - { - id: 'c2-1', - value: { - name: '北京西郊xxxx农副产品批发市场有限公司', - }, - children: [ - { - id: 'c2-1-1', - value: { - name: '法定代表人', - }, - children: [ - { - id: 'c2-1-1-1', - value: { - name: '李某某', - }, - }, - ], - }, - ], - }, - { - id: 'c2-2', - value: { - name: '委托诉讼代理人', - }, - children: [ - { - id: 'c2-2-1', - value: { - name: '北京xx律师事务所', - }, - }, - { - id: 'c2-2-2', - value: { - name: '张某某', - }, - }, - ], - }, - ], - }, - { - id: 'c3', - value: { - name: '被告2', - level: 2, - }, - children: [ - { - id: 'c3-1', - value: { - name: '徐某某', - }, - }, - { - id: 'c3-2', - value: { - name: '委托诉讼代理人', - }, - children: [ - { - id: 'c3-2-1', - value: { - name: '北京xx律师事务所', - }, - }, - { - id: 'c3-2-2', - value: { - name: '张某某', - }, - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }; - const getTextStyle = (level) => { - switch (level) { - case 1: - return 18; - case 2: - return 12; - default: - return 12; - } - }; - - const getRootTextAttrs = () => { - return { - fontSize: getTextStyle(1), - fontWeight: 'bold', - fill: '#fff', - }; - }; - - const getSecondTextStyle = () => { - return { - fontSize: getTextStyle(2), - color: '#000', - }; - }; - - const getRootNodeStyle = () => { - return { - fill: '#1E88E5', - stroke: '#1E88E5', - radius: 5, - }; - }; - - const getSecondNodeStyle = () => { - return { - fill: '#e8e8e8', - stroke: '#e8e8e8', - radius: 5, - }; - }; - - const calcStrLen = function calcStrLen(str) { - var len = 0; - for (var i = 0; i < str.length; i++) { - if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) { - len++; - } else { - len += 2; - } - } - return len; - }; - - const config = { - nodeCfg: { - size: [40, 40], - autoWidth: true, - padding: 10, - style: (item) => { - const { level } = item.value; - return { - fill: 'transparent', - stroke: 'transparent', - radius: 4, - cursor: 'pointer', - ...(level === 1 ? getRootNodeStyle() : {}), - ...(level === 2 ? getSecondNodeStyle() : {}), - }; - }, - nodeStateStyles: { - hover: { - lineWidth: 2, - stroke: '#96DEFF', - }, - }, - label: { - style: (cfg, group, type) => { - const { level, href } = cfg.value; - - if (type !== 'name') { - return {}; - } - return { - fontSize: getTextStyle(), - cursor: 'pointer', - fill: href ? '#1890ff' : '#000', - ...(level === 1 ? getRootTextAttrs() : {}), - ...(level === 2 ? getSecondTextStyle() : {}), - }; - }, - }, - anchorPoints: [ - [0, 0.5], - [1, 0.5], - ], - }, - edgeCfg: { - type: 'polyline', - style: { - stroke: '#000', - endArrow: false, - }, - }, - markerCfg: (cfg) => { - const { level, direction } = cfg.value; - const show = level !== 1 && cfg.children && cfg.children.length > 0; - return { - position: direction, - show, - }; - }, - layout: { - type: 'mindmap', - direction: 'H', - getWidth: (cfg) => { - const { name, level } = cfg.value; - const fontSize = getTextStyle(level); - const width = (fontSize * calcStrLen(name)) / 2; - return width; - }, - getHeight: () => { - return 25; - }, - getVGap: () => { - return 20; - }, - getHGap: () => { - return 40; - }, - getSide: (d) => { - return d.data.value.direction === 'left' ? 'left' : 'right'; - }, - }, - autoFit: true, - fitCenter: true, - animate: false, - behaviors: ['drag-canvas', 'zoom-canvas'], - onReady: (graph) => { - graph.on('node:click', (evt) => { - const { item, target } = evt; - const { value } = item.get('model'); - if (value.href) { - window.open(value.href); - } - }); - }, - }; - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/organization-graph/demo/meta.json b/site/examples/relation-graph/organization-graph/demo/meta.json deleted file mode 100644 index 7ff54143a..000000000 --- a/site/examples/relation-graph/organization-graph/demo/meta.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "组织架构图", - "en": "Basic" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/ZOrAJo18HO/5f53b378-ffc0-4982-83d4-a0ddcac86b4d.png" - }, - { - "filename": "style.js", - "title": { - "zh": "组织架构图-格式化设置", - "en": "Style" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/9Q5ftNEgkR/f9f6ae73-9a2c-4c8d-8ce2-919b16acdad0.png" - }, - { - "filename": "custom.js", - "title": { - "zh": "组织架构图-自定义样式", - "en": "Custom" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/JailFwu5B3/042ce37a-9b73-4218-ae2c-dcdb5994379a.png" - } - ] -} diff --git a/site/examples/relation-graph/organization-graph/demo/style.js b/site/examples/relation-graph/organization-graph/demo/style.js deleted file mode 100644 index 67d4383b8..000000000 --- a/site/examples/relation-graph/organization-graph/demo/style.js +++ /dev/null @@ -1,141 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { OrganizationGraph } from '@ant-design/graphs'; - -const DemoOrganizationGraph = () => { - const data = { - id: 'joel', - value: { - name: 'Joel Alan', - title: 'CEO', - // 建议使用 bae64 数据 - icon: 'https://avatars.githubusercontent.com/u/31396322?v=4', - }, - children: [ - { - id: 'c1', - value: { - name: 'c1', - title: 'CTO', - }, - children: [ - { - id: 'c1-1', - value: { - name: 'c1-1', - }, - }, - { - id: 'c1-2', - value: { - name: 'c1-2', - }, - children: [ - { - id: 'c1-2-1', - value: { - name: 'c1-2-1', - }, - }, - { - id: 'c1-2-2', - value: { - name: 'c1-2-2', - }, - }, - ], - }, - ], - }, - { - id: 'c2', - value: { - name: 'c2', - title: 'COO', - }, - }, - { - id: 'c3', - value: { - name: 'c3', - title: 'CFO', - }, - children: [ - { - id: 'c3-1', - value: { - name: 'c3-1', - }, - }, - { - id: 'c3-2', - value: { - name: 'c3-2', - }, - children: [ - { - id: 'c3-2-1', - value: { - name: 'c3-2-1', - }, - }, - { - id: 'c3-2-2', - value: { - name: 'c3-2-2', - }, - }, - { - id: 'c3-2-3', - value: { - name: 'c3-2-3', - }, - }, - ], - }, - { - id: 'c3-3', - value: { - name: 'c3-3', - }, - }, - ], - }, - ], - }; - - return ( - { - return node.id === 'joel' - ? { - fill: '#91d5ff', - stroke: '#91d5ff', - } - : {}; - }, - label: { - style: (node, group, type) => { - const styles = { - icon: { - width: 32, - height: 32, - }, - title: { - fill: '#fff', - }, - name: { - fill: '#fff', - }, - }; - return node.id === 'joel' ? styles[type] : {}; - }, - }, - }} - /> - ); -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/organization-graph/index.en.md b/site/examples/relation-graph/organization-graph/index.en.md deleted file mode 100644 index dee962beb..000000000 --- a/site/examples/relation-graph/organization-graph/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Organization Graph -order: 3 ---- diff --git a/site/examples/relation-graph/organization-graph/index.zh.md b/site/examples/relation-graph/organization-graph/index.zh.md deleted file mode 100644 index 91f7b83bb..000000000 --- a/site/examples/relation-graph/organization-graph/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 组织架构图 -order: 3 ---- diff --git a/site/examples/relation-graph/radial-graph/API.en.md b/site/examples/relation-graph/radial-graph/API.en.md deleted file mode 100644 index 101499f13..000000000 --- a/site/examples/relation-graph/radial-graph/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/radial-graph/API.zh.md b/site/examples/relation-graph/radial-graph/API.zh.md deleted file mode 100644 index 7f9528d9c..000000000 --- a/site/examples/relation-graph/radial-graph/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/radial-graph/demo/basic.js b/site/examples/relation-graph/radial-graph/demo/basic.js deleted file mode 100644 index 75327cc83..000000000 --- a/site/examples/relation-graph/radial-graph/demo/basic.js +++ /dev/null @@ -1,179 +0,0 @@ -import React, { useRef } from 'react'; -import ReactDOM from 'react-dom'; -import { RadialGraph } from '@ant-design/graphs'; - -const DemoRadialGraph = () => { - const chartRef = useRef(); - const RadialData = { - nodes: [ - { - id: '0', - label: '0', - }, - { - id: '1', - label: '1', - }, - { - id: '2', - label: '2', - }, - { - id: '3', - label: '3', - }, - { - id: '4', - label: '4', - }, - { - id: '5', - label: '5', - }, - { - id: '6', - label: '6', - }, - { - id: '7', - label: '7', - }, - { - id: '8', - label: '8', - }, - { - id: '9', - label: '9', - }, - ], - edges: [ - { - source: '0', - target: '1', - }, - { - source: '0', - target: '2', - }, - { - source: '0', - target: '3', - }, - { - source: '0', - target: '4', - }, - { - source: '0', - target: '5', - }, - { - source: '0', - target: '6', - }, - { - source: '0', - target: '7', - }, - { - source: '0', - target: '8', - }, - { - source: '0', - target: '9', - }, - ], - }; - - const fetchData = (node) => { - return new Promise((resolve, reject) => { - const data = new Array(Math.ceil(Math.random() * 10) + 2).fill('').map((_, i) => i + 1); - setTimeout(() => { - resolve({ - nodes: [ - { - ...node, - }, - ].concat( - data.map((i) => { - return { - id: `${node.id}-${i}`, - label: `${node.label}-${i}`, - }; - }), - ), - edges: data.map((i) => { - return { - source: node.id, - target: `${node.id}-${i}`, - }; - }), - }); - }, 1000); - }); - }; - - const asyncData = async (node) => { - return await fetchData(node); - }; - - const config = { - data: RadialData, - autoFit: false, - layout: { - unitRadius: 80, - /** 节点直径 */ - nodeSize: 20, - /** 节点间距 */ - nodeSpacing: 10, - }, - nodeCfg: { - asyncData, - size: 20, - style: { - fill: '#6CE8DC', - stroke: '#6CE8DC', - }, - labelCfg: { - style: { - fontSize: 5, - fill: '#000', - }, - }, - }, - menuCfg: { - customContent: (e) => { - return ( -
- -
- ); - }, - }, - edgeCfg: { - style: { - lineWidth: 1, - }, - endArrow: { - d: 10, - size: 2, - }, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - onReady: (graph) => { - chartRef.current = graph; - }, - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/radial-graph/demo/meta.json b/site/examples/relation-graph/radial-graph/demo/meta.json deleted file mode 100644 index 903e6234b..000000000 --- a/site/examples/relation-graph/radial-graph/demo/meta.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "辐射图", - "en": "Basic" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/LvOmM0I5dZ/explore.gif" - } - ] -} diff --git a/site/examples/relation-graph/radial-graph/index.en.md b/site/examples/relation-graph/radial-graph/index.en.md deleted file mode 100644 index 00d16afe6..000000000 --- a/site/examples/relation-graph/radial-graph/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Radial Graph -order: 4 ---- diff --git a/site/examples/relation-graph/radial-graph/index.zh.md b/site/examples/relation-graph/radial-graph/index.zh.md deleted file mode 100644 index 06b4d2274..000000000 --- a/site/examples/relation-graph/radial-graph/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 辐射图 -order: 4 ---- diff --git a/site/examples/relation-graph/radial-tree-graph/API.en.md b/site/examples/relation-graph/radial-tree-graph/API.en.md deleted file mode 100644 index 2573d7e7d..000000000 --- a/site/examples/relation-graph/radial-tree-graph/API.en.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/radial-tree-graph/API.zh.md b/site/examples/relation-graph/radial-tree-graph/API.zh.md deleted file mode 100644 index 938048a7b..000000000 --- a/site/examples/relation-graph/radial-tree-graph/API.zh.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/examples/relation-graph/radial-tree-graph/demo/basic.js b/site/examples/relation-graph/radial-tree-graph/demo/basic.js deleted file mode 100644 index 3a9ba9092..000000000 --- a/site/examples/relation-graph/radial-tree-graph/demo/basic.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { RadialTreeGraph } from '@ant-design/graphs'; - -const DemoRadialTreeGraph = () => { - const data = { - id: 'Modeling Methods', - children: [ - { - id: 'Classification', - children: [ - { id: 'Logistic regression', value: 'Logistic regression' }, - { id: 'Linear discriminant analysis', value: 'Linear discriminant analysis' }, - { id: 'Rules', value: 'Rules' }, - { id: 'Decision trees', value: 'Decision trees' }, - { id: 'Naive Bayes', value: 'Naive Bayes' }, - { id: 'K nearest neighbor', value: 'K nearest neighbor' }, - { id: 'Probabilistic neural network', value: 'Probabilistic neural network' }, - { id: 'Support vector machine', value: 'Support vector machine' }, - ], - value: 'Classification', - }, - { - id: 'Consensus', - children: [ - { - id: 'Models diversity', - children: [ - { id: 'Different initializations', value: 'Different initializations' }, - { id: 'Different parameter choices', value: 'Different parameter choices' }, - { id: 'Different architectures', value: 'Different architectures' }, - { id: 'Different modeling methods', value: 'Different modeling methods' }, - { id: 'Different training sets', value: 'Different training sets' }, - { id: 'Different feature sets', value: 'Different feature sets' }, - ], - value: 'Models diversity', - }, - { - id: 'Methods', - children: [ - { id: 'Classifier selection', value: 'Classifier selection' }, - { id: 'Classifier fusion', value: 'Classifier fusion' }, - ], - value: 'Methods', - }, - { - id: 'Common', - children: [ - { id: 'Bagging', value: 'Bagging' }, - { id: 'Boosting', value: 'Boosting' }, - { id: 'AdaBoost', value: 'AdaBoost' }, - ], - value: 'Common', - }, - ], - value: 'Consensus', - }, - { - id: 'Regression', - children: [ - { id: 'Multiple linear regression', value: 'Multiple linear regression' }, - { id: 'Partial least squares', value: 'Partial least squares' }, - { - id: 'Multi-layer feedforward neural network', - value: 'Multi-layer feedforward neural network', - }, - { id: 'General regression neural network', value: 'General regression neural network' }, - { id: 'Support vector regression', value: 'Support vector regression' }, - ], - value: 'Regression', - }, - ], - value: 'Modeling Methods', - }; - - const config = { - data, - nodeCfg: { - type: 'diamond', - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/radial-tree-graph/demo/layout.js b/site/examples/relation-graph/radial-tree-graph/demo/layout.js deleted file mode 100644 index 18c459a5c..000000000 --- a/site/examples/relation-graph/radial-tree-graph/demo/layout.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { RadialTreeGraph } from '@ant-design/graphs'; - -const DemoRadialTreeGraph = () => { - const data = { - id: 'Modeling Methods', - children: [ - { - id: 'Classification', - children: [ - { id: 'Logistic regression', value: 'Logistic regression' }, - { id: 'Linear discriminant analysis', value: 'Linear discriminant analysis' }, - { id: 'Rules', value: 'Rules' }, - { id: 'Decision trees', value: 'Decision trees' }, - { id: 'Naive Bayes', value: 'Naive Bayes' }, - { id: 'K nearest neighbor', value: 'K nearest neighbor' }, - { id: 'Probabilistic neural network', value: 'Probabilistic neural network' }, - { id: 'Support vector machine', value: 'Support vector machine' }, - ], - value: 'Classification', - }, - { - id: 'Consensus', - children: [ - { - id: 'Models diversity', - children: [ - { id: 'Different initializations', value: 'Different initializations' }, - { id: 'Different parameter choices', value: 'Different parameter choices' }, - { id: 'Different architectures', value: 'Different architectures' }, - { id: 'Different modeling methods', value: 'Different modeling methods' }, - { id: 'Different training sets', value: 'Different training sets' }, - { id: 'Different feature sets', value: 'Different feature sets' }, - ], - value: 'Models diversity', - }, - { - id: 'Methods', - children: [ - { id: 'Classifier selection', value: 'Classifier selection' }, - { id: 'Classifier fusion', value: 'Classifier fusion' }, - ], - value: 'Methods', - }, - { - id: 'Common', - children: [ - { id: 'Bagging', value: 'Bagging' }, - { id: 'Boosting', value: 'Boosting' }, - { id: 'AdaBoost', value: 'AdaBoost' }, - ], - value: 'Common', - }, - ], - value: 'Consensus', - }, - { - id: 'Regression', - children: [ - { id: 'Multiple linear regression', value: 'Multiple linear regression' }, - { id: 'Partial least squares', value: 'Partial least squares' }, - { - id: 'Multi-layer feedforward neural network', - value: 'Multi-layer feedforward neural network', - }, - { id: 'General regression neural network', value: 'General regression neural network' }, - { id: 'Support vector regression', value: 'Support vector regression' }, - ], - value: 'Regression', - }, - ], - value: 'Modeling Methods', - }; - - const config = { - data, - nodeCfg: { - type: 'diamond', - }, - layout: { - type: 'compactBox', - direction: 'RL', - getId: function getId(d) { - return d.id; - }, - getHeight: () => { - return 26; - }, - getWidth: () => { - return 26; - }, - getVGap: () => { - return 20; - }, - getHGap: () => { - return 30; - }, - radial: true, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ; -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/radial-tree-graph/demo/meta.json b/site/examples/relation-graph/radial-tree-graph/demo/meta.json deleted file mode 100644 index ee632caf5..000000000 --- a/site/examples/relation-graph/radial-tree-graph/demo/meta.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "title": { - "zh": "中文分类", - "en": "Category" - }, - "demos": [ - { - "filename": "basic.js", - "title": { - "zh": "辐射树图", - "en": "Basic" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/lXkLSzbqST/c58f6a93-e4de-478b-93c2-4c1fc1471fbd.png" - }, - { - "filename": "layout.js", - "title": { - "zh": "辐射树图-调整布局", - "en": "Layout" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/%242j6oyybYK/46bf0b8d-9d34-4fbe-a9bb-1580346f7f1b.png" - }, - { - "filename": "style.js", - "title": { - "zh": "辐射树图-格式化设置", - "en": "Style" - }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/tCykDJncjJ/2200d0c0-40bd-46a8-a885-30393fb165b1.png" - } - ] -} diff --git a/site/examples/relation-graph/radial-tree-graph/demo/style.js b/site/examples/relation-graph/radial-tree-graph/demo/style.js deleted file mode 100644 index 75c425bfe..000000000 --- a/site/examples/relation-graph/radial-tree-graph/demo/style.js +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { RadialTreeGraph } from '@ant-design/graphs'; - -const DemoRadialTreeGraph = () => { - const data = { - id: 'Modeling Methods', - children: [ - { - id: 'Classification', - children: [ - { id: 'Logistic regression', value: 'Logistic regression' }, - { - id: 'Linear discriminant analysis', - value: 'Linear discriminant analysis', - }, - { id: 'Rules', value: 'Rules' }, - { id: 'Decision trees', value: 'Decision trees' }, - { id: 'Naive Bayes', value: 'Naive Bayes' }, - { id: 'K nearest neighbor', value: 'K nearest neighbor' }, - { - id: 'Probabilistic neural network', - value: 'Probabilistic neural network', - }, - { id: 'Support vector machine', value: 'Support vector machine' }, - ], - value: 'Classification', - }, - { - id: 'Consensus', - children: [ - { - id: 'Models diversity', - children: [ - { - id: 'Different initializations', - value: 'Different initializations', - }, - { - id: 'Different parameter choices', - value: 'Different parameter choices', - }, - { - id: 'Different architectures', - value: 'Different architectures', - }, - { - id: 'Different modeling methods', - value: 'Different modeling methods', - }, - { - id: 'Different training sets', - value: 'Different training sets', - }, - { id: 'Different feature sets', value: 'Different feature sets' }, - ], - value: 'Models diversity', - }, - { - id: 'Methods', - children: [ - { id: 'Classifier selection', value: 'Classifier selection' }, - { id: 'Classifier fusion', value: 'Classifier fusion' }, - ], - value: 'Methods', - }, - { - id: 'Common', - children: [ - { id: 'Bagging', value: 'Bagging' }, - { id: 'Boosting', value: 'Boosting' }, - { id: 'AdaBoost', value: 'AdaBoost' }, - ], - value: 'Common', - }, - ], - value: 'Consensus', - }, - { - id: 'Regression', - children: [ - { - id: 'Multiple linear regression', - value: 'Multiple linear regression', - }, - { id: 'Partial least squares', value: 'Partial least squares' }, - { - id: 'Multi-layer feedforward neural network', - value: 'Multi-layer feedforward neural network', - }, - { - id: 'General regression neural network', - value: 'General regression neural network', - }, - { - id: 'Support vector regression', - value: 'Support vector regression', - }, - ], - value: 'Regression', - }, - ], - value: 'Modeling Methods', - }; - const themeColor = '#73B3D1'; - const config = { - data, - nodeCfg: { - size: 30, - type: 'circle', - label: { - style: { - fill: '#fff', - }, - }, - style: { - fill: themeColor, - stroke: '#0E1155', - lineWidth: 2, - strokeOpacity: 0.45, - shadowColor: themeColor, - shadowBlur: 25, - }, - nodeStateStyles: { - hover: { - stroke: themeColor, - lineWidth: 2, - strokeOpacity: 1, - }, - }, - }, - edgeCfg: { - style: { - stroke: themeColor, - shadowColor: themeColor, - shadowBlur: 20, - }, - endArrow: { - type: 'triangle', - fill: themeColor, - d: 15, - size: 8, - }, - edgeStateStyles: { - hover: { - stroke: themeColor, - lineWidth: 2, - }, - }, - }, - behaviors: ['drag-canvas', 'zoom-canvas', 'drag-node'], - }; - - return ( -
- -
- ); -}; - -ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relation-graph/radial-tree-graph/index.en.md b/site/examples/relation-graph/radial-tree-graph/index.en.md deleted file mode 100644 index 5f34626db..000000000 --- a/site/examples/relation-graph/radial-tree-graph/index.en.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Radial Tree Graph -order: 5 ---- diff --git a/site/examples/relation-graph/radial-tree-graph/index.zh.md b/site/examples/relation-graph/radial-tree-graph/index.zh.md deleted file mode 100644 index 07f5a3c62..000000000 --- a/site/examples/relation-graph/radial-tree-graph/index.zh.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 辐射树图 -order: 5 ---- diff --git a/site/docs/map-common/attribute/area-state.en.md b/site/examples/relations/dendrogram/API.en.md similarity index 100% rename from site/docs/map-common/attribute/area-state.en.md rename to site/examples/relations/dendrogram/API.en.md diff --git a/site/docs/map-common/attribute/area-style.en.md b/site/examples/relations/dendrogram/API.zh.md similarity index 100% rename from site/docs/map-common/attribute/area-style.en.md rename to site/examples/relations/dendrogram/API.zh.md diff --git a/site/examples/relations/dendrogram/demo/horizontal-dendrogram.js b/site/examples/relations/dendrogram/demo/horizontal-dendrogram.js new file mode 100644 index 000000000..07afa5341 --- /dev/null +++ b/site/examples/relations/dendrogram/demo/horizontal-dendrogram.js @@ -0,0 +1,24 @@ +import { Dendrogram, G6 } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoDendrogram = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => setData(treeToGraphData(data))); + }, []); + + const options = { + autoFit: 'view', + data, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/dendrogram/demo/horizontal-tidy-tree.js b/site/examples/relations/dendrogram/demo/horizontal-tidy-tree.js new file mode 100644 index 000000000..7e0caec80 --- /dev/null +++ b/site/examples/relations/dendrogram/demo/horizontal-tidy-tree.js @@ -0,0 +1,25 @@ +import { Dendrogram, G6 } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoDendrogram = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => setData(treeToGraphData(data))); + }, []); + + const options = { + autoFit: 'view', + data, + compact: true, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/dendrogram/demo/meta.json b/site/examples/relations/dendrogram/demo/meta.json new file mode 100644 index 000000000..e147bb0b5 --- /dev/null +++ b/site/examples/relations/dendrogram/demo/meta.json @@ -0,0 +1,56 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "horizontal-dendrogram.js", + "title": { + "zh": "水平生态树", + "en": "Horizontal Dendrogram" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*rRlBRIOnCjoAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "vertical-dendrogram.js", + "title": { + "zh": "垂直生态树", + "en": "Vertical Dendrogram" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Xzf1SKVmNxIAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "radial-dendrogram.js", + "title": { + "zh": "径向生态树", + "en": "Radial Dendrogram" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*mvnUTaA91H0AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "horizontal-tidy-tree.js", + "title": { + "zh": "水平紧凑树", + "en": "Horizontal Tidy Tree" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*3u63Q4dYzMQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "vertical-tidy-tree.js", + "title": { + "zh": "垂直紧凑树", + "en": "Vertical Tidy Tree" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GbheQoWEWREAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "radial-tidy-tree.js", + "title": { + "zh": "径向紧凑树", + "en": "Radial Tidy Tree" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*6xYwTbcLp20AAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/relations/dendrogram/demo/radial-dendrogram.js b/site/examples/relations/dendrogram/demo/radial-dendrogram.js new file mode 100644 index 000000000..6c3fd9549 --- /dev/null +++ b/site/examples/relations/dendrogram/demo/radial-dendrogram.js @@ -0,0 +1,23 @@ +import { Dendrogram } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDendrogram = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'view', + data, + direction: 'radial', + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/dendrogram/demo/radial-tidy-tree.js b/site/examples/relations/dendrogram/demo/radial-tidy-tree.js new file mode 100644 index 000000000..7ed17f5d2 --- /dev/null +++ b/site/examples/relations/dendrogram/demo/radial-tidy-tree.js @@ -0,0 +1,26 @@ +import { Dendrogram, G6 } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoDendrogram = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => setData(treeToGraphData(data))); + }, []); + + const options = { + autoFit: 'view', + data, + direction: 'radial', + compact: true, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/dendrogram/demo/vertical-dendrogram.js b/site/examples/relations/dendrogram/demo/vertical-dendrogram.js new file mode 100644 index 000000000..053b77316 --- /dev/null +++ b/site/examples/relations/dendrogram/demo/vertical-dendrogram.js @@ -0,0 +1,25 @@ +import { Dendrogram, G6 } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoDendrogram = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => setData(treeToGraphData(data))); + }, []); + + const options = { + autoFit: 'view', + data, + direction: 'vertical', + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/dendrogram/demo/vertical-tidy-tree.js b/site/examples/relations/dendrogram/demo/vertical-tidy-tree.js new file mode 100644 index 000000000..8cefe59e6 --- /dev/null +++ b/site/examples/relations/dendrogram/demo/vertical-tidy-tree.js @@ -0,0 +1,26 @@ +import { Dendrogram, G6 } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoDendrogram = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((data) => setData(treeToGraphData(data))); + }, []); + + const options = { + autoFit: 'view', + data, + direction: 'vertical', + compact: true, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/dendrogram/index.en.md b/site/examples/relations/dendrogram/index.en.md new file mode 100644 index 000000000..3abfa8765 --- /dev/null +++ b/site/examples/relations/dendrogram/index.en.md @@ -0,0 +1,4 @@ +--- +title: Dendrogram +order: 2 +--- diff --git a/site/examples/relations/dendrogram/index.zh.md b/site/examples/relations/dendrogram/index.zh.md new file mode 100644 index 000000000..51a91a725 --- /dev/null +++ b/site/examples/relations/dendrogram/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 生态树 +order: 2 +--- diff --git a/site/docs/map-common/attribute/color.en.md b/site/examples/relations/flow-direction-graph/API.en.md similarity index 100% rename from site/docs/map-common/attribute/color.en.md rename to site/examples/relations/flow-direction-graph/API.en.md diff --git a/site/docs/map-common/attribute/components.en.md b/site/examples/relations/flow-direction-graph/API.zh.md similarity index 100% rename from site/docs/map-common/attribute/components.en.md rename to site/examples/relations/flow-direction-graph/API.zh.md diff --git a/site/examples/relations/flow-direction-graph/demo/custom.js b/site/examples/relations/flow-direction-graph/demo/custom.js new file mode 100644 index 000000000..3e2b28896 --- /dev/null +++ b/site/examples/relations/flow-direction-graph/demo/custom.js @@ -0,0 +1,550 @@ +import { FlowDirectionGraph } from '@ant-design/graphs'; +import insertCss from 'insert-css'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = { + nodes: [ + { + id: 'node-0', + name: '页面-0', + layerName: '层级0', + measure: { + name: 'DAU', + value: 17500000000, + formattedValue: 175, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 1000000, + formattedValue: 100, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#F5A623', + }, + }, + { + id: 'node-1', + name: '页面1', + layerName: '层级0', + measure: { + name: 'DAU', + value: 5500000000, + formattedValue: 55, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 500000, + formattedValue: 50, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#F5A623', + }, + }, + { + id: 'node-2', + name: '页面2', + layerName: '层级0', + measure: { + name: 'DAU', + value: 1000000000, + formattedValue: 10, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 100000, + formattedValue: 10, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#F5A623', + }, + }, + { + id: 'node-3', + name: '页面3', + layerName: '层级0', + measure: { + name: 'DAU', + value: 900000000, + formattedValue: 9, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 50000, + formattedValue: 5, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#F5A623', + }, + }, + { + id: 'node-4', + name: '页面4', + layerName: '层级0', + measure: { + name: 'DAU', + value: 5700000000, + formattedValue: 57, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 60000, + formattedValue: 6, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#F5A623', + }, + }, + { + id: 'node-5', + name: '页面5', + layerName: '层级1', + measure: { + name: 'DAU', + value: 24000000000, + formattedValue: 240, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 1600000, + formattedValue: 160, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#4A90E2', + }, + }, + { + id: 'node-6', + name: '页面6', + layerName: '层级1', + measure: { + name: 'DAU', + value: 6600000000, + formattedValue: 66, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 110000, + formattedValue: 11, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#4A90E2', + }, + }, + { + id: 'node-7', + name: '页面7', + layerName: '层级2', + measure: { + name: 'DAU', + value: 5000000000, + formattedValue: 50, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 500000, + formattedValue: 50, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#7ED321', + }, + }, + { + id: 'node-8', + name: '页面8', + layerName: '层级2', + measure: { + name: 'DAU', + value: 5000000000, + formattedValue: 50, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 500000, + formattedValue: 50, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#7ED321', + }, + }, + { + id: 'node-9', + name: '页面9', + layerName: '层级2', + measure: { + name: 'DAU', + value: 90000000000, + formattedValue: 90, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 500000, + formattedValue: 50, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#7ED321', + }, + }, + { + id: 'node-10', + name: '页面10', + layerName: '层级2', + measure: { + name: 'DAU', + value: 100000000000, + formattedValue: 100, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 100000, + formattedValue: 10, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#7ED321', + }, + }, + { + id: 'node-11', + name: '页面11', + layerName: '层级2', + measure: { + name: 'DAU', + value: 1000000000, + formattedValue: 10, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 90000, + formattedValue: 9, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#7ED321', + }, + }, + { + id: 'node-12', + name: '页面12', + layerName: '层级2', + measure: { + name: 'DAU', + value: 600000000, + formattedValue: 6, + formattedUnit: '万', + }, + relatedMeasures: [ + { + name: 'MAU', + value: 10000, + formattedValue: 1, + formattedUnit: '万', + }, + ], + compareMeasures: [], + style: { + stroke: '#7ED321', + }, + }, + ], + edges: [ + { + id: 'edge-0', + source: 'node-0', + target: 'node-5', + measure: { + name: 'DAU', + value: 17500000000, + formattedValue: 175, + formattedUnit: '万', + }, + }, + { + id: 'edge-1', + source: 'node-1', + target: 'node-5', + measure: { + name: 'DAU', + value: 5500000000, + formattedValue: 55, + formattedUnit: '万', + }, + }, + { + id: 'edge-2', + source: 'node-2', + target: 'node-5', + measure: { + name: 'DAU', + value: 1000000000, + formattedValue: 10, + formattedUnit: '万', + }, + }, + { + id: 'edge-3', + source: 'node-3', + target: 'node-6', + measure: { + name: 'DAU', + value: 900000000, + formattedValue: 9, + formattedUnit: '万', + }, + }, + { + id: 'edge-4', + source: 'node-5', + target: 'node-7', + measure: { + name: 'DAU', + value: 5000000000, + formattedValue: 50, + formattedUnit: '万', + }, + }, + { + id: 'edge-5', + source: 'node-5', + target: 'node-8', + measure: { + name: 'DAU', + value: 5000000000, + formattedValue: 50, + formattedUnit: '万', + }, + }, + { + id: 'edge-6', + source: 'node-5', + target: 'node-9', + measure: { + name: 'DAU', + value: 9000000000, + formattedValue: 90, + formattedUnit: '万', + }, + }, + { + id: 'edge-7', + source: 'node-5', + target: 'node-10', + measure: { + name: 'DAU', + value: 5000000000, + formattedValue: 50, + formattedUnit: '万', + }, + }, + { + id: 'edge-8', + source: 'node-6', + target: 'node-11', + measure: { + name: 'DAU', + value: 1000000000, + formattedValue: 10, + formattedUnit: '万', + }, + }, + { + id: 'edge-9', + source: 'node-4', + target: 'node-6', + measure: { + name: 'DAU', + value: 5700000000, + formattedValue: 57, + formattedUnit: '万', + }, + }, + { + id: 'edge-11', + source: 'node-6', + target: 'node-12', + measure: { + name: 'DAU', + value: 600000000, + formattedValue: 6, + formattedUnit: '万', + }, + }, + { + id: 'edge-12', + source: 'node-6', + target: 'node-10', + measure: { + name: 'DAU', + value: 5000000000, + formattedValue: 50, + formattedUnit: '万', + }, + }, + ], +}; + +insertCss(` + .user-flow-node { + width: calc(100% - 32px); + height: calc(100% - 32px); + background-color: #f6f7f9; + border-radius: 8px; + padding: 16px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.1); + box-sizing: content-box; + } + + .user-flow-node-name { + font-size: 16px; + font-weight: bold; + color: #252525; + margin-bottom: 8px; + } + + .user-flow-node-metric { + font-size: 12px; + color: #666666; + display: flex; + justify-content: space-between; + } + + .user-flow-node-metric--value { + font-weight: bold; + } +`); + +const UserFlowNode = ({ data }) => { + const metrics = [ + { name: 'DAU', value: data.measure.formattedValue + data.measure.formattedUnit }, + { name: 'MAU', value: data.relatedMeasures[0].formattedValue + data.relatedMeasures[0].formattedUnit }, + ]; + + return ( +
+
{data.name}
+ {metrics.map((metric) => ( +
+
{metric.name}
+
{metric.value}
+
+ ))} +
+ ); +}; + +const transformData = (data) => { + const REF_NODE_IDS = ['node-5', 'node-6']; + const findNodeById = (id) => data.nodes.find((node) => node.id === id); + data.edges.forEach((edge) => { + edge.data ||= {}; + const isSplit = REF_NODE_IDS.includes(edge.source); + edge.data.type = isSplit ? 'split' : 'proportion'; + edge.data.ratio = edge.measure.value / findNodeById(isSplit ? edge.source : edge.target).measure.value; + }); + return data; +}; + +const DemoFlowDirectionGraph = () => { + const options = { + autoFit: 'view', + padding: 120, + data: transformData(data), + node: { + style: { + component: (data) => , + size: [160, 90], + }, + }, + edge: { + style: { + stroke: (d) => + d.data.type === 'split' ? 'l(0) 0:#F04864 0.5:#7EC2F3 1:#1890FF' : 'l(0) 0:#1890FF 0.5:#7EC2F3 1:#F04864', + labelText: (d) => { + const { type, ratio } = d.data; + const text = type === 'split' ? '分流' : '占比'; + return `${text} ${(Number(ratio) * 100).toFixed(2)}%`; + }, + labelBackground: true, + }, + }, + transforms: (prev) => [ + ...prev, + { + type: 'map-edge-line-width', + key: 'map-edge-line-width', + value: (d) => d.data.ratio, + minValue: 0, + maxValue: 1, + minLineWidth: 1, + maxLineWidth: 32, + }, + ], + layout: { + type: 'antv-dagre', + nodesep: -10, + ranksep: 100, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/flow-direction-graph/demo/default.js b/site/examples/relations/flow-direction-graph/demo/default.js new file mode 100644 index 000000000..2be5ba08d --- /dev/null +++ b/site/examples/relations/flow-direction-graph/demo/default.js @@ -0,0 +1,39 @@ +import { FlowDirectionGraph } from '@ant-design/graphs'; +import React, {useState, useEffect} from 'react'; +import ReactDOM from 'react-dom'; + +const DemoFlowDirectionGraph = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/flow-analysis.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'center', + data, + transforms: (prev) => [ + ...prev, + { + type: 'map-edge-line-width', + key: 'map-edge-line-width', + value: (d) => Math.random(), + minValue: 0, + maxValue: 1, + minLineWidth: 1, + maxLineWidth: 24, + }, + ], + layout: { + type: 'antv-dagre', + nodesep: 10, + ranksep: 60, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/flow-direction-graph/demo/meta.json b/site/examples/relations/flow-direction-graph/demo/meta.json new file mode 100644 index 000000000..0351c35af --- /dev/null +++ b/site/examples/relations/flow-direction-graph/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "default.js", + "title": { + "zh": "流向图", + "en": "Default Flow Direction Graph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*YHZzS6I5T1cAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom.js", + "title": { + "zh": "自定义流向图", + "en": "Custom Flow Direction Graph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*jOEPRKWxPE0AAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/relations/flow-direction-graph/index.en.md b/site/examples/relations/flow-direction-graph/index.en.md new file mode 100644 index 000000000..b56cb2293 --- /dev/null +++ b/site/examples/relations/flow-direction-graph/index.en.md @@ -0,0 +1,4 @@ +--- +title: Flow Direction Graph +order: 6 +--- diff --git a/site/examples/relations/flow-direction-graph/index.zh.md b/site/examples/relations/flow-direction-graph/index.zh.md new file mode 100644 index 000000000..91bf6798e --- /dev/null +++ b/site/examples/relations/flow-direction-graph/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 流向图 +order: 6 +--- diff --git a/site/docs/map-common/attribute/grid-style.en.md b/site/examples/relations/flow-graph/API.en.md similarity index 100% rename from site/docs/map-common/attribute/grid-style.en.md rename to site/examples/relations/flow-graph/API.en.md diff --git a/site/docs/map-common/attribute/map.en.md b/site/examples/relations/flow-graph/API.zh.md similarity index 100% rename from site/docs/map-common/attribute/map.en.md rename to site/examples/relations/flow-graph/API.zh.md diff --git a/site/examples/relations/flow-graph/demo/default.js b/site/examples/relations/flow-graph/demo/default.js new file mode 100644 index 000000000..a11bef2cc --- /dev/null +++ b/site/examples/relations/flow-graph/demo/default.js @@ -0,0 +1,22 @@ +import { FlowGraph } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const DemoFlowGraph = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/flow-analysis.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'center', + data, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/flow-graph/demo/hover-activate-chain.js b/site/examples/relations/flow-graph/demo/hover-activate-chain.js new file mode 100644 index 000000000..480883228 --- /dev/null +++ b/site/examples/relations/flow-graph/demo/hover-activate-chain.js @@ -0,0 +1,33 @@ +import { FlowGraph, RCNode } from '@ant-design/graphs'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const { TextNode } = RCNode; + +const DemoFlowGraph = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/flow-analysis.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'center', + data, + node: { + style: { + component: (d) => { + const isActive = d.states?.includes('active'); + return ; + }, + }, + }, + behaviors: (prev) => [...prev, 'hover-activate-chain'], + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/flow-graph/demo/meta.json b/site/examples/relations/flow-graph/demo/meta.json new file mode 100644 index 000000000..686a76f78 --- /dev/null +++ b/site/examples/relations/flow-graph/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "default.js", + "title": { + "zh": "流程图", + "en": "Default Flow Graph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*xldIRYgq6mAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "hover-activate-chain.js", + "title": { + "zh": "高亮元素及其所在链路", + "en": "Highlight Element and Its Chain" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*yGZwTKZwNaAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "task-scheduling-flow-graph.js", + "title": { + "zh": "任务调度流程图", + "en": "Task Scheduling Flow Graph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*yd-WSLmyxAkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "product-launch-flow-graph.js", + "title": { + "zh": "产品开通动线图", + "en": "Product Launch Flow Graph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*n9JgQIGi9BQAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/relations/flow-graph/demo/product-launch-flow-graph.js b/site/examples/relations/flow-graph/demo/product-launch-flow-graph.js new file mode 100644 index 000000000..b3a1d5073 --- /dev/null +++ b/site/examples/relations/flow-graph/demo/product-launch-flow-graph.js @@ -0,0 +1,190 @@ +import { FlowGraph } from '@ant-design/graphs'; +import insertCss from 'insert-css'; +import { isBoolean } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +insertCss(` + .step-card-wrapper { + height: 58px; + width: 120px; + background: #ecf2fe; + border-radius: 4px; + box-sizing: border-box; + padding: 6px 12px; + font-size: 10px; + font-weight: 500; + color: #252525; + display: flex; + flex-direction: column; + justify-content: center; + + .elapsed-time { + margin-top: 8px; + + .elapsed-time-title { + color: #aaa; + font-size: 8px; + } + } + } + + .step-group-card-wrapper { + width: inherit; + height: inherit; + border-radius: 4px; + box-sizing: border-box; + border: 1px solid #eee; + + .header { + height: 32px; + line-height: 32px; + background-color: #3875f7; + color: #fff; + border-radius: 4px 4px 0 0; + display: flex; + font-size: 10px; + padding: 0 12px; + gap: 2px; + + .header-content { + flex: 1; + display: flex; + justify-content: space-between; + + .elapsed-time { + display: flex; + gap: 2px; + font-size: 9px; + + &-title { + color: #acc7fb; + } + } + } + + .header-extra { + cursor: pointer; + width: fit-content; + color: #acc7fb; + } + } + + .header-collapsed { + border-radius: 4px; + } + + .step-card-group { + display: flex; + gap: 8px; + flex-direction: column; + align-items: center; + padding: 16px 0; + } + } +`); + +const StepCard = ({ name, elapsed_time }) => { + return ( +
+
{name}
+ {elapsed_time && ( +
+
80分位耗时
+
{elapsed_time}
+
+ )} +
+ ); +}; + +const StepGroupCard = (props) => { + const { name, elapsed_time, children, isCollapsed, toggleCollapse } = props; + return ( +
+
+
+
{name}
+ {elapsed_time && ( +
+
80分位耗时
+
{elapsed_time}
+
+ )} +
+
+ {isCollapsed ? '展开' : '收起'} +
+
+ {!isCollapsed && ( +
+ {children?.map((child, index) => ( + + ))} +
+ )} +
+ ); +}; + +function isGroupCollapsed(data) { + return isBoolean(data.style?.collapsed) ? data.style?.collapsed : data.data.status === 'finished'; +} + +function isSingleStep(data) { + return !data.data.children; +} + +const DemoFlowGraph = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/antd-charts/product-activation.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'view', + data, + node: { + style: { + component: function (data) { + if (isSingleStep(data)) return ; + const toggleCollapse = async () => { + const graph = this; + graph.updateNodeData([{ id: data.id, style: { collapsed: !isGroupCollapsed(data) } }]); + await graph.render(); + }; + return ; + }, + size: (data) => { + if (isSingleStep(data)) return [120, 58]; + const GAP = 8; + const height = isGroupCollapsed(data) ? 32 : 56 + (58 + GAP) * (data.data?.children?.length || 0); + return [200, height]; + }, + }, + }, + edge: { + style: { + lineWidth: 1, + labelBackground: true, + labelBackgroundOpacity: 1, + labelFill: '#aaa', + labelFontSize: 8, + labelFontWeight: 500, + labelText: (data) => (data.data?.elapsed_time ? `80分位耗时\n${data.data.elapsed_time}` : ''), + }, + }, + layout: { + type: 'dagre', + nodeSize: (data) => (isSingleStep(data) ? 160 : 400), + animation: false, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/flow-graph/demo/task-scheduling-flow-graph.js b/site/examples/relations/flow-graph/demo/task-scheduling-flow-graph.js new file mode 100644 index 000000000..aef60dd1b --- /dev/null +++ b/site/examples/relations/flow-graph/demo/task-scheduling-flow-graph.js @@ -0,0 +1,146 @@ +import { FlowGraph } from '@ant-design/graphs'; +import { Typography } from 'antd'; +import insertCss from 'insert-css'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +const { Text } = Typography; + +insertCss(` + .end-node { + width: inherit; + height: inherit; + box-sizing: border-box; + border: 1px solid #f1f5fe; + border-radius: 4px; + display: flex; + font-size: 12px; + } + .end-node-name { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + } + .end-node-type { + width: 40px; + background: #f1f5fe; + color: #808692; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + } + + .task-node { + width: inherit; + height: inherit; + border-radius: 4px; + color: #fff; + align-content: center; + box-sizing: border-box; + display: flex; + flex-direction: column; + } + + .task-node-title { + height: 50%; + } + + .task-node-name { + font-size: 12px; + padding-left: 6px; + color: #fff; + } + + .task-node-delay { + font-size: 12px; + padding-left: 6px; + color: #252525; + flex: 1; + align-content: center; + } + `); + +const EndNode = (props) => { + const { + data: { type, name }, + } = props; + return ( +
+
{type === 'source' ? '来源' : '结束'}
+
{name}
+
+ ); +}; + +const TaskNode = (props) => { + const { + data: { type, name, delay }, + isActive, + } = props; + + const colors = ['#1890ff', '#52c41a', '#ff4d4f']; + const lightColors = ['rgba(24, 144, 255, 0.5)', 'rgba(82, 196, 26, 0.5)', 'rgba(255, 77, 79, 0.5)']; + + const getColor = (colors) => (type === 'store' ? colors[0] : Number(delay) < 10 ? colors[1] : colors[2]); + + return ( +
+
+ + {name} + +
+ +
{`delay: ${delay}min`}
+
+ ); +}; + +const DemoFlowGraph = () => { + const [data, setData] = useState(undefined); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/antd-charts/task-scheduling.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'center', + data, + node: { + style: { + component: (d) => { + const isActive = d.states?.includes('active'); + const Component = ['source', 'target'].includes(d.data.type) ? EndNode : TaskNode; + return ; + }, + size: (d) => (['source', 'target'].includes(d.data.type) ? [120, 40] : [194, 58]), + }, + }, + edge: { + style: { + lineWidth: 1, + }, + state: { + active: { + stroke: '#1890ff', + halo: false, + }, + }, + }, + layout: { + type: 'dagre', + nodeSize: [194, 58], + nodesep: 40, + ranksep: 80, + }, + behaviors: (prev) => [...prev, 'hover-activate-chain'], + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/flow-graph/index.en.md b/site/examples/relations/flow-graph/index.en.md new file mode 100644 index 000000000..76d99bf6a --- /dev/null +++ b/site/examples/relations/flow-graph/index.en.md @@ -0,0 +1,4 @@ +--- +title: Flowchart +order: 7 +--- diff --git a/site/examples/relations/flow-graph/index.zh.md b/site/examples/relations/flow-graph/index.zh.md new file mode 100644 index 000000000..06d30eed4 --- /dev/null +++ b/site/examples/relations/flow-graph/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 流程图 +order: 7 +--- diff --git a/site/docs/map-common/attribute/path-color.en.md b/site/examples/relations/indented-tree/API.en.md similarity index 100% rename from site/docs/map-common/attribute/path-color.en.md rename to site/examples/relations/indented-tree/API.en.md diff --git a/site/docs/map-common/attribute/path-components.en.md b/site/examples/relations/indented-tree/API.zh.md similarity index 100% rename from site/docs/map-common/attribute/path-components.en.md rename to site/examples/relations/indented-tree/API.zh.md diff --git a/site/examples/relations/indented-tree/demo/auto-children.js b/site/examples/relations/indented-tree/demo/auto-children.js new file mode 100644 index 000000000..6a595f71f --- /dev/null +++ b/site/examples/relations/indented-tree/demo/auto-children.js @@ -0,0 +1,24 @@ +import { G6, IndentedTree } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoIndentedTree = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + mode: 'alternate', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/indented-tree/demo/boxed-type.js b/site/examples/relations/indented-tree/demo/boxed-type.js new file mode 100644 index 000000000..5dbb8da79 --- /dev/null +++ b/site/examples/relations/indented-tree/demo/boxed-type.js @@ -0,0 +1,24 @@ +import { G6, IndentedTree } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoIndentedTree = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + type: 'boxed', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/indented-tree/demo/collapse-expand.js b/site/examples/relations/indented-tree/demo/collapse-expand.js new file mode 100644 index 000000000..b925a3933 --- /dev/null +++ b/site/examples/relations/indented-tree/demo/collapse-expand.js @@ -0,0 +1,31 @@ +import { G6, IndentedTree } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoIndentedTree = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + type: 'boxed', + autoFit: 'view', + data, + transforms: (prev) => [ + ...prev.filter((transform) => transform.type !== 'collapse-expand-react-node'), + { + ...prev.find((transform) => transform.type === 'collapse-expand-react-node'), + enable: true, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/indented-tree/demo/left-children.js b/site/examples/relations/indented-tree/demo/left-children.js new file mode 100644 index 000000000..c4e941875 --- /dev/null +++ b/site/examples/relations/indented-tree/demo/left-children.js @@ -0,0 +1,24 @@ +import { G6, IndentedTree } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoIndentedTree = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + mode: 'left', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/indented-tree/demo/linear-type.js b/site/examples/relations/indented-tree/demo/linear-type.js new file mode 100644 index 000000000..0defb14e3 --- /dev/null +++ b/site/examples/relations/indented-tree/demo/linear-type.js @@ -0,0 +1,24 @@ +import { G6, IndentedTree } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoIndentedTree = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + type: 'linear', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/indented-tree/demo/meta.json b/site/examples/relations/indented-tree/demo/meta.json new file mode 100644 index 000000000..b0d58a44f --- /dev/null +++ b/site/examples/relations/indented-tree/demo/meta.json @@ -0,0 +1,57 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "right-children.js", + "title": { + "zh": "缩进树", + "en": "Indented Tree with Right Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*5AQcToF_ArQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "left-children.js", + "title": { + "zh": "缩进树-子节点左侧分布", + "en": "Indented Tree with Left Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-0joTLCuEdgAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "auto-children.js", + "title": { + "zh": "缩进树-子节点自动两侧分布", + "en": "Indented Tree with Auto Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*t1XJQ477x58AAAAAAAAAAAAADmJ7AQ/original" + }, + + { + "filename": "linear-type.js", + "title": { + "zh": "线条风格缩进树", + "en": "Indented Tree with Outlined Nodes" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*dATbQ4KqqJwAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "boxed-type.js", + "title": { + "zh": "方框风格缩进树", + "en": "Indented Tree with Boxed Nodes" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*JZZVT5PsWPQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "collapse-expand.js", + "title": { + "zh": "动态展开/收起子节点", + "en": "Expand/Collapse Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*F_2pQIwP5s4AAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/relations/indented-tree/demo/right-children.js b/site/examples/relations/indented-tree/demo/right-children.js new file mode 100644 index 000000000..d3dfae277 --- /dev/null +++ b/site/examples/relations/indented-tree/demo/right-children.js @@ -0,0 +1,23 @@ +import { G6, IndentedTree } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoIndentedTree = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/indented-tree/index.en.md b/site/examples/relations/indented-tree/index.en.md new file mode 100644 index 000000000..65f03ef5a --- /dev/null +++ b/site/examples/relations/indented-tree/index.en.md @@ -0,0 +1,4 @@ +--- +title: Indented Tree +order: 3 +--- diff --git a/site/examples/relations/indented-tree/index.zh.md b/site/examples/relations/indented-tree/index.zh.md new file mode 100644 index 000000000..d8fb172e9 --- /dev/null +++ b/site/examples/relations/indented-tree/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 缩进树 +order: 3 +--- diff --git a/site/docs/map-common/attribute/path-size.en.md b/site/examples/relations/mind-map/API.en.md similarity index 100% rename from site/docs/map-common/attribute/path-size.en.md rename to site/examples/relations/mind-map/API.en.md diff --git a/site/docs/map-common/attribute/path-style.en.md b/site/examples/relations/mind-map/API.zh.md similarity index 100% rename from site/docs/map-common/attribute/path-style.en.md rename to site/examples/relations/mind-map/API.zh.md diff --git a/site/examples/relations/mind-map/demo/auto-children.js b/site/examples/relations/mind-map/demo/auto-children.js new file mode 100644 index 000000000..26d3ef04f --- /dev/null +++ b/site/examples/relations/mind-map/demo/auto-children.js @@ -0,0 +1,23 @@ +import { G6, MindMap } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoMindMap = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/mind-map/demo/boxed-type.js b/site/examples/relations/mind-map/demo/boxed-type.js new file mode 100644 index 000000000..e1955ba24 --- /dev/null +++ b/site/examples/relations/mind-map/demo/boxed-type.js @@ -0,0 +1,24 @@ +import { G6, MindMap } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoMindMap = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + type: 'boxed', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/mind-map/demo/collapse-expand.js b/site/examples/relations/mind-map/demo/collapse-expand.js new file mode 100644 index 000000000..fd22aa7a4 --- /dev/null +++ b/site/examples/relations/mind-map/demo/collapse-expand.js @@ -0,0 +1,31 @@ +import { G6, MindMap } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoMindMap = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + type: 'boxed', + autoFit: 'view', + data, + transforms: (prev) => [ + ...prev.filter((transform) => transform.type !== 'collapse-expand-react-node'), + { + ...prev.find((transform) => transform.type === 'collapse-expand-react-node'), + enable: true, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/mind-map/demo/custom-node.js b/site/examples/relations/mind-map/demo/custom-node.js new file mode 100644 index 000000000..cb87f824d --- /dev/null +++ b/site/examples/relations/mind-map/demo/custom-node.js @@ -0,0 +1,37 @@ +import { G6, MindMap, RCNode, getNodeSide, measureTextSize } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData, idOf } = G6; +const { TextNode } = RCNode; + +const DemoMindMap = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + autoFit: 'view', + data, + node: { + style: { + component: (data) => { + return ; + }, + size: (data) => measureTextSize(idOf(data), [24, 16]), + dx: function (data) { + const side = getNodeSide(this, data); + const size = measureTextSize(idOf(data), [24, 16]); + return side === 'left' ? -size[0] : side === 'center' ? -size[0] / 2 : 0; + }, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/mind-map/demo/left-children.js b/site/examples/relations/mind-map/demo/left-children.js new file mode 100644 index 000000000..748d522f5 --- /dev/null +++ b/site/examples/relations/mind-map/demo/left-children.js @@ -0,0 +1,24 @@ +import { G6, MindMap } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoMindMap = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + mode: 'left', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/mind-map/demo/linear-type.js b/site/examples/relations/mind-map/demo/linear-type.js new file mode 100644 index 000000000..2260921c1 --- /dev/null +++ b/site/examples/relations/mind-map/demo/linear-type.js @@ -0,0 +1,24 @@ +import { G6, MindMap } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoMindMap = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + type: 'linear', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/mind-map/demo/meta.json b/site/examples/relations/mind-map/demo/meta.json new file mode 100644 index 000000000..d25522c0b --- /dev/null +++ b/site/examples/relations/mind-map/demo/meta.json @@ -0,0 +1,64 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "auto-children.js", + "title": { + "zh": "思维导图", + "en": "Mind Map with Auto Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1oVLTJtANRoAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "right-children.js", + "title": { + "zh": "思维导图-子节点右侧分布", + "en": "Mind Map with Right Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*KUJCT78B2p4AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "left-children.js", + "title": { + "zh": "思维导图-子节点左侧分布", + "en": "Mind Map with Left Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*dXyvTJ2dh4wAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "linear-type.js", + "title": { + "zh": "线条风格思维导图", + "en": "Mind Map with Outlined Nodes" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*K1f6T7pMh2AAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "boxed-type.js", + "title": { + "zh": "方框风格思维导图", + "en": "Mind Map with Boxed Nodes" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*cce0Sa7DR3cAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom-node.js", + "title": { + "zh": "自定义节点样式", + "en": "Mind Map with Custom Nodes" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*sB8PT7k8NmIAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "collapse-expand.js", + "title": { + "zh": "动态展开/收起子节点", + "en": "Expand/Collapse Children" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*5TNBR5YBjlYAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/relations/mind-map/demo/right-children.js b/site/examples/relations/mind-map/demo/right-children.js new file mode 100644 index 000000000..94d2b4d8b --- /dev/null +++ b/site/examples/relations/mind-map/demo/right-children.js @@ -0,0 +1,24 @@ +import { G6, MindMap } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { treeToGraphData } = G6; + +const DemoMindMap = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/algorithm-category.json') + .then((res) => res.json()) + .then((res) => setData(treeToGraphData(res))); + }, []); + + const options = { + mode: 'right', + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/mind-map/index.en.md b/site/examples/relations/mind-map/index.en.md new file mode 100644 index 000000000..0837d3e80 --- /dev/null +++ b/site/examples/relations/mind-map/index.en.md @@ -0,0 +1,4 @@ +--- +title: MindMap +order: 4 +--- diff --git a/site/examples/relations/mind-map/index.zh.md b/site/examples/relations/mind-map/index.zh.md new file mode 100644 index 000000000..8156c5215 --- /dev/null +++ b/site/examples/relations/mind-map/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 思维导图 +order: 4 +--- diff --git a/site/docs/map-common/attribute/radiation.en.md b/site/examples/relations/network-graph/API.en.md similarity index 100% rename from site/docs/map-common/attribute/radiation.en.md rename to site/examples/relations/network-graph/API.en.md diff --git a/site/docs/map-common/attribute/scale.en.md b/site/examples/relations/network-graph/API.zh.md similarity index 100% rename from site/docs/map-common/attribute/scale.en.md rename to site/examples/relations/network-graph/API.zh.md diff --git a/site/examples/relations/network-graph/demo/default.js b/site/examples/relations/network-graph/demo/default.js new file mode 100644 index 000000000..f11676b3b --- /dev/null +++ b/site/examples/relations/network-graph/demo/default.js @@ -0,0 +1,28 @@ +import { NetworkGraph } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const DemoNetworkGraph = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/graph.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'view', + data, + node: { + palette: { + field: 'group', + color: ['#D580FF', '#4292C6'], + }, + }, + animation: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/network-graph/demo/meta.json b/site/examples/relations/network-graph/demo/meta.json new file mode 100644 index 000000000..4851a4e16 --- /dev/null +++ b/site/examples/relations/network-graph/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "default.js", + "title": { + "zh": "网络图", + "en": "Default Network Graph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*q9AkRIF8fF4AAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/relations/network-graph/index.en.md b/site/examples/relations/network-graph/index.en.md new file mode 100644 index 000000000..3353a2aab --- /dev/null +++ b/site/examples/relations/network-graph/index.en.md @@ -0,0 +1,4 @@ +--- +title: Network Graph +order: 5 +--- diff --git a/site/examples/relations/network-graph/index.zh.md b/site/examples/relations/network-graph/index.zh.md new file mode 100644 index 000000000..ce9e2b4a3 --- /dev/null +++ b/site/examples/relations/network-graph/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 网络图 +order: 5 +--- diff --git a/site/docs/map-common/attribute/size.en.md b/site/examples/relations/organization-chart/API.en.md similarity index 100% rename from site/docs/map-common/attribute/size.en.md rename to site/examples/relations/organization-chart/API.en.md diff --git a/site/docs/map-common/attribute/state.en.md b/site/examples/relations/organization-chart/API.zh.md similarity index 100% rename from site/docs/map-common/attribute/state.en.md rename to site/examples/relations/organization-chart/API.zh.md diff --git a/site/examples/relations/organization-chart/demo/complex-node.js b/site/examples/relations/organization-chart/demo/complex-node.js new file mode 100644 index 000000000..0d4a60667 --- /dev/null +++ b/site/examples/relations/organization-chart/demo/complex-node.js @@ -0,0 +1,54 @@ +import { OrganizationChart, RCNode } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { OrganizationChartNode } = RCNode; + +const DemoOrganizationChart = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/organization-chart.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + padding: [40, 0, 0, 120], + autoFit: 'view', + data, + node: { + style: { + component: (d) => { + const { name, position, status } = d.data || {}; + const isActive = d.states?.includes('active'); + return ; + }, + size: [240, 80], + }, + }, + edge: { + style: { + radius: 16, + lineWidth: 2, + endArrow: true, + }, + }, + layout: { + type: 'antv-dagre', + nodesep: 24, + ranksep: -10, + }, + transforms: (prev) => [ + ...prev.filter((transform) => transform.type !== 'collapse-expand-react-node'), + { + ...prev.find((transform) => transform.type === 'collapse-expand-react-node'), + enable: true, + iconOffsetY: 24, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/organization-chart/demo/custom-icon.js b/site/examples/relations/organization-chart/demo/custom-icon.js new file mode 100644 index 000000000..f4632c138 --- /dev/null +++ b/site/examples/relations/organization-chart/demo/custom-icon.js @@ -0,0 +1,33 @@ +import { CollapseExpandIcon, OrganizationChart } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { ArrowCountIcon } = CollapseExpandIcon; + +const DemoOrganizationChart = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/organization-chart.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'view', + data, + transforms: (prev) => [ + ...prev.filter((transform) => transform.type !== 'collapse-expand-react-node'), + { + ...prev.find((transform) => transform.type === 'collapse-expand-react-node'), + enable: true, + iconRender: function (isCollapsed, data) { + return ; + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/organization-chart/demo/default.js b/site/examples/relations/organization-chart/demo/default.js new file mode 100644 index 000000000..c7c903f03 --- /dev/null +++ b/site/examples/relations/organization-chart/demo/default.js @@ -0,0 +1,21 @@ +import { OrganizationChart } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const DemoOrganizationChart = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/organization-chart.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + autoFit: 'view', + data, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/organization-chart/demo/horizontal.js b/site/examples/relations/organization-chart/demo/horizontal.js new file mode 100644 index 000000000..2e52e2091 --- /dev/null +++ b/site/examples/relations/organization-chart/demo/horizontal.js @@ -0,0 +1,55 @@ +import { OrganizationChart, RCNode } from '@ant-design/graphs'; +import React, { useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const { OrganizationChartNode } = RCNode; + +const DemoOrganizationChart = () => { + const [data, setData] = React.useState(); + + useEffect(() => { + fetch('https://assets.antv.antgroup.com/g6/organization-chart.json') + .then((res) => res.json()) + .then(setData); + }, []); + + const options = { + direction: 'horizontal', + padding: [40, 0, 0, 120], + autoFit: 'view', + data, + node: { + style: { + component: (d) => { + const { name, position, status } = d.data || {}; + const isActive = d.states?.includes('active'); + return ; + }, + size: [240, 80], + }, + }, + edge: { + style: { + radius: 16, + lineWidth: 2, + endArrow: true, + }, + }, + layout: { + type: 'antv-dagre', + nodesep: -10, + ranksep: 80, + }, + transforms: (prev) => [ + ...prev.filter((transform) => transform.type !== 'collapse-expand-react-node'), + { + ...prev.find((transform) => transform.type === 'collapse-expand-react-node'), + enable: true, + iconOffsetX: 12, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/relations/organization-chart/demo/meta.json b/site/examples/relations/organization-chart/demo/meta.json new file mode 100644 index 000000000..e6e370bde --- /dev/null +++ b/site/examples/relations/organization-chart/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "default.js", + "title": { + "zh": "组织结构图", + "en": "Default Organization Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*DrXgSICG9EsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "complex-node.js", + "title": { + "zh": "复杂节点的组织结构图", + "en": "Organization Chart with Complex Nodes" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*jgGCT7cMxg8AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "horizontal.js", + "title": { + "zh": "至左向右的组织结构图", + "en": "Organization Chart from Left to Right" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*9b1CQbVAYgMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom-icon.js", + "title": { + "zh": "自定义收起图标", + "en": "Custom Collapse Icon" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*N_DyQYva2YwAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/relations/organization-chart/index.en.md b/site/examples/relations/organization-chart/index.en.md new file mode 100644 index 000000000..e8990065f --- /dev/null +++ b/site/examples/relations/organization-chart/index.en.md @@ -0,0 +1,4 @@ +--- +title: Organization Chart +order: 4 +--- diff --git a/site/examples/relations/organization-chart/index.zh.md b/site/examples/relations/organization-chart/index.zh.md new file mode 100644 index 000000000..d3d223ff6 --- /dev/null +++ b/site/examples/relations/organization-chart/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 组织架构图 +order: 4 +--- diff --git a/site/examples/statistics/annotation-shape/API.en.md b/site/examples/statistics/annotation-shape/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/annotation-shape/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/annotation-shape/API.zh.md b/site/examples/statistics/annotation-shape/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/annotation-shape/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/annotation-shape/demo/interval-point.js b/site/examples/statistics/annotation-shape/demo/interval-point.js new file mode 100644 index 000000000..8f26f285f --- /dev/null +++ b/site/examples/statistics/annotation-shape/demo/interval-point.js @@ -0,0 +1,54 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoAnnotationShape = () => { + const data = [ + { x: 'Jan', tick: 9.3, value: 11.5 }, + { x: 'Feb', tick: 10.5, value: 12 }, + { x: 'Mar', tick: 11.2, value: 11.7 }, + { x: 'Apr', tick: 11.2, value: 12.4 }, + { x: 'May', tick: 12.7, value: 13.5 }, + { x: 'Jun', tick: 13.1, value: 11.9 }, + { x: 'Jul', tick: 12.2, value: 14.6 }, + { x: 'Aug', tick: 12.2, value: 17.2 }, + { x: 'Sep', tick: 10.1, value: 16.9 }, + { x: 'Oct', tick: 14.5, value: 15.4 }, + { x: 'Nov', tick: 14.5, value: 16.9 }, + { x: 'Dec', tick: 15.5, value: 17.2 }, + ]; + + const config = { + data, + xField: 'x', + yField: 'value', + paddingRight: 30, + sizeField: 20, + coordinate: { transform: [{ type: 'transpose' }] }, + axis: { x: { title: false } }, + labels: [ + { + text: 'value', + position: 'right', + formatter: (v) => `${v}min`, + dx: 4, + textAlign: 'start', + }, + ], + style: { fillOpacity: 0.65, lineWidth: 1 }, + annotations: [ + { + type: 'point', + xField: 'x', + yField: 'tick', + shapeField: 'line', + sizeField: 15, + style: { stroke: 'red' }, + tooltip: false, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/annotation-shape/demo/line-badge.js b/site/examples/statistics/annotation-shape/demo/line-badge.js new file mode 100644 index 000000000..f3e1421ab --- /dev/null +++ b/site/examples/statistics/annotation-shape/demo/line-badge.js @@ -0,0 +1,42 @@ +import { Line } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoAnnotationShape = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/blockchain.json', + transform: [ + { + type: 'fold', + fields: ['blockchain', 'nlp'], + key: 'type', + value: 'value', + }, + ], + }, + xField: (d) => new Date(d.date), + yField: 'value', + colorField: 'type', + axis: { x: { labelAutoHide: 'greedy' } }, + annotations: [ + { + type: 'text', + data: [new Date('2017-12-17'), 100], + shape: 'badge', + style: { + text: '100', + dy: -1, + markerSize: 24, + markerFill: '#6395FA', + markerFillOpacity: 0.55, + }, + tooltip: false, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/annotation-shape/demo/meta.json b/site/examples/statistics/annotation-shape/demo/meta.json new file mode 100644 index 000000000..0bbbe9630 --- /dev/null +++ b/site/examples/statistics/annotation-shape/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "interval-point.js", + "title": { + "zh": "点标记的条形图", + "en": "Interval, Point Annotation" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ypE2SbuV6kwAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "line-badge.js", + "title": { + "zh": "徽章标记的折线图", + "en": "Line, Badge Annotation" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*INjOS7ExVzYAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "watermark.js", + "title": { + "zh": "徽章水印", + "en": "Watermark" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*LA11Rqfk2Y4AAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/annotation-shape/demo/watermark.js b/site/examples/statistics/annotation-shape/demo/watermark.js new file mode 100644 index 000000000..e9e20a69e --- /dev/null +++ b/site/examples/statistics/annotation-shape/demo/watermark.js @@ -0,0 +1,88 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoAnnotationShape = () => { + const data = [ + { month: 'Jan.', profit: 387264, start: 0, end: 387264 }, + { month: 'Feb.', profit: 772096, start: 387264, end: 1159360 }, + { month: 'Mar.', profit: 638075, start: 1159360, end: 1797435 }, + { month: 'Apr.', profit: -211386, start: 1797435, end: 1586049 }, + { month: 'May', profit: -138135, start: 1586049, end: 1447914 }, + { month: 'Jun', profit: -267238, start: 1447914, end: 1180676 }, + { month: 'Jul.', profit: 431406, start: 1180676, end: 1612082 }, + { month: 'Aug.', profit: 363018, start: 1612082, end: 1975100 }, + { month: 'Sep.', profit: -224638, start: 1975100, end: 1750462 }, + { month: 'Oct.', profit: -299867, start: 1750462, end: 1450595 }, + { month: 'Nov.', profit: 607365, start: 1450595, end: 2057960 }, + { month: 'Dec.', profit: 1106986, start: 2057960, end: 3164946 }, + { month: 'Total', start: 0, end: 3164946 }, + ]; + + const config = { + data, + xField: 'month', + yField: ['end', 'start'], + colorField: (d) => (d.month === 'Total' ? 'Total' : d.profit > 0 ? 'Increase' : 'Decrease'), + axis: { y: { labelFormatter: '~s' } }, + tooltip: { + items: [ + { channel: 'y', valueFormatter: '~s' }, + { channel: 'y1', valueFormatter: '~s' }, + ], + }, + annotations: [ + { + type: 'shape', + style: { + x: '80%', + y: '70%', + render: ({ x, y }, context, d) => { + const { document } = context; + const g = document.createElement('g', {}); + const c1 = document.createElement('circle', { + style: { + cx: x, + cy: y, + lineWidth: 4, + r: 65, + stroke: 'red', + opacity: 0.3, + }, + }); + const c2 = document.createElement('circle', { + style: { + cx: x, + cy: y, + lineWidth: 2, + r: 50, + stroke: 'red', + opacity: 0.3, + }, + }); + const text = document.createElement('text', { + style: { + x, + y, + text: '数据保密', + transform: 'rotate(30)', + fontSize: 20, + fill: 'red', + textAlign: 'center', + textBaseline: 'middle', + fillOpacity: 0.3, + }, + }); + g.appendChild(c1); + g.appendChild(c2); + g.appendChild(text); + return g; + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/annotation-shape/index.en.md b/site/examples/statistics/annotation-shape/index.en.md new file mode 100644 index 000000000..6e1afd25b --- /dev/null +++ b/site/examples/statistics/annotation-shape/index.en.md @@ -0,0 +1,4 @@ +--- +title: Annotation Shape +order: 22 +--- \ No newline at end of file diff --git a/site/examples/statistics/annotation-shape/index.zh.md b/site/examples/statistics/annotation-shape/index.zh.md new file mode 100644 index 000000000..79c75c54b --- /dev/null +++ b/site/examples/statistics/annotation-shape/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 图形标注 +order: 22 +--- diff --git a/site/examples/statistics/area/API.en.md b/site/examples/statistics/area/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/area/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/area/API.zh.md b/site/examples/statistics/area/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/area/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/area/demo/area-difference.js b/site/examples/statistics/area/demo/area-difference.js new file mode 100644 index 000000000..81d8ab63a --- /dev/null +++ b/site/examples/statistics/area/demo/area-difference.js @@ -0,0 +1,37 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/temperature-compare.json', + transform: [ + { + type: 'fold', + fields: ['New York', 'San Francisco'], + key: 'city', + value: 'temperature', + }, + ], + }, + xField: (d) => new Date(d.date), + yField: 'temperature', + colorField: 'city', + shapeField: 'hvh', + diff: true, + scale: { + color: { range: ['#67a9cf', '#ef8a62'] }, + }, + line: { + yField: 'San Francisco', + style: { + stroke: '#000', + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/area-gradient.js b/site/examples/statistics/area/demo/area-gradient.js new file mode 100644 index 000000000..d688bed80 --- /dev/null +++ b/site/examples/statistics/area/demo/area-gradient.js @@ -0,0 +1,30 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/stocks.json', + transform: [{ type: 'filter', callback: (d) => d.symbol === 'GOOG' }], + }, + xField: (d) => new Date(d.date), + yField: 'price', + style: { + fill: 'linear-gradient(-90deg, white 0%, darkgreen 100%)', + }, + axis: { + y: { labelFormatter: '~s' }, + }, + line: { + style: { + stroke: 'darkgreen', + strokeWidth: 2, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/basic.js b/site/examples/statistics/area/demo/basic.js new file mode 100644 index 000000000..d6fc40cd5 --- /dev/null +++ b/site/examples/statistics/area/demo/basic.js @@ -0,0 +1,18 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/aapl.json', + }, + xField: (d) => new Date(d.date), + yField: 'close', + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/cascade-area.js b/site/examples/statistics/area/demo/cascade-area.js new file mode 100644 index 000000000..9e53bcb10 --- /dev/null +++ b/site/examples/statistics/area/demo/cascade-area.js @@ -0,0 +1,38 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/f38a8ad0-6e1f-4bb3-894c-7db50781fdec.json', + }, + xField: (d) => new Date(d.year), + yField: 'revenue', + seriesField: 'format', + colorField: 'group', + shapeField: 'smooth', + stack: { + orderBy: 'maxIndex', + reverse: true, + }, + axis: { + y: { labelFormatter: '~s' }, + }, + tooltip: { channel: 'y', valueFormatter: '.2f' }, + line: { + stack: { + orderBy: 'maxIndex', + reverse: true, + y: 'y1', + }, + style: { + stroke: 'white', + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/meta.json b/site/examples/statistics/area/demo/meta.json new file mode 100644 index 000000000..b62bfd63d --- /dev/null +++ b/site/examples/statistics/area/demo/meta.json @@ -0,0 +1,96 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "基础面积图", + "en": "Basic Area plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ZxtyTrhyN4sAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "missing-data-area.js", + "title": { + "zh": "数据缺失面积图", + "en": "missing data area plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ns2sS5S_VEAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "stacked-area.js", + "title": { + "zh": "堆叠面积图", + "en": "Stacked Area Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*z1rZToSDbZcAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "percentage-area.js", + "title": { + "zh": "归一化堆积面积图", + "en": "Normalized Area, MaxIndex" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*pC8lSIAE9g0AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "step-area.js", + "title": { + "zh": "阶梯面积图", + "en": "Step Area Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*NRD9RKPyOEsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "area-gradient.js", + "title": { + "zh": "渐变色面积图", + "en": "Gradient Area Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*VasvRpYGSLYAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "orderly-area.js", + "title": { + "zh": "彩条图", + "en": "Stripe Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*jyBQQo9Dr84AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "streamgraph.js", + "title": { + "zh": "河流图", + "en": "Streamgraph" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*q6vQRZf60uEAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "range-spline-area.js", + "title": { + "zh": "区间曲线面积图", + "en": "Range Spline Area Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*oLjrRb6l8CAAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "cascade-area.js", + "title": { + "zh": "出现顺序堆叠面积图", + "en": "Stacked Area" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*eLO6QLNTEAQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "area-difference.js", + "title": { + "zh": "差分面积图", + "en": "Area Difference" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ynkRQoVt2VAAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/area/demo/missing-data-area.js b/site/examples/statistics/area/demo/missing-data-area.js new file mode 100644 index 000000000..cb2d4b31f --- /dev/null +++ b/site/examples/statistics/area/demo/missing-data-area.js @@ -0,0 +1,22 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/aapl.json', + }, + xField: (d) => new Date(d.date), + yField: (d) => (new Date(d.date).getUTCMonth() <= 3 ? NaN : d.close), + connectNulls: { + connect: true, + connectFill: 'grey', + connectFillOpacity: 0.15, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/orderly-area.js b/site/examples/statistics/area/demo/orderly-area.js new file mode 100644 index 000000000..202c0ebc5 --- /dev/null +++ b/site/examples/statistics/area/demo/orderly-area.js @@ -0,0 +1,26 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/e58c9758-0a09-4527-aa90-fbf175b45925.json', + }, + xField: (d) => new Date(d.date), + yField: 'unemployed', + colorField: 'industry', + shapeField: 'smooth', + axis: { + x: { title: 'Date' }, + y: { labelFormatter: '~s' }, + }, + legend: { + color: { size: 72, autoWrap: true, maxRows: 3, cols: 6 }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/percentage-area.js b/site/examples/statistics/area/demo/percentage-area.js new file mode 100644 index 000000000..f1847c557 --- /dev/null +++ b/site/examples/statistics/area/demo/percentage-area.js @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/unemployment-by-industry.json', + }, + xField: (d) => new Date(d.date), + yField: 'unemployed', + colorField: 'industry', + normalize: true, + stack: true, + tooltip: { channel: 'y0', valueFormatter: '.0%' }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/range-spline-area.js b/site/examples/statistics/area/demo/range-spline-area.js new file mode 100644 index 000000000..ed9bac337 --- /dev/null +++ b/site/examples/statistics/area/demo/range-spline-area.js @@ -0,0 +1,46 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/range-spline-area.json', + transform: [ + { + type: 'map', + callback: ([x, low, high, v2, v3]) => ({ x, low, high, v2, v3 }), + }, + ], + }, + xField: 'x', + yField: ['low', 'high'], + shapeField: 'smooth', + style: { + fillOpacity: 0.5, + fill: '#64b5f6', + lineWidth: 1, + }, + axis: { + y: { title: false }, + }, + scale: { + x: { type: 'linear', tickCount: 10 }, + }, + point: { + yField: 'v2', + shapeField: 'point', + sizeField: 2, + }, + line: { + yField: 'v3', + style: { + stroke: '#FF6B3B', + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/stacked-area.js b/site/examples/statistics/area/demo/stacked-area.js new file mode 100644 index 000000000..3442712a7 --- /dev/null +++ b/site/examples/statistics/area/demo/stacked-area.js @@ -0,0 +1,20 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/unemployment-by-industry.json', + }, + xField: (d) => new Date(d.date), + yField: 'unemployed', + colorField: 'industry', + shapeField: 'smooth', + stack: true, // Try to remove this line. + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/step-area.js b/site/examples/statistics/area/demo/step-area.js new file mode 100644 index 000000000..11940b433 --- /dev/null +++ b/site/examples/statistics/area/demo/step-area.js @@ -0,0 +1,43 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: [ + { year: '1991', value: 15468 }, + { year: '1992', value: 16100 }, + { year: '1993', value: 15900 }, + { year: '1994', value: 17409 }, + { year: '1995', value: 17000 }, + { year: '1996', value: 31056 }, + { year: '1997', value: 31982 }, + { year: '1998', value: 32040 }, + { year: '1999', value: 33233 }, + ], + xField: 'year', + yField: 'value', + shapeField: 'hvh', + label: { + text: 'value', + style: { + fontSize: 10, + textAlign: (_, idx, arr) => { + if (idx === 0) return 'left'; + if (idx === arr.length - 1) return 'right'; + return 'center'; + }, + }, + }, + style: { + opacity: 0.4, + }, + axis: { + y: { labelFormatter: '~s' }, + }, + line: {}, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/demo/streamgraph.js b/site/examples/statistics/area/demo/streamgraph.js new file mode 100644 index 000000000..f1ec7ab4c --- /dev/null +++ b/site/examples/statistics/area/demo/streamgraph.js @@ -0,0 +1,20 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Area } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/unemployment-by-industry.json', + }, + xField: (d) => new Date(d.date), + yField: 'unemployed', + colorField: 'industry', + stack: true, + symmetry: true, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/area/index.en.md b/site/examples/statistics/area/index.en.md new file mode 100644 index 000000000..120f70425 --- /dev/null +++ b/site/examples/statistics/area/index.en.md @@ -0,0 +1,4 @@ +--- +title: Area +order: 1 +--- \ No newline at end of file diff --git a/site/examples/statistics/area/index.zh.md b/site/examples/statistics/area/index.zh.md new file mode 100644 index 000000000..b63642903 --- /dev/null +++ b/site/examples/statistics/area/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 面积图 +order: 1 +--- diff --git a/site/examples/statistics/bar/API.en.md b/site/examples/statistics/bar/API.en.md new file mode 100644 index 000000000..85b27cd8a --- /dev/null +++ b/site/examples/statistics/bar/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/bar/API.zh.md b/site/examples/statistics/bar/API.zh.md new file mode 100644 index 000000000..0e8937fd1 --- /dev/null +++ b/site/examples/statistics/bar/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/bar/demo/background.js b/site/examples/statistics/bar/demo/background.js new file mode 100644 index 000000000..3f2bb8267 --- /dev/null +++ b/site/examples/statistics/bar/demo/background.js @@ -0,0 +1,74 @@ +import { Bar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { + labelName: '蓝领', + value: 110, + }, + { + labelName: '白领', + value: 220, + }, + { + labelName: '制造业蓝领', + value: 330, + }, + { + labelName: '退休人员', + value: 440, + }, +]; + +const DemoBar = () => { + const config = { + data, + xField: 'labelName', + yField: 'value', + paddingRight: 80, + style: { + maxWidth: 25, + }, + markBackground: { + label: { + text: ({ originData }) => { + return `${(originData.value / 1000) * 100}% | ${originData.value}`; + }, + position: 'right', + dx: 80, + style: { + fill: '#aaa', + fillOpacity: 1, + fontSize: 14, + }, + }, + style: { + fill: '#eee', + }, + }, + scale: { + y: { + domain: [0, 1000], + }, + }, + axis: { + x: { + tick: false, + title: false, + }, + y: { + grid: false, + tick: false, + label: false, + title: false, + }, + }, + interaction: { + elementHighlight: false, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bar/demo/bar.js b/site/examples/statistics/bar/demo/bar.js new file mode 100644 index 000000000..9b7776e07 --- /dev/null +++ b/site/examples/statistics/bar/demo/bar.js @@ -0,0 +1,34 @@ +import { Bar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoBar = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/bar-bar.json', + }, + xField: 'letter', + yField: 'frequency', + sort: { + reverse: true, + }, + label: { + text: 'frequency', + formatter: '.1%', + style: { + textAlign: (d) => (+d.frequency > 0.008 ? 'right' : 'start'), + fill: (d) => (+d.frequency > 0.008 ? '#fff' : '#000'), + dx: (d) => (+d.frequency > 0.008 ? -5 : 5), + }, + }, + axis: { + y: { + labelFormatter: '.0%', + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bar/demo/gantt.js b/site/examples/statistics/bar/demo/gantt.js new file mode 100644 index 000000000..c4966f87c --- /dev/null +++ b/site/examples/statistics/bar/demo/gantt.js @@ -0,0 +1,27 @@ +import { Bar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoBar = () => { + + const events = [ + { name: 'event planning', startTime: 1, endTime: 4 }, + { name: 'layout logistics', startTime: 3, endTime: 13 }, + { name: 'select vendors', startTime: 5, endTime: 8 }, + { name: 'hire venue', startTime: 9, endTime: 13 }, + { name: 'hire caterer', startTime: 10, endTime: 14 }, + { name: 'hire event decorators', startTime: 12, endTime: 17 }, + { name: 'rehearsal', startTime: 14, endTime: 16 }, + { name: 'event celebration', startTime: 17, endTime: 18 }, + ]; + + const config = { + data: events, + xField: 'name', + yField: ['endTime', 'startTime'], + colorField: 'name' + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); \ No newline at end of file diff --git a/site/examples/statistics/bar/demo/meta.json b/site/examples/statistics/bar/demo/meta.json new file mode 100644 index 000000000..fa84488d1 --- /dev/null +++ b/site/examples/statistics/bar/demo/meta.json @@ -0,0 +1,56 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "bar.js", + "title": { + "zh": "柱形图", + "en": "Bar Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*eU1cRqm_fPAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "shape.js", + "title": { + "zh": "自定义条形图 shape", + "en": "Customize shape of bar plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*hB17Sq7hyuMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "stacked.js", + "title": { + "zh": "堆叠条形图", + "en": "Stacked Bar Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*LJi6RaswKdkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "normalized-stacked.js", + "title": { + "zh": "归一化条形图", + "en": "Normalized Bar Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*K_YBQ7TmxkQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "background.js", + "title": { + "zh": "背景条形图", + "en": "Background Bar Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*oDkdRodUnXwAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "gantt.js", + "title": { + "zh": "甘特图", + "en": "Gantt Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*y260TK0A4UQAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/bar/demo/normalized-stacked.js b/site/examples/statistics/bar/demo/normalized-stacked.js new file mode 100644 index 000000000..5cf3f4960 --- /dev/null +++ b/site/examples/statistics/bar/demo/normalized-stacked.js @@ -0,0 +1,34 @@ +import { Bar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoBar = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/bar-normalized-stacked.json', + }, + xField: 'state', + yField: 'population', + colorField: 'age', + stack: true, + normalize: true, + sort: { + reverse: true, + by: 'y', + }, + axis: { + y: { labelFormatter: '~s' }, + x: { + labelSpacing: 4, + style: { + labelTransform: 'rotate(90)', + }, + }, + }, + tooltip: { items: [{ channel: 'y0', valueFormatter: '.0%' }] }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bar/demo/shape.js b/site/examples/statistics/bar/demo/shape.js new file mode 100644 index 000000000..17f1f3672 --- /dev/null +++ b/site/examples/statistics/bar/demo/shape.js @@ -0,0 +1,27 @@ +import { Bar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { year: '1951 年', value: 38 }, + { year: '1952 年', value: 52 }, + { year: '1956 年', value: 61 }, + { year: '1957 年', value: 145 }, + { year: '1958 年', value: 48 }, +]; + +const DemoBar = () => { + const config = { + data, + xField: 'year', + yField: 'value', + shapeField: 'hollow', + colorField: 'year', + legend: { + color: { size: 72, autoWrap: true, maxRows: 3, cols: 6 }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bar/demo/stacked.js b/site/examples/statistics/bar/demo/stacked.js new file mode 100644 index 000000000..152b7c53a --- /dev/null +++ b/site/examples/statistics/bar/demo/stacked.js @@ -0,0 +1,32 @@ +import { Bar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoBar = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/bar-stacked.json', + }, + xField: 'state', + yField: 'population', + colorField: 'age', + stack: true, + sort: { + reverse: true, + by: 'y', + }, + axis: { + y: { labelFormatter: '~s' }, + x: { + labelSpacing: 4, + style: { + labelTransform: 'rotate(90)', + }, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bar/index.en.md b/site/examples/statistics/bar/index.en.md new file mode 100644 index 000000000..0e975f918 --- /dev/null +++ b/site/examples/statistics/bar/index.en.md @@ -0,0 +1,4 @@ +--- +title: Bar +order: 1 +--- \ No newline at end of file diff --git a/site/examples/statistics/bar/index.zh.md b/site/examples/statistics/bar/index.zh.md new file mode 100644 index 000000000..a3a0a685b --- /dev/null +++ b/site/examples/statistics/bar/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 条形图 +order: 1 +--- + + diff --git a/site/examples/statistics/base/API.en.md b/site/examples/statistics/base/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/base/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/base/API.zh.md b/site/examples/statistics/base/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/base/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/base/demo/circle.js b/site/examples/statistics/base/demo/circle.js new file mode 100644 index 000000000..87ca69400 --- /dev/null +++ b/site/examples/statistics/base/demo/circle.js @@ -0,0 +1,67 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Base } from '@ant-design/plots'; + +const Demobase = () => { + const config = { + type: 'facetCircle', + width: 480, + height: 480, + autoFit: false, + data: [ + { month: 'Jan.', name: 'A', value: 0.6326436603187056 }, + { month: 'Jan.', name: 'B', value: 0.9059036864077081 }, + { month: 'Jan.', name: 'C', value: 0.22780841416561715 }, + { month: 'Jan.', name: 'D', value: 0.1579683971505692 }, + { month: 'Feb.', name: 'A', value: 0.33301714406421823 }, + { month: 'Feb.', name: 'B', value: 0.03205686296291077 }, + { month: 'Feb.', name: 'C', value: 0.38611653432027015 }, + { month: 'Feb.', name: 'D', value: 0.7234835419120198 }, + { month: 'Mar.', name: 'A', value: 0.904928473886162 }, + { month: 'Mar.', name: 'B', value: 0.4484199491941676 }, + { month: 'Mar.', name: 'C', value: 0.2824508981652456 }, + { month: 'Mar.', name: 'D', value: 0.9685413602116679 }, + { month: 'Apr.', name: 'A', value: 0.041723574080341 }, + { month: 'Apr.', name: 'B', value: 0.8030787933582404 }, + { month: 'Apr.', name: 'C', value: 0.41748710621502005 }, + { month: 'Apr.', name: 'D', value: 0.5281546266115444 }, + { month: 'May', name: 'A', value: 0.8729036090146685 }, + { month: 'May', name: 'B', value: 0.28988839055401217 }, + { month: 'May', name: 'C', value: 0.33189556082639227 }, + { month: 'May', name: 'D', value: 0.21876873390293805 }, + { month: 'Jun.', name: 'A', value: 0.619594448441904 }, + { month: 'Jun.', name: 'B', value: 0.420356249903558 }, + { month: 'Jun.', name: 'C', value: 0.8796166275555974 }, + { month: 'Jun.', name: 'D', value: 0.6400454237168027 }, + { month: 'Jul.', name: 'A', value: 0.6908402378581739 }, + { month: 'Jul.', name: 'B', value: 0.12152124015288734 }, + { month: 'Jul.', name: 'C', value: 0.6033258688205794 }, + { month: 'Jul.', name: 'D', value: 0.5584958845688628 }, + { month: 'Aug.', name: 'A', value: 0.391095929118485 }, + { month: 'Aug.', name: 'B', value: 0.494137952382379 }, + { month: 'Aug.', name: 'C', value: 0.6116254958078564 }, + { month: 'Aug.', name: 'D', value: 0.5803641632635503 }, + { month: 'Sept.', name: 'A', value: 0.6506347276994731 }, + { month: 'Sept.', name: 'B', value: 0.8165757521460599 }, + { month: 'Sept.', name: 'C', value: 0.2279107933218536 }, + { month: 'Sept.', name: 'D', value: 0.37419172590345484 }, + { month: 'Oct.', name: 'A', value: 0.17980507555487946 }, + { month: 'Oct.', name: 'B', value: 0.8701220373856862 }, + { month: 'Oct.', name: 'C', value: 0.4737963124883502 }, + { month: 'Oct.', name: 'D', value: 0.7383798484457005 }, + { month: 'Nov.', name: 'A', value: 0.26679319143326663 }, + { month: 'Nov.', name: 'B', value: 0.15200589580375534 }, + { month: 'Nov.', name: 'C', value: 0.6648648719163961 }, + { month: 'Nov.', name: 'D', value: 0.5341976900165717 }, + { month: 'Dec.', name: 'A', value: 0.5889497642361026 }, + { month: 'Dec.', name: 'B', value: 0.7152071786469567 }, + { month: 'Dec.', name: 'C', value: 0.8096766390742625 }, + { month: 'Dec.', name: 'D', value: 0.8703522265977728 }, + ], + encode: { position: 'month' }, + children: [{ type: 'interval', encode: { x: 'name', y: 'value', color: 'name' } }], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/base/demo/facet-rect.js b/site/examples/statistics/base/demo/facet-rect.js new file mode 100644 index 000000000..ee677d83a --- /dev/null +++ b/site/examples/statistics/base/demo/facet-rect.js @@ -0,0 +1,44 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Base } from '@ant-design/plots'; + +const Demobase = () => { + const config = { + type: 'facetRect', + height: 640, + autoFit: false, + paddingLeft: 60, + paddingBottom: 60, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/penguins.json', + transform: [ + { + type: 'map', + callback: ({ culmen_depth_mm: depth, culmen_length_mm: length, ...d }) => ({ + ...d, + culmen_depth_mm: depth === 'NaN' ? NaN : depth, + culmen_length_mm: length === 'NaN' ? NaN : length, + }), + }, + ], + }, + encode: { x: 'sex', y: 'species' }, + children: [ + { + type: 'point', + frame: false, + encode: { x: 'culmen_depth_mm', y: 'culmen_length_mm' }, + style: { fill: '#ddd', strokeWidth: 0 }, + facet: false, + }, + { + type: 'point', + encode: { x: 'culmen_depth_mm', y: 'culmen_length_mm', color: 'island' }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/base/demo/matrix-col.js b/site/examples/statistics/base/demo/matrix-col.js new file mode 100644 index 000000000..5e7ca5fcb --- /dev/null +++ b/site/examples/statistics/base/demo/matrix-col.js @@ -0,0 +1,39 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Base } from '@ant-design/plots'; + +const Demobase = () => { + const config = { + type: 'repeatMatrix', + width: 300, + height: 720, + autoFit: false, + paddingLeft: 60, + paddingBottom: 60, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/weather.json', + transform: [ + { + type: 'map', + callback: ({ date, ...d }) => ({ + ...d, + date: new Date(date).getMonth() + '', + }), + }, + ], + }, + encode: { y: ['temp_max', 'precipitation', 'wind'], x: 'date' }, + children: [ + { + type: 'line', + encode: { color: 'location' }, + transform: [{ type: 'groupX', y: 'mean' }], + scale: { y: { zero: true } }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/base/demo/matrix.js b/site/examples/statistics/base/demo/matrix.js new file mode 100644 index 000000000..7d1466c70 --- /dev/null +++ b/site/examples/statistics/base/demo/matrix.js @@ -0,0 +1,44 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Base } from '@ant-design/plots'; + +const Demobase = () => { + const toNaN = (d) => (d === 'NaN' ? NaN : d); + const config = { + type: 'repeatMatrix', + width: 800, + height: 800, + autoFit: false, + paddingLeft: 70, + paddingBottom: 70, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/penguins.json', + transform: [ + { + type: 'map', + callback: ({ + culmen_depth_mm: cdepth, + culmen_length_mm: clength, + flipper_length_mm: flength, + body_mass_g: bmass, + ...d + }) => ({ + ...d, + culmen_depth_mm: toNaN(cdepth), + culmen_length_mm: toNaN(clength), + flipper_length_mm: toNaN(flength), + body_mass_g: toNaN(bmass), + }), + }, + ], + }, + encode: { + position: ['culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g'], + }, + children: [{ type: 'point', encode: { color: 'species' } }], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/base/demo/meta.json b/site/examples/statistics/base/demo/meta.json new file mode 100644 index 000000000..bd6c1f06c --- /dev/null +++ b/site/examples/statistics/base/demo/meta.json @@ -0,0 +1,56 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "space-layer.js", + "title": { + "zh": "层叠容器", + "en": "Space Layer" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*PuiQQZJXxNsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "space-flex.js", + "title": { + "zh": "弹性容器", + "en": "Space Flex" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*lLecQJkdPbIAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "facet-rect.js", + "title": { + "zh": "矩行分面", + "en": "Rect facet" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*gfDVQ7anJCMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "circle.js", + "title": { + "zh": "圆形分面", + "en": "Facet Circle" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Mp6ET7Ctw3UAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "matrix-col.js", + "title": { + "zh": "列重复矩阵", + "en": "Repeat Matrix, Col" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*MhTMTrLKT5UAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "matrix.js", + "title": { + "zh": "重复矩阵", + "en": "Repeat Matrix" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*nUQ-TK4x8AgAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/base/demo/space-flex.js b/site/examples/statistics/base/demo/space-flex.js new file mode 100644 index 000000000..a4573e9e9 --- /dev/null +++ b/site/examples/statistics/base/demo/space-flex.js @@ -0,0 +1,67 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Base } from '@ant-design/plots'; + +const Demobase = () => { + const config = { + type: 'spaceFlex', + width: 900, + autoFit: false, + ratio: [1, 2], + direction: 'col', + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/seattle-weather.json', + }, + children: [ + { + type: 'interval', + paddingBottom: 0, + paddingRight: 300, + encode: { + x: (d) => new Date(d.date).getUTCDate(), + y: 'temp_max', + color: 'steelblue', + }, + transform: [{ type: 'groupX', y: 'max' }], + axis: { x: false }, + }, + { + type: 'spaceFlex', + paddingBottom: 60, + ratio: [2, 1], + children: [ + { + type: 'cell', + paddingBottom: 60, + paddingRight: 0, + encode: { + x: (d) => new Date(d.date).getUTCDate(), + y: (d) => new Date(d.date).getUTCMonth(), + color: 'temp_max', + }, + transform: [{ type: 'group', color: 'max' }], + scale: { color: { palette: 'gnBu' } }, + style: { inset: 0.5 }, + axis: { x: { title: 'Date' }, y: { title: 'Month' } }, + legend: { color: false }, + }, + { + type: 'interval', + encode: { + x: (d) => new Date(d.date).getUTCMonth(), + y: 'temp_max', + color: 'steelblue', + }, + transform: [{ type: 'groupX', y: 'max' }], + coordinate: { transform: [{ type: 'transpose' }] }, + axis: { x: false }, + }, + ], + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/base/demo/space-layer.js b/site/examples/statistics/base/demo/space-layer.js new file mode 100644 index 000000000..9408b4c68 --- /dev/null +++ b/site/examples/statistics/base/demo/space-layer.js @@ -0,0 +1,36 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Base } from '@ant-design/plots'; + +const Demobase = () => { + const config = { + type: 'spaceLayer', + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/base-space-layer.json', + }, + children: [ + { + type: 'interval', + encode: { x: 'letter', y: 'frequency', color: 'letter' }, + transform: [{ type: 'sortX', reverse: true, by: 'y' }], + scale: { color: { palette: 'cool', offset: (t) => t * 0.8 + 0.1 } }, + }, + { + type: 'interval', + x: 300, + y: 50, + width: 300, + height: 300, + encode: { y: 'frequency', color: 'letter' }, + transform: [{ type: 'stackY' }], + scale: { color: { palette: 'cool', offset: (t) => t * 0.8 + 0.1 } }, + coordinate: { type: 'theta' }, + legend: false, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/base/index.en.md b/site/examples/statistics/base/index.en.md new file mode 100644 index 000000000..e177557cf --- /dev/null +++ b/site/examples/statistics/base/index.en.md @@ -0,0 +1,4 @@ +--- +title: Composite view +order: 16 +--- \ No newline at end of file diff --git a/site/examples/statistics/base/index.zh.md b/site/examples/statistics/base/index.zh.md new file mode 100644 index 000000000..590b0d59d --- /dev/null +++ b/site/examples/statistics/base/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 复合视图 +order: 16 +--- diff --git a/site/examples/statistics/bidirectional-bar/API.en.md b/site/examples/statistics/bidirectional-bar/API.en.md new file mode 100644 index 000000000..54832197b --- /dev/null +++ b/site/examples/statistics/bidirectional-bar/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/bidirectional-bar/API.zh.md b/site/examples/statistics/bidirectional-bar/API.zh.md new file mode 100644 index 000000000..a8d2ddbdb --- /dev/null +++ b/site/examples/statistics/bidirectional-bar/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/bidirectional-bar/demo/basic.js b/site/examples/statistics/bidirectional-bar/demo/basic.js new file mode 100644 index 000000000..62747e5ac --- /dev/null +++ b/site/examples/statistics/bidirectional-bar/demo/basic.js @@ -0,0 +1,67 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BidirectionalBar } from '@ant-design/plots'; + +const DemoBidirectionalBar = () => { + const data = [ + { + country: '乌拉圭', + '2016年耕地总面积': 13.4, + '2016年转基因种植面积': 12.3, + }, + { + country: '巴拉圭', + '2016年耕地总面积': 14.4, + '2016年转基因种植面积': 6.3, + }, + { + country: '南非', + '2016年耕地总面积': 18.4, + '2016年转基因种植面积': 8.3, + }, + { + country: '巴基斯坦', + '2016年耕地总面积': 34.4, + '2016年转基因种植面积': 13.8, + }, + { + country: '阿根廷', + '2016年耕地总面积': 44.4, + '2016年转基因种植面积': 19.5, + }, + { + country: '巴西', + '2016年耕地总面积': 24.4, + '2016年转基因种植面积': 18.8, + }, + { + country: '加拿大', + '2016年耕地总面积': 54.4, + '2016年转基因种植面积': 24.7, + }, + { + country: '中国', + '2016年耕地总面积': 104.4, + '2016年转基因种植面积': 5.3, + }, + { + country: '美国', + '2016年耕地总面积': 165.2, + '2016年转基因种植面积': 72.9, + }, + ]; + const config = { + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + style: { + fill: (d) => { + if (d.groupKey === '2016年转基因种植面积') return '#64DAAB'; + return '#6395FA'; + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bidirectional-bar/demo/layout.js b/site/examples/statistics/bidirectional-bar/demo/layout.js new file mode 100644 index 000000000..72dc3afd9 --- /dev/null +++ b/site/examples/statistics/bidirectional-bar/demo/layout.js @@ -0,0 +1,68 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BidirectionalBar } from '@ant-design/plots'; + +const DemoBidirectionalBar = () => { + const data = [ + { + country: '乌拉圭', + '2016年耕地总面积': 13.4, + '2016年转基因种植面积': 12.3, + }, + { + country: '巴拉圭', + '2016年耕地总面积': 14.4, + '2016年转基因种植面积': 6.3, + }, + { + country: '南非', + '2016年耕地总面积': 18.4, + '2016年转基因种植面积': 8.3, + }, + { + country: '巴基斯坦', + '2016年耕地总面积': 34.4, + '2016年转基因种植面积': 13.8, + }, + { + country: '阿根廷', + '2016年耕地总面积': 44.4, + '2016年转基因种植面积': 19.5, + }, + { + country: '巴西', + '2016年耕地总面积': 24.4, + '2016年转基因种植面积': 18.8, + }, + { + country: '加拿大', + '2016年耕地总面积': 54.4, + '2016年转基因种植面积': 24.7, + }, + { + country: '中国', + '2016年耕地总面积': 104.4, + '2016年转基因种植面积': 5.3, + }, + { + country: '美国', + '2016年耕地总面积': 165.2, + '2016年转基因种植面积': 72.9, + }, + ]; + const config = { + data, + xField: 'country', + layout: 'vertical', + style: { + fill: (d) => { + if (d.groupKey === '2016年转基因种植面积') return '#64DAAB'; + return '#6395FA'; + }, + }, + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bidirectional-bar/demo/meta.json b/site/examples/statistics/bidirectional-bar/demo/meta.json new file mode 100644 index 000000000..47a4feafe --- /dev/null +++ b/site/examples/statistics/bidirectional-bar/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "对称条形图", + "en": "Bidirectional Bar Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*19M9So-1OpoAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "layout.js", + "title": { + "zh": "垂直对称条形图", + "en": "Vertical Bidirectional Bar Chart" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*dpEiSZht_1AAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/site/examples/statistics/bidirectional-bar/index.en.md b/site/examples/statistics/bidirectional-bar/index.en.md new file mode 100644 index 000000000..89ee9ca7b --- /dev/null +++ b/site/examples/statistics/bidirectional-bar/index.en.md @@ -0,0 +1,4 @@ +--- +title: BidirectionalBar +order: 2 +--- diff --git a/site/examples/statistics/bidirectional-bar/index.zh.md b/site/examples/statistics/bidirectional-bar/index.zh.md new file mode 100644 index 000000000..5be7865ac --- /dev/null +++ b/site/examples/statistics/bidirectional-bar/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 对称条形图 +order: 2 +--- + + diff --git a/site/examples/statistics/box/API.en.md b/site/examples/statistics/box/API.en.md new file mode 100644 index 000000000..1869d0fd3 --- /dev/null +++ b/site/examples/statistics/box/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/box/API.zh.md b/site/examples/statistics/box/API.zh.md new file mode 100644 index 000000000..b8b0de5de --- /dev/null +++ b/site/examples/statistics/box/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/box/demo/basic.js b/site/examples/statistics/box/demo/basic.js new file mode 100644 index 000000000..edb655dd5 --- /dev/null +++ b/site/examples/statistics/box/demo/basic.js @@ -0,0 +1,32 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const data = [ + { x: 'Oceania', y: [1, 9, 16, 22, 24] }, + { x: 'East Europe', y: [1, 5, 8, 12, 16] }, + { x: 'Australia', y: [1, 8, 12, 19, 26] }, + { x: 'South America', y: [2, 8, 12, 21, 28] }, + { x: 'North Africa', y: [1, 8, 14, 18, 24] }, + { x: 'North America', y: [3, 10, 17, 28, 30] }, + { x: 'West Europe', y: [1, 7, 10, 17, 22] }, + { x: 'West Africa', y: [1, 6, 8, 13, 16] }, + ]; + + const config = { + data: { + value: data, + }, + xField: 'x', + yField: 'y', + style: { + stroke: '#545454', + fill: '#1890FF', + fillOpacity: 0.3, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/box-tooltip.js b/site/examples/statistics/box/demo/box-tooltip.js new file mode 100644 index 000000000..38c5eafda --- /dev/null +++ b/site/examples/statistics/box/demo/box-tooltip.js @@ -0,0 +1,37 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const data = [ + { x: 'Oceania', y: [1, 9, 16, 22, 24] }, + { x: 'East Europe', y: [1, 5, 8, 12, 16] }, + { x: 'Australia', y: [1, 8, 12, 19, 26] }, + { x: 'South America', y: [2, 8, 12, 21, 28] }, + { x: 'North Africa', y: [1, 8, 14, 18, 24] }, + { x: 'North America', y: [3, 10, 17, 28, 30] }, + { x: 'West Europe', y: [1, 7, 10, 17, 22] }, + { x: 'West Africa', y: [1, 6, 8, 13, 16] }, + ]; + + const config = { + data: { + value: data, + }, + xField: 'x', + yField: 'y', + scale: { x: { paddingInner: 0.6, paddingOuter: 0.3 }, y: { zero: true } }, + tooltip: { + items: [ + { name: '最低值', channel: 'y' }, + { name: '下四分位数', channel: 'y1' }, + { name: '最低值', channel: 'y2' }, + { name: '上四分位数', channel: 'y3' }, + { name: '最高值', channel: 'y4' }, + ], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/box.js b/site/examples/statistics/box/demo/box.js new file mode 100644 index 000000000..4039f9dcc --- /dev/null +++ b/site/examples/statistics/box/demo/box.js @@ -0,0 +1,38 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const data = [ + { x: 'Oceania', y: [1, 9, 16, 22, 24] }, + { x: 'East Europe', y: [1, 5, 8, 12, 16] }, + { x: 'Australia', y: [1, 8, 12, 19, 26] }, + { x: 'South America', y: [2, 8, 12, 21, 28] }, + { x: 'North Africa', y: [1, 8, 14, 18, 24] }, + { x: 'North America', y: [3, 10, 17, 28, 30] }, + { x: 'West Europe', y: [1, 7, 10, 17, 22] }, + { x: 'West Africa', y: [1, 6, 8, 13, 16] }, + ]; + + const config = { + data: { + value: data, + }, + xField: 'x', + yField: 'y', + colorField: 'x', + // 默认 boxType 为 box, box 预处理, boxplot 非预处理 + boxType: 'box', + legend: false, + style: { + stroke: 'black', + }, + scale: { + x: { paddingInner: 0.6, paddingOuter: 0.3 }, + y: { zero: true }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/boxplot-no-outlier.js b/site/examples/statistics/box/demo/boxplot-no-outlier.js new file mode 100644 index 000000000..c0f7a89bc --- /dev/null +++ b/site/examples/statistics/box/demo/boxplot-no-outlier.js @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/morley.json', + }, + boxType: 'boxplot', + xField: 'Expt', + yField: 'Speed', + style: { + point: false, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/boxplot-one-dimension.js b/site/examples/statistics/box/demo/boxplot-one-dimension.js new file mode 100644 index 000000000..d0204d81e --- /dev/null +++ b/site/examples/statistics/box/demo/boxplot-one-dimension.js @@ -0,0 +1,23 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const config = { + height: 120, + autoFit: false, + inset: 6, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/morley.json', + transform: [{ type: 'filter', callback: (d) => d.Expt === 1 }], + }, + boxType: 'boxplot', + yField: 'Speed', + coordinate: { transform: [{ type: 'transpose' }] }, + style: { boxFill: '#aaa', pointStroke: '#000' }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/boxplot-outlier.js b/site/examples/statistics/box/demo/boxplot-outlier.js new file mode 100644 index 000000000..5db4547e2 --- /dev/null +++ b/site/examples/statistics/box/demo/boxplot-outlier.js @@ -0,0 +1,18 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/morley.json', + }, + boxType: 'boxplot', + xField: 'Expt', + yField: 'Speed', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/boxplot-tooltip.js b/site/examples/statistics/box/demo/boxplot-tooltip.js new file mode 100644 index 000000000..b642164f9 --- /dev/null +++ b/site/examples/statistics/box/demo/boxplot-tooltip.js @@ -0,0 +1,34 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/morley.json', + }, + boxType: 'boxplot', + xField: 'Expt', + yField: 'Speed', + tooltip: { + boxTitle: { channel: 'y3' }, + boxItems: [ + { name: '最低值', channel: 'y' }, + { name: '下四分位数', channel: 'y1' }, + { name: '最低值', channel: 'y2' }, + { name: '上四分位数', channel: 'y3' }, + { name: '最高值', channel: 'y4' }, + ], + pointTitle: { channel: 'x' }, + pointItems: [{ channel: 'y', color: 'red', name: '异常点' }], + }, + style: { + pointFill: 'red', + pointStroke: 'red', + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/grouped-box.js b/site/examples/statistics/box/demo/grouped-box.js new file mode 100644 index 000000000..5fc3caaa8 --- /dev/null +++ b/site/examples/statistics/box/demo/grouped-box.js @@ -0,0 +1,101 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const data = [ + { + Species: 'I. setosa', + type: 'SepalLength', + value: 5.1, + bin: [4.3, 4.8, 5, 5.2, 5.8], + }, + { + Species: 'I. setosa', + type: 'SepalWidth', + value: 3.5, + bin: [2.3, 3.2, 3.4, 3.7, 4.4], + }, + { + Species: 'I. setosa', + type: 'PetalLength', + value: 1.4, + bin: [1, 1.4, 1.5, 1.6, 1.9], + }, + { + Species: 'I. setosa', + type: 'PetalWidth', + value: 0.2, + bin: [0.1, 0.2, 0.2, 0.3, 0.6], + }, + { + Species: 'I. versicolor', + type: 'SepalLength', + value: 7, + bin: [4.9, 5.6, 5.9, 6.3, 7], + }, + { + Species: 'I. versicolor', + type: 'SepalWidth', + value: 3.2, + bin: [2, 2.5, 2.8, 3, 3.4], + }, + { + Species: 'I. versicolor', + type: 'PetalLength', + value: 4.7, + bin: [3, 4, 4.35, 4.6, 5.1], + }, + { + Species: 'I. versicolor', + type: 'PetalWidth', + value: 1.4, + bin: [1, 1.2, 1.3, 1.5, 1.8], + }, + { + Species: 'I. virginica', + type: 'SepalLength', + value: 6.3, + bin: [4.9, 6.2, 6.5, 6.9, 7.9], + }, + { + Species: 'I. virginica', + type: 'SepalWidth', + value: 3.3, + bin: [2.2, 2.8, 3, 3.2, 3.8], + }, + { + Species: 'I. virginica', + type: 'PetalLength', + value: 6, + bin: [4.5, 5.1, 5.55, 5.9, 6.9], + }, + { + Species: 'I. virginica', + type: 'PetalWidth', + value: 2.5, + bin: [1.4, 1.8, 2, 2.3, 2.5], + }, + ]; + + const config = { + data: { + value: data, + }, + xField: 'type', + yField: 'bin', + colorField: 'Species', + seriesField: 'Species', + style: { + stroke: 'black', + }, + scale: { + x: { paddingInner: 0.2, paddingOuter: 0.1 }, + y: { zero: true }, + series: { paddingInner: 0.3, paddingOuter: 0.1 }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/grouped-boxplot-outlier.js b/site/examples/statistics/box/demo/grouped-boxplot-outlier.js new file mode 100644 index 000000000..35f206d61 --- /dev/null +++ b/site/examples/statistics/box/demo/grouped-boxplot-outlier.js @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/penguins.json', + }, + boxType: 'boxplot', + xField: 'species', + yField: 'flipper_length_mm', + colorField: 'sex', + seriesField: 'sex', + coordinate: { transform: [{ type: 'transpose' }] }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/demo/meta.json b/site/examples/statistics/box/demo/meta.json new file mode 100644 index 000000000..da4aee695 --- /dev/null +++ b/site/examples/statistics/box/demo/meta.json @@ -0,0 +1,88 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "基础箱线图", + "en": "Basic box plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*NDHsSaeEmxYAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "box.js", + "title": { + "zh": "预处理箱线图", + "en": "Boxplot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*A746SrYUxbsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "grouped-box.js", + "title": { + "zh": "预处理分组箱线图", + "en": "Grouped Boxplot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*90B8R433zSAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "boxplot-no-outlier.js", + "title": { + "zh": "无异常点箱线图", + "en": "Boxplot no outlier" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*xoCiQYXmOewAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "boxplot-outlier.js", + "title": { + "zh": "带异常点箱线图", + "en": "Boxplot with Outlier" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*yeZuSY9YIEAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "grouped-boxplot-outlier.js", + "title": { + "zh": "带异常点分组箱线图", + "en": "grouped Boxplot with Outlier" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*w565TbseqlMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "boxplot-one-dimension.js", + "title": { + "zh": "一维箱线图", + "en": "Boxplot one dimension" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ZOhpRaAAFpkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "polar-box.js", + "title": { + "zh": "径向箱线图", + "en": "Polar Boxplot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*G2DrSK5xIzsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "box-tooltip.js", + "title": { + "zh": "预处理箱线图 tooltip 配置", + "en": "Box tooltip" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*LeZrRoOmJxsAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "boxplot-tooltip.js", + "title": { + "zh": "箱线图 tooltip 配置", + "en": "Boxplot tooltip" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*KQ0ZTIRDDg0AAAAAAAAAAAAADo2bAQ/original" + } + ] +} diff --git a/site/examples/statistics/box/demo/polar-box.js b/site/examples/statistics/box/demo/polar-box.js new file mode 100644 index 000000000..6cc908d8d --- /dev/null +++ b/site/examples/statistics/box/demo/polar-box.js @@ -0,0 +1,33 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Box } from '@ant-design/plots'; + +const DemoBox = () => { + const data = [ + { x: 'Oceania', y: [1, 9, 16, 22, 24] }, + { x: 'East Europe', y: [1, 5, 8, 12, 16] }, + { x: 'Australia', y: [1, 8, 12, 19, 26] }, + { x: 'South America', y: [2, 8, 12, 21, 28] }, + { x: 'North Africa', y: [1, 8, 14, 18, 24] }, + { x: 'North America', y: [3, 10, 17, 28, 30] }, + { x: 'West Europe', y: [1, 7, 10, 17, 22] }, + { x: 'West Africa', y: [1, 6, 8, 13, 16] }, + ]; + + const config = { + data: { + value: data, + }, + xField: 'x', + yField: 'y', + colorField: 'x', + scale: { x: { paddingInner: 0.6, paddingOuter: 0.3 }, y: { zero: true } }, + coordinate: { type: 'polar', innerRadius: 0.2 }, + style: { stroke: 'black' }, + axis: { y: { tickCount: 5 } }, + legend: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/box/index.en.md b/site/examples/statistics/box/index.en.md new file mode 100644 index 000000000..efba1b20a --- /dev/null +++ b/site/examples/statistics/box/index.en.md @@ -0,0 +1,4 @@ +--- +title: Box +order: 3 +--- \ No newline at end of file diff --git a/site/examples/statistics/box/index.zh.md b/site/examples/statistics/box/index.zh.md new file mode 100644 index 000000000..5ee443674 --- /dev/null +++ b/site/examples/statistics/box/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 箱线图 +order: 3 +--- diff --git a/site/examples/statistics/bullet/API.en.md b/site/examples/statistics/bullet/API.en.md new file mode 100644 index 000000000..c79393a8e --- /dev/null +++ b/site/examples/statistics/bullet/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/bullet/API.zh.md b/site/examples/statistics/bullet/API.zh.md new file mode 100644 index 000000000..592f89c73 --- /dev/null +++ b/site/examples/statistics/bullet/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/bullet/demo/bullet-group.js b/site/examples/statistics/bullet/demo/bullet-group.js new file mode 100644 index 000000000..871a00cb8 --- /dev/null +++ b/site/examples/statistics/bullet/demo/bullet-group.js @@ -0,0 +1,44 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Bullet } from '@ant-design/plots'; + +const DemoBullet = () => { + const data = [ + { + title: '5🌟', + ranges: 100, + measures: 40, + targets: 85, + }, + { + title: '4🌟', + ranges: 100, + measures: 80, + targets: 40, + }, + { + title: '3🌟', + ranges: 100, + measures: 20, + targets: 22, + }, + { + title: '0-2🌟', + ranges: 100, + measures: 30, + targets: 10, + }, + ]; + + const color = { + targets: 'red', + }; + + const config = { + data, + color, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bullet/demo/bullet-more-cfg.js b/site/examples/statistics/bullet/demo/bullet-more-cfg.js new file mode 100644 index 000000000..4de28737c --- /dev/null +++ b/site/examples/statistics/bullet/demo/bullet-more-cfg.js @@ -0,0 +1,33 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Bullet } from '@ant-design/plots'; + +const DemoBullet = () => { + const config = { + data: [ + { + title: '满意度', + ranges: 100, + measures: 80, + targets: 85, + }, + ], + range: { + style: { + maxWidth: 50, + }, + }, + measure: { + style: { + lineWidth: 1, + stroke: '#fff', + }, + }, + target: { + sizeField: 40, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bullet/demo/bullet-vertical.js b/site/examples/statistics/bullet/demo/bullet-vertical.js new file mode 100644 index 000000000..d7624f861 --- /dev/null +++ b/site/examples/statistics/bullet/demo/bullet-vertical.js @@ -0,0 +1,20 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Bullet } from '@ant-design/plots'; + +const DemoBullet = () => { + const config = { + data: [ + { + title: '满意度', + ranges: 100, + measures: 80, + targets: 85, + }, + ], + layout: 'vertical', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bullet/demo/bullet.js b/site/examples/statistics/bullet/demo/bullet.js new file mode 100644 index 000000000..9262e1f23 --- /dev/null +++ b/site/examples/statistics/bullet/demo/bullet.js @@ -0,0 +1,20 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Bullet } from '@ant-design/plots'; + +const DemoBullet = () => { + const config = { + // default xField: 'title'、rangeField: 'ranges'、measureField: 'measures'、targetField: 'targets' + data: [ + { + title: '满意度', + ranges: 100, + measures: 80, + targets: 85, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bullet/demo/bullets-group.js b/site/examples/statistics/bullet/demo/bullets-group.js new file mode 100644 index 000000000..f9c4828ff --- /dev/null +++ b/site/examples/statistics/bullet/demo/bullets-group.js @@ -0,0 +1,47 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Bullet } from '@ant-design/plots'; + +const DemoBullet = () => { + const data = [ + { + title: '5🌟', + ranges: [100, 80], + measures: [50, 40], + targets: [85], + }, + { + title: '4🌟', + ranges: [100, 10], + measures: [12, 40], + targets: [40, 70], + }, + { + title: '3🌟', + ranges: [100], + measures: [20], + targets: [22], + }, + { + title: '0-2🌟', + ranges: [100], + measures: [30], + targets: [10], + }, + ]; + + const color = { + ranges: ['#FFbcb8', '#FFe0b0', '#bfeec8'], + measures: ['#5B8FF9', '#61DDAA'], + targets: ['#f0f'], + }; + + const config = { + data, + color, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bullet/demo/bullets.js b/site/examples/statistics/bullet/demo/bullets.js new file mode 100644 index 000000000..9494f290d --- /dev/null +++ b/site/examples/statistics/bullet/demo/bullets.js @@ -0,0 +1,36 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Bullet } from '@ant-design/plots'; + +const DemoBullet = () => { + const color = { + section: ['#bfeec8', '#FFe0b0', '#FFbcb8'], + score: ['#61DDAA', '#5B8FF9'], + target: '#39a3f4', + }; + + const config = { + data: [ + { + satisfaction: '满意度', + // 自动降序 + section: [40, 70, 100], + score: [30, 50], + target: [85], + }, + ], + color, + xField: 'satisfaction', + rangeField: 'section', + measureField: 'score', + targetField: 'target', + mapField: { + section: ['优', '良', '差'], + score: ['前段', '后段'], + target: ['目标'], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/bullet/demo/meta.json b/site/examples/statistics/bullet/demo/meta.json new file mode 100644 index 000000000..792e36e11 --- /dev/null +++ b/site/examples/statistics/bullet/demo/meta.json @@ -0,0 +1,56 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "bullet.js", + "title": { + "zh": "子弹图", + "en": "Bullet" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*R4hCRJwwMhgAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "bullets.js", + "title": { + "zh": "多指标子弹图", + "en": "Bullets" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*2VHuTLQBDJgAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "bullet-vertical.js", + "title": { + "zh": "子弹图 垂直方向", + "en": "Bullet vertical" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*8IYYQorPr_4AAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "bullet-group.js", + "title": { + "zh": "分组子弹图", + "en": "Bullet group" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*kAwGRZaRy3wAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "bullets-group.js", + "title": { + "zh": "分组多指标子弹图", + "en": "Bullets group" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*u_YoTKv82soAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "bullet-more-cfg.js", + "title": { + "zh": "子弹图 更多配置", + "en": "Bullet more Config" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*RI6-TYu5QaMAAAAAAAAAAAAADo2bAQ/original" + } + ] +} diff --git a/site/examples/statistics/bullet/index.en.md b/site/examples/statistics/bullet/index.en.md new file mode 100644 index 000000000..9da52243c --- /dev/null +++ b/site/examples/statistics/bullet/index.en.md @@ -0,0 +1,4 @@ +--- +title: Bullet +order: 3 +--- \ No newline at end of file diff --git a/site/examples/statistics/bullet/index.zh.md b/site/examples/statistics/bullet/index.zh.md new file mode 100644 index 000000000..3a74bcebc --- /dev/null +++ b/site/examples/statistics/bullet/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 子弹图 +order: 3 +--- diff --git a/site/examples/statistics/circle-packing/API.en.md b/site/examples/statistics/circle-packing/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/circle-packing/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/circle-packing/API.zh.md b/site/examples/statistics/circle-packing/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/circle-packing/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/circle-packing/demo/circlePacking.js b/site/examples/statistics/circle-packing/demo/circlePacking.js new file mode 100644 index 000000000..2063893b8 --- /dev/null +++ b/site/examples/statistics/circle-packing/demo/circlePacking.js @@ -0,0 +1,24 @@ +import { CirclePacking } from '@ant-design/plots'; +import { interpolateHcl } from 'd3-interpolate'; +import React from 'react'; +import ReactDOM from 'react-dom'; +const DemoCirclePacking = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/flare.json', + }, + valueField: 'value', + colorField: 'depth', + scale: { + color: { + domain: [0, 5], + range: ['hsl(152,80%,80%)', 'hsl(228,30%,40%)'], + interpolate: interpolateHcl, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/circle-packing/demo/circlePackingLabel.js b/site/examples/statistics/circle-packing/demo/circlePackingLabel.js new file mode 100644 index 000000000..3c2546eb4 --- /dev/null +++ b/site/examples/statistics/circle-packing/demo/circlePackingLabel.js @@ -0,0 +1,33 @@ +import { CirclePacking } from '@ant-design/plots'; +import { interpolateHcl } from 'd3-interpolate'; +import React from 'react'; +import ReactDOM from 'react-dom'; +const DemoCirclePackingLabel = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/flare.json', + }, + valueField: 'value', + colorField: 'depth', + scale: { + color: { + domain: [0, 5], + range: ['hsl(152,80%,80%)', 'hsl(228,30%,40%)'], + interpolate: interpolateHcl, + }, + }, + label: { + text: (d) => (d.height === 0 ? d.data.name : ''), + position: 'inside', + transform: [ + { + type: 'overflowHide', + }, + ], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/circle-packing/demo/circlePackingPadding.js b/site/examples/statistics/circle-packing/demo/circlePackingPadding.js new file mode 100644 index 000000000..c6a551741 --- /dev/null +++ b/site/examples/statistics/circle-packing/demo/circlePackingPadding.js @@ -0,0 +1,27 @@ +import { CirclePacking } from '@ant-design/plots'; +import { interpolateHcl } from 'd3-interpolate'; +import React from 'react'; +import ReactDOM from 'react-dom'; +const DemoCirclePackingPadding = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/flare.json', + }, + valueField: 'value', + colorField: 'depth', + scale: { + color: { + domain: [0, 5], + range: ['hsl(152,80%,80%)', 'hsl(228,30%,40%)'], + interpolate: interpolateHcl, + }, + }, + layout: { + padding: 10, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/circle-packing/demo/meta.json b/site/examples/statistics/circle-packing/demo/meta.json new file mode 100644 index 000000000..906390a03 --- /dev/null +++ b/site/examples/statistics/circle-packing/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "circlePacking.js", + "title": { + "zh": "基础捆绑图", + "en": "CirclePacking Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ZMEHT7WqQmIAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "circlePackingLabel.js", + "title": { + "zh": "捆绑图 - 显示Label", + "en": "CirclePacking Chart With Label" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*epG0TaxEVTsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "circlePackingPadding.js", + "title": { + "zh": "捆绑图 - 自定义padding距离", + "en": "Custom Padding Distance For CirclePacking Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*_4mJRL2Izd8AAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/circle-packing/index.en.md b/site/examples/statistics/circle-packing/index.en.md new file mode 100644 index 000000000..a55544128 --- /dev/null +++ b/site/examples/statistics/circle-packing/index.en.md @@ -0,0 +1,4 @@ +--- +title: Circle Packing +order: 13 +--- diff --git a/site/examples/statistics/circle-packing/index.zh.md b/site/examples/statistics/circle-packing/index.zh.md new file mode 100644 index 000000000..2765811fb --- /dev/null +++ b/site/examples/statistics/circle-packing/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 捆绑图 +order: 13 +--- diff --git a/site/examples/statistics/column/API.en.md b/site/examples/statistics/column/API.en.md new file mode 100644 index 000000000..158c417c8 --- /dev/null +++ b/site/examples/statistics/column/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/column/API.zh.md b/site/examples/statistics/column/API.zh.md new file mode 100644 index 000000000..60ad7061e --- /dev/null +++ b/site/examples/statistics/column/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/column/demo/25d.js b/site/examples/statistics/column/demo/25d.js new file mode 100644 index 000000000..89578ff3d --- /dev/null +++ b/site/examples/statistics/column/demo/25d.js @@ -0,0 +1,29 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { type: '1-3秒', value: 0.16 }, + { type: '4-10秒', value: 0.125 }, + { type: '11-30秒', value: 0.24 }, + { type: '31-60秒', value: 0.19 }, + { type: '1-3分', value: 0.22 }, + { type: '3-10分', value: 0.05 }, + { type: '10-30分', value: 0.01 }, + { type: '30+分', value: 0.015 }, +]; + +const DemoColumn = () => { + const config = { + data, + xField: 'type', + yField: 'value', + shapeField: 'column25D', + style: { + fill: 'rgba(126, 212, 236, 0.8)', + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/annotation-label.js b/site/examples/statistics/column/demo/annotation-label.js new file mode 100644 index 000000000..a2d7e6538 --- /dev/null +++ b/site/examples/statistics/column/demo/annotation-label.js @@ -0,0 +1,61 @@ +import { Column } from '@ant-design/plots'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { forEach, groupBy } from 'lodash'; + +const DemoColumn = () => { + const [data, setData] = useState([]); + + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/antfincdn/8elHX%26irfq/stack-column-data.json') + .then((response) => response.json()) + .then((json) => setData(json)) + .catch((error) => { + console.log('fetch data failed', error); + }); + }; + + useEffect(() => { + asyncFetch(); + }, []); + + const annotations = []; + forEach(groupBy(data, 'year'), (values, k) => { + const value = values.reduce((a, b) => a + b.value, 0); + annotations.push({ + type: 'text', + data: [k, value], + style: { + textAlign: 'center', + fontSize: 14, + fill: 'rgba(0,0,0,0.85)', + }, + xField: 'year', + yField: 'value', + style: { + text: `${value}`, + textBaseline: 'bottom', + position: 'top', + textAlign: 'center', + }, + tooltip: false, + }); + }); + + const config = { + data, + xField: 'year', + yField: 'value', + stack: true, + colorField: 'type', + label: { + text: 'value', + textBaseline: 'bottom', + position: 'inside', + }, + annotations, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/bar-dodged.js b/site/examples/statistics/column/demo/bar-dodged.js new file mode 100644 index 000000000..8f443bf5d --- /dev/null +++ b/site/examples/statistics/column/demo/bar-dodged.js @@ -0,0 +1,30 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/column-bar-dodged.json', + }, + xField: 'state', + yField: 'population', + colorField: 'age', + group: true, + sort: { + reverse: true, + by: 'y', + }, + axis: { + y: { labelFormatter: '~s' }, + }, + interaction: { + tooltip: { shared: true }, + elementHighlight: { background: true }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/change-data.js b/site/examples/statistics/column/demo/change-data.js new file mode 100644 index 000000000..c2d2b843e --- /dev/null +++ b/site/examples/statistics/column/demo/change-data.js @@ -0,0 +1,32 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DATA = [1, 2, 3, 4, 5, 6, 7, 8]; + +const DemoColumn = () => { + const [data, setData] = React.useState(DATA); + + React.useEffect(() => { + const time = setInterval(() => { + setData([ + ...DATA.sort(() => { + return Math.random() - 0.5; + }), + ]); + }, 2000); + return () => clearInterval(time); + }, []); + + const config = { + data: data.map((value) => ({ + index: value.toString(), + value, + })), + xField: 'index', + yField: 'value', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/color.js b/site/examples/statistics/column/demo/color.js new file mode 100644 index 000000000..ac1079aa7 --- /dev/null +++ b/site/examples/statistics/column/demo/color.js @@ -0,0 +1,44 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { type: '1-3秒', value: 0.16 }, + { type: '4-10秒', value: 0.125 }, + { type: '11-30秒', value: 0.24 }, + { type: '31-60秒', value: 0.19 }, + { type: '1-3分', value: 0.22 }, + { type: '3-10分', value: 0.05 }, + { type: '10-30分', value: 0.01 }, + { type: '30+分', value: 0.015 }, +]; + +const DemoColumn = () => { + const config = { + data, + xField: 'type', + yField: 'value', + style: { + fill: ({ type }) => { + if (type === '10-30分' || type === '30+分') { + return '#22CBCC'; + } + return '#2989FF'; + }, + }, + label: { + text: (originData) => { + const val = parseFloat(originData.value); + if (val < 0.05) { + return (val * 100).toFixed(1) + '%'; + } + return ''; + }, + offset: 10, + }, + legend: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/column-maxwidth.js b/site/examples/statistics/column/demo/column-maxwidth.js new file mode 100644 index 000000000..4c3d18b31 --- /dev/null +++ b/site/examples/statistics/column/demo/column-maxwidth.js @@ -0,0 +1,20 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Column } from '@ant-design/plots'; + +const DemoColumn = () => { + const config = { + data: [{ letter: 'A', frequency: 120 }], + xField: 'letter', + yField: 'frequency', + scale: { + x: { padding: 0.5 }, + }, + style: { + maxWidth: 200, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/column.js b/site/examples/statistics/column/demo/column.js new file mode 100644 index 000000000..9a580bb45 --- /dev/null +++ b/site/examples/statistics/column/demo/column.js @@ -0,0 +1,31 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/column-column.json', + }, + xField: 'letter', + yField: 'frequency', + label: { + text: (d) => `${(d.frequency * 100).toFixed(1)}%`, + textBaseline: 'bottom', + }, + axis: { + y: { + labelFormatter: '.0%', + }, + }, + style: { + // 圆角样式 + radiusTopLeft: 10, + radiusTopRight: 10, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/connect-area.js b/site/examples/statistics/column/demo/connect-area.js new file mode 100644 index 000000000..3fbb98790 --- /dev/null +++ b/site/examples/statistics/column/demo/connect-area.js @@ -0,0 +1,33 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antfincdn/8elHX%26irfq/stack-column-data.json', + }, + xField: 'year', + yField: 'value', + stack: true, + colorField: 'type', + label: { + text: 'value', + textBaseline: 'bottom', + position: 'inside', + }, + interaction: { + elementHighlightByColor: { + link: true, + }, + }, + state: { + active: { linkFill: 'rgba(0,0,0,0.25)', stroke: 'black', lineWidth: 0.5 }, + inactive: { opacity: 0.5 }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/conversion-tag.js b/site/examples/statistics/column/demo/conversion-tag.js new file mode 100644 index 000000000..63fe5929a --- /dev/null +++ b/site/examples/statistics/column/demo/conversion-tag.js @@ -0,0 +1,34 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: [ + { action: '浏览网站', pv: 50000 }, + { action: '放入购物车', pv: 35000 }, + { action: '生成订单', pv: 25000 }, + { action: '支付订单', pv: 15000 }, + { action: '完成交易', pv: 8500 }, + ], + xField: 'action', + yField: 'pv', + label: { + text: (d) => d.pv, + textBaseline: 'bottom', + }, + style: { + maxWidth: 50, + }, + conversionTag: { + size: 40, + spacing: 4, + text: { + formatter: (prev, next) => `${((next / prev) * 100).toFixed(1)}%`, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/dodge-padding.js b/site/examples/statistics/column/demo/dodge-padding.js new file mode 100644 index 000000000..1a7321595 --- /dev/null +++ b/site/examples/statistics/column/demo/dodge-padding.js @@ -0,0 +1,28 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antfincdn/iPY8JFnxdb/dodge-padding.json', + }, + xField: '月份', + yField: '月均降雨量', + colorField: 'name', + group: true, + style: { + // 矩形四个方向的内边距 + inset: 5, + // 矩形单个方向的内边距 + // insetLeft:5, + // insetRight:20, + // insetBottom:10 + // insetTop:10 + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/group-and-stack.js b/site/examples/statistics/column/demo/group-and-stack.js new file mode 100644 index 000000000..514662676 --- /dev/null +++ b/site/examples/statistics/column/demo/group-and-stack.js @@ -0,0 +1,62 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antfincdn/mor%26R5yBI9/stack-group-column.json', + }, + xField: 'product_type', + yField: 'order_amt', + seriesField: 'sex', + stack: { + groupBy: ['x', 'series'], + series: false, + }, + colorField: 'product_sub_type', + tooltip: (item) => { + return { origin: item }; + }, + interaction: { + tooltip: { + render: (e, { title, items }) => { + return ( +
+

{title}

+ {items.map((item) => { + const { name, color, origin } = item; + return ( +
+
+
+ + + {origin['product_sub_type']}-{name} + +
+ {origin['order_amt']} +
+
+ ); + })} +
+ ); + }, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/label-text.js b/site/examples/statistics/column/demo/label-text.js new file mode 100644 index 000000000..559b424f7 --- /dev/null +++ b/site/examples/statistics/column/demo/label-text.js @@ -0,0 +1,65 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { type: '1-3秒', value: 0.36 }, + { type: '4-10秒', value: 0.25 }, + { type: '11-30秒', value: 0.24 }, + { type: '31-60秒', value: 0.19 }, + { type: '1-3分', value: 0.12 }, + { type: '3-10分', value: 0.15 }, + { type: '10-30分', value: 0.16 }, + { type: '30+分', value: 0.1 }, +]; + +const DemoColumn = () => { + const chartRef = React.useRef(null); + + const medal = (datum, ranking) => { + if (ranking > 2) return datum; + const { chart } = chartRef.current; + const { document } = chart.getContext().canvas; + const group = document?.createElement('g', {}); + + const size = ranking === 0 ? 20 : 15; + const icon = document.createElement('image', { + style: { + src: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1NiMRKb2sfMAAAAAAAAAAAAADmJ7AQ/original', + width: size, + height: size, + anchor: '0.5 0.5', + }, + }); + const text = ['冠军🏆', '亚军🥈', '季军🥉'][ranking]; + const label = document.createElement('text', { + style: { + text, + fill: 'gray', + textAlign: 'center', + transform: `translate(0, 25)`, + }, + }); + + group.appendChild(icon); + group.appendChild(label); + return group; + }; + + const config = { + data, + xField: 'type', + yField: 'value', + colorField: 'type', + axis: { + x: { + size: 40, + labelFormatter: (datum, index) => medal(datum, index), + }, + }, + onReady: (plot) => (chartRef.current = plot), + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/legend.js b/site/examples/statistics/column/demo/legend.js new file mode 100644 index 000000000..24fbdda88 --- /dev/null +++ b/site/examples/statistics/column/demo/legend.js @@ -0,0 +1,80 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { type: '抖音', sold: 275 }, + { type: '快手', sold: 115 }, + { type: '小米', sold: 120 }, + { type: '微信', sold: 350 }, + { type: 'Keep', sold: 150 }, +]; + +const DemoColumn = () => { + const logo = [ + ['抖音', 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*8IXHQLvx9QkAAAAAAAAAAAAADmJ7AQ/original'], + ['快手', 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*swueRrrKvbcAAAAAAAAAAAAADmJ7AQ/original'], + ['小米', 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*79G3TIt3mBoAAAAAAAAAAAAADmJ7AQ/original'], + ['微信', 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*_ELBTJLp0dQAAAAAAAAAAAAADmJ7AQ/original'], + ['Keep', 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*JzbKRpFhR14AAAAAAAAAAAAADmJ7AQ/original'], + ]; + const chartRef = React.useRef(); + const config = { + data, + xField: 'type', + yField: 'sold', + colorField: 'type', + onReady: ({ chart }) => { + chartRef.current = chart; + }, + legend: { + color: { + itemMarker: (name, index) => () => { + const chart = chartRef.current; + const { canvas } = chart.getContext(); + const { document } = canvas; + window.c = chartRef.current; + const image = document.createElement('image', { + style: { + width: 20, + height: 20, + anchor: '0.5 0.5', + src: logo[index][1], + }, + }); + const tooltip = document.createElement('html', { + style: { + innerHTML: `

${name}

`, + fill: 'white', + stroke: '#ccc', + width: 80, + height: 30, + pointerEvents: 'none', + visibility: 'hidden', + }, + }); + canvas.appendChild(tooltip); + image.addEventListener('mousemove', (e) => { + tooltip.setPosition(e.x, e.y); + tooltip.style.visibility = 'visible'; + + console.log('move', e.target); + }); + image.addEventListener('mouseleave', (e) => { + tooltip.setPosition(0, 0); + tooltip.style.visibility = 'hidden'; + + console.log('leave', e.target); + }); + return image; + }, + itemMarkerSize: 40, + itemLabelText: (_, index) => logo[index][0], + maxRows: 1, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/meta.json b/site/examples/statistics/column/demo/meta.json new file mode 100644 index 000000000..fc8fd248b --- /dev/null +++ b/site/examples/statistics/column/demo/meta.json @@ -0,0 +1,168 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "column.js", + "title": { + "zh": "柱形图", + "en": "Column Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*5u8UTb6iifcAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "color.js", + "title": { + "zh": "自定义柱状图颜色", + "en": "Column plot color" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*yrKvQZUMaVoAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "column-maxwidth.js", + "title": { + "zh": "限制宽度的柱形图", + "en": "Column with maxWidth" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*j1LdS6KxxsAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "slider.js", + "title": { + "zh": "带缩略轴柱状图", + "en": "Column plot with slider" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*QhoHQ4QIts0AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "scrollbar.js", + "title": { + "zh": "带滚动条柱状图", + "en": "Column plot with scrollbar" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*9TgiRbYJBUsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "region-annotation.js", + "title": { + "zh": "带辅助框标注的基础柱状图", + "en": "Basic column plot with region annotation" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/ceFSs9FuNn/2922d5e4-df5f-4512-8f8f-6f2ec258c7b8.png" + }, + { + "filename": "range.js", + "title": { + "zh": "区间柱形图", + "en": "Range Column Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*OAr7RpZGGtwAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "group-and-stack.js", + "title": { + "zh": "分组堆叠柱状图", + "en": "Group and stack Column Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*PflbQJXr-WQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "bar-dodged.js", + "title": { + "zh": "分组条形图", + "en": "Dodged Bar Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*3-WvRqW_iPUAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "stacked.js", + "title": { + "zh": "堆叠柱形图", + "en": "Stacked Column Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*MMRnQbTjazcAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "connect-area.js", + "title": { + "zh": "自定义联通区域样式柱状图", + "en": "Stacked column plot with connectedArea" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*_f0VT6nfCE4AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "annotation-label.js", + "title": { + "zh": "借助图形标注展示总计 label", + "en": "Show total label with annotations" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ZdrbR4mdg9sAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "dodge-padding.js", + "title": { + "zh": "分组柱状图像素级组内柱子间距", + "en": "DodgePadding of grouped column plot" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/VL8xZlzwm5/2d96f1be-83c6-47c8-8a02-6023ecdbe035.png" + }, + { + "filename": "change-data.js", + "title": { + "zh": "数据更新", + "en": "Change Data" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*b5Q8R6DHJZMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "conversion-tag.js", + "title": { + "zh": "带转化率柱状图", + "en": "Column plot with conversion tag" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*YHpIRrooM4AAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "percent.js", + "title": { + "zh": "百分比柱状图", + "en": "Percent Column plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*3FTARIxIDHMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "label-text.js", + "title": { + "zh": "自定义坐标轴标签", + "en": "Customize axis labels" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*X_B0Q7Co9xEAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "tooltip.js", + "title": { + "zh": "自定义提示", + "en": "Customize tooltip" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*lZu1Q5vMEewAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "legend.js", + "title": { + "zh": "自定义Legend", + "en": "Customize legend" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*FFVDR4pgMPgAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "25d.js", + "title": { + "zh": "2.5D 柱状图", + "en": "2.5D Column Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*wXEWRIbUJ7cAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/column/demo/percent.js b/site/examples/statistics/column/demo/percent.js new file mode 100644 index 000000000..afa1d6553 --- /dev/null +++ b/site/examples/statistics/column/demo/percent.js @@ -0,0 +1,131 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Column } from '@ant-design/plots'; + +const data = [ + { + country: 'Asia', + year: '1750', + value: 502, + }, + { + country: 'Asia', + year: '1800', + value: 635, + }, + { + country: 'Asia', + year: '1850', + value: 809, + }, + { + country: 'Asia', + year: '1900', + value: 947, + }, + { + country: 'Asia', + year: '1950', + value: 1402, + }, + { + country: 'Asia', + year: '1999', + value: 3634, + }, + { + country: 'Asia', + year: '2050', + value: 5268, + }, + { + country: 'Africa', + year: '1750', + value: 106, + }, + { + country: 'Africa', + year: '1800', + value: 107, + }, + { + country: 'Africa', + year: '1850', + value: 111, + }, + { + country: 'Africa', + year: '1900', + value: 133, + }, + { + country: 'Africa', + year: '1950', + value: 221, + }, + { + country: 'Africa', + year: '1999', + value: 767, + }, + { + country: 'Africa', + year: '2050', + value: 1766, + }, + { + country: 'Europe', + year: '1750', + value: 163, + }, + { + country: 'Europe', + year: '1800', + value: 203, + }, + { + country: 'Europe', + year: '1850', + value: 276, + }, + { + country: 'Europe', + year: '1900', + value: 408, + }, + { + country: 'Europe', + year: '1950', + value: 547, + }, + { + country: 'Europe', + year: '1999', + value: 729, + }, + { + country: 'Europe', + year: '2050', + value: 628, + }, +]; + +const DemoArea = () => { + const config = { + data, + xField: 'year', + yField: 'value', + colorField: 'country', + percent: true, + stack: true, + interaction: { + tooltip: { + shared: true, + }, + }, + tooltip: { channel: 'y0', valueFormatter: '.0%' }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/range.js b/site/examples/statistics/column/demo/range.js new file mode 100644 index 000000000..4aa31d95f --- /dev/null +++ b/site/examples/statistics/column/demo/range.js @@ -0,0 +1,37 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const EPSILON = 1e-6; + +const DemoColumn = () => { + const config = { + data: [ + { month: 'Jan.', profit: 387264, start: 0, end: 387264 }, + { month: 'Feb.', profit: 772096, start: 387264, end: 1159360 }, + { month: 'Mar.', profit: 638075, start: 1159360, end: 1797435 }, + { month: 'Apr.', profit: -211386, start: 1797435, end: 1586049 }, + { month: 'May', profit: -138135, start: 1586049, end: 1447914 }, + { month: 'Jun', profit: -267238, start: 1447914, end: 1180676 }, + { month: 'Jul.', profit: 431406, start: 1180676, end: 1612082 }, + { month: 'Aug.', profit: 363018, start: 1612082, end: 1975100 }, + { month: 'Sep.', profit: -224638, start: 1975100, end: 1750462 }, + { month: 'Oct.', profit: -299867, start: 1750462, end: 1450595 }, + { month: 'Nov.', profit: 607365, start: 1450595, end: 2057960 }, + { month: 'Dec.', profit: 1106986, start: 2057960, end: 3164946 }, + { month: 'Total', start: 0, end: 3164946 }, + ], + xField: 'month', + yField: ['start', 'end'], + colorField: (d) => (d.month === 'Total' ? 'Total' : d.profit > 0 ? 'Increase' : 'Decrease'), + axis: { + y: { labelFormatter: '~s' }, + }, + tooltip: { + items: ['start', 'end'], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/region-annotation.js b/site/examples/statistics/column/demo/region-annotation.js new file mode 100644 index 000000000..4267cd473 --- /dev/null +++ b/site/examples/statistics/column/demo/region-annotation.js @@ -0,0 +1,47 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { month: '1', value: 1078 }, + { month: '2', value: 1216 }, + { month: '3', value: 758 }, + { month: '4', value: 623 }, + { month: '5', value: 319 }, + { month: '6', value: 422 }, + { month: '7', value: -4 }, + { month: '8', value: -217 }, + { month: '9', value: -358 }, + { month: '10', value: 1513 }, + { month: '11', value: 1388 }, + { month: '12', value: 597 }, +]; + +const DemoColumn = () => { + const config = { + data, + xField: 'month', + yField: 'value', + scale: { + y: { + domainMax: 2000, + domainMin: -1000, + }, + }, + axis: { + x: { + labelFormatter: (val) => `${val} 月`, + }, + }, + annotations: [ + { + type: 'rangeX', + data: [{ month: ['7', '9'] }], + xField: 'month', + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/scrollbar.js b/site/examples/statistics/column/demo/scrollbar.js new file mode 100644 index 000000000..2057499e4 --- /dev/null +++ b/site/examples/statistics/column/demo/scrollbar.js @@ -0,0 +1,22 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Column } from '@ant-design/plots'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/be63e0a2-d2be-4c45-97fd-c00f752a66d4.json', + }, + xField: '城市', + yField: '销售额', + scrollbar: { + x: { + ratio: 0.05, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/slider.js b/site/examples/statistics/column/demo/slider.js new file mode 100644 index 000000000..927d3638b --- /dev/null +++ b/site/examples/statistics/column/demo/slider.js @@ -0,0 +1,28 @@ +import React, { useEffect, useRef } from 'react'; +import ReactDOM from 'react-dom'; +import { Column } from '@ant-design/plots'; + +const DemoColumn = () => { + const chartRef = useRef(); + useEffect(() => { + console.log({ chartRef }); + if (chartRef.current) { + } + }, []); + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/be63e0a2-d2be-4c45-97fd-c00f752a66d4.json', + }, + xField: '城市', + yField: '销售额', + slider: { + x: { + values: [0.1, 0.2], + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/stacked.js b/site/examples/statistics/column/demo/stacked.js new file mode 100644 index 000000000..24d9ce406 --- /dev/null +++ b/site/examples/statistics/column/demo/stacked.js @@ -0,0 +1,32 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/column-stacked.json', + }, + xField: 'state', + yField: 'population', + colorField: 'age', + stack: true, + sort: { + reverse: true, + by: 'y', + }, + axis: { + y: { labelFormatter: '~s' }, + x: { + labelSpacing: 4, + style: { + labelTransform: 'rotate(90)', + }, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/demo/tooltip.js b/site/examples/statistics/column/demo/tooltip.js new file mode 100644 index 000000000..d8742ec23 --- /dev/null +++ b/site/examples/statistics/column/demo/tooltip.js @@ -0,0 +1,53 @@ +import { Column } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoColumn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antfincdn/8elHX%26irfq/stack-column-data.json', + }, + xField: 'year', + yField: 'value', + colorField: 'type', + stack: true, + interaction: { + tooltip: { + render: (e, { title, items }) => { + return ( +
+

{title}

+ {items.map((item) => { + const { name, value, color } = item; + return ( +
+
+
+ + {name} +
+ {value} +
+
+ ); + })} +
+ ); + }, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/column/index.en.md b/site/examples/statistics/column/index.en.md new file mode 100644 index 000000000..f8ad6a3b5 --- /dev/null +++ b/site/examples/statistics/column/index.en.md @@ -0,0 +1,4 @@ +--- +title: Column +order: 1 +--- \ No newline at end of file diff --git a/site/examples/statistics/column/index.zh.md b/site/examples/statistics/column/index.zh.md new file mode 100644 index 000000000..7a5eb1d8e --- /dev/null +++ b/site/examples/statistics/column/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 柱状图 +order: 1 +--- + +柱状图用于描述分类数据之间的对比,如果我们把时间周期,如周、月、年,也理解为一种分类数据 (time category),那么柱状图也可以用于描述时间周期之间的数值比较。 \ No newline at end of file diff --git a/site/examples/statistics/dual-axes/API.en.md b/site/examples/statistics/dual-axes/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/dual-axes/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/dual-axes/API.zh.md b/site/examples/statistics/dual-axes/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/dual-axes/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/dual-axes/demo/basic.js b/site/examples/statistics/dual-axes/demo/basic.js new file mode 100644 index 000000000..c17d6cb7e --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/basic.js @@ -0,0 +1,43 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const config = { + xField: 'time', + data: [ + { time: '10:10', call: 4, waiting: 2, people: 2 }, + { time: '10:15', call: 2, waiting: 6, people: 3 }, + { time: '10:20', call: 13, waiting: 2, people: 5 }, + { time: '10:25', call: 9, waiting: 9, people: 1 }, + { time: '10:30', call: 5, waiting: 2, people: 3 }, + { time: '10:35', call: 8, waiting: 2, people: 1 }, + { time: '10:40', call: 13, waiting: 1, people: 2 }, + ], + legend: { + color: { + itemMarker: (v) => { + if (v === 'waiting') return 'rect' + return 'smooth' + } + } + }, + children: [ + { + type: 'interval', + yField: 'waiting', + }, + { + type: 'line', + yField: 'people', + shapeField: 'smooth', + scale: { color: { relations: [['people', '#fdae6b']] } }, + axis: { y: { position: 'right' } }, + style: { lineWidth: 2 }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/column-line.js b/site/examples/statistics/dual-axes/demo/column-line.js new file mode 100644 index 000000000..afca8e156 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/column-line.js @@ -0,0 +1,35 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { time: '2019-03', value: 350, count: 800 }, + { time: '2019-04', value: 900, count: 600 }, + { time: '2019-05', value: 300, count: 400 }, + { time: '2019-06', value: 450, count: 380 }, + { time: '2019-07', value: 470, count: 220 }, + ]; + + const config = { + data, + xField: 'time', + legend: true, + children: [ + { + type: 'interval', + yField: 'value', + style: { maxWidth: 80 }, + }, + { + type: 'line', + yField: 'count', + style: { lineWidth: 2 }, + axis: { y: { position: 'right' } }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/column-multi-line.js b/site/examples/statistics/dual-axes/demo/column-multi-line.js new file mode 100644 index 000000000..f79077d5a --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/column-multi-line.js @@ -0,0 +1,58 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvData = [ + { time: '2019-03', value: 35 }, + { time: '2019-04', value: 90 }, + { time: '2019-05', value: 30 }, + { time: '2019-06', value: 45 }, + { time: '2019-07', value: 47 }, + ]; + + const transformData = [ + { time: '2019-03', count: 800, name: 'a' }, + { time: '2019-04', count: 600, name: 'a' }, + { time: '2019-05', count: 400, name: 'a' }, + { time: '2019-06', count: 380, name: 'a' }, + { time: '2019-07', count: 220, name: 'a' }, + { time: '2019-03', count: 750, name: 'b' }, + { time: '2019-04', count: 650, name: 'b' }, + { time: '2019-05', count: 450, name: 'b' }, + { time: '2019-06', count: 400, name: 'b' }, + { time: '2019-07', count: 320, name: 'b' }, + { time: '2019-03', count: 900, name: 'c' }, + { time: '2019-04', count: 600, name: 'c' }, + { time: '2019-05', count: 450, name: 'c' }, + { time: '2019-06', count: 300, name: 'c' }, + { time: '2019-07', count: 200, name: 'c' }, + ]; + + const config = { + xField: 'time', + legend: true, + scale: { color: { range: ['#1783FF', '#5AD8A6', '#5D7092', '#F6BD16'] } }, + interaction: { tooltip: { sort: (d) => ['value', 'a', 'b', 'c'].indexOf(d.name) } }, + children: [ + { + data: uvData, + type: 'interval', + yField: 'value', + style: { maxWidth: 80 }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + colorField: 'name', + seriesField: 'name', + axis: { y: { position: 'right' } }, + style: { lineWidth: 2 }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/custom-column-line.js b/site/examples/statistics/dual-axes/demo/custom-column-line.js new file mode 100644 index 000000000..c7a036a06 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/custom-column-line.js @@ -0,0 +1,67 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { time: '2019-03', value: 350, count: 800 }, + { time: '2019-04', value: 900, count: 600 }, + { time: '2019-05', value: 300, count: 400 }, + { time: '2019-06', value: 450, count: 380 }, + { time: '2019-07', value: 470, count: 220 }, + ]; + + const config = { + data, + xField: 'time', + legend: true, + children: [ + { + type: 'interval', + yField: 'value', + style: { maxWidth: 80 }, + label: { position: 'inside' }, + interaction: { + elementHighlight: true, + elementHighlight: { background: true }, + }, + }, + { + type: 'line', + yField: 'count', + shapeField: 'smooth', + style: { lineWidth: 2 }, + axis: { y: false }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + annotations: [ + { + type: 'text', + data: ['2019-05', 300], + style: { + text: '2019-05, 发布新版本', + dy: -30, + fill: '#2C3542', + fillOpacity: 0.65, + fontSize: 10, + background: true, + backgroundRadius: 4, + connector: true, + startMarker: true, + startMarkerSize: 6, + startMarkerFill: '#fff', + }, + tooltip: false, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/custom-dual-line.js b/site/examples/statistics/dual-axes/demo/custom-dual-line.js new file mode 100644 index 000000000..487aa5d65 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/custom-dual-line.js @@ -0,0 +1,84 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { year: '1991', value: 3, count: 10 }, + { year: '1992', value: 4, count: 4 }, + { year: '1993', value: 3.5, count: 5 }, + { year: '1994', value: 5, count: 5 }, + { year: '1995', value: 4.9, count: 4.9 }, + { year: '1996', value: 6, count: 35 }, + { year: '1997', value: 7, count: 7 }, + { year: '1998', value: 9, count: 1 }, + { year: '1999', value: 13, count: 20 }, + ]; + + const config = { + data, + xField: 'year', + legend: true, + children: [ + { + type: 'line', + yField: 'value', + style: { + lineWidth: 3, + lineDash: [5, 5], + }, + label: { + text: (datum) => `${datum.value}个`, + style: { + dy: -10, + textAlign: 'middle', + }, + }, + axis: { + y: { + title: 'value', + style: { titleFill: '#5B8FF9' }, + }, + }, + }, + { + type: 'line', + yField: 'count', + shapeField: 'smooth', + style: { + stroke: '#5AD8A6', + lineWidth: 4, + opacity: 0.5, + }, + label: { + text: (datum) => `${datum.count}个`, + style: { + dy: -10, + textAlign: 'middle', + }, + }, + axis: { + y: { + position: 'right', + title: 'count', + style: { titleFill: '#5AD8A6' }, + }, + }, + }, + { + type: 'point', + yField: 'count', + sizeField: 4, + style: { + stroke: '#5AD8A6', + fill: '#fff', + }, + axis: { y: false }, + tooltip: false, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/custom-grouped-column-line.js b/site/examples/statistics/dual-axes/demo/custom-grouped-column-line.js new file mode 100644 index 000000000..234a01d98 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/custom-grouped-column-line.js @@ -0,0 +1,65 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800 }, + { time: '2019-04', count: 600 }, + { time: '2019-05', count: 400 }, + { time: '2019-06', count: 380 }, + { time: '2019-07', count: 220 }, + ]; + + const config = { + xField: 'time', + legend: { + color: { + position: 'bottom', + layout: { justifyContent: 'center' }, + }, + }, + scale: { color: { range: ['#5B8FF9', '#5D7092', '#5AD8A6'] } }, + children: [ + { + data: uvBillData, + type: 'interval', + yField: 'value', + colorField: 'type', + group: true, + style: { maxWidth: 50 }, + label: { position: 'inside' }, + interaction: { elementHighlight: { background: true } }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + style: { lineWidth: 2 }, + axis: { y: { position: 'right' } }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/custom-stacked-column-line.js b/site/examples/statistics/dual-axes/demo/custom-stacked-column-line.js new file mode 100644 index 000000000..276dddd54 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/custom-stacked-column-line.js @@ -0,0 +1,71 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800 }, + { time: '2019-04', count: 600 }, + { time: '2019-05', count: 400 }, + { time: '2019-06', count: 380 }, + { time: '2019-07', count: 220 }, + ]; + + const config = { + xField: 'time', + legend: { + color: { + itemMarker: 'round', + itemMarkerSize: 14, + position: 'right', + }, + }, + children: [ + { + data: uvBillData, + type: 'interval', + yField: 'value', + stack: true, + colorField: 'type', + style: { maxWidth: 80 }, + label: { position: 'inside' }, + scale: { y: { domainMax: 1200 } }, + interaction: { + elementHighlight: true, + elementHighlight: { background: true }, + }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + colorField: () => 'count', + style: { lineWidth: 2 }, + axis: { y: { position: 'right' } }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + theme: { category10: ['#F4A49E', '#FACDAA', '#EE7B91', '#E85285', '#BE408C', '#BE408C'] }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/dual-aggregated-line-area.js b/site/examples/statistics/dual-axes/demo/dual-aggregated-line-area.js new file mode 100644 index 000000000..581534bd9 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/dual-aggregated-line-area.js @@ -0,0 +1,49 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/weather.json', + transform: [{ type: 'filter', callback: (d) => d.location === 'Seattle' }], + }, + children: [ + { + type: 'area', + xField: (d) => new Date(d.date).getUTCMonth(), + yField: ['temp_max', 'temp_min'], + transform: [{ type: 'groupX', y: 'mean', y1: 'mean' }], + style: { fill: '#85c5A6', fillOpacity: 0.3 }, + axis: { y: { title: 'Avg. Temperature (°C)', titleFill: '#85C5A6' } }, + tooltip: { + items: [ + { channel: 'y', valueFormatter: '.1f' }, + { channel: 'y1', valueFormatter: '.1f' }, + ], + }, + }, + { + type: 'line', + xField: (d) => new Date(d.date).getMonth(), + yField: 'precipitation', + shapeField: 'smooth', + transform: [{ type: 'groupX', y: 'mean' }], + style: { stroke: 'steelblue' }, + scale: { y: { nice: false } }, + axis: { + y: { + position: 'right', + title: 'Precipitation (inches)', + titleFill: 'steelblue', + }, + }, + tooltip: { items: [{ channel: 'y', valueFormatter: '.1f' }] }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/dual-line.js b/site/examples/statistics/dual-axes/demo/dual-line.js new file mode 100644 index 000000000..defa6e841 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/dual-line.js @@ -0,0 +1,57 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { year: '1991', value: 3, count: 10 }, + { year: '1992', value: 4, count: 4 }, + { year: '1993', value: 3.5, count: 5 }, + { year: '1994', value: 5, count: 5 }, + { year: '1995', value: 4.9, count: 4.9 }, + { year: '1996', value: 6, count: 35 }, + { year: '1997', value: 7, count: 7 }, + { year: '1998', value: 9, count: 1 }, + { year: '1999', value: 13, count: 20 }, + ]; + + const config = { + data, + xField: 'year', + legend: true, + children: [ + { + type: 'line', + yField: 'value', + style: { + stroke: '#5B8FF9', + lineWidth: 2, + }, + axis: { + y: { + title: 'value', + style: { titleFill: '#5B8FF9' }, + }, + }, + }, + { + type: 'line', + yField: 'count', + style: { + stroke: '#5AD8A6', + lineWidth: 2, + }, + axis: { + y: { + position: 'right', + title: 'count', + style: { titleFill: '#5AD8A6' } + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/dual-multi-line.js b/site/examples/statistics/dual-axes/demo/dual-multi-line.js new file mode 100644 index 000000000..f2e509e7b --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/dual-multi-line.js @@ -0,0 +1,72 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800, name: 'a' }, + { time: '2019-04', count: 600, name: 'a' }, + { time: '2019-05', count: 400, name: 'a' }, + { time: '2019-06', count: 380, name: 'a' }, + { time: '2019-07', count: 220, name: 'a' }, + { time: '2019-03', count: 750, name: 'b' }, + { time: '2019-04', count: 650, name: 'b' }, + { time: '2019-05', count: 450, name: 'b' }, + { time: '2019-06', count: 400, name: 'b' }, + { time: '2019-07', count: 320, name: 'b' }, + { time: '2019-03', count: 900, name: 'c' }, + { time: '2019-04', count: 600, name: 'c' }, + { time: '2019-05', count: 450, name: 'c' }, + { time: '2019-06', count: 300, name: 'c' }, + { time: '2019-07', count: 200, name: 'c' }, + ]; + + const config = { + xField: 'time', + scale: { color: { range: ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#6F5EF9'] } }, + children: [ + { + data: uvBillData, + type: 'line', + yField: 'value', + colorField: 'type', + shapeField: 'smooth', + style: { lineWidth: 3, lineDash: [5, 5] }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + colorField: 'name', + axis: { y: false }, + style: { lineWidth: 3 }, + }, + { + data: transformData, + type: 'point', + yField: 'count', + colorField: 'name', + sizeField: 3, + shapeField: 'point', + axis: { y: false }, + tooltip: false, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/dual-step-line.js b/site/examples/statistics/dual-axes/demo/dual-step-line.js new file mode 100644 index 000000000..a8e75d3f5 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/dual-step-line.js @@ -0,0 +1,53 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { year: '1991', value: 3, count: 10 }, + { year: '1992', value: 4, count: 4 }, + { year: '1993', value: 3.5, count: 5 }, + { year: '1994', value: 5, count: 5 }, + { year: '1995', value: 4.9, count: 4.9 }, + { year: '1996', value: 6, count: 35 }, + { year: '1997', value: 7, count: 7 }, + { year: '1998', value: 9, count: 1 }, + { year: '1999', value: 13, count: 20 }, + ]; + + const config = { + data, + xField: 'year', + legend: true, + children: [ + { + type: 'line', + yField: 'value', + shapeField: 'vh', + style: { + stroke: '#29cae4', + lineWidth: 2, + }, + }, + { + type: 'line', + yField: 'count', + shapeField: 'smooth', + style: { + stroke: '#5AD8A6', + lineWidth: 2, + }, + axis: { + y: { + position: 'right', + title: 'count', + style: { titleFill: '#5AD8A6' }, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/grouped-column-line.js b/site/examples/statistics/dual-axes/demo/grouped-column-line.js new file mode 100644 index 000000000..fba261ed3 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/grouped-column-line.js @@ -0,0 +1,57 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800 }, + { time: '2019-04', count: 600 }, + { time: '2019-05', count: 400 }, + { time: '2019-06', count: 380 }, + { time: '2019-07', count: 220 }, + ]; + + const config = { + xField: 'time', + legend: true, + children: [ + { + data: uvBillData, + type: 'interval', + yField: 'value', + colorField: 'type', + group: true, + interaction: { elementHighlight: { background: true } }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + style: { lineWidth: 2 }, + axis: { y: { position: 'right' } }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/grouped-column-multi-line.js b/site/examples/statistics/dual-axes/demo/grouped-column-multi-line.js new file mode 100644 index 000000000..4bd15098c --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/grouped-column-multi-line.js @@ -0,0 +1,69 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800, name: 'a' }, + { time: '2019-04', count: 600, name: 'a' }, + { time: '2019-05', count: 400, name: 'a' }, + { time: '2019-06', count: 380, name: 'a' }, + { time: '2019-07', count: 220, name: 'a' }, + { time: '2019-03', count: 750, name: 'b' }, + { time: '2019-04', count: 650, name: 'b' }, + { time: '2019-05', count: 450, name: 'b' }, + { time: '2019-06', count: 400, name: 'b' }, + { time: '2019-07', count: 320, name: 'b' }, + { time: '2019-03', count: 900, name: 'c' }, + { time: '2019-04', count: 600, name: 'c' }, + { time: '2019-05', count: 450, name: 'c' }, + { time: '2019-06', count: 300, name: 'c' }, + { time: '2019-07', count: 200, name: 'c' }, + ]; + + const config = { + xField: 'time', + children: [ + { + data: uvBillData, + type: 'interval', + yField: 'value', + colorField: 'type', + group: true, + style: { maxWidth: 80 }, + interaction: { elementHighlight: { background: true } }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + colorField: 'name', + style: { lineWidth: 2 }, + axis: { y: { position: 'right' } }, + scale: { series: { independent: true } }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/meta.json b/site/examples/statistics/dual-axes/demo/meta.json new file mode 100644 index 000000000..9b00029ba --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/meta.json @@ -0,0 +1,176 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": " 柱线混合", + "en": "Line Bar Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*GLvKQbqMjTQAAAAAAAAAAAAADmJ7AQ/fmt.webp" + }, + { + "filename": "segment.js", + "title": { + "zh": " 分段图", + "en": "Segment Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*t5wnSouqkP4AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "multi-line.js", + "title": { + "zh": "多轴图", + "en": "Multi Axis Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*BxouQYIIQ0kAAAAAAAAAAAAADmJ7AQ/fmt.webp" + }, + { + "filename": "dual-aggregated-line-area.js", + "title": { + "zh": "聚合线面双轴图", + "en": "Aggregated Dual Line And Area Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*J-lVRamRfgUAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "pareto.js", + "title": { + "zh": "帕累托图", + "en": "Pareto Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*COKsS6s75kYAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "column-line.js", + "title": { + "zh": "柱线混合图表", + "en": "Column line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*M4ldTo75WeEAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "custom-column-line.js", + "title": { + "zh": "柱线混合图表-自定义样式", + "en": "Custom column line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*vTPGSLODwLEAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "range-column-line.js", + "title": { + "zh": "柱线混合图表-区间柱", + "en": "range-column-line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*MEdZR5UQzosAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "column-multi-line.js", + "title": { + "zh": "柱线混合图表-显示多折线", + "en": "Column multi line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*FfmvR5_sLAEAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "slider-column-line.js", + "title": { + "zh": "柱线混合图表-滑块", + "en": "Slider with column line" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/fxukGXuXfg/89ea37e5-e00d-4424-b75e-1aba3ff8b633.png" + }, + { + "filename": "dual-line.js", + "title": { + "zh": "双折线图", + "en": "Dual line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*HS5DT4j34XEAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "custom-dual-line.js", + "title": { + "zh": "双折线图 - 自定义折线样式", + "en": "Dual line with style" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*FtUXRLU3mUgAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "dual-step-line.js", + "title": { + "zh": "双折线图 - 阶梯折线", + "en": "Dual Step Line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*5qvZTIy44UIAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "dual-multi-line.js", + "title": { + "zh": "双折线图 - 多折线", + "en": "Dual multi line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*BzyZQapC7ucAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "stacked-column-line.js", + "title": { + "zh": "堆叠柱线图表", + "en": "Stacked column line" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*q42bQKI2imQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom-stacked-column-line.js", + "title": { + "zh": "堆叠柱线图表-自定义样式", + "en": "Custom stacked column line" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*QPdnRqb4QH8AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "stacked-percent-column-line.js", + "title": { + "zh": "百分比堆叠柱线图表", + "en": "stacked-percent-column-line" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*0mcySZJTJ5sAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "stacked-column-multi-line.js", + "title": { + "zh": "堆叠柱线图表-显示多折线", + "en": "Stacked column multi line" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1UmPSZbc-XAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "grouped-column-line.js", + "title": { + "zh": "分组柱线图表", + "en": "Grouped column line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*ydQDSpPe9B0AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "custom-grouped-column-line.js", + "title": { + "zh": "分组柱线图表-自定义样式", + "en": "Custom grouped column line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*Ogg7R6trDvgAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "grouped-column-multi-line.js", + "title": { + "zh": "分组柱线图表-显示多折线", + "en": "Grouped column multi line" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*xzh4RYzYTFcAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/site/examples/statistics/dual-axes/demo/multi-line.js b/site/examples/statistics/dual-axes/demo/multi-line.js new file mode 100644 index 000000000..d872d2f8b --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/multi-line.js @@ -0,0 +1,135 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { + Month: 'Jan', + Evaporation: 2, + Precipitation: 2.6, + Temperature: 2, + }, + { + Month: 'Feb', + Evaporation: 4.9, + Precipitation: 5.9, + Temperature: 2.2, + }, + { + Month: 'Mar', + Evaporation: 7, + Precipitation: 9, + Temperature: 3.3, + }, + { + Month: 'Apr', + Evaporation: 23.2, + Precipitation: 26.4, + Temperature: 4.5, + }, + { + Month: 'May', + Evaporation: 25.6, + Precipitation: 28.7, + Temperature: 6.3, + }, + { + Month: 'Jun', + Evaporation: 76.7, + Precipitation: 70.7, + Temperature: 10.2, + }, + { + Month: 'Jul', + Evaporation: 135.6, + Precipitation: 175.6, + Temperature: 20.3, + }, + { + Month: 'Aug', + Evaporation: 162.2, + Precipitation: 182.2, + Temperature: 23.4, + }, + { + Month: 'Sep', + Evaporation: 32.6, + Precipitation: 48.7, + Temperature: 23, + }, + { + Month: 'Oct', + Evaporation: 20, + Precipitation: 18.8, + Temperature: 16.5, + }, + { + Month: 'Nov', + Evaporation: 6.4, + Precipitation: 6, + Temperature: 12, + }, + { + Month: 'Dec', + Evaporation: 3.3, + Precipitation: 2.3, + Temperature: 6.2, + }, + ]; + + const config = { + data, + xField: 'Month', + scale: { y: { nice: false } }, + children: [ + { + type: 'line', + yField: 'Temperature', + shapeField: 'smooth', + colorField: '#EE6666', + scale: { y: { domainMax: 30 } }, + axis: { + y: { + title: 'Temperature (°C)', + style: { titleFill: '#EE6666' }, + }, + }, + }, + { + type: 'interval', + yField: 'Evaporation', + colorField: '#5470C6', + scale: { y: { domainMax: 200 } }, + style: { fillOpacity: 0.8 }, + axis: { + y: { + position: 'right', + title: 'Evaporation (ml)', + style: { titleFill: '#5470C6' }, + }, + }, + }, + { + type: 'line', + yField: 'Precipitation', + shapeField: 'smooth', + colorField: '#91CC75', + style: { + lineWidth: 2, + lineDash: [2, 2], + }, + axis: { + y: { + position: 'right', + title: 'Precipitation (ml)', + style: { titleFill: '#91CC75' }, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/pareto.js b/site/examples/statistics/dual-axes/demo/pareto.js new file mode 100644 index 000000000..34f771eae --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/pareto.js @@ -0,0 +1,87 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const config = { + scale: { y: { nice: false } }, + data: { + type: 'inline', + value: [ + { x: 'Parking Difficult', value: 95 }, + { x: 'Sales Rep was Rude', value: 60 }, + { x: 'Poor Lighting', value: 45 }, + { x: 'Layout Confusing', value: 37 }, + { x: 'Sizes Limited', value: 30 }, + { x: 'Clothing Faded', value: 27 }, + { x: 'Clothing Shrank', value: 18 }, + ], + transform: [ + { + type: 'custom', + callback: (data) => { + const sum = data.reduce((r, curr) => r + curr.value, 0); + return data + .map((d) => ({ + ...d, + percentage: d.value / sum, + })) + .reduce((r, curr) => { + const v = r.length ? r[r.length - 1].accumulate : 0; + const accumulate = v + curr.percentage; + r.push({ + ...curr, + accumulate, + }); + return r; + }, []); + }, + }, + ], + }, + xField: 'x', + children: [ + { + type: 'interval', + yField: 'value', + scale: { x: { padding: 0.5 }, y: { domainMax: 312, tickCount: 5 } }, + style: { fill: (d) => (d.percentage < 0.1 ? '#E24B26' : '#78B3F0') }, + axis: { x: { title: null }, y: { title: 'Defect frequency' } }, + labels: [ + { + text: (d) => `${(d.percentage * 100).toFixed(1)}%`, + textBaseline: 'bottom', + }, + ], + }, + { + type: 'line', + yField: 'accumulate', + scale: { y: { domainMin: 0, tickCount: 5 } }, + axis: { + y: { + position: 'right', + title: 'Cumulative Percentage', + grid: null, + labelFormatter: (d) => `${(d * 100).toFixed(0)}%`, + }, + }, + tooltip: { + items: [{ channel: 'y', valueFormatter: (d) => `${(d * 100).toFixed(2)}%` }], + }, + }, + { + type: 'point', + yField: 'accumulate', + shapeField: 'diamond', + scale: { y: { domainMin: 0 } }, + axis: { y: false }, + tooltip: false, + }, + ], + title: 'Pareto Chart of Customer Complaints', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/range-column-line.js b/site/examples/statistics/dual-axes/demo/range-column-line.js new file mode 100644 index 000000000..82e61cead --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/range-column-line.js @@ -0,0 +1,35 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { time: '2019-03', value: [200, 350], count: 800 }, + { time: '2019-04', value: [400, 650], count: 600 }, + { time: '2019-05', value: [150, 350], count: 400 }, + { time: '2019-06', value: [100, 450], count: 380 }, + { time: '2019-07', value: [500, 550], count: 220 }, + ]; + + const config = { + data, + xField: 'time', + legend: true, + children: [ + { + type: 'interval', + yField: 'value', + style: { maxWidth: 100 }, + }, + { + type: 'line', + yField: 'count', + style: { lineWidth: 2 }, + axis: { y: { position: 'right' } }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/segment.js b/site/examples/statistics/dual-axes/demo/segment.js new file mode 100644 index 000000000..f631a857c --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/segment.js @@ -0,0 +1,53 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { year: '1991', value: null, count: 3 }, + { year: '1992', value: null, count: 3 }, + { year: '1993', value: null, count: 5 }, + { year: '1994', value: 5, count: 5 }, + { year: '1995', value: 6, count: null }, + { year: '1996', value: 8, count: null }, + { year: '1997', value: 7, count: null }, + { year: '1998', value: 9, count: null }, + ]; + + const config = { + data, + xField: 'year', + scale: { + y: { + independent: false, + key: 'sameKey', + domain: [0, 10], + }, + }, + tooltip: { + items: [ + (item) => ({ + name: '销售额', + value: item.value | item.count, + }), + ], + }, + children: [ + { + type: 'line', + yField: 'count', + }, + { + type: 'line', + yField: 'value', + style: { + lineDash: [2, 4], + stroke: 'red', + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/slider-column-line.js b/site/examples/statistics/dual-axes/demo/slider-column-line.js new file mode 100644 index 000000000..6d7776ceb --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/slider-column-line.js @@ -0,0 +1,63 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const data = [ + { time: '2020-08-20', consumeTime: 10868, completeTime: 649.483 }, + { time: '2020-08-21', consumeTime: 8786, completeTime: 1053.7 }, + { time: '2020-08-22', consumeTime: 10824, completeTime: 679.817 }, + { time: '2020-08-23', consumeTime: 7860, completeTime: 638.117 }, + { time: '2020-08-24', consumeTime: 13253, completeTime: 843.3 }, + { time: '2020-08-25', consumeTime: 17015, completeTime: 1092.983 }, + { time: '2020-08-26', consumeTime: 19298, completeTime: 1036.317 }, + { time: '2020-08-27', consumeTime: 13937, completeTime: 1031.9 }, + { time: '2020-08-28', consumeTime: 11541, completeTime: 803.467 }, + { time: '2020-08-29', consumeTime: 15244, completeTime: 830.733 }, + { time: '2020-08-30', consumeTime: 14247, completeTime: 709.867 }, + { time: '2020-08-31', consumeTime: 9402, completeTime: 665.233 }, + { time: '2020-09-01', consumeTime: 10440, completeTime: 696.367 }, + { time: '2020-09-02', consumeTime: 9345, completeTime: 692.867 }, + { time: '2020-09-03', consumeTime: 18459, completeTime: 936.017 }, + { time: '2020-09-04', consumeTime: 9763, completeTime: 782.867 }, + { time: '2020-09-05', consumeTime: 11074, completeTime: 653.8 }, + { time: '2020-09-06', consumeTime: 11770, completeTime: 856.683 }, + { time: '2020-09-07', consumeTime: 12206, completeTime: 777.15 }, + { time: '2020-09-08', consumeTime: 11434, completeTime: 773.283 }, + { time: '2020-09-09', consumeTime: 16218, completeTime: 833.3 }, + { time: '2020-09-10', consumeTime: 11914, completeTime: 793.517 }, + { time: '2020-09-11', consumeTime: 16781, completeTime: 894.45 }, + { time: '2020-09-12', consumeTime: 10555, completeTime: 725.55 }, + { time: '2020-09-13', consumeTime: 10899, completeTime: 709.967 }, + { time: '2020-09-14', consumeTime: 10713, completeTime: 787.6 }, + { time: '2020-09-15', consumeTime: 0, completeTime: 644.183 }, + { time: '2020-09-16', consumeTime: 0, completeTime: 1066.65 }, + { time: '2020-09-17', consumeTime: 20357, completeTime: 932.45 }, + { time: '2020-09-18', consumeTime: 10424, completeTime: 753.583 }, + ]; + + const config = { + data, + xField: 'time', + padding: 70, + legend: true, + slider: { x: true }, + scale: { y: { nice: false } }, + children: [ + { + type: 'interval', + yField: 'consumeTime', + style: { stroke: '#5B8FF9' }, + }, + { + type: 'line', + yField: 'completeTime', + style: { stroke: '#5AD8A6', lineWidth: 2 }, + axis: { y: { position: 'right' } }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/stacked-column-line.js b/site/examples/statistics/dual-axes/demo/stacked-column-line.js new file mode 100644 index 000000000..2f3d43395 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/stacked-column-line.js @@ -0,0 +1,59 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800 }, + { time: '2019-04', count: 600 }, + { time: '2019-05', count: 400 }, + { time: '2019-06', count: 380 }, + { time: '2019-07', count: 220 }, + ]; + + const config = { + xField: 'time', + children: [ + { + data: uvBillData, + type: 'interval', + yField: 'value', + stack: true, + colorField: 'type', + style: { maxWidth: 80 }, + scale: { y: { domainMax: 1200 } }, + interaction: { elementHighlight: { background: true } }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + colorField: () => 'count', + style: { lineWidth: 2 }, + axis: { y: { position: 'right' } }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/stacked-column-multi-line.js b/site/examples/statistics/dual-axes/demo/stacked-column-multi-line.js new file mode 100644 index 000000000..d2b5a5d50 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/stacked-column-multi-line.js @@ -0,0 +1,83 @@ +import { DualAxes } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800, name: 'a' }, + { time: '2019-04', count: 600, name: 'a' }, + { time: '2019-05', count: 400, name: 'a' }, + { time: '2019-06', count: 380, name: 'a' }, + { time: '2019-07', count: 220, name: 'a' }, + { time: '2019-03', count: 750, name: 'b' }, + { time: '2019-04', count: 650, name: 'b' }, + { time: '2019-05', count: 450, name: 'b' }, + { time: '2019-06', count: 400, name: 'b' }, + { time: '2019-07', count: 320, name: 'b' }, + { time: '2019-03', count: 900, name: 'c' }, + { time: '2019-04', count: 600, name: 'c' }, + { time: '2019-05', count: 450, name: 'c' }, + { time: '2019-06', count: 300, name: 'c' }, + { time: '2019-07', count: 200, name: 'c' }, + ]; + + const config = { + xField: 'time', + interaction: { tooltip: { sort: (d) => ['uv', 'bill', 'a', 'b', 'c'].indexOf(d.name) } }, + children: [ + { + data: uvBillData, + type: 'interval', + yField: 'value', + colorField: 'type', + stack: true, + style: { maxWidth: 80 }, + scale: { y: { domainMax: 1200 } }, + interaction: { elementHighlight: { background: true } }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + colorField: 'name', + style: { + lineWidth: 2, + opacity: (d) => { + if (d[0].name === 'a') { + return 1; + } + return 0.5; + }, + lineDash: (d) => { + if (d[0].name === 'a') { + return [1, 4]; + } + }, + }, + axis: { y: { position: 'right' } }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/demo/stacked-percent-column-line.js b/site/examples/statistics/dual-axes/demo/stacked-percent-column-line.js new file mode 100644 index 000000000..fb6731ee6 --- /dev/null +++ b/site/examples/statistics/dual-axes/demo/stacked-percent-column-line.js @@ -0,0 +1,66 @@ +import { DualAxes, G2 } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoDualAxes = () => { + const uvBillData = [ + { time: '2019-03', value: 350, type: 'uv' }, + { time: '2019-04', value: 900, type: 'uv' }, + { time: '2019-05', value: 300, type: 'uv' }, + { time: '2019-06', value: 450, type: 'uv' }, + { time: '2019-07', value: 470, type: 'uv' }, + { time: '2019-03', value: 220, type: 'bill' }, + { time: '2019-04', value: 300, type: 'bill' }, + { time: '2019-05', value: 250, type: 'bill' }, + { time: '2019-06', value: 220, type: 'bill' }, + { time: '2019-07', value: 362, type: 'bill' }, + ]; + + const transformData = [ + { time: '2019-03', count: 800 }, + { time: '2019-04', count: 600 }, + { time: '2019-05', count: 400 }, + { time: '2019-06', count: 380 }, + { time: '2019-07', count: 220 }, + ]; + + const config = { + xField: 'time', + children: [ + { + data: uvBillData, + type: 'interval', + yField: 'value', + stack: true, + percent: true, + colorField: 'type', + style: { maxWidth: 80 }, + tooltip: { channel: 'y0', valueFormatter: '.0%' }, + axis: { y: { title: 'value', style: { titleFill: '#5B8FF9' } } }, + interaction: { elementHighlight: { background: true } }, + }, + { + data: transformData, + type: 'line', + yField: 'count', + colorField: () => 'count', + style: { lineWidth: 2 }, + axis: { + y: { + position: 'right', + style: { titleFill: '#5AD8A6' }, + }, + }, + interaction: { + tooltip: { + crosshairs: false, + marker: false, + }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/dual-axes/index.en.md b/site/examples/statistics/dual-axes/index.en.md new file mode 100644 index 000000000..52dde4ca2 --- /dev/null +++ b/site/examples/statistics/dual-axes/index.en.md @@ -0,0 +1,4 @@ +--- +title: DualAxes +order: 2 +--- \ No newline at end of file diff --git a/site/examples/statistics/dual-axes/index.zh.md b/site/examples/statistics/dual-axes/index.zh.md new file mode 100644 index 000000000..293931348 --- /dev/null +++ b/site/examples/statistics/dual-axes/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 双轴图 +order: 2 +--- + + diff --git a/site/examples/statistics/funnel/API.en.md b/site/examples/statistics/funnel/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/funnel/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/funnel/API.zh.md b/site/examples/statistics/funnel/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/funnel/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/funnel/demo/funnel.js b/site/examples/statistics/funnel/demo/funnel.js new file mode 100644 index 000000000..1b05bf79c --- /dev/null +++ b/site/examples/statistics/funnel/demo/funnel.js @@ -0,0 +1,26 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Funnel } from '@ant-design/plots'; + +const DemoFunnel = () => { + const data = [ + { stage: '简历筛选', number: 253 }, + { stage: '初试人数', number: 151 }, + { stage: '复试人数', number: 113 }, + { stage: '录取人数', number: 87 }, + { stage: '入职人数', number: 59 }, + ]; + + const config = { + data, + xField: 'stage', + yField: 'number', + label: { + text: (d) => `${d.stage}\n${d.number}`, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/funnel/demo/meta.json b/site/examples/statistics/funnel/demo/meta.json new file mode 100644 index 000000000..c76aa7fc1 --- /dev/null +++ b/site/examples/statistics/funnel/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "funnel.js", + "title": { + "zh": "漏斗图", + "en": "Funnel" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*VHVOTK8LhxkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "pyramid.js", + "title": { + "zh": "金字塔图", + "en": "Pyramid" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GqpjQLFLh0QAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "mirror-funnel.js", + "title": { + "zh": "对比漏斗图", + "en": "Mirror Funnel" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*TCTrRaKsv78AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "transpose.js", + "title": { + "zh": "转化漏斗图-转置", + "en": "Annotation Funnel transpose" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Ov19Tqg60toAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/funnel/demo/mirror-funnel.js b/site/examples/statistics/funnel/demo/mirror-funnel.js new file mode 100644 index 000000000..cae4cfbe6 --- /dev/null +++ b/site/examples/statistics/funnel/demo/mirror-funnel.js @@ -0,0 +1,71 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Funnel } from '@ant-design/plots'; + +const DemoFunnel = () => { + const data = [ + { action: '访问', visitor: 500, site: '站点1' }, + { action: '浏览', visitor: 400, site: '站点1' }, + { action: '交互', visitor: 300, site: '站点1' }, + { action: '下单', visitor: 200, site: '站点1' }, + { action: '完成', visitor: 100, site: '站点1' }, + { action: '访问', visitor: 550, site: '站点2' }, + { action: '浏览', visitor: 420, site: '站点2' }, + { action: '交互', visitor: 280, site: '站点2' }, + { action: '下单', visitor: 150, site: '站点2' }, + { action: '完成', visitor: 80, site: '站点2' }, + ]; + + const uPosition = (item, values) => { + if (item.site === '站点2') { + return values[0]; + } + return values[1]; + }; + + const config = { + data, + xField: 'action', + yField: 'visitor', + compareField: 'site', + style: { + stroke: '#fff', + }, + label: [ + { + text: (d) => d.visitor, + position: 'inside', + fontSize: 16, + }, + { + render: ($, _, i) => { + if (i) + return ( +
+ ); + }, + position: (item) => uPosition(item, ['top-left', 'top-right']), + }, + { + text: (d, i, data) => { + if (i) return ((d.visitor / data[i - 1].visitor) * 100).toFixed(2) + '%'; + }, + position: (item) => uPosition(item, ['top-left', 'top-right']), + textAlign: (item) => uPosition(item, ['right', 'left']), + textBaseline: 'middle', + dx: (item) => uPosition(item, [-40, 40]), + }, + ], + legend: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/funnel/demo/pyramid.js b/site/examples/statistics/funnel/demo/pyramid.js new file mode 100644 index 000000000..0509ac91a --- /dev/null +++ b/site/examples/statistics/funnel/demo/pyramid.js @@ -0,0 +1,56 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Funnel } from '@ant-design/plots'; + +const DemoFunnel = () => { + const data = [ + { action: '浏览网站', pv: 50000 }, + { action: '放入购物车', pv: 35000 }, + { action: '生成订单', pv: 25000 }, + { action: '支付订单', pv: 15000 }, + { action: '完成交易', pv: 8000 }, + ]; + + const config = { + data, + xField: 'action', + yField: 'pv', + shapeField: 'pyramid', + label: [ + { + text: (d) => d.pv, + position: 'inside', + fontSize: 16, + }, + { + render: ($, _, i) => { + if (i) + return ( +
+ ); + }, + position: 'top-right', + }, + { + text: (d, i, data) => { + if (i) return ((d.pv / data[i - 1].pv) * 100).toFixed(2) + '%'; + }, + position: 'top-right', + textAlign: 'left', + textBaseline: 'middle', + dx: 40, + }, + ], + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/funnel/demo/transpose.js b/site/examples/statistics/funnel/demo/transpose.js new file mode 100644 index 000000000..a4b32b2d2 --- /dev/null +++ b/site/examples/statistics/funnel/demo/transpose.js @@ -0,0 +1,56 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Funnel } from '@ant-design/plots'; + +const DemoFunnel = () => { + const data = [ + { stage: '简历筛选', number: 253 }, + { stage: '初试人数', number: 151 }, + { stage: '复试人数', number: 113 }, + { stage: '录取人数', number: 87 }, + { stage: '入职人数', number: 59 }, + ]; + + const config = { + data: data, + xField: 'stage', + yField: 'number', + isTransposed: false, + label: [ + { + text: (d) => d.number, + position: 'inside', + fontSize: 16, + }, + { + render: ($, _, i) => { + if (i) + return ( +
+ ); + }, + position: 'top-left', + }, + { + text: (d, i, data) => { + if (i) return ((d.number / data[i - 1].number) * 100).toFixed(2) + '%'; + }, + position: 'top-left', + textAlign: 'middle', + textBaseline: 'bottom', + dy: -30, + }, + ], + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/funnel/index.en.md b/site/examples/statistics/funnel/index.en.md new file mode 100644 index 000000000..54853d3df --- /dev/null +++ b/site/examples/statistics/funnel/index.en.md @@ -0,0 +1,4 @@ +--- +title: Funnel +order: 2 +--- diff --git a/site/examples/statistics/funnel/index.zh.md b/site/examples/statistics/funnel/index.zh.md new file mode 100644 index 000000000..13edd1929 --- /dev/null +++ b/site/examples/statistics/funnel/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 漏斗图 +order: 2 +--- diff --git a/site/examples/statistics/gauge/API.en.md b/site/examples/statistics/gauge/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/gauge/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/gauge/API.zh.md b/site/examples/statistics/gauge/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/gauge/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/gauge/demo/gauge-color.js b/site/examples/statistics/gauge/demo/gauge-color.js new file mode 100644 index 000000000..86b0d8b0c --- /dev/null +++ b/site/examples/statistics/gauge/demo/gauge-color.js @@ -0,0 +1,29 @@ +import { Gauge } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoGauge = () => { + const config = { + width: 720, + height: 720, + autoFit: true, + data: { + target: 159, + total: 400, + name: 'score', + thresholds: [100, 200, 400], + }, + legend: false, + scale: { + color: { + range: ['#F4664A', '#FAAD14', 'green'], + }, + }, + style: { + textContent: (target, total) => `得分:${target}\n占比:${(target / total) * 100}%`, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/gauge/demo/gauge.js b/site/examples/statistics/gauge/demo/gauge.js new file mode 100644 index 000000000..0e100d169 --- /dev/null +++ b/site/examples/statistics/gauge/demo/gauge.js @@ -0,0 +1,20 @@ +import { Gauge } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoGauge = () => { + const config = { + width: 720, + height: 720, + autoFit: true, + data: { + target: 120, + total: 400, + name: 'score', + }, + legend: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/gauge/demo/meta.json b/site/examples/statistics/gauge/demo/meta.json new file mode 100644 index 000000000..ed5f9ad5f --- /dev/null +++ b/site/examples/statistics/gauge/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "gauge.js", + "title": { + "zh": "仪表盘", + "en": "Gauge Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*hpjTRr6LM7IAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "gauge-color.js", + "title": { + "zh": "自定义仪表盘颜色", + "en": "Gauge Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*VSrGTYrM954AAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/gauge/index.en.md b/site/examples/statistics/gauge/index.en.md new file mode 100644 index 000000000..4bed8ab2e --- /dev/null +++ b/site/examples/statistics/gauge/index.en.md @@ -0,0 +1,6 @@ +--- +title: Gauge +order: 14 +--- + + diff --git a/site/examples/statistics/gauge/index.zh.md b/site/examples/statistics/gauge/index.zh.md new file mode 100644 index 000000000..2e270dda4 --- /dev/null +++ b/site/examples/statistics/gauge/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 仪表盘 +order: 14 +--- + + diff --git a/site/examples/statistics/heatmap/API.en.md b/site/examples/statistics/heatmap/API.en.md new file mode 100644 index 000000000..b2f0b2008 --- /dev/null +++ b/site/examples/statistics/heatmap/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/heatmap/API.zh.md b/site/examples/statistics/heatmap/API.zh.md new file mode 100644 index 000000000..2dd0472a5 --- /dev/null +++ b/site/examples/statistics/heatmap/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/heatmap/demo/basic.js b/site/examples/statistics/heatmap/demo/basic.js new file mode 100644 index 000000000..41d20eb03 --- /dev/null +++ b/site/examples/statistics/heatmap/demo/basic.js @@ -0,0 +1,44 @@ +import React, { useState, useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { Heatmap } from '@ant-design/plots'; + +const DemoHeatmap = () => { + const [data, setData] = useState([]); + + useEffect(() => { + asyncFetch(); + }, []); + + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/bmw-prod/68d3f380-089e-4683-ab9e-4493200198f9.json') + .then((response) => response.json()) + .then((json) => setData(json)) + .catch((error) => { + console.log('fetch data failed', error); + }); + }; + const config = { + data, + xField: 'name', + yField: 'country', + colorField: 'value', + sizeField: 'value', + shapeField: 'square', + label: { + text: (d) => d.value, + position: 'inside', + style: { + fill: '#fff', + pointerEvents: 'none', + }, + }, + scale: { + size: { range: [12, 20] }, + color: { range: ['#dddddd', '#9ec8e0', '#5fa4cd', '#2e7ab6', '#114d90'] }, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/heatmap/demo/cell-aggregated.js b/site/examples/statistics/heatmap/demo/cell-aggregated.js new file mode 100644 index 000000000..4ff42e950 --- /dev/null +++ b/site/examples/statistics/heatmap/demo/cell-aggregated.js @@ -0,0 +1,30 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Heatmap } from '@ant-design/plots'; + +const DemoHeatmap = () => { + const config = { + height: 300, + autoFit: false, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/seattle-weather.json', + }, + xField: (d) => new Date(d.date).getUTCDate(), + yField: (d) => new Date(d.date).getUTCMonth(), + colorField: 'temp_max', + legend: {}, + mark: 'cell', + style: { inset: 0.5 }, + tooltip: { + title: 'date', + field: 'temp_max', + valueFormatter: '~s', + pointerEvents: 'none' + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/heatmap/demo/heatmap-density.js b/site/examples/statistics/heatmap/demo/heatmap-density.js new file mode 100644 index 000000000..512fc4a44 --- /dev/null +++ b/site/examples/statistics/heatmap/demo/heatmap-density.js @@ -0,0 +1,24 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Heatmap } from '@ant-design/plots'; + +const DemoHeatmap = () => { + const config = { + mark: 'heatmap', + data: { + type: "fetch", + value: "https://assets.antv.antgroup.com/g2/heatmap.json", + }, + xField: 'g', + yField: 'l', + colorField: 'tmp', + sizeField: 26, + style: { + opacity: 0, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/heatmap/demo/meta.json b/site/examples/statistics/heatmap/demo/meta.json new file mode 100644 index 000000000..2d2694234 --- /dev/null +++ b/site/examples/statistics/heatmap/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "热力形状大小映射", + "en": "Heatmap plot shape size mapping" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*oaJCT5yVPRsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "shape.js", + "title": { + "zh": "热力形状图", + "en": "Shape heatmap plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ztgPRrmeh84AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "cell-aggregated.js", + "title": { + "zh": "聚合热力图", + "en": "Aggregated Heatmap" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ru0HSZ6RiCsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "heatmap-density.js", + "title": { + "zh": "密度热力图", + "en": "Density Heatmap" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*mfxdT4ozJXAAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/heatmap/demo/shape.js b/site/examples/statistics/heatmap/demo/shape.js new file mode 100644 index 000000000..82196986c --- /dev/null +++ b/site/examples/statistics/heatmap/demo/shape.js @@ -0,0 +1,46 @@ +import React, { useState, useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { Heatmap } from '@ant-design/plots'; + +const DemoHeatmap = () => { + const [data, setData] = useState([]); + + useEffect(() => { + asyncFetch(); + }, []); + + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/bmw-prod/68d3f380-089e-4683-ab9e-4493200198f9.json') + .then((response) => response.json()) + .then((json) => setData(json)) + .catch((error) => { + console.log('fetch data failed', error); + }); + }; + const config = { + data, + xField: 'name', + yField: 'country', + colorField: 'value', + sizeField: 'value', + shapeField: 'point', + scale: { + size: { range: [12, 20] }, + color: { range: ['#0d5fbb', '#7eadfc', '#fd8b6f', '#aa3523'] }, + }, + label: { + text: (d) => d.value, + position: 'inside', + style: { + fill: '#fff', + shadowBlur: 2, + shadowColor: 'rgba(0, 0, 0, .45)', + pointerEvents: 'none', + }, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/heatmap/index.en.md b/site/examples/statistics/heatmap/index.en.md new file mode 100644 index 000000000..5072da822 --- /dev/null +++ b/site/examples/statistics/heatmap/index.en.md @@ -0,0 +1,4 @@ +--- +title: Heatmap +order: 1 +--- \ No newline at end of file diff --git a/site/examples/statistics/heatmap/index.zh.md b/site/examples/statistics/heatmap/index.zh.md new file mode 100644 index 000000000..15bd8930d --- /dev/null +++ b/site/examples/statistics/heatmap/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 热力图 +order: 1 +--- + + diff --git a/site/examples/statistics/histogram/API.en.md b/site/examples/statistics/histogram/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/histogram/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/histogram/API.zh.md b/site/examples/statistics/histogram/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/histogram/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/histogram/demo/binWidth.js b/site/examples/statistics/histogram/demo/binWidth.js new file mode 100644 index 000000000..8318a46bf --- /dev/null +++ b/site/examples/statistics/histogram/demo/binWidth.js @@ -0,0 +1,83 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Histogram } from '@ant-design/plots'; + +const data = [ + { value: 1.2 }, + { value: 3.4 }, + { value: 3.7 }, + { value: 4.3 }, + { value: 5.2 }, + { value: 5.8 }, + { value: 6.1 }, + { value: 6.5 }, + { value: 6.8 }, + { value: 7.1 }, + { value: 7.3 }, + { value: 7.7 }, + { value: 8.3 }, + { value: 8.6 }, + { value: 8.8 }, + { value: 9.1 }, + { value: 9.2 }, + { value: 9.4 }, + { value: 9.5 }, + { value: 9.7 }, + { value: 10.5 }, + { value: 10.7 }, + { value: 10.8 }, + { value: 11.0 }, + { value: 11.0 }, + { value: 11.1 }, + { value: 11.2 }, + { value: 11.3 }, + { value: 11.4 }, + { value: 11.4 }, + { value: 11.7 }, + { value: 12.0 }, + { value: 12.9 }, + { value: 12.9 }, + { value: 13.3 }, + { value: 13.7 }, + { value: 13.8 }, + { value: 13.9 }, + { value: 14.0 }, + { value: 14.2 }, + { value: 14.5 }, + { value: 15 }, + { value: 15.2 }, + { value: 15.6 }, + { value: 16.0 }, + { value: 16.3 }, + { value: 17.3 }, + { value: 17.5 }, + { value: 17.9 }, + { value: 18.0 }, + { value: 18.0 }, + { value: 20.6 }, + { value: 21 }, + { value: 23.4 }, +]; + +const DemoHistogram = () => { + const config = { + data, + style: { + inset: 0.5, + }, + binField: 'value', + channel: 'count', + binWidth: 4, + scale: { + x: { domainMin: 0, tickCount: 10 }, + y: { + domainMax: 15, + nice: true, + }, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/histogram/demo/binx-color.js b/site/examples/statistics/histogram/demo/binx-color.js new file mode 100644 index 000000000..6589fc7f1 --- /dev/null +++ b/site/examples/statistics/histogram/demo/binx-color.js @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Histogram } from '@ant-design/plots'; + +const DemoHistogram = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/athletes.json', + }, + binField: 'weight', + channel: 'count', + colorField: 'sex', + stack: { + orderBy: 'series', + }, + style: { + inset: 0.5, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/histogram/demo/binx.js b/site/examples/statistics/histogram/demo/binx.js new file mode 100644 index 000000000..1d6af1072 --- /dev/null +++ b/site/examples/statistics/histogram/demo/binx.js @@ -0,0 +1,23 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Histogram } from '@ant-design/plots'; + +const DemoHistogram = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/unemployment2.json', + }, + style: { + inset: 0.5, + }, + binField: 'rate', + channel: 'count', + // 分箱数量 + binNumber: 10, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/histogram/demo/meta.json b/site/examples/statistics/histogram/demo/meta.json new file mode 100644 index 000000000..43eedc408 --- /dev/null +++ b/site/examples/statistics/histogram/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "binx.js", + "title": { + "zh": "直方图", + "en": "Histogram" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*yTCIRruhfOoAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "binx-color.js", + "title": { + "zh": "颜色分类直方图", + "en": "Histogram with Color" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*sk8XRJTkuwYAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "binWidth.js", + "title": { + "zh": "直方图范围刻度", + "en": "Bin histogram plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*3Q40QKcUIfIAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/histogram/index.en.md b/site/examples/statistics/histogram/index.en.md new file mode 100644 index 000000000..f50b0a037 --- /dev/null +++ b/site/examples/statistics/histogram/index.en.md @@ -0,0 +1,4 @@ +--- +title: Histogram +order: 2 +--- \ No newline at end of file diff --git a/site/examples/statistics/histogram/index.zh.md b/site/examples/statistics/histogram/index.zh.md new file mode 100644 index 000000000..d32983558 --- /dev/null +++ b/site/examples/statistics/histogram/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 直方图 +order: 2 +--- diff --git a/site/examples/statistics/line/API.en.md b/site/examples/statistics/line/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/line/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/line/API.zh.md b/site/examples/statistics/line/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/line/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/line/demo/basic.js b/site/examples/statistics/line/demo/basic.js new file mode 100644 index 000000000..ec475abc2 --- /dev/null +++ b/site/examples/statistics/line/demo/basic.js @@ -0,0 +1,37 @@ +import { Line } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLine = () => { + const data = [ + { year: '1991', value: 3 }, + { year: '1992', value: 4 }, + { year: '1993', value: 3.5 }, + { year: '1994', value: 5 }, + { year: '1995', value: 4.9 }, + { year: '1996', value: 6 }, + { year: '1997', value: 7 }, + { year: '1998', value: 9 }, + { year: '1999', value: 13 }, + ]; + const config = { + data, + xField: 'year', + yField: 'value', + point: { + shapeField: 'square', + sizeField: 4, + }, + interaction: { + tooltip: { + marker: false, + }, + }, + style: { + lineWidth: 2, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/color.js b/site/examples/statistics/line/demo/color.js new file mode 100644 index 000000000..64b5eec70 --- /dev/null +++ b/site/examples/statistics/line/demo/color.js @@ -0,0 +1,30 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Line } from '@ant-design/plots'; + +const DemoLine = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/temperatures1.json', + }, + xField: (d) => new Date(d.date), + yField: 'value', + colorField: 'condition', + shapeField: 'hvh', + style: { + gradient: 'x', + lineWidth: 2, + }, + scale: { + y: { nice: true }, + color: { + domain: ['CLR', 'FEW', 'SCT', 'BKN', 'OVC', 'VV '], + range: ['deepskyblue', 'lightskyblue', 'lightblue', '#aaaaaa', '#666666', '#666666'], + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/connect-nulls.js b/site/examples/statistics/line/demo/connect-nulls.js new file mode 100644 index 000000000..fd579160f --- /dev/null +++ b/site/examples/statistics/line/demo/connect-nulls.js @@ -0,0 +1,30 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Line } from '@ant-design/plots'; + +const DemoLine = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/line-connect-nulls.json', + transform: [ + { + type: 'map', + callback: (d) => ({ + ...d, + close: new Date(d.date).getUTCMonth() < 3 ? NaN : d.close, + }), + }, + ], + }, + xField: (d) => new Date(d.date), + yField: 'close', + connectNulls: { + connect: true, + connectStroke: '#aaa', + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/line-text.js b/site/examples/statistics/line/demo/line-text.js new file mode 100644 index 000000000..b1d62e019 --- /dev/null +++ b/site/examples/statistics/line/demo/line-text.js @@ -0,0 +1,53 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Line } from '@ant-design/plots'; + +const DemoArea = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/blockchain.json', + transform: [ + { + type: 'fold', + fields: ['blockchain', 'nlp'], + key: 'type', + value: 'value', + }, + ], + }, + xField: (d) => new Date(d.date), + yField: 'value', + colorField: 'type', + axis: { + x: { labelAutoHide: 'greedy' }, + }, + annotations: [ + { + type: 'text', + data: [new Date('2017-12-17'), 100], + style: { + text: '2014-03, 受比特币影响,blockchain 1834', + wordWrap: true, + wordWrapWidth: 164, + dx: -174, + dy: 30, + fill: '#2C3542', + fillOpacity: 0.65, + fontSize: 10, + background: true, + backgroundRadius: 2, + connector: true, + startMarker: true, + startMarkerFill: '#2C3542', + startMarkerFillOpacity: 0.65, + }, + tooltip: false, + }, + ], + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/line-threshold.js b/site/examples/statistics/line/demo/line-threshold.js new file mode 100644 index 000000000..7422c8ebb --- /dev/null +++ b/site/examples/statistics/line/demo/line-threshold.js @@ -0,0 +1,32 @@ +import { Line } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLine = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/temperatures2.json', + }, + xField: (d) => new Date(d.date), + yField: 'value', + shapeField: 'hvh', + colorField: 'value', + axis: { + x: { title: 'date' }, + }, + style: { + gradient: 'y', + lineWidth: 1.5, + lineJoin: 'round', + }, + scale: { + x: { utc: true }, + y: { nice: true }, + color: { type: 'threshold', domain: [55], range: ['black', 'red'] }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/line-var-size.js b/site/examples/statistics/line/demo/line-var-size.js new file mode 100644 index 000000000..79c79a3ca --- /dev/null +++ b/site/examples/statistics/line/demo/line-var-size.js @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Line } from '@ant-design/plots'; + +const DemoLine = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/55424a73-7cb8-4f79-b60d-3ab627ac5698.json', + }, + xField: (d) => new Date(d.year), + yField: 'value', + sizeField: 'value', + shapeField: 'trail', + legend: { size: false }, + colorField: 'category', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/meta.json b/site/examples/statistics/line/demo/meta.json new file mode 100644 index 000000000..404e629a5 --- /dev/null +++ b/site/examples/statistics/line/demo/meta.json @@ -0,0 +1,88 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "基础折线图", + "en": "Basic line plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*EH-dTLnE4bcAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "line-var-size.js", + "title": { + "zh": "变宽折线图", + "en": "Var Size Line Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2G1GRr60VI0AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "connect-nulls.js", + "title": { + "zh": "连接空值", + "en": "Connect nulls plot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*46DiTJLA2t8AAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "series.js", + "title": { + "zh": "系列折线图", + "en": "Series Line plot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*y84BS5AqJLYAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "normalize.js", + "title": { + "zh": "归一化折线图", + "en": "Normalize Line plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*S3FFTaw_HTUAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "color.js", + "title": { + "zh": "多色折线图", + "en": "Colors Line plot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*WVwSRa-HQAcAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "line-threshold.js", + "title": { + "zh": "阈值折线图", + "en": "Threshold Line Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*YUrlS4AWYFkAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "line-text.js", + "title": { + "zh": "文本标记的折线图", + "en": "Line, Text Annotation" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*QQesT7AIqVgAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "slider.js", + "title": { + "zh": "缩略轴", + "en": "Slider" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*xZAVSKcWzrwAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "style-callback.js", + "title": { + "zh": "通过回调函数指定折线样式", + "en": "Set line style through callback" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*p1n4RI9YynkAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/line/demo/normalize.js b/site/examples/statistics/line/demo/normalize.js new file mode 100644 index 000000000..9fcd8c1dd --- /dev/null +++ b/site/examples/statistics/line/demo/normalize.js @@ -0,0 +1,33 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Line } from '@ant-design/plots'; + +const DemoLine = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/indices.json', + }, + xField: (d) => new Date(d.Date), + yField: 'Close', + colorField: 'Symbol', + normalize: { basis: 'first', groupBy: 'color' }, + scale: { + y: { type: 'log' }, + }, + axis: { + y: { title: '↑ Change in price (%)' }, + }, + label: { + text: 'Symbol', + selector: 'last', + style: { + fontSize: 10, + }, + }, + tooltip: { channel: 'y', valueFormatter: '.1f' }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/series.js b/site/examples/statistics/line/demo/series.js new file mode 100644 index 000000000..4b06c04ae --- /dev/null +++ b/site/examples/statistics/line/demo/series.js @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Line } from '@ant-design/plots'; + +const DemoLine = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/line-series.json', + }, + xField: (d) => new Date(d.date), + yField: 'unemployment', + colorField: 'steelblue', + seriesField: 'division', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/slider.js b/site/examples/statistics/line/demo/slider.js new file mode 100644 index 000000000..536609642 --- /dev/null +++ b/site/examples/statistics/line/demo/slider.js @@ -0,0 +1,23 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Line } from '@ant-design/plots'; +import { format } from 'fecha'; + +const DemoLine = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/line-slider.json', + }, + xField: (d) => new Date(d.date), + yField: 'close', + axis: { x: { title: false, size: 40 }, y: { title: false, size: 36 } }, + slider: { + x: { labelFormatter: (d) => format(d, 'YYYY/M/D') }, + y: { labelFormatter: '~s' }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/demo/style-callback.js b/site/examples/statistics/line/demo/style-callback.js new file mode 100644 index 000000000..d41bdaeb5 --- /dev/null +++ b/site/examples/statistics/line/demo/style-callback.js @@ -0,0 +1,45 @@ +import { Line } from '@ant-design/plots'; +import React, { useState, useEffect } from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLine = () => { + const [data, setData] = useState([]); + + useEffect(() => { + asyncFetch(); + }, []); + + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/bmw-prod/c48dbbb1-fccf-4a46-b68f-a3ddb4908b68.json') + .then((response) => response.json()) + .then((json) => setData(json)) + .catch((error) => { + console.log('fetch data failed', error); + }); + }; + const config = { + data, + xField: 'date', + yField: 'value', + colorField: 'type', + axis: { + y: { + labelFormatter: (v) => `${v}`.replace(/\d{1,3}(?=(\d{3})+$)/g, (s) => `${s},`), + }, + }, + scale: { color: { range: ['#30BF78', '#F4664A', '#FAAD14'] } }, + style: { + lineWidth: 2, + lineDash: (data) => { + if (data[0].type === 'register') return [4, 4]; + }, + opacity: (data) => { + if (data[0].type !== 'register') return 0.5; + }, + }, + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/line/index.en.md b/site/examples/statistics/line/index.en.md new file mode 100644 index 000000000..e92d9748d --- /dev/null +++ b/site/examples/statistics/line/index.en.md @@ -0,0 +1,4 @@ +--- +title: Line +order: 0 +--- \ No newline at end of file diff --git a/site/examples/statistics/line/index.zh.md b/site/examples/statistics/line/index.zh.md new file mode 100644 index 000000000..cb20f682d --- /dev/null +++ b/site/examples/statistics/line/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 折线图 +order: 0 +--- + + diff --git a/site/examples/statistics/liquid/API.en.md b/site/examples/statistics/liquid/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/liquid/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/liquid/API.zh.md b/site/examples/statistics/liquid/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/liquid/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/liquid/demo/liquid-background.js b/site/examples/statistics/liquid/demo/liquid-background.js new file mode 100644 index 000000000..f0a29b655 --- /dev/null +++ b/site/examples/statistics/liquid/demo/liquid-background.js @@ -0,0 +1,15 @@ +import { Liquid } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLiquid = () => { + const config = { + percent: 0.3, + style: { + backgroundFill: 'pink', + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/liquid/demo/liquid-custom-shape.js b/site/examples/statistics/liquid/demo/liquid-custom-shape.js new file mode 100644 index 000000000..814e99666 --- /dev/null +++ b/site/examples/statistics/liquid/demo/liquid-custom-shape.js @@ -0,0 +1,36 @@ +import { Liquid } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLiquid = () => { + const config = { + percent: 0.3, + style: { + shape: (x, y, r) => { + const path = []; + const w = r * 2; + + for (let i = 0; i < 5; i++) { + path.push([ + i === 0 ? 'M' : 'L', + (Math.cos(((18 + i * 72) * Math.PI) / 180) * w) / 2 + x, + (-Math.sin(((18 + i * 72) * Math.PI) / 180) * w) / 2 + y, + ]); + path.push([ + 'L', + (Math.cos(((54 + i * 72) * Math.PI) / 180) * w) / 4 + x, + (-Math.sin(((54 + i * 72) * Math.PI) / 180) * w) / 4 + y, + ]); + } + path.push(['Z']); + return path; + }, + outlineBorder: 4, + outlineDistance: 8, + waveLength: 128, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/liquid/demo/liquid-pin.js b/site/examples/statistics/liquid/demo/liquid-pin.js new file mode 100644 index 000000000..084420ae3 --- /dev/null +++ b/site/examples/statistics/liquid/demo/liquid-pin.js @@ -0,0 +1,19 @@ +import { Liquid } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLiquid = () => { + const config = { + percent: 0.7, + style: { + shape: 'pin', + textFill: '#fff', + outlineBorder: 4, + outlineDistance: 8, + waveLength: 128, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/liquid/demo/liquid.js b/site/examples/statistics/liquid/demo/liquid.js new file mode 100644 index 000000000..bcf95bcc0 --- /dev/null +++ b/site/examples/statistics/liquid/demo/liquid.js @@ -0,0 +1,17 @@ +import { Liquid } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLiquid = () => { + const config = { + percent: 0.3, + style: { + outlineBorder: 4, + outlineDistance: 8, + waveLength: 128, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/liquid/demo/meta.json b/site/examples/statistics/liquid/demo/meta.json new file mode 100644 index 000000000..112b31638 --- /dev/null +++ b/site/examples/statistics/liquid/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "liquid.js", + "title": { + "zh": "水波图", + "en": "Liquid Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*cHArRaizyBsAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "liquid-pin.js", + "title": { + "zh": "水滴形状水波图", + "en": "Pin Liquid Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*G5TyT4PNLK0AAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "liquid-background.js", + "title": { + "zh": "带背景的水波图", + "en": "Background Liquid Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*QgkTSq1OdvoAAAAAAAAAAAAADo2bAQ/original" + }, + { + "filename": "liquid-custom-shape.js", + "title": { + "zh": "自定义形状水波图", + "en": "Custom Shape Liquid Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_za7we3/afts/img/A*LYOpQLfrlqMAAAAAAAAAAAAADo2bAQ/original" + } + ] +} diff --git a/site/examples/statistics/liquid/index.en.md b/site/examples/statistics/liquid/index.en.md new file mode 100644 index 000000000..fea08a230 --- /dev/null +++ b/site/examples/statistics/liquid/index.en.md @@ -0,0 +1,4 @@ +--- +title: Liquid +order: 13 +--- diff --git a/site/examples/statistics/liquid/index.zh.md b/site/examples/statistics/liquid/index.zh.md new file mode 100644 index 000000000..74f6d8d64 --- /dev/null +++ b/site/examples/statistics/liquid/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 水波图 +order: 13 +--- diff --git a/site/examples/statistics/pie/API.en.md b/site/examples/statistics/pie/API.en.md new file mode 100644 index 000000000..d2904f041 --- /dev/null +++ b/site/examples/statistics/pie/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/pie/API.zh.md b/site/examples/statistics/pie/API.zh.md new file mode 100644 index 000000000..8279c839f --- /dev/null +++ b/site/examples/statistics/pie/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/pie/demo/association-pie.js b/site/examples/statistics/pie/demo/association-pie.js new file mode 100644 index 000000000..4d38a48f8 --- /dev/null +++ b/site/examples/statistics/pie/demo/association-pie.js @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const PlotMaps = {}; + +const DemoPie = () => { + const [data, setData] = useState({}); + + useEffect(() => { + asyncFetch(); + }, []); + + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/antfincdn/fKTgtjKdaN/association-pie.json') + .then((response) => response.json()) + .then((json) => setData(json)) + .catch((error) => { + console.log('fetch data failed', error); + }); + }; + + if (!Object.keys(data).length) { + return null; + } + + const showTooltip = (evt, pie) => { + Object.keys(PlotMaps).forEach((plot) => { + if (plot !== pie) { + PlotMaps[plot].chart.emit('tooltip:show', { + data: { data: { area: evt.data.data.area } }, + }); + PlotMaps[plot].chart.emit('element:highlight', { + data: { data: { area: evt.data.data.area } }, + }); + } + }); + }; + + const hideTooltip = (evt, pie) => { + Object.keys(PlotMaps).forEach((plot) => { + if (plot !== pie) { + PlotMaps[plot].chart.emit('tooltip:hide', { + data: { data: { area: evt.data.data.area } }, + }); + PlotMaps[plot].chart.emit('element:unhighlight', { + data: { data: { area: evt.data.data.area } }, + }); + } + }); + }; + + const LeftConfig = { + angleField: 'bill', + colorField: 'area', + data: data.pie1, + label: { + text: 'bill', + }, + legend: false, + tooltip: { + title: 'area', + }, + interaction: { + elementHighlight: true, + }, + state: { + inactive: { opacity: 0.5 }, + }, + }; + const RightConfig = { + angleField: 'value', + colorField: 'area', + data: data.pie2, + label: { + text: 'value', + }, + legend: false, + tooltip: { + title: 'area', + }, + interaction: { + elementHighlight: true, + }, + state: { + inactive: { opacity: 0.5 }, + }, + }; + return ( +
+ { + PlotMaps.leftPie = plot; + plot.chart.on('interval:pointerover', (evt) => { + showTooltip(evt, 'leftPie'); + }); + plot.chart.on('interval:pointerout', (evt) => { + hideTooltip(evt, 'leftPie'); + }); + }} + /> + { + PlotMaps.rightPie = plot; + plot.chart.on('interval:pointerover', (evt) => { + showTooltip(evt, 'rightPie'); + }); + plot.chart.on('interval:pointerout', (evt) => { + hideTooltip(evt, 'rightPie'); + }); + }} + /> +
+ ); +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/basic-donut.js b/site/examples/statistics/pie/demo/basic-donut.js new file mode 100644 index 000000000..2b550b00e --- /dev/null +++ b/site/examples/statistics/pie/demo/basic-donut.js @@ -0,0 +1,48 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const DemoPie = () => { + const config = { + data: [ + { type: '分类一', value: 27 }, + { type: '分类二', value: 25 }, + { type: '分类三', value: 18 }, + { type: '分类四', value: 15 }, + { type: '分类五', value: 10 }, + { type: '其他', value: 5 }, + ], + angleField: 'value', + colorField: 'type', + innerRadius: 0.6, + label: { + text: 'value', + style: { + fontWeight: 'bold', + }, + }, + legend: { + color: { + title: false, + position: 'right', + rowPadding: 5, + }, + }, + annotations: [ + { + type: 'text', + style: { + text: 'AntV\nCharts', + x: '50%', + y: '50%', + textAlign: 'center', + fontSize: 40, + fontStyle: 'bold', + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/basic.js b/site/examples/statistics/pie/demo/basic.js new file mode 100644 index 000000000..c71102175 --- /dev/null +++ b/site/examples/statistics/pie/demo/basic.js @@ -0,0 +1,34 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const DemoPie = () => { + const config = { + data: [ + { type: '分类一', value: 27 }, + { type: '分类二', value: 25 }, + { type: '分类三', value: 18 }, + { type: '分类四', value: 15 }, + { type: '分类五', value: 10 }, + { type: '其他', value: 5 }, + ], + angleField: 'value', + colorField: 'type', + label: { + text: 'value', + style: { + fontWeight: 'bold', + }, + }, + legend: { + color: { + title: false, + position: 'right', + rowPadding: 5, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/custom-label.js b/site/examples/statistics/pie/demo/custom-label.js new file mode 100644 index 000000000..54489ab23 --- /dev/null +++ b/site/examples/statistics/pie/demo/custom-label.js @@ -0,0 +1,44 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const data = [ + { type: '分类一', value: 27 }, + { type: '分类二', value: 25 }, + { type: '分类三', value: 18 }, + { type: '分类四', value: 15 }, + { type: '分类五', value: 10 }, + { type: '其他', value: 5 }, +]; + +const customLabel = (_, datum) => ( +
+
+
+ {datum.type} : {datum.value} +
+
+); + +const DemoPie = () => { + const config = { + data, + angleField: 'value', + colorField: 'type', + label: { + text: 'type', + position: 'outside', + textAlign: 'center', + transform: [ + { + type: 'contrastReverse', + }, + ], + render: customLabel, + }, + legend: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/doughnut.js b/site/examples/statistics/pie/demo/doughnut.js new file mode 100644 index 000000000..6e75b7f22 --- /dev/null +++ b/site/examples/statistics/pie/demo/doughnut.js @@ -0,0 +1,40 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const DemoPie = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/pie-doughnut.json', + }, + angleField: 'value', + colorField: 'name', + legend: false, + innerRadius: 0.6, + labels: [ + { text: 'name', style: { fontSize: 10, fontWeight: 'bold' } }, + { + text: (d, i, data) => (i < data.length - 3 ? d.value : ''), + style: { + fontSize: 9, + dy: 12, + }, + }, + ], + style: { + stroke: '#fff', + inset: 1, + radius: 10, + }, + scale: { + color: { + palette: 'spectral', + offset: (t) => t * 0.8 + 0.1, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/meta.json b/site/examples/statistics/pie/demo/meta.json new file mode 100644 index 000000000..d83d29db2 --- /dev/null +++ b/site/examples/statistics/pie/demo/meta.json @@ -0,0 +1,80 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "基础饼图", + "en": "Basic pie plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*DSItR6amdjMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "outer-label.js", + "title": { + "zh": "饼图 - 外部图形标签", + "en": "Pie plot - outer label" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*00hCQp7bMgsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "spider-label.js", + "title": { + "zh": "饼图 - 蜘蛛布局标签", + "en": "Pie plot - spider label" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Nln-QqYVb80AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom-label.js", + "title": { + "zh": "饼图 - 自定义标签", + "en": "Pie plot - custom label" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ZAK6RoTfHsAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "quarter-circle.js", + "title": { + "zh": "饼图 - 四分之一圆", + "en": "Pie plot - Quarter circle pie" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*HtmQSoTfo0gAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "pie-texture.js", + "title": { + "zh": "饼图 - 带纹理的饼图", + "en": "Pie plot - Pie plot fill with texture" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*340IRYjd26IAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "basic-donut.js", + "title": { + "zh": "基础环图", + "en": "Basic donut plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Fl1OT6t5avcAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "doughnut.js", + "title": { + "zh": "甜甜圈图", + "en": "Pie plot - Donut Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*_OHoQb0Xho8AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "association-pie.js", + "title": { + "zh": "饼图联动", + "en": "Association Pie" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/8hTlCI2Fbn/association-pie.gif" + } + ] +} diff --git a/site/examples/statistics/pie/demo/outer-label.js b/site/examples/statistics/pie/demo/outer-label.js new file mode 100644 index 000000000..80443930c --- /dev/null +++ b/site/examples/statistics/pie/demo/outer-label.js @@ -0,0 +1,32 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const DemoPie = () => { + const config = { + data: [ + { type: '分类一', value: 27 }, + { type: '分类二', value: 25 }, + { type: '分类三', value: 18 }, + { type: '分类四', value: 15 }, + { type: '分类五', value: 10 }, + { type: '其他', value: 5 }, + ], + angleField: 'value', + colorField: 'type', + label: { + text: 'value', + position: 'outside', + }, + legend: { + color: { + title: false, + position: 'right', + rowPadding: 5, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/pie-texture.js b/site/examples/statistics/pie/demo/pie-texture.js new file mode 100644 index 000000000..6d29a2de4 --- /dev/null +++ b/site/examples/statistics/pie/demo/pie-texture.js @@ -0,0 +1,34 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const DemoPie = () => { + const config = { + data: [ + { sex: '男', sold: 0.45 }, + { sex: '女', sold: 0.55 }, + ], + angleField: 'sold', + colorField: 'sex', + legend: false, + label: { + text: ({ sex, sold }) => { + return `${sex}: ${parseInt(sold * 100)}%`; + }, + fill: '#fff', + fontSize: 18, + }, + style: { + padding: 10, + fill: ({ sex }) => { + if (sex === '男') { + return 'p(a)https://gw.alipayobjects.com/zos/antfincdn/FioHMFgIld/pie-wenli1.png'; + } + return 'p(a)https://gw.alipayobjects.com/zos/antfincdn/Ye2DqRx%2627/pie-wenli2.png'; + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/quarter-circle.js b/site/examples/statistics/pie/demo/quarter-circle.js new file mode 100644 index 000000000..cea7e0763 --- /dev/null +++ b/site/examples/statistics/pie/demo/quarter-circle.js @@ -0,0 +1,43 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const DemoPie = () => { + const config = { + data: [ + { type: '分类一', value: 27 }, + { type: '分类二', value: 25 }, + { type: '分类三', value: 18 }, + { type: '分类四', value: 15 }, + { type: '分类五', value: 10 }, + { type: '其他', value: 5 }, + ], + angleField: 'value', + colorField: 'type', + startAngle: Math.PI, + endAngle: Math.PI * 1.5, + label: { + text: 'type', + style: { + fontSize: 14, + fontWeight: 'bold', + transform: 'rotate(40)', + }, + transform: [ + { + type: 'overlapHide', + }, + ], + }, + legend: { + color: { + title: false, + position: 'right', + rowPadding: 5, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/demo/spider-label.js b/site/examples/statistics/pie/demo/spider-label.js new file mode 100644 index 000000000..cf234b122 --- /dev/null +++ b/site/examples/statistics/pie/demo/spider-label.js @@ -0,0 +1,34 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Pie } from '@ant-design/plots'; + +const data = [ + { type: '分类一', value: 27 }, + { type: '分类二', value: 25 }, + { type: '分类三', value: 18 }, + { type: '分类四', value: 15 }, + { type: '分类五', value: 10 }, + { type: '其他', value: 5 }, +]; +const DemoPie = () => { + const config = { + data, + angleField: 'value', + colorField: 'type', + radius: 0.8, + label: { + text: (d) => `${d.type}\n ${d.value}`, + position: 'spider', + }, + legend: { + color: { + title: false, + position: 'right', + rowPadding: 5, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/pie/index.en.md b/site/examples/statistics/pie/index.en.md new file mode 100644 index 000000000..10ce89471 --- /dev/null +++ b/site/examples/statistics/pie/index.en.md @@ -0,0 +1,4 @@ +--- +title: Pie +order: 1 +--- \ No newline at end of file diff --git a/site/examples/statistics/pie/index.zh.md b/site/examples/statistics/pie/index.zh.md new file mode 100644 index 000000000..5538057b0 --- /dev/null +++ b/site/examples/statistics/pie/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 饼图 +order: 1 +--- + + diff --git a/site/examples/statistics/radar/API.en.md b/site/examples/statistics/radar/API.en.md new file mode 100644 index 000000000..6e912f286 --- /dev/null +++ b/site/examples/statistics/radar/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/radar/API.zh.md b/site/examples/statistics/radar/API.zh.md new file mode 100644 index 000000000..87995145b --- /dev/null +++ b/site/examples/statistics/radar/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/radar/demo/area-radial.js b/site/examples/statistics/radar/demo/area-radial.js new file mode 100644 index 000000000..895d46f85 --- /dev/null +++ b/site/examples/statistics/radar/demo/area-radial.js @@ -0,0 +1,57 @@ +import { Radar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoRadar = () => { + const config = { + autoFit: false, + width: 954, + height: 954, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/seasonal-weather.json', + transform: [ + { + type: 'map', + callback: (d) => ({ + ...d, + date: new Date(d.date), + }), + }, + ], + }, + xField: 'date', + yField: 'avg', + scale: { x: { utc: true } }, + style: { stroke: 'steelblue', strokeWidth: 1.5 }, + tooltip: { items: [{ channel: 'y', valueFormatter: '.1f' }] }, + innerRadius: 0.4, + axis: { + y: { + zIndex: 1, + direction: 'center', + title: null, + labelFormatter: (d, i, array) => (i === array.length - 1 ? `${d}°F` : `${d}`), + style: { labelStroke: '#fff', labelStrokeWidth: 5 }, + }, + x: { grid: true, position: 'inner' }, + }, + annotations: [ + { + type: 'area', + xField: 'date', + yField: ['minmin', 'maxmax'], + style: { fill: 'lightsteelblue', fillOpacity: 0.2 }, + }, + { + type: 'area', + xField: 'date', + yField: ['min', 'max'], + style: { fill: 'steelblue', fillOpacity: 0.2 }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radar/demo/basic.js b/site/examples/statistics/radar/demo/basic.js new file mode 100644 index 000000000..5d371c5e4 --- /dev/null +++ b/site/examples/statistics/radar/demo/basic.js @@ -0,0 +1,49 @@ +import { Radar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { name: 'G2', star: 10371 }, + { name: 'G6', star: 7380 }, + { name: 'F2', star: 7414 }, + { name: 'L7', star: 2140 }, + { name: 'X6', star: 660 }, + { name: 'AVA', star: 885 }, + { name: 'G2Plot', star: 1626 }, +]; + +const DemoRadar = () => { + const config = { + data: data.map((d) => ({ ...d, star: Math.sqrt(d.star) })), + xField: 'name', + yField: 'star', + area: { + style: { + fillOpacity: 0.2, + }, + }, + scale: { + x: { + padding: 0.5, + align: 0, + }, + y: { + nice: true, + }, + }, + axis: { + x: { + title: false, + grid: true, + }, + y: { + gridAreaFill: 'rgba(0, 0, 0, 0.04)', + label: false, + title: false, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radar/demo/meta.json b/site/examples/statistics/radar/demo/meta.json new file mode 100644 index 000000000..6f842cca9 --- /dev/null +++ b/site/examples/statistics/radar/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "基础雷达图", + "en": "Basic Radar" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*okGTT7D7fBEAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "radar.js", + "title": { + "zh": "雷达图(一般极坐标 polar)", + "en": "Radar (coordinate polar)" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*6QupRa5exboAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "parallel-radar.js", + "title": { + "zh": "雷达图(雷达图特殊极坐标 radar)", + "en": "Radar (coordinate radar)" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*4sqyTZUFCNkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "area-radial.js", + "title": { + "zh": "径向面积图", + "en": "Radial Area Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*-m-2QolJUt0AAAAAAAAAAAAADmJ7AQ" + } + ] +} diff --git a/site/examples/statistics/radar/demo/parallel-radar.js b/site/examples/statistics/radar/demo/parallel-radar.js new file mode 100644 index 000000000..325bdd018 --- /dev/null +++ b/site/examples/statistics/radar/demo/parallel-radar.js @@ -0,0 +1,64 @@ +import { Radar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const axis = { + zIndex: 1, + style: { + labelStroke: '#fff', + labelStrokeWidth: 5, + labelFontSize: 10, + labelStrokeLineJoin: 'round', + titleStroke: '#fff', + titleFontSize: 10, + titleStrokeWidth: 5, + titleStrokeLineJoin: 'round', + lineStroke: 'black', + tickStroke: 'black', + lineStrokeWidth: 1, + }, +}; + +const DemoRadar = () => { + const config = { + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/cars3.json', + }, + colorField: 'weight (lb)', + coordinateType: 'radar', + positionField: [ + 'economy (mpg)', + 'cylinders', + 'displacement (cc)', + 'power (hp)', + 'weight (lb)', + '0-60 mph (s)', + 'year', + ], + axis: { + position: axis, + position1: axis, + position2: axis, + position3: axis, + position4: axis, + position5: axis, + position6: axis, + position7: axis, + }, + legend: { + color: { + position: 'bottom', + labelFormatter: '~s', + length: 200, + layout: { justifyContent: 'center' }, + }, + }, + style: { strokeWidth: 1.5, strokeOpacity: 0.4 }, + interaction: { tooltip: { series: false } }, + scale: { color: { palette: 'brBG', offset: (t) => 1 - t } }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radar/demo/radar.js b/site/examples/statistics/radar/demo/radar.js new file mode 100644 index 000000000..0a3f96e3b --- /dev/null +++ b/site/examples/statistics/radar/demo/radar.js @@ -0,0 +1,49 @@ +import { Radar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { item: 'Design', type: 'a', score: 70 }, + { item: 'Design', type: 'b', score: 30 }, + { item: 'Development', type: 'a', score: 60 }, + { item: 'Development', type: 'b', score: 70 }, + { item: 'Marketing', type: 'a', score: 50 }, + { item: 'Marketing', type: 'b', score: 60 }, + { item: 'Users', type: 'a', score: 40 }, + { item: 'Users', type: 'b', score: 50 }, + { item: 'Test', type: 'a', score: 60 }, + { item: 'Test', type: 'b', score: 70 }, + { item: 'Language', type: 'a', score: 70 }, + { item: 'Language', type: 'b', score: 50 }, + { item: 'Technology', type: 'a', score: 50 }, + { item: 'Technology', type: 'b', score: 40 }, + { item: 'Support', type: 'a', score: 30 }, + { item: 'Support', type: 'b', score: 40 }, + { item: 'Sales', type: 'a', score: 60 }, + { item: 'Sales', type: 'b', score: 40 }, + { item: 'UX', type: 'a', score: 50 }, + { item: 'UX', type: 'b', score: 60 }, +]; + +const DemoRadar = () => { + const config = { + data, + xField: 'item', + yField: 'score', + colorField: 'type', + shapeField: 'smooth', + area: { + style: { + fillOpacity: 0.5, + }, + }, + scale: { x: { padding: 0.5, align: 0 }, y: { tickCount: 5, domainMax: 80 } }, + axis: { x: { grid: true }, y: { zIndex: 1, title: false } }, + style: { + lineWidth: 2, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radar/index.en.md b/site/examples/statistics/radar/index.en.md new file mode 100644 index 000000000..c129061b2 --- /dev/null +++ b/site/examples/statistics/radar/index.en.md @@ -0,0 +1,4 @@ +--- +title: Radar +order: 2 +--- \ No newline at end of file diff --git a/site/examples/statistics/radar/index.zh.md b/site/examples/statistics/radar/index.zh.md new file mode 100644 index 000000000..573b78ad0 --- /dev/null +++ b/site/examples/statistics/radar/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 雷达图 +order: 2 +--- + +雷达图为折线图或面积图的极坐标版本,可用于查看哪些变量具有相似数值,或者每个变量中有没有任何异常值。 diff --git a/site/examples/statistics/radial-bar/API.en.md b/site/examples/statistics/radial-bar/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/radial-bar/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/radial-bar/API.zh.md b/site/examples/statistics/radial-bar/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/radial-bar/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/radial-bar/demo/apple-watch-activity.js b/site/examples/statistics/radial-bar/demo/apple-watch-activity.js new file mode 100644 index 000000000..f841ee822 --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/apple-watch-activity.js @@ -0,0 +1,59 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { + name: 'activity1', + percent: 2370, + color: '#1ad5de', + icon: 'https://gw.alipayobjects.com/zos/antfincdn/ck11Y6aRrz/shangjiantou.png', + }, + { + name: 'activity2', + percent: 800, + color: '#a0ff03', + icon: 'https://gw.alipayobjects.com/zos/antfincdn/zY2JB7hhrO/shuangjiantou.png', + }, + { + name: 'activity3', + percent: 650, + color: '#e90b3a', + icon: 'https://gw.alipayobjects.com/zos/antfincdn/%24qBxSxdK05/jiantou.png', + }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'name', + yField: 'percent', + width: 400, + height: 244, + autoFit: false, + appendPadding: [50, 0, 50, 0], + // maxAngle: 90, //最大旋转角度, + radius: 1, + innerRadius: 0.2, + style: { + radius: 26, // 圆角 + }, + theme: 'dark', + colorField: 'name', + colorKey: 'color', + legend: false, + axis: { x: false, y: false }, + markBackground: { + opacity: 0.25, + color: 'color', + }, + scale: { + y: { + domain: [0, 12000], // 设定范围用于背景图的渲染获取最大值 + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/demo/background.js b/site/examples/statistics/radial-bar/demo/background.js new file mode 100644 index 000000000..f29ace413 --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/background.js @@ -0,0 +1,54 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { name: 'X6', star: 297 }, + { name: 'G', star: 506 }, + { name: 'AVA', star: 805 }, + { name: 'G2Plot', star: 1478 }, + { name: 'L7', star: 2029 }, + { name: 'G6', star: 7100 }, + { name: 'F2', star: 7346 }, + { name: 'G2', star: 10178 }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'name', + yField: 'star', + maxAngle: 350, + radius: 1, + innerRadius: 0.2, + tooltip: { + items: ['star'], + }, + legend: false, + axis: { + y: false, + }, + markBackground: { + opacity: 0.25, + }, + scale: { + y: { + domain: [0, 12000], // 设定范围用于背景图的渲染获取最大值 + }, + }, + style: { + radius: 180, + fill: ({ star }) => { + if (star > 10000) { + return '#6349ec'; + } else if (star > 1000) { + return '#ff9300'; + } + return '#ff93a7'; + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/demo/basic.js b/site/examples/statistics/radial-bar/demo/basic.js new file mode 100644 index 000000000..34b7ab265 --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/basic.js @@ -0,0 +1,31 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { name: 'X6', star: 297 }, + { name: 'G', star: 506 }, + { name: 'AVA', star: 805 }, + { name: 'G2Plot', star: 1478 }, + { name: 'L7', star: 2029 }, + { name: 'G6', star: 7100 }, + { name: 'F2', star: 7346 }, + { name: 'G2', star: 10178 }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'name', + yField: 'star', + // maxAngle: 90, //最大旋转角度, + radius: 1, + innerRadius: 0.2, + tooltip: { + items: ['star'], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/demo/color.js b/site/examples/statistics/radial-bar/demo/color.js new file mode 100644 index 000000000..38eacbbec --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/color.js @@ -0,0 +1,42 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { name: 'X6', star: 297 }, + { name: 'G', star: 506 }, + { name: 'AVA', star: 805 }, + { name: 'G2Plot', star: 1478 }, + { name: 'L7', star: 2029 }, + { name: 'G6', star: 7100 }, + { name: 'F2', star: 7346 }, + { name: 'G2', star: 10178 }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'name', + yField: 'star', + maxAngle: 270, + radius: 0.8, + innerRadius: 0.2, + tooltip: { + items: ['star'], + }, + // colorField: 'star', // 会默认给颜色 + style: { + fill: ({ star }) => { + if (star > 10000) { + return '#36c361'; + } else if (star > 1000) { + return '#2194ff'; + } + return '#ff4d4f'; + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/demo/grouped.js b/site/examples/statistics/radial-bar/demo/grouped.js new file mode 100644 index 000000000..4a9371fc3 --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/grouped.js @@ -0,0 +1,110 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { + year: '1991', + value: 3, + type: 'Lon', + }, + { + year: '1992', + value: 4, + type: 'Lon', + }, + { + year: '1993', + value: 3.5, + type: 'Lon', + }, + { + year: '1994', + value: 5, + type: 'Lon', + }, + { + year: '1995', + value: 4.9, + type: 'Lon', + }, + { + year: '1996', + value: 6, + type: 'Lon', + }, + { + year: '1997', + value: 7, + type: 'Lon', + }, + { + year: '1998', + value: 9, + type: 'Lon', + }, + { + year: '1999', + value: 13, + type: 'Lon', + }, + { + year: '1991', + value: 3, + type: 'Bor', + }, + { + year: '1992', + value: 4, + type: 'Bor', + }, + { + year: '1993', + value: 3.5, + type: 'Bor', + }, + { + year: '1994', + value: 5, + type: 'Bor', + }, + { + year: '1995', + value: 4.9, + type: 'Bor', + }, + { + year: '1996', + value: 6, + type: 'Bor', + }, + { + year: '1997', + value: 7, + type: 'Bor', + }, + { + year: '1998', + value: 9, + type: 'Bor', + }, + { + year: '1999', + value: 13, + type: 'Bor', + }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'year', + yField: 'value', + group: true, + maxAngle: 270, + colorField: 'type', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/demo/line.js b/site/examples/statistics/radial-bar/demo/line.js new file mode 100644 index 000000000..5164492ad --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/line.js @@ -0,0 +1,37 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { term: 'Zombieland', count: 9 }, + { term: 'Wieners', count: 8 }, + { term: 'Toy Story', count: 8 }, + { term: 'trashkannon', count: 7 }, + { term: 'the GROWLERS', count: 6 }, + { term: 'mudweiser', count: 6 }, + { term: 'ThunderCats', count: 4 }, + { term: 'The Taqwacores - Motion Picture', count: 4 }, + { term: 'The Shawshank Redemption', count: 2 }, + { term: 'The Olivia Experiment', count: 1 }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'term', + yField: 'count', + startAngle: Math.PI * 0.5, + maxAngle: 270, //最大旋转角度, + radius: 1, + innerRadius: 0.2, + legend: false, + axis: { y: false }, + tooltip: { + items: ['count'], + }, + sizeField: 10, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/demo/meta.json b/site/examples/statistics/radial-bar/demo/meta.json new file mode 100644 index 000000000..e9de352c9 --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/meta.json @@ -0,0 +1,66 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "玉珏图", + "en": "Radial-Bar plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-1GkR6WftskAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "round-corner.js", + "title": { + "zh": "带圆角的玉珏图", + "en": "Radial-Bar plot with rounded corner" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*FmTOT68Ai_EAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "color.js", + "title": { + "zh": "带自定义颜色的玉珏图", + "en": "Radial-Bar plot with custom color" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/l6uP5%26MiT7/385e3f80-52ec-49e9-9dfe-bd447e63203f.png" + }, + { + "filename": "stacked.js", + "title": { + "zh": "堆叠玉珏图", + "en": "Stacked Radial-Bar plot" + }, + + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/tft60hEBvN/594d5cfc-8da0-441b-89dd-81eb4f5657b6.png" + }, + { + "filename": "grouped.js", + "title": { + "zh": "分组玉珏图", + "en": "Grouped Radial-Bar plot" + }, + + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/M2EJJVBmHE/8984eee5-da51-4afb-8e81-34a8aaab47c0.png" + }, + { + "filename": "background.js", + "title": { + "zh": "带柱子背景的玉珏图", + "en": "Radial-Bar plot with background" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/A2Ik7iu%26YW/519113a4-f42e-48b2-a75d-1021add83c30.png" + }, + { + "filename": "line.js", + "title": { + "zh": "线形玉珏图", + "en": "Radial-line plot" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_f5c722/afts/img/A*GC6wT4GQ-oMAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/site/examples/statistics/radial-bar/demo/round-corner.js b/site/examples/statistics/radial-bar/demo/round-corner.js new file mode 100644 index 000000000..0089e8392 --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/round-corner.js @@ -0,0 +1,34 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { name: 'X6', star: 297 }, + { name: 'G', star: 506 }, + { name: 'AVA', star: 805 }, + { name: 'G2Plot', star: 1478 }, + { name: 'L7', star: 2029 }, + { name: 'G6', star: 7100 }, + { name: 'F2', star: 7346 }, + { name: 'G2', star: 10178 }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'name', + yField: 'star', + maxAngle: 90, + radius: 1, + innerRadius: 0.2, + style: { + radius: 26, // 圆角 + }, + scale: { + y: { nice: true }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/demo/stacked.js b/site/examples/statistics/radial-bar/demo/stacked.js new file mode 100644 index 000000000..0c4a3f65d --- /dev/null +++ b/site/examples/statistics/radial-bar/demo/stacked.js @@ -0,0 +1,110 @@ +import { RadialBar } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + { + year: '1991', + value: 3, + type: 'Lon', + }, + { + year: '1992', + value: 4, + type: 'Lon', + }, + { + year: '1993', + value: 3.5, + type: 'Lon', + }, + { + year: '1994', + value: 5, + type: 'Lon', + }, + { + year: '1995', + value: 4.9, + type: 'Lon', + }, + { + year: '1996', + value: 6, + type: 'Lon', + }, + { + year: '1997', + value: 7, + type: 'Lon', + }, + { + year: '1998', + value: 9, + type: 'Lon', + }, + { + year: '1999', + value: 13, + type: 'Lon', + }, + { + year: '1991', + value: 3, + type: 'Bor', + }, + { + year: '1992', + value: 4, + type: 'Bor', + }, + { + year: '1993', + value: 3.5, + type: 'Bor', + }, + { + year: '1994', + value: 5, + type: 'Bor', + }, + { + year: '1995', + value: 4.9, + type: 'Bor', + }, + { + year: '1996', + value: 6, + type: 'Bor', + }, + { + year: '1997', + value: 7, + type: 'Bor', + }, + { + year: '1998', + value: 9, + type: 'Bor', + }, + { + year: '1999', + value: 13, + type: 'Bor', + }, +]; + +const DemoRadialBar = () => { + const config = { + data, + xField: 'year', + yField: 'value', + stack: true, + maxAngle: 270, + colorField: 'type', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/radial-bar/index.en.md b/site/examples/statistics/radial-bar/index.en.md new file mode 100644 index 000000000..2e6859450 --- /dev/null +++ b/site/examples/statistics/radial-bar/index.en.md @@ -0,0 +1,4 @@ +--- +title: RadialBar +order: 21 +--- diff --git a/site/examples/statistics/radial-bar/index.zh.md b/site/examples/statistics/radial-bar/index.zh.md new file mode 100644 index 000000000..69c374d3c --- /dev/null +++ b/site/examples/statistics/radial-bar/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 玉珏图 +order: 21 +--- diff --git a/site/examples/statistics/rose/API.en.md b/site/examples/statistics/rose/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/rose/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/rose/API.zh.md b/site/examples/statistics/rose/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/rose/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/rose/demo/basic.js b/site/examples/statistics/rose/demo/basic.js new file mode 100644 index 000000000..850cc8883 --- /dev/null +++ b/site/examples/statistics/rose/demo/basic.js @@ -0,0 +1,18 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/basement_prod/6b4aa721-b039-49b9-99d8-540b3f87d339.json', + }, + xField: 'height', + yField: 'weight', + colorField: 'gender', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/rose/demo/meta.json b/site/examples/statistics/rose/demo/meta.json new file mode 100644 index 000000000..24f721c0b --- /dev/null +++ b/site/examples/statistics/rose/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "rose.js", + "title": { + "zh": "玫瑰图", + "en": "Rose Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*_DeKSr3oN5sAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "stacked-rose.js", + "title": { + "zh": "堆积玫瑰图", + "en": "Stacked Rose Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Lv1oT5JCtrMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "rose-label.js", + "title": { + "zh": "玫瑰图标签", + "en": "Rose Chart, Label" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Cx8zT7vT5bUAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/rose/demo/rose-label.js b/site/examples/statistics/rose/demo/rose-label.js new file mode 100644 index 000000000..179905379 --- /dev/null +++ b/site/examples/statistics/rose/demo/rose-label.js @@ -0,0 +1,35 @@ +import { Rose } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoRose = () => { + const config = { + width: 720, + height: 720, + autoFit: false, + radius: 0.85, + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/rose-rose-label.json', + }, + xField: 'year', + yField: 'people', + colorField: 'year', + transform: [{ type: 'groupX', y: 'sum' }], + scale: { y: { type: 'sqrt' }, x: { padding: 0 } }, + axis: false, + legend: { color: { length: 400, layout: { justifyContent: 'center' } } }, + labels: [ + { + text: 'people', + position: 'outside', + formatter: '~s', + transform: [{ type: 'overlapDodgeY' }], + }, + ], + tooltip: { items: [{ channel: 'y', valueFormatter: '~s' }] }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/rose/demo/rose.js b/site/examples/statistics/rose/demo/rose.js new file mode 100644 index 000000000..3aa9d38a0 --- /dev/null +++ b/site/examples/statistics/rose/demo/rose.js @@ -0,0 +1,32 @@ +import { Rose } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoRose = () => { + const config = { + width: 720, + height: 720, + autoFit: false, + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/rose-rose.json', + }, + xField: 'year', + yField: 'people', + scale: { y: { type: 'sqrt' } }, + transform: [{ type: 'groupX', y: 'sum' }], + axis: { + y: { + title: 'sum of people', + labelFormatter: '~s', + tickCount: 5, + tickFilter: (d, i) => i !== 0, + direction: 'right', + }, + }, + tooltip: { items: [{ channel: 'y', valueFormatter: '~s' }] }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/rose/demo/stacked-rose.js b/site/examples/statistics/rose/demo/stacked-rose.js new file mode 100644 index 000000000..9d52f1451 --- /dev/null +++ b/site/examples/statistics/rose/demo/stacked-rose.js @@ -0,0 +1,56 @@ +import { Rose } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoRose = () => { + const colors = ['#98abc5', '#8a89a6', '#7b6888', '#6b486b', '#a05d56', '#d0743c', '#ff8c00']; + + const config = { + width: 800, + height: 800, + autoFit: false, + innerRadius: 0.4, + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/rose-stacked-rose.json', + transform: [ + { + type: 'fold', + fields: [ + 'Under 5 Years', + '5 to 13 Years', + '14 to 17 Years', + '18 to 24 Years', + '25 to 44 Years', + '45 to 64 Years', + '65 Years and Over', + ], + key: 'Age', + value: 'Population', + }, + ], + }, + xField: 'State', + yField: 'Population', + colorField: 'Age', + stack: true, + scale: { + y: { type: 'sqrt' }, + color: { + range: colors, + }, + }, + axis: { + x: { position: 'inner' }, + y: { + labelFormatter: '~s', + tickFilter: (_, i) => i !== 0, + direction: 'center', + }, + }, + legend: { color: { position: 'center', display: 'grid', gridCol: 1 } }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/rose/index.en.md b/site/examples/statistics/rose/index.en.md new file mode 100644 index 000000000..4c833b4f2 --- /dev/null +++ b/site/examples/statistics/rose/index.en.md @@ -0,0 +1,4 @@ +--- +title: Rose +order: 13 +--- \ No newline at end of file diff --git a/site/examples/statistics/rose/index.zh.md b/site/examples/statistics/rose/index.zh.md new file mode 100644 index 000000000..c83f61907 --- /dev/null +++ b/site/examples/statistics/rose/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 玫瑰图 +order: 13 +--- + +玫瑰图区别于饼图,使用搬家来反映数值,而饼图通过弧度来表现。通过对数据进行夸张,有利于观测分辨数值相近的数据。 diff --git a/site/examples/statistics/sankey/API.en.md b/site/examples/statistics/sankey/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/sankey/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/sankey/API.zh.md b/site/examples/statistics/sankey/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/sankey/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/sankey/demo/alipay.js b/site/examples/statistics/sankey/demo/alipay.js new file mode 100644 index 000000000..8066a3f1a --- /dev/null +++ b/site/examples/statistics/sankey/demo/alipay.js @@ -0,0 +1,50 @@ +import { Sankey } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const colors = [ + '#5B8FF9', + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262fd', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; + +const data = [ + { source: '首次打开', target: '首页 UV', value: 160 }, + { source: '结果页', target: '首页 UV', value: 40 }, + { source: '验证页', target: '首页 UV', value: 10 }, + { source: '我的', target: '首页 UV', value: 10 }, + { source: '朋友', target: '首页 UV', value: 8 }, + { source: '其他来源', target: '首页 UV', value: 27 }, + { source: '首页 UV', target: '理财', value: 30 }, + { source: '首页 UV', target: '扫一扫', value: 40 }, + { source: '首页 UV', target: '服务', value: 35 }, + { source: '首页 UV', target: '蚂蚁森林', value: 25 }, + { source: '首页 UV', target: '跳失', value: 10 }, + { source: '首页 UV', target: '借呗', value: 30 }, + { source: '首页 UV', target: '花呗', value: 40 }, + { source: '首页 UV', target: '其他流向', value: 45 }, +]; + +const DemoSankey = () => { + const config = { + data, + scale: { color: { range: colors } }, + layout: { nodeWidth: 0.01 }, + linkColorField: (d) => d.source.key, + style: { + labelFontSize: 13, + linkFillOpacity: 0.4, + nodeStrokeWidth: 0, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sankey/demo/basic.js b/site/examples/statistics/sankey/demo/basic.js new file mode 100644 index 000000000..32730af57 --- /dev/null +++ b/site/examples/statistics/sankey/demo/basic.js @@ -0,0 +1,121 @@ +import { Sankey } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoSankey = () => { + const config = { + data: { + value: { + links: [ + { source: "Agricultural 'waste'", target: 'Bio-conversion', value: 124.729 }, + { source: 'Bio-conversion', target: 'Liquid', value: 0.597 }, + { source: 'Bio-conversion', target: 'Losses', value: 26.862 }, + { source: 'Bio-conversion', target: 'Solid', value: 280.322 }, + { source: 'Bio-conversion', target: 'Gas', value: 81.144 }, + { source: 'Biofuel imports', target: 'Liquid', value: 35 }, + { source: 'Biomass imports', target: 'Solid', value: 35 }, + { source: 'Coal imports', target: 'Coal', value: 11.606 }, + { source: 'Coal reserves', target: 'Coal', value: 63.965 }, + { source: 'Coal', target: 'Solid', value: 75.571 }, + { source: 'District heating', target: 'Industry', value: 10.639 }, + { source: 'District heating', target: 'Heating and cooling - commercial', value: 22.505 }, + { source: 'District heating', target: 'Heating and cooling - homes', value: 46.184 }, + { source: 'Electricity grid', target: 'Over generation / exports', value: 104.453 }, + { source: 'Electricity grid', target: 'Heating and cooling - homes', value: 113.726 }, + { source: 'Electricity grid', target: 'H2 conversion', value: 27.14 }, + { source: 'Electricity grid', target: 'Industry', value: 342.165 }, + { source: 'Electricity grid', target: 'Road transport', value: 37.797 }, + { source: 'Electricity grid', target: 'Agriculture', value: 4.412 }, + { source: 'Electricity grid', target: 'Heating and cooling - commercial', value: 40.858 }, + { source: 'Electricity grid', target: 'Losses', value: 56.691 }, + { source: 'Electricity grid', target: 'Rail transport', value: 7.863 }, + { source: 'Electricity grid', target: 'Lighting & appliances - commercial', value: 90.008 }, + { source: 'Electricity grid', target: 'Lighting & appliances - homes', value: 93.494 }, + { source: 'Gas imports', target: 'Ngas', value: 40.719 }, + { source: 'Gas reserves', target: 'Ngas', value: 82.233 }, + { source: 'Gas', target: 'Heating and cooling - commercial', value: 0.129 }, + { source: 'Gas', target: 'Losses', value: 1.401 }, + { source: 'Gas', target: 'Thermal generation', value: 151.891 }, + { source: 'Gas', target: 'Agriculture', value: 2.096 }, + { source: 'Gas', target: 'Industry', value: 48.58 }, + { source: 'Geothermal', target: 'Electricity grid', value: 7.013 }, + { source: 'H2 conversion', target: 'H2', value: 20.897 }, + { source: 'H2 conversion', target: 'Losses', value: 6.242 }, + { source: 'H2', target: 'Road transport', value: 20.897 }, + { source: 'Hydro', target: 'Electricity grid', value: 6.995 }, + { source: 'Liquid', target: 'Industry', value: 121.066 }, + { source: 'Liquid', target: 'International shipping', value: 128.69 }, + { source: 'Liquid', target: 'Road transport', value: 135.835 }, + { source: 'Liquid', target: 'Domestic aviation', value: 14.458 }, + { source: 'Liquid', target: 'International aviation', value: 206.267 }, + { source: 'Liquid', target: 'Agriculture', value: 3.64 }, + { source: 'Liquid', target: 'National navigation', value: 33.218 }, + { source: 'Liquid', target: 'Rail transport', value: 4.413 }, + { source: 'Marine algae', target: 'Bio-conversion', value: 4.375 }, + { source: 'Ngas', target: 'Gas', value: 122.952 }, + { source: 'Nuclear', target: 'Thermal generation', value: 839.978 }, + { source: 'Oil imports', target: 'Oil', value: 504.287 }, + { source: 'Oil reserves', target: 'Oil', value: 107.703 }, + { source: 'Oil', target: 'Liquid', value: 611.99 }, + { source: 'Other waste', target: 'Solid', value: 56.587 }, + { source: 'Other waste', target: 'Bio-conversion', value: 77.81 }, + { source: 'Pumped heat', target: 'Heating and cooling - homes', value: 193.026 }, + { source: 'Pumped heat', target: 'Heating and cooling - commercial', value: 70.672 }, + { source: 'Solar PV', target: 'Electricity grid', value: 59.901 }, + { source: 'Solar Thermal', target: 'Heating and cooling - homes', value: 19.263 }, + { source: 'Solar', target: 'Solar Thermal', value: 19.263 }, + { source: 'Solar', target: 'Solar PV', value: 59.901 }, + { source: 'Solid', target: 'Agriculture', value: 0.882 }, + { source: 'Solid', target: 'Thermal generation', value: 400.12 }, + { source: 'Solid', target: 'Industry', value: 46.477 }, + { source: 'Thermal generation', target: 'Electricity grid', value: 525.531 }, + { source: 'Thermal generation', target: 'Losses', value: 787.129 }, + { source: 'Thermal generation', target: 'District heating', value: 79.329 }, + { source: 'Tidal', target: 'Electricity grid', value: 9.452 }, + { source: 'UK land based bioenergy', target: 'Bio-conversion', value: 182.01 }, + { source: 'Wave', target: 'Electricity grid', value: 19.013 }, + { source: 'Wind', target: 'Electricity grid', value: 289.366 }, + ], + }, + }, + scale: { + color: { + range: [ + '#4e79a7', + '#f28e2c', + '#e15759', + '#76b7b2', + '#59a14f', + '#edc949', + '#af7aa1', + '#ff9da7', + '#9c755f', + '#bab0ab', + ], + }, + }, + layout: { nodeAlign: 'center', nodePadding: 0.03 }, + style: { + labelSpacing: 3, + labelFontWeight: 'bold', + nodeStrokeWidth: 1.2, + linkFillOpacity: 0.4, + }, + interaction: { + tooltip: { + render: (e, { title, items }) => { + console.log(title, items); + // const div = document.createElement('div'); + // const h3 = document.createElement('h3'); + // h3.innerHTML = title; + // div.appendChild(h3); + // div.innerHTML = `${source} → ${target}: ${value}`; + return
123
; + }, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sankey/demo/energy.js b/site/examples/statistics/sankey/demo/energy.js new file mode 100644 index 000000000..5f3441ee3 --- /dev/null +++ b/site/examples/statistics/sankey/demo/energy.js @@ -0,0 +1,19 @@ +import { Sankey } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoSankey = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/fa3414cc-75ed-47b4-8306-f2ffe8c40127.json', + }, + scale: { color: { range: ['red', 'green', 'yellow'] } }, + layout: { nodeWidth: 0.01, nodePadding: 0.01 }, + linkColorField: (d) => d.source.key, + style: { linkFillOpacity: 0.4 }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sankey/demo/meta.json b/site/examples/statistics/sankey/demo/meta.json new file mode 100644 index 000000000..ea33b3e69 --- /dev/null +++ b/site/examples/statistics/sankey/demo/meta.json @@ -0,0 +1,40 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "桑基图", + "en": "Sankey" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*dACBR7ANcfEAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "alipay.js", + "title": { + "zh": "支付宝流量桑基图", + "en": "Alipay sankey" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*fecqTpstXu0AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "energy.js", + "title": { + "zh": "能量关系桑基图", + "en": "Energy sankey" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*8o1RQJ2fqdsAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "node-sort-sankey.js", + "title": { + "zh": "节点排序前桑基图", + "en": "NodeSort sankey" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Ubm3Q4E1qCAAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/sankey/demo/node-sort-sankey.js b/site/examples/statistics/sankey/demo/node-sort-sankey.js new file mode 100644 index 000000000..01728f98d --- /dev/null +++ b/site/examples/statistics/sankey/demo/node-sort-sankey.js @@ -0,0 +1,53 @@ +import { Sankey } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const colors = [ + '#5B8FF9', + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262fd', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', +]; + +const data = [ + { source: '首次打开', target: '首页 UV', value: 160 }, + { source: '结果页', target: '首页 UV', value: 40 }, + { source: '验证页', target: '首页 UV', value: 10 }, + { source: '我的', target: '首页 UV', value: 10 }, + { source: '朋友', target: '首页 UV', value: 8 }, + { source: '其他来源', target: '首页 UV', value: 27 }, + { source: '首页 UV', target: '理财', value: 30 }, + { source: '首页 UV', target: '扫一扫', value: 40 }, + { source: '首页 UV', target: '服务', value: 35 }, + { source: '首页 UV', target: '蚂蚁森林', value: 25 }, + { source: '首页 UV', target: '跳失', value: 10 }, + { source: '首页 UV', target: '借呗', value: 30 }, + { source: '首页 UV', target: '花呗', value: 40 }, + { source: '首页 UV', target: '其他流向', value: 45 }, +]; + +const DemoSankey = () => { + const config = { + data, + scale: { color: { range: colors } }, + layout: { + nodeWidth: 0.01, + nodeSort: (a, b) => b.value - a.value, + }, + linkColorField: (d) => d.source.key, + style: { + labelFontSize: 13, + linkFillOpacity: 0.4, + nodeStrokeWidth: 0, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sankey/index.en.md b/site/examples/statistics/sankey/index.en.md new file mode 100644 index 000000000..e757ad045 --- /dev/null +++ b/site/examples/statistics/sankey/index.en.md @@ -0,0 +1,4 @@ +--- +title: Sankey +order: 20 +--- \ No newline at end of file diff --git a/site/examples/statistics/sankey/index.zh.md b/site/examples/statistics/sankey/index.zh.md new file mode 100644 index 000000000..b51710f20 --- /dev/null +++ b/site/examples/statistics/sankey/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 桑基图 +order: 20 +--- + + diff --git a/site/examples/statistics/scatter/API.en.md b/site/examples/statistics/scatter/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/scatter/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/scatter/API.zh.md b/site/examples/statistics/scatter/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/scatter/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/scatter/demo/basic.js b/site/examples/statistics/scatter/demo/basic.js new file mode 100644 index 000000000..850cc8883 --- /dev/null +++ b/site/examples/statistics/scatter/demo/basic.js @@ -0,0 +1,18 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/basement_prod/6b4aa721-b039-49b9-99d8-540b3f87d339.json', + }, + xField: 'height', + yField: 'weight', + colorField: 'gender', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/line.js b/site/examples/statistics/scatter/demo/line.js new file mode 100644 index 000000000..02b1055c4 --- /dev/null +++ b/site/examples/statistics/scatter/demo/line.js @@ -0,0 +1,67 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { regressionQuad } from 'd3-regression'; + +const data = [ + { x: 1, y: 4.181 }, + { x: 2, y: 4.665 }, + { x: 3, y: 5.296 }, + { x: 4, y: 5.365 }, + { x: 5, y: 5.448 }, + { x: 6, y: 5.744 }, + { x: 7, y: 5.653 }, + { x: 8, y: 5.844 }, + { x: 9, y: 6.362 }, + { x: 10, y: 6.38 }, + { x: 11, y: 6.311 }, + { x: 12, y: 6.457 }, + { x: 13, y: 6.479 }, + { x: 14, y: 6.59 }, + { x: 15, y: 6.74 }, + { x: 16, y: 6.58 }, + { x: 17, y: 6.852 }, + { x: 18, y: 6.531 }, + { x: 19, y: 6.682 }, + { x: 20, y: 7.013 }, + { x: 21, y: 6.82 }, + { x: 22, y: 6.647 }, + { x: 23, y: 6.951 }, + { x: 24, y: 7.121 }, + { x: 25, y: 7.143 }, + { x: 26, y: 6.914 }, + { x: 27, y: 6.941 }, + { x: 28, y: 7.226 }, + { x: 29, y: 6.898 }, + { x: 30, y: 7.392 }, + { x: 31, y: 6.938 }, +]; + +const lineData = regressionQuad() + .x((d) => d.x) + .y((d) => d.y) + .domain([2, 31])(data) + +const DemoScatter = () => { + const config = { + data, + xField: 'x', + yField: 'y', + sizeField: 5, + style: { + stroke: '#777777', + lineWidth: 1, + fill: '#5B8FF9', + }, + line: { + data: lineData, + xField: (d) => d[0], + yField: (d) => d[1], + style: { stroke: "#c7cbc7", lineWidth: 2 }, + tooltip: false, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/meta.json b/site/examples/statistics/scatter/demo/meta.json new file mode 100644 index 000000000..13fd26c0f --- /dev/null +++ b/site/examples/statistics/scatter/demo/meta.json @@ -0,0 +1,112 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "散点图", + "en": "Scatterplot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*u5aGSoyRDN8AAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "point-one-dimension.js", + "title": { + "zh": "一维散点图", + "en": "One Dimension Scatterplot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*SCXXQYObM_AAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "point-strip.js", + "title": { + "zh": "带子散点图", + "en": "Strip Plot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*pCUiTouHSbYAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "point-label.js", + "title": { + "zh": "标签散点图", + "en": "Scatterplot, Labeled" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*m7HITJRLnu8AAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "point-shape.js", + "title": { + "zh": "多形状散点图", + "en": "Multi-Shape Scatterplot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-OV3RJzmT1MAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "point-sequential.js", + "title": { + "zh": "渐变色散点图", + "en": "Gradient Scatterplot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*A1N1SbupvX8AAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "point-jitter.js", + "title": { + "zh": "扰动散点图", + "en": "Jitter Scatterplot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*PowVRZQkoZUAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "point-stacked.js", + "title": { + "zh": "堆叠散点图", + "en": "Stacked Scatterplot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*f26KS6m_fHgAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "point-bubble.js", + "title": { + "zh": "气泡图", + "en": "Bubble Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*OUogR5lReacAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "point-log.js", + "title": { + "zh": "对数气泡图", + "en": "Log Scatterplot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*y5UsRqFxILMAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "point-aggregated.js", + "title": { + "zh": "聚合气泡图", + "en": "Aggregated Bubble Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*TXB6R7tfCOUAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "point-dot.js", + "title": { + "zh": "点图", + "en": "Dot Plot" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*RtalTb-DPdkAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "line.js", + "title": { + "zh": "散点图-回归线", + "en": "Scatter plot regression line" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*WVncQ5FZmXMAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/scatter/demo/point-aggregated.js b/site/examples/statistics/scatter/demo/point-aggregated.js new file mode 100644 index 000000000..afa63f746 --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-aggregated.js @@ -0,0 +1,29 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + autoFit: false, + height: 240, + inset: 10, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/commits.json', + }, + xField: (d) => new Date(d.time).getUTCHours(), + yField: (d) => new Date(d.time).getUTCDay(), + sizeField: 'count', + colorField: 'count', + shapeField: 'point', + transform: [{ type: 'group', size: 'sum' }, { type: 'sortY' }], + scale: { y: { type: 'point' } }, + axis: { + x: { title: 'time (hours)', tickCount: 24 }, + y: { title: 'time (day)', grid: true }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-bubble.js b/site/examples/statistics/scatter/demo/point-bubble.js new file mode 100644 index 000000000..0aacdade3 --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-bubble.js @@ -0,0 +1,39 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/scatter-point-bubble.json', + transform: [ + { + type: 'filter', + callback: (d) => d.Entity !== 'All natural disasters', + }, + ], + }, + paddingLeft: 150, + paddingTop: 50, + xField: 'Year', + yField: 'Entity', + sizeField: 'Deaths', + colorField: 'Entity', + shapeField: 'point', + scale: { + size: { + rangeMax: 35, + }, + }, + legend: false, + style: { + stroke: 'black', + opacity: 0.8, + lineWidth: 1, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-dot.js b/site/examples/statistics/scatter/demo/point-dot.js new file mode 100644 index 000000000..234f9c677 --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-dot.js @@ -0,0 +1,37 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/scatter-point-dot.json', + }, + width: 800, + height: 1200, + autoFit: false, + coordinate: { transform: [{ type: 'transpose' }] }, + interaction: { tooltip: { shared: true } }, + xField: 'state', + yField: 'population', + colorField: 'age', + shapeField: 'point', + scale: { color: { palette: 'spectral' } }, + tooltip: { title: 'state', items: ['population'] }, + annotations: [ + { + type: 'link', + xField: 'state', + yField: 'population', + transform: [{ type: 'groupX', y: 'min', y1: 'max' }], + scale: { y: { labelFormatter: '.0%' } }, + style: { stroke: '#000' }, + tooltip: false, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-jitter.js b/site/examples/statistics/scatter/demo/point-jitter.js new file mode 100644 index 000000000..7a45f1c7e --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-jitter.js @@ -0,0 +1,25 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/scatter-point-jitter.json', + }, + transform: [{ type: 'jitterX' }, { type: 'sortX', channel: 'x' }], + xField: 'Cylinders', + yField: 'Horsepower', + colorField: 'Cylinders', + shapeField: 'hollow', + scale: { + x: { type: 'point' }, + y: { domain: [0, 200] }, + color: { type: 'ordinal' }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-label.js b/site/examples/statistics/scatter/demo/point-label.js new file mode 100644 index 000000000..a9900dda0 --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-label.js @@ -0,0 +1,34 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/scatter-point-label.json', + }, + xField: 'mpg', + yField: 'hp', + colorField: 'steelblue', + scale: { + x: { nice: true, domainMax: 38 }, + y: { nice: true }, + }, + label: { + text: 'name', + style: { + stroke: '#fff', + textAlign: 'start', + textBaseline: 'middle', + dx: 10, + position: 'left', + fontSize: 10, + lineWidth: 2, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-log.js b/site/examples/statistics/scatter/demo/point-log.js new file mode 100644 index 000000000..01242c120 --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-log.js @@ -0,0 +1,24 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/bubble.json', + }, + xField: 'GDP', + yField: 'LifeExpectancy', + sizeField: 'Population', + colorField: 'continent', + shapeField: 'point', + scale: { + size: { type: 'log', range: [4, 20] }, + }, + style: { fillOpacity: 0.3, lineWidth: 1 }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-one-dimension.js b/site/examples/statistics/scatter/demo/point-one-dimension.js new file mode 100644 index 000000000..4103e343a --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-one-dimension.js @@ -0,0 +1,18 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + height: 120, + autoFit: false, + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/basement_prod/6b4aa721-b039-49b9-99d8-540b3f87d339.json', + }, + xField: 'height', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-sequential.js b/site/examples/statistics/scatter/demo/point-sequential.js new file mode 100644 index 000000000..f54879d12 --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-sequential.js @@ -0,0 +1,32 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + paddingLeft: 60, + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/scatter-point-sequential.json', + }, + xField: (d) => new Date(d.date), + yField: 'value', + colorField: 'value', + shapeField: 'point', + style: { + stroke: '#000', + strokeOpacity: 0.2, + }, + scale: { + color: { + palette: 'rdBu', + offset: (t) => 1 - t, + }, + }, + tooltip: [{ channel: 'x', name: 'year', valueFormatter: (d) => d.getFullYear() }, { channel: 'y' }], + annotations: [{ type: 'lineY', data: [0], style: { stroke: '#000', strokeOpacity: 0.2 } }], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-shape.js b/site/examples/statistics/scatter/demo/point-shape.js new file mode 100644 index 000000000..8354f5f6c --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-shape.js @@ -0,0 +1,23 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/scatter-point-shape.json', + }, + xField: 'x', + yField: 'y', + colorField: 'category', + sizeField: 5, + shapeField: 'category', + scale: { + shape: { range: ['point', 'plus', 'diamond'] }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-stacked.js b/site/examples/statistics/scatter/demo/point-stacked.js new file mode 100644 index 000000000..aa51ac97f --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-stacked.js @@ -0,0 +1,37 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/88c601cd-c1ff-4c9b-90d5-740d0b710b7e.json', + }, + height: 360, + autoFit: false, + stack: { + y1: 'y', + }, + xField: (d) => 2021 - d.birth, + yField: (d) => (d.gender === 'M' ? 1 : -1), + colorField: 'gender', + shapeField: 'point', + scale: { + x: { nice: true }, + }, + axis: { + y: { + title: '← Women · Men →', + labelFormatter: (d) => `${Math.abs(+d)}`, + }, + x: { title: 'Age →' }, + }, + legend: { color: { title: 'Gender' } }, + tooltip: { items: [{ channel: 'x', name: 'age' }] }, + annotations: [{ type: 'lineY', data: [0], style: { stroke: 'black' } }], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/demo/point-strip.js b/site/examples/statistics/scatter/demo/point-strip.js new file mode 100644 index 000000000..d51e11ec2 --- /dev/null +++ b/site/examples/statistics/scatter/demo/point-strip.js @@ -0,0 +1,26 @@ +import { Scatter } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoScatter = () => { + const config = { + autoFit: false, + height: 300, + data: { + type: 'fetch', + value: 'https://render.alipay.com/p/yuyan/180020010001215413/antd-charts/scatter-point-strip.json', + }, + coordinate: { transform: [{ type: 'transpose' }] }, + xField: 'Cylinders', + yField: 'Horsepower', + sizeField: 20, + shapeField: 'line', + scale: { + y: { type: 'linear', zero: true }, + x: { domain: [0, 10] }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/scatter/index.en.md b/site/examples/statistics/scatter/index.en.md new file mode 100644 index 000000000..652631277 --- /dev/null +++ b/site/examples/statistics/scatter/index.en.md @@ -0,0 +1,4 @@ +--- +title: Scatter +order: 1 +--- \ No newline at end of file diff --git a/site/examples/statistics/scatter/index.zh.md b/site/examples/statistics/scatter/index.zh.md new file mode 100644 index 000000000..e18a7d4ee --- /dev/null +++ b/site/examples/statistics/scatter/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 散点图 +order: 1 +--- + +散点图利用点的粒度来分析数据的分布情况. diff --git a/site/examples/statistics/stock/API.en.md b/site/examples/statistics/stock/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/stock/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/stock/API.zh.md b/site/examples/statistics/stock/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/stock/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/stock/demo/basic.js b/site/examples/statistics/stock/demo/basic.js new file mode 100644 index 000000000..4b2268a0a --- /dev/null +++ b/site/examples/statistics/stock/demo/basic.js @@ -0,0 +1,30 @@ +import React, { useState, useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { Stock } from '@ant-design/plots'; + +const DemoStock = () => { + const [data, setData] = useState([]); + + useEffect(() => { + asyncFetch(); + }, []); + + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/antfincdn/qtQ9nYfYJe/stock-data.json') + .then((response) => response.json()) + .then((json) => setData(json)) + .catch((error) => { + console.log('fetch data failed', error); + }); + }; + + const config = { + xField: 'date', + yField: ['open', 'close', 'high', 'low'], + data: data.map((i) => ({ ...i, date: new Date(i.trade_date) })), + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/stock/demo/custom-color.js b/site/examples/statistics/stock/demo/custom-color.js new file mode 100644 index 000000000..77bee3f36 --- /dev/null +++ b/site/examples/statistics/stock/demo/custom-color.js @@ -0,0 +1,33 @@ +import React, { useState, useEffect } from 'react'; +import ReactDOM from 'react-dom'; +import { Stock } from '@ant-design/plots'; + +const DemoStock = () => { + const [data, setData] = useState([]); + + useEffect(() => { + asyncFetch(); + }, []); + + const asyncFetch = () => { + fetch('https://gw.alipayobjects.com/os/antfincdn/qtQ9nYfYJe/stock-data.json') + .then((response) => response.json()) + .then((json) => setData(json)) + .catch((error) => { + console.log('fetch data failed', error); + }); + }; + + const config = { + xField: 'date', + yField: ['open', 'close', 'high', 'low'], + // 绿涨红跌 + fallingFill: '#ef5350', + risingFill: '#26a69a', + data: data.map((i) => ({ ...i, date: new Date(i.trade_date) })), + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/stock/demo/meta.json b/site/examples/statistics/stock/demo/meta.json new file mode 100644 index 000000000..99d47201c --- /dev/null +++ b/site/examples/statistics/stock/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "股票图", + "en": "Stock" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "基础蜡烛图", + "en": "Basic candlestick plot" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*-HIwQbCnfFUAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "custom-color.js", + "title": { + "zh": "自定义颜色:绿涨红跌", + "en": "Custom stock color" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/4EtM9REqIP/8edb0a9e-293e-4b87-bc61-d814e958acbd.png" + } + ] +} diff --git a/site/examples/statistics/stock/index.en.md b/site/examples/statistics/stock/index.en.md new file mode 100644 index 000000000..c0587d930 --- /dev/null +++ b/site/examples/statistics/stock/index.en.md @@ -0,0 +1,4 @@ +--- +title: Stock +order: 1 +--- diff --git a/site/examples/statistics/stock/index.zh.md b/site/examples/statistics/stock/index.zh.md new file mode 100644 index 000000000..3b185dfbf --- /dev/null +++ b/site/examples/statistics/stock/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 股票图 +order: 1 +--- diff --git a/site/examples/statistics/sunburst/API.en.md b/site/examples/statistics/sunburst/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/sunburst/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/sunburst/API.zh.md b/site/examples/statistics/sunburst/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/sunburst/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/sunburst/demo/meta.json b/site/examples/statistics/sunburst/demo/meta.json new file mode 100644 index 000000000..3cb74a587 --- /dev/null +++ b/site/examples/statistics/sunburst/demo/meta.json @@ -0,0 +1,48 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "sunburst-default.js", + "title": { + "zh": "旭日图", + "en": "Sunburst Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*-aInRJfa4-8AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "sunburst-label.js", + "title": { + "zh": "旭日图带标签", + "en": "Sunburst Label Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GBxdT697NYQAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "sunburst-color.js", + "title": { + "zh": "旭日图自定义颜色通道", + "en": "Sunburst ColorField Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7L4tQ4F61ZkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "sunburst-style.js", + "title": { + "zh": "旭日图自定义样式", + "en": "Sunburst Style Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*BP7zQ6SMiKcAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "sunburst-interaction.js", + "title": { + "zh": "旭日图交互配置", + "en": "Sunburst Interaction Config Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7C0NQK9_TfwAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/sunburst/demo/sunburst-color.js b/site/examples/statistics/sunburst/demo/sunburst-color.js new file mode 100644 index 000000000..518fda6ba --- /dev/null +++ b/site/examples/statistics/sunburst/demo/sunburst-color.js @@ -0,0 +1,17 @@ +import { Sunburst } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoSunburst = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json', + }, + valueField: 'sum', + colorField: 'label', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sunburst/demo/sunburst-default.js b/site/examples/statistics/sunburst/demo/sunburst-default.js new file mode 100644 index 000000000..f90bac25f --- /dev/null +++ b/site/examples/statistics/sunburst/demo/sunburst-default.js @@ -0,0 +1,19 @@ +import { Sunburst } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoSunburst = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antfincdn/ryp44nvUYZ/coffee.json', + }, + animate: { + enter: { type: 'waveIn' } + }, + innerRadius: 0, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sunburst/demo/sunburst-interaction.js b/site/examples/statistics/sunburst/demo/sunburst-interaction.js new file mode 100644 index 000000000..38839664c --- /dev/null +++ b/site/examples/statistics/sunburst/demo/sunburst-interaction.js @@ -0,0 +1,44 @@ +import { Sunburst } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoSunburst = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json', + }, + valueField: 'sum', + label: { + text: 'name', + transform: [ + { + type: 'overflowHide', + }, + ], + }, + interaction: { + drillDown: { + breadCrumb: { + rootText: '起始', + style: { + fontSize: '18px', + fill: '#333', + }, + active: { + fill: 'red', + }, + }, + // FixedColor default: true, true -> drillDown update scale, false -> scale keep. + fixedColor: false, + }, + }, + state: { + active: { zIndex: 2, stroke: 'red' }, + inactive: { zIndex: 1, stroke: '#fff' }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sunburst/demo/sunburst-label.js b/site/examples/statistics/sunburst/demo/sunburst-label.js new file mode 100644 index 000000000..71c80db75 --- /dev/null +++ b/site/examples/statistics/sunburst/demo/sunburst-label.js @@ -0,0 +1,24 @@ +import { Sunburst } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoSunburst = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json', + }, + valueField: 'sum', + label: { + text: 'name', + transform: [ + { + type: 'overflowHide', + }, + ], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sunburst/demo/sunburst-style.js b/site/examples/statistics/sunburst/demo/sunburst-style.js new file mode 100644 index 000000000..806e5e73e --- /dev/null +++ b/site/examples/statistics/sunburst/demo/sunburst-style.js @@ -0,0 +1,22 @@ +import { Sunburst } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoSunburst = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json', + }, + valueField: 'sum', + style: { + fill: (v) => { + if (v['path'] === '类别 3') return 'red'; + if (v['name'] === '类别 2.1.1') return 'red'; + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/sunburst/index.en.md b/site/examples/statistics/sunburst/index.en.md new file mode 100644 index 000000000..518886789 --- /dev/null +++ b/site/examples/statistics/sunburst/index.en.md @@ -0,0 +1,4 @@ +--- +title: Sunburst +order: 13 +--- diff --git a/site/examples/statistics/sunburst/index.zh.md b/site/examples/statistics/sunburst/index.zh.md new file mode 100644 index 000000000..572a97c99 --- /dev/null +++ b/site/examples/statistics/sunburst/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 旭日图 +order: 13 +--- diff --git a/site/examples/statistics/tiny/API.en.md b/site/examples/statistics/tiny/API.en.md new file mode 100644 index 000000000..6414c91a9 --- /dev/null +++ b/site/examples/statistics/tiny/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/tiny/API.zh.md b/site/examples/statistics/tiny/API.zh.md new file mode 100644 index 000000000..cd41b9fc6 --- /dev/null +++ b/site/examples/statistics/tiny/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/tiny/demo/area-annotation.js b/site/examples/statistics/tiny/demo/area-annotation.js new file mode 100644 index 000000000..295b3135f --- /dev/null +++ b/site/examples/statistics/tiny/demo/area-annotation.js @@ -0,0 +1,50 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const data = [ + 264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192, +].map((value, index) => ({ value, index })); +const DemoArea = () => { + const config = { + data, + width: 480, + height: 80, + padding: 8, + shapeField: 'smooth', + xField: 'index', + yField: 'value', + style: { + fill: 'linear-gradient(-90deg, white 0%, darkgreen 100%)', + fillOpacity: 0.6, + }, + annotations: [ + { + type: 'lineY', + data: [data.reduce((acc, cur) => acc + cur.value, 0) / data.length], + label: { + text: '平均值', + position: 'left', + dx: -10, + style: { textBaseline: 'bottom' }, + }, + + style: { stroke: 'rgba(0, 0, 0)' }, + }, + { + type: 'lineY', + data: [800], + label: { + text: '目标值', + position: 'left', + dx: -10, + style: { textBaseline: 'bottom' }, + }, + style: { stroke: 'rgba(0, 0, 0)' }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/basic-area.js b/site/examples/statistics/tiny/demo/basic-area.js new file mode 100644 index 000000000..7580b02e6 --- /dev/null +++ b/site/examples/statistics/tiny/demo/basic-area.js @@ -0,0 +1,25 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoArea = () => { + const data = [ + 264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192, + ].map((value, index) => ({ value, index })); + const config = { + data, + width: 480, + height: 80, + padding: 8, + shapeField: 'smooth', + xField: 'index', + yField: 'value', + style: { + fill: 'linear-gradient(-90deg, white 0%, darkgreen 100%)', + fillOpacity: 0.6, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/basic-column.js b/site/examples/statistics/tiny/demo/basic-column.js new file mode 100644 index 000000000..a1abb2bbc --- /dev/null +++ b/site/examples/statistics/tiny/demo/basic-column.js @@ -0,0 +1,20 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLine = () => { + const data = [ + 264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192, + ].map((value, index) => ({ value, index })); + const config = { + data, + width: 480, + height: 80, + padding: 8, + xField: 'index', + yField: 'value', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/basic-line.js b/site/examples/statistics/tiny/demo/basic-line.js new file mode 100644 index 000000000..fc564d2f8 --- /dev/null +++ b/site/examples/statistics/tiny/demo/basic-line.js @@ -0,0 +1,30 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLine = () => { + const data = [ + 264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192, + ].map((value, index) => ({ value, index })); + const config = { + data, + width: 480, + height: 120, + shapeField: 'smooth', + xField: 'index', + yField: 'value', + label: { + selector: 'last', + text: (d) => d.value, + textAlign: 'right', + textBaseline: 'bottom', + dx: -10, + dy: -10, + connector: true, + style: { fontSize: 10 }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/basic-progress.js b/site/examples/statistics/tiny/demo/basic-progress.js new file mode 100644 index 000000000..51d5c76bc --- /dev/null +++ b/site/examples/statistics/tiny/demo/basic-progress.js @@ -0,0 +1,32 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoProgress = () => { + const progress = 0.7; + + const config = { + width: 480, + height: 60, + autoFit: false, + percent: progress, + color: ['#0f0f0f', '#85f231'], + annotations: [ + { + type: 'text', + style: { + text: `${progress * 100}%`, + x: '50%', + y: '50%', + textAlign: 'center', + fontSize: 16, + fontStyle: 'bold', + }, + }, + ], + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/column-annotation.js b/site/examples/statistics/tiny/demo/column-annotation.js new file mode 100644 index 000000000..96dd84d99 --- /dev/null +++ b/site/examples/statistics/tiny/demo/column-annotation.js @@ -0,0 +1,33 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoLine = () => { + const data = [ + 264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192, + ].map((value, index) => ({ value, index })); + const config = { + data, + width: 480, + height: 80, + padding: 8, + xField: 'index', + yField: 'value', + annotations: [ + { + type: 'lineY', + data: [700], + style: { arrow: true, stroke: 'red', lineDash: [2, 2] }, + label: { + text: 'value = 700', + position: 'right', + dx: -10, + style: { textBaseline: 'bottom' }, + }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/filled-area.js b/site/examples/statistics/tiny/demo/filled-area.js new file mode 100644 index 000000000..22a1eaf78 --- /dev/null +++ b/site/examples/statistics/tiny/demo/filled-area.js @@ -0,0 +1,25 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoArea = () => { + const data = [ + 264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192, + ].map((value, index) => ({ value, index })); + const config = { + data, + width: 480, + height: 80, + padding: 8, + shapeField: 'smooth', + xField: 'index', + yField: 'value', + style: { + fill: '#d6e3fd', + fillOpacity: 0.6, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/line-annotation.js b/site/examples/statistics/tiny/demo/line-annotation.js new file mode 100644 index 000000000..06d3844d3 --- /dev/null +++ b/site/examples/statistics/tiny/demo/line-annotation.js @@ -0,0 +1,41 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; +const data = [ + 264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192, +].map((value, index) => ({ value, index })); +const DemoLine = () => { + const config = { + data, + width: 480, + height: 120, + shapeField: 'smooth', + xField: 'index', + yField: 'value', + annotations: [ + { + type: 'lineY', + data: [data.reduce((acc, cur) => acc + cur.value, 0) / data.length], + label: { + text: '平均值', + position: 'left', + style: { textBaseline: 'bottom' }, + }, + style: { stroke: 'rgba(0, 0, 0)' }, + }, + { + type: 'lineY', + data: [800], + label: { + text: '目标值', + position: 'left', + style: { textBaseline: 'bottom' }, + }, + style: { stroke: 'rgba(0, 0, 0)' }, + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/demo/meta.json b/site/examples/statistics/tiny/demo/meta.json new file mode 100644 index 000000000..b27003a08 --- /dev/null +++ b/site/examples/statistics/tiny/demo/meta.json @@ -0,0 +1,80 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic-area.js", + "title": { + "zh": "迷你面积", + "en": "tiny area plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*e7gFTKK4RXwAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "area-annotation.js", + "title": { + "zh": "带辅助信息的迷你面积图", + "en": "Tiny area plot with annotations" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*4DlNQrsCYZAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "filled-area.js", + "title": { + "zh": "平铺填充迷你面积图", + "en": "Filled tiny area plot" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*tN1MTpkoO8gAAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "basic-column.js", + "title": { + "zh": "迷你柱形图", + "en": "tiny column plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*AHtKS7goLgkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "column-annotation.js", + "title": { + "zh": "带辅助线的迷你柱形图", + "en": "Tiny column plot with annotation" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*SucISYgPzscAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "basic-line.js", + "title": { + "zh": "迷你折线图", + "en": "Tiny line plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7mspQrtT-VYAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "line-annotation.js", + "title": { + "zh": "带辅助线的迷你折线图", + "en": "Tiny line plot with annotation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*KShOSrCq0X4AAAAAAAAAAAAAARQnAQ" + }, + { + "filename": "basic-progress.js", + "title": { + "zh": "迷你进度图", + "en": "Tiny progress plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ZJ6JSL4WFt8AAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "ring.js", + "title": { + "zh": "迷你进度环图", + "en": "Basic tiny ring progress" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*NaLRSIdTx_YAAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/site/examples/statistics/tiny/demo/ring.js b/site/examples/statistics/tiny/demo/ring.js new file mode 100644 index 000000000..39e041eb5 --- /dev/null +++ b/site/examples/statistics/tiny/demo/ring.js @@ -0,0 +1,30 @@ +import { Tiny } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoRing = () => { + const percent = 0.7; + const config = { + percent, + width: 120, + height: 120, + color: ['#E8EFF5', '#66AFF4'], + annotations: [ + { + type: 'text', + style: { + text: `${percent * 100}%`, + x: '50%', + y: '50%', + textAlign: 'center', + fontSize: 16, + fontStyle: 'bold', + }, + }, + ], + }; + + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/tiny/index.en.md b/site/examples/statistics/tiny/index.en.md new file mode 100644 index 000000000..9c2c58d71 --- /dev/null +++ b/site/examples/statistics/tiny/index.en.md @@ -0,0 +1,4 @@ +--- +title: Tiny +order: 2 +--- \ No newline at end of file diff --git a/site/examples/statistics/tiny/index.zh.md b/site/examples/statistics/tiny/index.zh.md new file mode 100644 index 000000000..6e9863b00 --- /dev/null +++ b/site/examples/statistics/tiny/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 迷你图 +order: 2 +--- diff --git a/site/examples/statistics/treemap/API.en.md b/site/examples/statistics/treemap/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/treemap/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/treemap/API.zh.md b/site/examples/statistics/treemap/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/treemap/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/treemap/demo/meta.json b/site/examples/statistics/treemap/demo/meta.json new file mode 100644 index 000000000..9cf03d8fd --- /dev/null +++ b/site/examples/statistics/treemap/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "treemap.js", + "title": { + "zh": "矩阵树图", + "en": "Treemap Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*T2zHS6J1cGMAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/treemap/demo/treemap.js b/site/examples/statistics/treemap/demo/treemap.js new file mode 100644 index 000000000..92198a93e --- /dev/null +++ b/site/examples/statistics/treemap/demo/treemap.js @@ -0,0 +1,56 @@ +import { Treemap } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoTreemap = () => { + const data = { + name: 'root', + children: [ + { name: '分类 1', value: 560 }, + { name: '分类 2', value: 500 }, + { name: '分类 3', value: 150 }, + { name: '分类 4', value: 140 }, + { name: '分类 5', value: 115 }, + { name: '分类 6', value: 95 }, + { name: '分类 7', value: 90 }, + { name: '分类 8', value: 75 }, + { name: '分类 9', value: 98 }, + { name: '分类 10', value: 60 }, + { name: '分类 11', value: 45 }, + { name: '分类 12', value: 40 }, + { name: '分类 13', value: 40 }, + { name: '分类 14', value: 35 }, + { name: '分类 15', value: 40 }, + { name: '分类 16', value: 40 }, + { name: '分类 17', value: 40 }, + { name: '分类 18', value: 30 }, + { name: '分类 19', value: 28 }, + { name: '分类 20', value: 16 }, + ], + }; + const config = { + data, + colorField: 'value', + valueField: 'value', + scale: { + color: { + range: [ + '#4e79a7', + '#f28e2c', + '#e15759', + '#76b7b2', + '#59a14f', + '#edc949', + '#af7aa1', + '#ff9da7', + '#9c755f', + '#bab0ab', + ], + }, + }, + legend: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/treemap/index.en.md b/site/examples/statistics/treemap/index.en.md new file mode 100644 index 000000000..2350dc982 --- /dev/null +++ b/site/examples/statistics/treemap/index.en.md @@ -0,0 +1,4 @@ +--- +title: Treemap +order: 12 +--- diff --git a/site/examples/statistics/treemap/index.zh.md b/site/examples/statistics/treemap/index.zh.md new file mode 100644 index 000000000..76764c386 --- /dev/null +++ b/site/examples/statistics/treemap/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 矩阵树图 +order: 12 +--- diff --git a/site/examples/statistics/venn/API.en.md b/site/examples/statistics/venn/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/venn/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/venn/API.zh.md b/site/examples/statistics/venn/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/venn/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/venn/demo/basic.js b/site/examples/statistics/venn/demo/basic.js new file mode 100644 index 000000000..8ecc54e14 --- /dev/null +++ b/site/examples/statistics/venn/demo/basic.js @@ -0,0 +1,35 @@ +import { Venn } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoVenn = () => { + const config = { + data: [ + { sets: ['A'], size: 12, label: 'A' }, + { sets: ['B'], size: 12, label: 'B' }, + { sets: ['C'], size: 12, label: 'C' }, + { sets: ['A', 'B'], size: 2, label: 'A&B' }, + { sets: ['A', 'C'], size: 2, label: 'A&C' }, + { sets: ['B', 'C'], size: 2, label: 'B&C' }, + { sets: ['A', 'B', 'C'], size: 1 }, + ], + setsField: 'sets', + sizeField: 'size', + style: { fillOpacity: 0.85 }, + label: { + position: 'inside', + text: (d) => d.label || '', + }, + tooltip: { + title: false, + items: [ + (d) => { + return { name: d.key, value: d.size }; + }, + ], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/venn/demo/custom-color.js b/site/examples/statistics/venn/demo/custom-color.js new file mode 100644 index 000000000..ad08273c1 --- /dev/null +++ b/site/examples/statistics/venn/demo/custom-color.js @@ -0,0 +1,42 @@ +import { Venn } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoVenn = () => { + const config = { + data: [ + { sets: ['A'], size: 12, label: 'A' }, + { sets: ['B'], size: 12, label: 'B' }, + { sets: ['C'], size: 12, label: 'C' }, + { sets: ['A', 'B'], size: 2, label: 'A&B' }, + { sets: ['A', 'C'], size: 2, label: 'A&C' }, + { sets: ['B', 'C'], size: 2, label: 'B&C' }, + { sets: ['A', 'B', 'C'], size: 1 }, + ], + setsField: 'sets', + sizeField: 'size', + label: { + position: 'inside', + text: (d) => d.label || '', + }, + tooltip: { + title: false, + items: [ + (d) => { + return { name: d.key, value: d.size }; + }, + ], + }, + style: { + fillOpacity: 0.85, + fill: (datum, index, data) => { + console.log(data); + const { size } = datum; + if (size <= 2) return '#f4bb51'; + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/venn/demo/custom.js b/site/examples/statistics/venn/demo/custom.js new file mode 100644 index 000000000..3c26ec335 --- /dev/null +++ b/site/examples/statistics/venn/demo/custom.js @@ -0,0 +1,51 @@ +import { Venn } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoVenn = () => { + const config = { + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/bmw-prod/c4c17fe9-0a93-4255-bc1e-1ff84966d24a.json', + transform: [ + { + type: 'venn', + sets: 'sets', + size: 'size', + as: ['key', 'path'], + }, + ], + }, + setsField: 'sets', + sizeField: 'size', + style: { fillOpacity: 0.85 }, + scale: { + color: { + range: ['#9DF5CA', '#61DDAA', '#42C090'], + }, + }, + label: { + position: 'inside', + style: { + lineHeight: 20, + }, + text: (datum) => { + return `${datum.size}`; + }, + }, + interaction: { + tooltip: { + // render 回调方法返回一个innerHTML 或者 DOM + render: (event, { title, items }) => { + return `
+

title:${title}

+
    ${items.map((d) => `
  • ${d.name} ${d.value}
  • `)}
+
`; + }, + }, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/venn/demo/interaction.js b/site/examples/statistics/venn/demo/interaction.js new file mode 100644 index 000000000..3561aecda --- /dev/null +++ b/site/examples/statistics/venn/demo/interaction.js @@ -0,0 +1,49 @@ +import { Venn } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoVenn = () => { + const config = { + data: [ + { sets: ['A'], size: 12, label: 'A' }, + { sets: ['B'], size: 12, label: 'B' }, + { sets: ['C'], size: 12, label: 'C' }, + { sets: ['A', 'B'], size: 2, label: 'A&B' }, + { sets: ['A', 'C'], size: 2, label: 'A&C' }, + { sets: ['B', 'C'], size: 2, label: 'B&C' }, + { sets: ['A', 'B', 'C'], size: 1 }, + ], + setsField: 'sets', + sizeField: 'size', + style: { fillOpacity: 0.85 }, + label: { + position: 'inside', + text: (d) => d.label || '', + }, + tooltip: { + title: false, + items: [ + (d) => { + return { name: d.key, value: d.size }; + }, + ], + }, + state: { + active: { + fillOpacity: 0.8, + stroke: 'red', + lineWidth: 1, + }, + inactive: { + fillOpacity: 0.2, + lineWidth: 0, + }, + }, + interaction: { + elementHighlight: true, + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/venn/demo/label.js b/site/examples/statistics/venn/demo/label.js new file mode 100644 index 000000000..22fa12b47 --- /dev/null +++ b/site/examples/statistics/venn/demo/label.js @@ -0,0 +1,28 @@ +import { Venn } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoVenn = () => { + const config = { + data: [ + { sets: ['A'], size: 12, label: 'A' }, + { sets: ['B'], size: 12, label: 'B' }, + { sets: ['C'], size: 12, label: 'C' }, + { sets: ['A', 'B'], size: 2, label: 'A&B' }, + { sets: ['A', 'C'], size: 2, label: 'A&C' }, + { sets: ['B', 'C'], size: 2, label: 'B&C' }, + { sets: ['A', 'B', 'C'], size: 1 }, + ], + setsField: 'sets', + sizeField: 'size', + style: { fillOpacity: 0.85 }, + label: { + position: 'inside', + text: (d) => d.label || '', + transform: [{ type: 'contrastReverse' }], + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/venn/demo/meta.json b/site/examples/statistics/venn/demo/meta.json new file mode 100644 index 000000000..fb8d7fc00 --- /dev/null +++ b/site/examples/statistics/venn/demo/meta.json @@ -0,0 +1,48 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.js", + "title": { + "zh": "基础韦恩图", + "en": "Basic venn plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*I7XlS4ps7IcAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom-color.js", + "title": { + "zh": "自定义颜色", + "en": "Custom color" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*STtHQ6brAMAAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "label.js", + "title": { + "zh": "设置label", + "en": "Label setting" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*h7FaRLXj2kgAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "custom.js", + "title": { + "zh": "自定义韦恩图", + "en": "Customize venn plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*7qg4S4iOrnkAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "interaction.js", + "title": { + "zh": "韦恩图-元素交互", + "en": "venn plot - with element action" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Y5IuSo4u7PEAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/venn/index.en.md b/site/examples/statistics/venn/index.en.md new file mode 100644 index 000000000..811ec7aab --- /dev/null +++ b/site/examples/statistics/venn/index.en.md @@ -0,0 +1,4 @@ +--- +title: Venn +order: 3 +--- diff --git a/site/examples/statistics/venn/index.zh.md b/site/examples/statistics/venn/index.zh.md new file mode 100644 index 000000000..584b58f51 --- /dev/null +++ b/site/examples/statistics/venn/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 韦恩图 +order: 3 +--- + + diff --git a/site/examples/statistics/violin/API.en.md b/site/examples/statistics/violin/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/violin/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/violin/API.zh.md b/site/examples/statistics/violin/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/violin/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/violin/demo/basic.js b/site/examples/statistics/violin/demo/basic.js new file mode 100644 index 000000000..34464dc11 --- /dev/null +++ b/site/examples/statistics/violin/demo/basic.js @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Violin } from '@ant-design/plots'; + +const DemoViolin = () => { + const config = { + violinType: 'normal', + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/species.json', + }, + xField: 'x', + yField: 'y', + seriesField: 'species', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/violin/demo/density.js b/site/examples/statistics/violin/demo/density.js new file mode 100644 index 000000000..ea844acc7 --- /dev/null +++ b/site/examples/statistics/violin/demo/density.js @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Violin } from '@ant-design/plots'; + +const DemoViolin = () => { + const config = { + violinType: 'density', + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/species.json', + }, + xField: 'x', + yField: 'y', + seriesField: 'species', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/violin/demo/meta.json b/site/examples/statistics/violin/demo/meta.json new file mode 100644 index 000000000..33737187f --- /dev/null +++ b/site/examples/statistics/violin/demo/meta.json @@ -0,0 +1,32 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "density.js", + "title": { + "zh": "和密度图", + "en": "Density plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*LKFOT6UA11QAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "basic.js", + "title": { + "zh": "基础小提琴图", + "en": "Basic violin plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*lU62ToCw4_AAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "polar.js", + "title": { + "zh": "极坐标小提琴图", + "en": "Polar violin plot" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*5-6LTohbpFsAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/violin/demo/polar.js b/site/examples/statistics/violin/demo/polar.js new file mode 100644 index 000000000..49c8e7224 --- /dev/null +++ b/site/examples/statistics/violin/demo/polar.js @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Violin } from '@ant-design/plots'; + +const DemoViolin = () => { + const config = { + violinType: 'polar', + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/species.json', + }, + xField: 'x', + yField: 'y', + seriesField: 'species', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/violin/index.en.md b/site/examples/statistics/violin/index.en.md new file mode 100644 index 000000000..15e7a93a7 --- /dev/null +++ b/site/examples/statistics/violin/index.en.md @@ -0,0 +1,4 @@ +--- +title: Violin +order: 15 +--- \ No newline at end of file diff --git a/site/examples/statistics/violin/index.zh.md b/site/examples/statistics/violin/index.zh.md new file mode 100644 index 000000000..71ae5e3d1 --- /dev/null +++ b/site/examples/statistics/violin/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 小提琴图 +order: 15 +--- \ No newline at end of file diff --git a/site/examples/statistics/waterfall/API.en.md b/site/examples/statistics/waterfall/API.en.md new file mode 100644 index 000000000..52fdae5c5 --- /dev/null +++ b/site/examples/statistics/waterfall/API.en.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/waterfall/API.zh.md b/site/examples/statistics/waterfall/API.zh.md new file mode 100644 index 000000000..b05937935 --- /dev/null +++ b/site/examples/statistics/waterfall/API.zh.md @@ -0,0 +1,2 @@ + + diff --git a/site/examples/statistics/waterfall/demo/annotation.js b/site/examples/statistics/waterfall/demo/annotation.js new file mode 100644 index 000000000..0d8c1af5a --- /dev/null +++ b/site/examples/statistics/waterfall/demo/annotation.js @@ -0,0 +1,49 @@ +import { Waterfall } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoWaterfall = () => { + const data = [ + { quarter: '第一季度', value: 6200000 }, + { quarter: '第二季度', value: -2600000 }, + { quarter: '第三季度', value: 4100000 }, + { quarter: '第四季度', value: 3700000 }, + { quarter: '总计', value: 11400000, isTotal: true }, + ]; + const config = { + data, + xField: 'quarter', + yField: 'value', + linkStyle: { + lineDash: [4, 2], + stroke: '#ccc', + }, + style: { + maxWidth: 60, + stroke: '#ccc', + fill: (d) => { + return d.isTotal ? '#96a6a6' : d.value > 0 ? '#F56E53' : '#3CC27F'; + }, + }, + label: [ + { + text: 'value', + formatter: '~s', + position: 'inside', + fontSize: 10, + }, + { + text: (arg) => { + return `${arg.y1 / 10000000} 亿`; + }, + position: (d) => (d.value > 0 ? 'top' : 'bottom'), + textBaseline: (d) => (d.value > 0 ? 'bottom' : 'top'), + fontSize: 10, + dy: (d) => (d.value > 0 ? -4 : 4), + }, + ], + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/waterfall/demo/meta.json b/site/examples/statistics/waterfall/demo/meta.json new file mode 100644 index 000000000..a5d6e64fb --- /dev/null +++ b/site/examples/statistics/waterfall/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "revenue-flow-waterfall.js", + "title": { + "zh": "线标记的瀑布图", + "en": "Revenue Flow Waterfall Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*fG4DT7XWNzUAAAAAAAAAAAAADmJ7AQ" + }, + { + "filename": "annotation.js", + "title": { + "zh": "瀑布图 - 添加标注", + "en": "Waterfall plot with annotation" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*xOfKT5qr9R4AAAAAAAAAAAAAARQnAQ" + } + ] +} diff --git a/site/examples/statistics/waterfall/demo/revenue-flow-waterfall.js b/site/examples/statistics/waterfall/demo/revenue-flow-waterfall.js new file mode 100644 index 000000000..93d05f3e7 --- /dev/null +++ b/site/examples/statistics/waterfall/demo/revenue-flow-waterfall.js @@ -0,0 +1,48 @@ +import { Waterfall } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoWaterfall = () => { + const config = { + data: [ + { x: 'Start', value: 23000000 }, + { x: 'Jan', value: 2200000 }, + { x: 'Feb', value: -4600000 }, + { x: 'Mar', value: -9100000 }, + { x: 'Apr', value: 3700000 }, + { x: 'May', value: -2100000 }, + { x: 'Jun', value: 5300000 }, + { x: 'Jul', value: 3100000 }, + { x: 'Aug', value: -1500000 }, + { x: 'Sep', value: 4200000 }, + { x: 'Oct', value: 5300000 }, + { x: 'Nov', value: -1500000 }, + { x: 'Dec', value: 5100000 }, + { x: 'End', isTotal: true, value: 33100000 }, + ], + xField: 'x', + yField: 'value', + linkStyle: { + lineDash: [4, 2], + stroke: '#ccc', + }, + style: { + maxWidth: 25, + stroke: '#ccc', + fill: (d, idx) => { + return idx === 0 || d.isTotal ? '#96a6a6' : d.value > 0 ? '#64b5f6' : '#ef6c00'; + }, + }, + label: { + text: 'value', + formatter: '~s', + position: (d) => (d.value > 0 ? 'top' : 'bottom'), + textBaseline: (d) => (d.value > 0 ? 'bottom' : 'top'), + fontSize: 10, + dy: (d) => (d.value > 0 ? -4 : 4), + }, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/waterfall/index.en.md b/site/examples/statistics/waterfall/index.en.md new file mode 100644 index 000000000..6ef4fdd4d --- /dev/null +++ b/site/examples/statistics/waterfall/index.en.md @@ -0,0 +1,4 @@ +--- +title: Waterfall +order: 1 +--- \ No newline at end of file diff --git a/site/examples/statistics/waterfall/index.zh.md b/site/examples/statistics/waterfall/index.zh.md new file mode 100644 index 000000000..c04cd0ed6 --- /dev/null +++ b/site/examples/statistics/waterfall/index.zh.md @@ -0,0 +1,6 @@ +--- +title: 瀑布图 +order: 1 +--- + + diff --git a/site/examples/statistics/wordCloud/API.en.md b/site/examples/statistics/wordCloud/API.en.md new file mode 100644 index 000000000..eb88c070c --- /dev/null +++ b/site/examples/statistics/wordCloud/API.en.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/wordCloud/API.zh.md b/site/examples/statistics/wordCloud/API.zh.md new file mode 100644 index 000000000..a8382b46d --- /dev/null +++ b/site/examples/statistics/wordCloud/API.zh.md @@ -0,0 +1 @@ + diff --git a/site/examples/statistics/wordCloud/demo/meta.json b/site/examples/statistics/wordCloud/demo/meta.json new file mode 100644 index 000000000..6c08a485b --- /dev/null +++ b/site/examples/statistics/wordCloud/demo/meta.json @@ -0,0 +1,24 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "wordCloud.js", + "title": { + "zh": "词云图", + "en": "WordCloud Chart" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*hC4uR6yuBa4AAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "wordCloud-image.js", + "title": { + "zh": "带图片遮罩的词云图", + "en": "WordCloud with image mask" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*xgP6QJlzghYAAAAAAAAAAAAADmJ7AQ/original" + } + ] +} diff --git a/site/examples/statistics/wordCloud/demo/wordCloud-image.js b/site/examples/statistics/wordCloud/demo/wordCloud-image.js new file mode 100644 index 000000000..777f3df87 --- /dev/null +++ b/site/examples/statistics/wordCloud/demo/wordCloud-image.js @@ -0,0 +1,25 @@ +import { WordCloud } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoWordCloud = () => { + const config = { + width: 1000, + height: 400, + autoFit: false, + data: { + type: 'fetch', + value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/antv-keywords.json', + }, + layout: { + imageMask: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*LKU4TYEiB-4AAAAAAAAAAAAADmJ7AQ/original', + fontSize: 10, + }, + colorField: 'name', + textField: 'name', + legend: false, + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/wordCloud/demo/wordCloud.js b/site/examples/statistics/wordCloud/demo/wordCloud.js new file mode 100644 index 000000000..a8e39325c --- /dev/null +++ b/site/examples/statistics/wordCloud/demo/wordCloud.js @@ -0,0 +1,18 @@ +import { WordCloud } from '@ant-design/plots'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const DemoWordCloud = () => { + const config = { + paddingTop: 40, + data: { + type: 'fetch', + value: 'https://assets.antv.antgroup.com/g2/philosophy-word.json', + }, + layout: { spiral: 'rectangular' }, + colorField: 'text', + }; + return ; +}; + +ReactDOM.render(, document.getElementById('container')); diff --git a/site/examples/statistics/wordCloud/index.en.md b/site/examples/statistics/wordCloud/index.en.md new file mode 100644 index 000000000..f069a50f6 --- /dev/null +++ b/site/examples/statistics/wordCloud/index.en.md @@ -0,0 +1,4 @@ +--- +title: WordCloud +order: 13 +--- diff --git a/site/examples/statistics/wordCloud/index.zh.md b/site/examples/statistics/wordCloud/index.zh.md new file mode 100644 index 000000000..3d29d42d2 --- /dev/null +++ b/site/examples/statistics/wordCloud/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 词云图 +order: 13 +--- diff --git a/site/package.json b/site/package.json index 08bb7173c..c3e16e8e1 100644 --- a/site/package.json +++ b/site/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@ant-design/charts-site", - "version": "0.0.1-beta.4", + "version": "2.0.0", "description": "React Visual component library", "keywords": [ "antv", @@ -22,32 +22,36 @@ "license": "MIT", "author": "https://github.com/orgs/antvis/people", "scripts": { - "build": "NODE_OPTIONS=--max_old_space_size=12288 dumi build", + "build": "cross-env NODE_OPTIONS=--max_old_space_size=12288 dumi build", "develop": "cross-env DUMI=true dumi dev", "deploy": "pnpm run build && gh-pages -d public", - "start": "NODE_OPTIONS=--max_old_space_size=8192 pnpm run develop" + "start": "cross-env NODE_OPTIONS=--max_old_space_size=8192 pnpm run develop" }, "devDependencies": { - "@antv/dumi-theme-antv": "^0.3.0", - "@types/react": "^16.14.8", - "@types/react-dom": "^16.9.13", + "@types/lodash": "^4.17.13", + "@types/react": "^16.14.62", + "@types/react-dom": "^16.9.25", + "@types/styled-components": "^5.1.34", "cross-env": "^7.0.3", - "dumi": "^2.1.11", - "gh-pages": "^2.1.1", - "typedoc": "^0.17.6", - "typedoc-plugin-markdown": "^2.2.11", - "typescript": "^3.6.5" + "d3-interpolate": "^3.0.1", + "d3-regression": "^1.3.10", + "fecha": "^4.2.3", + "gh-pages": "^2.2.0", + "typedoc": "^0.17.8", + "typedoc-plugin-markdown": "^2.4.2", + "typescript": "^3.9.10" }, "dependencies": { - "@ant-design/flowchart": "^1.0.2", - "@ant-design/graphs": "^1.0.1", - "@ant-design/maps": "^1.0.1", - "@ant-design/plots": "^2.0.0-alpha.0", - "@antv/data-set": "^0.11.8", - "@antv/util": "^2.0.9", - "antd": "^4.16.13", + "@ant-design/graphs": "workspace:*", + "@ant-design/plots": "workspace:*", + "@antv/dumi-theme-antv": "^0.5.5", + "antd": "^5.22.4", + "dumi": "^2.4.16", "insert-css": "^2.0.0", - "react": "^16.14.0", - "react-dom": "^16.14.0" + "lodash": "^4.17.21", + "rc-util": "^5.44.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "styled-components": "^6.1.13" } } diff --git a/template/chart/index.ejs b/template/chart/index.ejs index 6c2975c75..0fe012b42 100644 --- a/template/chart/index.ejs +++ b/template/chart/index.ejs @@ -1,40 +1,42 @@ -import React, { useEffect, useImperativeHandle, forwardRef } from 'react'; -import { <%= chartName %> as G2plot<%= chartName %>, <%= chartName %>Options as G2plotConfig } from '@antv/g2plot'; -import useChart from '../hooks/useChart'; -import { getChart } from '../util'; -import { ChartRefConfig, ContainerConfig } from '../interface'; -import { ErrorBoundary } from '../base'; -import ChartLoading from '../util/createLoading'; +import React, { useEffect, useImperativeHandle, forwardRef } from 'react'; +import { <%= chartName %> as G2plot<%= chartName %>, <%= chartName %>Options as G2plotConfig } from '@antv/g2'; + import useChart from '../hooks/useChart'; + import { getChart } from '../util'; + import { ChartRefConfig, ContainerConfig } from '../interface'; + import { ErrorBoundary } from '../base'; + import ChartLoading from '../util/createLoading'; -export interface <%= chartName %>Config extends G2plotConfig, ContainerConfig { - chartRef?: ChartRefConfig; -} + export interface <%= chartName %>Config extends G2plotConfig, ContainerConfig { + chartRef?: ChartRefConfig; + } -const <%= chartName %>Chart = forwardRef((props: <%= chartName %>Config, ref) => { - const { - chartRef, - style = { - height: 'inherit' - }, - className, - loading, - loadingTemplate, - errorTemplate, - ...rest - } = props; - const { chart, container } = useChart, <%= chartName %>Config>(G2plot<%= chartName %>, rest); - useEffect(() => { - getChart(chartRef, chart.current); - }, [chart.current]); - useImperativeHandle(ref, () => ({ - getChart: () => chart.current, - })); - return ( - - {loading && } -
- - ); -}); + const <%= chartName %>Chart = forwardRef((props: <%= chartName %>Config, ref) => { + const { + chartRef, + style = { + height: 'inherit' + }, + className, + loading, + loadingTemplate, + errorTemplate, + ...rest + } = props; + const { chart, container } = useChart, <%= chartName %>Config>(G2plot<%= chartName + %>, rest); + useEffect(() => { + getChart(chartRef, chart.current); + }, [chart.current]); + useImperativeHandle(ref, () => ({ + getChart: () => chart.current, + })); + return ( + + {loading && + } +
+ + ); + }); -export default <%= chartName %>Chart; \ No newline at end of file + export default <%= chartName %>Chart; \ No newline at end of file diff --git a/template/doc/demo.ejs b/template/doc/demo.ejs index 672427ab8..9d4babd51 100644 --- a/template/doc/demo.ejs +++ b/template/doc/demo.ejs @@ -2,10 +2,7 @@ import React, { useState, useEffect }from 'react'; import ReactDOM from 'react-dom'; import { <%= plotName %> } from '@ant-design/<%= lib %>'; <% if (utilName) { %> -import { <%= utilName %> } from '@antv/util'; -<% } %> -<% if (dataSet) { %> -import { <%= dataSet %> } from '@antv/data-set'; +import { <%= utilName %> } from 'lodash'; <% } %> const Demo<%= chartName %> = () => { @@ -13,4 +10,4 @@ const Demo<%= chartName %> = () => { return <<%= chartName %> {...config} />; }; -ReactDOM.render( />, document.getElementById('container')); \ No newline at end of file +ReactDOM.render( />, document.getElementById('container')); diff --git a/tests/dendrogram.spec.ts b/tests/dendrogram.spec.ts new file mode 100644 index 000000000..90415f9e4 --- /dev/null +++ b/tests/dendrogram.spec.ts @@ -0,0 +1,30 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('use Vertical as direction', async ({ page }) => { + await it(page, '/Dendrogram?direction=vertical&animation=false'); +}); + +test('use Horizontal as direction', async ({ page }) => { + await it(page, '/Dendrogram?direction=horizontal&animation=false'); +}); + +test('use Radial as direction', async ({ page }) => { + await it(page, '/Dendrogram?direction=radial&animation=false'); +}); + +test('Compact Vertical Dendrogram', async ({ page }) => { + await it(page, '/Dendrogram?direction=vertical&compact=true&animation=false'); +}); + +test('Compact Horizontal Dendrogram', async ({ page }) => { + await it(page, '/Dendrogram?direction=horizontal&compact=true&animation=false'); +}); + +test('Compact Radial Dendrogram', async ({ page }) => { + await it(page, '/Dendrogram?direction=radial&compact=true&animation=false'); +}); diff --git a/tests/dendrogram.spec.ts-snapshots/Compact-Horizontal-Dendrogram-1-chromium.png b/tests/dendrogram.spec.ts-snapshots/Compact-Horizontal-Dendrogram-1-chromium.png new file mode 100644 index 000000000..c83ca9e75 Binary files /dev/null and b/tests/dendrogram.spec.ts-snapshots/Compact-Horizontal-Dendrogram-1-chromium.png differ diff --git a/tests/dendrogram.spec.ts-snapshots/Compact-Radial-Dendrogram-1-chromium.png b/tests/dendrogram.spec.ts-snapshots/Compact-Radial-Dendrogram-1-chromium.png new file mode 100644 index 000000000..a1171b06c Binary files /dev/null and b/tests/dendrogram.spec.ts-snapshots/Compact-Radial-Dendrogram-1-chromium.png differ diff --git a/tests/dendrogram.spec.ts-snapshots/Compact-Vertical-Dendrogram-1-chromium.png b/tests/dendrogram.spec.ts-snapshots/Compact-Vertical-Dendrogram-1-chromium.png new file mode 100644 index 000000000..ec0b391bd Binary files /dev/null and b/tests/dendrogram.spec.ts-snapshots/Compact-Vertical-Dendrogram-1-chromium.png differ diff --git a/tests/dendrogram.spec.ts-snapshots/use-Horizontal-as-direction-1-chromium.png b/tests/dendrogram.spec.ts-snapshots/use-Horizontal-as-direction-1-chromium.png new file mode 100644 index 000000000..f190bb501 Binary files /dev/null and b/tests/dendrogram.spec.ts-snapshots/use-Horizontal-as-direction-1-chromium.png differ diff --git a/tests/dendrogram.spec.ts-snapshots/use-Radial-as-direction-1-chromium.png b/tests/dendrogram.spec.ts-snapshots/use-Radial-as-direction-1-chromium.png new file mode 100644 index 000000000..2eaa94383 Binary files /dev/null and b/tests/dendrogram.spec.ts-snapshots/use-Radial-as-direction-1-chromium.png differ diff --git a/tests/dendrogram.spec.ts-snapshots/use-Vertical-as-direction-1-chromium.png b/tests/dendrogram.spec.ts-snapshots/use-Vertical-as-direction-1-chromium.png new file mode 100644 index 000000000..502d7fa79 Binary files /dev/null and b/tests/dendrogram.spec.ts-snapshots/use-Vertical-as-direction-1-chromium.png differ diff --git a/tests/fishbone.spec.ts b/tests/fishbone.spec.ts new file mode 100644 index 000000000..c60673a49 --- /dev/null +++ b/tests/fishbone.spec.ts @@ -0,0 +1,14 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('use Decision as type', async ({ page }) => { + await it(page, '/Fishbone?type=decision'); +}); + +test('use Cause as type', async ({ page }) => { + await it(page, '/Fishbone?type=cause'); +}); diff --git a/tests/fishbone.spec.ts-snapshots/use-Cause-as-type-1-chromium.png b/tests/fishbone.spec.ts-snapshots/use-Cause-as-type-1-chromium.png new file mode 100644 index 000000000..22825693e Binary files /dev/null and b/tests/fishbone.spec.ts-snapshots/use-Cause-as-type-1-chromium.png differ diff --git a/tests/fishbone.spec.ts-snapshots/use-Decision-as-type-1-chromium.png b/tests/fishbone.spec.ts-snapshots/use-Decision-as-type-1-chromium.png new file mode 100644 index 000000000..f8e34d31a Binary files /dev/null and b/tests/fishbone.spec.ts-snapshots/use-Decision-as-type-1-chromium.png differ diff --git a/tests/flow-direction-graph.spec.ts b/tests/flow-direction-graph.spec.ts new file mode 100644 index 000000000..3af667956 --- /dev/null +++ b/tests/flow-direction-graph.spec.ts @@ -0,0 +1,14 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('default flow direction graph', async ({ page }) => { + await it(page, '/UserFlowDirectionDefault?animation=false'); +}); + +test('user flow direction graph', async ({ page }) => { + await it(page, '/UserFlowDirectionGraph?animation=false'); +}); diff --git a/tests/flow-direction-graph.spec.ts-snapshots/default-flow-direction-graph-1-chromium.png b/tests/flow-direction-graph.spec.ts-snapshots/default-flow-direction-graph-1-chromium.png new file mode 100644 index 000000000..d0a8d8a53 Binary files /dev/null and b/tests/flow-direction-graph.spec.ts-snapshots/default-flow-direction-graph-1-chromium.png differ diff --git a/tests/flow-direction-graph.spec.ts-snapshots/user-flow-direction-graph-1-chromium.png b/tests/flow-direction-graph.spec.ts-snapshots/user-flow-direction-graph-1-chromium.png new file mode 100644 index 000000000..294b12dd2 Binary files /dev/null and b/tests/flow-direction-graph.spec.ts-snapshots/user-flow-direction-graph-1-chromium.png differ diff --git a/tests/flow-graph.spec.ts b/tests/flow-graph.spec.ts new file mode 100644 index 000000000..e49d9102b --- /dev/null +++ b/tests/flow-graph.spec.ts @@ -0,0 +1,18 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('default flow graph', async ({ page }) => { + await it(page, '/FlowGraph?animation=false'); +}); + +test('product launch flow graph', async ({ page }) => { + await it(page, '/FlowGraphProductLaunch?animation=false'); +}); + +test('task scheduling flow graph', async ({ page }) => { + await it(page, '/FlowGraphTaskScheduling?animation=false'); +}); diff --git a/tests/flow-graph.spec.ts-snapshots/default-flow-graph-1-chromium.png b/tests/flow-graph.spec.ts-snapshots/default-flow-graph-1-chromium.png new file mode 100644 index 000000000..8b9582945 Binary files /dev/null and b/tests/flow-graph.spec.ts-snapshots/default-flow-graph-1-chromium.png differ diff --git a/tests/flow-graph.spec.ts-snapshots/product-launch-flow-graph-1-chromium.png b/tests/flow-graph.spec.ts-snapshots/product-launch-flow-graph-1-chromium.png new file mode 100644 index 000000000..5e0d8b38b Binary files /dev/null and b/tests/flow-graph.spec.ts-snapshots/product-launch-flow-graph-1-chromium.png differ diff --git a/tests/flow-graph.spec.ts-snapshots/task-scheduling-flow-graph-1-chromium.png b/tests/flow-graph.spec.ts-snapshots/task-scheduling-flow-graph-1-chromium.png new file mode 100644 index 000000000..9619496d9 Binary files /dev/null and b/tests/flow-graph.spec.ts-snapshots/task-scheduling-flow-graph-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts b/tests/indented-tree.spec.ts new file mode 100644 index 000000000..d58fbb087 --- /dev/null +++ b/tests/indented-tree.spec.ts @@ -0,0 +1,62 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('use Default as type', async ({ page }) => { + await it(page, '/IndentedTree?type=default'); +}); + +test('use Linear as type', async ({ page }) => { + await it(page, '/IndentedTree?type=linear'); +}); + +test('use Boxed as type', async ({ page }) => { + await it(page, '/IndentedTree?type=boxed'); +}); + +test('use 1 as a default expand level', async ({ page }) => { + await it(page, '/IndentedTree?type=linear&defaultExpandLevel=1'); +}); + +test('use 2 as a default expand level', async ({ page }) => { + await it(page, '/IndentedTree?type=linear&defaultExpandLevel=2'); +}); + +test('use Left as node direction', async ({ page }) => { + await it(page, '/IndentedTree?type=linear&direction=left'); +}); + +test('use Right as node direction', async ({ page }) => { + await it(page, '/IndentedTree?type=linear&direction=right'); +}); + +test('use Alternate as node direction', async ({ page }) => { + await it(page, '/IndentedTree?type=linear&direction=alternate'); +}); + +test('trigger collapse expand by icon', async ({ page }) => { + await it( + page, + '/IndentedTree?type=linear&defaultExpandLevel=2&collapseExpand=true&collapseExpandTrigger=icon', + async () => { + await page.locator('.arrow-count-icon-collapsed').nth(1).click(); + }, + ); +}); + +test('trigger collapse expand by node', async ({ page }) => { + await it( + page, + '/IndentedTree?type=linear&defaultExpandLevel=2&collapseExpand=true&collapseExpandTrigger=node', + async () => { + await page.getByText('Models diversity', { exact: true })?.click(); + }, + ); +}); + +test('use 60,100 as node width', async ({ page }) => { + await it(page, '/IndentedTree?type=boxed&nodeMinWidth=60&nodeMaxWidth=100'); +}); diff --git a/tests/indented-tree.spec.ts-snapshots/trigger-collapse-expand-by-icon-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/trigger-collapse-expand-by-icon-1-chromium.png new file mode 100644 index 000000000..a0a51ff1c Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/trigger-collapse-expand-by-icon-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/trigger-collapse-expand-by-node-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/trigger-collapse-expand-by-node-1-chromium.png new file mode 100644 index 000000000..3ebeee46c Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/trigger-collapse-expand-by-node-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-1-as-a-default-expand-level-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-1-as-a-default-expand-level-1-chromium.png new file mode 100644 index 000000000..f6b718234 Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-1-as-a-default-expand-level-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-2-as-a-default-expand-level-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-2-as-a-default-expand-level-1-chromium.png new file mode 100644 index 000000000..6d81a6a07 Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-2-as-a-default-expand-level-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-60-100-as-node-width-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-60-100-as-node-width-1-chromium.png new file mode 100644 index 000000000..dc78f4bd5 Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-60-100-as-node-width-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-Alternate-as-node-direction-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-Alternate-as-node-direction-1-chromium.png new file mode 100644 index 000000000..f392ab2e6 Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-Alternate-as-node-direction-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-Boxed-as-type-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-Boxed-as-type-1-chromium.png new file mode 100644 index 000000000..0ba28beff Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-Boxed-as-type-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-Default-as-type-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-Default-as-type-1-chromium.png new file mode 100644 index 000000000..8d4f3859e Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-Default-as-type-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-Left-as-node-direction-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-Left-as-node-direction-1-chromium.png new file mode 100644 index 000000000..909849e8f Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-Left-as-node-direction-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-Linear-as-type-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-Linear-as-type-1-chromium.png new file mode 100644 index 000000000..2da7a37ab Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-Linear-as-type-1-chromium.png differ diff --git a/tests/indented-tree.spec.ts-snapshots/use-Right-as-node-direction-1-chromium.png b/tests/indented-tree.spec.ts-snapshots/use-Right-as-node-direction-1-chromium.png new file mode 100644 index 000000000..2da7a37ab Binary files /dev/null and b/tests/indented-tree.spec.ts-snapshots/use-Right-as-node-direction-1-chromium.png differ diff --git a/tests/mind-map.spec.ts b/tests/mind-map.spec.ts new file mode 100644 index 000000000..37e2d8001 --- /dev/null +++ b/tests/mind-map.spec.ts @@ -0,0 +1,66 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('use Default as type', async ({ page }) => { + await it(page, '/MindMap?type=default'); +}); + +test('use Linear as type', async ({ page }) => { + await it(page, '/MindMap?type=linear'); +}); + +test('use Boxed as type', async ({ page }) => { + await it(page, '/MindMap?type=boxed'); +}); + +test('use 1 as a default expand level', async ({ page }) => { + await it(page, '/MindMap?type=linear&defaultExpandLevel=1'); +}); + +test('use 2 as a default expand level', async ({ page }) => { + await it(page, '/MindMap?type=linear&defaultExpandLevel=2'); +}); + +test('use Left as node direction', async ({ page }) => { + await it(page, '/MindMap?type=linear&direction=left'); +}); + +test('use Right as node direction', async ({ page }) => { + await it(page, '/MindMap?type=linear&direction=right'); +}); + +test('use Alternate as node direction', async ({ page }) => { + await it(page, '/MindMap?type=linear&direction=alternate'); +}); + +test('trigger collapse expand by icon', async ({ page }) => { + await it( + page, + '/MindMap?type=linear&defaultExpandLevel=2&collapseExpand=true&collapseExpandTrigger=icon', + async () => { + await page.locator('.arrow-count-icon-collapsed').nth(1).click(); + }, + ); +}); + +test('trigger collapse expand by node', async ({ page }) => { + await it( + page, + '/MindMap?type=linear&defaultExpandLevel=2&collapseExpand=true&collapseExpandTrigger=node', + async () => { + await page.getByText('Models diversity', { exact: true })?.click(); + }, + ); +}); + +test('use 60,100 as node width', async ({ page }) => { + await it(page, '/MindMap?type=boxed&nodeMinWidth=60&nodeMaxWidth=100'); +}); + +test('use custom node style', async ({ page }) => { + await it(page, '/MindMapCustom'); +}); diff --git a/tests/mind-map.spec.ts-snapshots/trigger-collapse-expand-by-icon-1-chromium.png b/tests/mind-map.spec.ts-snapshots/trigger-collapse-expand-by-icon-1-chromium.png new file mode 100644 index 000000000..44b792c59 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/trigger-collapse-expand-by-icon-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/trigger-collapse-expand-by-node-1-chromium.png b/tests/mind-map.spec.ts-snapshots/trigger-collapse-expand-by-node-1-chromium.png new file mode 100644 index 000000000..957f58fe4 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/trigger-collapse-expand-by-node-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-1-as-a-default-expand-level-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-1-as-a-default-expand-level-1-chromium.png new file mode 100644 index 000000000..d39f99ba3 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-1-as-a-default-expand-level-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-2-as-a-default-expand-level-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-2-as-a-default-expand-level-1-chromium.png new file mode 100644 index 000000000..3239a0381 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-2-as-a-default-expand-level-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-60-100-as-node-width-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-60-100-as-node-width-1-chromium.png new file mode 100644 index 000000000..a21005e1f Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-60-100-as-node-width-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-Alternate-as-node-direction-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-Alternate-as-node-direction-1-chromium.png new file mode 100644 index 000000000..543d7c6d2 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-Alternate-as-node-direction-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-Boxed-as-type-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-Boxed-as-type-1-chromium.png new file mode 100644 index 000000000..c492e4128 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-Boxed-as-type-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-Default-as-type-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-Default-as-type-1-chromium.png new file mode 100644 index 000000000..52de81a0b Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-Default-as-type-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-Left-as-node-direction-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-Left-as-node-direction-1-chromium.png new file mode 100644 index 000000000..b44f054c7 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-Left-as-node-direction-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-Linear-as-type-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-Linear-as-type-1-chromium.png new file mode 100644 index 000000000..543d7c6d2 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-Linear-as-type-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-Right-as-node-direction-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-Right-as-node-direction-1-chromium.png new file mode 100644 index 000000000..61119ae0a Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-Right-as-node-direction-1-chromium.png differ diff --git a/tests/mind-map.spec.ts-snapshots/use-custom-node-style-1-chromium.png b/tests/mind-map.spec.ts-snapshots/use-custom-node-style-1-chromium.png new file mode 100644 index 000000000..eac636323 Binary files /dev/null and b/tests/mind-map.spec.ts-snapshots/use-custom-node-style-1-chromium.png differ diff --git a/tests/network-graph.spec.ts b/tests/network-graph.spec.ts new file mode 100644 index 000000000..2cbabd888 --- /dev/null +++ b/tests/network-graph.spec.ts @@ -0,0 +1,10 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('language tree', async ({ page }) => { + await it(page, '/NetworkGraph'); +}); diff --git a/tests/network-graph.spec.ts-snapshots/language-tree-1-chromium.png b/tests/network-graph.spec.ts-snapshots/language-tree-1-chromium.png new file mode 100644 index 000000000..fc99eab95 Binary files /dev/null and b/tests/network-graph.spec.ts-snapshots/language-tree-1-chromium.png differ diff --git a/tests/organization-chart.spec.ts b/tests/organization-chart.spec.ts new file mode 100644 index 000000000..71e06abf6 --- /dev/null +++ b/tests/organization-chart.spec.ts @@ -0,0 +1,14 @@ +import { test } from '@playwright/test'; +import { it } from './util'; + +test.beforeEach(async ({}, testInfo) => { + testInfo.snapshotSuffix = ''; +}); + +test('default organization', async ({ page }) => { + await it(page, '/OrganizationChart?animation=false'); +}); + +test('organization with data', async ({ page }) => { + await it(page, '/OrganizationChart2?animation=false'); +}); diff --git a/tests/organization-chart.spec.ts-snapshots/default-organization-1-chromium.png b/tests/organization-chart.spec.ts-snapshots/default-organization-1-chromium.png new file mode 100644 index 000000000..0180cc939 Binary files /dev/null and b/tests/organization-chart.spec.ts-snapshots/default-organization-1-chromium.png differ diff --git a/tests/organization-chart.spec.ts-snapshots/organization-with-data-1-chromium.png b/tests/organization-chart.spec.ts-snapshots/organization-with-data-1-chromium.png new file mode 100644 index 000000000..0180cc939 Binary files /dev/null and b/tests/organization-chart.spec.ts-snapshots/organization-with-data-1-chromium.png differ diff --git a/tests/util.ts b/tests/util.ts new file mode 100644 index 000000000..4cf6072de --- /dev/null +++ b/tests/util.ts @@ -0,0 +1,10 @@ +import { expect, type Page } from '@playwright/test'; + +export const it = async (page: Page, url: string, callback?: () => Promise) => { + await page.goto(url); + await callback?.(); + await expect(page).toHaveScreenshot({ + clip: { x: 0, y: 0, width: 500, height: 500 }, + maxDiffPixelRatio: 0.1, + }); +}; diff --git a/tsconfig.json b/tsconfig.json index 84e843566..e5fdc0ced 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,6 @@ "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": false, - "suppressImplicitAnyIndexErrors": true, "experimentalDecorators": true, "strict": false, "skipLibCheck": true, @@ -20,7 +19,11 @@ "declaration": true, "noUnusedLocals": false, "noImplicitAny": false, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "preserveSymlinks": true, + "paths": { + "@@/*": ["src/.umi/*"] + } }, "exclude": [ "node_modules", @@ -47,8 +50,5 @@ "jest.config.js", "**/fixtures", "./tests/no-duplicated.ts" - ], - "paths": { - "@@/*": ["src/.umi/*"] - } + ] }