├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── assets ├── page.css └── page.js ├── index.html ├── lib └── d3-tree.js ├── package.json ├── test ├── d3-tree.test.js ├── helper.js └── mocha.opts └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [ 3 | 'env', 4 | 'stage-2' 5 | ], 6 | comments: false, 7 | env: { 8 | test: { 9 | plugins: [ 10 | 'istanbul' 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | **/node_modules 3 | **/dist 4 | **/assets 5 | **/build 6 | **/test 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true, 6 | "mocha": true 7 | }, 8 | "plugins": [ 9 | "mocha" 10 | ], 11 | // https://github.com/feross/eslint-config-standard 12 | "rules": { 13 | "accessor-pairs": 2, 14 | "block-scoped-var": 0, 15 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 16 | "camelcase": 0, 17 | "comma-dangle": [2, "never"], 18 | "comma-spacing": [2, { "before": false, "after": true }], 19 | "comma-style": [2, "last"], 20 | "complexity": 0, 21 | "consistent-return": 0, 22 | "consistent-this": 0, 23 | "curly": [2, "multi-line"], 24 | "default-case": 0, 25 | "dot-location": [2, "property"], 26 | "dot-notation": 0, 27 | "eol-last": 2, 28 | "eqeqeq": [2, "allow-null"], 29 | "func-names": 0, 30 | "func-style": 0, 31 | "generator-star-spacing": [2, "both"], 32 | "guard-for-in": 0, 33 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ], 34 | "indent": [2, 2], 35 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 36 | "linebreak-style": 0, 37 | "max-depth": 0, 38 | "max-len": 0, 39 | "max-nested-callbacks": 0, 40 | "max-params": 0, 41 | "max-statements": 0, 42 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 43 | "new-parens": 2, 44 | "no-alert": 0, 45 | "no-array-constructor": 2, 46 | "no-bitwise": 0, 47 | "no-caller": 2, 48 | "no-catch-shadow": 0, 49 | "no-cond-assign": 2, 50 | "no-console": 0, 51 | "no-constant-condition": 0, 52 | "no-continue": 0, 53 | "no-control-regex": 2, 54 | "no-debugger": 2, 55 | "no-delete-var": 2, 56 | "no-div-regex": 0, 57 | "no-dupe-args": 2, 58 | "no-dupe-keys": 2, 59 | "no-duplicate-case": 2, 60 | "no-else-return": 0, 61 | "no-empty": 0, 62 | "no-empty-character-class": 2, 63 | "no-eq-null": 0, 64 | "no-eval": 2, 65 | "no-ex-assign": 2, 66 | "no-extend-native": 2, 67 | "no-extra-bind": 2, 68 | "no-extra-boolean-cast": 2, 69 | "no-extra-semi": 0, 70 | "no-extra-strict": 0, 71 | "no-fallthrough": 2, 72 | "no-floating-decimal": 2, 73 | "no-func-assign": 2, 74 | "no-implied-eval": 2, 75 | "no-inline-comments": 0, 76 | "no-inner-declarations": [2, "functions"], 77 | "no-invalid-regexp": 2, 78 | "no-irregular-whitespace": 2, 79 | "no-iterator": 2, 80 | "no-label-var": 2, 81 | "no-labels": 2, 82 | "no-lone-blocks": 2, 83 | "no-lonely-if": 0, 84 | "no-loop-func": 0, 85 | "no-mixed-requires": 0, 86 | "no-mixed-spaces-and-tabs": [2, false], 87 | "no-multi-spaces": 2, 88 | "no-multi-str": 2, 89 | "no-multiple-empty-lines": [2, { "max": 1 }], 90 | "no-native-reassign": 2, 91 | "no-negated-in-lhs": 2, 92 | "no-nested-ternary": 0, 93 | "no-new": 0, 94 | "no-new-func": 2, 95 | "no-new-object": 2, 96 | "no-new-require": 2, 97 | "no-new-wrappers": 2, 98 | "no-obj-calls": 2, 99 | "no-octal": 2, 100 | "no-octal-escape": 2, 101 | "no-path-concat": 0, 102 | "no-plusplus": 0, 103 | "no-process-env": 0, 104 | "no-process-exit": 0, 105 | "no-proto": 2, 106 | "no-redeclare": 2, 107 | "no-regex-spaces": 2, 108 | "no-reserved-keys": 0, 109 | "no-restricted-modules": 0, 110 | "no-return-assign": 2, 111 | "no-script-url": 0, 112 | "no-self-compare": 2, 113 | "no-sequences": 2, 114 | "no-shadow": 0, 115 | "no-shadow-restricted-names": 2, 116 | "no-spaced-func": 2, 117 | "no-sparse-arrays": 2, 118 | "no-sync": 0, 119 | "no-ternary": 0, 120 | "no-throw-literal": 2, 121 | "no-trailing-spaces": 2, 122 | "no-undef": 2, 123 | "no-undef-init": 2, 124 | "no-undefined": 0, 125 | "no-underscore-dangle": 0, 126 | "no-unneeded-ternary": 2, 127 | "no-unreachable": 2, 128 | "no-unused-expressions": 0, 129 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 130 | "no-use-before-define": 0, 131 | "no-var": 0, 132 | "no-void": 0, 133 | "no-warning-comments": 0, 134 | "no-with": 2, 135 | "no-extra-parens": 0, 136 | "object-curly-spacing": 0, 137 | "one-var": [2, { "initialized": "never" }], 138 | "operator-assignment": 0, 139 | "operator-linebreak": [2, "after"], 140 | "padded-blocks": 0, 141 | "quote-props": 0, 142 | "quotes": [1, "single", "avoid-escape"], 143 | "radix": 2, 144 | "semi": [2, "always"], 145 | "semi-spacing": 0, 146 | "sort-vars": 0, 147 | "keyword-spacing": [2], 148 | "space-before-blocks": [2, "always"], 149 | "space-before-function-paren": 0, 150 | "space-in-parens": [2, "never"], 151 | "space-infix-ops": 2, 152 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 153 | "spaced-comment": [2, "always"], 154 | "strict": 0, 155 | "use-isnan": 2, 156 | "valid-jsdoc": 0, 157 | "valid-typeof": 2, 158 | "vars-on-top": 0, 159 | "wrap-iife": [2, "any"], 160 | "wrap-regex": 0, 161 | "yoda": [2, "never"] 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | .idea/* 5 | Thumbs.db 6 | .project 7 | coverage/ 8 | screenshots/ 9 | reports/ 10 | build/ 11 | dist/ 12 | *.sw* 13 | *.un~ 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .travis.yml 3 | .eslintrc 4 | .eslintignore 5 | coverage/ 6 | screenshots/ 7 | reports/ 8 | assets/ 9 | *.sw* 10 | *.un~ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - 12 5 | addons: 6 | apt: 7 | packages: 8 | - xvfb 9 | - libgconf-2-4 10 | install: 11 | - export DISPLAY=':99.0' 12 | - Xvfb :99 -screen 0 1366x768x24 > /dev/null 2>&1 & 13 | script: 14 | - npm i 15 | - npm run ci 16 | after_script: 17 | - npm i codecov && codecov 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 zhuyali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # d3-tree 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![build status][travis-image]][travis-url] 5 | [![Test coverage][coveralls-image]][coveralls-url] 6 | [![node version][node-image]][node-url] 7 | [![npm download][download-image]][download-url] 8 | 9 | [npm-image]: https://img.shields.io/npm/v/d3-tree.svg 10 | [npm-url]: https://npmjs.org/package/d3-tree 11 | [travis-image]: https://img.shields.io/travis/zhuyali/d3-tree.svg 12 | [travis-url]: https://travis-ci.org/zhuyali/d3-tree 13 | [coveralls-image]: https://img.shields.io/coveralls/zhuyali/d3-tree.svg 14 | [coveralls-url]: https://coveralls.io/r/zhuyali/d3-tree?branch=master 15 | [node-image]: https://img.shields.io/badge/node.js-%3E=_8-green.svg 16 | [node-url]: http://nodejs.org/download/ 17 | [download-image]: https://img.shields.io/npm/dm/d3-tree.svg 18 | [download-url]: https://npmjs.org/package/d3-tree 19 | 20 | > tree view based on d3 21 | 22 | ## Installment 23 | 24 | ```bash 25 | $ npm i d3-tree --save-dev 26 | ``` 27 | 28 | 29 | 30 | ## Contributors 31 | 32 | |[
xudafeng](https://github.com/xudafeng)
|[
zhuyali](https://github.com/zhuyali)
|[
zivyangll](https://github.com/zivyangll)
|[
SamuelZhaoY](https://github.com/SamuelZhaoY)
| 33 | | :---: | :---: | :---: | :---: | 34 | 35 | 36 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Sat Jul 03 2021 23:20:45 GMT+0800`. 37 | 38 | 39 | 40 | ## License 41 | 42 | The MIT License (MIT) 43 | -------------------------------------------------------------------------------- /assets/page.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | overflow: hidden; 3 | } 4 | 5 | .d3-tree-node { 6 | fill: #fff; 7 | font-weight: normal; 8 | font-size: 14px; 9 | font-family: Lato,Helvetica Neue For Number,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif; 10 | } 11 | 12 | .d3-tree-link { 13 | fill: none; 14 | stroke: #000; 15 | } 16 | 17 | #container svg { 18 | background: #fff; 19 | } 20 | -------------------------------------------------------------------------------- /assets/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const D3Tree = require('../lib/d3-tree'); 4 | 5 | const testImg = "https://avatars2.githubusercontent.com/u/9263023?s=200&v=4"; 6 | const testImg2 = `[ 7 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 8 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 9 | ]` 10 | const testImg3 = `[ 11 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 12 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 13 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 14 | ]` 15 | const testImg4 = `[ 16 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 17 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 18 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 19 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 20 | ]` 21 | const testImg5 = `[ 22 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 23 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 24 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 25 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 26 | https://avatars2.githubusercontent.com/u/9263023?s=200&v=4 27 | ]` 28 | let data = { 29 | data: { 30 | image: testImg5, 31 | text: 'five images', 32 | }, 33 | children: [{ 34 | data: { 35 | image: testImg, 36 | text: null, 37 | }, 38 | children: [{ 39 | data: { 40 | image: testImg4, 41 | text: 'multi\nline中文\n中文中文中文中文\nmulti\nline中文\nline中文', 42 | }, 43 | children: [] 44 | }, { 45 | data: { 46 | image: testImg2, 47 | text: 'two images', 48 | }, 49 | children: [] 50 | }] 51 | }, { 52 | data: { 53 | image: null, 54 | text: 'test12', 55 | }, 56 | children: [] 57 | }, { 58 | data: { 59 | image: testImg3, 60 | text: 'three images', 61 | }, 62 | children: [] 63 | }] 64 | }; 65 | 66 | var d3tree = window.d3tree = new D3Tree({ 67 | selector: '#container', 68 | data: data, 69 | width: window.innerWidth, 70 | height: window.innerHeight, 71 | duration: 1000 72 | }); 73 | 74 | d3tree.init(); 75 | 76 | document.querySelector('#append').addEventListener('click', () => { 77 | data.children.push({ 78 | image: testImg, 79 | text: 'new', 80 | children: [] 81 | }); 82 | data.children[0].children.push({ 83 | image: testImg, 84 | text: 'new', 85 | children: [] 86 | }); 87 | d3tree.update(data); 88 | }, false); 89 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | d3 tree 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /lib/d3-tree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const d3 = require('d3'); 4 | const _ = require('lodash'); 5 | 6 | class D3Tree { 7 | 8 | constructor(options) { 9 | 10 | if (options.data.data && options.data.data.image) { 11 | options.data.image = options.data.data.image; 12 | } 13 | if (options.data.data && options.data.data.text) { 14 | options.data.text = options.data.data.text; 15 | } 16 | 17 | this._transtromTree(options.data.children); 18 | this.tree = null; 19 | this.root = null; 20 | this.container = null; 21 | this.layerDepth = null; 22 | this.maxLayerDepth = 0; 23 | this.resourceHeight = 100; 24 | this.resourceWidth = 100; 25 | this.transition = null; 26 | this.textLength = 0; 27 | this.fontSize = 14; 28 | this.lineHeight = 1.5; 29 | 30 | this.options = Object.assign({ 31 | width: 800, 32 | height: 600, 33 | duration: 0, 34 | selector: 'body', 35 | prefixClass: 'd3-tree', 36 | data: {}, 37 | resourceHeight: 200, 38 | resourceWidth: 200, 39 | imageMargin: 10 40 | }, options); 41 | } 42 | 43 | _transtromTree(suites) { 44 | suites && suites.forEach((suite, index) => { 45 | if (suite.data && suite.data.image) { 46 | suite.image = suite.data.image; 47 | } 48 | if (suite.data && suite.data.text) { 49 | suite.text = suite.data.text; 50 | } 51 | 52 | if (suite.children) { 53 | this._transtromTree(suite.children); 54 | } 55 | }); 56 | return suites; 57 | } 58 | 59 | init() { 60 | this.container = d3 61 | .select(this.options.selector) 62 | .append('svg') 63 | .attr('width', this.options.width) 64 | .attr('height', this.options.height) 65 | .append('g'); 66 | this.renderTree(); 67 | } 68 | 69 | multiline() { 70 | return selection => { 71 | selection.each(function (d, i) { 72 | let text = d3.select(this); 73 | const lines = (d.data.text || '').split(/\n/); 74 | const lineCount = lines.length; 75 | const isMulti = lineCount !== 1; 76 | for (let i = 0; i < lineCount; i++) { 77 | const line = lines[i]; 78 | text.append('tspan') 79 | .attr('x', 0) 80 | .attr('dy', isMulti ? '1.5em' : '0') 81 | .text(line); 82 | } 83 | }); 84 | }; 85 | } 86 | 87 | findMaxLayer(root, layer, maxLayerDepth) { 88 | if (root.children) { 89 | root.children.forEach(child => { 90 | let childTextLength = child.data.text ? child.data.text.length : 0; 91 | const lines = (child.data.text || '').split(/\n/); 92 | const lineCount = lines.length; 93 | const isMulti = lineCount !== 1; 94 | if (isMulti) { 95 | let line = _.maxBy(lines, line => escape(line).length); 96 | childTextLength = line.length; 97 | } 98 | this.textLength = this.textLength < childTextLength ? childTextLength : this.textLength; 99 | maxLayerDepth[layer + 1]++; 100 | this.findMaxLayer(child, layer + 1, maxLayerDepth); 101 | }); 102 | } 103 | return maxLayerDepth; 104 | } 105 | 106 | checkResourceHeight(root) { 107 | if (root.children) { 108 | let imageMargin = root.children.length > 1 ? root.children[1].x - root.children[0].x : 0; 109 | if (this.resourceHeight + this.options.imageMargin > imageMargin && imageMargin) { 110 | this.resourceHeight = imageMargin - this.options.imageMargin; 111 | this.resourceWidth = this.resourceHeight * this.options.resourceWidth / this.options.resourceHeight; 112 | } 113 | root.children.forEach(child => { 114 | this.checkResourceHeight(child); 115 | }); 116 | } 117 | } 118 | 119 | renderTree() { 120 | const that = this; 121 | this.root = d3.hierarchy(this.options.data); 122 | this.layerDepth = Array(this.root.height + 1).fill(0); 123 | this.maxLayerDepth = this.findMaxLayer(this.root, 0, this.layerDepth); 124 | this.resourceHeight = this.options.height / Math.max(...this.maxLayerDepth) >= this.options.resourceHeight ? 125 | this.options.resourceHeight : 126 | this.options.height / Math.max(...this.maxLayerDepth) - 10; 127 | this.resourceWidth = this.resourceHeight * this.options.resourceWidth / this.options.resourceHeight; 128 | this.tree = d3.tree().size([ 129 | this.options.height, 130 | this.options.width - this.resourceWidth - this.fontSize * this.textLength 131 | ]); 132 | this.tree(this.root); 133 | this.checkResourceHeight(this.root); 134 | 135 | this 136 | .container 137 | .selectAll(`.${this.options.prefixClass}-link`) 138 | .data(this.root.descendants().slice(1)) 139 | .enter() 140 | .append('path') 141 | .attr('class', `${this.options.prefixClass}-link`) 142 | .attr('d', d => { 143 | return `M${d.y},${d.x}C${d.parent.y + this.resourceWidth},${d.x} ${d.parent.y + this.resourceWidth + 110},${d.parent.x} ${d.parent.y + this.resourceWidth + 10},${d.parent.x}`; 144 | }); 145 | 146 | const node = this 147 | .container 148 | .selectAll(`.${this.options.prefixClass}-node`) 149 | .data(this.root.descendants()) 150 | .enter() 151 | .append('g') 152 | .attr('class', `${this.options.prefixClass}-node`) 153 | .attr('transform', d => { 154 | return `translate(${d.y},${d.x})`; 155 | }) 156 | .each(function(d) { 157 | let resourceList = d.data.image; 158 | if (resourceList && !!~resourceList.indexOf('[')) { 159 | resourceList = resourceList 160 | .replace(/(\[\n|\n\]| )/g, '') 161 | .split('\n'); 162 | } else { 163 | resourceList = [resourceList]; 164 | } 165 | 166 | const resourceWidth = that.resourceWidth / resourceList.length; 167 | const resourceHeight = that.resourceHeight / resourceList.length; 168 | 169 | resourceList.forEach((resource, index) => { 170 | if (resource && resource.endsWith('.webm')) { 171 | d3.select(this) 172 | .append('video') 173 | .attr('src', resource) 174 | .attr('type', 'video/webm') 175 | .attr('controls', 'controls') 176 | .attr('data-index', index) 177 | .attr('style', d => { 178 | return `width: ${resourceWidth}px; height: ${resourceHeight}px`; 179 | }) 180 | .attr('transform', d => { 181 | return `translate(${resourceWidth * index}, -${resourceHeight / 2})`; 182 | }); 183 | } else { 184 | d3.select(this) 185 | .append('image') 186 | .attr('xlink:href', resource) 187 | .attr('data-index', index) 188 | .attr('height', d => { 189 | return resourceHeight; 190 | }) 191 | .attr('transform', d => { 192 | return `translate(${resourceWidth * index}, -${resourceHeight / 2})`; 193 | }) 194 | .attr('width', d => { 195 | return resourceWidth; 196 | }); 197 | } 198 | }); 199 | }); 200 | 201 | node 202 | .append('text') 203 | .attr('fill', '#111') 204 | .call(this.multiline()) 205 | .attr('transform', d => { 206 | const lines = (d.data.text || '').split(/\n/); 207 | const lineCount = lines.length; 208 | const isMulti = lineCount !== 1; 209 | let heightHalf = isMulti ? this.fontSize * this.lineHeight * (lineCount + 1) / 2 : 0; 210 | if (d.data.image) { 211 | return `translate(${that.resourceWidth + 10}, ${-heightHalf})`; 212 | } else { 213 | const textLength = d.data && d.data.text && d.data.text.length || 0; 214 | return `translate(${that.resourceWidth / 2 - 2 * textLength}, ${-heightHalf})`; 215 | } 216 | }); 217 | }; 218 | 219 | update(data) { 220 | this.options.data = data; 221 | this.renderTree(); 222 | this.transition = this.container.transition().duration(this.options.duration); 223 | this.transition.selectAll(`.${this.options.prefixClass}-link`) 224 | .attr('d', d => { 225 | return `M${d.y},${d.x}C${d.parent.y + this.resourceWidth},${d.x} ${d.parent.y + this.resourceWidth + 110},${d.parent.x} ${d.parent.y + this.resourceWidth + 10},${d.parent.x}`; 226 | }); 227 | this.transition.selectAll(`.${this.options.prefixClass}-node`) 228 | .attr('transform', d => { 229 | return `translate(${d.y},${d.x})`; 230 | }); 231 | this.transition.selectAll('text') 232 | .attr('transform', d => { 233 | if (d.data.image) { 234 | return `translate(${this.resourceWidth + 10}, 4)`; 235 | } else { 236 | const textLength = d.data && d.data.text && d.data.text.length || 0; 237 | return `translate(${this.resourceWidth / 2 - 2 * textLength}, 4)`; 238 | } 239 | }); 240 | } 241 | } 242 | 243 | module.exports = D3Tree; 244 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-tree", 3 | "version": "1.0.25", 4 | "description": "tree view based on d3", 5 | "keywords": [ 6 | "d3", 7 | "tree" 8 | ], 9 | "main": "./dist/d3-tree", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/zhuyali/d3-tree.git" 13 | }, 14 | "dependencies": { 15 | "d3": "^4.12.0", 16 | "lodash": "^4.17.4" 17 | }, 18 | "devDependencies": { 19 | "babel-cli": "^6.26.0", 20 | "babel-core": "^6.17.0", 21 | "babel-loader": "^7.1.0", 22 | "babel-plugin-istanbul": "^4.1.5", 23 | "babel-preset-env": "^1.6.1", 24 | "babel-preset-latest": "^6.24.1", 25 | "babel-preset-stage-2": "^6.22.0", 26 | "cross-env": "^5.1.2", 27 | "eslint": "^4.12.1", 28 | "eslint-plugin-mocha": "^4.11.0", 29 | "git-contributor": "1", 30 | "macaca-cli": "2", 31 | "macaca-coverage": "1", 32 | "macaca-electron": "11", 33 | "macaca-reporter": "1", 34 | "macaca-wd": "3", 35 | "pre-commit": "*", 36 | "webpack": "^3.10.0", 37 | "webpack-dev-server": "^2.9.7" 38 | }, 39 | "scripts": { 40 | "dev": "webpack-dev-server", 41 | "lint": "eslint --fix lib assets test", 42 | "dev:test": "cross-env NODE_ENV=test webpack-dev-server", 43 | "serve": "npm run dev:test &", 44 | "contributor": "git-contributor", 45 | "test": "macaca run -d ./test", 46 | "prepublish": "npm run build", 47 | "compile": "babel lib/ --out-dir dist/ -D", 48 | "build": "cross-env NODE_ENV=production webpack -p --progress --hide-modules && npm run compile", 49 | "ci": "npm run lint && npm run serve && npm run test" 50 | }, 51 | "pre-commit": [ 52 | "lint" 53 | ], 54 | "license": "MIT" 55 | } 56 | -------------------------------------------------------------------------------- /test/d3-tree.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | driver, 5 | BASE_URL 6 | } from './helper'; 7 | 8 | describe('test/d3-tree.test.js', () => { 9 | 10 | describe('page func testing', () => { 11 | 12 | before(() => { 13 | return driver 14 | .initWindow({ 15 | width: 1280, 16 | height: 800, 17 | deviceScaleFactor: 2 18 | }); 19 | }); 20 | 21 | afterEach(function () { 22 | return driver 23 | .coverage() 24 | .saveScreenshots(this); 25 | }); 26 | 27 | after(() => { 28 | return driver 29 | .openReporter(true) 30 | .quit(); 31 | }); 32 | 33 | it('page render should be ok', () => { 34 | return driver 35 | .getUrl(BASE_URL) 36 | .sleep(1000) 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | webpackHelper, 5 | } from 'macaca-wd'; 6 | 7 | export const { 8 | driver, 9 | } = webpackHelper; 10 | 11 | driver.configureHttp({ 12 | timeout: 100 * 1000, 13 | retries: 5, 14 | retryDelay: 5, 15 | }); 16 | 17 | const webpackDevServerPort = 8080; 18 | 19 | export const BASE_URL = `http://127.0.0.1:${webpackDevServerPort}`; 20 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter macaca-reporter 2 | --require babel-register 3 | --recursive 4 | --timeout 30000 5 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: { 5 | page: path.resolve(__dirname, 'assets', 'page'), 6 | }, 7 | output: { 8 | path: path.resolve(__dirname, 'build'), 9 | publicPath: '/build', 10 | filename: '[name].js' 11 | }, 12 | module: { 13 | loaders: [ 14 | { 15 | test: /\.js$/, 16 | loader: "babel-loader", 17 | exclude: /node_modules/ 18 | }, 19 | { 20 | test: /\.json$/, 21 | loader: 'json', 22 | exclude: /node_modules/ 23 | } 24 | ] 25 | }, 26 | resolve: { 27 | alias: { 28 | d3: 'd3/build/d3.js' 29 | } 30 | }, 31 | }; 32 | --------------------------------------------------------------------------------