A tree graph made using Vue.js and html css.
See the demo.
This is a small project which shows a part of a larger project. The idea is to create an example of how to create a graphical tree structure using Vue.js.
This project uses:
- Vue.js
- Vue CLI
- Vuex
- Vue Router (it could be ommited in this project)
This is what this project does:
- Creates a graphical tree structure (graph) based on a JSON structure managed in the store (Vuex).
- Allows zoom (with buttons).
- Shows current store (toggle with a button).
- Add lines to join the bottom of the parent node to the top of a node creating a hierarchical structure.
- Using drag and drop, allows nodes movement from one location to another. A node can be moved on the sides of an existing node with children and can be moved below a node without children.
- Add new nodes using drag and drop with the ' ' node above.
This are some functionalities that would be nice and maybe will be done in the future:
- Add a small box showing the full tree with a box showing the current view.
I've tested two ways of implementing the tree using only CSS: using flexbox
and using inline-block
.
Note: I also thought about using CSS grid
but that requires knowing the column count and changing CSS accordingly which would complicate things.
This is how the tree is constructed using Flexbox
.
/* Only relevant parts displayed */
.nodeRow {
overflow: auto;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: stretch;
align-items: flex-start;
}
Pro:
- The structure is rigid.
Con:
- When the content is not bigger than the container it's taken to the left (not centered).
- Using
justify-content: center;
does work when the zoom is far but when the zoom is at initial state and the content is larger than the container it gets cut off (This happens in Chrome version 80).
Here's an idea to avoid the cons but I haven't tried it:
When zooming there could be a JavaScript computation to see if the tree is smaller than its container, in that case use
justify-content: center;
. The calculation would take the first.nodeRow
children and would sum their full width (withh padding, outline, margin, etc.) and if their sum is smaller usejustify-content: center;
; if it's not usejustify-content: strech;
.
This is how the tree is constructed using inline-block
.
/* Only relevant parts displayed */
.nodeRow {
white-space: nowrap; /* Whithout this the tree is unordered when there is not enough space. */
overflow: auto;
}
.node {
display: inline-block;
vertical-align: top;
}
Possible con:
- Maybe with
inline-block
the tree could be unordered easily when changing outside CSS but I haven't done any testing.
# To see the project in action, after getting the files on hard drive (git, download or whatever), run:
npm install
# Compiles and hot-reloads for development:
npm run serve
# Compiles and minifies for production:
npm run build
# Lints and fixes files:
npm run lint
Custom configuration, see Configuration Reference.
This is what I did on the initial installation.
Install Vue CLI (Installation | Vue CLI):
npm install -g @vue/cli
This is how I created the Vue.js project (Creating a Project | Vue CLI):
vue create vuejs_tree
And these are the selections made:
Vue CLI v4.5.4
? Please pick a preset:
Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
❯ Manually select features
? Check the features needed for your project:
◉ Choose Vue version
◉ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
◉ Router
◉ Vuex
◉ CSS Pre-processors
◉ Linter / Formatter
❯◉ Unit Testing
◯ E2E Testing
? Choose a version of Vue.js that you want to start the project with
❯ 2.x
3.x (Preview)
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)
n
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
❯ Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
Less
Stylus
? Pick a linter / formatter config:
❯ ESLint with error prevention only
ESLint Airbnb config
ESLint Standard config
ESLint Prettier
? Pick additional lint features:
❯◉ Lint on save
◯ Lint and fix on commit
? Pick a unit testing solution:
Mocha Chai
❯ Jest
? Where do you prefer placing config for Babel, ESLint, etc.?
❯ In dedicated config files
In package.json
? Save this as a preset for future projects? (y/N) n
If you need to see or play with some variable in the console or elsewhere in the code, you can link it to the window as inside Vue.js variables are scoped.
// This is just an example, theState can be changed to whatever
// careful it doesn't class with something else.
// In this example we want to expose the state but it can be anything.
window.theState = state;
It can be used in two ways:
- As a replacement for the src directory, but it can be changed in webpack.
Apparently innode_modules/\@vue/cli-service/lib/config/base.js
or in webpack config but I haven't played with either.
More info: TIL: The @ symbol in JavaScript import statement | Jerrie Pelser's Blog.
You can do whatever you want with the process tree but if you want to try different structures here are a large one and a small one.
processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []}
]},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []},
{nodeValue: 'value', id: '', processTree: []}
]}
]}
]
processTree: [
// {nodeValue: 'value', id: '', processTree: [ // Uncomment this to get one main node
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []}
]},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []}
]},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []},
{nodeValue: 'value', id: '', processTree: []}
]}
]}
]}
]},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []}
]},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []}
]},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []},
{nodeValue: 'value', id: '', processTree: []}
]}
]},
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: [
{nodeValue: 'value', id: '', processTree: []},
{nodeValue: 'value', id: '', processTree: []}
]}
]}
]}
]}
]}
// ]} // Uncomment this to get one main node
]