├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .prettierrc ├── .torch.compile.opts.js ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTING.zh-CN.md ├── LICENSE ├── README.md ├── bin ├── doc-format.js ├── mkdir-dist.js ├── screenshot.js └── win-dev.js ├── bundler ├── app.js ├── assets │ ├── index.css │ └── index.js ├── data │ ├── blocks.json │ └── template.js └── index.njk ├── demos ├── adjacency.html ├── app.js ├── arc.html ├── assets │ ├── alice-mask.png │ ├── bootstrap-4.1.0 │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.js │ ├── clipboard-1.7.1.min.js │ ├── codemirror-5.29.0 │ │ ├── codemirror-merged.min.css │ │ └── codemirror-merged.min.js │ ├── common.css │ ├── d3-4.10.0.min.js │ ├── file-saver-1.3.8.min.js │ ├── g2.js │ ├── g2.min.js │ ├── index.css │ ├── index.js │ ├── jquery-3.2.1.min.js │ ├── jquery.resizable-0.20.0.js │ ├── lazyload-2.0.0-beta.2.min.js │ ├── lodash-4.17.4.min.js │ ├── love-mask.png │ ├── popper.js-1.12.5 │ │ ├── popper-utils.min.js │ │ └── popper.min.js │ └── routie-0.3.2.min.js ├── bar.html ├── chord.html ├── circle-packing.html ├── dagre.html ├── data │ └── love-the-world.txt ├── dendrogram.html ├── folding.html ├── geo-projections.html ├── hexagon.html ├── hexjson.html ├── histogram-stack.html ├── histogram.html ├── index.njk ├── kde.html ├── kernel-smooth-density.html ├── kernel-smooth-regression-1.html ├── kernel-smooth-regression-2.html ├── partition.html ├── periodic-table.html ├── quantile.html ├── rectangle.html ├── regression.html ├── sankey.html ├── tag-cloud.html ├── tree-layout-compact-box.html ├── tree-layout-dendrogram.html ├── tree-layout-indented.html ├── tree.html ├── treemap.html ├── waffle.html └── webpack-visualizer.html ├── docs ├── connector.md ├── dataset.md ├── overview.md └── transform.md ├── package.json ├── src ├── api │ ├── geo.ts │ ├── hierarchy.ts │ ├── partition.ts │ └── statistics.ts ├── connector-params.ts ├── connector │ ├── default.ts │ ├── dsv.ts │ ├── geo-graticule.ts │ ├── geojson.ts │ ├── graph.ts │ ├── hexjson.ts │ ├── hierarchy.ts │ └── topojson.ts ├── constants.ts ├── data-set.ts ├── index.ts ├── moudles.d.ts ├── transform-params.ts ├── transform │ ├── aggregate.ts │ ├── bin │ │ ├── hexagon.ts │ │ ├── histogram.ts │ │ ├── quantile.ts │ │ └── rectangle.ts │ ├── default.ts │ ├── diagram │ │ ├── arc.ts │ │ ├── dagre.ts │ │ ├── sankey.ts │ │ └── voronoi.ts │ ├── fill-rows.ts │ ├── filter.ts │ ├── fold.ts │ ├── geo │ │ ├── centroid.ts │ │ ├── projection.ts │ │ └── region.ts │ ├── hierarchy │ │ ├── cluster.ts │ │ ├── compact-box.ts │ │ ├── dendrogram.ts │ │ ├── indented.ts │ │ ├── pack.ts │ │ ├── partition.ts │ │ ├── tree.ts │ │ └── treemap.ts │ ├── impute.ts │ ├── kde.ts │ ├── kernel-smooth │ │ ├── density.ts │ │ └── regression.ts │ ├── map.ts │ ├── partition.ts │ ├── percent.ts │ ├── pick.ts │ ├── proportion.ts │ ├── regression.ts │ ├── rename.ts │ ├── reverse.ts │ ├── sort-by.ts │ ├── sort.ts │ ├── subset.ts │ ├── tag-cloud.ts │ └── waffle.ts ├── util │ ├── bandwidth.ts │ ├── euclidean-distance.ts │ ├── get-geo-projection.ts │ ├── get-series-values.ts │ ├── kernel.ts │ ├── option-parser.ts │ ├── p-by-fraction.ts │ ├── partition.ts │ ├── simple-sort-by.ts │ └── tag-cloud.ts └── view.ts ├── test ├── bugs │ ├── 98-spec.ts │ └── geo-max-call-stack-spec.ts ├── fixtures │ ├── china-geo.json │ ├── china-provinces.json │ ├── countries-geo.json │ ├── diamond.json │ ├── empty-topo.json │ ├── energy.json │ ├── flare.json │ ├── g2pv.json │ ├── iris-en.json │ ├── iris.json │ ├── periodic-table.hex.json │ ├── periodic-table.json │ ├── population-china.csv │ ├── population-china.json │ ├── relationship-with-weight.json │ ├── sample.csv │ ├── sample.json │ ├── sample.psv │ ├── sample.tsv │ ├── sample2.csv │ ├── sample2.json │ ├── sample2.tsv │ ├── top2000.json │ ├── us-states.hex.json │ ├── us-topo.json │ └── world-50m.json ├── support │ └── util.ts ├── tsconfig.json └── unit │ ├── api │ ├── geo-spec.ts │ ├── hierarchy-spec.ts │ └── statistics-spec.ts │ ├── connector │ ├── default-spec.ts │ ├── dsv-spec.ts │ ├── geo-graticule-spec.ts │ ├── geojson-spec.ts │ ├── hierarchy-spec.ts │ └── topojson-spec.ts │ ├── data-set-spec.ts │ ├── index-spec.ts │ ├── transform │ ├── aggregate-spec.ts │ ├── bin │ │ ├── hexagon-spec.ts │ │ ├── histogram-spec.ts │ │ ├── quantile-spec.ts │ │ └── rectangle-spec.ts │ ├── default-spec.ts │ ├── diagram │ │ └── voronoi-spec.ts │ ├── fill-rows-spec.ts │ ├── filter-spec.ts │ ├── fold-spec.ts │ ├── geo │ │ ├── centroid-spec.ts │ │ ├── projection-spec.ts │ │ └── region-spec.ts │ ├── hierarchy │ │ ├── compact-box-spec.ts │ │ └── treemap-spec.ts │ ├── impute-spec.ts │ ├── kde-spec.ts │ ├── map-spec.ts │ ├── percent-spec.ts │ ├── pick-spec.ts │ ├── proportion-spec.ts │ ├── regression-spec.ts │ ├── rename-spec.ts │ ├── reverse-spec.ts │ ├── sort-by-spec.ts │ ├── sort-spec.ts │ ├── subset-spec.ts │ └── tag-cloud-spec.ts │ ├── util │ ├── get-geo-projection-spec.ts │ ├── kernel-spec.ts │ ├── p-by-fraction-spec.ts │ └── partition-spec.ts │ └── view-spec.ts ├── tsconfig.json ├── webpack-dev.config.js └── webpack.config.js /.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 | build/ 2 | coverage/ 3 | demos/assets/ 4 | dist/ 5 | lib/ 6 | mocks/ 7 | node_modules/ 8 | webpack.config.js 9 | webpack-dev.config.js 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/eslint-recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "prettier" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "plugins": ["@typescript-eslint", "prettier"], 10 | "parserOptions": { 11 | "ecmaVersion": 6, 12 | "sourceType": "module", 13 | "impliedStrict": true 14 | }, 15 | "rules": { 16 | "no-sparse-arrays": 0, 17 | "no-inner-declarations": 0, 18 | "prettier/prettier": 2, 19 | "@typescript-eslint/indent": 0, 20 | "no-constant-condition": 0, 21 | "@typescript-eslint/no-empty-function": 0, 22 | "@typescript-eslint/explicit-member-accessibility": [2, { "accessibility": "no-public" }], 23 | "@typescript-eslint/no-non-null-assertion": 0, 24 | "@typescript-eslint/explicit-function-return-type": [1, { "allowExpressions": true }], 25 | "@typescript-eslint/no-use-before-define": [2, { "functions": false }], 26 | "@typescript-eslint/no-namespace": 0, 27 | "@typescript-eslint/ban-ts-ignore": 0, 28 | "@typescript-eslint/no-empty-interface": 1, 29 | "@typescript-eslint/camelcase": 0, 30 | "@typescript-eslint/no-explicit-any": 0 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | * **Link**: 18 | * **Platform**: 19 | * **Mini Showcase(like screenshots)**: 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ##### Checklist 12 | 13 | 14 | - [ ] `npm test` passes 15 | - [ ] tests and/or benchmarks are included 16 | - [ ] commit message follows commit guidelines 17 | 18 | ##### Description of change 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # lock 9 | package-lock.json 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | build 64 | dist 65 | temp 66 | .DS_Store 67 | .idea 68 | demos/assets/screenshots 69 | lib 70 | 71 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # lock 9 | package-lock.json 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # build 64 | demos 65 | temp 66 | test 67 | webpack-dev.config.js 68 | webpack.config.js 69 | .DS_Store 70 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 120, 5 | "arrowParens": "always" 6 | } 7 | -------------------------------------------------------------------------------- /.torch.compile.opts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | babelrc: { 3 | presets: ['@babel/env'], 4 | sourceMaps: 'inline', 5 | }, 6 | extensions: ['.es6', '.es', '.jsx', '.js', '.ts'], 7 | include: ['src/**', 'test/**'], 8 | exclude: ['node_modules/**'], 9 | tsconfig: require('./tsconfig.json'), 10 | }; 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | 6 | env: 7 | matrix: 8 | - TEST_TYPE=ci 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - xvfb 14 | 15 | install: 16 | - export DISPLAY=':99.0' 17 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 18 | - npm install 19 | 20 | script: 21 | - | 22 | if [ "$TEST_TYPE" = ci ]; then 23 | npm run ci 24 | fi 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 0.11.2 (2020-04-07) 2 | 3 | ##### New Features 4 | 5 | * Add sankey sort ([b7ee5f4b](https://github.com/antvis/data-set/commit/b7ee5f4b7e77879505f3a1328db3c8b8f52cf16b)) 6 | 7 | ##### Bug Fixes 8 | 9 | * 移除多余的判断,method 只支持特定字符串 ([e2386721](https://github.com/antvis/data-set/commit/e2386721efacc10b28c182d01fd643329b32c17d)) 10 | 11 | 12 | #### 0.11.0 (2020-02-12) 13 | 14 | ##### New Features 15 | 16 | * 升级为 TypeScript 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alipay.inc 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 | # data-set 2 | 3 | [![](https://img.shields.io/travis/antvis/data-set.svg)](https://travis-ci.org/antvis/data-set) 4 | ![](https://img.shields.io/badge/language-typescript-red.svg) 5 | ![](https://img.shields.io/badge/license-MIT-000000.svg) 6 | 7 | [![npm package](https://img.shields.io/npm/v/@antv/data-set.svg)](https://www.npmjs.com/package/@antv/data-set) 8 | [![NPM downloads](http://img.shields.io/npm/dm/@antv/data-set.svg)](https://npmjs.org/package/@antv/data-set) 9 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/data-set.svg)](http://isitmaintained.com/project/antvis/data-set 'Percentage of issues still open') 10 | 11 | Data set with state management. 12 | 13 | ## Installing 14 | 15 | `npm install @antv/data-set` 16 | 17 | ```js 18 | import DataSet from '@antv/data-set'; 19 | 20 | const ds = new DataSet({ 21 | state: { 22 | // initialize state 23 | foo: 'bar', 24 | }, 25 | }); 26 | ``` 27 | 28 | ## Document 29 | 30 | - [快速入门](./docs/overview.md) 31 | - [DataSet](./docs/dataset.md) 32 | - [Connector 数据接入](./docs/connector.md) 33 | - [Transform 数据转换](./docs/transform.md) 34 | -------------------------------------------------------------------------------- /bin/doc-format.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const commander = require('commander'); 3 | const d3Dsv = require('d3-dsv'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const pkg = require('../package.json'); 7 | 8 | function resolve(pathname) { 9 | return path.resolve(process.cwd(), pathname); 10 | } 11 | 12 | commander.version(pkg.version); 13 | 14 | commander.command('csv2json ') 15 | .description('convert csv file to json') 16 | .action(filename => { 17 | const content = fs.readFileSync(resolve(filename), 'utf8'); 18 | const converted = JSON.stringify(d3Dsv.csvParse(content), null, 2); 19 | process.stdout.write(converted); 20 | }); 21 | 22 | commander.command('tsv2json ') 23 | .description('convert tsv file to json') 24 | .action(filename => { 25 | const content = fs.readFileSync(resolve(filename), 'utf8'); 26 | const converted = JSON.stringify(d3Dsv.tsvParse(content), null, 2); 27 | process.stdout.write(converted); 28 | }); 29 | 30 | commander.command('compress-json ') 31 | .description('convert tsv file to json') 32 | .option('--override', 'override the origin file') 33 | .action((filename, options) => { 34 | const content = fs.readFileSync(resolve(filename), 'utf8'); 35 | const converted = JSON.stringify(JSON.parse(content)); 36 | if (options.override) { 37 | fs.writeFileSync(resolve(filename), converted, 'utf8'); 38 | } else { 39 | process.stdout.write(converted); 40 | } 41 | }); 42 | 43 | commander.parse(process.argv); 44 | 45 | if (process.argv.length === 2) { 46 | commander.outputHelp(); 47 | } 48 | -------------------------------------------------------------------------------- /bin/mkdir-dist.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path'); 3 | const shelljs = require('shelljs'); 4 | 5 | const pathname = path.join(process.cwd(), './dist'); 6 | shelljs.rm('-rf', pathname); 7 | shelljs.mkdir('-p', pathname); 8 | 9 | -------------------------------------------------------------------------------- /bin/screenshot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | process.env.DEBUG = 'app:*'; 3 | const debug = require('debug')('app:screenshot'); 4 | const MAX_POOL_SIZE = require('os').cpus().length; 5 | const Nightmare = require('nightmare'); 6 | const _ = require('lodash'); 7 | const commander = require('commander'); 8 | const connect = require('connect'); 9 | const getPort = require('get-port'); 10 | const http = require('http'); 11 | const path = require('path'); 12 | const basename = path.basename; 13 | const extname = path.extname; 14 | const join = path.join; 15 | const queue = require('d3-queue').queue; 16 | const serveStatic = require('serve-static'); 17 | const shelljs = require('shelljs'); 18 | const ls = shelljs.ls; 19 | const mkdir = shelljs.mkdir; 20 | const pkg = require('../package.json'); 21 | 22 | commander 23 | .version(pkg.version) 24 | .option('-p, --port ', 'specify a port number to run on', parseInt) 25 | .option('-n, --name ', 'specify the name for demos') 26 | .parse(process.argv); 27 | 28 | // assets 29 | const src = join(process.cwd(), './demos'); 30 | const dest = join(process.cwd(), './demos/assets/screenshots'); 31 | mkdir('-p', dest); 32 | 33 | const app = connect(); 34 | app.use('/', serveStatic(process.cwd())); 35 | 36 | const DELAY = 10000; 37 | 38 | getPort().then(port => { 39 | http.createServer(app).listen(port); 40 | const url = 'http://127.0.0.1:' + port; 41 | debug('server is ready on port ' + port + '! url: ' + url); 42 | 43 | const q = queue(MAX_POOL_SIZE > 2 ? MAX_POOL_SIZE - 1 : MAX_POOL_SIZE); 44 | const files = ls(src).filter(filename => (extname(filename) === '.html')); 45 | files.forEach(filename => { 46 | const name = basename(filename, '.html'); 47 | if (_.isString(commander.name) && filename.indexOf(commander.name) === -1) { 48 | debug(`>>>>>>>>> skipping because filename not matched: ${name}`); 49 | return; 50 | } 51 | q.defer(callback => { 52 | const t0 = Date.now(); 53 | const nightmare = Nightmare({ 54 | gotoTimeout: 600000, 55 | show: false 56 | }); 57 | const url = `http://127.0.0.1:${port}/demos/${name}.html`; 58 | const target = join(dest, `./${name}.png`); 59 | nightmare.viewport(800, 450) // 16 x 9 60 | .goto(url) 61 | .wait(DELAY) 62 | .screenshot(target, () => { 63 | debug(name + ' took ' + (Date.now() - t0) + ' to take a screenshot.'); 64 | callback(null); 65 | }) 66 | .end() 67 | .catch(e => { 68 | debug(url); 69 | debug(target); 70 | debug(name + ' failed to take a screenshot: ' + e); 71 | }); 72 | }); 73 | }); 74 | q.awaitAll(error => { 75 | if (error) { 76 | debug(error); 77 | process.exit(1); 78 | } 79 | debug('screenshots are all captured!'); 80 | process.exit(); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /bin/win-dev.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const shelljs = require('shelljs'); 3 | const exec = shelljs.exec; 4 | 5 | const childWatch = exec('npm run watch', { 6 | async: true 7 | }); 8 | childWatch.stdout.on('data', data => { 9 | if (data.indexOf('Hash') === 0) { 10 | exec('npm run demos-web', { 11 | async: true 12 | }); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /bundler/assets/index.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: 40px; 3 | } 4 | .block { 5 | border-radius: 8px; 6 | cursor: pointer; 7 | padding-top: 16px; 8 | text-align: center; 9 | background: #f9f9f9; 10 | outline: 1px solid #fff; 11 | } 12 | .block.selected, .block.selected:hover { 13 | background: #0069d9; 14 | color: white; 15 | } 16 | .block:hover { 17 | background: #dfd; 18 | } 19 | 20 | /* loading */ 21 | #loading { 22 | position: absolute; 23 | width: 100%; 24 | height: 100%; 25 | background: white; 26 | top: 0; 27 | opacity: .95; 28 | } 29 | 30 | .sk-cube-grid { 31 | width: 120px; 32 | height: 120px; 33 | margin: calc(40vh) auto 0; 34 | } 35 | 36 | .sk-cube-grid .sk-cube { 37 | width: 33%; 38 | height: 33%; 39 | background-color: #0069d9; 40 | float: left; 41 | -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; 42 | animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; 43 | } 44 | 45 | .sk-cube-grid .sk-cube1 { 46 | -webkit-animation-delay: 0.2s; 47 | animation-delay: 0.2s; 48 | } 49 | 50 | .sk-cube-grid .sk-cube2 { 51 | -webkit-animation-delay: 0.3s; 52 | animation-delay: 0.3s; 53 | } 54 | 55 | .sk-cube-grid .sk-cube3 { 56 | -webkit-animation-delay: 0.4s; 57 | animation-delay: 0.4s; 58 | } 59 | 60 | .sk-cube-grid .sk-cube4 { 61 | -webkit-animation-delay: 0.1s; 62 | animation-delay: 0.1s; 63 | } 64 | 65 | .sk-cube-grid .sk-cube5 { 66 | -webkit-animation-delay: 0.2s; 67 | animation-delay: 0.2s; 68 | } 69 | 70 | .sk-cube-grid .sk-cube6 { 71 | -webkit-animation-delay: 0.3s; 72 | animation-delay: 0.3s; 73 | } 74 | 75 | .sk-cube-grid .sk-cube7 { 76 | -webkit-animation-delay: 0s; 77 | animation-delay: 0s; 78 | } 79 | 80 | .sk-cube-grid .sk-cube8 { 81 | -webkit-animation-delay: 0.1s; 82 | animation-delay: 0.1s; 83 | } 84 | 85 | .sk-cube-grid .sk-cube9 { 86 | -webkit-animation-delay: 0.2s; 87 | animation-delay: 0.2s; 88 | } 89 | 90 | @-webkit-keyframes sk-cubeGridScaleDelay { 91 | 0%, 70%, 100% { 92 | -webkit-transform: scale3D(1, 1, 1); 93 | transform: scale3D(1, 1, 1); 94 | } 95 | 35% { 96 | -webkit-transform: scale3D(0, 0, 1); 97 | transform: scale3D(0, 0, 1); 98 | } 99 | } 100 | 101 | @keyframes sk-cubeGridScaleDelay { 102 | 0%, 70%, 100% { 103 | -webkit-transform: scale3D(1, 1, 1); 104 | transform: scale3D(1, 1, 1); 105 | } 106 | 35% { 107 | -webkit-transform: scale3D(0, 0, 1); 108 | transform: scale3D(0, 0, 1); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /bundler/assets/index.js: -------------------------------------------------------------------------------- 1 | const $loading = $('#loading'); 2 | 3 | $loading.hide(); 4 | 5 | $(document.body).on('click', '.block', function() { 6 | if ($(this).hasClass('selected')) { 7 | $(this).removeClass('selected'); 8 | } else { 9 | $(this).addClass('selected'); 10 | } 11 | }); 12 | 13 | function JSON_to_URLEncoded(element, key, list) { 14 | list = list || []; 15 | if (typeof (element) === 'object') { 16 | for (const idx in element) { 17 | JSON_to_URLEncoded(element[idx], key ? key + '[' + idx + ']' : idx, list); 18 | } 19 | } else { 20 | list.push(key + '=' + encodeURIComponent(element)); 21 | } 22 | return list.join('&'); 23 | } 24 | 25 | $('#select-and-build').on('click', () => { 26 | $loading.show(); 27 | const ids = $.map($('.selected.block'), item => $(item).data('index')); 28 | const xhr = new XMLHttpRequest(); 29 | xhr.open('POST', '/bundle', true); 30 | // xhr.setRequestHeader('Content-type', 'application/json'); 31 | xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 32 | xhr.responseType = 'blob'; 33 | xhr.onreadystatechange = () => { // Call a function when the state changes. 34 | if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { 35 | // Request finished. Do processing here. 36 | $loading.hide(); 37 | window.saveAs(xhr.response, 'data-set-dist.zip'); 38 | } 39 | }; 40 | xhr.send(JSON_to_URLEncoded({ ids })); 41 | // $.ajax({ 42 | // type: 'POST', 43 | // url: '/bundle', 44 | // data: { ids }, 45 | // beforeSend: jqXHR => { 46 | // jqXHR.responseType = 'binary'; 47 | // } 48 | // }).done(data => { 49 | // const binaryData = []; 50 | // binaryData.push(data); 51 | // window.saveAs(new Blob(binaryData, { type: 'application/zip' }), 'g2-dist.zip'); 52 | // }); 53 | }); 54 | 55 | $('#select-all').on('click', () => { 56 | $('.block').addClass('selected'); 57 | }); 58 | $('#cancel-select').on('click', () => { 59 | $('.selected').removeClass('selected'); 60 | }); 61 | -------------------------------------------------------------------------------- /bundler/data/template.js: -------------------------------------------------------------------------------- 1 | module.exports = blocks => ` 2 | ${blocks} 3 | module.exports = require('./data-set'); 4 | `; 5 | -------------------------------------------------------------------------------- /demos/adjacency.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Adjacency Diagram 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /demos/arc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Arc Diagram 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demos/assets/alice-mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antvis/data-set/0f68a94004a30b94005bd3496c2a5f3f2171f56d/demos/assets/alice-mask.png -------------------------------------------------------------------------------- /demos/assets/common.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | overflow: hidden; 3 | } 4 | ::-webkit-scrollbar { 5 | display: none; 6 | } 7 | -------------------------------------------------------------------------------- /demos/assets/file-saver-1.3.8.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 2 | var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})} 3 | -------------------------------------------------------------------------------- /demos/assets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | /*overflow: hidden;*/ 3 | } 4 | ::-webkit-scrollbar { 5 | display: none; 6 | } 7 | 8 | .filter { 9 | padding: 16px; 10 | } 11 | .demo-thumbnails { 12 | padding: 16px; 13 | } 14 | .demo-thumbnail { 15 | margin-bottom: 32px; 16 | } 17 | .thumbnail-container { 18 | overflow: hidden; 19 | max-height: 202px; 20 | } 21 | .thumbnail-container img { 22 | width: 100%; 23 | } 24 | .card-body { 25 | padding: 8px; 26 | } 27 | iframe { 28 | border: none; 29 | } 30 | .scaled-frame { 31 | margin: 0 auto; 32 | width: 800px; 33 | height: 450px; 34 | -webkit-transform: scale(0.45); 35 | -webkit-transform-origin: 0 0; 36 | } 37 | #doc-container { 38 | display: -webkit-box; 39 | display: -webkit-flex; 40 | display: -ms-flexbox; 41 | display: flex; 42 | -webkit-box-flex: 1; 43 | -webkit-flex: 1 1 auto; 44 | -ms-flex: 1 1 auto; 45 | flex: 1 1 auto; 46 | position: fixed; 47 | top: 0; 48 | left: 0; 49 | height: 100%; 50 | width: 100%; 51 | background: white; 52 | } 53 | .code-panel { 54 | -webkit-overflow-scrolling: touch; 55 | -webkit-transform: translateZ(0); 56 | background-color: #fff; 57 | display: block; 58 | width: 400px; 59 | height: 100%; 60 | overflow: hidden; 61 | padding: 0 16px 0 0; 62 | position: relative; 63 | right: 0; 64 | top: 0; 65 | transform: translateZ(0); 66 | white-space: nowrap; 67 | will-change: scroll-position; 68 | z-index: 1; 69 | } 70 | 71 | .code-banner { 72 | padding: .5em 0; 73 | } 74 | 75 | .code-panel, .code-editor .CodeMirror { 76 | height: 100%; 77 | } 78 | 79 | .code-editor { 80 | height: calc(100% - 40px); 81 | } 82 | 83 | #chart-panel { 84 | flex: 1; 85 | overflow: hidden; 86 | padding: 1rem; 87 | -webkit-transform: translateZ(0); 88 | transform: translateZ(0); 89 | will-change: scroll-position; 90 | -webkit-overflow-scrolling: touch; 91 | } 92 | #resize-handler { 93 | border-right: 2px solid #DEDEEB; 94 | cursor: col-resize; 95 | } 96 | .chart-frame { 97 | height: 100%; 98 | width: 100%; 99 | } 100 | 101 | .btn { 102 | cursor: pointer; 103 | } 104 | -------------------------------------------------------------------------------- /demos/assets/lazyload-2.0.0-beta.2.min.js: -------------------------------------------------------------------------------- 1 | /*! Lazy Load 2.0.0-beta.2 - MIT license - Copyright 2007-2017 Mika Tuupola */ 2 | !function(t,e){"function"==typeof define&&define.amd?define([],e(t)):"object"==typeof exports?module.exports=e(t):t.LazyLoad=e(t)}("undefined"!=typeof global?global:this.window||this.global,function(t){"use strict";function e(t,e){this.settings=r(s,e||{}),this.images=t||document.querySelectorAll(this.settings.selector),this.observer=null,this.init()}const s={src:"data-src",srcset:"data-srcset",selector:".lazyload"},r=function(){let t={},e=!1,s=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],s++);for(;s0){e.observer.unobserve(t.target);let s=t.target.getAttribute(e.settings.src),r=t.target.getAttribute(e.settings.srcset);"img"===t.target.tagName.toLowerCase()?(s&&(t.target.src=s),r&&(t.target.srcset=r)):t.target.style.backgroundImage="url("+s+")"}})},s),this.images.forEach(function(t){e.observer.observe(t)})},loadAndDestroy:function(){this.settings&&(this.loadImages(),this.destroy())},loadImages:function(){if(!this.settings)return;let t=this;this.images.forEach(function(e){let s=e.getAttribute(t.settings.src),r=e.getAttribute(t.settings.srcset);"img"===e.tagName.toLowerCase()?(s&&(e.src=s),r&&(e.srcset=r)):e.style.backgroundImage="url("+s+")"})},destroy:function(){this.settings&&(this.observer.disconnect(),this.settings=null)}},t.lazyload=function(t,s){return new e(t,s)},window.jQuery){const t=window.jQuery;t.fn.lazyload=function(s){return s=s||{},s.attribute=s.attribute||"data-src",new e(t.makeArray(this),s),this}}return e}); 3 | -------------------------------------------------------------------------------- /demos/assets/love-mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antvis/data-set/0f68a94004a30b94005bd3496c2a5f3f2171f56d/demos/assets/love-mask.png -------------------------------------------------------------------------------- /demos/assets/routie-0.3.2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * routie - a tiny hash router 3 | * v0.3.2 4 | * http://projects.jga.me/routie 5 | * copyright Greg Allen 2016 6 | * MIT License 7 | */ 8 | var Routie=function(a,b){var c=[],d={},e="routie",f=a[e],g=function(a,b){this.name=b,this.path=a,this.keys=[],this.fns=[],this.params={},this.regex=h(this.path,this.keys,!1,!1)};g.prototype.addHandler=function(a){this.fns.push(a)},g.prototype.removeHandler=function(a){for(var b=0,c=this.fns.length;c>b;b++){var d=this.fns[b];if(a==d)return void this.fns.splice(b,1)}},g.prototype.run=function(a){for(var b=0,c=this.fns.length;c>b;b++)this.fns[b].apply(this,a)},g.prototype.match=function(a,b){var c=this.regex.exec(a);if(!c)return!1;for(var d=1,e=c.length;e>d;++d){var f=this.keys[d-1],g="string"==typeof c[d]?decodeURIComponent(c[d]):c[d];f&&(this.params[f.name]=g),b.push(g)}return!0},g.prototype.toURL=function(a){var b=this.path;for(var c in a)b=b.replace("/:"+c,"/"+a[c]);if(b=b.replace(/\/:.*\?/g,"/").replace(/\?/g,""),-1!=b.indexOf(":"))throw new Error("missing parameters for url: "+b);return b};var h=function(a,b,c,d){return a instanceof RegExp?a:(a instanceof Array&&(a="("+a.join("|")+")"),a=a.concat(d?"":"/?").replace(/\/\(/g,"(?:/").replace(/\+/g,"__plus__").replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,function(a,c,d,e,f,g){return b.push({name:e,optional:!!g}),c=c||"",""+(g?"":c)+"(?:"+(g?c:"")+(d||"")+(f||d&&"([^/.]+?)"||"([^/]+?)")+")"+(g||"")}).replace(/([\/.])/g,"\\$1").replace(/__plus__/g,"(.+)").replace(/\*/g,"(.*)"),new RegExp("^"+a+"$",c?"":"i"))},i=function(a,b){var e=a.split(" "),f=2==e.length?e[0]:null;a=2==e.length?e[1]:e[0],d[a]||(d[a]=new g(a,f),c.push(d[a])),d[a].addHandler(b)},j=function(a,b){if("function"==typeof b)i(a,b),j.reload();else if("object"==typeof a){for(var c in a)i(c,a[c]);j.reload()}else"undefined"==typeof b&&j.navigate(a)};j.lookup=function(a,b){for(var d=0,e=c.length;e>d;d++){var f=c[d];if(f.name==a)return f.toURL(b)}},j.remove=function(a,b){var c=d[a];c&&c.removeHandler(b)},j.removeAll=function(){d={},c=[]},j.navigate=function(a,b){b=b||{};var c=b.silent||!1;c&&o(),setTimeout(function(){window.location.hash=a,c&&setTimeout(function(){n()},1)},1)},j.noConflict=function(){return a[e]=f,j};var k=function(){return window.location.hash.substring(1)},l=function(a,b){var c=[];return b.match(a,c)?(b.run(c),!0):!1},m=j.reload=function(){for(var a=k(),b=0,d=c.length;d>b;b++){var e=c[b];if(l(a,e))return}},n=function(){a.addEventListener?a.addEventListener("hashchange",m,!1):a.attachEvent("onhashchange",m)},o=function(){a.removeEventListener?a.removeEventListener("hashchange",m):a.detachEvent("onhashchange",m)};return n(),b?j:void(a[e]=j)};"undefined"==typeof module?Routie(window):module.exports=Routie(window,!0); -------------------------------------------------------------------------------- /demos/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Bar 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /demos/chord.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Chord Diagram 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /demos/circle-packing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tree 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /demos/dagre.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Sankey Diagram 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /demos/dendrogram.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dendrogram 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /demos/folding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | folding transform 6 | 7 | 8 | 9 |
10 | 11 | 12 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /demos/hexagon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hexagon 10 | 11 | 12 | 13 |
14 | 15 | 16 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /demos/hexjson.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Geo Projections 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /demos/histogram-stack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Histogram 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /demos/histogram.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Histogram 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /demos/index.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Demos 12 | 13 | 14 | 19 |
20 |
21 | 22 |
23 |
24 | {% for file in demoFiles %} 25 |
26 |
27 | 28 | 29 | 30 |
31 |
{{ file.basename }}
32 |
33 |
34 |
35 | {% endfor %} 36 |
37 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /demos/kernel-smooth-density.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | kernel smooth transform: density 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demos/kernel-smooth-regression-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | kernel smooth transform: regression for one field 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /demos/kernel-smooth-regression-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | kernel smooth transform: regression for two fields 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /demos/partition.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Partition 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /demos/periodic-table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Geo Projections 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /demos/quantile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Quantile Binning 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /demos/rectangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Rectangle Binning 10 | 11 | 12 | 13 |
14 | 15 | 16 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /demos/regression.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Regression 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /demos/sankey.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Sankey Diagram 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /demos/tree-layout-compact-box.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tree 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /demos/tree-layout-dendrogram.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tree 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /demos/tree-layout-indented.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tree 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /demos/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tree 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /docs/connector.md: -------------------------------------------------------------------------------- 1 | # Connector 数据接入 2 | 3 | 一个数据视图(DataSet.DataView)实例在接入数据时就会用到 Connector,其语法如下: 4 | 5 | ```javascript 6 | dv.source(data, { 7 | type: `${connectorName}`, 8 | ...otherOptions, 9 | }); 10 | ``` 11 | 12 | 举个例子: 13 | 14 | ```javascript 15 | const testCSV = `Expt,Run,Speed 16 | 1,1,850 17 | 1,2,740 18 | 1,3,900 19 | 1,4,1070`; 20 | 21 | const dv = new DataSet.DataView().source(testCSV, { 22 | type: 'csv', 23 | }); 24 | 25 | console.log(dv.rows); 26 | /* 27 | * dv.rows: 28 | * [ 29 | * {Expt: " 1", Run: "1", Speed: "850"} 30 | * {Expt: " 1", Run: "2", Speed: "740"} 31 | * {Expt: " 1", Run: "3", Speed: "900"} 32 | * {Expt: " 1", Run: "4", Speed: "1070"} 33 | * ] 34 | */ 35 | ``` 36 | 37 | 上述代码中,数据视图实例 `dv` 使用 `csv` 类型的 Connector 载入了一段 CSV 文本。 38 | 39 | 目前 DataSet 支持以下几种常用的 Connector: 40 | 41 | ## 默认 42 | 43 | 直接调用 `dv.source(data)`,不通过配置项指定使用的 Connector 时,则有以下两种默认的情形: 44 | 45 | 第一种,data 传入的是具体数组数据,那么 46 | 47 | ```javascript 48 | dv.rows = deepClone(data); 49 | ``` 50 | 51 | 第二种,data 传入的是另一个 DataView 的实例或者实例的名称,那么 52 | 53 | ```javascript 54 | dv.rows = deepClone(ds.getView(otherDv).rows); 55 | ``` 56 | 57 | ## dsv 58 | 59 | 具体用法见示例: 60 | 61 | ```javascript 62 | dv.source(dsvStr, { 63 | type: 'dsv', // 指定使用dsv connector 64 | delimiter: '|', // 指定分隔符 65 | }); 66 | ``` 67 | 68 | ## csv 69 | 70 | 具体用法见示例: 71 | 72 | ```javascript 73 | dv.source(csvStr, { 74 | type: 'csv', // 指定使用dsv connector 75 | delimiter: ',', // 指定分隔符 76 | }); 77 | ``` 78 | 79 | ## tsv 80 | 81 | 具体用法见示例: 82 | 83 | ```javascript 84 | dv.source(tsvStr, { 85 | type: 'tsv', // 指定使用tsv connector 86 | }); 87 | ``` 88 | 89 | ## GeoJSON 90 | 91 | 具体用法见示例: 92 | 93 | ```javascript 94 | dv.source(geojsonData, { 95 | type: 'GeoJSON', // 别名 geo / geojson 96 | }); 97 | ``` 98 | 99 | > dv.dataType 会被更改为 'geo',从而 dv 可以执行一些 Geo 相关的实例方法。 100 | 101 | ## TopoJSON 102 | 103 | 具体用法见示例: 104 | 105 | ```javascript 106 | dv.source(topojsonData, { 107 | type: 'TopoJSON', // 别名 topojson 108 | object: 'xxx', // TopoJSON 相当于多个 GeoJSON 合并起来做了压缩,其中每一个 object 都相当于一份 GeoJSON 数据,指定 object 就是从中提取一份 Geo 数据 109 | }); 110 | ``` 111 | 112 | > dv.dataType 会被更改为 'geo',从而 dv 可以执行一些 Geo 相关的实例方法。 113 | 114 | ## hierarchy 115 | 116 | 具体用法见示例: 117 | 118 | ```javascript 119 | dv.source(tree, { 120 | type: 'hierarchy', // 别名 tree 121 | children: (d) => d.children, // 可选,函数,返回子树 122 | }); 123 | ``` 124 | 125 | > dv.dataType 会被变更为 'hierarchy' ,从而 dv 可以执行一些树形结构相关的实例方法和 Transform。 126 | 127 | > dv.root 为根节点 128 | 129 | ## graph 130 | 131 | 具体用法见示例: 132 | 133 | ```javascript 134 | dv.source(graph, { 135 | type: 'graph', 136 | nodes: (d) => d.nodes, // 节点集对应字段 137 | edges: (d) => d.edges, // 边集对应字段 138 | }); 139 | ``` 140 | 141 | > dv.dataType 会被变更为 'graph',从而 dv 可以执行图相关的实例方法和 Transform。 142 | -------------------------------------------------------------------------------- /src/api/hierarchy.ts: -------------------------------------------------------------------------------- 1 | import { assign } from '@antv/util'; 2 | import { View } from '../view'; 3 | 4 | assign(View.prototype, { 5 | getAllNodes(this: View) { 6 | const nodes: any[] = []; 7 | const root = this.root; 8 | if (root && root.each) { 9 | // d3-hierarchy 10 | root.each((node: any) => { 11 | nodes.push(node); 12 | }); 13 | } else if (root && root.eachNode) { 14 | // @antv/hierarchy 15 | root.eachNode((node: any) => { 16 | nodes.push(node); 17 | }); 18 | } 19 | return nodes; 20 | }, 21 | getAllLinks(this: View) { 22 | const links: any[] = []; 23 | const nodes: any[] = [this.root]; 24 | let node: any; 25 | while ((node = nodes.pop())) { 26 | const children = node.children; 27 | if (children) { 28 | children.forEach((child: any) => { 29 | links.push({ 30 | source: node, 31 | target: child, 32 | }); 33 | nodes.push(child); 34 | }); 35 | } 36 | } 37 | return links; 38 | }, 39 | }); 40 | 41 | assign(View.prototype, { 42 | getAllEdges: View.prototype.getAllLinks, 43 | }); 44 | 45 | export interface HierarchyApi { 46 | root?: any; 47 | getAllNodes(): any[]; 48 | getAllLinks(): any[]; 49 | getAllEdges(): any[]; 50 | } 51 | -------------------------------------------------------------------------------- /src/api/partition.ts: -------------------------------------------------------------------------------- 1 | import { assign, values } from '@antv/util'; 2 | import partition from '../util/partition'; 3 | import { View } from '../view'; 4 | 5 | assign(View.prototype, { 6 | partition( 7 | this: View, 8 | group_by: string | string[] | ((item: any) => string), 9 | order_by: string | string[] | ((item: any) => number) = [] 10 | ) { 11 | return partition(this.rows, group_by, order_by); 12 | }, 13 | group( 14 | this: View, 15 | group_by: string | string[] | ((item: any) => string), 16 | order_by: string | string[] | ((item: any) => number) = [] 17 | ) { 18 | const groups = this.partition(group_by, order_by); 19 | return values(groups); 20 | }, 21 | groups( 22 | this: View, 23 | group_by: string | string[] | ((item: any) => string), 24 | order_by: string | string[] | ((item: any) => number) = [] 25 | ) { 26 | return this.group(group_by, order_by); 27 | }, 28 | }); 29 | 30 | export interface PartitionApi { 31 | partition( 32 | group_by: string | string[] | ((item: any) => string), 33 | order_by?: string | string[] | ((item: any) => number) 34 | ): any; 35 | group( 36 | group_by: string | string[] | ((item: any) => string), 37 | order_by?: string | string[] | ((item: any) => number) 38 | ): any; 39 | groups( 40 | group_by: string | string[] | ((item: any) => string), 41 | order_by?: string | string[] | ((item: any) => number) 42 | ): any; 43 | } 44 | -------------------------------------------------------------------------------- /src/api/statistics.ts: -------------------------------------------------------------------------------- 1 | import * as simpleStatistics from 'simple-statistics'; 2 | import { assign, flattenDeep, isArray } from '@antv/util'; 3 | import { View } from '../view'; 4 | import pByFraction from '../util/p-by-fraction'; 5 | import constants from '../constants'; 6 | 7 | const { STATISTICS_METHODS } = constants; 8 | 9 | function getColumnValues(view: View, column: string): any[] { 10 | let values = view.getColumn(column); 11 | if (isArray(values) && isArray(values[0])) { 12 | values = flattenDeep(values); 13 | } 14 | return values; 15 | } 16 | 17 | // statistics 18 | STATISTICS_METHODS.forEach((method) => { 19 | // @ts-ignore; 20 | View.prototype[method] = function(column: string) { 21 | // @ts-ignore 22 | return simpleStatistics[method](getColumnValues(this, column)); 23 | }; 24 | }); 25 | 26 | const { quantile } = simpleStatistics; 27 | 28 | assign(View.prototype, { 29 | average: View.prototype.mean, 30 | quantile(column: string, p: number) { 31 | return quantile(getColumnValues(this, column), p); 32 | }, 33 | quantiles(this: View, column: string, pArr: number[]) { 34 | const columnArr = getColumnValues(this, column); 35 | return pArr.map((p) => quantile(columnArr, p)); 36 | }, 37 | quantilesByFraction(column: string, fraction: number) { 38 | return this.quantiles(column, pByFraction(fraction)); 39 | }, 40 | range(column: string) { 41 | return [this.min(column), this.max(column)]; 42 | }, 43 | extent(column: string) { 44 | // alias 45 | return this.range(column); 46 | }, 47 | }); 48 | 49 | export interface StatisticsApi { 50 | max(column: string): number; 51 | mean(column: string): number; // alias: average 52 | median(column: string): number; 53 | min(column: string): number; 54 | mode(column: string): number; 55 | product(column: string): number; 56 | standardDeviation(column: string): number; 57 | sum(column: string): number; 58 | sumSimple(column: string): number; 59 | variance(column: string): number; 60 | average(column: string): number; 61 | extent(column: string): number; 62 | range(column: string): [number, number]; 63 | quantilesByFraction(column: string, fraction: number): number; 64 | quantiles(column: string, percents: number[]): number[]; 65 | quantile(column: string, percent: number): number; 66 | } 67 | -------------------------------------------------------------------------------- /src/connector-params.ts: -------------------------------------------------------------------------------- 1 | import { Options as GraphOptions } from './connector/graph'; 2 | import { Options as DsvOptions } from './connector/dsv'; 3 | import { Options as HexjsonOptions } from './connector/hexjson'; 4 | import { Options as HierarchyOptions } from './connector/hierarchy'; 5 | import { Options as TopojsonOptions } from './connector/topojson'; 6 | 7 | export interface ConnectorParams { 8 | csv: [string, {}]; 9 | tsv: [string, {}]; 10 | dsv: [string, DsvOptions]; 11 | graph: [any, GraphOptions]; 12 | diagram: [any, GraphOptions]; 13 | hex: [any[], HexjsonOptions]; 14 | hexjson: [any[], HexjsonOptions]; 15 | 'hex-json': [any[], HexjsonOptions]; 16 | HexJSON: [any[], HexjsonOptions]; 17 | geo: [any, {}]; 18 | geojson: [any, {}]; 19 | GeoJSON: [any, {}]; 20 | hierarchy: [any, HierarchyOptions]; 21 | tree: [any, HierarchyOptions]; 22 | topojson: [any, TopojsonOptions]; 23 | TopoJSON: [any, TopojsonOptions]; 24 | } 25 | -------------------------------------------------------------------------------- /src/connector/default.ts: -------------------------------------------------------------------------------- 1 | import { isString, deepMix } from '@antv/util'; 2 | import { DataSet } from '../data-set'; 3 | import { View } from '../view'; 4 | 5 | DataSet.registerConnector('default', (data: string | View, dataSet: DataSet) => { 6 | let view: View | undefined; 7 | 8 | if (isString(data)) { 9 | view = dataSet.getView(data); 10 | } else { 11 | view = data; 12 | } 13 | 14 | if (!view) { 15 | throw new TypeError('Invalid dataView'); 16 | } 17 | return deepMix([], view.rows); 18 | }); 19 | -------------------------------------------------------------------------------- /src/connector/dsv.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '@antv/util'; 2 | import { dsvFormat, csvParse, tsvParse } from 'd3-dsv'; 3 | import { DataSet } from '../data-set'; 4 | 5 | export interface Options { 6 | delimiter?: string; 7 | } 8 | 9 | DataSet.registerConnector('dsv', (str: string, options: Options = {}) => { 10 | const delimiter = options.delimiter || ','; 11 | if (!isString(delimiter)) { 12 | throw new TypeError('Invalid delimiter: must be a string!'); 13 | } 14 | return dsvFormat(delimiter).parse(str); 15 | }); 16 | 17 | DataSet.registerConnector('csv', (str: string) => { 18 | return csvParse(str); 19 | }); 20 | 21 | DataSet.registerConnector('tsv', (str: string) => { 22 | return tsvParse(str); 23 | }); 24 | -------------------------------------------------------------------------------- /src/connector/geo-graticule.ts: -------------------------------------------------------------------------------- 1 | import { geoGraticule } from 'd3-geo'; 2 | import { DataSet } from '../data-set'; 3 | import { View } from '../view'; 4 | 5 | export default function connector(_options: any, dataView: View): any { 6 | dataView.dataType = 'geo-graticule'; 7 | const data: any[] = geoGraticule().lines(); 8 | 9 | data.map((row, index) => { 10 | row.index = `${index}`; 11 | return row; 12 | }); 13 | 14 | dataView.rows = data; 15 | return data; 16 | } 17 | 18 | DataSet.registerConnector('geo-graticule', connector); 19 | -------------------------------------------------------------------------------- /src/connector/geojson.ts: -------------------------------------------------------------------------------- 1 | import getPointAtLength from 'point-at-length'; 2 | import { deepMix } from '@antv/util'; 3 | import { geoPath } from 'd3-geo'; 4 | import { DataSet } from '../data-set'; 5 | import { View } from '../view'; 6 | 7 | const geoPathGenerator = geoPath(); 8 | 9 | function GeoJSONConnector(data: any, _options: undefined, dataView: View): any { 10 | dataView.dataType = DataSet.CONSTANTS.GEO; 11 | const features: any[] = deepMix([], data.features); 12 | 13 | // pre-process 14 | features.forEach((feature) => { 15 | feature.name = feature.properties.name; 16 | feature.longitude = []; 17 | feature.latitude = []; 18 | const pathData = (feature.pathData = geoPathGenerator(feature)); 19 | const points = getPointAtLength(pathData); 20 | points._path.forEach((point: any[]) => { 21 | feature.longitude.push(point[1]); 22 | feature.latitude.push(point[2]); 23 | }); 24 | const centroid = geoPathGenerator.centroid(feature); 25 | feature.centroidX = centroid[0]; 26 | feature.centroidY = centroid[1]; 27 | }); 28 | 29 | // dataView.origin = features; 30 | return features; 31 | } 32 | 33 | DataSet.registerConnector('geo', GeoJSONConnector); 34 | DataSet.registerConnector('geojson', GeoJSONConnector); 35 | DataSet.registerConnector('GeoJSON', GeoJSONConnector); 36 | 37 | export default GeoJSONConnector; 38 | -------------------------------------------------------------------------------- /src/connector/graph.ts: -------------------------------------------------------------------------------- 1 | import { assign, isFunction } from '@antv/util'; 2 | import { DataSet } from '../data-set'; 3 | import { View } from '../view'; 4 | 5 | const DEFAULT_OPTIONS: Options = { 6 | nodes(d: any) { 7 | // optional 8 | return d.nodes; 9 | }, 10 | edges(d: any) { 11 | // optional 12 | return d.edges; 13 | }, 14 | }; 15 | 16 | export interface Options { 17 | nodes?(data: any): any[]; 18 | edges?(data: any): any[]; 19 | } 20 | 21 | function connector(data: any, options: Options, dataView: View): any { 22 | options = assign({}, DEFAULT_OPTIONS, options); 23 | dataView.dataType = DataSet.CONSTANTS.GRAPH; 24 | const { nodes, edges } = options; 25 | if (nodes && !isFunction(nodes)) { 26 | throw new TypeError('Invalid nodes: must be a function!'); 27 | } 28 | if (edges && !isFunction(edges)) { 29 | throw new TypeError('Invalid edges: must be a function!'); 30 | } 31 | // @ts-ignore 32 | dataView.rows = dataView.graph = { 33 | nodes: nodes!(data), 34 | edges: edges!(data), 35 | }; 36 | assign(dataView, dataView.graph); 37 | return dataView.rows; 38 | } 39 | 40 | DataSet.registerConnector('graph', connector); 41 | DataSet.registerConnector('diagram', connector); 42 | -------------------------------------------------------------------------------- /src/connector/hexjson.ts: -------------------------------------------------------------------------------- 1 | import { deepMix, assign } from '@antv/util'; 2 | import { getGridForHexJSON, renderHexJSON } from 'd3-hexjson'; 3 | import { DataSet } from '../data-set'; 4 | import { View } from '../view'; 5 | 6 | const DEFAULT_OPTIONS: Options = { 7 | width: 1, 8 | height: 1, 9 | }; 10 | 11 | export interface Options { 12 | width?: number; 13 | height?: number; 14 | } 15 | 16 | function processRow(row) { 17 | row.cx = row.x; 18 | row.cy = row.y; 19 | row.x = []; 20 | row.y = []; 21 | row.vertices.forEach((v) => { 22 | row.x.push(v.x + row.cx); 23 | row.y.push(v.y + row.cy); 24 | }); 25 | return row; 26 | } 27 | 28 | function HexJSONConnector(data: any[], options: Options, dataView: View): any { 29 | dataView.dataType = DataSet.CONSTANTS.HEX; 30 | options = assign({} as Options, DEFAULT_OPTIONS, options); 31 | const { width, height } = options; 32 | const HexJSON = deepMix([], data); 33 | dataView._HexJSON = HexJSON; 34 | const grid = (dataView._GridHexJSON = getGridForHexJSON(HexJSON)); 35 | const rows = (dataView.rows = renderHexJSON(HexJSON, width, height).map(processRow)); 36 | dataView._gridRows = renderHexJSON(grid, width, height).map(processRow); 37 | return rows; 38 | } 39 | 40 | DataSet.registerConnector('hex', HexJSONConnector); 41 | DataSet.registerConnector('hexjson', HexJSONConnector); 42 | DataSet.registerConnector('hex-json', HexJSONConnector); 43 | DataSet.registerConnector('HexJSON', HexJSONConnector); 44 | 45 | export default HexJSONConnector; 46 | -------------------------------------------------------------------------------- /src/connector/hierarchy.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '@antv/util'; 2 | import { hierarchy } from 'd3-hierarchy'; 3 | import { DataSet } from '../data-set'; 4 | import { View } from '../view'; 5 | 6 | /* 7 | * options: { 8 | * children(d) { // optional 9 | * return d.children 10 | * }, 11 | * } 12 | */ 13 | 14 | export interface Options { 15 | children?(data: any): any[]; 16 | pureData?: boolean; 17 | } 18 | 19 | function connector(data: any, options: Options, dataView: View): any { 20 | dataView.dataType = DataSet.CONSTANTS.HIERARCHY; 21 | const children = options && options.children ? options.children : null; 22 | 23 | if (children && !isFunction(children)) { 24 | throw new TypeError('Invalid children: must be a function!'); 25 | } 26 | 27 | if (!options.pureData) { 28 | // @ts-ignore 29 | dataView.rows = dataView.root = hierarchy(data, children); 30 | } else { 31 | dataView.rows = dataView.root = data; 32 | } 33 | return data; 34 | } 35 | 36 | DataSet.registerConnector('hierarchy', connector); 37 | DataSet.registerConnector('tree', connector); 38 | -------------------------------------------------------------------------------- /src/connector/topojson.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '@antv/util'; 2 | import { feature } from 'topojson-client'; 3 | import GeoJSONConnector from './geojson'; 4 | import { DataSet } from '../data-set'; 5 | import { View } from '../view'; 6 | import { Topology } from 'topojson-specification'; 7 | 8 | export interface Options { 9 | object: string; 10 | } 11 | 12 | function TopoJSONConnector(data: Topology, options: Options, dataView: View): any { 13 | const object = options.object; 14 | if (!isString(object)) { 15 | throw new TypeError('Invalid object: must be a string!'); 16 | } 17 | const geoData = feature(data, data.objects[object]); 18 | return GeoJSONConnector(geoData, undefined, dataView); 19 | } 20 | 21 | DataSet.registerConnector('topojson', TopoJSONConnector); 22 | DataSet.registerConnector('TopoJSON', TopoJSONConnector); 23 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | HIERARCHY: 'hierarchy', 3 | GEO: 'geo', 4 | HEX: 'hex', 5 | GRAPH: 'graph', 6 | TABLE: 'table', 7 | GEO_GRATICULE: 'geo-graticule', 8 | STATISTICS_METHODS: [ 9 | 'max', 10 | 'mean', // alias: average 11 | 'median', 12 | 'min', 13 | 'mode', 14 | 'product', 15 | 'standardDeviation', 16 | 'sum', 17 | 'sumSimple', 18 | 'variance', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // extra APIs 2 | import './api/geo'; 3 | import './api/hierarchy'; 4 | import './api/partition'; 5 | import './api/statistics'; 6 | 7 | // connectors 8 | import './connector/default'; 9 | import './connector/dsv'; 10 | import './connector/geo-graticule'; 11 | import './connector/geojson'; 12 | import './connector/graph'; 13 | import './connector/hexjson'; 14 | import './connector/hierarchy'; 15 | import './connector/topojson'; 16 | 17 | // transforms 18 | // static 19 | import './transform/default'; 20 | import './transform/filter'; 21 | import './transform/fold'; 22 | import './transform/map'; 23 | import './transform/partition'; 24 | import './transform/percent'; 25 | import './transform/pick'; 26 | import './transform/proportion'; 27 | import './transform/rename'; 28 | import './transform/reverse'; 29 | import './transform/sort'; 30 | import './transform/sort-by'; 31 | import './transform/subset'; 32 | // imputation 33 | import './transform/fill-rows'; 34 | import './transform/impute'; 35 | // statistics 36 | import './transform/aggregate'; 37 | // regression 38 | import './transform/regression'; 39 | // KDE 40 | import './transform/kde'; 41 | // binning 42 | import './transform/bin/hexagon'; 43 | import './transform/bin/histogram'; 44 | import './transform/bin/quantile'; 45 | import './transform/bin/rectangle'; 46 | // geo 47 | import './transform/geo/centroid'; 48 | import './transform/geo/projection'; 49 | import './transform/geo/region'; 50 | // diagram 51 | import './transform/diagram/arc'; 52 | import './transform/diagram/dagre'; 53 | import './transform/diagram/sankey'; 54 | import './transform/diagram/voronoi'; 55 | // hierarchy 56 | import './transform/hierarchy/cluster'; 57 | import './transform/hierarchy/compact-box'; 58 | import './transform/hierarchy/dendrogram'; 59 | import './transform/hierarchy/indented'; 60 | import './transform/hierarchy/pack'; 61 | import './transform/hierarchy/partition'; 62 | import './transform/hierarchy/tree'; 63 | import './transform/hierarchy/treemap'; 64 | // tag cloud 65 | import './transform/tag-cloud'; 66 | // waffle 67 | import './transform/waffle'; 68 | // kernel smoothing 69 | import './transform/kernel-smooth/density'; 70 | import './transform/kernel-smooth/regression'; 71 | 72 | import { DataSet } from './data-set'; 73 | 74 | export = DataSet; 75 | -------------------------------------------------------------------------------- /src/moudles.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'd3-geo-projection'; 2 | declare module 'point-at-length'; 3 | declare module '@antv/hierarchy'; 4 | declare module 'd3-composite-projections'; 5 | declare module 'd3-hexjson'; 6 | -------------------------------------------------------------------------------- /src/transform/bin/quantile.ts: -------------------------------------------------------------------------------- 1 | import { assign, forIn, isArray, isString } from '@antv/util'; 2 | import { quantile } from 'simple-statistics'; 3 | import partition from '../../util/partition'; 4 | import pByFraction from '../../util/p-by-fraction'; 5 | import { DataSet } from '../../data-set'; 6 | const { registerTransform } = DataSet; 7 | import { getField } from '../../util/option-parser'; 8 | import { View } from '../../view'; 9 | 10 | const DEFAULT_OPTIONS: Partial = { 11 | as: '_bin', 12 | groupBy: [], // optional 13 | fraction: 4, // default 14 | // p: [0.5, 0.3], // array of p parameter 15 | // field: 'y', // required 16 | }; 17 | export interface Options { 18 | as?: string; 19 | groupBy?: string[]; 20 | fraction?: number; 21 | p?: number[]; 22 | field: string; 23 | } 24 | 25 | function transform(dataView: View, options: Options): void { 26 | options = assign({} as Options, DEFAULT_OPTIONS, options); 27 | const field = getField(options); 28 | const as = options.as; 29 | if (!isString(as)) { 30 | throw new TypeError('Invalid as: it must be a string (e.g. "_bin")!'); 31 | } 32 | let pArray = options.p; 33 | const fraction = options.fraction; 34 | if (!isArray(pArray) || pArray.length === 0) { 35 | pArray = pByFraction(fraction); 36 | } 37 | const rows = dataView.rows; 38 | const groupBy = options.groupBy; 39 | const groups: Record = partition(rows, groupBy); 40 | const result: any[] = []; 41 | forIn(groups, (group: any[]) => { 42 | // const resultRow = pick(group[0], groupBy); 43 | const resultRow = group[0]; 44 | const binningColumn = group.map((row) => row[field]); 45 | const quantiles = pArray.map((p) => quantile(binningColumn, p)); 46 | resultRow[as] = quantiles; 47 | result.push(resultRow); 48 | }); 49 | dataView.rows = result; 50 | } 51 | 52 | registerTransform('bin.quantile', transform); 53 | -------------------------------------------------------------------------------- /src/transform/default.ts: -------------------------------------------------------------------------------- 1 | import { DataSet } from '../data-set'; 2 | import { View } from '../view'; 3 | 4 | DataSet.registerTransform('default', (dataView: View) => { 5 | return dataView; 6 | }); 7 | -------------------------------------------------------------------------------- /src/transform/diagram/dagre.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * for DAG 3 | * graph data required (nodes, edges) 4 | */ 5 | import { assign } from '@antv/util'; 6 | import dagre from 'dagre'; 7 | import { DataSet } from '../../data-set'; 8 | import { View } from '../../view'; 9 | 10 | const DEFAULT_OPTIONS = { 11 | // nodeId: node => node.index, 12 | rankdir: 'TB', 13 | align: 'TB', 14 | nodesep: 50, 15 | edgesep: 10, 16 | ranksep: 50, 17 | source: (edge) => edge.source, 18 | target: (edge) => edge.target, 19 | }; 20 | 21 | function transform(dv: View, options) { 22 | options = assign({}, DEFAULT_OPTIONS, options); 23 | const g = new dagre.graphlib.Graph(); 24 | // Set an object for the graph label 25 | g.setGraph({}); 26 | // Default to assigning a new object as a label for each new edge. 27 | g.setDefaultEdgeLabel(function() { 28 | return {}; 29 | }); 30 | 31 | dv.nodes.forEach((node) => { 32 | const nodeId = options.nodeId ? options.nodeId(node) : node.id; 33 | if (!node.height && !node.width) { 34 | node.height = node.width = options.edgesep; 35 | } 36 | g.setNode(nodeId, node); 37 | }); 38 | dv.edges.forEach((edge) => { 39 | g.setEdge(options.source(edge), options.target(edge)); 40 | }); 41 | dagre.layout(g); 42 | 43 | const nodes = []; 44 | const edges = []; 45 | 46 | g.nodes().forEach((node) => { 47 | const n = g.node(node); 48 | const { x, y, height, width } = n; 49 | /* points 50 | * 3---2 51 | * | | 52 | * 0---1 53 | */ 54 | // @ts-ignore 55 | n.x = [x - width / 2, x + width / 2, x + width / 2, x - width / 2]; 56 | // @ts-ignore 57 | n.y = [y + height / 2, y + height / 2, y - height / 2, y - height / 2]; 58 | nodes.push(n); 59 | }); 60 | 61 | g.edges().forEach((edge) => { 62 | const { points } = g.edge(edge); 63 | const e: any = {}; 64 | e.x = points.map((p) => p.x); 65 | e.y = points.map((p) => p.y); 66 | edges.push(e); 67 | }); 68 | 69 | dv.nodes = nodes; 70 | dv.edges = edges; 71 | } 72 | 73 | DataSet.registerTransform('diagram.dagre', transform); 74 | DataSet.registerTransform('dagre', transform); 75 | -------------------------------------------------------------------------------- /src/transform/diagram/voronoi.ts: -------------------------------------------------------------------------------- 1 | import * as d3Voronoi from 'd3-voronoi'; 2 | import { assign, isArray } from '@antv/util'; 3 | import { DataSet } from '../../data-set'; 4 | const { registerTransform } = DataSet; 5 | import { getFields } from '../../util/option-parser'; 6 | import { View } from '../../view'; 7 | 8 | const DEFAULT_OPTIONS: Partial = { 9 | // fields: [ 'x', 'y' ] // field x and field y, required 10 | // extend: [[x0, y0], [x1, y1]], // optional 11 | // size: [width, height], // optional 12 | as: ['_x', '_y'], 13 | }; 14 | 15 | export interface Options { 16 | fields: [string, string]; 17 | extend?: [[number, number], [number, number]]; 18 | size?: [number, number]; 19 | as?: [string, string]; 20 | } 21 | 22 | function transform(dataView: View, options: Options): void { 23 | options = assign({} as Options, DEFAULT_OPTIONS, options); 24 | 25 | const as = options.as; 26 | if (!isArray(as) || as.length !== 2) { 27 | throw new TypeError('Invalid as: must be an array with two strings!'); 28 | } 29 | const xField = as[0]; 30 | const yField = as[1]; 31 | 32 | const fields = getFields(options); 33 | if (!isArray(fields) || fields.length !== 2) { 34 | throw new TypeError('Invalid fields: must be an array with two strings!'); 35 | } 36 | const x = fields[0]; 37 | const y = fields[1]; 38 | 39 | const rows = dataView.rows; 40 | const data: [number, number][] = rows.map((row) => [row[x], row[y]]); 41 | const voronoi = d3Voronoi.voronoi(); 42 | if (options.extend) { 43 | voronoi.extent(options.extend); 44 | } 45 | if (options.size) { 46 | voronoi.size(options.size); 47 | } 48 | const polygons = voronoi(data).polygons(); 49 | rows.forEach((row, i) => { 50 | const polygon = polygons[i].filter((point) => !!point); // some points are null 51 | row[xField] = polygon.map((point) => point[0]); 52 | row[yField] = polygon.map((point) => point[1]); 53 | }); 54 | } 55 | 56 | registerTransform('diagram.voronoi', transform); 57 | registerTransform('voronoi', transform); 58 | -------------------------------------------------------------------------------- /src/transform/filter.ts: -------------------------------------------------------------------------------- 1 | import { DataSet } from '../data-set'; 2 | import { View } from '../view'; 3 | 4 | function defaultCallback(row: any): boolean { 5 | return !!row; 6 | } 7 | 8 | export interface Options { 9 | callback?(item: any): boolean; 10 | } 11 | 12 | DataSet.registerTransform('filter', (dataView: View, options: Options) => { 13 | dataView.rows = dataView.rows.filter(options.callback || defaultCallback); 14 | }); 15 | -------------------------------------------------------------------------------- /src/transform/fold.ts: -------------------------------------------------------------------------------- 1 | import { assign, difference, pick } from '@antv/util'; 2 | import { DataSet } from '../data-set'; 3 | import { getFields } from '../util/option-parser'; 4 | import { View } from '../view'; 5 | 6 | const DEFAULT_OPTIONS: Partial = { 7 | fields: [], 8 | key: 'key', 9 | retains: [], 10 | value: 'value', 11 | }; 12 | 13 | export interface Options { 14 | key?: string; 15 | value?: string; 16 | fields?: string[]; 17 | retains?: string[]; 18 | } 19 | 20 | DataSet.registerTransform('fold', (dataView: View, options: Options) => { 21 | const columns = dataView.getColumnNames(); 22 | options = assign({} as Options, DEFAULT_OPTIONS, options); 23 | let fields = getFields(options); 24 | if (fields.length === 0) { 25 | console.warn('warning: option fields is not specified, will fold all columns.'); 26 | fields = columns; 27 | } 28 | const key = options.key; 29 | const value = options.value; 30 | let retains = options.retains; 31 | if (!retains || retains.length === 0) { 32 | retains = difference(columns, fields); 33 | } 34 | const resultRows: any[] = []; 35 | dataView.rows.forEach((row) => { 36 | fields.forEach((field) => { 37 | const resultRow = pick(row, retains); 38 | resultRow[key] = field; 39 | resultRow[value] = row[field]; 40 | resultRows.push(resultRow); 41 | }); 42 | }); 43 | dataView.rows = resultRows; 44 | }); 45 | -------------------------------------------------------------------------------- /src/transform/geo/centroid.ts: -------------------------------------------------------------------------------- 1 | import { assign, isString, isArray } from '@antv/util'; 2 | import { DataSet } from '../../data-set'; 3 | const { registerTransform } = DataSet; 4 | import { getField } from '../../util/option-parser'; 5 | import { View } from '../../view'; 6 | 7 | const DEFAULT_OPTIONS: Partial = { 8 | // field: 'name', // required 9 | // geoView: view, // required 10 | // geoDataView: view, // alias 11 | as: ['_centroid_x', '_centroid_y'], 12 | }; 13 | 14 | export interface Options { 15 | field: string; 16 | geoDataView: View | string; 17 | as?: [string, string]; 18 | } 19 | 20 | function transform(view: View, options: Options): void { 21 | options = assign({} as Options, DEFAULT_OPTIONS, options); 22 | const field = getField(options); 23 | // @ts-ignore 24 | let geoView = options.geoView || options.geoDataView; // alias 25 | if (isString(geoView) && view.dataSet) { 26 | geoView = view.dataSet.getView(geoView); 27 | } 28 | if (!geoView || geoView.dataType !== 'geo') { 29 | throw new TypeError('Invalid geoView: must be a DataView of GEO dataType!'); 30 | } 31 | const as = options.as; 32 | if (!isArray(as) || as.length !== 2) { 33 | throw new TypeError('Invalid as: it must be an array with 2 strings (e.g. [ "cX", "cY" ])!'); 34 | } 35 | 36 | const centroidX = as[0]; 37 | const centroidY = as[1]; 38 | view.rows.forEach((row) => { 39 | const feature = geoView.geoFeatureByName(row[field]); 40 | if (feature) { 41 | if (geoView._projectedAs) { 42 | row[centroidX] = feature[geoView._projectedAs[2]]; 43 | row[centroidY] = feature[geoView._projectedAs[3]]; 44 | } else { 45 | row[centroidX] = feature.centroidX; 46 | row[centroidY] = feature.centroidY; 47 | } 48 | } 49 | }); 50 | } 51 | 52 | registerTransform('geo.centroid', transform); 53 | -------------------------------------------------------------------------------- /src/transform/geo/projection.ts: -------------------------------------------------------------------------------- 1 | import { assign, isArray } from '@antv/util'; 2 | import { geoPath } from 'd3-geo'; 3 | import getPointAtLength from 'point-at-length'; 4 | import { DataSet } from '../../data-set'; 5 | const { registerTransform } = DataSet; 6 | import getGeoProjection from '../../util/get-geo-projection'; 7 | import { View } from '../../view'; 8 | 9 | const DEFAULT_OPTIONS: Partial = { 10 | // projection: '', // default to null 11 | as: ['_x', '_y', '_centroid_x', '_centroid_y'], 12 | }; 13 | 14 | export interface Options { 15 | projection: string; 16 | as?: string[]; 17 | } 18 | 19 | function transform(dataView: View, options: Options): void { 20 | if (dataView.dataType !== 'geo' && dataView.dataType !== 'geo-graticule') { 21 | throw new TypeError('Invalid dataView: this transform is for Geo data only!'); 22 | } 23 | options = assign({} as Options, DEFAULT_OPTIONS, options); 24 | let projection = options.projection; 25 | if (!projection) { 26 | throw new TypeError('Invalid projection!'); 27 | } 28 | projection = getGeoProjection(projection); 29 | // @ts-ignore; 30 | const geoPathGenerator = geoPath(projection); 31 | const as = options.as; 32 | if (!isArray(as) || as.length !== 4) { 33 | throw new TypeError('Invalid as: it must be an array with 4 strings (e.g. [ "x", "y", "cX", "cY" ])!'); 34 | } 35 | dataView._projectedAs = as; 36 | const [lonField, latField, centroidX, centroidY] = as; 37 | dataView.rows.forEach((row) => { 38 | row[lonField] = []; 39 | row[latField] = []; 40 | const pathData = geoPathGenerator(row); 41 | if (pathData) { 42 | // TODO projection returns null 43 | const points = getPointAtLength(pathData); 44 | points._path.forEach((point) => { 45 | row[lonField].push(point[1]); 46 | row[latField].push(point[2]); 47 | }); 48 | const centroid = geoPathGenerator.centroid(row); 49 | row[centroidX] = centroid[0]; 50 | row[centroidY] = centroid[1]; 51 | } 52 | }); 53 | dataView.rows = dataView.rows.filter((row) => row[lonField].length !== 0); 54 | } 55 | 56 | registerTransform('geo.projection', transform); 57 | -------------------------------------------------------------------------------- /src/transform/geo/region.ts: -------------------------------------------------------------------------------- 1 | import { assign, isArray, isString } from '@antv/util'; 2 | import { DataSet } from '../../data-set'; 3 | const { registerTransform } = DataSet; 4 | import { getField } from '../../util/option-parser'; 5 | import { View } from '../../view'; 6 | 7 | const DEFAULT_OPTIONS: Partial = { 8 | // field: 'name', // required 9 | // geoView: view, // required 10 | // geoDataView: view, // alias 11 | as: ['_x', '_y'], 12 | }; 13 | export interface Options { 14 | field: string; 15 | geoDataView: View | string; 16 | as?: [string, string]; 17 | } 18 | 19 | function transform(view: View, options: Options): void { 20 | options = assign({} as Options, DEFAULT_OPTIONS, options); 21 | const field = getField(options); 22 | // @ts-ignore 23 | let geoView = options.geoView || options.geoDataView; // alias 24 | if (isString(geoView)) { 25 | geoView = view.dataSet.getView(geoView); 26 | } 27 | if (!geoView || geoView.dataType !== 'geo') { 28 | throw new TypeError('Invalid geoView: must be a DataView of GEO dataType!'); 29 | } 30 | const as = options.as; 31 | if (!isArray(as) || as.length !== 2) { 32 | throw new TypeError('Invalid as: it must be an array with 2 strings (e.g. [ "x", "y" ])!'); 33 | } 34 | const lonField = as[0]; 35 | const latField = as[1]; 36 | view.rows.forEach((row) => { 37 | const feature = geoView.geoFeatureByName(row[field]); 38 | if (feature) { 39 | if (geoView._projectedAs) { 40 | row[lonField] = feature[geoView._projectedAs[0]]; 41 | row[latField] = feature[geoView._projectedAs[1]]; 42 | } else { 43 | row[lonField] = feature.longitude; 44 | row[latField] = feature.latitude; 45 | } 46 | } 47 | }); 48 | } 49 | 50 | registerTransform('geo.region', transform); 51 | -------------------------------------------------------------------------------- /src/transform/hierarchy/cluster.ts: -------------------------------------------------------------------------------- 1 | import * as d3Hierarchy from 'd3-hierarchy'; 2 | import { assign, isArray } from '@antv/util'; 3 | import { DataSet } from '../../data-set'; 4 | import { getField } from '../../util/option-parser'; 5 | import { View } from '../../view'; 6 | 7 | const DEFAULT_OPTIONS = { 8 | field: 'value', 9 | size: [1, 1], // width, height 10 | nodeSize: null, 11 | separation: null, 12 | as: ['x', 'y'], 13 | }; 14 | 15 | interface Options { 16 | field: string; 17 | size: [number, number]; 18 | nodeSize: any; 19 | separation: any; 20 | as: [string, string]; 21 | } 22 | 23 | function transform(dataView: View, options: Options) { 24 | if (dataView.dataType !== DataSet.CONSTANTS.HIERARCHY || !dataView.root) { 25 | throw new TypeError('Invalid DataView: This transform is for Hierarchy data only!'); 26 | } 27 | const root = dataView.root; 28 | options = assign({} as Options, DEFAULT_OPTIONS, options); 29 | 30 | const as = options.as; 31 | if (!isArray(as) || as.length !== 2) { 32 | throw new TypeError('Invalid as: it must be an array with 2 strings (e.g. [ "x", "y" ])!'); 33 | } 34 | 35 | let field: string | undefined = undefined; 36 | try { 37 | field = getField(options); 38 | } catch (e) { 39 | console.warn(e); 40 | } 41 | 42 | if (field) { 43 | root.sum((d: any) => d[field!]); 44 | } 45 | 46 | const clusterLayout = d3Hierarchy.cluster(); 47 | clusterLayout.size(options.size); 48 | if (options.nodeSize) { 49 | clusterLayout.nodeSize(options.nodeSize); 50 | } 51 | if (options.separation) { 52 | clusterLayout.separation(options.separation); 53 | } 54 | clusterLayout(root); 55 | 56 | const x = as[0]; 57 | const y = as[1]; 58 | root.each((node: any) => { 59 | node[x] = node.x; 60 | node[y] = node.y; 61 | }); 62 | } 63 | 64 | DataSet.registerTransform('hierarchy.cluster', transform); 65 | DataSet.registerTransform('dendrogram', transform); 66 | -------------------------------------------------------------------------------- /src/transform/hierarchy/compact-box.ts: -------------------------------------------------------------------------------- 1 | import hierarchy from '@antv/hierarchy'; 2 | import { DataSet } from '../../data-set'; 3 | import { View } from '../../view'; 4 | 5 | const DEFAULT_OPTIONS = {}; 6 | 7 | function transform(dataView: View, options) { 8 | const root = dataView.root; 9 | options = Object.assign({}, DEFAULT_OPTIONS, options); 10 | 11 | if (dataView.dataType !== DataSet.CONSTANTS.HIERARCHY) { 12 | throw new TypeError('Invalid DataView: This transform is for Hierarchy data only!'); 13 | } 14 | 15 | dataView.root = hierarchy.compactBox(root, options); 16 | } 17 | 18 | DataSet.registerTransform('hierarchy.compact-box', transform); 19 | DataSet.registerTransform('compact-box-tree', transform); 20 | DataSet.registerTransform('non-layered-tidy-tree', transform); 21 | DataSet.registerTransform('mindmap-logical', transform); 22 | -------------------------------------------------------------------------------- /src/transform/hierarchy/dendrogram.ts: -------------------------------------------------------------------------------- 1 | import hierarchy from '@antv/hierarchy'; 2 | import { DataSet } from '../../data-set'; 3 | import { View } from '../../view'; 4 | 5 | const DEFAULT_OPTIONS = {}; 6 | 7 | function transform(dataView: View, options) { 8 | const root = dataView.root; 9 | options = Object.assign({}, DEFAULT_OPTIONS, options); 10 | 11 | if (dataView.dataType !== DataSet.CONSTANTS.HIERARCHY) { 12 | throw new TypeError('Invalid DataView: This transform is for Hierarchy data only!'); 13 | } 14 | 15 | dataView.root = hierarchy.dendrogram(root, options); 16 | } 17 | 18 | DataSet.registerTransform('hierarchy.dendrogram', transform); 19 | DataSet.registerTransform('dendrogram', transform); 20 | -------------------------------------------------------------------------------- /src/transform/hierarchy/indented.ts: -------------------------------------------------------------------------------- 1 | import hierarchy from '@antv/hierarchy'; 2 | import { DataSet } from '../../data-set'; 3 | import { View } from '../../view'; 4 | 5 | const DEFAULT_OPTIONS = {}; 6 | 7 | function transform(dataView: View, options) { 8 | const root = dataView.root; 9 | options = Object.assign({}, DEFAULT_OPTIONS, options); 10 | 11 | if (dataView.dataType !== DataSet.CONSTANTS.HIERARCHY) { 12 | throw new TypeError('Invalid DataView: This transform is for Hierarchy data only!'); 13 | } 14 | 15 | dataView.root = hierarchy.indented(root, options); 16 | } 17 | 18 | DataSet.registerTransform('hierarchy.indented', transform); 19 | DataSet.registerTransform('indented-tree', transform); 20 | -------------------------------------------------------------------------------- /src/transform/hierarchy/pack.ts: -------------------------------------------------------------------------------- 1 | import { assign, isArray } from '@antv/util'; 2 | import * as d3Hierarchy from 'd3-hierarchy'; 3 | import { DataSet } from '../../data-set'; 4 | import { getField } from '../../util/option-parser'; 5 | import { View } from '../../view'; 6 | 7 | const DEFAULT_OPTIONS = { 8 | field: 'value', 9 | size: [1, 1], // width, height 10 | padding: 0, 11 | as: ['x', 'y', 'r'], 12 | }; 13 | 14 | function transform(dataView: View, options) { 15 | if (dataView.dataType !== DataSet.CONSTANTS.HIERARCHY) { 16 | throw new TypeError('Invalid DataView: This transform is for Hierarchy data only!'); 17 | } 18 | const root = dataView.root; 19 | options = assign({}, DEFAULT_OPTIONS, options); 20 | 21 | const as = options.as; 22 | if (!isArray(as) || as.length !== 3) { 23 | throw new TypeError('Invalid as: it must be an array with 3 strings (e.g. [ "x", "y", "r" ])!'); 24 | } 25 | 26 | let field; 27 | try { 28 | field = getField(options); 29 | } catch (e) { 30 | console.warn(e); 31 | } 32 | if (field) { 33 | root.sum((d) => d[field]).sort((a, b) => b[field] - a[field]); 34 | } 35 | 36 | const packLayout = d3Hierarchy.pack(); 37 | packLayout.size(options.size); 38 | 39 | if (options.padding) { 40 | packLayout.padding(options.padding); 41 | } 42 | packLayout(root); 43 | 44 | const x = as[0]; 45 | const y = as[1]; 46 | const r = as[2]; 47 | root.each((node) => { 48 | node[x] = node.x; 49 | node[y] = node.y; 50 | node[r] = node.r; 51 | }); 52 | } 53 | 54 | DataSet.registerTransform('hierarchy.pack', transform); 55 | DataSet.registerTransform('hierarchy.circle-packing', transform); 56 | DataSet.registerTransform('circle-packing', transform); 57 | -------------------------------------------------------------------------------- /src/transform/hierarchy/partition.ts: -------------------------------------------------------------------------------- 1 | import * as d3Hierarchy from 'd3-hierarchy'; 2 | import { assign, isArray } from '@antv/util'; 3 | import { DataSet } from '../../data-set'; 4 | import { getField } from '../../util/option-parser'; 5 | import { View } from '../../view'; 6 | 7 | const DEFAULT_OPTIONS: Options = { 8 | field: 'value', 9 | size: [1, 1], // width, height 10 | round: false, 11 | // ratio: 1.618033988749895, // golden ratio 12 | padding: 0, 13 | sort: true, 14 | as: ['x', 'y'], 15 | }; 16 | 17 | export interface Options { 18 | field: string; 19 | size?: [number, number]; 20 | round?: boolean; 21 | ratio?: number; 22 | padding?: number; 23 | sort?: boolean; 24 | as?: [string, string]; 25 | } 26 | 27 | function transform(dataView: View, options: Options): void { 28 | if (dataView.dataType !== DataSet.CONSTANTS.HIERARCHY) { 29 | throw new TypeError('Invalid DataView: This transform is for Hierarchy data only!'); 30 | } 31 | const root = dataView.root; 32 | options = assign({} as Options, DEFAULT_OPTIONS, options); 33 | 34 | const as = options.as; 35 | if (!isArray(as) || as.length !== 2) { 36 | throw new TypeError('Invalid as: it must be an array with 2 strings (e.g. [ "x", "y" ])!'); 37 | } 38 | 39 | let field; 40 | try { 41 | field = getField(options); 42 | } catch (e) { 43 | console.warn(e); 44 | } 45 | if (field) { 46 | root.sum((d) => d[field]); 47 | } 48 | 49 | const partitionLayout = d3Hierarchy.partition(); 50 | partitionLayout 51 | .size(options.size) 52 | .round(options.round) 53 | .padding(options.padding); 54 | partitionLayout(root); 55 | 56 | /* 57 | * points: 58 | * 3 2 59 | * 0 1 60 | */ 61 | const x = as[0]; 62 | const y = as[1]; 63 | root.each((node) => { 64 | node[x] = [node.x0, node.x1, node.x1, node.x0]; 65 | node[y] = [node.y1, node.y1, node.y0, node.y0]; 66 | ['x0', 'x1', 'y0', 'y1'].forEach((prop) => { 67 | if (as.indexOf(prop) === -1) { 68 | delete node[prop]; 69 | } 70 | }); 71 | }); 72 | } 73 | 74 | DataSet.registerTransform('hierarchy.partition', transform); 75 | DataSet.registerTransform('adjacency', transform); 76 | -------------------------------------------------------------------------------- /src/transform/hierarchy/tree.ts: -------------------------------------------------------------------------------- 1 | import * as d3Hierarchy from 'd3-hierarchy'; 2 | import { assign, isArray } from '@antv/util'; 3 | import { DataSet } from '../../data-set'; 4 | import { getField } from '../../util/option-parser'; 5 | 6 | const DEFAULT_OPTIONS = { 7 | field: 'value', 8 | size: [1, 1], // width, height 9 | nodeSize: null, 10 | separation: null, 11 | as: ['x', 'y'], 12 | }; 13 | 14 | function transform(dataView, options) { 15 | if (dataView.dataType !== DataSet.CONSTANTS.HIERARCHY) { 16 | throw new TypeError('Invalid DataView: This transform is for Hierarchy data only!'); 17 | } 18 | const root = dataView.root; 19 | options = assign({}, DEFAULT_OPTIONS, options); 20 | 21 | const as = options.as; 22 | if (!isArray(as) || as.length !== 2) { 23 | throw new TypeError('Invalid as: it must be an array with 2 strings (e.g. [ "x", "y" ])!'); 24 | } 25 | 26 | let field; 27 | try { 28 | field = getField(options); 29 | } catch (e) { 30 | console.warn(e); 31 | } 32 | if (field) { 33 | root.sum((d) => d[field]); 34 | } 35 | 36 | const treeLayout = d3Hierarchy.tree(); 37 | treeLayout.size(options.size); 38 | if (options.nodeSize) { 39 | treeLayout.nodeSize(options.nodeSize); 40 | } 41 | if (options.separation) { 42 | treeLayout.separation(options.separation); 43 | } 44 | treeLayout(root); 45 | 46 | const x = as[0]; 47 | const y = as[1]; 48 | root.each((node) => { 49 | node[x] = node.x; 50 | node[y] = node.y; 51 | }); 52 | } 53 | 54 | DataSet.registerTransform('hierarchy.tree', transform); 55 | DataSet.registerTransform('tree', transform); 56 | -------------------------------------------------------------------------------- /src/transform/impute.ts: -------------------------------------------------------------------------------- 1 | import { assign, forIn, has, isFunction, isUndefined, isString } from '@antv/util'; 2 | import * as simpleStatistics from 'simple-statistics'; 3 | import partition from '../util/partition'; 4 | import { DataSet } from '../data-set'; 5 | import { getField } from '../util/option-parser'; 6 | import { View } from '../view'; 7 | 8 | const DEFAULT_OPTIONS: Partial = { 9 | // field: '', // required 10 | // method: 'value', // required 11 | // value: 10, // required if (method === 'value') 12 | groupBy: [], 13 | }; 14 | 15 | function notUndefinedValues(values: any[]): any[] { 16 | return values.filter((value) => !isUndefined(value)); 17 | } 18 | 19 | export interface Options { 20 | field: string; 21 | method: keyof Imputations | ((row: any, values: any[], value: any, group: any[]) => any); 22 | value?: any; 23 | groupBy?: string | string[] | ((item: any) => string); 24 | } 25 | 26 | const STATISTICS_METHODS = ['mean', 'median', 'max', 'min']; 27 | 28 | interface Imputations { 29 | mean(row: any, values: any[]): number; 30 | median(row: any, values: any[]): number; 31 | max(row: any, values: any[]): number; 32 | min(row: any, values: any[]): number; 33 | value(row: any, values: any[], value: any): any; 34 | } 35 | 36 | const imputations = {} as Imputations; 37 | 38 | STATISTICS_METHODS.forEach((method) => { 39 | // @ts-ignore 40 | imputations[method] = (row, values) => simpleStatistics[method](values); 41 | }); 42 | 43 | imputations.value = (_row, _values, value) => value; 44 | 45 | function transform(dataView: View, options: Options): void { 46 | options = assign({} as Options, DEFAULT_OPTIONS, options); 47 | const field = getField(options); 48 | const method = options.method; 49 | if (!method) { 50 | throw new TypeError('Invalid method!'); 51 | } 52 | if (method === 'value' && !has(options, 'value')) { 53 | throw new TypeError('Invalid value: it is nil.'); 54 | } 55 | const column = notUndefinedValues(dataView.getColumn(field)); 56 | const groups = partition(dataView.rows, options.groupBy); 57 | forIn(groups, (group: any[]) => { 58 | let fieldValues = notUndefinedValues(group.map((row) => row[field])); 59 | if (fieldValues.length === 0) { 60 | fieldValues = column; 61 | } 62 | group.forEach((row: any) => { 63 | if (isUndefined(row[field])) { 64 | if (isFunction(method)) { 65 | row[field] = method(row, fieldValues, options.value, group); 66 | } else if (isString(method)) { 67 | row[field] = imputations[method](row, fieldValues, options.value); 68 | } else { 69 | throw new TypeError(`Invalid method: must be a function or one of ${STATISTICS_METHODS.join(', ')}`); 70 | } 71 | } 72 | }); 73 | }); 74 | } 75 | 76 | DataSet.registerTransform('impute', transform); 77 | -------------------------------------------------------------------------------- /src/transform/map.ts: -------------------------------------------------------------------------------- 1 | import { DataSet } from '../data-set'; 2 | import { View } from '../view'; 3 | 4 | export interface Options { 5 | callback?(item: any, index: number, arr: any[]): any; 6 | } 7 | 8 | function defaultCallback(row: any): any { 9 | return row; 10 | } 11 | 12 | DataSet.registerTransform('map', (dataView: View, options: Options) => { 13 | dataView.rows = dataView.rows.map(options.callback || defaultCallback); 14 | }); 15 | -------------------------------------------------------------------------------- /src/transform/partition.ts: -------------------------------------------------------------------------------- 1 | import { values, assign } from '@antv/util'; 2 | import partition from '../util/partition'; 3 | import { DataSet } from '../data-set'; 4 | import { View } from '../view'; 5 | 6 | const DEFAULT_OPTIONS: Options = { 7 | groupBy: [], // optional 8 | orderBy: [], 9 | }; 10 | 11 | export interface Options { 12 | groupBy: string[]; 13 | orderBy?: string[]; 14 | } 15 | 16 | DataSet.registerTransform('partition', (dataView: View, options: Options) => { 17 | options = assign({} as Options, DEFAULT_OPTIONS, options); 18 | // TODO: rows 是否都只能是数组 19 | // @ts-ignore; 20 | dataView.rows = partition(dataView.rows, options.groupBy, options.orderBy); 21 | }); 22 | 23 | function group(dataView: View, options: Options): void { 24 | options = assign({} as Options, DEFAULT_OPTIONS, options); 25 | dataView.rows = values(partition(dataView.rows, options.groupBy, options.orderBy)); 26 | } 27 | 28 | DataSet.registerTransform('group', group); 29 | DataSet.registerTransform('groups', group); 30 | -------------------------------------------------------------------------------- /src/transform/percent.ts: -------------------------------------------------------------------------------- 1 | import { assign, forIn, isArray, isString } from '@antv/util'; 2 | import { sum } from 'simple-statistics'; 3 | import partition from '../util/partition'; 4 | import { DataSet } from '../data-set'; 5 | import { getField } from '../util/option-parser'; 6 | import { View } from '../view'; 7 | 8 | const DEFAULT_OPTIONS: Partial = { 9 | // field: 'y', // required 10 | // dimension: 'x', // required 11 | groupBy: [], // optional 12 | as: '_percent', 13 | }; 14 | 15 | export interface Options { 16 | field: string; 17 | dimension: string; 18 | groupBy?: string[]; 19 | as?: string; 20 | } 21 | 22 | function transform(dataView: View, options: Options): void { 23 | options = assign({} as Options, DEFAULT_OPTIONS, options); 24 | const field = getField(options); 25 | const { dimension, groupBy } = options; 26 | let as = options.as; 27 | if (!isString(dimension)) { 28 | throw new TypeError('Invalid dimension: must be a string!'); 29 | } 30 | if (isArray(as)) { 31 | console.warn('Invalid as: must be a string, will use the first element of the array specified.'); 32 | as = as[0]; 33 | } 34 | if (!isString(as)) { 35 | throw new TypeError('Invalid as: must be a string!'); 36 | } 37 | const rows = dataView.rows; 38 | const result: any[] = []; 39 | const groups = partition(rows, groupBy); 40 | forIn(groups, (group) => { 41 | const totalSum = sum(group.map((row: any) => row[field])); 42 | const innerGroups = partition(group, [dimension]); 43 | forIn(innerGroups, (innerGroup) => { 44 | const innerSum = sum(innerGroup.map((row: any) => row[field])); 45 | // const resultRow = pick(innerGroup[0], union(groupBy, [ dimension ])); 46 | const resultRow = innerGroup[0]; 47 | // FIXME in case dimension and field is the same 48 | const dimensionValue = resultRow[dimension]; 49 | resultRow[field] = innerSum; 50 | resultRow[dimension] = dimensionValue; 51 | if (totalSum === 0) { 52 | resultRow[as!] = 0; 53 | } else { 54 | resultRow[as!] = innerSum / totalSum; 55 | } 56 | result.push(resultRow); 57 | }); 58 | }); 59 | dataView.rows = result; 60 | } 61 | 62 | DataSet.registerTransform('percent', transform); 63 | -------------------------------------------------------------------------------- /src/transform/pick.ts: -------------------------------------------------------------------------------- 1 | import { pick } from '@antv/util'; 2 | import { DataSet } from '../data-set'; 3 | import { getFields } from '../util/option-parser'; 4 | import { View } from '../view'; 5 | 6 | /* 7 | * options: { 8 | * type: 'pick', 9 | * fields: [], 10 | * } 11 | */ 12 | 13 | export interface Options { 14 | fields?: string[]; 15 | } 16 | 17 | DataSet.registerTransform('pick', (dataView: View, options: Options) => { 18 | const columns = getFields(options, dataView.getColumnNames()); 19 | dataView.rows = dataView.rows.map((row) => pick(row, columns)); 20 | }); 21 | -------------------------------------------------------------------------------- /src/transform/proportion.ts: -------------------------------------------------------------------------------- 1 | import { assign, isArray, forIn, isString } from '@antv/util'; 2 | import partition from '../util/partition'; 3 | import { DataSet } from '../data-set'; 4 | import { getField } from '../util/option-parser'; 5 | import { View } from '../view'; 6 | 7 | const DEFAULT_OPTIONS: Partial = { 8 | // field: 'y', // required 9 | // dimension: 'x', // required 10 | groupBy: [], // optional 11 | as: '_proportion', 12 | }; 13 | 14 | export interface Options { 15 | field: string; 16 | dimension: string; 17 | groupBy?: string[]; 18 | as?: string; 19 | } 20 | 21 | function transform(dataView: View, options: Options): void { 22 | options = assign({} as Options, DEFAULT_OPTIONS, options); 23 | const field = getField(options); 24 | const dimension = options.dimension; 25 | const groupBy = options.groupBy; 26 | let as = options.as; 27 | if (!isString(dimension)) { 28 | throw new TypeError('Invalid dimension: must be a string!'); 29 | } 30 | if (isArray(as)) { 31 | console.warn('Invalid as: must be a string, will use the first element of the array specified.'); 32 | as = as[0]; 33 | } 34 | if (!isString(as)) { 35 | throw new TypeError('Invalid as: must be a string!'); 36 | } 37 | const rows = dataView.rows; 38 | const result: any = []; 39 | const groups = partition(rows, groupBy); 40 | forIn(groups, (group) => { 41 | const totalCount = group.length; 42 | const innerGroups = partition(group, [dimension]); 43 | forIn(innerGroups, (innerGroup) => { 44 | const innerCount = innerGroup.length; 45 | // const resultRow = pick(innerGroup[0], union(groupBy, [ dimension ])); 46 | const resultRow = innerGroup[0]; 47 | // FIXME in case dimension and field is the same 48 | const dimensionValue = resultRow[dimension]; 49 | resultRow[field] = innerCount; 50 | resultRow[dimension] = dimensionValue; 51 | resultRow[as] = innerCount / totalCount; 52 | result.push(resultRow); 53 | }); 54 | }); 55 | dataView.rows = result; 56 | } 57 | 58 | DataSet.registerTransform('proportion', transform); 59 | -------------------------------------------------------------------------------- /src/transform/regression.ts: -------------------------------------------------------------------------------- 1 | import regression from 'regression'; 2 | import { assign, isArray, isNumber } from '@antv/util'; 3 | import getSeriesValues from '../util/get-series-values'; 4 | import { DataSet } from '../data-set'; 5 | import { getFields } from '../util/option-parser'; 6 | import { silverman } from '../util/bandwidth'; 7 | import { View } from '../view'; 8 | 9 | const DEFAULT_OPTIONS: Partial = { 10 | as: ['x', 'y'], 11 | // fields: [ 'x', 'y' ], // required two fields 12 | method: 'linear', // regression method: linear, exponential, logarithmic, power, polynomial 13 | // extent: [], // extent to execute regression function, default: [ min(x), max(x) ] 14 | // bandwidth: 1, // bandWidth to execute regression function 15 | order: 2, // order of the polynomial curve 16 | precision: 2, // the number of significant figures the output is rounded to 17 | }; 18 | 19 | export interface Options { 20 | as?: string[]; 21 | method?: 'linear' | 'exponential' | 'logarithmic' | 'power' | 'polynomial'; 22 | fields: string[]; 23 | bandwidth?: number; 24 | extent?: [number, number]; 25 | order?: number; 26 | precision?: number; 27 | } 28 | 29 | const REGRESSION_METHODS = ['linear', 'exponential', 'logarithmic', 'power', 'polynomial']; 30 | 31 | function transform(dataView: View, options: Options): void { 32 | options = assign({} as Options, DEFAULT_OPTIONS, options); 33 | const fields = getFields(options); 34 | if (!isArray(fields) || fields.length !== 2) { 35 | throw new TypeError('invalid fields: must be an array of 2 strings.'); 36 | } 37 | const [xField, yField] = fields; 38 | const method = options.method; 39 | if (REGRESSION_METHODS.indexOf(method) === -1) { 40 | throw new TypeError(`invalid method: ${method}. Must be one of ${REGRESSION_METHODS.join(', ')}`); 41 | } 42 | const points: any[] = dataView.rows.map((row) => [row[xField], row[yField]]); 43 | const regressionResult = regression[method](points, options); 44 | let extent = options.extent; 45 | if (!isArray(extent) || extent.length !== 2) { 46 | extent = dataView.range(xField); 47 | } 48 | let bandwidth = options.bandwidth; 49 | if (!isNumber(bandwidth) || bandwidth <= 0) { 50 | bandwidth = silverman(dataView.getColumn(xField)); 51 | } 52 | const valuesToPredict = getSeriesValues(extent, bandwidth); 53 | const result: any[] = []; 54 | const [asX, asY] = options.as; 55 | valuesToPredict.forEach((value) => { 56 | const row: any = {}; 57 | const [x, y] = regressionResult.predict(value); 58 | row[asX] = x; 59 | row[asY] = y; 60 | if (isFinite(y)) { 61 | result.push(row); 62 | } 63 | }); 64 | dataView.rows = result; 65 | } 66 | 67 | DataSet.registerTransform('regression', transform); 68 | 69 | export default { 70 | REGRESSION_METHODS, 71 | }; 72 | -------------------------------------------------------------------------------- /src/transform/rename.ts: -------------------------------------------------------------------------------- 1 | import { forIn, isPlainObject, isString } from '@antv/util'; 2 | import { DataSet } from '../data-set'; 3 | import { View } from '../view'; 4 | 5 | /* 6 | * options: { 7 | * type: 'pick', 8 | * fields: [], 9 | * } 10 | */ 11 | 12 | export interface Options { 13 | map?: Record; 14 | } 15 | 16 | function transform(dataView: View, options: Options): void { 17 | const map = options.map || {}; 18 | const cleanMap: Record = {}; 19 | if (isPlainObject(map)) { 20 | forIn(map, (value, key) => { 21 | if (isString(value) && isString(key)) { 22 | cleanMap[key] = value; 23 | } 24 | }); 25 | } 26 | dataView.rows.forEach((row) => { 27 | forIn(cleanMap, (newKey, key) => { 28 | const temp = row[key]; 29 | delete row[key]; 30 | row[newKey] = temp; 31 | }); 32 | }); 33 | } 34 | 35 | DataSet.registerTransform('rename', transform); 36 | DataSet.registerTransform('rename-fields', transform); 37 | -------------------------------------------------------------------------------- /src/transform/reverse.ts: -------------------------------------------------------------------------------- 1 | import { DataSet } from '../data-set'; 2 | import { View } from '../view'; 3 | 4 | DataSet.registerTransform('reverse', (dataView: View) => { 5 | dataView.rows.reverse(); 6 | }); 7 | -------------------------------------------------------------------------------- /src/transform/sort-by.ts: -------------------------------------------------------------------------------- 1 | import { isArray, sortBy } from '@antv/util'; 2 | import { DataSet } from '../data-set'; 3 | import { getFields } from '../util/option-parser'; 4 | import { View } from '../view'; 5 | 6 | /* 7 | * options: { 8 | * type: 'sort-by', 9 | * fields: [], 10 | * order: 'ASC' // 'DESC' 11 | * } 12 | */ 13 | 14 | const VALID_ORDERS = ['ASC', 'DESC']; 15 | 16 | export interface Options { 17 | fields?: string[]; 18 | order?: 'ASC' | 'DESC'; 19 | } 20 | 21 | function transform(dataView: View, options: Options): void { 22 | const fields = getFields(options, [dataView.getColumnName(0)]); 23 | if (!isArray(fields)) { 24 | throw new TypeError('Invalid fields: must be an array with strings!'); 25 | } 26 | dataView.rows = sortBy(dataView.rows, fields); 27 | const order = options.order; 28 | if (order && VALID_ORDERS.indexOf(order) === -1) { 29 | throw new TypeError(`Invalid order: ${order} must be one of ${VALID_ORDERS.join(', ')}`); 30 | } else if (order === 'DESC') { 31 | dataView.rows.reverse(); 32 | } 33 | } 34 | 35 | DataSet.registerTransform('sort-by', transform); 36 | DataSet.registerTransform('sortBy', transform); 37 | -------------------------------------------------------------------------------- /src/transform/sort.ts: -------------------------------------------------------------------------------- 1 | import { DataSet } from '../data-set'; 2 | import { View } from '../view'; 3 | 4 | /* 5 | * options: { 6 | * type: 'sort', 7 | * callback, 8 | * } 9 | */ 10 | 11 | export interface Options { 12 | callback?(a: any, b: any): number; 13 | } 14 | 15 | DataSet.registerTransform('sort', (dataView: View, options: Options) => { 16 | const columnName = dataView.getColumnName(0); 17 | dataView.rows.sort(options.callback || ((a, b) => a[columnName] - b[columnName])); 18 | }); 19 | -------------------------------------------------------------------------------- /src/transform/subset.ts: -------------------------------------------------------------------------------- 1 | import { DataSet } from '../data-set'; 2 | import { getFields } from '../util/option-parser'; 3 | import { View } from '../view'; 4 | 5 | /* 6 | * options: { 7 | * type: 'subset', 8 | * startRowIndex: 0, 9 | * endRowIndex: 1, 10 | * fields: [], 11 | * } 12 | */ 13 | 14 | export interface Options { 15 | startRowIndex?: number; 16 | endRowIndex?: number; 17 | fields?: string[]; 18 | } 19 | 20 | DataSet.registerTransform('subset', (dataView: View, options: Options) => { 21 | const startIndex = options.startRowIndex || 0; 22 | const endIndex = options.endRowIndex || dataView.rows.length - 1; 23 | const columns = getFields(options, dataView.getColumnNames()); 24 | dataView.rows = dataView.getSubset(startIndex, endIndex, columns); 25 | }); 26 | -------------------------------------------------------------------------------- /src/util/bandwidth.ts: -------------------------------------------------------------------------------- 1 | import { standardDeviation, interquartileRange } from 'simple-statistics'; 2 | 3 | export function silverman(arr: number[]) { 4 | const stdev = standardDeviation(arr); 5 | const num = 4 * Math.pow(stdev, 5); 6 | const denom = 3 * arr.length; 7 | return Math.pow(num / denom, 0.2); 8 | } 9 | 10 | export function nrd(arr: number[]) { 11 | let s = standardDeviation(arr); 12 | const iqr = interquartileRange(arr); 13 | if (typeof iqr === 'number') { 14 | s = Math.min(s, iqr / 1.34); 15 | } 16 | return 1.06 * s * Math.pow(arr.length, -0.2); 17 | } 18 | -------------------------------------------------------------------------------- /src/util/euclidean-distance.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @reference: https://github.com/zeke/euclidean-distance 3 | */ 4 | export default (a: number[], b: number[]) => { 5 | let sum = 0; 6 | let n; 7 | for (n = 0; n < a.length; n++) { 8 | sum += Math.pow(a[n] - b[n], 2); 9 | } 10 | return Math.sqrt(sum); 11 | }; 12 | -------------------------------------------------------------------------------- /src/util/get-geo-projection.ts: -------------------------------------------------------------------------------- 1 | import { isString, isFunction } from '@antv/util'; 2 | import * as d3Geo from 'd3-geo'; 3 | import * as d3GeoProjection from 'd3-geo-projection'; 4 | import * as d3CompositeProjection from 'd3-composite-projections'; 5 | /* 6 | * getGeoProjection 7 | * 8 | * @param {string|function} projection projection name or projection function 9 | * @param {boolean} [exportRaw = false] - whether return the raw projection or not 10 | * */ 11 | export default (projection: string, exportRaw = false) => { 12 | if (isFunction(projection)) { 13 | return exportRaw ? projection : projection(); 14 | } 15 | if (isString(projection)) { 16 | // @ts-ignore 17 | if (d3Geo[projection]) { 18 | // @ts-ignore 19 | return exportRaw ? d3Geo[projection] : d3Geo[projection](); 20 | } 21 | if (d3GeoProjection[projection]) { 22 | return exportRaw ? d3GeoProjection[projection] : d3GeoProjection[projection](); 23 | } 24 | if (d3CompositeProjection[projection]) { 25 | return exportRaw ? d3CompositeProjection[projection] : d3CompositeProjection[projection](); 26 | } 27 | } 28 | return null; 29 | }; 30 | -------------------------------------------------------------------------------- /src/util/get-series-values.ts: -------------------------------------------------------------------------------- 1 | export default (extent: [number, number], bw: number) => { 2 | const bandwidth = bw || 1; 3 | const [min, max] = extent; 4 | const values = []; 5 | let tmp = min; 6 | while (tmp < max) { 7 | values.push(tmp); 8 | tmp += bandwidth; 9 | } 10 | values.push(max); 11 | return values; 12 | }; 13 | -------------------------------------------------------------------------------- /src/util/kernel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @reference: https://github.com/jasondavies/science.js/blob/master/src/stats/kernel.js 3 | * @reference: https://github.com/Planeshifter/kernel-smooth/blob/master/lib/index.js#L16 4 | */ 5 | function uniform(u: number) { 6 | return Math.abs(u) <= 1 ? 0.5 : 0; 7 | } 8 | 9 | function tricubed(u: number) { 10 | const abs = 1 - Math.pow(Math.abs(u), 3); 11 | return Math.pow(abs, 3); 12 | } 13 | 14 | export default { 15 | boxcar: uniform, 16 | cosine(u: number) { 17 | if (Math.abs(u) <= 1) { 18 | return (Math.PI / 4) * Math.cos((Math.PI / 2) * u); 19 | } 20 | return 0; 21 | }, 22 | epanechnikov(u: number) { 23 | return Math.abs(u) < 1 ? 0.75 * (1 - u * u) : 0; 24 | }, 25 | gaussian(u: number) { 26 | // return 1 / Math.sqrt(2 * Math.PI) * Math.exp(-0.5 * u * u); 27 | return 0.3989422804 * Math.exp(-0.5 * u * u); 28 | }, 29 | quartic(u: number) { 30 | if (Math.abs(u) < 1) { 31 | const tmp = 1 - u * u; 32 | return (15 / 16) * tmp * tmp; 33 | } 34 | return 0; 35 | }, 36 | triangular(u: number) { 37 | const abs = Math.abs(u); 38 | return abs < 1 ? 1 - abs : 0; 39 | }, 40 | tricube(u: number) { 41 | return Math.abs(u) < 1 ? (70 / 81) * tricubed(u) : 0; 42 | }, 43 | triweight(u: number) { 44 | if (Math.abs(u) < 1) { 45 | const tmp = 1 - u * u; 46 | return (35 / 32) * tmp * tmp * tmp; 47 | } 48 | return 0; 49 | }, 50 | uniform, 51 | }; 52 | -------------------------------------------------------------------------------- /src/util/option-parser.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isString } from '@antv/util'; 2 | 3 | const INVALID_FIELD_ERR_MSG = 'Invalid field: it must be a string!'; 4 | const INVALID_FIELDS_ERR_MSG = 'Invalid fields: it must be an array!'; 5 | 6 | interface Options { 7 | field?: string | string[]; 8 | fields?: string | string[]; 9 | } 10 | 11 | export function getField(options: Options, defaultField?: string): string { 12 | const { field, fields } = options; 13 | if (isString(field)) { 14 | return field; 15 | } 16 | if (isArray(field)) { 17 | console.warn(INVALID_FIELD_ERR_MSG); 18 | return field[0]; 19 | } 20 | console.warn(`${INVALID_FIELD_ERR_MSG} will try to get fields instead.`); 21 | if (isString(fields)) { 22 | return fields; 23 | } 24 | if (isArray(fields) && fields.length) { 25 | return fields[0]; 26 | } 27 | if (defaultField) { 28 | return defaultField; 29 | } 30 | throw new TypeError(INVALID_FIELD_ERR_MSG); 31 | } 32 | export function getFields(options: Options, defaultFields?: string[]): string[] { 33 | const { field, fields } = options; 34 | if (isArray(fields)) { 35 | return fields; 36 | } 37 | if (isString(fields)) { 38 | console.warn(INVALID_FIELDS_ERR_MSG); 39 | return [fields]; 40 | } 41 | console.warn(`${INVALID_FIELDS_ERR_MSG} will try to get field instead.`); 42 | if (isString(field)) { 43 | console.warn(INVALID_FIELDS_ERR_MSG); 44 | return [field]; 45 | } 46 | if (isArray(field) && field.length) { 47 | console.warn(INVALID_FIELDS_ERR_MSG); 48 | return field; 49 | } 50 | if (defaultFields) { 51 | return defaultFields; 52 | } 53 | throw new TypeError(INVALID_FIELDS_ERR_MSG); 54 | } 55 | -------------------------------------------------------------------------------- /src/util/p-by-fraction.ts: -------------------------------------------------------------------------------- 1 | export default (fraction: number) => { 2 | const step = 1 / fraction; 3 | const pArr = []; 4 | for (let i = 0; i <= 1; i = i + step) { 5 | pArr.push(i); 6 | } 7 | return pArr; 8 | }; 9 | -------------------------------------------------------------------------------- /src/util/partition.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isFunction, isString, groupBy } from '@antv/util'; 2 | import simpleSortBy from './simple-sort-by'; 3 | 4 | export default ( 5 | rows: any[], 6 | group_by: string | string[] | ((item: any) => string), 7 | order_by: string | string[] | ((item: any) => number) = [] 8 | ): Record => { 9 | let newRows = rows; 10 | if (order_by && order_by.length) { 11 | newRows = simpleSortBy(rows, order_by); 12 | } 13 | 14 | let groupingFn: (item: any) => string; 15 | 16 | if (isFunction(group_by)) { 17 | groupingFn = group_by; 18 | } else if (isArray(group_by)) { 19 | groupingFn = (row: any) => `_${group_by.map((col) => row[col]).join('-')}`; 20 | // NOTE: Object.keys({'b': 'b', '2': '2', '1': '1', 'a': 'a'}) => [ '1', '2', 'b', 'a' ] 21 | // that is why we have to add a prefix 22 | } else if (isString(group_by)) { 23 | groupingFn = (row: any) => `_${row[group_by]}`; 24 | } 25 | const groups = groupBy(newRows, groupingFn!); 26 | return groups; 27 | }; 28 | -------------------------------------------------------------------------------- /src/util/simple-sort-by.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isFunction, isString } from '@antv/util'; 2 | 3 | export type SortTarget = string | string[] | ((a: any, b: any) => number); 4 | 5 | export default function sortBy(arr: any[], keys: SortTarget = []): any[] { 6 | let comparer: ((a: any, b: any) => number) | undefined = undefined; 7 | if (isFunction(keys)) { 8 | comparer = keys; 9 | } else if (isArray(keys)) { 10 | comparer = (a, b) => { 11 | for (let i = 0; i < keys.length; i++) { 12 | const key = keys[i]; 13 | if (a[key] < b[key]) { 14 | return -1; 15 | } 16 | if (a[key] > b[key]) { 17 | return 1; 18 | } 19 | } 20 | return 0; 21 | }; 22 | } else if (isString(keys)) { 23 | comparer = (a, b) => { 24 | if (a[keys] < b[keys]) { 25 | return -1; 26 | } 27 | if (a[keys] > b[keys]) { 28 | return 1; 29 | } 30 | return 0; 31 | }; 32 | } 33 | return arr.sort(comparer); 34 | } 35 | -------------------------------------------------------------------------------- /test/bugs/98-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../src'; 3 | import { View } from '../../src/view'; 4 | 5 | describe('no error when size is [0, 0]', () => { 6 | const ds = new DataSet(); 7 | let dv: View; 8 | beforeEach(() => { 9 | dv = ds.createView().source( 10 | ['Hello', 'world', 'normally', 'you', 'want', 'more', 'words', 'than', 'this'].map((d) => { 11 | return { 12 | text: d, 13 | value: 10 + Math.random() * 90, 14 | test: 'haha', 15 | }; 16 | }) 17 | ); 18 | }); 19 | 20 | it('size: [0, 0]', () => { 21 | expect(() => { 22 | dv.transform({ 23 | type: 'tag-cloud', 24 | size: [0, 0], 25 | imageMask: new Image(), 26 | }); 27 | }).to.not.throw(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/bugs/geo-max-call-stack-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../src/'; 3 | import ChinaGEO from '../fixtures/china-geo.json'; 4 | import Provinces from '../fixtures/china-provinces.json'; 5 | 6 | describe('max call stack when cloning options', () => { 7 | it('geo.region', () => { 8 | expect(() => { 9 | const ds = new DataSet(); 10 | const chinaMap = ds.createView('map').source(ChinaGEO, { 11 | type: 'GeoJSON', 12 | }); 13 | 14 | const dvData = ds.createView('data').source(Provinces); 15 | dvData.transform({ 16 | type: 'geo.region', 17 | field: 'name', 18 | geoDataView: chinaMap, 19 | as: ['longitude', 'lantitude'], 20 | }); 21 | }).to.not.throw(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/fixtures/china-provinces.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "甘肃", "value": "甘肃" }, 3 | { "name": "青海", "value": "青海" }, 4 | { "name": "广西", "value": "广西" }, 5 | { "name": "贵州", "value": "贵州" }, 6 | { "name": "重庆", "value": "重庆" }, 7 | { "name": "北京", "value": "北京" }, 8 | { "name": "福建", "value": "福建" }, 9 | { "name": "安徽", "value": "安徽" }, 10 | { "name": "广东", "value": "广东" }, 11 | { "name": "西藏", "value": "西藏" }, 12 | { "name": "新疆", "value": "新疆" }, 13 | { "name": "海南", "value": "海南" }, 14 | { "name": "宁夏", "value": "宁夏" }, 15 | { "name": "陕西", "value": "陕西" }, 16 | { "name": "山西", "value": "山西" }, 17 | { "name": "湖北", "value": "湖北" }, 18 | { "name": "湖南", "value": "湖南" }, 19 | { "name": "四川", "value": "四川" }, 20 | { "name": "云南", "value": "云南" }, 21 | { "name": "河北", "value": "河北" }, 22 | { "name": "河南", "value": "河南" }, 23 | { "name": "辽宁", "value": "辽宁" }, 24 | { "name": "山东", "value": "山东" }, 25 | { "name": "天津", "value": "天津" }, 26 | { "name": "江西", "value": "江西" }, 27 | { "name": "江苏", "value": "江苏" }, 28 | { "name": "上海", "value": "上海" }, 29 | { "name": "浙江", "value": "浙江" }, 30 | { "name": "吉林", "value": "吉林" }, 31 | { "name": "内蒙古", "value": "内蒙古" }, 32 | { "name": "黑龙江", "value": "黑龙江" }, 33 | { "name": "台湾", "value": "台湾" }, 34 | { "name": "香港", "value": "香港" }, 35 | { "name": "澳门", "value": "澳门" }, 36 | { "name": "南海诸岛", "value": "南海诸岛" } 37 | ] 38 | -------------------------------------------------------------------------------- /test/fixtures/empty-topo.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Topology", 3 | "bbox": [0, 0, 10, 10], 4 | "objects": { 5 | "polygon": { 6 | "type": "Polygon", 7 | "arcs": [ 8 | [0] 9 | ] 10 | }, 11 | "line": { 12 | "type": "LineString", 13 | "arcs": [0] 14 | } 15 | }, 16 | "arcs": [ 17 | [[1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2]] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/population-china.csv: -------------------------------------------------------------------------------- 1 | year,population 2 | 2002,1284530000 3 | 2003,1292270000 4 | 2004,1299880000 5 | 2005,1307560000 6 | 2006,1314480000 7 | 2007,1321290000 8 | 2008,1328020000 9 | 2009,1334500000 10 | 2010,1340910000 11 | 2011,1347350000 12 | 2012,1354040000 13 | 2013,1360720000 14 | 2014,1367820000 15 | 2015,1374620000 16 | -------------------------------------------------------------------------------- /test/fixtures/population-china.json: -------------------------------------------------------------------------------- 1 | [{"year":"2002","population":"1284530000"},{"year":"2003","population":"1292270000"},{"year":"2004","population":"1299880000"},{"year":"2005","population":"1307560000"},{"year":"2006","population":"1314480000"},{"year":"2007","population":"1321290000"},{"year":"2008","population":"1328020000"},{"year":"2009","population":"1334500000"},{"year":"2010","population":"1340910000"},{"year":"2011","population":"1347350000"},{"year":"2012","population":"1354040000"},{"year":"2013","population":"1360720000"},{"year":"2014","population":"1367820000"},{"year":"2015","population":"1374620000"}] -------------------------------------------------------------------------------- /test/fixtures/sample.csv: -------------------------------------------------------------------------------- 1 | Hello,World 2 | 42,"""fish""" 3 | -------------------------------------------------------------------------------- /test/fixtures/sample.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Hello": "42", 4 | "World": "\"fish\"" 5 | } 6 | ] -------------------------------------------------------------------------------- /test/fixtures/sample.psv: -------------------------------------------------------------------------------- 1 | Hello|World 2 | 42|"""fish""" 3 | -------------------------------------------------------------------------------- /test/fixtures/sample.tsv: -------------------------------------------------------------------------------- 1 | Hello World 2 | 42 """fish""" 3 | -------------------------------------------------------------------------------- /test/fixtures/sample2.csv: -------------------------------------------------------------------------------- 1 | Hello,World 2 | 42,"""fish""" 3 | foo,bar 4 | -------------------------------------------------------------------------------- /test/fixtures/sample2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Hello": "42", 4 | "World": "\"fish\"" 5 | }, 6 | { 7 | "Hello": "foo", 8 | "World": "bar" 9 | } 10 | ] -------------------------------------------------------------------------------- /test/fixtures/sample2.tsv: -------------------------------------------------------------------------------- 1 | Hello World 2 | 42 """fish""" 3 | foo bar 4 | -------------------------------------------------------------------------------- /test/support/util.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { resolve } from 'path'; 3 | 4 | export default (pathname: string): string => { 5 | return readFileSync(resolve(process.cwd(), pathname), 'utf8'); 6 | }; 7 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "noEmit": true 6 | }, 7 | "include": ["./unit"] 8 | } 9 | -------------------------------------------------------------------------------- /test/unit/api/hierarchy-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | import flare from '../../fixtures/flare.json'; 4 | 5 | describe('View API: hierarchy', () => { 6 | const dv = new DataSet().createView('test').source(flare, { 7 | type: 'hierarchy', 8 | }); 9 | dv.transform({ 10 | type: 'hierarchy.indented', 11 | }); 12 | 13 | it('getAllNodes()', () => { 14 | expect(dv.getAllNodes().length).to.equal(252); 15 | }); 16 | it('getAllLinks()', () => { 17 | expect(dv.getAllLinks().length).to.equal(251); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/api/statistics-spec.ts: -------------------------------------------------------------------------------- 1 | import { map, flattenDeep } from 'lodash'; 2 | import { max, mean, median, min, mode, quantile, standardDeviation, sum, variance } from 'simple-statistics'; 3 | import { expect } from 'chai'; 4 | import DataSet from '../../../src'; 5 | import populationChina from '../../fixtures/population-china.json'; 6 | 7 | describe('View API: statistics', () => { 8 | // statistics 9 | it('statistics methods', () => { 10 | const dv = new DataSet.View().source(populationChina); 11 | dv.transform({ 12 | type: 'map', 13 | callback(row) { 14 | row.year = parseInt(row.year, 10); 15 | return row; 16 | }, 17 | }); 18 | const years = dv.getColumn('year'); 19 | expect(dv.max('year')).to.equal(max(years)); 20 | expect(dv.min('year')).to.equal(min(years)); 21 | expect(dv.mean('year')).to.equal(mean(years)); 22 | expect(dv.average('year')).to.equal(mean(years)); 23 | expect(dv.median('year')).to.equal(median(years)); 24 | expect(dv.mode('year')).to.equal(mode(years)); 25 | expect(dv.quantile('year', 0.5)).to.equal(quantile(years, 0.5)); 26 | expect(dv.quantiles('year', [0, 0.1, 0.5])).to.eql(map([0, 0.1, 0.5], (p) => quantile(years, p))); 27 | expect(dv.quantilesByFraction('year', 4)).to.eql(map([0, 0.25, 0.5, 0.75, 1], (p) => quantile(years, p))); 28 | expect(dv.standardDeviation('year')).to.equal(standardDeviation(years)); 29 | expect(dv.sum('year')).to.equal(sum(years)); 30 | expect(dv.variance('year')).to.equal(variance(years)); 31 | expect(dv.range('year')).to.eql([min(years), max(years)]); 32 | }); 33 | 34 | it('statistics methods on fields of array', () => { 35 | const data = []; 36 | for (let i = 1; i <= 10; i++) { 37 | // 1~10 38 | data.push({ 39 | a: [i, i + 10, [-i, i * i, [-i * i, 1 / i]]], 40 | }); 41 | } 42 | const dv = new DataSet.View().source(data); 43 | const values = flattenDeep(dv.getColumn('a')); 44 | expect(dv.max('a')).to.equal(max(values)); 45 | expect(dv.min('a')).to.equal(min(values)); 46 | expect(dv.mean('a')).to.equal(mean(values)); 47 | expect(dv.average('a')).to.equal(mean(values)); 48 | expect(dv.median('a')).to.equal(median(values)); 49 | expect(dv.mode('a')).to.equal(mode(values)); 50 | expect(dv.quantile('a', 0.5)).to.equal(quantile(values, 0.5)); 51 | expect(dv.quantiles('a', [0, 0.1, 0.5])).to.eql(map([0, 0.1, 0.5], (p) => quantile(values, p))); 52 | expect(dv.quantilesByFraction('a', 4)).to.eql(map([0, 0.25, 0.5, 0.75, 1], (p) => quantile(values, p))); 53 | expect(dv.standardDeviation('a')).to.equal(standardDeviation(values)); 54 | expect(dv.sum('a')).to.equal(sum(values)); 55 | expect(dv.variance('a')).to.equal(variance(values)); 56 | expect(dv.range('a')).to.eql([min(values), max(values)]); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/unit/connector/default-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getConnector } = DataSet; 4 | 5 | describe('View.source(): default', () => { 6 | const ds = new DataSet(); 7 | const testView = ds.createView('test').source([ 8 | { 9 | foo: 'bar', 10 | }, 11 | ]); 12 | 13 | it('api', () => { 14 | expect(getConnector('default')).to.be.a('function'); 15 | }); 16 | 17 | it('View instance', () => { 18 | const testView2 = ds.createView('test2').source(testView); 19 | expect(testView2.origin).to.eql(testView.rows); 20 | expect(testView2.origin === testView.rows).to.equal(false); 21 | }); 22 | 23 | it('string', () => { 24 | const testView3 = ds.createView('test3').source('test'); 25 | expect(testView3.origin).to.eql(testView.rows); 26 | expect(testView3.origin === testView.rows).to.equal(false); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/connector/dsv-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getConnector } = DataSet; 4 | import readFileSync from '../../support/util'; 5 | import data from '../../fixtures/sample.json'; 6 | import data2 from '../../fixtures/sample2.json'; 7 | import { View } from '../../../src/view'; 8 | 9 | describe('View.source(): dsv', () => { 10 | const source = { 11 | psv: readFileSync('./test/fixtures/sample.psv'), 12 | csv: readFileSync('./test/fixtures/sample.csv'), 13 | csv2: readFileSync('./test/fixtures/sample2.csv'), 14 | tsv: readFileSync('./test/fixtures/sample.tsv'), 15 | tsv2: readFileSync('./test/fixtures/sample2.tsv'), 16 | }; 17 | const ds = new DataSet(); 18 | let dv: View; 19 | beforeEach(() => { 20 | dv = ds.createView(); 21 | }); 22 | 23 | it('api', () => { 24 | expect(getConnector('dsv')).to.be.a('function'); 25 | expect(getConnector('csv')).to.be.a('function'); 26 | expect(getConnector('tsv')).to.be.a('function'); 27 | }); 28 | 29 | it('dsv', () => { 30 | dv.source(source.psv, { 31 | type: 'dsv', 32 | delimiter: '|', 33 | }); 34 | expect(dv.origin).to.eql(data); 35 | }); 36 | 37 | it('csv', () => { 38 | dv.source(source.csv, { 39 | type: 'csv', 40 | }); 41 | expect(dv.origin).to.eql(data); 42 | dv.source(source.csv2, { 43 | type: 'csv', 44 | }); 45 | expect(dv.origin).to.eql(data2); 46 | }); 47 | 48 | it('tsv', () => { 49 | dv.source(source.tsv, { 50 | type: 'tsv', 51 | }); 52 | expect(dv.origin).to.eql(data); 53 | dv.source(source.tsv2, { 54 | type: 'tsv', 55 | }); 56 | expect(dv.origin).to.eql(data2); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/unit/connector/geo-graticule-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | import { View } from '../../../src/view'; 4 | const { getConnector } = DataSet; 5 | 6 | describe('View.source(): geo-graticule', () => { 7 | const ds = new DataSet(); 8 | let dv: View; 9 | beforeEach(() => { 10 | dv = ds.createView(); 11 | }); 12 | 13 | it('api', () => { 14 | expect(getConnector('geo-graticule')).to.be.a('function'); 15 | }); 16 | 17 | it('default', () => { 18 | expect(() => { 19 | dv.source({ 20 | type: 'geo-graticule', 21 | }); 22 | }).to.not.throw(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/unit/connector/geojson-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getConnector } = DataSet; 4 | import geoWorld from '../../fixtures/countries-geo.json'; 5 | import { View } from '../../../src/view'; 6 | 7 | describe('View.source(): geojson', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView(); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getConnector('geo')).to.be.a('function'); 16 | expect(getConnector('geojson')).to.be.a('function'); 17 | expect(getConnector('GeoJSON')).to.be.a('function'); 18 | }); 19 | 20 | it('default', () => { 21 | expect(() => { 22 | dv.source(geoWorld, { 23 | type: 'geo', 24 | }); 25 | }).to.not.throw(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/unit/connector/hierarchy-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getConnector } = DataSet; 4 | import flare from '../../fixtures/flare.json'; 5 | import { View } from '../../../src/view'; 6 | 7 | describe('View.source(): hierarchy', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView(); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getConnector('hierarchy')).to.be.a('function'); 16 | expect(getConnector('tree')).to.be.a('function'); 17 | }); 18 | 19 | it('default', () => { 20 | expect(() => { 21 | dv.source(flare, { 22 | type: 'hierarchy', 23 | }); 24 | }).to.not.throw(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/connector/topojson-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getConnector } = DataSet; 4 | import topoUS from '../../fixtures/us-topo.json'; 5 | import { View } from '../../../src/view'; 6 | 7 | describe('View.source(): topojson', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView(); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getConnector('topojson')).to.be.a('function'); 16 | expect(getConnector('TopoJSON')).to.be.a('function'); 17 | }); 18 | 19 | it('default', () => { 20 | expect(() => { 21 | // @ts-ignore 22 | dv.source(topoUS, { type: 'topojson' }); 23 | }).to.throw(); 24 | 25 | expect(() => { 26 | dv.source(topoUS, { 27 | type: 'topojson', 28 | object: 'states', 29 | }); 30 | }).to.not.throw(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/data-set-spec.ts: -------------------------------------------------------------------------------- 1 | import { forIn, keys } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../src'; 4 | 5 | describe('DataSet', () => { 6 | it('Constructor', () => { 7 | expect(DataSet).to.be.a('function'); 8 | expect(new DataSet()).to.be.an('object'); 9 | }); 10 | 11 | it('init with state', () => { 12 | // init with state 13 | const ds0 = new DataSet(); 14 | expect(ds0.state).to.eql({}); 15 | 16 | const ds1 = new DataSet({ 17 | state: { 18 | foo: 'bar', 19 | }, 20 | }); 21 | expect(ds1.state.foo).to.equal('bar'); 22 | }); 23 | 24 | it('createView(name)', () => { 25 | const ds = new DataSet(); 26 | ds.createView('test'); 27 | expect(keys(ds.views).length).to.equal(1); 28 | ds.createView(); 29 | expect(keys(ds.views).length).to.equal(2); 30 | expect(() => { 31 | ds.createView('test'); 32 | }).to.throw(); 33 | }); 34 | 35 | it('setView(name, view)', () => { 36 | const ds = new DataSet(); 37 | const dv = ds.createView('test1'); 38 | ds.setView('test2', dv); 39 | expect(ds.getView('test2')).to.equal(ds.getView('test1')); 40 | }); 41 | 42 | it('setState(name, value)', () => { 43 | const ds = new DataSet(); 44 | const newState = { 45 | foo: 'bar', 46 | hello: 'world', 47 | hey: 'jude', 48 | }; 49 | let count = 0; 50 | ds.on('statechange', () => { 51 | count++; 52 | }); 53 | forIn(newState, (value, key) => { 54 | ds.setState(key, value); 55 | }); 56 | expect(ds.state).to.not.equal(newState); 57 | expect(ds.state).to.eql(newState); 58 | expect(count).to.equal(0); 59 | setTimeout(() => { 60 | expect(count).to.equal(1); 61 | }, 16); 62 | }); 63 | 64 | it('setState(name, value): View watching or not watching', () => { 65 | let emittedCount = 0; 66 | const ds = new DataSet(); 67 | const dv = ds.createView({ 68 | watchingStates: ['foo'], 69 | }); 70 | dv.on('change', () => { 71 | emittedCount++; 72 | }); 73 | ds.setState('hello', 'world'); 74 | ds.setState('foo', 'bar'); 75 | expect(emittedCount).to.equal(0); 76 | setTimeout(() => { 77 | expect(emittedCount).to.equal(1); 78 | }, 16); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/unit/index-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../src'; 3 | 4 | describe('index', () => { 5 | it('DataSet', () => { 6 | expect('DataSet').to.be.a('string'); 7 | expect(DataSet).to.be.a('function'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/unit/transform/bin/hexagon-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../../src'; 3 | import { View } from '../../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): bin.hexagon', () => { 7 | const data = []; 8 | for (let i = 0; i <= 10; i++) { 9 | data.push({ 10 | a: i, 11 | b: i, 12 | }); 13 | } 14 | const ds = new DataSet(); 15 | let dv: View; 16 | beforeEach(() => { 17 | dv = ds.createView().source(data); 18 | }); 19 | 20 | it('api', () => { 21 | expect(getTransform('bin.hexagon')).to.be.a('function'); 22 | expect(getTransform('bin.hex')).to.be.a('function'); 23 | expect(getTransform('hexbin')).to.be.a('function'); 24 | expect( 25 | getTransform('bin.hexagon') === getTransform('bin.hex') && getTransform('bin.hexagon') === getTransform('hexbin') 26 | ).to.equal(true); 27 | }); 28 | 29 | it('default', () => { 30 | dv.transform({ 31 | type: 'bin.hexagon', 32 | fields: ['a', 'b'], 33 | }); 34 | expect(dv.rows[0].x.length).to.equal(6); 35 | expect(dv.rows[0].y.length).to.equal(6); 36 | }); 37 | 38 | it('bins', () => { 39 | dv.transform({ 40 | type: 'bin.hexagon', 41 | fields: ['a', 'b'], 42 | bins: [2, 2], 43 | }); 44 | expect(dv.rows.length).to.equal(5); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/transform/bin/histogram-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../../src'; 3 | import { View } from '../../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): bin.histogram', () => { 7 | const data = []; 8 | for (let i = 0; i <= 100; i++) { 9 | data.push({ 10 | a: i, 11 | }); 12 | } 13 | const ds = new DataSet(); 14 | let dv: View; 15 | beforeEach(() => { 16 | dv = ds.createView().source(data); 17 | }); 18 | 19 | it('api', () => { 20 | expect(getTransform('bin.histogram')).to.be.a('function'); 21 | expect(getTransform('bin.dot')).to.be.a('function'); 22 | }); 23 | /** 改为Sturges formula 更符合数学的意义*/ 24 | it('default', () => { 25 | dv.transform({ 26 | type: 'bin.histogram', 27 | field: 'a', 28 | }); 29 | const firstRow = dv.rows[0]; 30 | const binNumber = Math.ceil(Math.log(data.length) / Math.LN2) + 1; 31 | // binWidth = width / binNumber; 32 | expect(firstRow.x[1] - firstRow.x[0]).to.equal(100 / binNumber); 33 | }); 34 | 35 | it('bins', () => { 36 | dv.transform({ 37 | type: 'bin.histogram', 38 | field: 'a', 39 | bins: 10, 40 | }); 41 | expect(dv.rows[0]).to.eql({ x: [0, 10], count: 10 }); 42 | expect(dv.rows[10]).to.eql({ x: [100, 110], count: 1 }); 43 | }); 44 | 45 | it('binWidth', () => { 46 | dv.transform({ 47 | type: 'bin.histogram', 48 | field: 'a', 49 | binWidth: 10, 50 | }); 51 | expect(dv.rows[0]).to.eql({ x: [0, 10], count: 10 }); 52 | expect(dv.rows[10]).to.eql({ x: [100, 110], count: 1 }); 53 | }); 54 | 55 | it('binWidth', () => { 56 | const emptyDv = ds.createView().source([]); 57 | expect(() => { 58 | emptyDv.transform({ 59 | type: 'bin.histogram', 60 | field: 'a', 61 | }); 62 | }).to.not.throw(); 63 | emptyDv.transform({ 64 | type: 'bin.histogram', 65 | field: 'a', 66 | }); 67 | expect(emptyDv.rows.length).to.equal(0); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/unit/transform/bin/quantile-spec.ts: -------------------------------------------------------------------------------- 1 | import { clone } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../../src'; 4 | import { View } from '../../../../src/view'; 5 | const { getTransform } = DataSet; 6 | 7 | describe('View.transform(): bin.quantile', () => { 8 | const data = [ 9 | { x: 1, y: 1, z: 1 }, 10 | { x: 2, y: 1, z: 2 }, 11 | { x: 3, y: 1, z: 3 }, 12 | { x: 4, y: 1, z: 4 }, 13 | { x: 1, y: 2, z: 5 }, 14 | { x: 2, y: 2, z: 6 }, 15 | { x: 3, y: 2, z: 7 }, 16 | { x: 4, y: 2, z: 8 }, 17 | ]; 18 | const ds = new DataSet(); 19 | let dv: View; 20 | beforeEach(() => { 21 | dv = ds.createView().source(clone(data)); 22 | }); 23 | 24 | it('api', () => { 25 | expect(getTransform('bin.quantile')).to.be.a('function'); 26 | expect(() => { 27 | dv.transform({ 28 | type: 'bin.quantile', 29 | }); 30 | }).to.throw(); 31 | }); 32 | 33 | it('fields', () => { 34 | dv.transform({ 35 | type: 'bin.quantile', 36 | field: 'z', 37 | }); 38 | const rows = dv.rows; 39 | expect(rows[0]._bin.length).to.equal(5); 40 | }); 41 | 42 | it('as', () => { 43 | dv.transform({ 44 | type: 'bin.quantile', 45 | field: 'z', 46 | as: '_z', 47 | }); 48 | const rows = dv.rows; 49 | expect(rows[0]._z.length).to.equal(5); 50 | }); 51 | 52 | it('grouBy', () => { 53 | dv.transform({ 54 | type: 'bin.quantile', 55 | field: 'z', 56 | groupBy: ['x'], 57 | as: '_z', 58 | }); 59 | const rows = dv.rows; 60 | expect(rows.length).to.equal(4); 61 | expect(rows[0]._z.length).to.equal(5); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/unit/transform/bin/rectangle-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../../src'; 3 | import { View } from '../../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): bin.rectangle', () => { 7 | const data = []; 8 | for (let i = 0; i <= 100; i++) { 9 | data.push({ 10 | a: i, 11 | b: i, 12 | }); 13 | } 14 | const ds = new DataSet(); 15 | let dv: View; 16 | beforeEach(() => { 17 | dv = ds.createView().source(data); 18 | }); 19 | 20 | it('api', () => { 21 | expect(getTransform('bin.rectangle')).to.be.a('function'); 22 | expect(getTransform('bin.rect')).to.be.a('function'); 23 | }); 24 | 25 | it('default', () => { 26 | dv.transform({ 27 | type: 'bin.rectangle', 28 | fields: ['a', 'b'], 29 | }); 30 | expect(dv.rows.length).to.equal(31); 31 | expect(dv.rows[30].count).to.equal(1); 32 | }); 33 | 34 | it('bins', () => { 35 | dv.transform({ 36 | type: 'bin.rectangle', 37 | fields: ['a', 'b'], 38 | bins: [20, 20], 39 | }); 40 | expect(dv.rows.length).to.equal(21); 41 | expect(dv.rows[20].count).to.equal(1); 42 | }); 43 | 44 | it('binWidth', () => { 45 | dv.transform({ 46 | type: 'bin.rectangle', 47 | fields: ['a', 'b'], 48 | binWidth: [20, 20], 49 | }); 50 | expect(dv.rows.length).to.equal(6); 51 | expect(dv.rows[5].count).to.equal(1); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/unit/transform/default-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getTransform } = DataSet; 4 | import populationChina from '../../fixtures/population-china.json'; 5 | import { View } from '../../../src/view'; 6 | 7 | describe('View.transform(): default', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView().source(populationChina); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getTransform()).to.be.a('function'); 16 | expect(getTransform('default')).to.be.a('function'); 17 | expect(getTransform('this-transform-is-not-exists-xxx')).to.be.a('function'); 18 | }); 19 | 20 | it('default', () => { 21 | dv.transform(); 22 | expect(dv.rows).to.eql(populationChina); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/unit/transform/diagram/voronoi-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../../src'; 3 | import { View } from '../../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): diagram.voronoi', () => { 7 | const ds = new DataSet(); 8 | let dv: View; 9 | const data = []; 10 | for (let i = 0; i < 10; i++) { 11 | data.push({ 12 | x: Math.random() * 100, 13 | y: Math.random() * 100, 14 | }); 15 | } 16 | beforeEach(() => { 17 | dv = ds.createView().source(data); 18 | }); 19 | 20 | it('api', () => { 21 | expect(getTransform('diagram.voronoi')).to.be.a('function'); 22 | expect(getTransform('voronoi')).to.be.a('function'); 23 | }); 24 | 25 | it('default', () => { 26 | expect(() => { 27 | dv.transform({ 28 | type: 'diagram.voronoi', 29 | as: ['_x', '_y'], 30 | }); 31 | }).to.throw(); 32 | expect(() => { 33 | dv.transform({ 34 | type: 'diagram.voronoi', 35 | fields: ['x', 'y'], 36 | as: ['_x', '_y', 'extra'], 37 | }); 38 | }).to.throw(); 39 | }); 40 | 41 | it('voronoi', () => { 42 | dv.transform({ 43 | type: 'diagram.voronoi', 44 | fields: ['x', 'y'], 45 | as: ['_x', '_y'], 46 | }); 47 | const rows = dv.rows; 48 | const firstRow = rows[0]; 49 | expect(firstRow._x).to.be.an('array'); 50 | expect(firstRow._y).to.be.an('array'); 51 | expect(firstRow._x.length).to.equal(firstRow._y.length); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/unit/transform/fill-rows-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | import { View } from '../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): fill-rows', () => { 7 | const data = [ 8 | { x: 0, y: 28, c: 0 }, 9 | { x: 0, y: 55, c: 1 }, 10 | { x: 1, y: 43, c: 0 }, 11 | { x: 1, y: 91, c: 1 }, 12 | { x: 2, y: 81, c: 0 }, 13 | { x: 2, y: 53, c: 1 }, 14 | { x: 3, y: 19, c: 0 }, 15 | ]; 16 | const originLength = data.length; 17 | const ds = new DataSet(); 18 | let dv: View; 19 | 20 | beforeEach(() => { 21 | dv = ds.createView().source(data); 22 | }); 23 | 24 | it('api', () => { 25 | expect(getTransform('fill-rows')).to.be.a('function'); 26 | expect(getTransform('fillRows')).to.be.a('function'); 27 | }); 28 | 29 | it('default', () => { 30 | dv.transform({ 31 | type: 'fill-rows', 32 | }); 33 | expect(dv.rows.length).to.equal(originLength); 34 | }); 35 | 36 | it('groupBy', () => { 37 | dv.transform({ 38 | type: 'fill-rows', 39 | groupBy: ['x'], 40 | }); 41 | const rows = dv.rows; 42 | expect(rows.length).to.equal(originLength + 1); 43 | expect(rows[originLength]).to.eql({ 44 | x: 3, 45 | }); 46 | }); 47 | 48 | it('orderBy', () => { 49 | dv.transform({ 50 | type: 'fill-rows', 51 | orderBy: ['c'], 52 | }); 53 | expect(dv.rows.length).to.equal(originLength); 54 | }); 55 | 56 | it('groupBy and orderBy', () => { 57 | dv.transform({ 58 | type: 'fill-rows', 59 | groupBy: ['x'], 60 | orderBy: ['c'], 61 | }); 62 | const rows = dv.rows; 63 | expect(rows.length).to.equal(originLength + 1); 64 | expect(rows[originLength]).to.eql({ 65 | x: 3, 66 | c: 1, 67 | }); 68 | }); 69 | }); 70 | 71 | describe('View.transform(): fill-rows: fillBy order', () => { 72 | const data = [ 73 | { x: 0, y: 28, c: 1 }, 74 | { x: 0, y: 55, c: 2 }, 75 | { x: 1, y: 43, c: 3 }, 76 | { x: 1, y: 91, c: 4 }, 77 | { x: 2, y: 81, c: 5 }, 78 | { x: 2, y: 53, c: 6 }, 79 | ]; 80 | const ds = new DataSet(); 81 | const dv = ds.createView().source(data); 82 | dv.transform({ 83 | type: 'fill-rows', 84 | fillBy: 'order', 85 | groupBy: ['x'], 86 | orderBy: ['c'], 87 | }); 88 | it('fillBy order', () => { 89 | const rows = dv.rows; 90 | expect(rows.length).to.equal(18); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/unit/transform/filter-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getTransform } = DataSet; 4 | import populationChina from '../../fixtures/population-china.json'; 5 | import { View } from '../../../src/view'; 6 | 7 | describe('View.transform(): filter', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView().source(populationChina); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getTransform('filter')).to.be.a('function'); 16 | }); 17 | 18 | it('default', () => { 19 | dv.transform({ 20 | type: 'filter', 21 | }); 22 | expect(dv.rows.length).to.equal(dv.origin.length); 23 | }); 24 | 25 | it('callback', () => { 26 | dv.transform({ 27 | type: 'filter', 28 | callback(row) { 29 | return row.year > '2002'; // origin data range: [2002, 2015] 30 | }, 31 | }); 32 | expect(dv.rows.length).to.equal(dv.origin.length - 1); 33 | }); 34 | 35 | it('empty condiction', () => { 36 | dv.transform({ 37 | type: 'filter', 38 | callback(row) { 39 | return row.year > '2100'; // origin data range: [2002, 2015] 40 | }, 41 | }); 42 | expect(dv.rows.length).to.equal(0); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/unit/transform/geo/centroid-spec.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../../src'; 4 | const { getTransform } = DataSet; 5 | import geoWorld from '../../../fixtures/countries-geo.json'; 6 | import { View } from '../../../../src/view'; 7 | 8 | describe('View.transform(): geo.centroid', () => { 9 | const data = [ 10 | { 11 | name: 'Afghanistan', 12 | value: 4, 13 | }, 14 | { 15 | name: 'Angola', 16 | value: 5, 17 | }, 18 | ]; 19 | const ds = new DataSet(); 20 | let dv: View; 21 | ds.createView('geo').source(geoWorld, { 22 | type: 'geo', 23 | }); 24 | 25 | beforeEach(() => { 26 | dv = ds.createView().source(data); 27 | }); 28 | 29 | it('api', () => { 30 | expect(getTransform('geo.centroid')).to.be.a('function'); 31 | }); 32 | 33 | it('default', () => { 34 | expect(() => { 35 | dv.transform({ 36 | type: 'geo.centroid', 37 | }); 38 | }).to.throw(); 39 | expect(() => { 40 | dv.transform({ 41 | type: 'geo.centroid', 42 | geoView: 'geo', 43 | }); 44 | }).to.throw(); 45 | }); 46 | 47 | it('geo.centroid', () => { 48 | dv.transform({ 49 | type: 'geo.centroid', 50 | field: 'name', 51 | geoView: 'geo', 52 | }); 53 | const rows = dv.rows; 54 | expect(isNumber(rows[0]._centroid_x)).to.be.true; 55 | expect(isNumber(rows[0]._centroid_y)).to.be.true; 56 | }); 57 | 58 | // projected 59 | const geoView = ds.getView('geo'); 60 | geoView.transform({ 61 | type: 'geo.projection', 62 | projection: 'geoAiry', 63 | }); 64 | 65 | it('geo.centroid: on a projected data view', () => { 66 | dv.transform({ 67 | type: 'geo.centroid', 68 | field: 'name', 69 | geoView: 'geo', 70 | }); 71 | const firstRow = dv.rows[0]; 72 | const feature = geoView.geoFeatureByName(firstRow.name); 73 | expect(firstRow._centroid_x).to.equal(feature._centroid_x); 74 | expect(firstRow._centroid_y).to.equal(feature._centroid_y); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/unit/transform/geo/projection-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../../src'; 3 | const { getTransform } = DataSet; 4 | import geoWorld from '../../../fixtures/countries-geo.json'; 5 | import { View } from '../../../../src/view'; 6 | 7 | describe('View.transform(): geo.projection', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView(); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getTransform('geo.projection')).to.be.a('function'); 16 | }); 17 | 18 | it('default', () => { 19 | expect(() => { 20 | dv.transform({ 21 | type: 'geo.projection', 22 | projection: 'geoAiry', 23 | as: ['x', 'y', 'centroidX', 'centroidY'], 24 | }); 25 | }).to.throw(); 26 | expect(() => { 27 | dv.source(geoWorld, { 28 | type: 'geo', 29 | }).transform({ 30 | type: 'geo.projection', 31 | as: ['x', 'y', 'centroidX', 'centroidY'], 32 | }); 33 | }).to.throw(); 34 | }); 35 | 36 | it('geo', () => { 37 | dv.source(geoWorld, { 38 | type: 'geo', 39 | }).transform({ 40 | type: 'geo.projection', 41 | projection: 'geoAiry', 42 | as: ['x', 'y', 'centroidX', 'centroidY'], 43 | }); 44 | const features = dv.rows; 45 | const feature = features[0]; 46 | expect(feature.x).to.exist; 47 | expect(feature.y).to.exist; 48 | expect(feature.centroidX).to.exist; 49 | expect(feature.centroidY).to.exist; 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/unit/transform/geo/region-spec.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../../src'; 4 | const { getTransform } = DataSet; 5 | import geoWorld from '../../../fixtures/countries-geo.json'; 6 | import { View } from '../../../../src/view'; 7 | 8 | describe('View.transform(): geo.region', () => { 9 | const data = [ 10 | { 11 | name: 'Afghanistan', 12 | value: 4, 13 | }, 14 | { 15 | name: 'Angola', 16 | value: 5, 17 | }, 18 | ]; 19 | const ds = new DataSet(); 20 | let dv: View; 21 | ds.createView('geo').source(geoWorld, { 22 | type: 'geo', 23 | }); 24 | 25 | beforeEach(() => { 26 | dv = ds.createView().source(data); 27 | }); 28 | 29 | it('api', () => { 30 | expect(getTransform('geo.region')).to.be.a('function'); 31 | }); 32 | 33 | it('default', () => { 34 | expect(() => { 35 | dv.transform({ 36 | type: 'geo.region', 37 | }); 38 | }).to.throw(); 39 | expect(() => { 40 | dv.transform({ 41 | type: 'geo.region', 42 | geoView: 'geo', 43 | }); 44 | }).to.throw(); 45 | }); 46 | 47 | it('geo.region', () => { 48 | dv.transform({ 49 | type: 'geo.region', 50 | field: 'name', 51 | geoView: 'geo', 52 | }); 53 | const rows = dv.rows; 54 | expect(isArray(rows[0]._x)).to.be.true; 55 | expect(isArray(rows[0]._y)).to.be.true; 56 | }); 57 | 58 | // projected 59 | const geoView = ds.getView('geo'); 60 | geoView.transform({ 61 | type: 'geo.projection', 62 | projection: 'geoAiry', 63 | }); 64 | 65 | it('geo.region: on a projected data view', () => { 66 | dv.transform({ 67 | type: 'geo.region', 68 | field: 'name', 69 | geoView: 'geo', 70 | }); 71 | const firstRow = dv.rows[0]; 72 | const feature = geoView.geoFeatureByName(firstRow.name); 73 | expect(firstRow._x).to.equal(feature._x); 74 | expect(firstRow._y).to.equal(feature._y); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/unit/transform/hierarchy/compact-box-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../../src'; 3 | import flare from '../../../fixtures/flare.json'; 4 | import { View } from '../../../../src/view'; 5 | const { getTransform } = DataSet; 6 | 7 | describe('View.transform(): hierarchy.compact-box', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView(); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getTransform('hierarchy.compact-box')).to.be.a('function'); 16 | expect(getTransform('compact-box-tree')).to.be.a('function'); 17 | expect(getTransform('non-layered-tidy-tree')).to.be.a('function'); 18 | expect(getTransform('mindmap-logical')).to.be.a('function'); 19 | }); 20 | 21 | it('default', () => { 22 | expect(() => { 23 | dv.transform({ 24 | type: 'hierarchy.compact-box', 25 | }); 26 | }).to.throw(); 27 | expect(() => { 28 | dv.source(flare, { 29 | type: 'hierarchy', 30 | }).transform({ 31 | type: 'hierarchy.compact-box', 32 | }); 33 | }).to.not.throw(); 34 | }); 35 | 36 | it('default layout', () => { 37 | dv.source(flare, { 38 | type: 'hierarchy', 39 | }).transform({ 40 | type: 'hierarchy.compact-box', 41 | }); 42 | const root = dv.root; 43 | expect(root.x).to.be.a('number'); 44 | expect(root.y).to.be.a('number'); 45 | expect(root.width).to.be.a('number'); 46 | expect(root.height).to.be.a('number'); 47 | }); 48 | 49 | it('mindmap horizontal layout', () => { 50 | dv.source(flare, { 51 | type: 'hierarchy', 52 | }).transform({ 53 | type: 'hierarchy.compact-box', 54 | direction: 'H', 55 | }); 56 | const root = dv.root; 57 | const children = root.children; 58 | expect(children[0].side).to.equal('right'); 59 | expect(children[children.length - 1].side).to.equal('left'); 60 | }); 61 | 62 | it('mindmap horizontal layout: getSide', () => { 63 | dv.source(flare, { 64 | type: 'hierarchy', 65 | }).transform({ 66 | type: 'hierarchy.compact-box', // compact-box-tree, non-layered-tidy-tree, mindmap-logical 67 | direction: 'H', 68 | getSide(child, index) { 69 | if (index) { 70 | return 'left'; 71 | } 72 | return 'right'; 73 | }, 74 | }); 75 | const root = dv.root; 76 | const children = root.children; 77 | expect(children[0].side).to.equal('right'); 78 | for (let i = 1; i < children.length; i++) { 79 | expect(children[i].side).to.equal('left'); 80 | } 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/unit/transform/hierarchy/treemap-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../../src'; 3 | const { getTransform } = DataSet; 4 | import flare from '../../../fixtures/flare.json'; 5 | import { View } from '../../../../src/view'; 6 | 7 | describe('View.transform(): hierarchy.treemap', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView(); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getTransform('hierarchy.treemap')).to.be.a('function'); 16 | expect(getTransform('treemap')).to.be.a('function'); 17 | }); 18 | 19 | it('default', () => { 20 | expect(() => { 21 | dv.transform({ 22 | type: 'hierarchy.treemap', 23 | as: ['x', 'y'], 24 | }); 25 | }).to.throw(); 26 | expect(() => { 27 | dv.source(flare, { 28 | type: 'hierarchy', 29 | }).transform({ 30 | type: 'hierarchy.treemap', 31 | as: ['x', 'y', 'extra'], 32 | }); 33 | }).to.throw(); 34 | }); 35 | 36 | it('treemap', () => { 37 | dv.source(flare, { 38 | type: 'hierarchy', 39 | }).transform({ 40 | type: 'hierarchy.treemap', 41 | as: ['x', 'y'], 42 | }); 43 | const root = dv.root; 44 | expect(root.x).to.be.an('array'); 45 | expect(root.y).to.be.an('array'); 46 | expect(root.x.length).to.equal(4); 47 | expect(root.y.length).to.equal(4); 48 | // console.log(root) 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/unit/transform/impute-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | import { View } from '../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): impute', () => { 7 | const data = [ 8 | { x: 0, y: 1 }, 9 | { x: 0, y: 2 }, 10 | { x: 0, y: 3 }, 11 | { x: 0 }, 12 | { x: 1, y: 5 }, 13 | { x: 1, y: 6 }, 14 | { x: 1, y: 7 }, 15 | { x: 1 }, 16 | { x: 1, y: 9 }, 17 | { x: 2, y: null }, 18 | { x: 2 }, 19 | ]; 20 | // const length = data.length; 21 | const ds = new DataSet(); 22 | let dv: View; 23 | 24 | beforeEach(() => { 25 | dv = ds.createView().source(data); 26 | }); 27 | 28 | it('api', () => { 29 | expect(getTransform('impute')).to.be.a('function'); 30 | }); 31 | 32 | it('default', () => { 33 | expect(() => { 34 | // @ts-ignore 35 | dv.transform({ type: 'impute' }); 36 | }).to.throw(); 37 | }); 38 | 39 | it('value', () => { 40 | dv.transform({ 41 | field: 'y', 42 | groupBy: ['x'], 43 | method: 'value', 44 | type: 'impute', 45 | value: 10, 46 | }); 47 | const rows = dv.rows; 48 | expect(rows[3].y).to.equal(10); 49 | expect(rows[7].y).to.equal(10); 50 | expect(rows[9].y).to.equal(null); 51 | }); 52 | 53 | it('max', () => { 54 | dv.transform({ 55 | field: 'y', 56 | groupBy: ['x'], 57 | method: 'max', 58 | type: 'impute', 59 | }); 60 | const rows = dv.rows; 61 | expect(rows[3].y).to.equal(3); 62 | expect(rows[7].y).to.equal(9); 63 | expect(rows[9].y).to.equal(null); 64 | }); 65 | 66 | it('not grouping', () => { 67 | dv.transform({ 68 | field: 'y', 69 | method: 'max', 70 | type: 'impute', 71 | }); 72 | const rows = dv.rows; 73 | expect(rows[3].y).to.equal(9); 74 | expect(rows[7].y).to.equal(9); 75 | expect(rows[9].y).to.equal(null); 76 | }); 77 | 78 | it('groupBy & orderBy', () => { 79 | dv.transform({ 80 | field: 'y', 81 | groupBy: 'x', 82 | // TODO: 这里应该无效 83 | // @ts-ignore 84 | orderBy: 'x', 85 | method: 'max', 86 | type: 'impute', 87 | }); 88 | const rows = dv.rows; 89 | expect(rows[3].y).to.equal(3); 90 | expect(rows[7].y).to.equal(9); 91 | expect(rows[9].y).to.equal(null); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/unit/transform/kde-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getTransform } = DataSet; 4 | import iris from '../../fixtures/iris-en.json'; 5 | import { View } from '../../../src/view'; 6 | 7 | describe('View.transform(): KDE', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | const fields = ['petalWidth', 'petalLength', 'sepalWidth', 'sepalLength']; 11 | 12 | beforeEach(() => { 13 | dv = ds.createView().source(iris); 14 | }); 15 | 16 | it('api', () => { 17 | expect(getTransform('KDE')).to.be.a('function'); 18 | expect(getTransform('kde')).to.be.a('function'); 19 | expect(getTransform('kernel-density-estimation')).to.be.a('function'); 20 | }); 21 | 22 | it('default', () => { 23 | expect(() => { 24 | dv.transform({ 25 | type: 'KDE', 26 | }); 27 | }).to.throw(); 28 | }); 29 | 30 | it('value', () => { 31 | dv.transform({ 32 | type: 'kde', 33 | fields, 34 | }); 35 | const rows = dv.rows; 36 | expect(rows.length).to.equal(4); 37 | }); 38 | 39 | it('groupBy', () => { 40 | dv.transform({ 41 | type: 'kde', 42 | fields, 43 | groupBy: ['species'], 44 | }); 45 | const rows = dv.rows; 46 | expect(rows.length).to.equal(12); 47 | }); 48 | 49 | it('step', () => { 50 | dv.transform({ 51 | type: 'kde', 52 | fields, 53 | extent: [0, 8], 54 | step: 1, 55 | as: ['x', 'y', 'size'], 56 | }); 57 | dv.rows.forEach((row) => { 58 | expect(row.y.length <= (8 - 0) / 1 + 1).to.equal(true); 59 | }); 60 | }); 61 | 62 | it('extent', () => { 63 | dv.transform({ 64 | type: 'kde', 65 | fields, 66 | extent: [2, 6], 67 | step: 1, 68 | as: ['x', 'y', 'size'], 69 | }); 70 | dv.rows.forEach((row) => { 71 | expect(row.y.length <= (6 - 2) / 1 + 1).to.equal(true); 72 | }); 73 | }); 74 | 75 | it('minSize', () => { 76 | dv.transform({ 77 | type: 'kde', 78 | fields, 79 | minSize: 0.1, 80 | }); 81 | dv.rows.forEach((row) => { 82 | row.size.forEach((size) => { 83 | expect(size >= 0.1).to.equal(true); 84 | }); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/unit/transform/map-spec.ts: -------------------------------------------------------------------------------- 1 | import { map } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../src'; 4 | const { getTransform } = DataSet; 5 | import populationChina from '../../fixtures/population-china.json'; 6 | import { View } from '../../../src/view'; 7 | 8 | describe('View.transform(): map', () => { 9 | const ds = new DataSet(); 10 | let dv: View; 11 | 12 | beforeEach(() => { 13 | dv = ds.createView().source(populationChina); 14 | }); 15 | 16 | it('api', () => { 17 | expect(getTransform('map')).to.be.a('function'); 18 | }); 19 | 20 | it('default', () => { 21 | dv.transform({ 22 | type: 'map', 23 | }); 24 | expect(dv.rows.length).to.equal(populationChina.length); 25 | }); 26 | 27 | it('callback', () => { 28 | dv.transform({ 29 | type: 'map', 30 | callback(row) { 31 | return row.year; // origin data range: [2002, 2015] 32 | }, 33 | }); 34 | expect(dv.rows).to.eql(map(populationChina, (row) => row.year)); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/transform/percent-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | import { View } from '../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): percent', () => { 7 | const data = [ 8 | { x: 1, y: 1, z: 1, extra: 'test' }, 9 | { x: 2, y: 1, z: 2, extra: 'test' }, 10 | { x: 3, y: 1, z: 3, extra: 'test' }, 11 | { x: 4, y: 1, z: 4, extra: 'test' }, 12 | { x: 1, y: 2, z: 5, extra: 'test' }, 13 | { x: 2, y: 2, z: 6, extra: 'test' }, 14 | { x: 3, y: 2, z: 7, extra: 'test' }, 15 | { x: 4, y: 2, z: 8, extra: 'test' }, 16 | ]; 17 | const ds = new DataSet(); 18 | let dv: View; 19 | 20 | beforeEach(() => { 21 | dv = ds.createView().source(data); 22 | }); 23 | 24 | it('api', () => { 25 | expect(getTransform('percent')).to.be.a('function'); 26 | expect(() => { 27 | // @ts-ignore 28 | dv.transform({ type: 'percent' }); 29 | }).to.throw(); 30 | }); 31 | 32 | it('default', () => { 33 | dv.transform({ 34 | type: 'percent', 35 | field: 'z', 36 | dimension: 'y', 37 | }); 38 | expect(dv.rows.length).to.equal(2); 39 | expect(dv.rows[0]).to.eql({ 40 | x: 1, 41 | y: 1, 42 | z: 10, 43 | extra: 'test', 44 | _percent: 10 / 36, 45 | }); 46 | }); 47 | 48 | it('as', () => { 49 | dv.transform({ 50 | type: 'percent', 51 | field: 'z', 52 | dimension: 'y', 53 | as: '_z', 54 | }); 55 | expect(dv.rows.length).to.equal(2); 56 | expect(dv.rows[0]).to.eql({ 57 | x: 1, 58 | y: 1, 59 | z: 10, 60 | extra: 'test', 61 | _z: 10 / 36, 62 | }); 63 | }); 64 | 65 | it('groupBy', () => { 66 | dv.transform({ 67 | type: 'percent', 68 | field: 'z', 69 | dimension: 'y', 70 | groupBy: ['x'], 71 | as: '_z', 72 | }); 73 | expect(dv.rows.length).to.equal(8); 74 | expect(dv.rows[0]).to.eql({ 75 | x: 1, 76 | y: 1, 77 | z: 1, 78 | extra: 'test', 79 | _z: 1 / 6, 80 | }); 81 | }); 82 | 83 | it('when dimension and field is the same', () => { 84 | dv.transform({ 85 | type: 'percent', 86 | field: 'y', 87 | dimension: 'y', 88 | as: '_y', 89 | }); 90 | expect(dv.rows.length).to.equal(2); 91 | expect(dv.rows[0]).to.eql({ 92 | x: 1, 93 | y: 1, 94 | z: 1, 95 | extra: 'test', 96 | _y: 4 / 12, 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/unit/transform/pick-spec.ts: -------------------------------------------------------------------------------- 1 | import { map, pick } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../src'; 4 | const { getTransform } = DataSet; 5 | import populationChina from '../../fixtures/population-china.json'; 6 | import { View } from '../../../src/view'; 7 | 8 | describe('View.transform(): pick', () => { 9 | const ds = new DataSet(); 10 | let dv: View; 11 | beforeEach(() => { 12 | dv = ds.createView().source(populationChina); 13 | }); 14 | 15 | it('api', () => { 16 | expect(getTransform('pick')).to.be.a('function'); 17 | }); 18 | 19 | it('default', () => { 20 | dv.transform({ 21 | type: 'pick', 22 | }); 23 | expect(dv.rows).to.eql(populationChina); 24 | }); 25 | 26 | it('fields', () => { 27 | dv.transform({ 28 | type: 'pick', 29 | fields: ['year'], 30 | }); 31 | expect(dv.rows).to.eql(map(populationChina, (row) => pick(row, ['year']))); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/unit/transform/proportion-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | import { View } from '../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): proportion', () => { 7 | const ds = new DataSet(); 8 | const data = [ 9 | { x: 1, y: 1, z: 1 }, 10 | { x: 2, y: 1, z: 2 }, 11 | { x: 3, y: 1, z: 3 }, 12 | { x: 4, y: 1, z: 4 }, 13 | { x: 1, y: 2, z: 5 }, 14 | { x: 2, y: 2, z: 6 }, 15 | { x: 3, y: 2, z: 7 }, 16 | { x: 4, y: 2, z: 8 }, 17 | ]; 18 | let dv: View; 19 | 20 | beforeEach(() => { 21 | dv = ds.createView().source(data); 22 | }); 23 | 24 | it('api', () => { 25 | expect(getTransform('proportion')).to.be.a('function'); 26 | expect(() => { 27 | // @ts-ignore 28 | dv.transform({ type: 'proportion' }); 29 | }).to.throw(); 30 | }); 31 | 32 | it('default', () => { 33 | dv.transform({ 34 | type: 'proportion', 35 | field: 'z', 36 | dimension: 'y', 37 | }); 38 | expect(dv.rows.length).to.equal(2); 39 | expect(dv.rows[0]).to.eql({ 40 | x: 1, 41 | y: 1, 42 | z: 4, 43 | _proportion: 0.5, 44 | }); 45 | }); 46 | 47 | it('as', () => { 48 | dv.transform({ 49 | type: 'proportion', 50 | field: 'z', 51 | dimension: 'y', 52 | as: '_z', 53 | }); 54 | expect(dv.rows.length).to.equal(2); 55 | expect(dv.rows[0]).to.eql({ 56 | x: 1, 57 | y: 1, 58 | z: 4, 59 | _z: 0.5, 60 | }); 61 | }); 62 | 63 | it('groupBy', () => { 64 | dv.transform({ 65 | type: 'proportion', 66 | field: 'z', 67 | dimension: 'y', 68 | groupBy: ['x'], 69 | as: '_z', 70 | }); 71 | expect(dv.rows.length).to.equal(8); 72 | expect(dv.rows[0]).to.eql({ 73 | x: 1, 74 | y: 1, 75 | z: 1, 76 | _z: 0.5, 77 | }); 78 | }); 79 | 80 | it('when dimension and field is the same', () => { 81 | dv.transform({ 82 | type: 'proportion', 83 | field: 'y', 84 | dimension: 'y', 85 | as: '_y', 86 | }); 87 | expect(dv.rows.length).to.equal(2); 88 | expect(dv.rows[0]).to.eql({ 89 | x: 1, 90 | y: 1, 91 | z: 1, 92 | _y: 1 / 2, 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/unit/transform/regression-spec.ts: -------------------------------------------------------------------------------- 1 | // const regression = require('regression'); 2 | import { each } from 'lodash'; 3 | import { expect } from 'chai'; 4 | import DataSet from '../../../src'; 5 | const { getTransform } = DataSet; 6 | import re from '../../../src/transform/regression'; 7 | import { View } from '../../../src/view'; 8 | const { REGRESSION_METHODS } = re; 9 | 10 | describe('View.transform(): regression', () => { 11 | const data = []; 12 | for (let i = 1; i <= 10; i++) { 13 | // 1~10 14 | const b = i % 2; 15 | data.push({ 16 | a: i, 17 | b, 18 | }); 19 | } 20 | const ds = new DataSet(); 21 | let dv: View; 22 | beforeEach(() => { 23 | dv = ds.createView().source(data); 24 | }); 25 | 26 | it('api', () => { 27 | expect(getTransform('regression')).to.be.a('function'); 28 | }); 29 | 30 | it('default: linear', () => { 31 | dv.transform({ 32 | type: 'regression', 33 | fields: ['a', 'b'], 34 | }); 35 | expect(dv.rows.length).to.equal(6); 36 | }); 37 | 38 | each(REGRESSION_METHODS, (method) => { 39 | it(`method: ${method}`, () => { 40 | dv.transform({ 41 | type: 'regression', 42 | fields: ['a', 'b'], 43 | }); 44 | expect(dv.rows.length).to.equal(6); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/transform/rename-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | import { View } from '../../../src/view'; 4 | const { getTransform } = DataSet; 5 | 6 | describe('View.transform(): rename', () => { 7 | const data = [{ a: 1, b: 2 }]; 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView().source(data); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getTransform('rename')).to.be.a('function'); 16 | expect(getTransform('rename-fields')).to.be.a('function'); 17 | expect(getTransform('rename-fields')).to.equal(getTransform('rename')); 18 | }); 19 | 20 | it('default', () => { 21 | dv.transform({ 22 | type: 'rename', 23 | }); 24 | expect(dv.rows).to.eql(data); 25 | }); 26 | 27 | it('map', () => { 28 | dv.transform({ 29 | type: 'rename', 30 | map: { 31 | a: 'c', 32 | }, 33 | }); 34 | expect(dv.rows).to.eql([{ c: 1, b: 2 }]); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/transform/reverse-spec.ts: -------------------------------------------------------------------------------- 1 | import { reverse } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../src'; 4 | const { getTransform } = DataSet; 5 | import populationChina from '../../fixtures/population-china.json'; 6 | import { View } from '../../../src/view'; 7 | 8 | describe('View.transform(): reverse', () => { 9 | const ds = new DataSet(); 10 | let dv: View; 11 | 12 | beforeEach(() => { 13 | dv = ds.createView().source(populationChina); 14 | }); 15 | 16 | it('api', () => { 17 | expect(getTransform('reverse')).to.be.a('function'); 18 | }); 19 | 20 | it('default', () => { 21 | dv.transform({ 22 | type: 'reverse', 23 | }); 24 | expect(dv.rows).to.eql(reverse(populationChina)); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/transform/sort-by-spec.ts: -------------------------------------------------------------------------------- 1 | import { reverse, sortBy } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../src'; 4 | const { getTransform } = DataSet; 5 | import populationChina from '../../fixtures/population-china.json'; 6 | import { View } from '../../../src/view'; 7 | const data = populationChina.concat({ 8 | year: '2001', 9 | population: '1274530000', 10 | }); 11 | 12 | describe('View.transform(): sort-by', () => { 13 | const ds = new DataSet(); 14 | let dv: View; 15 | 16 | beforeEach(() => { 17 | dv = ds.createView().source(data); 18 | }); 19 | 20 | it('api', () => { 21 | expect(getTransform('sort-by')).to.be.a('function'); 22 | expect(getTransform('sortBy')).to.be.a('function'); 23 | }); 24 | 25 | it('default', () => { 26 | dv.transform({ 27 | type: 'sort-by', 28 | }); 29 | expect(dv.rows).to.eql(data.sort((a, b) => a.year - b.year)); 30 | }); 31 | 32 | it('specify columns', () => { 33 | dv.transform({ 34 | type: 'sort-by', 35 | fields: ['year'], 36 | }); 37 | expect(dv.rows).to.eql(sortBy(data, ['year'])); 38 | }); 39 | 40 | it('specify order', () => { 41 | dv.transform({ 42 | type: 'sort-by', 43 | fields: ['year'], 44 | order: 'DESC', 45 | }); 46 | expect(dv.rows).to.eql(reverse(sortBy(data, ['year']))); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/unit/transform/sort-spec.ts: -------------------------------------------------------------------------------- 1 | import { sortBy } from 'lodash'; 2 | import { expect } from 'chai'; 3 | import DataSet from '../../../src'; 4 | const { getTransform } = DataSet; 5 | import populationChina from '../../fixtures/population-china.json'; 6 | import { View } from '../../../src/view'; 7 | const data = populationChina.concat({ 8 | year: '2001', 9 | population: '1274530000', 10 | }); 11 | 12 | describe('View.transform(): sort', () => { 13 | const ds = new DataSet(); 14 | let dv: View; 15 | 16 | beforeEach(() => { 17 | dv = ds.createView().source(data); 18 | }); 19 | 20 | it('api', () => { 21 | expect(getTransform('sort')).to.be.a('function'); 22 | }); 23 | 24 | it('default', () => { 25 | dv.transform({ 26 | type: 'sort', 27 | }); 28 | expect(dv.rows).to.eql(data.sort((a, b) => a.year - b.year)); 29 | }); 30 | 31 | it('callback', () => { 32 | dv.transform({ 33 | type: 'sort', 34 | callback(a, b) { 35 | return a.year - b.year; 36 | }, 37 | }); 38 | expect(dv.rows).to.eql(sortBy(data, ['year'])); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/unit/transform/subset-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import DataSet from '../../../src'; 3 | const { getTransform } = DataSet; 4 | import populationChina from '../../fixtures/population-china.json'; 5 | import { View } from '../../../src/view'; 6 | 7 | describe('View.transform(): subset', () => { 8 | const ds = new DataSet(); 9 | let dv: View; 10 | beforeEach(() => { 11 | dv = ds.createView().source(populationChina); 12 | }); 13 | 14 | it('api', () => { 15 | expect(getTransform('subset')).to.be.a('function'); 16 | }); 17 | 18 | it('default', () => { 19 | dv.transform({ 20 | type: 'subset', 21 | }); 22 | expect(dv.rows).to.eql(populationChina); 23 | }); 24 | 25 | it('only specify endRowIndex', () => { 26 | dv.transform({ 27 | type: 'subset', 28 | endRowIndex: 2, 29 | }); 30 | expect(dv.rows.length).to.equal(3); 31 | expect(dv.getColumnNames().length).to.equal(2); 32 | }); 33 | 34 | it('only specify startRowIndex', () => { 35 | dv.transform({ 36 | type: 'subset', 37 | startRowIndex: 1, 38 | }); 39 | expect(dv.rows.length).to.equal(populationChina.length - 1); 40 | expect(dv.getColumnNames().length).to.equal(2); 41 | }); 42 | 43 | it('only specify columns', () => { 44 | dv.transform({ 45 | type: 'subset', 46 | fields: ['year'], 47 | }); 48 | expect(dv.rows.length).to.equal(populationChina.length); 49 | expect(dv.getColumnNames().length).to.equal(1); 50 | }); 51 | 52 | it('specify all options', () => { 53 | dv.transform({ 54 | type: 'subset', 55 | startRowIndex: 1, 56 | endRowIndex: 2, 57 | fields: ['year'], 58 | }); 59 | expect(dv.rows.length).to.equal(2); 60 | expect(dv.getColumnNames().length).to.equal(1); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/util/get-geo-projection-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as d3Geo from 'd3-geo'; 3 | import * as d3GeoProjection from 'd3-geo-projection'; 4 | import * as d3CompositeProjection from 'd3-composite-projections'; 5 | import getGeoProjection from '../../../src/util/get-geo-projection'; 6 | 7 | describe('util: getGeoProjection(rows, groupBy, orderBy)', () => { 8 | it('api', () => { 9 | expect(getGeoProjection).to.be.a('function'); 10 | }); 11 | 12 | it('default', () => { 13 | expect(getGeoProjection()).to.be.a('null'); 14 | expect(getGeoProjection(() => 'test')).to.be.a('string'); 15 | expect(getGeoProjection('geoAzimuthalEqualArea')).to.be.a('function'); 16 | expect(getGeoProjection('geoAzimuthalEqualArea')([0, 0])).to.eql(d3Geo.geoAzimuthalEqualArea()([0, 0])); 17 | expect(getGeoProjection('geoAiry')([0, 0])).to.eql(d3GeoProjection.geoAiry()([0, 0])); 18 | expect(getGeoProjection('geoAlbersUsaTerritories')).to.be.a('function'); 19 | expect(getGeoProjection('geoAlbersUsaTerritories')([0, 0])).to.eql( 20 | d3CompositeProjection.geoAlbersUsaTerritories()([0, 0]) 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/unit/util/p-by-fraction-spec.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antvis/data-set/0f68a94004a30b94005bd3496c2a5f3f2171f56d/test/unit/util/p-by-fraction-spec.ts -------------------------------------------------------------------------------- /test/unit/util/partition-spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { cloneDeep, orderBy, sortBy } from 'lodash'; 3 | import partition from '../../../src/util/partition'; 4 | import simpleSortBy from '../../../src/util/simple-sort-by'; 5 | import top2000 from '../../fixtures/top2000.json'; 6 | 7 | describe('util: partition(rows, groupBy, orderBy)', () => { 8 | const data = [ 9 | { x: 0, y: 28, c: 0 }, 10 | { x: 0, y: 55, c: 1 }, 11 | { x: 1, y: 43, c: 0 }, 12 | ]; 13 | it('api', () => { 14 | expect(partition).to.be.a('function'); 15 | }); 16 | 17 | it('groupBy', () => { 18 | const groups = partition(data, ['c'], ['x']); 19 | expect(groups._0.length).to.equal(2); 20 | expect(groups._1.length).to.equal(1); 21 | }); 22 | 23 | xit('performance', (done) => { 24 | // ignoring for coverage code injection will slow down the function 25 | // FIXME: remove `.skip` to execute performance test 26 | 27 | const top2000Cloned1 = cloneDeep(top2000); 28 | const top2000Cloned2 = cloneDeep(top2000); 29 | const t0 = Date.now(); 30 | sortBy(top2000, ['release', 'title']); 31 | const t1 = Date.now(); 32 | orderBy(top2000Cloned1, ['release', 'title']); 33 | const t2 = Date.now(); 34 | simpleSortBy(top2000Cloned2, ['release', 'title']); 35 | const t3 = Date.now(); 36 | expect(t3 - t2 < t2 - t1).to.equal(true); 37 | expect(t3 - t2 < t1 - t0).to.equal(true); 38 | // console.log(t3 - t2, t2 - t1, t1 - t0); 39 | done(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["dom", "es2017"], 6 | "experimentalDecorators": true, 7 | "emitDecoratorMetadata": true, 8 | "declaration": true, 9 | "allowJs": true, 10 | "esModuleInterop": true, 11 | "importHelpers": true, 12 | "downlevelIteration": true, 13 | "outDir": "./lib", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "moduleResolution": "node" 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /webpack-dev.config.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack.config'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = _.merge( 5 | { 6 | devtool: 'cheap-source-map', 7 | mode: 'development', 8 | watch: true, 9 | watchOptions: { 10 | aggregateTimeout: 300, 11 | poll: 1000, 12 | }, 13 | }, 14 | webpackConfig 15 | ); 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const resolve = require('path').resolve; 2 | const pkg = require('./package.json'); 3 | 4 | module.exports = { 5 | devtool: 'cheap-source-map', 6 | mode: 'development', 7 | entry: { 8 | 'data-set': './src/index.ts', 9 | }, 10 | resolve: { 11 | extensions: ['.js', '.ts', '.json'], 12 | mainFields: ['browser', 'main', 'module'], 13 | }, 14 | output: { 15 | filename: '[name].js', 16 | library: 'DataSet', 17 | libraryTarget: 'umd', 18 | path: resolve(__dirname, 'build/'), 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.(t|j)s$/, 24 | include: [ 25 | resolve(__dirname, 'src'), 26 | function(path) { 27 | return /d3-.*/.test(path); 28 | }, 29 | ], 30 | loader: 'awesome-typescript-loader', 31 | }, 32 | { 33 | test: /data\-set\.js$/, 34 | loader: 'string-replace-loader', 35 | options: { 36 | search: '____DATASET_VERSION____', 37 | replace: pkg.version, 38 | }, 39 | }, 40 | ], 41 | }, 42 | }; 43 | --------------------------------------------------------------------------------