├── .bunfig.toml ├── .changelogrc.js ├── .commitlintrc.js ├── .dumirc.ts ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .fatherrc.ts ├── .github ├── ISSUE_TEMPLATE │ ├── 1_bug_report.yml │ ├── 2_feature_request.yml │ ├── 3_question.yml │ └── 4_other.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── issue-auto-comments.yml │ ├── issue-check-inactive.yml │ ├── issue-close-require.yml │ ├── pkg.pr.new.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .i18nrc.cjs ├── .npmignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .releaserc.js ├── .remarkrc.js ├── .stylelintrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── clean-package.config.js ├── docs ├── changelog.md ├── index.md └── index.tsx ├── package.json ├── public └── robots.txt ├── renovate.json ├── src ├── AreaChart │ ├── demos │ │ ├── axis.tsx │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── customTooltip.tsx │ │ ├── example.tsx │ │ └── index.tsx │ ├── index.md │ ├── index.tsx │ └── styles.ts ├── BarChart │ ├── demos │ │ ├── axis.tsx │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── customTooltip.tsx │ │ ├── example.tsx │ │ ├── groups.tsx │ │ ├── index.tsx │ │ ├── loading.tsx │ │ ├── noData.tsx │ │ └── xAxisLabelFormatter.tsx │ ├── index.md │ ├── index.tsx │ ├── renderShape.tsx │ └── styles.ts ├── BarList │ ├── demos │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── example.tsx │ │ └── index.tsx │ ├── index.md │ ├── index.tsx │ └── styles.ts ├── DataBars │ ├── CategoryBar.tsx │ ├── DeltaBar.tsx │ ├── MarkerBar.tsx │ ├── ProgressBar.tsx │ ├── demos │ │ ├── CategoryBar.tsx │ │ ├── DeltaBar.tsx │ │ ├── MarkerBar.tsx │ │ └── ProgressBar.tsx │ ├── index.md │ ├── index.tsx │ └── styles.ts ├── DonutChart │ ├── DonutChartTooltip.tsx │ ├── demos │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── customTooltip.tsx │ │ ├── example.tsx │ │ ├── index.tsx │ │ └── withLegend.tsx │ ├── index.md │ ├── index.tsx │ ├── inputParser.ts │ ├── renderInactiveShape.tsx │ └── styles.ts ├── FunnelChart │ ├── ArrowRightIcon.tsx │ ├── demos │ │ ├── CalculateFrom.tsx │ │ └── index.tsx │ ├── index.md │ ├── index.tsx │ ├── styles.ts │ └── types.ts ├── Heatmaps │ ├── Calendar.tsx │ ├── ChartLabels.tsx │ ├── Footer.tsx │ ├── demos │ │ ├── activityLevels.tsx │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── customTooltip.tsx │ │ ├── customization.tsx │ │ ├── data.ts │ │ ├── dataRanges.tsx │ │ ├── example.tsx │ │ ├── i18n.tsx │ │ ├── index.tsx │ │ └── loading.tsx │ ├── index.md │ ├── index.tsx │ └── styles.ts ├── Legend │ ├── LegendItem.tsx │ ├── ScrollButton.tsx │ ├── demos │ │ └── index.tsx │ ├── index.md │ └── index.tsx ├── LineChart │ ├── demos │ │ ├── axis.tsx │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── customTooltip.tsx │ │ ├── example.tsx │ │ └── index.tsx │ ├── index.md │ ├── index.tsx │ └── styles.ts ├── ScatterChart │ ├── ScatterChartTooltip.tsx │ ├── ScatterChartTooltipRow.tsx │ ├── demos │ │ ├── axis.tsx │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── customTooltip.tsx │ │ ├── example.tsx │ │ └── index.tsx │ ├── index.md │ ├── index.tsx │ ├── renderShape.tsx │ └── styles.ts ├── SparkChart │ ├── SparkAreaChart.tsx │ ├── SparkBarChart.tsx │ ├── SparkLineChart.tsx │ ├── demos │ │ ├── SparkAreaChart.tsx │ │ ├── SparkBarChart.tsx │ │ └── SparkLineChart.tsx │ ├── index.md │ └── index.tsx ├── Tracker │ ├── TrackerBlock.tsx │ ├── demos │ │ ├── clickEvent.tsx │ │ ├── customColors.tsx │ │ ├── customTooltip.tsx │ │ ├── example.tsx │ │ └── index.tsx │ ├── index.md │ ├── index.tsx │ └── styles.ts ├── common │ ├── BaseAnimationTimingProps.tsx │ ├── BaseChartProps.tsx │ ├── BaseSparkChartProps.tsx │ ├── ChartLegend.tsx │ ├── ChartTooltip │ │ ├── ChartTooltipFrame.tsx │ │ ├── ChartTooltipRow.tsx │ │ └── index.tsx │ ├── CustomTooltipProps.tsx │ ├── CustomYAxisTick.tsx │ ├── NoData.tsx │ └── utils.ts ├── hooks │ ├── useOnWindowResize.ts │ ├── usePrefersReducedMotion.ts │ └── useThemeColorRange.ts ├── index.ts ├── types │ ├── charts.ts │ └── global.d.ts └── utils │ ├── calendar.ts │ ├── getMaxLabelLength.ts │ ├── index.ts │ └── theme.ts ├── tsconfig.json └── vitest.config.ts /.bunfig.toml: -------------------------------------------------------------------------------- 1 | [install.lockfile] 2 | 3 | save = false 4 | -------------------------------------------------------------------------------- /.changelogrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@lobehub/lint').changelog; 2 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@lobehub/lint').commitlint; 2 | -------------------------------------------------------------------------------- /.dumirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi'; 2 | import { SiteThemeConfig } from 'dumi-theme-lobehub'; 3 | import { INavItem } from 'dumi/dist/client/theme-api/types'; 4 | 5 | import { description, homepage, name } from './package.json'; 6 | 7 | const isProduction = process.env.NODE_ENV === 'production'; 8 | const isWin = process.platform === 'win32'; 9 | 10 | const nav: INavItem[] = [ 11 | { link: '/components/bar-chart', title: 'Charts' }, 12 | { link: 'https://ui.lobehub.com', mode: 'override', title: 'UI' }, 13 | { link: 'https://icon.lobehub.com', mode: 'override', title: 'Icons' }, 14 | { link: '/changelog', title: 'Changelog' }, 15 | ]; 16 | 17 | const themeConfig: SiteThemeConfig = { 18 | actions: [ 19 | { 20 | github: true, 21 | link: homepage, 22 | openExternal: true, 23 | text: 'GitHub', 24 | }, 25 | { 26 | link: '/components/area-chart', 27 | text: 'Get Started', 28 | type: 'primary', 29 | }, 30 | ], 31 | analytics: { 32 | plausible: { 33 | domain: 'charts.lobehub.com', 34 | scriptBaseUrl: 'https://plausible.lobehub-inc.cn', 35 | }, 36 | }, 37 | apiHeader: { 38 | docUrl: `{github}/tree/master/src/{atomId}/index.md`, 39 | match: ['/components'], 40 | pkg: name, 41 | sourceUrl: `{github}/tree/master/src/{atomId}/index.tsx`, 42 | }, 43 | description: description, 44 | giscus: { 45 | category: 'Q&A', 46 | categoryId: 'DIC_kwDOLNrpbc4Cin_G', 47 | repo: 'lobehub/lobe-charts', 48 | repoId: 'R_kgDOLNrpbQ', 49 | }, 50 | metadata: { 51 | openGraph: { 52 | image: 53 | 'https://repository-images.githubusercontent.com/752544109/5bb43f23-aed9-44b0-b08e-91776eadfe1c', 54 | }, 55 | }, 56 | name: 'Charts', 57 | nav, 58 | prefersColor: { 59 | default: 'dark', 60 | switch: false, 61 | }, 62 | socialLinks: { 63 | discord: 'https://discord.gg/AYFPHvv2jT', 64 | github: homepage, 65 | }, 66 | title: 'Lobe Charts', 67 | }; 68 | 69 | export default defineConfig({ 70 | apiParser: isProduction ? {} : false, 71 | base: '/', 72 | define: { 73 | 'process.env': process.env, 74 | }, 75 | exportStatic: {}, 76 | extraBabelPlugins: ['antd-style'], 77 | favicons: ['https://lobehub.com/favicon.ico'], 78 | jsMinifier: 'swc', 79 | locales: [{ id: 'en-US', name: 'English' }], 80 | mako: isWin || isProduction ? false : {}, 81 | mfsu: isWin ? undefined : {}, 82 | npmClient: 'pnpm', 83 | publicPath: '/', 84 | resolve: isProduction 85 | ? { 86 | entryFile: './src/index.ts', 87 | } 88 | : undefined, 89 | sitemap: { 90 | hostname: 'https://charts.lobehub.com', 91 | }, 92 | ssr: isProduction ? {} : false, 93 | styles: [ 94 | `html, body { background: transparent; } 95 | 96 | @media (prefers-color-scheme: dark) { 97 | html, body { background: #000; } 98 | }`, 99 | ], 100 | themeConfig, 101 | title: 'Lobe Charts', 102 | }); 103 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Eslintignore for LobeHub 2 | ################################################################ 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # ci 8 | coverage 9 | .coverage 10 | 11 | # test 12 | jest* 13 | _test_ 14 | __test__ 15 | 16 | # umi 17 | .umi 18 | .umi-production 19 | .umi-test 20 | .dumi/tmp* 21 | !.dumirc.ts 22 | 23 | # production 24 | dist 25 | es 26 | lib 27 | logs 28 | 29 | # misc 30 | # add other ignore file below 31 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@lobehub/lint').eslint; 2 | -------------------------------------------------------------------------------- /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'father'; 2 | 3 | export default defineConfig({ 4 | esm: { output: 'es' }, 5 | }); 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: '🐛 反馈缺陷 Bug Report' 2 | description: '反馈一个问题缺陷 | Report an bug' 3 | title: '[Bug] ' 4 | labels: '🐛 Bug' 5 | body: 6 | - type: dropdown 7 | attributes: 8 | label: '💻 系统环境 | Operating System' 9 | options: 10 | - Windows 11 | - macOS 12 | - Ubuntu 13 | - Other Linux 14 | - Other 15 | validations: 16 | required: true 17 | - type: dropdown 18 | attributes: 19 | label: '🌐 浏览器 | Browser' 20 | options: 21 | - Chrome 22 | - Edge 23 | - Safari 24 | - Firefox 25 | - Other 26 | validations: 27 | required: true 28 | - type: textarea 29 | attributes: 30 | label: '🐛 问题描述 | Bug Description' 31 | description: A clear and concise description of the bug. 32 | validations: 33 | required: true 34 | - type: textarea 35 | attributes: 36 | label: '🚦 期望结果 | Expected Behavior' 37 | description: A clear and concise description of what you expected to happen. 38 | - type: textarea 39 | attributes: 40 | label: '📷 复现步骤 | Recurrence Steps' 41 | description: A clear and concise description of how to recurrence. 42 | - type: textarea 43 | attributes: 44 | label: '📝 补充信息 | Additional Information' 45 | description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: '🌠 功能需求 Feature Request' 2 | description: '需求或建议 | Suggest an idea' 3 | title: '[Request] ' 4 | labels: '🌠 Feature Request' 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: '🥰 需求描述 | Feature Description' 9 | description: Please add a clear and concise description of the problem you are seeking to solve with this feature request. 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: '🧐 解决方案 | Proposed Solution' 15 | description: Describe the solution you'd like in a clear and concise manner. 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: '📝 补充信息 | Additional Information' 21 | description: Add any other context about the problem here. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3_question.yml: -------------------------------------------------------------------------------- 1 | name: '😇 疑问或帮助 Help Wanted' 2 | description: '疑问或需要帮助 | Need help' 3 | title: '[Question] ' 4 | labels: '😇 Help Wanted' 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: '🧐 问题描述 | Proposed Solution' 9 | description: A clear and concise description of the proplem. 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: '📝 补充信息 | Additional Information' 15 | description: Add any other context about the problem here. 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/4_other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '📝 其他 Other' 3 | about: '其他问题 | Other issues' 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### 💻 变更类型 | Change Type 2 | 3 | 4 | 5 | - [ ] ✨ feat 6 | - [ ] 🐛 fix 7 | - [ ] ♻️ refactor 8 | - [ ] 💄 style 9 | - [ ] 🔨 chore 10 | - [ ] 📝 docs 11 | 12 | #### 🔀 变更说明 | Description of Change 13 | 14 | 15 | 16 | #### 📝 补充信息 | Additional Information 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/issue-auto-comments.yml: -------------------------------------------------------------------------------- 1 | name: Issue Auto Comment 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | - closed 7 | - assigned 8 | pull_request_target: 9 | types: 10 | - opened 11 | - closed 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | run: 18 | permissions: 19 | issues: write # for actions-cool/issues-helper to update issues 20 | pull-requests: write # for actions-cool/issues-helper to update PRs 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Auto Comment on Issues Opened 24 | uses: wow-actions/auto-comment@v1 25 | with: 26 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} 27 | issuesOpened: | 28 | 👀 @{{ author }} 29 | Thank you for raising an issue. We will investigate into the matter and get back to you as soon as possible. 30 | Please make sure you have given us as much context as possible.\ 31 | 非常感谢您提交 issue。我们会尽快调查此事,并尽快回复您。 请确保您已经提供了尽可能多的背景信息。 32 | - name: Auto Comment on Issues Closed 33 | uses: wow-actions/auto-comment@v1 34 | with: 35 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} 36 | issuesClosed: | 37 | ✅ @{{ author }} 38 |
39 | This issue is closed, If you have any questions, you can comment and reply.\ 40 | 此问题已经关闭。如果您有任何问题,可以留言并回复。 41 | - name: Auto Comment on Pull Request Opened 42 | uses: wow-actions/auto-comment@v1 43 | with: 44 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} 45 | pullRequestOpened: | 46 | 👍 @{{ author }} 47 |
48 | Thank you for raising your pull request and contributing to our Community 49 | Please make sure you have followed our contributing guidelines. We will review it as soon as possible. 50 | If you encounter any problems, please feel free to connect with us.\ 51 | 非常感谢您提出拉取请求并为我们的社区做出贡献,请确保您已经遵循了我们的贡献指南,我们会尽快审查它。 52 | 如果您遇到任何问题,请随时与我们联系。 53 | - name: Auto Comment on Pull Request Merged 54 | uses: actions-cool/pr-welcome@main 55 | if: github.event.pull_request.merged == true 56 | with: 57 | token: ${{ secrets.GH_TOKEN }} 58 | comment: | 59 | ❤️ Great PR @${{ github.event.pull_request.user.login }} ❤️ 60 |
61 | The growth of project is inseparable from user feedback and contribution, thanks for your contribution!\ 62 | 项目的成长离不开用户反馈和贡献,感谢您的贡献! 63 | emoji: 'hooray' 64 | pr-emoji: '+1, heart' 65 | - name: Remove inactive 66 | if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login 67 | uses: actions-cool/issues-helper@v3 68 | with: 69 | actions: 'remove-labels' 70 | token: ${{ secrets.GH_TOKEN }} 71 | issue-number: ${{ github.event.issue.number }} 72 | labels: 'Inactive' 73 | -------------------------------------------------------------------------------- /.github/workflows/issue-check-inactive.yml: -------------------------------------------------------------------------------- 1 | name: Issue Check Inactive 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 */15 * *' 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | issue-check-inactive: 12 | permissions: 13 | issues: write # for actions-cool/issues-helper to update issues 14 | pull-requests: write # for actions-cool/issues-helper to update PRs 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: check-inactive 18 | uses: actions-cool/issues-helper@v3 19 | with: 20 | actions: 'check-inactive' 21 | token: ${{ secrets.GH_TOKEN }} 22 | inactive-label: 'Inactive' 23 | inactive-day: 30 24 | -------------------------------------------------------------------------------- /.github/workflows/issue-close-require.yml: -------------------------------------------------------------------------------- 1 | name: Issue Close Require 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | issue-close-require: 12 | permissions: 13 | issues: write # for actions-cool/issues-helper to update issues 14 | pull-requests: write # for actions-cool/issues-helper to update PRs 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: need reproduce 18 | uses: actions-cool/issues-helper@v3 19 | with: 20 | actions: 'close-issues' 21 | token: ${{ secrets.GH_TOKEN }} 22 | labels: '✅ Fixed' 23 | inactive-day: 3 24 | body: | 25 | 👋 @{{ github.event.issue.user.login }} 26 |
27 | Since the issue was labeled with `✅ Fixed`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\ 28 | 由于该 issue 被标记为已修复,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。 29 | - name: need reproduce 30 | uses: actions-cool/issues-helper@v3 31 | with: 32 | actions: 'close-issues' 33 | token: ${{ secrets.GH_TOKEN }} 34 | labels: '🤔 Need Reproduce' 35 | inactive-day: 3 36 | body: | 37 | 👋 @{{ github.event.issue.user.login }} 38 |
39 | Since the issue was labeled with `🤔 Need Reproduce`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\ 40 | 由于该 issue 被标记为需要更多信息,却 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。 41 | - name: need reproduce 42 | uses: actions-cool/issues-helper@v3 43 | with: 44 | actions: 'close-issues' 45 | token: ${{ secrets.GH_TOKEN }} 46 | labels: "🙅🏻‍♀️ WON'T DO" 47 | inactive-day: 3 48 | body: | 49 | 👋 @{{ github.event.issue.user.login }} 50 |
51 | Since the issue was labeled with `🙅🏻‍♀️ WON'T DO`, and no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.\ 52 | 由于该 issue 被标记为暂不处理,同时 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。 53 | -------------------------------------------------------------------------------- /.github/workflows/pkg.pr.new.yml: -------------------------------------------------------------------------------- 1 | name: Pkg Pr New CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - '!master' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Install bun 16 | uses: oven-sh/setup-bun@v2 17 | 18 | - name: Install deps 19 | run: bun i 20 | 21 | - name: CI 22 | run: bun run ci 23 | 24 | - name: Test 25 | run: bun run test 26 | 27 | - name: Build 28 | run: bun run build 29 | 30 | - name: Release 31 | run: bunx pkg-pr-new publish --comment=update 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - alpha 7 | - beta 8 | - rc 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Install bun 18 | uses: oven-sh/setup-bun@v2 19 | 20 | - name: Install deps 21 | run: bun i 22 | 23 | - name: CI 24 | run: bun run ci 25 | 26 | - name: Test 27 | run: bun run test 28 | 29 | - name: Build 30 | run: bun run build 31 | 32 | - name: Release 33 | run: bun run release 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - '!main' 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Install bun 15 | uses: oven-sh/setup-bun@v2 16 | 17 | - name: Install deps 18 | run: bun i 19 | 20 | - name: CI 21 | run: bun run ci 22 | 23 | - name: Test and coverage 24 | run: bun run test:coverage 25 | 26 | - name: Upload coverage to Codecov 27 | uses: codecov/codecov-action@v4 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gitignore for LobeHub 2 | ################################################################ 3 | 4 | # general 5 | .DS_Store 6 | .idea 7 | .vscode 8 | .history 9 | .temp 10 | .env.local 11 | venv 12 | temp 13 | tmp 14 | 15 | # dependencies 16 | node_modules 17 | *.log 18 | *.lock 19 | package-lock.json 20 | 21 | # ci 22 | coverage 23 | .coverage 24 | .eslintcache 25 | .stylelintcache 26 | 27 | # production 28 | dist 29 | es 30 | lib 31 | logs 32 | test-output 33 | 34 | # umi 35 | .umi 36 | .umi-production 37 | .umi-test 38 | .dumi/tmp* 39 | 40 | # husky 41 | .husky/prepare-commit-msg 42 | 43 | # misc 44 | # add other ignore file below 45 | bun.lockb -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npx --no -- commitlint --edit ${1} 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npm run type-check 4 | npm run lint:circular 5 | npx --no-install lint-staged 6 | -------------------------------------------------------------------------------- /.i18nrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | markdown: { 3 | entry: ['./README.md'], 4 | entryLocale: 'en-US', 5 | outputLocales: ['zh-CN'] 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.backup 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | lockfile=false 2 | resolution-mode=highest 3 | public-hoist-pattern[]=*@umijs/lint* 4 | public-hoist-pattern[]=*changelog* 5 | public-hoist-pattern[]=*commitlint* 6 | public-hoist-pattern[]=*eslint* 7 | public-hoist-pattern[]=*postcss* 8 | public-hoist-pattern[]=*prettier* 9 | public-hoist-pattern[]=*remark* 10 | public-hoist-pattern[]=*semantic-release* 11 | public-hoist-pattern[]=*stylelint* 12 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Prettierignore for LobeHub 2 | ################################################################ 3 | 4 | # general 5 | .DS_Store 6 | .editorconfig 7 | .idea 8 | .vscode 9 | .history 10 | .temp 11 | .env.local 12 | .husky 13 | .npmrc 14 | .gitkeep 15 | venv 16 | temp 17 | tmp 18 | LICENSE 19 | 20 | # dependencies 21 | node_modules 22 | *.log 23 | *.lock 24 | package-lock.json 25 | 26 | # ci 27 | coverage 28 | .coverage 29 | .eslintcache 30 | .stylelintcache 31 | test-output 32 | __snapshots__ 33 | *.snap 34 | 35 | # production 36 | dist 37 | es 38 | lib 39 | logs 40 | 41 | # umi 42 | .umi 43 | .umi-production 44 | .umi-test 45 | .dumi/tmp* 46 | 47 | # ignore files 48 | .*ignore 49 | 50 | # docker 51 | docker 52 | Dockerfile* 53 | 54 | # image 55 | *.webp 56 | *.gif 57 | *.png 58 | *.jpg 59 | 60 | # misc 61 | # add other ignore file below 62 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@lobehub/lint').prettier; 2 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@lobehub/lint').semanticRelease; 2 | -------------------------------------------------------------------------------- /.remarkrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@lobehub/lint').remarklint; 2 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const config = require('@lobehub/lint').stylelint; 2 | 3 | module.exports = { 4 | ...config, 5 | rules: { 6 | 'custom-property-pattern': null, 7 | 'selector-pseudo-element-no-unknown': null, 8 | 'no-descending-specificity': null, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 LobeHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /clean-package.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | indent: 2, 3 | remove: ['scripts', 'lint-staged', 'devDependencies', 'publishConfig', 'clean-package'], 4 | }; 5 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | description: New updates and improvements to @lobehub/icons 4 | nav: 5 | title: Changelog 6 | order: 999 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hero: 3 | title: LobeHub Charts 4 | description: React modern charts components built on recharts 5 | --- 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/index.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Snippet } from '@lobehub/ui'; 2 | import { Card } from 'antd'; 3 | import { Center, Flexbox } from 'react-layout-kit'; 4 | 5 | import AreaChart from '@/AreaChart/demos/example'; 6 | import BarChart from '@/BarChart/demos/example'; 7 | import BarChartGroups from '@/BarChart/demos/groups'; 8 | import DonutChart from '@/DonutChart/demos/withLegend'; 9 | import LineChart from '@/LineChart/demos/customColors'; 10 | import ScatterChart from '@/ScatterChart/demos/example'; 11 | 12 | export default () => { 13 | return ( 14 | 15 |
16 |

To install Lobe Charts, run the following command:

17 | {'$ bun add @lobehub/charts'} 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lobehub/charts", 3 | "version": "2.0.0", 4 | "description": "React modern charts components built on recharts", 5 | "keywords": [ 6 | "lobehub", 7 | "charts" 8 | ], 9 | "homepage": "https://github.com/lobehub/lobe-charts", 10 | "bugs": { 11 | "url": "https://github.com/lobehub/lobe-charts/issues/new" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/lobehub/lobe-charts.git" 16 | }, 17 | "license": "MIT", 18 | "author": "LobeHub ", 19 | "sideEffects": false, 20 | "main": "es/index.js", 21 | "module": "es/index.js", 22 | "types": "es/index.d.ts", 23 | "files": [ 24 | "es" 25 | ], 26 | "scripts": { 27 | "build": "father build", 28 | "build:watch": "father dev", 29 | "ci": "npm run lint && npm run type-check", 30 | "clean": "rm -r es lib dist coverage .dumi/tmp .eslintcache node_modules/.cache", 31 | "dev": "dumi dev", 32 | "docs:build": "dumi build", 33 | "docs:build-analyze": "ANALYZE=1 dumi build", 34 | "doctor": "father doctor", 35 | "lint": "eslint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix", 36 | "lint:circular": "dpdm src/**/*.{ts,tsx} --warning false --tree false --exit-code circular:1 -T true", 37 | "lint:md": "remark . --quiet --frail --output", 38 | "lint:style": "stylelint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix", 39 | "prepack": "clean-package", 40 | "postpack": "clean-package restore", 41 | "prepare": "husky && npm run setup", 42 | "prepublishOnly": "npm run build", 43 | "prettier": "prettier -c --write --no-error-on-unmatched-pattern \"**/**\"", 44 | "pull": "git pull", 45 | "release": "semantic-release", 46 | "setup": "dumi setup", 47 | "start": "npm run dev", 48 | "test": "vitest --passWithNoTests", 49 | "test:coverage": "vitest run --coverage --passWithNoTests", 50 | "test:update": "vitest -u", 51 | "type-check": "tsc --noEmit" 52 | }, 53 | "lint-staged": { 54 | "*.md": [ 55 | "remark --quiet --output --", 56 | "prettier --write --no-error-on-unmatched-pattern" 57 | ], 58 | "*.json": [ 59 | "prettier --write --no-error-on-unmatched-pattern" 60 | ], 61 | "*.{js,jsx}": [ 62 | "prettier --write", 63 | "stylelint --fix", 64 | "eslint --fix" 65 | ], 66 | "*.{ts,tsx}": [ 67 | "prettier --parser=typescript --write", 68 | "stylelint --fix", 69 | "eslint --fix" 70 | ] 71 | }, 72 | "dependencies": { 73 | "@lobehub/ui": "^2.0.0", 74 | "ahooks": "^3.8.4", 75 | "antd-style": "^3.7.1", 76 | "chroma-js": "^3.1.2", 77 | "date-fns": "^4.1.0", 78 | "dayjs": "^1.11.13", 79 | "lucide-react": "^0.469.0", 80 | "polished": "^4.3.1", 81 | "react-layout-kit": "^1.9.1", 82 | "recharts": "^2.15.2" 83 | }, 84 | "devDependencies": { 85 | "@commitlint/cli": "^19.8.0", 86 | "@icons-pack/react-simple-icons": "^9.7.0", 87 | "@lobehub/lint": "^1.26.1", 88 | "@testing-library/react": "^16.3.0", 89 | "@types/chroma-js": "^3.1.1", 90 | "@types/pangu": "^4.0.2", 91 | "@types/react": "^19.1.0", 92 | "@types/react-dom": "^19.1.2", 93 | "@vitest/coverage-v8": "~1.2.2", 94 | "babel-plugin-antd-style": "^1.0.4", 95 | "clean-package": "^2.2.0", 96 | "commitlint": "^19.8.0", 97 | "cross-env": "^7.0.3", 98 | "dpdm": "^3.14.0", 99 | "dumi": "^2.4.20", 100 | "dumi-theme-lobehub": "^2.0.0", 101 | "eslint": "^8.57.1", 102 | "father": "^4.5.2", 103 | "husky": "^9.1.7", 104 | "jsdom": "^25.0.1", 105 | "lint-staged": "^15.5.0", 106 | "prettier": "^3.5.3", 107 | "react": "^19.1.0", 108 | "react-dom": "^19.1.0", 109 | "remark": "^15.0.1", 110 | "remark-cli": "^12.0.1", 111 | "semantic-release": "^21.1.2", 112 | "stylelint": "^15.11.0", 113 | "typescript": "^5.8.3", 114 | "vitest": "~1.2.2" 115 | }, 116 | "peerDependencies": { 117 | "antd": "^5.23.0", 118 | "react": "^19.0.0", 119 | "react-dom": "^19.0.0" 120 | }, 121 | "publishConfig": { 122 | "access": "public", 123 | "registry": "https://registry.npmjs.org" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Allow: / 3 | 4 | Host: https://charts.lobehub.com 5 | Sitemap: https://charts.lobehub.com/sitemap.xml 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "automerge": false, 4 | "dependencyDashboard": true, 5 | "ignoreDeps": [], 6 | "labels": ["dependencies"], 7 | "postUpdateOptions": ["yarnDedupeHighest"], 8 | "prConcurrentLimit": 30, 9 | "prHourlyLimit": 0, 10 | "rebaseWhen": "conflicted", 11 | "schedule": "on sunday before 6:00am", 12 | "timezone": "UTC" 13 | } 14 | -------------------------------------------------------------------------------- /src/AreaChart/demos/axis.tsx: -------------------------------------------------------------------------------- 1 | import { AreaChart, AreaChartProps } from '@lobehub/charts'; 2 | 3 | const data: AreaChartProps['data'] = [ 4 | { 5 | Inverters: 2338, 6 | SolarPanels: 2890, 7 | date: 'Jan 22', 8 | }, 9 | { 10 | Inverters: 2103, 11 | SolarPanels: 2756, 12 | date: 'Feb 22', 13 | }, 14 | { 15 | Inverters: 2194, 16 | SolarPanels: 3322, 17 | date: 'Mar 22', 18 | }, 19 | { 20 | Inverters: 2108, 21 | SolarPanels: 3470, 22 | date: 'Apr 22', 23 | }, 24 | { 25 | Inverters: 1812, 26 | SolarPanels: 3475, 27 | date: 'May 22', 28 | }, 29 | { 30 | Inverters: 1726, 31 | SolarPanels: 3129, 32 | date: 'Jun 22', 33 | }, 34 | { 35 | Inverters: 1982, 36 | SolarPanels: 3490, 37 | date: 'Jul 22', 38 | }, 39 | { 40 | Inverters: 2012, 41 | SolarPanels: 2903, 42 | date: 'Aug 22', 43 | }, 44 | { 45 | Inverters: 2342, 46 | SolarPanels: 2643, 47 | date: 'Sep 22', 48 | }, 49 | { 50 | Inverters: 2473, 51 | SolarPanels: 2837, 52 | date: 'Oct 22', 53 | }, 54 | { 55 | Inverters: 3848, 56 | SolarPanels: 2954, 57 | date: 'Nov 22', 58 | }, 59 | { 60 | Inverters: 3736, 61 | SolarPanels: 3239, 62 | date: 'Dec 22', 63 | }, 64 | ]; 65 | 66 | const valueFormatter: AreaChartProps['valueFormatter'] = (number) => { 67 | return '$ ' + new Intl.NumberFormat('us').format(number).toString(); 68 | }; 69 | 70 | export default () => { 71 | return ( 72 | 80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /src/AreaChart/demos/clickEvent.tsx: -------------------------------------------------------------------------------- 1 | import { AreaChart, AreaChartProps, EventProps } from '@lobehub/charts'; 2 | import { Highlighter } from '@lobehub/ui'; 3 | import { useState } from 'react'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | const data: AreaChartProps['data'] = [ 7 | { 8 | 2022: 45, 9 | 2023: 78, 10 | date: 'Jan 23', 11 | }, 12 | { 13 | 2022: 52, 14 | 2023: 71, 15 | date: 'Feb 23', 16 | }, 17 | { 18 | 2022: 48, 19 | 2023: 80, 20 | date: 'Mar 23', 21 | }, 22 | { 23 | 2022: 61, 24 | 2023: 65, 25 | date: 'Apr 23', 26 | }, 27 | { 28 | 2022: 55, 29 | 2023: 58, 30 | date: 'May 23', 31 | }, 32 | { 33 | 2022: 67, 34 | 2023: 62, 35 | date: 'Jun 23', 36 | }, 37 | { 38 | 2022: 60, 39 | 2023: 54, 40 | date: 'Jul 23', 41 | }, 42 | { 43 | 2022: 72, 44 | 2023: 49, 45 | date: 'Aug 23', 46 | }, 47 | { 48 | 2022: 65, 49 | 2023: 52, 50 | date: 'Sep 23', 51 | }, 52 | { 53 | 2022: 68, 54 | 2023: null, 55 | date: 'Oct 23', 56 | }, 57 | { 58 | 2022: 74, 59 | 2023: null, 60 | date: 'Nov 23', 61 | }, 62 | { 63 | 2022: 71, 64 | 2023: null, 65 | date: 'Dec 23', 66 | }, 67 | ]; 68 | 69 | export default () => { 70 | const [value, setValue] = useState(null); 71 | return ( 72 | 73 |

Closed Pull Requests

74 | setValue(v)} 80 | /> 81 | 82 | {JSON.stringify(value, null, 2)} 83 | 84 |
85 | ); 86 | }; 87 | -------------------------------------------------------------------------------- /src/AreaChart/demos/customColors.tsx: -------------------------------------------------------------------------------- 1 | import { AreaChart, AreaChartProps } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | 4 | const data: AreaChartProps['data'] = [ 5 | { 6 | 'Distance Running': 167, 7 | 'Hatha Yoga': 115, 8 | 'Open Water Swimming': 135, 9 | 'Road Cycling': 145, 10 | 'Street Basketball': 150, 11 | 'date': 'Jan 23', 12 | }, 13 | { 14 | 'Distance Running': 125, 15 | 'Hatha Yoga': 85, 16 | 'Open Water Swimming': 155, 17 | 'Road Cycling': 110, 18 | 'Street Basketball': 180, 19 | 'date': 'Feb 23', 20 | }, 21 | { 22 | 'Distance Running': 156, 23 | 'Hatha Yoga': 90, 24 | 'Open Water Swimming': 145, 25 | 'Road Cycling': 149, 26 | 'Street Basketball': 130, 27 | 'date': 'Mar 23', 28 | }, 29 | { 30 | 'Distance Running': 165, 31 | 'Hatha Yoga': 105, 32 | 'Open Water Swimming': 125, 33 | 'Road Cycling': 112, 34 | 'Street Basketball': 170, 35 | 'date': 'Apr 23', 36 | }, 37 | { 38 | 'Distance Running': 153, 39 | 'Hatha Yoga': 100, 40 | 'Open Water Swimming': 165, 41 | 'Road Cycling': 138, 42 | 'Street Basketball': 110, 43 | 'date': 'May 23', 44 | }, 45 | { 46 | 'Distance Running': 124, 47 | 'Hatha Yoga': 75, 48 | 'Open Water Swimming': 175, 49 | 'Road Cycling': 145, 50 | 'Street Basketball': 140, 51 | 'date': 'Jun 23', 52 | }, 53 | ]; 54 | 55 | export default () => { 56 | const theme = useTheme(); 57 | return ( 58 | 64 | ); 65 | }; 66 | -------------------------------------------------------------------------------- /src/AreaChart/demos/customTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { AreaChart, AreaChartProps, ChartTooltipFrame } from '@lobehub/charts'; 2 | import { Typography } from 'antd'; 3 | import { useTheme } from 'antd-style'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | const data: AreaChartProps['data'] = [ 7 | { 8 | Running: 167, 9 | date: 'Jan 23', 10 | }, 11 | { 12 | Running: 125, 13 | date: 'Feb 23', 14 | }, 15 | { 16 | Running: 156, 17 | date: 'Mar 23', 18 | }, 19 | { 20 | Running: 165, 21 | date: 'Apr 23', 22 | }, 23 | { 24 | Running: 153, 25 | date: 'May 23', 26 | }, 27 | { 28 | Running: 124, 29 | date: 'Jun 23', 30 | }, 31 | ]; 32 | 33 | export default () => { 34 | const theme = useTheme(); 35 | 36 | const customTooltip: AreaChartProps['customTooltip'] = ({ payload, active }) => { 37 | if (!active || !payload) return null; 38 | return ( 39 | 40 | {payload.map((category: any, idx: number) => ( 41 | 42 | 47 | 48 | 49 | {category.dataKey} 50 | 51 | 52 | {category.value} bpm 53 | 54 | 55 | 56 | ))} 57 | 58 | ); 59 | }; 60 | return ( 61 | 62 |

Average BPM

63 | 64 |
65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /src/AreaChart/demos/example.tsx: -------------------------------------------------------------------------------- 1 | import { AreaChart, AreaChartProps } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | const data: AreaChartProps['data'] = [ 5 | { 6 | Inverters: 2338, 7 | SolarPanels: 2890, 8 | date: 'Jan 22', 9 | }, 10 | { 11 | Inverters: 2103, 12 | SolarPanels: 2756, 13 | date: 'Feb 22', 14 | }, 15 | { 16 | Inverters: 2194, 17 | SolarPanels: 3322, 18 | date: 'Mar 22', 19 | }, 20 | { 21 | Inverters: 2108, 22 | SolarPanels: 3470, 23 | date: 'Apr 22', 24 | }, 25 | { 26 | Inverters: 1812, 27 | SolarPanels: 3475, 28 | date: 'May 22', 29 | }, 30 | { 31 | Inverters: 1726, 32 | SolarPanels: 3129, 33 | date: 'Jun 22', 34 | }, 35 | { 36 | Inverters: 1982, 37 | SolarPanels: 3490, 38 | date: 'Jul 22', 39 | }, 40 | { 41 | Inverters: 2012, 42 | SolarPanels: 2903, 43 | date: 'Aug 22', 44 | }, 45 | { 46 | Inverters: 2342, 47 | SolarPanels: 2643, 48 | date: 'Sep 22', 49 | }, 50 | { 51 | Inverters: 2473, 52 | SolarPanels: 2837, 53 | date: 'Oct 22', 54 | }, 55 | { 56 | Inverters: 3848, 57 | SolarPanels: 2954, 58 | date: 'Nov 22', 59 | }, 60 | { 61 | Inverters: 3736, 62 | SolarPanels: 3239, 63 | date: 'Dec 22', 64 | }, 65 | ]; 66 | 67 | const valueFormatter: AreaChartProps['valueFormatter'] = (number) => { 68 | return '$ ' + new Intl.NumberFormat('us').format(number).toString(); 69 | }; 70 | 71 | export default () => { 72 | return ( 73 | 74 |
Newsletter Revenue
75 |

$34,567

76 | 82 |
83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /src/AreaChart/demos/index.tsx: -------------------------------------------------------------------------------- 1 | import { AreaChart, AreaChartProps } from '@lobehub/charts'; 2 | import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook'; 3 | import { FC } from 'react'; 4 | 5 | const data: AreaChartProps['data'] = [ 6 | { 7 | date: 'Jan 22', 8 | inverters: 2338, 9 | solarPanels: 2890, 10 | }, 11 | { 12 | date: 'Feb 22', 13 | inverters: 2103, 14 | solarPanels: 2756, 15 | }, 16 | { 17 | date: 'Mar 22', 18 | inverters: 2194, 19 | solarPanels: 3322, 20 | }, 21 | { 22 | date: 'Apr 22', 23 | inverters: 2108, 24 | solarPanels: 3470, 25 | }, 26 | { 27 | date: 'May 22', 28 | inverters: 1812, 29 | solarPanels: 3475, 30 | }, 31 | { 32 | date: 'Jun 22', 33 | inverters: 1726, 34 | solarPanels: 3129, 35 | }, 36 | { 37 | date: 'Jul 22', 38 | inverters: 1982, 39 | solarPanels: 3490, 40 | }, 41 | { 42 | date: 'Aug 22', 43 | inverters: 2012, 44 | solarPanels: 2903, 45 | }, 46 | { 47 | date: 'Sep 22', 48 | inverters: 2342, 49 | solarPanels: 2643, 50 | }, 51 | { 52 | date: 'Oct 22', 53 | inverters: 2473, 54 | solarPanels: 2837, 55 | }, 56 | { 57 | date: 'Nov 22', 58 | inverters: 3848, 59 | solarPanels: 2954, 60 | }, 61 | { 62 | date: 'Dec 22', 63 | inverters: 3736, 64 | solarPanels: 3239, 65 | }, 66 | ]; 67 | 68 | const valueFormatter: AreaChartProps['valueFormatter'] = (number: number) => 69 | `$${Intl.NumberFormat('us').format(number).toString()}`; 70 | 71 | const Demo: FC = () => { 72 | const store = useCreateStore(); 73 | 74 | const props: AreaChartProps | any = useControls( 75 | { 76 | allowDecimals: true, 77 | animationDuration: { 78 | step: 1, 79 | value: 900, 80 | }, 81 | autoMinValue: false, 82 | connectNulls: false, 83 | enableLegendSlider: false, 84 | intervalType: { 85 | options: ['preserveStart', 'preserveEnd', 'preserveStartEnd', 'equidistantPreserveStart'], 86 | value: 'equidistantPreserveStart', 87 | }, 88 | showAnimation: false, 89 | showGradient: true, 90 | showGridLines: true, 91 | showLegend: true, 92 | showTooltip: true, 93 | showXAxis: true, 94 | showYAxis: true, 95 | stack: false, 96 | startEndOnly: false, 97 | tickGap: { 98 | step: 1, 99 | value: 5, 100 | }, 101 | xAxisLabel: '', 102 | yAxisAlign: { 103 | options: ['left', 'right'], 104 | value: 'left', 105 | }, 106 | yAxisLabel: '', 107 | }, 108 | { store }, 109 | ); 110 | 111 | return ( 112 | 113 | console.log(v)} 122 | valueFormatter={valueFormatter} 123 | {...props} 124 | /> 125 | 126 | ); 127 | }; 128 | 129 | Demo.displayName = 'AreaChartsDemo'; 130 | 131 | export default Demo; 132 | -------------------------------------------------------------------------------- /src/AreaChart/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: Components 3 | group: Charts 4 | description: An area chart displays quantitative data graphically. It is based on the line chart but the area between x-axis and line is emphasized with color. 5 | order: 2 6 | --- 7 | 8 | 9 | 10 | ## Usage example 11 | 12 | The example below shows a composition combining an `AreaChart` with text elements. 13 | 14 | 15 | 16 | ## Usage example with click event 17 | 18 | The example below shows an interacive chart using the `onValueChange` prop. 19 | 20 | 21 | 22 | ## Usage example with custom tooltip 23 | 24 | The example below shows a custom tooltip using `customTooltip` prop. 25 | 26 | 27 | 28 | ## Usage example with a custom colors 29 | 30 | The example below shows a chart with custom `colors`. 31 | 32 | 33 | 34 | ## Usage example with x-axis and y-axis labels 35 | 36 | The example below shows added axis labels using `xAxisLabel` and `yAxisLabel` prop. 37 | 38 | 39 | 40 | ## API 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/AreaChart/styles.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ css, token }) => ({ 4 | emphasis: css` 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | `, 9 | gridLines: css` 10 | stroke: ${token.colorBorderSecondary}; 11 | stroke-width: 1; 12 | `, 13 | label: css` 14 | font-size: 12px; 15 | line-height: 16px; 16 | fill: ${token.colorTextDescription}; 17 | `, 18 | strongLabel: css` 19 | font-size: 12px; 20 | font-weight: 500; 21 | line-height: 16px; 22 | fill: ${token.colorTextSecondary}; 23 | `, 24 | })); 25 | -------------------------------------------------------------------------------- /src/BarChart/demos/axis.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart, BarChartProps } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | const data: BarChartProps['data'] = [ 5 | { 6 | 'Number of threatened species': 2488, 7 | 'name': 'Amphibians', 8 | }, 9 | { 10 | 'Number of threatened species': 1445, 11 | 'name': 'Birds', 12 | }, 13 | { 14 | 'Number of threatened species': 743, 15 | 'name': 'Crustaceans', 16 | }, 17 | { 18 | 'Number of threatened species': 281, 19 | 'name': 'Ferns', 20 | }, 21 | { 22 | 'Number of threatened species': 251, 23 | 'name': 'Arachnids', 24 | }, 25 | { 26 | 'Number of threatened species': 232, 27 | 'name': 'Corals', 28 | }, 29 | { 30 | 'Number of threatened species': 98, 31 | 'name': 'Algae', 32 | }, 33 | ]; 34 | 35 | const valueFormatter: BarChartProps['valueFormatter'] = (number) => 36 | Intl.NumberFormat('us').format(number).toString(); 37 | 38 | export default () => { 39 | return ( 40 | 41 |

Number of species threatened with extinction (2021)

42 | 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/BarChart/demos/clickEvent.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart, BarChartProps, EventProps } from '@lobehub/charts'; 2 | import { Highlighter } from '@lobehub/ui'; 3 | import { useState } from 'react'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | const data: BarChartProps['data'] = [ 7 | { 8 | '2022': 45, 9 | '2023': 78, 10 | 'date': 'Jan 23', 11 | }, 12 | { 13 | '2022': 52, 14 | '2023': 71, 15 | 'date': 'Feb 23', 16 | }, 17 | { 18 | '2022': 48, 19 | '2023': 80, 20 | 'date': 'Mar 23', 21 | }, 22 | { 23 | '2022': 61, 24 | '2023': 65, 25 | 'date': 'Apr 23', 26 | }, 27 | { 28 | '2022': 55, 29 | '2023': 58, 30 | 'date': 'May 23', 31 | }, 32 | { 33 | '2022': 67, 34 | '2023': 62, 35 | 'date': 'Jun 23', 36 | }, 37 | { 38 | '2022': 60, 39 | '2023': 54, 40 | 'date': 'Jul 23', 41 | }, 42 | { 43 | '2022': 72, 44 | '2023': 49, 45 | 'date': 'Aug 23', 46 | }, 47 | { 48 | '2022': 65, 49 | '2023': 52, 50 | 'date': 'Sep 23', 51 | }, 52 | { 53 | '2022': 68, 54 | '2023': null, 55 | 'date': 'Oct 23', 56 | }, 57 | { 58 | '2022': 74, 59 | '2023': null, 60 | 'date': 'Nov 23', 61 | }, 62 | { 63 | '2022': 71, 64 | '2023': null, 65 | 'date': 'Dec 23', 66 | }, 67 | ]; 68 | 69 | export default () => { 70 | const [value, setValue] = useState(null); 71 | return ( 72 | 73 |

Closed Pull Requests

74 | setValue(v)} 79 | /> 80 | 81 | {JSON.stringify(value, null, 2)} 82 | 83 |
84 | ); 85 | }; 86 | -------------------------------------------------------------------------------- /src/BarChart/demos/customColors.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart, BarChartProps } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | 4 | const data: BarChartProps['data'] = [ 5 | { 6 | 'Distance Running': 167, 7 | 'Hatha Yoga': 115, 8 | 'Open Water Swimming': 135, 9 | 'Road Cycling': 145, 10 | 'Street Basketball': 150, 11 | 'date': 'Jan 23', 12 | }, 13 | { 14 | 'Distance Running': 125, 15 | 'Hatha Yoga': 85, 16 | 'Open Water Swimming': 155, 17 | 'Road Cycling': 110, 18 | 'Street Basketball': 180, 19 | 'date': 'Feb 23', 20 | }, 21 | { 22 | 'Distance Running': 156, 23 | 'Hatha Yoga': 90, 24 | 'Open Water Swimming': 145, 25 | 'Road Cycling': 149, 26 | 'Street Basketball': 130, 27 | 'date': 'Mar 23', 28 | }, 29 | { 30 | 'Distance Running': 165, 31 | 'Hatha Yoga': 105, 32 | 'Open Water Swimming': 125, 33 | 'Road Cycling': 112, 34 | 'Street Basketball': 170, 35 | 'date': 'Apr 23', 36 | }, 37 | { 38 | 'Distance Running': 153, 39 | 'Hatha Yoga': 100, 40 | 'Open Water Swimming': 165, 41 | 'Road Cycling': 138, 42 | 'Street Basketball': 110, 43 | 'date': 'May 23', 44 | }, 45 | { 46 | 'Distance Running': 124, 47 | 'Hatha Yoga': 75, 48 | 'Open Water Swimming': 175, 49 | 'Road Cycling': 145, 50 | 'Street Basketball': 140, 51 | 'date': 'Jun 23', 52 | }, 53 | ]; 54 | 55 | export default () => { 56 | const theme = useTheme(); 57 | return ( 58 | 64 | ); 65 | }; 66 | -------------------------------------------------------------------------------- /src/BarChart/demos/customTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart, BarChartProps, ChartTooltipFrame } from '@lobehub/charts'; 2 | import { Typography } from 'antd'; 3 | import { useTheme } from 'antd-style'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | const data: BarChartProps['data'] = [ 7 | { 8 | Running: 167, 9 | date: 'Jan 23', 10 | }, 11 | { 12 | Running: 125, 13 | date: 'Feb 23', 14 | }, 15 | { 16 | Running: 156, 17 | date: 'Mar 23', 18 | }, 19 | { 20 | Running: 165, 21 | date: 'Apr 23', 22 | }, 23 | { 24 | Running: 153, 25 | date: 'May 23', 26 | }, 27 | { 28 | Running: 124, 29 | date: 'Jun 23', 30 | }, 31 | { 32 | Running: 164, 33 | date: 'Jul 23', 34 | }, 35 | { 36 | Running: 123, 37 | date: 'Aug 23', 38 | }, 39 | { 40 | Running: 132, 41 | date: 'Sep 23', 42 | }, 43 | ]; 44 | 45 | export default () => { 46 | const theme = useTheme(); 47 | 48 | const customTooltip: BarChartProps['customTooltip'] = ({ payload, active }) => { 49 | if (!active || !payload) return null; 50 | return ( 51 | 52 | {payload.map((category: any, idx: number) => ( 53 | 54 | 59 | 60 | 61 | {category.dataKey} 62 | 63 | 64 | {category.value} bpm 65 | 66 | 67 | 68 | ))} 69 | 70 | ); 71 | }; 72 | 73 | return ( 74 | 75 |

Average BPM

76 | 83 |
84 | ); 85 | }; 86 | -------------------------------------------------------------------------------- /src/BarChart/demos/example.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart, BarChartProps } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | const data: BarChartProps['data'] = [ 5 | { 6 | 'Number of threatened species': 2488, 7 | 'name': 'Amphibians', 8 | }, 9 | { 10 | 'Number of threatened species': 1445, 11 | 'name': 'Birds', 12 | }, 13 | { 14 | 'Number of threatened species': 743, 15 | 'name': 'Crustaceans', 16 | }, 17 | { 18 | 'Number of threatened species': 281, 19 | 'name': 'Ferns', 20 | }, 21 | { 22 | 'Number of threatened species': 251, 23 | 'name': 'Arachnids', 24 | }, 25 | { 26 | 'Number of threatened species': 232, 27 | 'name': 'Corals', 28 | }, 29 | { 30 | 'Number of threatened species': 98, 31 | 'name': 'Algae', 32 | }, 33 | ]; 34 | 35 | const valueFormatter: BarChartProps['valueFormatter'] = (number) => 36 | Intl.NumberFormat('us').format(number).toString(); 37 | 38 | export default () => { 39 | return ( 40 | 41 |

Number of species threatened with extinction (2021)

42 | 48 |
49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/BarChart/demos/groups.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart, BarChartProps } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | const data: BarChartProps['data'] = [ 5 | { 6 | groupA: 890, 7 | groupB: 338, 8 | groupC: 538, 9 | groupD: 396, 10 | groupE: 138, 11 | groupF: 436, 12 | name: 'Topic 1', 13 | }, 14 | { 15 | groupA: 289, 16 | groupB: 233, 17 | groupC: 253, 18 | groupD: 333, 19 | groupE: 133, 20 | groupF: 533, 21 | name: 'Topic 2', 22 | }, 23 | { 24 | groupA: 380, 25 | groupB: 535, 26 | groupC: 352, 27 | groupD: 718, 28 | groupE: 539, 29 | groupF: 234, 30 | name: 'Topic 3', 31 | }, 32 | { 33 | groupA: 90, 34 | groupB: 98, 35 | groupC: 28, 36 | groupD: 33, 37 | groupE: 61, 38 | groupF: 53, 39 | name: 'Topic 4', 40 | }, 41 | ]; 42 | 43 | const valueFormatter: BarChartProps['valueFormatter'] = (number: number) => 44 | Intl.NumberFormat('us').format(number).toString(); 45 | 46 | export default () => { 47 | return ( 48 | 49 |

Writing Contest: Entries

50 | 56 |
57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /src/BarChart/demos/index.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart, BarChartProps } from '@lobehub/charts'; 2 | import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook'; 3 | import { FC } from 'react'; 4 | 5 | const data: BarChartProps['data'] = [ 6 | { 7 | groupA: 890, 8 | groupB: 338, 9 | 10 | name: 'Topic 1sdafsdafsd', 11 | }, 12 | { 13 | groupA: 289, 14 | groupB: 233, 15 | 16 | name: 'Topic 2', 17 | }, 18 | { 19 | groupA: 380, 20 | groupB: 535, 21 | 22 | name: 'Topic 3', 23 | }, 24 | { 25 | groupA: 90, 26 | groupB: 98, 27 | 28 | name: 'Topic 4', 29 | }, 30 | ]; 31 | 32 | const valueFormatter: BarChartProps['valueFormatter'] = (number) => 33 | Intl.NumberFormat('us').format(number).toString(); 34 | 35 | const BarChartDemo: FC = () => { 36 | const store = useCreateStore(); 37 | 38 | const props: BarChartProps | any = useControls( 39 | { 40 | allowDecimals: true, 41 | animationDuration: { 42 | step: 1, 43 | value: 900, 44 | }, 45 | autoMinValue: false, 46 | barCategoryGap: 10, 47 | enableLegendSlider: false, 48 | 49 | intervalType: { 50 | options: ['preserveStart', 'preserveEnd', 'preserveStartEnd', 'equidistantPreserveStart'], 51 | value: 'equidistantPreserveStart', 52 | }, 53 | layout: { 54 | options: ['horizontal', 'vertical'], 55 | value: 'horizontal', 56 | }, 57 | maxValue: 1000, 58 | minValue: 0, 59 | showAnimation: false, 60 | showGridLines: true, 61 | showLegend: true, 62 | showTooltip: true, 63 | showXAxis: true, 64 | showYAxis: true, 65 | stack: false, 66 | startEndOnly: false, 67 | tickGap: { 68 | step: 1, 69 | value: 5, 70 | }, 71 | xAxisLabel: '', 72 | yAxisAlign: { 73 | options: ['left', 'right'], 74 | value: 'left', 75 | }, 76 | yAxisLabel: '', 77 | }, 78 | { store }, 79 | ); 80 | 81 | return ( 82 | 83 | console.log(v)} 92 | valueFormatter={valueFormatter} 93 | {...props} 94 | /> 95 | 96 | ); 97 | }; 98 | 99 | BarChartDemo.displayName = 'BarChartDemo'; 100 | 101 | export default BarChartDemo; 102 | -------------------------------------------------------------------------------- /src/BarChart/demos/loading.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart } from '@lobehub/charts'; 2 | 3 | export default () => { 4 | return ; 5 | }; 6 | -------------------------------------------------------------------------------- /src/BarChart/demos/noData.tsx: -------------------------------------------------------------------------------- 1 | import { BarChart } from '@lobehub/charts'; 2 | 3 | export default () => { 4 | return ; 5 | }; 6 | -------------------------------------------------------------------------------- /src/BarChart/demos/xAxisLabelFormatter.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | import { BarChart } from '@/index'; 4 | 5 | const data = [ 6 | { 7 | count: 213, 8 | date: '2024-10-22', 9 | }, 10 | { 11 | count: 256, 12 | date: '2024-10-23', 13 | }, 14 | { 15 | count: 220, 16 | date: '2024-10-24', 17 | }, 18 | { 19 | count: 193, 20 | date: '2024-10-25', 21 | }, 22 | { 23 | count: 158, 24 | date: '2024-10-26', 25 | }, 26 | { 27 | count: 200, 28 | date: '2024-10-27', 29 | }, 30 | { 31 | count: 196, 32 | date: '2024-10-28', 33 | }, 34 | ]; 35 | 36 | export default () => { 37 | return ( 38 | dayjs(v).format('MM-DD')} 43 | /> 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/BarChart/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: Components 3 | group: Charts 4 | description: Bar charts compare numerical values and use the length of each bar to represent the value of each variable. 5 | order: 0 6 | --- 7 | 8 | 9 | 10 | ## Usage example 11 | 12 | The example below shows a chart composition combining a `BarChart` with text elements. 13 | 14 | 15 | 16 | ## Usage example with custom X axis labels 17 | 18 | The example below shows an interacive chart using the `xAxisLabelFormatter` prop. 19 | 20 | 21 | 22 | ## Usage example with groups 23 | 24 | The example below shows a chart with a group composition. 25 | 26 | 27 | 28 | ## Usage example with click event 29 | 30 | The example below shows an interacive chart using the `onValueChange` prop. 31 | 32 | 33 | 34 | ## Usage example with custom tooltip 35 | 36 | The example below shows a custom tooltip using `customTooltip` prop. 37 | 38 | 39 | 40 | ## Usage example with a custom colors 41 | 42 | The example below shows a chart with custom `colors`. 43 | 44 | 45 | 46 | ## Usage example with x-axis and y-axis labels 47 | 48 | The example below shows added axis labels using `xAxisLabel` and `yAxisLabel` props. 49 | 50 | 51 | 52 | ## No Data 53 | 54 | 55 | 56 | ## Loading 57 | 58 | 59 | 60 | ## API 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/BarChart/renderShape.tsx: -------------------------------------------------------------------------------- 1 | import { deepEqual } from '@/common/utils'; 2 | 3 | export const renderShape = ( 4 | props: any, 5 | activeBar: any | undefined, 6 | activeLegend: string | undefined, 7 | layout: string, 8 | ) => { 9 | const { fillOpacity, name, payload, value } = props; 10 | let { x, width, y, height } = props; 11 | 12 | if (layout === 'horizontal' && height < 0) { 13 | y += height; 14 | height = Math.abs(height); // height must be a positive number 15 | } else if (layout === 'vertical' && width < 0) { 16 | x += width; 17 | width = Math.abs(width); // width must be a positive number 18 | } 19 | 20 | return ( 21 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/BarChart/styles.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ css, token }) => ({ 4 | emphasis: css` 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | `, 9 | gridLines: css` 10 | stroke: ${token.colorBorderSecondary}; 11 | stroke-width: 1; 12 | `, 13 | label: css` 14 | font-size: 12px; 15 | line-height: 16px; 16 | fill: ${token.colorTextDescription}; 17 | `, 18 | strongLabel: css` 19 | font-size: 12px; 20 | font-weight: 500; 21 | line-height: 16px; 22 | fill: ${token.colorTextSecondary}; 23 | `, 24 | })); 25 | -------------------------------------------------------------------------------- /src/BarList/demos/clickEvent.tsx: -------------------------------------------------------------------------------- 1 | import { BarList, BarListProps } from '@lobehub/charts'; 2 | import { Highlighter } from '@lobehub/ui'; 3 | import { useState } from 'react'; 4 | 5 | const data: BarListProps['data'] = [ 6 | { name: '/home', value: 456 }, 7 | { name: '/imprint', value: 351 }, 8 | { name: '/cancellation', value: 51 }, 9 | ]; 10 | 11 | export default () => { 12 | const [value, setValue] = useState({}); 13 | return ( 14 | <> 15 | setValue(v)} /> 16 | 17 | {JSON.stringify(value, null, 2)} 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/BarList/demos/customColors.tsx: -------------------------------------------------------------------------------- 1 | import { BarList, BarListProps } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | 4 | export default () => { 5 | const theme = useTheme(); 6 | 7 | const data: BarListProps['data'] = [ 8 | { name: '/home', value: 456 }, 9 | { color: theme.magenta, name: '/imprint', value: 351 }, 10 | { name: '/cancellation', value: 51 }, 11 | ]; 12 | 13 | return ; 14 | }; 15 | -------------------------------------------------------------------------------- /src/BarList/demos/example.tsx: -------------------------------------------------------------------------------- 1 | import { SiGithub, SiGoogle, SiReddit, SiX, SiYoutube } from '@icons-pack/react-simple-icons'; 2 | import { BarList, BarListProps } from '@lobehub/charts'; 3 | import { Flexbox } from 'react-layout-kit'; 4 | 5 | const data: BarListProps['data'] = [ 6 | { 7 | href: 'https://x.com/tremorlabs', 8 | icon: , 9 | name: 'X', 10 | value: 456, 11 | }, 12 | { 13 | href: 'https://google.com', 14 | icon: , 15 | name: 'Google', 16 | value: 351, 17 | }, 18 | { 19 | href: 'https://github.com/tremorlabs/tremor', 20 | icon: , 21 | name: 'GitHub', 22 | value: 271, 23 | }, 24 | { 25 | href: 'https://reddit.com', 26 | icon: , 27 | name: 'Reddit', 28 | value: 191, 29 | }, 30 | { 31 | href: 'https://www.youtube.com/@tremorlabs3079', 32 | icon: , 33 | name: 'Youtube', 34 | value: 91, 35 | }, 36 | ]; 37 | 38 | export default () => { 39 | return ( 40 | 41 |

Website Analytics

42 | 43 |
44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/BarList/demos/index.tsx: -------------------------------------------------------------------------------- 1 | import { BarList, BarListProps } from '@lobehub/charts'; 2 | import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook'; 3 | 4 | const data: BarListProps['data'] = [ 5 | { name: '/home', value: 456 }, 6 | { name: '/imprint', value: 351 }, 7 | { name: '/cancellation', value: 51 }, 8 | ]; 9 | 10 | export default () => { 11 | const store = useCreateStore(); 12 | 13 | const props: BarListProps | any = useControls( 14 | { 15 | showAnimation: true, 16 | sortOrder: { 17 | options: ['ascending', 'descending', 'none'], 18 | value: 'descending', 19 | }, 20 | }, 21 | { store }, 22 | ); 23 | 24 | return ( 25 | 26 | 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/BarList/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: Components 3 | group: Charts 4 | description: Horizontal bars with a customizable label inside. 5 | --- 6 | 7 | 8 | 9 | ## Usage example 10 | 11 | You can also pass an `href` into the data, then your datapoint gets rendered as a clickable link with `target="_blank"`. 12 | 13 | 14 | 15 | ## Usage example with click event 16 | 17 | The example below shows an interacive chart using the `onValueChange` prop. 18 | 19 | 20 | 21 | ## Usage example with a custom colors 22 | 23 | The example below shows a chart with custom `colors`. 24 | 25 | 26 | 27 | ## API 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/BarList/styles.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ prefixCls, css, token }) => ({ 4 | bar: css` 5 | position: absolute; 6 | top: 0; 7 | bottom: 0; 8 | left: 0; 9 | 10 | max-width: 100%; 11 | height: 100%; 12 | border-radius: ${token.borderRadius}px; 13 | 14 | opacity: 0.25; 15 | 16 | transition: all 0.25s ${token.motionEaseInOut}; 17 | `, 18 | barContainer: css` 19 | position: relative; 20 | `, 21 | barHover: css` 22 | &:hover { 23 | .${prefixCls}-chart-bar-item { 24 | opacity: 0.4; 25 | } 26 | } 27 | `, 28 | emphasis: css` 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | white-space: nowrap; 32 | `, 33 | label: css` 34 | font-size: 12px; 35 | line-height: 16px; 36 | color: ${token.colorTextDescription}; 37 | `, 38 | sourceALabel: css` 39 | font-size: 14px; 40 | line-height: 16px; 41 | color: ${token.colorText} !important; 42 | 43 | &:hover { 44 | color: ${token.colorLinkHover} !important; 45 | } 46 | `, 47 | sourceLabel: css` 48 | font-size: 14px; 49 | line-height: 16px; 50 | color: ${token.colorText}; 51 | `, 52 | strongLabel: css` 53 | font-size: 14px; 54 | font-weight: 500; 55 | line-height: 16px; 56 | color: ${token.colorText}; 57 | `, 58 | })); 59 | -------------------------------------------------------------------------------- /src/DataBars/DeltaBar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Tooltip } from '@lobehub/ui'; 4 | import { ReactNode, forwardRef } from 'react'; 5 | import { Flexbox, FlexboxProps } from 'react-layout-kit'; 6 | 7 | import { DeltaTypes } from '@/types/charts'; 8 | import { mapInputsToDeltaType } from '@/utils'; 9 | 10 | import { useStyles } from './styles'; 11 | 12 | const getDeltaType = (value: number) => (value >= 0 ? DeltaTypes.Increase : DeltaTypes.Decrease); 13 | 14 | export interface DeltaBarProps extends FlexboxProps { 15 | bgColors?: string; 16 | color?: string; 17 | isIncreasePositive?: boolean; 18 | showAnimation?: boolean; 19 | size?: number; 20 | tooltip?: ReactNode; 21 | value: number; 22 | } 23 | 24 | const DeltaBar = forwardRef((props, ref) => { 25 | const { cx, styles, theme } = useStyles(); 26 | const { 27 | value, 28 | bgColors, 29 | color, 30 | isIncreasePositive = true, 31 | showAnimation = false, 32 | className, 33 | tooltip, 34 | width = '100%', 35 | size = 8, 36 | style, 37 | ...rest 38 | } = props; 39 | const deltaType = mapInputsToDeltaType(getDeltaType(value), isIncreasePositive); 40 | 41 | const colors = { 42 | [DeltaTypes.Increase]: theme.colorSuccess, 43 | [DeltaTypes.ModerateIncrease]: theme.colorSuccess, 44 | [DeltaTypes.Decrease]: theme.colorError, 45 | [DeltaTypes.ModerateDecrease]: theme.colorError, 46 | [DeltaTypes.Unchanged]: theme.colorWarning, 47 | }; 48 | 49 | return ( 50 | 51 | 64 | 76 | 84 | {value < 0 ? ( 85 |
95 | ) : undefined} 96 | 97 | 106 | 114 | {value >= 0 ? ( 115 |
125 | ) : undefined} 126 | 127 | 128 | 129 | ); 130 | }); 131 | 132 | DeltaBar.displayName = 'DeltaBar'; 133 | 134 | export default DeltaBar; 135 | -------------------------------------------------------------------------------- /src/DataBars/MarkerBar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Tooltip } from '@lobehub/ui'; 4 | import { ReactNode, forwardRef } from 'react'; 5 | import { Flexbox, FlexboxProps } from 'react-layout-kit'; 6 | 7 | import { useStyles } from './styles'; 8 | 9 | export interface MarkerBarProps extends FlexboxProps { 10 | bgColors?: string; 11 | color?: string; 12 | markerTooltip?: ReactNode; 13 | maxValue?: number; 14 | minValue?: number; 15 | rangeColors?: string; 16 | rangeTooltip?: ReactNode; 17 | showAnimation?: boolean; 18 | size?: number; 19 | value: number; 20 | } 21 | 22 | const MarkerBar = forwardRef((props, ref) => { 23 | const { 24 | value, 25 | minValue, 26 | maxValue, 27 | markerTooltip, 28 | rangeTooltip, 29 | showAnimation = false, 30 | color, 31 | bgColors, 32 | rangeColors, 33 | className, 34 | width = '100%', 35 | style, 36 | size = 8, 37 | ...rest 38 | } = props; 39 | const { cx, styles, theme } = useStyles(); 40 | return ( 41 | 54 | 66 | {minValue !== undefined && maxValue !== undefined ? ( 67 | 68 | 82 | 83 | ) : null} 84 | 85 |
91 |
98 |
99 | 100 | 101 | ); 102 | }); 103 | 104 | MarkerBar.displayName = 'MarkerBar'; 105 | 106 | export default MarkerBar; 107 | -------------------------------------------------------------------------------- /src/DataBars/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Tooltip } from '@lobehub/ui'; 4 | import { ReactNode, forwardRef } from 'react'; 5 | import { Flexbox, FlexboxProps } from 'react-layout-kit'; 6 | 7 | import { useStyles } from './styles'; 8 | 9 | export interface ProgressBarProps extends FlexboxProps { 10 | bgColors?: string; 11 | color?: string; 12 | showAnimation?: boolean; 13 | size?: number; 14 | tooltip?: ReactNode; 15 | value: number; 16 | } 17 | 18 | const ProgressBar = forwardRef((props, ref) => { 19 | const { cx, styles, theme } = useStyles(); 20 | const { 21 | value, 22 | color, 23 | bgColors, 24 | tooltip, 25 | showAnimation = false, 26 | className, 27 | width = '100%', 28 | size = 8, 29 | style, 30 | ...rest 31 | } = props; 32 | 33 | return ( 34 | 35 | 48 | 60 | 70 | 71 | 72 | ); 73 | }); 74 | 75 | ProgressBar.displayName = 'ProgressBar'; 76 | 77 | export default ProgressBar; 78 | -------------------------------------------------------------------------------- /src/DataBars/demos/CategoryBar.tsx: -------------------------------------------------------------------------------- 1 | import { CategoryBar } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | import { Flexbox } from 'react-layout-kit'; 4 | 5 | export default () => { 6 | const theme = useTheme(); 7 | return ( 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/DataBars/demos/DeltaBar.tsx: -------------------------------------------------------------------------------- 1 | import { DeltaBar } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | export default () => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/DataBars/demos/MarkerBar.tsx: -------------------------------------------------------------------------------- 1 | import { MarkerBar } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | 4 | export default () => { 5 | const theme = useTheme(); 6 | return ( 7 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/DataBars/demos/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | import { ProgressBar } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | 4 | export default () => { 5 | const theme = useTheme(); 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /src/DataBars/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: Components 3 | group: Visualizations 4 | atomId: 'ProgressBar, MarkerBar, DeltaBar, CategoryBar' 5 | description: Components to indicate progress, performance, or status. For example, an individual score compared to a benchmark, the length of a process, or the deviation of a value. 6 | --- 7 | 8 | ## Progress Bar 9 | 10 | 11 | 12 | ## Marker Bar 13 | 14 | 15 | 16 | ## Delta Bar 17 | 18 | 19 | 20 | ## Category Bar 21 | 22 | 23 | 24 | ## API 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/DataBars/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as CategoryBar, type CategoryBarProps } from './CategoryBar'; 2 | export { default as DeltaBar, type DeltaBarProps } from './DeltaBar'; 3 | export { default as MarkerBar, type MarkerBarProps } from './MarkerBar'; 4 | export { default as ProgressBar, type ProgressBarProps } from './ProgressBar'; 5 | -------------------------------------------------------------------------------- /src/DataBars/styles.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ css, token }) => ({ 4 | emphasis: css` 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | `, 9 | label: css` 10 | font-size: 12px; 11 | line-height: 16px; 12 | fill: ${token.colorTextDescription}; 13 | `, 14 | marker: css` 15 | width: 4px; 16 | border-radius: 4px; 17 | box-shadow: 0 0 0 3px ${token.colorBgContainer}; 18 | `, 19 | markerWrapper: css` 20 | position: absolute; 21 | right: 50%; 22 | width: 1.25rem; 23 | `, 24 | showAnimation: css` 25 | transition: all 0.25s ${token.motionEaseInOut}; 26 | `, 27 | })); 28 | -------------------------------------------------------------------------------- /src/DonutChart/DonutChartTooltip.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | import ChartTooltipFrame from '@/common/ChartTooltip/ChartTooltipFrame'; 5 | import ChartTooltipRow from '@/common/ChartTooltip/ChartTooltipRow'; 6 | import { ValueFormatter } from '@/types/charts'; 7 | 8 | export interface DonutChartTooltipProps { 9 | active: boolean | undefined; 10 | customCategories?: { 11 | [key: string]: string; 12 | }; 13 | payload: any; 14 | valueFormatter: ValueFormatter; 15 | } 16 | 17 | export const DonutChartTooltip = ({ 18 | customCategories, 19 | active, 20 | payload, 21 | valueFormatter, 22 | }: DonutChartTooltipProps) => { 23 | if (active && payload?.[0]) { 24 | const payloadRow = payload?.[0]; 25 | return ( 26 | 27 | 28 | 33 | 34 | 35 | ); 36 | } 37 | return null; 38 | }; 39 | -------------------------------------------------------------------------------- /src/DonutChart/demos/clickEvent.tsx: -------------------------------------------------------------------------------- 1 | import { DonutChart, DonutChartProps, EventProps } from '@lobehub/charts'; 2 | import { Highlighter } from '@lobehub/ui'; 3 | import { useState } from 'react'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | const data: DonutChartProps['data'] = [ 7 | { 8 | name: 'New York', 9 | sales: 980, 10 | }, 11 | { 12 | name: 'London', 13 | sales: 456, 14 | }, 15 | { 16 | name: 'Hong Kong', 17 | sales: 390, 18 | }, 19 | { 20 | name: 'San Francisco', 21 | sales: 240, 22 | }, 23 | { 24 | name: 'Singapore', 25 | sales: 190, 26 | }, 27 | ]; 28 | 29 | const valueFormatter: DonutChartProps['valueFormatter'] = (number) => 30 | `$ ${Intl.NumberFormat('us').format(number).toString()}`; 31 | 32 | export default () => { 33 | const [value, setValue] = useState(null); 34 | return ( 35 | 36 | setValue(v)} 41 | valueFormatter={valueFormatter} 42 | /> 43 | 44 | {JSON.stringify(value, null, 2)} 45 | 46 | 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /src/DonutChart/demos/customColors.tsx: -------------------------------------------------------------------------------- 1 | import { DonutChart, DonutChartProps } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | 4 | const data: DonutChartProps['data'] = [ 5 | { 6 | name: 'New York', 7 | sales: 980, 8 | }, 9 | { 10 | name: 'London', 11 | sales: 456, 12 | }, 13 | { 14 | name: 'Hong Kong', 15 | sales: 390, 16 | }, 17 | { 18 | name: 'San Francisco', 19 | sales: 240, 20 | }, 21 | { 22 | name: 'Singapore', 23 | sales: 190, 24 | }, 25 | { 26 | name: 'Zurich', 27 | sales: 139, 28 | }, 29 | ]; 30 | 31 | export default () => { 32 | const theme = useTheme(); 33 | return ( 34 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/DonutChart/demos/customTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { ChartTooltipFrame, DonutChart, DonutChartProps } from '@lobehub/charts'; 2 | import { Typography } from 'antd'; 3 | import { useTheme } from 'antd-style'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | const data: DonutChartProps['data'] = [ 7 | { 8 | name: 'New York', 9 | sales: 980, 10 | }, 11 | { 12 | name: 'London', 13 | sales: 456, 14 | }, 15 | { 16 | name: 'Hong Kong', 17 | sales: 390, 18 | }, 19 | { 20 | name: 'San Francisco', 21 | sales: 240, 22 | }, 23 | { 24 | name: 'Singapore', 25 | sales: 190, 26 | }, 27 | { 28 | name: 'Zurich', 29 | sales: 139, 30 | }, 31 | ]; 32 | 33 | const valueFormatter: DonutChartProps['valueFormatter'] = (number) => 34 | `$ ${Intl.NumberFormat('us').format(number).toString()}`; 35 | 36 | export default () => { 37 | const theme = useTheme(); 38 | 39 | const customTooltip: DonutChartProps['customTooltip'] = ({ payload, active }) => { 40 | if (!active || !payload) return null; 41 | return ( 42 | 43 | {payload.map((category: any, idx: number) => ( 44 | 45 | 50 | 51 | 52 | {category.dataKey} 53 | 54 | 55 | {category.value} bpm 56 | 57 | 58 | 59 | ))} 60 | 61 | ); 62 | }; 63 | return ( 64 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/DonutChart/demos/example.tsx: -------------------------------------------------------------------------------- 1 | import { DonutChart, DonutChartProps } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | const data: DonutChartProps['data'] = [ 5 | { 6 | name: 'Noche Holding AG', 7 | value: 9800, 8 | }, 9 | { 10 | name: 'Rain Drop AG', 11 | value: 4567, 12 | }, 13 | { 14 | name: 'Push Rail AG', 15 | value: 3908, 16 | }, 17 | { 18 | name: 'Flow Steal AG', 19 | value: 2400, 20 | }, 21 | { 22 | name: 'Tiny Loop Inc.', 23 | value: 2174, 24 | }, 25 | { 26 | name: 'Anton Resorts Holding', 27 | value: 1398, 28 | }, 29 | ]; 30 | 31 | const valueFormatter: DonutChartProps['valueFormatter'] = (number) => 32 | `$ ${Intl.NumberFormat('us').format(number).toString()}`; 33 | 34 | export default () => ( 35 | 36 |

Donut Variant

37 | console.log(v)} 40 | valueFormatter={valueFormatter} 41 | variant="donut" 42 | /> 43 |

Pie Variant

44 | console.log(v)} 47 | valueFormatter={valueFormatter} 48 | variant="pie" 49 | /> 50 |
51 | ); 52 | -------------------------------------------------------------------------------- /src/DonutChart/demos/index.tsx: -------------------------------------------------------------------------------- 1 | import { DonutChart, DonutChartProps } from '@lobehub/charts'; 2 | import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook'; 3 | 4 | const data: DonutChartProps['data'] = [ 5 | { 6 | name: 'New York', 7 | sales: 980, 8 | }, 9 | { 10 | name: 'London', 11 | sales: 456, 12 | }, 13 | { 14 | name: 'Hong Kong', 15 | sales: 390, 16 | }, 17 | { 18 | name: 'San Francisco', 19 | sales: 240, 20 | }, 21 | { 22 | name: 'Singapore', 23 | sales: 190, 24 | }, 25 | ]; 26 | 27 | const valueFormatter: DonutChartProps['valueFormatter'] = (number) => 28 | `$ ${Intl.NumberFormat('us').format(number).toString()}`; 29 | 30 | export default () => { 31 | const store = useCreateStore(); 32 | 33 | const props: any = useControls( 34 | { 35 | animationDuration: { 36 | step: 1, 37 | value: 900, 38 | }, 39 | label: '', 40 | showAnimation: false, 41 | showLabel: false, 42 | showTooltip: true, 43 | variant: { 44 | options: ['donut', 'pie'], 45 | value: 'donut', 46 | }, 47 | }, 48 | { store }, 49 | ); 50 | 51 | return ( 52 | 53 | 60 | 61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /src/DonutChart/demos/withLegend.tsx: -------------------------------------------------------------------------------- 1 | import { DonutChart, DonutChartProps, Legend } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | const data: DonutChartProps['data'] = [ 5 | { 6 | name: 'New York', 7 | sales: 980, 8 | }, 9 | { 10 | name: 'London', 11 | sales: 456, 12 | }, 13 | { 14 | name: 'Hong Kong', 15 | sales: 390, 16 | }, 17 | { 18 | name: 'San Francisco', 19 | sales: 240, 20 | }, 21 | { 22 | name: 'Singapore', 23 | sales: 190, 24 | }, 25 | ]; 26 | 27 | const valueFormatter: DonutChartProps['valueFormatter'] = (number) => 28 | `$ ${Intl.NumberFormat('us').format(number).toString()}`; 29 | 30 | export default () => { 31 | return ( 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/DonutChart/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: Components 3 | group: Charts 4 | description: A donut chart displays quantitative information through a circular visualization. 5 | --- 6 | 7 | 8 | 9 | ## Usage example 10 | 11 | The example below shows a composition of a `DonutChart` component. 12 | 13 | 14 | 15 | ## Usage example with legend 16 | 17 | The example below shows a composition of a `DonutChart` and `Legend` component. 18 | 19 | 20 | 21 | ## Usage example with click event 22 | 23 | The example below shows an interacive chart using the `onValueChange` prop. 24 | 25 | 26 | 27 | ## Usage example with custom tooltip 28 | 29 | The example below shows a custom tooltip using `customTooltip` prop. 30 | 31 | 32 | 33 | ## Usage example with a custom colors 34 | 35 | The example below shows a chart with custom `colors`. 36 | 37 | 38 | 39 | ## API 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/DonutChart/inputParser.ts: -------------------------------------------------------------------------------- 1 | import { ValueFormatter } from '@/types/charts'; 2 | import { sumNumericArray } from '@/utils'; 3 | 4 | const calculateDefaultLabel = (data: any[], category: string) => 5 | sumNumericArray(data.map((dataPoint) => dataPoint[category])); 6 | 7 | export const parseLabelInput = ( 8 | labelInput: string | undefined, 9 | valueFormatter: ValueFormatter, 10 | data: any[], 11 | category: string, 12 | ) => (labelInput ? labelInput : valueFormatter(calculateDefaultLabel(data, category))); 13 | -------------------------------------------------------------------------------- /src/DonutChart/renderInactiveShape.tsx: -------------------------------------------------------------------------------- 1 | import { Sector } from 'recharts'; 2 | 3 | export const renderInactiveShape = (props: any) => { 4 | const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, className } = props; 5 | 6 | return ( 7 | 8 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/DonutChart/styles.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ css, token }) => ({ 4 | emphasis: css` 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | `, 9 | gridLines: css` 10 | stroke: ${token.colorBorderSecondary}; 11 | stroke-width: 1; 12 | `, 13 | label: css` 14 | font-size: 12px; 15 | line-height: 16px; 16 | fill: ${token.colorTextDescription}; 17 | `, 18 | strongLabel: css` 19 | font-size: 16px; 20 | font-weight: 500; 21 | line-height: 16px; 22 | fill: ${token.colorTextSecondary}; 23 | `, 24 | })); 25 | -------------------------------------------------------------------------------- /src/FunnelChart/ArrowRightIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ArrowRightIcon = ({ ...props }) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default ArrowRightIcon; 10 | -------------------------------------------------------------------------------- /src/FunnelChart/demos/CalculateFrom.tsx: -------------------------------------------------------------------------------- 1 | import { FunnelChart, FunnelChartProps } from '@lobehub/charts'; 2 | 3 | const data: FunnelChartProps['data'] = [ 4 | { name: 'Session start', value: 31_943 }, 5 | { name: 'Account Created', value: 10_474 }, 6 | { 7 | name: 'Item Detail Page', 8 | value: 9482, 9 | }, 10 | { 11 | name: 'Added to cart', 12 | value: 4684, 13 | }, 14 | { 15 | name: 'Complete Purchase', 16 | value: 1283, 17 | }, 18 | ]; 19 | 20 | export default () => { 21 | return ( 22 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/FunnelChart/demos/index.tsx: -------------------------------------------------------------------------------- 1 | import { FunnelChart, FunnelChartProps } from '@lobehub/charts'; 2 | import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook'; 3 | 4 | const data: FunnelChartProps['data'] = [ 5 | { name: '1. Add credit Card', value: 89 }, 6 | { name: '2. Copy invite code', value: 6 }, 7 | { 8 | name: '3. Send invite code', 9 | value: 5, 10 | }, 11 | ]; 12 | 13 | export default () => { 14 | const store = useCreateStore(); 15 | 16 | const props: FunnelChartProps | any = useControls( 17 | { 18 | barGap: '20%', 19 | evolutionGradient: true, 20 | gradient: false, 21 | showArrow: true, 22 | showGridLines: true, 23 | showTooltip: true, 24 | showXAxis: true, 25 | showYAxis: true, 26 | variant: { 27 | options: ['base', 'center'], 28 | value: 'base', 29 | }, 30 | xAxisLabel: '', 31 | yAxisAlign: { 32 | options: ['left', 'right'], 33 | value: 'left', 34 | }, 35 | yAxisLabel: '', 36 | }, 37 | { store }, 38 | ); 39 | 40 | return ( 41 | 42 | 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/FunnelChart/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: Components 3 | group: Charts 4 | description: A funnel chart illustrates the progression or conversion rates through successive stages of a process. 5 | order: 3 6 | --- 7 | 8 | 9 | 10 | ## Usage example with evolution gradient 11 | 12 | The `calculateFrom` prop controls the referenced value to calculate the drop-off between steps. Here we set it to previous, to always reference the preceding bar as the calculation base. We also set `showArrow` to false. 13 | 14 | 15 | 16 | ## API 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/FunnelChart/styles.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ css, token }) => ({ 4 | emphasis: css` 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | `, 9 | gridLines: css` 10 | stroke: ${token.colorBorderSecondary}; 11 | stroke-width: 1; 12 | `, 13 | label: css` 14 | font-size: 12px; 15 | line-height: 16px; 16 | fill: ${token.colorTextDescription}; 17 | `, 18 | strongLabel: css` 19 | font-size: 12px; 20 | font-weight: 500; 21 | line-height: 16px; 22 | fill: ${token.colorTextSecondary}; 23 | `, 24 | })); 25 | -------------------------------------------------------------------------------- /src/FunnelChart/types.ts: -------------------------------------------------------------------------------- 1 | export type Tooltip = { 2 | data?: { 3 | className?: string; 4 | color?: string; 5 | dataKey: string; 6 | fill?: string; 7 | name: string; 8 | payload?: any; 9 | value: number; 10 | }; 11 | index?: number; 12 | x: number; 13 | y: number; 14 | }; 15 | 16 | export type DataT = { 17 | name: string; 18 | value: number; 19 | }; 20 | 21 | export type FormattedDataT = DataT & { 22 | barHeight: number; 23 | nextBarHeight: number; 24 | nextNormalizedValue: number; 25 | nextStartX: number; 26 | nextValue: number; 27 | normalizedValue: number; 28 | startX: number; 29 | startY: number; 30 | }; 31 | -------------------------------------------------------------------------------- /src/Heatmaps/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from '@lobehub/ui'; 2 | import { keyframes } from 'antd-style'; 3 | import { Fragment, ReactNode, memo } from 'react'; 4 | 5 | import type { Activity, Labels, Week } from '@/types/charts'; 6 | 7 | interface CalendarProps { 8 | blockMargin: number; 9 | blockRadius: number; 10 | blockSize: number; 11 | colorScale: string[]; 12 | customTooltip?: (activity: Activity) => ReactNode; 13 | enableAnimation: boolean; 14 | labelHeight: number; 15 | labels: Labels; 16 | maxLevel: number; 17 | onValueChange?: (value: Activity) => void; 18 | showTooltip: boolean; 19 | weeks: Week[]; 20 | } 21 | 22 | const Calendar = memo( 23 | ({ 24 | weeks, 25 | maxLevel, 26 | enableAnimation, 27 | colorScale, 28 | blockSize, 29 | blockRadius, 30 | blockMargin, 31 | labelHeight, 32 | showTooltip, 33 | customTooltip, 34 | labels, 35 | onValueChange, 36 | }) => { 37 | return ( 38 | <> 39 | {weeks 40 | .map((week, weekIndex) => 41 | week.map((activity, dayIndex) => { 42 | if (!activity) { 43 | return null; 44 | } 45 | 46 | if (activity.level < 0 || activity.level > maxLevel) { 47 | throw new RangeError( 48 | `Provided activity level ${activity.level} for ${activity.date} is out of range. It must be between 0 and ${maxLevel}.`, 49 | ); 50 | } 51 | 52 | const style = enableAnimation 53 | ? { 54 | animation: `${keyframes` 55 | 0% { 56 | fill: var(--lobe-heatmaps-loading); 57 | } 58 | 50% { 59 | fill: var(--lobe-heatmaps-loading-active); 60 | } 61 | 100% { 62 | fill: var(--lobe-heatmaps-loading); 63 | } 64 | `} 1.75s ease-in-out infinite`, 65 | animationDelay: `${weekIndex * 20 + dayIndex * 20}ms`, 66 | } 67 | : undefined; 68 | 69 | const block = ( 70 | onValueChange?.(activity)} 76 | rx={blockRadius} 77 | ry={blockRadius} 78 | style={{ 79 | cursor: onValueChange ? 'pointer' : undefined, 80 | ...style, 81 | }} 82 | width={blockSize} 83 | x={0} 84 | y={labelHeight + (blockSize + blockMargin) * dayIndex} 85 | /> 86 | ); 87 | 88 | return ( 89 | 90 | {showTooltip ? ( 91 | 102 | {block} 103 | 104 | ) : ( 105 | block 106 | )} 107 | 108 | ); 109 | }), 110 | ) 111 | .map((week, x) => ( 112 | 113 | {week} 114 | 115 | ))} 116 | 117 | ); 118 | }, 119 | ); 120 | 121 | export default Calendar; 122 | -------------------------------------------------------------------------------- /src/Heatmaps/ChartLabels.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | 3 | import type { Labels, Week } from '@/types/charts'; 4 | import { getMonthLabels } from '@/utils/calendar'; 5 | 6 | import { useStyles } from './styles'; 7 | 8 | interface ChartLabelsProps { 9 | blockMargin: number; 10 | blockSize: number; 11 | hideMonthLabels: boolean; 12 | labelHeight: number; 13 | labelMargin: number; 14 | labels: Labels; 15 | showWeekdayLabels: boolean; 16 | weekStart: number; 17 | weeks: Week[]; 18 | } 19 | 20 | const ChartLabels = memo( 21 | ({ 22 | labels, 23 | blockSize, 24 | labelHeight, 25 | blockMargin, 26 | labelMargin, 27 | showWeekdayLabels, 28 | hideMonthLabels, 29 | weeks, 30 | weekStart, 31 | }) => { 32 | const { cx } = useStyles(); 33 | 34 | return ( 35 | <> 36 | {showWeekdayLabels && weeks[0] && ( 37 | 38 | {weeks[0].map((_, index) => { 39 | if (index % 2 === 0) { 40 | return null; 41 | } 42 | 43 | const dayIndex = (index + weekStart) % 7; 44 | const maxLength = Math.floor((blockSize * 7 + blockMargin * 6) / 12); 45 | const label = labels?.weekdays?.[dayIndex] || ''; 46 | 47 | return ( 48 | 55 | {label.length > maxLength ? label.slice(0, maxLength) + '...' : label} 56 | 57 | ); 58 | })} 59 | 60 | )} 61 | {!hideMonthLabels && ( 62 | 63 | {getMonthLabels(weeks, labels.months).map(({ label, weekIndex }) => { 64 | const maxLength = Math.floor((blockSize * 4 + blockMargin * 3) / 12); 65 | return ( 66 | 71 | {label.length > maxLength ? label.slice(0, maxLength) + '...' : label} 72 | 73 | ); 74 | })} 75 | 76 | )} 77 | 78 | ); 79 | }, 80 | ); 81 | 82 | export default ChartLabels; 83 | -------------------------------------------------------------------------------- /src/Heatmaps/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | 3 | import { useStyles } from './styles'; 4 | 5 | interface FooterProps { 6 | blockRadius: number; 7 | blockSize: number; 8 | colorScale: string[]; 9 | hideColorLegend?: boolean; 10 | hideTotalCount?: boolean; 11 | labels: { 12 | legend: { 13 | less: string; 14 | more: string; 15 | }; 16 | totalCount?: string; 17 | }; 18 | loading: boolean; 19 | maxLevel: number; 20 | totalCount: number; 21 | weekdayLabelOffset?: number; 22 | year: number; 23 | } 24 | 25 | const Footer = memo( 26 | ({ 27 | hideTotalCount, 28 | hideColorLegend, 29 | weekdayLabelOffset, 30 | loading, 31 | labels, 32 | year, 33 | maxLevel, 34 | blockSize, 35 | colorScale, 36 | blockRadius, 37 | totalCount, 38 | }) => { 39 | const { cx, styles } = useStyles(); 40 | 41 | return ( 42 |
43 | {/* Placeholder */} 44 | {loading &&
 
} 45 | 46 | {!loading && !hideTotalCount && ( 47 |
48 | {labels.totalCount 49 | ? labels.totalCount 50 | .replace('{{count}}', String(totalCount)) 51 | .replace('{{year}}', String(year)) 52 | : `${totalCount} activities in ${year}`} 53 |
54 | )} 55 | 56 | {!loading && !hideColorLegend && ( 57 |
58 | {labels.legend.less} 59 | {Array.from({ length: maxLevel + 1 }) 60 | .fill(null) 61 | .map((_, level) => ( 62 | 63 | 70 | 71 | ))} 72 | {labels.legend.more} 73 |
74 | )} 75 |
76 | ); 77 | }, 78 | ); 79 | 80 | export default Footer; 81 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/activityLevels.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | 3 | import { yearData } from './data'; 4 | 5 | export default () => ; 6 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/clickEvent.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | import { Highlighter } from '@lobehub/ui'; 3 | import { useState } from 'react'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | import { yearData } from './data'; 7 | 8 | export default () => { 9 | const [value, setValue] = useState({}); 10 | return ( 11 | 12 | setValue(v)} /> 13 | 14 | {JSON.stringify(value, null, 2)} 15 | 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/customColors.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | import { useTheme } from 'antd-style'; 3 | import { Flexbox } from 'react-layout-kit'; 4 | 5 | import { yearData } from './data'; 6 | 7 | export default () => { 8 | const theme = useTheme(); 9 | 10 | return ( 11 | 12 | 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/customTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps, HeatmapsProps } from '@lobehub/charts'; 2 | import { Typography } from 'antd'; 3 | import { useTheme } from 'antd-style'; 4 | import { Flexbox } from 'react-layout-kit'; 5 | 6 | import { yearData } from './data'; 7 | 8 | export default () => { 9 | const theme = useTheme(); 10 | 11 | const customTooltip: HeatmapsProps['customTooltip'] = (payload) => { 12 | if (!payload) return null; 13 | return ( 14 | 15 | 20 | 21 | 25 | {payload.date} 26 | 27 | 28 | {payload.count} 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | return ; 36 | }; 37 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/customization.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | 3 | import { yearData } from './data'; 4 | 5 | export default () => { 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/dataRanges.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | import { Flexbox } from 'react-layout-kit'; 3 | 4 | import { monthData, weekData, yearData } from './data'; 5 | 6 | export default () => ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/example.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | 3 | import { yearData } from './data'; 4 | 5 | export default () => ; 6 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/i18n.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | 3 | import { yearData } from './data'; 4 | 5 | export default () => ( 6 | 32 | ); 33 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/index.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps, HeatmapsProps } from '@lobehub/charts'; 2 | import { StoryBook, useControls, useCreateStore } from '@lobehub/ui/storybook'; 3 | 4 | import { yearData } from './data'; 5 | 6 | export default () => { 7 | const store = useCreateStore(); 8 | 9 | const props: HeatmapsProps | any = useControls( 10 | { 11 | blockMargin: { 12 | step: 1, 13 | value: 4, 14 | }, 15 | blockRadius: { 16 | step: 1, 17 | value: 2, 18 | }, 19 | blockSize: { 20 | step: 1, 21 | value: 12, 22 | }, 23 | fontSize: { 24 | step: 1, 25 | value: 12, 26 | }, 27 | hideColorLegend: false, 28 | hideMonthLabels: false, 29 | hideTotalCount: false, 30 | loading: false, 31 | maxLevel: { 32 | max: 9, 33 | min: 1, 34 | step: 1, 35 | value: 4, 36 | }, 37 | showTooltip: true, 38 | showWeekdayLabels: false, 39 | weekStart: { 40 | options: { 41 | [`(0) Sunday`]: 0, 42 | [`(1) Monday`]: 1, 43 | [`(2) Tuesday`]: 2, 44 | [`(3) Wednesday`]: 3, 45 | [`(4) Thursday`]: 4, 46 | [`(5) Friday`]: 5, 47 | [`(6) Saturday`]: 6, 48 | }, 49 | value: 0, 50 | }, 51 | }, 52 | { store }, 53 | ); 54 | 55 | return ( 56 | 57 | 58 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /src/Heatmaps/demos/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Heatmaps } from '@lobehub/charts'; 2 | 3 | import { yearData } from './data'; 4 | 5 | export default () => ; 6 | -------------------------------------------------------------------------------- /src/Heatmaps/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: Components 3 | group: Visualizations 4 | description: A flexible React component to display activity data in a heatmaps chart. 5 | --- 6 | 7 | 8 | 9 | ## Usage example 10 | 11 | The example below shows a composition of a `Heatmaps` component. 12 | 13 | 14 | 15 | ## Loading 16 | 17 | 18 | 19 | ## Activity Levels 20 | 21 | Use the `maxLevel` prop to control the number of activity levels. This value is zero indexed (0 represents no activity), 22 | so for example a maxLevel of 2 will total in 3 levels as above. Each activity level must be in the interval from 0 to `maxLevel`, 23 | which is 4 per default. 24 | 25 | 26 | 27 | ## Date Ranges 28 | 29 | 30 | 31 | ## Usage example with click event 32 | 33 | The example below shows an interacive chart using the `onValueChange` prop. 34 | 35 | 36 | 37 | ## Usage example with custom tooltip 38 | 39 | The example below shows a custom tooltip using `customTooltip` prop. 40 | 41 | 42 | 43 | ## Usage example with a custom colors 44 | 45 | The example below shows a chart with custom `colors`. 46 | 47 | 48 | 49 | ## Customization 50 | 51 | 52 | 53 | ## Localization 54 | 55 | 56 | 57 | ## API 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/Heatmaps/styles.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ css, token }) => ({ 4 | calendar: css` 5 | overflow: visible; 6 | display: block; 7 | 8 | text { 9 | fill: currentcolor; 10 | } 11 | `, 12 | container: css` 13 | display: flex; 14 | flex-direction: column; 15 | gap: 8px; 16 | 17 | width: max-content; 18 | max-width: 100%; 19 | 20 | rect { 21 | stroke: ${token.colorFillTertiary}; 22 | stroke-width: 1px; 23 | shape-rendering: geometricprecision; 24 | } 25 | `, 26 | footer: css` 27 | display: flex; 28 | flex-wrap: wrap; 29 | gap: 4px 16px; 30 | white-space: nowrap; 31 | `, 32 | legendColors: css` 33 | display: flex; 34 | gap: 3px; 35 | align-items: center; 36 | margin-left: auto; 37 | `, 38 | scrollContainer: css` 39 | overflow: auto hidden; 40 | max-width: 100%; 41 | padding-block: 2px; 42 | `, 43 | })); 44 | -------------------------------------------------------------------------------- /src/Legend/LegendItem.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@lobehub/ui'; 2 | import { Typography } from 'antd'; 3 | import { createStyles } from 'antd-style'; 4 | import { Circle } from 'lucide-react'; 5 | import { memo } from 'react'; 6 | import { Flexbox } from 'react-layout-kit'; 7 | 8 | const useStyles = createStyles(({ css, token }) => ({ 9 | container: css` 10 | display: inline-flex; 11 | align-items: center; 12 | 13 | border-radius: ${token.borderRadius}px; 14 | 15 | color: ${token.colorTextDescription}; 16 | white-space: nowrap; 17 | `, 18 | hasOnValueChange: css` 19 | transition: all 0.25s ${token.motionEaseInOut}; 20 | 21 | &:hover { 22 | color: ${token.colorTextSecondary}; 23 | background: ${token.colorFillTertiary}; 24 | } 25 | `, 26 | itemContent: css` 27 | font-size: 12px; 28 | color: inherit; 29 | `, 30 | })); 31 | 32 | export interface LegendItemProps { 33 | activeLegend?: string; 34 | color: string; 35 | label: string; 36 | name: string; 37 | onClick?: (name: string, color: string) => void; 38 | } 39 | 40 | const LegendItem = memo(({ label, name, color, onClick, activeLegend }) => { 41 | const { cx, styles, theme } = useStyles(); 42 | const hasOnValueChange = !!onClick; 43 | 44 | return ( 45 | { 50 | e.stopPropagation(); 51 | onClick?.(name, color); 52 | }} 53 | paddingBlock={2} 54 | paddingInline={8} 55 | style={{ 56 | cursor: hasOnValueChange ? 'pointer' : 'default', 57 | }} 58 | > 59 | 68 | 77 | {label} 78 | 79 | 80 | ); 81 | }); 82 | 83 | export default LegendItem; 84 | -------------------------------------------------------------------------------- /src/Legend/ScrollButton.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@lobehub/ui'; 2 | import { Button } from 'antd'; 3 | import { LucideIcon } from 'lucide-react'; 4 | import { memo, useEffect, useRef, useState } from 'react'; 5 | 6 | export interface ScrollButtonProps { 7 | disabled?: boolean; 8 | icon: LucideIcon; 9 | onClick?: () => void; 10 | } 11 | 12 | const ScrollButton = memo(({ icon, onClick, disabled }) => { 13 | const [isPressed, setIsPressed] = useState(false); 14 | const intervalRef = useRef(null); 15 | 16 | useEffect(() => { 17 | if (isPressed) { 18 | intervalRef.current = setInterval(() => { 19 | onClick?.(); 20 | }, 300); 21 | } else { 22 | clearInterval(intervalRef.current as any); 23 | } 24 | return () => clearInterval(intervalRef.current as any); 25 | }, [isPressed, onClick]); 26 | 27 | useEffect(() => { 28 | if (disabled) { 29 | clearInterval(intervalRef.current as any); 30 | setIsPressed(false); 31 | } 32 | }, [disabled]); 33 | 34 | return ( 35 |