├── .github
└── workflows
│ ├── build.yml
│ ├── deploy.yml
│ └── release.yml
├── .gitignore
├── LICENSE.md
├── README-CN.md
├── README.md
├── docs
├── canvas-tree-chart-CN.md
├── canvas-tree-chart.md
├── react-tree-chart.md
├── tech-underneath.md
├── vue-tree-chart-CN.md
└── vue-tree-chart.md
├── lerna.json
├── package-lock.json
├── package.json
├── packages
├── react-tree-chart
│ ├── .babelrc
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── rollup.config.js
│ ├── src
│ │ ├── demo
│ │ │ ├── App.css
│ │ │ ├── App.tsx
│ │ │ ├── index.css
│ │ │ ├── logo.svg
│ │ │ └── react-app-env.d.ts
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── react-app-env.d.ts
│ │ ├── setupTests.ts
│ │ └── tree-chart
│ │ │ ├── react-tree-chart.scss
│ │ │ ├── react-tree-chart.tsx
│ │ │ └── typings.d.ts
│ └── tsconfig.json
├── tree-chart-core
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── base
│ │ │ └── uuid.ts
│ │ ├── index.ts
│ │ └── tree-chart
│ │ │ ├── constant.ts
│ │ │ ├── index.ts
│ │ │ ├── tree-chart.ts
│ │ │ └── util.ts
│ └── tsconfig.json
├── tree-chart-demo
│ ├── build
│ │ ├── webpack.config.base.js
│ │ ├── webpack.config.dev.js
│ │ └── webpack.config.prod.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── base
│ │ │ ├── color-util.ts
│ │ │ ├── data-generator.ts
│ │ │ ├── utils.ts
│ │ │ └── uuid.ts
│ │ ├── components
│ │ │ ├── CanvasTree.vue
│ │ │ ├── VueTreeDemo.vue
│ │ │ └── org-chart.ts
│ │ ├── demo
│ │ │ ├── App.vue
│ │ │ ├── google-icon.css
│ │ │ ├── main.ts
│ │ │ └── router
│ │ │ │ ├── constant.ts
│ │ │ │ └── index.ts
│ │ └── vue.shims.d.ts
│ ├── template
│ │ └── index.html
│ └── tsconfig.json
├── vue-tree-chart
│ ├── .babelrc
│ ├── .editorconfig
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── .postcssrc.js
│ ├── .prettierignore
│ ├── README.md
│ ├── build
│ │ ├── webpack.config.base.js
│ │ ├── webpack.config.dev.js
│ │ └── webpack.config.library.js
│ ├── library
│ │ ├── .gitkeep
│ │ └── vue-tree-chart.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── base
│ │ │ └── uuid.ts
│ │ ├── demo
│ │ │ ├── App.vue
│ │ │ └── main.ts
│ │ ├── vue-tree
│ │ │ ├── VueTree.vue
│ │ │ └── index.ts
│ │ └── vue.shims.d.ts
│ ├── template
│ │ ├── favicon.ico
│ │ └── index.html
│ └── tsconfig.json
└── vue3-tree-chart
│ ├── .gitignore
│ ├── README.md
│ ├── babel.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ └── index.html
│ ├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── base
│ │ └── uuid.js
│ ├── components
│ │ └── HelloWorld.vue
│ ├── main.js
│ └── vue-tree
│ │ ├── VueTree.vue
│ │ └── index.js
│ └── vite.config.js
└── screenshots
├── demo.jpeg
├── org-chart.gif
└── unique_color.png
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Check Build
5 |
6 | on:
7 | push:
8 | branches: [master]
9 | pull_request:
10 | branches: [master]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [14.x]
19 |
20 | steps:
21 | - uses: actions/checkout@v2
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | - name: install dependencies
27 | run: npm run install:deps
28 | - name: (tree-chart-core)Build
29 | working-directory: packages/tree-chart-core
30 | run: npm run build
31 | - name: (vue-tree-chart)Build
32 | working-directory: packages/vue-tree-chart
33 | run: npm run build:component
34 | - name: (tree-chart-demo)Build
35 | working-directory: packages/tree-chart-demo
36 | run: npm run build
37 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Deploy Github Page
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 |
10 | jobs:
11 | build:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | node-version: [14.x]
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - name: install dependencies
26 | run: npm run install:deps
27 | - name: Build
28 | working-directory: packages/tree-chart-demo
29 | run: |
30 | npm run build
31 | - name: Deploy 🚀
32 | uses: JamesIves/github-pages-deploy-action@releases/v3
33 | with:
34 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
35 | BRANCH: gh-pages # The branch the action should deploy to.
36 | FOLDER: packages/tree-chart-demo/dist # The folder the action should deploy.
37 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Release
5 |
6 | on:
7 | push:
8 | tags:
9 | - "*"
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-node@v2
17 | with:
18 | node-version: "12.x"
19 | registry-url: "https://registry.npmjs.org"
20 | - name: install dependencies
21 | run: npm run install:deps
22 | - run: npm run build:component
23 | working-directory: packages/vue-tree-chart
24 | - run: npm publish
25 | working-directory: packages/vue-tree-chart
26 | env:
27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | built/ # Ignore out dir in tsconfig.json
8 | es
9 |
10 |
11 | # Editor directories and files
12 | .idea
13 | .vscode
14 | *.suo
15 | *.ntvs*
16 | *.njsproj
17 | *.sln
18 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Shen Shuntian
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-CN.md:
--------------------------------------------------------------------------------
1 | ## [English](./README.md) | [中文](./README-CN.md)
2 |
3 | ## Demo 页面
4 |
5 | https://ssthouse.github.io/tree-chart/#/svgTree
6 |
7 | ## Demo 动图
8 |
9 | 
10 |
11 |
12 | ## Vue Tree Chart (同时支持 Vue2 和 Vue3)
13 |
14 | 请参考: [vue-tree-chart](./docs/vue-tree-chart.md)
15 |
16 | ## Canvas Tree Chart
17 |
18 | 请参考: [canvas tree chart](./docs/canvas-tree-chart.md)
19 |
20 | ## 开始开发
21 |
22 | ```bash
23 | npm install
24 |
25 | # serve with hot reload at localhost
26 | npm run dev
27 |
28 | # build for production with minification (build to ./docs folder, which can be auto servered by github page 🤓)
29 | npm run build
30 | ```
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## [English](./README.md) | [中文](./README-CN.md)
2 |
3 |
4 | ## Demo
5 |
6 | https://ssthouse.github.io/tree-chart/#/svgTree
7 |
8 | ## Demo Gif
9 |
10 | 
11 |
12 |
13 | ## Vue Tree Chart (support both Vue2 and Vue3)
14 |
15 | please refer [vue-tree-chart](./docs/vue-tree-chart.md)
16 |
17 | ## React Tree Chart
18 |
19 | please refer [react-tree-chart](./docs/react-tree-chart.md)
20 |
21 | ## Canvas Tree Chart
22 |
23 | please refer [canvas tree chart](./docs/canvas-tree-chart.md)
24 |
25 | ## Build Setup
26 |
27 | ```bash
28 | # install dependencies
29 | npm install
30 |
31 | # serve with hot reload at localhost
32 | npm run dev
33 |
34 | # build for production with minification (build to ./docs folder, which can be auto servered by github page 🤓)
35 | npm run build
36 | ```
37 |
--------------------------------------------------------------------------------
/docs/canvas-tree-chart-CN.md:
--------------------------------------------------------------------------------
1 | # Canvas Tree Chart
2 |
3 | ## Canvas 版本 使用到的技术点
4 |
5 | - 将 D3.js 和 Canvas 一起使用,提升绘制效率(其中 D3.js 使用虚拟 DOM 就行渲染,Canvas 取虚拟 DOM 节点坐标进行绘制)
6 | - 使用 `唯一颜色值`的方案,实现 Canvas 上点击事件的监听 (你也可以参考这篇文档了解其详细实现: https://medium.com/@lverspohl/how-to-turn-d3-and-canvas-into-good-friends-b7a240a32915)
7 |
8 | ## Canavs API
9 |
10 | canvas 的版本, 因为其绘制过程较难抽象, 且仅仅在数据量较大的情况下才有意义,所以没有发布为 npm module.
11 |
12 | 如果你希望使用 canvas 版本的 tree-chart,可以将源代码下载下来,并进行一下步骤替换为自己的数据集:
13 |
14 | - 将 `packages/tree-chart-demo/src/base/data-generator.js`文件中的数据替换为你自己的数据.
15 | - 在 `packages/tree-chart-demo/src/components/org-chart.js`文件中,修改`drawShowCanvas`函数的绘制逻辑.
16 |
--------------------------------------------------------------------------------
/docs/canvas-tree-chart.md:
--------------------------------------------------------------------------------
1 | # Canvas Tree Chart
2 |
3 | ## Canvas version Using Tech
4 |
5 | - use D3.js with Canvas to draw organizationChart more efficiently.
6 | - Use `unique-color` manner to identify mouse click event in Canvas (you can refer to https://medium.com/@lverspohl/how-to-turn-d3-and-canvas-into-good-friends-b7a240a32915 to see detail)
7 |
8 |
9 | ### Canvas version API
10 |
11 | the canvas version is not published with npm module.
12 |
13 | if you want to use this project's canvas version, please download the source code and edit with the following steps:
14 |
15 | - replace the data in `packages/tree-chart-demo/src/base/data-generator.js` with your own nested data.
16 | - add your data drawing logic in `packages/tree-chart-demo/src/components/org-chart.js #drawShowCanvas`
--------------------------------------------------------------------------------
/docs/react-tree-chart.md:
--------------------------------------------------------------------------------
1 | ## Demo page
2 |
3 | https://codesandbox.io/s/react-tree-chart-544i2?file=/src/App.tsx
4 |
5 | ## How to use?
6 |
7 | #### 1. install npm module
8 |
9 | install **Vue2 version**
10 |
11 | ```shell
12 | npm install @ssthouse/react-tree-chart
13 | ```
14 |
15 | #### 2. import component
16 |
17 | ```javascript
18 | import TreeChart from "@ssthouse/react-tree-chart";
19 | import "@ssthouse/react-tree-chart/lib/react-tree-chart.css";
20 | ```
21 |
22 | you can also check this [codesanbox example](https://codesandbox.io/s/react-tree-chart-544i2?file=/src/App.tsx)
23 |
24 | #### 3. use component
25 |
--------------------------------------------------------------------------------
/docs/tech-underneath.md:
--------------------------------------------------------------------------------
1 | ## Using Tech
2 |
3 | ### Svg version
4 |
5 | - use D3 to calculate node & link positon
6 | - use Vue to handle dom element entring and leaving
7 | - use Vue slot to let user easily use with their own data
--------------------------------------------------------------------------------
/docs/vue-tree-chart-CN.md:
--------------------------------------------------------------------------------
1 | ## [English](./vue-tree-chart.md)
2 |
3 | ## Demo 页面
4 |
5 | https://ssthouse.github.io/tree-chart/#/svgTree
6 |
7 | ## Demo 动图
8 |
9 | 
10 |
11 | ## 使用到的技术点
12 |
13 | ### Svg 版本
14 |
15 | - 使用 D3.js 计算**节点**和**链接线**的坐标
16 | - 使用 Vue 控制 DOM 节点的变更
17 | - 使用 Vue slot 抽象节点渲染流程, 让使用者可以高度定制化节点绘制
18 |
19 | ### Canvas 版本
20 |
21 | - 将 D3.js 和 Canvas 一起使用,提升绘制效率(其中 D3.js 使用虚拟 DOM 就行渲染,Canvas 取虚拟 DOM 节点坐标进行绘制)
22 | - 使用 `唯一颜色值`的方案,实现 Canvas 上点击事件的监听 (你也可以参考这篇文档了解其详细实现: https://medium.com/@lverspohl/how-to-turn-d3-and-canvas-into-good-friends-b7a240a32915)
23 |
24 | ## 如何将图中数据替换为我的数据?
25 |
26 | ### Svg version
27 |
28 | Svg 版本通过 Vue 进行了良好的封装,使用起来非常方便且灵活.
29 |
30 | #### 1.安装
31 |
32 | 执行下面的命令安装 Svg 版本的 tree-chart
33 |
34 | `npm install @ssthouse/vue-tree-chart`
35 |
36 | #### 2. 注册 `vue-tree` 组件
37 |
38 | ```javascript
39 | import VueTree from "@ssthouse/vue-tree-chart";
40 | import Vue from "vue";
41 | Vue.component("vue-tree", VueTree);
42 | ```
43 |
44 | #### 3. 使用组件
45 |
46 | **3.1 基本用法**
47 |
48 |
49 | See Code
50 |
51 | ```vue
52 |
53 |
54 |
59 |
60 |
61 |
62 |
63 |
80 |
81 |
88 | ```
89 |
90 |
91 |
92 | 
93 |
94 | **3.2 使用 vue-slot 异化展示折叠节点**
95 |
96 |
97 |
98 | See Code
99 |
100 | ```vue
101 |
102 |
103 |
108 |
109 | {{ node.value }}
114 |
115 |
116 |
117 |
118 |
119 |
136 |
137 |
154 | ```
155 |
156 |
157 |
158 |
159 | 
160 |
161 | **3.3 自定义渲染富媒体节点**
162 |
163 |
164 |
165 | See Code
166 |
167 |
168 | ```vue
169 |
170 |
171 |
176 |
177 |
189 |
190 |
191 |
192 |
193 |
194 |
258 |
259 |
278 | ```
279 |
280 |
281 |
282 | 
283 |
284 | #### 4. API
285 |
286 | **4.1 props 参数**
287 |
288 | | | type | default | description |
289 | | --------- | ------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
290 | | dataset | [Object, Array] | null | nested tree data or an array of nested tree (multi root tree) |
291 | | config | Object | {
nodeWidth: 100,
nodeHeight: 100,
levelHeight: 200
} | nodeWidth 和 nodeHeight 用于配置树状图节点大小. levelHeight 用于配置树状图一层的高度 |
292 | | linkStyle | String | 'curve' | 控制连接线样式, 可选项: 'curve' 或 'straight' |
293 | | direction | string | 'vertical' | 控制树状图方向, 可选项: 'vertical' 或 'horizontal' |
294 |
295 | **4.2 slot 参数**
296 |
297 | 该组件仅支持 **默认 slot**.
298 |
299 | 基本用法如下所示:
300 |
301 | ```vue
302 |
303 | {{ node.value }}
308 |
309 | ```
310 |
311 | slot 提供两个参数用于渲染树状图节点内容。
312 |
313 | | slot param | type | description |
314 | | ---------- | ------- | -------------------------------- |
315 | | node | Object | current node data to be rendered |
316 | | collapsed | Boolean | current node collapse status |
317 |
318 | **4.3 API > 缩放**
319 |
320 | 通过Vue ref,可以调用组件的缩放接口
321 |
322 | 支持的接口有:
323 |
324 | 缩小: `this.$refs.tree.zoomIn()`
325 |
326 | 放大: `this.$refs.tree.zoomOut()`
327 |
328 | 恢复原始大小: `this.$refs.tree.restoreScale()`
329 |
330 | ### Canavs 版本
331 |
332 | canvas 的版本, 因为其绘制过程较难抽象, 且仅仅在数据量较大的情况下才有意义,所以没有发布为 npm module.
333 |
334 | 如果你希望使用 canvas 版本的 tree-chart,可以将源代码下载下来,并进行一下步骤替换为自己的数据集:
335 |
336 | - 将 `/src/base/data-generator.js`文件中的数据替换为你自己的数据.
337 | - 在 `/src/components/org-chart.js`文件中,修改`drawShowCanvas`函数的绘制逻辑.
338 |
339 | ## 开始开发
340 |
341 | ```bash
342 | # install dependencies
343 | npm install
344 |
345 | # serve with hot reload at localhost
346 | npm run dev
347 |
348 | # build for production with minification (build to ./docs folder, which can be auto servered by github page 🤓)
349 | npm run build
350 | ```
351 |
--------------------------------------------------------------------------------
/docs/vue-tree-chart.md:
--------------------------------------------------------------------------------
1 | ## [中文](./vue-tree-chart-CN.md)
2 |
3 | 
4 |
5 | ## Demo page
6 |
7 | https://ssthouse.github.io/tree-chart/#/svgTree
8 |
9 | ## Demo Gif
10 |
11 | 
12 |
13 | ## Using Tech
14 |
15 | ### Svg version
16 |
17 | - use D3 to calculate node & link positon
18 | - use Vue to handle dom element entring and leaving
19 | - use Vue slot to let user easily use with their own data
20 |
21 | ## How to use?
22 |
23 | ### Svg version
24 |
25 | #### 1. install npm module
26 |
27 | install **Vue2 version**
28 |
29 | ```shell
30 | npm install @ssthouse/vue-tree-chart
31 | ```
32 |
33 | install **Vue3 version**
34 |
35 | ```shell
36 | npm install @ssthouse/vue3-tree-chart
37 | ```
38 |
39 | #### 2. register `vue-tree` component
40 |
41 | For Vue2
42 |
43 | ```javascript
44 | import VueTree from '@ssthouse/vue-tree-chart'
45 | import Vue from 'vue'
46 | Vue.component('vue-tree', VueTree)
47 | ```
48 |
49 | For Vue3
50 |
51 | ```javascript
52 | import VueTree from "@ssthouse/vue3-tree-chart";
53 | import "@ssthouse/vue3-tree-chart/dist/vue3-tree-chart.css";
54 | ```
55 |
56 | you can also check this [codesanbox example](https://codesandbox.io/s/vue3-tree-chart-demo-j11uj?file=/src/App.vue)
57 |
58 | #### 3. use component
59 |
60 | **3.1 basic usage**
61 |
62 |
63 | See Code
64 |
65 | ```vue
66 |
67 |
68 |
73 |
74 |
75 |
76 |
77 |
94 |
95 |
102 | ```
103 |
104 |
105 |
106 | 
107 |
108 | **3.2 show collapsed node in different style**
109 |
110 |
111 | See Code
112 |
113 | ```vue
114 |
115 |
116 |
121 |
122 | {{ node.value }}
127 |
128 |
129 |
130 |
131 |
132 |
149 |
150 |
167 | ```
168 |
169 |
170 |
171 | 
172 |
173 | **3.3 render rich media data**
174 |
175 |
176 | See Code
177 |
178 |
179 | ```vue
180 |
181 |
182 |
187 |
188 |
200 |
201 |
202 |
203 |
204 |
205 |
269 |
270 |
289 | ```
290 |
291 |
292 |
293 | 
294 |
295 | **3.4 render tree with multiple parents**
296 |
297 |
298 | See Code
299 |
300 | ```vue
301 |
302 |
303 |
309 |
310 |
314 | 能力值{{ node.name }}
317 |
318 |
319 |
320 |
321 |
322 |
376 |
377 |
396 | ```
397 |
398 |
399 | 
400 |
401 | #### 4. API
402 |
403 | **4.1 props**
404 |
405 | | | type | default | description |
406 | | --------- | ------ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
407 | | dataset | [Object,Array] | null | nested tree data or an array of nested tree (multi root tree) |
408 | | config | Object | {
nodeWidth: 100,
nodeHeight: 100,
levelHeight: 200
} | nodeWidth and nodeHeight config the tree node size. levelHeight is tree row height |
409 | | linkStyle | String | 'curve' | control link style, options: 'curve' or 'straight' |
410 | | direction | string | 'vertical' | control tree chart direction, options: 'vertical' or 'horizontal' |
411 | | collapse-enabled | Boolean | true | Control whether when clicking on a node it collapses its children |
412 |
413 | **4.2 slot**
414 |
415 | this component only support **default slot**.
416 |
417 | a sample usage like this:
418 |
419 | ```vue
420 |
421 | {{ node.value }}
426 |
427 | ```
428 |
429 | there are two slot params provided to render slot content:
430 |
431 | | slot param | type | description |
432 | | ---------- | ------- | -------------------------------- |
433 | | node | Object | current node data to be rendered |
434 | | collapsed | Boolean | current node collapse status |
435 |
436 |
437 | **4.3 API > zoom**
438 |
439 | use vue ref to call zoom api.
440 |
441 | support methods:
442 |
443 | zoom in: `this.$refs.tree.zoomIn()`
444 |
445 | zoom out: `this.$refs.tree.zoomOut()`
446 |
447 | restore initial scale: `this.$refs.tree.restoreScale()`
448 |
449 |
450 |
451 | ## Build Setup
452 |
453 | ```bash
454 | # install dependencies
455 | npm install
456 |
457 | # serve with hot reload at localhost
458 | npm run dev
459 |
460 | # build for production with minification (build to ./docs folder, which can be auto servered by github page 🤓)
461 | npm run build
462 | ```
463 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*"
4 | ],
5 | "version": "independent"
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ssthouse/vue-tree-chart",
3 | "private": true,
4 | "author": "ssthouse",
5 | "scripts": {
6 | "install:deps": "lerna bootstrap",
7 | "build:component": "lerna run build:component --scope @ssthouse/vue-tree-chart",
8 | "build:demo": "lerna run build --scope @ssthouse/vue-tree-chart",
9 | "release:all": "lerna run release"
10 | },
11 | "devDependencies": {
12 | "lerna": "^4.0.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-typescript", {
4 | "allExtensions": true,
5 | "isTsx": true
6 | }],
7 | "@babel/react"
8 | ]
9 |
10 | }
--------------------------------------------------------------------------------
/packages/react-tree-chart/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /lib
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `yarn eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ssthouse/react-tree-chart",
3 | "version": "0.1.2",
4 | "dependencies": {
5 | "@ssthouse/tree-chart-core": "1.1.2",
6 | "react": "^17.0.2",
7 | "react-dom": "^17.0.2",
8 | "react-transition-group": "^1.2.1"
9 | },
10 | "main": "lib/react-tree-chart.js",
11 | "module": "es/react-tree-chart.js",
12 | "scripts": {
13 | "start": "BROWSER=none react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test",
16 | "eject": "react-scripts eject",
17 | "build:component": "rm -rf lib es && rollup -c",
18 | "release": "npm run build:component && npm publish"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | },
38 | "devDependencies": {
39 | "@babel/cli": "^7.16.0",
40 | "@babel/preset-react": "^7.16.0",
41 | "@babel/preset-typescript": "^7.16.0",
42 | "@rollup/plugin-commonjs": "^21.0.1",
43 | "@testing-library/jest-dom": "^5.11.4",
44 | "@testing-library/react": "^11.1.0",
45 | "@testing-library/user-event": "^12.1.10",
46 | "@types/jest": "^26.0.15",
47 | "@types/node": "^12.0.0",
48 | "@types/react": "^17.0.0",
49 | "@types/react-dom": "^17.0.0",
50 | "node-sass": "^7.0.0",
51 | "react-scripts": "4.0.3",
52 | "rollup": "^2.61.1",
53 | "rollup-plugin-scss": "3",
54 | "rollup-plugin-typescript2": "^0.31.1",
55 | "sass": "^1.45.0",
56 | "typescript": "^4.1.2",
57 | "web-vitals": "^1.0.1"
58 | },
59 | "publishConfig": {
60 | "registry": "https://registry.npmjs.org/"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/packages/react-tree-chart/public/favicon.ico
--------------------------------------------------------------------------------
/packages/react-tree-chart/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/packages/react-tree-chart/public/logo192.png
--------------------------------------------------------------------------------
/packages/react-tree-chart/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/packages/react-tree-chart/public/logo512.png
--------------------------------------------------------------------------------
/packages/react-tree-chart/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from "@rollup/plugin-commonjs";
2 | import typescript from "rollup-plugin-typescript2";
3 | import scss from 'rollup-plugin-scss'
4 |
5 | const packageJson = require("./package.json");
6 |
7 | const rollupConfig = {
8 | input: "./src/tree-chart/react-tree-chart.tsx",
9 | output: [
10 | {
11 | file: packageJson.main,
12 | format: "cjs",
13 | sourcemap: true
14 | },
15 | {
16 | file: packageJson.module,
17 | format: "es",
18 | sourcemap: true
19 | }
20 | ],
21 | plugins: [
22 | typescript({
23 | rollupCommonJSResolveHack: false,
24 | clean: true,
25 | }),
26 | scss(),
27 | // peerDepsExternal(),
28 | // resolve(),
29 | commonjs(),
30 | ]
31 | };
32 |
33 | export default rollupConfig;
34 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/demo/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/demo/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | function App() {
6 | return (
7 |
23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/demo/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/demo/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/demo/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import TreeChart from './tree-chart/react-tree-chart';
5 |
6 | const sampleData = {
7 | value: "1",
8 | children: [
9 | { value: "2", children: [{ value: "4" }, { value: "5" }] },
10 | { value: "3" },
11 | ],
12 | };
13 |
14 | const sampleDataTwo = {
15 | value: "100",
16 | children: [
17 | { value: "3" },
18 | { value: "2", children: [{ value: "555" }, { value: "444" }] },
19 | ],
20 | };
21 |
22 | const Demo = () => {
23 | const [dataSet, setDataset] = useState(sampleData);
24 | const [enableCollapse, setEnableCollapse] = useState(true);
25 | const treeChartRef = useRef(null);
26 |
27 | return
28 |
29 |
33 |
34 |
35 |
45 | {(data as any).value + (collapsed ? 'yes' : 'no')}
46 |
}
47 | />
48 |
49 | }
50 |
51 | ReactDOM.render(
52 |
53 |
54 | ,
55 | document.getElementById('root')
56 | );
57 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/tree-chart/react-tree-chart.scss:
--------------------------------------------------------------------------------
1 | .tree-node-item-enter {
2 | opacity: 0.01;
3 | }
4 |
5 | .tree-node-item-enter.tree-node-item-enter-active {
6 | opacity: 1;
7 | transition: opacity 800md ease-in-out;
8 | }
9 |
10 | .tree-node-item-leave {
11 | opacity: 1;
12 | }
13 |
14 | .tree-node-item-leave.tree-node-item-leave-active {
15 | opacity: 0.01;
16 | transition: opacity 800ms ease-in-out;
17 | }
18 |
19 | .tree-container {
20 | position: relative;
21 | overflow: hidden;
22 |
23 | .vue-tree {
24 | position: relative;
25 | }
26 |
27 | > svg,
28 | .dom-container {
29 | width: 100%;
30 | height: 100%;
31 | position: absolute;
32 | left: 0;
33 | top: 0;
34 | overflow: visible;
35 | transform-origin: 0 50%;
36 | }
37 |
38 | .dom-container {
39 | z-index: 1;
40 | pointer-events: none;
41 | }
42 | }
43 |
44 | .node-slot {
45 | cursor: pointer;
46 | pointer-events: all;
47 | position: absolute;
48 | background-color: transparent;
49 | box-sizing: border-box;
50 | transform: translate(-50%, -50%);
51 | display: flex;
52 | align-items: center;
53 | justify-content: center;
54 | box-sizing: content-box;
55 | transition: all 0.8s;
56 | transition-timing-function: ease-in-out;
57 | }
58 |
59 | .tree-container {
60 | .node {
61 | fill: grey !important;
62 | }
63 |
64 | .link {
65 | stroke-width: 2px !important;
66 | fill: transparent !important;
67 | stroke: #cecece !important;
68 | }
69 | }
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/tree-chart/react-tree-chart.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
2 | import TreeChartCore, { DEFAULT_LEVEL_HEIGHT, DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH, Direction, TreeLinkStyle } from '@ssthouse/tree-chart-core';
3 | import { CSSTransitionGroup } from 'react-transition-group'
4 | import './react-tree-chart.scss';
5 |
6 | const formatDimension = (dimension: number | string) => {
7 | if (typeof dimension === "number") return `${dimension}px`;
8 | if (dimension.indexOf("px") !== -1) {
9 | return dimension;
10 | } else {
11 | return `${dimension}px`;
12 | }
13 | };
14 |
15 | export interface TreeChartNodeProps {
16 | data: unknown;
17 | collapsed: boolean;
18 | }
19 |
20 | interface TreeChartConfig {
21 | nodeWidth: number;
22 | nodeHeight: number;
23 | levelHeight: number;
24 | }
25 |
26 | interface TreeChartProps {
27 | /** basic tree node size config */
28 | config?: TreeChartConfig;
29 | /** link node with straight line or curve line */
30 | linkStyle?: TreeLinkStyle;
31 | /** chart direction, vertical or horizontal */
32 | direction?: Direction;
33 | /** if chart node can be click to toggle collapse status */
34 | collapseEnabled?: boolean;
35 | dataset: Object | Object[];
36 | /** css style for contaienr Div */
37 | style?: React.CSSProperties;
38 | /**
39 | * custom tree node component
40 | * default node is {value}
41 | * */
42 | renderCustomNode?: React.FC;
43 | }
44 |
45 | const DEFAULT_CONFIG = {
46 | nodeWidth: DEFAULT_NODE_WIDTH,
47 | nodeHeight: DEFAULT_NODE_HEIGHT,
48 | levelHeight: DEFAULT_LEVEL_HEIGHT,
49 | };
50 |
51 | const TreeChart = forwardRef((props: TreeChartProps, ref) => {
52 | const {
53 | direction = Direction.VERTICAL,
54 | config = DEFAULT_CONFIG,
55 | dataset,
56 | collapseEnabled = true,
57 | style = {},
58 | renderCustomNode: customNode
59 | } = props;
60 | const [treeChartCore, setTreeChartCore] = useState();
61 | const [initialTransformStyle, setInitialTransformStyle] = useState({})
62 | const [nodeDataList, setNodeDataList] = useState([]);
63 |
64 | // refs
65 | const containerRef = useRef(null);
66 | const domContainerRef = useRef(null);
67 | const svgRef = useRef(null);
68 |
69 | // init tree chart core with refs
70 | useEffect(() => {
71 | console.log('update collapse', collapseEnabled)
72 | const treeChartCore = new TreeChartCore({
73 | svgElement: svgRef.current as SVGElement,
74 | domElement: domContainerRef.current as HTMLDivElement,
75 | treeContainer: containerRef.current as HTMLDivElement,
76 | dataset,
77 | collapseEnabled,
78 | treeConfig: config,
79 | });
80 |
81 | setTreeChartCore(treeChartCore);
82 | }, [collapseEnabled, dataset, config])
83 |
84 | useEffect(() => {
85 | if (!treeChartCore) return;
86 | treeChartCore.init();
87 | const nodeDataList = treeChartCore.getNodeDataList();
88 | const initialTransformStyle = treeChartCore.getInitialTransformStyle();
89 | setInitialTransformStyle(initialTransformStyle)
90 | setNodeDataList(nodeDataList);
91 | }, [treeChartCore]);
92 |
93 | const onClickNode = (index: number) => {
94 | if (!treeChartCore) return;
95 | treeChartCore.onClickNode(index);
96 | setNodeDataList(treeChartCore.getNodeDataList());
97 | }
98 |
99 | useImperativeHandle(ref, () => {
100 | return {
101 | zoomIn() {
102 | treeChartCore?.zoomIn();
103 | },
104 | zoomOut() {
105 | treeChartCore?.zoomOut();
106 | },
107 | restoreScale() {
108 | treeChartCore?.setScale(1);
109 | },
110 | }
111 | })
112 | return
114 |
115 |
120 |
121 |
125 | {
126 | nodeDataList.map((node, index) => {
127 | return onClickNode(index)}
130 | key={node.data._key}
131 | style={{
132 | left: formatDimension(
133 | direction === Direction.VERTICAL ? node.x : node.y
134 | ),
135 | top: formatDimension(
136 | direction === Direction.VERTICAL ? node.y : node.x
137 | ),
138 | width: formatDimension(config.nodeWidth),
139 | height: formatDimension(config.nodeHeight),
140 | }}
141 | >
142 | {
143 | customNode
144 | ? customNode({ collapsed: node.data._collapsed, data: node.data })
145 | : {node.data.value}
146 | }
147 |
148 | })
149 | }
150 |
151 |
152 |
153 | });
154 |
155 | export default TreeChart;
156 |
--------------------------------------------------------------------------------
/packages/react-tree-chart/src/tree-chart/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'react-transition-group'
--------------------------------------------------------------------------------
/packages/react-tree-chart/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "declaration": true,
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src/tree-chart"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/tree-chart-core/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "eslint:recommended",
3 | "parserOptions": {
4 | "sourceType": "module",
5 | ecmaVersion: 2015
6 | }
7 | }
--------------------------------------------------------------------------------
/packages/tree-chart-core/.gitignore:
--------------------------------------------------------------------------------
1 | build
--------------------------------------------------------------------------------
/packages/tree-chart-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ssthouse/tree-chart-core",
3 | "version": "1.1.2",
4 | "description": "tree chart's core logic (shared by Vue2,Vue3,React)",
5 | "main": "build/index.js",
6 | "module": "build/index.js",
7 | "scripts": {
8 | "build": "rm -rf build && npx tsc --declaration true",
9 | "release": "npm run build && npm publish"
10 | },
11 | "author": "ssthouse",
12 | "license": "ISC",
13 | "dependencies": {
14 | "d3": "^7.2.0"
15 | },
16 | "devDependencies": {
17 | "eslint": "^8.4.1",
18 | "typescript": "^4.5.2"
19 | },
20 | "publishConfig": {
21 | "registry": "https://registry.npmjs.org/"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/tree-chart-core/src/base/uuid.ts:
--------------------------------------------------------------------------------
1 |
2 | export function uuid(): string {
3 | const s = []
4 | const hexDigits = '0123456789abcdef'
5 | for (let i = 0; i < 36; i++) {
6 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
7 | }
8 | s[14] = '4'
9 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
10 | s[8] = s[13] = s[18] = s[23] = '-'
11 | return s.join('')
12 | }
13 |
--------------------------------------------------------------------------------
/packages/tree-chart-core/src/index.ts:
--------------------------------------------------------------------------------
1 | import TreeChartCore from "./tree-chart/index";
2 |
3 | export default TreeChartCore;
4 |
5 | export * from './tree-chart/constant';
6 | export * from './tree-chart/tree-chart';
--------------------------------------------------------------------------------
/packages/tree-chart-core/src/tree-chart/constant.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_NODE_WIDTH = 100;
2 | export const DEFAULT_NODE_HEIGHT = 100;
3 | export const DEFAULT_LEVEL_HEIGHT = 200;
4 |
5 | /**
6 | * Used to decrement the height of the 'initTransformY' to center diagrams.
7 | * This is only a hotfix caused by the addition of '__invisible_root' node
8 | * for multi root purposes.
9 | */
10 | export const DEFAULT_HEIGHT_DECREMENT = 200;
11 |
12 | export const ANIMATION_DURATION = 800;
13 |
14 | export const MATCH_TRANSLATE_REGEX = /translate\((-?\d+)px, ?(-?\d+)px\)/i;
15 | export const MATCH_SCALE_REGEX = /scale\((\S*)\)/i;
--------------------------------------------------------------------------------
/packages/tree-chart-core/src/tree-chart/index.ts:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3';
2 | import { ANIMATION_DURATION, DEFAULT_HEIGHT_DECREMENT, DEFAULT_LEVEL_HEIGHT, DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH, MATCH_SCALE_REGEX, MATCH_TRANSLATE_REGEX } from './constant';
3 | import { TreeDataset, Direction, TreeLinkStyle } from './tree-chart';
4 | import { deepCopy, rotatePoint } from './util';
5 |
6 |
7 | interface TreeConfig {
8 | nodeWidth: number;
9 | nodeHeight: number;
10 | levelHeight: number;
11 | }
12 |
13 |
14 | interface TreeChartCoreParams {
15 | treeConfig?: TreeConfig;
16 | linkStyle?: TreeLinkStyle;
17 | direction?: Direction;
18 | collapseEnabled: boolean;
19 | dataset: TreeDataset;
20 | svgElement: SVGElement;
21 | domElement: HTMLDivElement;
22 | treeContainer: HTMLDivElement;
23 | }
24 |
25 | export default class TreeChartCore {
26 | treeConfig: TreeConfig = {
27 | nodeWidth: DEFAULT_NODE_WIDTH,
28 | nodeHeight: DEFAULT_NODE_HEIGHT,
29 | levelHeight: DEFAULT_LEVEL_HEIGHT,
30 | };
31 | linkStyle: TreeLinkStyle = TreeLinkStyle.CURVE;
32 | direction: Direction = Direction.VERTICAL;
33 | collapseEnabled: boolean = true
34 |
35 | dataset: TreeDataset;
36 |
37 | svgElement: SVGElement;
38 | svgSelection: any;
39 | domElement: HTMLDivElement;
40 | treeContainer: HTMLDivElement;
41 |
42 | nodeDataList: any[];
43 | linkDataList: any[];
44 | initTransformX: number;
45 | initTransformY: number;
46 |
47 | currentScale: number = 1;
48 |
49 | constructor(params: TreeChartCoreParams) {
50 | if (params.treeConfig) {
51 | this.treeConfig = params.treeConfig;
52 | }
53 | this.collapseEnabled = params.collapseEnabled
54 | this.svgElement = params.svgElement;
55 | this.domElement = params.domElement;
56 | this.treeContainer = params.treeContainer;
57 | this.dataset = this.updatedInternalData(params.dataset);
58 | if (params.direction) this.direction = params.direction;
59 | if (params.linkStyle) this.linkStyle = params.linkStyle
60 | }
61 |
62 | init() {
63 | this.draw();
64 | this.enableDrag();
65 | this.initTransform();
66 | }
67 |
68 | getNodeDataList() {
69 | return this.nodeDataList;
70 | }
71 |
72 | getInitialTransformStyle(): Record {
73 | return {
74 | transform: `scale(1) translate(${this.initTransformX}px, ${this.initTransformY}px)`,
75 | transformOrigin: "center",
76 | };
77 | }
78 |
79 | zoomIn() {
80 | const originTransformStr = this.domElement.style.transform;
81 | // 如果已有scale属性, 在原基础上修改
82 | let targetScale = 1 * 1.2;
83 | const scaleMatchResult = originTransformStr.match(MATCH_SCALE_REGEX);
84 | if (scaleMatchResult && scaleMatchResult.length > 0) {
85 | const originScale = parseFloat(scaleMatchResult[1]);
86 | targetScale *= originScale;
87 | }
88 | this.setScale(targetScale);
89 | }
90 |
91 | zoomOut() {
92 | const originTransformStr = this.domElement.style.transform;
93 | // 如果已有scale属性, 在原基础上修改
94 | let targetScale = 1 / 1.2;
95 | const scaleMatchResult = originTransformStr.match(MATCH_SCALE_REGEX);
96 | if (scaleMatchResult && scaleMatchResult.length > 0) {
97 | const originScale = parseFloat(scaleMatchResult[1]);
98 | targetScale = originScale / 1.2;
99 | }
100 | this.setScale(targetScale);
101 | }
102 |
103 | restoreScale() {
104 | this.setScale(1);
105 | }
106 |
107 | setScale(scaleNum) {
108 | if (typeof scaleNum !== "number") return;
109 | let pos = this.getTranslate();
110 | let translateString = `translate(${pos[0]}px, ${pos[1]}px)`;
111 | this.svgElement.style.transform = `scale(${scaleNum}) ` + translateString;
112 | this.domElement.style.transform =
113 | `scale(${scaleNum}) ` + translateString;
114 | this.currentScale = scaleNum;
115 | }
116 | getTranslate() {
117 | let string = this.svgElement.style.transform;
118 | let match = string.match(MATCH_TRANSLATE_REGEX);
119 | if (match === null) {
120 | return [null, null];
121 | }
122 | let x = parseInt(match[1]);
123 | let y = parseInt(match[2]);
124 | return [x, y];
125 | }
126 |
127 |
128 | isVertical() {
129 | return this.direction === Direction.VERTICAL;
130 | }
131 | /**
132 | * 根据link数据,生成svg path data
133 | */
134 | private generateLinkPath(d) {
135 | const self = this;
136 | if (this.linkStyle === TreeLinkStyle.CURVE) {
137 | return this.generateCurceLinkPath(self, d);
138 | }
139 | if (this.linkStyle === TreeLinkStyle.STRAIGHT) {
140 | // the link path is: source -> secondPoint -> thirdPoint -> target
141 | return this.generateStraightLinkPath(d);
142 | }
143 | }
144 |
145 | private generateCurceLinkPath(self: this, d: any) {
146 | const linkPath = this.isVertical()
147 | ? d3.linkVertical()
148 | : d3.linkHorizontal();
149 | linkPath
150 | .x(function (d) {
151 | return d.x;
152 | })
153 | .y(function (d) {
154 | return d.y;
155 | })
156 | .source(function (d) {
157 | const sourcePoint = {
158 | x: d.source.x,
159 | y: d.source.y,
160 | };
161 | return self.direction === Direction.VERTICAL
162 | ? sourcePoint
163 | : rotatePoint(sourcePoint);
164 | })
165 | .target(function (d) {
166 | const targetPoint = {
167 | x: d.target.x,
168 | y: d.target.y,
169 | };
170 | return self.direction === Direction.VERTICAL
171 | ? targetPoint
172 | : rotatePoint(targetPoint);
173 | });
174 | return linkPath(d);
175 | }
176 |
177 | private generateStraightLinkPath(d: any) {
178 | const linkPath = d3.path();
179 | let sourcePoint = { x: d.source.x, y: d.source.y };
180 | let targetPoint = { x: d.target.x, y: d.target.y };
181 | if (!this.isVertical()) {
182 | sourcePoint = rotatePoint(sourcePoint);
183 | targetPoint = rotatePoint(targetPoint);
184 | }
185 | const xOffset = targetPoint.x - sourcePoint.x;
186 | const yOffset = targetPoint.y - sourcePoint.y;
187 | const secondPoint = this.isVertical()
188 | ? { x: sourcePoint.x, y: sourcePoint.y + yOffset / 2 }
189 | : { x: sourcePoint.x + xOffset / 2, y: sourcePoint.y };
190 | const thirdPoint = this.isVertical()
191 | ? { x: targetPoint.x, y: sourcePoint.y + yOffset / 2 }
192 | : { x: sourcePoint.x + xOffset / 2, y: targetPoint.y };
193 | linkPath.moveTo(sourcePoint.x, sourcePoint.y);
194 | linkPath.lineTo(secondPoint.x, secondPoint.y);
195 | linkPath.lineTo(thirdPoint.x, thirdPoint.y);
196 | linkPath.lineTo(targetPoint.x, targetPoint.y);
197 | return linkPath.toString();
198 | }
199 |
200 | updateDataList() {
201 | let [nodeDataList, linkDataList] = this.buildTree()
202 | nodeDataList.splice(0, 1);
203 | linkDataList = linkDataList.filter(
204 | (x) => x.source.data.name !== "__invisible_root"
205 | );
206 | this.linkDataList = linkDataList;
207 | this.nodeDataList = nodeDataList;
208 | }
209 |
210 | private draw() {
211 | this.updateDataList();
212 | const identifier = this.dataset["identifier"];
213 | const specialLinks = this.dataset["links"];
214 | if (specialLinks && identifier) {
215 | for (const link of specialLinks) {
216 | let parent,
217 | children = undefined;
218 | if (identifier === "value") {
219 | parent = this.nodeDataList.find((d) => {
220 | return d[identifier] == link.parent;
221 | });
222 | children = this.nodeDataList.filter((d) => {
223 | return d[identifier] == link.child;
224 | });
225 | } else {
226 | parent = this.nodeDataList.find((d) => {
227 | return d["data"][identifier] == link.parent;
228 | });
229 | children = this.nodeDataList.filter((d) => {
230 | return d["data"][identifier] == link.child;
231 | });
232 | }
233 | if (parent && children) {
234 | for (const child of children) {
235 | const new_link = {
236 | source: parent,
237 | target: child,
238 | };
239 | this.linkDataList.push(new_link);
240 | }
241 | }
242 | }
243 | }
244 |
245 | this.svgSelection = d3.select(this.svgElement);
246 |
247 | const self = this;
248 | const links = this.svgSelection
249 | .selectAll(".link")
250 | .data(this.linkDataList, (d) => {
251 | return `${d.source.data._key}-${d.target.data._key}`;
252 | });
253 |
254 | links
255 | .enter()
256 | .append("path")
257 | .style("opacity", 0)
258 | .transition()
259 | .duration(ANIMATION_DURATION)
260 | .ease(d3.easeCubicInOut)
261 | .style("opacity", 1)
262 | .attr("class", "link")
263 | .attr("d", function (d) {
264 | return self.generateLinkPath(d);
265 | });
266 | links
267 | .transition()
268 | .duration(ANIMATION_DURATION)
269 | .ease(d3.easeCubicInOut)
270 | .attr("d", function (d) {
271 | return self.generateLinkPath(d);
272 | });
273 | links
274 | .exit()
275 | .transition()
276 | .duration(ANIMATION_DURATION / 2)
277 | .ease(d3.easeCubicInOut)
278 | .style("opacity", 0)
279 | .remove();
280 | }
281 |
282 | /**
283 | * Returns updated dataset by deep copying every nodes from the externalData and adding unique '_key' attributes.
284 | **/
285 | private updatedInternalData(externalData) {
286 | var data = { name: "__invisible_root", children: [] };
287 | if (!externalData) return data;
288 | if (Array.isArray(externalData)) {
289 | for (var i = externalData.length - 1; i >= 0; i--) {
290 | data.children.push(deepCopy(externalData[i]));
291 | }
292 | } else {
293 | data.children.push(deepCopy(externalData));
294 | }
295 | return data;
296 | }
297 |
298 | private buildTree() {
299 | const treeBuilder = d3
300 | .tree()
301 | .nodeSize([this.treeConfig.nodeWidth, this.treeConfig.levelHeight]);
302 | const tree = treeBuilder(d3.hierarchy(this.dataset));
303 | return [tree.descendants(), tree.links()];
304 | }
305 |
306 | private enableDrag() {
307 | let startX = 0;
308 | let startY = 0;
309 | let isDrag = false;
310 | // 保存鼠标点下时的位移
311 | let mouseDownTransform = "";
312 | this.treeContainer.onmousedown = (event) => {
313 | mouseDownTransform = this.svgElement.style.transform;
314 | startX = event.clientX;
315 | startY = event.clientY;
316 | isDrag = true;
317 | };
318 | this.treeContainer.onmousemove = (event) => {
319 | if (!isDrag) return;
320 | const originTransform = mouseDownTransform;
321 | let originOffsetX = 0;
322 | let originOffsetY = 0;
323 | if (originTransform) {
324 | const result = originTransform.match(MATCH_TRANSLATE_REGEX);
325 | if (result !== null && result.length !== 0) {
326 | const [offsetX, offsetY] = result.slice(1);
327 | originOffsetX = parseInt(offsetX);
328 | originOffsetY = parseInt(offsetY);
329 | }
330 | }
331 | let newX =
332 | Math.floor((event.clientX - startX) / this.currentScale) +
333 | originOffsetX;
334 | let newY =
335 | Math.floor((event.clientY - startY) / this.currentScale) +
336 | originOffsetY;
337 | let transformStr = `translate(${newX}px, ${newY}px)`;
338 | if (originTransform) {
339 | transformStr = originTransform.replace(
340 | MATCH_TRANSLATE_REGEX,
341 | transformStr
342 | );
343 | }
344 | this.svgElement.style.transform = transformStr;
345 | this.domElement.style.transform = transformStr;
346 | };
347 |
348 | this.treeContainer.onmouseup = () => {
349 | startX = 0;
350 | startY = 0;
351 | isDrag = false;
352 | };
353 | }
354 |
355 | initTransform() {
356 | const containerWidth = this.domElement.offsetWidth;
357 | const containerHeight = this.domElement.offsetHeight;
358 | if (this.isVertical()) {
359 | this.initTransformX = Math.floor(containerWidth / 2);
360 | this.initTransformY = Math.floor(
361 | this.treeConfig.nodeHeight - DEFAULT_HEIGHT_DECREMENT
362 | );
363 | } else {
364 | this.initTransformX = Math.floor(
365 | this.treeConfig.nodeWidth - DEFAULT_HEIGHT_DECREMENT
366 | );
367 | this.initTransformY = Math.floor(containerHeight / 2);
368 | }
369 | }
370 |
371 | onClickNode(index: number) {
372 | if (this.collapseEnabled) {
373 | const curNode = this.nodeDataList[index];
374 | if (curNode.data.children) {
375 | curNode.data._children = curNode.data.children;
376 | curNode.data.children = null;
377 | curNode.data._collapsed = true;
378 | } else {
379 | curNode.data.children = curNode.data._children;
380 | curNode.data._children = null;
381 | curNode.data._collapsed = false;
382 | }
383 | this.draw();
384 | }
385 | }
386 |
387 | /**
388 | * call this function to update dataset
389 | * notice : you need to update the view rendered by `nodeDataList` too
390 | * @param dataset the new dataset to show in chart
391 | */
392 | updateDataset(dataset: TreeDataset) {
393 | this.dataset = this.updatedInternalData(dataset);
394 | this.draw();
395 | }
396 |
397 | /**
398 | * release all dom reference
399 | */
400 | destroy() {
401 | this.svgElement = null;
402 | this.domElement = null;
403 | this.treeContainer = null;
404 | }
405 | }
--------------------------------------------------------------------------------
/packages/tree-chart-core/src/tree-chart/tree-chart.ts:
--------------------------------------------------------------------------------
1 |
2 | export enum Direction {
3 | VERTICAL = "vertical",
4 | HORIZONTAL = "horizontal",
5 | }
6 |
7 | export enum TreeLinkStyle {
8 | CURVE = "curve",
9 | STRAIGHT = "straight",
10 | }
11 |
12 | export type TreeDataset = Object | Object[];
13 |
--------------------------------------------------------------------------------
/packages/tree-chart-core/src/tree-chart/util.ts:
--------------------------------------------------------------------------------
1 | import { uuid } from "../base/uuid";
2 |
3 | export function rotatePoint({ x, y }: { x: number; y: number }) {
4 | return {
5 | x: y,
6 | y: x,
7 | };
8 | }
9 |
10 |
11 | /**
12 | * Returns a deep copy of selected node (copy of itself and it's children).
13 | * If selected node or it's children have no '_key' attribute it will assign a new one.
14 | **/
15 | export function deepCopy(node) {
16 | let obj = { _key: uuid() };
17 | for (var key in node) {
18 | if (node[key] === null) {
19 | obj[key] = null;
20 | } else if (Array.isArray(node[key])) {
21 | obj[key] = node[key].map((x) => deepCopy(x));
22 | } else if (typeof node[key] === "object") {
23 | obj[key] = deepCopy(node[key]);
24 | } else {
25 | obj[key] = node[key];
26 | }
27 | }
28 | return obj;
29 | }
30 |
--------------------------------------------------------------------------------
/packages/tree-chart-core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build/",
4 | "sourceMap": false,
5 | "strict": false,
6 | "noImplicitReturns": true,
7 | "module": "es2015",
8 | "lib": ["es2015", "DOM"],
9 | "moduleResolution": "node",
10 | "target": "es5"
11 | },
12 | "include": ["src/**/*"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/build/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { VueLoaderPlugin } = require('vue-loader')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | module.exports = {
6 | entry: './src/demo/main.ts',
7 | output: {
8 | path: path.resolve(__dirname, '../dist'),
9 | filename: 'bundle.js'
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.css$/,
15 | use: ['style-loader', 'css-loader']
16 | },
17 | {
18 | test: /\.less$/,
19 | use: ['style-loader', 'css-loader', 'less-loader']
20 | },
21 | {
22 | test: /\.(png|svg|jpg|gif)$/,
23 | use: ['file-loader']
24 | },
25 | {
26 | test: /\.vue$/,
27 | use: ['vue-loader']
28 | },
29 | {
30 | test: /\.ts$/,
31 | loader: 'ts-loader',
32 | exclude: /node_modules/,
33 | options: {
34 | appendTsSuffixTo: [/\.vue$/]
35 | }
36 | }
37 | ]
38 | },
39 | plugins: [
40 | new VueLoaderPlugin(),
41 | new HtmlWebpackPlugin({
42 | filename: 'index.html',
43 | template: './template/index.html',
44 | inject: true
45 | })
46 | ],
47 | resolve: {
48 | extensions: ['.ts', '.js', '.vue', '.json'],
49 | alias: {
50 | vue$: 'vue/dist/vue.esm.js'
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/build/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 |
3 | module.exports = merge(require('./webpack.config.base'), {
4 | mode: 'development',
5 | output: {
6 | publicPath: '/'
7 | },
8 | devtool: 'source-map'
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/build/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 | const webpack = require('webpack')
3 |
4 | module.exports = merge(require('./webpack.config.base'), {
5 | mode: 'production',
6 | devtool: 'source-map',
7 | output: {
8 | publicPath: './'
9 | },
10 | optimization: {
11 | minimize: true
12 | }
13 | })
14 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tree-chart-demo",
3 | "version": "0.0.1",
4 | "description": "tree chart demo page",
5 | "main": "index.js",
6 | "scripts": {
7 | "lint": "eslint --ext .js,.vue,.ts src",
8 | "dev": "webpack-dev-server --progress --config ./build/webpack.config.dev.js",
9 | "build": "webpack --config ./build/webpack.config.prod.js"
10 | },
11 | "author": "ssthouse",
12 | "license": "ISC",
13 | "dependencies": {
14 | "@ssthouse/tree-chart-core": "^1.0.0",
15 | "@ssthouse/vue-tree-chart": "^0.5.4",
16 | "d3": "^7.1.1",
17 | "vue": "2.6.8",
18 | "vue-router": "^3.5.3",
19 | "vuetify": "^1.0.18"
20 | },
21 | "devDependencies": {
22 | "@typescript-eslint/eslint-plugin": "^2.34.0",
23 | "@typescript-eslint/parser": "^2.34.0",
24 | "@vue/eslint-config-prettier": "^6.0.0",
25 | "@vue/eslint-config-typescript": "^5.0.2",
26 | "autoprefixer": "^7.1.2",
27 | "babel-core": "^6.22.1",
28 | "babel-eslint": "^8.2.1",
29 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
30 | "babel-loader": "^7.1.1",
31 | "babel-plugin-syntax-jsx": "^6.18.0",
32 | "babel-plugin-transform-runtime": "^6.22.0",
33 | "babel-plugin-transform-vue-jsx": "^3.5.0",
34 | "babel-preset-env": "^1.3.2",
35 | "babel-preset-stage-2": "^6.22.0",
36 | "chalk": "^2.0.1",
37 | "copy-webpack-plugin": "^9.0.1",
38 | "css-loader": "^6.5.0",
39 | "eslint": "^8.1.0",
40 | "eslint-config-standard": "^10.2.1",
41 | "eslint-friendly-formatter": "^3.0.0",
42 | "eslint-loader": "^1.7.1",
43 | "eslint-plugin-import": "^2.7.0",
44 | "eslint-plugin-node": "^5.2.0",
45 | "eslint-plugin-prettier": "^3.1.4",
46 | "eslint-plugin-promise": "^3.4.0",
47 | "eslint-plugin-standard": "^3.0.1",
48 | "eslint-plugin-vue": "^6.0.0",
49 | "extract-text-webpack-plugin": "^3.0.0",
50 | "file-loader": "^1.1.4",
51 | "html-webpack-plugin": "^5.5.0",
52 | "husky": "^7.0.4",
53 | "lerna": "^4.0.0",
54 | "less": "^4.1.2",
55 | "less-loader": "^10.2.0",
56 | "lint-staged": "^10.2.11",
57 | "node-notifier": "^8.0.1",
58 | "optimize-css-assets-webpack-plugin": "^6.0.1",
59 | "ora": "^1.2.0",
60 | "portfinder": "^1.0.13",
61 | "postcss-import": "^11.0.0",
62 | "postcss-loader": "^6.2.0",
63 | "postcss-url": "^7.2.1",
64 | "prettier": "^2.0.5",
65 | "rimraf": "^2.6.0",
66 | "semver": "^5.3.0",
67 | "shelljs": "^0.8.5",
68 | "style-loader": "^3.3.1",
69 | "ts-loader": "^9.2.6",
70 | "typescript": "^3.3.3333",
71 | "uglifyjs-webpack-plugin": "^2.2.0",
72 | "url-loader": "^4.1.1",
73 | "vue-class-component": "^7.0.1",
74 | "vue-loader": "^15.7.0",
75 | "vue-property-decorator": "^8.0.0",
76 | "vue-style-loader": "^4.1.3",
77 | "vue-template-compiler": "2.6.8",
78 | "webpack": "^5.61.0",
79 | "webpack-bundle-analyzer": "^3.3.2",
80 | "webpack-cli": "^4.9.1",
81 | "webpack-dev-server": "^4.4.0",
82 | "webpack-merge": "^4.1.0"
83 | },
84 | "browserslist": [
85 | "> 1%",
86 | "last 2 versions",
87 | "not ie <= 8"
88 | ],
89 | "prettier": {
90 | "#": "prettier config in here :)",
91 | "singleQuote": true,
92 | "semi": false,
93 | "arrowParens": "always",
94 | "space-before-function-paren": "off",
95 | "trailingComma": "none"
96 | },
97 | "husky": {
98 | "hooks": {
99 | "pre-commit": "lint-staged"
100 | }
101 | },
102 | "lint-staged": {
103 | "*.js": [
104 | "eslint",
105 | "prettier --write"
106 | ],
107 | "*.vue": [
108 | "eslint",
109 | "prettier --write"
110 | ]
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/base/color-util.ts:
--------------------------------------------------------------------------------
1 | export function randomColor(): string {
2 | const letters = [
3 | '0',
4 | '1',
5 | '2',
6 | '3',
7 | '4',
8 | '5',
9 | '6',
10 | '7',
11 | '8',
12 | '9',
13 | 'a',
14 | 'b',
15 | 'c',
16 | 'd',
17 | 'e',
18 | 'f'
19 | ]
20 | let color = '#'
21 | for (let i = 0; i < 6; i++) {
22 | color += letters[Math.floor(Math.random() * 16)]
23 | }
24 | return color
25 | }
26 |
27 | function appendFront0(numStr: string): string {
28 | return numStr.padStart(2, '0')
29 | }
30 |
31 | export function getColorStrFromCanvas(
32 | context: any,
33 | xIndex: number,
34 | yIndex: number
35 | ): string {
36 | const pixelData = context.getImageData(xIndex, yIndex, 1, 1).data
37 | const [r, g, b] = pixelData
38 | return (
39 | '#' +
40 | appendFront0(r.toString(16)) +
41 | appendFront0(g.toString(16)) +
42 | appendFront0(b.toString(16))
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/base/data-generator.ts:
--------------------------------------------------------------------------------
1 | export interface Data {
2 | name: string
3 | title: string
4 | children: any
5 | }
6 |
7 | export function generateOrgChartData(depth: number) {
8 | const data: Data = {
9 | name: 'Lao Lao',
10 | title: 'general manager',
11 | children: [
12 | { name: 'Bo Miao', title: 'department manager' },
13 | {
14 | name: 'Su Miao',
15 | title: 'department manager',
16 | children: [
17 | { name: 'Tie Hua', title: 'senior engineer' },
18 | {
19 | name: 'Hei Hei',
20 | title: 'senior engineer',
21 | children: [
22 | { name: 'Pang Pang', title: 'engineer' },
23 | { name: 'Xiang Xiang', title: 'UE engineer' }
24 | ]
25 | }
26 | ]
27 | },
28 | { name: 'Hong Miao', title: 'department manager' }
29 | ]
30 | }
31 |
32 | for (let i = 0; i < 3; i++) {
33 | data['children'].push({
34 | name: 'Lao Lao',
35 | title: 'general manager',
36 | children: [
37 | { name: 'Bo Miao', title: 'department manager' },
38 | {
39 | name: 'Su Miao',
40 | title: 'department manager',
41 | children: [{ name: 'Tie Hua', title: 'senior engineer' }]
42 | }
43 | ]
44 | })
45 | }
46 |
47 | let temp = data
48 | for (let i = 0; i < depth; i++) {
49 | if (!temp.children) {
50 | temp.children = []
51 | }
52 | temp.children.push({
53 | name: 'Lao Lao',
54 | title: 'general manager',
55 | children: [
56 | { name: 'Bo Miao', title: 'department manager' },
57 | {
58 | name: 'Su Miao',
59 | title: 'department manager',
60 | children: [
61 | { name: 'Tie Hua', title: 'senior engineer' },
62 | {
63 | name: 'Hei Hei',
64 | title: 'senior engineer',
65 | children: [{ name: 'Xiang Xiang', title: 'UE engineer' }]
66 | }
67 | ]
68 | }
69 | ]
70 | })
71 | temp = temp.children[0]
72 | }
73 | return data
74 | }
75 |
76 | export function generateOrgChartDataFolded(depth: any, foldDepth: number) {
77 | const data = this.generateOrgChartData(depth)
78 | let tempNode = data
79 | for (let i = 0; i < foldDepth && tempNode.children; i++) {
80 | tempNode = tempNode.children[0]
81 | }
82 | tempNode._children = tempNode.children
83 | tempNode.children = null
84 | return data
85 | }
86 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/base/utils.ts:
--------------------------------------------------------------------------------
1 | function text(ctx, text, x: number, y: number, fontSize, fontColor: string) {
2 | ctx.font = '14px Arial'
3 | ctx.fillStyle = fontColor
4 | ctx.fillText(text, x, y)
5 | }
6 |
7 | function wrapText(
8 | context,
9 | text: string,
10 | x: number,
11 | y: number,
12 | maxWidth: number,
13 | lineHeight: number,
14 | fontColor: string
15 | ) {
16 | context.fillStyle = fontColor
17 | const words = text.split(' ')
18 | let line = ''
19 | for (let n = 0; n < words.length; n++) {
20 | let testLine = `${line + words[n]} `
21 | let metrics = context.measureText(testLine)
22 | let testWidth = metrics.width
23 | if (testWidth > maxWidth && n > 0) {
24 | context.fillText(line, x, y)
25 | line = `${words[n]} `
26 | y += lineHeight
27 | } else {
28 | line = testLine
29 | }
30 | }
31 | context.fillText(line, x, y)
32 | }
33 |
34 | function roundRect(
35 | context: CanvasRenderingContext2D,
36 | x: number,
37 | y: number,
38 | width: number,
39 | height: number,
40 | radius: any,
41 | fill: boolean,
42 | stroke: boolean
43 | ): void {
44 | if (typeof stroke === 'undefined') {
45 | stroke = true
46 | }
47 | if (typeof radius === 'undefined') {
48 | radius = 5
49 | }
50 | if (typeof radius === 'number') {
51 | radius = { tl: radius, tr: radius, br: radius, bl: radius }
52 | } else {
53 | let defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 }
54 | for (let side in defaultRadius) {
55 | radius[side] = radius[side] || defaultRadius[side]
56 | }
57 | }
58 | context.beginPath()
59 | context.moveTo(x + radius.tl, y)
60 | context.lineTo(x + width - radius.tr, y)
61 | context.quadraticCurveTo(x + width, y, x + width, y + radius.tr)
62 | context.lineTo(x + width, y + height - radius.br)
63 | context.quadraticCurveTo(
64 | x + width,
65 | y + height,
66 | x + width - radius.br,
67 | y + height
68 | )
69 | context.lineTo(x + radius.bl, y + height)
70 | context.quadraticCurveTo(x, y + height, x, y + height - radius.bl)
71 | context.lineTo(x, y + radius.tl)
72 | context.quadraticCurveTo(x, y, x + radius.tl, y)
73 | context.closePath()
74 | if (fill) {
75 | context.fill()
76 | }
77 | if (stroke) {
78 | context.stroke()
79 | }
80 | }
81 |
82 | export default {
83 | text,
84 | wrapText,
85 | roundRect
86 | }
87 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/base/uuid.ts:
--------------------------------------------------------------------------------
1 |
2 | export function uuid(): string {
3 | const s = []
4 | const hexDigits = '0123456789abcdef'
5 | for (let i = 0; i < 36; i++) {
6 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
7 | }
8 | s[14] = '4'
9 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
10 | s[8] = s[13] = s[18] = s[23] = '-'
11 | return s.join('')
12 | }
13 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/components/CanvasTree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
25 |
26 |
27 |
66 |
67 |
104 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/components/VueTreeDemo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Basic usage | 基本使用
4 |
5 |
10 |
11 |
12 |
Show different style with folded nodes | 异化展示折叠节点
13 |
14 |
19 |
20 | {{ node.value }}
25 |
26 |
27 |
28 |
Rich media tree chart | 富媒体树状图
29 |
34 |
35 |
47 |
48 |
49 |
50 |
Link nodes with straight line | 直线连接
51 |
57 |
58 |
70 |
71 |
72 |
73 |
Horizontal tree chart | 横向树状图
74 |
80 |
81 |
93 |
94 |
95 |
96 |
Zoom in or out | 缩放
97 |
98 | +
99 | -
100 | 1:1
101 |
102 |
108 |
109 |
121 |
122 |
123 |
124 |
125 | Example of multiple parents with node collapse disabled |
126 | 支持多父节点(禁用折叠)
127 |
128 |
135 |
136 |
140 | 能力值 {{ node.name }}
143 |
144 |
145 |
146 |
147 |
148 | Example of multi-root with changing dataset | 多根节点, 支持切换数据源
149 |
150 |
153 |
160 |
161 |
165 | 能力值 {{ node.name }}
168 |
169 |
170 |
171 |
172 |
173 |
174 |
352 |
353 |
401 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/components/org-chart.ts:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3'
2 | import Util from '../base/utils'
3 | import { randomColor, getColorStrFromCanvas } from '../base/color-util'
4 |
5 | class OrgChart {
6 | d3: any
7 | width: number
8 | height: number
9 | padding: number
10 | nodeWidth: number
11 | nodeHeight: number
12 | unitPadding: number
13 | unitWidth: number
14 | unitHeight: number
15 | duration: number
16 | scale: number
17 | data: any
18 | treeGenerator: any
19 | treeData: any
20 | virtualContainerNode: any
21 | container: any
22 | canvasNode: any
23 | hiddenCanvasNode: any
24 | context: any
25 | hiddenContext: any
26 | colorNodeMap: {}
27 | onDrag_: boolean
28 | dragStartPoint_: { x: number; y: number }
29 |
30 | constructor() {
31 | this.d3 = d3
32 | this.init()
33 | }
34 |
35 | init() {
36 | this.initVariables()
37 | this.initCanvas()
38 | this.initVirtualNode()
39 | this.setCanvasListener()
40 | }
41 |
42 | initVariables() {
43 | this.width = window.innerWidth
44 | this.height = window.innerHeight
45 | this.padding = 20
46 | // tree node size
47 | this.nodeWidth = 180
48 | this.nodeHeight = 280
49 | // org unit size
50 | this.unitPadding = 20
51 | this.unitWidth = 140
52 | this.unitHeight = 100
53 | // animation duration
54 | this.duration = 600
55 | this.scale = 1.0
56 | }
57 |
58 | draw(data) {
59 | this.data = this.d3.hierarchy(data)
60 | this.treeGenerator = this.d3
61 | .tree()
62 | .nodeSize([this.nodeWidth, this.nodeHeight])
63 | this.update(null)
64 |
65 | const self = this
66 | this.d3.timer(function () {
67 | self.drawCanvas()
68 | })
69 | }
70 |
71 | update(targetTreeNode) {
72 | this.treeData = this.treeGenerator(this.data)
73 | const nodes = this.treeData.descendants()
74 | const links = this.treeData.links()
75 |
76 | let animatedStartX = 0
77 | let animatedStartY = 0
78 | let animatedEndX = 0
79 | let animatedEndY = 0
80 | if (targetTreeNode) {
81 | animatedStartX = targetTreeNode.x0
82 | animatedStartY = targetTreeNode.y0
83 | animatedEndX = targetTreeNode.x
84 | animatedEndY = targetTreeNode.y
85 | }
86 |
87 | this.updateOrgUnits(
88 | nodes,
89 | animatedStartX,
90 | animatedStartY,
91 | animatedEndX,
92 | animatedEndY
93 | )
94 | this.updateLinks(
95 | links,
96 | animatedStartX,
97 | animatedStartY,
98 | animatedEndX,
99 | animatedEndY
100 | )
101 |
102 | this.addColorKey()
103 | this.bindNodeToTreeData()
104 | }
105 |
106 | updateOrgUnits(
107 | nodes,
108 | animatedStartX,
109 | animatedStartY,
110 | animatedEndX,
111 | animatedEndY
112 | ) {
113 | let orgUnitSelection = this.virtualContainerNode
114 | .selectAll('.orgUnit')
115 | .data(nodes, (d) => d['colorKey'])
116 |
117 | orgUnitSelection
118 | .attr('class', 'orgUnit')
119 | .attr('x', function (data) {
120 | return data.x0
121 | })
122 | .attr('y', function (data) {
123 | return data.y0
124 | })
125 | .transition()
126 | .duration(this.duration)
127 | .attr('x', function (data) {
128 | return data.x
129 | })
130 | .attr('y', function (data) {
131 | return data.y
132 | })
133 | .attr('fillStyle', '#ff0000')
134 |
135 | orgUnitSelection
136 | .enter()
137 | .append('orgUnit')
138 | .attr('class', 'orgUnit')
139 | .attr('x', animatedStartX)
140 | .attr('y', animatedStartY)
141 | .transition()
142 | .duration(this.duration)
143 | .attr('x', function (data) {
144 | return data.x
145 | })
146 | .attr('y', function (data) {
147 | return data.y
148 | })
149 | .attr('fillStyle', '#ff0000')
150 |
151 | orgUnitSelection
152 | .exit()
153 | .transition()
154 | .duration(this.duration)
155 | .attr('x', animatedEndX)
156 | .attr('y', animatedEndY)
157 | .remove()
158 |
159 | // record origin index for animation
160 | nodes.forEach((treeNode) => {
161 | treeNode['x0'] = treeNode.x
162 | treeNode['y0'] = treeNode.y
163 | })
164 |
165 | orgUnitSelection = null
166 | }
167 |
168 | updateLinks(
169 | links,
170 | animatedStartX,
171 | animatedStartY,
172 | animatedEndX,
173 | animatedEndY
174 | ) {
175 | let linkSelection = this.virtualContainerNode
176 | .selectAll('.link')
177 | .data(links, function (d) {
178 | return d.source['colorKey'] + ':' + d.target['colorKey']
179 | })
180 |
181 | linkSelection
182 | .attr('class', 'link')
183 | .attr('sourceX', function (linkData) {
184 | return linkData.source['x00']
185 | })
186 | .attr('sourceY', function (linkData) {
187 | return linkData.source['y00']
188 | })
189 | .attr('targetX', function (linkData) {
190 | return linkData.target['x00']
191 | })
192 | .attr('targetY', function (linkData) {
193 | return linkData.target['y00']
194 | })
195 | .transition()
196 | .duration(this.duration)
197 | .attr('sourceX', function (linkData) {
198 | return linkData.source.x
199 | })
200 | .attr('sourceY', function (linkData) {
201 | return linkData.source.y
202 | })
203 | .attr('targetX', function (linkData) {
204 | return linkData.target.x
205 | })
206 | .attr('targetY', function (linkData) {
207 | return linkData.target.y
208 | })
209 |
210 | linkSelection
211 | .enter()
212 | .append('link')
213 | .attr('class', 'link')
214 | .attr('sourceX', animatedStartX)
215 | .attr('sourceY', animatedStartY)
216 | .attr('targetX', animatedStartX)
217 | .attr('targetY', animatedStartY)
218 | .transition()
219 | .duration(this.duration)
220 | .attr('sourceX', function (link) {
221 | return link.source.x
222 | })
223 | .attr('sourceY', function (link) {
224 | return link.source.y
225 | })
226 | .attr('targetX', function (link) {
227 | return link.target.x
228 | })
229 | .attr('targetY', function (link) {
230 | return link.target.y
231 | })
232 |
233 | linkSelection
234 | .exit()
235 | .transition()
236 | .duration(this.duration)
237 | .attr('sourceX', animatedEndX)
238 | .attr('sourceY', animatedEndY)
239 | .attr('targetX', animatedEndX)
240 | .attr('targetY', animatedEndY)
241 | .remove()
242 |
243 | // record origin data for animation
244 | links.forEach((treeNode) => {
245 | treeNode.source['x00'] = treeNode.source.x
246 | treeNode.source['y00'] = treeNode.source.y
247 | treeNode.target['x00'] = treeNode.target.x
248 | treeNode.target['y00'] = treeNode.target.y
249 | })
250 | linkSelection = null
251 | }
252 |
253 | initCanvas() {
254 | this.container = this.d3.select('#org-chart-container')
255 | this.canvasNode = this.container
256 | .append('canvas')
257 | .attr('class', 'orgChart')
258 | .attr('width', this.width)
259 | .attr('height', this.height)
260 | this.hiddenCanvasNode = this.container
261 | .append('canvas')
262 | .attr('class', 'orgChart')
263 | .attr('width', this.width)
264 | .attr('height', this.height)
265 | .style('display', '')
266 | this.context = this.canvasNode.node().getContext('2d')
267 | this.context.translate(this.width / 2, this.padding)
268 | this.hiddenContext = this.hiddenCanvasNode.node().getContext('2d')
269 | this.hiddenContext.translate(this.width / 2, this.padding)
270 | }
271 |
272 | initVirtualNode() {
273 | let virtualContainer = document.createElement('root')
274 | this.virtualContainerNode = this.d3.select(virtualContainer)
275 | this.colorNodeMap = {}
276 | }
277 |
278 | addColorKey() {
279 | // give each node a unique color
280 | const self = this
281 | this.virtualContainerNode.selectAll('.orgUnit').each(function () {
282 | const node = self.d3.select(this)
283 | let newColor = randomColor()
284 | while (self.colorNodeMap[newColor]) {
285 | newColor = randomColor()
286 | }
287 | node.attr('colorKey', newColor)
288 | node.data()[0]['colorKey'] = newColor
289 | self.colorNodeMap[newColor] = node
290 | })
291 | }
292 |
293 | bindNodeToTreeData() {
294 | // give each node a unique color
295 | const self = this
296 | this.virtualContainerNode.selectAll('.orgUnit').each(function () {
297 | const node = self.d3.select(this)
298 | const data = node.data()[0]
299 | data.node = node
300 | })
301 | }
302 |
303 | drawCanvas() {
304 | this.drawShowCanvas()
305 | this.drawHiddenCanvas()
306 | }
307 |
308 | drawShowCanvas() {
309 | this.context.clearRect(-50000, -10000, 100000, 100000)
310 |
311 | const self = this
312 | // draw links
313 | this.virtualContainerNode.selectAll('.link').each(function () {
314 | const node = self.d3.select(this)
315 | const linkPath = self.d3
316 | .linkVertical()
317 | .x(function (d) {
318 | return d.x
319 | })
320 | .y(function (d) {
321 | return d.y
322 | })
323 | .source(function () {
324 | return { x: node.attr('sourceX'), y: node.attr('sourceY') }
325 | })
326 | .target(function () {
327 | return { x: node.attr('targetX'), y: node.attr('targetY') }
328 | })
329 | const path = new Path2D(linkPath())
330 | self.context.stroke(path)
331 | })
332 |
333 | this.virtualContainerNode.selectAll('.orgUnit').each(function () {
334 | const node = self.d3.select(this)
335 | const treeNode = node.data()[0]
336 | const data = treeNode.data
337 | self.context.fillStyle = '#3ca0ff'
338 | const indexX = Number(node.attr('x')) - self.unitWidth / 2
339 | const indexY = Number(node.attr('y')) - self.unitHeight / 2
340 |
341 | // draw unit outline rect (if you want to modify this line ===> please modify the same line in `drawHiddenCanvas`)
342 | Util.roundRect(
343 | self.context,
344 | indexX,
345 | indexY,
346 | self.unitWidth,
347 | self.unitHeight,
348 | 4,
349 | true,
350 | false
351 | )
352 |
353 | Util.text(
354 | self.context,
355 | data.name,
356 | indexX + self.unitPadding,
357 | indexY + self.unitPadding,
358 | '20px',
359 | '#ffffff'
360 | )
361 | // Util.text(self.context, data.title, indexX + self.unitPadding, indexY + self.unitPadding + 30, '20px', '#000000')
362 | const maxWidth = self.unitWidth - 2 * self.unitPadding
363 | Util.wrapText(
364 | self.context,
365 | data.title,
366 | indexX + self.unitPadding,
367 | indexY + self.unitPadding + 24,
368 | maxWidth,
369 | 20,
370 | '#000000'
371 | )
372 | })
373 | }
374 |
375 | /**
376 | * fill the node outline with colorKey color
377 | */
378 | drawHiddenCanvas() {
379 | this.hiddenContext.clearRect(-50000, -10000, 100000, 100000)
380 |
381 | const self = this
382 | this.virtualContainerNode.selectAll('.orgUnit').each(function () {
383 | const node = self.d3.select(this)
384 | self.hiddenContext.fillStyle = node.attr('colorKey')
385 | Util.roundRect(
386 | self.hiddenContext,
387 | Number(node.attr('x')) - self.unitWidth / 2,
388 | Number(node.attr('y')) - self.unitHeight / 2,
389 | self.unitWidth,
390 | self.unitHeight,
391 | 4,
392 | true,
393 | false
394 | )
395 | })
396 | }
397 |
398 | setCanvasListener() {
399 | this.setClickListener()
400 | this.setDragListener()
401 | this.setMouseWheelZoomListener()
402 | }
403 |
404 | setClickListener() {
405 | const self = this
406 | this.canvasNode.node().addEventListener('click', (e) => {
407 | const colorStr = getColorStrFromCanvas(
408 | self.hiddenContext,
409 | e.layerX,
410 | e.layerY
411 | )
412 | const node = self.colorNodeMap[colorStr]
413 | if (node) {
414 | self.toggleTreeNode(node.data()[0])
415 | self.update(node.data()[0])
416 | }
417 | })
418 | }
419 |
420 | setMouseWheelZoomListener() {
421 | const self = this
422 | this.canvasNode.node().addEventListener('mousewheel', (event) => {
423 | event.preventDefault()
424 | if (event.deltaY < 0) {
425 | self.bigger()
426 | } else {
427 | self.smaller()
428 | }
429 | })
430 | }
431 |
432 | setDragListener() {
433 | this.onDrag_ = false
434 | this.dragStartPoint_ = { x: 0, y: 0 }
435 | const self = this
436 | this.canvasNode.node().onmousedown = function (e) {
437 | self.dragStartPoint_.x = e.x
438 | self.dragStartPoint_.y = e.y
439 | self.onDrag_ = true
440 | }
441 |
442 | this.canvasNode.node().onmousemove = function (e) {
443 | if (!self.onDrag_) {
444 | return
445 | }
446 | self.context.translate(
447 | (e.x - self.dragStartPoint_.x) / self.scale,
448 | (e.y - self.dragStartPoint_.y) / self.scale
449 | )
450 | self.hiddenContext.translate(
451 | (e.x - self.dragStartPoint_.x) / self.scale,
452 | (e.y - self.dragStartPoint_.y) / self.scale
453 | )
454 | self.dragStartPoint_.x = e.x
455 | self.dragStartPoint_.y = e.y
456 | }
457 |
458 | this.canvasNode.node().onmouseout = function (e) {
459 | if (self.onDrag_) {
460 | self.onDrag_ = false
461 | }
462 | }
463 |
464 | this.canvasNode.node().onmouseup = function (e) {
465 | if (self.onDrag_) {
466 | self.onDrag_ = false
467 | }
468 | }
469 | }
470 |
471 | toggleTreeNode(treeNode) {
472 | if (treeNode.children) {
473 | treeNode._children = treeNode.children
474 | treeNode.children = null
475 | } else {
476 | treeNode.children = treeNode._children
477 | treeNode._children = null
478 | }
479 | }
480 |
481 | bigger() {
482 | if (this.scale > 7) return
483 | this.clearCanvas_()
484 | this.scale = (this.scale * 5) / 4
485 | this.context.scale(5 / 4, 5 / 4)
486 | this.hiddenContext.scale(5 / 4, 5 / 4)
487 | }
488 |
489 | smaller() {
490 | if (this.scale < 0.2) return
491 | this.clearCanvas_()
492 |
493 | this.scale = (this.scale * 4) / 5
494 | this.context.scale(4 / 5, 4 / 5)
495 | this.hiddenContext.scale(4 / 5, 4 / 5)
496 | }
497 |
498 | clearCanvas_() {
499 | this.context.clearRect(-1000000, -10000, 2000000, 2000000)
500 | this.hiddenContext.clearRect(-1000000, -10000, 2000000, 2000000)
501 | }
502 | }
503 |
504 | export default OrgChart
505 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/demo/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Tree Chart
6 |
15 |
24 |
25 |
26 |
27 |
28 |
31 |
32 | About me
33 |
34 |
35 |
36 |
39 |
40 | Source Code
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
85 |
86 |
94 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/demo/google-icon.css:
--------------------------------------------------------------------------------
1 | /* fallback */
2 | @font-face {
3 | font-family: 'Material Icons';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: url(https://fonts.gstatic.com/s/materialicons/v46/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');
7 | }
8 | /* cyrillic-ext */
9 | @font-face {
10 | font-family: 'Roboto';
11 | font-style: normal;
12 | font-weight: 300;
13 | src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmSU5fCRc4AMP6lbBP.woff2) format('woff2');
14 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
15 | }
16 | /* cyrillic */
17 | @font-face {
18 | font-family: 'Roboto';
19 | font-style: normal;
20 | font-weight: 300;
21 | src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmSU5fABc4AMP6lbBP.woff2) format('woff2');
22 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
23 | }
24 | /* greek-ext */
25 | @font-face {
26 | font-family: 'Roboto';
27 | font-style: normal;
28 | font-weight: 300;
29 | src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmSU5fCBc4AMP6lbBP.woff2) format('woff2');
30 | unicode-range: U+1F00-1FFF;
31 | }
32 | /* greek */
33 | @font-face {
34 | font-family: 'Roboto';
35 | font-style: normal;
36 | font-weight: 300;
37 | src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmSU5fBxc4AMP6lbBP.woff2) format('woff2');
38 | unicode-range: U+0370-03FF;
39 | }
40 | /* vietnamese */
41 | @font-face {
42 | font-family: 'Roboto';
43 | font-style: normal;
44 | font-weight: 300;
45 | src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmSU5fCxc4AMP6lbBP.woff2) format('woff2');
46 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
47 | }
48 | /* latin-ext */
49 | @font-face {
50 | font-family: 'Roboto';
51 | font-style: normal;
52 | font-weight: 300;
53 | src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmSU5fChc4AMP6lbBP.woff2) format('woff2');
54 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
55 | }
56 | /* latin */
57 | @font-face {
58 | font-family: 'Roboto';
59 | font-style: normal;
60 | font-weight: 300;
61 | src: local('Roboto Light'), local('Roboto-Light'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmSU5fBBc4AMP6lQ.woff2) format('woff2');
62 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
63 | }
64 | /* cyrillic-ext */
65 | @font-face {
66 | font-family: 'Roboto';
67 | font-style: normal;
68 | font-weight: 400;
69 | src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');
70 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
71 | }
72 | /* cyrillic */
73 | @font-face {
74 | font-family: 'Roboto';
75 | font-style: normal;
76 | font-weight: 400;
77 | src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2');
78 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
79 | }
80 | /* greek-ext */
81 | @font-face {
82 | font-family: 'Roboto';
83 | font-style: normal;
84 | font-weight: 400;
85 | src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2) format('woff2');
86 | unicode-range: U+1F00-1FFF;
87 | }
88 | /* greek */
89 | @font-face {
90 | font-family: 'Roboto';
91 | font-style: normal;
92 | font-weight: 400;
93 | src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2) format('woff2');
94 | unicode-range: U+0370-03FF;
95 | }
96 | /* vietnamese */
97 | @font-face {
98 | font-family: 'Roboto';
99 | font-style: normal;
100 | font-weight: 400;
101 | src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2) format('woff2');
102 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
103 | }
104 | /* latin-ext */
105 | @font-face {
106 | font-family: 'Roboto';
107 | font-style: normal;
108 | font-weight: 400;
109 | src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2) format('woff2');
110 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
111 | }
112 | /* latin */
113 | @font-face {
114 | font-family: 'Roboto';
115 | font-style: normal;
116 | font-weight: 400;
117 | src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2');
118 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
119 | }
120 | /* cyrillic-ext */
121 | @font-face {
122 | font-family: 'Roboto';
123 | font-style: normal;
124 | font-weight: 500;
125 | src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fCRc4AMP6lbBP.woff2) format('woff2');
126 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
127 | }
128 | /* cyrillic */
129 | @font-face {
130 | font-family: 'Roboto';
131 | font-style: normal;
132 | font-weight: 500;
133 | src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fABc4AMP6lbBP.woff2) format('woff2');
134 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
135 | }
136 | /* greek-ext */
137 | @font-face {
138 | font-family: 'Roboto';
139 | font-style: normal;
140 | font-weight: 500;
141 | src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fCBc4AMP6lbBP.woff2) format('woff2');
142 | unicode-range: U+1F00-1FFF;
143 | }
144 | /* greek */
145 | @font-face {
146 | font-family: 'Roboto';
147 | font-style: normal;
148 | font-weight: 500;
149 | src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fBxc4AMP6lbBP.woff2) format('woff2');
150 | unicode-range: U+0370-03FF;
151 | }
152 | /* vietnamese */
153 | @font-face {
154 | font-family: 'Roboto';
155 | font-style: normal;
156 | font-weight: 500;
157 | src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fCxc4AMP6lbBP.woff2) format('woff2');
158 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
159 | }
160 | /* latin-ext */
161 | @font-face {
162 | font-family: 'Roboto';
163 | font-style: normal;
164 | font-weight: 500;
165 | src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fChc4AMP6lbBP.woff2) format('woff2');
166 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
167 | }
168 | /* latin */
169 | @font-face {
170 | font-family: 'Roboto';
171 | font-style: normal;
172 | font-weight: 500;
173 | src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2) format('woff2');
174 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
175 | }
176 | /* cyrillic-ext */
177 | @font-face {
178 | font-family: 'Roboto';
179 | font-style: normal;
180 | font-weight: 700;
181 | src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfCRc4AMP6lbBP.woff2) format('woff2');
182 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
183 | }
184 | /* cyrillic */
185 | @font-face {
186 | font-family: 'Roboto';
187 | font-style: normal;
188 | font-weight: 700;
189 | src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2) format('woff2');
190 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
191 | }
192 | /* greek-ext */
193 | @font-face {
194 | font-family: 'Roboto';
195 | font-style: normal;
196 | font-weight: 700;
197 | src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfCBc4AMP6lbBP.woff2) format('woff2');
198 | unicode-range: U+1F00-1FFF;
199 | }
200 | /* greek */
201 | @font-face {
202 | font-family: 'Roboto';
203 | font-style: normal;
204 | font-weight: 700;
205 | src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfBxc4AMP6lbBP.woff2) format('woff2');
206 | unicode-range: U+0370-03FF;
207 | }
208 | /* vietnamese */
209 | @font-face {
210 | font-family: 'Roboto';
211 | font-style: normal;
212 | font-weight: 700;
213 | src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfCxc4AMP6lbBP.woff2) format('woff2');
214 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
215 | }
216 | /* latin-ext */
217 | @font-face {
218 | font-family: 'Roboto';
219 | font-style: normal;
220 | font-weight: 700;
221 | src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfChc4AMP6lbBP.woff2) format('woff2');
222 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
223 | }
224 | /* latin */
225 | @font-face {
226 | font-family: 'Roboto';
227 | font-style: normal;
228 | font-weight: 700;
229 | src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) format('woff2');
230 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
231 | }
232 |
233 | .material-icons {
234 | font-family: 'Material Icons';
235 | font-weight: normal;
236 | font-style: normal;
237 | font-size: 24px;
238 | line-height: 1;
239 | letter-spacing: normal;
240 | text-transform: none;
241 | display: inline-block;
242 | white-space: nowrap;
243 | word-wrap: normal;
244 | direction: ltr;
245 | -webkit-font-feature-settings: 'liga';
246 | -webkit-font-smoothing: antialiased;
247 | }
248 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/demo/main.ts:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App.vue'
5 | import router from './router'
6 | import Vuetify from 'vuetify'
7 | import 'vuetify/dist/vuetify.min.css'
8 | import './google-icon.css'
9 |
10 | Vue.use(Vuetify)
11 | Vue.config.productionTip = false
12 |
13 | /* eslint-disable no-new */
14 | new Vue({
15 | el: '#app',
16 | router,
17 | components: { App },
18 | template: ''
19 | })
20 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/demo/router/constant.ts:
--------------------------------------------------------------------------------
1 | export const SVG_TREE = 'svgTree'
2 | export const CANVAS_TREE = 'canvasTree'
3 | export const JSON_VIEWER = 'jsonViewer'
4 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/demo/router/index.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import CanvasTree from '../../components/CanvasTree.vue'
4 | import VueTree from '../../components/VueTreeDemo.vue'
5 | import * as Cons from './constant'
6 |
7 | Vue.use(Router)
8 |
9 | export default new Router({
10 | routes: [
11 | {
12 | path: '/',
13 | redirect: Cons.SVG_TREE
14 | },
15 | {
16 | path: '/' + Cons.CANVAS_TREE,
17 | name: Cons.CANVAS_TREE,
18 | component: CanvasTree
19 | },
20 | {
21 | path: '/' + Cons.SVG_TREE,
22 | name: Cons.SVG_TREE,
23 | component: VueTree
24 | }
25 | ]
26 | })
27 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/src/vue.shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue'
3 | export default Vue
4 | }
5 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | organization-chart
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/tree-chart-demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build/",
4 | "sourceMap": true,
5 | "strict": false,
6 | "noImplicitReturns": true,
7 | "module": "es2015",
8 | "moduleResolution": "node",
9 | "target": "es5"
10 | },
11 | "include": ["src/**/*"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 | library/*.js
6 | src/**/*d.ts
7 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | 'node': true
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/typescript',
9 | '@vue/prettier',
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 2020,
13 | },
14 | rules: {
15 | 'prettier/prettier': [
16 | 'warn',
17 | {
18 | '#': 'prettier config in here :)',
19 | 'singleQuote': true,
20 | 'semi': false,
21 | 'trailingComma': 'none',
22 | 'space-before-function-paren': 'off'
23 | }
24 | ]
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/.prettierignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 | library/*.js
6 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/README.md:
--------------------------------------------------------------------------------
1 | # @ssthouse/vue-tree-chart
2 |
3 | ## Docs
4 |
5 | (https://github.com/ssthouse/vue-tree-chart)[https://github.com/ssthouse/vue-tree-chart]
6 |
7 | ## 文档
8 |
9 | (https://github.com/ssthouse/vue-tree-chart)[https://github.com/ssthouse/vue-tree-chart]
--------------------------------------------------------------------------------
/packages/vue-tree-chart/build/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { VueLoaderPlugin } = require('vue-loader')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | module.exports = {
6 | entry: './src/demo/main.ts',
7 | output: {
8 | path: path.resolve(__dirname, '../dist'),
9 | filename: 'bundle.js'
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.css$/,
15 | use: ['style-loader', 'css-loader']
16 | },
17 | {
18 | test: /\.less$/,
19 | use: ['style-loader', 'css-loader', 'less-loader']
20 | },
21 | {
22 | test: /\.(png|svg|jpg|gif)$/,
23 | use: ['file-loader']
24 | },
25 | {
26 | test: /\.vue$/,
27 | use: ['vue-loader']
28 | },
29 | {
30 | test: /\.ts$/,
31 | loader: 'ts-loader',
32 | exclude: /node_modules/,
33 | options: {
34 | appendTsSuffixTo: [/\.vue$/]
35 | }
36 | }
37 | ]
38 | },
39 | plugins: [
40 | new VueLoaderPlugin(),
41 | new HtmlWebpackPlugin({
42 | filename: 'index.html',
43 | template: './template/index.html',
44 | inject: true
45 | })
46 | ],
47 | resolve: {
48 | extensions: ['.ts', '.js', '.vue', '.json'],
49 | alias: {
50 | vue$: 'vue/dist/vue.esm.js'
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/build/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 |
3 | module.exports = merge(require('./webpack.config.base'), {
4 | mode: 'development',
5 | output: {
6 | publicPath: '/'
7 | },
8 | devtool: 'source-map'
9 | })
10 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/build/webpack.config.library.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { VueLoaderPlugin } = require('vue-loader')
3 |
4 | module.exports = {
5 | mode: 'development',
6 | entry: './src/vue-tree/index.ts',
7 | output: {
8 | path: path.resolve(__dirname, '..', 'library'),
9 | filename: 'vue-tree-chart.js',
10 | library: 'vue-tree-chart',
11 | libraryTarget: 'umd'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.css$/,
17 | use: ['style-loader', 'css-loader']
18 | },
19 | {
20 | test: /\.less$/,
21 | use: ['style-loader', 'css-loader', 'less-loader']
22 | },
23 | {
24 | test: /\.(png|svg|jpg|gif)$/,
25 | use: ['file-loader']
26 | },
27 | {
28 | test: /\.vue$/,
29 | use: ['vue-loader']
30 | },
31 | {
32 | test: /\.ts$/,
33 | loader: 'ts-loader',
34 | exclude: /node_modules/,
35 | options: {
36 | appendTsSuffixTo: [/\.vue$/]
37 | }
38 | }
39 | ]
40 | },
41 | plugins: [new VueLoaderPlugin()],
42 | resolve: {
43 | extensions: ['.ts', '.js', '.vue', '.json'],
44 | alias: {
45 | vue$: 'vue/dist/vue.esm.js'
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/library/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/packages/vue-tree-chart/library/.gitkeep
--------------------------------------------------------------------------------
/packages/vue-tree-chart/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ssthouse/vue-tree-chart",
3 | "version": "0.6.8",
4 | "description": "Tree Component for Vue2 powered by D3.js & SVG",
5 | "author": "ssthouse",
6 | "main": "library/vue-tree-chart.js",
7 | "scripts": {
8 | "lint": "eslint --ext .js,.vue,.ts src",
9 | "dev": "webpack serve --config ./build/webpack.config.dev.js",
10 | "build:component": "webpack --config ./build/webpack.config.library.js",
11 | "release": "npm run build:component && npm publish"
12 | },
13 | "dependencies": {
14 | "@ssthouse/tree-chart-core": "1.1.2"
15 | },
16 | "peerDependencies": {
17 | "vue": "^2.6.8"
18 | },
19 | "devDependencies": {
20 | "@types/d3": "^7.1.0",
21 | "@typescript-eslint/eslint-plugin": "^2.34.0",
22 | "@typescript-eslint/parser": "^2.34.0",
23 | "@vue/eslint-config-prettier": "^6.0.0",
24 | "@vue/eslint-config-typescript": "^5.0.2",
25 | "autoprefixer": "^7.1.2",
26 | "babel-core": "^6.22.1",
27 | "babel-eslint": "^8.2.1",
28 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
29 | "babel-loader": "^7.1.1",
30 | "babel-plugin-syntax-jsx": "^6.18.0",
31 | "babel-plugin-transform-runtime": "^6.22.0",
32 | "babel-plugin-transform-vue-jsx": "^3.5.0",
33 | "babel-preset-env": "^1.3.2",
34 | "babel-preset-stage-2": "^6.22.0",
35 | "chalk": "^2.0.1",
36 | "copy-webpack-plugin": "^9.0.1",
37 | "css-loader": "^6.5.0",
38 | "eslint": "^8.1.0",
39 | "eslint-config-standard": "^10.2.1",
40 | "eslint-friendly-formatter": "^3.0.0",
41 | "eslint-loader": "^1.7.1",
42 | "eslint-plugin-import": "^2.7.0",
43 | "eslint-plugin-node": "^5.2.0",
44 | "eslint-plugin-prettier": "^3.1.4",
45 | "eslint-plugin-promise": "^3.4.0",
46 | "eslint-plugin-standard": "^3.0.1",
47 | "eslint-plugin-vue": "^6.0.0",
48 | "extract-text-webpack-plugin": "^3.0.0",
49 | "file-loader": "^1.1.4",
50 | "html-webpack-plugin": "^5.5.0",
51 | "husky": "^7.0.4",
52 | "lerna": "^4.0.0",
53 | "less": "^4.1.2",
54 | "less-loader": "^10.2.0",
55 | "lint-staged": "^10.2.11",
56 | "node-notifier": "^8.0.1",
57 | "optimize-css-assets-webpack-plugin": "^6.0.1",
58 | "ora": "^1.2.0",
59 | "portfinder": "^1.0.13",
60 | "postcss-import": "^11.0.0",
61 | "postcss-loader": "^6.2.0",
62 | "postcss-url": "^7.2.1",
63 | "prettier": "^2.0.5",
64 | "rimraf": "^2.6.0",
65 | "semver": "^5.3.0",
66 | "shelljs": "^0.8.5",
67 | "style-loader": "^3.3.1",
68 | "ts-loader": "^9.2.6",
69 | "typescript": "^3.3.3333",
70 | "uglifyjs-webpack-plugin": "^2.2.0",
71 | "url-loader": "^4.1.1",
72 | "vue": "2.6.8",
73 | "vue-class-component": "^7.0.1",
74 | "vue-loader": "^15.7.0",
75 | "vue-property-decorator": "^8.0.0",
76 | "vue-style-loader": "^4.1.3",
77 | "vue-template-compiler": "2.6.8",
78 | "webpack": "^5.61.0",
79 | "webpack-bundle-analyzer": "^3.3.2",
80 | "webpack-cli": "^4.9.1",
81 | "webpack-dev-server": "^4.4.0",
82 | "webpack-merge": "^4.1.0"
83 | },
84 | "engines": {
85 | "node": ">= 6.0.0",
86 | "npm": ">= 3.0.0"
87 | },
88 | "browserslist": [
89 | "> 1%",
90 | "last 2 versions",
91 | "not ie <= 8"
92 | ],
93 | "prettier": {
94 | "#": "prettier config in here :)",
95 | "singleQuote": true,
96 | "semi": false,
97 | "arrowParens": "always",
98 | "space-before-function-paren": "off",
99 | "trailingComma": "none"
100 | },
101 | "husky": {
102 | "hooks": {
103 | "pre-commit": "lint-staged"
104 | }
105 | },
106 | "lint-staged": {
107 | "*.js": [
108 | "eslint",
109 | "prettier --write"
110 | ],
111 | "*.vue": [
112 | "eslint",
113 | "prettier --write"
114 | ]
115 | },
116 | "publishConfig": {
117 | "registry": "https://registry.npmjs.org/"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/src/base/uuid.ts:
--------------------------------------------------------------------------------
1 |
2 | export function uuid(): string {
3 | const s = []
4 | const hexDigits = '0123456789abcdef'
5 | for (let i = 0; i < 36; i++) {
6 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
7 | }
8 | s[14] = '4'
9 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
10 | s[8] = s[13] = s[18] = s[23] = '-'
11 | return s.join('')
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/src/demo/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Vue2
4 |
5 |
6 |
12 |
13 |
14 |
15 |
49 |
50 |
60 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/src/demo/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue'
3 |
4 | new Vue({
5 | el: '#app',
6 | components: { App },
7 | template: ''
8 | })
9 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/src/vue-tree/VueTree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
26 |
31 |
32 | {{ node.data.value }}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
147 |
148 |
161 |
162 |
216 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/src/vue-tree/index.ts:
--------------------------------------------------------------------------------
1 | import VueTree from './VueTree.vue'
2 |
3 | export default VueTree
4 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/src/vue.shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue'
3 | export default Vue
4 | }
5 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/template/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/packages/vue-tree-chart/template/favicon.ico
--------------------------------------------------------------------------------
/packages/vue-tree-chart/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= htmlWebpackPlugin.options.title %>
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/vue-tree-chart/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build/",
4 | "sourceMap": true,
5 | "strict": false,
6 | "noImplicitReturns": true,
7 | "module": "es2015",
8 | "moduleResolution": "node",
9 | "target": "es5"
10 | },
11 | "include": ["src/**/*"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/README.md:
--------------------------------------------------------------------------------
1 | # @ssthouse/vue3-tree-chart
2 |
3 | ## Docs
4 |
5 | [https://github.com/ssthouse/vue-tree-chart](https://github.com/ssthouse/vue-tree-chart)
6 |
7 | ## 文档
8 |
9 | [https://github.com/ssthouse/vue-tree-chart](https://github.com/ssthouse/vue-tree-chart)
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ssthouse/vue3-tree-chart",
3 | "author": "ssthouse",
4 | "version": "0.2.6",
5 | "description": "Tree Component for Vue3 powered by D3.js & SVG",
6 | "scripts": {
7 | "serve": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint",
10 | "release": "npm run build:component && npm publish",
11 | "build:component": "vue-cli-service build --target lib --name vue3-tree-chart src/vue-tree/index.js"
12 | },
13 | "main": "dist/vue3-tree-chart.umd.js",
14 | "dependencies": {
15 | "@ssthouse/tree-chart-core": "^1.1.0"
16 | },
17 | "devDependencies": {
18 | "@vue/cli-plugin-babel": "~4.5.0",
19 | "@vue/cli-plugin-eslint": "~4.5.0",
20 | "@vue/cli-service": "~4.5.0",
21 | "@vue/compiler-sfc": "^3.0.0",
22 | "babel-eslint": "^10.1.0",
23 | "eslint": "^6.7.2",
24 | "eslint-plugin-vue": "^7.0.0",
25 | "less": "^4.1.2",
26 | "less-loader": "^6.0.0",
27 | "vue": "^3.0.0"
28 | },
29 | "eslintConfig": {
30 | "root": true,
31 | "env": {
32 | "node": true
33 | },
34 | "extends": [
35 | "plugin:vue/vue3-essential",
36 | "eslint:recommended"
37 | ],
38 | "parserOptions": {
39 | "parser": "babel-eslint"
40 | },
41 | "rules": {}
42 | },
43 | "browserslist": [
44 | "> 1%",
45 | "last 2 versions",
46 | "not dead"
47 | ],
48 | "publishConfig": {
49 | "registry": "https://registry.npmjs.org/"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/packages/vue3-tree-chart/public/favicon.ico
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Vue3
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
49 |
50 |
60 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/packages/vue3-tree-chart/src/assets/logo.png
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/src/base/uuid.js:
--------------------------------------------------------------------------------
1 |
2 | export function uuid() {
3 | const s = []
4 | const hexDigits = '0123456789abcdef'
5 | for (let i = 0; i < 36; i++) {
6 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
7 | }
8 | s[14] = '4'
9 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
10 | s[8] = s[13] = s[18] = s[23] = '-'
11 | return s.join('')
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
Installed CLI Plugins
10 |
14 |
Essential Links
15 |
22 |
Ecosystem
23 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
59 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 |
4 | createApp(App).mount('#app')
5 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/src/vue-tree/VueTree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
26 |
31 |
32 | {{ node.data.value }}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
147 |
148 |
161 |
162 |
216 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/src/vue-tree/index.js:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import VueTree from './VueTree.vue'
3 |
4 | export default VueTree
5 |
--------------------------------------------------------------------------------
/packages/vue3-tree-chart/vite.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | css: { extract: false }
3 | }
--------------------------------------------------------------------------------
/screenshots/demo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/screenshots/demo.jpeg
--------------------------------------------------------------------------------
/screenshots/org-chart.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/screenshots/org-chart.gif
--------------------------------------------------------------------------------
/screenshots/unique_color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssthouse/tree-chart/948a4d2bcd2415ed1a8a34e78ee54e74c3c40c54/screenshots/unique_color.png
--------------------------------------------------------------------------------