├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── feaure_request.yml │ └── new_task.md ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ └── pr_cn.md ├── labeler.yml └── workflows │ ├── hotfix-release.yml │ ├── label.yml │ ├── pre-release.yml │ ├── release.yml │ ├── stale.yml │ └── unit-test.yml ├── .gitignore ├── .lintstagedrc ├── .prettierignore ├── .prettierrc.js ├── .vscode ├── find-unused-exports.json ├── launch.json ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTING.zh-CN.md ├── LICENSE ├── README.md ├── README.zh-CN.md ├── build.sh ├── common ├── autoinstallers │ ├── create │ │ ├── README.md │ │ ├── package.json │ │ ├── pnpm-lock.yaml │ │ ├── src │ │ │ ├── cli.ts │ │ │ ├── create.ts │ │ │ ├── global.d.ts │ │ │ ├── logger.ts │ │ │ └── template.ts │ │ ├── templates │ │ │ └── library │ │ │ │ ├── .eslintrc.js │ │ │ │ ├── README.md │ │ │ │ ├── bundler.config.js │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ ├── global.d.ts │ │ │ │ └── index.ts │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite │ │ │ │ ├── index.html │ │ │ │ ├── public │ │ │ │ └── vite.svg │ │ │ │ ├── src │ │ │ │ ├── App.css │ │ │ │ ├── App.tsx │ │ │ │ ├── assets │ │ │ │ │ └── react.svg │ │ │ │ ├── index.css │ │ │ │ ├── main.tsx │ │ │ │ └── vite-env.d.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tsconfig.node.json │ │ │ │ └── vite.config.ts │ │ └── tsconfig.json │ ├── lint │ │ ├── change-all.ts │ │ ├── commit-lint.js │ │ ├── commitlint.config.js │ │ ├── package.json │ │ ├── pnpm-lock.yaml │ │ ├── prettier.ts │ │ └── tsconfig.json │ └── run-script │ │ ├── package.json │ │ ├── pnpm-lock.yaml │ │ ├── run.ts │ │ ├── start.ts │ │ └── tsconfig.json ├── changes │ └── @visactor │ │ └── vlayouts │ │ └── fix-export-venn-utils_2025-05-16-03-21.json ├── config │ └── rush │ │ ├── .npmrc │ │ ├── .npmrc-publish │ │ ├── .pnpmfile.cjs │ │ ├── artifactory.json │ │ ├── build-cache.json │ │ ├── command-line.json │ │ ├── common-versions.json │ │ ├── experiments.json │ │ ├── pnpm-config.json │ │ ├── pnpm-lock.yaml │ │ ├── repo-state.json │ │ ├── rush-plugins.json │ │ └── version-policies.json ├── git-hooks │ ├── commit-msg │ ├── pre-commit │ └── pre-push └── scripts │ ├── apply-prerelease-version.js │ ├── get-package-json.js │ ├── install-run-rush-pnpm.js │ ├── install-run-rush.js │ ├── install-run-rushx.js │ ├── install-run.js │ ├── parse-version.js │ ├── pre-release.js │ ├── release.js │ ├── set-json-file.js │ ├── set-prerelease-version.js │ ├── sort_deps.js │ └── version-policies.js ├── packages ├── vdataset │ ├── .eslintrc.js │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── README.zh-CN.md │ ├── __tests__ │ │ ├── dataview.test.ts │ │ ├── fields.test.ts │ │ ├── parser-clone.test.ts │ │ └── parser-csv.test.ts │ ├── bundler.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── data-set.ts │ │ ├── data-view.ts │ │ ├── index.ts │ │ ├── moudles.d.ts │ │ ├── parser │ │ │ ├── bytejson.ts │ │ │ ├── data-view.ts │ │ │ ├── dsv.ts │ │ │ ├── geo │ │ │ │ ├── geobuf.ts │ │ │ │ ├── geojson.ts │ │ │ │ ├── geotree.ts │ │ │ │ └── topojson.ts │ │ │ ├── index.ts │ │ │ ├── svg.ts │ │ │ └── tree.ts │ │ ├── projection │ │ │ └── projections.ts │ │ ├── transform │ │ │ ├── fields.ts │ │ │ ├── filter.ts │ │ │ ├── fold.ts │ │ │ ├── geo │ │ │ │ ├── dissolve.ts │ │ │ │ ├── mercator.ts │ │ │ │ ├── projection.ts │ │ │ │ └── simplify.ts │ │ │ ├── hexagon.ts │ │ │ ├── index.ts │ │ │ ├── map.ts │ │ │ └── statistics.ts │ │ └── utils │ │ │ ├── color │ │ │ └── index.ts │ │ │ ├── csv.ts │ │ │ ├── geo.ts │ │ │ ├── js.ts │ │ │ └── uuid.ts │ ├── tscofig.eslint.json │ ├── tsconfig.json │ └── tsconfig.test.json ├── vlayouts │ ├── .eslintrc.js │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── README.zh-CN.md │ ├── __tests__ │ │ ├── hierarchy │ │ │ ├── circle-packing.test.ts │ │ │ ├── data │ │ │ │ └── tree.ts │ │ │ ├── format.test.ts │ │ │ ├── sunburst.test.ts │ │ │ ├── tree.test.ts │ │ │ ├── treemap.test.ts │ │ │ └── utils.test.ts │ │ ├── imagecloud │ │ │ ├── imagecloud.test.ts │ │ │ └── shape.test.ts │ │ ├── mock.ts │ │ ├── sankey │ │ │ ├── data.ts │ │ │ ├── format.test.ts │ │ │ └── layout.test.ts │ │ ├── venn │ │ │ └── venn.test.ts │ │ ├── wordcloud-shape │ │ │ └── wordcloud-shape.test.ts │ │ └── wordcloud │ │ │ ├── cloud-layout.test.ts │ │ │ ├── fast-layout.test.ts │ │ │ ├── grid-layout.test.ts │ │ │ ├── util.test.ts │ │ │ └── wordcloud.test.ts │ ├── bundler.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── circle-packing │ │ │ ├── enclose.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── layout.ts │ │ │ ├── siblings.ts │ │ │ └── transform.ts │ │ ├── imagecloud │ │ │ ├── imagecloud.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── layout │ │ │ │ ├── basic.ts │ │ │ │ ├── grid │ │ │ │ │ ├── circlGrid.ts │ │ │ │ │ ├── grid.ts │ │ │ │ │ ├── hexagonalGrid.ts │ │ │ │ │ └── rectGrid.ts │ │ │ │ ├── spiral.ts │ │ │ │ └── stack.ts │ │ │ └── util.ts │ │ ├── index.ts │ │ ├── interface │ │ │ ├── common.ts │ │ │ └── wordcloud.ts │ │ ├── sankey │ │ │ ├── format.ts │ │ │ ├── hierarchy.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── layout.ts │ │ │ └── transform.ts │ │ ├── sunburst │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── layout.ts │ │ │ └── transform.ts │ │ ├── tree │ │ │ ├── cluster.ts │ │ │ ├── format.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── layout.ts │ │ │ ├── transform.ts │ │ │ └── tree.ts │ │ ├── treemap │ │ │ ├── binary.ts │ │ │ ├── dice.ts │ │ │ ├── format.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── layout.ts │ │ │ ├── slice.ts │ │ │ ├── sliceDice.ts │ │ │ ├── squarify.ts │ │ │ └── transform.ts │ │ ├── utils │ │ │ ├── hierarchy.ts │ │ │ ├── image.ts │ │ │ ├── loader.ts │ │ │ ├── shapes.ts │ │ │ └── spirals.ts │ │ ├── venn │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── utils │ │ │ │ ├── index.ts │ │ │ │ ├── interface.ts │ │ │ │ ├── label.ts │ │ │ │ ├── layout │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── constrained-mds-layout.ts │ │ │ │ │ ├── greedy-layout.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layout.ts │ │ │ │ │ └── loss.ts │ │ │ │ ├── path.ts │ │ │ │ └── solution │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── normalize-solution.ts │ │ │ │ │ └── scale-solution.ts │ │ │ └── venn.ts │ │ ├── wordcloud-shape │ │ │ ├── cloud-shape-layout.ts │ │ │ ├── filling.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── layout.ts │ │ │ ├── segmentation.ts │ │ │ ├── util.ts │ │ │ ├── wordcloud-shape.ts │ │ │ └── wordle.ts │ │ └── wordcloud │ │ │ ├── base.ts │ │ │ ├── cloud-layout.ts │ │ │ ├── fast-layout.ts │ │ │ ├── grid-layout.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── util.ts │ │ │ └── wordcloud.ts │ ├── tscofig.eslint.json │ ├── tsconfig.json │ └── tsconfig.test.json ├── vscale │ ├── .bundle │ │ └── config │ ├── .eslintrc.js │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── README.zh-CN.md │ ├── __tests__ │ │ ├── band-invert.test.ts │ │ ├── band-width.test.ts │ │ ├── band.test.ts │ │ ├── clamp.test.ts │ │ ├── color.test.ts │ │ ├── common.test.ts │ │ ├── fish-eye.test.ts │ │ ├── identity-scale.test.ts │ │ ├── linear-nice-option.test.ts │ │ ├── linear.test.ts │ │ ├── log-nice-option.test.ts │ │ ├── log.test.ts │ │ ├── ordinal.test.ts │ │ ├── point.test.ts │ │ ├── pow.test.ts │ │ ├── quantile.test.ts │ │ ├── quantize.test.ts │ │ ├── sqrt.test.ts │ │ ├── symlog.test.ts │ │ ├── threshold.test.ts │ │ ├── tick-sample.test.ts │ │ ├── tick-single-value.test.ts │ │ ├── time.test.ts │ │ ├── utc.test.ts │ │ └── wilkinson-extended.test.ts │ ├── bundler.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── band-scale.ts │ │ ├── base-scale.ts │ │ ├── continuous-scale.ts │ │ ├── identity-scale.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── linear-scale.ts │ │ ├── log-nice-mixin.ts │ │ ├── log-scale.ts │ │ ├── ordinal-scale.ts │ │ ├── point-scale.ts │ │ ├── pow-scale.ts │ │ ├── quantile-scale.ts │ │ ├── quantize-scale.ts │ │ ├── sqrt-scale.ts │ │ ├── symlog-scale.ts │ │ ├── threshold-scale.ts │ │ ├── time-scale.ts │ │ ├── type.ts │ │ └── utils │ │ │ ├── index.ts │ │ │ ├── interpolate.ts │ │ │ ├── tick-sample-int.ts │ │ │ ├── tick-sample.ts │ │ │ ├── tick-wilkinson-extended.ts │ │ │ ├── time.ts │ │ │ └── utils.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ └── tsconfig.test.json └── vutils │ ├── .eslintrc.js │ ├── CHANGELOG.json │ ├── CHANGELOG.md │ ├── README.md │ ├── README.zh-CN.md │ ├── __tests__ │ ├── angle.test.ts │ ├── color │ │ ├── color.test.ts │ │ └── interpolate.test.ts │ ├── common │ │ ├── array.test.ts │ │ ├── ascending.test.ts │ │ ├── bisect.test.ts │ │ ├── clamper.test.ts │ │ ├── extent.test.ts │ │ ├── field.test.ts │ │ ├── getter.test.ts │ │ ├── isEqual.test.ts │ │ ├── isFunction.test.ts │ │ ├── isNumber.test.ts │ │ ├── isObject.test.ts │ │ ├── isObjectLike.test.ts │ │ ├── isPlainObject.test.ts │ │ ├── isShallowEqual.test.ts │ │ ├── isString.test.ts │ │ ├── merge.test.ts │ │ ├── pad.test.ts │ │ ├── pickWithout.test.ts │ │ ├── range.test.ts │ │ ├── regression-linear.test.ts │ │ ├── stringWidth.test.ts │ │ ├── substitute.test.ts │ │ └── to-percent.test.ts │ ├── data-structure │ │ ├── obb.test.ts │ │ └── point.test.ts │ ├── dom.test.ts │ ├── format │ │ └── number.test.ts │ ├── graphics │ │ ├── arc.test.ts │ │ ├── bounds-util.test.ts │ │ └── intersect.test.ts │ ├── index.spec.ts │ ├── logger │ │ └── logger.test.ts │ ├── math │ │ └── math.test.ts │ ├── padding.test.ts │ ├── text │ │ ├── textMeasure.test.ts │ │ └── util.ts │ └── time │ │ └── formatUtils.test.ts │ ├── bundler.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── angle.ts │ ├── color │ │ ├── Color.ts │ │ ├── hexToRgb.ts │ │ ├── hslToRgb.ts │ │ ├── index.ts │ │ ├── interpolate.ts │ │ ├── rgbToHex.ts │ │ └── rgbToHsl.ts │ ├── common │ │ ├── array.ts │ │ ├── ascending.ts │ │ ├── bisect.ts │ │ ├── clamp.ts │ │ ├── clampRange.ts │ │ ├── clamper.ts │ │ ├── clone.ts │ │ ├── cloneDeep.ts │ │ ├── constant.ts │ │ ├── debounce.ts │ │ ├── deviation.ts │ │ ├── extent.ts │ │ ├── field.ts │ │ ├── get.ts │ │ ├── getType.ts │ │ ├── has.ts │ │ ├── index.ts │ │ ├── interpolate.ts │ │ ├── isArray.ts │ │ ├── isArrayLike.ts │ │ ├── isBase64.ts │ │ ├── isBoolean.ts │ │ ├── isDate.ts │ │ ├── isEmpty.ts │ │ ├── isEqual.ts │ │ ├── isFunction.ts │ │ ├── isNil.ts │ │ ├── isNull.ts │ │ ├── isNumber.ts │ │ ├── isNumeric.ts │ │ ├── isObject.ts │ │ ├── isObjectLike.ts │ │ ├── isPlainObject.ts │ │ ├── isPrototype.ts │ │ ├── isRegExp.ts │ │ ├── isShallowEqual.ts │ │ ├── isString.ts │ │ ├── isType.ts │ │ ├── isUndefined.ts │ │ ├── isValid.ts │ │ ├── isValidNumber.ts │ │ ├── isValidUrl.ts │ │ ├── lowerFirst.ts │ │ ├── median.ts │ │ ├── memoize.ts │ │ ├── merge.ts │ │ ├── mixin.ts │ │ ├── number.ts │ │ ├── pad.ts │ │ ├── pick.ts │ │ ├── pickWithout.ts │ │ ├── quantileSorted.ts │ │ ├── random.ts │ │ ├── range.ts │ │ ├── regression-linear.ts │ │ ├── substitute.ts │ │ ├── throttle.ts │ │ ├── tickStep.ts │ │ ├── toDate.ts │ │ ├── toNumber.ts │ │ ├── toPercent.ts │ │ ├── toValidNumber.ts │ │ ├── truncate.ts │ │ ├── upperFirst.ts │ │ ├── uuid.ts │ │ ├── variance.ts │ │ └── zero.ts │ ├── data-structure │ │ ├── bounds.ts │ │ ├── hashTable.ts │ │ ├── index.ts │ │ ├── matrix.ts │ │ └── point.ts │ ├── dom.ts │ ├── fmin │ │ ├── blas1.ts │ │ ├── conjugate-gradient.ts │ │ ├── index.ts │ │ ├── linesearch.ts │ │ └── nelder-mead.ts │ ├── format │ │ ├── number │ │ │ ├── formatDecimal.ts │ │ │ ├── formatGroup.ts │ │ │ ├── formatPrefixAuto.ts │ │ │ ├── formatRounded.ts │ │ │ ├── formatTrim.ts │ │ │ ├── index.ts │ │ │ ├── number.ts │ │ │ └── specifier.ts │ │ └── time.ts │ ├── geo │ │ ├── circle-intersection.ts │ │ ├── constant.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ └── invariant.ts │ ├── graphics │ │ ├── algorithm │ │ │ ├── aabb.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── intersect.ts │ │ │ └── obb.ts │ │ ├── arc.ts │ │ ├── bounds-util.ts │ │ ├── graph-util.ts │ │ ├── image.ts │ │ ├── index.ts │ │ ├── polygon.ts │ │ └── text │ │ │ ├── index.ts │ │ │ ├── measure │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ ├── textMeasure.ts │ │ │ └── util.ts │ │ │ └── stringWidth.ts │ ├── index.ts │ ├── logger.ts │ ├── lru.ts │ ├── math.ts │ ├── padding.ts │ ├── time │ │ ├── formatUtils.ts │ │ ├── index.ts │ │ └── interval.ts │ └── type.ts │ ├── tscofig.eslint.json │ ├── tsconfig.json │ └── tsconfig.test.json ├── rush.json ├── share ├── eslint-config │ ├── package.json │ └── profile │ │ ├── common.js │ │ ├── lib.js │ │ └── react.js ├── jest-config │ ├── jest.base.js │ └── package.json └── ts-config │ ├── package.json │ └── tsconfig.base.json └── tools └── bundler ├── .eslintrc.js ├── README.md ├── bin └── index.js ├── fixtures └── config │ ├── .gitignore │ ├── bundler.config.js │ ├── package.json │ ├── source │ ├── custom.css │ ├── foo │ │ ├── bar │ │ │ ├── index.ts │ │ │ └── web.png │ │ └── index.ts │ ├── global.d.ts │ ├── index.ts │ └── utils │ │ └── index.ts │ └── tsconfig.json ├── package.json ├── src ├── bootstrap.ts ├── global.d.ts ├── index.ts ├── logic │ ├── alias.ts │ ├── babel.config.ts │ ├── config.test.ts │ ├── config.ts │ ├── debug.ts │ ├── package.ts │ └── rollup.config.ts └── tasks │ ├── clean.ts │ ├── copy.ts │ ├── modules.ts │ ├── style.ts │ └── umd.ts ├── tsconfig.json └── vitest.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | # contact_links: 3 | # - name: @VisaActor/Vutil 4 | # url: https://www.visactor.io/ 5 | # about: Please search question here before opening a new issue 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'New task' 3 | about: Describe this issue template's purpose here. 4 | title: 'test template in md' 5 | labels: 'test' 6 | assignees: '' 7 | --- 8 | 9 | some template text 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pr_cn.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### 🤔 这个分支是... 10 | 11 | - [ ] 新功能 12 | - [ ] Bug fix 13 | - [ ] Ts 类型更新 14 | - [ ] 打包优化 15 | - [ ] 性能优化 16 | - [ ] 功能增强 17 | - [ ] 重构 18 | - [ ] 依赖版本更新 19 | - [ ] 代码优化 20 | - [ ] 测试 case 更新 21 | - [ ] 分支合并 22 | - [ ] 网站/文档更新 23 | - [ ] demo 更新 24 | - [ ] Workflow 25 | - [ ] 配置修改 26 | - [ ] 其他 (具体是什么,请补充?) 27 | 28 | ### 🔗 相关 issue 连接 29 | 30 | 34 | 35 | ### 💡 问题的背景&解决方案 36 | 37 | 42 | 43 | ### 📝 Changelog 44 | 45 | 48 | 49 | | Language | Changelog | 50 | | ---------- | --------- | 51 | | 🇺🇸 English | | 52 | | 🇨🇳 Chinese | | 53 | 54 | ### ☑️ 自测 55 | 56 | ⚠️ 在提交 PR 之前,请检查一下内容. ⚠️ 57 | 58 | - [ ] 文档提供了,或者更新,或者不需要 59 | - [ ] Demo 提供了,或者更新,或者不需要 60 | - [ ] Ts 类型定义提供了,或者更新,或者不需要 61 | - [ ] Changelog 提供了,或者不需要 62 | 63 | --- 64 | 65 | 69 | 70 | ### 🚀 Summary 71 | 72 | copilot:summary 73 | 74 | ### 🔍 Walkthrough 75 | 76 | copilot:walkthrough 77 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Add 'dataset' label to any change within the 'core' package 2 | '@pkg/core': 3 | - packages/vdataset/src/** 4 | 5 | # Add 'util' label to any change within the 'core' package 6 | '@pkg/util': 7 | - packages/vutils/src/** 8 | 9 | # Add 'test' label to any change to packages/*/__tests__/* files within the source dir 10 | test: 11 | - packages/*/__tests__/* 12 | 13 | # Add 'docs' label to any change to docs/ files within the source dir 14 | docs: 15 | - docs/** 16 | 17 | # Add 'eslint' label to any change to docs/ files within the source dir 18 | eslint: 19 | - share/eslint-config/** 20 | - packages/*/.eslintrc.js 21 | 22 | # Add 'jest' label to any change to docs/ files within the source dir 23 | jest: 24 | - share/jest-config/** 25 | - packages/*/jest.config.js 26 | 27 | # Add 'typescript' label to any change to docs/ files within the source dir 28 | typescript: 29 | - share/ts-config/** 30 | - packages/*/tsconfig.json 31 | - packages/*/tsconfig.eslint.json 32 | - packages/vscale/src/type.ts 33 | - packages/vutils/src/type.ts 34 | 35 | # Add 'bundler' label to any change to tools/bunder/** files within the source dir 36 | bundler: 37 | - tools/bundler/** 38 | - packages/*/bundler.config.js 39 | 40 | # Add 'chore' label to any change to common/** files within the source dir 41 | chore: 42 | - common/autoinstallers/** 43 | - common/git-hooks/** 44 | - common/scripts/** 45 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler 7 | 8 | name: Labeler 9 | on: [pull_request_target] 10 | 11 | jobs: 12 | label: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | pull-requests: write 17 | 18 | steps: 19 | - uses: actions/labeler@master 20 | with: 21 | repo-token: '${{ secrets.GITHUB_TOKEN }}' 22 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Unit test CI 5 | 6 | on: 7 | pull_request: 8 | branches: ['main', 'develop'] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [18.x] 17 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'npm' 26 | cache-dependency-path: './common/config/rush/pnpm-lock.yaml' 27 | 28 | # Install rush 29 | - name: Install rush 30 | run: node common/scripts/install-run-rush.js install --bypass-policy 31 | - run: node common/scripts/install-run-rush.js build --only tag:package 32 | - run: node common/scripts/install-run-rush.js test --only tag:package 33 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "{packages,tools}/**/*.{ts,tsx}": ["eslint --color --fix", "prettier --write --ignore-unknown"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # https://prettier.io/docs/en/ignore.html 2 | #------------------------------------------------------------------------------------------------------------------- 3 | # Keep this section in sync with .gitignore 4 | #------------------------------------------------------------------------------------------------------------------- 5 | 6 | 👋 (copy + paste your .gitignore file contents here) 👋 7 | 8 | #------------------------------------------------------------------------------------------------------------------- 9 | # Prettier-specific overrides 10 | #------------------------------------------------------------------------------------------------------------------- 11 | 12 | # Rush files 13 | common/changes/ 14 | common/scripts/ 15 | common/config/ 16 | CHANGELOG.* 17 | 18 | # Package manager files 19 | pnpm-lock.yaml 20 | yarn.lock 21 | package-lock.json 22 | shrinkwrap.json 23 | 24 | # Build outputs 25 | dist 26 | lib -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Documentation for this file: https://prettier.io/en/configuration.html 2 | module.exports = { 3 | "printWidth": 120, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": true, 8 | "quoteProps": "as-needed", 9 | "trailingComma": "none", 10 | "bracketSpacing": true, 11 | "arrowParens": "avoid", 12 | "proseWrap": "preserve", 13 | "htmlWhitespaceSensitivity": "css", 14 | "endOfLine": "lf" 15 | }; 16 | -------------------------------------------------------------------------------- /.vscode/find-unused-exports.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": {} 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "configurations": [ 4 | { 5 | "name": "unit test", 6 | "type": "pwa-node", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "args": ["${file}"], 10 | "console": "integratedTerminal", 11 | "internalConsoleOptions": "neverOpen" 12 | } 13 | // { 14 | // "name": "page test", 15 | // "request": "launch", 16 | // "cleanUp": "onlyTab", 17 | // "file": "${workspaceFolder}/packages/chartspace/test/index.html", 18 | // "preLaunchTask": "web", 19 | // "type": "pwa-chrome" 20 | // } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Eslint 3 | "eslint.format.enable": true, 4 | "eslint.validate": ["typescript", "javascript", "javascriptreact", "typescriptreact"], 5 | // Formatter 6 | "javascript.format.enable": false, 7 | "typescript.format.enable": false, 8 | "[javascript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[json]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | // Editor 18 | "editor.defaultFormatter": "esbenp.prettier-vscode", 19 | "editor.codeActionsOnSave": ["source.fixAll.eslint"], 20 | "editor.formatOnSave": true, 21 | "editor.tabSize": 2, 22 | "editor.detectIndentation": false, 23 | // Typescript 24 | "typescript.preferences.importModuleSpecifier": "project-relative", 25 | "[python]": { 26 | "editor.defaultFormatter": "ms-python.black-formatter" 27 | }, 28 | "python.formatting.provider": "none", 29 | "cSpell.words": ["vutil"] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "web", 6 | "type": "shell", 7 | "isBackground": true, 8 | "command": "${workspaceFolder}/node_modules/.bin/tsc", 9 | "args": [ 10 | "${file}", 11 | "--outFile", 12 | "${workspaceFolder}/packages/chartspace/test/dist/index.js", 13 | "--strict", 14 | "--experimentalDecorators", 15 | "--sourceMap", 16 | "--skipLibCheck", 17 | "--watch" 18 | ], 19 | "problemMatcher": ["$tsc-watch"] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Bytedance, Inc. and its affiliates. 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 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SCM 仓库编译脚本,PSM: data/dp/chartspace4 3 | 4 | set -e 5 | #-- 准备环境 6 | # 切换node版本 change node version 7 | source /etc/profile 8 | 9 | nvm use lts/fermium 10 | 11 | echo 'node version is ' && node -v 12 | 13 | #-- 定义变量 14 | DIR=`pwd` 15 | DIST="$DIR/packages/v-util/dist" 16 | OUTPUT="$DIR/output" #该目录下文件会被打包上传到CDN 17 | TARGET="$OUTPUT/$BUILD_VERSION" 18 | 19 | mkdir -p $OUTPUT 20 | 21 | npm i --global @microsoft/rush 22 | rush update 23 | rush build --to @visactor/vutils 24 | 25 | 26 | # 这里目前是按需要只传了一个 27 | mkdir -p $TARGET 28 | cp -r $DIST/dp_v-util.js $TARGET/index.js 29 | -------------------------------------------------------------------------------- /common/autoinstallers/create/README.md: -------------------------------------------------------------------------------- 1 | # Create project 2 | 3 | ## Description 4 | 5 | Create a project 6 | 7 | ## Usage 8 | 9 | ```bash 10 | rush create [--name | -n] [packageName] 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /common/autoinstallers/create/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@microsoft/rush-lib": "5.94.1", 7 | "chalk": "4.1.2", 8 | "fast-glob": "3.2.11", 9 | "minimist": "1.2.6", 10 | "prettier": "2.7.1", 11 | "prompts": "2.4.2", 12 | "string.prototype.replaceall": "1.0.6", 13 | "ts-node": "10.9.0", 14 | "zx": "4.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/minimist": "1.2.2", 18 | "@types/node": "*", 19 | "@types/prompts": "2.0.14", 20 | "typescript": "4.9.5" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/autoinstallers/create/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'string.prototype.replaceall' { 2 | type ReplaceAll = (source: string, sbuStr: RegExp | string, newSubStr: string) => string; 3 | 4 | const replaceAll: ReplaceAll = () => {} 5 | 6 | export default replaceAll 7 | } 8 | -------------------------------------------------------------------------------- /common/autoinstallers/create/src/logger.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | 3 | export const logger = { 4 | info: (...messages: any[]) => console.info( 5 | chalk.greenBright.bold('[rush create]'), 6 | ...messages, 7 | ), 8 | 9 | error: (...messages: any[]) => { 10 | console.error( 11 | chalk.redBright.bold('[rush create][error]'), 12 | ...messages, 13 | ) 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-patch/modern-module-resolution'); 2 | 3 | module.exports = { 4 | extends: ['@internal/eslint-config/profile/node'], 5 | parserOptions: { tsconfigRootDir: __dirname }, 6 | // ignorePatterns: [], 7 | }; 8 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/README.md: -------------------------------------------------------------------------------- 1 | # @{{scope}}/{{projectName}} 2 | 3 | ## Description 4 | 5 | {{description}} 6 | 7 | ## Usage 8 | 9 | ```typescript 10 | import { xxx } from '@{{scope}}/{{projectName}}'; 11 | ``` 12 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/bundler.config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @type {Partial} 4 | */ 5 | module.exports = { 6 | formats: ["cjs", "es", "umd"], 7 | }; 8 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{scope}}/{{projectName}}", 3 | "version": "0.0.1", 4 | "description": "{{description}}", 5 | "sideEffects": false, 6 | "main": "cjs/index.js", 7 | "module": "es/index.js", 8 | "types": "es/index.d.ts", 9 | "files": [ 10 | "cjs", 11 | "es", 12 | "dist" 13 | ], 14 | "scripts": { 15 | "compile": "tsc --noEmit", 16 | "eslint": "eslint --debug --fix src/", 17 | "build": "bundle", 18 | "dev": "bundle --clean -f es -w", 19 | "start": "vite ./vite", 20 | "prepublishOnly": "npm run build" 21 | }, 22 | "dependencies": {}, 23 | "devDependencies": { 24 | "@internal/bundler": "workspace:*", 25 | "@internal/eslint-config": "workspace:*", 26 | "@internal/ts-config": "workspace:*", 27 | "@rushstack/eslint-patch": "~1.1.4", 28 | "react": "16.13.0", 29 | "react-dom": "16.13.0", 30 | "@types/react": "16.9.49", 31 | "@types/react-dom": "16.9.8", 32 | "@vitejs/plugin-react": "3.1.0", 33 | "eslint": "~8.18.0", 34 | "vite": "3.2.6", 35 | "typescript": "4.9.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __VERSION__: string; 2 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/src/index.ts: -------------------------------------------------------------------------------- 1 | export const version = __VERSION__; 2 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "output" 6 | }, 7 | "include": [ 8 | "src" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import reactLogo from './assets/react.svg'; 3 | import viteLogo from '/vite.svg'; 4 | import './App.css'; 5 | 6 | function App() { 7 | const [count, setCount] = useState(0); 8 | 9 | return ( 10 | <> 11 |
12 | 13 | Vite logo 14 | 15 | 16 | React logo 17 | 18 |
19 |

Vite + React

20 |
21 | 22 |

23 | Edit src/App.tsx and save to test HMR 24 |

25 |
26 |

Click on the Vite and React logos to learn more

27 | 28 | ); 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App.tsx'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') as HTMLElement 11 | ); 12 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": [ 5 | "DOM", 6 | "DOM.Iterable", 7 | "ESNext" 8 | ], 9 | "module": "ESNext", 10 | "skipLibCheck": true, 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react", 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true 23 | }, 24 | "include": [ 25 | "src" 26 | ], 27 | "references": [ 28 | { 29 | "path": "./tsconfig.node.json" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /common/autoinstallers/create/templates/library/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react({ jsxRuntime: 'classic' })] 7 | }); 8 | -------------------------------------------------------------------------------- /common/autoinstallers/lint/commit-lint.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const child_process = require("child_process"); 4 | 5 | const gitPath = path.resolve(__dirname, "../../../.git"); 6 | const configPath = path.resolve(__dirname, "./commitlint.config.js"); 7 | const commitlintBinPath = path.resolve(__dirname, "./node_modules/.bin/commitlint"); 8 | 9 | if (!fs.existsSync(gitPath)) { 10 | console.error("no valid .git path"); 11 | process.exit(1); 12 | } 13 | 14 | const result = child_process.spawnSync( 15 | "sh", 16 | ["-c", `${commitlintBinPath} --config ${configPath} --cwd ${path.dirname(gitPath)} --edit`], 17 | { 18 | stdio: "inherit", 19 | }, 20 | ); 21 | 22 | if (result.status !== 0) { 23 | process.exit(1); 24 | } 25 | -------------------------------------------------------------------------------- /common/autoinstallers/lint/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'header-min-length': [2, 'always', 16], 5 | 'not-allowed-chars': [2, 'always'] 6 | }, 7 | plugins: [ 8 | { 9 | rules: { 10 | 'not-allowed-chars': params => { 11 | const { raw } = params; 12 | const reg = 13 | /^[a-zA-Z0-9\s`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘',。、]+$/im; 14 | 15 | return [ 16 | reg.exec(raw), 17 | 'Your commit message should only contain english characters, numbers, empty space, and special characters.' 18 | ]; 19 | } 20 | } 21 | } 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /common/autoinstallers/lint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lint", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@commitlint/cli": "17.0.3", 7 | "@commitlint/config-conventional": "17.0.3", 8 | "@microsoft/rush-lib": "5.94.1", 9 | "commitizen": "4.2.5", 10 | "eslint": "~8.18.0", 11 | "lint-staged": "13.0.3", 12 | "minimist": "1.2.6", 13 | "prettier": "2.7.1", 14 | "ts-node": "10.9.0" 15 | }, 16 | "devDependencies": { 17 | "@types/minimist": "1.2.2", 18 | "@types/node": "*", 19 | "typescript": "5.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/autoinstallers/lint/prettier.ts: -------------------------------------------------------------------------------- 1 | import minimist, { ParsedArgs } from "minimist"; 2 | import { spawnSync } from "child_process"; 3 | import { RushConfiguration } from "@microsoft/rush-lib"; 4 | 5 | interface PrettierScriptArgv extends ParsedArgs { 6 | dir?: string; 7 | ext?: string; 8 | } 9 | 10 | function run() { 11 | const cwd = process.cwd(); 12 | const rushConfiguration = RushConfiguration.loadFromDefaultLocation({ startingFolder: cwd }); 13 | 14 | const argv: PrettierScriptArgv = minimist(process.argv.slice(2)); 15 | const configFilePath = rushConfiguration.rushJsonFolder + "/.prettierrc.js"; 16 | const ignoreFilePath = rushConfiguration.rushJsonFolder + "/.prettierignore"; 17 | 18 | let ext = "{ts,tsx,less}"; 19 | if (argv.ext) { 20 | const length = argv.ext.split(",").length; 21 | ext = length === 1 ? `${argv.ext}` : `{${argv.ext}}`; 22 | } 23 | 24 | let patterns = `{apps,libs}/**/src/**/**/*.${ext}`; 25 | if (argv.dir) { 26 | patterns = `${argv.dir}/src/**/**/*.${ext}`; 27 | } 28 | 29 | console.log(patterns); 30 | 31 | spawnSync( 32 | "sh", 33 | [ 34 | "-c", 35 | `prettier --config ${configFilePath} --ignore-path ${ignoreFilePath} --write ${patterns}`, 36 | ], 37 | { 38 | shell: false, 39 | stdio: [0, 1, 2], 40 | }, 41 | ); 42 | } 43 | 44 | run(); 45 | -------------------------------------------------------------------------------- /common/autoinstallers/run-script/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run-script", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@microsoft/rush-lib": "5.94.1", 7 | "minimist": "1.2.6", 8 | "ts-node": "10.9.0" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "*", 12 | "@types/minimist": "1.2.2", 13 | "typescript": "5.0.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/autoinstallers/run-script/run.ts: -------------------------------------------------------------------------------- 1 | import minimist, { ParsedArgs } from "minimist"; 2 | import { RushConfiguration } from "@microsoft/rush-lib"; 3 | import { spawnSync } from "child_process"; 4 | 5 | interface RunScriptArgv extends ParsedArgs { 6 | project?: string; 7 | script?: string; 8 | } 9 | 10 | function run() { 11 | const argv: RunScriptArgv = minimist(process.argv.slice(2)); 12 | const projects = RushConfiguration.loadFromDefaultLocation({ 13 | startingFolder: process.cwd(), 14 | }); 15 | 16 | const targetProject = projects.findProjectByShorthandName(argv.project!); 17 | 18 | if (targetProject) { 19 | spawnSync("sh", ["-c", `rushx ${argv.script}`], { 20 | cwd: targetProject?.projectFolder, 21 | shell: false, 22 | stdio: [0, 1, 2], 23 | }); 24 | } 25 | } 26 | 27 | run(); 28 | -------------------------------------------------------------------------------- /common/autoinstallers/run-script/start.ts: -------------------------------------------------------------------------------- 1 | import { RushConfiguration } from "@microsoft/rush-lib"; 2 | import { spawn } from "child_process"; 3 | 4 | function run() { 5 | const projects = RushConfiguration.loadFromDefaultLocation({ 6 | startingFolder: process.cwd(), 7 | }); 8 | 9 | const server = projects.findProjectByShorthandName("@bit-cloud/api"); 10 | const fe = projects.findProjectByShorthandName("@bit-cloud/fe"); 11 | 12 | if (server && fe) { 13 | spawn("sh", ["-c", "rushx dev"], { 14 | cwd: server.projectFolder, 15 | shell: false, 16 | stdio: [0, 1, 2], 17 | }); 18 | 19 | spawn("sh", ["-c", "rushx dev"], { 20 | cwd: fe.projectFolder, 21 | shell: false, 22 | stdio: [0, 1, 2], 23 | }); 24 | } 25 | } 26 | 27 | run(); 28 | -------------------------------------------------------------------------------- /common/changes/@visactor/vlayouts/fix-export-venn-utils_2025-05-16-03-21.json: -------------------------------------------------------------------------------- 1 | { 2 | "changes": [ 3 | { 4 | "comment": "fix: export utils and interface of venn\n\n", 5 | "type": "none", 6 | "packageName": "@visactor/vlayouts" 7 | } 8 | ], 9 | "packageName": "@visactor/vlayouts", 10 | "email": "dingling112@gmail.com" 11 | } -------------------------------------------------------------------------------- /common/config/rush/.npmrc: -------------------------------------------------------------------------------- 1 | # Rush uses this file to configure the NPM package registry during installation. It is applicable 2 | # to PNPM, NPM, and Yarn package managers. It is used by operations such as "rush install", 3 | # "rush update", and the "install-run.js" scripts. 4 | # 5 | # NOTE: The "rush publish" command uses .npmrc-publish instead. 6 | # 7 | # Before invoking the package manager, Rush will copy this file to the folder where installation 8 | # is performed. The copied file will omit any config lines that reference environment variables 9 | # that are undefined in that session; this avoids problems that would otherwise result due to 10 | # a missing variable being replaced by an empty string. 11 | # 12 | # * * * SECURITY WARNING * * * 13 | # 14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because 15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely, 16 | # for example if the machine loses power. A safer practice is to pass the token via an 17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example: 18 | # 19 | # //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} 20 | # 21 | registry=https://registry.npmjs.org/ 22 | always-auth=false 23 | -------------------------------------------------------------------------------- /common/config/rush/.npmrc-publish: -------------------------------------------------------------------------------- 1 | # This config file is very similar to common/config/rush/.npmrc, except that .npmrc-publish 2 | # is used by the "rush publish" command, as publishing often involves different credentials 3 | # and registries than other operations. 4 | # 5 | # Before invoking the package manager, Rush will copy this file to "common/temp/publish-home/.npmrc" 6 | # and then temporarily map that folder as the "home directory" for the current user account. 7 | # This enables the same settings to apply for each project folder that gets published. The copied file 8 | # will omit any config lines that reference environment variables that are undefined in that session; 9 | # this avoids problems that would otherwise result due to a missing variable being replaced by 10 | # an empty string. 11 | # 12 | # * * * SECURITY WARNING * * * 13 | # 14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because 15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely, 16 | # for example if the machine loses power. A safer practice is to pass the token via an 17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example: 18 | # 19 | # 20 | //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} 21 | # 22 | -------------------------------------------------------------------------------- /common/config/rush/.pnpmfile.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * When using the PNPM package manager, you can use pnpmfile.js to workaround 5 | * dependencies that have mistakes in their package.json file. (This feature is 6 | * functionally similar to Yarn's "resolutions".) 7 | * 8 | * For details, see the PNPM documentation: 9 | * https://pnpm.js.org/docs/en/hooks.html 10 | * 11 | * IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY TO INVALIDATE 12 | * ANY CACHED DEPENDENCY ANALYSIS. After any modification to pnpmfile.js, it's recommended to run 13 | * "rush update --full" so that PNPM will recalculate all version selections. 14 | */ 15 | module.exports = { 16 | hooks: { 17 | readPackage 18 | } 19 | }; 20 | 21 | /** 22 | * This hook is invoked during installation before a package's dependencies 23 | * are selected. 24 | * The `packageJson` parameter is the deserialized package.json 25 | * contents for the package that is about to be installed. 26 | * The `context` parameter provides a log() function. 27 | * The return value is the updated object. 28 | */ 29 | function readPackage(packageJson, context) { 30 | 31 | // // The karma types have a missing dependency on typings from the log4js package. 32 | // if (packageJson.name === '@types/karma') { 33 | // context.log('Fixed up dependencies for @types/karma'); 34 | // packageJson.dependencies['log4js'] = '0.6.38'; 35 | // } 36 | 37 | return packageJson; 38 | } 39 | -------------------------------------------------------------------------------- /common/config/rush/repo-state.json: -------------------------------------------------------------------------------- 1 | // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. 2 | { 3 | "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f" 4 | } 5 | -------------------------------------------------------------------------------- /common/config/rush/rush-plugins.json: -------------------------------------------------------------------------------- 1 | /** 2 | * This configuration file manages Rush's plugin feature. 3 | */ 4 | { 5 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-plugins.schema.json", 6 | "plugins": [ 7 | /** 8 | * Each item configures a plugin to be loaded by Rush. 9 | */ 10 | // { 11 | // /** 12 | // * The name of the NPM package that provides the plugin. 13 | // */ 14 | // "packageName": "@scope/my-rush-plugin", 15 | // /** 16 | // * The name of the plugin. This can be found in the "pluginName" 17 | // * field of the "rush-plugin-manifest.json" file in the NPM package folder. 18 | // */ 19 | // "pluginName": "my-plugin-name", 20 | // /** 21 | // * The name of a Rush autoinstaller that will be used for installation, which 22 | // * can be created using "rush init-autoinstaller". Add the plugin's NPM package 23 | // * to the package.json "dependencies" of your autoinstaller, then run 24 | // * "rush update-autoinstaller". 25 | // */ 26 | // "autoinstallerName": "rush-plugins" 27 | // } 28 | ] 29 | } -------------------------------------------------------------------------------- /common/config/rush/version-policies.json: -------------------------------------------------------------------------------- 1 | [{"definitionName":"lockStepVersion","policyName":"vutilMain","version":"1.0.5","nextBump":"patch"}] 2 | -------------------------------------------------------------------------------- /common/git-hooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | node common/scripts/install-run-rush.js commitlint || exit $? #++ -------------------------------------------------------------------------------- /common/git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # log rush change to stdout 4 | STAGE_FILES=$(git diff --cached --name-only) 5 | 6 | if [[ $STAGE_FILES != "" ]] ; then 7 | changedFiles='' 8 | # 红色文本的 ANSI 转义序列 9 | RED='\033[0;31m' 10 | # 大字体的 ANSI 转义序列 11 | BIG_FONT='\033[1m' 12 | 13 | # 重置颜色的 ANSI 转义序列 14 | RESET='\033[0m' 15 | 16 | for fileName in $STAGE_FILES;do 17 | if [[ $fileName =~ ^packages/.*\/src/.* ]]; then 18 | changedFiles="${changedFiles} 19 | ${RED}${fileName}${RESET}" 20 | fi 21 | done; 22 | 23 | if [[ $changedFiles != "" ]] ; then 24 | 25 | echo " 26 | [Notice]: please check, do you need to run ${RED}${BIG_FONT}rush change-all${RESET} to generate changelog, 27 | you has modified some src files, include: 28 | ${changedFiles} 29 | " 30 | fi 31 | fi 32 | 33 | # lint-staged 34 | node common/scripts/install-run-rush.js lint-staged || exit $? #++ 35 | 36 | # local pre-commit 37 | if [ -f "common/scripts/pre-commit" ]; then 38 | common/scripts/pre-commit 39 | fi 40 | -------------------------------------------------------------------------------- /common/git-hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # node common/scripts/install-run-rush.js test --only tag:package 4 | -------------------------------------------------------------------------------- /common/scripts/apply-prerelease-version.js: -------------------------------------------------------------------------------- 1 | const writePrereleaseVersion = require('./set-prerelease-version'); 2 | const checkAndUpdateNextBump = require('./version-policies'); 3 | 4 | 5 | function run() { 6 | const preReleaseName = process.argv.slice(2)[0]; 7 | const nextBump = checkAndUpdateNextBump(process.argv.slice(2)[1]); 8 | 9 | console.log('[apply prerelease version]: ', preReleaseName, nextBump); 10 | 11 | writePrereleaseVersion(nextBump, preReleaseName); 12 | } 13 | 14 | run() -------------------------------------------------------------------------------- /common/scripts/get-package-json.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | function getPackageJson(pkgJsonPath) { 4 | const pkgJson = fs.readFileSync(pkgJsonPath, { encoding: 'utf-8' }) 5 | return JSON.parse(pkgJson); 6 | } 7 | 8 | module.exports = getPackageJson; -------------------------------------------------------------------------------- /common/scripts/install-run-rush-pnpm.js: -------------------------------------------------------------------------------- 1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. 2 | // 3 | // This script is intended for usage in an automated build environment where the Rush command may not have 4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush 5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the 6 | // rush-pnpm command. 7 | // 8 | // An example usage would be: 9 | // 10 | // node common/scripts/install-run-rush-pnpm.js pnpm-command 11 | // 12 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ 13 | 14 | /******/ (() => { // webpackBootstrap 15 | /******/ "use strict"; 16 | var __webpack_exports__ = {}; 17 | /*!*****************************************************!*\ 18 | !*** ./lib-esnext/scripts/install-run-rush-pnpm.js ***! 19 | \*****************************************************/ 20 | 21 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. 22 | // See the @microsoft/rush package's LICENSE file for license information. 23 | require('./install-run-rush'); 24 | //# sourceMappingURL=install-run-rush-pnpm.js.map 25 | module.exports = __webpack_exports__; 26 | /******/ })() 27 | ; 28 | //# sourceMappingURL=install-run-rush-pnpm.js.map -------------------------------------------------------------------------------- /common/scripts/install-run-rushx.js: -------------------------------------------------------------------------------- 1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. 2 | // 3 | // This script is intended for usage in an automated build environment where the Rush command may not have 4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush 5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the 6 | // rushx command. 7 | // 8 | // An example usage would be: 9 | // 10 | // node common/scripts/install-run-rushx.js custom-command 11 | // 12 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ 13 | 14 | /******/ (() => { // webpackBootstrap 15 | /******/ "use strict"; 16 | var __webpack_exports__ = {}; 17 | /*!*************************************************!*\ 18 | !*** ./lib-esnext/scripts/install-run-rushx.js ***! 19 | \*************************************************/ 20 | 21 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. 22 | // See the @microsoft/rush package's LICENSE file for license information. 23 | require('./install-run-rush'); 24 | //# sourceMappingURL=install-run-rushx.js.map 25 | module.exports = __webpack_exports__; 26 | /******/ })() 27 | ; 28 | //# sourceMappingURL=install-run-rushx.js.map -------------------------------------------------------------------------------- /common/scripts/parse-version.js: -------------------------------------------------------------------------------- 1 | // see more about the regex here: https://semver.org/lang/zh-CN/ 2 | // reg test: https://regex101.com/r/vkijKf/1/ 3 | 4 | function parseVersion(version) { 5 | const res = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/gm.exec(version); 6 | 7 | if (res) { 8 | return { 9 | major: +res[1], 10 | minor: +res[2], 11 | patch: +res[3], 12 | preReleaseName: res[4], 13 | preReleaseType: res[4] && res[4].includes('.') ? res[4].split('.')[0] : res[4] 14 | }; 15 | } 16 | 17 | return null; 18 | } 19 | 20 | module.exports = parseVersion -------------------------------------------------------------------------------- /common/scripts/set-json-file.js: -------------------------------------------------------------------------------- 1 | 2 | function setJsonFileByKey(file, json, keys, newValue) { 3 | const prevValue = keys.reduce((res, k) => { 4 | return res[k]; 5 | }, json); 6 | 7 | if (prevValue !== newValue) { 8 | let startIndex = 0; 9 | 10 | keys.forEach(k => { 11 | const keyStr = `"${k}"`; 12 | const index = file.indexOf(keyStr, startIndex); 13 | 14 | if (index >= 0) { 15 | startIndex = index + keyStr.length + 1; 16 | } 17 | }) 18 | 19 | const leftIndex = file.indexOf('"', startIndex); 20 | const rightIndex = leftIndex >= 0 ? file.indexOf('"', leftIndex +1) : -1; 21 | 22 | if (leftIndex >= 0 && rightIndex >= 0) { 23 | return `${file.slice(0, leftIndex)}"${newValue}"${file.slice(rightIndex + 1)}` 24 | } 25 | } 26 | 27 | return file; 28 | } 29 | 30 | module.exports = setJsonFileByKey; -------------------------------------------------------------------------------- /packages/vdataset/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-patch/modern-module-resolution'); 2 | 3 | module.exports = { 4 | extends: ['@internal/eslint-config/profile/lib'], 5 | parserOptions: { tsconfigRootDir: __dirname }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/vdataset/README.md: -------------------------------------------------------------------------------- 1 | # @visactor/VDataSet 2 | 3 | ## Description 4 | 5 | data processing module 6 | 7 | ## Usage 8 | 9 | ```typescript 10 | import { xxx } from '@visactor/v-data-set'; 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/vdataset/README.zh-CN.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisActor/VUtil/8eb0060fa055c6e2e8a7e41f719428eeffb365eb/packages/vdataset/README.zh-CN.md -------------------------------------------------------------------------------- /packages/vdataset/__tests__/parser-clone.test.ts: -------------------------------------------------------------------------------- 1 | import { DataSet, DataView, csvParser } from '../src'; 2 | 3 | describe('parser', () => { 4 | it('clone in parserOption', () => { 5 | const dataSet = new DataSet(); 6 | dataSet.registerParser('csv', csvParser); 7 | const dataView = new DataView(dataSet, { 8 | name: 'test' 9 | }); 10 | const data = [ 11 | { x: 1, y: 2, c: { d: 3, e: 4 } }, 12 | { x: 11, y: 12, c: { d: 13, e: 14 } }, 13 | { x: 21, y: 22, c: { d: 23, e: 24 } } 14 | ]; 15 | dataView.parse(data, { 16 | type: null, 17 | clone: true 18 | }); 19 | expect(dataView.latestData.length).toEqual(3); 20 | expect(dataView.latestData).toEqual(data); 21 | expect(dataView.latestData[0] === data[0]).toEqual(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/vdataset/__tests__/parser-csv.test.ts: -------------------------------------------------------------------------------- 1 | import { DataSet, DataView, csvParser } from '../src'; 2 | 3 | describe('parser', () => { 4 | it('csv-parser', () => { 5 | const dataSet = new DataSet(); 6 | dataSet.registerParser('csv', csvParser); 7 | const dataView = new DataView(dataSet, { name: 'test' }); 8 | const csvData = `sex,height,weight 9 | 女性,150,45.5 10 | 女性,152,52.5 11 | 女性,154.3,53.5 12 | 女性,161.6,53.5`; 13 | dataView.parse(csvData, { 14 | type: 'csv' 15 | }); 16 | expect(dataView.latestData.length).toEqual(4); 17 | expect(dataView.latestData[0]).toEqual({ sex: '女性', height: '150', weight: '45.5' }); 18 | expect(dataView.latestData[1]).toEqual({ sex: '女性', height: '152', weight: '52.5' }); 19 | expect(dataView.latestData[2]).toEqual({ sex: '女性', height: '154.3', weight: '53.5' }); 20 | expect(dataView.latestData[3]).toEqual({ sex: '女性', height: '161.6', weight: '53.5' }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/vdataset/bundler.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {Partial} 3 | */ 4 | module.exports = { 5 | formats: ["cjs", "es", "umd"], 6 | name: "VDataset", 7 | umdOutputFilename: 'index' 8 | }; 9 | -------------------------------------------------------------------------------- /packages/vdataset/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const baseJestConfig = require('@internal/jest-config/jest.base'); 3 | 4 | module.exports = { 5 | ...baseJestConfig, 6 | moduleNameMapper: { 7 | ...baseJestConfig.moduleNameMapper, 8 | 'd3-geo': path.resolve(__dirname, './node_modules/d3-geo/dist/d3-geo.min.js'), 9 | 'd3-dsv': path.resolve(__dirname, './node_modules/d3-dsv/dist/d3-dsv.min.js'), 10 | 'd3-hexbin': path.resolve(__dirname, './node_modules/d3-hexbin/build/d3-hexbin.min.js'), 11 | 'd3-hierarchy': path.resolve(__dirname, './node_modules/d3-hierarchy/dist/d3-hierarchy.min.js'), 12 | '@visactor/vutils': path.resolve(__dirname, '../vutils/src/index.ts') 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /packages/vdataset/src/constants.ts: -------------------------------------------------------------------------------- 1 | export enum DATAVIEW_TYPE { 2 | DSV = 'dsv', 3 | TREE = 'tree', 4 | GEO = 'geo', 5 | BYTE = 'bytejson', 6 | HEX = 'hex', 7 | GRAPH = 'graph', 8 | TABLE = 'table', 9 | GEO_GRATICULE = 'geo-graticule' // 网格地球 10 | } 11 | 12 | export const STATISTICS_METHODS = [ 13 | 'max', 14 | 'mean', // alias: average 15 | 'median', 16 | 'min', 17 | 'mode', 18 | 'product', 19 | 'standardDeviation', 20 | 'sum', 21 | 'sumSimple', 22 | 'variance' 23 | ]; 24 | -------------------------------------------------------------------------------- /packages/vdataset/src/moudles.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'point-at-length'; 2 | 3 | declare module 'geojson-dissolve'; 4 | 5 | declare module 'simplify-geojson'; 6 | -------------------------------------------------------------------------------- /packages/vdataset/src/parser/bytejson.ts: -------------------------------------------------------------------------------- 1 | import type { DataView } from '../data-view'; 2 | import { DATAVIEW_TYPE } from '../constants'; 3 | 4 | export const byteJSONParser = (data: any[], options: any, dataView: DataView) => { 5 | dataView.type = DATAVIEW_TYPE.BYTE; 6 | const result: any[] = []; 7 | const { layerType } = options; 8 | data.forEach((item: any) => { 9 | let lType = layerType; 10 | let coord = []; 11 | // 飞线 12 | if (item.from) { 13 | lType = 'FlyLine'; 14 | coord = [item.from, item.to]; 15 | } 16 | 17 | // 点 18 | if (item.lng) { 19 | lType = 'Point'; 20 | coord = [item.lng, item.lat]; 21 | } 22 | 23 | if (item.coordinates) { 24 | // 通过嵌套层级判断 MultiLine/Line 25 | const suffix = lType === 'Line' ? 'LineString' : 'Polygon'; 26 | lType = Array.isArray(item.coordinates[0][0]) ? `Multi${suffix}` : suffix; 27 | coord = item.coordinates; 28 | } 29 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 30 | const { coordinates, ...others } = item; 31 | const dataItem = { 32 | ...others, 33 | geometry: { 34 | type: lType, 35 | coordinates: coord 36 | } 37 | }; 38 | result.push(dataItem); 39 | }); 40 | return result; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/vdataset/src/parser/data-view.ts: -------------------------------------------------------------------------------- 1 | import type { DataView } from '../data-view'; 2 | import type { Parser } from '.'; 3 | import { isBoolean, isArray } from '@visactor/vutils'; 4 | 5 | export interface IDataViewParserOptions { 6 | dependencyUpdate?: boolean; // 是否依赖更新 7 | } 8 | 9 | /** 10 | * dataView数据 解析器 11 | * @param data 12 | * @param options 13 | * @param dataView 14 | * @returns 15 | */ 16 | export const dataViewParser: Parser = (data: DataView[], options: IDataViewParserOptions, dataView: DataView) => { 17 | const dependencyUpdate = isBoolean(options?.dependencyUpdate) ? options?.dependencyUpdate : true; 18 | 19 | if (!data || !isArray(data)) { 20 | throw new TypeError('Invalid data: must be DataView array!'); 21 | } 22 | if (isArray(dataView.rawData)) { 23 | (dataView.rawData).forEach(rd => { 24 | if (rd.target) { 25 | rd.target.removeListener('change', dataView.reRunAllTransform); 26 | rd.target.removeListener('markRunning', dataView.markRunning); 27 | } 28 | }); 29 | } 30 | 31 | if (dependencyUpdate) { 32 | data.forEach(d => { 33 | d.target.addListener('change', dataView.reRunAllTransform); 34 | d.target.addListener('markRunning', dataView.markRunning); 35 | }); 36 | } 37 | return data; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/vdataset/src/parser/geo/geobuf.ts: -------------------------------------------------------------------------------- 1 | import * as geobuf from 'geobuf'; 2 | import Pbf from 'pbf'; 3 | import { DATAVIEW_TYPE } from '../../constants'; 4 | import type { DataView } from '../../data-view'; 5 | import { mergeDeepImmer } from '../../utils/js'; 6 | import type { Parser } from '..'; 7 | import type { IGeoJSONOptions } from './geojson'; 8 | import { DEFAULT_GEOJSON_OPTIONS, geoJSONParser } from './geojson'; 9 | 10 | export type IGeoBufOptions = IGeoJSONOptions; 11 | 12 | const DEFAULT_GEOBUF_OPTIONS = {}; 13 | /** 14 | * geobuf pbf 解码为 geojson 15 | * @param data 16 | * @returns 17 | */ 18 | export const geoBufParser: Parser = (data: ArrayBuffer, options: IGeoBufOptions = {}, dataView: DataView) => { 19 | dataView.type = DATAVIEW_TYPE.GEO; 20 | const mergeOptions = mergeDeepImmer(DEFAULT_GEOJSON_OPTIONS, DEFAULT_GEOBUF_OPTIONS, options); 21 | const geoData = geobuf.decode(new Pbf(data)); // 对GeoBuf解码 22 | return geoJSONParser(geoData, mergeOptions, dataView); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/vdataset/src/parser/geo/geotree.ts: -------------------------------------------------------------------------------- 1 | // import getPointAtLength from 'point-at-length'; 2 | // import { geoPath } from 'd3-geo'; 3 | import { cloneDeep } from '@visactor/vutils'; 4 | import { DATAVIEW_TYPE } from '../../constants'; 5 | import type { DataView } from '../../data-view'; 6 | import type { Parser } from '..'; 7 | 8 | // const geoPathGenerator = geoPath(); 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 11 | export interface IGeoTreeOptions { 12 | // nothing 13 | } 14 | 15 | type TreeID = string; 16 | 17 | type GeoTree = { 18 | parent: TreeID; 19 | treeID: TreeID; 20 | name: string; 21 | treeName: string; 22 | payload: any; 23 | children: any; 24 | version: any; 25 | }; 26 | 27 | // parent null 28 | // treeID "1" 29 | // name "江浙沪包邮区" 30 | // treeName "江浙沪包邮区_1" 31 | // payload {…} 32 | // children {…} 33 | // version "1.0.0" 34 | 35 | /** 36 | * WIP: 解析GeoTree 37 | * @param data 38 | * @param _options 39 | * @param dataView 40 | * @returns 41 | */ 42 | export const GeoTreeParser: Parser = (data: GeoTree, options: IGeoTreeOptions = {}, dataView: DataView) => { 43 | dataView.type = DATAVIEW_TYPE.GEO; 44 | return cloneDeep(data); 45 | }; 46 | -------------------------------------------------------------------------------- /packages/vdataset/src/parser/geo/topojson.ts: -------------------------------------------------------------------------------- 1 | import { feature } from 'topojson-client'; 2 | import type { Topology } from 'topojson-specification'; 3 | import { isString } from '@visactor/vutils'; 4 | import { DATAVIEW_TYPE } from '../../constants'; 5 | import type { DataView } from '../../data-view'; 6 | import type { Parser } from '..'; 7 | import { mergeDeepImmer } from '../../utils/js'; 8 | import type { IGeoJSONOptions } from './geojson'; 9 | import { DEFAULT_GEOJSON_OPTIONS, geoJSONParser } from './geojson'; 10 | 11 | export interface ITopoJsonParserOptions extends IGeoJSONOptions { 12 | object: string; // TopoJSON 相当于多个 GeoJSON 合并起来做了压缩,其中每一个 object 都相当于一份 GeoJSON 数据,指定 object 就是从中提取一份 Geo 数据 13 | } 14 | 15 | const DEFAULT_TOPOJSON_OPTIONS = {}; 16 | /**s 17 | * topojson 数据解析 18 | * @param data 19 | * @param options 20 | * @param dataView 21 | * @returns 22 | */ 23 | export const topoJSONParser: Parser = (data: Topology, options: ITopoJsonParserOptions, dataView: DataView) => { 24 | dataView.type = DATAVIEW_TYPE.GEO; 25 | const mergeOptions = mergeDeepImmer(DEFAULT_GEOJSON_OPTIONS, DEFAULT_TOPOJSON_OPTIONS, options); 26 | const { object } = mergeOptions; 27 | if (!isString(object)) { 28 | throw new TypeError('Invalid object: must be a string!'); 29 | } 30 | const geoData = feature(data, data.objects[object]); 31 | return geoJSONParser(geoData, mergeOptions, dataView); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/vdataset/src/parser/index.ts: -------------------------------------------------------------------------------- 1 | import type { DataView } from '../data-view'; 2 | 3 | /** 4 | * Parser 类型 5 | */ 6 | export type Parser = (data: any, options: any, dataView: DataView) => any; 7 | 8 | type ParserName = string; 9 | 10 | export interface IParserOptions { 11 | type: ParserName; 12 | clone?: boolean; 13 | options?: any; 14 | layerType?: string; 15 | } 16 | -------------------------------------------------------------------------------- /packages/vdataset/src/parser/tree.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '@visactor/vutils'; 2 | import { hierarchy } from 'd3-hierarchy'; 3 | import type { DataView } from '../data-view'; 4 | import { DATAVIEW_TYPE } from '../constants'; 5 | import { mergeDeepImmer } from '../utils/js'; 6 | import type { Parser } from '.'; 7 | 8 | export interface ITreeParserOptions { 9 | children?: (data: any) => any[]; // 指定的 children 访问器会为每个数据进行调用,从根 data 开始,并且必须返回一个数组用以表示当前数据的子节点,返回 null 表示当前数据没有子节点。如果没有指定 children 则默认为: d => d.children 10 | pureData?: boolean; 11 | } 12 | 13 | const DEFAULT_TREE_PARSER_OPTIONS: ITreeParserOptions = { 14 | children: d => d.children, 15 | pureData: false 16 | }; 17 | /** 18 | * 树形结构数据 解析器 19 | * @param data 20 | * @param options 21 | * @param dataView 22 | * @returns 23 | */ 24 | export const treeParser: Parser = (data: any, options: ITreeParserOptions, dataView: DataView) => { 25 | dataView.type = DATAVIEW_TYPE.TREE; 26 | const mergeOptions = mergeDeepImmer(DEFAULT_TREE_PARSER_OPTIONS, options); 27 | const { children } = mergeOptions; 28 | 29 | if (children && !isFunction(children)) { 30 | throw new TypeError('Invalid children: must be a function!'); 31 | } 32 | 33 | return hierarchy(data, children); 34 | // // if (!options.pureData) { 35 | // // // @ts-ignore 36 | // // dataView.rows = dataView.root = hierarchy(data, children); 37 | // // } else { 38 | // // dataView.rows = dataView.root = data; 39 | // // } 40 | // return data; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/filter.ts: -------------------------------------------------------------------------------- 1 | import type { Transform } from '.'; 2 | 3 | export interface IFilterOptions { 4 | callback?: (item: any) => boolean; 5 | } 6 | 7 | /** 8 | * 数据过滤 9 | * @param data 10 | * @param options 11 | * @returns 12 | */ 13 | export const filter: Transform = (data: Array, options?: IFilterOptions) => { 14 | const { callback } = options; 15 | if (callback) { 16 | data = data.filter(callback); 17 | } 18 | return data; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/fold.ts: -------------------------------------------------------------------------------- 1 | import type { Transform } from '.'; 2 | 3 | export interface IFoldOptions { 4 | fields: string[]; // 展开字段集 5 | key: string; // key字段 6 | value: string; // value字段 7 | retains?: string[]; // 保留字段集,默认为除 fields 以外的所有字段 8 | } 9 | 10 | /** 11 | * 12 | * @param data 数据展开 13 | * @param options 14 | * @returns 15 | */ 16 | export const fold: Transform = (data: Array, options?: IFoldOptions) => { 17 | const { fields, key, value, retains } = options; 18 | const results: any[] = []; 19 | for (let i = 0; i < data.length; i++) { 20 | fields.forEach(field => { 21 | const item = {}; 22 | item[key] = field; 23 | item[value] = data[i][field]; 24 | if (retains) { 25 | retains.forEach(retain => { 26 | item[retain] = data[i][retain]; 27 | }); 28 | } else { 29 | for (const prop in data[i]) { 30 | if (fields.indexOf(prop) === -1) { 31 | item[prop] = data[i][prop]; 32 | } 33 | } 34 | } 35 | 36 | results.push(item); 37 | }); 38 | } 39 | return results; 40 | }; 41 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/geo/dissolve.ts: -------------------------------------------------------------------------------- 1 | import type { FeatureCollection, MultiPolygon, Polygon } from '@turf/helpers'; 2 | import geoDissolve from 'geojson-dissolve'; 3 | import type { Transform } from '..'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 6 | export interface IDissolveOptions {} 7 | 8 | /** 9 | * 计算边界 10 | * @param data 11 | * @param options 12 | * @returns Polygon | MultiPolygon 13 | */ 14 | export const dissolve: Transform = (data: FeatureCollection, options?: IDissolveOptions): Polygon | MultiPolygon => { 15 | return geoDissolve(data); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/geo/mercator.ts: -------------------------------------------------------------------------------- 1 | import { project } from '../../utils/geo'; 2 | import type { Transform } from '..'; 3 | 4 | export const mercator: Transform = (data: Array, options?: any) => { 5 | const points: Array = []; 6 | data.forEach(item => { 7 | const [x, y] = project([item.lng, item.lat]); 8 | points.push({ 9 | ...item, 10 | coordinates: [x, y] 11 | }); 12 | }); 13 | 14 | return points; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/geo/projection.ts: -------------------------------------------------------------------------------- 1 | import { project } from '../../utils/geo'; 2 | import type { Transform } from '..'; 3 | 4 | const PROJECTION_GROUP = { 5 | webmercator: project 6 | }; 7 | 8 | export interface IProjectionOptions { 9 | projection: string; 10 | as: string; 11 | } 12 | 13 | export const projection: Transform = (data: any, options?: IProjectionOptions) => { 14 | if (!data || data.length === 0) { 15 | return data; 16 | } 17 | const { projection, as } = options; 18 | const prjFunc = (PROJECTION_GROUP as any)[projection]; 19 | if (data[0].lng) { 20 | const processData = data.map((item: { lng: any; lat: any }) => { 21 | return { 22 | ...item, 23 | [as]: prjFunc([item.lng, item.lat]) 24 | }; 25 | }); 26 | return processData; 27 | } 28 | const result = data.map((ele: any) => { 29 | const { coordinates } = ele.geometry || {}; 30 | if (!Array.isArray(coordinates[0])) { 31 | const processData = prjFunc(coordinates); 32 | return { 33 | ...ele, 34 | [as]: processData 35 | }; 36 | } 37 | const processData = coordinates.map((item: Array) => { 38 | return Array.isArray(item[0]) ? item.map((coord: number[]) => prjFunc(coord)) : prjFunc(item); 39 | }); 40 | return { 41 | ...ele, 42 | [as]: processData, 43 | geometry: { 44 | ...ele.geometry, 45 | [as]: processData 46 | } 47 | }; 48 | }); 49 | return result; 50 | }; 51 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/geo/simplify.ts: -------------------------------------------------------------------------------- 1 | import geoSimplify from 'simplify-geojson'; 2 | import type { FeatureCollection } from '@turf/helpers'; 3 | import { mergeDeepImmer } from '../../utils/js'; 4 | import type { Transform } from '..'; 5 | 6 | export interface ISimplifyOptions { 7 | tolerance?: number; // 简化容差,默认0.01 8 | } 9 | 10 | const DEFAULT_SIMPLIFY_OPTIONS: ISimplifyOptions = { 11 | tolerance: 0.01 12 | }; 13 | /** 14 | * 简化 15 | * @param data 16 | * @param options 17 | * @returns 18 | */ 19 | export const simplify: Transform = (data: FeatureCollection, options?: ISimplifyOptions): FeatureCollection => { 20 | const mergeOptions = mergeDeepImmer(DEFAULT_SIMPLIFY_OPTIONS, options); 21 | const { tolerance } = mergeOptions; 22 | return geoSimplify(data, tolerance); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/index.ts: -------------------------------------------------------------------------------- 1 | export type Transform = (data: any, options?: any) => any; 2 | 3 | type TransformName = string; 4 | export interface ITransformOptions { 5 | type: TransformName; 6 | options?: any; 7 | level?: number; 8 | } 9 | -------------------------------------------------------------------------------- /packages/vdataset/src/transform/map.ts: -------------------------------------------------------------------------------- 1 | import type { Transform } from '.'; 2 | 3 | export interface IMapOptions { 4 | callback?: (item: any, index: number, arr: any[]) => any; 5 | } 6 | 7 | /** 8 | * 9 | * @param data 数据加工 10 | * @param options 11 | * @returns 12 | */ 13 | export const map: Transform = (data: Array, options?: IMapOptions) => { 14 | const { callback } = options; 15 | if (callback) { 16 | data = data.map(callback); 17 | } 18 | return data; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/vdataset/src/utils/csv.ts: -------------------------------------------------------------------------------- 1 | export function readCSVTopNLine(csvFile: string, n: number): string { 2 | let res = ''; 3 | //可能的分隔符:\r,\n,\r\n 4 | const finish = ['\r\n', '\r', '\n'].some(splitter => { 5 | if (csvFile.includes(splitter)) { 6 | res = csvFile 7 | .split(splitter) 8 | .slice(0, n + 1) 9 | .join(splitter); 10 | return true; 11 | } 12 | return false; 13 | }); 14 | if (finish) { 15 | return res; 16 | } 17 | return csvFile; 18 | } 19 | -------------------------------------------------------------------------------- /packages/vdataset/src/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成唯一ID。 如果提供了 prefix ,会被添加到ID前缀上。 3 | * @param prefix 默认 dataset 4 | * @returns 5 | */ 6 | let idIndex: number = 0; 7 | const maxId = 100000000; 8 | export function getUUID(prefix: string = 'dataset'): string { 9 | if (idIndex > maxId) { 10 | idIndex = 0; 11 | } 12 | return prefix + '_' + idIndex++; 13 | } 14 | -------------------------------------------------------------------------------- /packages/vdataset/tscofig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"], 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": "./", 7 | "rootDir": "./" 8 | }, 9 | "include": ["src", "__tests__", "examples"], 10 | "exclude": ["bugserver-config"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/vdataset/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "rootDir": "./src", 6 | "outDir": "./es", 7 | "esModuleInterop": true, 8 | "composite": true 9 | }, 10 | "ts-node": { 11 | "transpileOnly": true, 12 | "compilerOptions": { 13 | "module": "commonjs" 14 | } 15 | }, 16 | "references": [ 17 | { 18 | "path": "../vutils" 19 | } 20 | ], 21 | "include": ["src", "__tests__", "examples"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/vdataset/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@visactor/vutils": ["../vutils/src"] 6 | } 7 | }, 8 | "references": [] 9 | } 10 | -------------------------------------------------------------------------------- /packages/vlayouts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-patch/modern-module-resolution'); 2 | 3 | module.exports = { 4 | extends: ['@internal/eslint-config/profile/lib'], 5 | parserOptions: { tsconfigRootDir: __dirname }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/vlayouts/CHANGELOG.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@visactor/vlayouts", 3 | "entries": [ 4 | { 5 | "version": "1.0.5", 6 | "tag": "@visactor/vlayouts_v1.0.5", 7 | "date": "Mon, 12 May 2025 02:42:28 GMT", 8 | "comments": {} 9 | }, 10 | { 11 | "version": "1.0.4", 12 | "tag": "@visactor/vlayouts_v1.0.4", 13 | "date": "Fri, 09 May 2025 10:41:56 GMT", 14 | "comments": {} 15 | }, 16 | { 17 | "version": "1.0.3", 18 | "tag": "@visactor/vlayouts_v1.0.3", 19 | "date": "Fri, 09 May 2025 07:25:42 GMT", 20 | "comments": { 21 | "none": [ 22 | { 23 | "comment": "fix: export more types for imagecloud" 24 | } 25 | ] 26 | } 27 | }, 28 | { 29 | "version": "1.0.2", 30 | "tag": "@visactor/vlayouts_v1.0.2", 31 | "date": "Wed, 07 May 2025 06:07:55 GMT", 32 | "comments": { 33 | "none": [ 34 | { 35 | "comment": "fix: fix error of projection\n\n" 36 | } 37 | ] 38 | } 39 | }, 40 | { 41 | "version": "1.0.1", 42 | "tag": "@visactor/vlayouts_v1.0.1", 43 | "date": "Tue, 06 May 2025 11:04:38 GMT", 44 | "comments": {} 45 | }, 46 | { 47 | "version": "0.19.6", 48 | "tag": "@visactor/vlayouts_v0.19.6", 49 | "date": "Mon, 28 Apr 2025 09:10:49 GMT", 50 | "comments": {} 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /packages/vlayouts/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log - @visactor/vlayouts 2 | 3 | This log was last generated on Mon, 12 May 2025 02:42:28 GMT and should not be manually modified. 4 | 5 | ## 1.0.5 6 | Mon, 12 May 2025 02:42:28 GMT 7 | 8 | _Version update only_ 9 | 10 | ## 1.0.4 11 | Fri, 09 May 2025 10:41:56 GMT 12 | 13 | _Version update only_ 14 | 15 | ## 1.0.3 16 | Fri, 09 May 2025 07:25:42 GMT 17 | 18 | ### Updates 19 | 20 | - fix: export more types for imagecloud 21 | 22 | ## 1.0.2 23 | Wed, 07 May 2025 06:07:55 GMT 24 | 25 | ### Updates 26 | 27 | - fix: fix error of projection 28 | 29 | 30 | 31 | ## 1.0.1 32 | Tue, 06 May 2025 11:04:38 GMT 33 | 34 | _Version update only_ 35 | 36 | ## 0.19.6 37 | Mon, 28 Apr 2025 09:10:49 GMT 38 | 39 | _Initial release_ 40 | 41 | -------------------------------------------------------------------------------- /packages/vlayouts/README.md: -------------------------------------------------------------------------------- 1 | # @visactor/vutils 2 | 3 | ## Installation 4 | 5 | [npm package](https://www.npmjs.com/package/@visactor/vutils) 6 | 7 | ```bash 8 | // npm 9 | npm install @visactor/vutils 10 | 11 | // yarn 12 | yarn add @visactor/vutils 13 | ``` 14 | 15 | ## 16 | 17 | [More demos and detailed tutorials](https://visactor.io/vutil) 18 | 19 | # Related Links 20 | 21 | - [VGrammar](https://github.com/VisActor/VGrammar) 22 | - [VChart](https://visactor.io/vchart) 23 | 24 | # Contribution 25 | 26 | If you would like to contribute, please read the [Code of Conduct ](./CODE_OF_CONDUCT.md) 和 [ Guide](./CONTRIBUTING.zh-CN.md) first。 27 | 28 | Small streams converge to make great rivers and seas! 29 | 30 | 31 | 32 | # License 33 | 34 | [MIT License](./LICENSE) 35 | -------------------------------------------------------------------------------- /packages/vlayouts/README.zh-CN.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisActor/VUtil/8eb0060fa055c6e2e8a7e41f719428eeffb365eb/packages/vlayouts/README.zh-CN.md -------------------------------------------------------------------------------- /packages/vlayouts/__tests__/imagecloud/imagecloud.test.ts: -------------------------------------------------------------------------------- 1 | import { imagecloudTransform } from '../../src'; 2 | import { getMockCreateCanvas, getMockCreateImage } from '../mock'; 3 | 4 | test('imagecloud should indicate when size is invalid', async () => { 5 | const data = [{ image: 'https://visactor.io/logo.png' }]; 6 | const result = imagecloudTransform( 7 | { 8 | size: [0, 0], 9 | image: { field: 'image' }, 10 | createCanvas: getMockCreateCanvas(), 11 | createImage: getMockCreateImage() 12 | }, 13 | data 14 | ); 15 | expect(result).toEqual([]); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/vlayouts/__tests__/mock.ts: -------------------------------------------------------------------------------- 1 | export const getMockCreateCanvas = () => { 2 | return ({ width, height }: { width?: number; height?: number; dpr?: number }) => { 3 | const canvas = document.createElement('canvas'); 4 | 5 | if (width) { 6 | canvas.style.width = `${width}px`; 7 | canvas.width = width; 8 | } 9 | if (height) { 10 | canvas.style.height = `${height}px`; 11 | canvas.height = height; 12 | } 13 | 14 | return canvas; 15 | }; 16 | }; 17 | 18 | export const getMockCreateImage = () => { 19 | return () => { 20 | // 21 | }; 22 | }; 23 | 24 | export const getMockGetTextBounds = () => { 25 | return (attrs: { text?: string; x?: number; y?: number; fontSize?: number }) => { 26 | const { x = 0, y = 0, text = '', fontSize = 12 } = attrs; 27 | const width = fontSize * text.length * 0.5; 28 | 29 | return { 30 | x1: x, 31 | x2: x + width, 32 | y1: y, 33 | y2: y + fontSize, 34 | width: () => width, 35 | height: () => fontSize 36 | }; 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/vlayouts/__tests__/wordcloud/util.test.ts: -------------------------------------------------------------------------------- 1 | import { getMinFontSizeOfEnv, randomHslColor, functor } from '../../src/wordcloud/util'; 2 | 3 | test('getMinFontSizeOfEnv()', () => { 4 | expect(getMinFontSizeOfEnv()).toBe(12); 5 | }); 6 | 7 | test('randomHslColor()', () => { 8 | expect(randomHslColor(0, 100).indexOf('hsl(')).toBe(0); 9 | }); 10 | 11 | test('functor()', () => { 12 | const a = () => 0; 13 | expect(functor(a)).toBe(a); 14 | expect(typeof functor('a')).toBe('function'); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/vlayouts/bundler.config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @type {Partial} 4 | */ 5 | module.exports = { 6 | formats: ["cjs", "es", "umd"], 7 | name: "VUtils", 8 | umdOutputFilename: 'index' 9 | }; 10 | -------------------------------------------------------------------------------- /packages/vlayouts/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const baseJestConfig = require('@internal/jest-config/jest.base'); 3 | 4 | module.exports = { 5 | ...baseJestConfig, 6 | testEnvironment: 'jsdom', 7 | moduleNameMapper: { 8 | ...baseJestConfig.moduleNameMapper, 9 | '@visactor/vutils': path.resolve(__dirname, '../vutils/src/index.ts'), 10 | '@visactor/vscale': path.resolve(__dirname, '../vscale/src/index.ts') 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /packages/vlayouts/src/circle-packing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interface'; 2 | export * from './layout'; 3 | export { transform as circlePackingTransform } from './transform'; 4 | -------------------------------------------------------------------------------- /packages/vlayouts/src/circle-packing/interface.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | HierarchicalDatum, 3 | HierarchicalNodeElement, 4 | HierarchyLabelAttrs, 5 | ViewBoxOptions 6 | } from '../interface/common'; 7 | 8 | export interface ICircle { 9 | x?: number; 10 | y?: number; 11 | radius?: number; 12 | } 13 | 14 | export interface CirclePackingOptions { 15 | nodeSort?: boolean | ((a: CirclePackingNodeElement, b: CirclePackingNodeElement) => number); 16 | padding?: number | number[]; 17 | /** 18 | * set the radius of node, if not specified, we'll set `Math.sqrt(node.value);`. 19 | */ 20 | setRadius?: (datum: CirclePackingNodeElement) => number; 21 | /** parse the key of node */ 22 | nodeKey?: string | number | ((datum: HierarchicalDatum) => string | number); 23 | includeRoot?: boolean; 24 | } 25 | 26 | export type CirclePackingTramsformOptions = CirclePackingOptions & 27 | ViewBoxOptions & { flatten?: boolean; maxDepth?: number }; 28 | 29 | /** 30 | * The node element after sunburst layout 31 | */ 32 | export interface CirclePackingNodeElement extends HierarchicalNodeElement, ICircle { 33 | children?: CirclePackingNodeElement[]; 34 | label?: HierarchyLabelAttrs; 35 | } 36 | -------------------------------------------------------------------------------- /packages/vlayouts/src/circle-packing/transform.ts: -------------------------------------------------------------------------------- 1 | import type { CirclePackingNodeElement, CirclePackingTramsformOptions } from './interface'; 2 | import { flattenNodes } from '../utils/hierarchy'; 3 | import { CirclePackingLayout } from './layout'; 4 | import type { HierarchicalData } from '../interface/common'; 5 | 6 | export const transform = (options: CirclePackingTramsformOptions, upstreamData: HierarchicalData) => { 7 | const layout = new CirclePackingLayout(options); 8 | 9 | const res = layout.layout( 10 | upstreamData, 11 | 'width' in options 12 | ? { 13 | width: options.width, 14 | height: options.height 15 | } 16 | : { 17 | x0: options.x0, 18 | x1: options.x1, 19 | y0: options.y0, 20 | y1: options.y1 21 | } 22 | ); 23 | 24 | if (options.flatten) { 25 | const nodes: CirclePackingNodeElement[] = []; 26 | flattenNodes(res, nodes, { maxDepth: options?.maxDepth }); 27 | 28 | return nodes; 29 | } 30 | 31 | return res; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/vlayouts/src/imagecloud/index.ts: -------------------------------------------------------------------------------- 1 | export { transform as imagecloudTransform } from './imagecloud'; 2 | export { GridLayout as ImageCloudGridLayout } from './layout/grid/grid'; 3 | export { StackLayout as ImageCloudStackLayout } from './layout/stack'; 4 | export { SpiralLayout as ImageCloudSpiralLayout } from './layout/spiral'; 5 | export * from './interface'; 6 | -------------------------------------------------------------------------------- /packages/vlayouts/src/imagecloud/util.ts: -------------------------------------------------------------------------------- 1 | import type { ImageCollageType } from './interface'; 2 | 3 | export enum IMAGECLOUD_HOOK_EVENT { 4 | BEFORE_IMAGECLOUD_LAYOUT = 'beforeImagecloudLayout', 5 | AFTER_IMAGECLOUD_LAYOUT = 'afterImagecloudLayout', 6 | AFTER_IMAGECLOUD_DRAW = 'afterImagecloudDraw' 7 | } 8 | 9 | export function setSize(image: ImageCollageType, longSideLength: number) { 10 | if (image.aspectRatio > 1) { 11 | image.width = longSideLength; 12 | image.height = ~~(longSideLength / image.aspectRatio); 13 | } else { 14 | image.height = longSideLength; 15 | image.width = ~~(longSideLength * image.aspectRatio); 16 | } 17 | } 18 | 19 | export function setSizeByShortSide(image: ImageCollageType, shortSideLength: number) { 20 | if (image.aspectRatio > 1) { 21 | image.height = shortSideLength; 22 | image.width = ~~(shortSideLength * image.aspectRatio); 23 | } else { 24 | image.width = shortSideLength; 25 | image.height = ~~(shortSideLength / image.aspectRatio); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/vlayouts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './circle-packing'; 2 | export * from './sankey'; 3 | export * from './sunburst'; 4 | export * from './tree'; 5 | export * from './treemap'; 6 | export * from './venn'; 7 | export * from './wordcloud'; 8 | export * from './wordcloud-shape'; 9 | export * from './imagecloud'; 10 | 11 | export * from './interface/common'; 12 | export * from './interface/wordcloud'; 13 | 14 | export * from './utils/hierarchy'; 15 | export * from './utils/image'; 16 | export * from './utils/loader'; 17 | export * from './utils/shapes'; 18 | export * from './utils/spirals'; 19 | -------------------------------------------------------------------------------- /packages/vlayouts/src/interface/common.ts: -------------------------------------------------------------------------------- 1 | export type ViewBoxOptions = { width: number; height: number } | { x0: number; x1: number; y0: number; y1: number }; 2 | 3 | export interface HierarchicalDatum { 4 | value?: number; 5 | children?: HierarchicalDatum[]; 6 | } 7 | 8 | export type HierarchicalData = HierarchicalDatum[]; 9 | 10 | export interface HierarchicalNodeElement { 11 | key: string; 12 | parentKey?: string; 13 | flattenIndex: number; 14 | index: number; 15 | /** the depth of node, from source to target */ 16 | depth: number; 17 | maxDepth: number; 18 | /** whether or not this node is a leaf node */ 19 | isLeaf?: boolean; 20 | value: number; 21 | datum: Datum[]; 22 | 23 | children?: HierarchicalNodeElement[]; 24 | } 25 | 26 | export type TextAlignType = 'left' | 'right' | 'center' | 'start' | 'end'; 27 | export type TextBaselineType = 'top' | 'middle' | 'bottom' | 'alphabetic'; 28 | 29 | export interface HierarchyLabelAttrs { 30 | x?: number; 31 | y?: number; 32 | text?: string; 33 | textAlign?: TextAlignType; 34 | textBaseline?: TextBaselineType; 35 | angle?: number; 36 | maxLineWidth?: number; 37 | } 38 | -------------------------------------------------------------------------------- /packages/vlayouts/src/sankey/hierarchy.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from '@visactor/vutils'; 2 | import type { HierarchyNodeDatum } from './interface'; 3 | 4 | export const calculateNodeValue = (subTree: HierarchyNodeDatum[]) => { 5 | let sum = 0; 6 | subTree.forEach((node, index) => { 7 | if (isNil(node.value)) { 8 | if (node.children?.length) { 9 | node.value = calculateNodeValue(node.children); 10 | } else { 11 | node.value = 0; 12 | } 13 | } 14 | 15 | sum += Math.abs(node.value); 16 | }); 17 | 18 | return sum; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/vlayouts/src/sankey/index.ts: -------------------------------------------------------------------------------- 1 | import { transform } from './transform'; 2 | 3 | export { SankeyLayout } from './layout'; 4 | export const sankeyTransform = transform; 5 | 6 | export * from './interface'; 7 | export * from './format'; 8 | -------------------------------------------------------------------------------- /packages/vlayouts/src/sankey/transform.ts: -------------------------------------------------------------------------------- 1 | import type { SankeyData, SankeyOptions } from './interface'; 2 | import { SankeyLayout } from './layout'; 3 | 4 | export const transform = ( 5 | options: SankeyOptions & ({ width: number; height: number } | { x0: number; x1: number; y0: number; y1: number }), 6 | upstreamData: SankeyData | SankeyData[] 7 | ) => { 8 | const layout = new SankeyLayout(options); 9 | 10 | const res = layout.layout( 11 | Array.isArray(upstreamData) ? upstreamData[0] : upstreamData, 12 | 'width' in options 13 | ? { 14 | width: options.width, 15 | height: options.height 16 | } 17 | : { 18 | x0: options.x0, 19 | x1: options.x1, 20 | y0: options.y0, 21 | y1: options.y1 22 | } 23 | ); 24 | return res ? [res] : []; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/vlayouts/src/sunburst/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interface'; 2 | export * from './layout'; 3 | export { transform as sunburstTransform } from './transform'; 4 | -------------------------------------------------------------------------------- /packages/vlayouts/src/sunburst/interface.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | HierarchicalDatum, 3 | HierarchicalNodeElement, 4 | HierarchyLabelAttrs, 5 | ViewBoxOptions 6 | } from '../interface/common'; 7 | 8 | type PositionType = string | number; 9 | 10 | export interface SunburstLabelConfig { 11 | align?: 'start' | 'end' | 'center'; 12 | rotate?: 'tangential' | 'radial'; 13 | offset?: number; 14 | } 15 | export type SunburstLabelOptions = boolean | SunburstLabelConfig; 16 | 17 | /** 18 | * The options of sunburst 19 | */ 20 | export interface SunburstOptions { 21 | startAngle?: number; 22 | endAngle?: number; 23 | center?: [PositionType, PositionType]; 24 | innerRadius?: PositionType | PositionType[]; 25 | outerRadius?: PositionType | PositionType[]; 26 | gapRadius?: number | number[]; 27 | /** parse the key of node */ 28 | nodeKey?: string | number | ((datum: HierarchicalDatum) => string | number); 29 | label?: SunburstLabelOptions | SunburstLabelOptions[]; 30 | } 31 | 32 | export type SunburstTramsformOptions = SunburstOptions & ViewBoxOptions & { flatten?: boolean; maxDepth?: number }; 33 | 34 | /** 35 | * The node element after sunburst layout 36 | */ 37 | export interface SunburstNodeElement extends HierarchicalNodeElement { 38 | innerRadius?: number; 39 | outerRadius?: number; 40 | startAngle?: number; 41 | endAngle?: number; 42 | x?: number; 43 | y?: number; 44 | 45 | children?: SunburstNodeElement[]; 46 | label?: HierarchyLabelAttrs; 47 | } 48 | -------------------------------------------------------------------------------- /packages/vlayouts/src/sunburst/transform.ts: -------------------------------------------------------------------------------- 1 | import { flattenNodes } from '../utils/hierarchy'; 2 | import type { HierarchicalData } from '../interface/common'; 3 | import type { SunburstNodeElement, SunburstTramsformOptions } from './interface'; 4 | import { SunburstLayout } from './layout'; 5 | 6 | export const transform = (options: SunburstTramsformOptions, upstreamData: HierarchicalData) => { 7 | const layout = new SunburstLayout(options); 8 | 9 | const res = layout.layout( 10 | upstreamData, 11 | 'width' in options 12 | ? { 13 | width: options.width, 14 | height: options.height 15 | } 16 | : { 17 | x0: options.x0, 18 | x1: options.x1, 19 | y0: options.y0, 20 | y1: options.y1 21 | } 22 | ); 23 | 24 | if (options.flatten) { 25 | const nodes: SunburstNodeElement[] = []; 26 | flattenNodes(res, nodes, { maxDepth: options?.maxDepth }); 27 | 28 | return nodes; 29 | } 30 | return res; 31 | }; 32 | -------------------------------------------------------------------------------- /packages/vlayouts/src/tree/format.ts: -------------------------------------------------------------------------------- 1 | import type { TreeLinkElement, TreeNodeElement } from './interface'; 2 | 3 | export const flattenTreeLinks = ( 4 | nodes: TreeNodeElement[], 5 | output: T[] = [], 6 | options?: { 7 | maxDepth?: number; 8 | callback?: (link: TreeLinkElement) => T; 9 | } 10 | ): T[] => { 11 | const hasMaxDepth = options?.maxDepth >= 0; 12 | 13 | nodes.forEach(node => { 14 | if (!hasMaxDepth || node.depth <= options.maxDepth - 1) { 15 | if (node.children) { 16 | node.children.forEach(child => { 17 | const link = { 18 | source: node, 19 | target: child, 20 | x0: node.x, 21 | y0: node.y, 22 | x1: child.x, 23 | y1: child.y, 24 | key: `${node.key}~${child.key}` 25 | }; 26 | 27 | output.push(options?.callback ? options.callback(link) : (link as unknown as T)); 28 | 29 | if (child.children?.length) { 30 | flattenTreeLinks([child], output, options); 31 | } 32 | }); 33 | } 34 | } 35 | }); 36 | 37 | return output; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/vlayouts/src/tree/index.ts: -------------------------------------------------------------------------------- 1 | export { transform as treeTransform } from './transform'; 2 | export * from './interface'; 3 | export * from './layout'; 4 | export * from './format'; 5 | -------------------------------------------------------------------------------- /packages/vlayouts/src/tree/interface.ts: -------------------------------------------------------------------------------- 1 | import type { HierarchicalDatum, HierarchicalNodeElement, ViewBoxOptions } from '../interface/common'; 2 | 3 | export interface TreeOptions { 4 | /** 5 | * The layout direction of chart 6 | */ 7 | direction?: 'horizontal' | 'vertical' | 'LR' | 'RL' | 'TB' | 'BT'; 8 | /** 9 | * layout tree in orthogonal | radial coordinate system 10 | */ 11 | layoutType?: 'orthogonal' | 'radial'; 12 | alignType?: 'leaf' | 'depth'; 13 | /** 14 | * Specify the width of link, if not specified, 15 | * Compute the depth-most nodes for extents. 16 | */ 17 | linkWidth?: number | number[]; 18 | /** 19 | * Specify the min gap between two sibling nodes, if not specified, scale nodeGap based on the extent. 20 | */ 21 | minNodeGap?: number; 22 | /** parse the key of node */ 23 | nodeKey?: string | number | ((datum: HierarchicalDatum) => string | number); 24 | } 25 | 26 | export type TreeTramsformOptions = TreeOptions & ViewBoxOptions & { flatten?: boolean; maxDepth?: number }; 27 | 28 | export interface TreeNodeElement extends HierarchicalNodeElement { 29 | children?: TreeNodeElement[]; 30 | x?: number; 31 | y?: number; 32 | } 33 | 34 | export interface TreeLinkElement { 35 | source: TreeNodeElement; 36 | target: TreeNodeElement; 37 | x0?: number; 38 | y0?: number; 39 | x1?: number; 40 | y1?: number; 41 | key?: string; 42 | } 43 | -------------------------------------------------------------------------------- /packages/vlayouts/src/tree/transform.ts: -------------------------------------------------------------------------------- 1 | import type { HierarchicalData } from '../interface/common'; 2 | import { flattenNodes } from '../utils/hierarchy'; 3 | import { flattenTreeLinks } from './format'; 4 | import type { TreeLinkElement, TreeNodeElement, TreeTramsformOptions } from './interface'; 5 | import { TreeLayout } from './layout'; 6 | 7 | export const transform = (options: TreeTramsformOptions, upstreamData: HierarchicalData) => { 8 | const layout = new TreeLayout(options); 9 | 10 | const res = layout.layout( 11 | upstreamData, 12 | 'width' in options 13 | ? { 14 | width: options.width, 15 | height: options.height 16 | } 17 | : { 18 | x0: options.x0, 19 | x1: options.x1, 20 | y0: options.y0, 21 | y1: options.y1 22 | } 23 | ); 24 | 25 | if (options.flatten) { 26 | const { maxDepth } = options ?? {}; 27 | const nodes: TreeNodeElement[] = []; 28 | flattenNodes(res, nodes, { maxDepth }); 29 | const links: TreeLinkElement[] = []; 30 | 31 | flattenTreeLinks(res, links, { maxDepth }); 32 | 33 | return { nodes, links }; 34 | } 35 | return res; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/vlayouts/src/treemap/dice.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-hierarchy by Mike Bostock 2 | * https://observablehq.com/collection/@d3/d3-hierarchy 3 | * Licensed under the ISC 4 | 5 | * url: https://github.com/d3/d3-hierarchy/blob/main/src/treemap/dice.js 6 | * License: https://github.com/d3/d3-hierarchy/blob/main/LICENSE 7 | * @license 8 | */ 9 | 10 | import type { HierarchicalDatum } from '../interface/common'; 11 | 12 | /** 13 | * split rect in horizontal direction 14 | */ 15 | export default function ( 16 | parent: T, 17 | x0: number, 18 | y0: number, 19 | x1: number, 20 | y1: number, 21 | keyMap: Record = { x0: 'x0', x1: 'x1', y0: 'y0', y1: 'y1' } 22 | ) { 23 | const nodes = parent.children; 24 | let node; 25 | let i = -1; 26 | const n = nodes.length; 27 | const k = parent.value && (x1 - x0) / parent.value; 28 | 29 | while (++i < n) { 30 | node = nodes[i]; 31 | (node as any)[keyMap.y0] = y0; 32 | (node as any)[keyMap.y1] = y1; 33 | (node as any)[keyMap.x0] = x0; 34 | (node as any)[keyMap.x1] = x0 += node.value * k; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/vlayouts/src/treemap/format.ts: -------------------------------------------------------------------------------- 1 | import type { TreemapNodeElement } from './interface'; 2 | 3 | export const flattenNodes = ( 4 | nodes: TreemapNodeElement[], 5 | output: T[] = [], 6 | options?: { 7 | maxDepth?: number; 8 | callback?: (node: TreemapNodeElement) => T; 9 | } 10 | ) => { 11 | const hasMaxDepth = options?.maxDepth >= 0; 12 | 13 | nodes.forEach(node => { 14 | if (!hasMaxDepth || node.depth <= options.maxDepth) { 15 | output.push(options?.callback ? options.callback(node) : (node as unknown as T)); 16 | if (node.children) { 17 | if (hasMaxDepth && node.depth === options.maxDepth) { 18 | node.children = null; 19 | node.isLeaf = true; 20 | } else { 21 | flattenNodes(node.children, output, options); 22 | } 23 | } 24 | } 25 | }); 26 | 27 | return output; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/vlayouts/src/treemap/index.ts: -------------------------------------------------------------------------------- 1 | export { transform as treemapTransform } from './transform'; 2 | export * from './interface'; 3 | export * from './layout'; 4 | -------------------------------------------------------------------------------- /packages/vlayouts/src/treemap/slice.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-hierarchy by Mike Bostock 2 | * https://observablehq.com/collection/@d3/d3-hierarchy 3 | * Licensed under the ISC 4 | 5 | * url: https://github.com/d3/d3-hierarchy/blob/main/src/treemap/slice.js 6 | * License: https://github.com/d3/d3-hierarchy/blob/main/LICENSE 7 | * @license 8 | */ 9 | 10 | import type { TreemapNodeElement } from './interface'; 11 | 12 | /** 13 | * split rect in vertical direction 14 | */ 15 | export default function (parent: TreemapNodeElement, x0: number, y0: number, x1: number, y1: number) { 16 | const nodes = parent.children; 17 | let node; 18 | let i = -1; 19 | const n = nodes.length; 20 | const k = parent.value && (y1 - y0) / parent.value; 21 | 22 | while (++i < n) { 23 | node = nodes[i]; 24 | node.x0 = x0; 25 | node.x1 = x1; 26 | node.y0 = y0; 27 | y0 += node.value * k; 28 | node.y1 = y0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/vlayouts/src/treemap/sliceDice.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-hierarchy by Mike Bostock 2 | * https://observablehq.com/collection/@d3/d3-hierarchy 3 | * Licensed under the ISC 4 | 5 | * url: https://github.com/d3/d3-hierarchy/blob/main/src/treemap/sliceDice.js 6 | * License: https://github.com/d3/d3-hierarchy/blob/main/LICENSE 7 | * @license 8 | */ 9 | 10 | /** 11 | * split rect in vertical, horizontal direction alternatively 12 | */ 13 | import dice from './dice'; 14 | import type { TreemapNodeElement } from './interface'; 15 | import slice from './slice'; 16 | 17 | export default function (parent: TreemapNodeElement, x0: number, y0: number, x1: number, y1: number) { 18 | (parent.depth % 2 === 1 ? slice : dice)(parent, x0, y0, x1, y1); 19 | } 20 | -------------------------------------------------------------------------------- /packages/vlayouts/src/treemap/transform.ts: -------------------------------------------------------------------------------- 1 | import { flattenNodes } from '../utils/hierarchy'; 2 | import type { TreemapData, TreemapNodeElement, TreemapTramsformOptions } from './interface'; 3 | import { TreemapLayout } from './layout'; 4 | 5 | export const transform = (options: TreemapTramsformOptions, upstreamData: TreemapData) => { 6 | const layout = new TreemapLayout(options); 7 | 8 | const res = layout.layout( 9 | upstreamData, 10 | 'width' in options 11 | ? { 12 | width: options.width, 13 | height: options.height 14 | } 15 | : { 16 | x0: options.x0, 17 | x1: options.x1, 18 | y0: options.y0, 19 | y1: options.y1 20 | } 21 | ); 22 | 23 | if (options.flatten) { 24 | const nodes: TreemapNodeElement[] = []; 25 | flattenNodes(res, nodes, { maxDepth: options?.maxDepth }); 26 | 27 | return nodes; 28 | } 29 | return res; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/vlayouts/src/utils/loader.ts: -------------------------------------------------------------------------------- 1 | import { isBase64, isValidUrl } from '@visactor/vutils'; 2 | import type { CreateImageFunction } from '../interface/wordcloud'; 3 | 4 | /** 5 | * 使用 ResourceLoader 加载图片 6 | */ 7 | export function loadImage(url: string, createImage: CreateImageFunction) { 8 | if (!url || (!isValidUrl(url) && !isBase64(url) && !url.startsWith(' { 12 | const imageMark = createImage({ image: url }); 13 | const imgData = imageMark.resources?.get(url); 14 | 15 | if (imgData && imgData.state === 'success' && imgData.data) { 16 | resolve(imgData.data); 17 | return; 18 | } 19 | 20 | imageMark.successCallback = () => { 21 | if (imageMark) { 22 | const imgData = imageMark.resources?.get(url); 23 | if (imgData && imgData.state === 'success' && imgData.data) { 24 | resolve(imgData.data); 25 | } else { 26 | reject(new Error('image load failed: ' + url)); 27 | } 28 | } else { 29 | reject(new Error('image load failed: ' + url)); 30 | } 31 | }; 32 | imageMark.failCallback = () => { 33 | reject(new Error('image load failed: ' + url)); 34 | }; 35 | }); 36 | } 37 | 38 | /** 39 | * 使用 ResourceLoader 加载多个图片 40 | */ 41 | export function loadImages(urls: string[], createImage: CreateImageFunction) { 42 | return Promise.allSettled(urls.map(url => loadImage(url, createImage))); 43 | } 44 | -------------------------------------------------------------------------------- /packages/vlayouts/src/utils/spirals.ts: -------------------------------------------------------------------------------- 1 | export const spirals: Record (t: number) => [number, number]> = { 2 | archimedean: archimedeanSpiral, 3 | rectangular: rectangularSpiral 4 | }; 5 | 6 | function archimedeanSpiral(size: [number, number]) { 7 | const e = size[0] / size[1]; 8 | return (t: number) => { 9 | return [e * (t *= 0.1) * Math.cos(t), t * Math.sin(t)] as [number, number]; 10 | }; 11 | } 12 | 13 | function rectangularSpiral(size: [number, number]) { 14 | const dy = 4; 15 | const dx = (dy * size[0]) / size[1]; 16 | let x = 0; 17 | let y = 0; 18 | return (t: number) => { 19 | const sign = t < 0 ? -1 : 1; 20 | // See triangular numbers: T_n = n * (n + 1) / 2. 21 | switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) { 22 | case 0: 23 | x += dx; 24 | break; 25 | case 1: 26 | y += dy; 27 | break; 28 | case 2: 29 | x -= dx; 30 | break; 31 | default: 32 | y -= dy; 33 | break; 34 | } 35 | return [x, y] as [number, number]; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/vlayouts/src/venn/index.ts: -------------------------------------------------------------------------------- 1 | import { transform, transformMark } from './venn'; 2 | 3 | export * from './interface'; 4 | export * from './utils/interface'; 5 | export * from './utils/path'; 6 | 7 | export const vennTransform = transform; 8 | export const vennMarkTransform = transformMark; 9 | -------------------------------------------------------------------------------- /packages/vlayouts/src/venn/interface.ts: -------------------------------------------------------------------------------- 1 | import type { IVennArea, IVennOverlapArc, IVennParams } from './utils/interface'; 2 | 3 | export interface IVennTransformOptions extends IVennParams { 4 | x0: number; 5 | x1: number; 6 | y0: number; 7 | y1: number; 8 | setField?: string; 9 | valueField?: string; 10 | orientation?: number; 11 | orientationOrder?: any; 12 | } 13 | 14 | export interface IVennTransformMarkOptions { 15 | datumType: 'circle' | 'overlap'; 16 | } 17 | 18 | export interface IVennCommonDatum extends IVennArea, IVennLabelDatum { 19 | datum: T; 20 | x: number; 21 | y: number; 22 | } 23 | 24 | export interface IVennCircleDatum extends IVennCommonDatum { 25 | type: 'circle'; 26 | radius: number; 27 | } 28 | 29 | export interface IVennOverlapDatum extends IVennCommonDatum { 30 | type: 'overlap'; 31 | path: string; 32 | arcs: IVennOverlapArc[]; 33 | } 34 | 35 | export interface IVennLabelDatum { 36 | labelX?: number; 37 | labelY?: number; 38 | } 39 | -------------------------------------------------------------------------------- /packages/vlayouts/src/venn/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout'; 2 | export * from './solution'; 3 | export * from './label'; 4 | -------------------------------------------------------------------------------- /packages/vlayouts/src/venn/utils/interface.ts: -------------------------------------------------------------------------------- 1 | import type { ICircle, IPointLike } from '@visactor/vutils'; 2 | 3 | // eg. 'A', 'B' 4 | export type VennCircleName = string; 5 | 6 | // eg. 'A', 'B', 'A,B' 7 | export type VennAreaName = string; 8 | 9 | export interface IVennCircle extends ICircle { 10 | setId: VennCircleName; 11 | parent?: IVennCircle; 12 | } 13 | 14 | export interface IVennArea { 15 | sets: VennCircleName[]; 16 | size: number; 17 | weight?: number; 18 | label?: string; 19 | } 20 | 21 | export interface IVennSingleArea { 22 | set: VennCircleName; 23 | size: number; 24 | weight?: number; 25 | } 26 | 27 | export interface IVennParams { 28 | maxIterations?: number; 29 | initialLayout?: (areas: IVennArea[], params: IVennParams) => Record; 30 | lossFunction?: (sets: Record, overlaps: IVennArea[]) => number; 31 | restarts?: number; 32 | history?: any; 33 | } 34 | 35 | export interface ICluster extends Array { 36 | size?: number; 37 | bounds?: { 38 | xRange: { 39 | max: number; 40 | min: number; 41 | }; 42 | yRange: { 43 | max: number; 44 | min: number; 45 | }; 46 | }; 47 | } 48 | 49 | export interface IVennOverlapArc { 50 | p1: IPointLike; 51 | p2: IPointLike; 52 | radius: number; 53 | largeArcFlag: boolean; 54 | setId: VennCircleName; 55 | } 56 | -------------------------------------------------------------------------------- /packages/vlayouts/src/venn/utils/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constrained-mds-layout'; 2 | export * from './greedy-layout'; 3 | export * from './layout'; 4 | export * from './loss'; 5 | -------------------------------------------------------------------------------- /packages/vlayouts/src/venn/utils/solution/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common'; 2 | export * from './normalize-solution'; 3 | export * from './scale-solution'; 4 | -------------------------------------------------------------------------------- /packages/vlayouts/src/wordcloud-shape/index.ts: -------------------------------------------------------------------------------- 1 | export { transform as wordcloudShapeTransform } from './wordcloud-shape'; 2 | 3 | export { WORDCLOUD_SHAPE_HOOK_EVENT } from './util'; 4 | -------------------------------------------------------------------------------- /packages/vlayouts/src/wordcloud/index.ts: -------------------------------------------------------------------------------- 1 | export { transform as wordcloudTransform } from './wordcloud'; 2 | 3 | export { CloudLayout as WordcloudCloudLayout } from './cloud-layout'; 4 | export { FastLayout as WordcloudFastLayout } from './fast-layout'; 5 | export { GridLayout as WordcloudGridLayout } from './grid-layout'; 6 | -------------------------------------------------------------------------------- /packages/vlayouts/src/wordcloud/util.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '@visactor/vutils'; 2 | 3 | export function getMinFontSizeOfEnv() { 4 | return 12; 5 | } 6 | 7 | export const randomHslColor = (min: number, max: number) => { 8 | return ( 9 | 'hsl(' + 10 | (Math.random() * 360).toFixed() + 11 | ',' + 12 | (Math.random() * 30 + 70).toFixed() + 13 | '%,' + 14 | (Math.random() * (max - min) + min).toFixed() + 15 | '%)' 16 | ); 17 | }; 18 | 19 | export function functor(d: any) { 20 | return isFunction(d) 21 | ? d 22 | : function () { 23 | return d; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/vlayouts/tscofig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"], 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": "./", 7 | "rootDir": "./" 8 | }, 9 | "include": ["src", "__tests__", "examples"], 10 | "exclude": ["bugserver-config"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/vlayouts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "rootDir": "./src", 6 | "outDir": "./es", 7 | "esModuleInterop": true 8 | }, 9 | "ts-node": { 10 | "transpileOnly": true, 11 | "compilerOptions": { 12 | "module": "commonjs" 13 | } 14 | }, 15 | "references": [ 16 | { 17 | "path": "../vutils" 18 | }, 19 | { 20 | "path": "../vscale" 21 | } 22 | ], 23 | "include": ["src", "__tests__", "examples"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/vlayouts/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./__tests__", "./src"], 4 | "compilerOptions": { 5 | "paths": { 6 | "@visactor/vutils": ["../vutils/src"], 7 | "@visactor/vscale": ["../vscale/src"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/vscale/.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_CLEAN: "true" 3 | -------------------------------------------------------------------------------- /packages/vscale/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-patch/modern-module-resolution'); 2 | 3 | module.exports = { 4 | extends: ['@internal/eslint-config/profile/lib'], 5 | parserOptions: { tsconfigRootDir: __dirname }, 6 | // ignorePatterns: [], 7 | }; 8 | -------------------------------------------------------------------------------- /packages/vscale/README.md: -------------------------------------------------------------------------------- 1 | # @visactor/vscale 2 | 3 | ## Description 4 | 5 | Scales and color schemes for visual encoding. 6 | 7 | ## Usage 8 | 9 | ```typescript 10 | import { xxx } from '@visactor/vscale'; 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/vscale/README.zh-CN.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisActor/VUtil/8eb0060fa055c6e2e8a7e41f719428eeffb365eb/packages/vscale/README.zh-CN.md -------------------------------------------------------------------------------- /packages/vscale/__tests__/band-invert.test.ts: -------------------------------------------------------------------------------- 1 | import { BandScale } from '../src/band-scale'; 2 | 3 | test('scaleBand() has the expected defaults', function () { 4 | const s = new BandScale(true).domain(['A', 'B', 'C', 'D', 'E']); 5 | 6 | expect(s.invert(0.31)).toBe('B'); 7 | expect(s.invert(-1)).toBe('A'); 8 | expect(s.invert(0.1)).toBe('A'); 9 | expect(s.invert(0.2)).toBe('A'); 10 | expect(s.invert(0.3)).toBe('B'); 11 | expect(s.invert(0.4)).toBe('B'); 12 | expect(s.invert(0.5)).toBe('C'); 13 | expect(s.invert(0.6)).toBe('C'); 14 | expect(s.invert(0.7)).toBe('D'); 15 | expect(s.invert(0.8)).toBe('D'); 16 | expect(s.invert(0.9)).toBe('E'); 17 | expect(s.invert(1)).toBe('E'); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/vscale/__tests__/band-width.test.ts: -------------------------------------------------------------------------------- 1 | import { BandScale } from '../src/band-scale'; 2 | 3 | test('scaleBand() should return right bandwidth', function () { 4 | const s = new BandScale().domain(['A', 'B', 'C', 'D']).range([0, 200]); 5 | 6 | expect(s.bandwidth()).toBeCloseTo(50); 7 | 8 | s.rangeFactor([0, 1]); 9 | 10 | expect(s.bandwidth()).toBeCloseTo(50); 11 | }); 12 | 13 | test('scaleBand() should return right bandwidth when set max bandwidth', function () { 14 | const s = new BandScale().domain(['A', 'B', 'C', 'D']).range([0, 200]).maxBandwidth(40).rangeFactor([0, 1]); 15 | 16 | expect(s.bandwidth()).toBeCloseTo(40); 17 | expect(s.maxBandwidth()).toBeCloseTo(40); 18 | 19 | s.rangeFactor([0, 1]); 20 | 21 | expect(s.bandwidth()).toBeCloseTo(40); 22 | }); 23 | 24 | test('scaleBand() should return right bandwidth when set min bandwidth', function () { 25 | const s = new BandScale().domain(['A', 'B', 'C', 'D']).range([0, 200]).minBandwidth(40); 26 | 27 | expect(s.bandwidth()).toBeCloseTo(50); 28 | 29 | s.rangeFactor([0, 1]); 30 | s.calculateWholeRangeSize(); 31 | 32 | expect(s.bandwidth()).toBeCloseTo(50); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/vscale/__tests__/clamp.test.ts: -------------------------------------------------------------------------------- 1 | import { LinearScale } from '../src/linear-scale'; 2 | 3 | test('linear(x) will not ignores extra range values if the domain is smaller than the range', function () { 4 | const scale = new LinearScale(); 5 | 6 | scale.clamp(true).domain([-10, 10]).range([100, 200]); 7 | expect(scale.scale(0)).toBe(150); 8 | expect(scale.scale(-50)).toBe(100); 9 | expect(scale.scale(50)).toBe(200); 10 | 11 | scale.domain([-100, 100]); 12 | expect(scale.scale(0)).toBe(150); 13 | expect(scale.scale(-50)).toBe(125); 14 | expect(scale.scale(50)).toBe(175); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/vscale/__tests__/color.test.ts: -------------------------------------------------------------------------------- 1 | import { LinearScale } from '../src/linear-scale'; 2 | 3 | test('scaleLinear() has the expected defaults', function () { 4 | const s = new LinearScale(); 5 | s.range(['red', 'green']); 6 | 7 | expect(s.domain()).toEqual([0, 1]); 8 | expect(s.range()).toEqual(['red', 'green']); 9 | expect(s.scale(0)).toEqual('rgb(255,0,0)'); 10 | expect(s.scale(0.5)).toEqual('rgb(128,64,0)'); 11 | expect(s.scale(1)).toEqual('rgb(0,128,0)'); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/vscale/__tests__/common.test.ts: -------------------------------------------------------------------------------- 1 | import { isValidScaleType, ScaleEnum } from '../src/type'; 2 | 3 | test('scaleLinear() has the expected defaults', function () { 4 | expect(isValidScaleType(ScaleEnum.Identity)).toBe(true); 5 | 6 | expect(isValidScaleType(ScaleEnum.Linear)).toBe(true); 7 | expect(isValidScaleType(ScaleEnum.Log)).toBe(true); 8 | expect(isValidScaleType(ScaleEnum.Pow)).toBe(true); 9 | expect(isValidScaleType(ScaleEnum.Sqrt)).toBe(true); 10 | expect(isValidScaleType(ScaleEnum.Symlog)).toBe(true); 11 | expect(isValidScaleType(ScaleEnum.Time)).toBe(true); 12 | 13 | expect(isValidScaleType(ScaleEnum.Quantile)).toBe(true); 14 | expect(isValidScaleType(ScaleEnum.Quantize)).toBe(true); 15 | expect(isValidScaleType(ScaleEnum.Threshold)).toBe(true); 16 | 17 | expect(isValidScaleType(ScaleEnum.Ordinal)).toBe(true); 18 | expect(isValidScaleType(ScaleEnum.Point)).toBe(true); 19 | expect(isValidScaleType(ScaleEnum.Band)).toBe(true); 20 | 21 | expect(isValidScaleType('unknown')).toBe(false); 22 | expect(isValidScaleType('aaaaa')).toBe(false); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/vscale/__tests__/sqrt.test.ts: -------------------------------------------------------------------------------- 1 | import { SqrtScale } from '../src/sqrt-scale'; 2 | 3 | it('new SqrtScale() has the expected defaults', () => { 4 | const s = new SqrtScale(); 5 | expect(s.domain()).toEqual([0, 1]); 6 | expect(s.range()).toEqual([0, 1]); 7 | expect(s.clamp()).toBe(false); 8 | expect(s.interpolate()('red', 'blue')(0.5)).toBe('rgb(128,0,128)'); 9 | }); 10 | 11 | it('new SqrtScale() is an alias for pow().exponent(0.5)', () => { 12 | const s = new SqrtScale(); 13 | 14 | expect(s.scale(0.5)).toBeCloseTo(Math.SQRT1_2, 6); 15 | expect(s.invert(Math.SQRT1_2)).toBeCloseTo(0.5, 6); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/vscale/bundler.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {Partial} 3 | */ 4 | module.exports = { 5 | formats: ['cjs', 'es', 'umd'], 6 | name: 'VScale', 7 | umdOutputFilename: 'index' 8 | }; 9 | -------------------------------------------------------------------------------- /packages/vscale/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const baseJestConfig = require('@internal/jest-config/jest.base'); 3 | 4 | module.exports = { 5 | ...baseJestConfig, 6 | moduleNameMapper: { 7 | ...baseJestConfig.moduleNameMapper, 8 | '@visactor/vutils': path.resolve(__dirname, '../vutils/src/index.ts') 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /packages/vscale/src/index.ts: -------------------------------------------------------------------------------- 1 | export { BandScale } from './band-scale'; 2 | export { ContinuousScale } from './continuous-scale'; 3 | export { LinearScale } from './linear-scale'; 4 | export { LogScale } from './log-scale'; 5 | export { OrdinalScale } from './ordinal-scale'; 6 | export { PointScale } from './point-scale'; 7 | export { PowScale } from './pow-scale'; 8 | export { QuantileScale } from './quantile-scale'; 9 | export { QuantizeScale } from './quantize-scale'; 10 | export { SqrtScale } from './sqrt-scale'; 11 | export { SymlogScale } from './symlog-scale'; 12 | export { ThresholdScale } from './threshold-scale'; 13 | export { TimeScale } from './time-scale'; 14 | export { IdentityScale } from './identity-scale'; 15 | 16 | export * from './interface'; 17 | // type 18 | export * from './type'; 19 | 20 | export * from './utils'; 21 | -------------------------------------------------------------------------------- /packages/vscale/src/point-scale.ts: -------------------------------------------------------------------------------- 1 | import { ScaleEnum } from './type'; 2 | import { BandScale } from './band-scale'; 3 | import type { DiscreteScaleType, IBandLikeScale } from './interface'; 4 | 5 | export class PointScale extends BandScale implements IBandLikeScale { 6 | readonly type: DiscreteScaleType = ScaleEnum.Point; 7 | protected _padding = 0; 8 | 9 | constructor(slience?: boolean) { 10 | super(false); 11 | this.paddingInner(1, slience); 12 | this.padding = this.paddingOuter; 13 | this.paddingInner = undefined; 14 | this.paddingOuter = undefined; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/vscale/src/sqrt-scale.ts: -------------------------------------------------------------------------------- 1 | import { sqrt, square } from './utils/utils'; 2 | import { LinearScale } from './linear-scale'; 3 | import { ScaleEnum } from './type'; 4 | import type { ContinuousScaleType } from './interface'; 5 | 6 | export class SqrtScale extends LinearScale { 7 | readonly type: ContinuousScaleType = ScaleEnum.Sqrt; 8 | 9 | constructor() { 10 | super(sqrt, square); 11 | } 12 | 13 | clone(): SqrtScale { 14 | return new SqrtScale() 15 | .domain(this._domain, true) 16 | .range(this._range, true) 17 | .unknown(this._unknown) 18 | .clamp(this.clamp(), null, true) 19 | .interpolate(this._interpolate) as LinearScale; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/vscale/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { scaleWholeRangeSize } from './utils'; 2 | export { wilkinsonExtended } from './tick-wilkinson-extended'; 3 | -------------------------------------------------------------------------------- /packages/vscale/src/utils/interpolate.ts: -------------------------------------------------------------------------------- 1 | import { ColorUtil, isNil, interpolateNumber, interpolateDate } from '@visactor/vutils'; 2 | 3 | const { interpolateRgb } = ColorUtil; 4 | 5 | export function interpolate(a: any, b: any) { 6 | const t = typeof b; 7 | let c; 8 | 9 | if (isNil(b) || t === 'boolean') { 10 | return () => b; 11 | } 12 | 13 | if (t === 'number') { 14 | return interpolateNumber(a, b); 15 | } 16 | 17 | if (t === 'string') { 18 | if ((c = ColorUtil.Color.parseColorString(b))) { 19 | const rgb = interpolateRgb(ColorUtil.Color.parseColorString(a as string), c); 20 | 21 | return (t: number) => { 22 | // #rrggbbaa 格式在部分浏览器存在兼容性问题,rgba()字符串兼容性更好,所以还是支持rgba()字符串 23 | return rgb(t).formatRgb(); 24 | }; 25 | } 26 | 27 | return interpolateNumber(Number(a), Number(b)); 28 | } 29 | 30 | if (b instanceof ColorUtil.RGB) { 31 | return interpolateRgb(a, b); 32 | } 33 | 34 | if (b instanceof ColorUtil.Color) { 35 | return interpolateRgb(a.color, b.color); 36 | } 37 | 38 | if (b instanceof Date) { 39 | return interpolateDate(a, b); 40 | } 41 | 42 | return interpolateNumber(Number(a), Number(b)); 43 | } 44 | -------------------------------------------------------------------------------- /packages/vscale/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"], 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": ".", 7 | "rootDir": "./" 8 | }, 9 | "include": ["src", "__tests__", "examples"], 10 | "exclude": [] 11 | } 12 | -------------------------------------------------------------------------------- /packages/vscale/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"], 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": ".", 7 | "rootDir": "./src", 8 | "outDir": "./es", 9 | "composite": true 10 | }, 11 | "references": [ 12 | { 13 | "path": "../vutils" 14 | } 15 | ], 16 | "include": ["src", "__tests__", "examples"], 17 | "exclude": [] 18 | } 19 | -------------------------------------------------------------------------------- /packages/vscale/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@visactor/vutils": ["../vutils/src"] 6 | } 7 | }, 8 | "references": [] 9 | } 10 | -------------------------------------------------------------------------------- /packages/vutils/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-patch/modern-module-resolution'); 2 | 3 | module.exports = { 4 | extends: ['@internal/eslint-config/profile/lib'], 5 | parserOptions: { tsconfigRootDir: __dirname }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/vutils/README.md: -------------------------------------------------------------------------------- 1 | # @visactor/vlayouts 2 | 3 | ## Installation 4 | 5 | [npm package](https://www.npmjs.com/package/@visactor/vlayouts) 6 | 7 | ```bash 8 | // npm 9 | npm install @visactor/vlayouts 10 | 11 | // yarn 12 | yarn add @visactor/vlayouts 13 | ``` 14 | 15 | ## 16 | 17 | # Contribution 18 | 19 | If you would like to contribute, please read the [Code of Conduct ](./CODE_OF_CONDUCT.md) 和 [ Guide](./CONTRIBUTING.zh-CN.md) first。 20 | 21 | Small streams converge to make great rivers and seas! 22 | 23 | 24 | 25 | # License 26 | 27 | [MIT License](./LICENSE) 28 | -------------------------------------------------------------------------------- /packages/vutils/README.zh-CN.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisActor/VUtil/8eb0060fa055c6e2e8a7e41f719428eeffb365eb/packages/vutils/README.zh-CN.md -------------------------------------------------------------------------------- /packages/vutils/__tests__/color/interpolate.test.ts: -------------------------------------------------------------------------------- 1 | import { ColorUtil } from '../../src'; 2 | 3 | describe('interpolateRgb', () => { 4 | it('interpolateRgb(RGB(255, 0, 0), RGB(0,0, 255))', () => { 5 | const red = new ColorUtil.Color('red').color; 6 | const blue = new ColorUtil.Color('blue').color; 7 | const interpolate = ColorUtil.interpolateRgb(red, blue); 8 | 9 | expect(interpolate(0).toString()).toBe('#ff0000'); 10 | expect(interpolate(0.5).toString()).toBe('#800080'); 11 | expect(interpolate(1).toString()).toBe('#0000ff'); 12 | }); 13 | 14 | it('interpolateRgb(RGB(255, 0, 0, 0.2), RGB(0,0, 255, 0.8))', () => { 15 | const red = new ColorUtil.Color('rgba(255, 0, 0, 0.2)').color; 16 | const blue = new ColorUtil.Color('rgba(0, 0, 255, 0.8)').color; 17 | const interpolate = ColorUtil.interpolateRgb(red, blue); 18 | 19 | expect(red.opacity).toBeCloseTo(0.2); 20 | expect(blue.opacity).toBeCloseTo(0.8); 21 | expect(interpolate(0).formatRgb()).toBe('rgba(255,0,0,0.2)'); 22 | expect(interpolate(0.5).formatRgb()).toBe('rgba(128,0,128,0.5)'); 23 | expect(interpolate(1).formatRgb()).toBe('rgba(0,0,255,0.8)'); 24 | 25 | expect(interpolate(0).toString()).toBe('#ff000033'); 26 | expect(interpolate(0.5).toString()).toBe('#80008080'); 27 | expect(interpolate(1).toString()).toBe('#0000ffcc'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/array.test.ts: -------------------------------------------------------------------------------- 1 | import { minInArray, maxInArray, shuffleArray } from '../../src'; 2 | 3 | const values = [1, 2, 3, 4, 5, 6]; 4 | 5 | describe('shuffleArray', () => { 6 | it('shuffleArray(values) should change the order of array', () => { 7 | const result = shuffleArray(values.slice()); 8 | 9 | expect(result).not.toEqual(values); 10 | expect(result.every(v => values.includes(v))).toBeTruthy(); 11 | expect(values.every(v => result.includes(v))).toBeTruthy(); 12 | }); 13 | }); 14 | 15 | describe('maxInArray and minInArray', () => { 16 | it('maxInArray/minInArray with custom compare function', () => { 17 | const list = [{ value: 1 }, { value: 3 }, { value: 2 }]; 18 | const max = maxInArray(list, (a, b) => a.value - b.value); 19 | const min = minInArray(list, (a, b) => a.value - b.value); 20 | expect(max).toEqual(list[1]); 21 | expect(min).toEqual(list[0]); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/ascending.test.ts: -------------------------------------------------------------------------------- 1 | import { ascending } from '../../src'; 2 | 3 | describe('ascending', () => { 4 | it('ascending(1, 2) should be -1', () => { 5 | expect(ascending(1, 2)).toBe(-1); 6 | }); 7 | 8 | it('ascending(2, 1) should be 1', () => { 9 | expect(ascending(2, 1)).toBe(1); 10 | }); 11 | 12 | it('ascending(2, 2) should be 0', () => { 13 | expect(ascending(2, 2)).toBe(0); 14 | }); 15 | 16 | it('ascending(1, NaN) should be true', () => { 17 | expect(ascending(1, NaN)).toBeNaN(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/clamper.test.ts: -------------------------------------------------------------------------------- 1 | import { clamper } from '../../src'; 2 | 3 | describe('clamper', () => { 4 | it('clamper(1, 100)', () => { 5 | const clamp = clamper(1, 100); 6 | expect(clamp(-1)).toBe(1); 7 | expect(clamp(120)).toBe(100); 8 | expect(clamp(50)).toBe(50); 9 | }); 10 | 11 | it('clamper(100, 1)', () => { 12 | const clamp = clamper(100, 1); 13 | expect(clamp(-1)).toBe(1); 14 | expect(clamp(120)).toBe(100); 15 | expect(clamp(50)).toBe(50); 16 | }); 17 | 18 | it('clamper(100, NaN)', () => { 19 | const clamp = clamper(100, NaN); 20 | expect(clamp(-1)).toBe(NaN); 21 | expect(clamp(120)).toBe(NaN); 22 | expect(clamp(50)).toBe(NaN); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/field.test.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from vega by University of Washington Interactive Data Lab 2 | * https://vega.github.io/vega/ 3 | * Licensed under the BSD-3-Clause 4 | 5 | * url: https://github.com/vega/vega/blob/main/packages/vega-util/test/field-test.js 6 | * License: https://github.com/vega/vega/blob/main/LICENSE 7 | * @license 8 | */ 9 | 10 | import { field } from '../../src'; 11 | 12 | test('field creates a field accessor', () => { 13 | const f = field('x'); 14 | expect(typeof f).toBe('function'); 15 | expect(f({ x: 'foo' })).toBe('foo'); 16 | expect(f({ x: 0 })).toBe(0); 17 | }); 18 | 19 | test('field(function(){})', () => { 20 | const func = (datum: any) => datum.x; 21 | const f = field(func); 22 | expect(typeof f).toBe('function'); 23 | expect(f).toBe(func); 24 | expect(f({ x: 'foo' })).toBe('foo'); 25 | expect(f({ x: 0 })).toBe(0); 26 | }); 27 | 28 | test('field() of function array', () => { 29 | const funcA = (datum: any) => datum.x; 30 | const funcB = (datum: any) => datum.x1; 31 | 32 | const f = field([funcA, funcB]); 33 | expect(typeof f).toBe('function'); 34 | expect(f({ x: 'foo', x1: '6' })).toEqual(['foo', '6']); 35 | expect(f({ x: 0 })).toEqual([0, undefined]); 36 | }); 37 | 38 | test('field() of string array', () => { 39 | const f = field(['x', 'x1']); 40 | expect(typeof f).toBe('function'); 41 | expect(f({ x: 'foo', x1: '6' })).toEqual(['foo', '6']); 42 | expect(f({ x: 0 })).toEqual([0, undefined]); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/getter.test.ts: -------------------------------------------------------------------------------- 1 | import { getter } from '../../src'; 2 | 3 | test('getter(arr)', () => { 4 | expect(getter(['a'])({ a: 1 })).toEqual(1); 5 | expect(getter(['a', 'b'])({ a: { b: 1 } })).toEqual(1); 6 | expect(getter(['a', 'b'])({ a: { b: { c: 2 } } })).toEqual({ c: 2 }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isEqual.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | import { isEqual } from '../../src'; 3 | 4 | describe('isEqual', () => { 5 | it('isEqual({a:1, b: [2, 3, 4]}, {a: 1, b: [2, 3, 4]}) should be true', () => { 6 | expect(isEqual({ a: 1, b: [2, 3, 4] }, { a: 1, b: [2, 3, 4] })).toBeTruthy(); 7 | }); 8 | 9 | it('isEqual({a:1, b: function}, {a: 1, b: function) should be false', () => { 10 | expect(isEqual({ a: 1, b: () => {} }, { a: 1, b: () => {} })).toBeFalsy(); 11 | }); 12 | 13 | it('should be equal when width object array', () => { 14 | const a = { 15 | points: [ 16 | { 17 | x: 218.7131069919538, 18 | y: 52.46233188097247 19 | }, 20 | { 21 | x: 217.93093466675265, 22 | y: 47.52389017799678 23 | } 24 | ], 25 | lineWidth: 1, 26 | strokeColor: '#999', 27 | strokeOpacity: 1 28 | }; 29 | const b = { 30 | points: [ 31 | { 32 | x: 218.7131069919538, 33 | y: 52.46233188097247 34 | }, 35 | { 36 | x: 217.93093466675265, 37 | y: 47.52389017799678 38 | } 39 | ], 40 | lineWidth: 1, 41 | strokeColor: '#999', 42 | strokeOpacity: 1 43 | }; 44 | expect(isEqual(a, b)).toBeTruthy(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isFunction.test.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '../../src'; 2 | 3 | describe('isFunction', () => { 4 | it('isFunction(console.log) should be true', () => { 5 | // eslint-disable-next-line no-console 6 | expect(isFunction(console.log)).toBeTruthy(); 7 | }); 8 | 9 | it('isFunction(/abc/) should be false', () => { 10 | expect(isFunction(/abc/)).toBeFalsy(); 11 | }); 12 | 13 | it('isFunction(setTimeout) should be true', () => { 14 | expect(isFunction(setTimeout)).toBeTruthy(); 15 | }); 16 | 17 | it('isFunction(123) should be false', () => { 18 | expect(isFunction(123)).toBeFalsy(); 19 | }); 20 | 21 | it('isFunction(() => 123) should be true', () => { 22 | expect(isFunction(() => 123)).toBeTruthy(); 23 | }); 24 | 25 | it('isFunction(Math.round) should be true', () => { 26 | expect(isFunction(Math.round)).toBeTruthy(); 27 | }); 28 | 29 | it('isFunction(async () => {}) should be true', () => { 30 | // eslint-disable-next-line @typescript-eslint/no-empty-function 31 | expect(isFunction(async () => {})).toBeTruthy(); 32 | }); 33 | 34 | it('isFunction(class Any{}) should be true', () => { 35 | expect(isFunction(class Any {})).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isNumber.test.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from '../../src'; 2 | 3 | describe('isNumber', () => { 4 | it('isNumber(3) should be true', () => { 5 | expect(isNumber(3)).toBeTruthy(); 6 | }); 7 | 8 | it('isNumber(Number.MIN_VALUE) should be true', () => { 9 | expect(isNumber(Number.MIN_VALUE)).toBeTruthy(); 10 | }); 11 | 12 | it('isNumber(Infinity) should be true', () => { 13 | expect(isNumber(Infinity)).toBeTruthy(); 14 | }); 15 | 16 | it('isNumber("3") should be false', () => { 17 | expect(isNumber('3')).toBeFalsy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isObject.test.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '../../src'; 2 | 3 | describe('isObject', () => { 4 | it('isObject({}) should be true', () => { 5 | expect(isObject({})).toBeTruthy(); 6 | }); 7 | 8 | it('isObject([1, 2, 3]) should be true', () => { 9 | expect(isObject([1, 2, 3])).toBeTruthy(); 10 | }); 11 | 12 | it('isObject(Function) should be true', () => { 13 | expect(isObject(Function)).toBeTruthy(); 14 | }); 15 | 16 | it('isObject(null) should be false', () => { 17 | expect(isObject(null)).toBeFalsy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isObjectLike.test.ts: -------------------------------------------------------------------------------- 1 | import { isObjectLike } from '../../src'; 2 | 3 | describe('isObjectLike', () => { 4 | it('isObjectLike({}) should be true', () => { 5 | expect(isObjectLike({})).toBeTruthy(); 6 | }); 7 | 8 | it('isObjectLike([1, 2, 3]) should be true', () => { 9 | expect(isObjectLike([1, 2, 3])).toBeTruthy(); 10 | }); 11 | 12 | it('isObjectLike(Function) should be false', () => { 13 | expect(isObjectLike(Function)).toBeFalsy(); 14 | }); 15 | 16 | it('isObjectLike(null) should be false', () => { 17 | expect(isObjectLike(null)).toBeFalsy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isPlainObject.test.ts: -------------------------------------------------------------------------------- 1 | import { isPlainObject } from '../../src'; 2 | 3 | describe('isPlainObject', () => { 4 | it('isPlainObject({}) should be true', () => { 5 | expect(isPlainObject({})).toBeTruthy(); 6 | }); 7 | 8 | it('isPlainObject([1, 2, 3]) should be false', () => { 9 | expect(isPlainObject([1, 2, 3])).toBeFalsy(); 10 | }); 11 | 12 | it('isPlainObject(Function) should be false', () => { 13 | expect(isPlainObject(Function)).toBeFalsy(); 14 | }); 15 | 16 | it('isPlainObject(null) should be false', () => { 17 | expect(isPlainObject(null)).toBeFalsy(); 18 | }); 19 | 20 | it('isPlainObject(undefined) should be false', () => { 21 | expect(isPlainObject(undefined)).toBeFalsy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isShallowEqual.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | import { isShallowEqual } from '../../src'; 3 | 4 | describe('isShallowEqual', () => { 5 | it('isShallowEqual({a:1, b: "b"}, {a: 1, b: "b"}) should be false', () => { 6 | expect(isShallowEqual({ a: 1, b: 'b' }, { a: 1, b: 'b' })).toBeTruthy(); 7 | }); 8 | 9 | it('isShallowEqual({a:1, b: [2, 3, 4]}, {a: 1, b: [2, 3, 4]}) should be false', () => { 10 | expect(isShallowEqual({ a: 1, b: [2, 3, 4] }, { a: 1, b: [2, 3, 4] })).toBeFalsy(); 11 | }); 12 | 13 | it('isShallowEqual({a:1, b: function}, {a: 1, b: function) should be false', () => { 14 | expect(isShallowEqual({ a: 1, b: () => {} }, { a: 1, b: () => {} })).toBeFalsy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/isString.test.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '../../src'; 2 | 3 | describe('isString', () => { 4 | it('isString("abc") should be true', () => { 5 | expect(isString('abc')).toBeTruthy(); 6 | }); 7 | 8 | it('isString(1) should be false', () => { 9 | expect(isString(1)).toBeFalsy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/pad.test.ts: -------------------------------------------------------------------------------- 1 | import { pad } from '../../src'; 2 | 3 | describe('pad', () => { 4 | it('pad("abc", 6)', () => { 5 | expect(pad('abc', 6)).toBe('abc '); 6 | }); 7 | 8 | it('pad("abc", 6, "_-")', () => { 9 | expect(pad('abc', 6, '_-')).toBe('abc_-_-_-'); 10 | }); 11 | 12 | it('pad("abc", 2)', () => { 13 | expect(pad('abc', 2)).toBe('abc'); 14 | }); 15 | 16 | it('pad("abc", 6, " ", "left")', () => { 17 | expect(pad('abc', 6, ' ', 'left')).toBe(' abc'); 18 | }); 19 | 20 | it('pad("abc", 5, "_-", "left")', () => { 21 | expect(pad('abc', 5, '_-', 'left')).toBe('_-_-abc'); 22 | }); 23 | 24 | it('pad("abc", 2, " ", "left")', () => { 25 | expect(pad('abc', 2, ' ', 'left')).toBe('abc'); 26 | }); 27 | 28 | it('pad("abc", 8, " ", "center")', () => { 29 | expect(pad('abc', 8, ' ', 'center')).toBe(' abc '); 30 | }); 31 | 32 | it('pad("abc", 8, "_-", "center")', () => { 33 | expect(pad('abc', 8, '_-', 'center')).toBe('_-_-abc_-_-_-'); 34 | }); 35 | 36 | it('pad("abc", 2, " ", "center")', () => { 37 | expect(pad('abc', 2, ' ', 'center')).toBe('abc'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/pickWithout.test.ts: -------------------------------------------------------------------------------- 1 | import { pickWithout } from '../../src'; 2 | 3 | describe('pickWithout', () => { 4 | it('pickWithout by string ', () => { 5 | const obj = { a: 1, b: 'b', c: [1, 2, 4] }; 6 | 7 | expect(pickWithout(obj, ['a', 'b'])).toEqual({ c: obj.c }); 8 | expect(pickWithout(obj, [])).toEqual(obj); 9 | }); 10 | 11 | it('pickWithout by regExp ', () => { 12 | const obj = { a: 1, b: 'b', c: [1, 2, 4], onClick: 'a', onMouse: 'a' }; 13 | 14 | expect(pickWithout(obj, [/^(on)/g])).toEqual({ a: 1, b: 'b', c: [1, 2, 4] }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/range.test.ts: -------------------------------------------------------------------------------- 1 | import { range } from '../../src'; 2 | 3 | describe('range', () => { 4 | it('range(1)', () => { 5 | expect(range(1)).toEqual([0]); 6 | }); 7 | 8 | it('range(1, 3)', () => { 9 | expect(range(1, 3)).toEqual([1, 2]); 10 | }); 11 | 12 | it('range(1, 10, 2)', () => { 13 | expect(range(1, 10, 2)).toEqual([1, 3, 5, 7, 9]); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/regression-linear.test.ts: -------------------------------------------------------------------------------- 1 | import { regressionLinear } from '../../src'; 2 | 3 | test('regressionLinear()', function () { 4 | const arr = [ 5 | { 6 | x: 1, 7 | y: 2 8 | }, 9 | { 10 | x: 2, 11 | y: 4 12 | } 13 | ]; 14 | const res = regressionLinear(arr); 15 | expect(res.coef).toEqual([0, 2]); 16 | expect(res.predict(1)).toBeCloseTo(2); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/stringWidth.test.ts: -------------------------------------------------------------------------------- 1 | import { stringWidth } from '../../src'; 2 | 3 | describe('string width', () => { 4 | it('char width 1', () => { 5 | expect(stringWidth('测试1')).toEqual(5); 6 | }); 7 | 8 | it('char width 2', () => { 9 | expect(stringWidth('の')).toEqual(2); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/substitute.test.ts: -------------------------------------------------------------------------------- 1 | import { substitute } from '../../src'; 2 | 3 | describe('substitute', () => { 4 | it('substitute("value: {value}", datum)', () => { 5 | const datum = { value: 30 }; 6 | expect(substitute('value: {value}', datum)).toEqual('value: 30'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/common/to-percent.test.ts: -------------------------------------------------------------------------------- 1 | import { toPercent } from '../../src'; 2 | 3 | test('percent string', function () { 4 | expect(toPercent('10%', 100)).toBe(10); 5 | expect(toPercent('200%', 100)).toBe(200); 6 | }); 7 | 8 | test('null / undefined', function () { 9 | expect((toPercent as any)(undefined, 100)).toBe(100); 10 | expect((toPercent as any)(null, 100)).toBe(100); 11 | }); 12 | 13 | test('percent value', function () { 14 | expect(toPercent(50, 100)).toBe(50); 15 | expect(toPercent(200, 100)).toBe(200); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/data-structure/obb.test.ts: -------------------------------------------------------------------------------- 1 | import { IOBBBounds, OBBBounds, obbSeparation } from '../../src/index'; 2 | 3 | describe('OBBBounds', () => { 4 | it('clone of OBBBounds', () => { 5 | const a = new OBBBounds(); 6 | a.setValue(10, 10, 100, 100, Math.PI / 4); 7 | const b = a.clone(); 8 | 9 | expect(a.x1).toBe(b.x1); 10 | expect(a.x2).toBe(b.x2); 11 | expect(a.y1).toBe(b.y1); 12 | expect(a.y2).toBe(b.y2); 13 | expect(a.angle).toBe(b.angle); 14 | }); 15 | 16 | it('distance of OBBBounds (simulation for xAxis label)', () => { 17 | const a = new OBBBounds(); 18 | a.setValue(0, 0, 100, 20, Math.PI / 4); 19 | const b = new OBBBounds(); 20 | b.setValue(0, 0, 100, 20, Math.PI / 4); 21 | b.translate(100, 0); 22 | expect(obbSeparation(a as any as IOBBBounds, b as any as IOBBBounds)).toBeCloseTo(100 / Math.sqrt(2) - 20); 23 | }); 24 | 25 | it('distance of OBBBounds (simulation for yAxis labels', () => { 26 | const a = new OBBBounds(); 27 | a.setValue(0, 0, 100, 20, Math.PI / 6); 28 | const b = new OBBBounds(); 29 | b.setValue(0, 0, 100, 20, Math.PI / 6); 30 | b.translate(0, 100); 31 | expect(obbSeparation(a as any as IOBBBounds, b as any as IOBBBounds)).toBeCloseTo((100 / 2) * Math.sqrt(3) - 20); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/data-structure/point.test.ts: -------------------------------------------------------------------------------- 1 | import { Point } from '../../src/index'; 2 | 3 | describe('Point', () => { 4 | it('new Point(x,y,x1,y1), x1 and y1 should be set', () => { 5 | const point = new Point(1, 1, 1, 1); 6 | 7 | expect(point.x).toBe(1); 8 | expect(point.y).toBe(1); 9 | expect(point.x1).toBe(1); 10 | expect(point.y1).toBe(1); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/dom.test.ts: -------------------------------------------------------------------------------- 1 | import { styleStringToObject, lowerCamelCaseToMiddle, isHTMLElement } from '../src'; 2 | 3 | describe('dom utils', () => { 4 | it('styleStringToObject', () => { 5 | expect(styleStringToObject(' color: red')).toEqual({ color: 'red' }); 6 | expect(styleStringToObject(' line-height: 12px;')).toEqual({ 'line-height': '12px' }); 7 | expect(styleStringToObject('font-size: 16px ; line-height: 12px;')).toEqual({ 8 | 'font-size': '16px', 9 | 'line-height': '12px' 10 | }); 11 | }); 12 | 13 | it('lowerCamelCaseToMiddle', () => { 14 | expect(lowerCamelCaseToMiddle('lineHeight')).toBe('line-height'); 15 | expect(lowerCamelCaseToMiddle('fontSize')).toBe('font-size'); 16 | }); 17 | 18 | it('isHTMLElement', () => { 19 | expect(isHTMLElement({})).toBeFalsy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/graphics/arc.test.ts: -------------------------------------------------------------------------------- 1 | import { calculateAnchorOfArc } from '../../src'; 2 | 3 | describe('arc', () => { 4 | it('calculateAnchorOfArc', () => { 5 | const arcAttr = { 6 | innerRadius: 50, 7 | outerRadius: 70, 8 | startAngle: 0, 9 | endAngle: 2 10 | }; 11 | 12 | expect(calculateAnchorOfArc(arcAttr, 'inner-start')).toEqual({ angle: 0, radius: 50 }); 13 | expect(calculateAnchorOfArc(arcAttr, 'inner-end')).toEqual({ angle: 2, radius: 50 }); 14 | expect(calculateAnchorOfArc(arcAttr, 'inner-middle')).toEqual({ angle: 1, radius: 50 }); 15 | expect(calculateAnchorOfArc(arcAttr, 'outer-start')).toEqual({ angle: 0, radius: 70 }); 16 | expect(calculateAnchorOfArc(arcAttr, 'outer-end')).toEqual({ angle: 2, radius: 70 }); 17 | expect(calculateAnchorOfArc(arcAttr, 'outer-middle')).toEqual({ angle: 1, radius: 70 }); 18 | expect(calculateAnchorOfArc(arcAttr, 'center')).toEqual({ angle: 1, radius: 60 }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/graphics/intersect.test.ts: -------------------------------------------------------------------------------- 1 | import { isIntersect, getIntersectPoint, getRectIntersect } from '../../src'; 2 | 3 | describe('intersects', () => { 4 | it('isIntersect', () => { 5 | expect(isIntersect([0, 0], [100, 100], [50, 20], [0, 100])).toBeTruthy(); 6 | }); 7 | 8 | it('getIntersectPoint', () => { 9 | const p = getIntersectPoint([0, 0], [100, 100], [50, 20], [0, 100]); 10 | expect(p[0]).toBeCloseTo(38.46153846153847); 11 | expect(p[1]).toBeCloseTo(38.46153846153847); 12 | }); 13 | 14 | it('getRectIntersect', () => { 15 | const p = getRectIntersect( 16 | { 17 | x1: 0, 18 | x2: 50, 19 | y1: 0, 20 | y2: 100 21 | }, 22 | { 23 | x1: 30, 24 | y1: 30, 25 | x2: 100, 26 | y2: 150 27 | }, 28 | true 29 | ); 30 | expect(p).toEqual({ x1: 30, x2: 50, y1: 30, y2: 100 }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as Util from '../src'; 2 | 3 | describe('VisUtil', () => { 4 | test('export EventEmitter', () => { 5 | expect(Util.EventEmitter).not.toBeUndefined; 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/logger/logger.test.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from './../../src/logger'; 2 | 3 | describe('logger', () => { 4 | it('test logger', () => { 5 | const logger = new Logger(); 6 | expect(logger.level()).toBe(0); 7 | logger.level(1); 8 | expect(logger.level()).toBe(1); 9 | expect(logger.canLogError()).toBe(true); 10 | expect(logger.canLogInfo()).toBe(false); 11 | }); 12 | 13 | it('test Logger.instance', () => { 14 | const logger = Logger.getInstance(1, 'log'); 15 | expect(logger.level()).toBe(1); 16 | Logger.setInstanceLevel(2); 17 | expect(logger.level()).toBe(2); 18 | Logger.clearInstance(); 19 | expect(logger).not.toBe(Logger.getInstance()); 20 | Logger.setInstance(logger); 21 | expect(logger).toBe(Logger.getInstance()); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/math/math.test.ts: -------------------------------------------------------------------------------- 1 | import { precisionAdd, precisionSub, pointAt } from '../../src/math'; 2 | 3 | describe('precision', () => { 4 | it('precision add', () => { 5 | expect(precisionAdd(0.1, 0.2)).toBe(0.3); 6 | }); 7 | it('precision sub', () => { 8 | expect(precisionSub(33, 23.33)).toBe(9.67); 9 | }); 10 | }); 11 | 12 | describe('pointAt', () => { 13 | it('pointAt of normal points', () => { 14 | expect(pointAt(1, 1, 3, 5, 0.5)).toEqual({ x: 2, y: 3 }); 15 | }); 16 | it('pointAt of undefined points', () => { 17 | expect(pointAt(1, undefined, 3, 5, 0.5)).toEqual({ x: 2, y: 5 }); 18 | expect(pointAt(undefined, 1, 3, 5, 0.5)).toEqual({ x: 3, y: 3 }); 19 | expect(pointAt(1, 1, 3, undefined, 0.5)).toEqual({ x: 2, y: undefined }); 20 | expect(pointAt(1, 1, undefined, 5, 0.5)).toEqual({ x: undefined, y: 3 }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/padding.test.ts: -------------------------------------------------------------------------------- 1 | import { normalizePadding } from '../src'; 2 | 3 | describe('padding', () => { 4 | it('normalizePadding', () => { 5 | expect(normalizePadding(3)).toEqual([3, 3, 3, 3]); 6 | expect(normalizePadding([3])).toEqual([3, 3, 3, 3]); 7 | expect(normalizePadding([3, 4])).toEqual([3, 4, 3, 4]); 8 | expect(normalizePadding([3, 4, 5])).toEqual([3, 4, 5, 4]); 9 | expect(normalizePadding([3, 4, 5, 6])).toEqual([3, 4, 5, 6]); 10 | // @ts-ignore 11 | expect(normalizePadding(null)).toEqual([0, 0, 0, 0]); 12 | 13 | expect(normalizePadding({})).toEqual([0, 0, 0, 0]); 14 | expect(normalizePadding({ top: 1 })).toEqual([1, 0, 0, 0]); 15 | expect(normalizePadding({ bottom: 1 })).toEqual([0, 0, 1, 0]); 16 | expect(normalizePadding({ left: 1 })).toEqual([0, 0, 0, 1]); 17 | expect(normalizePadding({ right: 1 })).toEqual([0, 1, 0, 0]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/text/textMeasure.test.ts: -------------------------------------------------------------------------------- 1 | import type { TextMeasureMethod } from '../../src'; 2 | import { TextMeasure } from '../../src'; 3 | import { TestTextMeasure } from './util'; 4 | 5 | describe('downgrade', () => { 6 | it('test without vrender', () => { 7 | const str = 'test'; 8 | const textMeasure = new TextMeasure({}); 9 | const { width: width1 } = textMeasure.measureWithNaiveCanvas(str); 10 | const { width: width2 } = textMeasure.fullMeasure(str); 11 | expect(width2).toBe(width1); 12 | }); 13 | }); 14 | 15 | describe('downgrade 1', () => { 16 | it('test without vrender', () => { 17 | const textMeasureTest = new TestTextMeasure({}); 18 | const testMethod = ['canvas', 'vrender'] as TextMeasureMethod[]; 19 | const { report } = textMeasureTest.test(testMethod, undefined); 20 | const { canvas, vrender } = report; 21 | expect(canvas.errMean.height).toEqual(vrender.errMean.height); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/vutils/__tests__/time/formatUtils.test.ts: -------------------------------------------------------------------------------- 1 | import { getFormatFromValue, getTimeFormatter } from '../../src'; 2 | 3 | describe('getFormatFromValue', () => { 4 | it('getFormatFromValue("2012") should be "YYYY"', () => { 5 | expect(getFormatFromValue('2012')).toBe('YYYY'); 6 | }); 7 | 8 | it('getFormatFromValue("2012-12") should be "YYYY"', () => { 9 | expect(getFormatFromValue('2012-12')).toBe('YYYY-MM'); 10 | }); 11 | }); 12 | 13 | describe('getTimeFormatter', () => { 14 | it('getTimeFormatter("YYYY")', () => { 15 | const formatter = getTimeFormatter('YYYY'); 16 | expect(formatter('2012-12-01 12:22:00')).toBe('2012'); 17 | 18 | expect(formatter('2012/12/01')).toBe('2012'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/vutils/bundler.config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @type {Partial} 4 | */ 5 | module.exports = { 6 | formats: ["cjs", "es", "umd"], 7 | name: "VUtils", 8 | umdOutputFilename: 'index' 9 | }; 10 | -------------------------------------------------------------------------------- /packages/vutils/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const baseJestConfig = require('@internal/jest-config/jest.base'); 3 | 4 | module.exports = { 5 | ...baseJestConfig, 6 | moduleNameMapper: { 7 | ...baseJestConfig.moduleNameMapper 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /packages/vutils/src/color/hexToRgb.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将 hex 格式颜色转换为 rgb 格式 3 | * @param str hex 格式的颜色值 4 | * @returns rgb 格式 5 | */ 6 | export default function hexToRgb(str: string): [number, number, number] { 7 | let r = ''; 8 | let g = ''; 9 | let b = ''; 10 | const strtIndex = str[0] === '#' ? 1 : 0; 11 | for (let i = strtIndex; i < str.length; i++) { 12 | if (str[i] === '#') { 13 | continue; 14 | } 15 | if (i < strtIndex + 2) { 16 | r += str[i]; 17 | } else if (i < strtIndex + 4) { 18 | g += str[i]; 19 | } else if (i < strtIndex + 6) { 20 | b += str[i]; 21 | } 22 | } 23 | const ri = parseInt(r, 16); 24 | const gi = parseInt(g, 16); 25 | const bi = parseInt(b, 16); 26 | return [ri, gi, bi]; 27 | } 28 | -------------------------------------------------------------------------------- /packages/vutils/src/color/hslToRgb.ts: -------------------------------------------------------------------------------- 1 | export default function hslToRgb(h: number, s: number, l: number) { 2 | s /= 100; 3 | l /= 100; 4 | 5 | const c = (1 - Math.abs(2 * l - 1)) * s; 6 | const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); 7 | const m = l - c / 2; 8 | let r = 0; 9 | let g = 0; 10 | let b = 0; 11 | 12 | if (0 <= h && h < 60) { 13 | r = c; 14 | g = x; 15 | b = 0; 16 | } else if (60 <= h && h < 120) { 17 | r = x; 18 | g = c; 19 | b = 0; 20 | } else if (120 <= h && h < 180) { 21 | r = 0; 22 | g = c; 23 | b = x; 24 | } else if (180 <= h && h < 240) { 25 | r = 0; 26 | g = x; 27 | b = c; 28 | } else if (240 <= h && h < 300) { 29 | r = x; 30 | g = 0; 31 | b = c; 32 | } else if (300 <= h && h < 360) { 33 | r = c; 34 | g = 0; 35 | b = x; 36 | } 37 | r = Math.round((r + m) * 255); 38 | g = Math.round((g + m) * 255); 39 | b = Math.round((b + m) * 255); 40 | return { r, g, b }; 41 | } 42 | -------------------------------------------------------------------------------- /packages/vutils/src/color/index.ts: -------------------------------------------------------------------------------- 1 | export { Color, RGB, DEFAULT_COLORS } from './Color'; 2 | export { default as hexToRgb } from './hexToRgb'; 3 | export { default as hslToRgb } from './hslToRgb'; 4 | export { default as rgbToHex } from './rgbToHex'; 5 | export { default as rgbToHsl } from './rgbToHsl'; 6 | export * from './interpolate'; 7 | -------------------------------------------------------------------------------- /packages/vutils/src/color/interpolate.ts: -------------------------------------------------------------------------------- 1 | import { RGB } from './Color'; 2 | 3 | export function interpolateRgb(colorA: RGB, colorB: RGB): (x: number) => RGB { 4 | const redA = colorA.r; 5 | const redB = colorB.r; 6 | const greenA = colorA.g; 7 | const greenB = colorB.g; 8 | const blueA = colorA.b; 9 | const blueB = colorB.b; 10 | const opacityA = colorA.opacity; 11 | const opacityB = colorB.opacity; 12 | 13 | return (t: number) => { 14 | const r = Math.round(redA * (1 - t) + redB * t); 15 | const g = Math.round(greenA * (1 - t) + greenB * t); 16 | const b = Math.round(blueA * (1 - t) + blueB * t); 17 | const opacity = opacityA * (1 - t) + opacityB * t; 18 | 19 | return new RGB(r, g, b, opacity); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/vutils/src/color/rgbToHex.ts: -------------------------------------------------------------------------------- 1 | export default function rgbToHex(r: number, g: number, b: number) { 2 | return Number((1 << 24) + (r << 16) + (g << 8) + b) 3 | .toString(16) 4 | .slice(1); 5 | } 6 | -------------------------------------------------------------------------------- /packages/vutils/src/color/rgbToHsl.ts: -------------------------------------------------------------------------------- 1 | export default function rgbToHsl(r: number, g: number, b: number) { 2 | r /= 255; 3 | g /= 255; 4 | b /= 255; 5 | 6 | // Find greatest and smallest channel values 7 | const cMin = Math.min(r, g, b); 8 | const cMax = Math.max(r, g, b); 9 | const delta = cMax - cMin; 10 | let h = 0; 11 | let s = 0; 12 | let l = 0; 13 | 14 | if (delta === 0) { 15 | h = 0; 16 | } 17 | // Red is max 18 | else if (cMax === r) { 19 | h = ((g - b) / delta) % 6; 20 | } 21 | // Green is max 22 | else if (cMax === g) { 23 | h = (b - r) / delta + 2; 24 | } 25 | // Blue is max 26 | else { 27 | h = (r - g) / delta + 4; 28 | } 29 | 30 | h = Math.round(h * 60); 31 | if (h < 0) { 32 | h += 360; 33 | } 34 | l = (cMax + cMin) / 2; 35 | 36 | // Calculate saturation 37 | s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); 38 | 39 | // Multiply l and s by 100 40 | s = +(s * 100).toFixed(1); 41 | l = +(l * 100).toFixed(1); 42 | return { 43 | h, 44 | s, 45 | l 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /packages/vutils/src/common/ascending.ts: -------------------------------------------------------------------------------- 1 | export function ascending(a: number, b: number) { 2 | return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; 3 | } 4 | -------------------------------------------------------------------------------- /packages/vutils/src/common/clamp.ts: -------------------------------------------------------------------------------- 1 | const clamp = function (input: number, min: number, max: number): number { 2 | if (input < min) { 3 | return min; 4 | } else if (input > max) { 5 | return max; 6 | } 7 | return input; 8 | }; 9 | 10 | export default clamp; 11 | -------------------------------------------------------------------------------- /packages/vutils/src/common/clampRange.ts: -------------------------------------------------------------------------------- 1 | const clampRange = (range: [number, number], min: number, max: number): [number, number] => { 2 | let [lowValue, highValue] = range; 3 | 4 | if (highValue < lowValue) { 5 | lowValue = range[1]; 6 | highValue = range[0]; 7 | } 8 | const span = highValue - lowValue; 9 | 10 | if (span >= max - min) { 11 | return [min, max]; 12 | } 13 | 14 | lowValue = Math.min(Math.max(lowValue, min), max - span); 15 | 16 | return [lowValue, lowValue + span]; 17 | }; 18 | 19 | export default clampRange; 20 | -------------------------------------------------------------------------------- /packages/vutils/src/common/clamper.ts: -------------------------------------------------------------------------------- 1 | export function clamper(a: number, b: number): (x: number) => number { 2 | let t; 3 | if (a > b) { 4 | t = a; 5 | a = b; 6 | b = t; 7 | } 8 | return (x: number) => { 9 | return Math.max(a, Math.min(b, x)); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /packages/vutils/src/common/constant.ts: -------------------------------------------------------------------------------- 1 | import isFunction from './isFunction'; 2 | 3 | const constant = (value: any) => { 4 | return isFunction(value) 5 | ? value 6 | : () => { 7 | return value; 8 | }; 9 | }; 10 | 11 | export default constant; 12 | -------------------------------------------------------------------------------- /packages/vutils/src/common/deviation.ts: -------------------------------------------------------------------------------- 1 | import { variance } from './variance'; 2 | 3 | export function deviation(values: any[], valueof?: (entry: any, index?: number, arr?: any[]) => number) { 4 | const v = variance(values, valueof); 5 | return v ? Math.sqrt(v) : v; 6 | } 7 | -------------------------------------------------------------------------------- /packages/vutils/src/common/extent.ts: -------------------------------------------------------------------------------- 1 | import isFunction from './isFunction'; 2 | import isNil from './isNil'; 3 | import isNumber from './isNumber'; 4 | /** 5 | * Return an array with minimum and maximum values, in the 6 | * form [min, max]. Ignores null, undefined, and NaN values. 7 | */ 8 | export const extent = (array: any[], func?: (val: any) => number) => { 9 | const valueGetter = isFunction(func) ? func : (val: any) => val; 10 | let min: number; 11 | let max: number; 12 | 13 | if (array && array.length) { 14 | const n = array.length; 15 | 16 | // find first valid value 17 | for (let i = 0; i < n; i += 1) { 18 | let value = valueGetter(array[i]); 19 | if (!isNil(value) && isNumber((value = +value)) && !Number.isNaN(value)) { 20 | if (isNil(min)) { 21 | min = value; 22 | max = value; 23 | } else { 24 | min = Math.min(min, value); 25 | max = Math.max(max, value); 26 | } 27 | } 28 | } 29 | 30 | return [min, max]; 31 | } 32 | 33 | return [min, max]; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/vutils/src/common/get.ts: -------------------------------------------------------------------------------- 1 | import type { Dict } from '../type'; 2 | import isString from './isString'; 3 | 4 | const get = (obj: Dict, path: string | string[], defaultValue?: any): any => { 5 | const paths = isString(path) ? (path as string).split('.') : path; 6 | 7 | for (let p = 0; p < paths.length; p++) { 8 | obj = obj ? obj[paths[p]] : undefined; 9 | } 10 | return obj === undefined ? defaultValue : obj; 11 | }; 12 | 13 | export default get; 14 | -------------------------------------------------------------------------------- /packages/vutils/src/common/getType.ts: -------------------------------------------------------------------------------- 1 | const getType = (value: any): string => { 2 | return {}.toString 3 | .call(value) 4 | .replace(/^\[object /, '') 5 | .replace(/]$/, ''); 6 | }; 7 | 8 | export default getType; 9 | -------------------------------------------------------------------------------- /packages/vutils/src/common/has.ts: -------------------------------------------------------------------------------- 1 | import type { Dict } from './../type'; 2 | 3 | /** Used to check objects for own properties. */ 4 | const hasOwnProperty = Object.prototype.hasOwnProperty; 5 | 6 | /** 7 | * Checks if `key` is a direct property of `object`. 8 | * 9 | * @param {Object} object The object to query. 10 | * @param {string} key The key to check. 11 | * @returns {boolean} Returns `true` if `key` exists, else `false`. 12 | * @example 13 | * 14 | * const object = { 'a': { 'b': 2 } } 15 | * const other = create({ 'a': create({ 'b': 2 }) }) 16 | * 17 | * has(object, 'a') 18 | * // => true 19 | * 20 | * has(other, 'a') 21 | * // => false 22 | */ 23 | const has = (object: Dict, key: any): boolean => { 24 | return object != null && hasOwnProperty.call(object, key); 25 | }; 26 | 27 | export default has; 28 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isArray.ts: -------------------------------------------------------------------------------- 1 | import isType from './isType'; 2 | 3 | const isArray = (value: any): value is Array => { 4 | return Array.isArray ? Array.isArray(value) : isType(value, 'Array'); 5 | }; 6 | 7 | export default isArray; 8 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isArrayLike.ts: -------------------------------------------------------------------------------- 1 | const isArrayLike = function (value: any): boolean { 2 | return value !== null && typeof value !== 'function' && Number.isFinite(value.length); 3 | }; 4 | 5 | export default isArrayLike; 6 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isBase64.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 迁移至 sirius 3 | * Checks if `value` is classified as a `Base64` string; 4 | * @param {string} The value to check. 5 | * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. 6 | */ 7 | const isBase64 = (value: string): boolean => { 8 | const exp = /^data:image\/(?:gif|png|jpeg|bmp|webp|svg\+xml)(?:;charset=utf-8)?;base64,(?:[A-Za-z0-9]|[+/])+={0,2}/g; 9 | const urlPattern = new RegExp(exp); 10 | return urlPattern.test(value); 11 | }; 12 | 13 | export default isBase64; 14 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isBoolean.ts: -------------------------------------------------------------------------------- 1 | import isType from './isType'; 2 | 3 | /** 4 | * Checks if `value` is classified as a boolean primitive or object. 5 | * @param {*} value The value to check. 6 | * @param {boolean} fuzzy Whether to perform fuzzy judgment, for better performance,default to `false`. 7 | * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. 8 | * @example 9 | * 10 | * isBoolean(false) 11 | * // => true 12 | * 13 | * isBoolean(null) 14 | * // => false 15 | */ 16 | const isBoolean = (value: any, fuzzy: boolean = false): value is boolean => { 17 | if (fuzzy) { 18 | return typeof value === 'boolean'; 19 | } 20 | return value === true || value === false || isType(value, 'Boolean'); 21 | }; 22 | 23 | export default isBoolean; 24 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isDate.ts: -------------------------------------------------------------------------------- 1 | import isType from './isType'; 2 | 3 | const isDate = (value: any): value is Date => { 4 | return isType(value, 'Date'); 5 | }; 6 | 7 | export default isDate; 8 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isEmpty.ts: -------------------------------------------------------------------------------- 1 | import isNil from './isNil'; 2 | import isArrayLike from './isArrayLike'; 3 | import getType from './getType'; 4 | import isPrototype from './isPrototype'; 5 | 6 | const hasOwnProperty = Object.prototype.hasOwnProperty; 7 | 8 | function isEmpty(value: any): boolean { 9 | /** 10 | * isEmpty(null) => true 11 | * isEmpty() => true 12 | * isEmpty(true) => true 13 | * isEmpty(1) => true 14 | * isEmpty([1, 2, 3]) => false 15 | * isEmpty('abc') => false 16 | * isEmpty({ a: 1 }) => false 17 | */ 18 | if (isNil(value)) { 19 | return true; 20 | } 21 | if (isArrayLike(value)) { 22 | return !value.length; 23 | } 24 | // TODO: 这里需要优化下 25 | const type = getType(value); 26 | if (type === 'Map' || type === 'Set') { 27 | return !value.size; 28 | } 29 | if (isPrototype(value)) { 30 | return !Object.keys(value).length; 31 | } 32 | for (const key in value) { 33 | if (hasOwnProperty.call(value, key)) { 34 | return false; 35 | } 36 | } 37 | return true; 38 | } 39 | 40 | export default isEmpty; 41 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isFunction.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `value` is classified as a `Function` object. 3 | * 4 | * @param {*} value The value to check. 5 | * @returns {boolean} Returns `true` if `value` is a function, else `false`. 6 | * @example 7 | * 8 | * isFunction(class Any{}) 9 | * // => true 10 | * 11 | * isFunction(() => {}) 12 | * // => true 13 | * 14 | * isFunction(async () => {}) 15 | * // => true 16 | * 17 | * isFunction(function * Any() {}) 18 | * // => true 19 | * 20 | * isFunction(Math.round) 21 | * // => true 22 | * 23 | * isFunction(/abc/) 24 | * // => false 25 | */ 26 | // eslint-disable-next-line @typescript-eslint/ban-types 27 | const isFunction = (value: any): value is Function => { 28 | return typeof value === 'function'; 29 | }; 30 | 31 | export default isFunction; 32 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isNil.ts: -------------------------------------------------------------------------------- 1 | const isNil = (value: any): value is null | undefined => { 2 | return value === null || value === undefined; 3 | }; 4 | 5 | export default isNil; 6 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isNull.ts: -------------------------------------------------------------------------------- 1 | const isNull = (value: any): value is null => { 2 | return value === null; 3 | }; 4 | 5 | export default isNull; 6 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isNumber.ts: -------------------------------------------------------------------------------- 1 | import isType from './isType'; 2 | 3 | /** 4 | * Checks if `value` is classified as a `Number` primitive or object. 5 | * 6 | * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are 7 | * classified as numbers, use the `Number.isFinite` method. 8 | * 9 | * @param {*} value The value to check. 10 | * @param {boolean} fuzzy Whether to perform fuzzy judgment, for better performance,default to `false`. 11 | * @returns {boolean} Returns `true` if `value` is a number, else `false`. 12 | * @example 13 | * 14 | * isNumber(3) 15 | * // => true 16 | * 17 | * isNumber(Number.MIN_VALUE) 18 | * // => true 19 | * 20 | * isNumber(Infinity) 21 | * // => true 22 | * 23 | * isNumber('3') 24 | * // => false 25 | */ 26 | const isNumber = (value: any, fuzzy: boolean = false): value is number => { 27 | const type = typeof value; 28 | if (fuzzy) { 29 | return type === 'number'; 30 | } 31 | return type === 'number' || isType(value, 'Number'); 32 | }; 33 | 34 | export default isNumber; 35 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isNumeric.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `value` is classified as a legal string number 3 | * 4 | * @param {*} value The string value to check. 5 | * @returns {boolean} Returns `true` if `value` is a legal string number, else `false`. 6 | * @example 7 | * 8 | * isNumeric(1) 9 | * // => false 10 | * 11 | * isNumeric('2.0') 12 | * // => true 13 | * 14 | * isNumeric('3a') 15 | * // => false 16 | * 17 | * isNumeric('4.a') 18 | * // => false 19 | * 20 | * isNumeric(Infinity) 21 | * // => false 22 | * 23 | * isNumeric('01') 24 | * // => true 25 | */ 26 | const isNumeric = (value: string): boolean => { 27 | if (typeof value !== 'string') { 28 | return false; 29 | } 30 | return !isNaN(Number(value)) && !isNaN(parseFloat(value)); 31 | }; 32 | 33 | export default isNumeric; 34 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isObject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `value` is the 3 | * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) 4 | * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 5 | * 6 | * @param {*} value The value to check. 7 | * @returns {boolean} Returns `true` if `value` is an object, else `false`. 8 | * @example 9 | * 10 | * isObject({}) 11 | * // => true 12 | * 13 | * isObject([1, 2, 3]) 14 | * // => true 15 | * 16 | * isObject(Function) 17 | * // => true 18 | * 19 | * isObject(null) 20 | * // => false 21 | */ 22 | // eslint-disable-next-line @typescript-eslint/ban-types 23 | const isObject = (value: any): value is T => { 24 | const type = typeof value; 25 | return (value !== null && type === 'object') || type === 'function'; 26 | }; 27 | 28 | export default isObject; 29 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isObjectLike.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if `value` is object-like. A value is object-like if it's not `null` 3 | * and has a `typeof` result of "object". 4 | * 5 | * @param {*} value The value to check. 6 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 7 | * @example 8 | * 9 | * isObjectLike({}) 10 | * // => true 11 | * 12 | * isObjectLike([1, 2, 3]) 13 | * // => true 14 | * 15 | * isObjectLike(Function) 16 | * // => false 17 | * 18 | * isObjectLike(null) 19 | * // => false 20 | */ 21 | // eslint-disable-next-line @typescript-eslint/ban-types 22 | const isObjectLike = (value: any): value is object | Function | Array => { 23 | return typeof value === 'object' && value !== null; 24 | }; 25 | 26 | export default isObjectLike; 27 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isPlainObject.ts: -------------------------------------------------------------------------------- 1 | import isObjectLike from './isObjectLike'; 2 | import isType from './isType'; 3 | 4 | /** 5 | * @see https://github.com/lodash/lodash/blob/master/isPlainObject.js 6 | * Checks if `value` is a plain object, that is, an object created by the 7 | * `Object` constructor or one with a `[[Prototype]]` of `null`. 8 | * 9 | * @param {*} value The value to check. 10 | * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. 11 | * @example 12 | * 13 | * function Foo() { 14 | * this.a = 1 15 | * } 16 | * 17 | * isPlainObject(new Foo) 18 | * // => false 19 | * 20 | * isPlainObject([1, 2, 3]) 21 | * // => false 22 | * 23 | * isPlainObject({ 'x': 0, 'y': 0 }) 24 | * // => true 25 | * 26 | * isPlainObject(Object.create(null)) 27 | * // => true 28 | */ 29 | // eslint-disable-next-line @typescript-eslint/ban-types 30 | const isPlainObject = function (value: any): value is object { 31 | if (!isObjectLike(value) || !isType(value, 'Object')) { 32 | return false; 33 | } 34 | if (Object.getPrototypeOf(value) === null) { 35 | return true; 36 | } 37 | let proto = value; 38 | while (Object.getPrototypeOf(proto) !== null) { 39 | proto = Object.getPrototypeOf(proto); 40 | } 41 | return Object.getPrototypeOf(value) === proto; 42 | }; 43 | 44 | export default isPlainObject; 45 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isPrototype.ts: -------------------------------------------------------------------------------- 1 | const objectProto = Object.prototype; 2 | 3 | const isPrototype = function (value: any): boolean { 4 | const Ctor = value && value.constructor; 5 | const proto = (typeof Ctor === 'function' && Ctor.prototype) || objectProto; 6 | return value === proto; 7 | }; 8 | 9 | export default isPrototype; 10 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isRegExp.ts: -------------------------------------------------------------------------------- 1 | import isType from './isType'; 2 | 3 | const isRegExp = (value: any): value is RegExp => { 4 | return isType(value, 'RegExp'); 5 | }; 6 | 7 | export default isRegExp; 8 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isShallowEqual.ts: -------------------------------------------------------------------------------- 1 | import isArray from './isArray'; 2 | import isObject from './isObject'; 3 | 4 | function is(x: any, y: any) { 5 | if (x === y) { 6 | return x !== 0 || y !== 0 || 1 / x === 1 / y; 7 | } 8 | // eslint-disable-next-line no-self-compare 9 | return x !== x && y !== y; // NaN == NaN 10 | } 11 | 12 | function length(obj: any) { 13 | if (isArray(obj)) { 14 | return obj.length; 15 | } 16 | if (isObject(obj)) { 17 | return Object.keys(obj).length; 18 | } 19 | return 0; 20 | } 21 | 22 | export function isShallowEqual(objA: any, objB: any) { 23 | if (is(objA, objB)) { 24 | return true; 25 | } 26 | 27 | if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { 28 | return false; 29 | } 30 | 31 | if (isArray(objA) !== isArray(objB)) { 32 | return false; 33 | } 34 | 35 | if (length(objA) !== length(objB)) { 36 | return false; 37 | } 38 | 39 | let ret = true; 40 | 41 | Object.keys(objA).forEach((k: any) => { 42 | const v = objA[k]; 43 | 44 | if (!is(v, objB[k])) { 45 | ret = false; 46 | return ret; 47 | } 48 | return true; 49 | }); 50 | 51 | return ret; 52 | } 53 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isString.ts: -------------------------------------------------------------------------------- 1 | import isType from './isType'; 2 | 3 | /** 4 | * Checks if `value` is classified as a `String` primitive or object. 5 | * @param {*} value The value to check. 6 | * @param {boolean} fuzzy Whether to perform fuzzy judgment, for better performance,default to `false`. 7 | * @returns {boolean} Returns `true` if `value` is a string, else `false`. 8 | * @example 9 | * 10 | * isString('abc') 11 | * // => true 12 | * 13 | * isString(1) 14 | * // => false 15 | */ 16 | const isString = (value: any, fuzzy: boolean = false): value is string => { 17 | const type = typeof value; 18 | if (fuzzy) { 19 | return type === 'string'; 20 | } 21 | 22 | return type === 'string' || isType(value, 'String'); 23 | }; 24 | 25 | export default isString; 26 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * check value type 3 | * @param value the value to check 4 | * @param type type 5 | * @returns 6 | */ 7 | const isType = (value: any, type: string): boolean => Object.prototype.toString.call(value) === `[object ${type}]`; 8 | 9 | export default isType; 10 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isUndefined.ts: -------------------------------------------------------------------------------- 1 | const isUndefined = (value: any): value is undefined => { 2 | return value === undefined; 3 | }; 4 | 5 | export default isUndefined; 6 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isValid.ts: -------------------------------------------------------------------------------- 1 | const isValid = (value: T): value is NonNullable => { 2 | return value !== null && value !== undefined; 3 | }; 4 | 5 | export default isValid; 6 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isValidNumber.ts: -------------------------------------------------------------------------------- 1 | import isNumber from './isNumber'; 2 | 3 | const isValidNumber = (value: any): value is number => { 4 | // isFinate 包含来 isNaN 的判断 5 | return isNumber(value) && Number.isFinite(value); 6 | }; 7 | 8 | export default isValidNumber; 9 | -------------------------------------------------------------------------------- /packages/vutils/src/common/isValidUrl.ts: -------------------------------------------------------------------------------- 1 | export const isValidUrl = (value: string) => { 2 | // TODO: 匹配其他形式的 url 3 | const exp = /^(http(s)?:\/\/)\w+[^\s]+(\.[^\s]+){1,}$/; 4 | const urlPattern = new RegExp(exp); 5 | return urlPattern.test(value); 6 | }; 7 | 8 | export default isValidUrl; 9 | -------------------------------------------------------------------------------- /packages/vutils/src/common/lowerFirst.ts: -------------------------------------------------------------------------------- 1 | const lowerFirst = function (str: string): string { 2 | return str.charAt(0).toLowerCase() + str.substring(1); 3 | }; 4 | 5 | export default lowerFirst; 6 | -------------------------------------------------------------------------------- /packages/vutils/src/common/median.ts: -------------------------------------------------------------------------------- 1 | import { ascending } from './ascending'; 2 | import { quantileSorted } from './quantileSorted'; 3 | 4 | export const median = (values: number[], isSorted?: boolean) => { 5 | let sorted = values; 6 | if (isSorted !== true) { 7 | sorted = values.sort(ascending); 8 | } 9 | 10 | return quantileSorted(sorted, 0.5); 11 | }; 12 | -------------------------------------------------------------------------------- /packages/vutils/src/common/memoize.ts: -------------------------------------------------------------------------------- 1 | export const memoize = any>(func: T) => { 2 | let lastArgs: any[] = null; 3 | let lastResult: any = null; 4 | 5 | return ((...args: any[]) => { 6 | if (lastArgs && args.every((val, i) => val === lastArgs[i])) { 7 | return lastResult; 8 | } 9 | 10 | lastArgs = args; 11 | lastResult = (func as any)(...args); 12 | 13 | return lastResult; 14 | }) as T; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/vutils/src/common/number.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 处理数值相关的方法 3 | */ 4 | 5 | const DEFAULT_ABSOLUTE_TOLERATE = 1e-10; 6 | const DEFAULT_RELATIVE_TOLERATE = 1e-10; 7 | 8 | /** 9 | * 判断两数是否接近相等,函数参数参照 python isClose 方法 10 | * @param {number} a 11 | * @param {number} b 12 | * @param refTol 指定的相对容差比例,将乘以两数的最大值作为相对容差 13 | * @param absTol 指定的绝对容差 14 | */ 15 | export function isNumberClose( 16 | a: number, 17 | b: number, 18 | relTol: number = DEFAULT_RELATIVE_TOLERATE, 19 | absTol: number = DEFAULT_ABSOLUTE_TOLERATE 20 | ) { 21 | const abs = absTol; 22 | const rel = relTol * Math.max(a, b); 23 | return Math.abs(a - b) <= Math.max(abs, rel); 24 | } 25 | 26 | /** 27 | * 判断 a 是否大于 b,并排除容差范围 28 | * @param a 29 | * @param b 30 | * @param relTol 指定的相对容差比例,将乘以两数的最大值作为相对容差 31 | * @param absTol 指定的绝对容差 32 | */ 33 | export function isGreater(a: number, b: number, relTol?: number, absTol?: number) { 34 | return a > b && !isNumberClose(a, b, relTol, absTol); 35 | } 36 | 37 | /** 38 | * 判断 a 是否小于 b,并排除容差范围 39 | * @param a 40 | * @param b 41 | * @param relTol 指定的相对容差比例,将乘以两数的最大值作为相对容差 42 | * @param absTol 指定的绝对容差 43 | */ 44 | export function isLess(a: number, b: number, relTol?: number, absTol?: number) { 45 | return a < b && !isNumberClose(a, b, relTol, absTol); 46 | } 47 | -------------------------------------------------------------------------------- /packages/vutils/src/common/pad.ts: -------------------------------------------------------------------------------- 1 | const repeat = (str: string | number, repeatCount: number = 0) => { 2 | let s = ''; 3 | let i = repeatCount - 1; 4 | while (i >= 0) { 5 | s = `${s}${str}`; 6 | i -= 1; 7 | } 8 | return s; 9 | }; 10 | 11 | /** 12 | * Pads `string` on the left and right, left or right sides if it's shorter than `length`. 13 | * Padding characters are truncated if they can't be evenly divided by `length`. 14 | * 15 | * @since 3.0.0 16 | * @category String 17 | * @param {string} [string=''] The string to pad. 18 | * @param {number} [length=0] The padding length. 19 | * @param {string} [chars=' '] The string used as padding. 20 | * @returns {string} Returns the padded string. 21 | * @example 22 | * 23 | * pad('abc', 8) 24 | * // => ' abc ' 25 | * 26 | * pad('abc', 8, '_-') 27 | * // => '_-abc_-_' 28 | * 29 | * pad('abc', 2) 30 | * // => 'abc' 31 | */ 32 | const pad = (str: string | number, length: number, padChar: string = ' ', align: string = 'right') => { 33 | const c = padChar; 34 | const s = str + ''; 35 | const n = length - s.length; 36 | 37 | if (n <= 0) { 38 | return s; 39 | } 40 | 41 | if (align === 'left') { 42 | return repeat(c, n) + s; 43 | } 44 | 45 | return align === 'center' ? repeat(c, Math.floor(n / 2)) + s + repeat(c, Math.ceil(n / 2)) : s + repeat(c, n); 46 | }; 47 | 48 | export default pad; 49 | -------------------------------------------------------------------------------- /packages/vutils/src/common/pick.ts: -------------------------------------------------------------------------------- 1 | import isPlainObject from './isPlainObject'; 2 | 3 | const hasOwnProperty = Object.prototype.hasOwnProperty; 4 | 5 | export default function pick(obj: T, keys: Array): Pick { 6 | if (!obj || !isPlainObject(obj)) { 7 | return obj; 8 | } 9 | const result = {} as Pick; 10 | keys.forEach(k => { 11 | if (hasOwnProperty.call(obj, k)) { 12 | result[k] = obj[k]; 13 | } 14 | }); 15 | return result; 16 | } 17 | -------------------------------------------------------------------------------- /packages/vutils/src/common/pickWithout.ts: -------------------------------------------------------------------------------- 1 | import isPlainObject from './isPlainObject'; 2 | import isString from './isString'; 3 | 4 | export default function pickWithout>(obj: T, keys: (string | RegExp)[]): Partial { 5 | if (!obj || !isPlainObject(obj)) { 6 | return obj; 7 | } 8 | const result = {}; 9 | 10 | Object.keys(obj).forEach((k: string) => { 11 | const v = obj[k]; 12 | 13 | let match = false; 14 | 15 | keys.forEach(itKey => { 16 | if (isString(itKey) && itKey === k) { 17 | match = true; 18 | } else if (itKey instanceof RegExp && k.match(itKey)) { 19 | match = true; 20 | } 21 | }); 22 | 23 | if (!match) { 24 | result[k] = v; 25 | } 26 | }); 27 | 28 | return result as Partial; 29 | } 30 | -------------------------------------------------------------------------------- /packages/vutils/src/common/quantileSorted.ts: -------------------------------------------------------------------------------- 1 | import { toNumber } from './toNumber'; 2 | 3 | export function quantileSorted( 4 | values: any[], 5 | percent: number, 6 | valueof: (entry: any, index: number, arr: any[]) => number = toNumber 7 | ) { 8 | const n = values.length; 9 | if (!n) { 10 | return; 11 | } 12 | if (percent <= 0 || n < 2) { 13 | return valueof(values[0], 0, values); 14 | } 15 | if (percent >= 1) { 16 | return valueof(values[n - 1], n - 1, values); 17 | } 18 | const i = (n - 1) * percent; 19 | const i0 = Math.floor(i); 20 | const value0 = valueof(values[i0], i0, values); 21 | const value1 = valueof(values[i0 + 1], i0 + 1, values); 22 | return value0 + (value1 - value0) * (i - i0); 23 | } 24 | -------------------------------------------------------------------------------- /packages/vutils/src/common/random.ts: -------------------------------------------------------------------------------- 1 | export function seedRandom(seed: number) { 2 | return parseFloat('0.' + Math.sin(seed).toString().substring(6)); 3 | } 4 | 5 | /* Adapted from vega by University of Washington Interactive Data Lab 6 | * https://vega.github.io/vega/ 7 | * Licensed under the BSD-3-Clause 8 | 9 | * url: https://github.com/vega/vega/blob/main/packages/vega-statistics/src/lcg.js 10 | * License: https://github.com/vega/vega/blob/main/LICENSE 11 | * @license 12 | */ 13 | 14 | // https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use 15 | const a = 1664525; 16 | const c = 1013904223; 17 | const m = 4294967296; // 2^32 18 | 19 | export function randomLCG(initS: number = 1) { 20 | let s = initS; 21 | return () => (s = (a * s + c) % m) / m; 22 | } 23 | 24 | /** 25 | * 随机拟合 26 | */ 27 | export const fakeRandom = () => { 28 | let i = -1; 29 | const arr = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; 30 | return () => { 31 | i = (i + 1) % arr.length; 32 | return arr[i]; 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/vutils/src/common/range.ts: -------------------------------------------------------------------------------- 1 | import isValid from './isValid'; 2 | 3 | export function range(start: number, stop?: number, step?: number): number[] { 4 | if (!isValid(stop)) { 5 | stop = start; 6 | start = 0; 7 | } 8 | if (!isValid(step)) { 9 | step = 1; 10 | } 11 | 12 | let i = -1; 13 | const n = Math.max(0, Math.ceil((stop - start) / step)) | 0; 14 | const range = new Array(n); 15 | 16 | while (++i < n) { 17 | range[i] = start + i * step; 18 | } 19 | 20 | return range; 21 | } 22 | -------------------------------------------------------------------------------- /packages/vutils/src/common/substitute.ts: -------------------------------------------------------------------------------- 1 | import type { Dict } from '../type'; 2 | 3 | export default function substitute(str: string, o: Dict) { 4 | if (!str || !o) { 5 | return str; 6 | } 7 | return str.replace(/\\?\{([^{}]+)\}/g, (match, name): any => { 8 | if (match.charAt(0) === '\\') { 9 | return match.slice(1); 10 | } 11 | return o[name] === undefined ? '' : o[name]; 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /packages/vutils/src/common/throttle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://github.com/lodash/lodash/blob/master/throttle.js 3 | */ 4 | import debounce from './debounce'; 5 | import isObject from './isObject'; 6 | 7 | function throttle( 8 | func: (...args: T[]) => S, 9 | wait: number, 10 | options?: { leading?: boolean; trailing?: boolean } 11 | ): (...args: T[]) => S { 12 | let leading = true; 13 | let trailing = true; 14 | 15 | if (typeof func !== 'function') { 16 | throw new TypeError('Expected a function'); 17 | } 18 | if (isObject(options)) { 19 | leading = 'leading' in options ? !!options.leading : leading; 20 | trailing = 'trailing' in options ? !!options.trailing : trailing; 21 | } 22 | return debounce(func, wait, { 23 | leading, 24 | trailing, 25 | maxWait: wait 26 | }); 27 | } 28 | 29 | export default throttle; 30 | -------------------------------------------------------------------------------- /packages/vutils/src/common/tickStep.ts: -------------------------------------------------------------------------------- 1 | const e10 = Math.sqrt(50); 2 | const e5 = Math.sqrt(10); 3 | const e2 = Math.sqrt(2); 4 | 5 | export function tickStep(start: number, stop: number, count: number) { 6 | const step0 = Math.abs(stop - start) / Math.max(0, count); 7 | let step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)); 8 | const error = step0 / step1; 9 | 10 | if (error >= e10) { 11 | step1 *= 10; 12 | } else if (error >= e5) { 13 | step1 *= 5; 14 | } else if (error >= e2) { 15 | step1 *= 2; 16 | } 17 | return stop < start ? -step1 : step1; 18 | } 19 | -------------------------------------------------------------------------------- /packages/vutils/src/common/toNumber.ts: -------------------------------------------------------------------------------- 1 | export function toNumber(a: any) { 2 | return Number(a); 3 | } 4 | -------------------------------------------------------------------------------- /packages/vutils/src/common/toPercent.ts: -------------------------------------------------------------------------------- 1 | import isNil from './isNil'; 2 | import isString from './isString'; 3 | 4 | export const toPercent = (percent: string | number, total: number) => { 5 | if (isNil(percent)) { 6 | return total; 7 | } 8 | 9 | return isString(percent) ? (total * parseFloat(percent as string)) / 100 : percent; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/vutils/src/common/toValidNumber.ts: -------------------------------------------------------------------------------- 1 | import isValidNumber from './isValidNumber'; 2 | 3 | export function toValidNumber(v: any) { 4 | if (isValidNumber(v)) { 5 | return v; 6 | } 7 | const value = +v; 8 | return isValidNumber(value) ? value : 0; 9 | } 10 | -------------------------------------------------------------------------------- /packages/vutils/src/common/truncate.ts: -------------------------------------------------------------------------------- 1 | import isNil from './isNil'; 2 | 3 | const truncate = ( 4 | str: string | number, 5 | length: number, 6 | align: 'left' | 'center' | 'right' | unknown = 'right', 7 | ellipsis?: string 8 | ) => { 9 | const e = !isNil(ellipsis) ? ellipsis : '\u2026'; 10 | const s = str + ''; 11 | const n = s.length; 12 | const l = Math.max(0, length - e.length); 13 | 14 | if (n <= length) { 15 | return s; 16 | } 17 | 18 | if (align === 'left') { 19 | return e + s.slice(n - l); 20 | } 21 | 22 | return align === 'center' ? s.slice(0, Math.ceil(l / 2)) + e + s.slice(n - Math.floor(l / 2)) : s.slice(0, l) + e; 23 | }; 24 | 25 | export default truncate; 26 | -------------------------------------------------------------------------------- /packages/vutils/src/common/upperFirst.ts: -------------------------------------------------------------------------------- 1 | const upperFirst = function (str: string): string { 2 | return str.charAt(0).toUpperCase() + str.substring(1); 3 | }; 4 | 5 | export default upperFirst; 6 | -------------------------------------------------------------------------------- /packages/vutils/src/common/uuid.ts: -------------------------------------------------------------------------------- 1 | // TODO: 性能不是很好,需要优化,后续升级到最新版本的 canvas 之后就可以删除了 2 | /** 3 | * 生成uuid字符串. 4 | * 参考https://github.com/yangjunning/issues/issues/56 5 | * @param len 6 | * @param radix 7 | * @returns 8 | */ 9 | const uuid = (len: number, radix: number): string => { 10 | const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 11 | const uuid = []; 12 | let i; 13 | radix = radix || chars.length; 14 | 15 | if (len) { 16 | // Compact form 17 | for (i = 0; i < len; i++) { 18 | uuid[i] = chars[0 | (Math.random() * radix)]; 19 | } 20 | } else { 21 | // rfc4122, version 4 form 22 | let r; 23 | 24 | // rfc4122 requires these characters 25 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 26 | uuid[14] = '4'; 27 | 28 | // Fill in random data. At i==19 set the high bits of clock sequence as 29 | // per rfc4122, sec. 4.1.5 30 | for (i = 0; i < 36; i++) { 31 | if (!uuid[i]) { 32 | r = 0 | (Math.random() * 16); 33 | uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]; 34 | } 35 | } 36 | } 37 | 38 | return uuid.join(''); 39 | }; 40 | 41 | export default uuid; 42 | -------------------------------------------------------------------------------- /packages/vutils/src/common/variance.ts: -------------------------------------------------------------------------------- 1 | export function variance(values: any[], valueof?: (entry: any, index?: number, arr?: any[]) => number) { 2 | let count = 0; 3 | let delta; 4 | let mean = 0; 5 | let sum = 0; 6 | if (valueof === undefined) { 7 | for (let value of values) { 8 | if (value != null && (value = +value) >= value) { 9 | delta = value - mean; 10 | mean += delta / ++count; 11 | sum += delta * (value - mean); 12 | } 13 | } 14 | } else { 15 | let index = -1; 16 | for (let value of values) { 17 | if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { 18 | delta = value - mean; 19 | mean += delta / ++count; 20 | sum += delta * (value - mean); 21 | } 22 | } 23 | } 24 | if (count > 1) { 25 | return sum / (count - 1); 26 | } 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /packages/vutils/src/common/zero.ts: -------------------------------------------------------------------------------- 1 | export const zero = (_: any) => 0; 2 | -------------------------------------------------------------------------------- /packages/vutils/src/data-structure/index.ts: -------------------------------------------------------------------------------- 1 | // 基于 array 和 object 的 HashTable 2 | export * from './hashTable'; 3 | // Point 4 | export * from './point'; 5 | export * from './bounds'; 6 | export * from './matrix'; 7 | -------------------------------------------------------------------------------- /packages/vutils/src/fmin/blas1.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from fmin by Ben Frederickson 2 | * https://github.com/benfred/fmin 3 | * Licensed under the BSD-3-Clause 4 | 5 | * url: https://github.com/benfred/fmin/blob/master/src/blas1.js 6 | * License: https://github.com/benfred/fmin/blob/master/LICENSE 7 | * @license 8 | */ 9 | 10 | import { dotProduct } from '../math'; 11 | 12 | // need some basic operations on vectors, rather than adding a dependency, 13 | // just define here 14 | export function zeros(x: number): number[] { 15 | const r = new Array(x); 16 | for (let i = 0; i < x; ++i) { 17 | r[i] = 0; 18 | } 19 | return r; 20 | } 21 | export function zerosM(x: number, y: number) { 22 | return zeros(x).map(function () { 23 | return zeros(y); 24 | }); 25 | } 26 | 27 | export function norm2(a: number[]) { 28 | return Math.sqrt(dotProduct(a, a)); 29 | } 30 | 31 | export function scale(ret: number[], value: number[], c: number) { 32 | for (let i = 0; i < value.length; ++i) { 33 | ret[i] = value[i] * c; 34 | } 35 | } 36 | 37 | export function weightedSum(ret: number[], w1: number, v1: number[], w2: number, v2: number[]) { 38 | for (let j = 0; j < ret.length; ++j) { 39 | ret[j] = w1 * v1[j] + w2 * v2[j]; 40 | } 41 | } 42 | 43 | export function gemv(output: number[], A: number[][], x: number[]) { 44 | for (let i = 0; i < output.length; ++i) { 45 | output[i] = dotProduct(A[i], x); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/vutils/src/fmin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './blas1'; 2 | export * from './nelder-mead'; 3 | export * from './conjugate-gradient'; 4 | -------------------------------------------------------------------------------- /packages/vutils/src/format/number/formatDecimal.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-time-format by Mike Bostock 2 | * https://github.com/d3/d3-format 3 | * Licensed under the ISC 4 | 5 | * License: https://github.com/d3/d3-format/blob/main/LICENSE 6 | * @license 7 | */ 8 | export function formatDecimal(x: number) { 9 | return Math.abs((x = Math.round(x))) >= 1e21 ? x.toLocaleString('en').replace(/,/g, '') : x.toString(10); 10 | } 11 | 12 | export function formatDecimalParts(x: number, p?: number): [string, number] | null { 13 | const _x = p ? x.toExponential(p - 1) : x.toExponential(); 14 | const i = _x.indexOf('e'); 15 | if (i < 0) { 16 | return null; // NaN, ±Infinity 17 | } 18 | const coefficient = _x.slice(0, i); 19 | 20 | return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +_x.slice(i + 1)]; 21 | } 22 | -------------------------------------------------------------------------------- /packages/vutils/src/format/number/formatGroup.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-time-format by Mike Bostock 2 | * https://github.com/d3/d3-format 3 | * Licensed under the ISC 4 | 5 | * License: https://github.com/d3/d3-format/blob/main/LICENSE 6 | * @license 7 | */ 8 | export function formatGroup(grouping: number[], thousands: string) { 9 | return function (value: string, width: number) { 10 | let i = value.length; 11 | const t = []; 12 | let j = 0; 13 | let g = grouping[0]; 14 | let length = 0; 15 | 16 | while (i > 0 && g > 0) { 17 | if (length + g + 1 > width) { 18 | g = Math.max(1, width - length); 19 | } 20 | t.push(value.substring((i -= g), i + g)); 21 | if ((length += g + 1) > width) { 22 | break; 23 | } 24 | g = grouping[(j = (j + 1) % grouping.length)]; 25 | } 26 | 27 | return t.reverse().join(thousands); 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/vutils/src/format/number/formatPrefixAuto.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-time-format by Mike Bostock 2 | * https://github.com/d3/d3-format 3 | * Licensed under the ISC 4 | 5 | * License: https://github.com/d3/d3-format/blob/main/LICENSE 6 | * @license 7 | */ 8 | import { formatDecimalParts } from './formatDecimal'; 9 | 10 | export let prefixExponent: number; 11 | 12 | export function formatPrefixAuto(x: number, p: number) { 13 | const d = formatDecimalParts(x, p); 14 | if (!d) { 15 | return x + ''; 16 | } 17 | const coefficient = d[0]; 18 | const exponent = d[1]; 19 | const i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1; 20 | const n = coefficient.length; 21 | return i === n 22 | ? coefficient 23 | : i > n 24 | ? coefficient + new Array(i - n + 1).join('0') 25 | : i > 0 26 | ? coefficient.slice(0, i) + '.' + coefficient.slice(i) 27 | : '0.' + new Array(1 - i).join('0') + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y! 28 | } 29 | -------------------------------------------------------------------------------- /packages/vutils/src/format/number/formatRounded.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-time-format by Mike Bostock 2 | * https://github.com/d3/d3-format 3 | * Licensed under the ISC 4 | 5 | * License: https://github.com/d3/d3-format/blob/main/LICENSE 6 | * @license 7 | */ 8 | import { formatDecimalParts } from './formatDecimal'; 9 | 10 | export function formatRounded(x: number, p: number) { 11 | const d = formatDecimalParts(x, p); 12 | if (!d) { 13 | return x + ''; 14 | } 15 | const coefficient = d[0]; 16 | const exponent = d[1]; 17 | return exponent < 0 18 | ? '0.' + new Array(-exponent).join('0') + coefficient 19 | : coefficient.length > exponent + 1 20 | ? coefficient.slice(0, exponent + 1) + '.' + coefficient.slice(exponent + 1) 21 | : coefficient + new Array(exponent - coefficient.length + 2).join('0'); 22 | } 23 | -------------------------------------------------------------------------------- /packages/vutils/src/format/number/formatTrim.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from d3-time-format by Mike Bostock 2 | * https://github.com/d3/d3-format 3 | * Licensed under the ISC 4 | 5 | * License: https://github.com/d3/d3-format/blob/main/LICENSE 6 | * @license 7 | */ 8 | export function formatTrim(s: string) { 9 | const n = s.length; 10 | let i0 = -1; 11 | let i1; 12 | out: for (let i = 1; i < n; ++i) { 13 | switch (s[i]) { 14 | case '.': 15 | i0 = i1 = i; 16 | break; 17 | case '0': 18 | if (i0 === 0) { 19 | i0 = i; 20 | } 21 | i1 = i; 22 | break; 23 | default: 24 | if (!+s[i]) { 25 | break out; 26 | } 27 | if (i0 > 0) { 28 | i0 = 0; 29 | } 30 | break; 31 | } 32 | } 33 | return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; 34 | } 35 | -------------------------------------------------------------------------------- /packages/vutils/src/format/number/index.ts: -------------------------------------------------------------------------------- 1 | export * from './number'; 2 | export * from './specifier'; 3 | -------------------------------------------------------------------------------- /packages/vutils/src/geo/constant.ts: -------------------------------------------------------------------------------- 1 | /* Adapted from venn.js by Ben Frederickson 2 | * https://github.com/benfred/venn.js 3 | * Licensed under the MIT 4 | 5 | * url: https://github.com/benfred/venn.js/blob/master/src/circleintersection.js 6 | * License: https://github.com/benfred/venn.js/blob/master/LICENSE 7 | * @license 8 | */ 9 | 10 | export const SMALL = 1e-10; 11 | -------------------------------------------------------------------------------- /packages/vutils/src/geo/index.ts: -------------------------------------------------------------------------------- 1 | export * from './invariant'; 2 | export * from './interface'; 3 | export * from './circle-intersection'; 4 | export * from './constant'; 5 | -------------------------------------------------------------------------------- /packages/vutils/src/geo/interface.ts: -------------------------------------------------------------------------------- 1 | import type { IPointLike } from '../data-structure'; 2 | 3 | export type { Feature, MultiPolygon, Polygon, Units } from '@turf/helpers'; 4 | 5 | export interface ICircle extends IPointLike { 6 | radius: number; 7 | size?: number; 8 | parent?: ICircle; 9 | } 10 | 11 | export interface IIntersectPoint extends IPointLike { 12 | parentIndex?: number[]; 13 | angle?: number; 14 | } 15 | 16 | export interface ICircleArc { 17 | circle: ICircle; 18 | width: number; 19 | p1: IIntersectPoint; 20 | p2: IIntersectPoint; 21 | } 22 | 23 | export interface IOverlapAreaStats { 24 | area?: number; 25 | arcArea?: number; 26 | polygonArea?: number; 27 | arcs?: ICircleArc[]; 28 | innerPoints?: IPointLike[]; 29 | intersectionPoints?: IIntersectPoint[]; 30 | } 31 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/algorithm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './intersect'; 2 | export * from './aabb'; 3 | export * from './obb'; 4 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/algorithm/interface.ts: -------------------------------------------------------------------------------- 1 | export interface Bound { 2 | x1: number; 3 | y1: number; 4 | x2: number; 5 | y2: number; 6 | } 7 | 8 | export interface OBB { 9 | point1: Point; 10 | point2: Point; 11 | point3: Point; 12 | point4: Point; 13 | width: number; 14 | height: number; 15 | left: number; 16 | top: number; 17 | } 18 | 19 | export interface Point { 20 | x: number; 21 | y: number; 22 | } 23 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/arc.ts: -------------------------------------------------------------------------------- 1 | export type ArcAnchorType = 2 | | 'inner-start' 3 | | 'inner-end' 4 | | 'inner-middle' 5 | | 'outer-start' 6 | | 'outer-end' 7 | | 'outer-middle' 8 | | 'center'; 9 | export type IArcLikeAttr = { 10 | innerRadius: number; 11 | outerRadius: number; 12 | startAngle: number; 13 | endAngle: number; 14 | }; 15 | export const calculateAnchorOfArc = (arcAttr: IArcLikeAttr, anchorType: ArcAnchorType) => { 16 | const { startAngle, endAngle, innerRadius, outerRadius } = arcAttr; 17 | let angle = (startAngle + endAngle) / 2; 18 | let radius = (innerRadius + outerRadius) / 2; 19 | 20 | switch (anchorType) { 21 | case 'inner-start': 22 | angle = startAngle; 23 | radius = innerRadius; 24 | break; 25 | case 'outer-start': 26 | angle = startAngle; 27 | radius = outerRadius; 28 | break; 29 | case 'inner-end': 30 | angle = endAngle; 31 | radius = innerRadius; 32 | break; 33 | case 'outer-end': 34 | angle = endAngle; 35 | radius = outerRadius; 36 | break; 37 | case 'inner-middle': 38 | radius = innerRadius; 39 | break; 40 | case 'outer-middle': 41 | radius = outerRadius; 42 | break; 43 | } 44 | return { angle, radius }; 45 | }; 46 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/image.ts: -------------------------------------------------------------------------------- 1 | const parseUint8ToImageData = (buffer: Uint8Array, width: number, height: number): ImageData => { 2 | const clampBuffer = new Uint8ClampedArray(buffer); 3 | const flipClampBuffer = new Uint8ClampedArray(buffer.length); 4 | for (let i = height - 1; i >= 0; i--) { 5 | for (let j = 0; j < width; j++) { 6 | const sourceIdx = i * width * 4 + j * 4; 7 | const targetIdx = (height - i) * width * 4 + j * 4; 8 | flipClampBuffer[targetIdx] = clampBuffer[sourceIdx]; 9 | flipClampBuffer[targetIdx + 1] = clampBuffer[sourceIdx + 1]; 10 | flipClampBuffer[targetIdx + 2] = clampBuffer[sourceIdx + 2]; 11 | flipClampBuffer[targetIdx + 3] = clampBuffer[sourceIdx + 3]; 12 | } 13 | } 14 | return new ImageData(flipClampBuffer, width, height); 15 | }; 16 | 17 | export { parseUint8ToImageData }; 18 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/index.ts: -------------------------------------------------------------------------------- 1 | // image 2 | export * from './image'; 3 | 4 | // computer geometry algorithm, cgs.js 5 | export * from './algorithm'; 6 | 7 | export * from './graph-util'; 8 | 9 | export * from './polygon'; 10 | 11 | export * from './arc'; 12 | 13 | export * from './text'; 14 | 15 | export * from './bounds-util'; 16 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/text/index.ts: -------------------------------------------------------------------------------- 1 | export { default as stringWidth, eastAsianCharacterInfo } from './stringWidth'; 2 | export * from './measure'; 3 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/text/measure/index.ts: -------------------------------------------------------------------------------- 1 | export * from './textMeasure'; 2 | export * from './interface'; 3 | export * from './util'; 4 | -------------------------------------------------------------------------------- /packages/vutils/src/graphics/text/measure/util.ts: -------------------------------------------------------------------------------- 1 | import type { ITextFontParams } from './interface'; 2 | 3 | // FIXME: from VRender 4 | export function getContextFont( 5 | text: Partial, 6 | defaultAttr: Partial = {}, 7 | fontSizeScale?: number 8 | ): string { 9 | if (!fontSizeScale) { 10 | fontSizeScale = 1; 11 | } 12 | const { 13 | fontStyle = defaultAttr.fontStyle, 14 | fontVariant = defaultAttr.fontVariant, 15 | fontWeight = defaultAttr.fontWeight, 16 | fontSize = defaultAttr.fontSize, 17 | fontFamily = defaultAttr.fontFamily 18 | } = text; 19 | return ( 20 | '' + 21 | (fontStyle ? fontStyle + ' ' : '') + 22 | (fontVariant ? fontVariant + ' ' : '') + 23 | (fontWeight ? fontWeight + ' ' : '') + 24 | fontSize * fontSizeScale + 25 | 'px ' + 26 | (fontFamily ? fontFamily : 'sans-serif') 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/vutils/src/index.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3'; 2 | 3 | /** 4 | * A high performance event emitter 5 | * @see {@link https://github.com/primus/eventemitter3} 6 | * @memberof PIXI.utils 7 | * @class EventEmitter 8 | */ 9 | export { EventEmitter }; 10 | 11 | export * from './common'; 12 | 13 | // data structure 14 | export * from './data-structure'; 15 | 16 | // algorithm 17 | export * from './lru'; 18 | 19 | // math 20 | export * from './math'; 21 | 22 | // angle 23 | export * from './angle'; 24 | 25 | // color 26 | // TODO: 后续删除这个导出,避免引入不需要的内容 27 | export * as ColorUtil from './color'; 28 | 29 | // methods about graphics 30 | export * from './graphics'; 31 | 32 | // type 33 | export * from './type'; 34 | 35 | // log 36 | export * from './logger'; 37 | 38 | // padding 39 | export * from './padding'; 40 | 41 | export * from './time'; 42 | 43 | // dom 44 | export * from './dom'; 45 | 46 | export * from './geo'; 47 | 48 | export * from './color'; 49 | 50 | export * from './format/time'; 51 | export * from './format/number'; 52 | 53 | export * from './fmin'; 54 | -------------------------------------------------------------------------------- /packages/vutils/src/padding.ts: -------------------------------------------------------------------------------- 1 | import isValidNumber from './common/isValidNumber'; 2 | import isArray from './common/isArray'; 3 | import isObject from './common/isObject'; 4 | 5 | export type IPadding = { 6 | top?: number; 7 | bottom?: number; 8 | left?: number; 9 | right?: number; 10 | }; 11 | 12 | /** 13 | * 将 padding 转换为通用的格式([上,右,下,左]) 14 | * @param padding 15 | * @return [ top, right, bottom, left ] 16 | */ 17 | export function normalizePadding(padding: number | number[] | IPadding) { 18 | if (isValidNumber(padding)) { 19 | return [padding, padding, padding, padding]; 20 | } 21 | 22 | if (isArray(padding)) { 23 | const length = padding.length; 24 | 25 | if (length === 1) { 26 | const paddingValue = padding[0]; 27 | return [paddingValue, paddingValue, paddingValue, paddingValue]; 28 | } 29 | 30 | if (length === 2) { 31 | const [vertical, horizontal] = padding; 32 | return [vertical, horizontal, vertical, horizontal]; 33 | } 34 | 35 | if (length === 3) { 36 | const [top, horizontal, bottom] = padding; 37 | return [top, horizontal, bottom, horizontal]; 38 | } 39 | 40 | if (length === 4) { 41 | return padding; 42 | } 43 | } 44 | 45 | if (isObject(padding)) { 46 | const { top = 0, right = 0, bottom = 0, left = 0 } = padding as IPadding; 47 | return [top, right, bottom, left]; 48 | } 49 | 50 | return [0, 0, 0, 0]; 51 | } 52 | -------------------------------------------------------------------------------- /packages/vutils/src/time/index.ts: -------------------------------------------------------------------------------- 1 | export * from './formatUtils'; 2 | export * from './interval'; 3 | -------------------------------------------------------------------------------- /packages/vutils/src/type.ts: -------------------------------------------------------------------------------- 1 | export type LooseFunction = (...args: any) => any; 2 | export type Dict = { [key: string]: T }; 3 | export type Maybe = T | null | undefined; 4 | 5 | export interface ILogger { 6 | addErrorHandler: (handler: (...args: any[]) => void) => void; 7 | removeErrorHandler: (handler: (...args: any[]) => void) => void; 8 | canLogInfo: () => boolean; 9 | canLogDebug: () => boolean; 10 | canLogError: () => boolean; 11 | canLogWarn: () => boolean; 12 | level: (levelValue?: number) => this | number; 13 | error: (...args: any[]) => this; 14 | warn: (...args: any[]) => this; 15 | info: (...args: any[]) => this; 16 | debug: (...args: any[]) => this; 17 | } 18 | -------------------------------------------------------------------------------- /packages/vutils/tscofig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"], 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": "./", 7 | "rootDir": "./" 8 | }, 9 | "include": ["src", "__tests__", "examples"], 10 | "exclude": ["bugserver-config"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/vutils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "rootDir": "./src", 6 | "outDir": "./es", 7 | "composite": true 8 | }, 9 | "ts-node": { 10 | "transpileOnly": true, 11 | "compilerOptions": { 12 | "module": "commonjs" 13 | } 14 | }, 15 | "include": ["src", "__tests__", "examples"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/vutils/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "paths": {} 5 | }, 6 | "references": [] 7 | } 8 | -------------------------------------------------------------------------------- /share/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@internal/eslint-config", 3 | "version": "0.0.1", 4 | "description": "sharable eslint configuration settings", 5 | "private": true, 6 | "dependencies": { 7 | "@typescript-eslint/eslint-plugin": "5.30.0", 8 | "@typescript-eslint/parser": "5.30.0", 9 | "eslint-config-prettier": "8.5.0", 10 | "eslint-plugin-promise": "6.0.0", 11 | "eslint-plugin-react": "7.30.1", 12 | "eslint-plugin-react-hooks": "4.6.0" 13 | }, 14 | "devDependencies": { 15 | "eslint": "~8.18.0", 16 | "typescript": "4.9.5" 17 | }, 18 | "peerDependencies": { 19 | "eslint": "~8.18.0", 20 | "typescript": ">=4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /share/eslint-config/profile/lib.js: -------------------------------------------------------------------------------- 1 | var config = require('./common')('node'); 2 | module.exports = config; 3 | -------------------------------------------------------------------------------- /share/eslint-config/profile/react.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./common')('react') -------------------------------------------------------------------------------- /share/jest-config/jest.base.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | testTimeout: 30000, 7 | silent: true, 8 | testMatch: ['**/__tests__/**/*.test.[jt]s'], 9 | transform: { 10 | '^.+\\.ts?$': [ 11 | 'ts-jest', 12 | { 13 | resolveJsonModule: true, 14 | esModuleInterop: true, 15 | experimentalDecorators: true, 16 | module: 'ESNext', 17 | tsconfig: '/tsconfig.test.json' 18 | } 19 | ] 20 | }, 21 | globals: { 22 | __DEV__: true 23 | }, 24 | verbose: false, 25 | collectCoverageFrom: ['**/*.{ts}', '!**/node_modules/**'], 26 | coverageDirectory: 'coverage', 27 | coverageProvider: 'v8', 28 | coverageReporters: ['json-summary', 'lcov', 'text'], 29 | coveragePathIgnorePatterns: ['node_modules', '__tests__', 'interface.ts', '.d.ts', 'typings'], 30 | collectCoverageFrom: [ 31 | '**/src/**', 32 | '!**/cjs/**', 33 | '!**/dist/**', 34 | '!**/es/**', 35 | '!**/node_modules/**', 36 | '!**/__tests__/**', 37 | '!**/types/**', 38 | '!**/interface.ts' 39 | ], 40 | coverageThreshold: { 41 | global: { 42 | branches: 80, 43 | functions: 80, 44 | lines: 80, 45 | statements: 80 46 | } 47 | }, 48 | moduleNameMapper: { 49 | 'd3-array': path.resolve(process.cwd(), './node_modules/d3-array/dist/d3-array.min.js') 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /share/jest-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@internal/jest-config", 3 | "private": true, 4 | "version": "0.0.1", 5 | "dependencies": { 6 | "@jest/globals": "~29.5.0", 7 | "ts-jest": "~29.1.0" 8 | }, 9 | "devDependencies": { 10 | "jest": "~29.5.0", 11 | "@types/jest": "~29.5.0", 12 | "typescript": "4.9.5" 13 | }, 14 | "peerDependencies": { 15 | "jest": "~29.5.0", 16 | "typescript": "4.3 - 6.x" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /share/ts-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@internal/ts-config", 3 | "private": true, 4 | "version": "0.0.1" 5 | } 6 | -------------------------------------------------------------------------------- /share/ts-config/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2016", 4 | "experimentalDecorators": true, 5 | "strict": true, 6 | "noImplicitAny": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "sourceMap": false, 10 | "moduleResolution": "node", 11 | "module": "esnext", 12 | "esModuleInterop": true, 13 | "importHelpers": false, 14 | "isolatedModules": false, 15 | "noImplicitReturns": false, 16 | "noImplicitThis": true, 17 | "noUnusedParameters": false, 18 | "noUnusedLocals": false, 19 | "resolveJsonModule": true, 20 | "suppressImplicitAnyIndexErrors": true, 21 | "strictNullChecks": false, 22 | "skipLibCheck": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tools/bundler/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require("@rushstack/eslint-patch/modern-module-resolution"); 2 | 3 | module.exports = { 4 | extends: ["@internal/eslint-config/profile/lib"], 5 | parserOptions: { tsconfigRootDir: __dirname }, 6 | ignorePatterns: ["bin", "output", "vitest.config.ts", "fixtures"], 7 | }; 8 | -------------------------------------------------------------------------------- /tools/bundler/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | require('../output/bootstrap'); 5 | -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/.gitignore: -------------------------------------------------------------------------------- 1 | es 2 | cjs 3 | dist 4 | umd -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@foo/qux", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "compile": "tsc --noEmit" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/source/foo/bar/index.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '@/utils'; 2 | 3 | export function bar(val: unknown) { 4 | return isObject(val); 5 | } 6 | -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/source/foo/bar/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisActor/VUtil/8eb0060fa055c6e2e8a7e41f719428eeffb365eb/tools/bundler/fixtures/config/source/foo/bar/web.png -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/source/foo/index.ts: -------------------------------------------------------------------------------- 1 | export const Foo = 'Foo'; 2 | -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/source/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __VERSION__: string; 2 | declare const __DEV__: boolean; 3 | -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/source/index.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from '@/utils'; 2 | 3 | export type Viking = { 4 | foo: string; 5 | }; 6 | 7 | export function bar(value: unknown) { 8 | if (isArray(value)) { 9 | return value.length; 10 | } 11 | if (__DEV__) { 12 | console.log('xxx'); 13 | } 14 | return 0; 15 | } 16 | 17 | export const version = __VERSION__; 18 | -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/source/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { Foo } from '@/foo'; 2 | 3 | export function isObject(value: unknown): value is Record { 4 | return Object.prototype.toString.call(value) === '[object Object]'; 5 | } 6 | 7 | export function isArray(value: unknown): value is Array { 8 | return Object.prototype.toString.call(value) === '[object Array]'; 9 | } 10 | 11 | export function isFunction(value: unknown): value is Function { 12 | return typeof value === 'function'; 13 | } 14 | 15 | export const Bar = Foo; 16 | -------------------------------------------------------------------------------- /tools/bundler/fixtures/config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "rootDir": "./", 6 | "outDir": "output", 7 | "target": "ES2015", 8 | "module": "ESNext", 9 | "declaration": true, 10 | "sourceMap": false, 11 | "jsx": "react-jsx", 12 | "paths": { 13 | "@/*": ["source/*"] 14 | } 15 | }, 16 | "include": ["source"] 17 | } 18 | 19 | -------------------------------------------------------------------------------- /tools/bundler/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'gulp-clean' { 2 | const m = (opt?: { force: boolean }): NodeJS.ReadWriteStream => {}; 3 | export = m; 4 | } 5 | -------------------------------------------------------------------------------- /tools/bundler/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { Config } from './logic/config'; 2 | export type { RawPackageJson } from './logic/package'; 3 | 4 | export { loadPackageJson } from './logic/package'; 5 | export { loadConfigFile } from './logic/config'; 6 | 7 | export { clean } from './tasks/clean'; 8 | export { buildStyle } from './tasks/style'; 9 | -------------------------------------------------------------------------------- /tools/bundler/src/logic/babel.config.ts: -------------------------------------------------------------------------------- 1 | import type { PluginItem } from '@babel/core'; 2 | 3 | export type BabelPlugins = { 4 | presets: PluginItem[]; 5 | plugins: PluginItem[]; 6 | }; 7 | export function getBabelPlugins(packageName: string): BabelPlugins { 8 | const plugins = [ 9 | require.resolve('@babel/plugin-proposal-export-default-from'), 10 | require.resolve('@babel/plugin-proposal-class-properties'), 11 | [ 12 | require.resolve('babel-plugin-import'), 13 | { 14 | style: true, 15 | libraryName: packageName, 16 | libraryDirectory: 'es', 17 | camel2DashComponentName: false 18 | } 19 | ] 20 | ]; 21 | 22 | const presets = [ 23 | require.resolve('@babel/preset-react'), 24 | require.resolve('@babel/preset-typescript'), 25 | [require.resolve('@babel/preset-env'), { targets: 'defaults and not IE 11' }] 26 | ]; 27 | 28 | return { 29 | presets, 30 | plugins 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /tools/bundler/src/logic/debug.ts: -------------------------------------------------------------------------------- 1 | import createDebug from 'debug'; 2 | 3 | const debug = createDebug('Bundler'); 4 | 5 | export const DebugConfig = debug.extend('config'); 6 | export const DebugCompile = debug.extend('compile'); 7 | -------------------------------------------------------------------------------- /tools/bundler/src/logic/package.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | export type RawPackageJson = { 4 | name: string; 5 | description?: string; 6 | version: string; 7 | main?: string; 8 | scripts?: Record; 9 | repository?: Record; 10 | devDependencies?: Record; 11 | dependencies?: Record; 12 | peerDependencies?: Record; 13 | keywords?: string[]; 14 | author?: string; 15 | browserslist?: string[]; 16 | homepage?: string; 17 | } & Record; 18 | 19 | export function loadPackageJson(absPath: string): RawPackageJson | null { 20 | try { 21 | return fs.readJsonSync(`${absPath}/package.json`, { encoding: 'utf-8' }); 22 | } catch (error) { 23 | // eslint-disable-next-line no-console 24 | console.error(error); 25 | return null; 26 | } 27 | } 28 | 29 | export function packageNameToPath(name: string) { 30 | return name.replace('@', '').replace('/', '_'); 31 | } 32 | -------------------------------------------------------------------------------- /tools/bundler/src/tasks/clean.ts: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import gulpClean from 'gulp-clean'; 3 | 4 | export function clean(folders: string[], cwd?: string) { 5 | return new Promise(resolve => { 6 | gulp 7 | .src(folders, { 8 | cwd: cwd, 9 | read: false, 10 | allowEmpty: true 11 | }) 12 | .pipe(gulpClean({ force: true })) 13 | .on('finish', () => { 14 | resolve(undefined); 15 | }); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /tools/bundler/src/tasks/copy.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import gulp from 'gulp'; 3 | import path from 'path'; 4 | 5 | import type { Config } from '../logic/config'; 6 | 7 | export function copyFiles(projectRoot: string, config: Config) { 8 | return new Promise(resolve => { 9 | const willCopyAssets = gulp.src(config.copy.map(ext => `${projectRoot}/${config.sourceDir}/**/*.${ext}`)); 10 | 11 | const esFolder = path.resolve(projectRoot, config.outputDir.es!); 12 | const cjsFolder = path.resolve(projectRoot, config.outputDir.cjs!); 13 | let stream: NodeJS.ReadableStream | undefined; 14 | if (config.formats.includes('cjs')) { 15 | stream = willCopyAssets.pipe(gulp.dest(cjsFolder)); 16 | } 17 | if (config.formats.includes('es')) { 18 | stream = (stream || willCopyAssets).pipe(gulp.dest(esFolder)); 19 | } 20 | 21 | stream?.on('finish', () => { 22 | resolve(undefined); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /tools/bundler/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internal/ts-config/tsconfig.base.json", 3 | "ts-node": { 4 | "transpileOnly": true, 5 | "compilerOptions": { 6 | "declaration": true, 7 | "sourceMap": true 8 | } 9 | }, 10 | "compilerOptions": { 11 | "baseUrl": ".", 12 | "outDir": "output", 13 | "target": "ES2015", 14 | "module": "CommonJS", 15 | "declaration": true, 16 | "sourceMap": false 17 | }, 18 | "include": [ 19 | "src" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tools/bundler/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ['src/**/*.{test,spec}.ts'] 6 | } 7 | }); 8 | --------------------------------------------------------------------------------