├── packages ├── ava │ ├── src │ │ ├── advisor │ │ │ ├── lint-pipeline │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── advise-pipeline │ │ │ │ └── index.ts │ │ │ ├── utils │ │ │ │ ├── isNil.ts │ │ │ │ ├── hasSubset.ts │ │ │ │ ├── intersects.ts │ │ │ │ ├── index.ts │ │ │ │ ├── compare.ts │ │ │ │ └── clone.ts │ │ │ ├── ruler │ │ │ │ └── rules │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── all-can-be-table.ts │ │ │ │ │ ├── aggregation-single-row.ts │ │ │ │ │ ├── purpose-check.ts │ │ │ │ │ ├── data-field-qty.ts │ │ │ │ │ ├── line-field-time-ordinal.ts │ │ │ │ │ ├── no-redundant-field.ts │ │ │ │ │ ├── bar-series-qty.ts │ │ │ │ │ ├── bar-without-axis-min.ts │ │ │ │ │ ├── data-check.ts │ │ │ │ │ └── series-qty-limit.ts │ │ │ └── constants.ts │ │ ├── data │ │ │ ├── dataset │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ ├── field │ │ │ │ │ └── index.ts │ │ │ │ └── graph │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── types.ts │ │ │ ├── analysis │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── types.ts │ │ │ ├── utils │ │ │ │ ├── isType │ │ │ │ │ ├── index.ts │ │ │ │ │ └── constants.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── statistics │ │ │ │ ├── index.ts │ │ │ │ ├── maxabs.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── buishand-u.ts │ │ │ │ ├── caches.ts │ │ │ │ ├── bayesian.ts │ │ │ │ ├── cdf.ts │ │ │ │ └── pettitt-test.ts │ │ ├── ntv │ │ │ ├── utils │ │ │ │ └── index.ts │ │ │ ├── schema │ │ │ │ ├── index.ts │ │ │ │ ├── common.ts │ │ │ │ └── structure.ts │ │ │ ├── index.ts │ │ │ ├── generate │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── insight │ │ │ ├── chart │ │ │ │ ├── generator │ │ │ │ │ ├── index.ts │ │ │ │ │ └── insights.ts │ │ │ │ ├── index.ts │ │ │ │ ├── strategy │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── commonMarks │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── areaMark.ts │ │ │ │ │ │ ├── pointMark.ts │ │ │ │ │ │ ├── intervalMark.ts │ │ │ │ │ │ └── textMark.ts │ │ │ │ │ ├── augmentedMarks │ │ │ │ │ │ ├── majority.ts │ │ │ │ │ │ ├── homogeneous.ts │ │ │ │ │ │ ├── correlation.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── lowVariance.ts │ │ │ │ │ └── viewSpec.ts │ │ │ │ ├── utils.ts │ │ │ │ └── constants.ts │ │ │ ├── algorithms │ │ │ │ ├── index.ts │ │ │ │ ├── categoryOutlier.ts │ │ │ │ ├── trendDirection.ts │ │ │ │ ├── changePoint.ts │ │ │ │ └── base │ │ │ │ │ └── compare.ts │ │ │ ├── index.ts │ │ │ ├── pipeline │ │ │ │ ├── util.ts │ │ │ │ └── index.ts │ │ │ ├── narrative │ │ │ │ ├── strategy │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── base.ts │ │ │ │ │ └── helpers.ts │ │ │ │ ├── factory.ts │ │ │ │ └── index.ts │ │ │ ├── constant.ts │ │ │ ├── utils │ │ │ │ └── common.ts │ │ │ └── insights │ │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── arr2map.ts │ │ │ ├── isType.ts │ │ │ └── dataFormat.ts │ │ ├── common │ │ │ └── types.ts │ │ └── ckb │ │ │ ├── index.ts │ │ │ └── i18n │ │ │ └── index.ts │ ├── .fatherrc.ts │ ├── jest.config.js │ ├── tsconfig.json │ └── __tests__ │ │ ├── unit │ │ ├── insight │ │ │ ├── algorithms │ │ │ │ ├── IQR.test.ts │ │ │ │ └── pettitt-test.test.ts │ │ │ └── extractors │ │ │ │ ├── trend.test.ts │ │ │ │ ├── change-point.test.ts │ │ │ │ ├── time-series-outlier.test.ts │ │ │ │ ├── majority.test.ts │ │ │ │ ├── low-variance.test.ts │ │ │ │ └── category-outlier.test.ts │ │ ├── ckb │ │ │ └── i18n.test.ts │ │ ├── advisor │ │ │ ├── ruler │ │ │ │ ├── rule-cases │ │ │ │ │ ├── aggregation-single-row.test.ts │ │ │ │ │ └── all-can-be-table.test.ts │ │ │ │ ├── hard-rule.test.ts │ │ │ │ └── design-rule.test.ts │ │ │ ├── advise-pipeline │ │ │ │ └── infer-data-type.test.ts │ │ │ └── utils │ │ │ │ └── utils.test.ts │ │ └── data │ │ │ ├── dataset │ │ │ └── field │ │ │ │ ├── utils.test.ts │ │ │ │ └── series │ │ │ │ └── fillValue.test.ts │ │ │ └── utils │ │ │ └── common.test.ts │ │ └── integration │ │ ├── ckb │ │ └── ckbDict.test.ts │ │ └── advisor │ │ └── advise-cases │ │ └── charts │ │ ├── scatter_plot.test.ts │ │ ├── bubble.test.ts │ │ ├── histogram.test.ts │ │ ├── donut.test.ts │ │ ├── area.test.ts │ │ ├── grouped_bar.test.ts │ │ ├── stacked_bar.test.ts │ │ ├── step_line.test.ts │ │ ├── stacked_column.test.ts │ │ ├── stacked_area.test.ts │ │ ├── percent_stacked_bar.test.ts │ │ ├── percent_stacked_area.test.ts │ │ ├── percent_stacked_column.test.ts │ │ ├── grouped_column.test.ts │ │ ├── bar.test.ts │ │ ├── heatmap.test.ts │ │ └── line.test.ts └── ava-react │ ├── src │ ├── index.ts │ ├── InsightCard │ │ ├── Toolbar │ │ │ ├── constants.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── ntvPlugins │ │ │ ├── types.ts │ │ │ ├── index.ts │ │ │ ├── plugins │ │ │ │ ├── chartPlugin.tsx │ │ │ │ └── subspaceDescriptionPlugin.tsx │ │ │ └── components │ │ │ │ └── G2Chart.tsx │ │ └── styled │ │ │ ├── iconButton.tsx │ │ │ ├── container.tsx │ │ │ └── tag.tsx │ ├── NarrativeTextVis │ │ ├── line-charts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useSvgWrapper.tsx │ │ │ │ └── getElementFontSize.ts │ │ │ ├── index.ts │ │ │ ├── line │ │ │ │ ├── index.ts │ │ │ │ ├── scaleLinear.ts │ │ │ │ └── SingleLineChart.tsx │ │ │ └── proportion │ │ │ │ ├── index.tsx │ │ │ │ ├── getArcPath.ts │ │ │ │ └── ProportionChart.tsx │ │ ├── types │ │ │ ├── index.ts │ │ │ └── enhance-spec.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── functionalize.ts │ │ │ └── getCollapseProps.tsx │ │ ├── styled │ │ │ ├── phrase.tsx │ │ │ ├── marks.tsx │ │ │ ├── index.tsx │ │ │ ├── container.tsx │ │ │ ├── entity.tsx │ │ │ └── paragraph.tsx │ │ ├── chore │ │ │ ├── exporter │ │ │ │ └── index.ts │ │ │ └── plugin │ │ │ │ ├── createCustomBlockFactory.ts │ │ │ │ ├── createPhraseFactory.ts │ │ │ │ ├── createCustomPhraseFactory.ts │ │ │ │ ├── index.ts │ │ │ │ └── presets │ │ │ │ ├── createTimeDesc.tsx │ │ │ │ ├── createDimensionValue.tsx │ │ │ │ ├── createMetricName.tsx │ │ │ │ ├── createMetricValue.tsx │ │ │ │ ├── createContributeRatio.tsx │ │ │ │ ├── createOtherMetricValue.tsx │ │ │ │ ├── createTrendDesc.tsx │ │ │ │ └── createProportion.tsx │ │ ├── theme │ │ │ ├── index.ts │ │ │ ├── getFontSize.ts │ │ │ ├── getLineHeight.ts │ │ │ └── getThemeColor.ts │ │ ├── constants.ts │ │ ├── assets │ │ │ └── icons.tsx │ │ ├── nested │ │ │ └── index.tsx │ │ ├── phrases │ │ │ └── index.tsx │ │ └── paragraph │ │ │ └── TextLine.tsx │ └── utils │ │ ├── getPrefixCls.ts │ │ ├── index.ts │ │ ├── isType.ts │ │ └── classnames.ts │ ├── .fatherrc.ts │ ├── jest.config.js │ └── tsconfig.json ├── site ├── externals.d.ts ├── examples │ ├── ntv │ │ ├── case │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ ├── report.tsx │ │ │ │ └── meta.json │ │ ├── basic │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ └── nested-paragraph.tsx │ │ ├── custom │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ └── meta.json │ │ └── interactive │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ └── meta.json │ ├── ckb │ │ └── CKBJson │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ └── meta.json │ ├── data │ │ ├── statistics │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ └── meta.json │ │ └── data-frame │ │ │ ├── index.en.md │ │ │ ├── index.zh.md │ │ │ └── demo │ │ │ └── meta.json │ ├── insight │ │ ├── basic │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ └── meta.json │ │ ├── custom │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ └── meta.json │ │ └── visualization │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ └── meta.json │ ├── advice │ │ ├── smart-color │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ └── meta.json │ │ ├── advise-and-lint │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ │ └── ca.jsx │ │ ├── advisor-only │ │ │ ├── index.en.md │ │ │ ├── index.zh.md │ │ │ └── demo │ │ │ │ ├── data-advisor.jsx │ │ │ │ └── meta.json │ │ └── linter-only │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ ├── meta.json │ │ │ └── chart-linter.jsx │ ├── others │ │ └── thumbnails │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ ├── meta.json │ │ │ └── select.jsx │ ├── insight-card │ │ └── basic │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ ├── meta.json │ │ │ └── insight.tsx │ ├── unused-demo │ │ ├── auto-chart │ │ │ ├── index.en.md │ │ │ ├── index.zh.md │ │ │ └── demo │ │ │ │ ├── mock.jsx │ │ │ │ ├── basic.jsx │ │ │ │ └── meta.json │ │ └── chart-advisor │ │ │ └── relation │ │ │ ├── index.zh.md │ │ │ ├── index.en.md │ │ │ └── demo │ │ │ ├── tree.jsx │ │ │ ├── table.jsx │ │ │ └── meta.json │ └── gallery │ │ ├── index.en.md │ │ └── index.zh.md ├── .gitignore ├── docs │ ├── api │ │ ├── ntv │ │ │ └── generate.en.md │ │ └── types │ │ │ ├── ckb.zh.md │ │ │ └── ckb.en.md │ ├── guide │ │ ├── ntv │ │ │ ├── ntv-schema.en.md │ │ │ ├── ntv-comp.en.md │ │ │ ├── plugin.en.md │ │ │ ├── intro.en.md │ │ │ └── ntv-comp.zh.md │ │ └── auto-chart │ │ │ └── intro.zh.md │ └── common │ │ ├── phrase.zh.md │ │ ├── style.md │ │ └── phrase.en.md ├── .eslintignore ├── .eslintrc.js ├── utils │ └── index.jsx └── LICENSE ├── .husky ├── pre-commit ├── pre-push └── commit-msg ├── playground ├── src │ ├── ContentPage │ │ ├── index.less │ │ └── index.tsx │ ├── DevPlayground │ │ ├── SmartBoard │ │ │ ├── utils │ │ │ │ ├── index.ts │ │ │ │ └── g2plotRender.ts │ │ │ ├── index.tsx │ │ │ ├── ChartSamples │ │ │ │ └── index.ts │ │ │ └── Page │ │ │ │ ├── index.tsx │ │ │ │ └── index.less │ │ ├── ChartAdvisor │ │ │ ├── data.json │ │ │ ├── index.tsx │ │ │ ├── ChartAdvisor.tsx │ │ │ ├── Linter │ │ │ │ └── LintCard.tsx │ │ │ ├── Chart.tsx │ │ │ └── Advisor │ │ │ │ └── AdviceCard.tsx │ │ ├── LiteInsight │ │ │ └── index.tsx │ │ ├── AutoChart │ │ │ ├── data.json │ │ │ └── index.tsx │ │ ├── GraphAdvisor │ │ │ ├── index.tsx │ │ │ └── SampleData │ │ │ │ ├── index.ts │ │ │ │ ├── SankeyGraph.js │ │ │ │ └── Subgraph.js │ │ └── CKBList │ │ │ └── index.tsx │ └── global.css ├── typings.d.ts ├── .umirc.ts └── tsconfig.json ├── CHANGELOG.md ├── .prettierrc ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── workflows │ ├── release-notify.yml │ ├── mirror.yml │ └── sync-after-auto-release.yml ├── jest.config.js ├── .eslintignore ├── lerna.json ├── .prettierignore ├── .markdownlint.json ├── .editorconfig ├── jest.config.base.js ├── .releaserc.js ├── tsconfig.json ├── .fatherrc.ts └── LICENSE /packages/ava/src/advisor/lint-pipeline/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/externals.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.less'; 2 | -------------------------------------------------------------------------------- /packages/ava/src/data/dataset/index.ts: -------------------------------------------------------------------------------- 1 | export * from './field'; 2 | -------------------------------------------------------------------------------- /packages/ava/src/data/dataset/types.ts: -------------------------------------------------------------------------------- 1 | export * from './field/types'; 2 | -------------------------------------------------------------------------------- /packages/ava/src/ntv/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isSpecType'; 2 | -------------------------------------------------------------------------------- /packages/ava/src/data/analysis/index.ts: -------------------------------------------------------------------------------- 1 | export { analyzeField } from './field'; 2 | -------------------------------------------------------------------------------- /site/examples/ntv/case/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV 使用场景 3 | order: 3 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/ckb/CKBJson/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CKB 的应用 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/data/statistics/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 统计学方法 3 | order: 1 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/insight/basic/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 自动洞察 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/insight/custom/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 高级使用 3 | order: 2 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/ntv/basic/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV 基本用法 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/ntv/custom/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV 自定义扩展 3 | order: 1 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/ntv/interactive/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV 交互 3 | order: 2 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/advice/smart-color/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 智能配色使用 3 | order: 3 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/ckb/CKBJson/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CKB Application 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/insight/visualization/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 可视化洞察 3 | order: 1 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/ntv/case/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV Show Case 3 | order: 3 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/ntv/custom/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV Custom 3 | order: 1 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-staged 5 | -------------------------------------------------------------------------------- /playground/src/ContentPage/index.less: -------------------------------------------------------------------------------- 1 | .content-page { 2 | background-color: bisque; 3 | } 4 | -------------------------------------------------------------------------------- /site/examples/data/data-frame/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DataFrame 3 | order: 0 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/data/data-frame/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DataFrame 示例 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/data/statistics/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Statistics 3 | order: 1 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/insight/basic/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Auto-Insights 3 | order: 0 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/ntv/basic/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV Basic Usage 3 | order: 0 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/others/thumbnails/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Thumbnails 缩略图 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/index.ts: -------------------------------------------------------------------------------- 1 | export { Advisor } from './advisor'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /site/examples/advice/advise-and-lint/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 完整使用 Advisor 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/advice/advisor-only/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advisor Only 3 | order: 1 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/advice/linter-only/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 仅使用 Advisor.lint() 3 | order: 2 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/insight-card/basic/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: InsightCard 基本用法 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/insight/custom/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced Usages 3 | order: 2 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/ntv/interactive/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NTV Interaction 3 | order: 2 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/others/thumbnails/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Thumbnails 3 | order: 0 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/unused-demo/auto-chart/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: AutoChart 3 | order: 0 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/unused-demo/auto-chart/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: AutoChart 示例 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # npm run lint && npm run prettier 5 | -------------------------------------------------------------------------------- /packages/ava-react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NarrativeTextVis'; 2 | export * from './InsightCard'; 3 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/advise-pipeline/index.ts: -------------------------------------------------------------------------------- 1 | export { advicesForChart } from './advicesForChart'; 2 | -------------------------------------------------------------------------------- /packages/ava/src/data/types.ts: -------------------------------------------------------------------------------- 1 | export * from './analysis/types'; 2 | export * from './dataset/types'; 3 | -------------------------------------------------------------------------------- /site/examples/advice/advisor-only/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 仅使用 Advisor.Advise() 3 | order: 1 4 | --- 5 | -------------------------------------------------------------------------------- /site/examples/advice/linter-only/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advise.Lint() only 3 | order: 2 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/advice/smart-color/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SmartColor Usage 3 | order: 3 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/insight-card/basic/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: InsightCard Basic Usage 3 | order: 0 4 | --- 5 | -------------------------------------------------------------------------------- /packages/ava/src/data/utils/isType/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isType'; 2 | export * from './isDateString'; 3 | -------------------------------------------------------------------------------- /site/examples/insight/visualization/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Visualize Insights 3 | order: 1 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/Toolbar/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TOOLS = ['copy', 'others'] as const; 2 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/generator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './homogeneous'; 2 | export * from './insights'; 3 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useSvgWrapper } from './useSvgWrapper'; 2 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './proportion'; 2 | export * from './line'; 3 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/line/index.ts: -------------------------------------------------------------------------------- 1 | export { SingleLineChart } from './SingleLineChart'; 2 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './props'; 2 | export * from './enhance-spec'; 3 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/SmartBoard/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './aggregate'; 2 | export * from './g2plotRender'; 3 | -------------------------------------------------------------------------------- /site/examples/advice/advise-and-lint/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advisor - advise and lint 3 | order: 0 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /site/examples/unused-demo/chart-advisor/relation/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 图可视化推荐 3 | order: 3 4 | hide: true 5 | --- 6 | -------------------------------------------------------------------------------- /packages/ava/.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import fatherConfig from '../../.fatherrc'; 2 | 3 | export default fatherConfig('ts', 'AVA'); 4 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/proportion/index.tsx: -------------------------------------------------------------------------------- 1 | export { ProportionChart } from './ProportionChart'; 2 | -------------------------------------------------------------------------------- /packages/ava-react/.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import fatherConfig from '../../.fatherrc'; 2 | 3 | export default fatherConfig('react', 'AVAReact'); 4 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/utils/isNil.ts: -------------------------------------------------------------------------------- 1 | const isNil = (input: any) => { 2 | return input === null; 3 | }; 4 | export default isNil; 5 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # gatsby files 2 | .cache/ 3 | public 4 | 5 | 6 | # dumi2 website builder 7 | .dumi/tmp 8 | .dumi/tmp-production 9 | -------------------------------------------------------------------------------- /site/examples/gallery/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gallery 3 | order: -1 4 | icon: other 5 | redirect_from: 6 | - /en/examples 7 | --- 8 | -------------------------------------------------------------------------------- /site/examples/gallery/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 所有图表 3 | order: -1 4 | icon: other 5 | redirect_from: 6 | - /zh/examples 7 | --- 8 | -------------------------------------------------------------------------------- /packages/ava-react/src/utils/getPrefixCls.ts: -------------------------------------------------------------------------------- 1 | export function getPrefixCls(suffixCls?: string) { 2 | return `avar-${suffixCls}`; 3 | } 4 | -------------------------------------------------------------------------------- /playground/src/global.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | @import '~@antv/auto-chart/esm/index.css'; 3 | @import '~katex/dist/katex.min.css'; 4 | -------------------------------------------------------------------------------- /packages/ava/src/data/analysis/types.ts: -------------------------------------------------------------------------------- 1 | export type { FieldType, FieldInfo, StringFieldInfo, NumberFieldInfo, DateFieldInfo } from './field/types'; 2 | -------------------------------------------------------------------------------- /packages/ava/src/data/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils used internally by ava. 3 | */ 4 | 5 | export * from './common'; 6 | export * from './isType'; 7 | -------------------------------------------------------------------------------- /packages/ava/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isType'; 2 | export * from './arr2map'; 3 | export { default as dataFormat } from './dataFormat'; 4 | -------------------------------------------------------------------------------- /site/docs/api/ntv/generate.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: generateTextSpec 3 | order: 2 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /site/docs/guide/ntv/ntv-schema.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ntv-schema 3 | order: 1 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /site/examples/unused-demo/chart-advisor/relation/index.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Visualize Relations Automatically 3 | order: 3 4 | hide: true 5 | --- 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGES in AVA 3 2 | 3 | ## 3.0.0 4 | 5 | 6 | [Migrating from v2 to v3](todo) 7 | -------------------------------------------------------------------------------- /packages/ava/src/insight/algorithms/index.ts: -------------------------------------------------------------------------------- 1 | export * from './categoryOutlier'; 2 | export * from './trendDirection'; 3 | export * from './changePoint'; 4 | -------------------------------------------------------------------------------- /packages/ava/src/data/dataset/field/index.ts: -------------------------------------------------------------------------------- 1 | import Series from './series'; 2 | import DataFrame from './dataFrame'; 3 | 4 | export { Series, DataFrame }; 5 | -------------------------------------------------------------------------------- /packages/ava/src/ntv/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common'; 2 | export * from './structure'; 3 | export * from './paragraph'; 4 | export * from './phrase'; 5 | -------------------------------------------------------------------------------- /site/docs/guide/ntv/ntv-comp.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React Component of NTV 3 | order: 2 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/ava-react/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { getPrefixCls } from './getPrefixCls'; 2 | export { classnames } from './classnames'; 3 | export * from './isType'; 4 | -------------------------------------------------------------------------------- /packages/ava/src/insight/algorithms/categoryOutlier.ts: -------------------------------------------------------------------------------- 1 | import { IQR } from '../../data/statistics/IQR'; 2 | 3 | export const categoryOutlier = { 4 | IQR, 5 | }; 6 | -------------------------------------------------------------------------------- /site/docs/guide/ntv/plugin.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced Usage - Custom Plugin 3 | order: 3 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/ChartAdvisor/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "price": 100, "type": "A" }, 3 | { "price": 120, "type": "B" }, 4 | { "price": 150, "type": "C" } 5 | ] 6 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/LiteInsight/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import AutoInsight from './AutoInsight'; 4 | 5 | export default ; 6 | -------------------------------------------------------------------------------- /site/docs/guide/ntv/intro.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Narrative Text Vis (NTV) Introduction 3 | order: 0 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { functionalize } from './functionalize'; 2 | export { default as getCollapseProps } from './getCollapseProps'; 3 | -------------------------------------------------------------------------------- /packages/ava/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('../../jest.config.base.js'); 2 | 3 | module.exports = { 4 | ...base, 5 | name: 'ava', 6 | displayName: 'ava', 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/utils/hasSubset.ts: -------------------------------------------------------------------------------- 1 | export function hasSubset(array1: any[], array2: any[]): boolean { 2 | return array2.every((e) => array1.includes(e)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/utils/intersects.ts: -------------------------------------------------------------------------------- 1 | export function intersects(array1: any[], array2: any[]): boolean { 2 | return array2.some((e) => array1.includes(e)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/ava/src/ntv/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { ENTITY_TYPES } from './schema'; 3 | export * from './utils'; 4 | export { generateTextSpec } from './generate'; 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "bracketSpacing": true, 6 | "printWidth": 120, 7 | "arrowParens": "always" 8 | } 9 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/styled/phrase.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const FormulaWrapper = styled.div` 4 | display: inline-block; 5 | `; 6 | -------------------------------------------------------------------------------- /packages/ava/src/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analysis'; 2 | export * from './dataset'; 3 | export * from './statistics'; 4 | export * from './utils'; 5 | export * from './types'; 6 | -------------------------------------------------------------------------------- /packages/ava/src/insight/algorithms/trendDirection.ts: -------------------------------------------------------------------------------- 1 | import { mkTest } from '../../data/statistics/mannkendall-test'; 2 | 3 | export const trendDirection = { 4 | mkTest, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/ava-react/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('../../jest.config.base.js'); 2 | 3 | module.exports = { 4 | ...base, 5 | name: 'ava-react', 6 | displayName: 'ava-react', 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/exporter/index.ts: -------------------------------------------------------------------------------- 1 | export { TextExporter } from './TextExporter'; 2 | export { copyToClipboard, getSelectionContentForCopy } from './helpers/copy'; 3 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generator/homogeneous'; 2 | export * from './generator/insights'; 3 | export * from './strategy/augmentedMarks'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### PR includes 2 | 3 | 4 | - [ ] fixed #0 5 | - [ ] add / modify test cases 6 | - [ ] documents, demos 7 | -------------------------------------------------------------------------------- /site/.eslintignore: -------------------------------------------------------------------------------- 1 | **/bundle.js 2 | **/*.min.js 3 | 4 | .DS_Store 5 | node_modules/ 6 | lib/ 7 | coverage/ 8 | es/ 9 | esm/ 10 | dist/ 11 | temp/ 12 | typings/ 13 | docs/ 14 | webpack.config.js 15 | -------------------------------------------------------------------------------- /packages/ava-react/src/utils/isType.ts: -------------------------------------------------------------------------------- 1 | import { toNumber, isNaN } from 'lodash'; 2 | 3 | export function isNumberLike(val: unknown) { 4 | const numVal = toNumber(val); 5 | return !isNaN(numVal); 6 | } 7 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/AutoChart/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "f1": "2019-01", "f2": 100 }, 3 | { "f1": "2019-02", "f2": 300 }, 4 | { "f1": "2019-03", "f2": 340 }, 5 | { "f1": "2019-04", "f2": 330 } 6 | ] 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **Package Name**: 2 | * **Package Version**: 3 | * **Platform**: 4 | * **Mini Showcase(like screenshots)**: 5 | * **CodePen Link**: 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/ava-react/src/utils/classnames.ts: -------------------------------------------------------------------------------- 1 | /** connect cls, remove empty */ 2 | export function classnames(...cls: string[]) { 3 | return cls.reduce((prev, curr) => (curr ? `${prev} ${curr}` : prev), ''); 4 | } 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./jest.config.base.js'); 2 | 3 | module.exports = { 4 | ...base, 5 | projects: ['/packages/*/jest.config.js'], 6 | coverageDirectory: '/coverage/', 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/styled/marks.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Bold = styled.strong``; 4 | export const Italic = styled.em``; 5 | export const Underline = styled.u``; 6 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/index.ts: -------------------------------------------------------------------------------- 1 | export { insight2ChartStrategy } from './chart'; 2 | export { viewSpecStrategy } from './viewSpec'; 3 | export * from './commonMarks'; 4 | export * from './augmentedMarks'; 5 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/index.ts: -------------------------------------------------------------------------------- 1 | export { InsightCard } from './InsightCard'; 2 | export type { ToolbarProps as InsightCardToolBarProps } from './Toolbar/types'; 3 | export type { InsightCardProps } from './types'; 4 | -------------------------------------------------------------------------------- /packages/ava/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "declaration": true, 6 | "declarationDir": "./lib" 7 | }, 8 | "include": ["./src"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/ntvPlugins/types.ts: -------------------------------------------------------------------------------- 1 | import type { G2Spec } from '@antv/g2'; 2 | import type { CustomBlockElement } from '@antv/ava'; 3 | 4 | export type ChartsSchema = CustomBlockElement & { 5 | chartSpecs: G2Spec[]; 6 | }; 7 | -------------------------------------------------------------------------------- /site/examples/unused-demo/auto-chart/demo/mock.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import { AutoChart } from '@antv/auto-chart'; 5 | 6 | ReactDOM.render(, document.getElementById('container')); 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/bundle.js 2 | **/*.min.js 3 | 4 | .DS_Store 5 | node_modules/ 6 | lib/ 7 | coverage/ 8 | es/ 9 | esm/ 10 | dist/ 11 | temp/ 12 | typings/ 13 | docs/ 14 | webpack.config.js 15 | 16 | # site 17 | public/ 18 | **/ava-site/examples/ 19 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/styled/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './container'; 2 | export * from './heading'; 3 | export * from './paragraph'; 4 | export * from './bullet'; 5 | export * from './marks'; 6 | export * from './entity'; 7 | export * from './phrase'; 8 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base'; 2 | export { cdf } from './cdf'; 3 | export { maxabs } from './maxabs'; 4 | export * from './quantile'; 5 | export * from './lowess'; 6 | export * from './vector'; 7 | export { pcorrtest } from './pcorrtest'; 8 | -------------------------------------------------------------------------------- /packages/ava/src/ntv/generate/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * generate text spec by structure and variable 3 | * thx @Lobster377 4 | */ 5 | 6 | export { default as generateTextSpec } from './generateTextSpec'; 7 | export type { Structure, StructureTemp, Variable } from './types'; 8 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/theme/index.ts: -------------------------------------------------------------------------------- 1 | export { seedToken } from './seed'; 2 | export { default as getFontSize } from './getFontSize'; 3 | export { default as getThemeColor } from './getThemeColor'; 4 | export { default as getLineHeight } from './getLineHeight'; 5 | -------------------------------------------------------------------------------- /packages/ava-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "declaration": true, 6 | "noUnusedLocals": false, 7 | "noUnusedParameters": false 8 | }, 9 | "include": ["./src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ava/src/utils/arr2map.ts: -------------------------------------------------------------------------------- 1 | export function arr2map(arr: T[], keyId: string) { 2 | return arr.reduce>((prev, curr) => { 3 | // eslint-disable-next-line no-param-reassign 4 | prev[curr[keyId]] = curr; 5 | return prev; 6 | }, {}); 7 | } 8 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/constants.ts: -------------------------------------------------------------------------------- 1 | import { getPrefixCls } from '../utils'; 2 | 3 | /** ntv comp className */ 4 | export const NTV_PREFIX_CLS = getPrefixCls('ntv'); // return 'avar-ntv' 5 | 6 | /** export img alt attr */ 7 | export const NTV_IMG_ALT = NTV_PREFIX_CLS; 8 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/maxabs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the maximum absolute value of array 3 | * @param x input array 4 | * */ 5 | export const maxabs = (x: number[]): number => { 6 | const absoluteX = x.map((value) => Math.abs(value)); 7 | return Math.max(...absoluteX); 8 | }; 9 | -------------------------------------------------------------------------------- /site/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | // to handle gatsby browser defined local packages not error 4 | 'import/no-unresolved': 0, 5 | 'react/prop-types': 0, 6 | // for import ava and ava-react 7 | 'import/no-extraneous-dependencies': 0, 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/styled/iconButton.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const IconButton = styled.div` 4 | margin-left: 8px; 5 | display: inline-block; 6 | &:hover, 7 | &:focus, 8 | &:active { 9 | color: @active-color; 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/SmartBoard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ContentPage from '../../ContentPage'; 4 | 5 | import Page from './Page'; 6 | 7 | const content = ( 8 | 9 | 10 | 11 | ); 12 | 13 | export default content; 14 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/theme/getFontSize.ts: -------------------------------------------------------------------------------- 1 | import { seedToken } from './seed'; 2 | 3 | import type { SizeType } from '../types'; 4 | 5 | export default function getFontSize(size: SizeType = 'normal') { 6 | return `${size === 'small' ? seedToken.fontSizeSmall : seedToken.fontSizeBase}px`; 7 | } 8 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/theme/getLineHeight.ts: -------------------------------------------------------------------------------- 1 | import { seedToken } from './seed'; 2 | 3 | import type { SizeType } from '../types'; 4 | 5 | export default function getLineHeight(size: SizeType = 'normal') { 6 | return `${size === 'small' ? seedToken.lineHeightSmall : seedToken.lineHeightBase}px`; 7 | } 8 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/commonMarks/index.ts: -------------------------------------------------------------------------------- 1 | export { lineMarkStrategy } from './lineMark'; 2 | export { pointMarkStrategy } from './pointMark'; 3 | export { textMarkStrategy } from './textMark'; 4 | export { areaMarkStrategy } from './areaMark'; 5 | export { intervalMarkStrategy } from './intervalMark'; 6 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MAX_SOFT_RULE_COEFFICIENT is the max benifit coefficient for soft rule in advisor 3 | * correspondingly, 4 | * ( 1 / MAX_SOFT_RULE_COEFFICIENT )is the "max" penalty coefficient for soft rule in advisor 5 | * */ 6 | export const MAX_SOFT_RULE_COEFFICIENT = 10; 7 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/GraphAdvisor/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ContentPage from '../../ContentPage'; 4 | 5 | import { GraphPanel } from './GraphPanel'; 6 | 7 | const content = ( 8 | 9 | 10 | 11 | ); 12 | 13 | export default content; 14 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/utils/functionalize.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from 'lodash'; 2 | 3 | import type { TypeOrMetaReturnType } from '../types'; 4 | 5 | export function functionalize(val: TypeOrMetaReturnType, defaultVal: T | undefined) { 6 | return isFunction(val) ? val : () => val || defaultVal; 7 | } 8 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/ChartAdvisor/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ContentPage from '../../ContentPage'; 4 | 5 | import ChartAdvisor from './ChartAdvisor'; 6 | 7 | const content = ( 8 | 9 | 10 | 11 | ); 12 | 13 | export default content; 14 | -------------------------------------------------------------------------------- /playground/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | declare module '*.css'; 3 | declare module '*.less'; 4 | declare module '*.png'; 5 | declare module '*.svg' { 6 | export function ReactComponent(props: React.SVGProps): React.ReactElement; 7 | const url: string; 8 | export default url; 9 | } 10 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/SmartBoard/ChartSamples/index.ts: -------------------------------------------------------------------------------- 1 | import { ChartListInfo } from '@antv/smart-board'; 2 | 3 | import chartSample1 from './chartSample1'; 4 | import chartSample2 from './chartSample2'; 5 | 6 | const CHART_SAMPLE_LIST: ChartListInfo[] = [chartSample1, chartSample2]; 7 | 8 | export default CHART_SAMPLE_LIST; 9 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "useWorkspaces": true, 3 | "command": { 4 | "publish": { 5 | "registry": "https://registry.npmjs.org/", 6 | "message": "chore(release): publish" 7 | }, 8 | "bootstrap": { 9 | "npmClientArgs": ["--no-lockfile", "--no-package-lock"] 10 | } 11 | }, 12 | "version": "independent" 13 | } 14 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/utils.ts: -------------------------------------------------------------------------------- 1 | import { Mark } from '@antv/g2'; 2 | import { flattenDeep, map } from 'lodash'; 3 | 4 | import { AugmentedMarks } from './types'; 5 | 6 | export const augmentedMarks2Marks = (augmentedMarks: AugmentedMarks): Mark[] => { 7 | return flattenDeep(map(augmentedMarks, (augmentedMark) => Object.values(augmentedMark))); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/ava/src/insight/index.ts: -------------------------------------------------------------------------------- 1 | export { getInsights } from './pipeline'; 2 | export { insightPatternsExtractor } from './insights'; 3 | export { generateInsightVisualizationSpec } from './pipeline/visualize'; 4 | export * from './types'; 5 | export { getSpecificInsight } from './pipeline/specificInsight'; 6 | export type { AugmentedMarks } from './chart'; 7 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/algorithms/IQR.test.ts: -------------------------------------------------------------------------------- 1 | import { categoryOutlier } from '../../../../src/insight/algorithms'; 2 | 3 | const data = [38, 52, 61, 145, 48, 38, 38, 38]; 4 | 5 | describe('IQR', () => { 6 | test('check outliers result', () => { 7 | expect(categoryOutlier.IQR(data).upper.indexes).toStrictEqual([3]); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | package.json 3 | package-lock.json 4 | public 5 | build 6 | dist 7 | es 8 | esm 9 | lib 10 | .* 11 | *.md 12 | *.png 13 | *.svg 14 | **/assets/** 15 | LICENSE 16 | 17 | node_modules/ 18 | public/ 19 | 20 | # umi 21 | **/*.md 22 | **/*.svg 23 | **/*.ejs 24 | **/*.html 25 | package.json 26 | .umi 27 | .umi-production 28 | .umi-test 29 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/createCustomBlockFactory.ts: -------------------------------------------------------------------------------- 1 | import type { BlockDescriptor } from './plugin-protocol.type'; 2 | 3 | export const createCustomBlockFactory = ( 4 | descriptor: Omit, 'isBlock'> 5 | ): BlockDescriptor => { 6 | return { ...descriptor, isBlock: true }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * TODO @GuangMingYouBei: migrate utils into one unified folder of in ava. 3 | */ 4 | 5 | export { default as deepMix } from './deep-mix'; 6 | export { default as compare } from './compare'; 7 | export { default as cloneDeep } from './clone'; 8 | export { hasSubset } from './hasSubset'; 9 | export { intersects } from './intersects'; 10 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/createPhraseFactory.ts: -------------------------------------------------------------------------------- 1 | import type { PhraseDescriptor } from './plugin-protocol.type'; 2 | 3 | export const createPhraseFactory = 4 | (isEntity: boolean) => 5 | (descriptor: Omit, 'isEntity'>): PhraseDescriptor => { 6 | return { isEntity, getText: (value) => value, ...descriptor }; 7 | }; 8 | -------------------------------------------------------------------------------- /playground/src/ContentPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './index.less'; 4 | 5 | interface ContentPageProps { 6 | children: React.ReactNode; 7 | } 8 | 9 | class ContentPage extends React.Component { 10 | render() { 11 | return
{this.props.children}
; 12 | } 13 | } 14 | 15 | export default ContentPage; 16 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/augmentedMarks/majority.ts: -------------------------------------------------------------------------------- 1 | import { Mark } from '@antv/g2'; 2 | 3 | import { MajorityInfo, InsightInfo } from '../../../types'; 4 | import { insight2ChartStrategy } from '../chart'; 5 | 6 | export const majorityStrategy = (insight: InsightInfo): Mark[] => { 7 | const chartMark = insight2ChartStrategy(insight); 8 | return [chartMark]; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/augmentedMarks/homogeneous.ts: -------------------------------------------------------------------------------- 1 | import { G2Spec } from '@antv/g2'; 2 | 3 | import { HomogeneousPatternInfo, InsightInfo } from '../../../types'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 6 | export const homogeneousStrategy = (_insight: InsightInfo): G2Spec => { 7 | // colorField: string 8 | return {}; 9 | }; 10 | -------------------------------------------------------------------------------- /site/examples/data/statistics/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "index.jsx", 9 | "title": { 10 | "zh": "常用统计学方法示例", 11 | "en": "Statistical methods" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/WLciSdHENb/statistics.gif" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /playground/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | 3 | export default defineConfig({ 4 | nodeModulesTransform: { 5 | type: 'none', 6 | }, 7 | routes: [ 8 | { path: '/', component: '@/pages/index' }, 9 | ], 10 | fastRefresh: {}, 11 | webpack5: {}, 12 | chainWebpack: (memo) => { 13 | memo.module.rule('ts-in-node_modules').include.clear(); 14 | return memo; 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "header-style": { "style": "atx" }, 4 | "ul-style": { "style": "asterisk" }, 5 | "MD007": { "indent": 2 }, 6 | "line-length": false, 7 | "no-hard-tabs": false, 8 | "whitespace": false, 9 | "no-inline-html": false, 10 | "first-line-heading": false, 11 | "commands-show-output": false, 12 | "single-title": false, 13 | "no-duplicate-header": false 14 | } 15 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/ckb/i18n.test.ts: -------------------------------------------------------------------------------- 1 | import { ckbDict } from '../../../src/ckb'; 2 | 3 | describe('api - ckbDict', () => { 4 | test('ckbDict returns translation list', () => { 5 | // return object 6 | const cn = ckbDict('zh-CN'); 7 | expect(!!cn).toBe(true); 8 | expect(Object.keys(cn)).toEqual(['concepts', 'chartTypes']); 9 | expect(cn.concepts.lom.Nominal).toBe('无序名词'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /site/examples/unused-demo/auto-chart/demo/basic.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import { AutoChart } from '@antv/ava'; 5 | 6 | const data = [ 7 | { f1: '2019-01', f2: 100 }, 8 | { f1: '2019-02', f2: 300 }, 9 | { f1: '2019-03', f2: 340 }, 10 | { f1: '2019-04', f2: 330 }, 11 | ]; 12 | 13 | ReactDOM.render(, document.getElementById('container')); 14 | -------------------------------------------------------------------------------- /site/utils/index.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | 4 | import ReactJson from 'react-json-view'; 5 | 6 | export const ShowJSON = ({ json }) => { 7 | return ( 8 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /site/examples/ckb/CKBJson/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "chartdic.jsx", 9 | "title": { 10 | "zh": "使用 CKB 制作图表词典", 11 | "en": "Chart Dictionary by CKB" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*TOjFQ6PQwyEAAAAAAAAAAAAAARQnAQ" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/ava/src/common/types.ts: -------------------------------------------------------------------------------- 1 | import { G2ChartSpec } from '../advisor/types'; 2 | /** 3 | * One row(record) of data in JSON. 4 | */ 5 | export type Datum = Record; 6 | 7 | /** 8 | * Rows(records) of data. 9 | */ 10 | export type Data = Datum[]; 11 | 12 | /** 13 | * Specification: declarative schema to describe a visualization. 14 | */ 15 | export type Specification = G2ChartSpec; 16 | export type ChartSpec = G2ChartSpec; 17 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/createCustomPhraseFactory.ts: -------------------------------------------------------------------------------- 1 | import { createPhraseFactory } from './createPhraseFactory'; 2 | 3 | import type { PhraseDescriptor, CustomPhraseDescriptor } from './plugin-protocol.type'; 4 | 5 | export const createCustomPhraseFactory = ( 6 | descriptor: Omit, 'isEntity'> 7 | ): PhraseDescriptor => createPhraseFactory(false)(descriptor); 8 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/constants.ts: -------------------------------------------------------------------------------- 1 | import { LowessOptions, PCorrTestParameter } from './types'; 2 | 3 | /** default options in LOWESS */ 4 | export const DEFAULT_LOWESS_OPTIONS: LowessOptions = { 5 | f: 2 / 3, 6 | nSteps: 3, 7 | delta: null, 8 | }; 9 | 10 | /** default options in LOWESS */ 11 | export const DEFAULT_PCORRTEST_OPTIONS: PCorrTestParameter = { 12 | alpha: 0.05, 13 | alternative: 'two-sided', 14 | rho: 0, 15 | }; 16 | -------------------------------------------------------------------------------- /site/examples/insight/visualization/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "index.jsx", 9 | "title": { 10 | "zh": "可视化数据洞察", 11 | "en": "Data insights visualization" 12 | }, 13 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*UCIJTZW_euAAAAAAAAAAAAAADmJ7AQ/original" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_RULE_WEIGHTS: Record = { 2 | 'bar-series-qty': 0.5, 3 | 'data-check': 1.0, 4 | 'data-field-qty': 1.0, 5 | 'diff-pie-sector': 0.5, 6 | 'landscape-or-portrait': 0.3, 7 | 'limit-series': 1.0, 8 | 'line-field-time-ordinal': 1.0, 9 | 'no-redundant-field': 1.0, 10 | 'nominal-enum-combinatorial': 1.0, 11 | 'purpose-check': 1.0, 12 | 'series-qty-limit': 0.8, 13 | }; 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [Makefile] 16 | indent_style = tab 17 | indent_size = 1 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/augmentedMarks/correlation.ts: -------------------------------------------------------------------------------- 1 | import { Mark } from '@antv/g2'; 2 | 3 | import { CorrelationInfo, InsightInfo } from '../../../types'; 4 | import { insight2ChartStrategy } from '../chart'; 5 | 6 | export const correlationStrategy = (insight: InsightInfo): Mark[] => { 7 | // todo add correlation regression line and value 8 | const chartMark = insight2ChartStrategy(insight); 9 | return [chartMark]; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/ava/src/insight/algorithms/changePoint.ts: -------------------------------------------------------------------------------- 1 | import { bayesian } from '../../data/statistics/bayesian'; 2 | import { windowBasedMean } from '../../data/statistics/window'; 3 | import { pettittTest } from '../../data/statistics/pettitt-test'; 4 | import { buishandUTest } from '../../data/statistics/buishand-u'; 5 | 6 | export const changePoint = { 7 | Bayesian: bayesian, 8 | Window: windowBasedMean, 9 | PettittTest: pettittTest, 10 | BuishandUTest: buishandUTest, 11 | }; 12 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/ChartAdvisor/ChartAdvisor.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { AdvisorPanel } from './Advisor/AdvisorPanel'; 4 | import { LinterPanel } from './Linter/LinterPanel'; 5 | import { CAPanel } from './CA/CAPanel'; 6 | 7 | export default function App() { 8 | return ( 9 |
10 | 11 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/styled/container.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | margin-bottom: 8px; 5 | padding: 8px; 6 | font-size: 12px; 7 | font-family: roboto-regular, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 8 | 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; 9 | background-color: #fff; 10 | border-radius: 2px; 11 | border: 1px solid #e8e8e8; 12 | `; 13 | -------------------------------------------------------------------------------- /packages/ava/src/utils/isType.ts: -------------------------------------------------------------------------------- 1 | import { toNumber, isNaN } from 'lodash'; 2 | 3 | export function isString(val: unknown): val is string { 4 | return typeof val === 'string'; 5 | } 6 | 7 | export function isObject(val: unknown) { 8 | return val !== null && typeof val === 'object'; 9 | } 10 | 11 | export function isUndefined(val: unknown) { 12 | return val === undefined; 13 | } 14 | 15 | export function isNumberLike(val: unknown) { 16 | const numVal = toNumber(val); 17 | return !isNaN(numVal); 18 | } 19 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/ntvPlugins/index.ts: -------------------------------------------------------------------------------- 1 | import { chartsDisplayPlugin } from './plugins/chartPlugin'; 2 | import { subspaceDescriptionPlugin } from './plugins/subspaceDescriptionPlugin'; 3 | 4 | import type { NtvPluginType } from '../../NarrativeTextVis'; 5 | 6 | export const insightCardPresetPluginsMap: Record = { 7 | chartsDisplayPlugin, 8 | subspaceDescriptionPlugin 9 | } 10 | 11 | export const insightCardPresetPlugins: NtvPluginType[] = Object.values(insightCardPresetPluginsMap); 12 | -------------------------------------------------------------------------------- /jest.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src', '/__tests__'], 3 | transform: { 4 | '^.+\\.ts$': 'ts-jest', 5 | }, 6 | testRegex: '(/__tests__/.*.(test|spec)).(jsx?|tsx?|ts?)$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 8 | collectCoverage: false, 9 | coveragePathIgnorePatterns: ['(tests/.*.mock).(jsx?|tsx?)$'], 10 | verbose: false, 11 | globals: { 12 | 'ts-jest': { 13 | // 跳过 ts 报错 14 | diagnostics: false, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/line/scaleLinear.ts: -------------------------------------------------------------------------------- 1 | export type Domain = [number, number]; 2 | export type Range = [number, number]; 3 | 4 | export type Scale = (n: number) => number; 5 | 6 | export const scaleLinear = 7 | (domain: Domain, range: Range): Scale => 8 | (n) => { 9 | const [d1, d2] = domain; 10 | const [r1, r2] = range; 11 | // Returns the intermediate value when the range is zero. 12 | if (r1 === r2) return (d2 - d1) / 2; 13 | return (n / (r2 - r1)) * (d2 - d1); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/utils/compare.ts: -------------------------------------------------------------------------------- 1 | import isNil from './isNil'; 2 | 3 | import type { BasicDataPropertyForAdvice } from '../ruler/types'; 4 | 5 | const compare = (f1: BasicDataPropertyForAdvice, f2: BasicDataPropertyForAdvice): number => { 6 | if (isNil(f1.distinct) || isNil(f2.distinct)) { 7 | if (f1.distinct! < f2!.distinct!) { 8 | return 1; 9 | } 10 | if (f1.distinct! > f2.distinct!) { 11 | return -1; 12 | } 13 | return 0; 14 | } 15 | return 0; 16 | }; 17 | 18 | export default compare; 19 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: [ 3 | 'stable', 4 | ], 5 | extends: 'semantic-release-monorepo', 6 | plugins: [ 7 | '@semantic-release/commit-analyzer', 8 | '@semantic-release/release-notes-generator', 9 | '@semantic-release/changelog', 10 | '@semantic-release/npm', 11 | [ 12 | '@semantic-release/git', 13 | { 14 | message: 'chore(release): ${nextRelease.gitTag} [skip ci]', 15 | }, 16 | ], 17 | '@semantic-release/github', 18 | ], 19 | preset: 'angular', 20 | }; 21 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/styled/container.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import { getThemeColor, getFontSize, getLineHeight } from '../theme'; 4 | 5 | import type { ThemeStylesProps } from '../types'; 6 | 7 | export const Container = styled.div` 8 | font-family: PingFangSC, sans-serif; 9 | color: ${({ theme }) => getThemeColor({ colorToken: 'colorBase', theme })}; 10 | font-size: ${({ size }) => getFontSize(size)}; 11 | line-height: ${({ size }) => getLineHeight(size)}; 12 | `; 13 | -------------------------------------------------------------------------------- /packages/ava/src/ckb/index.ts: -------------------------------------------------------------------------------- 1 | // TODO @neoddish: todo list for ckb 2 | // 2. ckb 结构中增加 encode 相关的表达,把原来 ckb - spec 那一层手写的映射包掉 (v3.1 or v4) 3 | // 重新考虑 mark,channel,encode,shape 的关系和表述 4 | // 3. 完善文档,重点透出: 5 | // a. 结构,并用几个简单图表类型做示例。并提供推荐最常用的图表类型 id(比如最常用的 10 个) 6 | // c. 图表类型分割的具体逻辑(比如 单色柱状图、多色柱状图 是否是两个id?条形图、柱状图呢?为什么) 7 | 8 | // the main CKB API - ckb 9 | export { ckb } from './ckb'; 10 | // CKB i18n functions 11 | export { ckbDict } from './i18n'; 12 | // CKB constants 13 | export * from './constants'; 14 | // CKB types 15 | export * from './types'; 16 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/all-can-be-table.ts: -------------------------------------------------------------------------------- 1 | import { isUndefined } from '../utils'; 2 | 3 | import type { RuleModule } from '../types'; 4 | 5 | const applyChartTypes = ['table']; 6 | 7 | export const allCanBeTable: RuleModule = { 8 | id: 'all-can-be-table', 9 | type: 'HARD', 10 | docs: { 11 | lintText: 'all dataset can be table', 12 | }, 13 | trigger: ({ chartType }) => { 14 | return applyChartTypes.includes(chartType); 15 | }, 16 | validator: ({ weight }): number => { 17 | return isUndefined(weight) ? 1 : weight; 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/algorithms/pettitt-test.test.ts: -------------------------------------------------------------------------------- 1 | import { changePoint } from '../../../../src/insight/algorithms'; 2 | 3 | const data = [ 4 | 2413.291, 2201.967, 2363.555, 2086.259, 2070.092, 2242.442, 3091.346, 1326.768, 1595.619, 1631.493, 1797.879, 5 | 2044.798, 1904.171, 1746.416, 1875.368, 1826.619, 1853.982, 1887.834, 1802.647, 1783.05, 1925.268, 1777.375, 1970.239, 6 | 1782.715, 7 | ]; 8 | 9 | describe('Pettitt Test', () => { 10 | test('check change point result', () => { 11 | expect(changePoint.PettittTest(data).index).toBe(7); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import { PluginManager } from './PluginManager'; 2 | import { presetPlugins } from './presets'; 3 | 4 | export { createCustomPhraseFactory } from './createCustomPhraseFactory'; 5 | export { createEntityPhraseFactory } from './createEntityPhraseFactory'; 6 | export { createCustomBlockFactory } from './createCustomBlockFactory'; 7 | export { PluginManager } from './PluginManager'; 8 | export * from './presets'; 9 | export * from './plugin-protocol.type'; 10 | 11 | export const presetPluginManager = new PluginManager(presetPlugins); 12 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/ckb/ckbDict.test.ts: -------------------------------------------------------------------------------- 1 | import { COORDINATE_SYSTEMS, ckbDict } from '../../../src/ckb'; 2 | 3 | describe('ckbDict usage', () => { 4 | test('use ckbDict to get Chinese coord names', () => { 5 | const cn = ckbDict('zh-CN'); 6 | const cnCoordOptions = COORDINATE_SYSTEMS.map((coord) => cn.concepts.coord[coord]); 7 | 8 | expect(cnCoordOptions).toEqual([ 9 | '数轴', 10 | '二维直角坐标系', 11 | '对称直角坐标系', 12 | '三维直角坐标系', 13 | '极坐标系', 14 | '点线关系网络', 15 | '雷达型坐标系', 16 | '地理坐标系', 17 | '其他', 18 | ]); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/AutoChart/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { AutoChart } from '@antv/auto-chart'; 4 | 5 | import ContentPage from '../../ContentPage'; 6 | 7 | import testData from './data.json'; 8 | 9 | const autoChartExp = ( 10 |
11 |

case 1

12 | 13 |

case 2

14 | 15 |
16 | ); 17 | 18 | export default {autoChartExp}; 19 | -------------------------------------------------------------------------------- /site/docs/common/phrase.zh.md: -------------------------------------------------------------------------------- 1 | ***IPhrase*** 短语描述 2 | 3 | 短语描述用 type 区分,分为文本短语和实体短语,实体短语用于标记词汇属性。目前在 lite-insight 模块描述涉及实体类型有: 4 | 5 | * `'metric_name'`: 指标名; 6 | * `'metric_value'`: 指标值; 7 | * `'trend_desc'`: 趋势描述; 8 | * `'dim_value'`: 维度值; 9 | 10 | ```ts 11 | export type IPhrase = ITextPhrase | IEntityPhrase; 12 | 13 | export interface ITextPhrase { 14 | type: 'text'; 15 | value: string; 16 | } 17 | 18 | export interface IEntityPhrase { 19 | type: 'entity'; 20 | value?: string; 21 | metadata?: { 22 | entityType: 'metric_name' | 'metric_value' | 'trend_desc' | 'dim_value'; 23 | }; 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/styled/tag.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Tag = styled.span` 4 | display: inline-block; 5 | margin-right: 4px; 6 | padding: 3px; 7 | color: #250462; 8 | font-weight: 400; 9 | font-size: 12px; 10 | line-height: 12px; 11 | background-color: rgba(37, 4, 98, 0.08); 12 | border-radius: 2px; 13 | `; 14 | 15 | export const MeasureName = styled.span` 16 | display: inline-block; 17 | height: 20px; 18 | margin-right: 4px; 19 | color: rgba(0, 0, 0, 0.88); 20 | font-weight: 500; 21 | font-size: 14px; 22 | line-height: 20px; 23 | `; 24 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/commonMarks/areaMark.ts: -------------------------------------------------------------------------------- 1 | import { AreaMark } from '@antv/g2'; 2 | 3 | import { AreaMarkData, AreaMarkConfig } from '../../types'; 4 | 5 | export const areaMarkStrategy = (data: AreaMarkData, { encode, style, tooltip }: AreaMarkConfig): AreaMark => { 6 | const common: AreaMark = { 7 | style, 8 | tooltip, 9 | }; 10 | 11 | if (data) { 12 | return { 13 | ...common, 14 | type: 'area', 15 | data, 16 | encode: { 17 | x: 'x', 18 | y: 'y', 19 | ...encode, 20 | }, 21 | }; 22 | } 23 | 24 | return null; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/scatter_plot.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a scatter_plot chart. 6 | describe('should advise scatter_plot', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('scatter_plot'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices[0].type).toBe('scatter_plot'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/ava/src/ntv/types.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | NarrativeTextSpec, 3 | HeadlineSpec, 4 | SectionSpec, 5 | StandardSectionSpec, 6 | ParagraphSpec, 7 | HeadingParagraphSpec, 8 | TextParagraphSpec, 9 | BulletsParagraphSpec, 10 | BulletItemSpec, 11 | CustomBlockElement, 12 | NestedParagraphSpec, 13 | PhraseSpec, 14 | TextPhraseSpec, 15 | EntityPhraseSpec, 16 | EscapePhraseSpec, 17 | FormulaPhraseSpec, 18 | CustomPhraseSpec, 19 | ValueAssessment, 20 | EntityType, 21 | EntityMetaData, 22 | CustomMetaData, 23 | } from './schema'; 24 | export type { Structure, StructureTemp, Variable } from './generate'; 25 | -------------------------------------------------------------------------------- /site/examples/data/data-frame/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "df.jsx", 9 | "title": { 10 | "zh": "读取数据", 11 | "en": "Read data" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/4qbDDKfhu2/DataFrame.gif" 14 | }, 15 | { 16 | "filename": "info.jsx", 17 | "title": { 18 | "zh": "数据字段分析", 19 | "en": "Data field analysis" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/BTQp84MnNH/DF-info.gif" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/ava/src/data/dataset/graph/utils.ts: -------------------------------------------------------------------------------- 1 | export function flatObject(obj, concatenator = '.') { 2 | return Object.keys(obj).reduce((acc, key) => { 3 | if (typeof obj[key] !== 'object' || obj[key] === null) { 4 | return { 5 | ...acc, 6 | [key]: obj[key], 7 | }; 8 | } 9 | 10 | const flattenedChild = flatObject(obj[key], concatenator); 11 | 12 | return { 13 | ...acc, 14 | ...Object.keys(flattenedChild).reduce( 15 | (childAcc, childKey) => ({ ...childAcc, [`${key}${concatenator}${childKey}`]: flattenedChild[childKey] }), 16 | {} 17 | ), 18 | }; 19 | }, {}); 20 | } 21 | -------------------------------------------------------------------------------- /site/docs/common/style.md: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/SmartBoard/utils/g2plotRender.ts: -------------------------------------------------------------------------------- 1 | import * as G2Plot from '@antv/g2plot'; 2 | 3 | export type G2PlotType = 'Line' | 'Area' | 'Column' | 'Bar' | 'Pie' | 'Rose' | 'Scatter' | 'Histogram' | 'Heatmap'; 4 | 5 | export function g2plotRender(container: string | HTMLElement, type: any, data: any, options: any) { 6 | const containerDOM = typeof container === 'string' ? document.getElementById(container) : container; 7 | if (!containerDOM) return null; 8 | const plot = new G2Plot[type as G2PlotType](containerDOM, { 9 | height: 280, 10 | data, 11 | ...options, 12 | }); 13 | plot.render(); 14 | return plot; 15 | } 16 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/bubble.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a bubble chart. 6 | describe('should advise bubble', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('bubble_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | 13 | expect(advices.map((advice) => advice.type).includes('bubble_chart')).toBe(true); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createTimeDesc.tsx: -------------------------------------------------------------------------------- 1 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 2 | import { getThemeColor } from '../../../theme'; 3 | 4 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 5 | 6 | const defaultTimeDescDescriptor: SpecificEntityPhraseDescriptor = { 7 | encoding: { 8 | color: (value, metadata, { theme, palette }) => 9 | getThemeColor({ colorToken: 'colorDimensionValue', theme, palette, type: 'time_desc' }), 10 | }, 11 | tooltip: false, 12 | }; 13 | 14 | export const createTimeDesc = createEntityPhraseFactory('time_desc', defaultTimeDescDescriptor); 15 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/augmentedMarks/index.ts: -------------------------------------------------------------------------------- 1 | export { trendStrategy, trendAugmentedMarksStrategy } from './trend'; 2 | export { lowVarianceStrategy, lowVarianceAugmentedMarkStrategy } from './lowVariance'; 3 | export { categoryOutlierStrategy, categoryOutlierAugmentedMarksStrategy } from './categoryOutlier'; 4 | export { changePointStrategy, changePointAugmentedMarksStrategy } from './changePoint'; 5 | export { majorityStrategy } from './majority'; 6 | export { correlationStrategy } from './correlation'; 7 | export { homogeneousStrategy } from './homogeneous'; 8 | export { timeSeriesOutlierStrategy, timeSeriesOutlierStrategyAugmentedMarksStrategy } from './timeSeriesOutlier'; 9 | -------------------------------------------------------------------------------- /packages/ava/src/ntv/schema/common.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from 'csstype'; 2 | 3 | /** common props for block ele and inline ele */ 4 | export type CommonProps = { 5 | styles?: Properties; 6 | className?: string; 7 | key?: string; 8 | }; 9 | 10 | /** basic block element structure, used for extends */ 11 | export type CustomBlockElement = CommonProps & { 12 | // customType is required for custom block structure 13 | customType: string; 14 | [key: string]: unknown; 15 | }; 16 | 17 | /** custom phrase metadata */ 18 | export type CustomMetaData = { 19 | // customType is required for custom block structure 20 | customType: string; 21 | [key: string]: unknown; 22 | }; 23 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/GraphAdvisor/SampleData/index.ts: -------------------------------------------------------------------------------- 1 | import { FlowGraphData } from './FlowGraph'; 2 | import { KnowledgeTreeData } from './KnowledgeTree'; 3 | import { SankeyGraphData } from './SankeyGraph'; 4 | import { SubgraphData } from './Subgraph'; 5 | import { EuroCreditData } from './table'; 6 | 7 | export const ExampleDataset = [ 8 | { id: 'flowgraph', name: 'Flow Graph', data: FlowGraphData }, 9 | { id: 'table', name: 'Euro Credit', data: EuroCreditData }, 10 | { id: 'sankey', name: 'Sankey', data: SankeyGraphData }, 11 | { id: 'tree', name: 'Knowledge Tree', data: KnowledgeTreeData }, 12 | { id: 'subgraphs', name: 'Subgraphs', data: SubgraphData }, 13 | ]; 14 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "importHelpers": true, 8 | "jsx": "react-jsx", 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "baseUrl": "./", 12 | "strict": true, 13 | "paths": { 14 | "@/*": ["src/*"], 15 | "@@/*": ["src/.umi/*"] 16 | }, 17 | "allowSyntheticDefaultImports": true 18 | }, 19 | "include": ["mock/**/*", "src/**/*", "config/**/*", ".umirc.ts", "typings.d.ts"], 20 | "exclude": ["node_modules", "lib", "es", "dist", "typings", "**/__test__", "test", "docs", "tests"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/ntvPlugins/plugins/chartPlugin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { createCustomBlockFactory } from '../../../NarrativeTextVis'; 4 | import { DISPLAY_CHARTS_PLUGIN_KEY } from '../../constants'; 5 | import { ChartCarousel } from '../components/ChartCarousel'; 6 | import { ChartsSchema } from '../types'; 7 | 8 | /** plugins to display charts in ntv */ 9 | export const chartsDisplayPlugin = createCustomBlockFactory({ 10 | key: DISPLAY_CHARTS_PLUGIN_KEY, 11 | render(item) { 12 | const { chartSpecs } = item; 13 | if (chartSpecs) { 14 | return ; 15 | } 16 | return null; 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /site/examples/insight/custom/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "insight-type.jsx", 9 | "title": { 10 | "zh": "自定义洞察类型", 11 | "en": "Custom insight types" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/vvRy2rN%24cO/li-custom-type.gif" 14 | }, 15 | { 16 | "filename": "impact.jsx", 17 | "title": { 18 | "zh": "自定义影响力指标和权重", 19 | "en": "Custom Impact measures and weight" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/WZQxVppHR%26/li-impact-measure.gif" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createDimensionValue.tsx: -------------------------------------------------------------------------------- 1 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 2 | import { getThemeColor } from '../../../theme'; 3 | 4 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 5 | 6 | const defaultDimensionValueDescriptor: SpecificEntityPhraseDescriptor = { 7 | encoding: { 8 | color: (value, metadata, { theme, palette }) => 9 | getThemeColor({ colorToken: 'colorDimensionValue', theme, palette, type: 'dim_value' }), 10 | }, 11 | tooltip: false, 12 | }; 13 | 14 | export const createDimensionValue = createEntityPhraseFactory('dim_value', defaultDimensionValueDescriptor); 15 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/advisor/ruler/rule-cases/aggregation-single-row.test.ts: -------------------------------------------------------------------------------- 1 | import { CHART_IDS } from '../../../../../src'; 2 | import { aggregationSingleRow } from '../../../../../src/advisor/ruler/rules/aggregation-single-row'; 3 | 4 | const applyChartTypes = CHART_IDS; 5 | 6 | describe('Test: aggregation-single-row', () => { 7 | test('type', () => { 8 | expect(aggregationSingleRow.type).toBe('HARD'); 9 | }); 10 | 11 | test('trigger', () => { 12 | applyChartTypes.forEach((chartType) => { 13 | expect( 14 | aggregationSingleRow.trigger({ 15 | chartType, 16 | dataProps: undefined, 17 | }) 18 | ).toBe(true); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /site/docs/api/types/ckb.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CKB 相关类型 3 | order: 1 4 | --- 5 | 6 | 7 | 8 | ### ChartKnowledge 9 | 10 | 图表类型的信息结构的 TS 类型,可能是不同语言版本, 值和图表类型本身可能是自定义的。自定义图标应当包含 toSpec 方法。 11 | 12 | ```ts 13 | type ChartKnowledge = { 14 | id: string; 15 | name: string; 16 | alias: string[]; 17 | family: string[]; 18 | def: string; 19 | purpose: string[]; 20 | coord: string[]; 21 | category: string[]; 22 | shape: string[]; 23 | dataPres: (Omit & { fieldConditions: string[] })[]; 24 | channel: string[]; 25 | recRate: string; 26 | toSpec?: (data: Data, dataProps: any) => Specification | null; 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createMetricName.tsx: -------------------------------------------------------------------------------- 1 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 2 | import { getThemeColor } from '../../../theme'; 3 | 4 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 5 | 6 | const defaultMetricNameDescriptor: SpecificEntityPhraseDescriptor = { 7 | encoding: { 8 | fontWeight: 500, 9 | color: (value, metadata, { theme, palette }) => 10 | getThemeColor({ colorToken: 'colorMetricName', theme, palette, type: 'metric_name' }), 11 | }, 12 | tooltip: false, 13 | }; 14 | 15 | export const createMetricName = createEntityPhraseFactory('metric_name', defaultMetricNameDescriptor); 16 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/advisor/ruler/hard-rule.test.ts: -------------------------------------------------------------------------------- 1 | import { aggregationSingleRow } from '../../../../src/advisor/ruler/rules/aggregation-single-row'; 2 | import { allCanBeTable } from '../../../../src/advisor/ruler/rules/all-can-be-table'; 3 | 4 | describe('Test all hard rules', () => { 5 | test('aggregation-single-row', () => { 6 | // @ts-ignore 7 | expect( 8 | aggregationSingleRow.validator({ 9 | chartType: 'anyChart', 10 | dataProps: [{ count: 1, levelOfMeasurements: ['discrete'] }], 11 | }) 12 | ).toBe(1); 13 | }); 14 | 15 | test('all-can-be-table', () => { 16 | // @ts-ignore 17 | expect(allCanBeTable.validator({ weight: 1 })).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /site/examples/unused-demo/auto-chart/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "basic.jsx", 9 | "title": { 10 | "en": "Basic AutoChart usage", 11 | "zh": "基础 AutoChart 用法" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*Bd18ToZd2WEAAAAAAAAAAAAAARQnAQ" 14 | }, 15 | { 16 | "filename": "mock.jsx", 17 | "title": { 18 | "en": "AutoChart mock data", 19 | "zh": "AutoChart 无数据状态" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*g2xbTZciph8AAAAAAAAAAAAAARQnAQ" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /site/examples/advice/smart-color/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "data-advisor.jsx", 9 | "title": { 10 | "en": "Charts with specific color", 11 | "zh": "指定颜色的图表推荐" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*EJqOQr8DoLEAAAAAAAAAAAAAARQnAQ" 14 | }, 15 | { 16 | "filename": "auto-color.jsx", 17 | "title": { 18 | "en": "Charts with auto color", 19 | "zh": "自动配色的图表推荐" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*c3fdQ5dW0YsAAAAAAAAAAAAAARQnAQ" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /site/examples/ntv/interactive/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "coordinated-chart.tsx", 9 | "title": { 10 | "en": "Coordinated with Chart", 11 | "zh": "图文联动" 12 | }, 13 | "screenshot": "https://mdn.alipayobjects.com/huamei_vvq19s/afts/img/A*Nut_S6vkJkEAAAAAAAAAAAAADi2DAQ/original" 14 | }, 15 | { 16 | "filename": "analysis.tsx", 17 | "title": { 18 | "en": "Analysis with Text", 19 | "zh": "分析交互" 20 | }, 21 | "screenshot": "https://mdn.alipayobjects.com/huamei_vvq19s/afts/img/A*tUMpTIzwLeMAAAAAAAAAAAAADi2DAQ/original" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/SmartBoard/Page/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import CHART_SAMPLE_LIST from '../ChartSamples'; 4 | 5 | import Dashboard from './Dashboard'; 6 | import Toolbar from './Toolbar'; 7 | 8 | import './index.less'; 9 | 10 | const Page = () => { 11 | const [interactionMode, changeMode] = useState('defaultMode'); 12 | const [chartSamplesIndex, changeSampleIndex] = useState(0); 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ); 20 | }; 21 | 22 | export default Page; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "ES5", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2017"], 7 | "experimentalDecorators": true, 8 | "emitDecoratorMetadata": true, 9 | "declaration": true, 10 | "importHelpers": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "downlevelIteration": true, 14 | "outDir": "./lib", 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "skipLibCheck": true 21 | }, 22 | "exclude": ["node_modules", "**/esm", "**/lib", "**/dist", "**/build"] 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/release-notify.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 Release Notify 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | notify: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Release Notify 13 | uses: visiky/dingtalk-release-notify@main 14 | with: 15 | DING_TALK_TOKEN: ${{ secrets.DINGTALK_ROBOT_TOKEN }} 16 | notify_title: '🎉 AVA 新版本发布 {release_tag} 🎉' 17 | notify_body: '## { title }
![preview](https://gw.alipayobjects.com/zos/antfincdn/IUEpmsGeBj/AVA-publish-success.png)
{ body }
' 18 | notify_footer: '> 前往 [**AntV/AVA Releases**]({ release_url }) 查看完整信息' 19 | at_all: false 20 | enable_prerelease: true 21 | -------------------------------------------------------------------------------- /site/examples/insight-card/basic/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "basic.tsx", 9 | "title": { 10 | "en": "Visualize insight result", 11 | "zh": "展示洞察结果" 12 | }, 13 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*kUVoRZBZ9VQAAAAAAAAAAAAADmJ7AQ/original" 14 | }, 15 | { 16 | "filename": "insight.tsx", 17 | "title": { 18 | "en": "Combine with @antv/insight", 19 | "zh": "和 @antv/insight 串联使用" 20 | }, 21 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*8eIiTIk5gs8AAAAAAAAAAAAADmJ7AQ/original" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/theme/getThemeColor.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash'; 2 | 3 | import { seedToken } from './seed'; 4 | 5 | import type { EntityType } from '@antv/ava'; 6 | import type { ColorToken } from './seed'; 7 | import type { ThemeType, ThemeStylesProps } from '../types'; 8 | 9 | export default function getThemeColor({ 10 | type, 11 | colorToken, 12 | theme, 13 | palette, 14 | }: { 15 | colorToken: ColorToken; 16 | theme: ThemeType; 17 | type?: EntityType | 'text'; 18 | palette?: ThemeStylesProps['palette']; 19 | }) { 20 | const color = get(palette, [theme, type, 'color']); 21 | if (color) return color; 22 | return theme === 'light' ? seedToken[colorToken] : seedToken[`${colorToken}Dark`]; 23 | } 24 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/utils/getCollapseProps.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons'; 4 | import { isBoolean } from 'lodash'; 5 | 6 | import type { ExtensionProps, CollapseConfig } from '../types'; 7 | 8 | const defaultCollapseProps: CollapseConfig = { 9 | showBulletsLine: true, 10 | switcherIcon: (collapsed) => (collapsed ? : ), 11 | }; 12 | 13 | export default function getCollapseProps(collapseProps: ExtensionProps['showCollapse']) { 14 | if (isBoolean(collapseProps)) { 15 | return collapseProps ? defaultCollapseProps : false; 16 | } 17 | return { ...defaultCollapseProps, ...collapseProps }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/ava/src/ntv/schema/structure.ts: -------------------------------------------------------------------------------- 1 | import type { CommonProps, CustomBlockElement } from './common'; 2 | import type { PhraseSpec } from './phrase'; 3 | import type { ParagraphSpec } from './paragraph'; 4 | 5 | export type NarrativeTextSpec = CommonProps & { 6 | headline?: HeadlineSpec; 7 | sections?: SectionSpec[]; 8 | }; 9 | 10 | export type HeadlineSpec = CommonProps & { 11 | type: 'headline'; 12 | phrases: PhraseSpec[]; 13 | }; 14 | 15 | export type StandardSectionSpec = { 16 | paragraphs?: ParagraphSpec[]; 17 | }; 18 | 19 | export type SectionSpec = (StandardSectionSpec | CustomBlockElement) & CommonProps; 20 | 21 | export type NestedParagraphSpec = CommonProps & { 22 | children: (ParagraphSpec | NestedParagraphSpec)[]; 23 | }; 24 | -------------------------------------------------------------------------------- /site/examples/ntv/case/demo/report.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | import { Skeleton } from 'antd'; 4 | import ReactDOM from 'react-dom'; 5 | import { NarrativeTextVis } from '@antv/ava-react'; 6 | 7 | const App = () => { 8 | const [loading, setLoading] = useState(true); 9 | const [spec, setSpec] = useState({}); 10 | useEffect(() => { 11 | fetch('https://assets.antv.antgroup.com/ava/ntv-report1.json') 12 | .then((res) => res.json()) 13 | .then((res) => { 14 | setSpec(res); 15 | setLoading(false); 16 | }); 17 | }, []); 18 | return <>{loading ? : }; 19 | }; 20 | 21 | ReactDOM.render(, document.getElementById('container')); 22 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/proportion/getArcPath.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * make data between 0 ~ 1 3 | */ 4 | function normalizeProportion(data: number | undefined) { 5 | if (typeof data !== 'number') return 0; 6 | if (data > 1) return 1; 7 | if (data < 0) return 0; 8 | return data; 9 | } 10 | 11 | export function getArcPath(size: number, data: number) { 12 | const cx = size / 2; 13 | const cy = size / 2; 14 | const r = size / 2; 15 | const angle = normalizeProportion(data) * 2 * Math.PI; 16 | const dx = cx + r * Math.sin(angle); 17 | const dy = cy - r * Math.cos(angle); 18 | const path = ` 19 | M${cx} ${0} 20 | A ${cx} ${cy} 0 ${angle > Math.PI ? 1 : 0} 1 ${dx} ${dy} 21 | L ${cx} ${cy} Z 22 | `; 23 | return path; 24 | } 25 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/aggregation-single-row.ts: -------------------------------------------------------------------------------- 1 | import type { RuleModule } from '../types'; 2 | 3 | export const aggregationSingleRow: RuleModule = { 4 | id: 'aggregation-single-row', 5 | type: 'HARD', 6 | docs: { 7 | lintText: 'Recommend kpi_panel when only one row of aggregated data is available.', 8 | }, 9 | trigger: () => { 10 | return true; 11 | }, 12 | validator: (args): number => { 13 | let result = 0; 14 | const { chartType, dataProps } = args; 15 | if (dataProps.every((i) => i.count === 1 && i.levelOfMeasurements.includes('Interval'))) { 16 | result = chartType === 'kpi_panel' ? 1 : 0.2; 17 | } else { 18 | result = chartType === 'kpi_panel' ? 0 : 1; 19 | } 20 | return result; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ava/src/insight/pipeline/util.ts: -------------------------------------------------------------------------------- 1 | import Heap from 'heap-js'; 2 | 3 | import type { InsightInfo, PatternInfo, HomogeneousPatternInfo } from '../types'; 4 | 5 | export const insightPriorityComparator = (a: InsightInfo, b: InsightInfo) => 6 | a.score - b.score; 7 | 8 | export const homogeneousInsightPriorityComparator = ( 9 | a: InsightInfo, 10 | b: InsightInfo 11 | ) => a.score - b.score; 12 | 13 | export function addInsightsToHeap(insights: InsightInfo[], heap: Heap>) { 14 | insights?.forEach((item) => { 15 | if (heap.length >= heap.limit) { 16 | heap.pushpop(item); 17 | } else { 18 | heap.add(item); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /site/examples/advice/advise-and-lint/demo/ca.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import { PagList, JSONView } from 'antv-site-demo-rc'; 5 | import { Advisor } from '@antv/ava'; 6 | 7 | // contants 8 | 9 | const defaultData = [ 10 | { price: 100, type: 'A' }, 11 | { price: 120, type: 'B' }, 12 | { price: 150, type: 'C' }, 13 | ]; 14 | 15 | // usage 16 | const myChartAdvisor = new Advisor(); 17 | const results = myChartAdvisor.advise({ data: defaultData }); 18 | 19 | const App = () => ( 20 | } 23 | /> 24 | ); 25 | 26 | ReactDOM.render(, document.getElementById('container')); 27 | -------------------------------------------------------------------------------- /packages/ava/src/ckb/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { zhCN } from './zh-CN'; 2 | import { CkbDictionary, I18nLanguage } from './types'; 3 | 4 | /** 5 | * Mapping from languages to translation lists. 6 | * 7 | * 从语言类型到翻译表的映射 8 | */ 9 | const translateMapping: Record = { 10 | 'zh-CN': zhCN, 11 | }; 12 | 13 | /** 14 | * Get CKB dictionary of specific language except English. 15 | * 16 | * 得到除英文外的所有语言词汇表 17 | * 18 | * @param lang i18n Language code 19 | * @returns TranslateList or null 20 | */ 21 | export function ckbDict(lang: I18nLanguage): CkbDictionary { 22 | if (!lang || !Object.keys(translateMapping).includes(lang)) { 23 | throw new Error(`No CKB Dictionary for lang code ${lang}!`); 24 | } 25 | 26 | return translateMapping[lang]; 27 | } 28 | -------------------------------------------------------------------------------- /site/docs/api/types/ckb.en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Types about CKB 3 | order: 1 4 | --- 5 | 6 | 7 | 8 | ### ChartKnowledge 9 | 10 | TS type of knowledge for a chart type. 11 | Could be in any language or values(string), or for custom chart types(with a `toSpec` function). 12 | 13 | ```ts 14 | type ChartKnowledge = { 15 | id: string; 16 | name: string; 17 | alias: string[]; 18 | family: string[]; 19 | def: string; 20 | purpose: string[]; 21 | coord: string[]; 22 | category: string[]; 23 | shape: string[]; 24 | dataPres: (Omit & { fieldConditions: string[] })[]; 25 | channel: string[]; 26 | recRate: string; 27 | toSpec?: (data: Data, dataProps: any) => Specification | null; 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/styled/entity.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import { getFontSize, getThemeColor } from '../theme'; 4 | 5 | import type { ThemeStylesProps } from '../types'; 6 | 7 | export const Entity = styled.span` 8 | align-items: center; 9 | box-sizing: border-box; 10 | font-size: ${({ size }) => getFontSize(size)}; 11 | font-family: Roboto-Medium, PingFangSC, sans-serif; 12 | border-radius: 2px; 13 | // 这里 type 没有区分 colorBase 还是 colorEntityBase 都是 ‘text’,因为 colorToken 虽然不同但是目前默认色板一样,所以就先这样。 14 | // TODO @yuxi 待之后对标题等额外处理的时候可以一起考虑 15 | color: ${({ theme, palette }) => getThemeColor({ colorToken: 'colorEntityBase', theme, palette, type: 'text' })}; 16 | margin: 0 1px; 17 | overflow-wrap: break-word; 18 | `; 19 | -------------------------------------------------------------------------------- /packages/ava/src/insight/pipeline/index.ts: -------------------------------------------------------------------------------- 1 | import { extractInsights, generateInsightsWithVisualizationSpec } from './insight'; 2 | 3 | import type { Datum, InsightOptions, InsightsResult, InsightVisualizationOptions } from '../types'; 4 | 5 | export function getInsights(sourceData: Datum[], options?: InsightOptions): InsightsResult { 6 | const extractResult = extractInsights(sourceData, options); 7 | if (options?.visualization) { 8 | // Provide all vis options 9 | const visOption: InsightVisualizationOptions = { 10 | lang: 'en-US', 11 | ...(options?.visualization === true ? {} : options?.visualization), 12 | }; 13 | return generateInsightsWithVisualizationSpec(extractResult, { ...options, visualization: visOption }); 14 | } 15 | return extractResult; 16 | } 17 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/types/enhance-spec.ts: -------------------------------------------------------------------------------- 1 | import type { EntityMetaData } from '@antv/ava'; 2 | import type { ThemeStylesProps } from './props'; 3 | 4 | export type TypeOrMetaReturnType = 5 | | T 6 | | ((value: string, metadata: EntityMetaData, themeStyles: ThemeStylesProps) => T); 7 | 8 | /** entity phrase encoding channel */ 9 | export type EntityEncoding = Partial<{ 10 | color: TypeOrMetaReturnType; 11 | bgColor: TypeOrMetaReturnType; 12 | fontSize: TypeOrMetaReturnType; 13 | fontWeight: TypeOrMetaReturnType; 14 | underline: TypeOrMetaReturnType; 15 | prefix: TypeOrMetaReturnType; 16 | suffix: TypeOrMetaReturnType; 17 | inlineChart: TypeOrMetaReturnType; 18 | }>; 19 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/commonMarks/pointMark.ts: -------------------------------------------------------------------------------- 1 | import { PointMark } from '@antv/g2'; 2 | import { isNil } from 'lodash'; 3 | 4 | import { PointPatternInfo } from '../../../types'; 5 | import { PointMarkConfig } from '../../types'; 6 | 7 | /** get mark for point patterns, the patterns should have same dimension and measure */ 8 | export const pointMarkStrategy = (patterns: PointPatternInfo[], config: PointMarkConfig): PointMark => { 9 | const data = []; 10 | patterns.forEach(({ x, y }) => { 11 | if (isNil(x) || isNil(y)) return; 12 | data.push({ x, y }); 13 | }); 14 | 15 | const pointMark: PointMark = { 16 | type: 'point', 17 | data, 18 | encode: { 19 | x: 'x', 20 | y: 'y', 21 | }, 22 | ...config, 23 | }; 24 | return pointMark; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/data/dataset/field/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { generateArrayIndex, isAxis } from '../../../../../src/data/dataset/field/utils'; 2 | 3 | test('isAxis', () => { 4 | expect(isAxis(1)).toBeTruthy(); 5 | expect(isAxis('axis')).toBeTruthy(); 6 | }); 7 | 8 | test('generateArrayIndex', () => { 9 | const data = ['a', 'b', 'c', 'd', 'e', 'f']; 10 | expect(generateArrayIndex(['a', 'b', 'c', 'd', 'e', 'f'])).toStrictEqual([0, 1, 2, 3, 4, 5]); 11 | expect(generateArrayIndex(['a', 'b', 'c', 'd', 'e', 'f'], [6, 7, 8, 9, 10, 11])).toStrictEqual([6, 7, 8, 9, 10, 11]); 12 | const extraIndex = [1, 2, 3]; 13 | expect(() => generateArrayIndex(['a', 'b', 'c', 'd', 'e', 'f'], [1, 2, 3])).toThrow( 14 | `Index length is ${extraIndex.length}, but data size is ${data.length}` 15 | ); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/data/utils/common.test.ts: -------------------------------------------------------------------------------- 1 | import { unique, range, assert, isParentChild } from '../../../../src/data/utils/common'; 2 | 3 | test('unique', () => { 4 | const data = [1, 2, 3, 3, 2, 1]; 5 | expect(unique(data)).toStrictEqual([1, 2, 3]); 6 | }); 7 | 8 | test('range', () => { 9 | const data = 4; 10 | expect(range(data)).toStrictEqual([0, 1, 2, 3]); 11 | }); 12 | 13 | test('assert', () => { 14 | expect(() => assert(false, 'It is false!')).toThrow('It is false!'); 15 | }); 16 | 17 | test('isParentChild', () => { 18 | expect(isParentChild(['a', 'b', 'c'], [1, 2, 3])).toBe(true); 19 | expect(isParentChild(['a', 'a', 'c'], [1, 1, 3])).toBe(true); 20 | expect(isParentChild(['a', 'a', 'c'], [1, 2, 3])).toBe(true); 21 | expect(isParentChild(['a', 'b', 'c'], [1, 1, 3])).toBe(false); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/ava/src/insight/narrative/strategy/index.ts: -------------------------------------------------------------------------------- 1 | // 趋势类 trend 2 | export { default as ChangePointNarrativeStrategy } from './changePoint'; 3 | export { default as TimeSeriesOutlierNarrativeStrategy } from './timeSeriesOutlier'; 4 | export { default as TrendNarrativeStrategy } from './trend'; 5 | 6 | // 分布类 distribute 7 | export { default as MajorityNarrativeStrategy } from './majority'; 8 | export { default as LowVarianceNarrativeStrategy } from './lowVariance'; 9 | export { default as CategoryNarrativeStrategy } from './categoryOutlier'; 10 | 11 | // 多指标 multi metric 12 | export { default as CorrelationNarrativeStrategy } from './correlation'; 13 | 14 | // 共性/例外 homogeneous 15 | export { default as CommonnessNarrativeStrategy } from './commonness'; 16 | export { default as ExceptionNarrativeStrategy } from './exception'; 17 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/advisor/advise-pipeline/infer-data-type.test.ts: -------------------------------------------------------------------------------- 1 | import { inferDataType } from '../../../../src/advisor/utils/inferDataType'; 2 | 3 | describe('chart mapping', () => { 4 | test('chart mapping from CKB', () => { 5 | const spec = { 6 | type: 'interval', 7 | data: [ 8 | { genre: 'Sports', sold: 275 }, 9 | { genre: 'Strategy', sold: 115 }, 10 | { genre: 'Action', sold: 120 }, 11 | { genre: 'Shooter', sold: 350 }, 12 | { genre: 'Other', sold: 150 }, 13 | ], 14 | encode: { 15 | x: 'genre', 16 | y: 'sold', 17 | }, 18 | }; 19 | const dataType = inferDataType(spec.data, 'genre', undefined); 20 | 21 | const expectDataType = 'categorical'; 22 | 23 | expect(dataType).toEqual(expectDataType); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /site/docs/guide/ntv/ntv-comp.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NarrativeTextVis 组件 3 | order: 2 4 | --- 5 | 6 | `NarrativeTextVis` 是一个用于渲染数据解读文本的 React 组件,以 [ntv-schema](./ntv-schema.zh.md) 为基础数据结构。 7 | 8 | ## 基础使用 9 | 10 | 1. 引入组件; 11 | 2. 构建符合 [ntv-schema](./ntv-schema.zh.md) 的数据格式; 12 | 3. 传入组件进行渲染; 13 | 14 | 15 | ```tsx 16 | import { NarrativeTextVis, NarrativeTextSpec } from '@antv/ava-react'; 17 | 18 | const textSpec: NarrativeTextSpec = { 19 | // ... 20 | }; 21 | 22 | export default () => { 23 | return ; 24 | } 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 40 | -------------------------------------------------------------------------------- /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'father'; 2 | 3 | export default (type, name) => { 4 | const commonConfig = { 5 | esm: { 6 | output: 'esm', 7 | }, 8 | cjs: { 9 | output: 'lib', 10 | }, 11 | umd: { 12 | name, 13 | output: 'dist', 14 | }, 15 | }; 16 | 17 | if (type === 'ts') { 18 | return defineConfig({ 19 | umd: { 20 | name, 21 | output: 'dist', 22 | }, 23 | }); 24 | } 25 | if (type === 'react') { 26 | return defineConfig({ 27 | ...commonConfig, 28 | umd: { 29 | name, 30 | output: 'dist', 31 | externals: { 32 | antd: 'antd', 33 | react: 'react', 34 | 'react-dom': 'ReactDom', 35 | }, 36 | }, 37 | }); 38 | } 39 | return defineConfig(commonConfig); 40 | }; 41 | -------------------------------------------------------------------------------- /site/docs/common/phrase.en.md: -------------------------------------------------------------------------------- 1 | ***IPhrase*** Description of phrase 2 | 3 | Phrase descriptions are distinguished by type, which is divided into text phrases and entity phrases, and entity phrases are used to mark lexical attributes. 4 | The current descriptions in the lite-insight module involve entity types such as 5 | 6 | * `'metric_name'`: Metric Name; 7 | * `'metric_value'`: Metric Value; 8 | * `'trend_desc'`: Trend Description; 9 | * `'dim_value'`: Dimension Value; 10 | 11 | ```ts 12 | export type IPhrase = ITextPhrase | IEntityPhrase; 13 | 14 | export interface ITextPhrase { 15 | type: 'text'; 16 | value: string; 17 | } 18 | 19 | export interface IEntityPhrase { 20 | type: 'entity'; 21 | value?: string; 22 | metadata?: { 23 | entityType: 'metric_name' | 'metric_value' | 'trend_desc' | 'dim_value'; 24 | }; 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createMetricValue.tsx: -------------------------------------------------------------------------------- 1 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 2 | import { getThemeColor } from '../../../theme'; 3 | import { isNumberLike } from '../../../../utils'; 4 | 5 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 6 | 7 | const defaultMetricValueDescriptor: SpecificEntityPhraseDescriptor = { 8 | encoding: { 9 | color: (value, metadata, { theme, palette }) => 10 | getThemeColor({ colorToken: 'colorMetricValue', theme, palette, type: 'metric_value' }), 11 | }, 12 | tooltip: { 13 | title: (value, metadata) => (isNumberLike(metadata.origin) ? `${metadata.origin}` : null), 14 | }, 15 | }; 16 | 17 | export const createMetricValue = createEntityPhraseFactory('metric_value', defaultMetricValueDescriptor); 18 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/buishand-u.ts: -------------------------------------------------------------------------------- 1 | import { sumBy, sum, mean } from 'lodash'; 2 | 3 | import { maxabs } from '..'; 4 | 5 | import { calcPValue } from './window'; 6 | 7 | import type { ChangePointItem } from './types'; 8 | 9 | /** 10 | * Buishad U statistics test 11 | */ 12 | export function buishandUTest(data: number[]): ChangePointItem & { uValue: number } { 13 | const n = data?.length; 14 | const meanValue = mean(data); 15 | const Sk = data.map((_, index) => sum(data.slice(0, index + 1)) - meanValue * (index + 1)); 16 | const U = sumBy(Sk.slice(0, n - 1), (item) => item ** 2) / (n * (n + 1)); 17 | const Smax = maxabs(Sk); 18 | 19 | const maxIndex = Sk.findIndex((item) => item === Smax); 20 | 21 | return { 22 | uValue: U, 23 | index: maxIndex, 24 | significance: 1 - calcPValue(data, maxIndex), 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/caches.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Cache some statistics results to improve performance. 3 | */ 4 | const CACHES: WeakMap> = new WeakMap(); 5 | 6 | /** 7 | * Cache the value for target and key. 8 | * @param target - target 9 | * @param key - key 10 | * @param value - value 11 | */ 12 | export function set(target: any[], key: string, value: T) { 13 | if (!CACHES.get(target)) { 14 | CACHES.set(target, new Map()); 15 | } 16 | CACHES.get(target).set(key, value); 17 | return value; 18 | } 19 | 20 | /** 21 | * Get the cached value for target and key. 22 | * @param target - target 23 | * @param key - key 24 | */ 25 | export function get(target: any[], key: string): T | undefined { 26 | const cache = CACHES.get(target); 27 | if (!cache) return undefined; 28 | return cache.get(key); 29 | } 30 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/commonMarks/intervalMark.ts: -------------------------------------------------------------------------------- 1 | import { RectMark } from '@antv/g2'; 2 | 3 | import { PointPatternInfo } from '../../../types'; 4 | import { INSIGHT_COLOR_PLATTE } from '../../constants'; 5 | import { IntervalMarkConfig } from '../../types'; 6 | 7 | /** get mark for point patterns, the patterns should have same dimension and measure */ 8 | export const intervalMarkStrategy = (patterns: PointPatternInfo[], config?: IntervalMarkConfig): RectMark => { 9 | const data = patterns.map(({ x, y }) => ({ x, y })); 10 | 11 | const intervalMark: RectMark = { 12 | type: 'interval', 13 | data, 14 | encode: { 15 | x: 'x', 16 | y: 'y', 17 | }, 18 | ...config, 19 | style: { 20 | ...config?.style, 21 | fill: INSIGHT_COLOR_PLATTE.outlier, 22 | }, 23 | }; 24 | return intervalMark; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/advisor/utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { deepMix } from '../../../../src/advisor/utils'; 2 | 3 | describe('utils test', () => { 4 | test('deep-mix', () => { 5 | const data = [ 6 | { price: 100, cost: 100, revenue: 500, amount: 150, type: 'A' }, 7 | { price: 120, cost: 200, revenue: 550, amount: 250, type: 'B' }, 8 | { price: 150, cost: 410, revenue: 600, amount: 60, type: 'C' }, 9 | ]; 10 | const spec = { 11 | type: 'point', 12 | data, 13 | encode: { 14 | x: 'price', 15 | y: 'volume', 16 | }, 17 | axis: { 18 | domain: false, 19 | }, 20 | }; 21 | const partOfNewSpec = { 22 | axis: { 23 | ticks: false, 24 | }, 25 | }; 26 | deepMix(spec, partOfNewSpec); 27 | expect(spec.axis).toHaveProperty('ticks'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createContributeRatio.tsx: -------------------------------------------------------------------------------- 1 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 2 | import { getThemeColor } from '../../../theme'; 3 | import { isNumberLike } from '../../../../utils'; 4 | 5 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 6 | 7 | const defaultContributeRatioDescriptor: SpecificEntityPhraseDescriptor = { 8 | encoding: { 9 | color: (value, metadata, { theme, palette }) => 10 | getThemeColor({ colorToken: 'colorConclusion', theme, palette, type: 'contribute_ratio' }), 11 | }, 12 | tooltip: { 13 | title: (value, metadata) => (isNumberLike(metadata.origin) ? `${metadata.origin}` : null), 14 | }, 15 | }; 16 | 17 | export const createContributeRatio = createEntityPhraseFactory('contribute_ratio', defaultContributeRatioDescriptor); 18 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/purpose-check.ts: -------------------------------------------------------------------------------- 1 | import type { RuleModule } from '../types'; 2 | 3 | export const purposeCheck: RuleModule = { 4 | id: 'purpose-check', 5 | type: 'HARD', 6 | docs: { 7 | lintText: 'Choose chart types that satisfy the purpose, if purpose is defined.', 8 | }, 9 | trigger: () => { 10 | return true; 11 | }, 12 | validator: (args): number => { 13 | let result = 0; 14 | const { chartType, purpose, chartWIKI } = args; 15 | 16 | // if purpose is not defined 17 | if (!purpose) { 18 | result = 1; 19 | return result; 20 | } 21 | 22 | if (chartType && chartWIKI[chartType] && purpose) { 23 | const purp = chartWIKI[chartType].purpose || ''; 24 | if (purp.includes(purpose)) { 25 | result = 1; 26 | return result; 27 | } 28 | } 29 | return result; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/utils/clone.ts: -------------------------------------------------------------------------------- 1 | const cloneDeep = (obj) => { 2 | if (typeof obj !== 'object' || obj === null) { 3 | return obj; 4 | } 5 | let result; 6 | if (Array.isArray(obj)) { 7 | result = []; 8 | for (let i = 0, l = obj.length; i < l; i += 1) { 9 | if (typeof obj[i] === 'object' && obj[i] != null) { 10 | result[i] = cloneDeep(obj[i]); 11 | } else { 12 | result[i] = obj[i]; 13 | } 14 | } 15 | } else { 16 | result = {}; 17 | const objKeys = Object.keys(obj); 18 | for (let i = 0; i < objKeys.length; i += 1) { 19 | const key = objKeys[i]; 20 | if (typeof obj[key] === 'object' && obj[key] != null) { 21 | result[key] = cloneDeep(obj[key]); 22 | } else { 23 | result[key] = obj[key]; 24 | } 25 | } 26 | } 27 | 28 | return result; 29 | }; 30 | 31 | export default cloneDeep; 32 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/constants.ts: -------------------------------------------------------------------------------- 1 | import { ChartType, InsightType } from '../types'; 2 | 3 | export const INSIGHT_COLOR_PLATTE: Record = { 4 | highlight: '#E09322', 5 | outlier: '#CB5140', 6 | font: '#2C3542', 7 | defaultPointColor: '#fff', 8 | } as const; 9 | 10 | export const BOLD_FONT_WEIGHT = 500; 11 | 12 | export const TEXT_STYLE = { 13 | textAlign: 'center', 14 | fill: INSIGHT_COLOR_PLATTE.font, 15 | opacity: 0.65, 16 | }; 17 | 18 | export const ChartTypeMap: Record = { 19 | category_outlier: 'column_chart', 20 | trend: 'line_chart', 21 | change_point: 'line_chart', 22 | time_series_outlier: 'line_chart', 23 | majority: 'pie_chart', 24 | low_variance: 'column_chart', 25 | correlation: 'scatter_plot', 26 | }; 27 | 28 | export const PIE_RADIUS_STYLE = { 29 | innerRadius: 0.25, 30 | outerRadius: 0.8, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createOtherMetricValue.tsx: -------------------------------------------------------------------------------- 1 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 2 | import { getThemeColor } from '../../../theme'; 3 | import { isNumberLike } from '../../../../utils'; 4 | 5 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 6 | 7 | const defaultOtherMetricValueDescriptor: SpecificEntityPhraseDescriptor = { 8 | encoding: { 9 | color: (value, metadata, { theme, palette }) => 10 | getThemeColor({ colorToken: 'colorOtherValue', theme, palette, type: 'other_metric_value' }), 11 | }, 12 | tooltip: { 13 | title: (value, metadata) => (isNumberLike(metadata.origin) ? `${metadata.origin}` : null), 14 | }, 15 | }; 16 | 17 | export const createOtherMetricValue = createEntityPhraseFactory( 18 | 'other_metric_value', 19 | defaultOtherMetricValueDescriptor 20 | ); 21 | -------------------------------------------------------------------------------- /packages/ava/src/insight/narrative/strategy/base.ts: -------------------------------------------------------------------------------- 1 | import type { InsightType, InsightInfo, Language, HomogeneousInsightType, HomogeneousPatternInfo } from '../../types'; 2 | import type { ParagraphSpec, Structure, StructureTemp } from '../../../ntv/types'; 3 | 4 | export type HomogeneousInsightInfo = HomogeneousPatternInfo & Omit; 5 | 6 | export abstract class InsightNarrativeStrategy< 7 | P = any // `any` allow input all insight 8 | > { 9 | static insightType: InsightType | HomogeneousInsightType; 10 | 11 | protected static structures: Record; 12 | 13 | protected static structureTemps?: Record; 14 | 15 | abstract generateTextSpec(insightInfo: InsightInfo

| HomogeneousInsightInfo, lang: Language): ParagraphSpec[]; 16 | 17 | // TODO support text 18 | // abstract generateStr(lang: Language): string[]; 19 | } 20 | -------------------------------------------------------------------------------- /packages/ava/src/insight/narrative/factory.ts: -------------------------------------------------------------------------------- 1 | import * as insightNarrativeStrategies from './strategy'; 2 | 3 | import type { InsightType, HomogeneousInsightType } from '../types'; 4 | import type { InsightNarrativeStrategy } from './strategy/base'; 5 | 6 | export default class InsightNarrativeStrategyFactory { 7 | private static narrativeStrategyMap = new Map(); 8 | 9 | static { 10 | Object.values(insightNarrativeStrategies).forEach((Strategy) => { 11 | this.narrativeStrategyMap.set(Strategy.insightType, new Strategy()); 12 | }); 13 | } 14 | 15 | static getStrategy(type: InsightType | HomogeneousInsightType) { 16 | const strategy = InsightNarrativeStrategyFactory.narrativeStrategyMap.get(type); 17 | if (!strategy) throw Error(`There is no description policy for this '${type}'.`); 18 | return strategy; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/extractors/trend.test.ts: -------------------------------------------------------------------------------- 1 | import { insightPatternsExtractor } from '../../../../src/insight/insights'; 2 | 3 | const data = [ 4 | { year: '1991', value: 3 }, 5 | { year: '1992', value: 4 }, 6 | { year: '1993', value: 3.5 }, 7 | { year: '1994', value: 5 }, 8 | { year: '1995', value: 4.9 }, 9 | { year: '1996', value: 6 }, 10 | { year: '1997', value: 7 }, 11 | { year: '1998', value: 9 }, 12 | { year: '1999', value: 13 }, 13 | ]; 14 | 15 | describe('extract trend insight', () => { 16 | test('check trend result', () => { 17 | const result = insightPatternsExtractor({ 18 | data, 19 | dimensions: [{ fieldName: 'year' }], 20 | measures: [{ fieldName: 'value', method: 'SUM' }], 21 | insightType: 'trend', 22 | options: { 23 | filterInsight: true, 24 | }, 25 | }); 26 | expect(result[0]?.trend).toEqual('increasing'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/examples/insight-card/basic/demo/insight.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import { InsightCard } from '@antv/ava-react'; 5 | import { getInsights } from '@antv/ava'; 6 | 7 | const data = [ 8 | { year: '2000', value: 1 }, 9 | { year: '2001', value: -1 }, 10 | { year: '2002', value: 2 }, 11 | { year: '2003', value: -2 }, 12 | { year: '2004', value: 7 }, 13 | { year: '2005', value: 3 }, 14 | { year: '2006', value: -3 }, 15 | { year: '2007', value: 0 }, 16 | { year: '2008', value: 0 }, 17 | { year: '2009', value: 1 }, 18 | ]; 19 | 20 | const firstInsight = getInsights(data).insights[0]; 21 | 22 | // 如果需要修改语言配置,使用 visualizationOptions={{ lang: 'zh-CN' }} 默认为 'en-US' 23 | // If language config needed, use visualizationOptions={{ lang: 'zh-CN' }}, 'en-US' by default 24 | ReactDOM.render(, document.getElementById('container')); 25 | -------------------------------------------------------------------------------- /site/examples/unused-demo/chart-advisor/relation/demo/tree.jsx: -------------------------------------------------------------------------------- 1 | import { Advisor } from '@antv/ava'; 2 | import { specToG6Plot } from '@antv/antv-spec'; 3 | 4 | // Prepare hierachical data: some entities have a parent-child relationship with each other 5 | fetch('https://gw.alipayobjects.com/os/antfincdn/0cfrQND8L/ava-knowledgetree-demo.json') 6 | .then((res) => res.json()) 7 | .then((data) => { 8 | // Initialize an advisor and pass the data to its advise function 9 | const myAdvisor = new Advisor(); 10 | const advices = myAdvisor.advise({ data }); 11 | 12 | // The advices are returns in order from largest score to smallest score, you can choose the best advice to generate visualization 13 | const bestAdvice = advices[0]; 14 | if (bestAdvice) { 15 | const { spec } = bestAdvice; 16 | const container = document.getElementById('container'); 17 | specToG6Plot(spec, container); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/assets/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const MARGIN_RIGHT = 2; 4 | 5 | export const ArrowUp = () => ( 6 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | export const ArrowDown = () => ( 21 | 29 | 30 | 31 | 32 | 33 | ); 34 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/nested/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { v4 } from 'uuid'; 4 | 5 | import { Paragraph, type ParagraphProps } from '../paragraph'; 6 | import { Container } from '../styled'; 7 | 8 | import type { NestedParagraphSpec } from '@antv/ava'; 9 | 10 | type NestedParagraphProps = Omit & { 11 | spec: NestedParagraphSpec; 12 | }; 13 | 14 | export function NestedParagraph({ spec, ...paragraphProps }: NestedParagraphProps) { 15 | return ( 16 | 17 | {spec.children?.map((item) => { 18 | if ('children' in item && !('customType' in item)) { 19 | return ; 20 | } 21 | return ; 22 | })} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /site/examples/insight/basic/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "basic.jsx", 9 | "title": { 10 | "zh": "基础用法", 11 | "en": "Basic usage" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/zt2jXO97%262/li-basic.gif" 14 | }, 15 | { 16 | "filename": "custom.jsx", 17 | "title": { 18 | "zh": "自定义指标和维度", 19 | "en": "Custom measures and dimensions" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/ixkElKx8UT/li-custom-measures.gif" 22 | }, 23 | { 24 | "filename": "specify-type.jsx", 25 | "title": { 26 | "zh": "指定洞察类型", 27 | "en": "Extract insight of a specified type" 28 | }, 29 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/zt2jXO97%262/li-basic.gif" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/extractors/change-point.test.ts: -------------------------------------------------------------------------------- 1 | import { insightPatternsExtractor } from '../../../../src/insight/insights'; 2 | 3 | const data = [ 4 | { year: '1991', value: 0.3 }, 5 | { year: '1992', value: -0.5 }, 6 | { year: '1993', value: 0.05 }, 7 | { year: '1994', value: -0.2 }, 8 | { year: '1995', value: 0.4 }, 9 | { year: '1996', value: 6 }, 10 | { year: '1997', value: 3 }, 11 | { year: '1998', value: 9 }, 12 | { year: '1999', value: 5 }, 13 | ]; 14 | 15 | describe('extract change-point insight', () => { 16 | test('check change-point result', () => { 17 | const result = insightPatternsExtractor({ 18 | data, 19 | dimensions: [{ fieldName: 'year' }], 20 | measures: [{ fieldName: 'value', method: 'SUM' }], 21 | insightType: 'change_point', 22 | options: { 23 | filterInsight: true, 24 | }, 25 | }); 26 | expect(result[0]?.index).toEqual(5); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/ChartAdvisor/Linter/LintCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { List } from 'antd'; 4 | 5 | import { Lint } from '../../../../../packages/ava/lib'; 6 | 7 | type LintProps = { 8 | lints: Lint[]; 9 | }; 10 | 11 | export const LintCard = ({ lints }: LintProps) => { 12 | return ( 13 | { 21 | return ( 22 | 23 | Error ID: {item.id} 24 |

Error Type: {item.type}
25 |
Score: {item.score}
26 |
docs: {item.docs.lintText}
27 | 28 | ); 29 | }} 30 | > 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/data-field-qty.ts: -------------------------------------------------------------------------------- 1 | import type { RuleModule } from '../types'; 2 | 3 | export const dataFieldQty: RuleModule = { 4 | id: 'data-field-qty', 5 | type: 'HARD', 6 | docs: { 7 | lintText: 'Data must have at least the min qty of the prerequisite.', 8 | }, 9 | trigger: () => { 10 | return true; 11 | }, 12 | validator: (args): number => { 13 | let result = 0; 14 | const { dataProps, chartType, chartWIKI } = args; 15 | if (dataProps && chartType && chartWIKI[chartType]) { 16 | result = 1; 17 | const dataPres = chartWIKI[chartType].dataPres || []; 18 | const minFieldQty = dataPres.map((e: any) => e.minQty).reduce((acc: number, cv: number) => acc + cv); 19 | 20 | if (dataProps.length) { 21 | const fieldQty = dataProps.length; 22 | if (fieldQty >= minFieldQty) { 23 | result = 1; 24 | } 25 | } 26 | } 27 | return result; 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /site/examples/others/thumbnails/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "usage.jsx", 9 | "title": { 10 | "en": "Thumbnails Usage", 11 | "zh": "Thumbnails 缩略图用法" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/ObPIQYzZJU/thumbnails-usage.png" 14 | }, 15 | { 16 | "filename": "all.jsx", 17 | "title": { 18 | "en": "View All Thumbnails", 19 | "zh": "Thumbnails 缩略图一览" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/YQGTC0Zq%26t/thumbnails-viewall.gif" 22 | }, 23 | { 24 | "filename": "select.jsx", 25 | "title": { 26 | "en": "Select Chart by Thumbnails", 27 | "zh": "通过缩略图选择图表" 28 | }, 29 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/zwo0GDlia6/thumbnails-select.gif" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/ChartAdvisor/Chart.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | import { render } from '@antv/g2'; 4 | 5 | export const Chart = ({ id, spec }: any) => { 6 | const containerRef = useRef(null); 7 | useEffect(() => { 8 | if (containerRef.current) { 9 | const container = document.getElementById('container'); 10 | const style = container ? getComputedStyle(container) : undefined; 11 | const size = style 12 | ? { 13 | width: parseInt(style.width, 10), 14 | height: parseInt(style.height, 10), 15 | } 16 | : {}; 17 | const node = render({ 18 | ...size, 19 | ...spec, 20 | // theme:'classic' 21 | }); 22 | containerRef.current.appendChild(node); 23 | } 24 | }, []); 25 | // @ts-ignore 待 g2 确认渲染方式 26 | return
; 27 | }; 28 | -------------------------------------------------------------------------------- /site/examples/ntv/custom/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "entity.tsx", 9 | "title": { 10 | "en": "Custom Entities", 11 | "zh": "自定义实体短语" 12 | }, 13 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*A0AGQLEX1nwAAAAAAAAAAAAADmJ7AQ/original" 14 | }, 15 | { 16 | "filename": "phrase.tsx", 17 | "title": { 18 | "en": "Custom Phrases", 19 | "zh": "自定义短语" 20 | }, 21 | "screenshot": "https://mdn.alipayobjects.com/huamei_vvq19s/afts/img/A*IXhfQ41Sz44AAAAAAAAAAAAADi2DAQ/original" 22 | }, 23 | { 24 | "filename": "block.tsx", 25 | "title": { 26 | "en": "Custom Block", 27 | "zh": "自定义区块" 28 | }, 29 | "screenshot": "https://mdn.alipayobjects.com/huamei_vvq19s/afts/img/A*H8hzQo2NAHMAAAAAAAAAAAAADi2DAQ/original" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /site/examples/ntv/case/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "chart-explanation.tsx", 9 | "title": { 10 | "en": "Chart Explanation", 11 | "zh": "图表解读" 12 | }, 13 | "screenshot": "https://mdn.alipayobjects.com/huamei_vvq19s/afts/img/A*K3EmTK-ywA0AAAAAAAAAAAAADi2DAQ/original" 14 | }, 15 | { 16 | "filename": "report.tsx", 17 | "title": { 18 | "en": "Briefing", 19 | "zh": "业务简报" 20 | }, 21 | "screenshot": "https://mdn.alipayobjects.com/huamei_vvq19s/afts/img/A*Kw1LQrr9slcAAAAAAAAAAAAADi2DAQ/original" 22 | }, 23 | { 24 | "filename": "fluctuation.tsx", 25 | "title": { 26 | "en": "Fluctuation", 27 | "zh": "波动分析" 28 | }, 29 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*VPS0Q7DGHlQAAAAAAAAAAAAADmJ7AQ/original" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/bayesian.ts: -------------------------------------------------------------------------------- 1 | import BayesianChangePoint, { BreakPoint } from 'bayesian-changepoint'; 2 | 3 | import { calcPValue } from './pettitt-test'; 4 | 5 | import type { ChangePointItem } from './types'; 6 | 7 | function breakpointVerifier(next: BreakPoint, prev: BreakPoint): boolean { 8 | if (Math.abs(next.data - prev.data) >= 1) { 9 | return true; 10 | } 11 | 12 | return false; 13 | } 14 | 15 | /** 16 | * Bayesian Online Changepoint Detection 17 | */ 18 | export function bayesian(series: number[] = []): ChangePointItem[] { 19 | const detection = new BayesianChangePoint({ 20 | breakpointVerifier, 21 | chunkSize: series.length, 22 | iteratee: (t: number) => t, 23 | }); 24 | 25 | detection.exec(series); 26 | 27 | const result = detection.breakPoints().map((breakPoint) => ({ 28 | index: breakPoint.index, 29 | significance: 1 - calcPValue(series, breakPoint.index), 30 | })); 31 | 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /packages/ava/src/insight/narrative/strategy/helpers.ts: -------------------------------------------------------------------------------- 1 | import { lowerCase } from 'lodash'; 2 | 3 | import type { Language, InsightType } from '../../types'; 4 | 5 | export const getDiffDesc = (value: number, lang: Language) => { 6 | if (value > 0) return lang === 'en-US' ? 'more than' : '超过'; 7 | if (value < 0) return lang === 'en-US' ? 'less than' : '少于'; 8 | return lang === 'en-US' ? 'equal' : '持平'; 9 | }; 10 | 11 | export const INSIGHT_TYPE_NAME: Record = { 12 | change_point: '突变点', 13 | time_series_outlier: '时序异常值', 14 | trend: '趋势', 15 | majority: '主要因素', 16 | low_variance: '分布均匀', 17 | category_outlier: '异常值', 18 | correlation: '相关性', 19 | }; 20 | 21 | export const getInsightName = (insightType: InsightType, lang: Language) => { 22 | if (lang === 'en-US') return lowerCase(insightType); 23 | return INSIGHT_TYPE_NAME[insightType]; 24 | }; 25 | 26 | export const getDefaultSeparator = (lang: Language) => { 27 | return lang === 'zh-CN' ? ',' : ', '; 28 | }; 29 | -------------------------------------------------------------------------------- /site/examples/advice/advisor-only/demo/data-advisor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import { PagList, JSONView } from 'antv-site-demo-rc'; 5 | // import 6 | import { Advisor } from '@antv/ava'; 7 | 8 | // contants 9 | 10 | const defaultData = [ 11 | { year: '2007', sales: 28 }, 12 | { year: '2008', sales: 55 }, 13 | { year: '2009', sales: 43 }, 14 | { year: '2010', sales: 91 }, 15 | { year: '2011', sales: 81 }, 16 | { year: '2012', sales: 53 }, 17 | { year: '2013', sales: 19 }, 18 | { year: '2014', sales: 87 }, 19 | { year: '2015', sales: 52 }, 20 | ]; 21 | 22 | // usage 23 | const myAdvisor = new Advisor(); 24 | const advices = myAdvisor.advise({ data: defaultData }); 25 | 26 | const App = () => ( 27 | } 30 | /> 31 | ); 32 | 33 | ReactDOM.render(, document.getElementById('container')); 34 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/Toolbar/types.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import type { ReactNode } from 'react'; 4 | import type { InsightCardInfo } from '../types'; 5 | import type { DEFAULT_TOOLS } from './constants'; 6 | 7 | export type Tool = { 8 | type: string; 9 | /** display icon */ 10 | icon?: ReactNode | ((type: string, data?: InsightCardInfo) => ReactNode); 11 | /** tool description, if assigned, it will show as a tooltip when icon is hovered */ 12 | description?: ReactNode | ((type: string, data?: InsightCardInfo) => ReactNode); 13 | onClick?: (event: React.MouseEvent, type: string, data?: InsightCardInfo) => void; 14 | }; 15 | 16 | export type ToolbarProps = { 17 | tools: Tool[]; 18 | data?: InsightCardInfo; 19 | }; 20 | 21 | export type DefaultToolType = (typeof DEFAULT_TOOLS)[number]; 22 | export type BaseIconButtonProps = { 23 | icon: ReactNode; 24 | onClick?: React.MouseEventHandler; 25 | description?: ReactNode; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/ntvPlugins/plugins/subspaceDescriptionPlugin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Typography } from "antd"; 4 | import { createCustomPhraseFactory } from "../../../NarrativeTextVis"; 5 | import { SUBSPACE_DESCRIPTION_PLUGIN_KEY } from "../../constants"; 6 | 7 | export const SubspaceDescription = ({ subspaceDescription }: { subspaceDescription: string }) => { 8 | return ( 9 | 18 | {subspaceDescription} 19 | 20 | ); 21 | }; 22 | 23 | export const subspaceDescriptionPlugin = createCustomPhraseFactory({ 24 | key: SUBSPACE_DESCRIPTION_PLUGIN_KEY, 25 | overwrite: (node, value) => { 26 | return ; 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /packages/ava/src/insight/constant.ts: -------------------------------------------------------------------------------- 1 | // Should be confidence level. Is the SIGNIFICANCE_BENCHMARK naming correct? by @pddpd 2 | export const SIGNIFICANCE_BENCHMARK = 0.95; 3 | 4 | export const SIGNIFICANCE_LEVEL = 0.05; 5 | 6 | export const INSIGHT_SCORE_BENCHMARK = 0.01; 7 | 8 | export const IMPACT_SCORE_WEIGHT = 0.2; 9 | 10 | export const INSIGHT_DEFAULT_LIMIT = 20; 11 | 12 | export const IQR_K = 1.5; 13 | 14 | export const LOWESS_N_STEPS = 2; 15 | 16 | export const PATTERN_TYPES = [ 17 | 'category_outlier', 18 | 'trend', 19 | 'change_point', 20 | 'time_series_outlier', 21 | 'majority', 22 | 'low_variance', 23 | 'correlation', 24 | ] as const; 25 | 26 | export const HOMOGENEOUS_PATTERN_TYPES = ['commonness', 'exception'] as const; 27 | 28 | export const VERIFICATION_FAILURE_INFO = 'The input does not meet the requirements.'; 29 | 30 | export const NO_PATTERN_INFO = 'No insights were found at the specified significance threshold.'; 31 | 32 | export const CHANGE_POINT_SIGNIFICANCE_BENCHMARK = 0.15; 33 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createTrendDesc.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { isArray } from 'lodash'; 4 | 5 | import { SingleLineChart } from '../../../line-charts'; 6 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 7 | import { getThemeColor } from '../../../theme'; 8 | 9 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 10 | 11 | const defaultTrendDescDescriptor: SpecificEntityPhraseDescriptor = { 12 | encoding: { 13 | color: (value, metadata, { theme, palette }) => 14 | getThemeColor({ colorToken: 'colorConclusion', theme, palette, type: 'trend_desc' }), 15 | inlineChart: (value, { detail }, themeStyles) => { 16 | if (isArray(detail) && detail.length) return ; 17 | return null; 18 | }, 19 | }, 20 | tooltip: false, 21 | }; 22 | 23 | export const createTrendDesc = createEntityPhraseFactory('trend_desc', defaultTrendDescDescriptor); 24 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/extractors/time-series-outlier.test.ts: -------------------------------------------------------------------------------- 1 | import { insightPatternsExtractor } from '../../../../src/insight/insights'; 2 | 3 | const data = [ 4 | { year: '1991', value: 3 }, 5 | { year: '1992', value: 4 }, 6 | { year: '1993', value: 3.5 }, 7 | { year: '1994', value: 5 }, 8 | { year: '1995', value: 4.9 }, 9 | { year: '1996', value: 6 }, 10 | { year: '1997', value: 7 }, 11 | { year: '1998', value: 13 }, 12 | { year: '1999', value: 9 }, 13 | ]; 14 | 15 | describe('extract time-series-outlier insight', () => { 16 | test('check outliers result', () => { 17 | const result = insightPatternsExtractor({ 18 | data, 19 | dimensions: [{ fieldName: 'year' }], 20 | measures: [{ fieldName: 'value', method: 'SUM' }], 21 | insightType: 'time_series_outlier', 22 | options: { 23 | filterInsight: true, 24 | }, 25 | }); 26 | const outliers = result?.map((item) => item.index); 27 | expect(outliers).toStrictEqual([7]); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/viewSpec.ts: -------------------------------------------------------------------------------- 1 | import type { G2Spec, Mark } from '@antv/g2'; 2 | import type { InsightInfo, PatternInfo } from '../../types'; 3 | 4 | export const viewSpecStrategy = (marks: Mark[], insight?: InsightInfo): G2Spec => { 5 | // majority insight pattern visualizes as 'pie', should not use y nice (G2 handle it as rescale y, the pie chart will be less than 100%) 6 | const isMajorityPattern = insight.patterns.map((pattern) => pattern.type).includes('majority'); 7 | const viewConfig = isMajorityPattern 8 | ? { scale: { y: { nice: false } } } 9 | : { 10 | scale: { y: { nice: true } }, 11 | }; 12 | 13 | return { 14 | type: 'view', 15 | theme: 'classic', 16 | axis: { 17 | x: { labelAutoHide: true, labelAutoRotate: false, title: false }, 18 | y: { title: false }, 19 | }, 20 | interaction: { 21 | tooltip: { groupName: false }, 22 | }, 23 | legend: false, 24 | children: marks, 25 | ...viewConfig, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/ava/src/data/utils/isType/constants.ts: -------------------------------------------------------------------------------- 1 | export const SPECIAL_BOOLEANS = [ 2 | [true, false], 3 | [0, 1], 4 | ['true', 'false'], 5 | ['Yes', 'No'], 6 | ['True', 'False'], 7 | ['0', '1'], 8 | ['是', '否'], 9 | ]; 10 | 11 | // For isDateString.ts 12 | export const DELIMITER = '([-_./\\s])'; 13 | export const YEAR = '(?(18|19|20)\\d{2})'; 14 | export const MONTH = '(?0?[1-9]|1[012])'; 15 | export const DAY = '(?0?[1-9]|[12]\\d|3[01])'; 16 | export const WEEK = '(?[0-4]\\d|5[0-2])'; 17 | export const WEEKDAY = '(?[1-7])'; 18 | export const BASE_HOUR = '(0?\\d|1\\d|2[0-4])'; 19 | export const BASE_MINUTE = '(0?\\d|[012345]\\d)'; 20 | export const HOUR = `(?${BASE_MINUTE})`; 21 | export const MINUTE = `(?${BASE_MINUTE})`; 22 | export const SECOND = `(?${BASE_MINUTE})`; 23 | export const MILLISECOND = '(?\\d{1,4})'; 24 | export const YEARDAY = '(?(([0-2]\\d|3[0-5])\\d)|36[0-6])'; 25 | export const OFFSET = `(?Z|[+-]${BASE_HOUR}(:${BASE_MINUTE})?)`; 26 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/styled/paragraph.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import { getThemeColor, getFontSize, getLineHeight } from '../theme'; 4 | 5 | import type { TextParagraphSpec } from '@antv/ava'; 6 | import type { ThemeStylesProps } from '../types'; 7 | 8 | export const P = styled.p>` 9 | white-space: pre-wrap; // 默认 pre 显示,可以显示空格和转义字符 10 | font-family: PingFangSC, sans-serif; 11 | color: ${({ theme, palette }) => getThemeColor({ colorToken: 'colorBase', theme, palette, type: 'text' })}; 12 | font-size: ${({ size }) => getFontSize(size)}; 13 | min-height: 24px; 14 | line-height: ${({ size }) => getLineHeight(size)}; 15 | margin-bottom: 4px; 16 | text-indent: ${({ indents }) => indents?.find((item) => item.type === 'first-line')?.length}; 17 | padding-left: ${({ indents }) => indents?.find((item) => item.type === 'left')?.length}; 18 | padding-right: ${({ indents }) => indents?.find((item) => item.type === 'right')?.length}; 19 | `; 20 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/proportion/ProportionChart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { getThemeColor } from '../../theme'; 4 | import { useSvgWrapper } from '../hooks/useSvgWrapper'; 5 | 6 | import { getArcPath } from './getArcPath'; 7 | 8 | import type { ThemeStylesProps } from '../../types'; 9 | 10 | export const ProportionChart: React.FC<{ data: number } & ThemeStylesProps> = ({ 11 | data, 12 | size = 'normal', 13 | theme = 'light', 14 | }) => { 15 | const [Svg, fontSize] = useSvgWrapper(size); 16 | const r = fontSize / 2; 17 | return ( 18 | 19 | 20 | {data >= 1 ? ( 21 | 22 | ) : ( 23 | 24 | )} 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/line-field-time-ordinal.ts: -------------------------------------------------------------------------------- 1 | import { intersects } from '../../utils'; 2 | 3 | import { MAX_SOFT_RULE_COEFFICIENT } from './constants'; 4 | 5 | import type { RuleModule } from '../types'; 6 | 7 | const applyChartTypes = ['line_chart', 'area_chart', 'stacked_area_chart', 'percent_stacked_area_chart']; 8 | 9 | export const lineFieldTimeOrdinal: RuleModule = { 10 | id: 'line-field-time-ordinal', 11 | type: 'SOFT', 12 | docs: { 13 | lintText: 'Data containing time or ordinal fields are suitable for line or area charts.', 14 | }, 15 | trigger: ({ chartType }) => { 16 | return applyChartTypes.includes(chartType); 17 | }, 18 | validator: (args): number => { 19 | let result = 1; 20 | const { dataProps } = args; 21 | if (dataProps) { 22 | const field4TimeOrOrdinal = dataProps.find((field) => intersects(field.levelOfMeasurements, ['Ordinal', 'Time'])); 23 | 24 | if (field4TimeOrOrdinal) { 25 | result = MAX_SOFT_RULE_COEFFICIENT * 0.5; 26 | } 27 | } 28 | return result; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/no-redundant-field.ts: -------------------------------------------------------------------------------- 1 | import type { RuleModule } from '../types'; 2 | 3 | export const noRedundantField: RuleModule = { 4 | id: 'no-redundant-field', 5 | type: 'HARD', 6 | docs: { 7 | lintText: 'No redundant field.', 8 | }, 9 | trigger: () => { 10 | return true; 11 | }, 12 | validator: (args): number => { 13 | let result = 0; 14 | const { dataProps, chartType, chartWIKI } = args; 15 | 16 | if (dataProps && chartType && chartWIKI[chartType]) { 17 | const dataPres = chartWIKI[chartType].dataPres || []; 18 | const maxFieldQty = dataPres 19 | .map((e: any) => { 20 | if (e.maxQty === '*') { 21 | return 99; 22 | } 23 | return e.maxQty; 24 | }) 25 | .reduce((acc: number, cv: number) => acc + cv); 26 | 27 | if (dataProps.length) { 28 | const fieldQty = dataProps.length; 29 | if (fieldQty <= maxFieldQty) { 30 | result = 1; 31 | } 32 | } 33 | } 34 | 35 | return result; 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/histogram.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a histogram chart. 6 | describe('should advise histogram', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('histogram'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices[0].type).toBe('histogram'); 13 | }); 14 | }); 15 | 16 | // In the following cases, the recommended result should NOT be a histogram chart. 17 | describe('should NOT advise histogram', () => { 18 | test('categrorical field should not be x-axis of histogram chart', async () => { 19 | const data = await dataByChartId('line_chart'); 20 | 21 | const myAdvisor = new Advisor(); 22 | const advices = myAdvisor.advise({ data }); 23 | expect(advices[0].type === 'histogram').toBe(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/extractors/majority.test.ts: -------------------------------------------------------------------------------- 1 | import { insightPatternsExtractor } from '../../../../src/insight/insights'; 2 | 3 | const data = [ 4 | { 5 | type: 'A', 6 | sales: 38, 7 | }, 8 | { 9 | type: 'B', 10 | sales: 52, 11 | }, 12 | { 13 | type: 'C', 14 | sales: 48, 15 | }, 16 | { 17 | type: 'D', 18 | sales: 45, 19 | }, 20 | { 21 | type: 'E', 22 | sales: 48, 23 | }, 24 | { 25 | type: 'F', 26 | sales: 473, 27 | }, 28 | { 29 | type: 'G', 30 | sales: 38, 31 | }, 32 | { 33 | type: 'H', 34 | sales: 38, 35 | }, 36 | ]; 37 | 38 | describe('extract majority insight', () => { 39 | test('check majority result', () => { 40 | const result = insightPatternsExtractor({ 41 | data, 42 | dimensions: [{ fieldName: 'type' }], 43 | measures: [{ fieldName: 'sales', method: 'SUM' }], 44 | insightType: 'majority', 45 | options: { 46 | filterInsight: true, 47 | }, 48 | }); 49 | expect(result[0]?.index).toEqual(5); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/advisor/ruler/design-rule.test.ts: -------------------------------------------------------------------------------- 1 | import { Advisor } from '../../../../src/advisor'; 2 | import { barWithoutAxisMin } from '../../../../src/advisor/ruler/rules/bar-without-axis-min'; 3 | 4 | // design rule test 5 | test('x-axis-line-fading', () => { 6 | const myAdvisor = new Advisor(); 7 | const data = [ 8 | { price: 520, year: 2005 }, 9 | { price: 600, year: 2006 }, 10 | { price: 1500, year: 2007 }, 11 | ]; 12 | const advices = myAdvisor.advise({ data, fields: ['price', 'year'], options: { refine: true } }); 13 | const chartSpec = advices.filter((e) => e.type === 'line_chart')[0].spec; 14 | if (chartSpec) { 15 | const layerEnc = chartSpec.layer && 'encoding' in chartSpec.layer[0] ? chartSpec.layer[0].encoding : null; 16 | if (layerEnc) { 17 | expect(layerEnc.x).toHaveProperty('axis'); 18 | expect(layerEnc.y).toHaveProperty('scale'); 19 | } 20 | } 21 | }); 22 | 23 | test('bar-without-axis-min', () => { 24 | // @ts-ignore 25 | expect(barWithoutAxisMin.trigger({ chartType: 'bar_chart' })).toBe(true); 26 | }); 27 | -------------------------------------------------------------------------------- /.github/workflows/mirror.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 Sync to Gitee Mirror 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 🔁 Sync to Gitee 13 | uses: wearerequired/git-mirror-action@master 14 | env: 15 | # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY 16 | SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} 17 | with: 18 | # 注意替换为你的 GitHub 源仓库地址 19 | source-repo: 'git@github.com:antvis/AVA.git' 20 | # 注意替换为你的 Gitee 目标仓库地址 21 | destination-repo: 'git@gitee.com:antv-ava/antv-ava.git' 22 | 23 | - name: ✅ Build Gitee Pages 24 | uses: yanglbme/gitee-pages-action@master 25 | with: 26 | # 注意替换为你的 Gitee 用户名 27 | gitee-username: neoddish 28 | # 注意在 Settings->Secrets 配置 GITEE_PASSWORD 29 | gitee-password: ${{ secrets.GITEE_PASSWORD }} 30 | # 注意替换为你的 Gitee 仓库 31 | gitee-repo: antv-ava/antv-ava 32 | # 要部署的分支 33 | branch: gh-pages 34 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/donut.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a donut chart. 6 | describe('should advise donut', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('donut_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices.map((advice) => advice.type).includes('donut_chart')).toBe(true); 13 | }); 14 | 15 | test('a categorical field + a quantitative field with significantly different values', () => { 16 | const data = [ 17 | { price: 100, type: 'A' }, 18 | { price: 120, type: 'B' }, 19 | { price: 150, type: 'C' }, 20 | ]; 21 | 22 | const myAdvisor = new Advisor(); 23 | const advices = myAdvisor.advise({ data }); 24 | expect(advices.map((advice) => advice.type).includes('donut_chart')).toBe(true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/generator/insights.ts: -------------------------------------------------------------------------------- 1 | import { G2Spec, Mark } from '@antv/g2'; 2 | 3 | import { InsightInfo, PatternInfo } from '../../types'; 4 | import { 5 | categoryOutlierStrategy, 6 | changePointStrategy, 7 | lowVarianceStrategy, 8 | majorityStrategy, 9 | correlationStrategy, 10 | timeSeriesOutlierStrategy, 11 | trendStrategy, 12 | viewSpecStrategy, 13 | } from '../strategy'; 14 | 15 | export function generateInsightChartSpec(insight: InsightInfo): G2Spec { 16 | const { type: insightType } = insight.patterns[0]; 17 | 18 | const insightType2Strategy: Record) => Mark[]> = { 19 | trend: trendStrategy, 20 | time_series_outlier: timeSeriesOutlierStrategy, 21 | category_outlier: categoryOutlierStrategy, 22 | change_point: changePointStrategy, 23 | low_variance: lowVarianceStrategy, 24 | majority: majorityStrategy, 25 | correlation: correlationStrategy, 26 | }; 27 | 28 | const marks = insightType2Strategy[insightType]?.(insight); 29 | return viewSpecStrategy(marks, insight); 30 | } 31 | -------------------------------------------------------------------------------- /packages/ava/src/insight/narrative/index.ts: -------------------------------------------------------------------------------- 1 | import InsightNarrativeStrategyFactory from './factory'; 2 | 3 | import type { HomogeneousInsightInfo } from './strategy/base'; 4 | import type { InsightInfo, InsightVisualizationOptions, PatternInfo, HomogeneousPatternInfo } from '../types'; 5 | 6 | function isHomogeneousPattern( 7 | insightInfo: InsightInfo | HomogeneousPatternInfo 8 | ): insightInfo is HomogeneousPatternInfo { 9 | return 'childPatterns' in insightInfo; 10 | } 11 | 12 | export default function generateInsightNarrative( 13 | insightInfo: Omit, 'visualizationSpecs'> | HomogeneousInsightInfo, 14 | options: InsightVisualizationOptions 15 | ) { 16 | const insightType = isHomogeneousPattern(insightInfo) ? insightInfo?.type : insightInfo?.patterns[0]?.type; 17 | if (!insightType) throw Error('insight info has no insight type'); 18 | 19 | const { lang } = options; 20 | 21 | const strategy = InsightNarrativeStrategyFactory.getStrategy(insightType); 22 | const result = strategy.generateTextSpec(insightInfo, lang); 23 | return result; 24 | } 25 | -------------------------------------------------------------------------------- /site/docs/guide/auto-chart/intro.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: AutoChart 简介 3 | order: 0 4 | --- 5 | 6 | 7 | 8 | AutoChart 是一个可以根据数据自动推荐合适的图表并渲染的 React 组件。 9 | 10 | ## ✨ 功能特性 11 | 12 | AutoChart 中透出了 `AutoChart` 组件供用户使用。 13 | 它结合了 AVA 中的图表推荐库 `ChartAdvisor` 的核心能力。 14 | 15 | AutoChart 可以到做到基于给定数据和分析需求来自动生成并渲染合适的图表, 16 | 我们推出 AutoChart 的核心目的就是为用户提供一行代码实现智能可视化的能力。 17 | 18 | ## 🔨 使用 19 | 20 | 21 | ```js 22 | import { AutoChart } from '@antv/auto-chart'; 23 | 24 | const defaultData = [ 25 | { price: 100, type: 'A' }, 26 | { price: 120, type: 'B' }, 27 | { price: 150, type: 'C' }, 28 | ]; 29 | 30 | ReactDOM.render( 31 | <> 32 | 38 | , 39 | mountNode, 40 | ); 41 | ``` 42 | 43 | 44 | ### AutoChart 演示案例 45 | 46 | 47 | 48 | ## 📖 文档 49 | 50 | 更多用法请移步至 [官网API](https://ava.antv.antgroup.com/zh/docs/api/auto-chart/AutoChart) 51 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/area.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a area/line chart. 6 | describe('should advise area/line', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('area_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices.map((advice) => advice.type).includes('area_chart')).toBe(true); 13 | }); 14 | }); 15 | 16 | // In the following cases, the recommended result should NOT be a area/line chart. 17 | describe('should NOT advise area/line', () => { 18 | test('categrorical field should not be x-axis of area chart', async () => { 19 | const data = await dataByChartId('bar_chart'); 20 | 21 | const myAdvisor = new Advisor(); 22 | const advices = myAdvisor.advise({ data }); 23 | expect(advices[0].type === 'area_chart').toBe(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/extractors/low-variance.test.ts: -------------------------------------------------------------------------------- 1 | import { insightPatternsExtractor } from '../../../../src/insight/insights'; 2 | 3 | const data = [ 4 | { 5 | type: 'A', 6 | sales: 38, 7 | }, 8 | { 9 | type: 'B', 10 | sales: 52, 11 | }, 12 | { 13 | type: 'C', 14 | sales: 48, 15 | }, 16 | { 17 | type: 'D', 18 | sales: 45, 19 | }, 20 | { 21 | type: 'E', 22 | sales: 48, 23 | }, 24 | { 25 | type: 'F', 26 | sales: 38, 27 | }, 28 | { 29 | type: 'G', 30 | sales: 38, 31 | }, 32 | { 33 | type: 'H', 34 | sales: 38, 35 | }, 36 | ]; 37 | 38 | describe('extract low-variance insight', () => { 39 | test('check low-variance result', () => { 40 | const result = insightPatternsExtractor({ 41 | data, 42 | dimensions: [{ fieldName: 'type' }], 43 | measures: [{ fieldName: 'sales', method: 'SUM' }], 44 | insightType: 'low_variance', 45 | options: { 46 | filterInsight: true, 47 | }, 48 | }); 49 | expect(result[0]?.significance).toBeGreaterThan(0.85); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/ava/src/utils/dataFormat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * data format. 3 | * dataFormat(0.8849) = 0.88 4 | * dataFormat(12, 1) = 12 5 | * dataFormat(1234, 1) = 1.2k 6 | * dataFormat(123.456, 2) = 123.46 7 | * @param value value to be formatted 8 | * @param digits the number of digits to keep after the decimal point, 2 by default 9 | * @returns formatted value string 10 | */ 11 | export default function dataFormat(value: number | string, digits: number = 2) { 12 | if (typeof value === 'string') return value; 13 | 14 | const formatMap = [ 15 | { value: 1, symbol: '' }, 16 | { value: 1e3, symbol: 'k' }, 17 | { value: 1e6, symbol: 'M' }, 18 | { value: 1e9, symbol: 'G' }, 19 | { value: 1e12, symbol: 'T' }, 20 | { value: 1e15, symbol: 'P' }, 21 | { value: 1e18, symbol: 'E' }, 22 | ]; 23 | const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; 24 | const item = formatMap 25 | .slice() 26 | .reverse() 27 | .find((item) => { 28 | return value >= item.value; 29 | }); 30 | return item ? (value / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : value.toFixed(digits); 31 | } 32 | -------------------------------------------------------------------------------- /site/examples/unused-demo/chart-advisor/relation/demo/table.jsx: -------------------------------------------------------------------------------- 1 | import { Advisor } from '@antv/ava'; 2 | import { specToG6Plot } from '@antv/antv-spec'; 3 | 4 | // Prepare tabular data that describe relations: each row of data represents an edge 5 | fetch('https://gw.alipayobjects.com/os/antfincdn/h7Bil5Cia/ava-eurocredit-data.json') 6 | .then((res) => res.json()) 7 | .then((data) => { 8 | // specify which fields are used for source and target 9 | const extra = { 10 | sourceKey: 'Creditor', 11 | targetKey: 'Debtor', 12 | }; 13 | 14 | // Initialize an advisor and pass the data to its advise function 15 | const myAdvisor = new Advisor(); 16 | const advices = myAdvisor.advise({ data, options: { extra } }); 17 | 18 | // The advices are returns in order from largest score to smallest score, you can choose the best advice to generate visualization 19 | const bestAdvice = advices[0]; 20 | if (bestAdvice) { 21 | const { spec } = bestAdvice; 22 | const container = document.getElementById('container'); 23 | specToG6Plot(spec, container); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/grouped_bar.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a grouped_bar chart. 6 | describe('should advise grouped_bar', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('stacked_bar_chart'); 9 | const myAdvisor = new Advisor(); 10 | const advices = myAdvisor.advise({ data }); 11 | expect(advices.map((advice) => advice.type).includes('grouped_bar_chart')).toBe(true); 12 | }); 13 | }); 14 | 15 | // In the following cases, the recommended result should NOT be a grouped_bar chart. 16 | describe('should NOT advise bar/bar', () => { 17 | test('categrorical field should not be x-axis of bar chart', async () => { 18 | const data = await dataByChartId('line_chart'); 19 | const myAdvisor = new Advisor(); 20 | const advices = myAdvisor.advise({ data }); 21 | expect(advices[0].type === 'grouped_bar_chart').toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/stacked_bar.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a stacked_bar chart. 6 | describe('should advise stacked_bar', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('stacked_bar_chart'); 9 | const myAdvisor = new Advisor(); 10 | const advices = myAdvisor.advise({ data }); 11 | expect(advices.map((advice) => advice.type).includes('stacked_bar_chart')).toBe(true); 12 | }); 13 | }); 14 | 15 | // In the following cases, the recommended result should NOT be a stacked_bar chart. 16 | describe('should NOT advise bar/bar', () => { 17 | test('categrorical field should not be x-axis of bar chart', async () => { 18 | const data = await dataByChartId('line_chart'); 19 | const myAdvisor = new Advisor(); 20 | const advices = myAdvisor.advise({ data }); 21 | expect(advices[0].type === 'stacked_bar_chart').toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/step_line.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a step_line chart. 6 | describe('should advise step_line', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('step_line_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices.map((advice) => advice.type).includes('step_line_chart')).toBe(true); 13 | }); 14 | }); 15 | 16 | // In the following cases, the recommended result should NOT be a step_line chart. 17 | describe('should NOT advise step_line', () => { 18 | test('categrorical field should not be x-axis of step_line chart', async () => { 19 | const data = await dataByChartId('bar_chart'); 20 | 21 | const myAdvisor = new Advisor(); 22 | const advices = myAdvisor.advise({ data }); 23 | expect(advices[0].type === 'step_line_chart').toBe(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/ava/src/insight/algorithms/base/compare.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * lodash sortby asc function 3 | * @param left unknown 4 | * @param right unknown 5 | * @returns number 6 | */ 7 | export function ascending(left: unknown, right: unknown) { 8 | const leftIsNull = left === null || left === undefined; 9 | const rightIsNull = right === null || right === undefined; 10 | if (leftIsNull && rightIsNull) { 11 | return 0; 12 | } 13 | if (leftIsNull) { 14 | return 1; 15 | } 16 | if (rightIsNull) { 17 | return -1; 18 | } 19 | return (left as number) - (right as number); 20 | } 21 | 22 | /** 23 | * lodash sortby desc function 24 | * @param left any 25 | * @param right any 26 | * @returns number 27 | */ 28 | export function descending(left: unknown, right: unknown) { 29 | const leftIsNull = left === null || left === undefined; 30 | const rightIsNull = right === null || right === undefined; 31 | if (leftIsNull && rightIsNull) { 32 | return 0; 33 | } 34 | if (leftIsNull) { 35 | return 1; 36 | } 37 | if (rightIsNull) { 38 | return -1; 39 | } 40 | return (right as number) - (left as number); 41 | } 42 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/cdf.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from 'lodash'; 2 | 3 | /** 4 | * Evaluates the cumulative distribution function (CDF) for a normal distribution at a value x 5 | * - Reference to equation 26.2.17 in https://personal.math.ubc.ca/~cbm/aands/abramowitz_and_stegun.pdf 6 | * @param mu mean 7 | * @param sigma standard deviation 8 | * */ 9 | export const cdf = (x: number, mu: number = 0, sigma: number = 1): number => { 10 | if (sigma < 0 || [x, mu, sigma].some((value) => isNil(value))) return NaN; 11 | // transfer to standard normal distribution 12 | const normalX = Math.abs((x - mu) / sigma); 13 | /** probability density function of the standard normal distribution */ 14 | const Zx = (1 / Math.sqrt(2 * Math.PI)) * Math.exp((-1 * normalX ** 2) / 2); 15 | /** 16 | * - use approximate elementary functional algorithm, error less than 4.5e-4 17 | * @todo add document explaining the derivation process 18 | * */ 19 | const t = 1 / (1 + 0.33267 * normalX); 20 | // error less than 1e-5 21 | const Px = 1 - Zx * (0.4361836 * t - 0.1201676 * t ** 2 + 0.937298 * t ** 3); 22 | return x > mu ? Px : 1 - Px; 23 | }; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2023 AntV team 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 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/stacked_column.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a stacked_column chart. 6 | describe('should advise stacked_column', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('stacked_column_chart'); 9 | const myAdvisor = new Advisor(); 10 | const advices = myAdvisor.advise({ data }); 11 | expect(advices.map((advice) => advice.type).includes('stacked_column_chart')).toBe(true); 12 | }); 13 | }); 14 | 15 | // In the following cases, the recommended result should NOT be a stacked_column chart. 16 | describe('should NOT advise column/column', () => { 17 | test('categrorical field should not be x-axis of column chart', async () => { 18 | const data = await dataByChartId('line_chart'); 19 | const myAdvisor = new Advisor(); 20 | const advices = myAdvisor.advise({ data }); 21 | expect(advices[0].type === 'stacked_column_chart').toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/stacked_area.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a stacked_area chart. 6 | describe('should advise stacked_area', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('stacked_area_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices.map((advice) => advice.type).includes('stacked_area_chart')).toBe(true); 13 | }); 14 | }); 15 | 16 | // In the following cases, the recommended result should NOT be a stacked_area chart. 17 | describe('should NOT advise stacked_area', () => { 18 | test('categrorical field should not be x-axis of stacked_area chart', async () => { 19 | const data = await dataByChartId('bar_chart'); 20 | 21 | const myAdvisor = new Advisor(); 22 | const advices = myAdvisor.advise({ data }); 23 | expect(advices[0].type === 'stacked_area_chart').toBe(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/CKBList/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactJson from 'react-json-view'; 4 | 5 | import { ckb } from '../../../../packages/ava/lib'; 6 | 7 | import type { ReactJsonViewProps } from 'react-json-view'; 8 | 9 | export interface JSONViewProps { 10 | prefixCls?: string; 11 | className?: string; 12 | style?: React.CSSProperties; 13 | rjvConfigs?: Omit; 14 | json: any; 15 | } 16 | 17 | export const CKBJSONView: React.FC = ({ json }) => { 18 | return ( 19 |
26 | {/* react types 导致抛出错误 JSX element type 'ReactElement | null' is not a constructor function for JSX elements */} 27 | {/* @ts-ignore */} 28 | 29 |
30 | ); 31 | }; 32 | 33 | const content = ( 34 | <> 35 |

图表字典(JSON版)

36 | 37 | 38 | ); 39 | 40 | export default content; 41 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/insight/extractors/category-outlier.test.ts: -------------------------------------------------------------------------------- 1 | import { insightPatternsExtractor } from '../../../../src/insight/insights'; 2 | 3 | const data = [ 4 | { 5 | type: 'A', 6 | sales: 38, 7 | }, 8 | { 9 | type: 'B', 10 | sales: 52, 11 | }, 12 | { 13 | type: 'C', 14 | sales: 61, 15 | }, 16 | { 17 | type: 'D', 18 | sales: 145, 19 | }, 20 | { 21 | type: 'E', 22 | sales: 48, 23 | }, 24 | { 25 | type: 'F', 26 | sales: 38, 27 | }, 28 | { 29 | type: 'G', 30 | sales: 38, 31 | }, 32 | { 33 | type: 'H', 34 | sales: 38, 35 | }, 36 | ]; 37 | 38 | describe('extract category-outlier insight', () => { 39 | test('check outliers result', () => { 40 | const result = insightPatternsExtractor({ 41 | data, 42 | dimensions: [{ fieldName: 'type' }], 43 | measures: [{ fieldName: 'sales', method: 'SUM' }], 44 | insightType: 'category_outlier', 45 | options: { 46 | filterInsight: true, 47 | }, 48 | }); 49 | const outlierIndexes = result?.map((item) => item.index); 50 | expect(outlierIndexes).toStrictEqual([3]); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/percent_stacked_bar.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a percent_stacked_bar chart. 6 | describe('should advise percent_stacked_bar', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('percent_stacked_bar_chart'); 9 | const myAdvisor = new Advisor(); 10 | const advices = myAdvisor.advise({ data }); 11 | expect(advices.map((advice) => advice.type).includes('percent_stacked_bar_chart')).toBe(true); 12 | }); 13 | }); 14 | 15 | // In the following cases, the recommended result should NOT be a percent_stacked_bar chart. 16 | describe('should NOT advise bar/bar', () => { 17 | test('categrorical field should not be x-axis of bar chart', async () => { 18 | const data = await dataByChartId('line_chart'); 19 | const myAdvisor = new Advisor(); 20 | const advices = myAdvisor.advise({ data }); 21 | expect(advices[0].type === 'percent_stacked_bar_chart').toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/ava/src/insight/utils/common.ts: -------------------------------------------------------------------------------- 1 | import { lastIndexOf } from 'lodash'; 2 | 3 | // sign 4 | export function sign(value: number) { 5 | if (value > 0) return 1; 6 | return value < 0 ? -1 : 0; 7 | } 8 | 9 | // unique 10 | export function unique(arr: string[] | number[]): [(string | number)[], number[]] { 11 | const sorted = arr.slice().sort(); 12 | 13 | const uniqArr = [sorted[0]]; 14 | const countArr = [1]; 15 | for (let i = 1; i < sorted.length; i += 1) { 16 | if (sorted[i] !== uniqArr[uniqArr.length - 1]) { 17 | uniqArr.push(sorted[i]); 18 | countArr.push(1); 19 | } else { 20 | countArr[countArr.length - 1] += 1; 21 | } 22 | } 23 | 24 | return [uniqArr, countArr]; 25 | } 26 | 27 | // rank 28 | export function rank(arr: (string | number)[]): number[] { 29 | const sorted = arr.slice().sort(); 30 | const rank: number[] = []; 31 | for (let i = 0; i < arr.length; i += 1) { 32 | const value = arr[i]; 33 | const firstRank = sorted.indexOf(value) + 1; 34 | const lastRank = lastIndexOf(sorted, value) + 1; 35 | rank.push(firstRank === lastRank ? firstRank : (firstRank + lastRank) / 2); 36 | } 37 | return rank; 38 | } 39 | -------------------------------------------------------------------------------- /site/LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2015-present Ant UED, https://xtech.antfin.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/percent_stacked_area.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a stacked_area chart. 6 | describe('should advise stacked_area', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('percent_stacked_area_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices.map((advice) => advice.type).includes('percent_stacked_area_chart')).toBe(true); 13 | }); 14 | }); 15 | 16 | // In the following cases, the recommended result should NOT be a stacked_area chart. 17 | describe('should NOT advise stacked_area', () => { 18 | test('categrorical field should not be x-axis of stacked_area chart', async () => { 19 | const data = await dataByChartId('bar_chart'); 20 | 21 | const myAdvisor = new Advisor(); 22 | const advices = myAdvisor.advise({ data }); 23 | expect(advices[0].type === 'percent_stacked_area_chart').toBe(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/ava/src/data/dataset/graph/types.ts: -------------------------------------------------------------------------------- 1 | import type { Axis } from '../field/types'; 2 | 3 | /** 4 | * Graph extra info. 5 | */ 6 | export type GraphExtra = { 7 | nodeKey?: string; // key for node array in data object 8 | linkKey?: string; // key for link array in data object 9 | sourceKey?: string; // key for link source in link object 10 | targetKey?: string; 11 | childrenKey?: string; 12 | nodeIndexes?: Axis[]; 13 | nodeColumns?: Axis[]; 14 | linkIndexes?: Axis[]; 15 | linkColumns?: Axis[]; 16 | }; 17 | 18 | export type NodeData = { 19 | id: string; 20 | name?: string; 21 | [key: string]: unknown; 22 | }; 23 | 24 | export type LinkData = { 25 | source: string; 26 | target: string; 27 | [key: string]: unknown; 28 | }; 29 | 30 | export type GraphInput = 31 | | TreeNode 32 | // object array, such as array of link object, e.g. [ {source: 1, target: 1}, ...] 33 | | { 34 | [key: string]: unknown; 35 | }[] 36 | // array object, such as { nodes: [], links: [] } 37 | | { 38 | [key: string]: unknown[]; 39 | }; 40 | 41 | export type TreeNode = { 42 | id: string; 43 | children: TreeNode[]; 44 | [key: string]: unknown; 45 | }; 46 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/ChartAdvisor/Advisor/AdviceCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { List } from 'antd'; 4 | 5 | import { Advice } from '../../../../../packages/ava/lib'; 6 | import { Chart } from '../Chart'; 7 | 8 | type AdviceProps = { 9 | advices: Advice[]; 10 | }; 11 | 12 | export const AdviceCard = ({ advices }: AdviceProps) => { 13 | return ( 14 | { 24 | return ( 25 | 26 |
27 | 28 | {' '} 29 | 30 |
31 |
Type: {item.type}
32 |
Score: {item.score.toFixed(2)}
33 |
34 |
35 |
36 | ); 37 | }} 38 | >
39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/GraphAdvisor/SampleData/SankeyGraph.js: -------------------------------------------------------------------------------- 1 | export const SankeyGraphData = [ 2 | { 3 | label: 'A', 4 | id: 'A', 5 | to: [ 6 | { 7 | id: 'C', 8 | value: 200, 9 | }, 10 | ], 11 | }, 12 | { 13 | label: 'B', 14 | id: 'B', 15 | to: [ 16 | { 17 | id: 'C', 18 | value: 400, 19 | }, 20 | ], 21 | }, 22 | { 23 | label: 'C', 24 | id: 'C', 25 | to: [ 26 | { 27 | id: 'D', 28 | value: 300, 29 | }, 30 | { 31 | id: 'E', 32 | value: 300, 33 | }, 34 | ], 35 | }, 36 | { 37 | label: 'D', 38 | id: 'D', 39 | to: [ 40 | { 41 | id: 'F', 42 | value: 100, 43 | }, 44 | { 45 | id: 'G', 46 | value: 200, 47 | }, 48 | ], 49 | }, 50 | { 51 | label: 'E', 52 | id: 'E', 53 | to: [ 54 | { 55 | id: 'F', 56 | value: 200, 57 | }, 58 | { 59 | id: 'G', 60 | value: 100, 61 | }, 62 | ], 63 | }, 64 | { 65 | label: 'F', 66 | id: 'F', 67 | }, 68 | { 69 | label: 'G', 70 | id: 'G', 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/percent_stacked_column.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a percent_stacked_column chart. 6 | describe('should advise percent_stacked_column', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('percent_stacked_column_chart'); 9 | const myAdvisor = new Advisor(); 10 | const advices = myAdvisor.advise({ data }); 11 | expect(advices.map((advice) => advice.type).includes('percent_stacked_column_chart')).toBe(true); 12 | }); 13 | }); 14 | 15 | // In the following cases, the recommended result should NOT be a percent_stacked_column chart. 16 | describe('should NOT advise column/column', () => { 17 | test('categrorical field should not be x-axis of column chart', async () => { 18 | const data = await dataByChartId('line_chart'); 19 | const myAdvisor = new Advisor(); 20 | const advices = myAdvisor.advise({ data }); 21 | expect(advices[0].type === 'percent_stacked_column_chart').toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/grouped_column.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a grouped_column chart. 6 | describe('should advise grouped_column', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('stacked_bar_chart'); 9 | const myAdvisor = new Advisor(); 10 | const advices = myAdvisor.advise({ data }); 11 | expect(advices.map((advice) => advice.type).includes('grouped_column_chart')).toBe(true); 12 | }); 13 | }); 14 | 15 | // In the following cases, the recommended result should NOT be a grouped_column chart. 16 | describe('should NOT advise bar/column', () => { 17 | test('categrorical field should not be x-axis of bar chart', async () => { 18 | const data = await dataByChartId('line_chart'); 19 | const myAdvisor = new Advisor(); 20 | const advices = myAdvisor.advise({ data }); 21 | expect(advices[0].type === 'grouped_bar_chart').toBe(false); 22 | expect(advices[0].type === 'grouped_column_chart').toBe(false); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.github/workflows/sync-after-auto-release.yml: -------------------------------------------------------------------------------- 1 | name: 🔁 Sync After Auto Release 2 | 3 | # Sync Package.json and CHANGELOG After Auto Release 4 | on: 5 | workflow_run: 6 | workflows: ["🤖 Auto Release"] 7 | branches: 8 | - stable 9 | types: 10 | - completed 11 | 12 | jobs: 13 | sync: 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | ref: 'stable' 20 | 21 | - name: Git bootstrap 22 | run: | 23 | git config --global user.name 'pddpd' 24 | git config --global user.email 'pddpengdi@gmail.com' 25 | git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/$GITHUB_REPOSITORY 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 28 | 29 | - name: Create PR 30 | run: | 31 | gh pr create --title "chore: update package.json and CHANGELOG" --body "🤖 Create by [Sync After Auto Release](https://github.com/antvis/AVA/blob/master/.github/workflows/sync-after-auto-release.yml)." 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/phrases/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { presetPluginManager } from '../chore/plugin'; 4 | 5 | import { Phrase } from './Phrase'; 6 | 7 | import type { PhraseSpec } from '@antv/ava'; 8 | import type { ThemeStylesProps, ExtensionProps, PhraseEvents } from '../types'; 9 | 10 | type PhrasesProps = ThemeStylesProps & 11 | Pick & 12 | PhraseEvents & { 13 | /** 14 | * @description specification of phrase text spec 15 | * @description.zh-CN 短语描述 json 信息 16 | */ 17 | spec: PhraseSpec[]; 18 | }; 19 | 20 | export function Phrases({ 21 | spec, 22 | size = 'normal', 23 | theme = 'light', 24 | palette, 25 | pluginManager = presetPluginManager, 26 | ...events 27 | }: PhrasesProps) { 28 | const themeStyles = { theme, size, palette }; 29 | return ( 30 | <> 31 | {spec?.map((phrase, index) => { 32 | const key = `${index}-${phrase.value}`; 33 | return ( 34 | 35 | ); 36 | })} 37 | 38 | ); 39 | } 40 | 41 | export { Phrase } from './Phrase'; 42 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/paragraph/TextLine.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { NTV_PREFIX_CLS } from '../constants'; 4 | import { P as StyledP } from '../styled'; 5 | import { Phrases } from '../phrases'; 6 | import { classnames as cx } from '../../utils'; 7 | import { presetPluginManager } from '../chore/plugin'; 8 | 9 | import type { TextParagraphSpec } from '@antv/ava'; 10 | import type { ThemeStylesProps, ExtensionProps, PhraseEvents } from '../types'; 11 | 12 | type TextLineProps = ThemeStylesProps & 13 | Pick & 14 | PhraseEvents & { 15 | spec: TextParagraphSpec; 16 | }; 17 | 18 | export function TextLine({ 19 | spec, 20 | size = 'normal', 21 | theme = 'light', 22 | palette, 23 | pluginManager = presetPluginManager, 24 | ...events 25 | }: TextLineProps) { 26 | const themeStyles = { size, theme, palette }; 27 | return ( 28 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/commonMarks/textMark.ts: -------------------------------------------------------------------------------- 1 | import { TextMark } from '@antv/g2'; 2 | import { isFunction } from 'lodash'; 3 | 4 | import { PointPatternInfo } from '../../../types'; 5 | import { TEXT_STYLE } from '../../constants'; 6 | import { TextMarkConfig } from '../../types'; 7 | 8 | /** get mark for point patterns, the patterns should have same dimension and measure */ 9 | export const textMarkStrategy = (patterns: PointPatternInfo[], textConfig?: TextMarkConfig): TextMark => { 10 | const { style, label, formatter } = textConfig || {}; 11 | const { measure, dimension } = patterns[0]; 12 | const data = patterns.map((pattern) => { 13 | const customLabel = isFunction(label) ? label(pattern) : label; 14 | const value = isFunction(formatter) ? formatter(pattern.y) : pattern.y; 15 | return { 16 | [dimension]: pattern.x, 17 | [measure]: pattern.y, 18 | label: customLabel ?? `${pattern.x}\n${value}`, 19 | }; 20 | }); 21 | 22 | return { 23 | type: 'text', 24 | data, 25 | encode: { 26 | x: dimension, 27 | y: measure, 28 | text: 'label', 29 | }, 30 | style: { 31 | ...TEXT_STYLE, 32 | ...style, 33 | }, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/ava/src/data/statistics/pettitt-test.ts: -------------------------------------------------------------------------------- 1 | import { sumBy } from 'lodash'; 2 | 3 | import { rank } from '../../insight/utils/common'; 4 | 5 | import { ChangePointItem } from './types'; 6 | 7 | /** 8 | * Pettitt's (1979) method is a rank-based nonparametric test for abrupt changes in a time series. 9 | * */ 10 | export function pettittTest(data: number[]): ChangePointItem { 11 | const n = data?.length; 12 | const rankArr = rank(data); 13 | 14 | let Umax = 0; 15 | let UmaxIndex = -1; 16 | for (let k = 0; k < n; k += 1) { 17 | const U = Math.abs(2 * sumBy(rankArr.slice(0, k)) - k * (n + 1)); 18 | if (U > Umax) { 19 | Umax = U; 20 | UmaxIndex = k; 21 | } 22 | } 23 | const pvalue = 2 * Math.exp((-6 * Umax ** 2) / (n ** 2 + n ** 3)); 24 | return { 25 | index: UmaxIndex, 26 | significance: 1 - pvalue, 27 | }; 28 | } 29 | 30 | /** 31 | * p-value calc in Pettitt 32 | */ 33 | export function calcPValue(data: number[], index: number) { 34 | const n = data?.length; 35 | const rankArr = rank(data); 36 | 37 | const U = Math.abs(2 * sumBy(rankArr.slice(0, index)) - index * (n + 1)); 38 | 39 | const pvalue = 2 * Math.exp((-6 * U ** 2) / (n ** 2 + n ** 3)); 40 | 41 | return pvalue; 42 | } 43 | -------------------------------------------------------------------------------- /packages/ava/src/insight/chart/strategy/augmentedMarks/lowVariance.ts: -------------------------------------------------------------------------------- 1 | import { Mark } from '@antv/g2'; 2 | 3 | import { LowVarianceInfo, InsightInfo } from '../../../types'; 4 | import { lineMarkStrategy } from '../commonMarks'; 5 | import { insight2ChartStrategy } from '../chart'; 6 | import { LowVarianceMark } from '../../types'; 7 | import { augmentedMarks2Marks } from '../../utils'; 8 | import { dataFormat } from '../../../../utils'; 9 | 10 | export const lowVarianceAugmentedMarkStrategy = (insight: InsightInfo): LowVarianceMark[] => { 11 | const { patterns } = insight; 12 | const marks: LowVarianceMark[] = []; 13 | patterns.forEach((pattern) => { 14 | const { mean } = pattern; 15 | const meanLineMark = lineMarkStrategy({ y: mean }, { label: `mean: ${dataFormat(mean)}` }); 16 | marks.push({ 17 | meanLine: [meanLineMark], 18 | }); 19 | }); 20 | return marks; 21 | }; 22 | 23 | export const lowVarianceStrategy = (insight: InsightInfo): Mark[] => { 24 | const chartMark = insight2ChartStrategy(insight); 25 | const lowVarianceMarks = lowVarianceAugmentedMarkStrategy(insight); 26 | const marks = augmentedMarks2Marks(lowVarianceMarks); 27 | return [chartMark, ...marks]; 28 | }; 29 | -------------------------------------------------------------------------------- /site/examples/advice/linter-only/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "linter-steps.jsx", 9 | "title": { 10 | "en": "Existing Chart to Lint", 11 | "zh": "给定图表到 Advisor.lint() 进行优化" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/Gu8zDCffu/linter-steps.gif" 14 | }, 15 | { 16 | "filename": "chart-linter.jsx", 17 | "title": { 18 | "en": "Basic Lint", 19 | "zh": "Advisor.lint() 基础用法" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/1bZn%26b5Os/chart-linter.gif" 22 | }, 23 | { 24 | "filename": "custom-rules-linter.jsx", 25 | "title": { 26 | "en": "Custom rules for Linter", 27 | "zh": "定制 Lint 所使用的规则" 28 | }, 29 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/2Jjp04b6%26/custom-rules-linter.gif" 30 | }, 31 | { 32 | "filename": "fixer.jsx", 33 | "title": { 34 | "en": "Basic Fix", 35 | "zh": "Fix 基础用法" 36 | }, 37 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/kOe0rIsAR7/basic-fixer.gif" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/bar.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a bar chart. 6 | describe('should advise bar/column', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('bar_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect( 13 | !!advices.filter((advice) => { 14 | return advice.type === 'column_chart' || advice.type === 'bar_chart'; 15 | }) 16 | ).toBe(true); 17 | }); 18 | }); 19 | 20 | // In the following cases, the recommended result should NOT be a bar chart. 21 | describe('should NOT advise bar/column', () => { 22 | test('categrorical field should not be x-axis of bar chart', async () => { 23 | const data = await dataByChartId('line_chart'); 24 | 25 | const myAdvisor = new Advisor(); 26 | const advices = myAdvisor.advise({ data }); 27 | expect(advices[0].type === 'bar_chart').toBe(false); 28 | expect(advices[0].type === 'column_chart').toBe(false); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /site/examples/advice/linter-only/demo/chart-linter.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import { ChartView, LintCard } from 'antv-site-demo-rc'; 5 | // import 6 | import { Advisor } from '@antv/ava'; 7 | 8 | // contants 9 | 10 | const errorSpec = { 11 | type: 'interval', 12 | data: [ 13 | { category: 'A', value: 4 }, 14 | { category: 'B', value: 6 }, 15 | { category: 'C', value: 10 }, 16 | { category: 'D', value: 3 }, 17 | { category: 'E', value: 7 }, 18 | { category: 'F', value: 8 }, 19 | ], 20 | encode: { 21 | color: 'category', 22 | y: 'value', 23 | }, 24 | scale: { 25 | color: { range: ['#5b8ff9', '#753d91', '#b03c63', '#d5b471', '#4fb01f', '#608b7d'] }, 26 | }, 27 | transform: [{ type: 'stackY' }], 28 | coordinate: { type: 'theta' }, 29 | }; 30 | 31 | // usage 32 | const myAdvisor = new Advisor(); 33 | const problems = myAdvisor.lint({ spec: errorSpec }); 34 | 35 | const App = () => { 36 | const myRef = useRef(); 37 | return ( 38 | <> 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | ReactDOM.render(, document.getElementById('container')); 46 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/advisor/ruler/rule-cases/all-can-be-table.test.ts: -------------------------------------------------------------------------------- 1 | import { CHART_IDS } from '../../../../../src'; 2 | import { ChartRuleModule } from '../../../../../src/advisor/ruler'; 3 | import { allCanBeTable } from '../../../../../src/advisor/ruler/rules/all-can-be-table'; 4 | 5 | const applyChartTypes = ['table']; 6 | const notApplyChartTypes = CHART_IDS.filter((chartType) => !applyChartTypes.includes(chartType)); 7 | 8 | describe('Test: all-can-be-table', () => { 9 | test('type', () => { 10 | expect(allCanBeTable.type).toBe('HARD'); 11 | }); 12 | 13 | test('trigger', () => { 14 | applyChartTypes.forEach((chartType) => { 15 | expect( 16 | allCanBeTable.trigger({ 17 | chartType, 18 | dataProps: undefined, 19 | }) 20 | ).toBe(true); 21 | }); 22 | 23 | notApplyChartTypes.forEach((chartType) => { 24 | expect( 25 | allCanBeTable.trigger({ 26 | chartType, 27 | dataProps: undefined, 28 | }) 29 | ).toBe(false); 30 | }); 31 | }); 32 | 33 | test('validator', () => { 34 | expect( 35 | (allCanBeTable as ChartRuleModule).validator({ 36 | weight: 1, 37 | dataProps: undefined, 38 | }) 39 | ).toBe(1); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/chore/plugin/presets/createProportion.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { isNaN } from 'lodash'; 4 | 5 | import { ProportionChart } from '../../../line-charts'; 6 | import { createEntityPhraseFactory } from '../createEntityPhraseFactory'; 7 | import { isNumberLike } from '../../../../utils'; 8 | 9 | import type { SpecificEntityPhraseDescriptor } from '../plugin-protocol.type'; 10 | 11 | function getProportionNumber(text: string, value?: number | undefined): number { 12 | if (value && !isNaN(value)) return value; 13 | if (text?.endsWith('%')) { 14 | const percentageValue = text?.replace(/%$/, ''); 15 | if (!isNaN(Number(percentageValue))) return Number(percentageValue) / 100; 16 | } 17 | return NaN; 18 | } 19 | 20 | const defaultProportionDescriptor: SpecificEntityPhraseDescriptor = { 21 | encoding: { 22 | inlineChart: (value, { origin }, themeStyles) => ( 23 | 24 | ), 25 | }, 26 | tooltip: { 27 | title: (value, metadata) => (isNumberLike(metadata.origin) ? `${metadata.origin}` : null), 28 | }, 29 | }; 30 | 31 | export const createProportion = createEntityPhraseFactory('proportion', defaultProportionDescriptor); 32 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/heatmap.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a line chart. 6 | describe('should advise line', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('heatmap'); 9 | 10 | /* 11 | 这个数据是这样的:[ 12 | { "x": -5, "y": -5, "z": 50 }, 13 | { "x": -4, "y": -5, "z": 41 }, 14 | { "x": -3, "y": -5, "z": 34 }, 15 | { "x": -2, "y": -5, "z": 29 }, 16 | { "x": -1, "y": -5, "z": 26 }, 17 | { "x": 0, "y": -5, "z": 25 }, 18 | { "x": 1, "y": -5, "z": 26 }, 19 | ...] 20 | 会导致 dw 自动将 x,y 判断为 interval 类型。 21 | 会导致推荐出错,因此在推荐前,将 x,y 强行指定为 nominal 类型。 22 | */ 23 | 24 | const myAdvisor = new Advisor(); 25 | const advices = myAdvisor.advise({ 26 | data, 27 | // 传 dataprops 的原因 参考上方注释 28 | dataProps: [ 29 | { name: 'x', levelOfMeasurements: ['Nominal'] }, 30 | { name: 'y', levelOfMeasurements: ['Nominal'] }, 31 | ], 32 | }); 33 | expect(advices.map((advice) => advice.type).includes('heatmap')).toBe(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/hooks/useSvgWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useLayoutEffect } from 'react'; 2 | 3 | import { seedToken } from '../../theme'; 4 | 5 | import { getElementFontSize } from './getElementFontSize'; 6 | 7 | import type { PropsWithChildren } from 'react'; 8 | import type { SizeType } from '../../types'; 9 | 10 | type SvgProps = PropsWithChildren>; 11 | 12 | export const useSvgWrapper = (size: SizeType = 'normal') => { 13 | const ele = useRef(null); 14 | const [fontSize, setFontSize] = useState(seedToken.fontSizeBase); 15 | useLayoutEffect(() => { 16 | if (size) { 17 | setFontSize(size === 'normal' ? seedToken.fontSizeBase : seedToken.fontSizeSmall); 18 | } else if (ele.current) { 19 | setFontSize(getElementFontSize(ele.current, seedToken.fontSizeBase)); 20 | } 21 | }, [size]); 22 | const Svg = ({ children, ...otherProps }: SvgProps) => { 23 | return ( 24 | 32 | {children} 33 | 34 | ); 35 | }; 36 | return [Svg, fontSize] as const; 37 | }; 38 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/hooks/getElementFontSize.ts: -------------------------------------------------------------------------------- 1 | import { seedToken } from '../../theme'; 2 | 3 | function getStyle(ele: Element, style: string): string | undefined { 4 | // @ts-ignore currentStyle for IE 5 | return window.getComputedStyle ? window.getComputedStyle(ele, null)[style] : ele?.currentStyle?.[style]; 6 | } 7 | 8 | function isAbsoluteUnitPx(str: string | undefined): boolean | undefined { 9 | return str?.endsWith('px'); 10 | } 11 | 12 | function getPxNumber(str: string): number | undefined { 13 | const removeUnit = str.replace(/px$/, ''); 14 | const resultNumber = Number(removeUnit); 15 | if (!Number.isNaN(resultNumber)) return resultNumber; 16 | return undefined; 17 | } 18 | 19 | export function getElementFontSize(ele: Element, defaultSize = seedToken.fontSizeBase): number { 20 | const FONT_SIZE = 'font-size'; 21 | const eleFontSizeStr = getStyle(ele, FONT_SIZE); 22 | if (eleFontSizeStr && isAbsoluteUnitPx(eleFontSizeStr)) { 23 | const px = getPxNumber(eleFontSizeStr); 24 | if (px) return px; 25 | } 26 | const bodyFontSizeStr = getStyle(window.document.body, FONT_SIZE); 27 | if (bodyFontSizeStr && isAbsoluteUnitPx(bodyFontSizeStr)) { 28 | const px = getPxNumber(bodyFontSizeStr); 29 | if (px) return px; 30 | } 31 | return defaultSize; 32 | } 33 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/SmartBoard/Page/index.less: -------------------------------------------------------------------------------- 1 | #dashboard { 2 | flex: 1; 3 | display: flex; 4 | flex-wrap: wrap; 5 | } 6 | .chart_view { 7 | width: 33.3%; 8 | border-radius: 5px; 9 | padding: 14px; 10 | } 11 | 12 | .cluster_0 { 13 | background-color: rgba(175, 220, 202, 0.3); 14 | } 15 | .cluster_1 { 16 | background-color: rgba(247, 232, 145, 0.3); 17 | } 18 | .cluster_2 { 19 | background-color: rgba(242, 205, 197, 0.3); 20 | } 21 | 22 | #toolbar { 23 | display: flex; 24 | justify-content: space-between; 25 | padding: 8px 16px 8px 16px; 26 | background-color: #ffffff; 27 | } 28 | 29 | .title_view { 30 | display: flex; 31 | flex: none; 32 | align-items: center; 33 | margin-bottom: 12px; 34 | color: #000; 35 | font-size: 14px; 36 | line-height: 24px; 37 | position: relative; 38 | 39 | .title_info { 40 | width: calc(100% - 20px); 41 | line-height: 30px; 42 | cursor: default; 43 | 44 | span { 45 | margin-bottom: 5px; 46 | } 47 | } 48 | 49 | .right_icons { 50 | align-items: center; 51 | margin-left: 8px; 52 | flex: 1; 53 | text-align: right; 54 | 55 | span { 56 | margin-left: 8px; 57 | } 58 | } 59 | } 60 | 61 | .toolbar_icons { 62 | position: relative; 63 | top: 50%; 64 | transform: translate(0, -50%); 65 | } 66 | -------------------------------------------------------------------------------- /site/examples/ntv/basic/demo/nested-paragraph.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import { NarrativeTextVis } from '@antv/ava-react'; 5 | 6 | import type { NestedParagraphSpec } from '@antv/ava-react'; 7 | 8 | const { NestedParagraph } = NarrativeTextVis; 9 | 10 | const spec: NestedParagraphSpec = { 11 | children: [ 12 | { type: 'normal', phrases: [{ type: 'text', value: '智能分析报告' }] }, 13 | { 14 | key: '省份1', 15 | children: [ 16 | { type: 'normal', phrases: [{ type: 'text', value: '省份1的overview表现...' }] }, 17 | { key: '城市1', children: [{ type: 'normal', phrases: [{ type: 'text', value: '城市1xxx' }] }] }, 18 | { key: '城市2', children: [{ type: 'normal', phrases: [{ type: 'text', value: '城市2xxx' }] }] }, 19 | ], 20 | metadata: {}, 21 | }, 22 | { 23 | key: '省份2', 24 | children: [ 25 | { type: 'normal', phrases: [{ type: 'text', value: '省份2的overview表现...' }] }, 26 | { key: '城市3', children: [{ type: 'normal', phrases: [{ type: 'text', value: '城市3xxx' }] }] }, 27 | { key: '城市4', children: [{ type: 'normal', phrases: [{ type: 'text', value: '城市4xxx' }] }] }, 28 | ], 29 | }, 30 | ], 31 | }; 32 | 33 | ReactDOM.render(, document.getElementById('container')); 34 | -------------------------------------------------------------------------------- /site/examples/unused-demo/chart-advisor/relation/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "basic.jsx", 9 | "title": { 10 | "en": "Automatic node-links visualization", 11 | "zh": "自动可视化点边(关系型)数据" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*0D3FRZknLz8AAAAAAAAAAAAAARQnAQ" 14 | }, 15 | { 16 | "filename": "tree.jsx", 17 | "title": { 18 | "en": "Automatic hierarchy visualization", 19 | "zh": "自动可视化层次型数据" 20 | }, 21 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*swl0Sq88vagAAAAAAAAAAAAAARQnAQ" 22 | }, 23 | { 24 | "filename": "table.jsx", 25 | "title": { 26 | "en": "Show relations in tabular data", 27 | "zh": "从表格数据中发现并展示关系" 28 | }, 29 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*y_3aR6iCG0MAAAAAAAAAAAAAARQnAQ" 30 | }, 31 | { 32 | "filename": "sankey.jsx", 33 | "title": { 34 | "en": "User customized graph visualization", 35 | "zh": "用户自定义配置的图可视化" 36 | }, 37 | "screenshot": "https://gw.alipayobjects.com/mdn/rms_fabca5/afts/img/A*58fVRbZm_xEAAAAAAAAAAAAAARQnAQ" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/bar-series-qty.ts: -------------------------------------------------------------------------------- 1 | import { hasSubset } from '../../utils'; 2 | 3 | import { MAX_SOFT_RULE_COEFFICIENT } from './constants'; 4 | 5 | import type { RuleModule } from '../types'; 6 | 7 | const applyChartTypes = [ 8 | 'bar_chart', 9 | 'grouped_bar_chart', 10 | 'stacked_bar_chart', 11 | 'percent_stacked_bar_chart', 12 | 'column_chart', 13 | 'grouped_column_chart', 14 | 'stacked_column_chart', 15 | 'percent_stacked_column_chart', 16 | ]; 17 | 18 | export const barSeriesQty: RuleModule = { 19 | id: 'bar-series-qty', 20 | type: 'SOFT', 21 | docs: { 22 | lintText: 'Bar chart should has proper number of bars or bar groups.', 23 | }, 24 | trigger: ({ chartType }) => { 25 | return applyChartTypes.includes(chartType); 26 | }, 27 | validator: (args): number => { 28 | let result = 1; 29 | const { dataProps, chartType } = args; 30 | if (dataProps && chartType) { 31 | const field4Series = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Nominal'])); 32 | const seriesQty = field4Series && field4Series.count ? field4Series.count : 0; 33 | 34 | if (seriesQty > 20) { 35 | result = 20 / seriesQty; 36 | } 37 | } 38 | result = result < 1 / MAX_SOFT_RULE_COEFFICIENT ? 1 / MAX_SOFT_RULE_COEFFICIENT : result; 39 | return result; 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /packages/ava-react/src/NarrativeTextVis/line-charts/line/SingleLineChart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { getThemeColor } from '../../theme'; 4 | import { useSvgWrapper } from '../hooks/useSvgWrapper'; 5 | 6 | import { useLineCompute } from './useLineCompute'; 7 | 8 | import type { ThemeStylesProps } from '../../types'; 9 | 10 | const LINEAR_FILL_COLOR_ID = 'wsc-line-fill'; 11 | 12 | export const SingleLineChart: React.FC<{ data: number[] } & ThemeStylesProps> = ({ 13 | data, 14 | size = 'normal', 15 | theme = 'light', 16 | }) => { 17 | const [Svg, fontSize] = useSvgWrapper(size); 18 | const { width, height, linePath, polygonPath } = useLineCompute(fontSize, data); 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {linePath && ( 28 | 29 | )} 30 | {polygonPath && } 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /packages/ava/__tests__/integration/advisor/advise-cases/charts/line.test.ts: -------------------------------------------------------------------------------- 1 | import { dataByChartId } from '@antv/data-samples'; 2 | 3 | import { Advisor } from '../../../../../src/advisor/index'; 4 | 5 | // In the following cases, the recommended result should be a line chart. 6 | describe('should advise line', () => { 7 | test('test case from @antv/data-samples', async () => { 8 | const data = await dataByChartId('line_chart'); 9 | 10 | const myAdvisor = new Advisor(); 11 | const advices = myAdvisor.advise({ data }); 12 | expect(advices[0].type).toBe('line_chart'); 13 | }); 14 | test('test case from @antv/data-samples', async () => { 15 | const data = await dataByChartId('stacked_area_chart'); 16 | 17 | const myAdvisor = new Advisor(); 18 | const advices = myAdvisor.advise({ data }); 19 | expect(advices.map((advice) => advice.type).includes('line_chart')).toBe(true); 20 | }); 21 | }); 22 | 23 | // In the following cases, the recommended result should NOT be a line chart. 24 | describe('should NOT advise line', () => { 25 | test('categrorical field should not be x-axis of line chart', async () => { 26 | const data = await dataByChartId('bar_chart'); 27 | 28 | const myAdvisor = new Advisor(); 29 | const advices = myAdvisor.advise({ data }); 30 | expect(advices[0].type === 'line_chart').toBe(false); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/ava/__tests__/unit/data/dataset/field/series/fillValue.test.ts: -------------------------------------------------------------------------------- 1 | import Series from '../../../../../../src/data/dataset/field/series'; 2 | 3 | describe('Series data with fillValue', () => { 4 | test('1D: basic type', () => { 5 | const s = new Series(undefined, { fillValue: 201 }); 6 | expect(s.data).toStrictEqual([201]); 7 | }); 8 | 9 | test('1D: basic type with extra indexes', () => { 10 | const s = new Series(null, { indexes: [0, 1, 2], fillValue: 201 }); 11 | expect(s.data).toStrictEqual([201, 201, 201]); 12 | }); 13 | 14 | test('1D: object', () => { 15 | const s = new Series({ a: 1, b: null, c: 3 }, { fillValue: 201 }); 16 | expect(s.data).toStrictEqual([1, 201, 3]); 17 | }); 18 | 19 | test('1D: object with extra indexes', () => { 20 | const s = new Series({ a: 1, b: 2, c: '' }, { indexes: ['c', 'a'], fillValue: 201 }); 21 | expect(s.data).toStrictEqual([201, 1]); 22 | }); 23 | 24 | test('1D: array', () => { 25 | const s = new Series(['', 2, ''], { fillValue: 201 }); 26 | expect(s.data).toStrictEqual([201, 2, 201]); 27 | }); 28 | 29 | test('1D: array with extra indexes', () => { 30 | const s = new Series([undefined, 2, 3], { indexes: ['a', 'b', 'c'], fillValue: 201 }); 31 | expect(s.axes).toStrictEqual([['a', 'b', 'c']]); 32 | expect(s.data).toStrictEqual([201, 2, 3]); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/bar-without-axis-min.ts: -------------------------------------------------------------------------------- 1 | import type { ChartSpec } from '../../../common/types'; 2 | import type { RuleModule } from '../types'; 3 | 4 | const applyChartTypes = [ 5 | 'bar_chart', 6 | 'grouped_bar_chart', 7 | 'stacked_bar_chart', 8 | 'percent_stacked_bar_chart', 9 | 'column_chart', 10 | 'grouped_column_chart', 11 | 'stacked_column_chart', 12 | 'percent_stacked_column_chart', 13 | ]; 14 | 15 | export const barWithoutAxisMin: RuleModule = { 16 | id: 'bar-without-axis-min', 17 | type: 'DESIGN', 18 | docs: { 19 | lintText: 'It is not recommended to set the minimum value of axis for the bar or column chart.', 20 | fixText: 'Remove the minimum value config of axis.', 21 | }, 22 | trigger: ({ chartType }) => { 23 | return applyChartTypes.includes(chartType); 24 | }, 25 | optimizer: (_, chartSpec: ChartSpec): object => { 26 | const { scale } = chartSpec; 27 | if (!scale) return {}; 28 | // @ts-ignore 待 g2 发版后去掉@ts-ignore 29 | const xMin = scale.x?.domainMin; 30 | // @ts-ignore 同上 31 | const yMin = scale.y?.domainMin; 32 | if (xMin || yMin) { 33 | const newScale = JSON.parse(JSON.stringify(scale)); 34 | if (xMin) newScale.x.domainMin = 0; 35 | if (yMin) newScale.y.domainMin = 0; 36 | return { scale: newScale }; 37 | } 38 | return {}; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /playground/src/DevPlayground/GraphAdvisor/SampleData/Subgraph.js: -------------------------------------------------------------------------------- 1 | export const SubgraphData = { nodes: [], edges: [] }; 2 | for (let i = 0; i < 32; i += 1) { 3 | SubgraphData.nodes.push({ 4 | id: `${i}`, 5 | label: i < 17 ? `employee-${i}` : `company-${i - 17}`, 6 | dataType: i < 17 ? 'employee' : 'company', 7 | }); 8 | } 9 | SubgraphData.edges = [ 10 | { source: '0', target: '1' }, 11 | { source: '0', target: '2' }, 12 | { source: '0', target: '3' }, 13 | { source: '0', target: '4' }, 14 | { source: '0', target: '5' }, 15 | { source: '0', target: '6' }, 16 | { source: '1', target: '2' }, 17 | { source: '1', target: '3' }, 18 | { source: '1', target: '4' }, 19 | { source: '1', target: '5' }, 20 | { source: '1', target: '6' }, 21 | { source: '2', target: '3' }, 22 | { source: '2', target: '4' }, 23 | { source: '2', target: '5' }, 24 | { source: '2', target: '6' }, 25 | 26 | { source: '7', target: '8' }, 27 | { source: '8', target: '9' }, 28 | { source: '9', target: '10' }, 29 | 30 | { source: '11', target: '12' }, 31 | { source: '12', target: '13' }, 32 | { source: '13', target: '14' }, 33 | { source: '14', target: '15' }, 34 | { source: '15', target: '16' }, 35 | { source: '11', target: '14' }, 36 | 37 | { source: '31', target: '11' }, 38 | { source: '24', target: '4' }, 39 | { source: '23', target: '7' }, 40 | ]; 41 | -------------------------------------------------------------------------------- /site/examples/advice/advisor-only/demo/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "zh": "中文分类", 4 | "en": "Category" 5 | }, 6 | "demos": [ 7 | { 8 | "filename": "advisor-steps.jsx", 9 | "title": { 10 | "en": "Data to Advisor.advise()", 11 | "zh": "从数据到 Advisor.advise() 推荐图表" 12 | }, 13 | "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/HkgFEc28b/advisor-steps.gif" 14 | }, 15 | { 16 | "filename": "data-advisor.jsx", 17 | "title": { 18 | "en": "Basic Advisor.advise()", 19 | "zh": "Advisor.advise() 基础用法" 20 | }, 21 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ylwZRLCHaSkAAAAAAAAAAAAADmJ7AQ/original" 22 | }, 23 | { 24 | "filename": "custom-ckb-advisor.jsx", 25 | "title": { 26 | "en": "Custom CKB for Advisor.advise()", 27 | "zh": "定制 Advisor.advise() 所使用的 CKB" 28 | }, 29 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*CfxBQrOGrycAAAAAAAAAAAAADmJ7AQ/original" 30 | }, 31 | { 32 | "filename": "custom-rules-advisor.jsx", 33 | "title": { 34 | "en": "Custom rules for Advisor.advise()", 35 | "zh": "定制 Advisor.advise() 所使用的规则" 36 | }, 37 | "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*k-cSRJAA3QsAAAAAAAAAAAAADmJ7AQ/original" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /site/examples/others/thumbnails/demo/select.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { CaretDownOutlined } from '@ant-design/icons'; 4 | import Thumbnails from '@antv/thumbnails'; 5 | import { Thumbnail } from '@antv/thumbnails-component'; 6 | import { Dropdown, Space } from 'antd'; 7 | import ReactDOM from 'react-dom'; 8 | 9 | const chartTypeList = Object.keys(Thumbnails); 10 | 11 | class App extends React.Component { 12 | state = { 13 | current: chartTypeList[0], 14 | }; 15 | 16 | handleClick = ({ key }) => { 17 | this.setState({ 18 | current: key, 19 | }); 20 | }; 21 | 22 | render() { 23 | const { current } = this.state; 24 | const items = chartTypeList.map((item) => { 25 | return { key: item, label: {item} }; 26 | }); 27 | 28 | return ( 29 |
30 | 31 | 32 | e.preventDefault()}> 33 | 34 | Select Chart Type 35 | 36 | 37 | 38 | : {current} 39 |
40 | ); 41 | } 42 | } 43 | 44 | ReactDOM.render(, document.getElementById('container')); 45 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/data-check.ts: -------------------------------------------------------------------------------- 1 | import { verifyDataProps } from '../utils'; 2 | import { intersects } from '../../utils'; 3 | 4 | import type { RuleModule, BasicDataPropertyForAdvice } from '../types'; 5 | 6 | export const dataCheck: RuleModule = { 7 | id: 'data-check', 8 | type: 'HARD', 9 | docs: { 10 | lintText: 'Data must satisfy the data prerequisites.', 11 | }, 12 | trigger: () => { 13 | return true; 14 | }, 15 | validator: (args): number => { 16 | let result = 0; 17 | const { dataProps, chartType, chartWIKI } = args; 18 | 19 | if (dataProps && chartType && chartWIKI[chartType]) { 20 | result = 1; 21 | const dataPres = chartWIKI[chartType].dataPres || []; 22 | dataPres.forEach((dataPre) => { 23 | if (!verifyDataProps(dataPre, dataProps as BasicDataPropertyForAdvice[])) { 24 | result = 0; 25 | } 26 | }); 27 | const fieldsLOMs = dataProps.map((info: any) => { 28 | return info.levelOfMeasurements; 29 | }); 30 | fieldsLOMs.forEach((fieldLOM) => { 31 | let flag = false; 32 | dataPres.forEach((dataPre) => { 33 | if (fieldLOM && intersects(fieldLOM, dataPre.fieldConditions)) { 34 | flag = true; 35 | } 36 | }); 37 | if (!flag) { 38 | result = 0; 39 | } 40 | }); 41 | } 42 | return result; 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /packages/ava/src/advisor/ruler/rules/series-qty-limit.ts: -------------------------------------------------------------------------------- 1 | import { hasSubset } from '../../utils'; 2 | 3 | import { MAX_SOFT_RULE_COEFFICIENT } from './constants'; 4 | 5 | import type { RuleModule } from '../types'; 6 | 7 | const applyChartTypes = ['pie_chart', 'donut_chart', 'radar_chart', 'rose_chart']; 8 | 9 | export const seriesQtyLimit: RuleModule = { 10 | id: 'series-qty-limit', 11 | type: 'SOFT', 12 | docs: { 13 | lintText: 'Some charts should has at most N values for the series.', 14 | }, 15 | trigger: ({ chartType }) => { 16 | return applyChartTypes.includes(chartType); 17 | }, 18 | validator: (args): number => { 19 | let result = 1; 20 | const { dataProps, chartType } = args; 21 | let { limit } = args; 22 | 23 | if (!Number.isInteger(limit) || limit <= 0) { 24 | limit = 6; 25 | if (chartType === 'pie_chart' || chartType === 'donut_chart' || chartType === 'rose_chart') limit = 6; 26 | if (chartType === 'radar_chart') limit = 8; 27 | } 28 | 29 | if (dataProps) { 30 | const field4Series = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Nominal'])); 31 | const seriesQty = field4Series && field4Series.count ? field4Series.count : 0; 32 | if (seriesQty >= 2 && seriesQty <= limit) { 33 | result = MAX_SOFT_RULE_COEFFICIENT * 0.5 + 2 / seriesQty; 34 | } 35 | } 36 | return result; 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /packages/ava-react/src/InsightCard/ntvPlugins/components/G2Chart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | 3 | import { Chart, G2Spec } from '@antv/g2'; 4 | 5 | import { INSIGHT_CARD_PREFIX_CLS } from '../../constants'; 6 | 7 | export const G2Chart = ({ spec, height, width }: { spec: G2Spec; height?: number; width?: number }) => { 8 | const containerRef = useRef(null); 9 | const chartRef = React.useRef(null); 10 | const isRendering = React.useRef(false); 11 | 12 | const renderChart = async () => { 13 | if (!chartRef?.current) { 14 | chartRef.current = new Chart({ 15 | container: containerRef?.current, 16 | autoFit: true, 17 | padding: 'auto', 18 | }); 19 | chartRef.current.options(spec); 20 | } else { 21 | chartRef.current.clear(); 22 | chartRef.current.options({ 23 | autoFit: true, 24 | padding: 'auto', 25 | }); 26 | chartRef.current.options(spec); 27 | } 28 | 29 | if (!isRendering.current) { 30 | isRendering.current = true; 31 | await chartRef.current?.render(); 32 | isRendering.current = false; 33 | } 34 | }; 35 | 36 | useEffect(() => { 37 | renderChart(); 38 | }, [spec]); 39 | 40 | return ( 41 |
42 |
43 |
44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /packages/ava/src/insight/insights/index.ts: -------------------------------------------------------------------------------- 1 | import { flow, isFunction } from 'lodash'; 2 | 3 | import { InsightExtractorProps, PatternInfo } from '../types'; 4 | 5 | import { getCategoryOutlierInfo } from './extractors/categoryOutlier'; 6 | import { getChangePointInfo } from './extractors/changePoint'; 7 | import { getCorrelationInfo } from './extractors/correlation'; 8 | import { getLowVarianceInfo } from './extractors/lowVariance'; 9 | import { getMajorityInfo } from './extractors/majority'; 10 | import { getTimeSeriesOutlierInfo } from './extractors/timeSeriesOutlier'; 11 | import { getTrendInfo } from './extractors/trend'; 12 | import { pickValidPattern } from './util'; 13 | 14 | export const extractorMap = { 15 | category_outlier: getCategoryOutlierInfo, 16 | trend: getTrendInfo, 17 | change_point: getChangePointInfo, 18 | time_series_outlier: getTimeSeriesOutlierInfo, 19 | majority: getMajorityInfo, 20 | low_variance: getLowVarianceInfo, 21 | correlation: getCorrelationInfo, 22 | }; 23 | 24 | export const insightPatternsExtractor = (props: InsightExtractorProps): PatternInfo[] => { 25 | const { insightType = 'trend', options } = props; 26 | const { filterInsight = false } = options || {}; 27 | const extractor = extractorMap[insightType]; 28 | if (!isFunction(extractor)) return []; 29 | return flow([extractor, ...(filterInsight ? [pickValidPattern] : [])])(props); 30 | }; 31 | 32 | export * from './checkers'; 33 | --------------------------------------------------------------------------------