diff --git a/.changeset/README.md b/.changeset/README.md
new file mode 100644
index 0000000..e5b6d8d
--- /dev/null
+++ b/.changeset/README.md
@@ -0,0 +1,8 @@
+# Changesets
+
+Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
+with multi-package repos, or single-package repos to help you version and publish your code. You can
+find the full documentation for it [in our repository](https://github.com/changesets/changesets)
+
+We have a quick list of common questions to get you started engaging with this project in
+[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
diff --git a/.changeset/config.json b/.changeset/config.json
new file mode 100644
index 0000000..dc04c94
--- /dev/null
+++ b/.changeset/config.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
+ "changelog": ["@changesets/changelog-github", { "repo": "antvis/GPT-Vis" }],
+ "commit": false,
+ "fixed": [],
+ "linked": [],
+ "access": "restricted",
+ "baseBranch": "main",
+ "updateInternalDependencies": "patch",
+ "ignore": []
+}
diff --git a/.changeset/pretty-weeks-knock.md b/.changeset/pretty-weeks-knock.md
new file mode 100644
index 0000000..d9c4972
--- /dev/null
+++ b/.changeset/pretty-weeks-knock.md
@@ -0,0 +1,5 @@
+---
+'@antv/gpt-vis': patch
+---
+
+feat: 20 + Visual components
diff --git a/.dumirc.ts b/.dumirc.ts
new file mode 100644
index 0000000..bd2df23
--- /dev/null
+++ b/.dumirc.ts
@@ -0,0 +1,26 @@
+import { defineConfig } from 'dumi';
+
+export default defineConfig({
+ outputPath: 'docs-dist',
+ favicons: ['https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png'],
+ themeConfig: {
+ name: 'GPT-Vis',
+ logo: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
+ footer: `Open-source MIT Licensed | Copyright © 2024
+
+Powered by Antv `,
+ socialLinks: {
+ github: 'https://github.com/antvis/GPT-Vis',
+ },
+ },
+ externals: {
+ 'mapbox-gl': 'window.mapboxgl',
+ 'maplibre-gl': 'window.maplibregl',
+ },
+ theme: {
+ '@c-primary': '#691eff',
+ '@s-content-width': '100%',
+ '@s-content-padding': '48px',
+ '@s-sidebar-width': '300px',
+ },
+});
diff --git a/.editorconfig b/.editorconfig
index e717f5e..7e3649a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -11,3 +11,6 @@ insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index 2898340..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- extends: require.resolve('@umijs/lint/dist/config/eslint'),
-};
diff --git a/.fatherrc.ts b/.fatherrc.ts
new file mode 100644
index 0000000..e983fef
--- /dev/null
+++ b/.fatherrc.ts
@@ -0,0 +1,49 @@
+import { defineConfig } from 'father';
+import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
+
+export default defineConfig({
+ esm: {
+ output: 'dist/esm',
+ },
+ cjs: {
+ output: 'dist/cjs',
+ },
+ // https://github.com/umijs/father/blob/master/docs/config.md#umd
+ umd: {
+ name: 'GPTVis',
+ output: {
+ path: 'dist/umd',
+ filename: 'index.min.js',
+ },
+ platform: 'browser',
+ targets: {
+ chrome: 51,
+ },
+ externals: {
+ lodash: '_',
+ 'lodash-es': '_',
+ react: 'React',
+ 'mapbox-gl': {
+ root: 'mapboxgl',
+ commonjs: 'mapbox-gl',
+ commonjs2: 'mapbox-gl',
+ amd: 'mapbox-gl',
+ },
+ 'maplibre-gl': {
+ root: 'maplibregl',
+ commonjs: 'maplibre-gl',
+ commonjs2: 'maplibre-gl',
+ amd: 'maplibre-gl',
+ },
+ },
+ chainWebpack(memo) {
+ // 关闭压缩方便调试,默认开启
+ // memo.optimization.minimize(false);
+
+ memo
+ .plugin('webpack-bundle-analyzer')
+ .use(BundleAnalyzerPlugin, [{ analyzerMode: 'static', openAnalyzer: false }]);
+ return memo;
+ },
+ },
+});
diff --git a/.github/.gitleaks.toml b/.github/.gitleaks.toml
new file mode 100644
index 0000000..8dca9e6
--- /dev/null
+++ b/.github/.gitleaks.toml
@@ -0,0 +1,124 @@
+title = "gitleaks config"
+[[rules]]
+ description = "AWS Manager ID"
+ regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
+ tags = ["key", "AWS"]
+[[rules]]
+ description = "AWS Secret Key"
+ regex = '''(?i)aws(.{0,20})?(?-i)['\"][0-9a-zA-Z\/+]{40}['\"]'''
+ tags = ["key", "AWS"]
+[[rules]]
+ description = "AWS MWS key"
+ regex = '''amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'''
+ tags = ["key", "AWS", "MWS"]
+[[rules]]
+ description = "Facebook Secret Key"
+ regex = '''(?i)(facebook|fb)(.{0,20})?(?-i)['\"][0-9a-f]{32}['\"]'''
+ tags = ["key", "Facebook"]
+[[rules]]
+ description = "Facebook Client ID"
+ regex = '''(?i)(facebook|fb)(.{0,20})?['\"][0-9]{13,17}['\"]'''
+ tags = ["key", "Facebook"]
+[[rules]]
+ description = "Twitter Secret Key"
+ regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{35,44}['\"]'''
+ tags = ["key", "Twitter"]
+[[rules]]
+ description = "Twitter Client ID"
+ regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{18,25}['\"]'''
+ tags = ["client", "Twitter"]
+[[rules]]
+ description = "Github"
+ regex = '''(?i)github(.{0,20})?(?-i)['\"][0-9a-zA-Z]{35,40}['\"]'''
+ tags = ["key", "Github"]
+[[rules]]
+ description = "Github Token"
+ regex = '''[0-9a-zA-Z]{35,40}'''
+ tags = ["key", "Github Token"]
+[[rules]]
+ description = "Alibaba"
+ regex = '''(alibaba|antfin)-inc'''
+ tags = ["key", "Alibaba"]
+[[rules]]
+ description = "antfin"
+ regex = '''(?i)antfin(.{0,20})?(?-i)['\"][0-9a-zA-Z]{35,40}['\"]'''
+ tags = ["key", "Antfin"]
+[[rules]]
+ description = "LinkedIn Client ID"
+ regex = '''(?i)linkedin(.{0,20})?(?-i)['\"][0-9a-z]{12}['\"]'''
+ tags = ["client", "LinkedIn"]
+[[rules]]
+ description = "LinkedIn Secret Key"
+ regex = '''(?i)linkedin(.{0,20})?['\"][0-9a-z]{16}['\"]'''
+ tags = ["secret", "LinkedIn"]
+[[rules]]
+ description = "Slack"
+ regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?'''
+ tags = ["key", "Slack"]
+[[rules]]
+ description = "Asymmetric Private Key"
+ regex = '''-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----'''
+ tags = ["key", "AsymmetricPrivateKey"]
+[[rules]]
+ description = "Public Key"
+ regex = '''ssh-rsa'''
+ tags = ["keys", "public key"]
+[[rules]]
+ description = "Gitlab Key"
+ regex = '''privateToken|private-token'''
+ tags = ["keys", "Gitlab"]
+[[rules]]
+ description = "Generic Credential"
+ regex = '''(?i)(api_key|apikey|secret)(.{0,20})?['|"][0-9a-zA-Z]{16,45}['|"]'''
+ tags = ["key", "API", "generic"]
+[[rules]]
+ description = "Google API key"
+ regex = '''AIza[0-9A-Za-z\\-_]{35}'''
+ tags = ["key", "Google"]
+[[rules]]
+ description = "Heroku API key"
+ regex = '''(?i)heroku(.{0,20})?['"][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"]'''
+ tags = ["key", "Heroku"]
+[[rules]]
+ description = "MailChimp API key"
+ regex = '''(?i)(mailchimp|mc)(.{0,20})?['"][0-9a-f]{32}-us[0-9]{1,2}['"]'''
+ tags = ["key", "Mailchimp"]
+[[rules]]
+ description = "Mailgun API key"
+ regex = '''(?i)(mailgun|mg)(.{0,20})?['"][0-9a-z]{32}['"]'''
+ tags = ["key", "Mailgun"]
+[[rules]]
+ description = "PayPal Braintree access token"
+ regex = '''access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}'''
+ tags = ["key", "Paypal"]
+[[rules]]
+ description = "Picatic API key"
+ regex = '''sk_live_[0-9a-z]{32}'''
+ tags = ["key", "Picatic"]
+[[rules]]
+ description = "SendGrid API Key"
+ regex = '''SG\.[\w_]{16,32}\.[\w_]{16,64}'''
+ tags = ["key", "SendGrid"]
+[[rules]]
+ description = "Slack Webhook"
+ regex = '''https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}'''
+ tags = ["key", "slack"]
+[[rules]]
+ description = "Stripe API key"
+ regex = '''(?i)stripe(.{0,20})?['\"][sk|rk]_live_[0-9a-zA-Z]{24}'''
+ tags = ["key", "Stripe"]
+[[rules]]
+ description = "Square access token"
+ regex = '''sq0atp-[0-9A-Za-z\-_]{22}'''
+ tags = ["key", "square"]
+[[rules]]
+ description = "Square OAuth secret"
+ regex = '''sq0csp-[0-9A-Za-z\\-_]{43}'''
+ tags = ["key", "square"]
+[[rules]]
+ description = "Twilio API key"
+ regex = '''(?i)twilio(.{0,20})?['\"][0-9a-f]{32}['\"]'''
+ tags = ["key", "twilio"]
+[whitelist]
+ description = "Whitelisted files"
+ file = '''(^\.?gitleaks.toml$|(.*?)(jpg|gif|doc|pdf|bin)|package-lock\.json$)'''
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..9265590
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,13 @@
+### PR includes
+
+
+
+- [ ] fixed #0
+- [ ] add / modify test cases
+- [ ] documents, demos
+
+### Screenshot
+
+| Before | After |
+| ------ | ----- |
+| ❌ | ✅ |
diff --git a/.github/actions/prepare-install/action.yml b/.github/actions/prepare-install/action.yml
new file mode 100644
index 0000000..c1796de
--- /dev/null
+++ b/.github/actions/prepare-install/action.yml
@@ -0,0 +1,24 @@
+name: 'Prepare Install'
+description: 'Prepare repository and install all dependencies'
+
+inputs:
+ node-version:
+ description: 'The node version to setup'
+ required: true
+
+runs:
+ using: 'composite'
+ steps:
+ - uses: pnpm/action-setup@v3
+ with:
+ version: 9
+
+ - name: Use Node.js ${{ inputs.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ inputs.node-version }}
+ # cache: 'pnpm' # pnpm-lock.yaml caching dependencies to speed up workflows
+
+ - name: Install dependencies
+ shell: bash
+ run: pnpm install
diff --git a/.github/release.yaml b/.github/release.yaml
new file mode 100644
index 0000000..6caf9dd
--- /dev/null
+++ b/.github/release.yaml
@@ -0,0 +1,24 @@
+# for .github/workflows/release-label.yml
+# These labels can be used in conjunction GitHub's [automatically generated release notes](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes)
+
+changelog:
+ exclude:
+ labels:
+ - ignore-for-release
+ - pr(chore)
+ categories:
+ - title: 🎉 New Features
+ labels:
+ - pr(feature)
+ - title: 📖 Documentation Changes
+ labels:
+ - pr(documentation)
+ - title: 🐛 Bug Fixes
+ labels:
+ - pr(bugfix)
+ - title: 🛠 Breaking Changes
+ labels:
+ - pr(breaking)
+ - title: Other Changes
+ labels:
+ - '*'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..96d4084
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,68 @@
+name: CI
+
+on:
+ push:
+ branches: [master]
+ paths:
+ - 'src/**'
+ - '__tests__/**'
+ pull_request:
+ branches:
+ - '**'
+ paths:
+ - 'src/**'
+ - '__tests__/**'
+
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
+concurrency:
+ group: '${{ github.workflow }} - ${{ github.head_ref || github.ref }}'
+ cancel-in-progress: true
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18.x]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ uses: ./.github/actions/prepare-install
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Check
+ run: |
+ pnpm check-deps
+ pnpm check-format
+ - name: Lint
+ run: pnpm lint:ts
+
+ # unit-test:
+ # runs-on: ubuntu-latest
+ # strategy:
+ # matrix:
+ # node-version: [18.x]
+ # steps:
+ # - uses: actions/checkout@v4
+ # - name: Install dependencies
+ # uses: ./.github/actions/prepare-install
+ # with:
+ # node-version: ${{ matrix.node-version }}
+ # - name: Unit Test
+ # run: pnpm test:unit
+
+ size-test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18.x]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ uses: ./.github/actions/prepare-install
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Szie Test With Build
+ run: pnpm test:size
diff --git a/.github/workflows/create-bumb-version-pr.yml b/.github/workflows/create-bumb-version-pr.yml
new file mode 100644
index 0000000..8d9fc96
--- /dev/null
+++ b/.github/workflows/create-bumb-version-pr.yml
@@ -0,0 +1,48 @@
+name: Create bump version PR
+
+on:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }} - ${{ github.ref }}'
+ cancel-in-progress: true
+
+jobs:
+ bump-version-pr:
+ name: Bump version PR
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18.x]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ uses: ./.github/actions/prepare-install
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Pre build ckeck
+ run: pnpm build
+
+ - name: Create Version Pull Request
+ id: changesets
+ uses: changesets/action@v1
+ with:
+ version: pnpm publish-version
+ commit: 'chore: version'
+ title: 'chore: version'
+ setupGitUser: true
+ createGithubReleases: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: After changesets
+ run: |
+ if [[ ${{ steps.changesets.outputs.hasChangesets }} == false ]]; then
+ # No changesets
+ echo "::error title=No changesets found::All changesets are empty, not creating PR, please add changeset"
+ exit 1
+ else
+ # Created Pull Request
+ echo "::notice title=PR is created::Created bump version PR address is: https://github.com/${{github.repository}}/pull/${{steps.changesets.outputs.pullRequestNumber}}"
+ fi
diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml
new file mode 100644
index 0000000..5eb150c
--- /dev/null
+++ b/.github/workflows/deploy-site.yml
@@ -0,0 +1,40 @@
+name: Deploy Site
+
+on:
+ workflow_dispatch:
+ release:
+ # released: only latest release will trigger
+ types: [released]
+
+concurrency:
+ group: '${{ github.workflow }} - ${{ github.ref }}'
+ cancel-in-progress: true
+
+jobs:
+ publish-site:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18.x]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ uses: ./.github/actions/prepare-install
+ with:
+ node-version: ${{ matrix.node-version }}
+ - run: pnpm docs:build
+ - run: |
+ echo "gpt-vis.antv.vision" > docs-dist/CNAME
+ - run: |
+ cd docs-dist
+ git init
+ git config --local user.name antv
+ git config --local user.email antv@antfin.com
+ git add .
+ git commit -m "update by release action"
+ - uses: ad-m/github-push-action@master
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ directory: docs-dist
+ branch: gh-pages
+ force: true
diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml
new file mode 100644
index 0000000..4cde88c
--- /dev/null
+++ b/.github/workflows/gitleaks.yml
@@ -0,0 +1,15 @@
+name: Gitleaks
+
+on: push
+
+jobs:
+ gitleaks:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: '1'
+ - name: gitleaks-action
+ uses: zricethezav/gitleaks-action@v1.6.0
+ with:
+ config-path: .github/.gitleaks.toml
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
new file mode 100644
index 0000000..743318f
--- /dev/null
+++ b/.github/workflows/preview.yml
@@ -0,0 +1,25 @@
+name: Surge PR Preview
+
+on:
+ pull_request:
+ paths:
+ - 'docs/**'
+ - 'src/**'
+jobs:
+ preview:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18.x]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ uses: ./.github/actions/prepare-install
+ with:
+ node-version: ${{ matrix.node-version }}
+ - uses: afc163/surge-preview@v1
+ with:
+ surge_token: ${{ secrets.SURGE_TOKEN }}
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ dist: docs-dist
+ build: pn docs:build
diff --git a/.github/workflows/release-label.yml b/.github/workflows/release-label.yml
new file mode 100644
index 0000000..4de49ad
--- /dev/null
+++ b/.github/workflows/release-label.yml
@@ -0,0 +1,15 @@
+name: Conventional Release Labels
+
+# Warning, do not check out untrusted code with
+# the pull_request_target event.
+on:
+ pull_request_target:
+ types: [opened, edited]
+jobs:
+ label:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: bcoe/conventional-release-labels@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ type_labels: '{"feat": "pr(feature)", "fix": "pr(bugfix)", "breaking": "pr(breaking)", "docs": "pr(documentation)", "chore": "pr(chore)"}'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..4af1c28
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,94 @@
+name: Release
+
+on:
+ workflow_dispatch:
+ pull_request_review:
+ types: [submitted]
+
+concurrency:
+ group: '${{ github.workflow }} - ${{ github.head_ref || github.ref }}'
+ cancel-in-progress: true
+
+jobs:
+ release:
+ # created version approved PR by changesets bot or manual dispatch
+ if: |
+ (
+ github.event_name == 'pull_request_review' &&
+ github.event.pull_request.user.type == 'Bot' &&
+ startswith(github.event.pull_request.head.ref, 'changeset-release/') &&
+ github.event.review.state == 'APPROVED'
+ ) ||
+ github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18.x]
+ outputs:
+ published: ${{ steps.changesets.outputs.published }}
+ publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ uses: ./.github/actions/prepare-install
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Release NPM and Github
+ id: changesets
+ uses: changesets/action@v1
+ env:
+ # need personal access token when Triggering a workflow from a workflow
+ # triggering release event of Release Notify and Deploy Site
+ # https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow
+ GITHUB_TOKEN: ${{ secrets.YJ_GITHUB_TOKEN }}
+ NPM_TOKEN: ${{ secrets.YJ_NPM_TOKEN }}
+ with:
+ publish: pnpm publish-package
+ createGithubReleases: true
+
+ # when release not publish
+ - name: Released not publish
+ if: steps.changesets.outputs.published == 'false'
+ run: |
+ if [[ ${{ steps.changesets.outputs.hasChangesets }} == false ]]; then
+ # No changesets
+ echo "::error title=No changesets found::All changesets are empty, please add changeset"
+ else
+ # Released failed
+ echo "::error title=Publish failed::Released NPM or Github failed"
+ fi
+ exit 1
+
+ # when release published successful
+ release-succeeded:
+ needs: release
+ if: needs.release.outputs.published == 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Release succeeded comment
+ if: github.event_name == 'pull_request_review'
+ uses: peter-evans/create-or-update-comment@v4
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ body: |
+ Released to successful 🎉
+
+ - Released [NPM][1]
+ - Released [Github][2]
+
+ `${{ needs.release.outputs.publishedPackages }}`
+
+ [1]: https://www.npmjs.com/package/@antv/GPT-vis?activeTab=versions
+ [2]: https://github.com/${{github.repository}}/releases
+ reactions: 'rocket'
+
+ - name: automerge
+ if: github.event_name == 'pull_request_review'
+ uses: 'pascalgn/automerge-action@v0.16.2'
+ env:
+ MERGE_LABELS: ''
+ PULL_REQUEST: ${{ github.event.pull_request.number }}
+ MERGE_METHOD: 'squash'
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 64f1da6..fc3ead2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,30 +1,81 @@
+
+# Created by https://www.gitignore.io/api/node
+
+### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-lerna-debug.log*
yarn.lock
package-lock.json
pnpm-lock.yaml
-# Sys
-.DS_Store
-.idea
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
-# Node
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
node_modules/
-.npmrc
-# Build
-dist
-lib
-esm
-es
+#history
+.history/
-# Test
-coverage
+jspm_packages/
-# Bundle visualizer
-stats.html
+# Typescript v1 declaration files
+# typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+
+# End of https://www.gitignore.io/api/node
+
+.DS_Store
+.cache
+.idea
+
+# Build
+dist
+docs-dist
+.dumi
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..f0abd67
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+npx --no -- commitlint --edit "$1"
diff --git a/.husky/pre-commit b/.husky/pre-commit
old mode 100755
new mode 100644
index d24fdfc..3b608ae
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,3 @@
-#!/usr/bin/env sh
-. "$(dirname -- "$0")/_/husky.sh"
+#!/bin/sh
npx lint-staged
diff --git a/.prettierignore b/.prettierignore
index 6a50e20..0c1459a 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,2 +1,18 @@
-/dist
-*.yaml
+*.svg
+.DS_Store
+.gitignore
+.prettierignore
+.editorconfig
+.eslintignore
+node_modules
+yarn.lock
+yarn-error.log
+npm-debug.log
+CNAME
+LICENSE
+.cache
+.history
+
+# output dist
+dist/**
+
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..772dd4f
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "printWidth": 100,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "arrowParens": "always",
+ "overrides": [
+ { "files": ".prettierrc", "options": { "parser": "json" } }
+ ],
+ "plugins": ["prettier-plugin-organize-imports"]
+}
diff --git a/.prettierrc.js b/.prettierrc.js
deleted file mode 100644
index e6df093..0000000
--- a/.prettierrc.js
+++ /dev/null
@@ -1,19 +0,0 @@
-module.exports = {
- pluginSearchDirs: false,
- plugins: [
- require.resolve('prettier-plugin-organize-imports'),
- require.resolve('prettier-plugin-packagejson'),
- ],
- printWidth: 80,
- proseWrap: 'never',
- singleQuote: true,
- trailingComma: 'all',
- overrides: [
- {
- files: '*.md',
- options: {
- proseWrap: 'preserve',
- },
- },
- ],
-};
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..021ac36
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,8 @@
+{
+ "recommendations": [
+ "dbaeumer.vscode-eslint",
+ "editorconfig.editorconfig",
+ "esbenp.prettier-vscode"
+ ],
+ "unwantedRecommendations": ["hookyqr.beautify", "dbaeumer.jshint"]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..d465499
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ // An array of language ids which should be validated by ESLint
+ "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
+ // required to make the extension pickup the flat config
+ "eslint.useFlatConfig": true,
+
+ // typescript auto-format settings
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+}
diff --git a/LICENSE b/LICENSE
index b0c7139..769de4e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
-MIT License
+The MIT License (MIT)
-Copyright (c)
+Copyright (c) 2024 AntV
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 426cd63..869c864 100644
--- a/README.md
+++ b/README.md
@@ -1,78 +1,93 @@
-
-GPT-VIS
-
+ English | [简体中文](./README.zh-CN.md)
+
+GPT-Vis
+
Components for GPTs, generative AI, and LLM projects. Not only UI Components.
-![GPT-vis logo-06](https://github.com/eosphoros-ai/GPT-Vis/assets/17919400/c8804ffb-d3d6-45d3-846f-cf217681ab05)
+
+ Document •
+ Knowledge •
+ Huggingface •
+ Agent Demo
+
+
+
-## 特性
+
-- 🤖 **LLM 相关**:针对 LLM 对话式交互,以及服务端序列化输出而设计,方便快速集成到 AI 应用中。
-- 🍡 **丰富组件**:内置有 20+ 美观好看的常用 UI 组件,满足常规需求。
-- 🔨 **易于扩展**:对于自己的特殊 UI 定制需求,提供方便的扩展机制和架构设计。
+## ✨ Features
-## 安装
+- 🤖 **LLM Protocol**: A visual protocol for LLM Agent cards, designed for LLM conversational interaction and service serialized output, to facilitate rapid integration into AI applications.
+- 🍡 **LLM Component**: Developed components for LLM applications, with 20+ commonly used VIS components built-in, providing convenient expansion mechanism and architecture design for customized UI requirements.
+- 📈 **LLM access**: Chart knowledge base and chart recommendation model for seamless access to LLM, directly output visual cards for LLM, and provide knowledge base and recommended model solutions for Agent.
-使用 NodeJS 包管理工具安装依赖。
+## 📦 Installation
```bash
-$npm i --save gpt-vis
+$ npm i @antv/gpt-vis --save
```
-## 快速使用
+## 🔨 Usage
+
+```jsx
+import { GPTVis } from '@antv/gpt-vis';
-快速使用 gpt-vis渲染出对话卡片的 UI。
+const markdownContent = `
+# GPT-VIS \n\nComponents for GPTs, generative AI, and LLM projects. Not only UI Components.
-```tsx
-import { Conversation, Components } from 'gpt-vis';
+Here’s a visualization of Haidilao's food delivery revenue from 2013 to 2022. You can see a steady increase over the years, with notable *growth* particularly in recent years.
-function Demo() {
- // 服务端返回的协议内容
- const content =
- '# GPT-VIS \n\nComponents for GPTs, generative AI, and LLM projects. Not only UI Components.';
+\`\`\`vis-chart
+{ "type": "line","data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}] }
+\`\`\`
+`;
- return {content} ;
-}
+export default () => {
+ return {markdownContent} ;
+};
```
-使用自定义的 UI 组件。UI 渲染最终使用 markdown 格式,所以自定义的方式有两种,一种是基于 markdown code 标签去扩展语言,一种是扩展标签。
+## 🛠 Custom renderer
-````tsx
-import { Conversation, Components } from 'gpt-vis';
+```jsx
+import { GPTVis, withDefaultChartCode, VisText } from '@antv/gpt-vis';
-const custom = {
- 'my-ui': () => {},
-};
+const markdownContent = `
+本月 共产生决策数量 2,783 个,环比增长 15.2% 。高优先级决策 占比56.2% ,呈现稳定上升 趋势,预计下月 将突破3,000 大关。
+
+\`\`\`my-ui
+my data
+\`\`\`
+`;
-function Demo() {
- // 服务端返回的协议内容
- const content = '# GPT-VIS \n\n```my-ui\n{"value": "1"}```';
+const customRenderers = { 'my-ui': ({ children }) => {children}
};
+const components = {
+ 'vis-text': VisText,
+ code: withDefaultChartCode({ languageRenderers: customRenderers }),
+};
- return (
-
- {content}
-
- );
-}
-````
+export default () => {
+ return {markdownContent} ;
+};
+```
-## Development
+## 💻 Development
```bash
# install dependencies
-$ npm install
+$ pnpm install
# develop library by docs demo
-$ npm run dev
+$ pnpm dev
# build library source code
-$ npm run build
+$ pnpm build
```
## License
-MIT
+[MIT](./LICENSE)
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..c95bdf1
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,93 @@
+ [English](./README.md) | 简体中文
+
+GPT-Vis
+
+
+
+Components for GPTs, generative AI, and LLM projects. Not only UI Components.
+
+
+ 文档 •
+ 知识库 •
+ Huggingface •
+ 体验 Agent
+
+
+
+
+
+
+
+
+## 特性
+
+- 🤖 **LLM 协议**:面向 LLM Agent 卡片的可视化协议,针对 LLM 对话式交互,以及服务序列化输出而设计,方便快速集成到 AI 应用中。
+- 🍡 **LLM 组件**:面向 LLM 应用研发组件, 内置有 20+ 常用 VIS 组件,对于定制 UI 需求,提供方便的扩展机制和架构设计。
+- 📈 **LLM 接入**:面向 LLM 无缝接入的图表知识库和图表推荐模型,针对 LLM 直接输出可视化卡片,为 Agent 提供知识库以及推荐模型方案。
+
+## 📦 安装
+
+```bash
+$ npm i @antv/gpt-vis --save
+```
+
+## 🔨 使用
+
+```jsx
+import { GPTVis } from '@antv/gpt-vis';
+
+const markdownContent = `
+# GPT-VIS \n\nComponents for GPTs, generative AI, and LLM projects. Not only UI Components.
+
+Here’s a visualization of Haidilao's food delivery revenue from 2013 to 2022. You can see a steady increase over the years, with notable *growth* particularly in recent years.
+
+\`\`\`vis-chart
+{ "type": "line","data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}] }
+\`\`\`
+`;
+
+export default () => {
+ return {markdownContent} ;
+};
+```
+
+## 🛠 定制渲染器
+
+```jsx
+import { GPTVis, withDefaultChartCode, VisText } from '@antv/gpt-vis';
+
+const markdownContent = `
+本月 共产生决策数量 2,783 个,环比增长 15.2% 。高优先级决策 占比56.2% ,呈现稳定上升 趋势,预计下月 将突破3,000 大关。
+
+\`\`\`my-ui
+my data
+\`\`\`
+`;
+
+const customRenderers = { 'my-ui': ({ children }) => {children}
};
+const components = {
+ 'vis-text': VisText,
+ code: withDefaultChartCode({ languageRenderers: customRenderers }),
+};
+
+export default () => {
+ return {markdownContent} ;
+};
+```
+
+## 💻 本地开发
+
+```bash
+# install dependencies
+$ pnpm install
+
+# develop library by docs demo
+$ pnpm dev
+
+# build library source code
+$ pnpm build
+```
+
+## License
+
+[MIT](./LICENSE)
diff --git a/__tests__/version.spec.ts b/__tests__/version.spec.ts
new file mode 100644
index 0000000..7a24115
--- /dev/null
+++ b/__tests__/version.spec.ts
@@ -0,0 +1,9 @@
+import pkg from '../package.json';
+import { version } from '../src';
+
+describe('version', () => {
+ it('should match the `version` field of package.json', () => {
+ const expected = pkg.version;
+ expect(version).toBe(expected);
+ });
+});
diff --git a/commitlint.config.ts b/commitlint.config.ts
new file mode 100644
index 0000000..f2886c8
--- /dev/null
+++ b/commitlint.config.ts
@@ -0,0 +1,25 @@
+export default {
+ extends: ['@commitlint/config-conventional'],
+ rules: {
+ 'type-enum': [
+ 2,
+ 'always',
+ [
+ 'build',
+ 'chore',
+ 'ci',
+ 'docs',
+ 'feat',
+ 'fix',
+ 'perf',
+ 'refactor',
+ 'revert',
+ 'style',
+ 'test',
+ 'deps',
+ 'wip',
+ ],
+ ],
+ 'header-max-length': [2, 'always', 100],
+ },
+};
diff --git a/dev/demo/basic.ts b/dev/demo/basic.ts
deleted file mode 100644
index cb9a297..0000000
--- a/dev/demo/basic.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-export const Basic = `
-# [GPT-VIS](https://github.com/eosphoros-ai/GPT-VIS)
-
-Components for GPTs, generative AI, and LLM projects. Not only UI Components.;
-
-
-
-Some *emphasis* and strong !
-
-
-
-Here is some JavaScript code:
-
-~~~js
-const greeting = 'hello, world';
-
-const sayHello = (greeting) => {}
-
-sayHello(greeting);
-~~~
-`;
diff --git a/dev/demo/index.ts b/dev/demo/index.ts
deleted file mode 100644
index d0b140f..0000000
--- a/dev/demo/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Basic } from './basic';
diff --git a/dev/index.html b/dev/index.html
deleted file mode 100644
index 1496fd8..0000000
--- a/dev/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/dev/main.tsx b/dev/main.tsx
deleted file mode 100644
index db92fe4..0000000
--- a/dev/main.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import { Components, Conversation } from '../src';
-import * as markdowns from './demo';
-
-const select = document.getElementById('selector') as HTMLSelectElement;
-select.onchange = () => {
- const { value } = select;
- history.pushState({ value }, '', `?name=${value}`);
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- render();
-};
-select.style.display = 'block';
-select.style.minWidth = '120px';
-
-Object.keys(markdowns).forEach((d) => {
- const option = document.createElement('option');
- option.textContent = d;
- option.value = d;
- select.append(option);
-});
-
-let root;
-/**
- * 渲染 markdown 组件
- */
-function render() {
- if (root) root.unmount();
- const markdown = markdowns[select.value];
-
- root = createRoot(document.getElementById('root')!);
-
- root.render(
-
- {markdown}
- ,
- );
-}
-
-const initialValue = new URL(location as any).searchParams.get(
- 'name',
-) as string;
-if (markdowns[initialValue]) select.value = initialValue;
-
-render();
diff --git a/docs/guide/antd-design-x.md b/docs/guide/antd-design-x.md
new file mode 100644
index 0000000..e5b1a0b
--- /dev/null
+++ b/docs/guide/antd-design-x.md
@@ -0,0 +1,170 @@
+---
+title: 在 Antd Design X 中使用
+nav: { title: '指南', order: 0 }
+toc: content
+order: 1
+---
+
+## 使用 Markdown 协议
+
+1.定义图表 Markdown 代码块
+
+```js
+const markdownContent = `
+## GPT-VIS
+Components for GPTs, generative AI, and LLM projects. Not only UI Components.
+
+ \`\`\`vis-chart
+ {
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+ }
+\`\`\``;
+```
+
+2.扩展聊天气泡渲染
+
+```tsx | pure
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { GPTVis } from '@antv/gpt-vis';
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => {content} ;
+
+export default () => {
+ return (
+
+ );
+};
+```
+
+```tsx
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { GPTVis } from '@antv/gpt-vis';
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const markdownContent = `
+## GPT-VIS
+Components for GPTs, generative AI, and LLM projects. Not only UI Components.
+
+\`\`\`vis-chart
+ {
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+ }
+\`\`\``;
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => {content} ;
+
+export default () => {
+ return (
+
+
+
+ );
+};
+```
+
+## 使用结构化的数据
+
+1. 定义你的图表数据
+
+```js
+const mockdata = [
+ { category: '分类一', value: 27 },
+ { category: '分类二', value: 25 },
+ { category: '分类三', value: 18 },
+ { category: '分类四', value: 15 },
+ { category: '分类五', value: 10 },
+ { category: '其他', value: 5 },
+];
+```
+
+2. 渲染聊天气泡
+
+```tsx | pure
+import { Pie } from '@antv/gpt-vis';
+import { Bubble } from '@ant-design/x';
+
+export default () => {
+ return (
+ }
+ styles={{ content: { background: '#fff' } }}
+ />
+ );
+};
+```
+
+```tsx
+import { Pie } from '@antv/gpt-vis';
+import { Bubble } from '@ant-design/x';
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const mockdata = [
+ { category: '分类一', value: 27 },
+ { category: '分类二', value: 25 },
+ { category: '分类三', value: 18 },
+ { category: '分类四', value: 15 },
+ { category: '分类五', value: 10 },
+ { category: '其他', value: 5 },
+];
+
+export default () => {
+ return (
+
+ }
+ avatar={{
+ src: 'https://mdn.alipayobjects.com/huamei_je4oko/afts/img/A*6LRBT7rjOkQAAAAAAAAAAAAADsZ-AQ/original',
+ }}
+ variant="shadow"
+ styles={{ content: { background: '#fff' } }}
+ />
+
+ );
+};
+```
diff --git a/docs/guide/custome-markdown.md b/docs/guide/custome-markdown.md
new file mode 100644
index 0000000..df82b56
--- /dev/null
+++ b/docs/guide/custome-markdown.md
@@ -0,0 +1,34 @@
+---
+title: 扩展 MD 渲染器使用
+nav: { title: '指南', order: 0 }
+toc: content
+order: 2
+---
+
+## 扩展 react-markdown 使用
+
+```tsx | pure
+import React from 'react';
+import Markdown from 'react-markdown';
+import { withChartCode, withDefaultChartCode, ChartType, Line } from '@antv/gpt-vis';
+
+const markdownContent = `
+# GPT-VIS \n\nComponents for GPTs, generative AI, and LLM projects. Not only UI Components.
+
+Here’s a visualization of Haidilao's food delivery revenue from 2013 to 2022. You can see a steady increase over the years, with notable *growth* particularly in recent years.
+
+\`\`\`vis-chart
+{ "type": "line","data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}] }
+\`\`\`
+`;
+
+const CodeBlock = withChartCode({
+ components: { [ChartType.Line]: Line },
+});
+// 或
+// const CodeBlock = withDefaultChartCode();
+
+export default () => {
+ return {markdownContent} ;
+};
+```
diff --git a/docs/guide/customize-style.md b/docs/guide/customize-style.md
new file mode 100644
index 0000000..754f27a
--- /dev/null
+++ b/docs/guide/customize-style.md
@@ -0,0 +1,276 @@
+---
+title: 定制样式
+nav: { title: '指南', order: 0 }
+toc: content
+order: 3
+---
+
+通过在 [ConfigProvider](/components/config-provider) 中传入样式属性,来配置图表组件的全局样式。
+
+## 定制组件级样式
+
+给各个组件定制样式
+
+```tsx | pure
+import { ConfigProvider } from '@antv/gpt-vis';
+
+// 设置甜甜圈样式
+const pieConfig = {
+ legend: false,
+ innerRadius: 0.6,
+ style: {
+ stroke: '#fff',
+ inset: 1,
+ radius: 10,
+ },
+};
+
+// 面积图设置渐变色
+const areaConfig = {
+ style: {
+ fill: 'linear-gradient(-90deg, white 0%, darkgreen 100%)',
+ },
+ line: {
+ style: {
+ stroke: 'darkgreen',
+ strokeWidth: 2,
+ },
+ tooltip: false,
+ },
+};
+
+// 地图设置图标
+const pinMapConfig = {
+ markerStyle: {
+ iconPath:
+ 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*JZf9T6psSzkAAAAAAAAAAAAADmJ7AQ/original',
+ },
+};
+
+export default () => {
+ return (
+
+ // ...
+
+ );
+};
+```
+
+```tsx
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ConfigProvider, GPTVis } from '@antv/gpt-vis';
+
+// 设置甜甜圈样式
+const pieConfig = {
+ legend: false,
+ innerRadius: 0.6,
+ style: {
+ stroke: '#fff',
+ inset: 1,
+ radius: 10,
+ },
+};
+
+// 面积图设置渐变色
+const areaConfig = {
+ style: {
+ fill: 'linear-gradient(-90deg, white 0%, darkgreen 100%)',
+ },
+ line: {
+ style: {
+ stroke: 'darkgreen',
+ strokeWidth: 2,
+ },
+ tooltip: false,
+ },
+};
+
+// 地图设置图标
+const pinMapConfig = {
+ markerStyle: {
+ iconPath:
+ 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*JZf9T6psSzkAAAAAAAAAAAAADmJ7AQ/original',
+ },
+};
+
+const markdownContent = `
+\`\`\`vis-chart
+{
+ "type": "area",
+ "data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}]
+}
+\`\`\`
+
+\`\`\`vis-chart
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+}
+\`\`\`
+
+\`\`\`vis-chart
+ {
+ "type": "pin-map",
+ "data": [
+ { "label": "杨梅岭", "longitude": 120.118362, "latitude": 30.217175 },
+ { "label": "理安寺", "longitude": 120.112958, "latitude": 30.207319 },
+ { "label": "九溪烟树", "longitude": 120.11335, "latitude": 30.202395 },
+ { "label": "飞来峰", "longitude": 120.100549, "latitude": 30.236875 },
+ { "label": "灵隐寺", "longitude": 120.101406, "latitude": 30.240826 },
+ { "label": "天竺三寺", "longitude": 120.105337, "latitude": 30.236818 },
+ { "label": "杭州植物园", "longitude": 120.116979, "latitude": 30.252876 },
+ { "label": "杭州花圃", "longitude": 120.127654, "latitude": 30.245663 },
+ { "label": "苏堤", "longitude": 120.135764, "latitude": 30.251448 },
+ { "label": "虎跑公园", "longitude": 120.130095, "latitude": 30.207505 },
+ { "label": "玉皇飞云", "longitude": 120.145323, "latitude": 30.214993 },
+ { "label": "长桥公园", "longitude": 120.155057, "latitude": 30.232985 }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => {content} ;
+
+export default () => {
+ return (
+
+
+
+
+
+ );
+};
+```
+
+## 定制图表级主题
+
+```tsx | pure
+import { ConfigProvider } from '@antv/gpt-vis';
+
+export default () => {
+ return (
+
+ // ...
+
+ );
+};
+```
+
+```tsx
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ConfigProvider, GPTVis } from '@antv/gpt-vis';
+
+const markdownContent = `
+\`\`\`vis-chart
+{
+ "type": "area",
+ "data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}]
+}
+\`\`\`
+
+\`\`\`vis-chart
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+}
+\`\`\`
+
+\`\`\`vis-chart
+ {
+ "type": "pin-map",
+ "data": [
+ { "label": "杨梅岭", "longitude": 120.118362, "latitude": 30.217175 },
+ { "label": "理安寺", "longitude": 120.112958, "latitude": 30.207319 },
+ { "label": "九溪烟树", "longitude": 120.11335, "latitude": 30.202395 },
+ { "label": "飞来峰", "longitude": 120.100549, "latitude": 30.236875 },
+ { "label": "灵隐寺", "longitude": 120.101406, "latitude": 30.240826 },
+ { "label": "天竺三寺", "longitude": 120.105337, "latitude": 30.236818 },
+ { "label": "杭州植物园", "longitude": 120.116979, "latitude": 30.252876 },
+ { "label": "杭州花圃", "longitude": 120.127654, "latitude": 30.245663 },
+ { "label": "苏堤", "longitude": 120.135764, "latitude": 30.251448 },
+ { "label": "虎跑公园", "longitude": 120.130095, "latitude": 30.207505 },
+ { "label": "玉皇飞云", "longitude": 120.145323, "latitude": 30.214993 },
+ { "label": "长桥公园", "longitude": 120.155057, "latitude": 30.232985 }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => {content} ;
+
+export default () => {
+ return (
+
+
+
+
+
+ );
+};
+```
+
+更多用法详见 [ConfigProvider](/components/config-provider)
diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md
new file mode 100644
index 0000000..97b2b26
--- /dev/null
+++ b/docs/guide/quick-start.md
@@ -0,0 +1,61 @@
+---
+title: 快速上手
+nav: { title: '指南', order: 0 }
+toc: content
+order: 0
+---
+
+## ⏬ 安装
+
+```shell
+$ npm install @antv/gpt-vis --save
+```
+
+## 🌰 使用
+
+```tsx | pure
+import React from 'react';
+import { GPTVis } from '@antv/gpt-vis';
+
+const markdownContent = `
+# GPT-VIS \n\nComponents for GPTs, generative AI, and LLM projects. Not only UI Components.
+
+Here’s a visualization of Haidilao's food delivery revenue from 2013 to 2022. You can see a steady increase over the years, with notable *growth* particularly in recent years.
+
+\`\`\`vis-chart
+{ "type": "line","data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}] }
+\`\`\`
+`;
+
+export default () => {
+ return {markdownContent} ;
+};
+```
+
+## 🛠 定制渲染器
+
+业务定制 UI 场景,提供方便的扩展机制,支持标签和代码块扩展。
+
+```tsx | pure
+import { GPTVis, withDefaultChartCode, VisText } from '@antv/gpt-vis';
+
+const markdownContent = `
+\`\`\`my-ui
+my data
+\`\`\`
+
+本月 共产生决策数量 2,783 个,环比增长 15.2% 。高优先级决策 占比56.2% ,呈现稳定上升 趋势,预计下月 将突破3,000 大关。
+`;
+
+const customRenderers = {
+ 'my-ui': ({ children }) => {children}
,
+};
+const components = {
+ 'vis-text': VisText,
+ code: withDefaultChartCode({ languageRenderers: customRenderers }),
+};
+
+export default () => {
+ return {markdownContent} ;
+};
+```
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..606dd8d
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,11 @@
+---
+title: GPT-Vis - Components for GPTs
+hero:
+ title: GPT-Vis
+ description: Components for GPTs, generative AI, and LLM projects. Not only UI Components.
+ actions:
+ - text: Start
+ link: /guide/quick-start
+ - text: GitHub
+ link: https://github.com/antvis/GPT-Vis
+---
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..4a3637f
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,105 @@
+// @ts-check
+
+import eslint from '@eslint/js';
+import prettierConfig from 'eslint-config-prettier';
+import jestPlugin from 'eslint-plugin-jest';
+import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
+import reactPlugin from 'eslint-plugin-react';
+import reactHooks from 'eslint-plugin-react-hooks';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
+
+import { dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default tseslint.config(
+ // config ignores files, equal `.eslintignore`
+ {
+ ignores: [
+ '**/node_modules/**',
+ '**/dist/**',
+ '**/.cache/**',
+ '**/.history/**',
+ '**/.idea/**',
+ './*.?*.ts',
+
+ // Files of the build
+ 'dist/*',
+ ],
+ },
+
+ // extends configs
+ eslint.configs.recommended,
+
+ // basic config
+ {
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: {
+ ...globals.es2021,
+ ...globals.browser,
+ },
+ },
+ rules: {
+ 'no-unused-vars': 'warn',
+ 'no-unsafe-optional-chaining': 'warn',
+ 'no-constant-condition': 'off',
+ 'no-prototype-builtins': 'off',
+ 'no-case-declarations': 'off',
+ 'no-useless-catch': 'off',
+ 'prefer-rest-params': 'off',
+ },
+ },
+
+ // ts files linting
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [...tseslint.configs.recommended],
+ languageOptions: {
+ parserOptions: {
+ project: './tsconfig.eslint.json',
+ tsconfigRootDir: __dirname,
+ },
+ },
+ rules: {
+ '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
+ '@typescript-eslint/no-loss-of-precision': 0,
+ '@typescript-eslint/no-inferrable-types': 0,
+ '@typescript-eslint/ban-types': 0,
+ '@typescript-eslint/ban-ts-comment': 0,
+ '@typescript-eslint/no-empty-function': 0,
+ '@typescript-eslint/explicit-module-boundary-types': 0,
+ '@typescript-eslint/no-explicit-any': 0,
+ '@typescript-eslint/no-duplicate-enum-values': 0,
+ '@typescript-eslint/no-use-before-define': 0,
+ '@typescript-eslint/no-empty-object-type': 0,
+ },
+ },
+
+ // react tsx files linting
+ {
+ files: ['**/*.{tsx}'],
+ ...reactPlugin.configs.recommended,
+ ...reactHooks.configs.recommended,
+ plugins: {
+ 'jsx-a11y': jsxA11yPlugin,
+ },
+ rules: {},
+ },
+
+ //
+ // test jest file linting
+ //
+ {
+ files: ['test/**/*.spec.ts'],
+ ...jestPlugin.configs['flat/recommended'],
+ rules: {},
+ },
+
+ // after all eslint plugins configs to override, see https://github.com/prettier/eslint-config-prettier
+ // @ts-ignore
+ prettierConfig,
+);
diff --git a/jest.config.ts b/jest.config.ts
new file mode 100644
index 0000000..5442e15
--- /dev/null
+++ b/jest.config.ts
@@ -0,0 +1,18 @@
+import type { Config } from 'jest';
+
+const esm = ['internmap', 'd3-*'].join('|');
+
+const config: Config = {
+ preset: 'ts-jest',
+ testEnvironment: 'jsdom',
+ collectCoverage: false,
+ testRegex: '(/__tests__/.*\\.(test|spec))\\.ts$',
+ collectCoverageFrom: ['src/**/*.ts'],
+ // Transform esm to cjs.
+ transformIgnorePatterns: [
+ `/node_modules/(?!(${esm}))`,
+ `/node_modules/.pnpm/(?!(${esm}))`,
+ ],
+};
+
+export default config;
diff --git a/package.json b/package.json
index c709f5f..6c7a199 100644
--- a/package.json
+++ b/package.json
@@ -1,79 +1,132 @@
{
- "name": "GPT-Vis",
+ "name": "@antv/gpt-vis",
"version": "0.0.1",
- "description": "GPT-Vis",
- "repository": "git@github.com:eosphoros-ai/GPT-Vis.git",
+ "description": "Components for GPTs, generative AI, and LLM projects. Not only UI Components.",
+ "keywords": [
+ "antv",
+ "GPTs",
+ "LLM",
+ "vis",
+ "LUI"
+ ],
+ "homepage": "https://gpt-vis.antv.com",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/antvis/GPT-Vis"
+ },
"license": "MIT",
+ "author": "antvis",
"sideEffects": false,
- "module": "es/index.js",
- "types": "es/index.d.ts",
+ "type": "module",
+ "main": "dist/cjs/index.js",
+ "module": "dist/esm/index.js",
+ "typings": "dist/esm/index.d.ts",
+ "jsdelivr": "dist/umd/index.min.js",
+ "unpkg": "dist/umd/index.min.js",
"files": [
- "src",
- "lib",
- "es"
+ "dist",
+ "README.md",
+ "!dist/umd/report.html"
],
"scripts": {
- "build": "npm run build:lib && npm run build:es",
- "build:es": "rm -rf es && tsc --module esnext --outDir es",
- "build:lib": "rm -rf lib && tsc --module commonjs --outDir lib",
- "dev": "vite dev",
- "prepublishOnly": "npm run build"
+ "prepare": "husky",
+ "start": "pnpm dev",
+ "dev": "dumi dev",
+ "build": "father build",
+ "docs:build": "dumi build",
+ "docs:build-analyze": "ANALYZE=1 dumi build",
+ "docs:deploy": "gh-pages -d docs-dist",
+ "deploy": "pnpm docs:build && pnpm docs:deploy",
+ "check-deps": "father doctor",
+ "check-format": "prettier './**/*.{ts,tsx,js,jsx,json,md,css,less}' --check",
+ "format": "prettier './**/*.{ts,tsx,js,jsx,json,md,css,less}' --write",
+ "lint:ts": "eslint src/**",
+ "lint:ts-fix": "pnpm lint:ts --fix",
+ "test:unit": "jest --config ./jest.config.ts",
+ "test:size": "pnpm build && limit-size",
+ "changeset": "changeset add",
+ "publish-version": "changeset version && node scripts/sync-version",
+ "publish-package": "pnpm build && changeset publish"
},
- "husky": {
- "hooks": {
- "pre-commit": "lint-staged"
- }
+ "dependencies": {
+ "@ant-design/graphs": "^2.0.0-beta.4",
+ "@ant-design/icons": "^5.4.0",
+ "@ant-design/plots": "^2.2.5",
+ "@ant-design/x": "^1.0.0-alpha.12",
+ "@antv/l7": "^2.22.0",
+ "@antv/larkmap": "^1.5.1",
+ "@babel/runtime": "^7.18.0",
+ "lodash": "^4.17.21",
+ "react-markdown": "^9.0.1",
+ "rehype-raw": "^7.0.0",
+ "styled-components": "^6.0.7"
+ },
+ "devDependencies": {
+ "@changesets/changelog-github": "^0.5.0",
+ "@changesets/cli": "^2.27.9",
+ "@commitlint/cli": "^19.5.0",
+ "@commitlint/config-conventional": "^19.5.0",
+ "@types/jest": "^29.5.14",
+ "@types/lodash": "^4.17.13",
+ "@types/react": "^18.0.0",
+ "antd": "^5.0.0",
+ "dumi": "^2.4.13",
+ "eslint": "^9.14.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-jest": "^28.9.0",
+ "eslint-plugin-jsx-a11y": "^6.10.2",
+ "eslint-plugin-react": "^7.37.2",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "father": "^4.5.1",
+ "gh-pages": "^6.2.0",
+ "globals": "^15.12.0",
+ "husky": "^9.1.6",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "limit-size": "^0.1.4",
+ "lint-staged": "^15.2.10",
+ "npm-run-all": "^4.1.5",
+ "prettier": "^3.3.3",
+ "prettier-plugin-organize-imports": "^4.1.0",
+ "react": "^18.0.0",
+ "ts-jest": "^29.2.5",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.6.3",
+ "typescript-eslint": "^8.13.0",
+ "webpack-bundle-analyzer": "^4.10.2"
+ },
+ "peerDependencies": {
+ "antd": "^5.0.0",
+ "react": ">=16.9.0"
+ },
+ "engines": {
+ "node": ">=18",
+ "pnpm": ">=8"
},
"lint-staged": {
"*.{md,json}": [
- "prettier --write --no-error-on-unmatched-pattern"
+ "prettier --cache --write --no-error-on-unmatched-pattern"
],
"*.{css,less}": [
- "prettier --write"
+ "prettier --cache --write"
],
"*.{js,jsx}": [
"eslint --fix",
- "prettier --write"
+ "prettier --cache --write"
],
"*.{ts,tsx}": [
"eslint --fix",
- "prettier --parser=typescript --write"
+ "prettier --cache --write"
]
},
- "dependencies": {
- "@ant-design/icons": "^5.2.6",
- "@antv/ava": "^3.3.1",
- "@berryv/g2-react": "^0.1.0",
- "antd": "^5.12.2",
- "copy-to-clipboard": "^3.3.3",
- "lodash": "^4.17.21",
- "react-markdown": "^8.0.7",
- "react-syntax-highlighter": "^15.5.0",
- "rehype-raw": "6.1.1",
- "sql-formatter": "^14.0.0"
- },
- "devDependencies": {
- "@types/lodash": "^4.14.202",
- "@types/react": "^18.2.45",
- "@types/react-dom": "^18.2.18",
- "@types/react-syntax-highlighter": "^15.5.11",
- "@umijs/lint": "^4.0.89",
- "@umijs/plugins": "^4.0.89",
- "eslint": "^8.56.0",
- "husky": "^8.0.3",
- "lint-staged": "^15.2.0",
- "prettier": "^3.1.1",
- "prettier-plugin-organize-imports": "^3.2.4",
- "prettier-plugin-packagejson": "^2.4.7",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "tailwindcss": "^3.3.6",
- "typescript": "^5.4.5",
- "vite": "^5.2.13"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- },
- "authors": []
+ "limit-size": [
+ {
+ "path": "dist/umd/index.min.js",
+ "limit": "5 Mb"
+ }
+ ],
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org"
+ }
}
diff --git a/scripts/sync-version.js b/scripts/sync-version.js
new file mode 100644
index 0000000..7a4e8b1
--- /dev/null
+++ b/scripts/sync-version.js
@@ -0,0 +1,26 @@
+import { exec } from 'node:child_process';
+import { writeFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import pkg from '../package.json' with { type: 'json' };
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+writeFileSync(
+ join(__dirname, '..', 'src', 'version.ts'),
+ `export default '${pkg.version}'`,
+ 'utf8',
+);
+
+exec('git add .', (error, stdout, stderr) => {
+ if (error) {
+ console.log(`sync version error: ${error.message}`);
+ return;
+ }
+ if (stderr) {
+ console.log(`sync version stderr: ${stderr}`);
+ return;
+ }
+ console.log(`sync version success.`);
+});
diff --git a/src/Area/demos/common.tsx b/src/Area/demos/common.tsx
new file mode 100644
index 0000000..47a8a0e
--- /dev/null
+++ b/src/Area/demos/common.tsx
@@ -0,0 +1,20 @@
+import { Area } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ { time: '1991', value: 3 },
+ { time: '1992', value: 4 },
+ { time: '1993', value: 3.5 },
+ { time: '1994', value: 5 },
+ { time: '1995', value: 4.9 },
+ { time: '1996', value: 6 },
+ { time: '1997', value: 7 },
+ { time: '1998', value: 9 },
+ { time: '1999', value: 13 },
+];
+
+export default () => {
+ return (
+
+ );
+};
diff --git a/src/Area/demos/markdown.tsx b/src/Area/demos/markdown.tsx
new file mode 100644
index 0000000..66b109c
--- /dev/null
+++ b/src/Area/demos/markdown.tsx
@@ -0,0 +1,55 @@
+import type { BubbleProps } from '@ant-design/x';
+import { Bubble } from '@ant-design/x';
+import { Area, ChartType, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个面积图
+
+\`\`\`vis-chart
+{
+ "type": "area",
+ "data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}],
+ "axisXTitle": "year",
+ "axisYTitle": "GDP"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ padding: 20,
+ background: '#f7f7f7',
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Area]: Area },
+ style: { width: 350 },
+});
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Area/demos/stack.tsx b/src/Area/demos/stack.tsx
new file mode 100644
index 0000000..c71ba1f
--- /dev/null
+++ b/src/Area/demos/stack.tsx
@@ -0,0 +1,117 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { Area, ChartType, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个面积图
+
+\`\`\`vis-chart
+{
+ "type": "area",
+ "data": [
+ {
+ "time": "1974",
+ "value": 107,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1974",
+ "value": 208,
+ "group": "Renewables"
+ },
+ {
+ "time": "1974",
+ "value": 356,
+ "group": "Fossil fuels"
+ },
+ {
+ "time": "1975",
+ "value": 173,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1975",
+ "value": 415,
+ "group": "Renewables"
+ },
+ {
+ "time": "1975",
+ "value": 364,
+ "group": "Fossil fuels"
+ },
+ {
+ "time": "1976",
+ "value": 117,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1976",
+ "value": 220,
+ "group": "Renewables"
+ },
+ {
+ "time": "1976",
+ "value": 373,
+ "group": "Fossil fuels"
+ },
+ {
+ "time": "1977",
+ "value": 122,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1977",
+ "value": 225,
+ "group": "Renewables"
+ },
+ {
+ "time": "1977",
+ "value": 382,
+ "group": "Fossil fuels"
+ }
+ ],
+ "stack": true,
+ "axisXTitle": "year",
+ "axisYTitle": "value"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Area]: Area },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Area/index.md b/src/Area/index.md
new file mode 100644
index 0000000..1dc975e
--- /dev/null
+++ b/src/Area/index.md
@@ -0,0 +1,53 @@
+---
+order: 4
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+nav: { title: '组件', order: 1 }
+---
+
+# Area 面积图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+堆叠面积图
+
+## Spec
+
+```json
+{
+ "type": "area",
+ "data": [
+ { "time": 2018, "value": 91.9 },
+ { "time": 2019, "value": 99.1 },
+ { "time": 2020, "value": 101.6 },
+ { "time": 2021, "value": 114.4 },
+ { "time": 2022, "value": 121 }
+ ]
+}
+```
+
+## API
+
+### AreaProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | -------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | AreaDataItem[] | 是 | - | 数据 |
+| stack | boolean | 否 | - | 是否开启堆叠,开启堆叠面积图需数据中含有 group 字段 |
+| title | string | 否 | - | 图表的标题 |
+| axisXTitle | string | 否 | - | x 轴的标题 |
+| axisYTitle | string | 否 | - | y 轴的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### AreaDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------ | -------- | ------ | -------------- |
+| time | string | 是 | - | 数据的时序名称 |
+| value | number | 是 | - | 数据的值 |
+| group | string | 否 | - | 数据分组名称 |
diff --git a/src/Area/index.tsx b/src/Area/index.tsx
new file mode 100644
index 0000000..c9d9952
--- /dev/null
+++ b/src/Area/index.tsx
@@ -0,0 +1,43 @@
+import type { AreaConfig } from '@ant-design/plots';
+import { Area as ADCArea } from '@ant-design/plots';
+import { get } from 'lodash';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+type AreaDataItem = {
+ time: string | number;
+ value: number;
+ [key: string]: string | number;
+};
+
+export type AreaProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: AreaConfig): AreaConfig => {
+ const { data, xField = 'time', yField = 'value' } = props;
+ const hasGroupField = get(data, '[0].group') !== undefined;
+ const axisYTitle = get(props, 'axis.y.title');
+ const defalutStyle = hasGroupField ? {} : { opacity: 0.6 };
+
+ return {
+ xField,
+ yField,
+ style: defalutStyle,
+ colorField: hasGroupField ? 'group' : undefined,
+ tooltip: (d: Record) => {
+ const tooltipName = axisYTitle || d[xField as string];
+ return {
+ name: tooltipName,
+ value: d[yField as string],
+ };
+ },
+ };
+};
+
+const Area = (props: AreaProps) => {
+ const config = usePlotConfig('Area', defaultConfig, props);
+
+ return ;
+};
+
+export default Area;
diff --git a/src/Bar/demos/common.tsx b/src/Bar/demos/common.tsx
new file mode 100644
index 0000000..fa921f7
--- /dev/null
+++ b/src/Bar/demos/common.tsx
@@ -0,0 +1,229 @@
+import { Bar } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ {
+ 城市: '七台河',
+ 销售额: 52827.32,
+ },
+ {
+ 城市: '万县',
+ 销售额: 16921.576,
+ },
+ {
+ 城市: '三亚',
+ 销售额: 22698.396,
+ },
+ {
+ 城市: '三岔子',
+ 销售额: 3262.98,
+ },
+ {
+ 城市: '上海',
+ 销售额: 182450.567,
+ },
+ {
+ 城市: '上虞',
+ 销售额: 10672.872,
+ },
+ {
+ 城市: '东丰',
+ 销售额: 1785.84,
+ },
+ {
+ 城市: '东台',
+ 销售额: 2789.892,
+ },
+ {
+ 城市: '东宁',
+ 销售额: 2706.2,
+ },
+ {
+ 城市: '东村',
+ 销售额: 13692.14,
+ },
+ {
+ 城市: '东海',
+ 销售额: 4508.28,
+ },
+ {
+ 城市: '东胜',
+ 销售额: 12766.068,
+ },
+ {
+ 城市: '东莞',
+ 销售额: 10165.89,
+ },
+ {
+ 城市: '东营',
+ 销售额: 17153.92,
+ },
+ {
+ 城市: '中枢',
+ 销售额: 1050.42,
+ },
+ {
+ 城市: '丰县',
+ 销售额: 10309.516,
+ },
+ {
+ 城市: '丰镇',
+ 销售额: 3507.336,
+ },
+ {
+ 城市: '临水',
+ 销售额: 21443.1,
+ },
+ {
+ 城市: '临江',
+ 销售额: 36496.74,
+ },
+ {
+ 城市: '临汾',
+ 销售额: 26205.48,
+ },
+ {
+ 城市: '临沂',
+ 销售额: 97200.74,
+ },
+ {
+ 城市: '临海',
+ 销售额: 7071.456,
+ },
+ {
+ 城市: '临清',
+ 销售额: 38676.12,
+ },
+ {
+ 城市: '丹东',
+ 销售额: 45447.612,
+ },
+ {
+ 城市: '丹江口',
+ 销售额: 4879.616,
+ },
+ {
+ 城市: '丽水',
+ 销售额: 3983.616,
+ },
+ {
+ 城市: '义乌',
+ 销售额: 34511.624,
+ },
+ {
+ 城市: '义马',
+ 销售额: 1144.024,
+ },
+ {
+ 城市: '乌海',
+ 销售额: 16096.64,
+ },
+ {
+ 城市: '乌达',
+ 销售额: 3474.66,
+ },
+ {
+ 城市: '乐城',
+ 销售额: 3241,
+ },
+ {
+ 城市: '乐山',
+ 销售额: 12561.892,
+ },
+ {
+ 城市: '九台',
+ 销售额: 32535.944,
+ },
+ {
+ 城市: '九江',
+ 销售额: 29890.7,
+ },
+ {
+ 城市: '二道江',
+ 销售额: 4461.24,
+ },
+ {
+ 城市: '云浮',
+ 销售额: 6351.212,
+ },
+ {
+ 城市: '云阳',
+ 销售额: 24699.64,
+ },
+ {
+ 城市: '五常',
+ 销售额: 3771.46,
+ },
+ {
+ 城市: '亳州',
+ 销售额: 16961.14,
+ },
+ {
+ 城市: '仙居',
+ 销售额: 6868.512,
+ },
+ {
+ 城市: '仙桃',
+ 销售额: 16600.164,
+ },
+ {
+ 城市: '仪征',
+ 销售额: 8566.628,
+ },
+ {
+ 城市: '任丘',
+ 销售额: 12106.78,
+ },
+ {
+ 城市: '余下',
+ 销售额: 103.04,
+ },
+ {
+ 城市: '余姚',
+ 销售额: 12621.84,
+ },
+ {
+ 城市: '佛山',
+ 销售额: 7500.388,
+ },
+ {
+ 城市: '佳木斯',
+ 销售额: 63263.34,
+ },
+ {
+ 城市: '依兰',
+ 销售额: 26874.82,
+ },
+ {
+ 城市: '保定',
+ 销售额: 124133.1,
+ },
+ {
+ 城市: '信宜',
+ 销售额: 4771.06,
+ },
+ {
+ 城市: '信阳',
+ 销售额: 38849.412,
+ },
+ {
+ 城市: '兖州',
+ 销售额: 28648.2,
+ },
+ {
+ 城市: '公主岭',
+ 销售额: 21210.756,
+ },
+ {
+ 城市: '六合',
+ 销售额: 5775.98,
+ },
+ {
+ 城市: '兰州',
+ 销售额: 42543.144,
+ },
+];
+
+export default () => {
+ return ;
+};
diff --git a/src/Bar/demos/group.tsx b/src/Bar/demos/group.tsx
new file mode 100644
index 0000000..9dc2be7
--- /dev/null
+++ b/src/Bar/demos/group.tsx
@@ -0,0 +1,72 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { Bar, ChartType, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个条形图
+
+\`\`\`vis-chart
+{
+ "type": "bar",
+ "data": [
+ { "group": "London", "category": "Jan.", "value": 18.9 },
+ { "group": "London", "category": "Feb.", "value": 28.8 },
+ { "group": "London", "category": "Mar.", "value": 39.3 },
+ { "group": "London", "category": "Apr.", "value": 81.4 },
+ { "group": "London", "category": "May.", "value": 47 },
+ { "group": "London", "category": "Jun.", "value": 20.3 },
+ { "group": "London", "category": "Jul.", "value": 24 },
+ { "group": "London", "category": "Aug.", "value": 35.6 },
+ { "group": "Berlin", "category": "Jan.", "value": 12.4 },
+ { "group": "Berlin", "category": "Feb.", "value": 23.2 },
+ { "group": "Berlin", "category": "Mar.", "value": 34.5 },
+ { "group": "Berlin", "category": "Apr.", "value": 99.7 },
+ { "group": "Berlin", "category": "May.", "value": 52.6 },
+ { "group": "Berlin", "category": "Jun.", "value": 35.5 },
+ { "group": "Berlin", "category": "Jul.", "value": 37.4 },
+ { "group": "Berlin", "category": "Aug.", "value": 42.4 }
+ ],
+ "group": true,
+ "axisXTitle": "month",
+ "axisYTitle": "value"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Bar]: Bar },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Bar/demos/markdown.tsx b/src/Bar/demos/markdown.tsx
new file mode 100644
index 0000000..e24da85
--- /dev/null
+++ b/src/Bar/demos/markdown.tsx
@@ -0,0 +1,63 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { Bar, ChartType, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个条形图
+
+\`\`\`vis-chart
+{
+ "type": "bar",
+ "data": [
+ { "category": "1951 年", "value": 38 },
+ { "category": "1952 年", "value": 52 },
+ { "category": "1956 年", "value": 61 },
+ { "category": "1957 年", "value": 145 },
+ { "category": "1958 年", "value": 48 },
+ { "category": "1959 年", "value": 38 },
+ { "category": "1960 年", "value": 38 },
+ { "category": "1962 年", "value": 38 }
+ ],
+ "axisXTitle": "year",
+ "axisYTitle": "sales"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Bar]: Bar },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Bar/demos/stack.tsx b/src/Bar/demos/stack.tsx
new file mode 100644
index 0000000..9255d9a
--- /dev/null
+++ b/src/Bar/demos/stack.tsx
@@ -0,0 +1,71 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { Bar, ChartType, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个条形图
+
+\`\`\`vis-chart
+{
+ "type": "bar",
+ "data": [
+ { "group": "London", "category": "Jan.", "value": 18.9 },
+ { "group": "London", "category": "Feb.", "value": 28.8 },
+ { "group": "London", "category": "Mar.", "value": 39.3 },
+ { "group": "London", "category": "Apr.", "value": 81.4 },
+ { "group": "London", "category": "May.", "value": 47 },
+ { "group": "London", "category": "Jun.", "value": 20.3 },
+ { "group": "London", "category": "Jul.", "value": 24 },
+ { "group": "London", "category": "Aug.", "value": 35.6 },
+ { "group": "Berlin", "category": "Jan.", "value": 12.4 },
+ { "group": "Berlin", "category": "Feb.", "value": 23.2 },
+ { "group": "Berlin", "category": "Mar.", "value": 34.5 },
+ { "group": "Berlin", "category": "Apr.", "value": 99.7 },
+ { "group": "Berlin", "category": "May.", "value": 52.6 },
+ { "group": "Berlin", "category": "Jun.", "value": 35.5 },
+ { "group": "Berlin", "category": "Jul.", "value": 37.4 },
+ { "group": "Berlin", "category": "Aug.", "value": 42.4 }
+ ],
+ "stack": true,
+ "axisXTitle": "month",
+ "axisYTitle": "value"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Bar]: Bar },
+});
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Bar/index.md b/src/Bar/index.md
new file mode 100644
index 0000000..49a65e4
--- /dev/null
+++ b/src/Bar/index.md
@@ -0,0 +1,53 @@
+---
+order: 4
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# Bar 条形图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+
+分组条形图
+堆叠条形图
+
+## Spec
+
+```json
+{
+ "type": "bar",
+ "data": [
+ { "category": "<分类一>", "value": <数值> },
+ { "category": "<分类二>", "value": <数值> },
+ { "category": "<分类三>", "value": <数值> }
+ ]
+}
+```
+
+## API
+
+### BarProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | ------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | BarDataItem[] | 是 | - | 数据 |
+| title | string | 否 | - | 图表的标题 |
+| group | boolean | 否 | - | 是否开启分组,开启分组条形图需数据中含有 group 字段 |
+| stack | boolean | 否 | - | 是否开启堆叠,开启堆叠条形图需数据中含有 group 字段 |
+| axisXTitle | string | 否 | - | x 轴的标题 |
+| axisYTitle | string | 否 | - | y 轴的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### BarDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| -------- | ------ | -------- | ------ | ------------ |
+| category | string | 是 | - | 数据分类名称 |
+| value | number | 是 | - | 数据分类值 |
+| group | number | 否 | - | 数据分组名称 |
diff --git a/src/Bar/index.tsx b/src/Bar/index.tsx
new file mode 100644
index 0000000..399eacf
--- /dev/null
+++ b/src/Bar/index.tsx
@@ -0,0 +1,46 @@
+import type { BarConfig } from '@ant-design/plots';
+import { Bar as ADCBar } from '@ant-design/plots';
+import { get } from 'lodash';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+type BarDataItem = {
+ category: string;
+ value: number;
+ [key: string]: string | number;
+};
+
+export type BarProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: BarConfig): BarConfig => {
+ const { data, xField = 'category', yField = 'value' } = props;
+ const hasGroupField = get(data, '[0].group') !== undefined;
+ const axisYTitle = get(props, 'axis.y.title');
+
+ return {
+ xField,
+ yField,
+ colorField: hasGroupField ? 'group' : undefined,
+ tooltip: (d) => {
+ const tooltipName = axisYTitle || d[xField as string];
+ return {
+ name: tooltipName,
+ value: d[yField as string],
+ };
+ },
+ style: {
+ // 圆角样式
+ radiusTopLeft: 5,
+ radiusTopRight: 5,
+ },
+ };
+};
+
+const Bar = (props: BarProps) => {
+ const config = usePlotConfig('Bar', defaultConfig, props);
+
+ return ;
+};
+
+export default Bar;
diff --git a/src/ChartCodeRender/Loading.tsx b/src/ChartCodeRender/Loading.tsx
new file mode 100644
index 0000000..d951e7f
--- /dev/null
+++ b/src/ChartCodeRender/Loading.tsx
@@ -0,0 +1,30 @@
+import { LoadingOutlined } from '@ant-design/icons';
+import React from 'react';
+import styled from 'styled-components';
+
+const StyledLoading = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ height: 300px;
+ background-image: linear-gradient(135deg, #e3f3ff 0%, #f1eeff 100%);
+ color: rgba(0, 0, 0, 88%);
+
+ &-icon {
+ margin-bottom: 6px;
+ }
+`;
+
+const Loading = () => {
+ return (
+
+
+
+
+ 数据生成中
+
+ );
+};
+
+export default Loading;
diff --git a/src/ChartCodeRender/VisChart.tsx b/src/ChartCodeRender/VisChart.tsx
new file mode 100644
index 0000000..d75f968
--- /dev/null
+++ b/src/ChartCodeRender/VisChart.tsx
@@ -0,0 +1,77 @@
+import React, { memo, useRef, useState } from 'react';
+import styled, { createGlobalStyle } from 'styled-components';
+import Loading from './Loading';
+import type { ChartComponents, ChartJson } from './type';
+
+const StyledGPTVis = styled.div`
+ min-width: 300px;
+ height: 300px;
+ max-width: 100%;
+`;
+const GlobalStyles = createGlobalStyle`
+ pre:has(.gpt-vis) {
+ overflow: hidden;
+ }
+`;
+
+type RenderVisChartProps = {
+ content: string;
+ components: ChartComponents;
+ debug?: boolean;
+ loadingTimeout: number;
+ style?: React.CSSProperties;
+};
+
+export const RenderVisChart: React.FC = memo(
+ ({ style, content, components, debug, loadingTimeout }) => {
+ const timeoutRef = useRef();
+ const [loading, setLoading] = useState(true);
+ let chartJson: ChartJson;
+
+ try {
+ chartJson = JSON.parse(content);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (e) {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ if (debug) {
+ console.warn('GPT-Vis withChartCode parse content timeout');
+ }
+ }
+ timeoutRef.current = setTimeout(() => {
+ setLoading(false);
+ }, loadingTimeout);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return Chart generation timeout.
;
+ }
+
+ const { type, ...chartProps } = chartJson;
+ const ChartComponent = components[type];
+
+ // debug mode print chartJson
+ if (debug) {
+ console.log('GPT-Vis withChartCode get chartJson parse from vis-chart code block', chartJson);
+ }
+
+ // If the chart type is not supported, display an error message
+ if (!ChartComponent) {
+ return {`Chart type "${type}" is not supported.`}
;
+ }
+
+ // Render the supported chart component with data
+ return (
+
+
+
+
+ );
+ },
+);
diff --git a/src/ChartCodeRender/demos/common.tsx b/src/ChartCodeRender/demos/common.tsx
new file mode 100644
index 0000000..a68d42f
--- /dev/null
+++ b/src/ChartCodeRender/demos/common.tsx
@@ -0,0 +1,35 @@
+import { ChartType, Column, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+\`\`\`vis-chart
+{
+ "type": "column",
+ "data": [
+ { "category": "第一产业", "value": 7200.0 },
+ { "category": "第二产业", "value": 36600.0 },
+ { "category": "第三产业" ,"value": 41000.0 },
+ { "category": "第四产业" ,"value": 21000.0 },
+ { "category": "其他产业" ,"value": 81000.0 }
+ ]
+}
+\`\`\`
+`;
+
+// 自定义代码块渲染组件,NOTE: withChartCode 不要直接放入函数内部,避免重复渲染抖动问题!!!
+const CodeComponent = withChartCode({
+ components: { [ChartType.Column]: Column },
+ debug: true,
+});
+
+export default () => {
+ return (
+
+ {markdownContent}
+
+ );
+};
diff --git a/src/ChartCodeRender/demos/default.tsx b/src/ChartCodeRender/demos/default.tsx
new file mode 100644
index 0000000..b3d543d
--- /dev/null
+++ b/src/ChartCodeRender/demos/default.tsx
@@ -0,0 +1,42 @@
+import { GPTVis, withDefaultChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const commonString = `
+当然可以!在 JavaScript 中,你可以使用加法运算符 \`+\` 来实现两个数字的相加。以下是一个简单的函数,它接受两个参数 \`a\` 和 \`b\`,并返回它们的和:
+
+\`\`\`javascript\nfunction add(a, b) {\n return a + b;\n}\n\n// 示例用法\nconst result = add(3, 4);\nconsole.log(result); // 输出:7\n\`\`\`\n\n在这个示例中,我们定义了一个名为 \`add\` 的函数,它接受两个参数 \`a\` 和 \`b\`。函数的主体只有一行代码,使用加法运算符 \`+\` 将 \`a\` 和 \`b\` 相加,并返回结果。\n\n然后,我们调用
+`;
+
+const markdownContent = `
+\`\`\`vis-chart
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+}
+\`\`\`
+`;
+
+// 自定义代码块渲染组件,NOTE: withDefaultChartCode 不要直接放入函数内部,避免重复渲染抖动问题!!!
+const CodeComponent = withDefaultChartCode({
+ debug: true,
+});
+
+export default () => (
+
+ {commonString}
+
+ {markdownContent}
+
+
+);
diff --git a/src/ChartCodeRender/demos/extra.tsx b/src/ChartCodeRender/demos/extra.tsx
new file mode 100644
index 0000000..38a1ca1
--- /dev/null
+++ b/src/ChartCodeRender/demos/extra.tsx
@@ -0,0 +1,49 @@
+import type { CodeBlockComponent } from '@antv/gpt-vis';
+import { GPTVis, withDefaultChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+/**
+ * 自定义代码块渲染器
+ */
+const MyUIRenderer: CodeBlockComponent = ({ children }) => {
+ return {children}
;
+};
+
+const markdownContent = `
+\`\`\`my-ui
+my data ...
+\`\`\`
+
+\`\`\`vis-chart
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+}
+\`\`\`
+`;
+
+// 自定义代码块渲染组件,NOTE: withDefaultChartCode 不要直接放入函数内部,避免重复渲染抖动问题!!!
+const CodeComponent = withDefaultChartCode({
+ languageRenderers: {
+ 'my-ui': MyUIRenderer,
+ },
+});
+
+export default () => (
+
+
+ {markdownContent}
+
+
+);
diff --git a/src/ChartCodeRender/demos/stream.tsx b/src/ChartCodeRender/demos/stream.tsx
new file mode 100644
index 0000000..c125197
--- /dev/null
+++ b/src/ChartCodeRender/demos/stream.tsx
@@ -0,0 +1,110 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, Column, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React, { useEffect, useRef, useState } from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个柱状图
+
+\`\`\`vis-chart
+{
+ "type": "column",
+ "data": [
+ { "category": "第一产业", "value": 7200.0 },
+ { "category": "第二产业", "value": 36600.0 },
+ { "category": "第三产业" ,"value": 41000.0 }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+// 自定义代码块渲染组件,NOTE: withChartCode 不要直接放入函数内部,避免重复渲染抖动问题!!!
+const CodeComponent = withChartCode({
+ components: { [ChartType.Column]: Column },
+ loadingTimeout: 3000,
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+const useStreamText = () => {
+ const [text, setText] = useState('');
+ const nowTextRef = useRef('');
+ const timerRef = useRef(null);
+
+ /** 模拟流式输出markdownContent */
+ const streamOutput = () => {
+ timerRef.current = setInterval(() => {
+ const step = parseInt((Math.random() * 10).toString(), 20);
+ const nowText =
+ nowTextRef.current +
+ markdownContent.substring(nowTextRef.current.length, nowTextRef.current.length + step);
+ nowTextRef.current = nowText;
+ setText(nowText);
+ if (text.length === markdownContent.length - 1) {
+ clearTimeout(timerRef.current);
+ }
+ }, 200);
+ };
+
+ const restart = () => {
+ if (timerRef.current) clearTimeout(timerRef.current);
+ timerRef.current = null;
+ nowTextRef.current = '';
+ setText('');
+ streamOutput();
+ };
+
+ return [text, restart] as const;
+};
+
+export default () => {
+ const [text, restart] = useStreamText();
+
+ useEffect(() => {
+ restart();
+ }, []);
+
+ return (
+
+
+ 重新演示
+
+
+
+
+
+
+ );
+};
diff --git a/src/ChartCodeRender/index.md b/src/ChartCodeRender/index.md
new file mode 100644
index 0000000..1929c59
--- /dev/null
+++ b/src/ChartCodeRender/index.md
@@ -0,0 +1,46 @@
+---
+order: 2
+group:
+ order: 10
+ title: 其他
+---
+
+# withChartCode 拓展代码块渲染
+
+自定义拓展 Markdown 代码块渲染,将代码块自定义可视化。
+
+:::warning
+这是一条警告信息
+`withChartCode`、`withDefaultChartCode` 方法不要直接放入函数内部,避免重复渲染造成抖动问题!!!如需放入函数内部,用 `useMemo` 缓存一下。
+:::
+
+## 使用 withChartCode
+
+
+
+## 使用 withDefaultChartCode
+
+`withDefaultChartCode`包含了[默认的图表](https://github.com/antvis/GPT-Vis/tree/main/src/export.ts#L76),接入简单
+
+
+
+## 在流式输出中使用
+
+
+
+## 拓展其他 code 的自定义渲染
+
+
+
+## API
+
+### WithChartCodeOptions
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----------------- | --------------------- | -------- | ------- | -------------------------------------- |
+| components | `ChartComponents` | 否 | - | 要额外加载的图表组件 |
+| languageRenderers | `LanguageRenderers` | 否 | - | 自定义其它语言代码块渲染器 |
+| defaultRenderer | `CodeRenderer` | 否 | - | 默认的代码渲染器 |
+| debug | `boolean` | 否 | `false` | 打开调试日志 |
+| loadingTimeout | `number` | 否 | - | 设置 loading 动画的超时时间,默认为 5s |
+| style | `React.CSSProperties` | 否 | - | 图表样式,配置容器样式 |
diff --git a/src/ChartCodeRender/index.tsx b/src/ChartCodeRender/index.tsx
new file mode 100644
index 0000000..ea3fb23
--- /dev/null
+++ b/src/ChartCodeRender/index.tsx
@@ -0,0 +1,78 @@
+import { get } from 'lodash';
+import React from 'react';
+import { DEFAULT_CHART_COMPONENTS } from '../export';
+import type { CodeBlockComponent, WithChartCodeOptions } from './type';
+import { RenderVisChart } from './VisChart';
+
+const RenderDefaultCode: CodeBlockComponent = (props) => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { children, className = '', node, ...rest } = props;
+ return (
+
+ {children}
+
+ );
+};
+
+const withCodeBlock = (options: WithChartCodeOptions): CodeBlockComponent => {
+ // Render code block component
+ return function CodeBlock(props) {
+ const { children, className = '' } = props;
+ const content = String(children).trim();
+ const isVisChart = className.includes('language-vis-chart');
+ const {
+ components,
+ languageRenderers,
+ defaultRenderer: DefaultRenderer,
+ debug,
+ loadingTimeout = 5000,
+ style,
+ } = options;
+
+ // If the code block is a VisChart, render the corresponding chart component
+ if (isVisChart) {
+ return (
+
+ );
+ }
+
+ // If the code block math extraRenderer languageName, the corresponding extra languageRenderers component
+ const languageName = className.match(/language-(.*)/)?.[1] || '';
+ const extraLanguageRenderers = languageRenderers;
+ const ExtraRendererComponent = extraLanguageRenderers && extraLanguageRenderers[languageName];
+ if (ExtraRendererComponent) {
+ return ;
+ }
+
+ // If the code block is not a VisChart, render plain code
+ return DefaultRenderer ? : ;
+ };
+};
+
+// Create a higher-order component (HOC) with chart code
+export const withChartCode = (options: WithChartCodeOptions): CodeBlockComponent => {
+ return withCodeBlock(options);
+};
+
+/**
+ * Includes built-in chart components such as line charts, pie charts, etc.
+ * @param componentsArray
+ * @returns
+ */
+export const withDefaultChartCode = (
+ options?: Partial,
+): CodeBlockComponent => {
+ return withChartCode({
+ ...options,
+ components: {
+ ...DEFAULT_CHART_COMPONENTS,
+ ...get(options, 'components', {}),
+ },
+ });
+};
diff --git a/src/ChartCodeRender/type.ts b/src/ChartCodeRender/type.ts
new file mode 100644
index 0000000..74e75bc
--- /dev/null
+++ b/src/ChartCodeRender/type.ts
@@ -0,0 +1,59 @@
+import type { FC } from 'react';
+import type { Components, ExtraProps } from 'react-markdown';
+
+export type WithChartCodeOptions = {
+ /**
+ * 要额外加载的图表组件
+ */
+ components: ChartComponents;
+ /**
+ * 自定义其它语言代码块渲染器
+ */
+ languageRenderers?: LanguageRenderers;
+ /**
+ * 默认的代码渲染器
+ */
+ defaultRenderer?: CodeRenderer;
+ /**
+ * 打开调试日志
+ */
+ debug?: boolean;
+ /**
+ * 设置loading动画的超时时间,默认为 5s
+ */
+ loadingTimeout?: number;
+ /**
+ * 图表样式,配置容器样式
+ */
+ style?: React.CSSProperties;
+};
+/**
+ * 图表渲染数据接口,后续拓展,这里只是写个示例
+ */
+export interface ChartJson {
+ type: string;
+ data: any;
+}
+
+/**
+ * 图表组件字典
+ */
+export interface ChartComponents {
+ [key: string]: FC;
+}
+
+/**
+ * 代码块渲染器接口
+ */
+export type CodeBlockComponent = Components['code'];
+
+type CodeRenderer = FC<
+ React.ClassAttributes & React.HTMLAttributes & ExtraProps
+>;
+
+/**
+ * 自定义其它语言代码块渲染器
+ */
+interface LanguageRenderers {
+ [key: string]: CodeRenderer;
+}
diff --git a/src/Column/demos/common.tsx b/src/Column/demos/common.tsx
new file mode 100644
index 0000000..b20ce25
--- /dev/null
+++ b/src/Column/demos/common.tsx
@@ -0,0 +1,26 @@
+import { Column } from '@antv/gpt-vis';
+import React from 'react';
+
+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 },
+];
+
+export default () => {
+ return (
+
+ );
+};
diff --git a/src/Column/demos/group.tsx b/src/Column/demos/group.tsx
new file mode 100644
index 0000000..74a2cff
--- /dev/null
+++ b/src/Column/demos/group.tsx
@@ -0,0 +1,66 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, Column, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+以下是为你绘制的一个柱状图
+
+\`\`\`vis-chart
+{
+ "type": "column",
+ "data": [
+ { "category": "北京", "value": 825.6, "group": "油车" },
+ { "category": "北京", "value": 60.2, "group": "新能源汽车" },
+ { "category": "上海", "value": 450, "group": "油车" },
+ { "category": "上海", "value": 95, "group": "新能源汽车" },
+ { "category": "深圳", "value": 506, "group": "油车" },
+ { "category": "深圳", "value": 76.7, "group": "新能源汽车" },
+ { "category": "广州", "value": 976.6, "group": "油车" },
+ { "category": "广州", "value": 97.2, "group": "新能源汽车" },
+ { "category": "杭州", "value": 651.2, "group": "油车" },
+ { "category": "杭州", "value": 62, "group": "新能源汽车" }
+ ],
+ "group": true,
+ "axisXTitle": "城市",
+ "axisYTitle": "售量"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Column]: Column },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Column/demos/markdown.tsx b/src/Column/demos/markdown.tsx
new file mode 100644
index 0000000..43b497f
--- /dev/null
+++ b/src/Column/demos/markdown.tsx
@@ -0,0 +1,55 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, Column, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个柱状图
+
+\`\`\`vis-chart
+{
+ "type": "column",
+ "data": [{"category":2013,"value":59.3},{"category":2014,"value":64.4},{"category":2015,"value":68.9},{"category":2016,"value":74.4},{"category":2017,"value":82.7},{"category":2018,"value":91.9},{"category":2019,"value":99.1},{"category":2020,"value":101.6},{"category":2021,"value":114.4},{"category":2022,"value":121}],
+ "axisXTitle": "year",
+ "axisYTitle": "GDP"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Column]: Column },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Column/demos/stack.tsx b/src/Column/demos/stack.tsx
new file mode 100644
index 0000000..ec02f0c
--- /dev/null
+++ b/src/Column/demos/stack.tsx
@@ -0,0 +1,66 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, Column, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+以下是为你绘制的一个柱状图
+
+\`\`\`vis-chart
+{
+ "type": "column",
+ "data": [
+ { "category": "北京", "value": 825.6, "group": "油车" },
+ { "category": "北京", "value": 60.2, "group": "新能源汽车" },
+ { "category": "上海", "value": 450, "group": "油车" },
+ { "category": "上海", "value": 95, "group": "新能源汽车" },
+ { "category": "深圳", "value": 506, "group": "油车" },
+ { "category": "深圳", "value": 76.7, "group": "新能源汽车" },
+ { "category": "广州", "value": 976.6, "group": "油车" },
+ { "category": "广州", "value": 97.2, "group": "新能源汽车" },
+ { "category": "杭州", "value": 651.2, "group": "油车" },
+ { "category": "杭州", "value": 62, "group": "新能源汽车" }
+ ],
+ "stack": true,
+ "axisXTitle": "城市",
+ "axisYTitle": "售量"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Column]: Column },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Column/index.md b/src/Column/index.md
new file mode 100644
index 0000000..5c6cfb8
--- /dev/null
+++ b/src/Column/index.md
@@ -0,0 +1,53 @@
+---
+order: 2
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# Column 柱形图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+
+分组柱形图
+堆叠柱形图
+
+## Spec
+
+```json
+{
+ "type": "column",
+ "data": [
+ { "category": "分类一", "value": 91.9 },
+ { "category": "分类二", "value": 99.1 },
+ { "category": "分类三", "value": 101.6 }
+ ]
+}
+```
+
+## API
+
+### ColumnProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | ---------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | ColumnDataItem[] | 是 | - | 数据 |
+| group | boolean | 否 | - | 是否开启分组,开启分组柱形图需数据中含有 group 字段 |
+| stack | boolean | 否 | - | 是否开启堆叠,开启堆叠柱形图需数据中含有 group 字段 |
+| title | string | 否 | - | 图表的标题 |
+| axisXTitle | string | 否 | - | x 轴的标题 |
+| axisYTitle | string | 否 | - | y 轴的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### ColumnDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| -------- | ------ | -------- | ------ | ------------ |
+| category | string | 是 | - | 数据分类名称 |
+| value | number | 是 | - | 数据分类值 |
+| group | number | 否 | - | 数据分组名称 |
diff --git a/src/Column/index.tsx b/src/Column/index.tsx
new file mode 100644
index 0000000..f8ea256
--- /dev/null
+++ b/src/Column/index.tsx
@@ -0,0 +1,46 @@
+import type { ColumnConfig } from '@ant-design/plots';
+import { Column as ADCColumn } from '@ant-design/plots';
+import { get } from 'lodash';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+export type ColumnDataItem = {
+ category: string | number;
+ value: number;
+ [key: string]: string | number;
+};
+
+export type ColumnProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: ColumnConfig): ColumnConfig => {
+ const { data, xField = 'category', yField = 'value' } = props;
+ const hasGroupField = get(data, '[0].group') !== undefined;
+ const axisYTitle = get(props, 'axis.y.title');
+
+ return {
+ xField,
+ yField,
+ colorField: hasGroupField ? 'group' : undefined,
+ tooltip: (d) => {
+ const tooltipName = axisYTitle || d[xField as string];
+ return {
+ name: tooltipName,
+ value: d[yField as string],
+ };
+ },
+ style: {
+ // 圆角样式
+ radiusTopLeft: 10,
+ radiusTopRight: 10,
+ },
+ };
+};
+
+const Column = (props: ColumnProps) => {
+ const config = usePlotConfig('Column', defaultConfig, props);
+
+ return ;
+};
+
+export default Column;
diff --git a/src/ConfigProvider/context.ts b/src/ConfigProvider/context.ts
new file mode 100644
index 0000000..f44072c
--- /dev/null
+++ b/src/ConfigProvider/context.ts
@@ -0,0 +1,5 @@
+import React from 'react';
+import { DEFAULT_GLOBAL_CONFIG } from '../constants';
+import type { GlobalConfig } from '../types';
+
+export const ConfigContext = React.createContext(DEFAULT_GLOBAL_CONFIG);
diff --git a/src/ConfigProvider/demos/components-config.tsx b/src/ConfigProvider/demos/components-config.tsx
new file mode 100644
index 0000000..058d808
--- /dev/null
+++ b/src/ConfigProvider/demos/components-config.tsx
@@ -0,0 +1,37 @@
+import { ConfigProvider, GPTVis } from '@antv/gpt-vis';
+import React from 'react';
+
+const content = `
+ ~~~vis-chart
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+}
+~~~`;
+
+const pieConfig = {
+ legend: false,
+ innerRadius: 0.6,
+ style: {
+ stroke: '#fff',
+ inset: 1,
+ radius: 10,
+ },
+};
+
+export default () => (
+
+ {content}
+
+);
diff --git a/src/ConfigProvider/demos/graph-components-config.tsx b/src/ConfigProvider/demos/graph-components-config.tsx
new file mode 100644
index 0000000..ed6b3e3
--- /dev/null
+++ b/src/ConfigProvider/demos/graph-components-config.tsx
@@ -0,0 +1,64 @@
+import { ConfigProvider, GPTVis } from '@antv/gpt-vis';
+import React from 'react';
+
+const content = `
+ ~~~vis-chart
+{
+ "type": "mind-map",
+ "data": {
+ "name": "台风形成的因素",
+ "children": [
+ {
+ "name": "气象条件",
+ "children": [
+ { "name": "温暖的海水" },
+ { "name": "气压分布" },
+ { "name": "湿度水平" },
+ { "name": "风的切变" }
+ ]
+ },
+ {
+ "name": "地理环境",
+ "children": [
+ { "name": "大陆架的形状与深度" },
+ { "name": "海洋暖流的分布" },
+ { "name": "热带地区的气候特征" },
+ { "name": "岛屿的影响" }
+ ]
+ }
+ ]
+ }
+}
+~~~`;
+
+const mindmapConfig = {
+ type: 'linear',
+ direction: 'right',
+ behaviors: (behaviors) => [
+ // console.log(behaviors) 👉 [{ key: 'zoom-canvas', type: 'zoom-canvas' }, { key: 'drag-canvas', type: 'drag-canvas' }]
+ // 默认启用两个交互,缩放画布和拖拽画布。此处移除缩放画布并添加拖拽元素
+ ...behaviors.filter((behavior) => behavior.key !== 'zoom-canvas'),
+ {
+ key: 'drag-element',
+ type: 'drag-element',
+ },
+ ],
+ transforms: (prev) => [
+ // 默认节点支持折叠展开,此处禁用
+ ...prev.filter((transform) => transform.key !== 'collapse-expand-react-node'),
+ {
+ ...prev.find((transform) => transform.key === 'collapse-expand-react-node'),
+ enable: false,
+ },
+ ],
+};
+
+export default () => (
+
+ {content}
+
+);
diff --git a/src/ConfigProvider/demos/map-config.tsx b/src/ConfigProvider/demos/map-config.tsx
new file mode 100644
index 0000000..3e15729
--- /dev/null
+++ b/src/ConfigProvider/demos/map-config.tsx
@@ -0,0 +1,47 @@
+import { ConfigProvider, GPTVis } from '@antv/gpt-vis';
+import React from 'react';
+
+const content = `
+ ~~~vis-chart
+ {
+ "type": "pin-map",
+ "data": [
+ {
+ "longitude": 120.210792,
+ "latitude": 30.246026,
+ "label": "杭州"
+ },
+ {
+ "longitude": 121.473667,
+ "latitude": 31.230525,
+ "label": "上海"
+ },
+ {
+ "longitude": 120.585294,
+ "latitude": 31.299758,
+ "label": "苏州"
+ },
+ {
+ "longitude": 118.796624,
+ "latitude": 32.059344,
+ "label": "南京"
+ }
+ ]
+ }
+~~~`;
+
+export default () => (
+
+ {content}
+
+);
diff --git a/src/ConfigProvider/demos/plot-theme.tsx b/src/ConfigProvider/demos/plot-theme.tsx
new file mode 100644
index 0000000..2b78ff8
--- /dev/null
+++ b/src/ConfigProvider/demos/plot-theme.tsx
@@ -0,0 +1,31 @@
+import { ConfigProvider, GPTVis } from '@antv/gpt-vis';
+import React from 'react';
+
+const content = `
+ ~~~vis-chart
+{
+ "type": "line",
+ "data": [
+ { "time": "2015 年", "value": 1700, "group": "出生人口" },
+ { "time": "2015 年", "value": 965, "group": "死亡人口" },
+ { "time": "2016 年", "value": 1500, "group": "出生人口" },
+ { "time": "2016 年", "value": 846, "group": "死亡人口" },
+ { "time": "2017 年", "value": 1200, "group": "出生人口" },
+ { "time": "2017 年", "value": 782, "group": "死亡人口" },
+ { "time": "2018 年", "value": 1250, "group": "出生人口" },
+ { "time": "2018 年", "value": 762, "group": "死亡人口" },
+ { "time": "2019 年", "value": 1290, "group": "出生人口" },
+ { "time": "2019 年", "value": 862, "group": "死亡人口" },
+ { "time": "2020 年", "value": 1100, "group": "出生人口" },
+ { "time": "2020 年", "value": 962, "group": "死亡人口" }
+ ],
+ "axisXTitle": "year",
+ "axisYTitle": "count"
+}
+~~~`;
+
+export default () => (
+
+ {content}
+
+);
diff --git a/src/ConfigProvider/hooks/index.ts b/src/ConfigProvider/hooks/index.ts
new file mode 100644
index 0000000..4852f57
--- /dev/null
+++ b/src/ConfigProvider/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useConfig';
diff --git a/src/ConfigProvider/hooks/useConfig.ts b/src/ConfigProvider/hooks/useConfig.ts
new file mode 100644
index 0000000..102975e
--- /dev/null
+++ b/src/ConfigProvider/hooks/useConfig.ts
@@ -0,0 +1,93 @@
+import type { GraphOptions } from '@ant-design/graphs';
+import type { CommonConfig } from '@ant-design/plots';
+import React from 'react';
+import type { MapProps } from '../../Map';
+import type { Charts } from '../../types';
+import { mergeGraphOptions } from '../../utils/config';
+import { transform2ADCProps } from '../../utils/plot';
+import { ConfigContext } from '../context';
+
+function useConfig() {
+ const context = React.useContext(ConfigContext);
+ return context;
+}
+
+export function useComponentGlobalConfig(name: Charts) {
+ const globalConfig = useConfig();
+ const { components = {} } = globalConfig;
+ const config = components?.[name];
+
+ return config;
+}
+
+function usePlotGlobalConfig(name: Charts) {
+ const componentConfig = useComponentGlobalConfig(name);
+ const { plot: plotConfig } = useConfig();
+ const config = {
+ ...plotConfig,
+ ...componentConfig,
+ };
+
+ return config;
+}
+
+export function usePlotConfig(
+ name: Charts,
+ defaultConfig: Partial | ((props: Partial) => Partial),
+ props: Partial,
+): Partial {
+ const transformedProps = transform2ADCProps(props);
+
+ const _defaultConfig =
+ typeof defaultConfig === 'function' ? defaultConfig(transformedProps) : defaultConfig;
+
+ const globalConfig = usePlotGlobalConfig(name);
+
+ const config = {
+ ..._defaultConfig,
+ ...globalConfig,
+ ...transformedProps,
+ };
+
+ return config;
+}
+
+function useMapGlobalConfig(name: Charts) {
+ const componentConfig = useComponentGlobalConfig(name);
+ const { map: mapConfig } = useConfig();
+ const transformedProps = {
+ mapType: mapConfig?.style,
+ token: mapConfig?.token,
+ };
+
+ const config = {
+ ...transformedProps,
+ ...componentConfig,
+ };
+
+ return config;
+}
+
+export function useMapConfig(name: Charts, props: T) {
+ const globalConfig = useMapGlobalConfig(name);
+
+ const mapConfig = {
+ ...globalConfig,
+ ...props,
+ };
+
+ return mapConfig;
+}
+
+function useGraphGlobalConfig(name: Charts) {
+ // TODO: global config
+ const componentConfig = useComponentGlobalConfig(name);
+
+ return componentConfig || {};
+}
+
+export function useGraphConfig(name: Charts, defaultConfig: T, props: T) {
+ const globalConfig = useGraphGlobalConfig(name);
+
+ return mergeGraphOptions(defaultConfig, globalConfig, props);
+}
diff --git a/src/ConfigProvider/index.md b/src/ConfigProvider/index.md
new file mode 100644
index 0000000..aa43d0d
--- /dev/null
+++ b/src/ConfigProvider/index.md
@@ -0,0 +1,60 @@
+---
+order: 3
+group:
+ order: 10
+ title: 其他
+demo: { cols: 2 }
+---
+
+# ConfigProvider 全局化配置
+
+## 代码演示
+
+设置统计图表主题
+
+设置地图 token 和样式
+设置组件的默认属性
+设置关系图组件的默认属性
+
+## API
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | ------------------------ | -------- | ------ | -------- |
+| plot | `PlotGlobalConfig` | 否 | - | 图表配置 |
+| map | `MapGlobalConfig` | 否 | - | 地图配置 |
+| components | `ComponentsGlobalConfig` | 否 | - | 组件配置 |
+
+### PlotGlobalConfig
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | --------- | -------- | ------ | ---------------------------------------------------------------------------------------------------- |
+| theme | `G2Theme` | 否 | - | 统计图表主题,详见 [plots theme](https://ant-design-charts.antgroup.com/options/plots/theme/academy) |
+
+### MapGlobalConfig
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------------------------------------------ | -------- | ------ | -------------- |
+| style | `'normal' | 'light' | 'dark' | string` | 否 | - | 地图样式 |
+| token | `string` | 是 | - | 高德地图 token |
+
+### ComponentsGlobalConfig
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----------------- | -------------------------- | -------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
+| Line | `LineConfig` | 否 | - | 折线图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| Column | `ColumnConfig` | 否 | - | 柱形图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| Pie | `PieConfig` | 否 | - | 饼图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| Area | `AreaConfig` | 否 | - | 面积图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| Bar | `BarConfig` | 否 | - | 条形图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| Scatter | `ScatterConfig` | 否 | - | 散点图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| Radar | `RadarConfig` | 否 | - | 雷达图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| Treemap | `TreemapConfig` | 否 | - | 矩阵树图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| WordCloud | `WordCloudConfig` | 否 | - | 词语图图组件默认属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+| VisText | `TextConfig` | 否 | - | 可视文本组件个性化定制,详见 [VisText](/components/text#globalconfigcomponentsvistext) |
+| PinMap | `PinMapConfig` | 否 | - | 标注地图组件默认属性,详见 [PinMap](/components/pin-map/api) |
+| PathMa | `PathMapConfig` | 否 | - | 路径地图组件默认属性,详见 [PathMa](/components/path-map/api) |
+| HeatMap | `TextConfig` | 否 | - | 热力地图组件默认属性,详见 [HeatMap](/components/heat-map/api) |
+| MindMap | `MindMapOptions` | 否 | - | 思维导图组件默认属性,详见 [Ant Design Charts](https://ant-design-charts.antgroup.com/options/graphs/mind-map) |
+| FlowDiagram | `FlowGraphOptions` | 否 | - | 流程图组件默认属性,详见 [Ant Design Charts](https://ant-design-charts.antgroup.com/options/graphs/flow-graph) |
+| NetworkGraph | `NetworkGraphOptions` | 否 | - | 网络图组件默认属性,详见 [Ant Design Charts](https://ant-design-charts.antgroup.com/options/graphs/network-graph) |
+| OrganizationChart | `OrganizationChartOptions` | 否 | - | 组织架构图组件默认属性,详见 [Ant Design Charts](https://ant-design-charts.antgroup.com/options/graphs/organization-chart) |
diff --git a/src/ConfigProvider/index.tsx b/src/ConfigProvider/index.tsx
new file mode 100644
index 0000000..d62f835
--- /dev/null
+++ b/src/ConfigProvider/index.tsx
@@ -0,0 +1,18 @@
+import React, { useMemo } from 'react';
+import type { GlobalConfig } from '../types';
+import { mergeGlobalConfig } from '../utils/config';
+import { ConfigContext } from './context';
+
+export type ConfigProviderProps = {
+ children?: React.ReactNode;
+} & GlobalConfig;
+
+const ConfigProvider: React.FC = (props) => {
+ const { children, ...config } = props;
+
+ const contextValue = useMemo(() => mergeGlobalConfig(config), []);
+
+ return {children} ;
+};
+
+export default ConfigProvider;
diff --git a/src/DualAxes/demos/common.tsx b/src/DualAxes/demos/common.tsx
new file mode 100644
index 0000000..46853b0
--- /dev/null
+++ b/src/DualAxes/demos/common.tsx
@@ -0,0 +1,62 @@
+import type { DualAxesProps } from '@antv/gpt-vis';
+import { DualAxes } from '@antv/gpt-vis';
+import React from 'react';
+
+const config: DualAxesProps = {
+ categories: [
+ '2020-08-20',
+ '2020-08-21',
+ '2020-08-22',
+ '2020-08-23',
+ '2020-08-24',
+ '2020-08-25',
+ '2020-08-26',
+ '2020-08-27',
+ '2020-08-28',
+ '2020-08-29',
+ '2020-08-30',
+ '2020-08-31',
+ '2020-09-01',
+ '2020-09-02',
+ '2020-09-03',
+ '2020-09-04',
+ '2020-09-05',
+ '2020-09-06',
+ '2020-09-07',
+ '2020-09-08',
+ '2020-09-09',
+ '2020-09-10',
+ '2020-09-11',
+ '2020-09-12',
+ '2020-09-13',
+ '2020-09-14',
+ '2020-09-15',
+ '2020-09-16',
+ '2020-09-17',
+ '2020-09-18',
+ ],
+ series: [
+ {
+ type: 'column',
+ data: [
+ 10868, 8786, 10824, 7860, 13253, 17015, 19298, 13937, 11541, 15244, 14247, 9402, 10440,
+ 9345, 18459, 9763, 11074, 11770, 12206, 11434, 16218, 11914, 16781, 10555, 10899, 10713, 0,
+ 0, 20357, 10424,
+ ],
+ axisYTitle: '消耗时间',
+ },
+ {
+ type: 'line',
+ data: [
+ 649.483, 1053.7, 679.817, 638.117, 843.3, 1092.983, 1036.317, 1031.9, 803.467, 830.733,
+ 709.867, 665.233, 696.367, 692.867, 936.017, 782.867, 653.8, 856.683, 777.15, 773.283,
+ 833.3, 793.517, 894.45, 725.55, 709.967, 787.6, 644.183, 1066.65, 932.45, 753.583,
+ ],
+ axisYTitle: '完成时间',
+ },
+ ],
+};
+
+export default () => {
+ return ;
+};
diff --git a/src/DualAxes/demos/markdown.tsx b/src/DualAxes/demos/markdown.tsx
new file mode 100644
index 0000000..fc346ae
--- /dev/null
+++ b/src/DualAxes/demos/markdown.tsx
@@ -0,0 +1,65 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, DualAxes, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个双轴图
+
+\`\`\`vis-chart
+{
+ "type": "dual-axes",
+ "categories": ["2020", "2021", "2022"],
+ "series": [
+ {
+ "type": "column",
+ "data": [500, 600, 700],
+ "axisYTitle": "amount"
+ },
+ {
+ "type": "line",
+ "data": [10, 12, 15],
+ "axisYTitle": "rate"
+ }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.DualAxes]: DualAxes },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/DualAxes/demos/multiple.tsx b/src/DualAxes/demos/multiple.tsx
new file mode 100644
index 0000000..56ad3a0
--- /dev/null
+++ b/src/DualAxes/demos/multiple.tsx
@@ -0,0 +1,122 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, DualAxes, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个多轴图
+
+\`\`\`vis-chart
+{
+ "type": "dual-axes",
+ "categories": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec"
+ ],
+ "series": [
+ {
+ "type": "column",
+ "data": [
+ 26,
+ 59,
+ 90,
+ 264,
+ 287,
+ 707,
+ 1756,
+ 1822,
+ 487,
+ 188,
+ 60,
+ 23
+ ],
+ "axisYTitle": "总体增长量"
+ },
+ {
+ "type": "line",
+ "data": [
+ 2.6,
+ 5.9,
+ 9,
+ 26.4,
+ 28.7,
+ 70.7,
+ 175.6,
+ 182.2,
+ 48.7,
+ 18.8,
+ 6,
+ 2.3
+ ],
+ "axisYTitle": "A增长率"
+ },
+ {
+ "type": "line",
+ "data": [
+ 2,
+ 2.2,
+ 3.3,
+ 4.5,
+ 6.3,
+ 10.2,
+ 20.3,
+ 23.4,
+ 23,
+ 16.5,
+ 12,
+ 6.2
+ ],
+ "axisYTitle": "B增长率"
+ }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.DualAxes]: DualAxes },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/DualAxes/index.md b/src/DualAxes/index.md
new file mode 100644
index 0000000..81a311f
--- /dev/null
+++ b/src/DualAxes/index.md
@@ -0,0 +1,59 @@
+---
+order: 10
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# DualAxes 双轴图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+多轴图
+
+## Spec
+
+```json
+{
+ "type": "dual-axes",
+ "categories": ["2018", "2019", "2020", "2021", "2022"],
+ "title": "2018-2022销售额与利润率",
+ "axisXTitle": "年份",
+ "series": [
+ {
+ "type": "column",
+ "data": [91.9, 99.1, 101.6, 114.4, 121],
+ "axisYTitle": "销售额"
+ },
+ {
+ "type": "line",
+ "data": [0.055, 0.06, 0.062, 0.07, 0.075],
+ "axisYTitle": "利润率"
+ }
+ ]
+}
+```
+
+## API
+
+### DualAxesProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | ---------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| categories | string[] | 是 | - | x 轴对应的数据 |
+| series | SeriesDataItem[] | 是 | - | 子图的数据 |
+| title | string | 否 | - | 图表的标题 |
+| axisXTitle | string | 否 | - | x 轴的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### SeriesDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | -------- | -------- | ------ | ---------- |
+| type | string | 是 | - | 子图的类型 |
+| data | number[] | 是 | - | 子图的数据 |
+| axisYTitle | string | 否 | - | y 轴的标题 |
diff --git a/src/DualAxes/index.tsx b/src/DualAxes/index.tsx
new file mode 100644
index 0000000..8d9a45f
--- /dev/null
+++ b/src/DualAxes/index.tsx
@@ -0,0 +1,55 @@
+import type { DualAxesConfig } from '@ant-design/plots';
+import { DualAxes as ADCDualAxes } from '@ant-design/plots';
+import React, { useMemo } from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import { transform } from './util';
+
+export type DualAxesProps = Partial & {
+ categories: string[];
+ axisXTitle?: string;
+ series: DualAxesSeriesItem[];
+ legendTypeList: string[];
+};
+
+export type DualAxesSeriesItem = {
+ type: string;
+ data: number[];
+ axisYTitle?: string;
+};
+
+const defaultConfig = (props: DualAxesProps): DualAxesConfig => {
+ const { xField = 'category', axisXTitle, legendTypeList } = props;
+ return {
+ xField,
+ axis: {
+ x: {
+ title: axisXTitle,
+ },
+ },
+ legend: {
+ color: {
+ itemMarker: (v: string, i: number) => {
+ return legendTypeList[i];
+ },
+ },
+ },
+ scale: {
+ color: {
+ palette: 'category10',
+ },
+ },
+ };
+};
+
+const DualAxes = (props: DualAxesProps) => {
+ const { categories, series, ...others } = props;
+ const transformData = useMemo(() => transform(series, categories), [props]);
+ const config = usePlotConfig('DualAxes', defaultConfig, {
+ ...others,
+ ...transformData,
+ });
+
+ return ;
+};
+
+export default DualAxes;
diff --git a/src/DualAxes/util.ts b/src/DualAxes/util.ts
new file mode 100644
index 0000000..e7646d7
--- /dev/null
+++ b/src/DualAxes/util.ts
@@ -0,0 +1,57 @@
+import type { DualAxesSeriesItem } from '.';
+import { ChartType } from '../types';
+
+export function transform(series: DualAxesSeriesItem[], categories: string[]) {
+ const newChildren = series.map((item: any, index: number) => {
+ const { type, axisYTitle, ...others } = item;
+
+ const defaultYField = axisYTitle || `value_${index + 1}`;
+ const baseConfig = {
+ ...others,
+ yField: defaultYField,
+ axis: { y: { title: axisYTitle } },
+ // data放在最外层
+ data: undefined,
+ };
+
+ if (type === ChartType.Column) {
+ return { ...baseConfig, type: 'interval' };
+ }
+
+ if (type === ChartType.Line) {
+ return {
+ ...baseConfig,
+ type,
+ shapeField: 'smooth',
+ axis: { y: { position: 'right', title: axisYTitle } },
+ style: { lineWidth: 2 },
+ };
+ }
+
+ return baseConfig;
+ });
+
+ const newData = categories.map((item: string, index: number) => {
+ const temp = {
+ category: item,
+ } as {
+ category: string;
+ [key: string]: any;
+ };
+ series.forEach((s: DualAxesSeriesItem, i: number) => {
+ const defaultYField = s.axisYTitle || `value_${i + 1}`;
+ temp[defaultYField] = s.data[index];
+ });
+ return temp;
+ });
+
+ const legendTypeList = series.map((item: any) => {
+ return item.type === ChartType.Line ? 'smooth' : 'rect';
+ });
+
+ return {
+ children: newChildren,
+ data: newData,
+ legendTypeList,
+ };
+}
diff --git a/src/FlowDiagram/demos/common.tsx b/src/FlowDiagram/demos/common.tsx
new file mode 100644
index 0000000..0c3b119
--- /dev/null
+++ b/src/FlowDiagram/demos/common.tsx
@@ -0,0 +1,26 @@
+import { FlowDiagram } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = {
+ nodes: [
+ { name: '访问注册页面' },
+ { name: '填写并提交注册表单' },
+ { name: '验证用户信息' },
+ { name: '创建新用户账户' },
+ { name: '提示修改错误信息' },
+ { name: '发送验证邮件' },
+ { name: '点击验证链接' },
+ { name: '注册成功,跳转到登录页面' },
+ ],
+ edges: [
+ { source: '访问注册页面', target: '填写并提交注册表单' },
+ { source: '填写并提交注册表单', target: '验证用户信息' },
+ { source: '验证用户信息', target: '创建新用户账户', name: '信息无误' },
+ { source: '验证用户信息', target: '提示修改错误信息', name: '信息有误' },
+ { source: '创建新用户账户', target: '发送验证邮件' },
+ { source: '发送验证邮件', target: '点击验证链接' },
+ { source: '点击验证链接', target: '注册成功,跳转到登录页面' },
+ ],
+};
+
+export default () => ;
diff --git a/src/FlowDiagram/demos/markdown.tsx b/src/FlowDiagram/demos/markdown.tsx
new file mode 100644
index 0000000..54fd245
--- /dev/null
+++ b/src/FlowDiagram/demos/markdown.tsx
@@ -0,0 +1,69 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, FlowDiagram, GPTVis, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个流程图
+
+\`\`\`vis-chart
+{
+ "type": "flow-diagram",
+ "data": {
+ "nodes": [
+ { "name": "客户下单" },
+ { "name": "系统生成订单" },
+ { "name": "仓库拣货" },
+ { "name": "仓库打包" },
+ { "name": "物流配送" },
+ { "name": "客户收货" }
+ ],
+ "edges": [
+ { "source": "客户下单", "target": "系统生成订单" },
+ { "source": "系统生成订单", "target": "仓库拣货" },
+ { "source": "仓库拣货", "target": "仓库打包" },
+ { "source": "仓库打包", "target": "物流配送" },
+ { "source": "物流配送", "target": "客户收货" }
+ ]
+ }
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.FlowDiagram]: FlowDiagram },
+ style: { width: 500, height: 250 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/FlowDiagram/index.md b/src/FlowDiagram/index.md
new file mode 100644
index 0000000..816f8ea
--- /dev/null
+++ b/src/FlowDiagram/index.md
@@ -0,0 +1,61 @@
+---
+order: 2
+group:
+ order: 3
+ title: 关系图
+---
+
+# FlowDiagram 流程图
+
+流程图,用于直观地表示过程或系统的步骤和决策点。
+
+## 代码演示
+
+### 单独使用
+
+
+
+### 使用 Markdown 协议
+
+
+
+## Spec
+
+```json
+{
+ "type": "flow-diagram",
+ "data": {
+ "nodes": [{ "name": "node1" }, { "name": "node2" }],
+ "edges": [{ "source": "node1", "target": "node2", "name": "edge1" }]
+ }
+}
+```
+
+## API
+
+### FlowDiagramProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | ----------------- | -------- | ------ | ---- |
+| data | `FlowDiagramData` | 是 | - | 数据 |
+
+### FlowDiagramData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------------------- | -------- | ------ | ---------------------------------------------- |
+| nodes | `FlowDiagramNode[]` | 是 | - | 网络图中的节点数组,每个节点表示一个实体 |
+| edges | `FlowDiagramEdge[]` | 是 | - | 网络图中的边数组,每条边表示两个节点之间的关系 |
+
+### FlowDiagramNode
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | -------- | -------- | ------ | ---------------------------------- |
+| name | `string` | 是 | - | 节点的名称,必须唯一,用于标识节点 |
+
+### FlowDiagramEdge
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ------ | -------- | -------- | ------ | ------------------------------------------------------- |
+| source | `string` | 是 | - | 边的起始节点名称,指向 `FlowDiagramNode` 的 `name` 属性 |
+| target | `string` | 是 | - | 边的目标节点名称,指向 `FlowDiagramNode` 的 `name` 属性 |
+| name | `string` | 否 | - | 边的名称,用于标识边 |
diff --git a/src/FlowDiagram/index.tsx b/src/FlowDiagram/index.tsx
new file mode 100644
index 0000000..4d09691
--- /dev/null
+++ b/src/FlowDiagram/index.tsx
@@ -0,0 +1,75 @@
+import type { FlowGraphOptions, G6 } from '@ant-design/graphs';
+import { FlowGraph as ADCFlowGraph, RCNode } from '@ant-design/graphs';
+import React, { useMemo } from 'react';
+import { useGraphConfig } from '../ConfigProvider/hooks';
+import type { GraphProps } from '../types';
+import { visGraphData2GraphData } from '../utils/graph';
+
+const { TextNode } = RCNode;
+
+export interface FlowDiagramProps extends GraphProps {}
+
+const defaultConfig: FlowGraphOptions = {
+ autoResize: true,
+ node: {
+ style: {
+ component: (d: G6.NodeData) => {
+ const isActive = d.states?.includes('active');
+ return (
+
+ );
+ },
+ size: [140, 32],
+ },
+ animation: { enter: false },
+ },
+ edge: {
+ style: {
+ endArrow: true,
+ labelBackground: true,
+ labelMaxLines: 2,
+ labelMaxWidth: '40%',
+ labelWordWrap: true,
+ },
+ state: {
+ active: {
+ halo: false,
+ labelWordWrap: false,
+ stroke: '#001f98',
+ },
+ },
+ animation: { enter: false },
+ },
+ behaviors: (prev) => [
+ ...prev,
+ {
+ type: 'hover-activate-neighbors',
+ onHover: (e: G6.IPointerEvent) => {
+ e.view.setCursor('pointer');
+ },
+ onHoverEnd: (e: G6.IPointerEvent) => {
+ e.view.setCursor('default');
+ },
+ },
+ ],
+};
+
+const FlowDiagram: React.FC = (props) => {
+ const { data: propsData, ...restProps } = props;
+
+ const data = useMemo(() => visGraphData2GraphData(propsData), [propsData]);
+
+ const config = useGraphConfig('FlowDiagram', defaultConfig, restProps);
+
+ return ;
+};
+
+export default FlowDiagram;
diff --git a/src/GPTVis/demos/code.tsx b/src/GPTVis/demos/code.tsx
new file mode 100644
index 0000000..e3de201
--- /dev/null
+++ b/src/GPTVis/demos/code.tsx
@@ -0,0 +1,44 @@
+import type { CodeBlockComponent } from '@antv/gpt-vis';
+import { ChartType, GPTVis, PinMap, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+/**
+ * 自定义代码块渲染器
+ */
+const MyUIRenderer: CodeBlockComponent = ({ children }) => {
+ return {children}
;
+};
+
+const components = {
+ code: withChartCode({
+ languageRenderers: { kotlin: MyUIRenderer },
+ components: { [ChartType.PinMap]: PinMap },
+ }),
+};
+
+const content = `
+\`\`\`my-ui
+my data ...
+\`\`\`
+
+\`\`\`vis-chart
+{
+ "type": "pin-map",
+ "data": [
+ { "label": "杨梅岭", "longitude": 120.118362, "latitude": 30.217175 },
+ { "label": "理安寺", "longitude": 120.112958, "latitude": 30.207319 },
+ { "label": "九溪烟树", "longitude": 120.11335, "latitude": 30.202395 },
+ { "label": "飞来峰", "longitude": 120.100549, "latitude": 30.236875 },
+ { "label": "灵隐寺", "longitude": 120.101406, "latitude": 30.240826 },
+ { "label": "天竺三寺", "longitude": 120.105337, "latitude": 30.236818 },
+ { "label": "杭州植物园", "longitude": 120.116979, "latitude": 30.252876 },
+ { "label": "杭州花圃", "longitude": 120.127654, "latitude": 30.245663 },
+ { "label": "苏堤", "longitude": 120.135764, "latitude": 30.251448 },
+ { "label": "虎跑公园", "longitude": 120.130095, "latitude": 30.207505 },
+ { "label": "玉皇飞云", "longitude": 120.145323, "latitude": 30.214993 },
+ { "label": "长桥公园", "longitude": 120.155057, "latitude": 30.232985 }
+ ]
+}
+\`\`\`
+`;
+export default () => {content} ;
diff --git a/src/GPTVis/demos/default.tsx b/src/GPTVis/demos/default.tsx
new file mode 100644
index 0000000..aa340b6
--- /dev/null
+++ b/src/GPTVis/demos/default.tsx
@@ -0,0 +1,23 @@
+import { GPTVis } from '@antv/gpt-vis';
+import React from 'react';
+
+const content = `# GPT-VIS \n\nComponents for GPTs, generative AI, and LLM projects. Not only UI Components.
+
+\`\`\`vis-chart
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+}
+\`\`\`
+`;
+
+export default () => {
+ return {content} ;
+};
diff --git a/src/GPTVis/demos/tag.tsx b/src/GPTVis/demos/tag.tsx
new file mode 100644
index 0000000..489ff0c
--- /dev/null
+++ b/src/GPTVis/demos/tag.tsx
@@ -0,0 +1,48 @@
+import { GPTVis, withDefaultChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const components = {
+ // Rewrite `a` style
+ a(props: any) {
+ const { href, children } = props;
+ return (
+
+ {children}
+
+ );
+ },
+ // Rewrite `em`s (`*like so*`) to `i` with a color.
+ em(props: any) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { node, ...rest } = props;
+ return (
+
+ );
+ },
+ code: withDefaultChartCode(),
+};
+
+const content = `
+# Haidilao's Food Delivery Revenue (2013-2022)
+
+Here’s a visualization of [Haidilao](/)'s food delivery revenue from 2013 to 2022. You can see a steady increase over the years, with notable *growth* particularly in recent years.
+
+\`\`\`vis-chart
+{
+ "type": "line",
+ "data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}]
+}
+\`\`\`
+`;
+export default () => {content} ;
diff --git a/src/GPTVis/index.md b/src/GPTVis/index.md
new file mode 100644
index 0000000..232d97a
--- /dev/null
+++ b/src/GPTVis/index.md
@@ -0,0 +1,26 @@
+---
+order: 1
+group:
+ order: 10
+ title: 其他
+---
+
+# GPTVis 协议渲染器
+
+GPTVis 协议的 Markdown 渲染器,基于 Markdown 语法扩展 `vis-chart` 语法块,支持自定义渲染组件。
+
+## 基础使用
+
+
+
+## 自定义标签渲染
+
+
+
+## 自定义 code 渲染块
+
+
+
+## API
+
+继承 [react-markdown](https://github.com/remarkjs/react-markdown#options) 组件全部属性。
diff --git a/src/GPTVis/index.tsx b/src/GPTVis/index.tsx
new file mode 100644
index 0000000..040ad00
--- /dev/null
+++ b/src/GPTVis/index.tsx
@@ -0,0 +1,30 @@
+import React, { memo } from 'react';
+import type { Options } from 'react-markdown';
+import Markdown from 'react-markdown';
+import rehypeRaw from 'rehype-raw';
+import { withDefaultChartCode } from '../ChartCodeRender';
+
+export interface GPTVisProps extends Options {
+ /** 自定义 markdown components样式 */
+ components?:
+ | Options['components']
+ | {
+ [key: string]: (props: any) => React.ReactNode;
+ };
+}
+
+const CodeBlock = withDefaultChartCode();
+
+const GPTVis: React.FC = ({ children, components, rehypePlugins, ...rest }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default memo(GPTVis);
diff --git a/src/HeatMap/index.md b/src/HeatMap/index.md
new file mode 100644
index 0000000..8bbcca6
--- /dev/null
+++ b/src/HeatMap/index.md
@@ -0,0 +1,237 @@
+---
+order: 3
+group:
+ order: 2
+ title: 地图
+---
+
+# HeatMap 热力地图
+
+热力地图,直观展示数据密度与频率的空间分布。
+
+## 代码演示
+
+### 单独使用
+
+```jsx
+import React from 'react';
+import { HeatMap } from '@antv/gpt-vis';
+
+const data = [
+ {
+ longitude: 121.473117,
+ latitude: 31.230125,
+ value: 20,
+ },
+ {
+ longitude: 121.473337,
+ latitude: 31.230325,
+ value: 100,
+ },
+ {
+ longitude: 121.473557,
+ latitude: 31.230525,
+ value: 300,
+ },
+ {
+ longitude: 121.473777,
+ latitude: 31.230725,
+ value: 600,
+ },
+ {
+ longitude: 121.473997,
+ latitude: 31.230925,
+ value: 1000,
+ },
+];
+
+export default () => ;
+```
+
+### 使用 Markdown 协议
+
+```tsx
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { HeatMap, withChartCode, ChartType, GPTVis } from '@antv/gpt-vis';
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const markdownContent = `
+ ~~~vis-chart
+ {
+ "type": "heat-map",
+ "data": [
+ {
+ "longitude": 121.474856,
+ "latitude": 31.249162,
+ "value": 800
+ },
+ {
+ "longitude": 121.499718,
+ "latitude": 31.239703,
+ "value": 1000
+ },
+ {
+ "longitude": 121.48612,
+ "latitude": 31.24166,
+ "value": 1200
+ },
+ {
+ "longitude": 121.449895,
+ "latitude": 31.228609,
+ "value": 500
+ },
+ {
+ "longitude": 121.449486,
+ "latitude": 31.222042,
+ "value": 900
+ },
+ {
+ "longitude": 121.431826,
+ "latitude": 31.204638,
+ "value": 400
+ },
+ {
+ "longitude": 121.438573,
+ "latitude": 31.204188,
+ "value": 1000
+ },
+ {
+ "longitude": 121.448453,
+ "latitude": 31.222341,
+ "value": 300
+ },
+ {
+ "longitude": 121.474856,
+ "latitude": 31.249162,
+ "value": 800
+ },
+ {
+ "longitude": 121.473688,
+ "latitude": 31.249921,
+ "value": 1000
+ },
+ {
+ "longitude": 121.449895,
+ "latitude": 31.228609,
+ "value": 500
+ },
+ {
+ "longitude": 121.449486,
+ "latitude": 31.222042,
+ "value": 900
+ },
+ {
+ "longitude": 121.431826,
+ "latitude": 31.204638,
+ "value": 400
+ },
+ {
+ "longitude": 121.438573,
+ "latitude": 31.204188,
+ "value": 1000
+ },
+ {
+ "longitude": 121.448453,
+ "latitude": 31.222341,
+ "value": 300
+ },
+ {
+ "longitude": 121.448997,
+ "latitude": 31.203590,
+ "value": 400
+ }
+ ]
+ }
+~~~`;
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.HeatMap]: HeatMap },
+ style: { width: 500 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => {
+ return (
+
+
+
+
+ );
+};
+```
+
+## Spec
+
+```json
+{
+ "type": "heat-map",
+ "data": [
+ {
+ "longitude": 121.473117,
+ "latitude": 31.230125,
+ "value": 20
+ },
+ {
+ "longitude": 121.473337,
+ "latitude": 31.230325,
+ "value": 100
+ },
+ {
+ "longitude": 121.473557,
+ "latitude": 31.230525,
+ "value": 300
+ },
+ {
+ "longitude": 121.473777,
+ "latitude": 31.230725,
+ "value": 600
+ },
+ {
+ "longitude": 121.473997,
+ "latitude": 31.230925,
+ "value": 1000
+ }
+ ]
+}
+```
+
+## API
+
+### HeatMapProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | ----------------- | -------- | ------ | ---- |
+| data | HeatMapDataItem[] | 是 | - | 数据 |
+
+### HeatMapDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| --------- | ------ | -------- | ------ | ------ |
+| longitude | number | 是 | - | 经度 |
+| latitude | number | 是 | - | 纬度 |
+| value | number | 是 | - | 热力值 |
diff --git a/src/HeatMap/index.tsx b/src/HeatMap/index.tsx
new file mode 100644
index 0000000..5d23c8c
--- /dev/null
+++ b/src/HeatMap/index.tsx
@@ -0,0 +1,57 @@
+import type { HeatmapLayerProps } from '@antv/larkmap';
+import { HeatmapLayer } from '@antv/larkmap';
+import React, { useMemo, type FC } from 'react';
+import { useMapConfig } from '../ConfigProvider/hooks';
+import type { MapProps } from '../Map';
+import Map from '../Map';
+
+const heapLayerOptions: Omit = {
+ autoFit: true,
+ shape: 'heatmap' as const,
+ size: {
+ field: 'value',
+ value: [0, 1],
+ },
+ style: {
+ intensity: 3,
+ radius: 20,
+ opacity: 1,
+ rampColors: {
+ colors: ['#FF4818', '#F7B74A', '#FFF598', '#F27DEB', '#8C1EB2', '#421EB2'],
+ positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
+ },
+ },
+};
+
+type GeoData = {
+ longitude: number;
+ latitude: number;
+ value: number;
+};
+
+export type HeatMapProps = MapProps & {
+ data?: GeoData[];
+};
+
+const HeatMap: FC = (props) => {
+ const { children, data = [], ...mapConfigRest } = useMapConfig('HeatMap', props);
+
+ const source = useMemo(
+ () => ({
+ data,
+ parser: { type: 'json', x: 'longitude', y: 'latitude' },
+ }),
+ [data],
+ );
+
+ return (
+ <>
+
+
+ {children}
+
+ >
+ );
+};
+
+export default HeatMap;
diff --git a/src/Histogram/demos/common.tsx b/src/Histogram/demos/common.tsx
new file mode 100644
index 0000000..ed8dc16
--- /dev/null
+++ b/src/Histogram/demos/common.tsx
@@ -0,0 +1,71 @@
+import { Histogram } from '@antv/gpt-vis';
+import React from 'react';
+
+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 },
+];
+
+export default () => {
+ return (
+
+ );
+};
diff --git a/src/Histogram/demos/markdown.tsx b/src/Histogram/demos/markdown.tsx
new file mode 100644
index 0000000..de7e0d9
--- /dev/null
+++ b/src/Histogram/demos/markdown.tsx
@@ -0,0 +1,343 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Histogram, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个直方图
+
+\`\`\`vis-chart
+{
+ "type": "histogram",
+ "data": [
+ {
+ "value": 45
+ },
+ {
+ "value": 55
+ },
+ {
+ "value": 67
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 42
+ },
+ {
+ "value": 61
+ },
+ {
+ "value": 33
+ },
+ {
+ "value": 76
+ },
+ {
+ "value": 59
+ },
+ {
+ "value": 51
+ },
+ {
+ "value": 70
+ },
+ {
+ "value": 63
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 48
+ },
+ {
+ "value": 37
+ },
+ {
+ "value": 72
+ },
+ {
+ "value": 55
+ },
+ {
+ "value": 52
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 46
+ },
+ {
+ "value": 65
+ },
+ {
+ "value": 68
+ },
+ {
+ "value": 60
+ },
+ {
+ "value": 45
+ },
+ {
+ "value": 54
+ },
+ {
+ "value": 75
+ },
+ {
+ "value": 49
+ },
+ {
+ "value": 56
+ },
+ {
+ "value": 47
+ },
+ {
+ "value": 51
+ },
+ {
+ "value": 62
+ },
+ {
+ "value": 53
+ },
+ {
+ "value": 71
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 40
+ },
+ {
+ "value": 57
+ },
+ {
+ "value": 36
+ },
+ {
+ "value": 69
+ },
+ {
+ "value": 42
+ },
+ {
+ "value": 63
+ },
+ {
+ "value": 44
+ },
+ {
+ "value": 64
+ },
+ {
+ "value": 77
+ },
+ {
+ "value": 59
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 53
+ },
+ {
+ "value": 61
+ },
+ {
+ "value": 48
+ },
+ {
+ "value": 58
+ },
+ {
+ "value": 66
+ },
+ {
+ "value": 51
+ },
+ {
+ "value": 39
+ },
+ {
+ "value": 60
+ },
+ {
+ "value": 56
+ },
+ {
+ "value": 57
+ },
+ {
+ "value": 67
+ },
+ {
+ "value": 64
+ },
+ {
+ "value": 53
+ },
+ {
+ "value": 73
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 45
+ },
+ {
+ "value": 61
+ },
+ {
+ "value": 58
+ },
+ {
+ "value": 54
+ },
+ {
+ "value": 68
+ },
+ {
+ "value": 41
+ },
+ {
+ "value": 62
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 46
+ },
+ {
+ "value": 70
+ },
+ {
+ "value": 42
+ },
+ {
+ "value": 69
+ },
+ {
+ "value": 55
+ },
+ {
+ "value": 60
+ },
+ {
+ "value": 51
+ },
+ {
+ "value": 66
+ },
+ {
+ "value": 48
+ },
+ {
+ "value": 59
+ },
+ {
+ "value": 52
+ },
+ {
+ "value": 63
+ },
+ {
+ "value": 57
+ },
+ {
+ "value": 61
+ },
+ {
+ "value": 74
+ },
+ {
+ "value": 65
+ },
+ {
+ "value": 55
+ },
+ {
+ "value": 47
+ },
+ {
+ "value": 53
+ },
+ {
+ "value": 68
+ },
+ {
+ "value": 62
+ },
+ {
+ "value": 49
+ },
+ {
+ "value": 58
+ },
+ {
+ "value": 66
+ },
+ {
+ "value": 50
+ },
+ {
+ "value": 44
+ },
+ {
+ "value": 72
+ },
+ {
+ "value": 41
+ }
+ ],
+ "binNumber": 10
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Histogram]: Histogram },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Histogram/index.md b/src/Histogram/index.md
new file mode 100644
index 0000000..838f19f
--- /dev/null
+++ b/src/Histogram/index.md
@@ -0,0 +1,44 @@
+---
+order: 5
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# Histogram 直方图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+
+## Spec
+
+```json
+{
+ "type": "histogram",
+ "data": [{ "value": 2 }, { "value": 5 }, { "value": 8 }, { "value": 3 }],
+ "binNumber": 4
+}
+```
+
+## API
+
+### HistogramProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | ------------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | HistogramDataItem[] | 是 | - | 数据 |
+| binNumber | number | 否 | - | 区间个数,用于定义直方图的区间数量 |
+| title | string | 否 | - | 图表的标题 |
+| axisXTitle | string | 否 | - | x 轴的标题 |
+| axisYTitle | string | 否 | - | y 轴的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### HistogramDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------ | -------- | ------ | -------- |
+| value | number | 是 | - | 数据的值 |
diff --git a/src/Histogram/index.tsx b/src/Histogram/index.tsx
new file mode 100644
index 0000000..59759ae
--- /dev/null
+++ b/src/Histogram/index.tsx
@@ -0,0 +1,33 @@
+import type { HistogramConfig } from '@ant-design/plots';
+import { Histogram as ADCHistogram } from '@ant-design/plots';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+type HistogramDataItem = {
+ value: number;
+ [key: string]: string | number;
+};
+// binNumber和binWidth为互斥属性,选其一即可
+type ADCHistogramConfig = Omit;
+
+export type HistogramProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: HistogramConfig): ADCHistogramConfig => {
+ const { data, binField = 'value', binNumber } = props;
+
+ return {
+ data,
+ binField,
+ binNumber,
+ style: { inset: 0.5 },
+ };
+};
+
+const Histogram = (props: HistogramProps) => {
+ const config = usePlotConfig('Histogram', defaultConfig, props);
+
+ return ;
+};
+
+export default Histogram;
diff --git a/src/IndentedTree/demos/common.tsx b/src/IndentedTree/demos/common.tsx
new file mode 100644
index 0000000..17c8dba
--- /dev/null
+++ b/src/IndentedTree/demos/common.tsx
@@ -0,0 +1,63 @@
+import { IndentedTree } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = {
+ name: 'my-project',
+ children: [
+ {
+ name: 'src',
+ children: [
+ {
+ name: 'components',
+ children: [
+ {
+ name: 'Header.tsx',
+ },
+ {
+ name: 'Footer.tsx',
+ },
+ ],
+ },
+ {
+ name: 'pages',
+ children: [
+ {
+ name: 'Home.tsx',
+ },
+ {
+ name: 'About.tsx',
+ },
+ ],
+ },
+ {
+ name: 'App.tsx',
+ },
+ {
+ name: 'index.tsx',
+ },
+ ],
+ },
+ {
+ name: 'public',
+ children: [
+ {
+ name: 'index.html',
+ },
+ {
+ name: 'favicon.ico',
+ },
+ ],
+ },
+ {
+ name: 'package.json',
+ },
+ {
+ name: 'tsconfig.json',
+ },
+ {
+ name: 'README.md',
+ },
+ ],
+};
+
+export default () => ;
diff --git a/src/IndentedTree/demos/markdown.tsx b/src/IndentedTree/demos/markdown.tsx
new file mode 100644
index 0000000..255326f
--- /dev/null
+++ b/src/IndentedTree/demos/markdown.tsx
@@ -0,0 +1,79 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, IndentedTree, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个缩进树
+
+\`\`\`vis-chart
+{
+ "type": "indented-tree",
+ "data": {
+ "name": "导航菜单",
+ "children": [
+ { "name": "首页" },
+ {
+ "name": "产品",
+ "children": [
+ {
+ "name": "产品分类1",
+ "children": [{ "name": "产品1-1" }, { "name": "产品1-2" }]
+ },
+ {
+ "name": "产品分类2",
+ "children": [{ "name": "产品2-1" }, { "name": "产品2-2" }]
+ }
+ ]
+ },
+ {
+ "name": "关于我们",
+ "children": [{ "name": "公司简介" }, { "name": "团队介绍" }]
+ },
+ {
+ "name": "服务",
+ "children": [{ "name": "咨询服务" }, { "name": "技术支持" }]
+ },
+ { "name": "联系我们" }
+ ]
+ }
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.IndentedTree]: IndentedTree },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/IndentedTree/index.md b/src/IndentedTree/index.md
new file mode 100644
index 0000000..84e6ef1
--- /dev/null
+++ b/src/IndentedTree/index.md
@@ -0,0 +1,47 @@
+---
+order: 5
+group:
+ order: 3
+ title: 关系图
+demo: { cols: 2 }
+---
+
+# IndentedTree 缩进树
+
+缩进树,用于直观地展示层级结构和父子关系。
+
+## 代码演示
+
+单独使用
+使用 Markdown 协议
+
+## Spec
+
+```json
+{
+ "type": "indented-tree",
+ "data": {
+ "name": "node1",
+ "children": [
+ { "name": "node 1-1", "children": [{ "name": "node 1-1-1" }] },
+ { "name": "node 1-2" },
+ { "name": "node 1-3" }
+ ]
+ }
+}
+```
+
+## API
+
+### IndentedTreeProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | ------------------ | -------- | ------ | ---- |
+| data | `IndentedTreeData` | 是 | - | 数据 |
+
+### IndentedTreeData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| -------- | -------------------- | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| name | `string` | 是 | - | 节点的名称,用于显示在思维导图的节点上 |
+| children | `IndentedTreeData[]` | 否 | - | 当前节点的子节点集合。如果当前节点没有子节点,该字段可以省略。每个子节点本身也是一个 `IndentedTreeData` 对象,这意味着它可以包含自己的子节点,从而递归地构建出一个多层次的树状结构 |
diff --git a/src/IndentedTree/index.tsx b/src/IndentedTree/index.tsx
new file mode 100644
index 0000000..5372e18
--- /dev/null
+++ b/src/IndentedTree/index.tsx
@@ -0,0 +1,39 @@
+import type { G6, IndentedTreeOptions } from '@ant-design/graphs';
+import { IndentedTree as ADCIndentedTree } from '@ant-design/graphs';
+import React, { useMemo } from 'react';
+import { useGraphConfig } from '../ConfigProvider/hooks';
+import type { TreeGraphProps } from '../types';
+import { visTreeData2GraphData } from '../utils/graph';
+
+export interface IndentedTreeProps extends TreeGraphProps {}
+
+const defaultConfig: IndentedTreeOptions = {
+ type: 'linear',
+ autoFit: 'view',
+ autoResize: true,
+ node: { animation: { update: false, translate: false } },
+ edge: { animation: { update: false, translate: false } },
+ transforms: (prev) => [
+ ...prev.filter(
+ (transform) => (transform as G6.BaseTransformOptions).type !== 'collapse-expand-react-node',
+ ),
+ {
+ ...(prev.find(
+ (transform) => (transform as G6.BaseTransformOptions).type === 'collapse-expand-react-node',
+ ) as G6.BaseTransformOptions),
+ enable: true,
+ },
+ ],
+};
+
+const IndentedTree: React.FC = (props) => {
+ const { data: propsData, ...restProps } = props;
+
+ const data = useMemo(() => visTreeData2GraphData(propsData), [propsData]);
+
+ const config = useGraphConfig('IndentedTree', defaultConfig, restProps);
+
+ return ;
+};
+
+export default IndentedTree;
diff --git a/src/Line/demos/category.tsx b/src/Line/demos/category.tsx
new file mode 100644
index 0000000..b6eac66
--- /dev/null
+++ b/src/Line/demos/category.tsx
@@ -0,0 +1,114 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Line, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+以下是为你绘制的一个折线图
+
+\`\`\`vis-chart
+{
+ "type": "line",
+ "data": [
+ {
+ "time": "1974",
+ "value": 107,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1974",
+ "value": 208,
+ "group": "Renewables"
+ },
+ {
+ "time": "1974",
+ "value": 356,
+ "group": "Fossil fuels"
+ },
+ {
+ "time": "1975",
+ "value": 173,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1975",
+ "value": 415,
+ "group": "Renewables"
+ },
+ {
+ "time": "1975",
+ "value": 364,
+ "group": "Fossil fuels"
+ },
+ {
+ "time": "1976",
+ "value": 117,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1976",
+ "value": 220,
+ "group": "Renewables"
+ },
+ {
+ "time": "1976",
+ "value": 373,
+ "group": "Fossil fuels"
+ },
+ {
+ "time": "1977",
+ "value": 122,
+ "group": "Gas flaring"
+ },
+ {
+ "time": "1977",
+ "value": 225,
+ "group": "Renewables"
+ },
+ {
+ "time": "1977",
+ "value": 382,
+ "group": "Fossil fuels"
+ }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Line]: Line },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Line/demos/common.tsx b/src/Line/demos/common.tsx
new file mode 100644
index 0000000..17599cf
--- /dev/null
+++ b/src/Line/demos/common.tsx
@@ -0,0 +1,20 @@
+import { Line } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ { time: '1991', value: 3 },
+ { time: '1992', value: 4 },
+ { time: '1993', value: 3.5 },
+ { time: '1994', value: 5 },
+ { time: '1995', value: 4.9 },
+ { time: '1996', value: 6 },
+ { time: '1997', value: 7 },
+ { time: '1998', value: 9 },
+ { time: '1999', value: 13 },
+];
+
+export default () => {
+ return (
+
+ );
+};
diff --git a/src/Line/demos/markdown.tsx b/src/Line/demos/markdown.tsx
new file mode 100644
index 0000000..e1a1a1d
--- /dev/null
+++ b/src/Line/demos/markdown.tsx
@@ -0,0 +1,55 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Line, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个折线图
+
+\`\`\`vis-chart
+{
+ "type": "line",
+ "data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}],
+ "axisXTitle": "year",
+ "axisYTitle": "GDP"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Line]: Line },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Line/index.md b/src/Line/index.md
new file mode 100644
index 0000000..99f9929
--- /dev/null
+++ b/src/Line/index.md
@@ -0,0 +1,51 @@
+---
+order: 1
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# Line 折线图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+多折线图
+
+## Spec
+
+```json
+{
+ "type": "line",
+ "data": [
+ { "time": 2018, "value": 91.9 },
+ { "time": 2019, "value": 99.1 },
+ { "time": 2020, "value": 101.6 },
+ { "time": 2021, "value": 114.4 },
+ { "time": 2022, "value": 121 }
+ ]
+}
+```
+
+## API
+
+### LineProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | -------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | LineDataItem[] | 是 | - | 数据 |
+| title | string | 否 | - | 图表的标题 |
+| axisXTitle | string | 否 | - | x 轴的标题 |
+| axisYTitle | string | 否 | - | y 轴的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### LineDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------ | -------- | ------ | -------------- |
+| time | string | 是 | - | 数据的时序名称 |
+| value | number | 是 | - | 数据的值 |
+| group | string | 否 | - | 数据分组名称 |
diff --git a/src/Line/index.tsx b/src/Line/index.tsx
new file mode 100644
index 0000000..46b6535
--- /dev/null
+++ b/src/Line/index.tsx
@@ -0,0 +1,41 @@
+import type { LineConfig } from '@ant-design/plots';
+import { Line as ADCLine } from '@ant-design/plots';
+import { get } from 'lodash';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+export type LineDataItem = {
+ time: string | number;
+ value: number;
+ [key: string]: string | number;
+};
+
+export type LineProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: LineConfig): LineConfig => {
+ const { data, xField = 'time', yField = 'value' } = props;
+ const hasGroupField = get(data, '[0].group') !== undefined;
+ const axisYTitle = get(props, 'axis.y.title');
+
+ return {
+ xField,
+ yField,
+ colorField: hasGroupField ? 'group' : undefined,
+ tooltip: (d) => {
+ const tooltipName = axisYTitle || d[xField as string];
+ return {
+ name: tooltipName,
+ value: d[yField as string],
+ };
+ },
+ };
+};
+
+const Line = (props: LineProps) => {
+ const config = usePlotConfig('Line', defaultConfig, props);
+
+ return ;
+};
+
+export default Line;
diff --git a/src/Map/data.json b/src/Map/data.json
new file mode 100644
index 0000000..dff60af
--- /dev/null
+++ b/src/Map/data.json
@@ -0,0 +1,305 @@
+{
+ "days": "4天",
+ "title": "杭州西湖赏桂之旅",
+ "city": "杭州",
+ "summary": "杭州的秋天,桂花盛开,香气四溢。杭小微为您推荐四条赏桂路线,让您在湖山之间,近距离感受杭州的浪漫。",
+ "locations": [
+ "石屋洞",
+ "满觉陇",
+ "杨梅岭",
+ "理安寺",
+ "九溪烟树",
+ "飞来峰",
+ "灵隐寺",
+ "天竺三寺",
+ "杭州植物园",
+ "杭州花圃",
+ "苏堤",
+ "孤山",
+ "虎跑公园",
+ "玉皇飞云",
+ "长桥公园"
+ ],
+ "thumbnail": "http://store.is.autonavi.com/showpic/a8638ebc8d3b4a388974b0c9cc6051f3",
+ "details": [
+ {
+ "day": "第1天",
+ "id": 0,
+ "title": "第1天",
+ "detail": [
+ {
+ "name": "石屋洞",
+ "description": "一座遍植桂花的江南庭院,赏景品茗,美到无法言说。",
+ "id": "B0G3RHAU1A",
+ "location": [120.130638, 30.219835],
+ "cityname": "杭州市",
+ "address": "一座遍植桂花的江南庭院,赏景品茗,美到无法言说。",
+ "type": "风景名胜;风景名胜相关;旅游景点",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/a8638ebc8d3b4a388974b0c9cc6051f3",
+ "https://aos-comment.amap.com/B0G3RHAU1A/comment/4081b98294905eb6c4f03fb3a15bde46_2048_2048_80.jpg",
+ "https://aos-comment.amap.com/B0G3RHAU1A/comment/245E4E3C_DB29_41A8_84E2_88A6C319359F_L0_001_2048_1536__1668077914572_a8508018.jpg"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.7",
+ "cost": [],
+ "title": "石屋洞"
+ },
+ {
+ "name": "满觉陇",
+ "description": "赏桂“顶流”目的地,各类珍稀品种的桂花组成了“桂花瀑布”,走进其中就能发现新西湖十景“满陇桂雨”这个名字的妙处。",
+ "id": "BV11343845",
+ "location": [120.128125, 30.219386],
+ "cityname": "杭州市",
+ "address": "赏桂“顶流”目的地,各类珍稀品种的桂花组成了“桂花瀑布”,走进其中就能发现新西湖十景“满陇桂雨”这个名字的妙处。",
+ "type": "交通设施服务;公交车站;公交车站相关",
+ "photos": [
+ "https://aos-comment.amap.com/BV11343845/comment/3d926a998413f8dd339eb27f9f6d6ae2_2048_2048_80.jpg",
+ "https://aos-comment.amap.com/BV11343845/comment/1945875b7d33194f96a6328fef5cb7ce_2048_2048_80.jpg"
+ ],
+ "openTime": "全天开放",
+ "rating": [],
+ "cost": [],
+ "title": "满觉陇"
+ },
+ {
+ "name": "杨梅岭",
+ "description": "精致的打卡点,散落着多个博物馆、村落,有象征“事事如意”的网红柿子树、可以俯瞰西湖的秋千,更有经典的烟霞三洞,带来第一缕桂香的少儿公园。",
+ "id": "B023B0ACLZ",
+ "location": [120.118362, 30.217175],
+ "cityname": "杭州市",
+ "address": "精致的打卡点,散落着多个博物馆、村落,有象征“事事如意”的网红柿子树、可以俯瞰西湖的秋千,更有经典的烟霞三洞,带来第一缕桂香的少儿公园。",
+ "type": "风景名胜;风景名胜;国家级景点",
+ "photos": [
+ "https://aos-comment.amap.com/B023B0ACLZ/comment/0e75682f46fb455890c3ce646897201c_2048_2048_80.jpg",
+ "https://aos-comment.amap.com/B023B0ACLZ/comment/fee22ab4ae625b9faa7019f7fedbd872_2048_2048_80.jpg",
+ "https://aos-comment.amap.com/B023B0ACLZ/headerImg/6050a39568530c5fb095d8025248d3f3_2048_2048_80.jpg"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.8",
+ "cost": [],
+ "title": "杨梅岭"
+ },
+ {
+ "name": "理安寺",
+ "description": "",
+ "id": "B0FFFDE5LV",
+ "location": [120.112958, 30.207319],
+ "cityname": "杭州市",
+ "address": "",
+ "type": "风景名胜;风景名胜;寺庙道观",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/b5a0fc92c07383efeab973714e5d66ce",
+ "https://store.is.autonavi.com/showpic/0BC01430C60248FDB8C3E56C64926804",
+ "http://store.is.autonavi.com/showpic/30e9cda185aef570d8d7c5749e229785"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.7",
+ "cost": [],
+ "title": "理安寺"
+ },
+ {
+ "name": "九溪烟树",
+ "description": "秋天的九溪也堪称杭城徒步“天花板”,行走在潺潺作响的溪流之间,感受微风卷起一路的隐隐花香,身心都得到了治愈。",
+ "id": "B0FFHMH4J8",
+ "location": [120.11335, 30.202395],
+ "cityname": "杭州市",
+ "address": "秋天的九溪也堪称杭城徒步“天花板”,行走在潺潺作响的溪流之间,感受微风卷起一路的隐隐花香,身心都得到了治愈。",
+ "type": "风景名胜;风景名胜;风景名胜",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/a79a5a8c6a341147c3c47d6a9d53db84",
+ "http://store.is.autonavi.com/showpic/66058afc1e0a15e1dd96057a384fe73c",
+ "http://store.is.autonavi.com/showpic/b0cf38d0d35223936a0c7df11c9be2c5"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.9",
+ "cost": [],
+ "title": "九溪烟树"
+ }
+ ]
+ },
+ {
+ "day": "第2天",
+ "id": 1,
+ "title": "第2天",
+ "detail": [
+ {
+ "name": "飞来峰",
+ "description": "古代名刹,从飞来峰进入,便可以见到这一路风光。",
+ "id": "B023B09DPT",
+ "location": [120.100549, 30.236875],
+ "cityname": "杭州市",
+ "address": "古代名刹,从飞来峰进入,便可以见到这一路风光。",
+ "type": "地名地址信息;自然地名;山",
+ "photos": [
+ "https://aos-comment.amap.com/B023B09DPT/comment/f3a976ca78db5c7214084ca101398a17_2048_2048_80.jpg",
+ "https://aos-comment.amap.com/B023B09DPT/comment/ef271bff0902bceed60aa6471fc6da36_2048_2048_80.jpg",
+ "https://aos-comment.amap.com/B023B09DPT/comment/a61e430e8c7b5b3e11a866aa4af55c68_2048_2048_80.jpg"
+ ],
+ "openTime": "全天开放",
+ "rating": [],
+ "cost": [],
+ "title": "飞来峰"
+ },
+ {
+ "name": "灵隐寺",
+ "description": "",
+ "id": "B023B02842",
+ "location": [120.101406, 30.240826],
+ "cityname": "杭州市",
+ "address": "",
+ "type": "风景名胜;风景名胜;国家级景点",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/4aa0a6a1b6ee72c9833441f363cbb43a",
+ "http://store.is.autonavi.com/showpic/c7cccce2249dbb7aa013ee0b06888e9d",
+ "http://store.is.autonavi.com/showpic/69e2dbe5322886d53ed57057e9ab5514"
+ ],
+ "openTime": "全天开放",
+ "rating": "5.0",
+ "cost": "75.00",
+ "title": "灵隐寺"
+ },
+ {
+ "name": "天竺三寺",
+ "description": "通称上天竺法喜讲寺、中天竺法净禅寺、三天竺法镜讲寺,每处点位自成风景。",
+ "id": "B023B1D0D7",
+ "location": [120.105337, 30.236818],
+ "cityname": "杭州市",
+ "address": "通称上天竺法喜讲寺、中天竺法净禅寺、三天竺法镜讲寺,每处点位自成风景。",
+ "type": "风景名胜;风景名胜;寺庙道观",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/939552e3d75b06078ac793d45d07e9be",
+ "http://store.is.autonavi.com/showpic/267df72456caeb1ebb24221a3583384e",
+ "http://store.is.autonavi.com/showpic/8ccaa44716883c2a563ba57180af6922"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.3",
+ "cost": [],
+ "title": "天竺三寺"
+ }
+ ]
+ },
+ {
+ "day": "第3天",
+ "id": 2,
+ "title": "第3天",
+ "detail": [
+ {
+ "name": "杭州植物园",
+ "description": "一千余株桂花,品种多、树龄长、规模大,还有一株丹桂王,让植物爱好者流连忘返。",
+ "id": "B023B09LTB",
+ "location": [120.116979, 30.252876],
+ "cityname": "杭州市",
+ "address": "一千余株桂花,品种多、树龄长、规模大,还有一株丹桂王,让植物爱好者流连忘返。",
+ "type": "风景名胜;公园广场;植物园",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/cd40a50a625f3772ba2bbc115422ae5a",
+ "http://store.is.autonavi.com/showpic/e29859ae9a1be77e84eb86d56f7ff4d2",
+ "http://store.is.autonavi.com/showpic/f207a7316fd5da51239a570d8f484158"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.9",
+ "cost": "10.00",
+ "title": "杭州植物园"
+ },
+ {
+ "name": "杭州花圃",
+ "description": "一进正门就能闻见桂花的香气,金桂夹杂在大片的银桂之间,花圃内也有专门的赏桂景点“金秋桂满”,桂花的数量和质量都非常可观。",
+ "id": "B023B09ORY",
+ "location": [120.127654, 30.245663],
+ "cityname": "杭州市",
+ "address": "一进正门就能闻见桂花的香气,金桂夹杂在大片的银桂之间,花圃内也有专门的赏桂景点“金秋桂满”,桂花的数量和质量都非常可观。",
+ "type": "风景名胜;风景名胜;风景名胜",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/8df57a06a54b587dbaee20a63b03eb4c",
+ "http://store.is.autonavi.com/showpic/51f27d9e13ddb1edae4c2b7568d3732c",
+ "http://store.is.autonavi.com/showpic/787bab72a3971313f75384bf62f29e9c"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.8",
+ "cost": [],
+ "title": "杭州花圃"
+ },
+ {
+ "name": "苏堤",
+ "description": "欣赏西湖美景的同时,沿途也有桂花可寻。",
+ "id": "B0FFGQ6OHF",
+ "location": [120.135764, 30.251448],
+ "cityname": "杭州市",
+ "address": "欣赏西湖美景的同时,沿途也有桂花可寻。",
+ "type": "地名地址信息;交通地名;道路名",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/b211a305fb6e6bee83f281954db59c54",
+ "http://store.is.autonavi.com/showpic/e21048b71a5aacdd5fd9c64ecfd1cedd",
+ "http://store.is.autonavi.com/showpic/287332c8a30aa8eeeabfc2ce7ff45b6e"
+ ],
+ "openTime": "全天开放",
+ "rating": [],
+ "cost": [],
+ "title": "苏堤"
+ }
+ ]
+ },
+ {
+ "day": "第4天",
+ "id": 3,
+ "title": "第4天",
+ "detail": [
+ {
+ "name": "虎跑公园",
+ "description": "曲径深幽的江南园林中,有着馥郁芬芳的百年老桂树,一走近就香气扑鼻。",
+ "id": "B023B08Q06",
+ "location": [120.130095, 30.207505],
+ "cityname": "杭州市",
+ "address": "曲径深幽的江南园林中,有着馥郁芬芳的百年老桂树,一走近就香气扑鼻。",
+ "type": "风景名胜;公园广场;公园",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/806ddeb10e5d81026f9a380b153686f0",
+ "http://store.is.autonavi.com/showpic/64221359470ab89bf1994cb7f0c32df6",
+ "http://store.is.autonavi.com/showpic/7137b9d90ed98fe2cf77cd3b2cbd43d2"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.8",
+ "cost": "15.00",
+ "title": "虎跑公园"
+ },
+ {
+ "name": "玉皇飞云",
+ "description": "上山路上每隔几米就能看见桂花树,秋高气爽的天气里爬到半山腰,即可一览八卦田风光。",
+ "id": "B0FFFDSY6F",
+ "location": [120.145323, 30.214993],
+ "cityname": "杭州市",
+ "address": "上山路上每隔几米就能看见桂花树,秋高气爽的天气里爬到半山腰,即可一览八卦田风光。",
+ "type": "风景名胜;风景名胜;风景名胜",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/0209116fc1ba3159757fb0dc17deac21",
+ "http://store.is.autonavi.com/showpic/bce42b9835f919dbcec73dc0aba4d001",
+ "http://store.is.autonavi.com/showpic/30e26d0cd77cae6a7b3016142880d5d5"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.2",
+ "cost": [],
+ "title": "玉皇飞云"
+ },
+ {
+ "name": "长桥公园",
+ "description": "有历史记忆也有风光美景,有口皆碑的绝美落日观赏地,站在桂花树下看波光粼粼的湖面上,一轮红日逐渐下沉,一路上的疲惫也一定会消去几分。",
+ "id": "B023B01EAA",
+ "location": [120.155057, 30.232985],
+ "cityname": "杭州市",
+ "address": "有历史记忆也有风光美景,有口皆碑的绝美落日观赏地,站在桂花树下看波光粼粼的湖面上,一轮红日逐渐下沉,一路上的疲惫也一定会消去几分。",
+ "type": "风景名胜;公园广场;公园",
+ "photos": [
+ "http://store.is.autonavi.com/showpic/74b51d5141a58ac662b09957c7db6713",
+ "http://store.is.autonavi.com/showpic/d6349365855846ad6c37600ddf1f4083",
+ "http://store.is.autonavi.com/showpic/40b3d10b36daae77717c820479bd8d55"
+ ],
+ "openTime": "全天开放",
+ "rating": "4.8",
+ "cost": [],
+ "title": "长桥公园"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/Map/index.md b/src/Map/index.md
new file mode 100644
index 0000000..2e99e71
--- /dev/null
+++ b/src/Map/index.md
@@ -0,0 +1,27 @@
+---
+order: 1
+group:
+ order: 2
+ title: 地图
+debug: true
+---
+
+### 代码示例
+
+```jsx
+import React, { useEffect, useMemo } from 'react';
+import { Map } from '@antv/gpt-vis';
+
+export default () => {
+ const mapConfig = {
+ mapType: 'light',
+ scale: 17,
+ longitude: 120.130638,
+ latitude: 30.219835,
+ skew: 0,
+ rotate: 0,
+ };
+
+ return ;
+};
+```
diff --git a/src/Map/index.tsx b/src/Map/index.tsx
new file mode 100644
index 0000000..2bf0821
--- /dev/null
+++ b/src/Map/index.tsx
@@ -0,0 +1,49 @@
+import type { ILayer, Scene } from '@antv/l7';
+import { LarkMap } from '@antv/larkmap';
+import React, { type FC } from 'react';
+import type { BaseMapProps } from '../types';
+import { formatMapStyle, setMapContext, setMapView, setMarkers, setPolyline } from '../utils/map';
+
+export type MapProps = Omit, 'data'>;
+
+const Map: FC = (props) => {
+ const { className, containerStyle, children } = props;
+ const allLayers: ILayer[] = [];
+ const mapConfig = formatMapStyle(props);
+
+ const onSceneLoaded = async (scene: Scene) => {
+ // 初始地图视野
+ setMapView(props, scene);
+ // 初始化地图资源和状态
+ await setMapContext(props, scene);
+
+ // 添加线图层
+ if (props.polyline) {
+ const polylineLayer = setPolyline(props.polyline || []);
+ allLayers.push(...polylineLayer);
+ }
+
+ // 添加标记
+ if (props.markers) {
+ const markerLayer = setMarkers(props.markers || []);
+ allLayers.push(...markerLayer);
+ }
+
+ allLayers.forEach((item) => {
+ scene.addLayer(item);
+ });
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default Map;
diff --git a/src/MindMap/demos/common.tsx b/src/MindMap/demos/common.tsx
new file mode 100644
index 0000000..57db742
--- /dev/null
+++ b/src/MindMap/demos/common.tsx
@@ -0,0 +1,26 @@
+import { MindMap } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = {
+ name: '项目计划',
+ children: [
+ {
+ name: '研究阶段',
+ children: [{ name: '市场调研' }, { name: '技术可行性分析' }],
+ },
+ {
+ name: '设计阶段',
+ children: [{ name: '产品功能确定' }, { name: 'UI 设计' }],
+ },
+ {
+ name: '开发阶段',
+ children: [{ name: '编写代码' }, { name: '单元测试' }],
+ },
+ {
+ name: '测试阶段',
+ children: [{ name: '功能测试' }, { name: '性能测试' }],
+ },
+ ],
+};
+
+export default () => ;
diff --git a/src/MindMap/demos/markdown.tsx b/src/MindMap/demos/markdown.tsx
new file mode 100644
index 0000000..c0cb9b5
--- /dev/null
+++ b/src/MindMap/demos/markdown.tsx
@@ -0,0 +1,75 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, MindMap, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个思维导图
+
+\`\`\`vis-chart
+{
+ "type": "mind-map",
+ "data": {
+ "name": "台风形成的因素",
+ "children": [
+ {
+ "name": "气象条件",
+ "children": [
+ { "name": "温暖的海水" },
+ { "name": "气压分布" },
+ { "name": "湿度水平" },
+ { "name": "风的切变" }
+ ]
+ },
+ {
+ "name": "地理环境",
+ "children": [
+ { "name": "大陆架的形状与深度" },
+ { "name": "海洋暖流的分布" },
+ { "name": "热带地区的气候特征" },
+ { "name": "岛屿的影响" }
+ ]
+ }
+ ]
+ }
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.MindMap]: MindMap },
+ style: { width: 500 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/MindMap/index.md b/src/MindMap/index.md
new file mode 100644
index 0000000..6707ed0
--- /dev/null
+++ b/src/MindMap/index.md
@@ -0,0 +1,51 @@
+---
+order: 1
+group:
+ order: 3
+ title: 关系图
+---
+
+# MindMap 思维导图
+
+思维导图,直观地展示信息的层次结构和关联关系。
+
+## 代码演示
+
+### 单独使用
+
+
+
+### 使用 Markdown 协议
+
+
+
+## Spec
+
+```json
+{
+ "type": "mind-map",
+ "data": {
+ "name": "main topic",
+ "children": [
+ { "name": "topic 1", "children": [{ "name": "sub topic 1-1" }] },
+ { "name": "topic 2" },
+ { "name": "topic 3" }
+ ]
+ }
+}
+```
+
+## API
+
+### MindMapProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | ------------- | -------- | ------ | ---- |
+| data | `MindMapData` | 是 | - | 数据 |
+
+### MindMapData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| -------- | --------------- | -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| name | `string` | 是 | - | 节点的名称,用于显示在思维导图的节点上 |
+| children | `MindMapData[]` | 否 | - | 当前节点的子节点集合。如果当前节点没有子节点,该字段可以省略。每个子节点本身也是一个 `MindMapData` 对象,这意味着它可以包含自己的子节点,从而递归地构建出一个多层次的树状结构 |
diff --git a/src/MindMap/index.tsx b/src/MindMap/index.tsx
new file mode 100644
index 0000000..9d6b759
--- /dev/null
+++ b/src/MindMap/index.tsx
@@ -0,0 +1,41 @@
+import type { G6, MindMapOptions } from '@ant-design/graphs';
+import { MindMap as ADCMindMap } from '@ant-design/graphs';
+import type { FC } from 'react';
+import React, { useMemo } from 'react';
+import { useGraphConfig } from '../ConfigProvider/hooks';
+import type { TreeGraphProps } from '../types';
+import { visTreeData2GraphData } from '../utils/graph';
+
+const defaultConfig: MindMapOptions = {
+ type: 'boxed',
+ autoFit: 'view',
+ autoResize: true,
+ padding: 2,
+ node: { animation: { translate: false, update: false } },
+ edge: { animation: { translate: false, update: false } },
+ transforms: (prev) => [
+ ...prev.filter(
+ (transform) => (transform as G6.BaseTransformOptions).type !== 'collapse-expand-react-node',
+ ),
+ {
+ ...(prev.find(
+ (transform) => (transform as G6.BaseTransformOptions).type === 'collapse-expand-react-node',
+ ) as G6.BaseTransformOptions),
+ enable: true,
+ },
+ ],
+};
+
+export interface MindMapProps extends TreeGraphProps {}
+
+const MindMap: FC = (props) => {
+ const { data: propsData, ...restProps } = props;
+
+ const data = useMemo(() => visTreeData2GraphData(propsData), [propsData]);
+
+ const config = useGraphConfig('MindMap', defaultConfig, restProps);
+
+ return ;
+};
+
+export default MindMap;
diff --git a/src/NetworkGraph/demos/common.tsx b/src/NetworkGraph/demos/common.tsx
new file mode 100644
index 0000000..5032f73
--- /dev/null
+++ b/src/NetworkGraph/demos/common.tsx
@@ -0,0 +1,19 @@
+import { NetworkGraph } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = {
+ nodes: [
+ { name: '哈利·波特' },
+ { name: '赫敏·格兰杰' },
+ { name: '罗恩·韦斯莱' },
+ { name: '伏地魔' },
+ ],
+ edges: [
+ { source: '哈利·波特', target: '赫敏·格兰杰', name: '朋友' },
+ { source: '哈利·波特', target: '罗恩·韦斯莱', name: '朋友' },
+ { source: '哈利·波特', target: '伏地魔', name: '敌人' },
+ { source: '伏地魔', target: '哈利·波特', name: '试图杀死' },
+ ],
+};
+
+export default () => ;
diff --git a/src/NetworkGraph/demos/markdown.tsx b/src/NetworkGraph/demos/markdown.tsx
new file mode 100644
index 0000000..e599b7b
--- /dev/null
+++ b/src/NetworkGraph/demos/markdown.tsx
@@ -0,0 +1,66 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, NetworkGraph, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个网络图
+
+\`\`\`vis-chart
+{
+ "type": "network-graph",
+ "data": {
+ "nodes": [
+ { "name": "哈利·波特" },
+ { "name": "赫敏·格兰杰" },
+ { "name": "罗恩·韦斯莱" },
+ { "name": "伏地魔" }
+ ],
+ "edges": [
+ { "source": "哈利·波特", "target": "赫敏·格兰杰", "name": "朋友" },
+ { "source": "哈利·波特", "target": "罗恩·韦斯莱", "name": "朋友" },
+ { "source": "哈利·波特", "target": "伏地魔", "name": "敌人" },
+ { "source": "伏地魔", "target": "哈利·波特", "name": "试图杀死" }
+ ]
+ }
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.NetworkGraph]: NetworkGraph },
+ style: { width: 400 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/NetworkGraph/index.md b/src/NetworkGraph/index.md
new file mode 100644
index 0000000..df22035
--- /dev/null
+++ b/src/NetworkGraph/index.md
@@ -0,0 +1,61 @@
+---
+order: 1
+group:
+ order: 3
+ title: 关系图
+---
+
+# NetworkGraph 网络图
+
+网络图,又名力导向图,用于展示节点(实体)之间的关系(边),直观地表示复杂的网络结构。
+
+## 代码演示
+
+### 单独使用
+
+
+
+### 使用 Markdown 协议
+
+
+
+## Spec
+
+```json
+{
+ "type": "network-graph",
+ "data": {
+ "nodes": [{ "name": "node1" }, { "name": "node2" }],
+ "edges": [{ "source": "node1", "target": "node2", "name": "edge1" }]
+ }
+}
+```
+
+## API
+
+### NetworkGraphProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | ------------------ | -------- | ------ | ---- |
+| data | `NetworkGraphData` | 是 | - | 数据 |
+
+### NetworkGraphData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | -------------------- | -------- | ------ | ---------------------------------------------- |
+| nodes | `NetworkGraphNode[]` | 是 | - | 网络图中的节点数组,每个节点表示一个实体 |
+| edges | `NetworkGraphEdge[]` | 是 | - | 网络图中的边数组,每条边表示两个节点之间的关系 |
+
+### NetworkGraphNode
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | -------- | -------- | ------ | ---------------------------------- |
+| name | `string` | 是 | - | 节点的名称,必须唯一,用于标识节点 |
+
+### NetworkGraphEdge
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ------ | -------- | -------- | ------ | -------------------------------------------------------- |
+| source | `string` | 是 | - | 边的起始节点名称,指向 `NetworkGraphNode` 的 `name` 属性 |
+| target | `string` | 是 | - | 边的目标节点名称,指向 `NetworkGraphNode` 的 `name` 属性 |
+| name | `string` | 是 | - | 边的名称,用于标识边 |
diff --git a/src/NetworkGraph/index.tsx b/src/NetworkGraph/index.tsx
new file mode 100644
index 0000000..0b043d2
--- /dev/null
+++ b/src/NetworkGraph/index.tsx
@@ -0,0 +1,48 @@
+import type { NetworkGraphOptions } from '@ant-design/graphs';
+import { NetworkGraph as ADCNetworkGraph } from '@ant-design/graphs';
+import React, { useMemo } from 'react';
+import { useGraphConfig } from '../ConfigProvider/hooks';
+import type { GraphProps } from '../types';
+import { visGraphData2GraphData } from '../utils/graph';
+
+export interface NetworkGraphProps extends GraphProps {}
+
+const defaultConfig: NetworkGraphOptions = {
+ autoResize: true,
+ node: {
+ style: {
+ size: 28,
+ labelFontSize: 10,
+ labelBackground: true,
+ },
+ animation: {
+ enter: false,
+ },
+ },
+ edge: {
+ style: {
+ labelFontSize: 10,
+ labelBackground: true,
+ endArrow: true,
+ },
+ animation: { enter: false },
+ },
+ behaviors: (prev) => [...prev, { key: 'hover-activate', type: 'hover-activate', degree: 1 }],
+ transforms: (prev) => [...prev, 'process-parallel-edges'],
+ layout: {
+ type: 'force',
+ animation: false,
+ },
+};
+
+const NetworkGraph: React.FC = (props) => {
+ const { data: propsData, ...restProps } = props;
+
+ const data = useMemo(() => visGraphData2GraphData(propsData), [propsData]);
+
+ const config = useGraphConfig('NetworkGraph', defaultConfig, restProps);
+
+ return ;
+};
+
+export default NetworkGraph;
diff --git a/src/OrganizationChart/demos/common.tsx b/src/OrganizationChart/demos/common.tsx
new file mode 100644
index 0000000..eb3a9bf
--- /dev/null
+++ b/src/OrganizationChart/demos/common.tsx
@@ -0,0 +1,39 @@
+import { OrganizationChart } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = {
+ name: 'Alice Johnson',
+ description: 'Chief Technology Officer',
+ children: [
+ {
+ name: 'Bob Smith',
+ description: 'Senior Software Engineer',
+ children: [
+ {
+ name: 'Charlie Brown',
+ description: 'Software Engineer',
+ },
+ {
+ name: 'Diana White',
+ description: 'Software Engineer',
+ },
+ ],
+ },
+ {
+ name: 'Eve Black',
+ description: 'IT Support Department Head',
+ children: [
+ {
+ name: 'Frank Green',
+ description: 'IT Support Specialist',
+ },
+ {
+ name: 'Grace Blue',
+ description: 'IT Support Specialist',
+ },
+ ],
+ },
+ ],
+};
+
+export default () => ;
diff --git a/src/OrganizationChart/demos/markdown.tsx b/src/OrganizationChart/demos/markdown.tsx
new file mode 100644
index 0000000..2aaf432
--- /dev/null
+++ b/src/OrganizationChart/demos/markdown.tsx
@@ -0,0 +1,62 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, OrganizationChart, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个组织架构图
+
+\`\`\`vis-chart
+{
+ "type": "organization-chart",
+ "data": {
+ "name": "Eric Joplin",
+ "description": "Chief Executive Officer",
+ "children": [
+ {
+ "name": "Linda Newland",
+ "description": "Chief Executive Assistant"
+ }
+ ]
+ }
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.OrganizationChart]: OrganizationChart },
+ style: { width: 600, height: 250 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/OrganizationChart/index.md b/src/OrganizationChart/index.md
new file mode 100644
index 0000000..4aef93e
--- /dev/null
+++ b/src/OrganizationChart/index.md
@@ -0,0 +1,54 @@
+---
+order: 4
+group:
+ order: 3
+ title: 关系图
+---
+
+# OrganizationChart 组织架构图
+
+组织架构图,用于展示组织内部的层级结构和部门关系。
+
+## 代码演示
+
+### 单独使用
+
+
+
+### 使用 Markdown 协议
+
+
+
+## Spec
+
+```json
+{
+ "type": "organization-chart",
+ "data": {
+ "name": "Eric Joplin",
+ "description": "Chief Executive Officer",
+ "children": [
+ {
+ "name": "Linda Newland",
+ "description": "Chief Executive Assistant"
+ }
+ ]
+ }
+}
+```
+
+## API
+
+### OrganizationChartProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | ----------------------- | -------- | ------ | ---- |
+| data | `OrganizationChartData` | 是 | - | 数据 |
+
+### OrganizationChartData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----------- | ------------------------- | -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| name | `string` | 是 | - | 节点的名称,表示职位或部门的名称,必须唯一 |
+| description | `string` | 否 | - | 节点的描述信息,可以包含职位职责或部门简介等 |
+| children | `OrganizationChartData[]` | 否 | - | 节点数组,表示下级职位或部门。如果当前节点没有子节点,该字段可以省略。每个子节点本身也是一个 `OrganizationChartData` 对象,这意味着它可以包含自己的子节点,从而递归地构建出一个多层次的树状结构 |
diff --git a/src/OrganizationChart/index.tsx b/src/OrganizationChart/index.tsx
new file mode 100644
index 0000000..ffb5009
--- /dev/null
+++ b/src/OrganizationChart/index.tsx
@@ -0,0 +1,68 @@
+import type { G6, OrganizationChartOptions } from '@ant-design/graphs';
+import { OrganizationChart as ADCOrganizationChart, RCNode } from '@ant-design/graphs';
+import React, { useMemo } from 'react';
+import { useGraphConfig } from '../ConfigProvider/hooks';
+import type { TreeGraphProps } from '../types';
+import { visTreeData2GraphData } from '../utils/graph';
+
+const { OrganizationChartNode } = RCNode;
+
+export interface OrganizationChartProps extends TreeGraphProps {}
+
+const defaultConfig: OrganizationChartOptions = {
+ padding: [40, 0, 0, 120],
+ autoFit: 'view',
+ autoResize: true,
+ node: {
+ style: {
+ component: (d: G6.NodeData) => {
+ const isActive = d.states?.includes('active');
+ return (
+
+ );
+ },
+ size: [280, 80],
+ },
+ },
+ edge: {
+ state: {
+ active: {
+ stroke: '#1890ff',
+ halo: false,
+ },
+ },
+ },
+ behaviors: (prev) => [...prev, 'hover-activate-neighbors'],
+ transforms: (prev) => [
+ ...prev.filter((t) => (t as G6.BaseTransformOptions).type !== 'collapse-expand-react-node'),
+ {
+ ...(prev.find(
+ (t) => (t as G6.BaseTransformOptions).type === 'collapse-expand-react-node',
+ ) as G6.BaseTransformOptions),
+ enable: true,
+ iconOffsetY: 24,
+ },
+ ],
+ animation: false,
+};
+
+const OrganizationChart: React.FC = (props) => {
+ const { data: propsData, ...restProps } = props;
+
+ const data = useMemo(() => visTreeData2GraphData(propsData), [propsData]);
+
+ const config = useGraphConfig(
+ 'OrganizationChart',
+ defaultConfig,
+ restProps,
+ );
+
+ return ;
+};
+
+export default OrganizationChart;
diff --git a/src/PathMap/demos/default.tsx b/src/PathMap/demos/default.tsx
new file mode 100644
index 0000000..0506375
--- /dev/null
+++ b/src/PathMap/demos/default.tsx
@@ -0,0 +1,45 @@
+import { PathMap } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ {
+ points: [
+ { longitude: 120.130638, latitude: 30.219835, label: '石屋洞' },
+ { longitude: 120.128125, latitude: 30.219386, label: '满觉陇' },
+ { longitude: 120.118362, latitude: 30.217175, label: '杨梅岭' },
+ { longitude: 120.112958, latitude: 30.207319, label: '理安寺' },
+ { longitude: 120.11335, latitude: 30.202395, label: '九溪烟树' },
+ ],
+ },
+ {
+ points: [
+ { longitude: 120.100549, latitude: 30.236875, label: '飞来峰' },
+ { longitude: 120.101406, latitude: 30.240826, label: '灵隐寺' },
+ { longitude: 120.105337, latitude: 30.236818, label: '天竺三寺' },
+ ],
+ },
+ {
+ points: [
+ { longitude: 120.116979, latitude: 30.252876, label: '杭州植物园' },
+ { longitude: 120.127654, latitude: 30.245663, label: '杭州花圃' },
+ { longitude: 120.135764, latitude: 30.251448, label: '苏堤' },
+ ],
+ },
+ {
+ points: [
+ { longitude: 120.130095, latitude: 30.207505, label: '虎跑公园' },
+ { longitude: 120.145323, latitude: 30.214993, label: '玉皇飞云' },
+ { longitude: 120.155057, latitude: 30.232985, label: '长桥公园' },
+ ],
+ },
+];
+const routeData = data.map((item) => {
+ return {
+ path: item,
+ markers: item.points,
+ };
+});
+
+export default () => {
+ return ;
+};
diff --git a/src/PathMap/demos/line.tsx b/src/PathMap/demos/line.tsx
new file mode 100644
index 0000000..5c3a8ce
--- /dev/null
+++ b/src/PathMap/demos/line.tsx
@@ -0,0 +1,70 @@
+import { PathMap } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ {
+ points: [
+ { longitude: 120.130638, latitude: 30.219835, label: '石屋洞' },
+ { longitude: 120.128125, latitude: 30.219386, label: '满觉陇' },
+ { longitude: 120.118362, latitude: 30.217175, label: '杨梅岭' },
+ { longitude: 120.112958, latitude: 30.207319, label: '理安寺' },
+ { longitude: 120.11335, latitude: 30.202395, label: '九溪烟树' },
+ ],
+ },
+ {
+ points: [
+ { longitude: 120.100549, latitude: 30.236875, label: '飞来峰' },
+ { longitude: 120.101406, latitude: 30.240826, label: '灵隐寺' },
+ { longitude: 120.105337, latitude: 30.236818, label: '天竺三寺' },
+ ],
+ },
+ {
+ points: [
+ { longitude: 120.116979, latitude: 30.252876, label: '杭州植物园' },
+ { longitude: 120.127654, latitude: 30.245663, label: '杭州花圃' },
+ { longitude: 120.135764, latitude: 30.251448, label: '苏堤' },
+ ],
+ },
+ {
+ points: [
+ { longitude: 120.130095, latitude: 30.207505, label: '虎跑公园' },
+ { longitude: 120.145323, latitude: 30.214993, label: '玉皇飞云' },
+ { longitude: 120.155057, latitude: 30.232985, label: '长桥公园' },
+ ],
+ },
+];
+const routeData = data.map((item) => {
+ return {
+ path: item,
+ markers: item.points,
+ };
+});
+
+const markerStyle = {
+ iconPath:
+ 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ufrWTqCNCScAAAAAAAAAAAAADmJ7AQ/original',
+ width: 5,
+ anchorY: 1,
+ label: {
+ color: '#735142',
+ fontSize: 10,
+ bgColor: '#ffffff',
+ },
+};
+
+const pathStyle = {
+ color: '#86f',
+ width: 1,
+ dottedLine: true,
+};
+
+export default () => {
+ return (
+
+ );
+};
diff --git a/src/PathMap/demos/markdown.tsx b/src/PathMap/demos/markdown.tsx
new file mode 100644
index 0000000..def92e9
--- /dev/null
+++ b/src/PathMap/demos/markdown.tsx
@@ -0,0 +1,156 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, ConfigProvider, GPTVis, PathMap, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const markdownContent = `
+ ~~~vis-chart
+ {
+ "type": "path-map",
+ "data": [
+ {
+ "path": {
+ "points": [
+ {
+ "longitude": 120.130638,
+ "latitude": 30.219835,
+ "label": "石屋洞"
+ },
+ {
+ "longitude": 120.128125,
+ "latitude": 30.219386,
+ "label": "满觉陇"
+ },
+ {
+ "longitude": 120.118362,
+ "latitude": 30.217175,
+ "label": "杨梅岭"
+ },
+ {
+ "longitude": 120.112958,
+ "latitude": 30.207319,
+ "label": "理安寺"
+ },
+ {
+ "longitude": 120.11335,
+ "latitude": 30.202395,
+ "label": "九溪烟树"
+ }
+ ]
+ },
+ "markers": [
+ {
+ "longitude": 120.130638,
+ "latitude": 30.219835,
+ "label": "石屋洞"
+ },
+ {
+ "longitude": 120.128125,
+ "latitude": 30.219386,
+ "label": "满觉陇"
+ },
+ {
+ "longitude": 120.118362,
+ "latitude": 30.217175,
+ "label": "杨梅岭"
+ },
+ {
+ "longitude": 120.112958,
+ "latitude": 30.207319,
+ "label": "理安寺"
+ },
+ {
+ "longitude": 120.11335,
+ "latitude": 30.202395,
+ "label": "九溪烟树"
+ }
+ ]
+ },
+ {
+ "path": {
+ "points": [
+ {
+ "longitude": 120.100549,
+ "latitude": 30.236875,
+ "label": "飞来峰"
+ },
+ {
+ "longitude": 120.101406,
+ "latitude": 30.240826,
+ "label": "灵隐寺"
+ },
+ {
+ "longitude": 120.105337,
+ "latitude": 30.236818,
+ "label": "天竺三寺"
+ }
+ ]
+ },
+ "markers": [
+ {
+ "longitude": 120.100549,
+ "latitude": 30.236875,
+ "label": "飞来峰"
+ },
+ {
+ "longitude": 120.101406,
+ "latitude": 30.240826,
+ "label": "灵隐寺"
+ },
+ {
+ "longitude": 120.105337,
+ "latitude": 30.236818,
+ "label": "天竺三寺"
+ }
+ ]
+ }
+ ]
+}
+~~~`;
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.PathMap]: PathMap },
+ style: { width: 500 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/PathMap/index.md b/src/PathMap/index.md
new file mode 100644
index 0000000..85f461b
--- /dev/null
+++ b/src/PathMap/index.md
@@ -0,0 +1,158 @@
+---
+order: 2
+group:
+ order: 2
+ title: 地图
+demo: { cols: 2 }
+---
+
+# PathMap 路径地图
+
+路径地图,用于可视化路径线路数据。
+
+## 代码演示
+
+默认地图
+自定义样式
+
+使用 Markdown 协议
+
+## Spec
+
+```json
+{
+ "type": "path-map",
+ "data": [
+ {
+ "path": {
+ "points": [
+ {
+ "longitude": 120.130638,
+ "latitude": 30.219835,
+ "label": "石屋洞"
+ },
+ {
+ "longitude": 120.128125,
+ "latitude": 30.219386,
+ "label": "满觉陇"
+ },
+ {
+ "longitude": 120.118362,
+ "latitude": 30.217175,
+ "label": "杨梅岭"
+ },
+ {
+ "longitude": 120.112958,
+ "latitude": 30.207319,
+ "label": "理安寺"
+ },
+ {
+ "longitude": 120.11335,
+ "latitude": 30.202395,
+ "label": "九溪烟树"
+ }
+ ]
+ },
+ "markers": [
+ {
+ "longitude": 120.130638,
+ "latitude": 30.219835,
+ "label": "石屋洞"
+ },
+ {
+ "longitude": 120.128125,
+ "latitude": 30.219386,
+ "label": "满觉陇"
+ },
+ {
+ "longitude": 120.118362,
+ "latitude": 30.217175,
+ "label": "杨梅岭"
+ },
+ {
+ "longitude": 120.112958,
+ "latitude": 30.207319,
+ "label": "理安寺"
+ },
+ {
+ "longitude": 120.11335,
+ "latitude": 30.202395,
+ "label": "九溪烟树"
+ }
+ ]
+ },
+ {
+ "path": {
+ "points": [
+ {
+ "longitude": 120.100549,
+ "latitude": 30.236875,
+ "label": "飞来峰"
+ },
+ {
+ "longitude": 120.101406,
+ "latitude": 30.240826,
+ "label": "灵隐寺"
+ },
+ {
+ "longitude": 120.105337,
+ "latitude": 30.236818,
+ "label": "天竺三寺"
+ }
+ ]
+ },
+ "markers": [
+ {
+ "longitude": 120.100549,
+ "latitude": 30.236875,
+ "label": "飞来峰"
+ },
+ {
+ "longitude": 120.101406,
+ "latitude": 30.240826,
+ "label": "灵隐寺"
+ },
+ {
+ "longitude": 120.105337,
+ "latitude": 30.236818,
+ "label": "天竺三寺"
+ }
+ ]
+ }
+ ]
+}
+```
+
+## API
+
+### PathMapProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----------- | ---------- | -------- | ------ | ---------- |
+| data | RoutData[] | 是 | - | 数据 |
+| markerStyle | Marker | 否 | - | 标记点样式 |
+| pathStyle | Polyline | 否 | - | 线样式 |
+
+### RoutData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ------- | ------------ | -------- | ------ | -------- |
+| markers | MarkerData[] | 否 | - | 路径标注 |
+| path | Polyline | 是 | - | 路径轨迹 |
+
+### Polyline
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | -------- | -------- | ------ | -------- |
+| points | LngLat[] | 是 | - | 路径标注 |
+| width | Polyline | 否 | 2 | 轨迹宽度 |
+| color | string | 否 | #16f | 颜色 |
+| dottedLine | boolean | 否 | false | 是否虚线 |
+
+### MarkerData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| --------- | ------ | -------- | ------ | -------- |
+| longitude | number | 是 | - | 经度 |
+| latitude | number | 是 | - | 纬度 |
+| label | number | 是 | - | 文字标注 |
diff --git a/src/PathMap/index.tsx b/src/PathMap/index.tsx
new file mode 100644
index 0000000..3138cb0
--- /dev/null
+++ b/src/PathMap/index.tsx
@@ -0,0 +1,33 @@
+import React, { useMemo, type FC } from 'react';
+import { useMapConfig } from '../ConfigProvider/hooks';
+import type { MapProps } from '../Map';
+import Map from '../Map';
+import type { PathMap as _PathMap, MarkerData, Polyline } from '../types/map';
+import { formatMakerStyle, formatPolylineStyle } from '../utils/map';
+
+export type PathMapProps = MapProps & _PathMap;
+
+const PathMap: FC = (props) => {
+ const { data = [], markerStyle = {}, pathStyle = {}, ...rest } = useMapConfig('PathMap', props);
+
+ const markerdata = useMemo(() => {
+ const markers: MarkerData[] = [];
+ data.forEach((item) => {
+ if (item.markers) {
+ markers.push(...item.markers);
+ }
+ });
+ return formatMakerStyle(markers, markerStyle);
+ }, [data, markerStyle]);
+
+ const linedata = useMemo(() => {
+ const lines = data.map((item) => {
+ return item.path;
+ }) as Polyline[];
+ return formatPolylineStyle(lines, pathStyle);
+ }, [data, pathStyle]);
+
+ return ;
+};
+
+export default PathMap;
diff --git a/src/Pie/demos/common.tsx b/src/Pie/demos/common.tsx
new file mode 100644
index 0000000..cbf18fa
--- /dev/null
+++ b/src/Pie/demos/common.tsx
@@ -0,0 +1,15 @@
+import { Pie } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ { category: '分类一', value: 27 },
+ { category: '分类二', value: 25 },
+ { category: '分类三', value: 18 },
+ { category: '分类四', value: 15 },
+ { category: '分类五', value: 10 },
+ { category: '其他', value: 5 },
+];
+
+export default () => {
+ return ;
+};
diff --git a/src/Pie/demos/markdown.tsx b/src/Pie/demos/markdown.tsx
new file mode 100644
index 0000000..6ba190b
--- /dev/null
+++ b/src/Pie/demos/markdown.tsx
@@ -0,0 +1,61 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Pie, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+以下是为你绘制的一个饼图
+
+\`\`\`vis-chart
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ],
+ "innerRadius": 0.6
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Pie]: Pie },
+ style: { width: 350, height: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Pie/index.md b/src/Pie/index.md
new file mode 100644
index 0000000..968fd79
--- /dev/null
+++ b/src/Pie/index.md
@@ -0,0 +1,47 @@
+---
+order: 3
+group:
+ order: 1
+ title: 统计图
+---
+
+# Pie 饼图
+
+## 代码演示
+
+单独使用
+使用 Markdown 协议
+
+## Spec
+
+```json
+{
+ "type": "pie",
+ "data": [
+ { "category": "分类一", "value": 27 },
+ { "category": "分类二", "value": 25 },
+ { "category": "分类三", "value": 18 },
+ { "category": "分类四", "value": 15 },
+ { "category": "分类五", "value": 10 },
+ { "category": "其他", "value": 5 }
+ ]
+}
+```
+
+## API
+
+### PieProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----------- | ------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | PieDataItem[] | 是 | - | 饼图数据 |
+| title | string | 否 | - | 图表的标题 |
+| innerRadius | number | 否 | - | 饼图内半径,设置为环图 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### PieDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| -------- | ------ | -------- | ------ | -------------- |
+| category | string | 是 | - | 扇形区域的名称 |
+| value | number | 是 | - | 扇形区域的值 |
diff --git a/src/Pie/index.text.tsx b/src/Pie/index.text.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/Pie/index.tsx b/src/Pie/index.tsx
new file mode 100644
index 0000000..1885a2d
--- /dev/null
+++ b/src/Pie/index.tsx
@@ -0,0 +1,65 @@
+import type { PieConfig } from '@ant-design/plots';
+import { Pie as ADCPie } from '@ant-design/plots';
+import { round, sumBy } from 'lodash';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+/**
+ * PieDataItem is the type for each data item in the pie chart.
+ * It includes the name of the sector and its corresponding value.
+ * @param category The name of the sector.
+ * @param value The value of the sector.
+ */
+type PieDataItem = {
+ category: string;
+ value: number;
+ [key: string]: string | number;
+};
+
+/**
+ * the props for the Pie
+ * @param data pie data
+ */
+export type PieProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: PieConfig): PieConfig => {
+ const { data = [], angleField = 'value', colorField = 'category' } = props;
+ const sumValue = sumBy(data, angleField);
+
+ return {
+ angleField,
+ colorField,
+ tooltip: (d) => {
+ return {
+ name: d[colorField as string],
+ value: d[angleField as string],
+ };
+ },
+ label: {
+ position: 'outside',
+ // text: angleField,
+ text: (d: Record) =>
+ `${d[colorField as string]}: ${round((d[angleField as string] / sumValue) * 100, 1)}%`,
+ },
+ legend: {
+ color: {
+ title: false,
+ position: 'top',
+ },
+ },
+ interaction: {
+ elementSelect: {
+ single: true,
+ },
+ },
+ };
+};
+
+const Pie = (props: PieProps) => {
+ const config = usePlotConfig('Pie', defaultConfig, props);
+
+ return ;
+};
+
+export default Pie;
diff --git a/src/PinMap/demos/default.tsx b/src/PinMap/demos/default.tsx
new file mode 100644
index 0000000..8ae4812
--- /dev/null
+++ b/src/PinMap/demos/default.tsx
@@ -0,0 +1,32 @@
+import { PinMap } from '@antv/gpt-vis';
+import React from 'react';
+
+export default () => {
+ const data = [
+ {
+ label: '石屋洞',
+ longitude: 120.130638,
+ latitude: 30.219835,
+ cityname: '杭州市',
+ },
+ {
+ label: '满觉陇',
+ longitude: 120.128125,
+ latitude: 30.219386,
+ },
+ { label: '杨梅岭', longitude: 120.118362, latitude: 30.217175 },
+ { label: '理安寺', longitude: 120.112958, latitude: 30.207319 },
+ { label: '九溪烟树', longitude: 120.11335, latitude: 30.202395 },
+ { label: '飞来峰', longitude: 120.100549, latitude: 30.236875 },
+ { label: '灵隐寺', longitude: 120.101406, latitude: 30.240826 },
+ { label: '天竺三寺', longitude: 120.105337, latitude: 30.236818 },
+ { label: '杭州植物园', longitude: 120.116979, latitude: 30.252876 },
+ { label: '杭州花圃', longitude: 120.127654, latitude: 30.245663 },
+ { label: '苏堤', longitude: 120.135764, latitude: 30.251448 },
+ { label: '虎跑公园', longitude: 120.130095, latitude: 30.207505 },
+ { label: '玉皇飞云', longitude: 120.145323, latitude: 30.214993 },
+ { label: '长桥公园', longitude: 120.155057, latitude: 30.232985 },
+ ];
+
+ return ;
+};
diff --git a/src/PinMap/demos/markdown.tsx b/src/PinMap/demos/markdown.tsx
new file mode 100644
index 0000000..f40734e
--- /dev/null
+++ b/src/PinMap/demos/markdown.tsx
@@ -0,0 +1,81 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, ConfigProvider, GPTVis, PinMap, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+const markdownContent = `
+ ~~~vis-chart
+ {
+ "type": "pin-map",
+ "data": [
+ { "label": "杨梅岭", "longitude": 120.118362, "latitude": 30.217175 },
+ { "label": "理安寺", "longitude": 120.112958, "latitude": 30.207319 },
+ { "label": "九溪烟树", "longitude": 120.11335, "latitude": 30.202395 },
+ { "label": "飞来峰", "longitude": 120.100549, "latitude": 30.236875 },
+ { "label": "灵隐寺", "longitude": 120.101406, "latitude": 30.240826 },
+ { "label": "天竺三寺", "longitude": 120.105337, "latitude": 30.236818 },
+ { "label": "杭州植物园", "longitude": 120.116979, "latitude": 30.252876 },
+ { "label": "杭州花圃", "longitude": 120.127654, "latitude": 30.245663 },
+ { "label": "苏堤", "longitude": 120.135764, "latitude": 30.251448 },
+ { "label": "虎跑公园", "longitude": 120.130095, "latitude": 30.207505 },
+ { "label": "玉皇飞云", "longitude": 120.145323, "latitude": 30.214993 },
+ { "label": "长桥公园", "longitude": 120.155057, "latitude": 30.232985 }
+ ]
+}
+~~~`;
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.PinMap]: PinMap },
+ debug: true,
+ style: { width: 500 },
+});
+
+export default () => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/PinMap/demos/marker.tsx b/src/PinMap/demos/marker.tsx
new file mode 100644
index 0000000..8808a9e
--- /dev/null
+++ b/src/PinMap/demos/marker.tsx
@@ -0,0 +1,48 @@
+import { PinMap } from '@antv/gpt-vis';
+import React from 'react';
+export default () => {
+ const data = [
+ {
+ label: '石屋洞',
+ longitude: 120.130638,
+ latitude: 30.219835,
+ cityname: '杭州市',
+ },
+ {
+ label: '满觉陇',
+ longitude: 120.128125,
+ latitude: 30.219386,
+ },
+ { label: '杨梅岭', longitude: 120.118362, latitude: 30.217175 },
+ { label: '理安寺', longitude: 120.112958, latitude: 30.207319 },
+ { label: '九溪烟树', longitude: 120.11335, latitude: 30.202395 },
+ { label: '飞来峰', longitude: 120.100549, latitude: 30.236875 },
+ { label: '灵隐寺', longitude: 120.101406, latitude: 30.240826 },
+ { label: '天竺三寺', longitude: 120.105337, latitude: 30.236818 },
+ { label: '杭州植物园', longitude: 120.116979, latitude: 30.252876 },
+ { label: '杭州花圃', longitude: 120.127654, latitude: 30.245663 },
+ { label: '苏堤', longitude: 120.135764, latitude: 30.251448 },
+ { label: '虎跑公园', longitude: 120.130095, latitude: 30.207505 },
+ { label: '玉皇飞云', longitude: 120.145323, latitude: 30.214993 },
+ { label: '长桥公园', longitude: 120.155057, latitude: 30.232985 },
+ ];
+ const markerStyle = {
+ iconPath:
+ 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ufrWTqCNCScAAAAAAAAAAAAADmJ7AQ/original',
+ width: 5,
+ anchorY: -1,
+ label: {
+ color: '#735142',
+ fontSize: 10,
+ bgColor: '#ffffff',
+ },
+ };
+
+ return (
+
+ );
+};
diff --git a/src/PinMap/index.md b/src/PinMap/index.md
new file mode 100644
index 0000000..ff6dbd7
--- /dev/null
+++ b/src/PinMap/index.md
@@ -0,0 +1,58 @@
+---
+order: 1
+group:
+ order: 2
+ title: 地图
+demo: { cols: 2 }
+---
+
+# PinMap 点标注地图
+
+点标注地图,用于可视化展示 POI 点位数据。
+
+## 代码演示
+
+默认地图
+自定义样式
+
+使用 Markdown 协议
+
+## Spec
+
+```js
+{
+ "type": "pin-map",
+ "data": [
+ { label: '杨梅岭', longitude: 120.118362, latitude: 30.217175 },
+ { label: '理安寺', longitude: 120.112958, latitude: 30.207319 },
+ { label: '九溪烟树', longitude: 120.11335, latitude: 30.202395 },
+ { label: '飞来峰', longitude: 120.100549, latitude: 30.236875 },
+ { label: '灵隐寺', longitude: 120.101406, latitude: 30.240826 },
+ { label: '天竺三寺', longitude: 120.105337, latitude: 30.236818 },
+ { label: '杭州植物园', longitude: 120.116979, latitude: 30.252876 },
+ { label: '杭州花圃', longitude: 120.127654, latitude: 30.245663 },
+ { label: '苏堤', longitude: 120.135764, latitude: 30.251448 },
+ { label: '虎跑公园', longitude: 120.130095, latitude: 30.207505 },
+ { label: '玉皇飞云', longitude: 120.145323, latitude: 30.214993 },
+ { label: '长桥公园', longitude: 120.155057, latitude: 30.232985 },
+ ]
+}
+
+```
+
+## API
+
+### PinMapProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----------- | ------------ | -------- | ------ | ---------- |
+| data | MarkerData[] | 是 | - | 数据 |
+| markerStyle | Marker | 否 | - | 标记点样式 |
+
+### MarkerData
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| --------- | ------ | -------- | ------ | -------- |
+| longitude | number | 是 | - | 经度 |
+| latitude | number | 是 | - | 纬度 |
+| label | number | 是 | - | 文字标注 |
diff --git a/src/PinMap/index.tsx b/src/PinMap/index.tsx
new file mode 100644
index 0000000..6edb8ad
--- /dev/null
+++ b/src/PinMap/index.tsx
@@ -0,0 +1,18 @@
+import React, { useMemo, type FC } from 'react';
+import { useMapConfig } from '../ConfigProvider/hooks';
+import type { MapProps } from '../Map';
+import Map from '../Map';
+import type { PinMap as PinMapType } from '../types/map';
+import { formatMakerStyle } from '../utils/map';
+
+export type PinMapProps = PinMapType & MapProps;
+
+const PinMap: FC = (props) => {
+ const { data = [], markerStyle = {}, ...rest } = useMapConfig('PinMap', props);
+
+ const markerdata = useMemo(() => formatMakerStyle(data, markerStyle), [data, markerStyle]);
+
+ return ;
+};
+
+export default PinMap;
diff --git a/src/PinMap/maker.svg b/src/PinMap/maker.svg
new file mode 100644
index 0000000..ea2757f
--- /dev/null
+++ b/src/PinMap/maker.svg
@@ -0,0 +1 @@
+
diff --git a/src/Radar/demos/category.tsx b/src/Radar/demos/category.tsx
new file mode 100644
index 0000000..dcc0612
--- /dev/null
+++ b/src/Radar/demos/category.tsx
@@ -0,0 +1,65 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Radar, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个雷达图
+
+\`\`\`vis-chart
+{
+ "type": "radar",
+ "data": [
+ { "group": "Apple", "name": "Vitamin C", "value": 5 },
+ { "group": "Apple", "name": "Fiber", "value": 7 },
+ { "group": "Apple", "name": "Sugar", "value": 6 },
+ { "group": "Apple", "name": "Protein", "value": 2 },
+ { "group": "Apple", "name": "Iron", "value": 3 },
+ { "group": "Apple", "name": "Calcium", "value": 2 },
+ { "group": "Banana", "name": "Vitamin C", "value": 4 },
+ { "group": "Banana", "name": "Fiber", "value": 5 },
+ { "group": "Banana", "name": "Sugar", "value": 7 },
+ { "group": "Banana", "name": "Protein", "value": 3 },
+ { "group": "Banana", "name": "Iron", "value": 2 },
+ { "group": "Banana", "name": "Calcium", "value": 3 }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Radar]: Radar },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Radar/demos/common.tsx b/src/Radar/demos/common.tsx
new file mode 100644
index 0000000..0e3d1d7
--- /dev/null
+++ b/src/Radar/demos/common.tsx
@@ -0,0 +1,15 @@
+import { Radar } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ { name: '沟通能力', value: 2 },
+ { name: '协作能力', value: 3 },
+ { name: '领导能力', value: 2 },
+ { name: '学习能力', value: 5 },
+ { name: '创新能力', value: 6 },
+ { name: '技术能力', value: 9 },
+];
+
+export default () => {
+ return ;
+};
diff --git a/src/Radar/demos/markdown.tsx b/src/Radar/demos/markdown.tsx
new file mode 100644
index 0000000..3fe498f
--- /dev/null
+++ b/src/Radar/demos/markdown.tsx
@@ -0,0 +1,59 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Radar, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个雷达图
+
+\`\`\`vis-chart
+{
+ "type": "radar",
+ "data": [
+ { "name": "Vitamin C", "value": 7 },
+ { "name": "Fiber", "value": 6 },
+ { "name": "Sugar", "value": 5 },
+ { "name": "Protein", "value": 4 },
+ { "name": "Iron", "value": 3 },
+ { "name": "Calcium", "value": 2 }
+ ]
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Radar]: Radar },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Radar/index.md b/src/Radar/index.md
new file mode 100644
index 0000000..9b573be
--- /dev/null
+++ b/src/Radar/index.md
@@ -0,0 +1,50 @@
+---
+order: 11
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# Radar 雷达图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+分组雷达图
+
+## Spec
+
+```json
+{
+ "type": "radar",
+ "data": [
+ { "name": "沟通能力", "value": 2 },
+ { "name": "协作能力", "value": 3 },
+ { "name": "领导能力", "value": 2 },
+ { "name": "学习能力", "value": 5 },
+ { "name": "创新能力", "value": 6 },
+ { "name": "技术能力", "value": 9 }
+ ]
+}
+```
+
+## API
+
+### RadarProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | --------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | RadarDataItem[] | 是 | - | 数据 |
+| title | string | 否 | - | 图表的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### RadarDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------ | -------- | ------ | ------------ |
+| name | string | 是 | - | 数据分类名称 |
+| value | number | 是 | - | 数据的值 |
+| group | string | 否 | - | 数据分组名称 |
diff --git a/src/Radar/index.tsx b/src/Radar/index.tsx
new file mode 100644
index 0000000..dcd71b8
--- /dev/null
+++ b/src/Radar/index.tsx
@@ -0,0 +1,65 @@
+import type { RadarConfig } from '@ant-design/plots';
+import { Radar as ADCRadar } from '@ant-design/plots';
+import { get } from 'lodash';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+export type RadarDataItem = {
+ name: string;
+ value: number;
+ [key: string]: string | number;
+};
+
+export type RadarProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: RadarConfig): RadarConfig => {
+ const { data, xField = 'name', yField = 'value' } = props;
+ const hasGroupField = get(data, '[0].group') !== undefined;
+
+ return {
+ xField,
+ yField,
+ colorField: hasGroupField ? 'group' : undefined,
+ area: {
+ style: {
+ fillOpacity: 0.5,
+ },
+ },
+ scale: {
+ x: {
+ padding: 0.6,
+ align: 0,
+ },
+ y: {
+ nice: true,
+ },
+ },
+ axis: {
+ x: {
+ title: false,
+ grid: true,
+ },
+ y: {
+ zIndex: 1,
+ title: false,
+ gridConnect: 'line',
+ gridLineWidth: 1,
+ },
+ },
+ tooltip: (d) => {
+ return {
+ name: d[xField as string],
+ value: d[yField as string],
+ };
+ },
+ };
+};
+
+const Radar = (props: RadarProps) => {
+ const config = usePlotConfig('Radar', defaultConfig, props);
+
+ return ;
+};
+
+export default Radar;
diff --git a/src/Scatter/demos/common.tsx b/src/Scatter/demos/common.tsx
new file mode 100644
index 0000000..027ff48
--- /dev/null
+++ b/src/Scatter/demos/common.tsx
@@ -0,0 +1,2044 @@
+import { Scatter } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ {
+ x: 161.2,
+ y: 51.6,
+ },
+ {
+ x: 167.5,
+ y: 59,
+ },
+ {
+ x: 159.5,
+ y: 49.2,
+ },
+ {
+ x: 157,
+ y: 63,
+ },
+ {
+ x: 155.8,
+ y: 53.6,
+ },
+ {
+ x: 170,
+ y: 59,
+ },
+ {
+ x: 159.1,
+ y: 47.6,
+ },
+ {
+ x: 166,
+ y: 69.8,
+ },
+ {
+ x: 176.2,
+ y: 66.8,
+ },
+ {
+ x: 160.2,
+ y: 75.2,
+ },
+ {
+ x: 172.5,
+ y: 55.2,
+ },
+ {
+ x: 170.9,
+ y: 54.2,
+ },
+ {
+ x: 172.9,
+ y: 62.5,
+ },
+ {
+ x: 153.4,
+ y: 42,
+ },
+ {
+ x: 160,
+ y: 50,
+ },
+ {
+ x: 147.2,
+ y: 49.8,
+ },
+ {
+ x: 168.2,
+ y: 49.2,
+ },
+ {
+ x: 175,
+ y: 73.2,
+ },
+ {
+ x: 157,
+ y: 47.8,
+ },
+ {
+ x: 167.6,
+ y: 68.8,
+ },
+ {
+ x: 159.5,
+ y: 50.6,
+ },
+ {
+ x: 175,
+ y: 82.5,
+ },
+ {
+ x: 166.8,
+ y: 57.2,
+ },
+ {
+ x: 176.5,
+ y: 87.8,
+ },
+ {
+ x: 170.2,
+ y: 72.8,
+ },
+ {
+ x: 174,
+ y: 54.5,
+ },
+ {
+ x: 173,
+ y: 59.8,
+ },
+ {
+ x: 179.9,
+ y: 67.3,
+ },
+ {
+ x: 170.5,
+ y: 67.8,
+ },
+ {
+ x: 160,
+ y: 47,
+ },
+ {
+ x: 154.4,
+ y: 46.2,
+ },
+ {
+ x: 162,
+ y: 55,
+ },
+ {
+ x: 176.5,
+ y: 83,
+ },
+ {
+ x: 160,
+ y: 54.4,
+ },
+ {
+ x: 152,
+ y: 45.8,
+ },
+ {
+ x: 162.1,
+ y: 53.6,
+ },
+ {
+ x: 170,
+ y: 73.2,
+ },
+ {
+ x: 160.2,
+ y: 52.1,
+ },
+ {
+ x: 161.3,
+ y: 67.9,
+ },
+ {
+ x: 166.4,
+ y: 56.6,
+ },
+ {
+ x: 168.9,
+ y: 62.3,
+ },
+ {
+ x: 163.8,
+ y: 58.5,
+ },
+ {
+ x: 167.6,
+ y: 54.5,
+ },
+ {
+ x: 160,
+ y: 50.2,
+ },
+ {
+ x: 161.3,
+ y: 60.3,
+ },
+ {
+ x: 167.6,
+ y: 58.3,
+ },
+ {
+ x: 165.1,
+ y: 56.2,
+ },
+ {
+ x: 160,
+ y: 50.2,
+ },
+ {
+ x: 170,
+ y: 72.9,
+ },
+ {
+ x: 157.5,
+ y: 59.8,
+ },
+ {
+ x: 167.6,
+ y: 61,
+ },
+ {
+ x: 160.7,
+ y: 69.1,
+ },
+ {
+ x: 163.2,
+ y: 55.9,
+ },
+ {
+ x: 152.4,
+ y: 46.5,
+ },
+ {
+ x: 157.5,
+ y: 54.3,
+ },
+ {
+ x: 168.3,
+ y: 54.8,
+ },
+ {
+ x: 180.3,
+ y: 60.7,
+ },
+ {
+ x: 165.5,
+ y: 60,
+ },
+ {
+ x: 165,
+ y: 62,
+ },
+ {
+ x: 164.5,
+ y: 60.3,
+ },
+ {
+ x: 156,
+ y: 52.7,
+ },
+ {
+ x: 160,
+ y: 74.3,
+ },
+ {
+ x: 163,
+ y: 62,
+ },
+ {
+ x: 165.7,
+ y: 73.1,
+ },
+ {
+ x: 161,
+ y: 80,
+ },
+ {
+ x: 162,
+ y: 54.7,
+ },
+ {
+ x: 166,
+ y: 53.2,
+ },
+ {
+ x: 174,
+ y: 75.7,
+ },
+ {
+ x: 172.7,
+ y: 61.1,
+ },
+ {
+ x: 167.6,
+ y: 55.7,
+ },
+ {
+ x: 151.1,
+ y: 48.7,
+ },
+ {
+ x: 164.5,
+ y: 52.3,
+ },
+ {
+ x: 163.5,
+ y: 50,
+ },
+ {
+ x: 152,
+ y: 59.3,
+ },
+ {
+ x: 169,
+ y: 62.5,
+ },
+ {
+ x: 164,
+ y: 55.7,
+ },
+ {
+ x: 161.2,
+ y: 54.8,
+ },
+ {
+ x: 155,
+ y: 45.9,
+ },
+ {
+ x: 170,
+ y: 70.6,
+ },
+ {
+ x: 176.2,
+ y: 67.2,
+ },
+ {
+ x: 170,
+ y: 69.4,
+ },
+ {
+ x: 162.5,
+ y: 58.2,
+ },
+ {
+ x: 170.3,
+ y: 64.8,
+ },
+ {
+ x: 164.1,
+ y: 71.6,
+ },
+ {
+ x: 169.5,
+ y: 52.8,
+ },
+ {
+ x: 163.2,
+ y: 59.8,
+ },
+ {
+ x: 154.5,
+ y: 49,
+ },
+ {
+ x: 159.8,
+ y: 50,
+ },
+ {
+ x: 173.2,
+ y: 69.2,
+ },
+ {
+ x: 170,
+ y: 55.9,
+ },
+ {
+ x: 161.4,
+ y: 63.4,
+ },
+ {
+ x: 169,
+ y: 58.2,
+ },
+ {
+ x: 166.2,
+ y: 58.6,
+ },
+ {
+ x: 159.4,
+ y: 45.7,
+ },
+ {
+ x: 162.5,
+ y: 52.2,
+ },
+ {
+ x: 159,
+ y: 48.6,
+ },
+ {
+ x: 162.8,
+ y: 57.8,
+ },
+ {
+ x: 159,
+ y: 55.6,
+ },
+ {
+ x: 179.8,
+ y: 66.8,
+ },
+ {
+ x: 162.9,
+ y: 59.4,
+ },
+ {
+ x: 161,
+ y: 53.6,
+ },
+ {
+ x: 151.1,
+ y: 73.2,
+ },
+ {
+ x: 168.2,
+ y: 53.4,
+ },
+ {
+ x: 168.9,
+ y: 69,
+ },
+ {
+ x: 173.2,
+ y: 58.4,
+ },
+ {
+ x: 171.8,
+ y: 56.2,
+ },
+ {
+ x: 178,
+ y: 70.6,
+ },
+ {
+ x: 164.3,
+ y: 59.8,
+ },
+ {
+ x: 163,
+ y: 72,
+ },
+ {
+ x: 168.5,
+ y: 65.2,
+ },
+ {
+ x: 166.8,
+ y: 56.6,
+ },
+ {
+ x: 172.7,
+ y: 105.2,
+ },
+ {
+ x: 163.5,
+ y: 51.8,
+ },
+ {
+ x: 169.4,
+ y: 63.4,
+ },
+ {
+ x: 167.8,
+ y: 59,
+ },
+ {
+ x: 159.5,
+ y: 47.6,
+ },
+ {
+ x: 167.6,
+ y: 63,
+ },
+ {
+ x: 161.2,
+ y: 55.2,
+ },
+ {
+ x: 160,
+ y: 45,
+ },
+ {
+ x: 163.2,
+ y: 54,
+ },
+ {
+ x: 162.2,
+ y: 50.2,
+ },
+ {
+ x: 161.3,
+ y: 60.2,
+ },
+ {
+ x: 149.5,
+ y: 44.8,
+ },
+ {
+ x: 157.5,
+ y: 58.8,
+ },
+ {
+ x: 163.2,
+ y: 56.4,
+ },
+ {
+ x: 172.7,
+ y: 62,
+ },
+ {
+ x: 155,
+ y: 49.2,
+ },
+ {
+ x: 156.5,
+ y: 67.2,
+ },
+ {
+ x: 164,
+ y: 53.8,
+ },
+ {
+ x: 160.9,
+ y: 54.4,
+ },
+ {
+ x: 162.8,
+ y: 58,
+ },
+ {
+ x: 167,
+ y: 59.8,
+ },
+ {
+ x: 160,
+ y: 54.8,
+ },
+ {
+ x: 160,
+ y: 43.2,
+ },
+ {
+ x: 168.9,
+ y: 60.5,
+ },
+ {
+ x: 158.2,
+ y: 46.4,
+ },
+ {
+ x: 156,
+ y: 64.4,
+ },
+ {
+ x: 160,
+ y: 48.8,
+ },
+ {
+ x: 167.1,
+ y: 62.2,
+ },
+ {
+ x: 158,
+ y: 55.5,
+ },
+ {
+ x: 167.6,
+ y: 57.8,
+ },
+ {
+ x: 156,
+ y: 54.6,
+ },
+ {
+ x: 162.1,
+ y: 59.2,
+ },
+ {
+ x: 173.4,
+ y: 52.7,
+ },
+ {
+ x: 159.8,
+ y: 53.2,
+ },
+ {
+ x: 170.5,
+ y: 64.5,
+ },
+ {
+ x: 159.2,
+ y: 51.8,
+ },
+ {
+ x: 157.5,
+ y: 56,
+ },
+ {
+ x: 161.3,
+ y: 63.6,
+ },
+ {
+ x: 162.6,
+ y: 63.2,
+ },
+ {
+ x: 160,
+ y: 59.5,
+ },
+ {
+ x: 168.9,
+ y: 56.8,
+ },
+ {
+ x: 165.1,
+ y: 64.1,
+ },
+ {
+ x: 162.6,
+ y: 50,
+ },
+ {
+ x: 165.1,
+ y: 72.3,
+ },
+ {
+ x: 166.4,
+ y: 55,
+ },
+ {
+ x: 160,
+ y: 55.9,
+ },
+ {
+ x: 152.4,
+ y: 60.4,
+ },
+ {
+ x: 170.2,
+ y: 69.1,
+ },
+ {
+ x: 162.6,
+ y: 84.5,
+ },
+ {
+ x: 170.2,
+ y: 55.9,
+ },
+ {
+ x: 158.8,
+ y: 55.5,
+ },
+ {
+ x: 172.7,
+ y: 69.5,
+ },
+ {
+ x: 167.6,
+ y: 76.4,
+ },
+ {
+ x: 162.6,
+ y: 61.4,
+ },
+ {
+ x: 167.6,
+ y: 65.9,
+ },
+ {
+ x: 156.2,
+ y: 58.6,
+ },
+ {
+ x: 175.2,
+ y: 66.8,
+ },
+ {
+ x: 172.1,
+ y: 56.6,
+ },
+ {
+ x: 162.6,
+ y: 58.6,
+ },
+ {
+ x: 160,
+ y: 55.9,
+ },
+ {
+ x: 165.1,
+ y: 59.1,
+ },
+ {
+ x: 182.9,
+ y: 81.8,
+ },
+ {
+ x: 166.4,
+ y: 70.7,
+ },
+ {
+ x: 165.1,
+ y: 56.8,
+ },
+ {
+ x: 177.8,
+ y: 60,
+ },
+ {
+ x: 165.1,
+ y: 58.2,
+ },
+ {
+ x: 175.3,
+ y: 72.7,
+ },
+ {
+ x: 154.9,
+ y: 54.1,
+ },
+ {
+ x: 158.8,
+ y: 49.1,
+ },
+ {
+ x: 172.7,
+ y: 75.9,
+ },
+ {
+ x: 168.9,
+ y: 55,
+ },
+ {
+ x: 161.3,
+ y: 57.3,
+ },
+ {
+ x: 167.6,
+ y: 55,
+ },
+ {
+ x: 165.1,
+ y: 65.5,
+ },
+ {
+ x: 175.3,
+ y: 65.5,
+ },
+ {
+ x: 157.5,
+ y: 48.6,
+ },
+ {
+ x: 163.8,
+ y: 58.6,
+ },
+ {
+ x: 167.6,
+ y: 63.6,
+ },
+ {
+ x: 165.1,
+ y: 55.2,
+ },
+ {
+ x: 165.1,
+ y: 62.7,
+ },
+ {
+ x: 168.9,
+ y: 56.6,
+ },
+ {
+ x: 162.6,
+ y: 53.9,
+ },
+ {
+ x: 164.5,
+ y: 63.2,
+ },
+ {
+ x: 176.5,
+ y: 73.6,
+ },
+ {
+ x: 168.9,
+ y: 62,
+ },
+ {
+ x: 175.3,
+ y: 63.6,
+ },
+ {
+ x: 159.4,
+ y: 53.2,
+ },
+ {
+ x: 160,
+ y: 53.4,
+ },
+ {
+ x: 170.2,
+ y: 55,
+ },
+ {
+ x: 162.6,
+ y: 70.5,
+ },
+ {
+ x: 167.6,
+ y: 54.5,
+ },
+ {
+ x: 162.6,
+ y: 54.5,
+ },
+ {
+ x: 160.7,
+ y: 55.9,
+ },
+ {
+ x: 160,
+ y: 59,
+ },
+ {
+ x: 157.5,
+ y: 63.6,
+ },
+ {
+ x: 162.6,
+ y: 54.5,
+ },
+ {
+ x: 152.4,
+ y: 47.3,
+ },
+ {
+ x: 170.2,
+ y: 67.7,
+ },
+ {
+ x: 165.1,
+ y: 80.9,
+ },
+ {
+ x: 172.7,
+ y: 70.5,
+ },
+ {
+ x: 165.1,
+ y: 60.9,
+ },
+ {
+ x: 170.2,
+ y: 63.6,
+ },
+ {
+ x: 170.2,
+ y: 54.5,
+ },
+ {
+ x: 170.2,
+ y: 59.1,
+ },
+ {
+ x: 161.3,
+ y: 70.5,
+ },
+ {
+ x: 167.6,
+ y: 52.7,
+ },
+ {
+ x: 167.6,
+ y: 62.7,
+ },
+ {
+ x: 165.1,
+ y: 86.3,
+ },
+ {
+ x: 162.6,
+ y: 66.4,
+ },
+ {
+ x: 152.4,
+ y: 67.3,
+ },
+ {
+ x: 168.9,
+ y: 63,
+ },
+ {
+ x: 170.2,
+ y: 73.6,
+ },
+ {
+ x: 175.2,
+ y: 62.3,
+ },
+ {
+ x: 175.2,
+ y: 57.7,
+ },
+ {
+ x: 160,
+ y: 55.4,
+ },
+ {
+ x: 165.1,
+ y: 104.1,
+ },
+ {
+ x: 174,
+ y: 55.5,
+ },
+ {
+ x: 170.2,
+ y: 77.3,
+ },
+ {
+ x: 160,
+ y: 80.5,
+ },
+ {
+ x: 167.6,
+ y: 64.5,
+ },
+ {
+ x: 167.6,
+ y: 72.3,
+ },
+ {
+ x: 167.6,
+ y: 61.4,
+ },
+ {
+ x: 154.9,
+ y: 58.2,
+ },
+ {
+ x: 162.6,
+ y: 81.8,
+ },
+ {
+ x: 175.3,
+ y: 63.6,
+ },
+ {
+ x: 171.4,
+ y: 53.4,
+ },
+ {
+ x: 157.5,
+ y: 54.5,
+ },
+ {
+ x: 165.1,
+ y: 53.6,
+ },
+ {
+ x: 160,
+ y: 60,
+ },
+ {
+ x: 174,
+ y: 73.6,
+ },
+ {
+ x: 162.6,
+ y: 61.4,
+ },
+ {
+ x: 174,
+ y: 55.5,
+ },
+ {
+ x: 162.6,
+ y: 63.6,
+ },
+ {
+ x: 161.3,
+ y: 60.9,
+ },
+ {
+ x: 156.2,
+ y: 60,
+ },
+ {
+ x: 149.9,
+ y: 46.8,
+ },
+ {
+ x: 169.5,
+ y: 57.3,
+ },
+ {
+ x: 160,
+ y: 64.1,
+ },
+ {
+ x: 175.3,
+ y: 63.6,
+ },
+ {
+ x: 169.5,
+ y: 67.3,
+ },
+ {
+ x: 160,
+ y: 75.5,
+ },
+ {
+ x: 172.7,
+ y: 68.2,
+ },
+ {
+ x: 162.6,
+ y: 61.4,
+ },
+ {
+ x: 157.5,
+ y: 76.8,
+ },
+ {
+ x: 176.5,
+ y: 71.8,
+ },
+ {
+ x: 164.4,
+ y: 55.5,
+ },
+ {
+ x: 160.7,
+ y: 48.6,
+ },
+ {
+ x: 174,
+ y: 66.4,
+ },
+ {
+ x: 163.8,
+ y: 67.3,
+ },
+ {
+ x: 174,
+ y: 65.6,
+ },
+ {
+ x: 175.3,
+ y: 71.8,
+ },
+ {
+ x: 193.5,
+ y: 80.7,
+ },
+ {
+ x: 186.5,
+ y: 72.6,
+ },
+ {
+ x: 187.2,
+ y: 78.8,
+ },
+ {
+ x: 181.5,
+ y: 74.8,
+ },
+ {
+ x: 184,
+ y: 86.4,
+ },
+ {
+ x: 184.5,
+ y: 78.4,
+ },
+ {
+ x: 175,
+ y: 62,
+ },
+ {
+ x: 184,
+ y: 81.6,
+ },
+ {
+ x: 180,
+ y: 76.6,
+ },
+ {
+ x: 177.8,
+ y: 83.6,
+ },
+ {
+ x: 192,
+ y: 90,
+ },
+ {
+ x: 176,
+ y: 74.6,
+ },
+ {
+ x: 174,
+ y: 71,
+ },
+ {
+ x: 184,
+ y: 79.6,
+ },
+ {
+ x: 192.7,
+ y: 93.8,
+ },
+ {
+ x: 171.5,
+ y: 70,
+ },
+ {
+ x: 173,
+ y: 72.4,
+ },
+ {
+ x: 176,
+ y: 85.9,
+ },
+ {
+ x: 176,
+ y: 78.8,
+ },
+ {
+ x: 180.5,
+ y: 77.8,
+ },
+ {
+ x: 172.7,
+ y: 66.2,
+ },
+ {
+ x: 176,
+ y: 86.4,
+ },
+ {
+ x: 173.5,
+ y: 81.8,
+ },
+ {
+ x: 178,
+ y: 89.6,
+ },
+ {
+ x: 180.3,
+ y: 82.8,
+ },
+ {
+ x: 180.3,
+ y: 76.4,
+ },
+ {
+ x: 164.5,
+ y: 63.2,
+ },
+ {
+ x: 173,
+ y: 60.9,
+ },
+ {
+ x: 183.5,
+ y: 74.8,
+ },
+ {
+ x: 175.5,
+ y: 70,
+ },
+ {
+ x: 188,
+ y: 72.4,
+ },
+ {
+ x: 189.2,
+ y: 84.1,
+ },
+ {
+ x: 172.8,
+ y: 69.1,
+ },
+ {
+ x: 170,
+ y: 59.5,
+ },
+ {
+ x: 182,
+ y: 67.2,
+ },
+ {
+ x: 170,
+ y: 61.3,
+ },
+ {
+ x: 177.8,
+ y: 68.6,
+ },
+ {
+ x: 184.2,
+ y: 80.1,
+ },
+ {
+ x: 186.7,
+ y: 87.8,
+ },
+ {
+ x: 171.4,
+ y: 84.7,
+ },
+ {
+ x: 172.7,
+ y: 73.4,
+ },
+ {
+ x: 175.3,
+ y: 72.1,
+ },
+ {
+ x: 180.3,
+ y: 82.6,
+ },
+ {
+ x: 182.9,
+ y: 88.7,
+ },
+ {
+ x: 188,
+ y: 84.1,
+ },
+ {
+ x: 177.2,
+ y: 94.1,
+ },
+ {
+ x: 172.1,
+ y: 74.9,
+ },
+ {
+ x: 167,
+ y: 59.1,
+ },
+ {
+ x: 169.5,
+ y: 75.6,
+ },
+ {
+ x: 174,
+ y: 86.2,
+ },
+ {
+ x: 172.7,
+ y: 75.3,
+ },
+ {
+ x: 182.2,
+ y: 87.1,
+ },
+ {
+ x: 164.1,
+ y: 55.2,
+ },
+ {
+ x: 163,
+ y: 57,
+ },
+ {
+ x: 171.5,
+ y: 61.4,
+ },
+ {
+ x: 184.2,
+ y: 76.8,
+ },
+ {
+ x: 174,
+ y: 86.8,
+ },
+ {
+ x: 174,
+ y: 72.2,
+ },
+ {
+ x: 177,
+ y: 71.6,
+ },
+ {
+ x: 186,
+ y: 84.8,
+ },
+ {
+ x: 167,
+ y: 68.2,
+ },
+ {
+ x: 171.8,
+ y: 66.1,
+ },
+ {
+ x: 182,
+ y: 72,
+ },
+ {
+ x: 167,
+ y: 64.6,
+ },
+ {
+ x: 177.8,
+ y: 74.8,
+ },
+ {
+ x: 164.5,
+ y: 70,
+ },
+ {
+ x: 192,
+ y: 101.6,
+ },
+ {
+ x: 175.5,
+ y: 63.2,
+ },
+ {
+ x: 171.2,
+ y: 79.1,
+ },
+ {
+ x: 181.6,
+ y: 78.9,
+ },
+ {
+ x: 167.4,
+ y: 67.7,
+ },
+ {
+ x: 181.1,
+ y: 66,
+ },
+ {
+ x: 177,
+ y: 68.2,
+ },
+ {
+ x: 174.5,
+ y: 63.9,
+ },
+ {
+ x: 177.5,
+ y: 72,
+ },
+ {
+ x: 170.5,
+ y: 56.8,
+ },
+ {
+ x: 182.4,
+ y: 74.5,
+ },
+ {
+ x: 197.1,
+ y: 90.9,
+ },
+ {
+ x: 180.1,
+ y: 93,
+ },
+ {
+ x: 175.5,
+ y: 80.9,
+ },
+ {
+ x: 180.6,
+ y: 72.7,
+ },
+ {
+ x: 184.4,
+ y: 68,
+ },
+ {
+ x: 175.5,
+ y: 70.9,
+ },
+ {
+ x: 180.6,
+ y: 72.5,
+ },
+ {
+ x: 177,
+ y: 72.5,
+ },
+ {
+ x: 177.1,
+ y: 83.4,
+ },
+ {
+ x: 181.6,
+ y: 75.5,
+ },
+ {
+ x: 176.5,
+ y: 73,
+ },
+ {
+ x: 175,
+ y: 70.2,
+ },
+ {
+ x: 174,
+ y: 73.4,
+ },
+ {
+ x: 165.1,
+ y: 70.5,
+ },
+ {
+ x: 177,
+ y: 68.9,
+ },
+ {
+ x: 192,
+ y: 102.3,
+ },
+ {
+ x: 176.5,
+ y: 68.4,
+ },
+ {
+ x: 169.4,
+ y: 65.9,
+ },
+ {
+ x: 182.1,
+ y: 75.7,
+ },
+ {
+ x: 179.8,
+ y: 84.5,
+ },
+ {
+ x: 175.3,
+ y: 87.7,
+ },
+ {
+ x: 184.9,
+ y: 86.4,
+ },
+ {
+ x: 177.3,
+ y: 73.2,
+ },
+ {
+ x: 167.4,
+ y: 53.9,
+ },
+ {
+ x: 178.1,
+ y: 72,
+ },
+ {
+ x: 168.9,
+ y: 55.5,
+ },
+ {
+ x: 157.2,
+ y: 58.4,
+ },
+ {
+ x: 180.3,
+ y: 83.2,
+ },
+ {
+ x: 170.2,
+ y: 72.7,
+ },
+ {
+ x: 177.8,
+ y: 64.1,
+ },
+ {
+ x: 172.7,
+ y: 72.3,
+ },
+ {
+ x: 165.1,
+ y: 65,
+ },
+ {
+ x: 186.7,
+ y: 86.4,
+ },
+ {
+ x: 165.1,
+ y: 65,
+ },
+ {
+ x: 174,
+ y: 88.6,
+ },
+ {
+ x: 175.3,
+ y: 84.1,
+ },
+ {
+ x: 185.4,
+ y: 66.8,
+ },
+ {
+ x: 177.8,
+ y: 75.5,
+ },
+ {
+ x: 180.3,
+ y: 93.2,
+ },
+ {
+ x: 180.3,
+ y: 82.7,
+ },
+ {
+ x: 177.8,
+ y: 58,
+ },
+ {
+ x: 177.8,
+ y: 79.5,
+ },
+ {
+ x: 177.8,
+ y: 78.6,
+ },
+ {
+ x: 177.8,
+ y: 71.8,
+ },
+ {
+ x: 177.8,
+ y: 116.4,
+ },
+ {
+ x: 163.8,
+ y: 72.2,
+ },
+ {
+ x: 188,
+ y: 83.6,
+ },
+ {
+ x: 198.1,
+ y: 85.5,
+ },
+ {
+ x: 175.3,
+ y: 90.9,
+ },
+ {
+ x: 166.4,
+ y: 85.9,
+ },
+ {
+ x: 190.5,
+ y: 89.1,
+ },
+ {
+ x: 166.4,
+ y: 75,
+ },
+ {
+ x: 177.8,
+ y: 77.7,
+ },
+ {
+ x: 179.7,
+ y: 86.4,
+ },
+ {
+ x: 172.7,
+ y: 90.9,
+ },
+ {
+ x: 190.5,
+ y: 73.6,
+ },
+ {
+ x: 185.4,
+ y: 76.4,
+ },
+ {
+ x: 168.9,
+ y: 69.1,
+ },
+ {
+ x: 167.6,
+ y: 84.5,
+ },
+ {
+ x: 175.3,
+ y: 64.5,
+ },
+ {
+ x: 170.2,
+ y: 69.1,
+ },
+ {
+ x: 190.5,
+ y: 108.6,
+ },
+ {
+ x: 177.8,
+ y: 86.4,
+ },
+ {
+ x: 190.5,
+ y: 80.9,
+ },
+ {
+ x: 177.8,
+ y: 87.7,
+ },
+ {
+ x: 184.2,
+ y: 94.5,
+ },
+ {
+ x: 176.5,
+ y: 80.2,
+ },
+ {
+ x: 177.8,
+ y: 72,
+ },
+ {
+ x: 180.3,
+ y: 71.4,
+ },
+ {
+ x: 171.4,
+ y: 72.7,
+ },
+ {
+ x: 172.7,
+ y: 84.1,
+ },
+ {
+ x: 172.7,
+ y: 76.8,
+ },
+ {
+ x: 177.8,
+ y: 63.6,
+ },
+ {
+ x: 177.8,
+ y: 80.9,
+ },
+ {
+ x: 182.9,
+ y: 80.9,
+ },
+ {
+ x: 170.2,
+ y: 85.5,
+ },
+ {
+ x: 167.6,
+ y: 68.6,
+ },
+ {
+ x: 175.3,
+ y: 67.7,
+ },
+ {
+ x: 165.1,
+ y: 66.4,
+ },
+ {
+ x: 185.4,
+ y: 102.3,
+ },
+ {
+ x: 181.6,
+ y: 70.5,
+ },
+ {
+ x: 172.7,
+ y: 95.9,
+ },
+ {
+ x: 190.5,
+ y: 84.1,
+ },
+ {
+ x: 179.1,
+ y: 87.3,
+ },
+ {
+ x: 175.3,
+ y: 71.8,
+ },
+ {
+ x: 170.2,
+ y: 65.9,
+ },
+ {
+ x: 193,
+ y: 95.9,
+ },
+ {
+ x: 171.4,
+ y: 91.4,
+ },
+ {
+ x: 177.8,
+ y: 81.8,
+ },
+ {
+ x: 177.8,
+ y: 96.8,
+ },
+ {
+ x: 167.6,
+ y: 69.1,
+ },
+ {
+ x: 167.6,
+ y: 82.7,
+ },
+ {
+ x: 180.3,
+ y: 75.5,
+ },
+ {
+ x: 182.9,
+ y: 79.5,
+ },
+ {
+ x: 176.5,
+ y: 73.6,
+ },
+ {
+ x: 186.7,
+ y: 91.8,
+ },
+ {
+ x: 188,
+ y: 84.1,
+ },
+ {
+ x: 188,
+ y: 85.9,
+ },
+ {
+ x: 177.8,
+ y: 81.8,
+ },
+ {
+ x: 174,
+ y: 82.5,
+ },
+ {
+ x: 177.8,
+ y: 80.5,
+ },
+ {
+ x: 171.4,
+ y: 70,
+ },
+ {
+ x: 185.4,
+ y: 81.8,
+ },
+ {
+ x: 185.4,
+ y: 84.1,
+ },
+ {
+ x: 188,
+ y: 90.5,
+ },
+ {
+ x: 188,
+ y: 91.4,
+ },
+ {
+ x: 182.9,
+ y: 89.1,
+ },
+ {
+ x: 176.5,
+ y: 85,
+ },
+ {
+ x: 175.3,
+ y: 69.1,
+ },
+ {
+ x: 175.3,
+ y: 73.6,
+ },
+ {
+ x: 188,
+ y: 80.5,
+ },
+ {
+ x: 188,
+ y: 82.7,
+ },
+ {
+ x: 175.3,
+ y: 86.4,
+ },
+ {
+ x: 170.5,
+ y: 67.7,
+ },
+ {
+ x: 179.1,
+ y: 92.7,
+ },
+ {
+ x: 177.8,
+ y: 93.6,
+ },
+ {
+ x: 175.3,
+ y: 70.9,
+ },
+ {
+ x: 182.9,
+ y: 75,
+ },
+ {
+ x: 170.8,
+ y: 93.2,
+ },
+ {
+ x: 188,
+ y: 93.2,
+ },
+ {
+ x: 180.3,
+ y: 77.7,
+ },
+ {
+ x: 177.8,
+ y: 61.4,
+ },
+ {
+ x: 185.4,
+ y: 94.1,
+ },
+ {
+ x: 168.9,
+ y: 75,
+ },
+ {
+ x: 185.4,
+ y: 83.6,
+ },
+ {
+ x: 180.3,
+ y: 85.5,
+ },
+ {
+ x: 174,
+ y: 73.9,
+ },
+ {
+ x: 167.6,
+ y: 66.8,
+ },
+ {
+ x: 182.9,
+ y: 87.3,
+ },
+ {
+ x: 160,
+ y: 72.3,
+ },
+ {
+ x: 180.3,
+ y: 88.6,
+ },
+ {
+ x: 167.6,
+ y: 75.5,
+ },
+ {
+ x: 186.7,
+ y: 101.4,
+ },
+ {
+ x: 175.3,
+ y: 91.1,
+ },
+ {
+ x: 175.3,
+ y: 67.3,
+ },
+ {
+ x: 175.9,
+ y: 77.7,
+ },
+ {
+ x: 175.3,
+ y: 81.8,
+ },
+ {
+ x: 179.1,
+ y: 75.5,
+ },
+ {
+ x: 181.6,
+ y: 84.5,
+ },
+ {
+ x: 177.8,
+ y: 76.6,
+ },
+ {
+ x: 182.9,
+ y: 85,
+ },
+ {
+ x: 177.8,
+ y: 102.5,
+ },
+ {
+ x: 184.2,
+ y: 77.3,
+ },
+ {
+ x: 179.1,
+ y: 71.8,
+ },
+ {
+ x: 176.5,
+ y: 87.9,
+ },
+ {
+ x: 188,
+ y: 94.3,
+ },
+ {
+ x: 174,
+ y: 70.9,
+ },
+ {
+ x: 167.6,
+ y: 64.5,
+ },
+ {
+ x: 170.2,
+ y: 77.3,
+ },
+ {
+ x: 167.6,
+ y: 72.3,
+ },
+ {
+ x: 188,
+ y: 87.3,
+ },
+ {
+ x: 174,
+ y: 80,
+ },
+ {
+ x: 176.5,
+ y: 82.3,
+ },
+ {
+ x: 180.3,
+ y: 73.6,
+ },
+ {
+ x: 167.6,
+ y: 74.1,
+ },
+ {
+ x: 188,
+ y: 85.9,
+ },
+ {
+ x: 180.3,
+ y: 73.2,
+ },
+ {
+ x: 167.6,
+ y: 76.3,
+ },
+ {
+ x: 183,
+ y: 65.9,
+ },
+ {
+ x: 183,
+ y: 90.9,
+ },
+ {
+ x: 179.1,
+ y: 89.1,
+ },
+ {
+ x: 170.2,
+ y: 62.3,
+ },
+ {
+ x: 177.8,
+ y: 82.7,
+ },
+ {
+ x: 179.1,
+ y: 79.1,
+ },
+ {
+ x: 190.5,
+ y: 98.2,
+ },
+ {
+ x: 177.8,
+ y: 84.1,
+ },
+ {
+ x: 180.3,
+ y: 83.2,
+ },
+ {
+ x: 180.3,
+ y: 83.2,
+ },
+];
+
+export default () => {
+ return (
+
+ );
+};
diff --git a/src/Scatter/demos/markdown.tsx b/src/Scatter/demos/markdown.tsx
new file mode 100644
index 0000000..3aaa2d8
--- /dev/null
+++ b/src/Scatter/demos/markdown.tsx
@@ -0,0 +1,105 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Scatter, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个散点图
+
+\`\`\`vis-chart
+{
+ "type": "scatter",
+ "data": [
+ { "x": 22, "y": 3000 },
+ { "x": 23, "y": 3200 },
+ { "x": 24, "y": 3100 },
+ { "x": 25, "y": 3500 },
+ { "x": 26, "y": 3300 },
+ { "x": 27, "y": 3600 },
+ { "x": 28, "y": 4000 },
+ { "x": 29, "y": 3900 },
+ { "x": 30, "y": 4200 },
+ { "x": 31, "y": 4100 },
+ { "x": 32, "y": 4500 },
+ { "x": 33, "y": 4700 },
+ { "x": 34, "y": 4600 },
+ { "x": 35, "y": 4800 },
+ { "x": 36, "y": 5000 },
+ { "x": 37, "y": 5200 },
+ { "x": 38, "y": 5100 },
+ { "x": 39, "y": 5500 },
+ { "x": 40, "y": 5300 },
+ { "x": 41, "y": 5700 },
+ { "x": 42, "y": 5600 },
+ { "x": 43, "y": 5900 },
+ { "x": 44, "y": 5800 },
+ { "x": 45, "y": 6000 },
+ { "x": 46, "y": 6100 },
+ { "x": 47, "y": 6500 },
+ { "x": 48, "y": 6300 },
+ { "x": 49, "y": 6700 },
+ { "x": 50, "y": 6400 },
+ { "x": 51, "y": 6800 },
+ { "x": 52, "y": 6900 },
+ { "x": 53, "y": 7100 },
+ { "x": 54, "y": 7000 },
+ { "x": 55, "y": 7300 },
+ { "x": 56, "y": 7200 },
+ { "x": 57, "y": 7500 },
+ { "x": 58, "y": 7400 },
+ { "x": 59, "y": 7600 },
+ { "x": 60, "y": 7700 },
+ { "x": 61, "y": 7900 },
+ { "x": 62, "y": 7800 },
+ { "x": 63, "y": 8000 },
+ { "x": 64, "y": 8100 },
+ { "x": 65, "y": 8200 },
+ { "x": 66, "y": 8300 },
+ { "x": 67, "y": 8500 },
+ { "x": 68, "y": 8400 },
+ { "x": 69, "y": 8600 },
+ { "x": 70, "y": 8700 }
+ ],
+ "axisXTitle": "age",
+ "axisYTitle": "income"
+}
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Scatter]: Scatter },
+ style: { width: 350 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Scatter/index.md b/src/Scatter/index.md
new file mode 100644
index 0000000..e7c57a2
--- /dev/null
+++ b/src/Scatter/index.md
@@ -0,0 +1,48 @@
+---
+order: 6
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# Scatter 散点图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+
+## Spec
+
+```json
+{
+ "type": "scatter",
+ "data": [
+ { "x": 10, "y": 15 },
+ { "x": 20, "y": 25 },
+ { "x": 30, "y": 35 },
+ { "x": 40, "y": 45 }
+ ]
+}
+```
+
+## API
+
+### ScatterProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---------- | ----------------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | ScatterDataItem[] | 是 | - | 数据 |
+| title | string | 否 | - | 图表的标题 |
+| axisXTitle | string | 否 | - | x 轴的标题 |
+| axisYTitle | string | 否 | - | y 轴的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### ScatterDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ---- | ------ | -------- | ------ | ---------------- |
+| x | number | 是 | - | X 轴上的数值变量 |
+| y | number | 是 | - | Y 轴上的数值变量 |
diff --git a/src/Scatter/index.tsx b/src/Scatter/index.tsx
new file mode 100644
index 0000000..02aecfc
--- /dev/null
+++ b/src/Scatter/index.tsx
@@ -0,0 +1,38 @@
+import type { ScatterConfig } from '@ant-design/plots';
+import { Scatter as ADCScatter } from '@ant-design/plots';
+import { get } from 'lodash';
+import React from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+type ScatterDataItem = {
+ x: number;
+ y: number;
+ [key: string]: string | number;
+};
+
+export type ScatterProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: ScatterConfig): ScatterConfig => {
+ const { data, xField = 'x', yField = 'y' } = props;
+ const axisXTitle = get(props, 'axis.x.title');
+ const axisYTitle = get(props, 'axis.y.title');
+
+ return {
+ data,
+ xField,
+ yField,
+ tooltip: [
+ { channel: 'x', name: axisXTitle || 'x' },
+ { channel: 'y', name: axisYTitle || 'y' },
+ ],
+ };
+};
+
+const Scatter = (props: ScatterProps) => {
+ const config = usePlotConfig('Scatter', defaultConfig, props);
+
+ return ;
+};
+
+export default Scatter;
diff --git a/src/Text/VisText.tsx b/src/Text/VisText.tsx
new file mode 100644
index 0000000..f534f6f
--- /dev/null
+++ b/src/Text/VisText.tsx
@@ -0,0 +1,44 @@
+import { Tooltip, Typography } from 'antd';
+import { pick, toString } from 'lodash';
+import React from 'react';
+import { useComponentGlobalConfig } from '../ConfigProvider/hooks';
+import { type TextConfig, STATICS_KEY } from './config';
+import type { VisTextProps } from './types';
+
+const { Text } = Typography;
+
+function renderPrefixSuffix(
+ symbol: string,
+ staticsConfig: TextConfig[typeof STATICS_KEY],
+ props: VisTextProps,
+) {
+ if (!symbol) return null;
+ const Component = staticsConfig?.[symbol];
+ if (Component) return ;
+ return symbol;
+}
+
+const VisText = (props: VisTextProps) => {
+ const { children, className, style, type, origin } = props;
+ const textConfig = useComponentGlobalConfig('VisText');
+ const encoding = type ? textConfig?.[type] : {};
+ const staticsConfig = textConfig?.[STATICS_KEY];
+ return (
+ // TODO @羽熙 暂时简单处理 tooltip 直接显示 origin,后续可以根据 origin 类型分类处理
+
+
+ {renderPrefixSuffix(encoding?.prefix, staticsConfig, props)}
+ {children}
+ {renderPrefixSuffix(encoding?.suffix, staticsConfig, props)}
+
+
+ );
+};
+
+export default VisText;
diff --git a/src/Text/config.tsx b/src/Text/config.tsx
new file mode 100644
index 0000000..d801872
--- /dev/null
+++ b/src/Text/config.tsx
@@ -0,0 +1,106 @@
+import type { CSSProperties } from 'react';
+import { TEXT_THEME } from './constants';
+import { ArrowDown, ArrowUp } from './custom-icons';
+import { ProportionChart, SingleLineChart } from './mini-charts';
+import type { VisTextProps } from './types';
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const INNER_ENTITY_TYPES = [
+ 'metric_name', // 指标名
+ 'metric_value', // 主指标值
+ 'other_metric_value', // 其他指标值
+ 'time_desc', // 时间描述
+ 'dim_value', // 维值
+ 'trend_desc', // 趋势描述
+ 'delta_value', // 变化值(非正非负)
+ 'ratio_value', // 变化率(非正非负)
+ 'delta_value_pos', // 变化值正
+ 'delta_value_neg', // 变化值负
+ 'ratio_value_pos', // 变化率正
+ 'ratio_value_neg', // 变化率负
+ 'proportion', // 占比
+ 'contribute_ratio', // 贡献度
+] as const;
+
+export type InnerEntityType = (typeof INNER_ENTITY_TYPES)[number];
+
+export type TextEntityType = InnerEntityType | string;
+
+export type TextEncoding = Partial<
+ Pick & {
+ /** 前缀 */
+ prefix: string;
+ /** 后缀 */
+ suffix: string;
+
+ // TODO @羽熙 支持更多文本映射通道,eg underline 等
+ }
+>;
+
+export const STATICS_KEY = '__statics' as const;
+
+/** 文本组件配置信息 */
+export type TextConfig =
+ // 实体短语类型映射
+ Record & {
+ // 静态组件映射, 如 icon 和 mimi-chart
+ [STATICS_KEY]: Record>;
+ };
+
+export const DEFAULT_VIS_TEXT_CONFIG: TextConfig = {
+ [STATICS_KEY]: {
+ 'icon:arrow-up': ArrowUp,
+ 'icon:arrow-down': ArrowDown,
+ 'mini-chart:proportion': ProportionChart,
+ // TODO @羽熙 当前仅支持单折线图,之后可以考虑支持多折线图,在组件内实现分支判断
+ 'mini-chart:line': SingleLineChart,
+ },
+ metric_name: {
+ color: TEXT_THEME.black88,
+ fontWeight: 500,
+ },
+ metric_value: {
+ color: TEXT_THEME.primaryColor,
+ },
+ other_metric_value: {
+ color: TEXT_THEME.black65,
+ },
+ delta_value: {
+ color: TEXT_THEME.black65,
+ },
+ ratio_value: {
+ color: TEXT_THEME.black65,
+ },
+ delta_value_pos: {
+ color: TEXT_THEME.posColor,
+ prefix: '+',
+ },
+ delta_value_neg: {
+ color: TEXT_THEME.negColor,
+ prefix: '-',
+ },
+ ratio_value_pos: {
+ color: TEXT_THEME.posColor,
+ prefix: 'icon:arrow-up',
+ },
+ ratio_value_neg: {
+ color: TEXT_THEME.negColor,
+ prefix: 'icon:arrow-down',
+ },
+ contribute_ratio: {
+ color: TEXT_THEME.statisticsInsightColor,
+ },
+ trend_desc: {
+ color: TEXT_THEME.statisticsInsightColor,
+ suffix: 'mini-chart:line',
+ },
+ dim_value: {
+ color: TEXT_THEME.black88,
+ },
+ time_desc: {
+ color: TEXT_THEME.black88,
+ },
+ proportion: {
+ suffix: 'mini-chart:proportion',
+ },
+};
diff --git a/src/Text/constants.ts b/src/Text/constants.ts
new file mode 100644
index 0000000..ed7cd4f
--- /dev/null
+++ b/src/Text/constants.ts
@@ -0,0 +1,9 @@
+export const TEXT_THEME = {
+ fontSizeBase: 14,
+ primaryColor: '#1677FF',
+ black88: 'rgba(0, 0, 0, 0.88)',
+ black65: 'rgba(0, 0, 0, 0.65)',
+ posColor: '#FA541C',
+ negColor: '#13A8A8',
+ statisticsInsightColor: '#1F0352',
+};
diff --git a/src/Text/custom-icons/index.tsx b/src/Text/custom-icons/index.tsx
new file mode 100644
index 0000000..31efd5f
--- /dev/null
+++ b/src/Text/custom-icons/index.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+const MARGIN_RIGHT = 2;
+export const ArrowUp = () => (
+
+
+
+
+
+);
+export const ArrowDown = () => (
+
+
+
+
+
+);
diff --git a/src/Text/demos/common.tsx b/src/Text/demos/common.tsx
new file mode 100644
index 0000000..847b714
--- /dev/null
+++ b/src/Text/demos/common.tsx
@@ -0,0 +1,50 @@
+import { VisText } from '@antv/gpt-vis';
+import { Descriptions, Space } from 'antd';
+import React from 'react';
+
+export default () => {
+ return (
+
+
+ DAU
+
+
+
+ 90.1w
+
+
+
+ 30%
+
+
+
+ 100.33
+ 100.33
+
+
+
+
+ 30%
+ 30%
+
+
+
+ 20%
+
+
+
+ 趋势上涨
+
+
+
+ 杭州
+
+
+ 2021-10-11
+
+
+ 30%
+
+
+ );
+};
diff --git a/src/Text/demos/custom-markdown.tsx b/src/Text/demos/custom-markdown.tsx
new file mode 100644
index 0000000..aa8ad63
--- /dev/null
+++ b/src/Text/demos/custom-markdown.tsx
@@ -0,0 +1,64 @@
+import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ConfigProvider, GPTVis, VisText } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+DAU 是 1.23亿 ,环比昨日 80万 (2.3% ),建议保持关注 。
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+
+);
diff --git a/src/Text/demos/markdown.tsx b/src/Text/demos/markdown.tsx
new file mode 100644
index 0000000..6718e1f
--- /dev/null
+++ b/src/Text/demos/markdown.tsx
@@ -0,0 +1,47 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { GPTVis, VisText } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+### 决策数量:
+本月 共产生决策数量 2,783 个,环比增长 15.2% ,同比增长 23.5% 。其中,营销部门 贡献最多,占比38.7% ;其次是产品部门 ,占比27.3% 。高优先级决策 占比56.2% ,较上月上升 3.8 个百分点。决策数量 呈现稳定上升 趋势,预计下月 将突破3,000 大关。
+
+### 决策准确率:
+本月 整体决策准确率 为87.6% ,环比下降 1.2 个百分点,但仍高于年度目标2.6% 。财务部门 表现最佳,准确率达94.3% ;人力资源部门 表现欠佳,准确率为76.8% ,建议加强培训。数据驱动型决策 的准确率(91.2% )显著高于经验驱动型决策 (82.4% ),凸显了数据分析的重要性。
+
+### 决策执行率:
+本月 决策执行率 为82.3% ,环比提升 5.7 个百分点,创下近6个月新高。其中,高优先级决策 执行率达95.6% ,低优先级决策 执行率仅为68.7% 。技术部门 的执行率最高,达91.2% ;市场部门 最低,为74.5% 。建议关注市场部门 的决策执行障碍,提供必要支持。
+
+### 决策收益:
+本月 决策收益 带来的总收益为1,275 万元,同比增长31.8% ,超出预期18.6% 。产品决策 贡献最大,占总收益的42.3% ;其次是营销决策 ,占27.6% 。高风险决策 的平均收益(87.3 万元/个)显著高于低风险决策 (23.1 万元/个)。建议在风险可控的前提下,适当增加高收益潜力 的决策比例。
+
+### 数据质量:
+本月 数据质量 评分为88.5 分(满分100分),环比提升提升 2.3 分。数据完整性 (93.2 分)和及时性 (91.7 分)表现优异,而准确性 (84.6 分)和一致性 (82.5 分)仍有提升空间。财务数据 质量最高,达95.3 分;用户行为数据 质量最低,为81.2 分。建议优化用户行为数据 采集流程,提高数据准确性 和一致性 。
+
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+);
diff --git a/src/Text/demos/playground-dataset-check.tsx b/src/Text/demos/playground-dataset-check.tsx
new file mode 100644
index 0000000..a01bbc7
--- /dev/null
+++ b/src/Text/demos/playground-dataset-check.tsx
@@ -0,0 +1,177 @@
+import { UserOutlined } from '@ant-design/icons';
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ConfigProvider, GPTVis, VisText } from '@antv/gpt-vis';
+import type { UploadProps } from 'antd';
+import { Button, message, Row, Space, Upload, type GetProp } from 'antd';
+import type { CSSProperties } from 'react';
+import React, { useState } from 'react';
+
+const presetDataset = [
+ {
+ prompt: '本季度销售额达到3.45亿,环比增长500万(+1.5%),主要得益于线上渠道的持续增长。',
+ answer:
+ '本季度 销售额 达到 3.45亿 ,环比增长 500万 (1.5% ),主要得益于 线上渠道 的持续增长。',
+ },
+ {
+ prompt: '用户活跃度达到800万,较上月提高80万(+11%),用户留存率保持在90%。',
+ answer:
+ '用户活跃度 达到 800万 ,较上月提高 80万 (11% ),用户留存率 保持在 90% 。',
+ },
+ {
+ prompt: '新用户注册数达到50万,环比增长20万(+66.7%),主要来自社交媒体的推广效果。',
+ answer:
+ '新用户注册数 达到 50万 ,环比增长 20万 (66.7% ),主要来自 社交媒体 的推广效果。',
+ },
+ {
+ prompt: '市场份额达到15%,相比去年提升3个百分点,主要原因是产品的质量提升和服务的优化。',
+ answer:
+ '市场份额 达到 15% ,相比去年提升 3个百分点 ,主要原因是 产品 的质量提升和 服务 的优化。',
+ },
+ {
+ prompt: '本月广告收入达到1.2亿,环比下降200万(-1.6%),受到市场竞争加剧的影响。',
+ answer:
+ '广告收入 达到 1.2亿 ,环比下降 200万 (1.6% ),受到 市场竞争 加剧的影响。',
+ },
+ {
+ prompt: '客户满意度评分提升至88分,较上季度增加了5分,反映了服务质量的显著改善。',
+ answer:
+ '客户满意度评分 提升至 88分 ,较上季度增加了 5分 ,反映了 服务质量 的显著改善。',
+ },
+ {
+ prompt: '移动端访问量达到900万,环比增长150万(+20%),显示出用户对移动体验的偏好。',
+ answer:
+ '移动端访问量 达到 900万 ,环比增长 150万 (20% ),显示出用户对 移动体验 的偏好。',
+ },
+ {
+ prompt: '在线教育平台的课程完成率达到了75%,较上月提升了10个百分点,体现了用户学习积极性。',
+ answer:
+ '课程完成率 达到了 75% ,较上月提升了 10个百分点 ,体现了 用户 学习积极性。',
+ },
+ {
+ prompt: '电商平台的退货率降低至5%,相比去年下降了2个百分点,优化了客户购物体验。',
+ answer:
+ '退货率 降低至 5% ,相比去年下降了 2个百分点 ,优化了 客户购物体验 。',
+ },
+ {
+ prompt: '年度利润达到1.5亿,较去年增长3000万(+25%),主要由于成本控制措施的实施。',
+ answer:
+ '年度利润 达到 1.5亿 ,较去年增长 3000万 (25% ),主要由于 成本控制措施 的实施。',
+ },
+];
+
+const bgStyle: CSSProperties = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+ maxHeight: 800,
+ overflowY: 'auto',
+};
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+const roles: GetProp = {
+ ai: {
+ placement: 'start',
+ avatar: {
+ src: 'https://mdn.alipayobjects.com/huamei_je4oko/afts/img/A*6LRBT7rjOkQAAAAAAAAAAAAADsZ-AQ/original',
+ },
+ typing: { step: 5, interval: 20 },
+ messageRender: RenderMarkdown,
+ style: {
+ maxWidth: '80%',
+ },
+ },
+ user: {
+ placement: 'end',
+ avatar: { icon: , style: { background: '#87d068' } },
+ },
+};
+
+export default () => {
+ const [dataset, setDataset] = useState(presetDataset);
+
+ const beforeUpload: UploadProps['beforeUpload'] = (file) => {
+ const isJsonl = file.type === 'application/json' || file.name.endsWith('.jsonl');
+
+ if (!isJsonl) {
+ message.error('You can only upload JSONL files!');
+ }
+
+ return isJsonl;
+ };
+
+ const handleUpload: UploadProps['onChange'] = (rcFile) => {
+ const file = rcFile.fileList[0]?.originFileObj;
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const content = e.target?.result;
+ try {
+ setDataset(JSON.parse(content as string));
+ } catch (e) {
+ message.error(`${e}`);
+ }
+ };
+ if (file) reader.readAsText(file);
+ return false;
+ };
+
+ const handleClear = () => {
+ setDataset([]);
+ };
+
+ return (
+ <>
+
+
+
+ 点击上传训练数据集 jsonl 文件
+
+ 清空
+
+
+
+
+ {
+ return [
+ {
+ key: i + 'prompt',
+ role: 'user',
+ content: prompt,
+ },
+ {
+ key: i + 'answer',
+ role: 'ai',
+ content: answer,
+ },
+ ];
+ })
+ .flat()}
+ />
+
+
+ >
+ );
+};
diff --git a/src/Text/index.md b/src/Text/index.md
new file mode 100644
index 0000000..e96d599
--- /dev/null
+++ b/src/Text/index.md
@@ -0,0 +1,65 @@
+---
+order: 1
+group:
+ order: 3
+ title: 文本
+---
+
+# VisText 文本
+
+## 代码演示
+
+### 单独使用
+
+
+
+### 使用 Markdown 协议
+
+
+
+### 使用 ConfigProvider 全局配置,个性化定制
+
+- 修改内置 entityType 的 encoding 样式,如将‘红涨绿跌’该为‘红跌绿涨’;
+- 为自定义 entityType 定义 encoding 样式,如增加‘行动建议’实体类型;
+
+
+
+## API
+
+### VisTextProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| -------- | ------ | -------- | ------ | -------------------------------------------------- |
+| type | string | 否 | - | 实体类型 |
+| children | string | 否 | - | 文本内容 |
+| origin | any | 否 | - | 原始数据,比如未经格式化的指标值、占比、趋势详情等 |
+
+### GlobalConfig.components.VisText
+
+通过 `ConfigProvider` 全局配置,个性化定制 VisText 组件。
+
+```tsx | pure
+
+
+ ;
+
+/** 文本组件配置信息 */
+export type TextConfig =
+ // 实体短语类型映射
+ Record & {
+ // 静态组件映射, 如 icon 和 mimi-chart
+ __statics: Record>;
+ };
+```
+
+## Playground
+
+### 用于训练数据集的校验
+
+该组件可以用于对文本训练数据集的校验,对比文本增强前后的效果对比。
+
+
diff --git a/src/Text/index.ts b/src/Text/index.ts
new file mode 100644
index 0000000..6476247
--- /dev/null
+++ b/src/Text/index.ts
@@ -0,0 +1,3 @@
+export { DEFAULT_VIS_TEXT_CONFIG } from './config';
+export type { VisTextProps } from './types';
+export { default as VisText } from './VisText';
diff --git a/src/Text/mini-charts/hooks/getElementFontSize.ts b/src/Text/mini-charts/hooks/getElementFontSize.ts
new file mode 100644
index 0000000..0de1a02
--- /dev/null
+++ b/src/Text/mini-charts/hooks/getElementFontSize.ts
@@ -0,0 +1,35 @@
+import { TEXT_THEME } from '../../constants';
+
+function getStyle(ele: Element, style: string): string | undefined {
+ return window.getComputedStyle
+ ? // @ts-ignore style as any
+ window.getComputedStyle(ele, null)[style]
+ : // @ts-ignore currentStyle for IE
+ ele?.currentStyle?.[style];
+}
+
+function isAbsoluteUnitPx(str: string | undefined): boolean | undefined {
+ return str?.endsWith('px');
+}
+
+function getPxNumber(str: string): number | undefined {
+ const removeUnit = str.replace(/px$/, '');
+ const resultNumber = Number(removeUnit);
+ if (!Number.isNaN(resultNumber)) return resultNumber;
+ return undefined;
+}
+
+export function getElementFontSize(ele: Element, defaultSize = TEXT_THEME.fontSizeBase): number {
+ const FONT_SIZE = 'font-size';
+ const eleFontSizeStr = getStyle(ele, FONT_SIZE);
+ if (eleFontSizeStr && isAbsoluteUnitPx(eleFontSizeStr)) {
+ const px = getPxNumber(eleFontSizeStr);
+ if (px) return px;
+ }
+ const bodyFontSizeStr = getStyle(window.document.body, FONT_SIZE);
+ if (bodyFontSizeStr && isAbsoluteUnitPx(bodyFontSizeStr)) {
+ const px = getPxNumber(bodyFontSizeStr);
+ if (px) return px;
+ }
+ return defaultSize;
+}
diff --git a/src/Text/mini-charts/hooks/index.ts b/src/Text/mini-charts/hooks/index.ts
new file mode 100644
index 0000000..48c1d90
--- /dev/null
+++ b/src/Text/mini-charts/hooks/index.ts
@@ -0,0 +1 @@
+export { useSvgWrapper } from './useSvgWrapper';
diff --git a/src/Text/mini-charts/hooks/useSvgWrapper.tsx b/src/Text/mini-charts/hooks/useSvgWrapper.tsx
new file mode 100644
index 0000000..fa0f0a9
--- /dev/null
+++ b/src/Text/mini-charts/hooks/useSvgWrapper.tsx
@@ -0,0 +1,31 @@
+import type { PropsWithChildren } from 'react';
+import React, { useLayoutEffect, useRef, useState } from 'react';
+import { TEXT_THEME } from '../../constants';
+import { getElementFontSize } from './getElementFontSize';
+
+type SvgProps = PropsWithChildren>;
+
+export const useSvgWrapper = () => {
+ const ele = useRef(null);
+ const [fontSize, setFontSize] = useState(TEXT_THEME.fontSizeBase);
+ useLayoutEffect(() => {
+ if (ele.current) {
+ setFontSize(getElementFontSize(ele.current, TEXT_THEME.fontSizeBase));
+ }
+ }, []);
+ const Svg = ({ children, ...otherProps }: SvgProps) => {
+ return (
+
+ {children}
+
+ );
+ };
+ return [Svg, fontSize] as const;
+};
diff --git a/src/Text/mini-charts/index.ts b/src/Text/mini-charts/index.ts
new file mode 100644
index 0000000..d0994bb
--- /dev/null
+++ b/src/Text/mini-charts/index.ts
@@ -0,0 +1,2 @@
+export * from './line';
+export * from './proportion';
diff --git a/src/Text/mini-charts/line/SingleLineChart.tsx b/src/Text/mini-charts/line/SingleLineChart.tsx
new file mode 100644
index 0000000..bc68edf
--- /dev/null
+++ b/src/Text/mini-charts/line/SingleLineChart.tsx
@@ -0,0 +1,41 @@
+import { isArray, isString, size } from 'lodash';
+import React, { useMemo } from 'react';
+import type { VisTextProps } from '../../types';
+import { useSvgWrapper } from '../hooks';
+import { useLineCompute } from './useLineCompute';
+
+const LINEAR_FILL_COLOR_ID = 'wsc-line-fill';
+
+const LINE_STROKE_COLOR = '#5B8FF9';
+
+function getLineChartData(origin: any) {
+ if (isArray(origin)) return origin;
+ if (isString(origin)) {
+ try {
+ const data = JSON.parse(origin);
+ if (isArray(data)) return data;
+ } catch (e) {
+ console.warn(e, `${origin} is not a valid json string`);
+ }
+ }
+}
+
+export const SingleLineChart: React.FC = ({ origin }) => {
+ const [Svg, fontSize] = useSvgWrapper();
+ const data = useMemo(() => getLineChartData(origin), [origin]);
+ const { width, height, linePath, polygonPath } = useLineCompute(fontSize, data);
+ return (
+ size(data) > 0 && (
+
+
+
+
+
+
+
+ {linePath && }
+ {polygonPath && }
+
+ )
+ );
+};
diff --git a/src/Text/mini-charts/line/index.ts b/src/Text/mini-charts/line/index.ts
new file mode 100644
index 0000000..c570dff
--- /dev/null
+++ b/src/Text/mini-charts/line/index.ts
@@ -0,0 +1 @@
+export { SingleLineChart } from './SingleLineChart';
diff --git a/src/Text/mini-charts/line/scaleLinear.ts b/src/Text/mini-charts/line/scaleLinear.ts
new file mode 100644
index 0000000..cdf3d94
--- /dev/null
+++ b/src/Text/mini-charts/line/scaleLinear.ts
@@ -0,0 +1,14 @@
+export type Domain = [number, number];
+export type Range = [number, number];
+
+export type Scale = (n: number) => number;
+
+export const scaleLinear =
+ (domain: Domain, range: Range): Scale =>
+ (n) => {
+ const [d1, d2] = domain;
+ const [r1, r2] = range;
+ // Returns the intermediate value when the range is zero.
+ if (r1 === r2) return (d2 - d1) / 2;
+ return (n / (r2 - r1)) * (d2 - d1);
+ };
diff --git a/src/Text/mini-charts/line/useLineCompute.ts b/src/Text/mini-charts/line/useLineCompute.ts
new file mode 100644
index 0000000..f52efa2
--- /dev/null
+++ b/src/Text/mini-charts/line/useLineCompute.ts
@@ -0,0 +1,87 @@
+import { useEffect, useState } from 'react';
+import { TEXT_THEME } from '../../constants';
+import type { Scale } from './scaleLinear';
+import { scaleLinear } from './scaleLinear';
+
+// adjust to draw line width
+const SCALE_ADJUST = 2;
+
+class Line {
+ protected data: number[] = [];
+
+ protected size: number = TEXT_THEME.fontSizeBase;
+
+ protected height: number = this.size;
+
+ protected width: number = this.getWidth();
+
+ protected xScale: Scale | undefined;
+
+ protected yScale: Scale | undefined;
+
+ protected points: [number, number][] = [];
+
+ constructor(size: number, data: number[] | undefined) {
+ this.size = size;
+ if (data) {
+ this.data = data;
+ this.compute();
+ }
+ }
+
+ protected getWidth() {
+ return Math.max(this.size * 2, this.data?.length * 2);
+ }
+
+ protected compute() {
+ if (!this.data) return;
+ this.height = this.size;
+ this.width = this.getWidth();
+ this.xScale = scaleLinear([0, this.width], [0, this.data?.length - 1]);
+ const [min, max] = [Math.min(...this.data), Math.max(...this.data)];
+ this.yScale = scaleLinear([SCALE_ADJUST, this.height - SCALE_ADJUST], [min, max]);
+ this.points = this.data.map((item, index) => [
+ this.xScale!(index),
+ this.height - this.yScale!(item),
+ ]);
+ }
+
+ getLinePath(): null | string {
+ if (!this.data?.length || !this.xScale || !this.yScale) return null;
+ const path = this.points.reduce((prev, [x, y], index) => {
+ if (index === 0) return `M${x} ${y}`;
+ return `${prev} L ${x} ${y}`;
+ }, '');
+ return path;
+ }
+
+ getPolygonPath(): null | string {
+ if (!this.data?.length || !this.xScale || !this.yScale) return null;
+ const polygonPoints = [...this.points];
+ const last = this.points[this.points.length - 1];
+ polygonPoints.push([last[0], this.height]);
+ polygonPoints.push([0, this.height]);
+ const startPoint = this.points[0];
+ polygonPoints.push(startPoint);
+
+ const path = polygonPoints.reduce((prev, [x, y]) => `${prev} ${x},${y}`, '');
+ return path;
+ }
+
+ getContainer() {
+ return [this.width, this.height];
+ }
+}
+
+export const useLineCompute = (size: number, data: undefined | number[]) => {
+ const [line, setLine] = useState(new Line(size, data));
+ useEffect(() => {
+ setLine(new Line(size, data));
+ }, [size, data]);
+ return {
+ width: line.getContainer()[0],
+ height: line.getContainer()[1],
+ linePath: line.getLinePath(),
+ polygonPath: line.getPolygonPath(),
+ };
+};
diff --git a/src/Text/mini-charts/proportion/getArcPath.ts b/src/Text/mini-charts/proportion/getArcPath.ts
new file mode 100644
index 0000000..44a271d
--- /dev/null
+++ b/src/Text/mini-charts/proportion/getArcPath.ts
@@ -0,0 +1,24 @@
+/**
+ * make data between 0 ~ 1
+ */
+function normalizeProportion(data: number | undefined) {
+ if (typeof data !== 'number') return 0;
+ if (data > 1) return 1;
+ if (data < 0) return 0;
+ return data;
+}
+
+export function getArcPath(size: number, data: number) {
+ const cx = size / 2;
+ const cy = size / 2;
+ const r = size / 2;
+ const angle = normalizeProportion(data) * 2 * Math.PI;
+ const dx = cx + r * Math.sin(angle);
+ const dy = cy - r * Math.cos(angle);
+ const path = `
+ M${cx} ${0}
+ A ${cx} ${cy} 0 ${angle > Math.PI ? 1 : 0} 1 ${dx} ${dy}
+ L ${cx} ${cy} Z
+ `;
+ return path;
+}
diff --git a/src/Text/mini-charts/proportion/index.tsx b/src/Text/mini-charts/proportion/index.tsx
new file mode 100644
index 0000000..a471936
--- /dev/null
+++ b/src/Text/mini-charts/proportion/index.tsx
@@ -0,0 +1,50 @@
+import { isNaN, isString, toNumber } from 'lodash';
+import React, { useMemo } from 'react';
+import type { VisTextProps } from '../../types';
+import { useSvgWrapper } from '../hooks/useSvgWrapper';
+import { getArcPath } from './getArcPath';
+
+const SHADOW_COLOR = '#CDDDFD';
+const FILL_COLOR = '#3471F9';
+
+function getProportionNumber(
+ children: string | string[], // markdown 中传入的 children 文本为 string[]
+ origin?: any,
+): number | undefined {
+ let result: number | undefined;
+ const originNumber = toNumber(origin);
+ if (!isNaN(originNumber)) {
+ result = originNumber;
+ } else {
+ let text;
+ if (isString(children)) {
+ text = children;
+ } else if (Array.isArray(children) && isString(children[0])) {
+ text = children[0];
+ }
+ if (text?.endsWith?.('%')) {
+ const percentageStr = text?.replace(/%$/, '');
+ const proportionNumber = toNumber(percentageStr);
+ if (!isNaN(proportionNumber)) result = proportionNumber / 100;
+ }
+ }
+ return result;
+}
+
+export const ProportionChart: React.FC = ({ origin, children }) => {
+ const data = useMemo(() => getProportionNumber(children, origin), [children, origin]);
+ const [Svg, fontSize] = useSvgWrapper();
+ const r = fontSize / 2;
+ return (
+ data && (
+
+
+ {data >= 1 ? (
+
+ ) : (
+
+ )}
+
+ )
+ );
+};
diff --git a/src/Text/types.ts b/src/Text/types.ts
new file mode 100644
index 0000000..852c467
--- /dev/null
+++ b/src/Text/types.ts
@@ -0,0 +1,24 @@
+import type { CSSProperties, PropsWithChildren } from 'react';
+import type { TextEntityType } from './config';
+
+/**
+ * the props for the Text
+ */
+export type VisTextProps = PropsWithChildren<{
+ // common props
+ style?: CSSProperties;
+ className?: string;
+
+ children: string; // 明确指出 children 是文本
+
+ /** 类型,包括内置的类型,以及 string 用于用户自行扩展 */
+ type?: TextEntityType;
+ /**
+ * 原始数值
+ * 用于传递各种情况的原始数据值,比如:
+ * 1. metric_value: 未经过格式化的指标值,原始数据;eg 1.234
+ * 2. proportion: 百分比,eg 0.37
+ * 3. trend_desc: 趋势值,eg [1, 2, 6, 18, 24, 48]
+ */
+ origin?: any;
+}>;
diff --git a/src/Treemap/demos/common.tsx b/src/Treemap/demos/common.tsx
new file mode 100644
index 0000000..7c1f351
--- /dev/null
+++ b/src/Treemap/demos/common.tsx
@@ -0,0 +1,29 @@
+import { Treemap } from '@antv/gpt-vis';
+import React from 'react';
+
+const data = [
+ { 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 },
+];
+
+export default () => {
+ return ;
+};
diff --git a/src/Treemap/demos/markdown.tsx b/src/Treemap/demos/markdown.tsx
new file mode 100644
index 0000000..79b343f
--- /dev/null
+++ b/src/Treemap/demos/markdown.tsx
@@ -0,0 +1,63 @@
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { ChartType, GPTVis, Treemap, withChartCode } from '@antv/gpt-vis';
+import React from 'react';
+
+const markdownContent = `
+当然了,以下是为你绘制的一个矩阵树图
+
+\`\`\`vis-chart
+{
+ "type": "treemap",
+ "data": [
+ {
+ "name": "产品A",
+ "value": 500,
+ "children": [
+ { "name": "子产品A1", "value": 200 },
+ { "name": "子产品A2", "value": 300 }
+ ]
+ },
+ { "name": "产品B", "value": 400 }
+ ]
+}
+
+\`\`\`
+`;
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.Treemap]: Treemap },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => (
+
+
+
+
+);
diff --git a/src/Treemap/index.md b/src/Treemap/index.md
new file mode 100644
index 0000000..436e042
--- /dev/null
+++ b/src/Treemap/index.md
@@ -0,0 +1,60 @@
+---
+order: 8
+group:
+ order: 1
+ title: 统计图
+demo: { cols: 2 }
+---
+
+# Treemap 矩阵树图
+
+## 代码演示
+
+单独使用
+
+使用 Markdown 协议
+
+## Spec
+
+```json
+{
+ "type": "treemap",
+ "data": [
+ {
+ "name": "<分类名称一>",
+ "value": <数值>,
+ "children": [
+ { "name": "<子分类名称>", "value": <数值> },
+ { "name": "<子分类名称>", "value": <数值> },
+ { "name": "<子分类名称>", "value": <数值> }
+ ]
+ },
+ {
+ "name": "<分类名称二>",
+ "value": <数值>,
+ "children": [
+ { "name": "<子分类名称>", "value": <数值> },
+ { "name": "<子分类名称>", "value": <数值> }
+ ]
+ }
+ ],
+}
+```
+
+## API
+
+### TreemapProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ---------- | -------- | ------ | -------------------------------------------------------------------------------------------------- |
+| data | TreeNode[] | 是 | - | 数据 |
+| title | string | 否 | - | 图表的标题 |
+| ... | - | - | - | 更多属性,详见 [Ant Design Charts ](https://ant-design-charts.antgroup.com/options/plots/overview) |
+
+### TreeNode
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| -------- | ---------- | -------- | ------ | -------------- |
+| name | string | 是 | - | 分类名称 |
+| value | number | 是 | - | 分类的数值大小 |
+| children | TreeNode[] | 否 | - | 子分类列表 |
diff --git a/src/Treemap/index.tsx b/src/Treemap/index.tsx
new file mode 100644
index 0000000..dc3244b
--- /dev/null
+++ b/src/Treemap/index.tsx
@@ -0,0 +1,55 @@
+import type { TreemapConfig } from '@ant-design/plots';
+import { Treemap as ADCTreemap } from '@ant-design/plots';
+import React, { useMemo } from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+type TreeNode = {
+ name: string;
+ value: number;
+ children?: TreeNode[];
+};
+// binNumber和binWidth为互斥属性,选其一即可
+type ADCTreemapConfig = TreemapConfig & {
+ valueField: string;
+};
+export type TreemapProps = BasePlotProps & Partial;
+
+const defaultConfig = (props: ADCTreemapConfig): TreemapConfig => {
+ const { valueField = 'value' } = props;
+ return {
+ legend: false,
+ layout: {
+ tile: 'treemapBinary',
+ paddingInner: 3,
+ },
+ valueField,
+ tooltip: {
+ items: [
+ (d) => {
+ return {
+ name: d.data?.name,
+ value: d[valueField],
+ };
+ },
+ ],
+ },
+ style: { fillOpacity: 0.8, labelFontSize: 12 },
+ };
+};
+const transform = (data: TreeNode[]) => {
+ return {
+ name: 'root',
+ children: data,
+ };
+};
+
+const Treemap = (props: TreemapProps) => {
+ const config = usePlotConfig('Treemap', defaultConfig, props);
+ const { data, ...others } = config;
+ const transformData = useMemo(() => transform(data), [data]);
+
+ return ;
+};
+
+export default Treemap;
diff --git a/src/WordCloud/index.md b/src/WordCloud/index.md
new file mode 100644
index 0000000..484ae0b
--- /dev/null
+++ b/src/WordCloud/index.md
@@ -0,0 +1,232 @@
+---
+order: 7
+group:
+ order: 1
+ title: 统计图
+---
+
+# WordCloud 词云图
+
+词云图通过把文本中常出现的关键词放大,让用户更容易找到重要信息。
+
+## 代码演示
+
+### 单独使用
+
+```jsx
+import React from 'react';
+import { WordCloud } from '@antv/gpt-vis';
+
+const data = [
+ { value: 11.3865516372, text: '物质' },
+ { value: 7.75434839431, text: '万物' },
+ { value: 9.29550746599, text: '感官' },
+ { value: 6.89126871927, text: '事物' },
+ { value: 11.739204307083542, text: '会变' },
+ { value: 9.29550746599, text: '感官' },
+ { value: 8.70772080109, text: '认知' },
+ { value: 13.68728134056, text: '元素' },
+ { value: 8.29487558568, text: '世间' },
+ { value: 8.77077893705, text: '肉眼' },
+ { value: 8.10461990121, text: '大自然' },
+ { value: 7.69410172525, text: '粒子' },
+ { value: 7.65457088649, text: '分割' },
+ { value: 21.6192703972, text: '积木' },
+ { value: 10.7226238216, text: '永恒不变' },
+ { value: 7.14957618304, text: '原子' },
+ { value: 7.95787827685, text: '尺度' },
+ { value: 7.36109169636, text: '衡量' },
+ { value: 11.7034530746, text: '明辨是非' },
+ { value: 9.14102377386, text: '存在' },
+ { value: 6.90950076485, text: '理性' },
+ { value: 12.06102341102, text: '模式' },
+ { value: 11.739204307083542, text: '理型' },
+ { value: 11.3865516372, text: '物质' },
+ { value: 9.54656840164, text: '东西' },
+ { value: 8.73505881114, text: '世界' },
+ { value: 34.45634359635, text: '事物' },
+ { value: 27.886522397969998, text: '感官' },
+ { value: 6.47412857958, text: '痛苦' },
+ { value: 6.06866347147, text: '避免' },
+ { value: 5.02315619812, text: '形式' },
+ { value: 4.80149232937, text: '方式' },
+ { value: 21.209681572, text: '万事万物' },
+ { value: 7.48068272383, text: '上帝' },
+ { value: 7.37711534583, text: '一体' },
+ { value: 7.36834335975, text: '宇宙' },
+ { value: 9.17328983326, text: '赦免' },
+ { value: 8.04274449749, text: '拯救' },
+ { value: 7.48068272383, text: '上帝' },
+ { value: 14.96136544766, text: '上帝' },
+ { value: 7.95787827685, text: '世人' },
+ { value: 14.96136544766, text: '上帝' },
+ { value: 11.67082488616, text: '创造' },
+ { value: 9.80633308975, text: '虚空' },
+ { value: 8.73505881114, text: '世界' },
+ { value: 16.57509909118, text: '启示' },
+ { value: 14.51638170122, text: '信仰' },
+ { value: 13.8190015297, text: '理性' },
+ { value: 10.5684731418, text: '观感' },
+ { value: 12.57964972316, text: '地球' },
+ { value: 12.45583951066, text: '太阳' },
+ { value: 11.39201164604, text: '运行' },
+ { value: 14.35128801962, text: '物体' },
+ { value: 11.18052531558, text: '状态' },
+ { value: 10.0505300503, text: '轴心' },
+ { value: 9.56994431169, text: '星球' },
+ { value: 9.29550746599, text: '感官' },
+ { value: 21.52693202943, text: '物体' },
+ { value: 7.36834335975, text: '宇宙' },
+ { value: 22.44204817149, text: '上帝' },
+ { value: 12.1089181827, text: '主宰世界' },
+ { value: 10.8096351986, text: '牵线' },
+ { value: 10.6818018271, text: '自然法则' },
+ { value: 18.59101493198, text: '感官' },
+ { value: 15.51728049278, text: '心灵' },
+ { value: 10.1394775363, text: '任何事物' },
+ { value: 14.95161725614, text: '哲学家' },
+ { value: 13.78796485028, text: '感受' },
+ { value: 10.3743171274, text: '天主' },
+ { value: 10.2117981979, text: '质料' },
+ { value: 9.14102377386, text: '存在' },
+ { value: 14.96136544766, text: '上帝' },
+ { value: 14.35850390238, text: '头顶' },
+ { value: 12.5143832909, text: '因果律' },
+ { value: 11.0674643079, text: '根植' },
+ { value: 16.20923980242, text: '大自然' },
+ { value: 13.10258821671, text: '世界' },
+ { value: 11.3865516372, text: '物质' },
+ { value: 10.1164880181, text: '无休止' },
+ { value: 13.10258821671, text: '世界' },
+ { value: 11.23942650284, text: '精神' },
+ { value: 10.1394775363, text: '脚踏实地' },
+ { value: 26.6063055869, text: '阶段' },
+ { value: 13.0408437271, text: '真实' },
+ { value: 10.48883682488, text: '是否' },
+ { value: 17.0798274558, text: '物质' },
+ { value: 11.739204307083542, text: '新的' },
+ { value: 11.23942650284, text: '精神' },
+ { value: 10.4471578861, text: '改变' },
+ { value: 10.21443458184, text: '造成' },
+ { value: 16.20923980242, text: '一艘' },
+ { value: 15.07529909688, text: '航行' },
+ { value: 11.739204307083542, text: '人则' },
+ { value: 11.7034530746, text: '物竞天择' },
+ { value: 10.5684731418, text: '适者生存' },
+ { value: 19.89886786678, text: '潜意识' },
+ { value: 12.76848869812, text: '意识' },
+];
+export default () => ;
+```
+
+### 使用 Markdown 协议
+
+```tsx
+import { Bubble, type BubbleProps } from '@ant-design/x';
+import { WordCloud, withChartCode, ChartType, GPTVis } from '@antv/gpt-vis';
+
+const bgStyle = {
+ display: 'grid',
+ gridGap: '20px 0',
+ background: '#f7f7f7',
+ padding: 20,
+ borderRadius: 8,
+};
+
+const markdownContent = `
+ ~~~vis-chart
+ {
+ "type": "word-cloud",
+ "data": [
+ { "value": 9, "text": "AntV" },
+ { "value": 8, "text": "F2" },
+ { "value": 8, "text": "G2" },
+ { "value": 8, "text": "G6" },
+ { "value": 8, "text": "DataSet" },
+ { "value": 8, "text": "墨者学院" },
+ { "value": 6, "text": "Analysis" },
+ { "value": 6, "text": "Data Mining" },
+ { "value": 6, "text": "Data Vis" },
+ { "value": 6, "text": "Design" },
+ { "value": 6, "text": "Grammar" },
+ { "value": 6, "text": "Graphics" },
+ { "value": 6, "text": "Graph" },
+ { "value": 6, "text": "Hierarchy" },
+ { "value": 6, "text": "Labeling" },
+ { "value": 6, "text": "Layout" },
+ { "value": 6, "text": "Quantitative" },
+ { "value": 6, "text": "Relation" },
+ { "value": 6, "text": "Statistics" },
+ { "value": 6, "text": "可视化" },
+ { "value": 6, "text": "数据" },
+ { "value": 6, "text": "数据可视化" }
+ ]
+ }
+~~~`;
+
+const CodeComponent = withChartCode({
+ components: { [ChartType.WordCloud]: WordCloud },
+ style: { width: 400 },
+});
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => (
+ {content}
+);
+
+export default () => {
+ return (
+
+
+
+
+ );
+};
+```
+
+## Spec
+
+```json
+{
+ "type": "word-cloud",
+ "data": [
+ { "value": 11.739204307083542, "text": "水是" },
+ { "value": 9.23723855786, "text": "之源" },
+ { "value": 7.75434839431, "text": "万物" },
+ { "value": 11.3865516372, "text": "物质" },
+ { "value": 7.75434839431, "text": "万物" },
+ { "value": 5.83541244308, "text": "创造" },
+ { "value": 4.27215339948, "text": "形成" }
+ ]
+}
+```
+
+## API
+
+### WordCloudProps
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------------------- | -------- | ------ | ---------- |
+| data | WordCloudDataItem[] | 是 | - | 数据 |
+| title | string | 否 | - | 图表的标题 |
+
+### WordCloudDataItem
+
+| 属性 | 类型 | 是否必传 | 默认值 | 说明 |
+| ----- | ------ | -------- | ------ | ---- |
+| text | string | 是 | - | 文本 |
+| value | number | 是 | - | 词频 |
diff --git a/src/WordCloud/index.tsx b/src/WordCloud/index.tsx
new file mode 100644
index 0000000..28943c9
--- /dev/null
+++ b/src/WordCloud/index.tsx
@@ -0,0 +1,28 @@
+import type { WordCloudConfig } from '@ant-design/plots';
+import { WordCloud as ADCWordCloud } from '@ant-design/plots';
+import React, { type FC } from 'react';
+import { usePlotConfig } from '../ConfigProvider/hooks';
+import type { BasePlotProps } from '../types';
+
+type WordCloudDataItem = {
+ text: string;
+ value: number;
+};
+
+const defaultConfig = {
+ autoFit: true,
+ layout: { fontSize: [20, 100] },
+ colorField: 'text',
+ legend: false,
+ tooltip: false,
+};
+
+export type WordCloudProps = BasePlotProps & Partial;
+
+const WordCloud: FC = (props) => {
+ const config = usePlotConfig('WordCloud', defaultConfig, props);
+
+ return ;
+};
+
+export default WordCloud;
diff --git a/src/components/A/index.tsx b/src/components/A/index.tsx
deleted file mode 100644
index e3d1667..0000000
--- a/src/components/A/index.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { LinkOutlined } from '@ant-design/icons';
-import React from 'react';
-
-export type AProps = {
- /**
- * 链接地址
- */
- href: string;
- /**
- *
- */
- children: string;
-};
-
-export const A: React.FC = ({ href, children }) => {
- console.log(222, children, href);
- return (
-
- );
-};
diff --git a/src/components/Code/index.tsx b/src/components/Code/index.tsx
deleted file mode 100644
index 3bbfb3d..0000000
--- a/src/components/Code/index.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-
-export type CodeProps = {
- className: string;
- inline: boolean;
- style: CSSStyleDeclaration;
- children: JSX.Element;
-};
-
-export const Reference: React.FC = () => {
- return
;
-};
diff --git a/src/components/index.ts b/src/components/index.ts
deleted file mode 100644
index 2c77bbd..0000000
--- a/src/components/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Components as ComponentsType } from 'react-markdown';
-import { A } from './A';
-
-export const Components: ComponentsType = {
- a: A,
-};
diff --git a/src/constants/index.ts b/src/constants/index.ts
new file mode 100644
index 0000000..ec88e86
--- /dev/null
+++ b/src/constants/index.ts
@@ -0,0 +1,10 @@
+import type { GlobalConfig } from '../types';
+
+import { DEFAULT_VIS_TEXT_CONFIG } from '../Text';
+
+export const DEFAULT_GLOBAL_CONFIG: GlobalConfig = {
+ map: { style: 'light' },
+ components: {
+ VisText: DEFAULT_VIS_TEXT_CONFIG,
+ },
+};
diff --git a/src/core/Conversation/index.tsx b/src/core/Conversation/index.tsx
deleted file mode 100644
index 24db827..0000000
--- a/src/core/Conversation/index.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import Markdown, { Options } from 'react-markdown';
-import rehypeRaw from 'rehype-raw';
-
-export type ConversationProps = {
- /**
- * 需要被渲染的 markdown 字符串
- */
- children: string;
- /**
- * 注册的组件
- */
- components?: Options['components'];
- /**
- * 注册的 rehype 插件
- */
- // rehypePlugins: Options['rehypePlugins'];
-};
-
-export const Conversation: React.FC = ({
- children,
- components,
-}) => {
- console.log('components', components, children);
- return (
-
- {children}
-
- );
-};
diff --git a/src/core/index.ts b/src/core/index.ts
deleted file mode 100644
index cfb9ea9..0000000
--- a/src/core/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Conversation } from './Conversation';
diff --git a/src/export.ts b/src/export.ts
new file mode 100644
index 0000000..aeff9d9
--- /dev/null
+++ b/src/export.ts
@@ -0,0 +1,77 @@
+import { default as Area, type AreaProps } from './Area';
+import { default as Bar, type BarProps } from './Bar';
+import { default as Column, type ColumnProps } from './Column';
+import { default as DualAxes, type DualAxesProps } from './DualAxes';
+import { default as FlowDiagram, type FlowDiagramProps } from './FlowDiagram';
+import { default as HeatMap, type HeatMapProps } from './HeatMap';
+import { default as Histogram, type HistogramProps } from './Histogram';
+import { default as Line, type LineProps } from './Line';
+import { default as MindMap, type MindMapProps } from './MindMap';
+import { default as NetworkGraph, type NetworkGraphProps } from './NetworkGraph';
+import { default as PathMap, type PathMapProps } from './PathMap';
+import { default as Pie, type PieProps } from './Pie';
+import { default as PinMap, type PinMapProps } from './PinMap';
+import { default as Radar, type RadarProps } from './Radar';
+import { default as Scatter, type ScatterProps } from './Scatter';
+import { default as Treemap, type TreemapProps } from './Treemap';
+import { ChartType } from './types';
+import { default as WordCloud, type WordCloudProps } from './WordCloud';
+
+export { default as ConfigProvider, type ConfigProviderProps } from './ConfigProvider';
+export { default as IndentedTree, type IndentedTreeProps } from './IndentedTree';
+export { default as Map, type MapProps } from './Map';
+export { default as OrganizationChart, type OrganizationChartProps } from './OrganizationChart';
+
+export {
+ Area,
+ Bar,
+ Column,
+ DualAxes,
+ FlowDiagram,
+ HeatMap,
+ Histogram,
+ Line,
+ MindMap,
+ NetworkGraph,
+ PathMap,
+ Pie,
+ PinMap,
+ Radar,
+ Scatter,
+ Treemap,
+ WordCloud,
+ type AreaProps,
+ type BarProps,
+ type ColumnProps,
+ type DualAxesProps,
+ type FlowDiagramProps,
+ type HeatMapProps,
+ type HistogramProps,
+ type LineProps,
+ type MindMapProps,
+ type NetworkGraphProps,
+ type PathMapProps,
+ type PieProps,
+ type PinMapProps,
+ type RadarProps,
+ type ScatterProps,
+ type TreemapProps,
+ type WordCloudProps,
+};
+
+export { VisText, type VisTextProps } from './Text';
+
+export const DEFAULT_CHART_COMPONENTS: Record> = {
+ [ChartType.Line]: Line,
+ [ChartType.Column]: Column,
+ [ChartType.Pie]: Pie,
+ [ChartType.Bar]: Bar,
+ [ChartType.Area]: Area,
+ [ChartType.Scatter]: Scatter,
+ [ChartType.PinMap]: PinMap,
+ [ChartType.PathMap]: PathMap,
+ [ChartType.HeatMap]: HeatMap,
+ [ChartType.MindMap]: MindMap,
+ [ChartType.FlowDiagram]: FlowDiagram,
+ [ChartType.NetworkGraph]: NetworkGraph,
+};
diff --git a/src/global.d.ts b/src/global.d.ts
deleted file mode 100644
index 22f0fe6..0000000
--- a/src/global.d.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-declare namespace JSX {
- interface IntrinsicElements {
- 'chart-view': React.DetailedHTMLProps<
- React.HTMLAttributes,
- HTMLElement
- >;
- 'custom-view': React.DetailedHTMLProps<
- React.HTMLAttributes,
- HTMLElement
- >;
- 'references-view': React.DetailedHTMLProps<
- React.HTMLAttributes & {
- references: any;
- },
- HTMLElement
- >;
- 'summary-view': React.DetailedHTMLProps<
- React.HTMLAttributes,
- HTMLElement
- >;
- }
-}
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 0ca23c5..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.markdown p {
- margin-block-end: 12px;
-}
-
-.markdown blockquote {
- margin-block-end: 12px;
-}
diff --git a/src/index.ts b/src/index.ts
index 192078f..9ca8e80 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,8 @@
-export { Components } from './components';
-export { Conversation } from './core/Conversation';
+export * from './export';
+export { default as version } from './version';
-import './index.css';
+export { withChartCode, withDefaultChartCode } from './ChartCodeRender';
+export type { CodeBlockComponent, WithChartCodeOptions } from './ChartCodeRender/type';
+export { default as GPTVis, type GPTVisProps } from './GPTVis';
+
+export * from './types';
diff --git a/src/types/chart.ts b/src/types/chart.ts
new file mode 100644
index 0000000..76b1f85
--- /dev/null
+++ b/src/types/chart.ts
@@ -0,0 +1,77 @@
+import type { LarkMapProps } from '@antv/larkmap';
+import type { CSSProperties, ReactNode } from 'react';
+import type { Map } from './map';
+
+export type { LarkMapProps };
+
+export enum ChartType {
+ Pie = 'pie',
+ Column = 'column',
+ Line = 'line',
+ Area = 'area',
+ Scatter = 'scatter',
+ Histogram = 'histogram',
+ Treemap = 'treemap',
+ Bar = 'bar',
+ WordCloud = 'word-cloud',
+ DualAxes = 'dual-axes',
+ Radar = 'radar',
+ PinMap = 'pin-map',
+ PathMap = 'path-map',
+ HeatMap = 'heat-map',
+ MindMap = 'mind-map',
+ NetworkGraph = 'network-graph',
+ FlowDiagram = 'flow-diagram',
+ OrganizationChart = 'organization-chart',
+ IndentedTree = 'indented-tree',
+ VisText = 'vis-text',
+}
+
+export type Charts = keyof typeof ChartType;
+
+export interface BaseChartProps {
+ containerStyle?: CSSProperties;
+ className?: string;
+ children?: ReactNode;
+}
+
+export interface BasePlotProps extends BaseChartProps {
+ data: T[];
+ axisXTitle?: string;
+ axisYTitle?: string;
+}
+
+export interface BaseMapProps extends BaseChartProps, Map {
+ data: T[];
+ // 高德地图密钥
+ token?: string;
+}
+
+export interface BaseGraphProps extends BaseChartProps {
+ data: T;
+}
+
+interface GraphNode {
+ name: string;
+}
+
+interface GraphEdge {
+ source: string;
+ target: string;
+ name?: string;
+}
+
+export interface GraphData {
+ nodes: GraphNode[];
+ edges: GraphEdge[];
+}
+
+export interface TreeGraphData {
+ name: string;
+ children?: TreeGraphData[];
+ [key: string]: any;
+}
+
+export interface GraphProps extends BaseGraphProps {}
+
+export interface TreeGraphProps extends BaseGraphProps {}
diff --git a/src/types/config.ts b/src/types/config.ts
new file mode 100644
index 0000000..3c6091a
--- /dev/null
+++ b/src/types/config.ts
@@ -0,0 +1,23 @@
+import type { G2 } from '@ant-design/plots';
+import type { Charts } from './chart';
+
+type PlotGlobalConfig = {
+ theme?: G2.Theme;
+ [key: string]: any;
+};
+
+type MapGlobalConfig = {
+ style?: 'normal' | 'light' | 'dark' | string;
+ token?: string;
+ enableZoom?: boolean;
+ enableRotate?: boolean;
+ [key: string]: any;
+};
+
+type ComponentsGlobalConfig = Partial>>;
+
+export type GlobalConfig = {
+ plot?: PlotGlobalConfig;
+ map?: MapGlobalConfig;
+ components?: ComponentsGlobalConfig;
+};
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..196b998
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,2 @@
+export * from './chart';
+export type { GlobalConfig } from './config';
diff --git a/src/types/map.ts b/src/types/map.ts
new file mode 100644
index 0000000..21c0159
--- /dev/null
+++ b/src/types/map.ts
@@ -0,0 +1,248 @@
+type AsyncCall<
+ T = { [k: string | number | symbol]: any },
+ Success = any,
+ Fail = any,
+ Complete = any,
+> = (
+ params: T & {
+ success: (params: Success) => void;
+ fail: (params: Fail) => void;
+ complete?: (params: Complete) => void;
+ },
+) => void;
+
+/**
+ * 地图组件上下文对象
+ *
+ * @see https://opendocs.alipay.com/mini/api/mapcontext
+ */
+export interface MapContext {
+ /**
+ * 设置地图底图类型
+ *
+ * @see https://opendocs.alipay.com/mini/api/setmaptype
+ */
+ setMapType: AsyncCall<{ mapType: 0 | 1 | 2 | 3 | 4 }>;
+ /**
+ * 平移缩放到指定区域
+ *
+ * @see https://opendocs.alipay.com/mini/api/includepoints
+ */
+ includePoints: AsyncCall<{
+ points: Array<{ longitude: number; latitude: number }>;
+ padding?: [number, number, number, number];
+ }>;
+}
+
+export interface MapInstance {
+ getContext: () => MapContext;
+}
+
+/**
+ * 经纬度坐标点
+ */
+export interface LngLat {
+ longitude: number;
+ latitude: number;
+}
+
+/**
+ * 标记点
+ *
+ * 更多属性信息查看 @see https://opendocs.alipay.com/mini/component/map#markers
+ */
+export interface Marker extends LngLat {
+ id: string;
+ iconPath?: string;
+ width?: number;
+ height?: number;
+ alpha?: number;
+ anchorX?: number;
+ anchorY?: number;
+ label?: {
+ content?: string;
+ color?: string;
+ fontSize?: number;
+ borderRadius?: number;
+ bgColor?: string;
+ padding?: number;
+ };
+}
+
+/**
+ * 折线
+ *
+ * 更多属性信息查看 @see https://opendocs.alipay.com/mini/component/map#polyline
+ */
+export interface Polyline {
+ points: LngLat[];
+ //
+ width?: number;
+ color?: string;
+ dottedLine?: boolean;
+ //
+ iconPath?: string;
+ iconWidth?: number;
+ colorList?: string[];
+}
+
+/**
+ * 面
+ *
+ * @see https://opendocs.alipay.com/mini/component/map?pathHash=c9886630#polygon
+ */
+export interface Polygon {
+ points: LngLat[];
+ color?: string;
+ fillColor?: string;
+ width?: number;
+}
+
+/**
+ * @see https://opendocs.alipay.com/mini/component/map
+ */
+export interface Map {
+ id?: string;
+ /**
+ * @title 地图类型
+ * @description 底图的样式
+ *
+ * - 0 标准地图
+ * - 1 卫星地图
+ * - 2 夜视地图
+ * - 3 导航地图
+ * - 4 公交地图
+ */
+ mapType?: 0 | 1 | 2 | 3 | 4 | string;
+ /**
+ * @title 展示实时定位点
+ * @description 展示用户当前带箭头的实时定位点
+ */
+ showLocation?: boolean;
+ /**
+ * @title 中心纬度
+ * @description
+ */
+ latitude?: number;
+ /**
+ * @title 中心经度
+ * @description
+ */
+ longitude?: number;
+ /**
+ * @title 缩放级别
+ * @description 取值范围为 5-18
+ * @default "16"
+ */
+ scale?: number;
+ /**
+ * @title 倾斜角度
+ * @description 范围 0 ~ 40 , 关于 z 轴的倾
+ * @default "0"
+ */
+ skew?: number;
+ /**
+ * @title 顺时针旋转的角度
+ * @description 范围 0 ~ 360
+ * @default "0"
+ */
+ rotate?: number;
+ /**
+ * @title 视野范围
+ * @description 视野将进行小范围延伸包含传入的坐标
+ */
+ includePoints?: LngLat[];
+ /**
+ * @title 视野边界
+ * @description 视野在地图 padding 范围内展示
+ */
+ includePadding?: { left: number; right: number; top: number; bottom: number };
+ /**
+ * @title 是否启用缩放
+ * @description 缩放交互开关
+ * @default true
+ */
+ enableZoom?: boolean;
+ /**
+ * @title 是否启用拖动
+ * @description 拖动交互开关
+ * @default true
+ */
+ enableScroll?: boolean;
+ /**
+ * @title 是否启用旋转
+ * @description 旋转交互开关
+ * @default true
+ */
+ enableRotate?: boolean;
+
+ // * ------------------------------------------------------------ 数据
+
+ /**
+ * @title 标记点
+ * @description 可在地图上展示多个标记点
+ */
+ markers?: Marker[];
+ /**
+ * @title 折线
+ * @description 可在地图上展示多个折线
+ */
+ polyline?: Polyline[];
+ /**
+ * @title 面
+ * @description 可在地图上展示多个面
+ */
+ polygon?: Polygon[];
+
+ // * ------------------------------------------------------------ 事件
+
+ /**
+ * @title 地图视野变化事件
+ * @description 拖动/旋转/缩放地图时触发
+ */
+ onRegionChange?: (event: {
+ type: 'begin' | 'end';
+ latitude: number;
+ longitude: number;
+ scale: number;
+ skew: number;
+ rotate: number;
+ causedBy: 'update' | 'gesture';
+ }) => void;
+ /**
+ * @title 地图点击事件
+ * @description 点击地图时触发
+ */
+ onTap?: (event: { latitude: number; longitude: number }) => void;
+ /**
+ * @title 标记点击事件
+ * @description 点击标记及其标签时触发
+ */
+ onMarkerTap?: (event: { markerId: string; latitude: number; longitude: number }) => void;
+ /**
+ * @title 地图初始化完成事件
+ * @description 地图初始化完成即将开始渲染第一帧时触发
+ */
+ onInitComplete?: () => void;
+}
+
+export interface MarkerData extends Omit {
+ id?: string;
+ label?: string | Marker['label'];
+}
+
+export interface PinMap extends Map {
+ data?: MarkerData[];
+ markerStyle?: Partial;
+}
+
+export interface RouteData {
+ markers?: MarkerData[];
+ path?: Polyline;
+}
+
+export interface PathMap extends Map {
+ data?: RouteData[];
+ markerStyle?: Partial;
+ pathStyle?: Partial;
+}
diff --git a/src/utils/config.ts b/src/utils/config.ts
new file mode 100644
index 0000000..1144263
--- /dev/null
+++ b/src/utils/config.ts
@@ -0,0 +1,59 @@
+import { isPlainObject, mergeWith } from 'lodash';
+import { DEFAULT_GLOBAL_CONFIG } from '../constants';
+import type { GlobalConfig } from '../types';
+
+export const mergeGlobalConfig = (config: GlobalConfig) => {
+ const globalConfig = mergeWith({}, DEFAULT_GLOBAL_CONFIG, config, (objValue) => {
+ if (Array.isArray(objValue)) {
+ return objValue;
+ }
+ });
+
+ return globalConfig;
+};
+
+function mergeGraphFunctions(
+ key: string,
+ currValue: (value: any) => any,
+ prevValue: (value: any) => any,
+) {
+ if (['plugins', 'behaviors', 'transforms'].includes(key)) {
+ return (prev: any[]) => currValue(prev);
+ } else {
+ return function (datum: any) {
+ // @ts-ignore this refers to the graph instance
+ const value = currValue.call(this, datum);
+ if (isPlainObject(value)) return mergeGraphOptions(prevValue, value);
+ return value;
+ };
+ }
+}
+
+export function mergeGraphOptions(...options: Record[]) {
+ if (options.length === 0) return {};
+
+ 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') {
+ merged[key] = mergeGraphFunctions(key, currValue, prevValue);
+ } else if (isPlainObject(currValue) && isPlainObject(prevValue)) {
+ merged[key] = mergeGraphOptions(prevValue, currValue);
+ } else {
+ merged[key] = currValue;
+ }
+ }
+ }
+ }
+
+ return merged;
+}
diff --git a/src/utils/graph.ts b/src/utils/graph.ts
new file mode 100644
index 0000000..b507326
--- /dev/null
+++ b/src/utils/graph.ts
@@ -0,0 +1,39 @@
+// FIXME: 全量导入 G6 模块
+import { G6 } from '@ant-design/graphs';
+import type { GraphData, TreeGraphData } from '../types';
+
+const { treeToGraphData } = G6;
+
+export function visTreeData2GraphData(data: TreeGraphData) {
+ return treeToGraphData(data as unknown as G6.TreeData, {
+ getNodeData: (datum, depth) => {
+ datum.id = datum.name;
+ datum.depth = depth;
+ if (!datum.children) return datum as G6.NodeData;
+ const { children, ...restDatum } = datum;
+ return {
+ ...restDatum,
+ children: children.map((child) => child.name),
+ } as G6.NodeData;
+ },
+ getEdgeData: (source, target) =>
+ ({
+ source: source.name,
+ target: target.name,
+ }) as G6.EdgeData,
+ });
+}
+
+export function visGraphData2GraphData(data: GraphData) {
+ return {
+ nodes: data.nodes.map((node) => ({
+ id: node.name,
+ style: { labelText: node.name },
+ })),
+ edges: data.edges.map((edge) => ({
+ source: edge.source,
+ target: edge.target,
+ style: { labelText: edge.name },
+ })),
+ };
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 0000000..111a393
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1 @@
+export * from './map';
diff --git a/src/utils/map/context.ts b/src/utils/map/context.ts
new file mode 100644
index 0000000..956551c
--- /dev/null
+++ b/src/utils/map/context.ts
@@ -0,0 +1,21 @@
+import type { Scene } from '@antv/l7';
+import type { Map, Marker } from '../../types/map';
+import { urlToMarkerId } from './util';
+
+export function setMapContext(props: Map, scene: Scene) {
+ //初始化Image
+ if (props.markers) {
+ const icons = new Set(
+ props.markers
+ .filter((item: Marker) => item.iconPath !== undefined)
+ .map((item: Marker) => item.iconPath),
+ ) as unknown as string[];
+
+ return Promise.all(
+ Array.from(icons.values()).map(async (url: string) => {
+ const id = urlToMarkerId(url);
+ return await scene.addImage(id, url);
+ }),
+ );
+ }
+}
diff --git a/src/utils/map/controls.ts b/src/utils/map/controls.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/utils/map/index.ts b/src/utils/map/index.ts
new file mode 100644
index 0000000..8a1bfa0
--- /dev/null
+++ b/src/utils/map/index.ts
@@ -0,0 +1,6 @@
+export * from './context';
+export * from './markers';
+export * from './polyline';
+export * from './style';
+export * from './util';
+export * from './view';
diff --git a/src/utils/map/markers.ts b/src/utils/map/markers.ts
new file mode 100644
index 0000000..9d56252
--- /dev/null
+++ b/src/utils/map/markers.ts
@@ -0,0 +1,69 @@
+import { PointLayer } from '@antv/l7';
+import { isObject } from 'lodash';
+import type { Marker as MarkerProps } from '../../types/map';
+import { urlToMarkerId } from './util';
+
+export function setMarkers(data: MarkerProps[]) {
+ const items = data.map((item: MarkerProps) => {
+ return {
+ ...item,
+ label: isObject(item.label) ? item.label!.content : item.label,
+ color: item.label?.color,
+ bgColor: item.label?.bgColor,
+ fontSize: item.label?.fontSize,
+ offsets: [item.anchorX || 0, item.anchorY || -1],
+ iconPath: item.iconPath ? urlToMarkerId(item.iconPath) : undefined,
+ };
+ });
+ const icons = items.filter((item) => item.iconPath !== undefined);
+ const texts = items.filter((item) => item.label !== undefined);
+ const layers = [];
+ if (texts.length > 0) {
+ const offsets = texts[0].offsets;
+ const fontSize = texts[0].fontSize || 10;
+ const text = new PointLayer({
+ zIndex: 2,
+ })
+ .source(texts, {
+ parser: {
+ type: 'json',
+ x: 'longitude',
+ y: 'latitude',
+ },
+ })
+ .shape('label', 'text')
+ .size('fontSize')
+ .color('color')
+ .style({
+ opacity: 1,
+ textOffset: [offsets[0], -2 * offsets[1] * fontSize],
+ fontWeight: 600,
+ textAnchor: 'center',
+ stroke: texts[0].bgColor || '#ffffff', // 描边颜色
+ strokeWidth: 2, // 描边宽度
+ strokeOpacity: 1.0,
+ padding: [10, 10],
+ });
+ layers.push(text);
+ }
+ if (icons.length !== 0) {
+ const offsets = icons[0].offsets;
+
+ const width = icons[0].width || 10;
+ const iconLayer = new PointLayer()
+ .source(icons, {
+ parser: {
+ type: 'json',
+ x: 'longitude',
+ y: 'latitude',
+ },
+ })
+ .shape('iconPath')
+ .size('width')
+ .style({
+ offsets: [offsets[0], offsets[1] * width],
+ });
+ layers.push(iconLayer);
+ }
+ return layers;
+}
diff --git a/src/utils/map/polyline.ts b/src/utils/map/polyline.ts
new file mode 100644
index 0000000..1590a37
--- /dev/null
+++ b/src/utils/map/polyline.ts
@@ -0,0 +1,31 @@
+import { LineLayer } from '@antv/l7';
+import type { Polyline as PolylinePorps } from '../../types/map';
+
+export function setPolyline(data: PolylinePorps[]) {
+ const lineData = data.map((item: PolylinePorps) => {
+ const coord = item.points.map((point) => [point.longitude, point.latitude]);
+ return {
+ ...item,
+ coordinates: coord,
+ color: item.color,
+ width: item.width,
+ };
+ });
+ const isdash = lineData[0].dottedLine;
+ const lineLayer = new LineLayer()
+ .source(lineData, {
+ parser: {
+ type: 'json',
+ coordinates: 'coordinates',
+ },
+ })
+ .size('width')
+ .shape('line')
+ .color('color')
+ .style({
+ opacity: 1.0,
+ lineType: isdash ? 'dash' : 'solid',
+ dashArray: [3, 1],
+ });
+ return [lineLayer];
+}
diff --git a/src/utils/map/style.ts b/src/utils/map/style.ts
new file mode 100644
index 0000000..8f02444
--- /dev/null
+++ b/src/utils/map/style.ts
@@ -0,0 +1,98 @@
+import { isObject } from 'lodash';
+import type { MapProps } from '../../Map';
+import type { LarkMapProps } from '../../types';
+import type { Marker, MarkerData, Polyline } from '../../types/map';
+
+const DefaultMapConfig = {
+ mapOptions: {
+ center: [120.210792, 30.246026] as [number, number],
+ zoom: 16,
+ maxZoom: 18,
+ pitch: 0,
+ rotation: 0,
+ zoomEnable: true,
+ pitchEnable: true,
+ },
+};
+
+const formatMapStyle = (props: MapProps) => {
+ const config: LarkMapProps = {
+ mapOptions: {
+ ...DefaultMapConfig.mapOptions,
+ center:
+ props.longitude && props.latitude
+ ? ([props.longitude, props.latitude] as [number, number])
+ : DefaultMapConfig.mapOptions.center,
+ zoom: props.scale || DefaultMapConfig.mapOptions.zoom,
+ pitch: props.skew || DefaultMapConfig.mapOptions.pitch,
+ rotation: props.rotate || DefaultMapConfig.mapOptions.rotation,
+ zoomEnable: props.enableZoom || DefaultMapConfig.mapOptions.zoomEnable,
+ pitchEnable: props.enableRotate || DefaultMapConfig.mapOptions.pitchEnable,
+ // 地图底图样式
+ style: props?.mapType,
+ // 高德地图密钥
+ token: props?.token,
+ },
+ logoVisible: false,
+ };
+ return config;
+};
+
+const DefaultMarkerStyle = {
+ width: 12,
+ anchorX: 0,
+ anchorY: 1,
+ label: {
+ content: '',
+ color: '#000000',
+ fontSize: 10,
+ borderRadius: 5,
+ bgColor: '#ffffff',
+ padding: 5,
+ },
+ iconPath:
+ 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*3XdDTbsQ84QAAAAAAAAAAAAADmJ7AQ/original',
+};
+
+const formatMakerStyle = (data: MarkerData[], markerStyle: Partial) => {
+ const labelStyle = Object.assign({}, DefaultMarkerStyle.label, markerStyle?.label);
+ // label 优先级 data > markerStyle > DefaultMarkerStyle
+ return data.map((marker: MarkerData, index: number) => {
+ const { label, id, ...rest } = marker;
+ return {
+ ...DefaultMarkerStyle,
+ ...markerStyle,
+ ...rest,
+ label: {
+ ...labelStyle,
+ ...(isObject(label) ? label : { content: label }),
+ },
+ id: id || index.toString(),
+ };
+ });
+};
+
+const DefaultPolylineStyle = {
+ width: 2,
+ color: '#16f',
+ dottedLine: false,
+ zIndex: 1,
+};
+
+const formatPolylineStyle = (data: Polyline[] = [], polylineStyle: Partial) => {
+ return data.map((item: any) => {
+ return {
+ ...DefaultPolylineStyle,
+ ...polylineStyle,
+ ...item,
+ };
+ });
+};
+
+export {
+ DefaultMarkerStyle,
+ DefaultPolylineStyle,
+ formatMakerStyle,
+ formatMapStyle,
+ formatPolylineStyle,
+};
diff --git a/src/utils/map/util.ts b/src/utils/map/util.ts
new file mode 100644
index 0000000..ecf2a02
--- /dev/null
+++ b/src/utils/map/util.ts
@@ -0,0 +1,13 @@
+export function urlToMarkerId(url: string) {
+ let hash = 0;
+ if (url.length > 0) {
+ for (let i = 0; i < url.length; i++) {
+ hash = ((hash << 5) - hash + url.charCodeAt(i)) | 0;
+ }
+ }
+ // 将哈希值转换为四位数
+ const id = Math.abs(hash % 10000)
+ .toString()
+ .padStart(4, '0');
+ return `marker-${id}`;
+}
diff --git a/src/utils/map/view.ts b/src/utils/map/view.ts
new file mode 100644
index 0000000..2eddd22
--- /dev/null
+++ b/src/utils/map/view.ts
@@ -0,0 +1,77 @@
+import type { Scene } from '@antv/l7';
+import type { Map } from '../../types/map';
+
+function fitIncludePoints(
+ includePoints: { longitude: number; latitude: number }[],
+ scene: Scene,
+ includePadding: Map['includePadding'],
+) {
+ if (includePoints.length === 1) {
+ scene.setCenter([includePoints[0].longitude, includePoints[0].latitude]);
+ } else {
+ const bounds = [180, 90, -180, -90];
+ includePoints.forEach((point: any) => {
+ if (bounds[0] > point.longitude) {
+ bounds[0] = point.longitude;
+ }
+ if (bounds[1] > point.latitude) {
+ bounds[1] = point.latitude;
+ }
+ if (bounds[2] < point.longitude) {
+ bounds[2] = point.longitude;
+ }
+ if (bounds[3] < point.latitude) {
+ bounds[3] = point.latitude;
+ }
+ });
+ const { left = 20, right = 20, bottom = 20, top = 20 } = includePadding || {};
+ const padding = [left, top, right, bottom];
+ // @ts-ignore
+ scene.map.setBounds(bounds, false, padding);
+ }
+}
+
+export const setMapView = (props: Map, scene: Scene) => {
+ // 单个点,多个点
+ if (props.includePoints) {
+ fitIncludePoints(props.includePoints, scene, props.includePadding);
+ } else {
+ const points: { longitude: number; latitude: number }[] = [];
+ if (props.markers) {
+ props.markers.forEach((item) => {
+ points.push({ longitude: item.longitude, latitude: item.latitude });
+ });
+ }
+ if (props.polyline) {
+ props.polyline.forEach((item) => {
+ item.points.forEach((point) => {
+ points.push({ longitude: point.longitude, latitude: point.latitude });
+ });
+ });
+ }
+ if (points.length) {
+ fitIncludePoints(points, scene, props.includePadding);
+ }
+ }
+
+ if (props.enableZoom !== undefined) {
+ scene.setMapStatus({
+ zoomEnable: props.enableZoom,
+ });
+ }
+ if (props.enableRotate !== undefined) {
+ scene.setMapStatus({
+ rotateEnable: props.enableRotate,
+ });
+ }
+ if (props.enableScroll !== undefined) {
+ scene.setMapStatus({
+ dragEnable: props.enableScroll,
+ });
+ }
+
+ if (props.onInitComplete) {
+ scene.off('loaded', props.onInitComplete);
+ scene.on('loaded', props.onInitComplete);
+ }
+};
diff --git a/src/utils/plot.ts b/src/utils/plot.ts
new file mode 100644
index 0000000..f7a65fb
--- /dev/null
+++ b/src/utils/plot.ts
@@ -0,0 +1,63 @@
+import { isUndefined } from 'lodash';
+
+const ADC_ENCODE_FIELDS = new Map([
+ ['x', 'xField'],
+ ['y', 'yField'],
+ ['series', 'seriesField'],
+ ['size', 'sizeField'],
+ ['color', 'colorField'],
+ ['shape', 'shapeField'],
+ ['y', 'angleField'],
+]);
+
+/**
+ * 将 G2 encode 写法转换为 ADC Plot 字段映射
+ */
+function visG2Encode2ADCEncode>(config: T) {
+ const encodeConfig = config.encode;
+ if (!encodeConfig) return config;
+
+ const _config = { ...config };
+ for (const field of encodeConfig) {
+ const adcField = ADC_ENCODE_FIELDS.get(field);
+ if (adcField) {
+ // @ts-expect-error
+ _config[adcField] = encodeConfig[field];
+ }
+ }
+
+ return _config;
+}
+
+/**
+ * 将缩写的 axisTitle 转换为 G2 axis 配置
+ */
+function axisTitle2G2axis>(config: T) {
+ const { axisXTitle, axisYTitle, axis } = config;
+
+ if (!isUndefined(axis)) return config;
+
+ const _config = { axis: {}, ...config };
+
+ if (axisXTitle) {
+ // @ts-expect-error
+ _config.axis.x = { title: axisXTitle };
+ }
+
+ if (axisYTitle) {
+ // @ts-expect-error
+ _config.axis.y = { title: axisYTitle };
+ }
+
+ return _config;
+}
+
+/**
+ * 将 GPT-Vis 图表的配置转换为 ADC 的配置
+ */
+export function transform2ADCProps>(props: T) {
+ const transformedEncode = visG2Encode2ADCEncode(props);
+ const transformedAxis = axisTitle2G2axis(transformedEncode);
+
+ return transformedAxis;
+}
diff --git a/src/version.ts b/src/version.ts
new file mode 100644
index 0000000..8b2ab62
--- /dev/null
+++ b/src/version.ts
@@ -0,0 +1 @@
+export default '0.0.1';
diff --git a/tailwind.config.js b/tailwind.config.js
deleted file mode 100644
index 1db67d1..0000000
--- a/tailwind.config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-module.exports = {
- content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
- darkMode: 'class',
- theme: {
- fontFamily: {
- sans: ['"Josefin Sans"'],
- },
- extend: {},
- },
- plugins: [],
-};
diff --git a/tailwind.css b/tailwind.css
deleted file mode 100644
index cc826d3..0000000
--- a/tailwind.css
+++ /dev/null
@@ -1,7 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-svg {
- display: inline-block;
-}
\ No newline at end of file
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
new file mode 100644
index 0000000..4131afc
--- /dev/null
+++ b/tsconfig.eslint.json
@@ -0,0 +1,14 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "noEmit": true
+ },
+ // paths intend to lint
+ "include": [
+ "./*.?*.ts",
+ "src",
+ // test workspace lint
+ "__tests__"
+ ],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/tsconfig.json b/tsconfig.json
index c3185b8..a579fbc 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,22 +1,18 @@
{
"compilerOptions": {
- "strictNullChecks": true,
+ "target": "esnext",
"module": "esnext",
"moduleResolution": "node",
- "esModuleInterop": true,
- "experimentalDecorators": true,
"jsx": "react",
- "jsxFactory": "React.createElement",
- "jsxFragmentFactory": "React.Fragment",
- "noUnusedParameters": true,
- "noUnusedLocals": true,
- "noImplicitAny": true,
- "target": "es6",
- "lib": ["dom", "es2017"],
+ "esModuleInterop": true,
+ "strict": true,
"skipLibCheck": true,
- "stripInternal": true,
- "declaration": true
+ "declaration": true,
+ "resolveJsonModule": true,
+ "baseUrl": "./",
+ "paths": {
+ "@antv/gpt-vis": ["./src/index.ts"]
+ }
},
- "include": ["src/**/*"],
- "exclude": ["node_modules", "lib", "es"]
+ "exclude": ["node_modules", "dist"]
}
diff --git a/vite.config.js b/vite.config.js
deleted file mode 100644
index 5d00302..0000000
--- a/vite.config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import path from 'path';
-import { defineConfig } from 'vite';
-
-export default defineConfig({
- root: path.resolve('./dev'),
- server: { port: 8080, open: '/' },
-});