├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── .wait.go-build ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .markdownlint.json ├── .prettierrc.js ├── API.md ├── LICENSE ├── README.md ├── __tests__ ├── channel.test.ts ├── index.test.ts ├── integration │ └── index.test.ts └── mark.test.ts ├── commitlint.config.js ├── demo ├── .babelrc ├── package.json ├── public │ └── index.html ├── src │ ├── app.tsx │ ├── index.html │ ├── index.less │ ├── index.tsx │ └── typings.d.ts ├── tsconfig.json └── webpack.config.js ├── docs └── readme.md ├── examples ├── area.json ├── bar.json ├── bubble.json ├── column.json ├── donut.json ├── graph.json ├── group_bar.json ├── group_column.json ├── group_stack_column.json ├── heatmap.json ├── histogram.json ├── index.ts ├── line.json ├── multi_line.json ├── percent_stack_area.json ├── percent_stack_bar.json ├── percent_stack_column.json ├── pie.json ├── pie_color.json ├── scatter.json ├── stack_area.json ├── stack_bar.json ├── stack_column.json └── step_line.json ├── jest.config.js ├── package.json ├── rollup.config.js ├── src ├── adaptor │ ├── g2plot │ │ ├── index.ts │ │ ├── render.ts │ │ └── toConfig.ts │ ├── g6 │ │ ├── index.ts │ │ ├── render.ts │ │ └── toConfig.ts │ └── index.ts ├── index.ts └── schema │ ├── basis │ └── index.ts │ ├── component │ ├── annotation.ts │ └── index.ts │ ├── data │ ├── dataRow.ts │ ├── graphData.ts │ └── index.ts │ ├── encoding │ ├── aggregate.ts │ ├── axis.ts │ ├── bin.ts │ ├── color.ts │ ├── column.ts │ ├── index.ts │ ├── row.ts │ ├── scale.ts │ ├── size.ts │ ├── stack.ts │ ├── theta.ts │ ├── type.ts │ ├── x.ts │ └── y.ts │ ├── index.ts │ ├── interaction │ └── index.ts │ ├── layer │ └── index.ts │ ├── layout │ └── index.ts │ └── mark │ └── index.ts └── tsconfig.json /.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 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/bundle.js 2 | **/*.min.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | jest: true, 7 | }, 8 | extends: [ 9 | 'eslint:recommended', 10 | 'airbnb-base', 11 | 'plugin:react/recommended', 12 | 'plugin:import/recommended', 13 | 'plugin:import/typescript', 14 | 'prettier', 15 | ], 16 | parser: '@typescript-eslint/parser', 17 | parserOptions: { 18 | ecmaVersion: 12, 19 | sourceType: 'module', 20 | ecmaFeatures: { 21 | jsx: true, 22 | }, 23 | }, 24 | plugins: ['@typescript-eslint', 'import', 'react'], 25 | rules: { 26 | semi: 'error', 27 | quotes: [1, 'single'], 28 | 'no-unused-vars': 'off', 29 | '@typescript-eslint/no-unused-vars': ['error'], 30 | 'import/extensions': 'off', 31 | 'import/prefer-default-export': 'off', 32 | 'object-curly-newline': 'off', 33 | 'class-methods-use-this': 'off', 34 | 'no-shadow': 'off', 35 | 'no-console': 'off', 36 | 'arrow-body-style': 'off', 37 | 'no-useless-constructor': 'off', 38 | 'no-sparse-arrays': 0, 39 | 'no-inner-declarations': 0, 40 | 'no-use-before-define': 'off', 41 | '@typescript-eslint/no-use-before-define': ['error'], 42 | '@typescript-eslint/indent': 0, 43 | 'no-constant-condition': 0, 44 | '@typescript-eslint/explicit-function-return-type': 0, 45 | '@typescript-eslint/no-empty-function': 0, 46 | '@typescript-eslint/explicit-member-accessibility': [2, { accessibility: 'no-public' }], 47 | '@typescript-eslint/no-non-null-assertion': 0, 48 | '@typescript-eslint/no-namespace': 0, 49 | '@typescript-eslint/ban-ts-ignore': 0, 50 | '@typescript-eslint/no-empty-interface': 1, 51 | '@typescript-eslint/camelcase': 0, 52 | '@typescript-eslint/no-explicit-any': 0, 53 | '@typescript-eslint/type-annotation-spacing': 0, 54 | 'import/order': [ 55 | 'error', 56 | { 57 | groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type', 'unknown'], 58 | pathGroups: [ 59 | { 60 | pattern: 'react', 61 | group: 'external', 62 | position: 'before', 63 | }, 64 | { 65 | pattern: '*.{less,css}', 66 | patternOptions: { matchBase: true }, 67 | group: 'unknown', 68 | position: 'before', 69 | }, 70 | ], 71 | pathGroupsExcludedImportTypes: ['builtin'], 72 | warnOnUnassignedImports: true, 73 | }, 74 | ], 75 | }, 76 | settings: { 77 | react: { 78 | version: 'detect', 79 | }, 80 | 'import/parsers': { 81 | '@typescript-eslint/parser': ['.ts', '.tsx'], 82 | }, 83 | 'import/resolver': { 84 | typescript: { 85 | alwaysTryTypes: true, 86 | }, 87 | }, 88 | }, 89 | }; 90 | -------------------------------------------------------------------------------- /.github/workflows/.wait.go-build: -------------------------------------------------------------------------------- 1 | # name: build 2 | 3 | # on: [push, pull_request] 4 | 5 | # jobs: 6 | # build: 7 | # runs-on: ubuntu-latest 8 | 9 | # steps: 10 | # - name: Checkout 11 | # uses: actions/checkout@v2.3.4 12 | 13 | # - name: Setup Node.js environment 14 | # uses: actions/setup-node@v2.1.5 15 | # with: 16 | # node-version: "12" 17 | 18 | # - name: Cache node modules 19 | # uses: actions/cache@v2 20 | # env: 21 | # cache-name: cache-node-modules 22 | # with: 23 | # path: ./node_modules 24 | # key: ${{ runner.os }}-build-cache-node-modules-${{ hashFiles('**/package.json') }} 25 | # restore-keys: | 26 | # cache-node-modules- 27 | 28 | # - name: Run ci 29 | # run: | 30 | # npm install 31 | # npm run prepare:demo 32 | # npm run ci 33 | 34 | # - name: coverall 35 | # if: success() 36 | # uses: coverallsapp/github-action@master 37 | # with: 38 | # github-token: ${{ secrets.GITHUB_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Compiled binary addons (https://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directories 28 | node_modules 29 | **/node_modules 30 | jspm_packages/ 31 | 32 | # TypeScript v1 declaration files 33 | typings/ 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional eslint cache 39 | .eslintcache 40 | 41 | # Output of 'npm pack' 42 | *.tgz 43 | 44 | # Yarn Integrity file 45 | .yarn-integrity 46 | 47 | # dotenv environment variables file 48 | .env 49 | 50 | # parcel-bundler cache (https://parceljs.org/) 51 | .cache 52 | 53 | # IDE 54 | .idea 55 | 56 | # build 57 | lib 58 | es 59 | esm 60 | dist 61 | build 62 | 63 | # lock 64 | package-lock.json 65 | 66 | # test 67 | packages/torch/renderer/index.html 68 | 69 | # yarn-lock 70 | yarn.lock 71 | 72 | # temp 73 | temp.diff 74 | .DS_Store 75 | 76 | # exception 77 | !packages/torch/lib 78 | !packages/istanbul/lib 79 | !packages/demos/src/dist 80 | 81 | # ignore dist files 82 | .vscode/ 83 | 84 | # demo build 85 | demo/public/* 86 | !demo/public/index.html 87 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run format && npm run lint-staged -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD003": { "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 | } 14 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | singleQuote: true, 4 | trailingComma: 'es5', 5 | bracketSpacing: true, 6 | printWidth: 120, 7 | arrowParens: 'always', 8 | }; 9 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## Schema 4 | 5 | antv-spec describes visualization as json-format schema. 6 | 7 | ### Overview 8 | 9 | All visualizations in antv-spec contain the following properties. 10 | | Property | Type | Required | Description | 11 | |--------------|------------------------------|----------|---------------------------------------------------------------------------| 12 | | basis | Basis | True | The basic information of the visualization, such as `type`, `width`, etc. | 13 | | data | Data | True | The data used to encode visualization | 14 | | Layer | ChartLayer[] \| GraphLayer[] | True | The Layers of different marks and data encodings to form a visualization | 15 | | Components | tbd | False | tbd | 16 | | Interactions | tbd | False | tbd | 17 | 18 | 19 | ### Basis 20 | 21 | `Basis` is the basic information of the visualization. 22 | 23 | ```json 24 | { 25 | "type": ..., // visualization type 26 | "width": ..., 27 | "height": ..., 28 | "padding": ..., 29 | } 30 | 31 | ``` 32 | 33 | | Property | Type | Required | Description | 34 | |----------|------------------------------|----------|---------------------------------------------------------------------------------------| 35 | | type | string | True | Indicate what kind of visualization this spec is about, including `chart` and `graph` | 36 | | width | number | False | indicating the visualization's width | 37 | | height | number | False | indicating the visualization's height | 38 | | padding | number \| number[] \| 'auto' | False | indicating the visualization's padding | 39 | 40 | 41 | ### Data 42 | 43 | `data` in antv-spec can be described as `ChartInlineData`, `ChartOnlineData`, `GraphInlineData`, and `GraphOnlineData`. 44 | 45 | **ChartInlineData**: data provided directly in the specification for statistical charts. 46 | 47 | ```json 48 | { 49 | "type": "json-array", 50 | "values": ... 51 | } 52 | ``` 53 | 54 | | Property | Type | Required | Description | 55 | |----------|-----------------------|----------|---------------------------------------------------------------------| 56 | | type | string | False | type of data, currently supporting `json-array` | 57 | | values | Record[] | True | data formatting as json array, such as `[{a:1, b:2}, {a:11, b:22}]` | 58 | 59 | 60 | **ChartOnlineData**: data provided by online URL for statistical charts. 61 | 62 | ```json 63 | { 64 | "type": "url", 65 | "values": ..., 66 | "config": ... 67 | } 68 | ``` 69 | 70 | | Property | Type | Required | Description | 71 | |----------|------------|----------|---------------------------------------------------------------| 72 | | type | string | False | type of data, currently supporting `url` | 73 | | values | string | True | url of data source | 74 | | format | string | False | format of data source for parsing, including `csv` and `json` | 75 | | config | DataConfig | False | configuration for parsing 76 | 77 | 78 | For supporting graph's nodes and links definition, **GraphData** should include at least two properties, such as the `persons` data for nodes and `relation` data for links. 79 | 80 | ```json 81 | { 82 | "persons": [ 83 | { 84 | "id": "node1", 85 | "label": "node1", 86 | "cost": 60, 87 | "type": "A" 88 | }, 89 | { 90 | "id": "node2", 91 | "label": "node2", 92 | "cost": 25, 93 | "type": "B" 94 | }, 95 | { 96 | "id": "node3", 97 | "label": "node3", 98 | "cost": 25, 99 | "type": "B" 100 | } 101 | ], 102 | "relation": [ 103 | { 104 | "source": "node1", 105 | "target": "node2", 106 | "weight": 5, 107 | "type": "X" 108 | }, 109 | { 110 | "source": "node2", 111 | "target": "node3", 112 | "weight": 10, 113 | "type": "Y" 114 | } 115 | ] 116 | } 117 | ``` 118 | 119 | **GraphInlineData**: data provided directly in the specification for graph. 120 | 121 | ```json 122 | { 123 | "type": "json", 124 | "values": ... // GraphData has specific structure 125 | } 126 | ``` 127 | 128 | | Property | Type | Required | Description | 129 | |----------|-----------------------|----------|---------------------------------------------------------------------| 130 | | type | string | False | type of data, currently supporting `json` | 131 | | values | GraphData | True | data formatting as `GraphData` | 132 | 133 | 134 | **GraphOnlineData**: data provided by online URL in the specification for graph. 135 | 136 | ```json 137 | { 138 | "type": "url", 139 | "values": ..., 140 | } 141 | ``` 142 | 143 | | Property | Type | Required | Description | 144 | |----------|------------|----------|---------------------------------------------------------------| 145 | | type | string | False | type of data, currently supporting `url` | 146 | | values | string | True | url of data source | 147 | | format | string | False | format of data source for parsing, only supporting `json` | 148 | 149 | ### Mark 150 | 151 | `mark` in visualization is the basic geometric element to depict data, such as `line` for line charts, `bar` for bar charts. 152 | 153 | #### Mark Types 154 | 155 | Antv-spec supports the following mark types categorized by visualization types: 156 | 157 | * charts: `bar`, `line`, `arc`, `area`, `point`, `rect` 158 | * graph: 159 | * nodes: `point`, `arc`, `rect` 160 | * lines: `line` 161 | 162 | `mark` can be defined by the corresponding string such as: 163 | 164 | ```json 165 | { 166 | "mark": "line" 167 | } 168 | ``` 169 | 170 | Or to customize the configuration of the given mark by: 171 | 172 | ```json 173 | { 174 | "mark": { 175 | "type": ..., 176 | "style": { ... } 177 | } 178 | } 179 | ``` 180 | 181 | #### Mark Configuration 182 | 183 | | Property | Type | Required | Description | 184 | |----------|--------|----------|-------------------------------------------| 185 | | type | string | True | the mark type | 186 | | style | object | False | the customized configuration for the mark | 187 | | interpolate | string | False | the line interpolation method for `line` and `area` charts only, currently support `step` | 188 | 189 | 190 | ##### Style Properties 191 | 192 | | Property | Type | Required | Description | 193 | |-------------|--------|----------|-------------------------------------------------------------------| 194 | | size | number | False | the size of the mark | 195 | | lineWidth | number | False | the line width for `line` mark | 196 | | strokeWidth | number | False | the stroke width for marks with stroke properties such as `point` | 197 | | color | string | False | the color for marks, in hex format, such as "#ffffff" | 198 | | opacity | number | False | the opacity for marks | 199 | | shape | string | False | the shape for `point` mark, such as `triangle`, `star` | 200 | | innerRadius | number | False | the inner radius for `arc` mark, to specify donut charts | 201 | 202 | ### Encoding 203 | 204 | `encoding` denotes the mapping between data and marks' appearance, such as position, color, etc. 205 | 206 | Antv-spec supports the following encoding channels categorized by visualization types: 207 | 208 | * charts: `x`, `y`, `color`, `theta`, `size`, `column`, `row` 209 | * graph: `size`, `color`, `theta` 210 | 211 | ```json 212 | { 213 | "encoding": { 214 | ...: { ... } 215 | } 216 | } 217 | ``` 218 | 219 | #### x / y 220 | 221 | | Property | Type | Required | Description | 222 | |-----------|-------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 223 | | field | string | False | the data field mapping to the channel, only be optional when the aggregation type is `count` | 224 | | type | string | True | the level of measurement of the data field, including `quantitative`, `temporal`, `ordinal` and `nominal` | 225 | | axis | object | False | the axis configuration, see **Axis** | 226 | | aggregate | string | False | the aggregation type of the data field, including `count`, `sum`, `min` and `max` | 227 | | bin | boolean | False | whether to perform binning transformation on the data field | 228 | | stack | boolean \| string | False | the stacking method on the data field, including `true`, `false`, `zero` and `normalize`. `true` and `zero` are the same and `normalize` is used for percentage stacking | 229 | 230 | ##### Axis 231 | 232 | | Property | Type | Required | Description | 233 | |--------------|---------|----------|----------------------------------------------------------------------| 234 | | top | boolean | False | whether draw the axis on the top of the layer | 235 | | position | string | False | direction of the axis, including `top`, `bottom`, `left` and `right` | 236 | | title | object | False | title configuration of the axis, see **AxisTitleConfig** | 237 | | label | object | False | label configuration of the axis, see **AxisLabelConfig** | 238 | | min | number | False | min of the axis | 239 | | max | number | False | max of the axis | 240 | | tickInterval | number | False | interval of the ticks in the axis | 241 | | ticks | boolean | False | whether to show ticks in the axis | 242 | | domain | boolean | False | whether to draw the domain line of the axis | 243 | 244 | **AxisTitleConfig** 245 | 246 | | Property | Type | Required | Description | 247 | |--------------|---------|----------|-------------------------------------------------------| 248 | | text | string | True | axis title text | 249 | | position | string | False | axis title anchor, including `start`, `center`, `end` | 250 | 251 | **AxisLabelConfig** 252 | 253 | | Property | Type | Required | Description | 254 | |--------------|---------|----------|-------------------------------------------------------------| 255 | | offset | number | False | axis label offset | 256 | | angle | number | False | axis label rotation angle | 257 | | autoRotate | boolean | False | whether to automatically rotate axis labels | 258 | | autoHide | boolean | False | whether to automatically hid axis labels when overlapping | 259 | | autoEllipsis | boolean | False | whether to automatically ellipsis axis labels when overflow | 260 | 261 | #### color 262 | 263 | | Property | Type | Required | Description | 264 | |--------------|---------|----------|-----------------------------------------------------------------------------------------------------------| 265 | | field | string | False | the data field mapping to the channel, only be optional when the aggregation type is `count` | 266 | | type | string | True | the level of measurement of the data field, including `quantitative`, `temporal`, `ordinal` and `nominal` | 267 | | aggregate | string | False | the aggregation type of the data field, including `count`, `sum`, `min` and `max` | 268 | | scale | object | False | the scale functions to transform data to visual values, see **Scale** | 269 | 270 | ##### Scale 271 | 272 | | Property | Type | Required | Description | 273 | |-----------|----------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------| 274 | | range | string[] \| number[] | False | range for data mapping, for example, mapping `['apple', 'banana']` to color `['#ff4500', '#f7ff00']` | 275 | | rangeMin | string \| number | False | minimum value of the range for data mapping | 276 | | rangeMax | string \| number | False | maximum value of the range for data mapping | 277 | | domain | number[] | False | domain for data mapping, for example, data within [0, 100], `domain: [10, 90]` means filtering out values less than 10 and larger than 90 | 278 | | domainMin | number | False | minimum value of the domain for data mapping | 279 | | domainMax | number | False | maximum value of the domain for data mapping | 280 | 281 | #### theta 282 | 283 | | Property | Type | Required | Description | 284 | |--------------|---------|----------|-----------------------------------------------------------------------------------------------------------| 285 | | field | string | False | the data field mapping to the channel, only be optional when the aggregation type is `count` | 286 | | type | string | True | the level of measurement of the data field, including `quantitative`, `temporal`, `ordinal` and `nominal` | 287 | | aggregate | string | False | the aggregation type of the data field, including `count`, `sum`, `min` and `max` | 288 | 289 | #### size 290 | 291 | | Property | Type | Required | Description | 292 | |--------------|---------|----------|-----------------------------------------------------------------------------------------------------------| 293 | | field | string | False | the data field mapping to the channel, only be optional when the aggregation type is `count` | 294 | | type | string | True | the level of measurement of the data field, including `quantitative`, `temporal`, `ordinal` and `nominal` | 295 | | aggregate | string | False | the aggregation type of the data field, including `count`, `sum`, `min` and `max` | 296 | 297 | 298 | #### column 299 | 300 | `column` and `row` are used for facet / subplot in the visualization, which means the horizontal direction is divided into two dimensions: column and x (row and y for the horizontal bar chart). The column means that the horizontal direction is divided into n parts according to field A, and the x-axis is plotted in each part encoding field B. 301 | 302 | | Property | Type | Required | Description | 303 | |--------------|---------|----------|-----------------------------------------------------------------------------------------------------------| 304 | | field | string | False | the data field mapping to the channel, only be optional when the aggregation type is `count` | 305 | | type | string | True | the level of measurement of the data field, including `quantitative`, `temporal`, `ordinal` and `nominal` | 306 | 307 | #### row 308 | 309 | | Property | Type | Required | Description | 310 | |--------------|---------|----------|-----------------------------------------------------------------------------------------------------------| 311 | | field | string | False | the data field mapping to the channel, only be optional when the aggregation type is `count` | 312 | | type | string | True | the level of measurement of the data field, including `quantitative`, `temporal`, `ordinal` and `nominal` | 313 | 314 | ### Layer 315 | 316 | Visualization defined by antv-spec is built by one or multiple view layers, for example, a simple line chart contains a layer with `line` mark and `x` and `y` encodings, a node-link graph is comprised of one layer assigning encoding for nodes and another layer for links. 317 | 318 | ```json 319 | { 320 | "layer": [ 321 | ... 322 | ] 323 | } 324 | ``` 325 | 326 | | Property | Type | Required | Description | 327 | |----------|------------------------------|----------|-------------------------------------| 328 | | layer | ChartLayer[] \| GraphLayer[] | True | layers to compose the visualization | 329 | 330 | 331 | **ChartLayer** 332 | 333 | ```json 334 | { 335 | "mark": ..., 336 | "encoding": { ... } 337 | } 338 | ``` 339 | 340 | **GraphLayer** 341 | 342 | ```json 343 | { 344 | "nodes": { 345 | "mark": ..., 346 | "encoding": { ... } 347 | }, 348 | "links": { 349 | "mark": ..., 350 | "encoding": { ... } 351 | } 352 | } 353 | ``` 354 | 355 | ## Adaptor 356 | 357 | Currently, antv-spec provide a simple adaptor of G2Plot. Contributions for adaptors of other chart libraries are welcomed. 358 | 359 | ### G2Plot 360 | 361 | > Experimental version, can not access the full functionality of G2Plot 362 | 363 | ```ts 364 | specToG2Plot(spec, container); 365 | ``` 366 | 367 | **spec** 368 | The visualization specification described in antv-spec. 369 | 370 | **container** 371 | The DOM element of the container to draw the visualization in. 372 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # antv-spec 2 | A declarative grammar that supports various technology stacks of AntV. 3 | 4 | > WIP: This is still an experimental project. Its purpose is to build a low-level declarative language that can support all AntV visualization libraries(statistic chart, graph, map, etc.) as an infrastructure for intelligent visualization. 5 | 6 | ## Features 7 | **schema**: uniform visualization schema for AntV 8 | **adaptor**: translate schema to chart library 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm install @antv/antv-spec 14 | ``` 15 | 16 | ## Example 17 | 18 | ```ts 19 | import { useEffect } from "react"; 20 | import { AntVSpec, specToG2Plot } from "@antv/antv-spec"; 21 | 22 | export default function App() { 23 | const chartSpec: AntVSpec = { 24 | basis: { 25 | type: "chart" 26 | }, 27 | data: { 28 | type: "json-array", 29 | values: [ 30 | { year: "2007", sales: 28, type: "A" }, 31 | { year: "2008", sales: 55, type: "A" }, 32 | { year: "2009", sales: 43, type: "A" }, 33 | { year: "2010", sales: 91, type: "A" }, 34 | { year: "2011", sales: 81, type: "A" }, 35 | { year: "2012", sales: 53, type: "A" }, 36 | { year: "2013", sales: 19, type: "A" }, 37 | { year: "2014", sales: 87, type: "A" }, 38 | { year: "2015", sales: 52, type: "A" }, 39 | 40 | { year: "2007", sales: 34, type: "B" }, 41 | { year: "2008", sales: 52, type: "B" }, 42 | { year: "2009", sales: 70, type: "B" }, 43 | { year: "2010", sales: 11, type: "B" }, 44 | { year: "2011", sales: 46, type: "B" }, 45 | { year: "2012", sales: 79, type: "B" }, 46 | { year: "2013", sales: 23, type: "B" }, 47 | { year: "2014", sales: 54, type: "B" }, 48 | { year: "2015", sales: 99, type: "B" } 49 | ] 50 | }, 51 | layer: [ 52 | { 53 | mark: { 54 | type: "line", 55 | style: { color: "#444444" } 56 | }, 57 | encoding: { 58 | x: { 59 | field: "year", 60 | type: "temporal" 61 | }, 62 | y: { 63 | field: "sales", 64 | type: "quantitative" 65 | }, 66 | color: { 67 | field: "type", 68 | type: "nominal", 69 | scale: { 70 | range: ["#5c0011", "#ffec3d", "#7cb305", "#08979c", "#003a8c"] 71 | } 72 | } 73 | } 74 | } 75 | ] 76 | }; 77 | 78 | useEffect(() => { 79 | specToG2Plot(chartSpec, document.getElementById("container")); 80 | }); 81 | 82 | return
; 83 | } 84 | 85 | ``` 86 | 87 | 88 | ## Documentation 89 | 90 | This project is still an alpha version. We eagerly welcome any contribution. 91 | 92 | For more usages, please check the [Quick API](./API.md). 93 | 94 | ## Inspiration 95 | 96 | [Vega](https://vega.github.io/vega/) - Vega is a visualization grammar, a declarative language for creating, saving, and sharing interactive visualization designs. 97 | 98 | [Vega-Lite](https://vega.github.io/vega-lite/) - Vega-Lite is a high-level grammar of interactive graphics. It provides a concise, declarative JSON syntax to create an expressive range of visualizations for data analysis and presentation. 99 | -------------------------------------------------------------------------------- /__tests__/channel.test.ts: -------------------------------------------------------------------------------- 1 | import { CHART_CHANNELS, GRAPH_CHANNELS } from '../src/schema/encoding'; 2 | 3 | describe('channel test', () => { 4 | test('chart channels', () => { 5 | expect(CHART_CHANNELS).toEqual(['x', 'y', 'color', 'theta', 'size', 'column', 'row']); 6 | }); 7 | 8 | test('graph channels', () => { 9 | expect(GRAPH_CHANNELS).toEqual(['size', 'color', 'theta']); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { matchersWithOptions } from 'jest-json-schema'; 2 | import antvSpec from '../build/antv-spec.json'; 3 | 4 | expect.extend( 5 | matchersWithOptions({ 6 | schemas: [antvSpec], 7 | }) 8 | ); 9 | 10 | describe('schema test', () => { 11 | test('schema itself should be valid', () => { 12 | expect(antvSpec).toBeValidSchema(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /__tests__/integration/index.test.ts: -------------------------------------------------------------------------------- 1 | import { matchersWithOptions } from 'jest-json-schema'; 2 | import antvSpec from '../../build/antv-spec.json'; 3 | import lineSpec from '../../examples/line.json'; 4 | import barSpec from '../../examples/bar.json'; 5 | import pieSpec from '../../examples/pie.json'; 6 | import graphSpec from '../../examples/graph.json'; 7 | import groupedStackColumnSpec from '../../examples/group_stack_column.json'; 8 | import histrogramSpec from '../../examples/histogram.json'; 9 | import pieColor from '../../examples/pie_color.json'; 10 | 11 | expect.extend( 12 | matchersWithOptions({ 13 | schemas: [antvSpec], 14 | }) 15 | ); 16 | 17 | describe('spec test', () => { 18 | test('line spec should be valid', () => { 19 | expect(lineSpec).toMatchSchema(antvSpec); 20 | }); 21 | 22 | test('bar spec should be valid', () => { 23 | expect(barSpec).toMatchSchema(antvSpec); 24 | }); 25 | 26 | test('pie spec should be valid', () => { 27 | expect(pieSpec).toMatchSchema(antvSpec); 28 | }); 29 | 30 | test('graph spec should be valid', () => { 31 | expect(graphSpec).toMatchSchema(antvSpec); 32 | }); 33 | 34 | test('grouped stack bar chart sepc should be valid', () => { 35 | expect(groupedStackColumnSpec).toMatchSchema(antvSpec); 36 | }); 37 | 38 | test('histogram spec should be valid', () => { 39 | expect(histrogramSpec).toMatchSchema(antvSpec); 40 | }); 41 | 42 | test('pie spec with color scale should be valid', () => { 43 | expect(pieColor).toMatchSchema(antvSpec); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /__tests__/mark.test.ts: -------------------------------------------------------------------------------- 1 | import { NODE_MARK_TYPES, LINK_MARK_TYPES, CHART_MARK_TYPES } from '../src/schema'; 2 | 3 | describe('mark test', () => { 4 | test('chart mark type', () => { 5 | expect(CHART_MARK_TYPES).toEqual(['bar', 'line', 'arc', 'area', 'point', 'rect']); 6 | }); 7 | 8 | test('node mark type in graph', () => { 9 | expect(NODE_MARK_TYPES).toEqual(['point', 'arc', 'rect']); 10 | }); 11 | 12 | test('link mark tyoe in graph', () => { 13 | expect(LINK_MARK_TYPES).toEqual(['line']); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "import", 5 | { 6 | "libraryName": "antd", 7 | "style": true // or 'css' 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antv-spec-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack serve --port 8888", 9 | "build:site": "Webpack ./src", 10 | "deploy": "gh-pages -d public", 11 | "deploy:site": "npm install && npm run build:site && npm run deploy" 12 | }, 13 | "author": { 14 | "name": "AntV", 15 | "url": "https://antv.vision/" 16 | }, 17 | "license": "MIT", 18 | "homepage": "https://antvis.github.io/antv-spec", 19 | "dependencies": { 20 | "@antv/g2plot": "^2.3.27", 21 | "@antv/g6": "^4.3.4", 22 | "ajv": "^8.6.1", 23 | "antd": "^4.16.13", 24 | "monaco-editor": "^0.33.0", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2", 27 | "react-monaco-editor": "^0.43.0" 28 | }, 29 | "devDependencies": { 30 | "@babel/plugin-proposal-class-properties": "^7.14.5", 31 | "@babel/plugin-proposal-object-rest-spread": "^7.14.7", 32 | "@babel/plugin-transform-runtime": "^7.14.5", 33 | "@babel/preset-env": "^7.14.7", 34 | "@babel/preset-react": "^7.14.5", 35 | "@babel/preset-typescript": "^7.14.5", 36 | "@types/react": "^17.0.14", 37 | "@types/react-dom": "^17.0.9", 38 | "babel-loader": "^8.2.2", 39 | "babel-plugin-import": "^1.13.3", 40 | "css-loader": "^5.2.7", 41 | "file-loader": "^6.2.0", 42 | "gh-pages": "^3.2.3", 43 | "less": "^4.1.1", 44 | "less-loader": "^10.0.1", 45 | "monaco-editor-webpack-plugin": "^7.0.1", 46 | "style-loader": "^3.1.0", 47 | "webpack": "^5.70.0", 48 | "webpack-cli": "^4.9.2", 49 | "webpack-dev-server": "^4.7.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AntV Spec Demo 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import Ajv from 'ajv'; 3 | import MonacoEditor from 'react-monaco-editor'; 4 | import { Select } from 'antd'; 5 | import { ChartAntVSpec, GraphAntVSpec } from '../../src'; 6 | import specSchema from '../../build/antv-spec.json'; 7 | import demos from '../../examples'; 8 | import { specToG2Plot, specToG6Plot } from '../../src/adaptor'; 9 | import './index.less'; 10 | 11 | const { Option } = Select; 12 | 13 | const ajv = new Ajv(); 14 | const validateSchema = ajv.compile(specSchema); 15 | 16 | function editorWillMount(monaco: any) { 17 | monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ 18 | validate: true, 19 | enableSchemaRequest: true, 20 | schemas: [ 21 | { 22 | schema: specSchema, 23 | uri: 'https://gw.alipayobjects.com/os/antfincdn/WGPnH2dl9L/antv-spec.json', 24 | }, 25 | ], 26 | }); 27 | } 28 | 29 | const isValid = (specStr: string) => { 30 | try { 31 | const spec = JSON.parse(specStr); 32 | return validateSchema(spec); 33 | } catch (error) { 34 | return false; 35 | } 36 | }; 37 | 38 | const formatJSONObject = (json: Object): string => { 39 | return JSON.stringify(json, null, 2); 40 | }; 41 | 42 | export default function App() { 43 | const canvas = useRef(null); 44 | 45 | const [currentDemo, setCurrentDemo] = useState(Object.keys(demos)[1]); 46 | 47 | const defaultSpec = (demos as any)[currentDemo]; 48 | 49 | const [lastValidSpec, setLastValidSpec] = useState(defaultSpec); 50 | const [editorContent, _setEditorContent] = useState(formatJSONObject(defaultSpec)); 51 | 52 | const setEditorContent = (editorContent: string) => { 53 | if (isValid(editorContent)) { 54 | setLastValidSpec(JSON.parse(editorContent)); 55 | } 56 | _setEditorContent(editorContent); 57 | }; 58 | 59 | const editorChange = (newSpec: string) => { 60 | setEditorContent(newSpec); 61 | }; 62 | 63 | const handleDataSelect = (value: any) => { 64 | const demoKey = value; 65 | setCurrentDemo(demoKey); 66 | setEditorContent(formatJSONObject((demos as any)[demoKey])); 67 | }; 68 | 69 | useEffect(() => { 70 | // check visualization type 71 | const visType = lastValidSpec.basis?.type === 'graph' ? 'graph' : 'chart'; 72 | 73 | if (canvas.current) { 74 | switch (visType) { 75 | case 'graph': 76 | specToG6Plot(lastValidSpec as GraphAntVSpec, canvas.current); 77 | break; 78 | case 'chart': 79 | specToG2Plot(lastValidSpec as ChartAntVSpec, canvas.current); 80 | break; 81 | default: 82 | break; 83 | } 84 | } 85 | }, [lastValidSpec]); 86 | 87 | return ( 88 |
89 |

antv-spec to G2Plot/G6Plot

90 |
91 | Spec Examples: 92 | 99 |
100 |
101 |
102 | 110 |
111 |
112 |
118 |
119 |

invalid spec!

120 |
121 |
122 |
123 |

124 | Attention: change encoding to `theta` and `color` before changing mark type to `arc`. 125 |

126 |
127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Demo 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/src/index.less: -------------------------------------------------------------------------------- 1 | @import 'antd/dist/antd.less'; 2 | 3 | .vis-wrapper { 4 | position: relative; 5 | height: 600px; 6 | width: 100%; 7 | overflow: hidden; 8 | flex: 5; 9 | } 10 | 11 | .vis { 12 | position: absolute; 13 | height: 100%; 14 | width: 100%; 15 | } 16 | 17 | .vis-mask { 18 | position: absolute; 19 | height: 100%; 20 | width: 100%; 21 | background-color: rgba(141, 65, 65, 0.5); 22 | bottom: -100%; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | 27 | h2 { 28 | color: black; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './app'; 4 | import './index.less'; 5 | 6 | ReactDOM.render(, document.querySelector('#root')); 7 | -------------------------------------------------------------------------------- /demo/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.less'; 2 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "ES5", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2017"], 7 | "declaration": true, 8 | "outDir": "./lib", 9 | "strict": true, 10 | "allowJs": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "typeRoots": ["./node_modules/@types"], 16 | "baseUrl": ".", 17 | "paths": { 18 | "@src/*": ["../src/*"] 19 | } 20 | }, 21 | "include": ["src"], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); 4 | 5 | const devConfig = { 6 | mode: 'development', 7 | devtool: 'cheap-module-source-map', 8 | entry: { 9 | app: './src/index', 10 | }, 11 | output: { 12 | path: path.join(__dirname, 'public'), 13 | filename: 'bundle.js', 14 | chunkFilename: '[chunkhash].js', 15 | publicPath: '/', 16 | }, 17 | externals: { 18 | react: 'React', 19 | 'react-dom': 'ReactDOM', 20 | antd: 'antd', 21 | }, 22 | target: 'web', 23 | resolve: { 24 | extensions: ['.js', '.ts', '.tsx', '.json'], 25 | mainFields: ['module', 'browser', 'main'], 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.(t|j)sx?$/, 31 | exclude: /node_modules/, 32 | loader: 'babel-loader', 33 | options: { 34 | presets: ['@babel/preset-typescript', ['@babel/preset-env', { modules: 'commonjs' }], '@babel/preset-react'], 35 | plugins: [ 36 | '@babel/plugin-transform-runtime', 37 | '@babel/plugin-proposal-class-properties', 38 | '@babel/plugin-proposal-object-rest-spread', 39 | ], 40 | }, 41 | }, 42 | { 43 | test: /\.(css|less)$/, 44 | use: [ 45 | { loader: 'style-loader' }, 46 | { loader: 'css-loader', options: { sourceMap: true } }, 47 | { 48 | loader: 'less-loader', 49 | options: { 50 | sourceMap: true, 51 | lessOptions: { 52 | javascriptEnabled: true, 53 | }, 54 | }, 55 | }, 56 | ], 57 | }, 58 | { 59 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, 60 | use: [ 61 | { 62 | loader: 'file-loader', 63 | options: { 64 | name: '[name].[ext]', 65 | outputPath: 'fonts/', 66 | }, 67 | }, 68 | ], 69 | }, 70 | ], 71 | }, 72 | devServer: { 73 | allowedHosts: 'all', 74 | host: '0.0.0.0', 75 | hot: true, 76 | static: { 77 | directory: path.join(__dirname, 'src'), 78 | publicPath: '/', 79 | }, 80 | }, 81 | plugins: [ 82 | new webpack.IgnorePlugin({ resourceRegExp: /^(fs|child_process)$/ }), 83 | new webpack.EnvironmentPlugin({ 84 | NODE_ENV: 'development', 85 | }), 86 | new MonacoWebpackPlugin({ 87 | // available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options 88 | languages: ['json'], 89 | }), 90 | ], 91 | }; 92 | 93 | module.exports = devConfig; 94 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Doc 2 | -------------------------------------------------------------------------------- /examples/area.json: -------------------------------------------------------------------------------- 1 | { 2 | "basis": { 3 | "type": "chart" 4 | }, 5 | "data": { 6 | "type": "json-array", 7 | "values": [ 8 | { "year": "2007", "sales": 28 }, 9 | { "year": "2008", "sales": 55 }, 10 | { "year": "2009", "sales": 43 }, 11 | { "year": "2010", "sales": 91 }, 12 | { "year": "2011", "sales": 81 }, 13 | { "year": "2012", "sales": 53 }, 14 | { "year": "2013", "sales": 19 }, 15 | { "year": "2014", "sales": 87 }, 16 | { "year": "2015", "sales": 52 } 17 | ] 18 | }, 19 | "layer": [ 20 | { 21 | "mark": { 22 | "type": "area", 23 | "style": { 24 | "color": "#8b8b8b" 25 | } 26 | }, 27 | "encoding": { 28 | "x": { 29 | "field": "year", 30 | "type": "temporal" 31 | }, 32 | "y": { 33 | "field": "sales", 34 | "type": "quantitative" 35 | } 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /examples/bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/29UjnPUQJo/antv-spec.json", 3 | "basis": { 4 | "type": "chart", 5 | "width": 500, 6 | "height": 500 7 | }, 8 | "data": { 9 | "type": "json-array", 10 | "values": [ 11 | {"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}, 12 | {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}, 13 | {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52} 14 | ] 15 | }, 16 | "layer": [ 17 | { 18 | "mark": { 19 | "type": "bar" 20 | }, 21 | "encoding": { 22 | "y": { 23 | "field": "a", 24 | "type": "nominal" 25 | }, 26 | "x": { 27 | "field": "b", 28 | "type": "quantitative" 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/bubble.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YsVcObYeV9/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { "sales": 28, "volume": 183, "type": "A", "weight": 123 }, 10 | { "sales": 55, "volume": 12, "type": "A", "weight": 23 }, 11 | { "sales": 43, "volume": 234, "type": "A", "weight": 134 }, 12 | { "sales": 91, "volume": 321, "type": "A", "weight": 12 }, 13 | { "sales": 81, "volume": 56, "type": "B", "weight": 356 }, 14 | { "sales": 53, "volume": 20, "type": "B", "weight": 23 }, 15 | { "sales": 19, "volume": 508, "type": "B", "weight": 78 }, 16 | { "sales": 87, "volume": 276, "type": "B", "weight": 87 }, 17 | { "sales": 52, "volume": 11, "type": "B", "weight": 108 } 18 | ] 19 | }, 20 | "layer": [ 21 | { 22 | "mark": "point", 23 | "encoding": { 24 | "x": { 25 | "field": "volume", 26 | "type": "quantitative" 27 | }, 28 | "y": { 29 | "field": "sales", 30 | "type": "quantitative" 31 | }, 32 | "color": { 33 | "field": "type", 34 | "type": "nominal" 35 | }, 36 | "size": { 37 | "field": "weight", 38 | "type": "quantitative" 39 | } 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /examples/column.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/29UjnPUQJo/antv-spec.json", 3 | "basis": { 4 | "type": "chart", 5 | "width": 500, 6 | "height": 500 7 | }, 8 | "data": { 9 | "type": "json-array", 10 | "values": [ 11 | {"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}, 12 | {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}, 13 | {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52} 14 | ] 15 | }, 16 | "layer": [ 17 | { 18 | "mark": { 19 | "type": "bar" 20 | }, 21 | "encoding": { 22 | "x": { 23 | "field": "a", 24 | "type": "nominal" 25 | }, 26 | "y": { 27 | "field": "b", 28 | "type": "quantitative" 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/donut.json: -------------------------------------------------------------------------------- 1 | { 2 | "basis": { 3 | "type": "chart" 4 | }, 5 | "data": { 6 | "type": "json-array", 7 | "values": [ 8 | {"category": "A", "value": 4}, 9 | {"category": "B", "value": 6}, 10 | {"category": "C", "value": 10}, 11 | {"category": "D", "value": 3}, 12 | {"category": "E", "value": 7}, 13 | {"category": "F", "value": 8} 14 | ] 15 | }, 16 | "layer": [ 17 | { 18 | "mark": { "type": "arc", "style": { "innerRadius": 10}}, 19 | "encoding": { 20 | "theta": {"field": "value", "type": "quantitative"}, 21 | "color": {"field": "category", "type": "nominal"} 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/graph.json: -------------------------------------------------------------------------------- 1 | { 2 | "basis": { 3 | "type": "graph" 4 | }, 5 | "data": { 6 | "type": "json", 7 | "values": { 8 | "persons": [ 9 | { 10 | "id": "node1", 11 | "label": "node1", 12 | "cost": 60, 13 | "type": "A" 14 | }, 15 | { 16 | "id": "node2", 17 | "label": "node2", 18 | "cost": 25, 19 | "type": "B" 20 | }, 21 | { 22 | "id": "node3", 23 | "label": "node3", 24 | "cost": 25, 25 | "type": "B" 26 | } 27 | ], 28 | "relation": [ 29 | { 30 | "source": "node1", 31 | "target": "node2", 32 | "weight": 5, 33 | "type": "X" 34 | }, 35 | { 36 | "source": "node2", 37 | "target": "node3", 38 | "weight": 10, 39 | "type": "Y" 40 | } 41 | ] 42 | } 43 | }, 44 | "layout": { 45 | "type": "force", 46 | "nodes": "persons", 47 | "links": "relation" 48 | }, 49 | "layer": [ 50 | { 51 | "nodes": { 52 | "mark": "point", 53 | "encoding": { 54 | "size": { 55 | "field": "cost", 56 | "type": "quantitative" 57 | }, 58 | "color": { 59 | "field": "type", 60 | "type": "nominal" 61 | } 62 | } 63 | }, 64 | "links": { 65 | "mark": "line", 66 | "encoding": { 67 | "size": { 68 | "field": "weight", 69 | "type": "quantitative" 70 | } 71 | } 72 | } 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /examples/group_bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YowJmG0%26V1/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { 10 | "product_type": "Office Supplies", 11 | "sex": "Male", 12 | "order_amt": 8, 13 | "product_sub_type": "Eraser" 14 | }, 15 | { 16 | "product_type": "Office Supplies", 17 | "sex": "Male", 18 | "order_amt": 10, 19 | "product_sub_type": "Bookcase" 20 | }, 21 | { 22 | "product_type": "Office Supplies", 23 | "sex": "Male", 24 | "order_amt": 20, 25 | "product_sub_type": "Inkstone" 26 | }, 27 | { 28 | "product_type": "Office Supplies", 29 | "sex": "Female", 30 | "order_amt": 13, 31 | "product_sub_type": "Inkstone" 32 | }, 33 | { 34 | "product_type": "Office Supplies", 35 | "sex": "Female", 36 | "order_amt": 21, 37 | "product_sub_type": "Eraser" 38 | }, 39 | { 40 | "product_type": "Office Supplies", 41 | "sex": "Female", 42 | "order_amt": 21, 43 | "product_sub_type": "Bookcase" 44 | }, 45 | 46 | { 47 | "product_type": "Home Appliance", 48 | "sex": "Male", 49 | "order_amt": 13, 50 | "product_sub_type": "Washing machine" 51 | }, 52 | { 53 | "product_type": "Home Appliance", 54 | "sex": "Female", 55 | "order_amt": 2, 56 | "product_sub_type": "Washing machine" 57 | }, 58 | { 59 | "product_type": "Home Appliance", 60 | "sex": "Male", 61 | "order_amt": 5, 62 | "product_sub_type": "Microwaves" 63 | }, 64 | { 65 | "product_type": "Home Appliance", 66 | "sex": "Male", 67 | "order_amt": 14, 68 | "product_sub_type": "Induction Cooker" 69 | }, 70 | { 71 | "product_type": "Home Appliance", 72 | "sex": "Female", 73 | "order_amt": 23, 74 | "product_sub_type": "Microwaves" 75 | }, 76 | { 77 | "product_type": "Home Appliance", 78 | "sex": "Female", 79 | "order_amt": 23, 80 | "product_sub_type": "Induction Cooker" 81 | }, 82 | 83 | { 84 | "product_type": "Electronics", 85 | "sex": "Male", 86 | "order_amt": 33, 87 | "product_sub_type": "Computer" 88 | }, 89 | { 90 | "product_type": "Electronics", 91 | "sex": "Female", 92 | "order_amt": 4, 93 | "product_sub_type": "Computer" 94 | }, 95 | { 96 | "product_type": "Electronics", 97 | "sex": "Female", 98 | "order_amt": 23, 99 | "product_sub_type": "switch" 100 | }, 101 | { 102 | "product_type": "Electronics", 103 | "sex": "Male", 104 | "order_amt": 20.9, 105 | "product_sub_type": "switch" 106 | }, 107 | { 108 | "product_type": "Electronics", 109 | "sex": "Male", 110 | "order_amt": 5.9, 111 | "product_sub_type": "Mouse" 112 | }, 113 | { 114 | "product_type": "Electronics", 115 | "sex": "Female", 116 | "order_amt": 5.9, 117 | "product_sub_type": "Mouse" 118 | } 119 | ] 120 | }, 121 | "layer": [ 122 | { 123 | "mark": "bar", 124 | "encoding": { 125 | "row": { 126 | "field": "product_type", 127 | "type": "nominal" 128 | }, 129 | "y": { 130 | "field": "sex", 131 | "type": "nominal" 132 | }, 133 | "x": { 134 | "field": "order_amt", 135 | "type": "quantitative", 136 | "aggregate": "sum" 137 | } 138 | } 139 | } 140 | ] 141 | } 142 | -------------------------------------------------------------------------------- /examples/group_column.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YowJmG0%26V1/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { 10 | "product_type": "Office Supplies", 11 | "sex": "Male", 12 | "order_amt": 8, 13 | "product_sub_type": "Eraser" 14 | }, 15 | { 16 | "product_type": "Office Supplies", 17 | "sex": "Male", 18 | "order_amt": 10, 19 | "product_sub_type": "Bookcase" 20 | }, 21 | { 22 | "product_type": "Office Supplies", 23 | "sex": "Male", 24 | "order_amt": 20, 25 | "product_sub_type": "Inkstone" 26 | }, 27 | { 28 | "product_type": "Office Supplies", 29 | "sex": "Female", 30 | "order_amt": 13, 31 | "product_sub_type": "Inkstone" 32 | }, 33 | { 34 | "product_type": "Office Supplies", 35 | "sex": "Female", 36 | "order_amt": 21, 37 | "product_sub_type": "Eraser" 38 | }, 39 | { 40 | "product_type": "Office Supplies", 41 | "sex": "Female", 42 | "order_amt": 21, 43 | "product_sub_type": "Bookcase" 44 | }, 45 | 46 | { 47 | "product_type": "Home Appliance", 48 | "sex": "Male", 49 | "order_amt": 13, 50 | "product_sub_type": "Washing machine" 51 | }, 52 | { 53 | "product_type": "Home Appliance", 54 | "sex": "Female", 55 | "order_amt": 2, 56 | "product_sub_type": "Washing machine" 57 | }, 58 | { 59 | "product_type": "Home Appliance", 60 | "sex": "Male", 61 | "order_amt": 5, 62 | "product_sub_type": "Microwaves" 63 | }, 64 | { 65 | "product_type": "Home Appliance", 66 | "sex": "Male", 67 | "order_amt": 14, 68 | "product_sub_type": "Induction Cooker" 69 | }, 70 | { 71 | "product_type": "Home Appliance", 72 | "sex": "Female", 73 | "order_amt": 23, 74 | "product_sub_type": "Microwaves" 75 | }, 76 | { 77 | "product_type": "Home Appliance", 78 | "sex": "Female", 79 | "order_amt": 23, 80 | "product_sub_type": "Induction Cooker" 81 | }, 82 | 83 | { 84 | "product_type": "Electronics", 85 | "sex": "Male", 86 | "order_amt": 33, 87 | "product_sub_type": "Computer" 88 | }, 89 | { 90 | "product_type": "Electronics", 91 | "sex": "Female", 92 | "order_amt": 4, 93 | "product_sub_type": "Computer" 94 | }, 95 | { 96 | "product_type": "Electronics", 97 | "sex": "Female", 98 | "order_amt": 23, 99 | "product_sub_type": "switch" 100 | }, 101 | { 102 | "product_type": "Electronics", 103 | "sex": "Male", 104 | "order_amt": 20.9, 105 | "product_sub_type": "switch" 106 | }, 107 | { 108 | "product_type": "Electronics", 109 | "sex": "Male", 110 | "order_amt": 5.9, 111 | "product_sub_type": "Mouse" 112 | }, 113 | { 114 | "product_type": "Electronics", 115 | "sex": "Female", 116 | "order_amt": 5.9, 117 | "product_sub_type": "Mouse" 118 | } 119 | ] 120 | }, 121 | "layer": [ 122 | { 123 | "mark": "bar", 124 | "encoding": { 125 | "column": { 126 | "field": "product_type", 127 | "type": "nominal" 128 | }, 129 | "x": { 130 | "field": "sex", 131 | "type": "nominal" 132 | }, 133 | "y": { 134 | "field": "order_amt", 135 | "type": "quantitative", 136 | "aggregate": "sum" 137 | } 138 | } 139 | } 140 | ] 141 | } 142 | -------------------------------------------------------------------------------- /examples/group_stack_column.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YowJmG0%26V1/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { 10 | "product_type": "Office Supplies", 11 | "sex": "Male", 12 | "order_amt": 8, 13 | "product_sub_type": "Eraser" 14 | }, 15 | { 16 | "product_type": "Office Supplies", 17 | "sex": "Male", 18 | "order_amt": 10, 19 | "product_sub_type": "Bookcase" 20 | }, 21 | { 22 | "product_type": "Office Supplies", 23 | "sex": "Male", 24 | "order_amt": 20, 25 | "product_sub_type": "Inkstone" 26 | }, 27 | { 28 | "product_type": "Office Supplies", 29 | "sex": "Female", 30 | "order_amt": 13, 31 | "product_sub_type": "Inkstone" 32 | }, 33 | { 34 | "product_type": "Office Supplies", 35 | "sex": "Female", 36 | "order_amt": 21, 37 | "product_sub_type": "Eraser" 38 | }, 39 | { 40 | "product_type": "Office Supplies", 41 | "sex": "Female", 42 | "order_amt": 21, 43 | "product_sub_type": "Bookcase" 44 | }, 45 | 46 | { 47 | "product_type": "Home Appliance", 48 | "sex": "Male", 49 | "order_amt": 13, 50 | "product_sub_type": "Washing machine" 51 | }, 52 | { 53 | "product_type": "Home Appliance", 54 | "sex": "Female", 55 | "order_amt": 2, 56 | "product_sub_type": "Washing machine" 57 | }, 58 | { 59 | "product_type": "Home Appliance", 60 | "sex": "Male", 61 | "order_amt": 5, 62 | "product_sub_type": "Microwaves" 63 | }, 64 | { 65 | "product_type": "Home Appliance", 66 | "sex": "Male", 67 | "order_amt": 14, 68 | "product_sub_type": "Induction Cooker" 69 | }, 70 | { 71 | "product_type": "Home Appliance", 72 | "sex": "Female", 73 | "order_amt": 23, 74 | "product_sub_type": "Microwaves" 75 | }, 76 | { 77 | "product_type": "Home Appliance", 78 | "sex": "Female", 79 | "order_amt": 23, 80 | "product_sub_type": "Induction Cooker" 81 | }, 82 | 83 | { 84 | "product_type": "Electronics", 85 | "sex": "Male", 86 | "order_amt": 33, 87 | "product_sub_type": "Computer" 88 | }, 89 | { 90 | "product_type": "Electronics", 91 | "sex": "Female", 92 | "order_amt": 4, 93 | "product_sub_type": "Computer" 94 | }, 95 | { 96 | "product_type": "Electronics", 97 | "sex": "Female", 98 | "order_amt": 23, 99 | "product_sub_type": "switch" 100 | }, 101 | { 102 | "product_type": "Electronics", 103 | "sex": "Male", 104 | "order_amt": 20.9, 105 | "product_sub_type": "switch" 106 | }, 107 | { 108 | "product_type": "Electronics", 109 | "sex": "Male", 110 | "order_amt": 5.9, 111 | "product_sub_type": "Mouse" 112 | }, 113 | { 114 | "product_type": "Electronics", 115 | "sex": "Female", 116 | "order_amt": 5.9, 117 | "product_sub_type": "Mouse" 118 | } 119 | ] 120 | }, 121 | "layer": [ 122 | { 123 | "mark": "bar", 124 | "encoding": { 125 | "column": { 126 | "field": "product_type", 127 | "type": "nominal" 128 | }, 129 | "x": { 130 | "field": "sex", 131 | "type": "nominal" 132 | }, 133 | "y": { 134 | "field": "order_amt", 135 | "type": "quantitative", 136 | "aggregate": "sum" 137 | }, 138 | "color": { 139 | "field": "product_sub_type", 140 | "type": "nominal" 141 | } 142 | } 143 | } 144 | ] 145 | } 146 | -------------------------------------------------------------------------------- /examples/heatmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "basis": { 3 | "type": "chart" 4 | }, 5 | "data": { 6 | "type": "json-array", 7 | "values": [ 8 | { "year": "2007", "sales": 28, "type": "A" }, 9 | { "year": "2008", "sales": 55, "type": "A" }, 10 | { "year": "2009", "sales": 43, "type": "A" }, 11 | { "year": "2010", "sales": 91, "type": "A" }, 12 | { "year": "2011", "sales": 81, "type": "A" }, 13 | { "year": "2012", "sales": 53, "type": "A" }, 14 | { "year": "2013", "sales": 19, "type": "A" }, 15 | { "year": "2014", "sales": 87, "type": "A" }, 16 | { "year": "2015", "sales": 52, "type": "A" }, 17 | { "year": "2007", "sales": 28, "type": "B" }, 18 | { "year": "2008", "sales": 45, "type": "B" }, 19 | { "year": "2009", "sales": 13, "type": "B" }, 20 | { "year": "2010", "sales": 52, "type": "B" }, 21 | { "year": "2011", "sales": 80, "type": "B" }, 22 | { "year": "2012", "sales": 13, "type": "B" }, 23 | { "year": "2013", "sales": 28, "type": "B" }, 24 | { "year": "2014", "sales": 37, "type": "B" }, 25 | { "year": "2015", "sales": 36, "type": "B" }, 26 | { "year": "2007", "sales": 29, "type": "C" }, 27 | { "year": "2008", "sales": 60, "type": "C" }, 28 | { "year": "2009", "sales": 16, "type": "C" }, 29 | { "year": "2010", "sales": 58, "type": "C" }, 30 | { "year": "2011", "sales": 50, "type": "C" }, 31 | { "year": "2012", "sales": 17, "type": "C" }, 32 | { "year": "2013", "sales": 80, "type": "C" }, 33 | { "year": "2014", "sales": 84, "type": "C" }, 34 | { "year": "2015", "sales": 29, "type": "C" } 35 | ] 36 | }, 37 | "layer": [ 38 | { 39 | "mark": "rect", 40 | "encoding": { 41 | "x": { 42 | "field": "year", 43 | "type": "temporal" 44 | }, 45 | "color": { 46 | "field": "sales", 47 | "type": "quantitative" 48 | }, 49 | "y": { 50 | "field": "type", 51 | "type": "nominal" 52 | } 53 | } 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /examples/histogram.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YsVcObYeV9/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}, 9 | {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}, 10 | {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}] 11 | }, 12 | "layer": [ 13 | { 14 | "mark": "bar", 15 | "encoding": { 16 | "x": { 17 | "field": "b", 18 | "type": "quantitative", 19 | "bin": true 20 | }, 21 | "y": { 22 | "aggregate": "count", 23 | "type": "quantitative" 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /examples/index.ts: -------------------------------------------------------------------------------- 1 | // line 2 | import lineDemo from './line.json'; 3 | import stepLineDemo from './step_line.json'; 4 | import multiLineDemo from './multi_line.json'; 5 | // pie 6 | import pieDemo from './pie.json'; 7 | import donutDemo from './donut.json'; 8 | // column 9 | import columnDemo from './column.json'; 10 | import groupColumnDemo from './group_column.json'; 11 | import stackColumnDemo from './stack_column.json'; 12 | import groupStackColumnDemo from './group_stack_column.json'; 13 | import percentStackColumnDemo from './percent_stack_column.json'; 14 | // bar 15 | import barDemo from './bar.json'; 16 | import groupBarDemo from './group_bar.json'; 17 | import stackBarDemo from './stack_bar.json'; 18 | import percentStackBarDemo from './percent_stack_bar.json'; 19 | // area 20 | import areaDemo from './area.json'; 21 | import stackAreaDemo from './stack_area.json'; 22 | import percentStackAreaDemo from './percent_stack_area.json'; 23 | // scatter 24 | import scatterDemo from './scatter.json'; 25 | import bubbleDemo from './bubble.json'; 26 | // heatmap 27 | import heatmapDemo from './heatmap.json'; 28 | // histogram 29 | import histogramDemo from './histogram.json'; 30 | // graph 31 | import graphDemo from './graph.json'; 32 | 33 | const demos = { 34 | lineDemo, 35 | stepLineDemo, 36 | pieDemo, 37 | donutDemo, 38 | columnDemo, 39 | groupColumnDemo, 40 | stackColumnDemo, 41 | groupStackColumnDemo, 42 | percentStackColumnDemo, 43 | barDemo, 44 | groupBarDemo, 45 | stackBarDemo, 46 | percentStackBarDemo, 47 | areaDemo, 48 | stackAreaDemo, 49 | percentStackAreaDemo, 50 | scatterDemo, 51 | bubbleDemo, 52 | heatmapDemo, 53 | histogramDemo, 54 | graphDemo, 55 | multiLineDemo, 56 | }; 57 | 58 | export default demos; 59 | -------------------------------------------------------------------------------- /examples/line.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YsVcObYeV9/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { "year": "2007", "sales": 28 }, 10 | { "year": "2008", "sales": 55 }, 11 | { "year": "2009", "sales": 43 }, 12 | { "year": "2010", "sales": 91 }, 13 | { "year": "2011", "sales": 81 }, 14 | { "year": "2012", "sales": 53 }, 15 | { "year": "2013", "sales": 19 }, 16 | { "year": "2014", "sales": 87 }, 17 | { "year": "2015", "sales": 52 } 18 | ] 19 | }, 20 | "layer": [ 21 | { 22 | "mark": "line", 23 | "encoding": { 24 | "x": { 25 | "field": "year", 26 | "type": "temporal" 27 | }, 28 | "y": { 29 | "field": "sales", 30 | "type": "quantitative" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /examples/multi_line.json: -------------------------------------------------------------------------------- 1 | { 2 | "basis": { 3 | "type": "chart" 4 | }, 5 | "data": { 6 | "type": "json-array", 7 | "values": [ 8 | { "year": "2007", "sales": 28, "type": "A" }, 9 | { "year": "2008", "sales": 55, "type": "A" }, 10 | { "year": "2009", "sales": 43, "type": "A" }, 11 | { "year": "2010", "sales": 91, "type": "A" }, 12 | { "year": "2011", "sales": 81, "type": "A" }, 13 | { "year": "2012", "sales": 53, "type": "A" }, 14 | { "year": "2013", "sales": 19, "type": "A" }, 15 | { "year": "2014", "sales": 87, "type": "A" }, 16 | { "year": "2015", "sales": 52, "type": "A" }, 17 | 18 | { "year": "2007", "sales": 34, "type": "B" }, 19 | { "year": "2008", "sales": 52, "type": "B" }, 20 | { "year": "2009", "sales": 70, "type": "B" }, 21 | { "year": "2010", "sales": 11, "type": "B" }, 22 | { "year": "2011", "sales": 46, "type": "B" }, 23 | { "year": "2012", "sales": 79, "type": "B" }, 24 | { "year": "2013", "sales": 23, "type": "B" }, 25 | { "year": "2014", "sales": 54, "type": "B" }, 26 | { "year": "2015", "sales": 99, "type": "B" } 27 | ] 28 | }, 29 | "layer": [ 30 | { 31 | "mark": { 32 | "type": "line", 33 | "style": { "color": "#444444" } 34 | }, 35 | "encoding": { 36 | "x": { 37 | "field": "year", 38 | "type": "temporal" 39 | }, 40 | "y": { 41 | "field": "sales", 42 | "type": "quantitative" 43 | }, 44 | "color": { 45 | "field": "type", 46 | "type": "nominal", 47 | "scale": { 48 | "range": ["#5c0011", "#ffec3d", "#7cb305", "#08979c", "#003a8c"] 49 | } 50 | } 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /examples/percent_stack_area.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YsVcObYeV9/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { "year": "2007", "sales": 28, "type": "A" }, 10 | { "year": "2008", "sales": 55, "type": "A" }, 11 | { "year": "2009", "sales": 43, "type": "A" }, 12 | { "year": "2010", "sales": 91, "type": "A" }, 13 | { "year": "2011", "sales": 81, "type": "A" }, 14 | { "year": "2012", "sales": 53, "type": "A" }, 15 | { "year": "2013", "sales": 19, "type": "A" }, 16 | { "year": "2014", "sales": 87, "type": "A" }, 17 | { "year": "2015", "sales": 52, "type": "A" }, 18 | { "year": "2007", "sales": 28, "type": "B" }, 19 | { "year": "2008", "sales": 45, "type": "B" }, 20 | { "year": "2009", "sales": 13, "type": "B" }, 21 | { "year": "2010", "sales": 52, "type": "B" }, 22 | { "year": "2011", "sales": 80, "type": "B" }, 23 | { "year": "2012", "sales": 13, "type": "B" }, 24 | { "year": "2013", "sales": 28, "type": "B" }, 25 | { "year": "2014", "sales": 37, "type": "B" }, 26 | { "year": "2015", "sales": 36, "type": "B" } 27 | ] 28 | }, 29 | "layer": [ 30 | { 31 | "mark": "area", 32 | "encoding": { 33 | "x": { 34 | "field": "year", 35 | "type": "temporal" 36 | }, 37 | "y": { 38 | "field": "sales", 39 | "type": "quantitative", 40 | "stack": "normalize" 41 | }, 42 | "color": { 43 | "field": "type", 44 | "type": "nominal" 45 | } 46 | } 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /examples/percent_stack_bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YowJmG0%26V1/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { 10 | "product_type": "Office Supplies", 11 | "sex": "Male", 12 | "order_amt": 8, 13 | "product_sub_type": "Eraser" 14 | }, 15 | { 16 | "product_type": "Office Supplies", 17 | "sex": "Male", 18 | "order_amt": 10, 19 | "product_sub_type": "Bookcase" 20 | }, 21 | { 22 | "product_type": "Office Supplies", 23 | "sex": "Male", 24 | "order_amt": 20, 25 | "product_sub_type": "Inkstone" 26 | }, 27 | { 28 | "product_type": "Office Supplies", 29 | "sex": "Female", 30 | "order_amt": 13, 31 | "product_sub_type": "Inkstone" 32 | }, 33 | { 34 | "product_type": "Office Supplies", 35 | "sex": "Female", 36 | "order_amt": 21, 37 | "product_sub_type": "Eraser" 38 | }, 39 | { 40 | "product_type": "Office Supplies", 41 | "sex": "Female", 42 | "order_amt": 21, 43 | "product_sub_type": "Bookcase" 44 | }, 45 | 46 | { 47 | "product_type": "Home Appliance", 48 | "sex": "Male", 49 | "order_amt": 13, 50 | "product_sub_type": "Washing machine" 51 | }, 52 | { 53 | "product_type": "Home Appliance", 54 | "sex": "Female", 55 | "order_amt": 2, 56 | "product_sub_type": "Washing machine" 57 | }, 58 | { 59 | "product_type": "Home Appliance", 60 | "sex": "Male", 61 | "order_amt": 5, 62 | "product_sub_type": "Microwaves" 63 | }, 64 | { 65 | "product_type": "Home Appliance", 66 | "sex": "Male", 67 | "order_amt": 14, 68 | "product_sub_type": "Induction Cooker" 69 | }, 70 | { 71 | "product_type": "Home Appliance", 72 | "sex": "Female", 73 | "order_amt": 23, 74 | "product_sub_type": "Microwaves" 75 | }, 76 | { 77 | "product_type": "Home Appliance", 78 | "sex": "Female", 79 | "order_amt": 23, 80 | "product_sub_type": "Induction Cooker" 81 | }, 82 | 83 | { 84 | "product_type": "Electronics", 85 | "sex": "Male", 86 | "order_amt": 33, 87 | "product_sub_type": "Computer" 88 | }, 89 | { 90 | "product_type": "Electronics", 91 | "sex": "Female", 92 | "order_amt": 4, 93 | "product_sub_type": "Computer" 94 | }, 95 | { 96 | "product_type": "Electronics", 97 | "sex": "Female", 98 | "order_amt": 23, 99 | "product_sub_type": "switch" 100 | }, 101 | { 102 | "product_type": "Electronics", 103 | "sex": "Male", 104 | "order_amt": 20.9, 105 | "product_sub_type": "switch" 106 | }, 107 | { 108 | "product_type": "Electronics", 109 | "sex": "Male", 110 | "order_amt": 5.9, 111 | "product_sub_type": "Mouse" 112 | }, 113 | { 114 | "product_type": "Electronics", 115 | "sex": "Female", 116 | "order_amt": 5.9, 117 | "product_sub_type": "Mouse" 118 | } 119 | ] 120 | }, 121 | "layer": [ 122 | { 123 | "mark": "bar", 124 | "encoding": { 125 | "y": { 126 | "field": "product_type", 127 | "type": "nominal" 128 | }, 129 | "color": { 130 | "field": "sex", 131 | "type": "nominal" 132 | }, 133 | "x": { 134 | "field": "order_amt", 135 | "type": "quantitative", 136 | "aggregate": "sum", 137 | "stack": "normalize" 138 | } 139 | } 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /examples/percent_stack_column.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YowJmG0%26V1/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { 10 | "product_type": "Office Supplies", 11 | "sex": "Male", 12 | "order_amt": 8, 13 | "product_sub_type": "Eraser" 14 | }, 15 | { 16 | "product_type": "Office Supplies", 17 | "sex": "Male", 18 | "order_amt": 10, 19 | "product_sub_type": "Bookcase" 20 | }, 21 | { 22 | "product_type": "Office Supplies", 23 | "sex": "Male", 24 | "order_amt": 20, 25 | "product_sub_type": "Inkstone" 26 | }, 27 | { 28 | "product_type": "Office Supplies", 29 | "sex": "Female", 30 | "order_amt": 13, 31 | "product_sub_type": "Inkstone" 32 | }, 33 | { 34 | "product_type": "Office Supplies", 35 | "sex": "Female", 36 | "order_amt": 21, 37 | "product_sub_type": "Eraser" 38 | }, 39 | { 40 | "product_type": "Office Supplies", 41 | "sex": "Female", 42 | "order_amt": 21, 43 | "product_sub_type": "Bookcase" 44 | }, 45 | 46 | { 47 | "product_type": "Home Appliance", 48 | "sex": "Male", 49 | "order_amt": 13, 50 | "product_sub_type": "Washing machine" 51 | }, 52 | { 53 | "product_type": "Home Appliance", 54 | "sex": "Female", 55 | "order_amt": 2, 56 | "product_sub_type": "Washing machine" 57 | }, 58 | { 59 | "product_type": "Home Appliance", 60 | "sex": "Male", 61 | "order_amt": 5, 62 | "product_sub_type": "Microwaves" 63 | }, 64 | { 65 | "product_type": "Home Appliance", 66 | "sex": "Male", 67 | "order_amt": 14, 68 | "product_sub_type": "Induction Cooker" 69 | }, 70 | { 71 | "product_type": "Home Appliance", 72 | "sex": "Female", 73 | "order_amt": 23, 74 | "product_sub_type": "Microwaves" 75 | }, 76 | { 77 | "product_type": "Home Appliance", 78 | "sex": "Female", 79 | "order_amt": 23, 80 | "product_sub_type": "Induction Cooker" 81 | }, 82 | 83 | { 84 | "product_type": "Electronics", 85 | "sex": "Male", 86 | "order_amt": 33, 87 | "product_sub_type": "Computer" 88 | }, 89 | { 90 | "product_type": "Electronics", 91 | "sex": "Female", 92 | "order_amt": 4, 93 | "product_sub_type": "Computer" 94 | }, 95 | { 96 | "product_type": "Electronics", 97 | "sex": "Female", 98 | "order_amt": 23, 99 | "product_sub_type": "switch" 100 | }, 101 | { 102 | "product_type": "Electronics", 103 | "sex": "Male", 104 | "order_amt": 20.9, 105 | "product_sub_type": "switch" 106 | }, 107 | { 108 | "product_type": "Electronics", 109 | "sex": "Male", 110 | "order_amt": 5.9, 111 | "product_sub_type": "Mouse" 112 | }, 113 | { 114 | "product_type": "Electronics", 115 | "sex": "Female", 116 | "order_amt": 5.9, 117 | "product_sub_type": "Mouse" 118 | } 119 | ] 120 | }, 121 | "layer": [ 122 | { 123 | "mark": "bar", 124 | "encoding": { 125 | "x": { 126 | "field": "product_type", 127 | "type": "nominal" 128 | }, 129 | "color": { 130 | "field": "sex", 131 | "type": "nominal" 132 | }, 133 | "y": { 134 | "field": "order_amt", 135 | "type": "quantitative", 136 | "aggregate": "sum", 137 | "stack": "normalize" 138 | } 139 | } 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /examples/pie.json: -------------------------------------------------------------------------------- 1 | { 2 | "basis": { 3 | "type": "chart" 4 | }, 5 | "data": { 6 | "type": "json-array", 7 | "values": [ 8 | {"category": 1, "value": 4}, 9 | {"category": 2, "value": 6}, 10 | {"category": 3, "value": 10}, 11 | {"category": 4, "value": 3}, 12 | {"category": 5, "value": 7}, 13 | {"category": 6, "value": 8} 14 | ] 15 | }, 16 | "layer": [ 17 | { 18 | "mark": "arc", 19 | "encoding": { 20 | "theta": {"field": "value", "type": "quantitative"}, 21 | "color": {"field": "category", "type": "nominal"} 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/pie_color.json: -------------------------------------------------------------------------------- 1 | { 2 | "basis": { 3 | "type": "chart" 4 | }, 5 | "data": { 6 | "type": "json-array", 7 | "values": [ 8 | {"category": 1, "value": 4}, 9 | {"category": 2, "value": 6}, 10 | {"category": 3, "value": 10}, 11 | {"category": 4, "value": 3}, 12 | {"category": 5, "value": 7}, 13 | {"category": 6, "value": 8} 14 | ] 15 | }, 16 | "layer": [ 17 | { 18 | "mark": "arc", 19 | "encoding": { 20 | "theta": {"field": "value", "type": "quantitative"}, 21 | "color": {"field": "category", "type": "nominal", "scale": { "range": ["#5c0011", "#874d00", "#ffec3d", "#7cb305", "#08979c", "#003a8c"]}} 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/scatter.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YsVcObYeV9/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { "sales": 28, "volume": 183, "type": "A" }, 10 | { "sales": 55, "volume": 12, "type": "A" }, 11 | { "sales": 43, "volume": 234, "type": "A" }, 12 | { "sales": 91, "volume": 321, "type": "A" }, 13 | { "sales": 81, "volume": 56, "type": "B" }, 14 | { "sales": 53, "volume": 20, "type": "B" }, 15 | { "sales": 19, "volume": 508, "type": "B" }, 16 | { "sales": 87, "volume": 276, "type": "B" }, 17 | { "sales": 52, "volume": 11, "type": "B" } 18 | ] 19 | }, 20 | "layer": [ 21 | { 22 | "mark": "point", 23 | "encoding": { 24 | "x": { 25 | "field": "volume", 26 | "type": "quantitative" 27 | }, 28 | "y": { 29 | "field": "sales", 30 | "type": "quantitative" 31 | }, 32 | "color": { 33 | "field": "type", 34 | "type": "nominal" 35 | } 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /examples/stack_area.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YsVcObYeV9/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { "year": "2007", "sales": 28, "type": "A" }, 10 | { "year": "2008", "sales": 55, "type": "A" }, 11 | { "year": "2009", "sales": 43, "type": "A" }, 12 | { "year": "2010", "sales": 91, "type": "A" }, 13 | { "year": "2011", "sales": 81, "type": "A" }, 14 | { "year": "2012", "sales": 53, "type": "A" }, 15 | { "year": "2013", "sales": 19, "type": "A" }, 16 | { "year": "2014", "sales": 87, "type": "A" }, 17 | { "year": "2015", "sales": 52, "type": "A" }, 18 | { "year": "2007", "sales": 28, "type": "B" }, 19 | { "year": "2008", "sales": 45, "type": "B" }, 20 | { "year": "2009", "sales": 13, "type": "B" }, 21 | { "year": "2010", "sales": 52, "type": "B" }, 22 | { "year": "2011", "sales": 80, "type": "B" }, 23 | { "year": "2012", "sales": 13, "type": "B" }, 24 | { "year": "2013", "sales": 28, "type": "B" }, 25 | { "year": "2014", "sales": 37, "type": "B" }, 26 | { "year": "2015", "sales": 36, "type": "B" } 27 | ] 28 | }, 29 | "layer": [ 30 | { 31 | "mark": "area", 32 | "encoding": { 33 | "x": { 34 | "field": "year", 35 | "type": "temporal" 36 | }, 37 | "y": { 38 | "field": "sales", 39 | "type": "quantitative" 40 | }, 41 | "color": { 42 | "field": "type", 43 | "type": "nominal" 44 | } 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /examples/stack_bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YowJmG0%26V1/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { 10 | "product_type": "Office Supplies", 11 | "sex": "Male", 12 | "order_amt": 8, 13 | "product_sub_type": "Eraser" 14 | }, 15 | { 16 | "product_type": "Office Supplies", 17 | "sex": "Male", 18 | "order_amt": 10, 19 | "product_sub_type": "Bookcase" 20 | }, 21 | { 22 | "product_type": "Office Supplies", 23 | "sex": "Male", 24 | "order_amt": 20, 25 | "product_sub_type": "Inkstone" 26 | }, 27 | { 28 | "product_type": "Office Supplies", 29 | "sex": "Female", 30 | "order_amt": 13, 31 | "product_sub_type": "Inkstone" 32 | }, 33 | { 34 | "product_type": "Office Supplies", 35 | "sex": "Female", 36 | "order_amt": 21, 37 | "product_sub_type": "Eraser" 38 | }, 39 | { 40 | "product_type": "Office Supplies", 41 | "sex": "Female", 42 | "order_amt": 21, 43 | "product_sub_type": "Bookcase" 44 | }, 45 | 46 | { 47 | "product_type": "Home Appliance", 48 | "sex": "Male", 49 | "order_amt": 13, 50 | "product_sub_type": "Washing machine" 51 | }, 52 | { 53 | "product_type": "Home Appliance", 54 | "sex": "Female", 55 | "order_amt": 2, 56 | "product_sub_type": "Washing machine" 57 | }, 58 | { 59 | "product_type": "Home Appliance", 60 | "sex": "Male", 61 | "order_amt": 5, 62 | "product_sub_type": "Microwaves" 63 | }, 64 | { 65 | "product_type": "Home Appliance", 66 | "sex": "Male", 67 | "order_amt": 14, 68 | "product_sub_type": "Induction Cooker" 69 | }, 70 | { 71 | "product_type": "Home Appliance", 72 | "sex": "Female", 73 | "order_amt": 23, 74 | "product_sub_type": "Microwaves" 75 | }, 76 | { 77 | "product_type": "Home Appliance", 78 | "sex": "Female", 79 | "order_amt": 23, 80 | "product_sub_type": "Induction Cooker" 81 | }, 82 | 83 | { 84 | "product_type": "Electronics", 85 | "sex": "Male", 86 | "order_amt": 33, 87 | "product_sub_type": "Computer" 88 | }, 89 | { 90 | "product_type": "Electronics", 91 | "sex": "Female", 92 | "order_amt": 4, 93 | "product_sub_type": "Computer" 94 | }, 95 | { 96 | "product_type": "Electronics", 97 | "sex": "Female", 98 | "order_amt": 23, 99 | "product_sub_type": "switch" 100 | }, 101 | { 102 | "product_type": "Electronics", 103 | "sex": "Male", 104 | "order_amt": 20.9, 105 | "product_sub_type": "switch" 106 | }, 107 | { 108 | "product_type": "Electronics", 109 | "sex": "Male", 110 | "order_amt": 5.9, 111 | "product_sub_type": "Mouse" 112 | }, 113 | { 114 | "product_type": "Electronics", 115 | "sex": "Female", 116 | "order_amt": 5.9, 117 | "product_sub_type": "Mouse" 118 | } 119 | ] 120 | }, 121 | "layer": [ 122 | { 123 | "mark": "bar", 124 | "encoding": { 125 | "y": { 126 | "field": "product_type", 127 | "type": "nominal" 128 | }, 129 | "color": { 130 | "field": "sex", 131 | "type": "nominal" 132 | }, 133 | "x": { 134 | "field": "order_amt", 135 | "type": "quantitative", 136 | "aggregate": "sum", 137 | "stack": true 138 | } 139 | } 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /examples/stack_column.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YowJmG0%26V1/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { 10 | "product_type": "Office Supplies", 11 | "sex": "Male", 12 | "order_amt": 8, 13 | "product_sub_type": "Eraser" 14 | }, 15 | { 16 | "product_type": "Office Supplies", 17 | "sex": "Male", 18 | "order_amt": 10, 19 | "product_sub_type": "Bookcase" 20 | }, 21 | { 22 | "product_type": "Office Supplies", 23 | "sex": "Male", 24 | "order_amt": 20, 25 | "product_sub_type": "Inkstone" 26 | }, 27 | { 28 | "product_type": "Office Supplies", 29 | "sex": "Female", 30 | "order_amt": 13, 31 | "product_sub_type": "Inkstone" 32 | }, 33 | { 34 | "product_type": "Office Supplies", 35 | "sex": "Female", 36 | "order_amt": 21, 37 | "product_sub_type": "Eraser" 38 | }, 39 | { 40 | "product_type": "Office Supplies", 41 | "sex": "Female", 42 | "order_amt": 21, 43 | "product_sub_type": "Bookcase" 44 | }, 45 | 46 | { 47 | "product_type": "Home Appliance", 48 | "sex": "Male", 49 | "order_amt": 13, 50 | "product_sub_type": "Washing machine" 51 | }, 52 | { 53 | "product_type": "Home Appliance", 54 | "sex": "Female", 55 | "order_amt": 2, 56 | "product_sub_type": "Washing machine" 57 | }, 58 | { 59 | "product_type": "Home Appliance", 60 | "sex": "Male", 61 | "order_amt": 5, 62 | "product_sub_type": "Microwaves" 63 | }, 64 | { 65 | "product_type": "Home Appliance", 66 | "sex": "Male", 67 | "order_amt": 14, 68 | "product_sub_type": "Induction Cooker" 69 | }, 70 | { 71 | "product_type": "Home Appliance", 72 | "sex": "Female", 73 | "order_amt": 23, 74 | "product_sub_type": "Microwaves" 75 | }, 76 | { 77 | "product_type": "Home Appliance", 78 | "sex": "Female", 79 | "order_amt": 23, 80 | "product_sub_type": "Induction Cooker" 81 | }, 82 | 83 | { 84 | "product_type": "Electronics", 85 | "sex": "Male", 86 | "order_amt": 33, 87 | "product_sub_type": "Computer" 88 | }, 89 | { 90 | "product_type": "Electronics", 91 | "sex": "Female", 92 | "order_amt": 4, 93 | "product_sub_type": "Computer" 94 | }, 95 | { 96 | "product_type": "Electronics", 97 | "sex": "Female", 98 | "order_amt": 23, 99 | "product_sub_type": "switch" 100 | }, 101 | { 102 | "product_type": "Electronics", 103 | "sex": "Male", 104 | "order_amt": 20.9, 105 | "product_sub_type": "switch" 106 | }, 107 | { 108 | "product_type": "Electronics", 109 | "sex": "Male", 110 | "order_amt": 5.9, 111 | "product_sub_type": "Mouse" 112 | }, 113 | { 114 | "product_type": "Electronics", 115 | "sex": "Female", 116 | "order_amt": 5.9, 117 | "product_sub_type": "Mouse" 118 | } 119 | ] 120 | }, 121 | "layer": [ 122 | { 123 | "mark": "bar", 124 | "encoding": { 125 | "x": { 126 | "field": "product_type", 127 | "type": "nominal" 128 | }, 129 | "color": { 130 | "field": "sex", 131 | "type": "nominal" 132 | }, 133 | "y": { 134 | "field": "order_amt", 135 | "type": "quantitative", 136 | "aggregate": "sum", 137 | "stack": true 138 | } 139 | } 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /examples/step_line.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://gw.alipayobjects.com/os/antfincdn/YsVcObYeV9/antv-spec.json", 3 | "basis": { 4 | "type": "chart" 5 | }, 6 | "data": { 7 | "type": "json-array", 8 | "values": [ 9 | { "year": "2007", "sales": 28 }, 10 | { "year": "2008", "sales": 55 }, 11 | { "year": "2009", "sales": 43 }, 12 | { "year": "2010", "sales": 91 }, 13 | { "year": "2011", "sales": 81 }, 14 | { "year": "2012", "sales": 53 }, 15 | { "year": "2013", "sales": 19 }, 16 | { "year": "2014", "sales": 87 }, 17 | { "year": "2015", "sales": 52 } 18 | ] 19 | }, 20 | "layer": [ 21 | { 22 | "mark": { "type": "line", "interpolate": "step"}, 23 | "encoding": { 24 | "x": { 25 | "field": "year", 26 | "type": "temporal" 27 | }, 28 | "y": { 29 | "field": "sales", 30 | "type": "quantitative" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | preset: 'ts-jest', 4 | globals: { 5 | 'ts-jest': { 6 | diagnostics: false, 7 | }, 8 | }, 9 | testPathIgnorePatterns: ['/node_modules/'], 10 | collectCoverage: true, 11 | collectCoverageFrom: ['src/**/*.ts'], 12 | testRegex: '(/__tests__/.*\\.(test|spec))\\.ts$', 13 | verbose: false, 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@antv/antv-spec", 3 | "version": "0.1.0-alpha.18", 4 | "description": "A declarative grammar that supports various technology stacks of AntV.", 5 | "main": "lib/src/index.js", 6 | "types": "lib/src/index.d.ts", 7 | "unpkg": "dist/index.min.js", 8 | "module": "esm/src/index.js", 9 | "files": [ 10 | "esm", 11 | "lib", 12 | "dist", 13 | "build" 14 | ], 15 | "scripts": { 16 | "prepare": "husky install", 17 | "format": "prettier --write \"src/**/*.ts\" \"__tests__/**/*.ts\" \"demo/**/*.{ts,tsx}\"", 18 | "format-check": " prettier ./src/**/*.ts ./__tests__/**/*.ts ./demo/**/*.{ts,tsx} --check", 19 | "lint": "eslint ./src/**/*.ts ./__tests__/**/*.ts ./demo/**/*.{ts,tsx} && npm run format-check", 20 | "fix": "eslint ./src/**/*.ts ./__tests__/**/*.ts ./demo/**/*.{ts,tsx} --fix && npm run format", 21 | "lint-staged": "lint-staged", 22 | "size": "limit-size", 23 | "build:umd": "rimraf ./dist && rollup -c && npm run size", 24 | "build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib", 25 | "build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm", 26 | "build": "run-p build:* && npm run size", 27 | "clean": "rimraf lib esm dist", 28 | "schema": "mkdir -p build && ts-json-schema-generator -f tsconfig.json -p src/index.ts -t AntVSpec --no-type-check --no-ref-encode > build/antv-spec.json", 29 | "test": "jest", 30 | "ci": "run-s schema lint test build", 31 | "setup:demo": "cd demo && npm install --package-lock=false", 32 | "start:demo": "npm run setup:demo && cd demo && npm start", 33 | "deploy:demo": "cd demo && npm run deploy:site", 34 | "one-stop-setup": "npm install --package-lock=false && run-s setup:demo ci", 35 | "prepublishOnly": "npm run ci", 36 | "preversion": "npm run lint", 37 | "version": "npm run format && git add -A src", 38 | "postversion": "git push && git push --tags" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/antvis/antv-spec.git" 43 | }, 44 | "author": { 45 | "name": "AntV", 46 | "url": "https://antv.vision/" 47 | }, 48 | "license": "MIT", 49 | "publishConfig": { 50 | "access": "public" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/antvis/antv-spec/issues" 54 | }, 55 | "homepage": "https://github.com/antvis/antv-spec#readme", 56 | "devDependencies": { 57 | "@antv/g2plot": "^2.3.27", 58 | "@antv/g6": "^4.3.11", 59 | "@babel/runtime": "^7.14.6", 60 | "@commitlint/cli": "^13.2.1", 61 | "@commitlint/config-conventional": "^13.2.0", 62 | "@rollup/plugin-commonjs": "^20.0.0", 63 | "@rollup/plugin-json": "^4.1.0", 64 | "@rollup/plugin-node-resolve": "^13.0.4", 65 | "@rollup/plugin-typescript": "^8.2.5", 66 | "@types/jest": "^27.0.2", 67 | "@types/jest-json-schema": "^2.1.3", 68 | "@typescript-eslint/eslint-plugin": "^4.28.3", 69 | "@typescript-eslint/parser": "^4.28.3", 70 | "eslint": "^7.30.0", 71 | "eslint-config-airbnb": "^18.2.1", 72 | "eslint-config-prettier": "^8.3.0", 73 | "eslint-import-resolver-typescript": "^2.4.0", 74 | "eslint-plugin-import": "^2.23.4", 75 | "eslint-plugin-prettier": "^3.4.0", 76 | "eslint-plugin-react": "^7.24.0", 77 | "husky": "^7.0.1", 78 | "jest": "^27.3.1", 79 | "jest-json-schema": "^5.0.0", 80 | "limit-size": "^0.1.4", 81 | "lint-staged": "^11.0.1", 82 | "npm-run-all": "^4.1.5", 83 | "prettier": "^2.3.2", 84 | "rimraf": "^3.0.2", 85 | "rollup": "^2.59.0", 86 | "rollup-plugin-terser": "^7.0.2", 87 | "ts-jest": "^27.0.7", 88 | "ts-json-schema-generator": "^0.94.1", 89 | "typescript": "^4.4.4" 90 | }, 91 | "peerDependencies": { 92 | "@antv/g2plot": "^2.3.27", 93 | "@antv/g6": "^4.3.11" 94 | }, 95 | "lint-staged": { 96 | "*.{ts,tsx}": [ 97 | "eslint --fix", 98 | "prettier --write", 99 | "git add" 100 | ] 101 | }, 102 | "limit-size": [ 103 | { 104 | "path": "dist/index.min.js", 105 | "limit": "10 Kb", 106 | "gzip": true 107 | }, 108 | { 109 | "path": "dist/index.min.js", 110 | "limit": "24 Kb" 111 | } 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | import { terser } from 'rollup-plugin-terser'; 5 | import json from '@rollup/plugin-json'; 6 | 7 | module.exports = [ 8 | { 9 | input: 'src/index.ts', 10 | output: { 11 | file: 'dist/index.min.js', 12 | name: 'AntVSpec', 13 | format: 'umd', 14 | sourcemap: 'inline', 15 | globals: { 16 | '@antv/g2plot': 'G2Plot', 17 | '@antv/g6': 'G6', 18 | }, 19 | }, 20 | plugins: [resolve(), commonjs(), typescript({ module: 'ESNext' }), terser(), json()], 21 | external: ['@antv/g2plot', '@antv/g6'], 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /src/adaptor/g2plot/index.ts: -------------------------------------------------------------------------------- 1 | import { ChartAntVSpec } from '../../schema'; 2 | import { specToG2PlotConfig } from './toConfig'; 3 | import { g2plotRender } from './render'; 4 | 5 | export const specToG2Plot = (spec: ChartAntVSpec, container: HTMLElement) => { 6 | const g2plotConfig = specToG2PlotConfig(spec); 7 | 8 | // remove existing chart in the container 9 | // eslint-disable-next-line no-param-reassign 10 | container.innerHTML = ''; 11 | 12 | if (g2plotConfig) { 13 | const plot = g2plotRender(g2plotConfig, container); 14 | return plot; 15 | } 16 | 17 | return null; 18 | }; 19 | 20 | export { g2plotRender }; 21 | -------------------------------------------------------------------------------- /src/adaptor/g2plot/render.ts: -------------------------------------------------------------------------------- 1 | import * as G2Plot from '@antv/g2plot'; 2 | import { G2PlotType } from './toConfig'; 3 | 4 | export function g2plotRender(g2plotCfg: any, container: HTMLElement) { 5 | const { chartType } = g2plotCfg; 6 | if (g2plotCfg && chartType) { 7 | const plot = new G2Plot[chartType as G2PlotType]( 8 | container, 9 | // @ts-ignore 10 | g2plotCfg.config 11 | ); 12 | plot.render(); 13 | return plot; 14 | } 15 | 16 | return null; 17 | } 18 | -------------------------------------------------------------------------------- /src/adaptor/g2plot/toConfig.ts: -------------------------------------------------------------------------------- 1 | import { ChartAntVSpec, AxisLabelProps, AxisProps, AxisTitleProps } from '../../schema'; 2 | 3 | export type G2PlotType = 'Line' | 'Area' | 'Column' | 'Bar' | 'Pie' | 'Rose' | 'Scatter' | 'Histogram' | 'Heatmap'; 4 | 5 | const CHART_TYPES_WITH_STACK = ['Area', 'Column', 'Bar']; 6 | 7 | /** 8 | * get chart type (Line / Bar / ...) from spec's mark (bar / line / ...) 9 | * @param spec input spec 10 | * @returns chart type 11 | */ 12 | export function markToChart(spec: ChartAntVSpec) { 13 | if (spec.layer.length === 1) { 14 | const layer = spec.layer[0]; 15 | const mark = typeof layer.mark === 'string' ? layer.mark : layer.mark.type; 16 | let chartType; 17 | switch (mark) { 18 | case 'line': 19 | chartType = 'Line'; 20 | break; 21 | case 'area': 22 | chartType = 'Area'; 23 | break; 24 | case 'point': 25 | chartType = 'Scatter'; 26 | break; 27 | case 'arc': 28 | // pie and donut are all Pie(), with/without innerRadius 29 | chartType = 'Pie'; 30 | break; 31 | case 'bar': { 32 | const hasX = layer.encoding.x; 33 | const xType = hasX ? hasX.type : ''; 34 | if (xType === 'quantitative') { 35 | chartType = 'Bar'; 36 | } else { 37 | chartType = 'Column'; 38 | } 39 | break; 40 | } 41 | case 'rect': 42 | chartType = 'Heatmap'; 43 | break; 44 | default: 45 | chartType = ''; 46 | break; 47 | } 48 | return chartType; 49 | } 50 | // TODO: other chart types (dual axea, etc) 51 | return ''; 52 | } 53 | 54 | /** 55 | * translate antv-spec to g2plot configuration 56 | * @param spec 57 | * @returns configuration to plot g2plot 58 | */ 59 | export function specToG2PlotConfig(spec: ChartAntVSpec) { 60 | // g2plot configuration 61 | const config: Record = {}; 62 | 63 | // chart type and g2plot config (to return) 64 | const configs = { 65 | chartType: '', 66 | config, 67 | }; 68 | 69 | // step 1: convert chart type 70 | const chartType = markToChart(spec); 71 | configs.chartType = chartType; 72 | // if not valid g2plot type or not supported yet 73 | if (!chartType) { 74 | return { 75 | chartType: '', 76 | config, 77 | }; 78 | } 79 | 80 | // step 2: convert basis 81 | if (spec.basis) { 82 | const { basis } = spec; 83 | // to remove `type` in basis because G2Plot config has property with same name 84 | const { type, ...basisWithoutType } = basis; 85 | if (type !== 'chart') { 86 | return { 87 | chartType: '', 88 | config, 89 | }; 90 | } 91 | configs.config = basisWithoutType; 92 | } 93 | 94 | // step 3: convert mark style 95 | if (spec.layer.length === 1 && 'mark' in spec.layer[0]) { 96 | if (typeof spec.layer[0].mark !== 'string') { 97 | // object style to describe 'mark' 98 | if (spec.layer[0].mark.style) { 99 | const styles = spec.layer[0].mark.style; 100 | // for donut chart 101 | if (styles.innerRadius) { 102 | // TODO actual innerRadius 103 | // user input innerRadius may be `px`, but G2Plot treat it as the ratio to the drawing area 104 | configs.config.innerRadius = styles.innerRadius >= 0 && styles.innerRadius < 1 ? styles.innerRadius : 0.6; 105 | } 106 | 107 | // for single color declaration of mark 108 | if (styles.color) { 109 | configs.config.color = styles.color; 110 | } 111 | } 112 | 113 | // line chart && area chart 114 | if (spec.layer[0].mark.interpolate) { 115 | if (spec.layer[0].mark.interpolate === 'step') { 116 | // TODO allow user input hv | vh | hvh | vhv 117 | configs.config.stepType = 'vh'; 118 | } else if (['hv', 'vh', 'vhv', 'hvh'].includes(spec.layer[0].mark.interpolate)) { 119 | configs.config.stepType = spec.layer[0].mark.interpolate; 120 | } 121 | } 122 | } 123 | } 124 | 125 | // step 4: convert data 126 | if (spec.data.type === 'json-array') { 127 | configs.config.data = spec.data.values; 128 | } 129 | 130 | // step 5: convert encoding 131 | if (spec.layer.length === 1 && 'encoding' in spec.layer[0]) { 132 | const layer = spec.layer[0]; 133 | Object.keys(layer.encoding).forEach((key) => { 134 | if (key === 'column' && chartType === 'Column') { 135 | configs.config.xField = layer.encoding[key]?.field; 136 | } 137 | if (key === 'row' && chartType === 'Bar') { 138 | configs.config.yField = layer.encoding[key]?.field; 139 | } 140 | if (key === 'x' || key === 'y') { 141 | if (chartType === 'Column' && 'column' in layer.encoding && key === 'x') { 142 | configs.config.seriesField = layer.encoding[key]?.field; 143 | configs.config.isGroup = true; 144 | } else if (chartType === 'Bar' && 'row' in layer.encoding && key === 'y') { 145 | configs.config.seriesField = layer.encoding[key]?.field; 146 | configs.config.isGroup = true; 147 | } else { 148 | configs.config[`${key}Field`] = layer.encoding[key]?.field; 149 | } 150 | // check if percentage stacking 151 | if (layer.encoding[key]?.stack) { 152 | if (layer.encoding[key]?.stack === 'normalize') { 153 | configs.config.isPercent = true; 154 | } 155 | } 156 | // axis config 157 | const tmpAxis = layer.encoding[key]?.axis; 158 | const tmpAxisCfg: any = {}; 159 | AxisProps.forEach((prop) => { 160 | if (prop === 'title') { 161 | const tmpTitle = layer.encoding[key]?.axis?.title; 162 | if (tmpTitle) { 163 | const tmpTitleCfg: any = {}; 164 | AxisTitleProps.forEach((prop) => { 165 | if (tmpTitle && Object.prototype.hasOwnProperty.call(tmpTitle, prop)) { 166 | tmpTitleCfg[prop] = tmpTitle[prop]; 167 | } 168 | }); 169 | tmpAxisCfg.title = tmpTitleCfg; 170 | } 171 | } else if (prop === 'label') { 172 | const tmpLabel = layer.encoding[key]?.axis?.label; 173 | if (tmpLabel) { 174 | const tmpLabelCfg: any = {}; 175 | AxisLabelProps.forEach((prop) => { 176 | if (tmpLabel && Object.prototype.hasOwnProperty.call(tmpLabel, prop)) { 177 | tmpLabelCfg[prop === 'angle' ? 'rotate' : prop] = tmpLabel[prop]; 178 | } 179 | }); 180 | tmpAxisCfg.label = tmpLabelCfg; 181 | } 182 | } else if (tmpAxis && Object.prototype.hasOwnProperty.call(tmpAxis, prop)) { 183 | tmpAxisCfg[prop] = tmpAxis[prop]; 184 | 185 | if (prop === 'min' || prop === 'max') { 186 | tmpAxisCfg[`${prop}Limit`] = tmpAxis[prop]; 187 | } 188 | } 189 | }); 190 | if (tmpAxis) { 191 | configs.config[`${key}Axis`] = tmpAxisCfg; 192 | } 193 | } else if (key === 'size') { 194 | configs.config.sizeField = layer.encoding[key]?.field; 195 | // TODO: size scale need to be determined by input 196 | configs.config.size = [10, 30]; 197 | } else if (key === 'theta') { 198 | configs.config.angleField = layer.encoding[key]?.field; 199 | } else if (key === 'color') { 200 | // if have color encoding, ignore color setting in mark style 201 | if (Object.keys(configs.config).includes('color')) { 202 | delete configs.config.color; 203 | } 204 | if (CHART_TYPES_WITH_STACK.includes(chartType) || chartType === 'Line') { 205 | // stacking 206 | configs.config.seriesField = layer.encoding[key]?.field; 207 | configs.config.isStack = true; 208 | } else { 209 | configs.config.colorField = layer.encoding[key]?.field; 210 | } 211 | if (layer.encoding[key]?.scale && layer.encoding[key]?.scale.range) { 212 | // define color 213 | configs.config.color = layer.encoding[key]?.scale.range; 214 | } 215 | } 216 | }); 217 | } 218 | 219 | return configs; 220 | } 221 | -------------------------------------------------------------------------------- /src/adaptor/g6/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphAntVSpec } from '../../schema'; 2 | import { specToG6Config } from './toConfig'; 3 | import { g6Render } from './render'; 4 | 5 | export const specToG6Plot = (spec: GraphAntVSpec, container: HTMLElement) => { 6 | const g6Config = specToG6Config(spec); 7 | // remove existing chart in the container 8 | // eslint-disable-next-line no-param-reassign 9 | container.innerHTML = ''; 10 | 11 | if (g6Config) { 12 | const graph = g6Render(g6Config, container); 13 | return graph; 14 | } 15 | 16 | return null; 17 | }; 18 | 19 | export { g6Render }; 20 | -------------------------------------------------------------------------------- /src/adaptor/g6/render.ts: -------------------------------------------------------------------------------- 1 | import G6 from '@antv/g6'; 2 | 3 | export function g6Render(g6Cfg: any, container: HTMLElement) { 4 | if (g6Cfg?.data && g6Cfg?.cfg && container) { 5 | const graph = new G6.Graph({ 6 | container, 7 | ...g6Cfg.cfg, 8 | }); 9 | graph.data(g6Cfg.data); 10 | graph.render(); 11 | 12 | // auto resize the graph to fit the container viewport 13 | const resizeGraphToFit = () => { 14 | const width = container.clientWidth; 15 | const height = container.clientHeight; 16 | if (width && height) { 17 | const maxX = Math.max(...graph.getNodes().map((node) => node.getModel().x)); 18 | const maxY = Math.max(...graph.getNodes().map((node) => node.getModel().y)); 19 | const { x: clientMaxX, y: clientMaxY } = graph.getClientByPoint(maxX, maxY); 20 | graph.zoomTo(Math.min(width / clientMaxX, height / clientMaxY)); 21 | graph.changeSize(width, height); 22 | } 23 | }; 24 | resizeGraphToFit(); 25 | window.onresize = () => { 26 | resizeGraphToFit(); 27 | }; 28 | return graph; 29 | } 30 | 31 | return null; 32 | } 33 | -------------------------------------------------------------------------------- /src/adaptor/g6/toConfig.ts: -------------------------------------------------------------------------------- 1 | import { GraphAntVSpec } from '../../schema'; 2 | 3 | const DEFAULT_WIDTH = 1000; 4 | const DEFAULT_HEIGHT = 600; 5 | 6 | /** Linear scale */ 7 | const linearScaleMap = (scale, x: number) => { 8 | const minRange = Math.min(...scale.range); 9 | const minDomain = Math.min(...scale.domain); 10 | const maxRange = Math.max(...scale.range); 11 | const maxDomain = Math.max(...scale.domain); 12 | return ((maxRange - minRange) / (maxDomain - minDomain)) * (x - minDomain) + minRange; 13 | }; 14 | 15 | export function specToG6Config(spec: GraphAntVSpec) { 16 | const config: Record = {}; 17 | const g6Cfg = { 18 | cfg: config, 19 | data: {}, 20 | }; 21 | 22 | if (spec.basis) { 23 | g6Cfg.cfg.height = spec.basis?.height || DEFAULT_HEIGHT; 24 | g6Cfg.cfg.width = spec.basis?.width || DEFAULT_WIDTH; 25 | } 26 | 27 | if ('layout' in spec) { 28 | const layoutCfg: Record = {}; 29 | if (spec.layout?.type) { 30 | layoutCfg.type = spec.layout.type; 31 | layoutCfg.options = (spec.layout as any).options; 32 | } 33 | g6Cfg.cfg.layout = layoutCfg; 34 | } 35 | 36 | // convert data to { "nodes": [{...}, ], "links": [{...}, ]} 37 | const dataVals = spec.data.values; 38 | const g6Data: Record = {}; 39 | const nodeKey = spec.layout.nodes; 40 | const linkKey = spec.layout.links; 41 | g6Data.nodes = dataVals[nodeKey]; 42 | g6Data.edges = dataVals[linkKey]; 43 | 44 | // mapping size/color encoding of edges and nodes in data 45 | const { nodes } = g6Data; 46 | nodes.forEach((node: any) => { 47 | const updateNode = node; 48 | updateNode.oriSize = updateNode.size; 49 | updateNode.oriLabel = updateNode.label; 50 | return updateNode; 51 | }); 52 | const nodesEnc = 'nodes' in spec.layer[0] ? spec.layer[0].nodes : null; 53 | if (nodesEnc) { 54 | if (nodesEnc.mark) { 55 | nodes.forEach((node: any) => { 56 | const updateNode = node; 57 | updateNode.type = nodesEnc.mark === 'point' ? 'circle' : nodesEnc.mark; 58 | }); 59 | } 60 | if (nodesEnc.encoding?.color) { 61 | // have color encoding for nodes 62 | const { field, scale } = nodesEnc.encoding.color; 63 | const colorMap = new Map(); 64 | let colorId = 0; 65 | if (scale) { 66 | nodes.forEach((node: any) => { 67 | const updateNode = node; 68 | if (node[field] && colorMap.get(node[field]) === undefined) { 69 | colorMap.set(node[field], colorId); 70 | colorId += 1; 71 | } 72 | const cid = colorMap.get(node[field]); 73 | if (!updateNode.style) updateNode.style = {}; 74 | updateNode.style.fill = scale.range[cid % scale.range.length]; 75 | updateNode.style.stroke = scale.range[cid % scale.range.length]; 76 | return updateNode; 77 | }); 78 | } 79 | } 80 | if (nodesEnc.encoding?.size) { 81 | // have size encoding for nodes 82 | const { field, scale } = nodesEnc.encoding.size as any; 83 | if (scale) { 84 | nodes.forEach((node: any) => { 85 | const updateNode = node; 86 | updateNode.size = updateNode.size || linearScaleMap(scale, updateNode[field]); 87 | if (updateNode.type === 'rect') { 88 | updateNode.size = [updateNode.size * 2, updateNode.size]; 89 | } 90 | return updateNode; 91 | }); 92 | } 93 | } 94 | if ((nodesEnc.encoding as any)?.label) { 95 | const { field } = (nodesEnc.encoding as any).label; 96 | nodes.forEach((node: any) => { 97 | const updateNode = node; 98 | updateNode.label = updateNode.label || updateNode[field]; 99 | return updateNode; 100 | }); 101 | } 102 | } 103 | 104 | const { edges } = g6Data; 105 | const edgesEnc = 'links' in spec.layer[0] ? spec.layer[0].links : null; 106 | if (edgesEnc) { 107 | if (edgesEnc.mark) { 108 | edges.forEach((edge: any) => { 109 | const updateEdge = edge; 110 | updateEdge.type = edgesEnc.mark; 111 | }); 112 | } 113 | if (edgesEnc.encoding?.color) { 114 | // have color encoding for edges 115 | const { field, scale } = edgesEnc.encoding.color; 116 | const colorMap = new Map(); 117 | let colorId = 0; 118 | if (scale) { 119 | edges.forEach((edge: any) => { 120 | const updateEdge = edge; 121 | if (edge[field] && colorMap.get(edge[field]) === undefined) { 122 | colorMap.set(edge[field], colorId); 123 | colorId += 1; 124 | } 125 | const cid = colorMap.get(edge[field]); 126 | if (!updateEdge.style) updateEdge.style = {}; 127 | updateEdge.style.stroke = scale.range[cid % scale.range.length]; 128 | return updateEdge; 129 | }); 130 | } 131 | } 132 | if (edgesEnc.encoding?.size) { 133 | // have size encoding for edges 134 | const { field, scale } = edgesEnc.encoding.size as any; 135 | if (scale) { 136 | edges.forEach((edge: any) => { 137 | const updateEdge = edge; 138 | if (!updateEdge.style) updateEdge.style = {}; 139 | updateEdge.style.lineWidth = linearScaleMap(scale, updateEdge[field]); 140 | return updateEdge; 141 | }); 142 | } 143 | } 144 | } 145 | 146 | g6Cfg.data = g6Data; 147 | g6Cfg.cfg.linkCenter = true; 148 | g6Cfg.cfg.modes = { 149 | default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // default canvas interactions 150 | }; 151 | return g6Cfg; 152 | } 153 | -------------------------------------------------------------------------------- /src/adaptor/index.ts: -------------------------------------------------------------------------------- 1 | export { specToG2Plot, g2plotRender } from './g2plot'; 2 | export { specToG2PlotConfig } from './g2plot/toConfig'; 3 | export { specToG6Plot, g6Render } from './g6'; 4 | export { specToG6Config } from './g6/toConfig'; 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import pkg from '../package.json'; 2 | 3 | export const { version } = pkg; 4 | 5 | export * from './schema'; 6 | export { specToG2Plot, specToG6Plot, specToG2PlotConfig, specToG6Config, g2plotRender, g6Render } from './adaptor'; 7 | -------------------------------------------------------------------------------- /src/schema/basis/index.ts: -------------------------------------------------------------------------------- 1 | export type Basis = ChartBasis | GraphBasis; 2 | 3 | export type ChartBasis = GenericBasis & { 4 | /** indicate what kind of visualization this spec is about */ 5 | type: 'chart'; 6 | }; 7 | 8 | export type GraphBasis = GenericBasis & { 9 | /** indicate what kind of visualization this spec is about */ 10 | type: 'graph'; 11 | }; 12 | 13 | interface GenericBasis { 14 | /** 15 | * vis's width 16 | * @minimum 0 17 | */ 18 | width?: number; 19 | /** 20 | * vis's height 21 | * @minimum 0 22 | */ 23 | height?: number; 24 | /** 25 | * vis's padding 26 | */ 27 | padding?: number[] | number | 'auto'; 28 | } 29 | -------------------------------------------------------------------------------- /src/schema/component/annotation.ts: -------------------------------------------------------------------------------- 1 | // TODO: 2 | // `position` and `style` detail structure 3 | // detailed structure for non-text type 4 | export type Annotation = 5 | | { 6 | type: 'text'; 7 | position: object; 8 | content: string; 9 | style?: object; 10 | } 11 | | { 12 | type: 'line'; 13 | position: object; 14 | style: object; 15 | start?: any; 16 | end?: any; 17 | } 18 | | { 19 | type: string; 20 | position: object; 21 | style: object; 22 | }; 23 | -------------------------------------------------------------------------------- /src/schema/component/index.ts: -------------------------------------------------------------------------------- 1 | import { Annotation } from './annotation'; 2 | 3 | export interface Component { 4 | annotations?: Annotation[]; 5 | } 6 | -------------------------------------------------------------------------------- /src/schema/data/dataRow.ts: -------------------------------------------------------------------------------- 1 | export type DataRow = { [columnTitles: string]: any }; 2 | -------------------------------------------------------------------------------- /src/schema/data/graphData.ts: -------------------------------------------------------------------------------- 1 | export interface GraphData { 2 | [key: string]: any; 3 | } 4 | -------------------------------------------------------------------------------- /src/schema/data/index.ts: -------------------------------------------------------------------------------- 1 | import { DataRow } from './dataRow'; 2 | import { GraphData } from './graphData'; 3 | 4 | export type Data = GraphInlineData | GraphOnlineData | ChartInlineData | ChartOnlineData; 5 | 6 | export type ChartDataDef = ChartInlineData | ChartOnlineData; 7 | export type GraphDataDef = GraphInlineData | GraphOnlineData; 8 | 9 | type DataConfig = { 10 | delimiter?: string; 11 | }; 12 | 13 | export type GraphInlineData = { 14 | /** 15 | * type of `values` 16 | */ 17 | type?: 'json'; 18 | values: GraphData; 19 | }; 20 | 21 | export type GraphOnlineData = { 22 | type?: 'url'; 23 | /** 24 | * URL of the data source. 25 | */ 26 | values: string; 27 | /** 28 | * type of the data, for correctly parsing 29 | */ 30 | format?: 'json'; 31 | }; 32 | 33 | export type ChartInlineData = { 34 | type?: 'json-array'; 35 | values: DataRow[]; 36 | }; 37 | 38 | export type ChartOnlineData = { 39 | type?: 'url'; 40 | /** 41 | * URL of the data source. 42 | */ 43 | values: string; 44 | /** 45 | * type of the data, for correctly parsing 46 | */ 47 | format?: 'csv' | 'json'; 48 | /** 49 | * config for parsing data 50 | */ 51 | config?: DataConfig; 52 | }; 53 | 54 | export { DataRow }; 55 | -------------------------------------------------------------------------------- /src/schema/encoding/aggregate.ts: -------------------------------------------------------------------------------- 1 | // TODO more aggregation 2 | export type Aggregate = 'count' | 'sum' | 'min' | 'max'; 3 | -------------------------------------------------------------------------------- /src/schema/encoding/axis.ts: -------------------------------------------------------------------------------- 1 | export interface AxisTitleConfig { 2 | text?: string; 3 | position?: string; 4 | // TODO: fill what AVA contains 5 | } 6 | 7 | export interface AxisLabelConfig { 8 | offset?: number; 9 | 10 | angle?: number; 11 | autoRotate?: boolean; 12 | autoHide?: boolean; 13 | autoEllipsis?: boolean; 14 | 15 | // TODO: function 16 | formatter?: any; 17 | // TODO: style 18 | } 19 | export interface Axis { 20 | /** 21 | * whether draw the axis on the top of the layer 22 | */ 23 | top?: boolean; 24 | /** 25 | * direction of the axis, top | bottom | left | right 26 | */ 27 | position?: string; 28 | /** 29 | * title of the axis 30 | */ 31 | title?: AxisTitleConfig; 32 | /** 33 | * label of the axis 34 | */ 35 | label?: AxisLabelConfig; 36 | /** 37 | * min of the axis 38 | */ 39 | min?: number; 40 | /** 41 | * max of the axis 42 | */ 43 | max?: number; 44 | /** 45 | * interval of the ticks in the axis 46 | */ 47 | tickInterval?: number; 48 | /** 49 | * whether to show ticks in the axis 50 | */ 51 | ticks?: boolean; 52 | /** 53 | * whether to draw the domain line of the axis 54 | */ 55 | domain?: boolean; 56 | } 57 | 58 | export const AxisProps = ['top', 'position', 'title', 'label', 'min', 'max', 'tickInterval'] as const; 59 | export const AxisTitleProps = ['text', 'position'] as const; 60 | export const AxisLabelProps = ['offset', 'angle', 'autoRotate', 'autoHide', 'autoEllipsis', 'formatter'] as const; 61 | 62 | /** 63 | * @public 64 | */ 65 | // export type AxisProps = typeof AXIS_PROPS[number]; 66 | 67 | // /** 68 | // * @public 69 | // */ 70 | // export type AxisTitleProps = typeof AXIS_TITLE_PROPS[number]; 71 | 72 | // /** 73 | // * @public 74 | // */ 75 | // export type AxisLabelProps = typeof AXIS_LABEL_PROPS[number]; 76 | -------------------------------------------------------------------------------- /src/schema/encoding/bin.ts: -------------------------------------------------------------------------------- 1 | // TODO more bin config such as step, etc; 2 | export type Bin = true | false; 3 | -------------------------------------------------------------------------------- /src/schema/encoding/color.ts: -------------------------------------------------------------------------------- 1 | import { EType } from './type'; 2 | import { Aggregate } from './aggregate'; 3 | import { Scale } from './scale'; 4 | 5 | export interface Color { 6 | field?: string; 7 | type: EType; 8 | aggregate?: Aggregate; 9 | scale?: Scale; 10 | } 11 | -------------------------------------------------------------------------------- /src/schema/encoding/column.ts: -------------------------------------------------------------------------------- 1 | import { EType } from './type'; 2 | 3 | export interface Column { 4 | field: string; 5 | type: EType; 6 | } 7 | -------------------------------------------------------------------------------- /src/schema/encoding/index.ts: -------------------------------------------------------------------------------- 1 | import { X } from './x'; 2 | import { Y } from './y'; 3 | import { Color } from './color'; 4 | import { Theta } from './theta'; 5 | import { Size } from './size'; 6 | import { Column } from './column'; 7 | import { Row } from './row'; 8 | 9 | export type NodeEncoding = { 10 | size?: Size; 11 | color?: Color; 12 | theta?: Theta; 13 | }; 14 | 15 | export type LinkEncoding = { 16 | size?: Size; 17 | color?: Color; 18 | }; 19 | 20 | export type ChartEncoding = { 21 | x?: X; 22 | y?: Y; 23 | color?: Color; 24 | theta?: Theta; 25 | size?: Size; 26 | column?: Column; 27 | row?: Row; 28 | }; 29 | 30 | export const CHART_CHANNELS = ['x', 'y', 'color', 'theta', 'size', 'column', 'row']; 31 | export const GRAPH_CHANNELS = ['size', 'color', 'theta']; 32 | 33 | export * from './x'; 34 | export * from './y'; 35 | export * from './color'; 36 | export * from './theta'; 37 | export * from './size'; 38 | export * from './row'; 39 | export * from './column'; 40 | -------------------------------------------------------------------------------- /src/schema/encoding/row.ts: -------------------------------------------------------------------------------- 1 | import { EType } from './type'; 2 | 3 | export interface Row { 4 | field: string; 5 | type: EType; 6 | } 7 | -------------------------------------------------------------------------------- /src/schema/encoding/scale.ts: -------------------------------------------------------------------------------- 1 | // TODO: detailed type denifition. currently to support color hex string 2 | export type Scale = { 3 | // range after data mapping 4 | // such as ['apple', 'banana'] --map to--> ['red', 'yellow'](range) 5 | range?: (string | number)[]; 6 | rangeMin?: string | number; 7 | rangeMax?: string | number; 8 | // data domain used to data mapping 9 | // such as [0, 100] --domain:[10, 90]--> [10, 90](filter data less than 10) 10 | domain?: number[]; 11 | domainMin?: number; 12 | domaminMax?: number; 13 | }; 14 | -------------------------------------------------------------------------------- /src/schema/encoding/size.ts: -------------------------------------------------------------------------------- 1 | import { EType } from './type'; 2 | import { Aggregate } from './aggregate'; 3 | 4 | export interface Size { 5 | field: string; 6 | type: EType; 7 | aggregate?: Aggregate; 8 | } 9 | -------------------------------------------------------------------------------- /src/schema/encoding/stack.ts: -------------------------------------------------------------------------------- 1 | // TODO: more config for stacking; 2 | export type Stack = true | false | 'zero' | 'normalize'; 3 | -------------------------------------------------------------------------------- /src/schema/encoding/theta.ts: -------------------------------------------------------------------------------- 1 | import { EType } from './type'; 2 | import { Aggregate } from './aggregate'; 3 | 4 | export interface Theta { 5 | field?: string; 6 | type: EType; 7 | aggregate?: Aggregate; 8 | } 9 | -------------------------------------------------------------------------------- /src/schema/encoding/type.ts: -------------------------------------------------------------------------------- 1 | export type EType = 'quantitative' | 'temporal' | 'ordinal' | 'nominal'; 2 | -------------------------------------------------------------------------------- /src/schema/encoding/x.ts: -------------------------------------------------------------------------------- 1 | import { Axis } from './axis'; 2 | import { EType } from './type'; 3 | import { Aggregate } from './aggregate'; 4 | import { Bin } from './bin'; 5 | import { Stack } from './stack'; 6 | 7 | export interface X { 8 | /** 9 | * field can be optional once the aggregation type is `count` 10 | */ 11 | field?: string; 12 | type: EType; 13 | axis?: Axis; 14 | aggregate?: Aggregate; 15 | bin?: Bin; 16 | stack?: Stack; 17 | } 18 | 19 | export * from './axis'; 20 | -------------------------------------------------------------------------------- /src/schema/encoding/y.ts: -------------------------------------------------------------------------------- 1 | import { Aggregate } from './aggregate'; 2 | import { Axis } from './axis'; 3 | import { EType } from './type'; 4 | import { Bin } from './bin'; 5 | import { Stack } from './stack'; 6 | 7 | export interface Y { 8 | /** 9 | * field can be optional once the aggregation type is `count` 10 | */ 11 | field?: string; 12 | type: EType; 13 | axis?: Axis; 14 | /** 15 | * aggregate and bin cannot be declared together 16 | */ 17 | aggregate?: Aggregate; 18 | bin?: Bin; 19 | stack?: Stack; 20 | } 21 | -------------------------------------------------------------------------------- /src/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphDataDef, ChartDataDef } from './data'; 2 | import { ChartBasis, GraphBasis } from './basis'; 3 | import { Component } from './component'; 4 | import { Interaction } from './interaction'; 5 | import { Layout } from './layout'; 6 | import { GraphLayer, ChartLayer } from './layer'; 7 | 8 | export type AntVSpec = CommonAntVSpec & (ChartAntVSpecDef | GraphAntVSpecDef); 9 | export type ChartAntVSpec = CommonAntVSpec & ChartAntVSpecDef; 10 | export type GraphAntVSpec = CommonAntVSpec & GraphAntVSpecDef; 11 | 12 | type CommonAntVSpec = { 13 | /** 14 | * URL of the schema 15 | */ 16 | $schema?: string; 17 | /** 18 | * Components definition such as Annotation 19 | */ 20 | component?: Component; 21 | interactions?: Interaction[]; 22 | }; 23 | type ChartAntVSpecDef = { 24 | basis: ChartBasis; 25 | data: ChartDataDef; 26 | layer: ChartLayer[]; 27 | }; 28 | 29 | type GraphAntVSpecDef = { 30 | basis: GraphBasis; 31 | data: GraphDataDef; 32 | layout: Layout; 33 | layer: GraphLayer[]; 34 | }; 35 | 36 | export * from './basis'; 37 | export * from './data'; 38 | export * from './mark'; 39 | export * from './encoding'; 40 | export * from './component'; 41 | -------------------------------------------------------------------------------- /src/schema/interaction/index.ts: -------------------------------------------------------------------------------- 1 | export interface Interaction { 2 | type: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/schema/layer/index.ts: -------------------------------------------------------------------------------- 1 | import { ChartMarkDef, NodeMarkDef, LinkMarkDef } from '../mark'; 2 | import { ChartEncoding, NodeEncoding, LinkEncoding } from '../encoding'; 3 | 4 | export type ChartLayer = { 5 | mark: ChartMarkDef; 6 | encoding: ChartEncoding; 7 | }; 8 | 9 | export type GraphLayer = { 10 | nodes: NodeDef; 11 | links: LinkDef; 12 | }; 13 | 14 | type NodeDef = { 15 | mark: NodeMarkDef; 16 | encoding: NodeEncoding; 17 | }; 18 | 19 | type LinkDef = { 20 | mark: LinkMarkDef; 21 | encoding: LinkEncoding; 22 | }; 23 | -------------------------------------------------------------------------------- /src/schema/layout/index.ts: -------------------------------------------------------------------------------- 1 | export interface Layout { 2 | /** layout type */ 3 | type: 4 | | 'random' 5 | | 'radial' 6 | | 'mds' 7 | | 'circular' 8 | | 'fruchterman' 9 | | 'force' 10 | | 'gForce' 11 | | 'dagre' 12 | | 'concentric' 13 | | 'grid' 14 | | 'forceAtlas2'; 15 | /** the field name used to map nodes in the `json` type data */ 16 | nodes: string; 17 | /** the field name used to map links in the `json` type data */ 18 | links: string; 19 | /** layout detail configurations */ 20 | options?: object; 21 | } 22 | -------------------------------------------------------------------------------- /src/schema/mark/index.ts: -------------------------------------------------------------------------------- 1 | export type Mark = 2 | | MarkType 3 | | { 4 | type: MarkType; 5 | style?: MarkStyleConfig; 6 | }; 7 | 8 | /** 9 | * Mark definition for `Chart` type visualization 10 | */ 11 | export type ChartMarkDef = 12 | | ChartMarkType 13 | | { 14 | type: ChartMarkType; 15 | /** 16 | * for `line` and `area` only 17 | * TODO: 'step' is used in AVA but more interpolate type can be defined 18 | */ 19 | interpolate?: 'step'; 20 | style?: MarkStyleConfig; 21 | }; 22 | 23 | /** 24 | * Mark definition for node of `Graph` type visualization 25 | */ 26 | export type NodeMarkDef = 27 | | NodeMarkType 28 | | { 29 | type: NodeMarkType; 30 | style?: MarkStyleConfig; 31 | }; 32 | 33 | /** 34 | * Mark definition for link of `Graph` type visualization 35 | */ 36 | export type LinkMarkDef = 37 | | LinkMarkType 38 | | { 39 | type: LinkMarkType; 40 | style?: MarkStyleConfig; 41 | }; 42 | 43 | /** 44 | * Mark type supported by antv-spec 45 | */ 46 | export type MarkType = ChartMarkType | GraphMarkType; 47 | export const CHART_MARK_TYPES = ['bar', 'line', 'arc', 'area', 'point', 'rect'] as const; 48 | export type ChartMarkType = typeof CHART_MARK_TYPES[number]; 49 | export type GraphMarkType = NodeMarkType & LinkMarkType; 50 | export const NODE_MARK_TYPES = ['point', 'arc', 'rect']; 51 | export type NodeMarkType = typeof NODE_MARK_TYPES[number]; 52 | export const LINK_MARK_TYPES = ['line']; 53 | export type LinkMarkType = typeof LINK_MARK_TYPES[number]; 54 | 55 | /** 56 | * Mark style configuration 57 | */ 58 | interface MarkStyleConfig { 59 | size?: number; 60 | lineWidth?: number; 61 | strokeWidth?: number; 62 | color?: ColorCfg; 63 | fillColor?: ColorCfg; 64 | strokeColor?: ColorCfg; 65 | opacity?: number; 66 | fillOpacity?: number; 67 | strokeOpacity?: number; 68 | shape?: string; // specific shape, e.g. triangle 69 | innerRadius?: number; 70 | } 71 | 72 | // current support hex string 73 | export type ColorCfg = string; 74 | 75 | export interface RGBColor { 76 | /** 77 | * @minimum 0 78 | * @maximum 255 79 | */ 80 | r: number; 81 | /** 82 | * @minimum 0 83 | * @maximum 255 84 | */ 85 | g: number; 86 | /** 87 | * @minimum 0 88 | * @maximum 255 89 | */ 90 | b: number; 91 | } 92 | 93 | export interface HSLColor { 94 | /** 95 | * @minimum 0 96 | * @maximum 360 97 | */ 98 | h: number; 99 | /** 100 | * @minimum 0 101 | * @maximum 1 102 | */ 103 | s: number; 104 | /** 105 | * @minimum 0 106 | * @maximum 1 107 | */ 108 | l: number; 109 | } 110 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib", 4 | "module": "commonjs", 5 | "resolveJsonModule": true, 6 | "importHelpers": true, 7 | "target": "ES2019", 8 | "jsx": "preserve", 9 | "moduleResolution": "node", 10 | "experimentalDecorators": true, 11 | "declaration": true, 12 | "sourceMap": true, 13 | "allowSyntheticDefaultImports": true, 14 | "esModuleInterop": true, 15 | "pretty": true, 16 | "lib": ["dom", "ES2019"], 17 | "skipLibCheck": true, 18 | "sourceRoot": "src", 19 | "baseUrl": ".", 20 | "paths": { 21 | "@src/*": ["src/*"] 22 | } 23 | }, 24 | "include": ["src/**/*", "__tests__/**/*"], 25 | "exclude": ["node_modules"] 26 | } 27 | --------------------------------------------------------------------------------