Repository: ProtoTeam/tcharts.js Branch: master Commit: adeedecd5f7c Files: 41 Total size: 49.8 KB Directory structure: gitextract_4qpz9orr/ ├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── __tests__/ │ ├── axis.js │ ├── bar.js │ ├── box.js │ ├── hbar.js │ ├── index.js │ ├── line.js │ ├── point.js │ ├── rect.js │ ├── recttext.js │ ├── table.js │ ├── text.js │ └── utils.js ├── package.json ├── src/ │ ├── charts/ │ │ ├── Bar.js │ │ ├── Box.js │ │ ├── Chart.js │ │ ├── HBar.js │ │ └── Table.js │ ├── const.js │ ├── core/ │ │ ├── Axis.js │ │ ├── Element.js │ │ ├── Layer.js │ │ ├── Line.js │ │ ├── Point.js │ │ ├── Rect.js │ │ ├── RectText.js │ │ ├── Text.js │ │ └── index.js │ ├── index.js │ └── utils/ │ ├── array.js │ ├── invariant.js │ ├── number.js │ ├── string.js │ └── types.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["es2015", "stage-0", "react"], "env": { "test": { "presets": ["es2015", "stage-0", "react"] } } } ================================================ FILE: .eslintignore ================================================ benchmark dist ================================================ FILE: .eslintrc ================================================ { "env": { "browser": true, "node": true, "jest": true, "es6": true }, "parser": "babel-eslint", "parserOptions": { "sourceType": "module" }, "extends": [ "eslint-config-airbnb" ], "globals": { }, "rules": { "comma-dangle": 0, "consistent-return": 0, "func-names": 0, "global-require": 0, "import/no-extraneous-dependencies": 0, "import/no-unresolved": 0, "indent": 0, "key-spacing": 0, "linebreak-style": 0, "new-cap": 0, "no-cond-assign": 0, "no-empty": 0, "no-fallthrough": 0, "no-loop-func": 0, "no-mixed-operators": 0, "no-new": 0, "no-param-reassign": 0, "no-restricted-syntax": 0, "no-return-assign": 0, "no-underscore-dangle": 0, "object-curly-spacing": 0, "prefer-rest-params": 0, "react/jsx-filename-extension": 0, "react/jsx-space-before-closing": 0, "strict": 0, "max-len": [ 2, 120, 4, { "ignoreUrls": true } ], "id-length": [ 2, { "min": 1 } ], "class-methods-use-this": [ 1, { "exceptMethods": [ "CLASSNAME" ] } ] } } ================================================ FILE: .gitignore ================================================ .idea coverage node_modules .coveralls.yml npm-debug.* l.js ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - '4' - '4' - '5' - '6' - '7' - '8' after_success: - npm run coveralls ================================================ FILE: README.md ================================================ # tcharts.js > [TCharts.js](http://tcharts.org) is a Lightweight and fast terminal ASCII charts for nodejs and browser. [![Ver](https://img.shields.io/npm/v/tcharts.js.svg)](https://www.npmjs.com/package/tcharts.js) [![Build Status](https://travis-ci.org/ProtoTeam/tcharts.js.svg?branch=master)](https://travis-ci.org/ProtoTeam/tcharts.js) [![Coverage Status](https://coveralls.io/repos/github/ProtoTeam/tcharts.js/badge.svg?branch=master)](https://coveralls.io/github/ProtoTeam/tcharts.js) ``` +--------------+----------------------+---------------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | C:25% | Hello:25% | | | | | | | | | | A:25% | | | | | | | | | | | | +----------------------+---------------------+ | | | | | | | | | | | B:25% | | | | | | | +--------------+--------------------------------------------+ ``` ## 1. Install & Usage > npm i -S tcharts.js ### Table ```js const TCharts = require('tcharts.js'); const { Table } = TCharts; const table = new Table(0.2); // set gap rate = 0.2 table.setData([ ['標識符', '名字', '生日'], ['#1', '圖靈', 24], ['#2', '潘金蓮', false], ['#3', '西門慶', null], ['#4', '明日花绮罗'], ]); console.log(table.string()); ``` ### Bar ```js const TCharts = require('tcharts.js'); const { Bar } = TCharts; const bar = new Bar(); bar.setData([ {value:100, name:'A'}, {value:45, name:'B'}, {value:70, name:'C'}, {value:30, name:'D'}, ]); console.log(bar.string()); ``` ### HBar ```js const TCharts = require('tcharts.js'); const { HBar } = TCharts; const hbar = new HBar(); hbar.setData([ {value: 100, name: 'A'}, {value: 45, name: 'B'}, {value: 70, name: 'C'}, {value: 30, name: 'D'}, ]); console.log(hbar.string()); ``` ### Box ```js const TCharts = require('tcharts.js'); const { Box } = TCharts; const box = new Box(60, 20); // width, height box.setData([ {value:100, name:'A'}, {value:100, name:'B'}, {value:100, name:'C'}, {value:100, name:'Hello'}, ]); console.log(box.string()); ``` ## 2. Supported charts - `Bar`: bar chart, with x, y. - `HBar`: horizontal bar chart. - `Box`: box chart showing with a square. - `Table`: data table in terminal. How to use them, you can see the testcases in `__tests__` folder. ## 3. Build & Test ``` npm i npm run build npm test ``` Then you can see the result of test cases. ## 4. License ISC@[ProtoTeam](https://github.com/ProtoTeam). ================================================ FILE: __tests__/axis.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Point, Axis) => { test('1. draw a axis element.', () => { const point0 = new Point(0, 0); const pointX = new Point(20, 0); const pointY = new Point(0, 5); const axis = new Axis(point0, pointX, pointY); console.log(axis.toString()); const axisLayer = axis.draw(); expect(axisLayer.box).toEqual({ x1: 0, y1: 0, x2: 20, y2: 5, }); expect(axisLayer.array()).toEqual([ '^ '.split(''), '| '.split(''), '| '.split(''), '| '.split(''), '| '.split(''), '+------------------->'.split(''), ]); expect(axis.clone().draw().array()).toEqual([ '^ '.split(''), '| '.split(''), '| '.split(''), '| '.split(''), '| '.split(''), '+------------------->'.split(''), ]); console.log(axisLayer.string()); expect(axis.CLASSNAME).toBe('Axis'); expect(axisLayer.CLASSNAME).toBe('Layer'); }); }; ================================================ FILE: __tests__/bar.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Bar) => { test('1. draw a bar chart.', () => { let bar = new Bar(20); bar.setData([ {value:100, name:'A'}, {value:45, name:'B'}, {value:70, name:'C'}, {value:30, name:'D'}, ]); const r = ` ^ | A:100 | +--+ | | | | | | C:70 | | | +--+ | | | B:45 | | | | | +--+ | | D:30 | | | | | | | +--+ | | | | | | | | | | | | | | | | | | +--+--+--+--+--+--+--+--+-->`.trim(); expect(bar.string()).toBe(r); bar = new Bar(20, 0.1); bar.setData([ {value:100, name:'A'}, {value:45, name:'B'}, {value:70, name:'C'}, {value:30, name:'D'}, ]); console.log(bar.string()); bar = new Bar(); bar.setData([ {value:100, name:'A'}, {value:45, name:'B'}, {value:70, name:'C'}, {value:30, name:'D'}, ]); bar.array(); console.log(bar.string()); }); }; ================================================ FILE: __tests__/box.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Box) => { test('1. draw a box chart.', () => { let box = new Box(60, 20); box.setData([ {value:100, name:'A'}, {value:100, name:'B'}, {value:100, name:'C'}, {value:100, name:'Hello'}, ]); const r = ` +--------------+----------------------+---------------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | C:25% | Hello:25% | | | | | | | | | | A:25% | | | | | | | | | | | | +----------------------+---------------------+ | | | | | | | | | | | B:25% | | | | | | | +--------------+--------------------------------------------+`.trim(); expect(box.string()).toBe(r); box = new Box(); box.setData([ {value:100, name:'A'}, {value:100, name:'B'}, {value:100, name:'C'}, {value:100, name:'Hello'}, ]); console.log(box.string()); }); }; ================================================ FILE: __tests__/hbar.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (HBar) => { test('1. draw a hbar chart.', () => { const hbar = new HBar(); hbar.setData([ {value: 100, name: 'A'}, {value: 45, name: 'B'}, {value: 70, name: 'C'}, {value: 30, name: 'D'}, ]); console.log(hbar.string()); const r = ` ^ | +---------------+ | D:30| +---------------+ | +------------------------------------+ | C:70| +------------------------------------+ | +-----------------------+ | B:45| +-----------------------+ | +----------------------------------------------------+ | A:100| +----------------------------------------------------+ | +-----------------------------------------------------> `.trim(); expect(hbar.string()).toBe(r); }); }; ================================================ FILE: __tests__/index.js ================================================ /** * Created by hustcc on 17/6/21. */ // test for library and test // eslint-disable-next-line const { Box, Bar, HBar, Table } = require(process.env.NODE_ENV !== 'production' ? '../src/' : '../'); const { Axis, Line, Point, Rect, Text, RectText } = require('../src/core/'); const StringUtils = require('../src/utils/string'); const NumberUtils = require('../src/utils/number'); describe('Testcases of tcharts.js', () => { describe('1. Point element.', () => { require('./point')(Point); }); describe('2. Line element.', () => { require('./line')(Point, Line); }); describe('3. Text element.', () => { require('./text')(Point, Text); }); describe('4. Rect element.', () => { require('./rect')(Point, Rect); }); describe('5. RectText element.', () => { require('./recttext')(Point, RectText); }); describe('6. Axis element.', () => { require('./axis')(Point, Axis); }); describe('7. Box chart.', () => { require('./box')(Box); }); describe('8. Bar chart.', () => { require('./bar')(Bar); }); describe('9. HBar chart.', () => { require('./hbar')(HBar); }); describe('10. Table chart.', () => { require('./table')(Table); }); describe('11. utils.', () => { require('./utils')(StringUtils, NumberUtils); }); }); ================================================ FILE: __tests__/line.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Point, Line) => { test('1. draw a line element.', () => { let start = new Point(0, 0); let end = new Point(0, 50); const line = new Line(start, end); let lineLayer = line.draw(); expect(lineLayer.box).toEqual({ x1: 0, y1: 0, x2: 0, y2: 50, }); expect(lineLayer.array()).toEqual(new Array(51).fill(new Array(1).fill('|'))); start = new Point(0, 0); end = new Point(50, 0); lineLayer = new Line(start, end).draw(); expect(lineLayer.box).toEqual({ x1: 0, y1: 0, x2: 50, y2: 0, }); expect(lineLayer.array()).toEqual(new Array(1).fill(new Array(51).fill('-'))); expect(line.clone().CLASSNAME).toBe('Line'); expect(line.toString()).toBe('Line(Point(0, 0), Point(0, 50))'); }); }; ================================================ FILE: __tests__/point.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Point) => { test('1. draw a point element.', () => { const pointLayer = new Point(50, 50).draw(); expect(pointLayer.box).toEqual({ x1: 50, y1: 50, x2: 50, y2: 50, }); expect(pointLayer.array()).toEqual([['+']]); }); }; ================================================ FILE: __tests__/rect.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Point, Rect) => { test('1. draw a rect element.', () => { const start = new Point(0, 0); const end = new Point(4, 3); const rect = new Rect(start, end); const rectLayer = rect.draw(); expect(rectLayer.box).toEqual({ x1: 0, y1: 0, x2: 4, y2: 3, }); expect(rectLayer.array()).toEqual([ '+---+'.split(''), '| |'.split(''), '| |'.split(''), '+---+'.split(''), ]); expect(rect.clone().CLASSNAME).toBe('Rect'); rect.toString(); }); }; ================================================ FILE: __tests__/recttext.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Point, RectText) => { test('1. draw a rect-text element.', () => { const start = new Point(0, 0); const end = new Point(10, 5); const reactText = new RectText(start, end, 'TC'); const rectTextLayer = reactText.draw(); expect(rectTextLayer.box).toEqual({ x1: 0, y1: 0, x2: 10, y2: 5, }); expect(rectTextLayer.array()).toEqual([ '+---------+'.split(''), '| |'.split(''), '| |'.split(''), ['|', ' ', ' ', ' ', ' ', 'TC', '', ' ', ' ', ' ', '|'], '| |'.split(''), '+---------+'.split(''), ]); expect(reactText.clone().CLASSNAME).toBe('RectText'); reactText.toString(); }); }; ================================================ FILE: __tests__/table.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Table) => { test('1. draw a table chart.', () => { let table = new Table(); table.setData([ ['id', 'name', 'birthday'], ['#1', 'xiaowei', '1992-08-01'], ['#2', 'hello', '1992-09-20'], ['#3', 'tcharts', '2017-06-27'], ['#4', 'world'], ]); const r = ` +--+-------+----------+ |id| name | birthday | +--+-------+----------+ |#1|xiaowei|1992-08-01| +--+-------+----------+ |#2| hello |1992-09-20| +--+-------+----------+ |#3|tcharts|2017-06-27| +--+-------+----------+ |#4| world | | +--+-------+----------+`.trim(); expect(table.string()).toBe(r); table = new Table(0.5); // set gap rate = 0.5 table.setData([ ['id', 'name', 'birthday'], ['#1', 'xiaowei', '1992-08-01'], ['#2', 'hello', '1992-09-20'], ['#3', 'tcharts', '2017-06-27'], ['#4', 'world'], ]); console.log(table.string()); }); test('2. draw a table chart, contains chinese.', () => { let table = new Table(); table.setData([ ['標識符', '名字', '生日'], ['#1', '圖靈', '1992-08-01'], ['#2', '潘金蓮', '1992-09-20'], ['#3', '西門慶', '2017-06-27'], ['#4', '明日花绮罗'], ]); const r = ` +------+----------+----------+ |標識符| 名字 | 生日 | +------+----------+----------+ | #1 | 圖靈 |1992-08-01| +------+----------+----------+ | #2 | 潘金蓮 |1992-09-20| +------+----------+----------+ | #3 | 西門慶 |2017-06-27| +------+----------+----------+ | #4 |明日花绮罗| | +------+----------+----------+`.trim(); expect(table.string()).toBe(r); table = new Table(0.5); // set gap rate = 0.5 table.setData([ ['標識符', '名字', '生日'], ['#1', '圖靈', '1992-08-01'], ['#2', '潘金蓮', '1992-09-20'], ['#3', '西門慶', '2017-06-27'], ['#4', '明日花绮罗'], ]); console.log(table.string()); table = new Table(0.5); // set gap rate = 0.5 table.setData([ ['標識符', '名字', '生日'], ['#1', '圖靈', 24], ['#2', '潘金蓮', false], ['#3', '西門慶', null], ['#4', '明日花绮罗'], ]); console.log(table.string()); }); }; ================================================ FILE: __tests__/text.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (Point, Text) => { test('1. draw a text element.', () => { const text = 'Hello world.'; const p = new Point(40, 50); const textE = new Text(p, text); const textLayer = textE.draw(); expect(textLayer.box).toEqual({ x1: 35, y1: 50, x2: 46, y2: 50, }); const r = new Array(12).fill(''); r[0] = text; expect(textLayer.array()).toEqual([ r ]); expect(textE.clone().CLASSNAME).toBe('Text'); textE.toString(); }); }; ================================================ FILE: __tests__/utils.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ module.exports = (StringUtils, NumberUtils) => { test('1. toString.', () => { expect(StringUtils.toString(null)).toBe('null'); expect(StringUtils.toString(undefined)).toBe('undefined'); expect(StringUtils.toString(false)).toBe('false'); expect(StringUtils.toString('')).toBe(''); expect(StringUtils.toString(123)).toBe('123'); expect(StringUtils.toString('hustcc')).toBe('hustcc'); expect(StringUtils.toString([1, '2'])).toBe('[1,"2"]'); expect(StringUtils.toString({a: 1, b: '2'})).toBe('{"a":1,"b":"2"}'); expect(typeof StringUtils.toString(new Date())).toBe('string'); }); test('2. toPercent.', () => { expect(NumberUtils.toPercent(123)).toBe('12300%'); expect(NumberUtils.toPercent(0.234, 0)).toBe('23%'); }); }; ================================================ FILE: package.json ================================================ { "name": "tcharts.js", "version": "0.0.4", "description": "Lightweight and fast terminal ASCII charts for nodejs and browser.", "main": "dist/tcharts.min.js", "files": [ "dist" ], "scripts": { "lint": "eslint src && eslint __tests__", "cover": "jest --coverage", "test": "npm run lint && npm run cover", "coveralls": "cat ./coverage/lcov.info | coveralls", "build": "webpack && cross-env NODE_ENV=production jest --coverage" }, "repository": { "type": "git", "url": "git+https://github.com/hustcc/tcharts.js.git" }, "keywords": [ "terminal", "console", "charts", "ascii", "terminal-charts", "tcharts", "tcharts.js" ], "author": "hustcc", "license": "ISC", "bugs": { "url": "https://github.com/hustcc/tcharts.js/issues" }, "homepage": "https://github.com/hustcc/tcharts.js#readme", "devDependencies": { "babel-cli": "^6.24.1", "babel-core": "^6.24.1", "babel-eslint": "^7.2.3", "babel-jest": "^20.0.3", "babel-loader": "^7.1.0", "babel-preset-env": "^1.5.2", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "coveralls": "^2.13.1", "cross-env": "^5.0.5", "eslint": "^3.19.0", "eslint-config-airbnb": "^15.0.1", "eslint-config-airbnb-base": "^11.2.0", "eslint-plugin-import": "^2.3.0", "eslint-plugin-jsx-a11y": "^5.0.3", "eslint-plugin-react": "^7.0.1", "jest": "^20.0.4", "webpack": "^3.0.0" }, "dependencies": { "area-divide": "^0.0.1", "evenly": "^1.0.2", "fixed-round": "^0.0.1", "invariant": "^2.2.2", "variable-type": "^0.2.0", "what.js": "^1.0.1", "word-width": "^1.0.1" }, "jest": { "testRegex": "(/__tests__/index)\\.(ts|tsx|js)$", "moduleFileExtensions": [ "ts", "tsx", "js", "json" ], "collectCoverage": true, "collectCoverageFrom": [ "src/*.{js,jsx}", "src/*/*.{js,jsx}", "!**/node_modules/**", "!**/vendor/**" ], "moduleDirectories": [ "node_modules", "src" ], "transform": { "^.+\\.js?$": "babel-jest" } } } ================================================ FILE: src/charts/Bar.js ================================================ /** * Created by hustcc. */ const Chart = require('./Chart'); const invariant = require('../utils/invariant'); const { round } = require('../utils/number'); const Axis = require('../core/Axis'); const Point = require('../core/Point'); const Text = require('../core/Text'); const Rect = require('../core/Rect'); const { BAR_DATA_TYPE } = require('../const'); /** * 柱形图 * * ^ * | A:70 * | +---+ * | | | * | | | C:50 * | | | +---+ * | | | | | * | | | | | * | | | B:30 | | * | | | +---+ | | * | | | | | | | * | | | | | | | * | | | | | | | * +---+---+----+---+----+---+-----> * * * data 结构: [ {value:335, name:'A'}, {value:310, name:'B'}, {value:274, name:'C'}, {value:235, name:'D'}, {value:400, name:'E'}, ] * */ class Bar extends Chart { constructor(minWidth = 20, heightRate = 0.4) { super(0, 0); this.minWidth = minWidth; this.heightRate = heightRate; // 高度 / 宽度比例 this.barWidth = 2; } setData = (data) => { invariant( BAR_DATA_TYPE.check(data), 'TCharts: data of `Bar` chart should be type of Array.' ); this.data = data; // 计算宽高 let width = (this.data.length * 2 + 1) * (this.barWidth + 1); width = Math.max(width, this.minWidth); const height = round(width * this.heightRate); this.resetSize(width, height); this.generateLayer(); }; /** * 具体图表的实现 */ generateLayer = () => { // 1. 构造 text 键值 let maxValue = 0; this.data.forEach((d) => { d.text = `${d.name}:${d.value}`; maxValue = Math.max(maxValue, d.value); }); // 2. 计算每个数值的柱形图高度d this.data.forEach((d) => { d.height = round((d.value / maxValue) * (this.height - 2)); // -2 是为了显示文本 }); // 3. 绘制坐标轴图层 const axisLayer = new Axis( new Point(0, 0), new Point(this.width, 0), new Point(0, this.height) ).draw(); // 4. 绘制矩形图层 const widthStep = round(this.width / (this.data.length * 2 + 1)); const rectLayers = this.data.map((d, i) => new Rect( new Point(widthStep * (i * 2 + 1), 0), new Point(widthStep * (i * 2 + 2), d.height)).draw() ); // 5. 绘制文字图层(局左对齐) const textLayers = this.data.map((d, i) => new Text( new Point(widthStep * (i * 2 + 1), d.height + 1), // 文本高度为 1 d.text, 'left').draw() ); // 6. 合并图层 let layers = []; layers.push(axisLayer); layers = layers.concat(rectLayers); layers = layers.concat(textLayers); this.layer.mergeArray(layers); } } module.exports = Bar; ================================================ FILE: src/charts/Box.js ================================================ /** * Created by hustcc. */ const areaDivide = require('area-divide'); const Chart = require('./Chart'); const { toPercent } = require('../utils/number'); const RectText = require('../core/RectText'); const Point = require('../core/Point'); const invariant = require('../utils/invariant'); const { BOX_DATA_TYPE } = require('../const'); /** * 面积区域占比图 * * +-------------------+------------+ * | | B:10% | * | | | * | |------------+ * | A:75% | | * | | C:20% | * | | | * +-------------------+------------+ * * data 结构: [ {value:335, name:'A'}, {value:310, name:'B'}, {value:274, name:'C'}, {value:235, name:'D'}, {value:400, name:'E'}, ] * */ class Box extends Chart { constructor(width = 60, height = 20) { super(width, height); } setData = (data) => { invariant( BOX_DATA_TYPE.check(data), 'TCharts: data of `Box` chart should be type of Array.' ); this.data = data; this.generateLayer(); }; /** * 具体图表的实现 */ generateLayer = () => { // 1. 计算总数 let total = 0; this.data.forEach((d) => { total += d.value; }); // 2. 计算占比 this.data.forEach((d) => { d.percent = d.value / total; d.text = `${d.name}:${toPercent(d.percent, 0)}`; }); // 3. 降序排序 this.data.sort((x, y) => y.value - x.value); // 4. 瓜分面积 // 获得比率,然后调用方法瓜分算法 const percents = this.data.map(e => e.percent); const plan = areaDivide(this.width, this.height, percents); const layers = plan.map((p, index) => new RectText( new Point(p.x1, p.y1), new Point(p.x2, p.y2), this.data[index].text).draw() ); this.layer.mergeArray(layers); }; } module.exports = Box; ================================================ FILE: src/charts/Chart.js ================================================ /** * Created by hustcc. */ const Layer = require('../core/Layer'); /** * 图表的基类 * 1. 拥有画布的宽高属性 * 2. 可以设置图形的 data 配置 * 3. 拥有将图层变成字符串的能力 string() / array(); * 4. 拥有一个图层,这个是最最终图层 */ class Chart { constructor(width, height) { this.resetSize(width, height); this.data = []; } resetSize = (width, height) => { this.width = width; this.height = height; // 创建一个空的图层 this.layer = Layer.emptyInstance({ x1: 0, y1: 0, x2: this.width, y2: this.height, }); }; // 返回图表的文本,可以直接用脑输出 string = () => this.layer.string(); // 返回图表的文本数组,逐行打印就可以显示了 array = () => this.layer.array(); } module.exports = Chart; ================================================ FILE: src/charts/HBar.js ================================================ /** * Created by hustcc. */ const Chart = require('./Chart'); const invariant = require('../utils/invariant'); const { round } = require('../utils/number'); const Axis = require('../core/Axis'); const Point = require('../core/Point'); const Rect = require('../core/Rect'); const Text = require('../core/Text'); const { HBAR_DATA_TYPE } = require('../const'); /** * 横向柱形图 * * ^ * | * +--------------------------+ * | A:70 | * +--------------------------+ * | * | * +-----------------+ * | B:30 | * +-----------------+ * | * | * +---------------------+ * | C:50 | * +---------------------+ * | * +---+---+----+---+----+---+-----> * */ class HBar extends Chart { constructor(minHeight = 8, widthRate = 3) { super(0, 0); this.minHeight = minHeight; this.widthRate = widthRate; // 高度 / 宽度比例 this.barHeight = 1; } setData = (data) => { invariant( HBAR_DATA_TYPE.check(data), 'TCharts: data of `HBar` chart should be type of Array.' ); this.data = data; // 计算宽高 let height = (this.data.length * 2 + 1) * (this.barHeight + 1); height = Math.max(height, this.minHeight); const width = round(height * this.widthRate); this.resetSize(width, height); this.generateLayer(); }; /** * 具体图表的实现 */ generateLayer = () => { // 1. 构造 text 键值 let maxValue = 0; this.data.forEach((d) => { d.text = `${d.name}:${d.value}`; maxValue = Math.max(maxValue, d.value); }); // 2. 计算每个数值的柱形图宽度 this.data.forEach((d) => { d.width = round((d.value / maxValue) * (this.width - 1)); // -1 图形美观 }); // 3. 绘制坐标轴图层 const axisLayer = new Axis( new Point(0, 0), new Point(this.width, 0), new Point(0, this.height) ).draw(); // 4. 绘制矩形图层 const heightStep = round(this.height / (this.data.length * 2 + 1)); const rectLayers = this.data.map((d, i) => new Rect( new Point(0, heightStep * (i * 2 + 1)), new Point(d.width, heightStep * (i * 2 + 2))).draw() ); // 5. 绘制文字图层(局左对齐) const textLayers = this.data.map((d, i) => { if (d.text.length >= d.width) { // 文本长度超过柱子长度,在在柱形右边显示 return new Text( new Point(d.width + 1, heightStep * (i * 2 + 1) + 1), d.text).draw(); } // 居右显示 return new Text( new Point(d.width - 1, heightStep * (i * 2 + 1) + 1), d.text, 'right').draw(); }); // 6. 合并图层 let layers = []; layers.push(axisLayer); layers = layers.concat(rectLayers); layers = layers.concat(textLayers); this.layer.mergeArray(layers); } } module.exports = HBar; ================================================ FILE: src/charts/Table.js ================================================ /** * Created by hustcc. */ const Chart = require('./Chart'); const invariant = require('../utils/invariant'); const RectText = require('../core/RectText'); const Point = require('../core/Point'); const { arrayClone } = require('../utils/array'); const { wordWidth } = require('../utils/string'); const { round } = require('../utils/number'); const { TABLE_DATA_TYPE } = require('../const'); /** * 表格 * * * +----+----------+----------------+ * | id | name | birthday | * +----+----------+----------------+ * | #1 | xiaowei | 1992-08-01 | * +----+----------+----------------+ * | #2 | hello | 1992-09-20 | * +----+----------+----------------+ * | #3 | tcharts | 2017-06-27 | * +----+----------+----------------+ * | #4 | d | | * +----+----------+----------------+ * * * */ class Table extends Chart { constructor(rate = 0) { super(0, 0); // table 的宽高有内容自动伸缩 this.rate = rate; // 比例,比如文字宽度为10, rate = 0.1,则表格 cell 宽度为 12 } // 通过内容计算每一列的宽高 _calColSizes = (data, row, col) => { let sizes = new Array(col).fill(0); data.forEach((d) => { sizes = sizes.map((s, i) => Math.max(wordWidth(d[i]), s)); }); // 乘以 rate sizes = sizes.map(s => s + round(s * this.rate) * 2); return sizes; }; _getRowAndCol = (data) => { const row = data.length; let col = 0; data.forEach((d) => { col = Math.max(col, d.length); }); // 数据不能为零长度 invariant( row !== 0 && col !== 0, `TCharts: data of \`Table\` chart should be type of matrix Array, and can not be zero row or column. Got row: %s, column: %s.`, row, col ); return { row, col, }; }; // 填充数据(对于有空缺的数据) _fullFillData = (data, row, col) => { const cloneData = arrayClone(data); // 遍历来填充数据 data.forEach((d, index) => { if (col > d.length) { // 补充一些空的文本 cloneData[index].splice(cloneData[index].length, 0, ...new Array(col - d.length).fill('')); } }); return cloneData; }; _calTableSizes = (colSizes, row, col) => { // width height 是从 0 开始计数的,所以这里的 width height 比实际的 - 1 const height = row * 2; const width = col + colSizes.reduce((r, ele) => r + ele); return { width, height, }; }; setData = (data) => { invariant( TABLE_DATA_TYPE.check(data), 'TCharts: data of `Table` chart should be type of matrix Array.' ); const { row, col } = this._getRowAndCol(data); this.data = this._fullFillData(data, row, col); const colSizes = this._calColSizes(this.data, row, col); const { width, height } = this._calTableSizes(colSizes, row, col); this.resetSize(width, height); this.generateLayer(colSizes, row, col); }; /** * 具体 table 的实现 */ generateLayer = (colSizes, row, col) => { const rectTexts = []; // 非常多有的 rectText 实例 let startX = 0; let startY = 0; for (let i = 0; i < row; i += 1) { startX = 0; for (let j = 0; j < col; j += 1) { rectTexts.push(new RectText( new Point(startX, startY), new Point(startX + colSizes[j] + 1, startY + 2), this.data[row - i - 1][j]).draw()); startX += (colSizes[j] + 1); } startY += 2; } return this.layer.mergeArray(rectTexts); } } module.exports = Table; ================================================ FILE: src/const.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ const VT = require('variable-type'); const COMMON_TYPE = VT.arrayOf( VT.shape({ name: VT.or([ VT.number, VT.string, ]), value: VT.number, }) ); const BAR_DATA_TYPE = COMMON_TYPE; const BOX_DATA_TYPE = COMMON_TYPE; const HBAR_DATA_TYPE = COMMON_TYPE; const TABLE_DATA_TYPE = VT.arrayOf( VT.arrayOf( VT.any, ), ); // const TREE_DATA_TYPE = VT.shape({ // name: VT.or([ // VT.number, // VT.string, // ]), // // TODO children 一个递归结构的数组 // children: VT.arrayOf( // VT.recursive // ), // }); module.exports = { BAR_DATA_TYPE, BOX_DATA_TYPE, HBAR_DATA_TYPE, TABLE_DATA_TYPE, // TREE_DATA_TYPE, }; ================================================ FILE: src/core/Axis.js ================================================ /** * Created by hustcc. */ const invariant = require('../utils/invariant'); const types = require('../utils/types'); const Element = require('./Element'); const Line = require('./Line'); /** * 一个坐标轴系统,不带 x,y 文字 * * ^ * | * | * | * | * | * | * | * | * | * +------------------------------> * */ class Axis extends Element { /** * 构造方法 * @param point0 零点 * @param pointX 横向轴 * @param pointY 纵向轴 */ constructor(point0, pointX, pointY) { super(); invariant( types.isPoint(point0) && types.isPoint(pointX) && types.isPoint(pointY), 'TCharts: constructor props of Axis should be (Point, Point, Point), got (%s, %s, %s).', types.typeOf(point0), types.typeOf(pointX), types.typeOf(pointY)); invariant( point0.y === pointX.y, 'TCharts: constructor props `pointX` of Axis should be an Horizontal line with `point0`.'); invariant( point0.x === pointY.x, 'TCharts: constructor props `pointY` of Axis should be an Horizontal line with `point0`.'); this.point0 = point0; this.pointX = pointX; this.pointY = pointY; this.box = { x1: this.point0.x, y1: this.point0.y, x2: this.pointX.x, y2: this.pointY.y, }; this.initLayer(); } clone = () => new Axis(this.point0, this.pointX, this.pointY); toString = () => `Axis(${this.point0}, ${this.pointX}, ${this.pointY})`; draw = () => { // X 轴图层 const lineXLayer = new Line(this.point0, this.pointX).draw(); // Y 轴图层 const lineYLayer = new Line(this.point0, this.pointY).draw(); // 后合并点,可以提高性能,同时不会导致层覆盖问题 return this.layer.merge( lineXLayer, // x 轴 lineYLayer, // y 轴 this.point0.draw({ fill: '+' }), // 左边圆点 this.pointX.draw({ fill: '>' }), // x 轴箭头 this.pointY.draw({ fill: '^' }) // y 轴箭头 ); }; get CLASSNAME() { return 'Axis'; } } module.exports = Axis; ================================================ FILE: src/core/Element.js ================================================ /** * Created by hustcc. */ const { floorCenter } = require('../utils/number'); const Layer = require('./Layer'); /** * 元素基类,像积木一样一层一层的搭建组件元素 * 每隔元素都可以通过 draw 方法获得一个图层 * 上层的元素是利用下层元素组合出来的,然后形成新的图层 * 一层一层,直到形成最终的图表 charts * * 元素都有一个 box 结构(盒模型?)限定了元素的起始位置 * 另外具有一个 z-index 属性,可以设置元素显示的层级 * * 元素的 clone 可以直接获得一个相同的元素,引用不同 */ class Element { constructor(zIndex = 0) { this.zIndex = zIndex; // TODO 用于控制渲染层级,后期可以用于重构渲染性能 // box 限制了元素的起始结束点坐标,以 this.box = { x1: 0, y1: 0, x2: 0, y2: 0 }; } /** * 初始化空的 layer */ initLayer = () => { this.layer = Layer.emptyInstance(this.box, this.zIndex); }; // setZIndex = (zIndex = 0) => { // this.zIndex = zIndex; // }; /** * 获得中心点的坐标 * @returns {{x: number, y: number}} */ center = () => ({ x: floorCenter(this.box.x1, this.box.x2), y: floorCenter(this.box.y1, this.box.y2), }); /** * 获得元素的大小 * @returns {{width: number, height: number}} */ size = () => { const width = this.box.x2 - this.box.x1 + 1; const height = this.box.y2 - this.box.y1 + 1; return { width, height, }; }; // clone = () => new Element(); // // toString = () => { // invariant(false, 'TCharts: Element\'s method `toString` should be implemented by children Class.'); // }; // // // /** // * 绘制图形,产生图层,图层将用于最终的渲染 // * @param options { fill: '*', line: '-' } // */ // draw = () => { // invariant(false, 'TCharts: Element\'s method `draw` should be implemented by children Class.'); // }; // // get CLASSNAME() { // invariant(false, 'TCharts: Element\'s method `CLASSNAME` should be implemented by children Class.'); // } } module.exports = Element; ================================================ FILE: src/core/Layer.js ================================================ /** * Created by hustcc. */ const invariant = require('../utils/invariant'); const types = require('../utils/types'); const { fillMatrix } = require('../utils/array'); /** * 定义一个图层,图层包括一个范围和填充的内容 * 图层是最终将用于显示的部分,图层可以通过 merge 合并 * 最终显示在控制台的仅仅只有一个图层 * 这个图层是有很多的小元素图层合并而来 * * 如何设计图层,更好划分图层,将提高 merge 性能 * * 图层也就是 Element 元素 draw 方法的返回值 */ class Layer { constructor(box = { x1: 0, y1: 0, x2: 0, y2: 0 }, ascii = [], zIndex = 0) { invariant( types.isObject(box) && types.isArray(ascii) && types.isNumber(zIndex), 'TCharts: constructor props of Layer should be (object, array, number), got (%s, %s, %s).', types.typeOf(box), types.typeOf(ascii), types.typeOf(zIndex)); this.box = box; this.ascii = ascii; this.zIndex = zIndex; } static emptyInstance(box, zIndex = 0) { const width = box.x2 - box.x1 + 1; const height = box.y2 - box.y1 + 1; const ascii = fillMatrix(height, width, ' '); return new Layer(box, ascii, zIndex); } // box 盒模型是不是大于 // gt = (layer, includeEqual = true) => { // if (includeEqual) { // if ( // this.box.x1 <= layer.box.x1 && // this.box.y1 <= layer.box.y1 && // this.box.x2 >= layer.box.x2 && // this.box.y2 >= layer.box.y2) { // return true; // } // return false; // } // if ( // this.box.x1 < layer.box.x1 && // this.box.y1 < layer.box.y1 && // this.box.x2 > layer.box.x2 && // this.box.y2 > layer.box.y2) { // return true; // } // return false; // }; /** * 按顺序合并另外的 layers * @param layer */ merge = (...layers) => this.mergeArray(layers); /** * 合并一个 layer * @param layer */ mergeOne = (layer) => { // 校验:前提条件是,this 的范围肯定大于 layer // invariant( // this.gt(layer), // 'TCharts: layer\'box should be greater then layer which will be merged.'); // 算法 const { x1, y1, x2, y2 } = layer.box; const { x1: thisX1, y1: thisY1, x2: thisX2, y2: thisY2 } = this.box; const rangeX1 = Math.max(x1, thisX1); const rangeY1 = Math.max(y1, thisY1); const rangeX2 = Math.min(x2, thisX2); const rangeY2 = Math.min(y2, thisY2); // box 的偏移量 let i1; let i2; for (let i = rangeX1; i <= rangeX2; i += 1) { // 减少计算量 i1 = i - thisX1; i2 = i - x1; for (let j = rangeY1; j <= rangeY2; j += 1) { this.ascii[j - thisY1][i1] = layer.ascii[j - y1][i2]; } } return this; }; /** * 合并一个 layer * @param layer */ mergeArray = (layers) => { // 首先先按照 zIndex 升序排列,越大越在上层 // chrome 下 sort 方法可能为不稳定,这点需要注意。 // layers.sort((x, y) => x - y); // TODO :暂时不要 zIndex 排序特性 // 排序之后遍历,先绘制底层的,后使用上层覆盖 layers.forEach((layer) => { this.mergeOne(layer); }); return this; }; /** * 返回图表的文本,可以直接用脑输出 * @returns {string} */ string = () => { const asciiArray = this.ascii; // 逆向遍历 const rst = []; for (let i = asciiArray.length - 1; i >= 0; i -= 1) { rst.push(`${asciiArray[i].join('')}`); } return rst.join('\n'); }; /** * 返回图表的文本数组,逐行打印就可以显示了 */ array = () => this.ascii.slice().reverse(); get CLASSNAME() { return 'Layer'; } } module.exports = Layer; ================================================ FILE: src/core/Line.js ================================================ /** * Created by hustcc. */ const invariant = require('../utils/invariant'); const types = require('../utils/types'); const Element = require('./Element'); const { fillMatrix } = require('../utils/array'); // const Layer = require('./Layer'); /** * 一个线段,包括横线和竖线 * * 横线 * ------------------- * * 竖线 * | * | * | * | * | * | * */ class Line extends Element { constructor(start, end) { super(); // 参数必须是 point 类型的 invariant( types.isPoint(start) && types.isPoint(end), 'TCharts: constructor props of Line should be (Point, Point), got (%s, %s).', types.typeOf(start), types.typeOf(end)); // 控制台模式下,仅仅只能输入横线或者竖线 invariant( start.x === end.x || start.y === end.y, 'TCharts: we can only draw Horizontal / Vertical line in terminal. got(%s, %s).', start, end); // 校验通过,赋值属性 this.start = start; this.end = end; this.box = { x1: Math.min(this.start.x, this.end.x), y1: Math.min(this.start.y, this.end.y), x2: Math.max(this.start.x, this.end.x), y2: Math.max(this.start.y, this.end.y), }; this.initLayer(); } clone = () => new Line(this.start.clone(), this.end.clone()); toString = () => `Line(${this.start}, ${this.end})`; draw = () => { const { width, height } = this.size(); const isHorizontal = this.box.y1 === this.box.y2; let ascii = null; if (isHorizontal) { // 横向线 ascii = fillMatrix(1, width, '-'); } else { // 纵向线 ascii = fillMatrix(height, 1, '|'); } this.layer.ascii = ascii; return this.layer; }; get CLASSNAME() { return 'Line'; } } module.exports = Line; ================================================ FILE: src/core/Point.js ================================================ /** * Created by hustcc. */ const invariant = require('../utils/invariant'); const types = require('../utils/types'); const Element = require('./Element'); /** * 一个点 * * + * */ class Point extends Element { constructor(x, y) { super(); invariant( types.isNumber(x) && types.isNumber(y), 'TCharts: constructor props of Point should be (number, number), got (%s, %s).', types.typeOf(x), types.typeOf(y)); this.x = x; this.y = y; this.box = { x1: this.x, y1: this.y, x2: this.x, y2: this.y, }; this.initLayer(); } clone = () => new Point(this.x, this.y); toString = () => `Point(${this.x}, ${this.y})`; draw = (options = { fill: '+' }) => { this.layer.ascii = [[options.fill]]; return this.layer; }; get CLASSNAME() { return 'Point'; } } module.exports = Point; ================================================ FILE: src/core/Rect.js ================================================ /** * Created by hustcc. */ const invariant = require('../utils/invariant'); const types = require('../utils/types'); const Element = require('./Element'); const Point = require('./Point'); const Line = require('./Line'); // const Layer = require('./Layer'); /** * 带边框的填充矩形,在控制台下面,仅仅只能绘制四边形 * * +---------------------------+ * | | * | | * | | * | | * +---------------------------+ * */ class Rect extends Element { constructor(start, end) { super(); // 参数必须是 point 类型的 invariant( types.isPoint(start) && types.isPoint(end), 'TCharts: constructor props of Rect should be (Point, Point), got (%s, %s).', types.typeOf(start), types.typeOf(end)); // 校验通过,赋值属性 this.start = start; this.end = end; this.box = { x1: Math.min(this.start.x, this.end.x), y1: Math.min(this.start.y, this.end.y), x2: Math.max(this.start.x, this.end.x), y2: Math.max(this.start.y, this.end.y), }; this.initLayer(); } clone = () => new Rect(this.start.clone(), this.end.clone()); toString = () => `Rect(${this.start}, ${this.end})`; draw = () => { // 四个顶点 const points = [ new Point(this.box.x1, this.box.y1), new Point(this.box.x2, this.box.y1), new Point(this.box.x2, this.box.y2), new Point(this.box.x1, this.box.y2), ]; const corners = points.map(p => p.draw()); // 四条边 const lines = [ new Line(points[0], points[1]).draw(), new Line(points[1], points[2]).draw(), new Line(points[2], points[3]).draw(), new Line(points[3], points[0]).draw(), ]; // 合并图层部分 return this.layer.mergeArray(lines.concat(corners)); }; get CLASSNAME() { return 'Rect'; } } module.exports = Rect; ================================================ FILE: src/core/RectText.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ const invariant = require('../utils/invariant'); const types = require('../utils/types'); const Element = require('./Element'); const Point = require('./Point'); const Rect = require('./Rect'); const Text = require('./Text'); /** * 带边框的填充矩形,并且显示说明文本,在控制台下面,仅仅只能绘制四边形 * * +---------------------------+ * | | * | ABC | * | | * | | * +---------------------------+ * */ class RectText extends Element { constructor(start, end, text) { super(); // 参数必须是 point 类型的 invariant( types.isPoint(start) && types.isPoint(end), 'TCharts: constructor props of RectText should be (Point, Point, any), got (%s, %s, %s).', types.typeOf(start), types.typeOf(end), types.typeOf(text)); // 校验通过,赋值属性 this.start = start; this.end = end; this.text = text; this.box = { x1: Math.min(this.start.x, this.end.x), y1: Math.min(this.start.y, this.end.y), x2: Math.max(this.start.x, this.end.x), y2: Math.max(this.start.y, this.end.y), }; this.initLayer(); } clone = () => new RectText(this.start.clone(), this.end.clone(), this.text); toString = () => `RectText(${this.start}, ${this.end}, ${this.text})`; draw = () => { // 四个顶点 const rect = new Rect(this.start, this.end); const center = this.center(); // 居中位置 const text = new Text(new Point(center.x, center.y), this.text); // 合并图层 return this.layer.merge(rect.draw(), text.draw()); }; get CLASSNAME() { return 'RectText'; } } module.exports = RectText; ================================================ FILE: src/core/Text.js ================================================ /** * Created by hustcc. */ const evenly = require('evenly'); const { wordWidth, toString } = require('../utils/string'); const invariant = require('../utils/invariant'); const types = require('../utils/types'); const { fillMatrix } = require('../utils/array'); const Element = require('./Element'); /** * 一段文本(目前仅支持横向) * * hello world,这个是一个控制台图标库 */ class Text extends Element { constructor(p, text, align = 'center') { super(); invariant( types.isPoint(p), 'TCharts: constructor props of Text should be (Point, any), got (%s, %s).', types.typeOf(p), types.typeOf(text)); invariant( ['center', 'left', 'right'].indexOf(align) >= 0, 'TCharts: constructor props `align` of Text should be one of [\'center\', \'left\', \'right\'], got %s.', align); this.p = p; this.text = toString(text); this.align = align; const textWidth = wordWidth(this.text); if (this.align === 'center') { // 居中显示 const _width = evenly(textWidth, 2, 0); this.box = { x1: p.x - _width[0] + 1, // 因为当前 p 位置还可以存一个字符串 y1: p.y, x2: p.x + _width[1], y2: p.y, }; } else if (this.align === 'left') { // 局左显示 this.box = { x1: p.x, y1: p.y, x2: p.x + textWidth - 1, y2: p.y, }; } else { // 居右显示 this.box = { x1: p.x - textWidth + 1, y1: p.y, x2: p.x, y2: p.y, }; } this.initLayer(); } clone = () => new Text(this.p, this.text); toString = () => `Text(${this.p}, ${this.text})`; draw = () => { const { width } = this.size(); this.layer.ascii = fillMatrix(1, width, ''); this.layer.ascii[0][0] = this.text; return this.layer; }; get CLASSNAME() { return 'Text'; } } module.exports = Text; ================================================ FILE: src/core/index.js ================================================ /** * Created by hustcc. */ const Axis = require('./Axis'); const Line = require('./Line'); const Point = require('./Point'); const Rect = require('./Rect'); const Text = require('./Text'); const RectText = require('./RectText'); module.exports = { Axis, Line, Point, Rect, Text, RectText, }; ================================================ FILE: src/index.js ================================================ /** * Created by hustcc. */ const Bar = require('./charts/Bar'); const Box = require('./charts/Box'); const HBar = require('./charts/HBar'); const Table = require('./charts/Table'); module.exports = { Box, Bar, HBar, Table, }; ================================================ FILE: src/utils/array.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ 'use strict'; /** * fastest way to fill a array * here: https://jsperf.com/zeroarrayjs/10 * @param len * @param val * @returns {Array} */ const fillArray = (len, val) => { const res = new Array(len); for (let i = 0; i < len; i += 1) { res[i] = val; } return res; }; /** * Return a of val * * @param {Number} `row` number of rows * @param {Number} `col` number of columns * @param {*} `val` * @return {*[][]} `matrix` of val */ const fillMatrix = (row, col, val) => { const res = new Array(row); for (let i = 0; i < row; i += 1) { res[i] = fillArray(col, val); } return res; }; /** * deep clone array * @param arr * @returns {*} */ const arrayClone = (arr) => { let i; let copy; if (Array.isArray(arr)) { copy = arr.slice(0); for (i = 0; i < copy.length; i += 1) { copy[i] = arrayClone(copy[i]); } return copy; } return arr; }; module.exports = { fillArray, fillMatrix, arrayClone, }; ================================================ FILE: src/utils/invariant.js ================================================ module.exports = require('invariant'); ================================================ FILE: src/utils/number.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ const round = require('fixed-round'); const center = (x1, x2) => round((x1 + x2) / 2); const floorCenter = (x1, x2) => Math.floor((x1 + x2) / 2); const toPercent = (number, fixed = 2) => `${round(number * 100, fixed)}%`; module.exports = { round, center, floorCenter, toPercent, }; ================================================ FILE: src/utils/string.js ================================================ /** * Created by hustcc. * Contract: i@hust.cc */ const WordWidth = require('word-width'); const types = require('./types'); const toString = (v) => { if (v === null) return 'null'; if (v === undefined) return 'undefined'; if (types.isNumber(v) || types.isString(v) || types.isBool(v)) return `${v}`; if (types.isArray(v) || types.isObject(v)) return JSON.stringify(v); return Object({}).toString.call(v); }; const wordWidth = s => WordWidth(toString(s)); module.exports = { wordWidth, toString, }; ================================================ FILE: src/utils/types.js ================================================ /** * Created by hustcc. */ const VT = require('variable-type'); const what = require('what.js'); const isNumber = v => VT.number.check(v); const isString = v => VT.string.check(v); const isArray = v => VT.array.check(v); const isObject = v => VT.object.check(v); const isBool = v => VT.bool.check(v); const isEmpty = v => VT.or([ VT.null, VT.undefined ]).check(v); const isPoint = v => v.CLASSNAME === 'Point'; const typeOf = (v) => { if (v && v.CLASSNAME) return v.CLASSNAME; return what(v); }; module.exports = { isNumber, isString, isArray, isObject, isBool, isEmpty, isPoint, typeOf, }; ================================================ FILE: webpack.config.js ================================================ /** * Copyright (c) 2017 hustcc * License: ISC * GitHub: https://github.com/hustcc/tcharts.js **/ var path = require('path'); var webpack = require('webpack'); module.exports = { entry: './src/index.js', output: { filename: 'tcharts.min.js', path: path.resolve(__dirname, 'dist'), library: 'TCharts', libraryTarget: 'umd', umdNamedDefine: true }, module: { loaders: [{ test: /.js$/, loader: 'babel-loader' }] }, devtool: 'source-map', plugins: [ new webpack.optimize.UglifyJsPlugin({ output: { comments: false }, compress: { warnings: false } }) ] };