├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── dist
├── css
│ ├── graph-editor-dark.css
│ ├── graph-editor-dark.min.css
│ ├── graph-editor-dark.scss
│ ├── graph-editor.css
│ ├── graph-editor.min.css
│ └── graph-editor.scss
└── js
│ ├── graph-editor.js
│ └── graph-editor.min.js
├── docs
├── events.jsdoc.html
├── fonts
│ ├── OpenSans-Bold-webfont.eot
│ ├── OpenSans-Bold-webfont.svg
│ ├── OpenSans-Bold-webfont.woff
│ ├── OpenSans-BoldItalic-webfont.eot
│ ├── OpenSans-BoldItalic-webfont.svg
│ ├── OpenSans-BoldItalic-webfont.woff
│ ├── OpenSans-Italic-webfont.eot
│ ├── OpenSans-Italic-webfont.svg
│ ├── OpenSans-Italic-webfont.woff
│ ├── OpenSans-Light-webfont.eot
│ ├── OpenSans-Light-webfont.svg
│ ├── OpenSans-Light-webfont.woff
│ ├── OpenSans-LightItalic-webfont.eot
│ ├── OpenSans-LightItalic-webfont.svg
│ ├── OpenSans-LightItalic-webfont.woff
│ ├── OpenSans-Regular-webfont.eot
│ ├── OpenSans-Regular-webfont.svg
│ └── OpenSans-Regular-webfont.woff
├── ge.Angle.html
├── ge.BBox.html
├── ge.ContainerSize.html
├── ge.GraphEditor.html
├── ge.Link.html
├── ge.Node.html
├── ge.Point.html
├── ge.SaveLoad.html
├── ge.TextSize.html
├── ge.html
├── ge.path.Line.html
├── ge.path.Path.html
├── ge.path.html
├── ge.shape.Circle.html
├── ge.shape.Rect.html
├── ge.shape.Shape.html
├── ge.shape.html
├── global.html
├── index.html
├── js_controls.js.html
├── js_events.js.html
├── js_init.js.html
├── js_link.js.html
├── js_node.js.html
├── js_options.js.html
├── js_path_base.js.html
├── js_path_line.js.html
├── js_shape_base.js.html
├── js_shape_circle.js.html
├── js_shape_rect.js.html
├── js_update.js.html
├── js_util_angle.js.html
├── js_util_bbox.js.html
├── js_util_common.js.html
├── js_util_container-size.js.html
├── js_util_point.js.html
├── js_util_save-load.js.html
├── js_util_text-size.js.html
├── main.js.html
├── scripts
│ ├── linenumber.js
│ └── prettify
│ │ ├── Apache-License-2.0.txt
│ │ ├── lang-css.js
│ │ └── prettify.js
├── styles
│ ├── jsdoc-default.css
│ ├── prettify-jsdoc.css
│ └── prettify-tomorrow.css
└── types.jsdoc.html
├── examples
├── index.css
└── index.html
├── jsdoc.conf.js
├── karma.conf.js
├── package.json
├── src
├── css
│ ├── graph-editor-dark.scss
│ └── graph-editor.scss
├── events.jsdoc
├── js
│ ├── controls.js
│ ├── events.js
│ ├── init.js
│ ├── link.js
│ ├── node.js
│ ├── options.js
│ ├── path
│ │ ├── base.js
│ │ └── line.js
│ ├── shape
│ │ ├── base.js
│ │ ├── circle.js
│ │ └── rect.js
│ ├── update.js
│ └── util
│ │ ├── angle.js
│ │ ├── bbox.js
│ │ ├── common.js
│ │ ├── container-size.js
│ │ ├── point.js
│ │ ├── save-load.js
│ │ └── text-size.js
├── main.js
├── types.jsdoc
└── umd
│ ├── umd-end.js
│ └── umd-start.js
└── tests
├── .eslintrc.json
├── common.test.js
├── module.test.js
└── test-start.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_style = tab
8 | indent_size = 4
9 |
10 | [*.html]
11 | indent_size = 2
12 |
13 | [package.json]
14 | indent_style = space
15 | indent_size = 2
16 |
17 | [*.md]
18 | indent_style = space
19 | indent_size = 2
20 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/umd/*
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "es6": true,
6 | "node": true,
7 | "jasmine": true
8 | },
9 | "extends": "eslint:recommended",
10 | "plugins": [
11 | "jasmine"
12 | ],
13 | "parserOptions": {
14 | "ecmaVersion": 6,
15 | "ecmaFeatures": {
16 | "jsx": true
17 | },
18 | "sourceType": "module"
19 | },
20 | "rules": {
21 | "indent": [
22 | "warn",
23 | "tab",
24 | {
25 | "SwitchCase": 1
26 | }
27 | ],
28 | "linebreak-style": [
29 | "error",
30 | "unix"
31 | ],
32 | "quotes": [
33 | "error",
34 | "single"
35 | ],
36 | "semi": [
37 | "error",
38 | "always"
39 | ],
40 | "no-unused-vars": "warn",
41 | "no-console": "off",
42 | "no-mixed-spaces-and-tabs": "off",
43 | "no-constant-condition": "off"
44 | },
45 | "globals": {
46 | "$": true,
47 | "d3": true,
48 | "ge": true,
49 | "exports": true
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /build
3 | /tmp
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 dead-beef
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | define rwildcard
2 | $(sort $(wildcard $1$2)) \
3 | $(foreach d,$(sort $(wildcard $1*)),$(call rwildcard,$d/,$2))
4 | endef
5 |
6 | BUILD_DIR := build
7 | DIST_DIR := dist
8 | DOC_DIR := docs
9 | DIRS := $(BUILD_DIR) $(DIST_DIR)/js $(DIST_DIR)/css
10 |
11 | NAME := graph-editor
12 |
13 | JS_FILES := src/umd/umd-start.js \
14 | src/main.js \
15 | $(sort $(wildcard src/js/util/*.js)) \
16 | $(sort $(wildcard src/js/path/*.js)) \
17 | $(sort $(wildcard src/js/shape/*.js)) \
18 | $(sort $(filter-out %/common.js,$(wildcard src/js/*.js))) \
19 | src/umd/umd-end.js
20 |
21 | CSS_MAIN := src/css/$(NAME).scss
22 | CSS_DARK_MAIN := src/css/$(NAME)-dark.scss
23 | CSS_DEPS := $(BUILD_DIR)/deps.mk
24 |
25 | DIST_FILES := $(DIST_DIR)/js/$(NAME).js \
26 | $(DIST_DIR)/js/$(NAME).min.js \
27 | $(DIST_DIR)/css/$(NAME).scss \
28 | $(DIST_DIR)/css/$(NAME).css \
29 | $(DIST_DIR)/css/$(NAME).min.css \
30 | $(DIST_DIR)/css/$(NAME)-dark.scss \
31 | $(DIST_DIR)/css/$(NAME)-dark.css \
32 | $(DIST_DIR)/css/$(NAME)-dark.min.css
33 |
34 | .PHONY: all clean install rebuild lint docs watch test test-watch
35 |
36 | all: $(BUILD_DIR)/install.touch $(DIST_FILES)
37 |
38 | install:
39 | npm install
40 |
41 | watch:
42 | chokidar 'src/**/*' package.json Makefile -i '**/.*' -c 'env -u MAKEFILES make'
43 |
44 | clean:
45 | rm -rf $(BUILD_DIR) $(DIST_DIR)
46 |
47 | rebuild:
48 | make clean
49 | make
50 |
51 | test: all
52 | karma start karma.conf.js --single-run
53 |
54 | test-watch:
55 | karma start karma.conf.js
56 |
57 | lint:
58 | eslint src
59 |
60 | docs:
61 | rm -rf $(DOC_DIR)/*
62 | jsdoc -c jsdoc.conf.js
63 | mv $(DOC_DIR)/$(NAME)/*/* $(DOC_DIR)/
64 | rm -rf $(DOC_DIR)/$(NAME)
65 |
66 | $(DIST_DIR)/js/$(NAME).js: $(JS_FILES) | $(DIST_DIR)/js
67 | eslint $?
68 | cat $(JS_FILES) >$@
69 |
70 | $(DIST_DIR)/css/$(NAME).css: $(CSS_MAIN) | $(BUILD_DIR) $(DIST_DIR)/css
71 | node-sass $< >$(BUILD_DIR)/tmp.css
72 | postcss $(BUILD_DIR)/tmp.css --use autoprefixer --no-map -o $@
73 |
74 | $(DIST_DIR)/css/$(NAME).scss: $(CSS_MAIN) | $(DIST_DIR)/css
75 | cp -f $< $@
76 |
77 | $(DIST_DIR)/css/$(NAME)-dark.css: $(CSS_DARK_MAIN) | $(BUILD_DIR) $(DIST_DIR)/css
78 | node-sass $< >$(BUILD_DIR)/tmp.css
79 | postcss $(BUILD_DIR)/tmp.css --use autoprefixer --no-map -o $@
80 |
81 | $(DIST_DIR)/css/$(NAME)-dark.scss: $(CSS_DARK_MAIN) | $(DIST_DIR)/css
82 | cp -f $< $@
83 |
84 | %.min.js: %.js
85 | uglifyjs $< -c -m -o $@
86 |
87 | %.min.css: %.css
88 | csso -i $< -o $@
89 |
90 | $(DIRS):
91 | mkdir -p $@
92 |
93 | $(CSS_DEPS): $(CSS_MAIN) $(CSS_DARK_MAIN) | $(BUILD_DIR)
94 | sass-makedepend -m -r -p $(DIST_DIR)/css/ $^ >$(BUILD_DIR)/tmp.mk
95 | mv -f $(BUILD_DIR)/tmp.mk $@
96 |
97 | $(BUILD_DIR)/install.touch: package.json | $(BUILD_DIR)
98 | npm install
99 | touch $@
100 |
101 | -include $(CSS_DEPS)
102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # graph-editor - graph editor frontend component
2 |
3 | [](
4 | https://www.npmjs.com/package/graph-editor
5 | ) [](
6 | https://nodejs.org/
7 | ) [](
8 | https://libraries.io/npm/graph-editor/
9 | ) [](
10 | https://github.com/dead-beef/graph-editor/blob/master/LICENSE
11 | )
12 |
13 | ## Overview
14 |
15 | It exports a class which should be bound to an element and provided data for [Graph](https://en.wikipedia.org/wiki/Graph_%28abstract_data_type%29) visualisation and editing.
16 |
17 | ## Requirements
18 |
19 | - [`D3 v5`](https://d3js.org/)
20 |
21 | ## Installation
22 |
23 | ```
24 | npm install graph-editor
25 | ```
26 |
27 | ## Usage
28 |
29 | - [`Documentation`](https://dead-beef.github.io/graph-editor)
30 | - [`Usage example`](https://codepen.io/deadbeef/pen/wRVMej)
31 |
32 | ## Development
33 |
34 | ### Requirements
35 |
36 | - [`Node.js`](https://nodejs.org/)
37 | - [`NPM`](https://nodejs.org/)
38 | - [`Git`](https://git-scm.com/)
39 | - [`GNU Make`](https://www.gnu.org/software/make/)
40 |
41 | ### Installation
42 |
43 | ```bash
44 | git clone https://github.com/dead-beef/graph-editor.git
45 | cd graph-editor
46 | make install
47 | ```
48 |
49 | ### Building
50 |
51 | ```bash
52 | # single run
53 | make
54 | # continuous
55 | make watch
56 | ```
57 |
58 | ### Testing
59 |
60 | ```bash
61 | # unit, single run
62 | make test
63 | # unit, continuous
64 | make test-watch
65 | # test library bundle
66 | TEST_BUNDLE=1 make test
67 | # test minified library bundle
68 | TEST_MIN_BUNDLE=1 make test
69 | # select browsers (default: Chromium)
70 | TEST_BROWSERS="Firefox Chrome" make test
71 | ```
72 |
73 | ### Code Linting
74 |
75 | ```
76 | make lint
77 | ```
78 |
79 | ### Documentation
80 |
81 | ```
82 | make docs
83 | ```
84 |
85 | ## Licenses
86 |
87 | * [`graph-editor`](https://github.com/dead-beef/graph-editor/blob/master/LICENSE)
88 |
--------------------------------------------------------------------------------
/dist/css/graph-editor-dark.css:
--------------------------------------------------------------------------------
1 | /*$ge-graph-transition: stroke $ge-graph-transition-duration,
2 | fill $ge-graph-transition-duration,
3 | color $ge-graph-transition-duration !default;*/
4 | .ge-graph * {
5 | /*transition: $ge-graph-transition;*/
6 | -webkit-transition-property: stroke, fill, color;
7 | transition-property: stroke, fill, color;
8 | -webkit-transition-duration: 0.3s;
9 | transition-duration: 0.3s; }
10 |
11 | .ge-graph marker#ge-dragline-end path, .ge-graph marker#ge-dragline-end circle, .ge-graph marker#ge-dragline-start path, .ge-graph marker#ge-dragline-start circle {
12 | stroke: #a44;
13 | fill: #a44; }
14 |
15 | .ge-graph .ge-hidden {
16 | display: none; }
17 |
18 | .ge-graph .ge-dragline {
19 | pointer-events: none;
20 | stroke-width: 2px;
21 | stroke: #a44; }
22 |
23 | .ge-graph .ge-link {
24 | cursor: pointer;
25 | font-size: 14px;
26 | stroke-width: 2px;
27 | /* * { vector-effect: non-scaling-stroke } */ }
28 | .ge-graph .ge-link path {
29 | stroke: #888;
30 | fill: none; }
31 | .ge-graph .ge-link text {
32 | fill: #888; }
33 | .ge-graph .ge-link:hover path {
34 | stroke: #fff; }
35 | .ge-graph .ge-link:hover text {
36 | fill: #fff; }
37 |
38 | .ge-graph .ge-node {
39 | cursor: pointer;
40 | /* * { vector-effect: non-scaling-stroke } */ }
41 | .ge-graph .ge-node path {
42 | stroke-width: 2px;
43 | stroke: #888;
44 | fill: #111; }
45 | .ge-graph .ge-node div {
46 | display: -webkit-box;
47 | display: -ms-flexbox;
48 | display: flex;
49 | -webkit-box-orient: horizontal;
50 | -webkit-box-direction: normal;
51 | -ms-flex-direction: row;
52 | flex-direction: row;
53 | -webkit-box-align: center;
54 | -ms-flex-align: center;
55 | align-items: center;
56 | width: 100%;
57 | height: 100%; }
58 | .ge-graph .ge-node div span {
59 | display: -webkit-box;
60 | display: -ms-flexbox;
61 | display: flex;
62 | -webkit-box-orient: vertical;
63 | -webkit-box-direction: normal;
64 | -ms-flex-direction: column;
65 | flex-direction: column;
66 | -webkit-box-flex: 1;
67 | -ms-flex-positive: 1;
68 | flex-grow: 1;
69 | -webkit-box-align: center;
70 | -ms-flex-align: center;
71 | align-items: center;
72 | text-align: center;
73 | color: #888;
74 | font-size: 16px;
75 | word-wrap: break-word; }
76 | .ge-graph .ge-node:hover path {
77 | stroke: #fff;
78 | fill: #333; }
79 | .ge-graph .ge-node:hover div span {
80 | color: #fff; }
81 |
82 | .ge-graph.ge-selection .ge-link path {
83 | stroke: #888; }
84 |
85 | .ge-graph.ge-selection .ge-link text {
86 | fill: #888; }
87 |
88 | .ge-graph.ge-selection .ge-link:hover path {
89 | stroke: #c4c4c4; }
90 |
91 | .ge-graph.ge-selection .ge-link:hover text {
92 | fill: #c4c4c4; }
93 |
94 | .ge-graph.ge-selection .ge-link.ge-selection path {
95 | stroke: #aae; }
96 |
97 | .ge-graph.ge-selection .ge-link.ge-selection text {
98 | fill: #aae; }
99 |
100 | .ge-graph.ge-selection .ge-link.ge-selection:hover path {
101 | stroke: #d5d5f7; }
102 |
103 | .ge-graph.ge-selection .ge-link.ge-selection:hover text {
104 | fill: #d5d5f7; }
105 |
106 | .ge-graph.ge-selection .ge-node path {
107 | stroke: #888;
108 | fill: #111; }
109 |
110 | .ge-graph.ge-selection .ge-node span {
111 | color: #888; }
112 |
113 | .ge-graph.ge-selection .ge-node:hover path {
114 | stroke: #d5d5f7;
115 | fill: #222222; }
116 |
117 | .ge-graph.ge-selection .ge-node:hover span {
118 | color: #d5d5f7; }
119 |
120 | .ge-graph.ge-selection .ge-node.ge-selection path {
121 | stroke: #aae;
122 | fill: #111; }
123 |
124 | .ge-graph.ge-selection .ge-node.ge-selection span {
125 | color: #aae; }
126 |
127 | .ge-graph.ge-selection .ge-node.ge-selection:hover path {
128 | stroke: #d5d5f7;
129 | fill: #222222; }
130 |
131 | .ge-graph.ge-selection .ge-node.ge-selection:hover span {
132 | color: #d5d5f7; }
133 |
134 | .ge-graph.ge-selection .ge-link.ge-link-selection path, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection path, .ge-graph.ge-link-selection .ge-link.ge-link-selection path, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection path {
135 | stroke: #ada; }
136 |
137 | .ge-graph.ge-selection .ge-link.ge-link-selection text, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection text, .ge-graph.ge-link-selection .ge-link.ge-link-selection text, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection text {
138 | fill: #ada; }
139 |
140 | .ge-graph.ge-selection .ge-link.ge-link-selection:hover path, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover path, .ge-graph.ge-link-selection .ge-link.ge-link-selection:hover path, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover path {
141 | stroke: #d5d5f7; }
142 |
143 | .ge-graph.ge-selection .ge-link.ge-link-selection:hover text, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover text, .ge-graph.ge-link-selection .ge-link.ge-link-selection:hover text, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover text {
144 | fill: #d5d5f7; }
145 |
--------------------------------------------------------------------------------
/dist/css/graph-editor-dark.min.css:
--------------------------------------------------------------------------------
1 | .ge-graph *{-webkit-transition-property:stroke,fill,color;transition-property:stroke,fill,color;-webkit-transition-duration:.3s;transition-duration:.3s}.ge-graph marker#ge-dragline-end circle,.ge-graph marker#ge-dragline-end path,.ge-graph marker#ge-dragline-start circle,.ge-graph marker#ge-dragline-start path{stroke:#a44;fill:#a44}.ge-graph .ge-hidden{display:none}.ge-graph .ge-dragline{pointer-events:none;stroke-width:2px;stroke:#a44}.ge-graph .ge-link{cursor:pointer;font-size:14px;stroke-width:2px}.ge-graph .ge-link path{stroke:#888;fill:none}.ge-graph .ge-link text{fill:#888}.ge-graph .ge-link:hover path{stroke:#fff}.ge-graph .ge-link:hover text{fill:#fff}.ge-graph .ge-node{cursor:pointer}.ge-graph .ge-node path{stroke-width:2px;stroke:#888;fill:#111}.ge-graph .ge-node div,.ge-graph .ge-node div span{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-direction:normal;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ge-graph .ge-node div{width:100%;height:100%;-webkit-box-orient:horizontal;-ms-flex-direction:row;flex-direction:row}.ge-graph .ge-node div span{-webkit-box-orient:vertical;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center;color:#888;font-size:16px;word-wrap:break-word}.ge-graph .ge-node:hover path{stroke:#fff;fill:#333}.ge-graph .ge-node:hover div span{color:#fff}.ge-graph.ge-selection .ge-link path{stroke:#888}.ge-graph.ge-selection .ge-link text{fill:#888}.ge-graph.ge-selection .ge-link:hover path{stroke:#c4c4c4}.ge-graph.ge-selection .ge-link:hover text{fill:#c4c4c4}.ge-graph.ge-selection .ge-link.ge-selection path{stroke:#aae}.ge-graph.ge-selection .ge-link.ge-selection text{fill:#aae}.ge-graph.ge-selection .ge-link.ge-selection:hover path{stroke:#d5d5f7}.ge-graph.ge-selection .ge-link.ge-selection:hover text{fill:#d5d5f7}.ge-graph.ge-selection .ge-node path{stroke:#888;fill:#111}.ge-graph.ge-selection .ge-node span{color:#888}.ge-graph.ge-selection .ge-node.ge-selection:hover path,.ge-graph.ge-selection .ge-node:hover path{stroke:#d5d5f7;fill:#222}.ge-graph.ge-selection .ge-node.ge-selection:hover span,.ge-graph.ge-selection .ge-node:hover span{color:#d5d5f7}.ge-graph.ge-selection .ge-node.ge-selection path{stroke:#aae;fill:#111}.ge-graph.ge-selection .ge-node.ge-selection span{color:#aae}.ge-graph.ge-link-selection .ge-link.ge-link-selection path,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection path,.ge-graph.ge-selection .ge-link.ge-link-selection path,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection path{stroke:#ada}.ge-graph.ge-link-selection .ge-link.ge-link-selection text,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection text,.ge-graph.ge-selection .ge-link.ge-link-selection text,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection text{fill:#ada}.ge-graph.ge-link-selection .ge-link.ge-link-selection:hover path,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover path,.ge-graph.ge-selection .ge-link.ge-link-selection:hover path,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover path{stroke:#d5d5f7}.ge-graph.ge-link-selection .ge-link.ge-link-selection:hover text,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover text,.ge-graph.ge-selection .ge-link.ge-link-selection:hover text,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover text{fill:#d5d5f7}
--------------------------------------------------------------------------------
/dist/css/graph-editor-dark.scss:
--------------------------------------------------------------------------------
1 | $ge-node-fg: #888 !default;
2 | $ge-node-bg: #111 !default;
3 | $ge-node-hover-fg: #fff !default;
4 | $ge-node-hover-bg: #333 !default;
5 |
6 | $ge-link-fg: #888 !default;
7 | $ge-link-hover-fg: #fff !default;
8 |
9 | $ge-dragline-fg: #a44 !default;
10 |
11 | $ge-selection-fg: #aae !default;
12 | $ge-selected-link-fg: #ada !default;
13 | $ge-selected-node-bg: #111 !default;
14 |
15 | $ge-unselected-fg: $ge-node-fg !default;
16 | $ge-unselected-bg: $ge-node-bg !default;
17 |
18 | @import "graph-editor";
19 |
--------------------------------------------------------------------------------
/dist/css/graph-editor.css:
--------------------------------------------------------------------------------
1 | /*$ge-graph-transition: stroke $ge-graph-transition-duration,
2 | fill $ge-graph-transition-duration,
3 | color $ge-graph-transition-duration !default;*/
4 | .ge-graph * {
5 | /*transition: $ge-graph-transition;*/
6 | -webkit-transition-property: stroke, fill, color;
7 | transition-property: stroke, fill, color;
8 | -webkit-transition-duration: 0.3s;
9 | transition-duration: 0.3s; }
10 |
11 | .ge-graph marker#ge-dragline-end path, .ge-graph marker#ge-dragline-end circle, .ge-graph marker#ge-dragline-start path, .ge-graph marker#ge-dragline-start circle {
12 | stroke: #a44;
13 | fill: #a44; }
14 |
15 | .ge-graph .ge-hidden {
16 | display: none; }
17 |
18 | .ge-graph .ge-dragline {
19 | pointer-events: none;
20 | stroke-width: 2px;
21 | stroke: #a44; }
22 |
23 | .ge-graph .ge-link {
24 | cursor: pointer;
25 | font-size: 14px;
26 | stroke-width: 2px;
27 | /* * { vector-effect: non-scaling-stroke } */ }
28 | .ge-graph .ge-link path {
29 | stroke: #000;
30 | fill: none; }
31 | .ge-graph .ge-link text {
32 | fill: #000; }
33 | .ge-graph .ge-link:hover path {
34 | stroke: #4aa; }
35 | .ge-graph .ge-link:hover text {
36 | fill: #4aa; }
37 |
38 | .ge-graph .ge-node {
39 | cursor: pointer;
40 | /* * { vector-effect: non-scaling-stroke } */ }
41 | .ge-graph .ge-node path {
42 | stroke-width: 2px;
43 | stroke: #000;
44 | fill: #ddd; }
45 | .ge-graph .ge-node div {
46 | display: -webkit-box;
47 | display: -ms-flexbox;
48 | display: flex;
49 | -webkit-box-orient: horizontal;
50 | -webkit-box-direction: normal;
51 | -ms-flex-direction: row;
52 | flex-direction: row;
53 | -webkit-box-align: center;
54 | -ms-flex-align: center;
55 | align-items: center;
56 | width: 100%;
57 | height: 100%; }
58 | .ge-graph .ge-node div span {
59 | display: -webkit-box;
60 | display: -ms-flexbox;
61 | display: flex;
62 | -webkit-box-orient: vertical;
63 | -webkit-box-direction: normal;
64 | -ms-flex-direction: column;
65 | flex-direction: column;
66 | -webkit-box-flex: 1;
67 | -ms-flex-positive: 1;
68 | flex-grow: 1;
69 | -webkit-box-align: center;
70 | -ms-flex-align: center;
71 | align-items: center;
72 | text-align: center;
73 | color: #000;
74 | font-size: 16px;
75 | word-wrap: break-word; }
76 | .ge-graph .ge-node:hover path {
77 | stroke: #000;
78 | fill: #aee; }
79 | .ge-graph .ge-node:hover div span {
80 | color: #000; }
81 |
82 | .ge-graph.ge-selection .ge-link path {
83 | stroke: #666; }
84 |
85 | .ge-graph.ge-selection .ge-link text {
86 | fill: #666; }
87 |
88 | .ge-graph.ge-selection .ge-link:hover path {
89 | stroke: #558888; }
90 |
91 | .ge-graph.ge-selection .ge-link:hover text {
92 | fill: #558888; }
93 |
94 | .ge-graph.ge-selection .ge-link.ge-selection path {
95 | stroke: #44a; }
96 |
97 | .ge-graph.ge-selection .ge-link.ge-selection text {
98 | fill: #44a; }
99 |
100 | .ge-graph.ge-selection .ge-link.ge-selection:hover path {
101 | stroke: #4477aa; }
102 |
103 | .ge-graph.ge-selection .ge-link.ge-selection:hover text {
104 | fill: #4477aa; }
105 |
106 | .ge-graph.ge-selection .ge-node path {
107 | stroke: #666;
108 | fill: #eee; }
109 |
110 | .ge-graph.ge-selection .ge-node span {
111 | color: #666; }
112 |
113 | .ge-graph.ge-selection .ge-node:hover path {
114 | stroke: #222255;
115 | fill: #aaccee; }
116 |
117 | .ge-graph.ge-selection .ge-node:hover span {
118 | color: #222255; }
119 |
120 | .ge-graph.ge-selection .ge-node.ge-selection path {
121 | stroke: #44a;
122 | fill: #aae; }
123 |
124 | .ge-graph.ge-selection .ge-node.ge-selection span {
125 | color: #44a; }
126 |
127 | .ge-graph.ge-selection .ge-node.ge-selection:hover path {
128 | stroke: #222255;
129 | fill: #aaccee; }
130 |
131 | .ge-graph.ge-selection .ge-node.ge-selection:hover span {
132 | color: #222255; }
133 |
134 | .ge-graph.ge-selection .ge-link.ge-link-selection path, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection path, .ge-graph.ge-link-selection .ge-link.ge-link-selection path, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection path {
135 | stroke: #488; }
136 |
137 | .ge-graph.ge-selection .ge-link.ge-link-selection text, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection text, .ge-graph.ge-link-selection .ge-link.ge-link-selection text, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection text {
138 | fill: #488; }
139 |
140 | .ge-graph.ge-selection .ge-link.ge-link-selection:hover path, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover path, .ge-graph.ge-link-selection .ge-link.ge-link-selection:hover path, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover path {
141 | stroke: #4477aa; }
142 |
143 | .ge-graph.ge-selection .ge-link.ge-link-selection:hover text, .ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover text, .ge-graph.ge-link-selection .ge-link.ge-link-selection:hover text, .ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover text {
144 | fill: #4477aa; }
145 |
--------------------------------------------------------------------------------
/dist/css/graph-editor.min.css:
--------------------------------------------------------------------------------
1 | .ge-graph *{-webkit-transition-property:stroke,fill,color;transition-property:stroke,fill,color;-webkit-transition-duration:.3s;transition-duration:.3s}.ge-graph marker#ge-dragline-end circle,.ge-graph marker#ge-dragline-end path,.ge-graph marker#ge-dragline-start circle,.ge-graph marker#ge-dragline-start path{stroke:#a44;fill:#a44}.ge-graph .ge-hidden{display:none}.ge-graph .ge-dragline{pointer-events:none;stroke-width:2px;stroke:#a44}.ge-graph .ge-link{cursor:pointer;font-size:14px;stroke-width:2px}.ge-graph .ge-link path{stroke:#000;fill:none}.ge-graph .ge-link text{fill:#000}.ge-graph .ge-link:hover path{stroke:#4aa}.ge-graph .ge-link:hover text{fill:#4aa}.ge-graph .ge-node{cursor:pointer}.ge-graph .ge-node path{stroke-width:2px;stroke:#000;fill:#ddd}.ge-graph .ge-node div,.ge-graph .ge-node div span{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-direction:normal;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ge-graph .ge-node div{width:100%;height:100%;-webkit-box-orient:horizontal;-ms-flex-direction:row;flex-direction:row}.ge-graph .ge-node div span{-webkit-box-orient:vertical;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center;color:#000;font-size:16px;word-wrap:break-word}.ge-graph .ge-node:hover path{stroke:#000;fill:#aee}.ge-graph .ge-node:hover div span{color:#000}.ge-graph.ge-selection .ge-link path{stroke:#666}.ge-graph.ge-selection .ge-link text{fill:#666}.ge-graph.ge-selection .ge-link:hover path{stroke:#588}.ge-graph.ge-selection .ge-link:hover text{fill:#588}.ge-graph.ge-selection .ge-link.ge-selection path{stroke:#44a}.ge-graph.ge-selection .ge-link.ge-selection text{fill:#44a}.ge-graph.ge-selection .ge-link.ge-selection:hover path{stroke:#47a}.ge-graph.ge-selection .ge-link.ge-selection:hover text{fill:#47a}.ge-graph.ge-selection .ge-node path{stroke:#666;fill:#eee}.ge-graph.ge-selection .ge-node span{color:#666}.ge-graph.ge-selection .ge-node.ge-selection:hover path,.ge-graph.ge-selection .ge-node:hover path{stroke:#225;fill:#ace}.ge-graph.ge-selection .ge-node.ge-selection:hover span,.ge-graph.ge-selection .ge-node:hover span{color:#225}.ge-graph.ge-selection .ge-node.ge-selection path{stroke:#44a;fill:#aae}.ge-graph.ge-selection .ge-node.ge-selection span{color:#44a}.ge-graph.ge-link-selection .ge-link.ge-link-selection path,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection path,.ge-graph.ge-selection .ge-link.ge-link-selection path,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection path{stroke:#488}.ge-graph.ge-link-selection .ge-link.ge-link-selection text,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection text,.ge-graph.ge-selection .ge-link.ge-link-selection text,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection text{fill:#488}.ge-graph.ge-link-selection .ge-link.ge-link-selection:hover path,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover path,.ge-graph.ge-selection .ge-link.ge-link-selection:hover path,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover path{stroke:#47a}.ge-graph.ge-link-selection .ge-link.ge-link-selection:hover text,.ge-graph.ge-link-selection .ge-link.ge-selection.ge-link-selection:hover text,.ge-graph.ge-selection .ge-link.ge-link-selection:hover text,.ge-graph.ge-selection .ge-link.ge-selection.ge-link-selection:hover text{fill:#47a}
--------------------------------------------------------------------------------
/dist/css/graph-editor.scss:
--------------------------------------------------------------------------------
1 | $ge-graph-transition-duration: 0.3s !default;
2 | $ge-graph-transition-properties: stroke, fill, color;
3 | /*$ge-graph-transition: stroke $ge-graph-transition-duration,
4 | fill $ge-graph-transition-duration,
5 | color $ge-graph-transition-duration !default;*/
6 |
7 | $ge-node-font-size: 16px !default;
8 | $ge-node-border: 2px !default;
9 |
10 | $ge-node-fg: #000 !default;
11 | $ge-node-bg: #ddd !default;
12 | $ge-node-hover-fg: #000 !default;
13 | $ge-node-hover-bg: #aee !default;
14 |
15 | $ge-link-font-size: 14px !default;
16 | $ge-link-size: 2px !default;
17 |
18 | $ge-link-fg: #000 !default;
19 | $ge-link-hover-fg: #4aa !default;
20 |
21 | $ge-dragline-fg: #a44 !default;
22 | $ge-dragline-size: 2px;
23 |
24 | $ge-selection-fg: #44a !default;
25 | $ge-selected-link-fg: #488 !default;
26 | $ge-selected-node-fg: $ge-selection-fg !default;
27 | $ge-selected-node-bg: #aae !default;
28 |
29 | $ge-unselected-fg: #666 !default;
30 | $ge-unselected-bg: #eee !default;
31 |
32 | $ge-unselected-link-hover-fg: mix($ge-unselected-fg, $ge-link-hover-fg) !default;
33 | $ge-unselected-node-hover-fg: mix($ge-selected-node-fg, $ge-node-hover-fg) !default;
34 | $ge-unselected-node-hover-bg: mix($ge-selected-node-bg, $ge-node-hover-bg) !default;
35 | $ge-selected-link-hover-fg: mix($ge-selection-fg, $ge-link-hover-fg) !default;
36 | $ge-selected-node-hover-fg: mix($ge-selected-node-fg, $ge-node-hover-fg) !default;
37 | $ge-selected-node-hover-bg: mix($ge-selected-node-bg, $ge-node-hover-bg) !default;
38 |
39 |
40 | .ge-graph {
41 | * {
42 | /*transition: $ge-graph-transition;*/
43 | transition-property: $ge-graph-transition-properties;
44 | transition-duration: $ge-graph-transition-duration;
45 | }
46 |
47 | marker {
48 | ge-dragline-end, ge-dragline-start {
49 | path, circle {
50 | stroke: $ge-dragline-fg;
51 | fill: $ge-dragline-fg;
52 | }
53 | }
54 | }
55 |
56 | .ge-hidden {
57 | display: none;
58 | }
59 |
60 | .ge-dragline {
61 | pointer-events: none;
62 | stroke-width: $ge-dragline-size;
63 | stroke: $ge-dragline-fg;
64 | }
65 |
66 | .ge-link {
67 | cursor: pointer;
68 |
69 | font-size: $ge-link-font-size;
70 | stroke-width: $ge-link-size;
71 |
72 | /* * { vector-effect: non-scaling-stroke } */
73 |
74 | path {
75 | stroke: $ge-link-fg;
76 | fill: none;
77 | }
78 |
79 | text {
80 | fill: $ge-link-fg;
81 | }
82 |
83 | &:hover {
84 | path {
85 | stroke: $ge-link-hover-fg;
86 | }
87 | text {
88 | fill: $ge-link-hover-fg;
89 | }
90 | }
91 | }
92 |
93 | .ge-node {
94 | cursor: pointer;
95 |
96 | /* * { vector-effect: non-scaling-stroke } */
97 |
98 | path {
99 | stroke-width: $ge-node-border;
100 | stroke: $ge-node-fg;
101 | fill: $ge-node-bg;
102 | }
103 |
104 | div {
105 | display: flex;
106 | flex-direction: row;
107 | align-items: center;
108 |
109 | width: 100%;
110 | height: 100%;
111 |
112 | span {
113 | display: flex;
114 | flex-direction: column;
115 | flex-grow: 1;
116 | align-items: center;
117 | text-align: center;
118 |
119 | color: $ge-node-fg;
120 | font-size: $ge-node-font-size;
121 | word-wrap: break-word;
122 | }
123 | }
124 |
125 | &:hover {
126 | path {
127 | stroke: $ge-node-hover-fg;
128 | fill: $ge-node-hover-bg;
129 | }
130 | div {
131 | span {
132 | color: $ge-node-hover-fg;
133 | }
134 | }
135 | }
136 | }
137 |
138 | &.ge-selection {
139 | .ge-link {
140 | path { stroke: $ge-unselected-fg; }
141 | text { fill: $ge-unselected-fg; }
142 |
143 | &:hover {
144 | path { stroke: $ge-unselected-link-hover-fg; }
145 | text { fill: $ge-unselected-link-hover-fg; }
146 | }
147 |
148 | &.ge-selection {
149 | path { stroke: $ge-selection-fg; }
150 | text { fill: $ge-selection-fg; }
151 |
152 | &:hover {
153 | path { stroke: $ge-selected-link-hover-fg; }
154 | text { fill: $ge-selected-link-hover-fg; }
155 | }
156 | }
157 | }
158 |
159 | .ge-node {
160 | path {
161 | stroke: $ge-unselected-fg;
162 | fill: $ge-unselected-bg;
163 | }
164 | span { color: $ge-unselected-fg; }
165 |
166 | &:hover {
167 | path {
168 | stroke: $ge-unselected-node-hover-fg;
169 | fill: $ge-unselected-node-hover-bg;
170 | }
171 | span { color: $ge-unselected-node-hover-fg; }
172 | }
173 |
174 | &.ge-selection {
175 | path {
176 | stroke: $ge-selected-node-fg;
177 | fill: $ge-selected-node-bg;
178 | }
179 | span { color: $ge-selected-node-fg; }
180 |
181 | &:hover {
182 | path {
183 | stroke: $ge-selected-node-hover-fg;
184 | fill: $ge-selected-node-hover-bg;
185 | }
186 | span { color: $ge-selected-node-hover-fg; }
187 | }
188 | }
189 | }
190 | }
191 |
192 | &.ge-selection, &.ge-link-selection {
193 | .ge-link {
194 | &.ge-link-selection, &.ge-selection.ge-link-selection {
195 | path { stroke: $ge-selected-link-fg; }
196 | text { fill: $ge-selected-link-fg; }
197 |
198 | &:hover {
199 | path { stroke: $ge-selected-link-hover-fg; }
200 | text { fill: $ge-selected-link-hover-fg; }
201 | }
202 | }
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/docs/events.jsdoc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: events.jsdoc
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: events.jsdoc
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Graph SVG click/touch event.
31 | * @event click
32 | * @this ge.GraphEditor
33 | * @param {Point} pos Click/touch position.
34 | */
35 |
36 | /**
37 | * Node click/touch event.
38 | * @event node-click
39 | * @this ge.GraphEditor
40 | * @param {Node} node Clicked/touched node.
41 | */
42 |
43 | /**
44 | * Link click/touch event.
45 | * @event link-click
46 | * @this ge.GraphEditor
47 | * @param {Link} link Clicked/touched link.
48 | */
49 |
50 | /**
51 | * New link start event.
52 | * @event new-link-start
53 | * @this ge.GraphEditor
54 | * @param {Node} source New link source.
55 | */
56 |
57 | /**
58 | * New link end event.
59 | * @event new-link-end
60 | * @this ge.GraphEditor
61 | * @param {Node} source New link source.
62 | * @param {Node} target New link target.
63 | */
64 |
65 | /**
66 | * New link cancel event.
67 | * @event new-link-cancel
68 | * @this ge.GraphEditor
69 | * @param {Node} source Cancelled link source.
70 | */
71 |
72 | /**
73 | * Simulation start event.
74 | * @event simulation-start
75 | * @this ge.GraphEditor
76 | */
77 |
78 | /**
79 | * Simulation stop event.
80 | * @event simulation-stop
81 | * @this ge.GraphEditor
82 | */
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | Classes Events Namespaces
94 |
95 |
96 |
97 |
98 |
99 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Bold-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Bold-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Bold-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-BoldItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-BoldItalic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-BoldItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-BoldItalic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Italic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Italic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Italic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Italic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Light-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Light-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Light-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-LightItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-LightItalic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-LightItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-LightItalic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Regular-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dead-beef/graph-editor/0c1622e87e160a2fa006f2118603f5fe63d72221/docs/fonts/OpenSans-Regular-webfont.woff
--------------------------------------------------------------------------------
/docs/ge.path.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Namespace: path
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Namespace: path
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ge . path
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Link shapes.
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Source:
75 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | Classes
98 |
99 |
100 | Line
101 |
102 |
103 | Path
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | Classes Events Namespaces
132 |
133 |
134 |
135 |
136 |
137 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/docs/ge.shape.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Namespace: shape
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Namespace: shape
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ge . shape
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Node shapes.
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Source:
75 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | Classes
98 |
99 |
100 | Circle
101 |
102 |
103 | Rect
104 |
105 |
106 | Shape
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | Classes Events Namespaces
135 |
136 |
137 |
138 |
139 |
140 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
141 |
142 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Home
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Home
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
graph-editor 0.1.1
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Classes Events Namespaces
89 |
90 |
91 |
92 |
93 |
94 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/docs/js_init.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/init.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/init.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 'use strict';
30 |
31 | /**
32 | * Create SVG markers.
33 | * @private
34 | * @param {D3Selection} svg
35 | * @returns {ge.GraphEditor}
36 | */
37 | ge.GraphEditor.prototype.initMarkers = function initMarkers(svg) {
38 | /*var defs = d3.select('#' + this.options.css.markers).node();
39 | if(defs !== null) {
40 | return this;
41 | }*/
42 |
43 | /*defs = d3.select('head')
44 | .append('svg')
45 | .attr('id', this.options.css.markers)
46 | .append('svg:defs');*/
47 |
48 | var defs = svg.append('svg:defs');
49 |
50 | defs.append('marker')
51 | .attr('id', 'ge-dragline-end')
52 | .attr('viewBox', '0 -7 12 14')
53 | .attr('refX', '7')
54 | .attr('refY', '0')
55 | .attr('markerWidth', 3.5)
56 | .attr('markerHeight', 3.5)
57 | .attr('orient', 'auto')
58 | .append('path')
59 | .attr('d', 'M0,-5L10,0L0,5Z');
60 |
61 | defs.append('marker')
62 | .attr('id', 'ge-dragline-start')
63 | .attr('viewBox', '-5 -5 5 5')
64 | .attr('refX', -2)
65 | .attr('refY', -2)
66 | .attr('markerWidth', 4)
67 | .attr('markerHeight', 4)
68 | .append('circle')
69 | .attr('r', 2)
70 | .attr('cx', -2)
71 | .attr('cy', -2);
72 |
73 | return this;
74 | };
75 |
76 | /**
77 | * Initialize SVG element.
78 | * @private
79 | * @param {D3Selection} svg
80 | * @this ge.GraphEditor
81 | * @returns {ge.GraphEditor}
82 | */
83 | ge.GraphEditor.prototype.initSvg = function initSvg(svg) {
84 | svg.attr('id', this.options.id)
85 | .classed(this.options.css.graph, true)
86 | .classed(this.options.css.digraph, this.options.directed);
87 |
88 | this.initMarkers(svg);
89 |
90 | var g = svg.append('g');
91 | var defsContainer = svg.select('defs'); //g.append('defs');
92 | var linkContainer = g.append('g');
93 | var nodeContainer = g.append('g');
94 |
95 | /**
96 | * Graph container element.
97 | * @readonly
98 | * @member {D3Selection}
99 | */
100 | this.container = g;
101 | /**
102 | * Link text path elements.
103 | * @readonly
104 | * @member {D3Selection}
105 | */
106 | this.defs = defsContainer.selectAll('.' + this.options.css.textpath);
107 | /**
108 | * Link elements.
109 | * @readonly
110 | * @member {D3Selection}
111 | */
112 | this.links = linkContainer.selectAll('g');
113 | /**
114 | * Node elements.
115 | * @readonly
116 | * @member {D3Selection}
117 | */
118 | this.nodes = nodeContainer.selectAll('g');
119 | /**
120 | * 'Drag to link nodes' line.
121 | * @readonly
122 | * @member {D3Selection}
123 | */
124 | this.dragLine = g.append('path')
125 | .classed(this.options.css.dragline, true)
126 | .classed(this.options.css.hide, true)
127 | .attr('marker-start', 'url(#ge-dragline-start)')
128 | .attr('marker-end', 'url(#ge-dragline-end)')
129 | .attr('d', 'M0,0L0,0');
130 | /**
131 | * Text size calculator.
132 | * @readonly
133 | * @member {ge.TextSize}
134 | */
135 | this.textSize = new ge.TextSize(
136 | svg.append('text')
137 | .classed(this.options.css.node, true)
138 | .node()
139 | );
140 |
141 | /**
142 | * Graph SVG element.
143 | * @readonly
144 | * @member {D3Selection}
145 | */
146 | this.svg = svg;
147 |
148 | return this;
149 | };
150 |
151 |
152 | /**
153 | * Initialize graph data.
154 | * @private
155 | * @param {ImportGraphData} data
156 | * @this ge.GraphEditor
157 | * @returns {ge.GraphEditor}
158 | */
159 | ge.GraphEditor.prototype.initData = function initData(data) {
160 | /**
161 | * Nodes by ID.
162 | * @readonly
163 | * @member {Object<ID,Node>}
164 | */
165 | this.nodeById = {};
166 | /**
167 | * Links by ID.
168 | * @readonly
169 | * @member {Object<ID,Link>}
170 | */
171 | this.linkById = {};
172 | /**
173 | * Graph data.
174 | * @readonly
175 | * @member {GraphData}
176 | */
177 | this.data = {
178 | nodes: [],
179 | links: []
180 | };
181 | this.addNodes(data.nodes, true);
182 | this.addLinks(data.links, true);
183 | return this;
184 | };
185 |
186 | /**
187 | * Initialize graph state.
188 | * @private
189 | * @this ge.GraphEditor
190 | * @returns {ge.GraphEditor}
191 | */
192 | ge.GraphEditor.prototype.initState = function initState() {
193 | /**
194 | * Graph editor state.
195 | * @readonly
196 | * @member {Object}
197 | * @property {boolean} zoomed False if the graph is clicked.
198 | * @property {boolean} dragged False if a node is clicked.
199 | * @property {?D3Simulation} simulation Current simulation object.
200 | * @property {boolean} simulationStarted True if a simulation is started.
201 | * @property {boolean} dragToLink True in 'drag to link nodes' mode.
202 | * @property {?Node} newLinkTarget New link target in 'drag to link nodes' mode.
203 | * @property {?Node} selectedNode Selected node.
204 | * @property {?Link} selectedLink Selected link.
205 | * @property {?Point} dragPos Last drag end position.
206 | * @property {number} zoom Zoom level.
207 | */
208 | this.state = {
209 | zoomed: false,
210 | dragged: false,
211 | simulation: null,
212 | simulationStarted: false,
213 | dragToLink: false,
214 | newLinkTarget: null,
215 | selectedNode: null,
216 | selectedLink: null,
217 | dragPos: null,
218 | zoom: 1
219 | //sizeScale: 1
220 | };
221 | return this;
222 | };
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 | Classes Events Namespaces
234 |
235 |
236 |
237 |
238 |
239 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
240 |
241 |
242 |
243 |
244 |
245 |
246 |
--------------------------------------------------------------------------------
/docs/js_options.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/options.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/options.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 'use strict';
30 |
31 | /**
32 | * Default options for all graph types.
33 | * @type GraphOptions
34 | */
35 | ge.GraphEditor.prototype.defaults = {
36 | id: null,
37 | directed: false,
38 |
39 | node: {
40 | shape: new ge.shape.Circle(),
41 | border: 2,
42 | size: {
43 | def: 10,
44 | min: 10
45 | },
46 | text: {
47 | dx: 0,
48 | dy: 0,
49 | inside: true
50 | }
51 | },
52 |
53 | link: {
54 | size: {
55 | def: 2
56 | },
57 | text: {
58 | dx: 0,
59 | dy: '1.1em',
60 | offset: null,
61 | anchor: null
62 | }
63 | },
64 |
65 | simulation: {
66 | create: ge.defaultSimulation,
67 | start: false,
68 | stop: true,
69 | step: 1
70 | },
71 |
72 | transition: {
73 | zoom: 250,
74 | drag: 0,
75 | simulation: 50
76 | },
77 |
78 | scale: {
79 | min: 0.25,
80 | max: 8.0,
81 | /*size: {
82 | min: 0.5,
83 | max: 2.0
84 | }*/
85 | },
86 |
87 | bbox: {
88 | padding: 80
89 | },
90 |
91 | css: {
92 | //markers: 'ge-markers',
93 | node: 'ge-node',
94 | graph: 'ge-graph',
95 | digraph: 'ge-digraph',
96 | hide: 'ge-hidden',
97 | dragline: 'ge-dragline',
98 | link: 'ge-link',
99 | textpath: 'ge-text-path',
100 | //connect: 'ge-connect',
101 | selection: {
102 | node: 'ge-selection',
103 | link: 'ge-link-selection'
104 | }
105 | }
106 | };
107 |
108 | /**
109 | * Default options by graph type.
110 | * @type Array<GraphOptions>
111 | */
112 | ge.GraphEditor.prototype.typeDefaults = [
113 | {
114 | link: {
115 | shape: new ge.path.Line({
116 | loopStart: 0,
117 | loopEnd: 90
118 | }),
119 | text: {
120 | offset: '50%',
121 | anchor: 'middle'
122 | }
123 | }
124 | },
125 | {
126 | link: {
127 | shape: new ge.path.Line({
128 | arrow: true,
129 | loopStart: 0,
130 | loopEnd: 90
131 | }),
132 | text: {
133 | offset: '20%',
134 | anchor: 'start'
135 | }
136 | }
137 | }
138 | ];
139 |
140 | /**
141 | * Initialize graph options.
142 | * @private
143 | * @param {GraphOptions} options
144 | * @param {D3Selection} svg
145 | * @returns {ge.GraphEditor}
146 | */
147 | ge.GraphEditor.prototype.initOptions = function initOptions(options, svg) {
148 | var directed;
149 | if(options && options.hasOwnProperty('directed')) {
150 | directed = options.directed;
151 | }
152 | else {
153 | directed = this.defaults.directed;
154 | }
155 |
156 | var typeDefaults = this.typeDefaults[+directed];
157 |
158 | var opt = ge.extend({}, this.defaults, typeDefaults, options);
159 |
160 | opt.id = opt.id
161 | || svg.attr('id')
162 | || 'ge'.concat(Math.floor(Math.random() * 100));
163 |
164 | var reversed = (100 - parseInt(opt.link.text.offset)) + '%';
165 | opt.link.text.offset = [ opt.link.text.offset, reversed ];
166 |
167 | switch(opt.link.text.anchor) {
168 | case 'start':
169 | reversed = 'end';
170 | break;
171 | case 'end':
172 | reversed = 'start';
173 | break;
174 | default:
175 | reversed = 'middle';
176 | }
177 | opt.link.text.anchor = [ opt.link.text.anchor, reversed ];
178 |
179 | this.options = opt;
180 | return this;
181 | };
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | Classes Events Namespaces
193 |
194 |
195 |
196 |
197 |
198 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
199 |
200 |
201 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------
/docs/js_path_base.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/path/base.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/path/base.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Link shapes.
31 | * @namespace
32 | */
33 | ge.path = new ge.SaveLoad();
34 |
35 | /**
36 | * Abstract base path class.
37 | * @class
38 | * @abstract
39 | * @param {?object} [data] JSON data.
40 | * @param {?number} [data.loopStart=180] Loop start angle in degrees.
41 | * @param {?number} [data.loopEnd=270] Loop end angle in degrees.
42 | */
43 | ge.path.Path = function Path(data) { // eslint-disable-line no-unused-vars
44 | if(this.constructor === Path) {
45 | throw new Error('abstract class');
46 | }
47 | data = data || {};
48 | this.loopStart = new ge.Angle(+data.loopStart || 180);
49 | this.loopEnd = new ge.Angle(+data.loopEnd || 270);
50 | };
51 |
52 | /* eslint-disable no-unused-vars */
53 |
54 | /**
55 | * Return text path and set link path.
56 | * @abstract
57 | * @param {ge.Link} link Link data.
58 | * @returns {string} SVG path.
59 | */
60 | ge.path.Path.prototype.path = function path(link) {
61 | throw new Error('abstract method');
62 | };
63 |
64 | /* eslint-enable no-unused-vars */
65 |
66 | /**
67 | * Convert to JSON.
68 | * @returns {object} JSON data.
69 | */
70 | ge.path.Path.prototype.toJson = function toJson() {
71 | return {
72 | loopStart: this.loopStart.deg,
73 | loopEnd: this.loopEnd.deg
74 | };
75 | };
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Classes Events Namespaces
87 |
88 |
89 |
90 |
91 |
92 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/docs/js_path_line.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/path/line.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/path/line.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Line.
31 | * @class
32 | * @param {?object} [data] JSON data.
33 | * @param {?number} [data.loopStart=180] Loop start angle in degrees.
34 | * @param {?number} [data.loopEnd=270] Loop end angle in degrees.
35 | * @param {?boolean} [data.arrow=false]
36 | * @param {?number} [data.arrowLength=10]
37 | * @param {?number} [data.arrowAngle=15]
38 | */
39 | ge.path.Line = function Line(data) {
40 | ge.path.Path.call(this, data);
41 | this.arrow = !!data.arrow;
42 | this.arrowLength = +data.arrowLength || 10;
43 | this.arrowAngle = new ge.Angle(+data.arrowAngle || 15);
44 | this.arrowAngle2 = new ge.Angle(-data.arrowAngle || -15);
45 | };
46 |
47 | ge.path.Line.prototype = Object.create(ge.path.Line);
48 | Object.defineProperty(
49 | ge.path.Line.prototype,
50 | 'constructor',
51 | {
52 | value: ge.path.Line,
53 | enumerable: false,
54 | writable: true
55 | }
56 | );
57 | ge.path.addClass(ge.path.Line);
58 |
59 | /**
60 | * Return arrow path.
61 | * @param {ge.Point} src Link source.
62 | * @param {ge.Point} dst Link target.
63 | * @returns {string} SVG path.
64 | */
65 | ge.path.Line.prototype.arrowPath = function arrow(src, dst) {
66 | var b = src.clone().sub(dst).normalize();
67 | var c = b.clone().rotate(this.arrowAngle2).mul(this.arrowLength).add(dst);
68 | b.rotate(this.arrowAngle).mul(this.arrowLength).add(dst);
69 | return 'M'.concat(
70 | dst.x, ',', dst.y,
71 | 'L', b.x, ',', b.y,
72 | 'L', c.x, ',', c.y,
73 | 'Z'
74 | );
75 | };
76 |
77 | /**
78 | * Return text path and set link path.
79 | * @param {ge.Link} link Link data.
80 | * @returns {string} SVG path.
81 | */
82 | ge.path.Line.prototype.path = function path(link) {
83 | var src, dst;
84 |
85 | if(link.source === link.target) {
86 | src = link.source.shape.getPoint(link.source, this.loopStart);
87 | dst = link.source.shape.getPoint(link.source, this.loopEnd);
88 | var rx = link.source.width / 2;
89 | var ry = link.source.height / 2;
90 |
91 | link.reversed = false;
92 | link.path = 'M'.concat(
93 | src.x, ',', src.y,
94 | 'A', rx, ',', ry, ',0,1,0,', dst.x, ',', dst.y
95 | );
96 |
97 | /*if(this.arrow) {
98 | }*/
99 |
100 | return link.path;
101 | }
102 |
103 | src = new ge.Point(link.source.x, link.source.y);
104 | dst = new ge.Point(link.target.x, link.target.y);
105 | var src2 = link.source.shape.intersect(link.source, link.target);
106 | var dst2 = link.target.shape.intersect(link.target, link.source);
107 | src = src2 || src;
108 | dst = dst2 || dst;
109 |
110 | var dst3 = dst;
111 |
112 | if(this.arrow) {
113 | dst3 = dst.clone().sub(src);
114 | var l = dst3.length();
115 | var l2 = this.arrowLength * this.arrowAngle.cos;
116 | dst3.mul(1 - l2 / l).add(src);
117 | }
118 |
119 | link.path = 'M'.concat(src.x, ',', src.y, 'L', dst3.x, ',', dst3.y);
120 |
121 | if(this.arrow) {
122 | link.path += this.arrowPath(src, dst);
123 | }
124 |
125 | if((link.reversed = src.x > dst.x)) {
126 | src2 = src;
127 | src = dst;
128 | dst = src2;
129 | }
130 |
131 | return 'M'.concat(src.x, ',', src.y, 'L', dst.x, ',', dst.y);
132 | };
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | Classes Events Namespaces
144 |
145 |
146 |
147 |
148 |
149 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/docs/js_shape_base.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/shape/base.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/shape/base.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Node shapes.
31 | * @namespace
32 | */
33 |
34 | ge.shape = new ge.SaveLoad();
35 |
36 | /**
37 | * Abstract base shape class.
38 | * @class
39 | * @abstract
40 | * @param {?object} [data] JSON data.
41 | */
42 | ge.shape.Shape = function Shape(data) { // eslint-disable-line no-unused-vars
43 | if(this.constructor === Shape) {
44 | throw new Error('abstract class');
45 | }
46 | };
47 |
48 | /* eslint-disable no-unused-vars */
49 |
50 | /**
51 | * Return SVG path.
52 | * @abstract
53 | * @param {ge.Node} node Node data.
54 | * @returns {string} SVG path.
55 | */
56 | ge.shape.Shape.prototype.path = function path(node) {
57 | throw new Error('abstract method');
58 | };
59 |
60 | /**
61 | * Return point at angle.
62 | * @abstract
63 | * @param {ge.Node} node Node data.
64 | * @param {ge.Angle} angle Angle.
65 | * @returns {ge.Point}
66 | */
67 | ge.shape.Shape.prototype.getPoint = function getPoint(node, angle) {
68 | throw new Error('abstract method');
69 | };
70 |
71 | /**
72 | * Resize a node to fit its title inside.
73 | * @abstract
74 | * @param {ge.Node} node Node data.
75 | * @param {ge.ContainerSize} size Text container size.
76 | * @param {ge.TextSize} textSize Text size calculator.
77 | */
78 | ge.shape.Shape.prototype.doFitTitleInside = function doFitTitleInside(
79 | node,
80 | size,
81 | textSize
82 | ) {
83 | throw new Error('abstract method');
84 | };
85 |
86 |
87 | /* eslint-enable no-unused-vars */
88 |
89 | /**
90 | * Convert to JSON.
91 | * @returns {object} JSON data.
92 | */
93 | ge.shape.Shape.prototype.toJson = function toJson() {
94 | return {};
95 | };
96 |
97 | /**
98 | * Intersect with a link.
99 | * @param {ge.Node} node Node data.
100 | * @param {ge.Node} node2 Node data.
101 | * @returns {?ge.Point} Intersection point.
102 | */
103 | ge.shape.Shape.prototype.intersect = function intersect(node, node2) {
104 | var a = Math.atan2(node2.y - node.y, node2.x - node.x);
105 | return this.getPoint(node, new ge.Angle(a, true));
106 | };
107 |
108 | /**
109 | * Resize a node to fit its title inside if necessary.
110 | * @param {ge.Node} node Node data.
111 | * @param {ge.TextSize} textSize Text size calculator.
112 | */
113 | ge.shape.Shape.prototype.fitTitleInside = function fitTitleInside(
114 | node,
115 | textSize
116 | ) {
117 | if(node.title === node.prevTitle) {
118 | return;
119 | }
120 | var size = textSize.containerSize(node.title);
121 | this.doFitTitleInside(node, size, textSize);
122 | node.prevTitle = node.title;
123 | };
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | Classes Events Namespaces
135 |
136 |
137 |
138 |
139 |
140 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
141 |
142 |
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/docs/js_shape_circle.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/shape/circle.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/shape/circle.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Circle.
31 | * @class
32 | * @param {?object} [data] JSON data.
33 | */
34 | ge.shape.Circle = function Circle(data) {
35 | ge.shape.Shape.call(this, data);
36 | };
37 |
38 | ge.shape.Circle.prototype = Object.create(ge.shape.Shape.prototype);
39 | Object.defineProperty(
40 | ge.shape.Circle.prototype,
41 | 'constructor',
42 | {
43 | value: ge.shape.Circle,
44 | enumerable: false,
45 | writable: true
46 | }
47 | );
48 | ge.shape.addClass(ge.shape.Circle);
49 |
50 | /**
51 | * Return SVG path.
52 | * @param {ge.Node} node Node data.
53 | * @returns {string} SVG path.
54 | */
55 | ge.shape.Circle.prototype.path = function path(node) {
56 | var dx = node.width, rx = dx / 2, ry = node.height / 2;
57 | var arc = 'a'.concat(rx, ',', ry, ',0,1,0,');
58 | return 'M'.concat(-rx, ',0', arc, dx, ',0', arc, -dx, ',0');
59 | };
60 |
61 | /**
62 | * Return point at angle.
63 | * @abstract
64 | * @param {ge.Node} node Node data.
65 | * @param {ge.Angle} angle Angle.
66 | * @returns {ge.Point}
67 | */
68 | ge.shape.Circle.prototype.getPoint = function getPoint(node, angle) {
69 | return new ge.Point(
70 | node.x + node.width * angle.cos * 0.5,
71 | node.y + node.height * angle.sin * 0.5
72 | );
73 | };
74 |
75 | /**
76 | * Resize a node to fit its title inside.
77 | * @abstract
78 | * @param {ge.Node} node Node data.
79 | * @param {ge.ContainerSize} size Text container size.
80 | * @param {ge.TextSize} textSize Text size calculator.
81 | */
82 | ge.shape.Circle.prototype.doFitTitleInside = function doFitTitleInside(
83 | node,
84 | size/*,
85 | textSize*/
86 | ) {
87 | node.width = node.height = Math.max(
88 | Math.ceil(Math.sqrt(size.minArea * 2)),
89 | size.minWidth,
90 | size.minHeight
91 | );
92 | node.textWidth = node.textHeight = Math.ceil(node.width / 1.414213);
93 | };
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Classes Events Namespaces
105 |
106 |
107 |
108 |
109 |
110 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/docs/js_shape_rect.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/shape/rect.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/shape/rect.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Rectangle.
31 | * @class
32 | * @param {?object} [data] JSON data.
33 | * @param {?number} [data.aspect=1] Aspect ratio.
34 | */
35 | ge.shape.Rect = function Rect(data) {
36 | data = data || {};
37 | ge.shape.Shape.call(this, data);
38 | /**
39 | * Aspect ratio.
40 | * @member {number}
41 | */
42 | this.aspect = data.aspect || 1;
43 | };
44 |
45 | ge.shape.Rect.prototype = Object.create(ge.shape.Shape.prototype);
46 | Object.defineProperty(
47 | ge.shape.Rect.prototype,
48 | 'constructor',
49 | {
50 | value: ge.shape.Rect,
51 | enumerable: false,
52 | writable: true
53 | }
54 | );
55 | ge.shape.addClass(ge.shape.Rect);
56 |
57 | /**
58 | * Convert to JSON.
59 | * @abstract
60 | * @returns {object} JSON data.
61 | */
62 | ge.shape.Rect.prototype.toJson = function toJson() {
63 | return {
64 | aspect: this.aspect
65 | };
66 | };
67 |
68 | /**
69 | * Return SVG path.
70 | * @param {ge.Node} node Node data.
71 | * @returns {string} SVG path.
72 | */
73 | ge.shape.Rect.prototype.path = function path(node) {
74 | var w = node.width, h = node.height;
75 | return 'M'.concat(
76 | (-w / 2), ',', (-h / 2),
77 | 'l', w, ',0l0,', h, 'l', -w, ',0z'
78 | );
79 | };
80 |
81 | /**
82 | * Return point at angle.
83 | * @abstract
84 | * @param {ge.Node} node Node data.
85 | * @param {ge.Angle} angle Angle.
86 | * @returns {ge.Point}
87 | */
88 | ge.shape.Rect.prototype.getPoint = function getPoint(node, angle) {
89 | var w = node.width / 2;
90 | var h = node.height / 2;
91 |
92 | var x, y, t;
93 | var ret = new ge.Point(node.x, node.y);
94 |
95 | x = null;
96 | if(angle.cos > 1e-8) {
97 | x = w;
98 | }
99 | else if(angle.cos < -1e-8) {
100 | x = -w;
101 | }
102 | if(x !== null) {
103 | t = x / angle.cos;
104 | y = t * angle.sin;
105 | if(y < h + 1e-8 && y > -h - 1e-8) {
106 | ret.x += x;
107 | ret.y += y;
108 | return ret;
109 | }
110 | }
111 |
112 | y = null;
113 | if(angle.sin > 1e-8) {
114 | y = h;
115 | }
116 | else if(angle.sin < -1e-8) {
117 | y = -h;
118 | }
119 | if(y !== null) {
120 | t = y / angle.sin;
121 | x = t * angle.cos;
122 | if(x < w + 1e-8 && x > -w - 1e-8) {
123 | ret.x += x;
124 | ret.y += y;
125 | return ret;
126 | }
127 | }
128 |
129 | // throw new Error()
130 | return ret;
131 | };
132 |
133 | /**
134 | * Resize a node to fit its title inside.
135 | * @abstract
136 | * @param {ge.Node} node Node data.
137 | * @param {ge.ContainerSize} size Text container size.
138 | * @param {ge.TextSize} textSize Text size calculator.
139 | */
140 | ge.shape.Rect.prototype.doFitTitleInside = function doFitTitleInside(
141 | node,
142 | size/*,
143 | textSize*/
144 | ) {
145 | node.width = Math.max(
146 | Math.ceil(this.aspect * Math.sqrt(size.minArea)),
147 | size.minWidth
148 | );
149 | node.height = Math.max(size.minHeight, node.width / this.aspect);
150 | node.textWidth = node.width;
151 | node.textHeight = node.height;
152 | };
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | Classes Events Namespaces
164 |
165 |
166 |
167 |
168 |
169 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
170 |
171 |
172 |
173 |
174 |
175 |
176 |
--------------------------------------------------------------------------------
/docs/js_util_angle.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/util/angle.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/util/angle.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Angle constructor.
31 | * @class
32 | * @classdesc Angle class.
33 | * @param {?number} value Angle value.
34 | * @param {?boolean} rad true if value is in radians, false if in degrees.
35 | */
36 | ge.Angle = function Angle(value, rad) {
37 | /**
38 | * Angle in degrees.
39 | * @member {number}
40 | */
41 | this.deg = 0;
42 | /**
43 | * Angle in radians.
44 | * @member {number}
45 | */
46 | this.rad = 0;
47 |
48 | if(rad) {
49 | this.rad = +value || 0;
50 | this.deg = this.rad * 180.0 / Math.PI;
51 | }
52 | else {
53 | this.deg = +value || 0;
54 | this.rad = this.deg * Math.PI / 180.0;
55 | }
56 |
57 | /**
58 | * Sine.
59 | * @member {number}
60 | */
61 | this.sin = Math.sin(this.rad);
62 | /**
63 | * Cosine.
64 | * @member {number}
65 | */
66 | this.cos = Math.cos(this.rad);
67 | };
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Classes Events Namespaces
79 |
80 |
81 |
82 |
83 |
84 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/docs/js_util_bbox.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/util/bbox.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/util/bbox.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Bounding box constructor.
31 | * @class
32 | * @classdesc Bounding box class.
33 | * @param {?number} left Left X coordinate.
34 | * @param {?number} top Top Y coordinate.
35 | * @param {?number} right Right X coordinate.
36 | * @param {?number} bottom Bottom Y coordinate.
37 | */
38 | ge.BBox = function BBox(left, top, right, bottom) {
39 | /**
40 | * Left X coordinate.
41 | * @member {number}
42 | */
43 | this.left = +left || 0;
44 | /**
45 | * Top Y coordinate.
46 | * @member {number}
47 | */
48 | this.top = +top || 0;
49 | /**
50 | * Right X coordinate.
51 | * @member {number}
52 | */
53 | this.right = +right || 0;
54 | /**
55 | * Bottom Y coordinate.
56 | * @member {number}
57 | */
58 | this.bottom = +bottom || 0;
59 | };
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Classes Events Namespaces
71 |
72 |
73 |
74 |
75 |
76 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/docs/js_util_common.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/util/common.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/util/common.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Get object ID.
31 | * @param {?(Object|undefined)} obj
32 | * @returns {?ID}
33 | */
34 | ge.id = function id(obj) {
35 | return obj && obj.id || null;
36 | };
37 |
38 | /**
39 | * @param {Object} _this
40 | * @param {function} func
41 | * @returns {function}
42 | */
43 | ge.bind = function bind(_this, func) {
44 | return function bound() {
45 | return func.apply(_this, arguments);
46 | };
47 | };
48 |
49 | /**
50 | * Compare numbers or arrays of numbers.
51 | * @param {(number|Array<number>)} u
52 | * @param {(number|Array<number>)} v
53 | * @param {number} [eps=1e-5] Precision.
54 | * @returns {boolean}
55 | */
56 | ge.equal = function equal(u, v, eps) {
57 | eps = eps || 1e-5;
58 | var eq = function(x, y) { return Math.abs(x - y) < eps; };
59 |
60 | if(u === null || v === null
61 | || u === undefined || v === undefined
62 | || typeof u === 'number' && Array.isArray(v)
63 | || typeof v === 'number' && Array.isArray(u)) {
64 | return false;
65 | }
66 |
67 | if(typeof u === 'number' && typeof v === 'number') {
68 | return eq(u, v);
69 | }
70 |
71 | if(!Array.isArray(u) || !Array.isArray(v)) {
72 | throw new Error(
73 | 'ge.equal: invalid argument type: '
74 | .concat(u.toString(), ' ', v.toString())
75 | );
76 | }
77 |
78 | if(u.length !== v.length) {
79 | return false;
80 | }
81 |
82 | for(var i = 0; i < u.length; ++i) {
83 | if(!eq(u[i], v[i])) {
84 | return false;
85 | }
86 | }
87 |
88 | return true;
89 | };
90 |
91 | /**
92 | * Default simulation update function.
93 | * @param {?D3Simulation} simulation Old simulation object.
94 | * @param {Array<ge.Node>} nodes Graph nodes.
95 | * @param {Array<ge.Link>} links Graph links.
96 | * @this ge.GraphEditor
97 | * @returns {D3Simulation} New/updated simulation object.
98 | */
99 | ge.defaultSimulation = function defaultSimulation(simulation, nodes, links) {
100 | if(!simulation) {
101 | simulation = d3.forceSimulation()
102 | .force('charge', d3.forceManyBody())
103 | .force('link', d3.forceLink())
104 | .force('center', d3.forceCenter());
105 | }
106 |
107 | var dist = 2 * d3.max(nodes, function(d) {
108 | return Math.max(d.width, d.height);
109 | });
110 |
111 | var cx = d3.mean(nodes, function(d) { return d.x; });
112 | var cy = d3.mean(nodes, function(d) { return d.y; });
113 |
114 | simulation.nodes(nodes);
115 | simulation.force('center').x(cx).y(cy);
116 | simulation.force('link').links(links).distance(dist);
117 |
118 | return simulation;
119 | };
120 |
121 | /**
122 | * Extend an object.
123 | * @param {object} dst Object to extend.
124 | * @param {object} src Source object.
125 | * @returns {object} Extended object.
126 | */
127 | ge._extend = function _extend(dst, src) {
128 | if(!src) {
129 | return dst;
130 | }
131 | if(typeof src !== 'object') {
132 | throw new Error('src is not an object: ' + src.toString());
133 | }
134 |
135 | if(!dst) {
136 | dst = {};
137 | }
138 | else if(typeof dst !== 'object') {
139 | throw new Error('dst is not an object: ' + dst.toString());
140 | }
141 |
142 | for(var key in src) {
143 | var value = src[key];
144 | if(typeof value === 'object' && value !== null) {
145 | if(Array.isArray(value)) {
146 | dst[key] = value.slice();
147 | }
148 | else if(value.constructor.name !== 'Object') {
149 | dst[key] = value;
150 | }
151 | else {
152 | var dstValue = dst[key];
153 | if(!dstValue
154 | || typeof dstValue !== 'object'
155 | || Array.isArray(dstValue)) {
156 | dst[key] = dstValue = {};
157 | }
158 | ge._extend(dstValue, value);
159 | }
160 | }
161 | else {
162 | dst[key] = value;
163 | }
164 | }
165 | return dst;
166 | };
167 |
168 | /**
169 | * Extend an object.
170 | * @param {object} dst Object to extend.
171 | * @param {...object} src Source objects.
172 | * @returns {object} Extended object.
173 | */
174 | ge.extend = function extend(dst, src) { // eslint-disable-line no-unused-vars
175 | for(var i = 1; i < arguments.length; ++i) {
176 | dst = ge._extend(dst, arguments[i]);
177 | }
178 | return dst;
179 | };
180 |
181 | /*ge.debounceD3 = function debounceD3(func, delay) {
182 | var timeout = null;
183 |
184 | return function() {
185 | var context = this;
186 | var args = arguments;
187 | var d3ev = d3.event;
188 |
189 | if(timeout !== null) {
190 | clearTimeout(timeout);
191 | }
192 |
193 | timeout = setTimeout(
194 | function() {
195 | var tmp = d3.event;
196 | timeout = null;
197 | d3.event = d3ev;
198 | try {
199 | func.apply(context, args);
200 | }
201 | finally {
202 | d3.event = tmp;
203 | }
204 | },
205 | delay
206 | );
207 | };
208 | };*/
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 | Classes Events Namespaces
220 |
221 |
222 |
223 |
224 |
225 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
226 |
227 |
228 |
229 |
230 |
231 |
232 |
--------------------------------------------------------------------------------
/docs/js_util_container-size.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/util/container-size.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/util/container-size.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Container size constructor.
31 | * @class
32 | * @classdesc Point class.
33 | * @param {?number} [minWidth=1] Minimum width.
34 | * @param {?number} [minHeight=1] Minimum height.
35 | * @param {?number} [minArea=minWidth*minHeight] Minimum area.
36 | */
37 | ge.ContainerSize = function ContainerSize(minWidth, minHeight, minArea) {
38 | /**
39 | * Minimum width.
40 | * @member {number}
41 | */
42 | this.minWidth = +minWidth || 1;
43 | /**
44 | * Minimum height.
45 | * @member {number}
46 | */
47 | this.minHeight = +minHeight || 1;
48 | /**
49 | * Minimum area.
50 | * @member {number}
51 | */
52 | this.minArea = +minArea || this.minWidth * this.minHeight;
53 | };
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | Classes Events Namespaces
65 |
66 |
67 |
68 |
69 |
70 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/docs/js_util_point.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/util/point.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/util/point.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Point constructor.
31 | * @class
32 | * @classdesc Point class.
33 | * @param {?number} x X coordinate.
34 | * @param {?number} y Y coordinate.
35 | */
36 | ge.Point = function Point(x, y) {
37 | /**
38 | * X coordinate.
39 | * @member {number}
40 | */
41 | this.x = +x || 0;
42 | /**
43 | * Y coordinate.
44 | * @member {number}
45 | */
46 | this.y = +y || 0;
47 | };
48 |
49 | /**
50 | * Add.
51 | * @param {ge.Point} p
52 | * @returns {ge.Point}
53 | */
54 | ge.Point.prototype.add = function add(p) {
55 | this.x += p.x;
56 | this.y += p.y;
57 | return this;
58 | };
59 |
60 | /**
61 | * Subtract.
62 | * @param {ge.Point} p
63 | * @returns {ge.Point}
64 | */
65 | ge.Point.prototype.sub = function sub(p) {
66 | this.x -= p.x;
67 | this.y -= p.y;
68 | return this;
69 | };
70 |
71 | /**
72 | * Multiply.
73 | * @param {number} k
74 | * @returns {ge.Point}
75 | */
76 | ge.Point.prototype.mul = function mul(k) {
77 | this.x *= k;
78 | this.y *= k;
79 | return this;
80 | };
81 |
82 | /**
83 | * Return vector length.
84 | * @returns {number}
85 | */
86 | ge.Point.prototype.length = function length() {
87 | return Math.sqrt(this.x * this.x + this.y * this.y);
88 | };
89 |
90 | /**
91 | * Normalize.
92 | * @returns {ge.Point}
93 | */
94 | ge.Point.prototype.normalize = function normalize() {
95 | var l = this.length();
96 | if(l > 1e-8) {
97 | this.x /= l;
98 | this.y /= l;
99 | }
100 | return this;
101 | };
102 |
103 | /**
104 | * Rotate.
105 | * @param {ge.Angle} a
106 | * @returns {ge.Point}
107 | */
108 | ge.Point.prototype.rotate = function rotate(a) {
109 | var x = this.x, y = this.y;
110 | this.x = x * a.cos - y * a.sin;
111 | this.y = x * a.sin + y * a.cos;
112 | return this;
113 | };
114 |
115 | /**
116 | * Clone.
117 | * @returns {ge.Point}
118 | */
119 | ge.Point.prototype.clone = function clone() {
120 | return new ge.Point(this.x, this.y);
121 | };
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | Classes Events Namespaces
133 |
134 |
135 |
136 |
137 |
138 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
139 |
140 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/docs/js_util_save-load.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/util/save-load.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/util/save-load.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Class for saving/loading JSON.
31 | * @class
32 | * @param {?number} x X coordinate.
33 | * @param {?number} y Y coordinate.
34 | */
35 | ge.SaveLoad = function SaveLoad() {
36 | /**
37 | * Class constructors by name.
38 | * @member {object}
39 | */
40 | this.classes = {};
41 | };
42 |
43 | /**
44 | * Add a class.
45 | * @param {function} constructor Class constructor.
46 | * @param {string} [name=constructor.name] Class name.
47 | * @param {boolean} [overwrite=false] Overwrite if class exists.
48 | */
49 | ge.SaveLoad.prototype.addClass = function addClass(
50 | constructor,
51 | name,
52 | overwrite
53 | ) {
54 | name = name || constructor.name;
55 | if(this.classes[name] && !overwrite) {
56 | throw new Error('class "' + name +'" exists');
57 | }
58 | this.classes[name] = constructor;
59 | };
60 |
61 | /**
62 | * Get class by name.
63 | * @param {string} name Class name.
64 | * @returns {function} Class constructor.
65 | */
66 | ge.SaveLoad.prototype.getClass = function getClass(name) {
67 | var ret = this.classes[name];
68 | if(!ret) {
69 | throw new Error('class "' + name +'" not found');
70 | }
71 | return ret;
72 | };
73 |
74 | /**
75 | * Load from JSON.
76 | * @param {object} data JSON data.
77 | * @returns {object}
78 | */
79 | ge.SaveLoad.prototype.fromJson = function fromJson(data) {
80 | var cls = this.getClass(data['class']);
81 | return new cls(data);
82 | };
83 |
84 | /**
85 | * Save to JSON.
86 | * @param {object} obj Object to save.
87 | * @param {string} [name=obj.constructor.name] Class name.
88 | * @returns {object}
89 | */
90 | ge.SaveLoad.prototype.toJson = function toJson(obj, name) {
91 | var ret = obj.toJson();
92 | ret['class'] = name || obj.constructor.name;
93 | return ret;
94 | };
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | Classes Events Namespaces
106 |
107 |
108 |
109 |
110 |
111 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
112 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/docs/js_util_text-size.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: js/util/text-size.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: js/util/text-size.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Text size calculator constructor.
31 | * @class
32 | * @classdesc Text size calculator class.
33 | * @param {SVGElement} el SVG <text> element.
34 | */
35 | ge.TextSize = function TextSize(el) {
36 | el.setAttribute('style', 'stroke:none;fill:none');
37 | this.el = el;
38 | return this;
39 | };
40 |
41 | /**
42 | * Compute text width.
43 | * @param {string} text
44 | * @returns {number}
45 | */
46 | ge.TextSize.prototype.width = function length(text) {
47 | this.el.textContent = text;
48 | return this.el.getComputedTextLength();
49 | };
50 |
51 | /**
52 | * Compute text width and height.
53 | * @param {string} text
54 | * @returns {ge.Point}
55 | */
56 | ge.TextSize.prototype.size = function size(text) {
57 | this.el.textContent = text;
58 | var bbox = this.el.getBBox();
59 | return new ge.Point(bbox.width, bbox.height);
60 | };
61 |
62 | /**
63 | * Compute text container size.
64 | * @param {string} text
65 | * @returns {ge.ContainerSize}
66 | */
67 | ge.TextSize.prototype.containerSize = function containerSize(text) {
68 | var size = this.size(text);
69 | var words = text.split(/\s+/);
70 | var width;
71 | if(words.length == 1) {
72 | width = size.x;
73 | }
74 | else {
75 | width = 0;
76 | var self = this;
77 | words.forEach(function(word) {
78 | width = Math.max(width, self.width(word));
79 | });
80 | }
81 | return new ge.ContainerSize(
82 | width,
83 | size.y,
84 | size.x * size.y
85 | );
86 | };
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | Classes Events Namespaces
98 |
99 |
100 |
101 |
102 |
103 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/docs/main.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: main.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: main.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 'use strict';
30 |
31 | /* eslint-disable no-unused-vars */
32 |
33 | /**
34 | * Graph editor module.
35 | * @namespace
36 | */
37 | var ge = exports;
38 |
39 | /**
40 | * Graph editor constructor.
41 | * @class
42 | * @classdesc Graph editor class.
43 | * @param {(SVGElement|Selector|D3Selection)} svg SVG element.
44 | * @param {ImportGraphData} data Graph data.
45 | * @param {GraphOptions} options Graph options.
46 | */
47 | ge.GraphEditor = function GraphEditor(svg, data, options) {
48 | if(!svg.select) {
49 | svg = d3.select(svg);
50 | }
51 |
52 | this.initOptions(options, svg)
53 | .initSvg(svg)
54 | .initData(data)
55 | .initState();
56 |
57 | /**
58 | * @readonly
59 | * @member {D3Dispatch}
60 | */
61 | this.dispatch = d3.dispatch(
62 | 'node-click', 'link-click',
63 | 'new-link-start', 'new-link-end', 'new-link-cancel',
64 | 'click',
65 | 'simulation-start', 'simulation-stop'
66 | );
67 |
68 | /**
69 | * Graph bounding box.
70 | * @readonly
71 | * @member {BBox}
72 | */
73 | this.bbox = [[0, 0], [0, 0]];
74 | /**
75 | * Graph zoom behavior.
76 | * @readonly
77 | * @member {D3Zoom}
78 | */
79 | this.zoom = this.zoomEvents(d3.zoom())
80 | .scaleExtent([this.options.scale.min, this.options.scale.max]);
81 | /**
82 | * Node drag behavior.
83 | * @readonly
84 | * @member {D3Drag}
85 | */
86 | this.drag = this.dragEvents(d3.drag());
87 | this.svg.call(this.zoom);
88 |
89 | /**
90 | * Window resize event handler.
91 | * @readonly
92 | * @member {function}
93 | */
94 | this.onresize = ge.bind(this, this.resized);
95 | window.addEventListener('resize', this.onresize);
96 |
97 | this.updateBBox();
98 |
99 | setTimeout(ge.bind(this, function() {
100 | this.update()
101 | .resized()
102 | .simulation(this.options.simulation.start);
103 | }), 10);
104 |
105 | return this;
106 | };
107 |
108 | /* eslint-enable no-unused-vars */
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | Classes Events Namespaces
120 |
121 |
122 |
123 |
124 |
125 | Documentation generated by JSDoc 3.5.5 on Sat Jan 19 2019 08:35:36 GMT+0000 (UTC)
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/docs/scripts/linenumber.js:
--------------------------------------------------------------------------------
1 | /*global document */
2 | (function() {
3 | var source = document.getElementsByClassName('prettyprint source linenums');
4 | var i = 0;
5 | var lineNumber = 0;
6 | var lineId;
7 | var lines;
8 | var totalLines;
9 | var anchorHash;
10 |
11 | if (source && source[0]) {
12 | anchorHash = document.location.hash.substring(1);
13 | lines = source[0].getElementsByTagName('li');
14 | totalLines = lines.length;
15 |
16 | for (; i < totalLines; i++) {
17 | lineNumber++;
18 | lineId = 'line' + lineNumber;
19 | lines[i].id = lineId;
20 | if (lineId === anchorHash) {
21 | lines[i].className += ' selected';
22 | }
23 | }
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/docs/scripts/prettify/lang-css.js:
--------------------------------------------------------------------------------
1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
3 |
--------------------------------------------------------------------------------
/docs/styles/jsdoc-default.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Open Sans';
3 | font-weight: normal;
4 | font-style: normal;
5 | src: url('../fonts/OpenSans-Regular-webfont.eot');
6 | src:
7 | local('Open Sans'),
8 | local('OpenSans'),
9 | url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
10 | url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
11 | url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
12 | }
13 |
14 | @font-face {
15 | font-family: 'Open Sans Light';
16 | font-weight: normal;
17 | font-style: normal;
18 | src: url('../fonts/OpenSans-Light-webfont.eot');
19 | src:
20 | local('Open Sans Light'),
21 | local('OpenSans Light'),
22 | url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
23 | url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
24 | url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
25 | }
26 |
27 | html
28 | {
29 | overflow: auto;
30 | background-color: #fff;
31 | font-size: 14px;
32 | }
33 |
34 | body
35 | {
36 | font-family: 'Open Sans', sans-serif;
37 | line-height: 1.5;
38 | color: #4d4e53;
39 | background-color: white;
40 | }
41 |
42 | a, a:visited, a:active {
43 | color: #0095dd;
44 | text-decoration: none;
45 | }
46 |
47 | a:hover {
48 | text-decoration: underline;
49 | }
50 |
51 | header
52 | {
53 | display: block;
54 | padding: 0px 4px;
55 | }
56 |
57 | tt, code, kbd, samp {
58 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
59 | }
60 |
61 | .class-description {
62 | font-size: 130%;
63 | line-height: 140%;
64 | margin-bottom: 1em;
65 | margin-top: 1em;
66 | }
67 |
68 | .class-description:empty {
69 | margin: 0;
70 | }
71 |
72 | #main {
73 | float: left;
74 | width: 70%;
75 | }
76 |
77 | article dl {
78 | margin-bottom: 40px;
79 | }
80 |
81 | article img {
82 | max-width: 100%;
83 | }
84 |
85 | section
86 | {
87 | display: block;
88 | background-color: #fff;
89 | padding: 12px 24px;
90 | border-bottom: 1px solid #ccc;
91 | margin-right: 30px;
92 | }
93 |
94 | .variation {
95 | display: none;
96 | }
97 |
98 | .signature-attributes {
99 | font-size: 60%;
100 | color: #aaa;
101 | font-style: italic;
102 | font-weight: lighter;
103 | }
104 |
105 | nav
106 | {
107 | display: block;
108 | float: right;
109 | margin-top: 28px;
110 | width: 30%;
111 | box-sizing: border-box;
112 | border-left: 1px solid #ccc;
113 | padding-left: 16px;
114 | }
115 |
116 | nav ul {
117 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
118 | font-size: 100%;
119 | line-height: 17px;
120 | padding: 0;
121 | margin: 0;
122 | list-style-type: none;
123 | }
124 |
125 | nav ul a, nav ul a:visited, nav ul a:active {
126 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
127 | line-height: 18px;
128 | color: #4D4E53;
129 | }
130 |
131 | nav h3 {
132 | margin-top: 12px;
133 | }
134 |
135 | nav li {
136 | margin-top: 6px;
137 | }
138 |
139 | footer {
140 | display: block;
141 | padding: 6px;
142 | margin-top: 12px;
143 | font-style: italic;
144 | font-size: 90%;
145 | }
146 |
147 | h1, h2, h3, h4 {
148 | font-weight: 200;
149 | margin: 0;
150 | }
151 |
152 | h1
153 | {
154 | font-family: 'Open Sans Light', sans-serif;
155 | font-size: 48px;
156 | letter-spacing: -2px;
157 | margin: 12px 24px 20px;
158 | }
159 |
160 | h2, h3.subsection-title
161 | {
162 | font-size: 30px;
163 | font-weight: 700;
164 | letter-spacing: -1px;
165 | margin-bottom: 12px;
166 | }
167 |
168 | h3
169 | {
170 | font-size: 24px;
171 | letter-spacing: -0.5px;
172 | margin-bottom: 12px;
173 | }
174 |
175 | h4
176 | {
177 | font-size: 18px;
178 | letter-spacing: -0.33px;
179 | margin-bottom: 12px;
180 | color: #4d4e53;
181 | }
182 |
183 | h5, .container-overview .subsection-title
184 | {
185 | font-size: 120%;
186 | font-weight: bold;
187 | letter-spacing: -0.01em;
188 | margin: 8px 0 3px 0;
189 | }
190 |
191 | h6
192 | {
193 | font-size: 100%;
194 | letter-spacing: -0.01em;
195 | margin: 6px 0 3px 0;
196 | font-style: italic;
197 | }
198 |
199 | table
200 | {
201 | border-spacing: 0;
202 | border: 0;
203 | border-collapse: collapse;
204 | }
205 |
206 | td, th
207 | {
208 | border: 1px solid #ddd;
209 | margin: 0px;
210 | text-align: left;
211 | vertical-align: top;
212 | padding: 4px 6px;
213 | display: table-cell;
214 | }
215 |
216 | thead tr
217 | {
218 | background-color: #ddd;
219 | font-weight: bold;
220 | }
221 |
222 | th { border-right: 1px solid #aaa; }
223 | tr > th:last-child { border-right: 1px solid #ddd; }
224 |
225 | .ancestors, .attribs { color: #999; }
226 | .ancestors a, .attribs a
227 | {
228 | color: #999 !important;
229 | text-decoration: none;
230 | }
231 |
232 | .clear
233 | {
234 | clear: both;
235 | }
236 |
237 | .important
238 | {
239 | font-weight: bold;
240 | color: #950B02;
241 | }
242 |
243 | .yes-def {
244 | text-indent: -1000px;
245 | }
246 |
247 | .type-signature {
248 | color: #aaa;
249 | }
250 |
251 | .name, .signature {
252 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
253 | }
254 |
255 | .details { margin-top: 14px; border-left: 2px solid #DDD; }
256 | .details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
257 | .details dd { margin-left: 70px; }
258 | .details ul { margin: 0; }
259 | .details ul { list-style-type: none; }
260 | .details li { margin-left: 30px; padding-top: 6px; }
261 | .details pre.prettyprint { margin: 0 }
262 | .details .object-value { padding-top: 0; }
263 |
264 | .description {
265 | margin-bottom: 1em;
266 | margin-top: 1em;
267 | }
268 |
269 | .code-caption
270 | {
271 | font-style: italic;
272 | font-size: 107%;
273 | margin: 0;
274 | }
275 |
276 | .prettyprint
277 | {
278 | border: 1px solid #ddd;
279 | width: 80%;
280 | overflow: auto;
281 | }
282 |
283 | .prettyprint.source {
284 | width: inherit;
285 | }
286 |
287 | .prettyprint code
288 | {
289 | font-size: 100%;
290 | line-height: 18px;
291 | display: block;
292 | padding: 4px 12px;
293 | margin: 0;
294 | background-color: #fff;
295 | color: #4D4E53;
296 | }
297 |
298 | .prettyprint code span.line
299 | {
300 | display: inline-block;
301 | }
302 |
303 | .prettyprint.linenums
304 | {
305 | padding-left: 70px;
306 | -webkit-user-select: none;
307 | -moz-user-select: none;
308 | -ms-user-select: none;
309 | user-select: none;
310 | }
311 |
312 | .prettyprint.linenums ol
313 | {
314 | padding-left: 0;
315 | }
316 |
317 | .prettyprint.linenums li
318 | {
319 | border-left: 3px #ddd solid;
320 | }
321 |
322 | .prettyprint.linenums li.selected,
323 | .prettyprint.linenums li.selected *
324 | {
325 | background-color: lightyellow;
326 | }
327 |
328 | .prettyprint.linenums li *
329 | {
330 | -webkit-user-select: text;
331 | -moz-user-select: text;
332 | -ms-user-select: text;
333 | user-select: text;
334 | }
335 |
336 | .params .name, .props .name, .name code {
337 | color: #4D4E53;
338 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
339 | font-size: 100%;
340 | }
341 |
342 | .params td.description > p:first-child,
343 | .props td.description > p:first-child
344 | {
345 | margin-top: 0;
346 | padding-top: 0;
347 | }
348 |
349 | .params td.description > p:last-child,
350 | .props td.description > p:last-child
351 | {
352 | margin-bottom: 0;
353 | padding-bottom: 0;
354 | }
355 |
356 | .disabled {
357 | color: #454545;
358 | }
359 |
--------------------------------------------------------------------------------
/docs/styles/prettify-jsdoc.css:
--------------------------------------------------------------------------------
1 | /* JSDoc prettify.js theme */
2 |
3 | /* plain text */
4 | .pln {
5 | color: #000000;
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | /* string content */
11 | .str {
12 | color: #006400;
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
17 | /* a keyword */
18 | .kwd {
19 | color: #000000;
20 | font-weight: bold;
21 | font-style: normal;
22 | }
23 |
24 | /* a comment */
25 | .com {
26 | font-weight: normal;
27 | font-style: italic;
28 | }
29 |
30 | /* a type name */
31 | .typ {
32 | color: #000000;
33 | font-weight: normal;
34 | font-style: normal;
35 | }
36 |
37 | /* a literal value */
38 | .lit {
39 | color: #006400;
40 | font-weight: normal;
41 | font-style: normal;
42 | }
43 |
44 | /* punctuation */
45 | .pun {
46 | color: #000000;
47 | font-weight: bold;
48 | font-style: normal;
49 | }
50 |
51 | /* lisp open bracket */
52 | .opn {
53 | color: #000000;
54 | font-weight: bold;
55 | font-style: normal;
56 | }
57 |
58 | /* lisp close bracket */
59 | .clo {
60 | color: #000000;
61 | font-weight: bold;
62 | font-style: normal;
63 | }
64 |
65 | /* a markup tag name */
66 | .tag {
67 | color: #006400;
68 | font-weight: normal;
69 | font-style: normal;
70 | }
71 |
72 | /* a markup attribute name */
73 | .atn {
74 | color: #006400;
75 | font-weight: normal;
76 | font-style: normal;
77 | }
78 |
79 | /* a markup attribute value */
80 | .atv {
81 | color: #006400;
82 | font-weight: normal;
83 | font-style: normal;
84 | }
85 |
86 | /* a declaration */
87 | .dec {
88 | color: #000000;
89 | font-weight: bold;
90 | font-style: normal;
91 | }
92 |
93 | /* a variable name */
94 | .var {
95 | color: #000000;
96 | font-weight: normal;
97 | font-style: normal;
98 | }
99 |
100 | /* a function name */
101 | .fun {
102 | color: #000000;
103 | font-weight: bold;
104 | font-style: normal;
105 | }
106 |
107 | /* Specify class=linenums on a pre to get line numbering */
108 | ol.linenums {
109 | margin-top: 0;
110 | margin-bottom: 0;
111 | }
112 |
--------------------------------------------------------------------------------
/docs/styles/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/examples/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | width: 100%;
5 | height: 100%;
6 | display: flex;
7 | flex-direction: row;
8 | color: #ffffff;
9 | background-color: #222222;
10 | }
11 |
12 | h4, h5 {
13 | text-align: center;
14 | margin: 0;
15 | width: 100%;
16 | }
17 |
18 | h5 {
19 | margin-top: 10px;
20 | }
21 |
22 | #graph-container {
23 | margin: 1em;
24 | padding: 0;
25 | border: 1px solid #ffffff;
26 | display: flex;
27 | flex-grow: 1;
28 | }
29 |
30 | #sidebar {
31 | margin: 1em;
32 | padding: 1em;
33 | border: 1px solid #ffffff;
34 | display: flex;
35 | flex-direction: column;
36 | width: 300px;
37 | overflow-y: auto;
38 | overflow-x: hidden;
39 | }
40 |
41 | #graph {
42 | width: 100%;
43 | height: 100%;
44 | }
45 |
46 | @media(max-width: 600px) {
47 | body {
48 | flex-direction: column;
49 | }
50 | #graph-container {
51 | order: 2;
52 | height: 100%;
53 | }
54 | #sidebar {
55 | order: 1;
56 | width: auto;
57 | height: auto;
58 | }
59 | }
60 |
61 | .hide {
62 | display: none !important;
63 | }
64 |
65 | .form {
66 | display: flex;
67 | flex-direction: column;
68 | min-height: 100px;
69 | }
70 |
71 | .form > div {
72 | display: flex;
73 | flex-direction: row;
74 | align-items: center;
75 | }
76 |
77 | .form > div > * {
78 | display: flex;
79 | margin: 4px;
80 | }
81 |
82 | .form > div > label {
83 | min-width: 50px;
84 | }
85 |
86 | .form > div > input, .form > div > select {
87 | flex-grow: 1;
88 | flex-shrink: 1;
89 | }
90 |
91 | input, select {
92 | background: #000000;
93 | border: 1px solid #ffffff;
94 | color: #ffffff;
95 | padding: 4px;
96 | }
97 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
48 |
49 |
50 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/jsdoc.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | plugins: [],
5 | recurseDepth: 10,
6 | source: {
7 | include: './src/',
8 | exclude: ['./src/umd/umd-start.js', './src/umd/umd-end.js'],
9 | includePattern: '.+\\.js(doc|x)?$',
10 | excludePattern: '(^|\\/|\\\\)_'
11 | },
12 | sourceType: 'module',
13 | tags: {
14 | allowUnknownTags: true,
15 | dictionaries: ['jsdoc', 'closure']
16 | },
17 | templates: {
18 | cleverLinks: true,
19 | //monospaceLinks: false,
20 | },
21 | opts: {
22 | template: 'templates/default',
23 | encoding: 'utf8',
24 | destination: './docs/',
25 | recurse: true,
26 | 'private': true,
27 | 'package': './package.json',
28 | verbose: true,
29 | readme: './README.md',
30 | //tutorials: 'path/to/tutorials',
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = (config) => {
2 | const packageJson = require('./package.json');
3 | const path = require('path');
4 |
5 | let files = [
6 | require.resolve('jquery'),
7 | path.join(path.dirname(require.resolve('d3')), 'd3.js')
8 | ];
9 |
10 | if(packageJson.dependencies.jquery !== undefined) {
11 | files.push({
12 | pattern: require.resolve('jasmine-jquery'),
13 | watched: false,
14 | served: true
15 | });
16 | }
17 |
18 | if(process.env.TEST_MIN_BUNDLE) {
19 | files.push(path.join(__dirname, 'dist/js/graph-editor.min.js'));
20 | }
21 | else if(process.env.TEST_BUNDLE) {
22 | files.push(path.join(__dirname, './dist/js/graph-editor.js'));
23 | }
24 | else {
25 | files.push.apply(files, [
26 | path.join(__dirname, 'tests/test-start.js'),
27 | path.join(__dirname, 'src/main.js'),
28 | path.join(__dirname, 'src/js/**/*.js')
29 | ]);
30 | }
31 |
32 | files.push(path.join(__dirname, 'tests/**/*.test.js'));
33 |
34 | let browsers = process.env.TEST_BROWSERS;
35 | if(browsers) {
36 | browsers = browsers.split(/\s+/);
37 | }
38 | if(!(browsers && browsers[0])) {
39 | browsers = ['Chromium'];
40 | }
41 |
42 | config.set({
43 | basePath: '../',
44 |
45 | files: files,
46 |
47 | frameworks: ['jasmine'],
48 |
49 | reporters: ['spec'],
50 | specReporter: {
51 | maxLogLines: 5,
52 | suppressErrorSummary: true,
53 | suppressFailed: false,
54 | suppressPassed: false,
55 | suppressSkipped: true,
56 | showSpecTiming: false,
57 | failFast: false
58 | },
59 |
60 | port: 9876,
61 |
62 | colors: true,
63 | logLevel: config.LOG_INFO,
64 |
65 | autoWatch: true,
66 | singleRun: false,
67 |
68 | browsers: browsers
69 | });
70 | };
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graph-editor",
3 | "version": "0.1.1",
4 | "author": "dead-beef",
5 | "contributors": [],
6 | "license": "MIT",
7 | "private": false,
8 | "description": "Graph editor frontend component",
9 | "keywords": [
10 | "dom",
11 | "svg",
12 | "graph",
13 | "editor"
14 | ],
15 | "main": "dist/js/graph-editor.js",
16 | "files": [
17 | "dist/"
18 | ],
19 | "homepage": "https://dead-beef.github.io/graph-editor",
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/dead-beef/graph-editor"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/dead-beef/graph-editor/issues"
26 | },
27 | "engines" : {
28 | "node" : ">=6.11.4"
29 | },
30 | "dependencies": {
31 | "d3": "^5.7.0"
32 | },
33 | "devDependencies": {
34 | "autoprefixer": "^7.2.5",
35 | "chokidar-cli": "^1.2.0",
36 | "csso-cli": "^1.0.0",
37 | "eslint": "^4.15.0",
38 | "eslint-plugin-jasmine": "^2.10.1",
39 | "http-server": "^0.11.1",
40 | "jasmine-core": "^2.9.0",
41 | "jasmine-jquery": "^2.1.1",
42 | "jasmine-spec-reporter": "^4.1.1",
43 | "jsdoc": "^3.5.5",
44 | "karma": "^2.0.0",
45 | "karma-chrome-launcher": "^2.2.0",
46 | "karma-firefox-launcher": "^1.1.0",
47 | "karma-jasmine": "^1.1.1",
48 | "karma-sourcemap-loader": "^0.3.7",
49 | "karma-spec-reporter": "0.0.32",
50 | "node-sass": "^4.7.2",
51 | "postcss-cli": "^4.1.1",
52 | "sass-makedepend": "^0.2.1",
53 | "source-map-concat-cli": "^1.0.0",
54 | "uglify-js": "^3.3.8"
55 | },
56 | "scripts": {
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/css/graph-editor-dark.scss:
--------------------------------------------------------------------------------
1 | $ge-node-fg: #888 !default;
2 | $ge-node-bg: #111 !default;
3 | $ge-node-hover-fg: #fff !default;
4 | $ge-node-hover-bg: #333 !default;
5 |
6 | $ge-link-fg: #888 !default;
7 | $ge-link-hover-fg: #fff !default;
8 |
9 | $ge-dragline-fg: #a44 !default;
10 |
11 | $ge-selection-fg: #aae !default;
12 | $ge-selected-link-fg: #ada !default;
13 | $ge-selected-node-bg: #111 !default;
14 |
15 | $ge-unselected-fg: $ge-node-fg !default;
16 | $ge-unselected-bg: $ge-node-bg !default;
17 |
18 | @import "graph-editor";
19 |
--------------------------------------------------------------------------------
/src/css/graph-editor.scss:
--------------------------------------------------------------------------------
1 | $ge-graph-transition-duration: 0.3s !default;
2 | $ge-graph-transition-properties: stroke, fill, color;
3 | /*$ge-graph-transition: stroke $ge-graph-transition-duration,
4 | fill $ge-graph-transition-duration,
5 | color $ge-graph-transition-duration !default;*/
6 |
7 | $ge-node-font-size: 16px !default;
8 | $ge-node-border: 2px !default;
9 |
10 | $ge-node-fg: #000 !default;
11 | $ge-node-bg: #ddd !default;
12 | $ge-node-hover-fg: #000 !default;
13 | $ge-node-hover-bg: #aee !default;
14 |
15 | $ge-link-font-size: 14px !default;
16 | $ge-link-size: 2px !default;
17 |
18 | $ge-link-fg: #000 !default;
19 | $ge-link-hover-fg: #4aa !default;
20 |
21 | $ge-dragline-fg: #a44 !default;
22 | $ge-dragline-size: 2px;
23 |
24 | $ge-selection-fg: #44a !default;
25 | $ge-selected-link-fg: #488 !default;
26 | $ge-selected-node-fg: $ge-selection-fg !default;
27 | $ge-selected-node-bg: #aae !default;
28 |
29 | $ge-unselected-fg: #666 !default;
30 | $ge-unselected-bg: #eee !default;
31 |
32 | $ge-unselected-link-hover-fg: mix($ge-unselected-fg, $ge-link-hover-fg) !default;
33 | $ge-unselected-node-hover-fg: mix($ge-selected-node-fg, $ge-node-hover-fg) !default;
34 | $ge-unselected-node-hover-bg: mix($ge-selected-node-bg, $ge-node-hover-bg) !default;
35 | $ge-selected-link-hover-fg: mix($ge-selection-fg, $ge-link-hover-fg) !default;
36 | $ge-selected-node-hover-fg: mix($ge-selected-node-fg, $ge-node-hover-fg) !default;
37 | $ge-selected-node-hover-bg: mix($ge-selected-node-bg, $ge-node-hover-bg) !default;
38 |
39 |
40 | .ge-graph {
41 | * {
42 | /*transition: $ge-graph-transition;*/
43 | transition-property: $ge-graph-transition-properties;
44 | transition-duration: $ge-graph-transition-duration;
45 | }
46 |
47 | marker {
48 | ge-dragline-end, ge-dragline-start {
49 | path, circle {
50 | stroke: $ge-dragline-fg;
51 | fill: $ge-dragline-fg;
52 | }
53 | }
54 | }
55 |
56 | .ge-hidden {
57 | display: none;
58 | }
59 |
60 | .ge-dragline {
61 | pointer-events: none;
62 | stroke-width: $ge-dragline-size;
63 | stroke: $ge-dragline-fg;
64 | }
65 |
66 | .ge-link {
67 | cursor: pointer;
68 |
69 | font-size: $ge-link-font-size;
70 | stroke-width: $ge-link-size;
71 |
72 | /* * { vector-effect: non-scaling-stroke } */
73 |
74 | path {
75 | stroke: $ge-link-fg;
76 | fill: none;
77 | }
78 |
79 | text {
80 | fill: $ge-link-fg;
81 | }
82 |
83 | &:hover {
84 | path {
85 | stroke: $ge-link-hover-fg;
86 | }
87 | text {
88 | fill: $ge-link-hover-fg;
89 | }
90 | }
91 | }
92 |
93 | .ge-node {
94 | cursor: pointer;
95 |
96 | /* * { vector-effect: non-scaling-stroke } */
97 |
98 | path {
99 | stroke-width: $ge-node-border;
100 | stroke: $ge-node-fg;
101 | fill: $ge-node-bg;
102 | }
103 |
104 | div {
105 | display: flex;
106 | flex-direction: row;
107 | align-items: center;
108 |
109 | width: 100%;
110 | height: 100%;
111 |
112 | span {
113 | display: flex;
114 | flex-direction: column;
115 | flex-grow: 1;
116 | align-items: center;
117 | text-align: center;
118 |
119 | color: $ge-node-fg;
120 | font-size: $ge-node-font-size;
121 | word-wrap: break-word;
122 | }
123 | }
124 |
125 | &:hover {
126 | path {
127 | stroke: $ge-node-hover-fg;
128 | fill: $ge-node-hover-bg;
129 | }
130 | div {
131 | span {
132 | color: $ge-node-hover-fg;
133 | }
134 | }
135 | }
136 | }
137 |
138 | &.ge-selection {
139 | .ge-link {
140 | path { stroke: $ge-unselected-fg; }
141 | text { fill: $ge-unselected-fg; }
142 |
143 | &:hover {
144 | path { stroke: $ge-unselected-link-hover-fg; }
145 | text { fill: $ge-unselected-link-hover-fg; }
146 | }
147 |
148 | &.ge-selection {
149 | path { stroke: $ge-selection-fg; }
150 | text { fill: $ge-selection-fg; }
151 |
152 | &:hover {
153 | path { stroke: $ge-selected-link-hover-fg; }
154 | text { fill: $ge-selected-link-hover-fg; }
155 | }
156 | }
157 | }
158 |
159 | .ge-node {
160 | path {
161 | stroke: $ge-unselected-fg;
162 | fill: $ge-unselected-bg;
163 | }
164 | span { color: $ge-unselected-fg; }
165 |
166 | &:hover {
167 | path {
168 | stroke: $ge-unselected-node-hover-fg;
169 | fill: $ge-unselected-node-hover-bg;
170 | }
171 | span { color: $ge-unselected-node-hover-fg; }
172 | }
173 |
174 | &.ge-selection {
175 | path {
176 | stroke: $ge-selected-node-fg;
177 | fill: $ge-selected-node-bg;
178 | }
179 | span { color: $ge-selected-node-fg; }
180 |
181 | &:hover {
182 | path {
183 | stroke: $ge-selected-node-hover-fg;
184 | fill: $ge-selected-node-hover-bg;
185 | }
186 | span { color: $ge-selected-node-hover-fg; }
187 | }
188 | }
189 | }
190 | }
191 |
192 | &.ge-selection, &.ge-link-selection {
193 | .ge-link {
194 | &.ge-link-selection, &.ge-selection.ge-link-selection {
195 | path { stroke: $ge-selected-link-fg; }
196 | text { fill: $ge-selected-link-fg; }
197 |
198 | &:hover {
199 | path { stroke: $ge-selected-link-hover-fg; }
200 | text { fill: $ge-selected-link-hover-fg; }
201 | }
202 | }
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/events.jsdoc:
--------------------------------------------------------------------------------
1 | /**
2 | * Graph SVG click/touch event.
3 | * @event click
4 | * @this ge.GraphEditor
5 | * @param {Point} pos Click/touch position.
6 | */
7 |
8 | /**
9 | * Node click/touch event.
10 | * @event node-click
11 | * @this ge.GraphEditor
12 | * @param {Node} node Clicked/touched node.
13 | */
14 |
15 | /**
16 | * Link click/touch event.
17 | * @event link-click
18 | * @this ge.GraphEditor
19 | * @param {Link} link Clicked/touched link.
20 | */
21 |
22 | /**
23 | * New link start event.
24 | * @event new-link-start
25 | * @this ge.GraphEditor
26 | * @param {Node} source New link source.
27 | */
28 |
29 | /**
30 | * New link end event.
31 | * @event new-link-end
32 | * @this ge.GraphEditor
33 | * @param {Node} source New link source.
34 | * @param {Node} target New link target.
35 | */
36 |
37 | /**
38 | * New link cancel event.
39 | * @event new-link-cancel
40 | * @this ge.GraphEditor
41 | * @param {Node} source Cancelled link source.
42 | */
43 |
44 | /**
45 | * Simulation start event.
46 | * @event simulation-start
47 | * @this ge.GraphEditor
48 | */
49 |
50 | /**
51 | * Simulation stop event.
52 | * @event simulation-stop
53 | * @this ge.GraphEditor
54 | */
55 |
--------------------------------------------------------------------------------
/src/js/events.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Add an event handler.
5 | * @param {string} event
6 | * @param {?function} handler
7 | * @returns {ge.GraphEditor}
8 | * @see [d3.dispatch.on]{@link https://github.com/d3/d3-dispatch#dispatch_on}
9 | */
10 | ge.GraphEditor.prototype.on = function on(event, handler) {
11 | this.dispatch.on(event, handler);
12 | return this;
13 | };
14 |
15 | /**
16 | * Get mouse or touch coordinates relative to container.
17 | * @private
18 | * @param {(HTMLElement|SVGElement)} [container=this.container.node()]
19 | * @returns {Point}
20 | * @see [d3.mouse]{@link https://github.com/d3/d3-selection/blob/master/README.md#mouse}
21 | * @see [d3.touch]{@link https://github.com/d3/d3-selection/blob/master/README.md#touch}
22 | */
23 | ge.GraphEditor.prototype.clickPosition = function clickPosition(container) {
24 | container = container || this.container.node();
25 | return d3.touch(container) || d3.mouse(container);
26 | };
27 |
28 | /**
29 | * Get touched node.
30 | * @private
31 | * @returns {?Node}
32 | * @see [d3.event]{@link https://github.com/d3/d3-selection/blob/master/README.md#event}
33 | * @see [document.elementFromPoint]{@link https://developer.mozilla.org/en-US/docs/Web/API/Document/elementFromPoint}
34 | */
35 | ge.GraphEditor.prototype.touchedNode = function touchedNode() {
36 | var x = d3.event.touches[0].pageX;
37 | var y = d3.event.touches[0].pageY;
38 | var el = document.elementFromPoint(x, y);
39 | var nodeClass = this.options.css.node;
40 | while(true) {
41 | if(!el) {
42 | return null;
43 | }
44 | switch(el.tagName.toLowerCase()) {
45 | case 'svg':
46 | case 'body':
47 | return null;
48 | case 'g':
49 | if(el.classList.contains(nodeClass)) {
50 | return d3.select(el).datum();
51 | }
52 | }
53 | el = el.parentNode;
54 | }
55 | };
56 |
57 | /**
58 | * Set node drag event handlers.
59 | * @private
60 | * @param {D3Drag} drag Node drag behavior.
61 | * @fires node-click
62 | * @fires new-link-start
63 | * @fires new-link-end
64 | * @fires new-link-cancel
65 | * @returns {D3Drag}
66 | */
67 | ge.GraphEditor.prototype.dragEvents = function dragEvents(drag) {
68 | var self = this;
69 |
70 | return drag
71 | .on('start', function dragStart(d) {
72 | self.state.dragged = false;
73 | self.state.dragPos = null;
74 | d3.select(this).raise();
75 |
76 | if(self.state.simulationStarted && !self.state.dragToLink) {
77 | d.fx = d.x;
78 | d.fy = d.y;
79 | self.simulation('restart');
80 | }
81 | })
82 | .on('drag', function drag(d) {
83 | if(self.state.dragToLink) {
84 | var mouse = self.clickPosition();
85 | var path = ''.concat(
86 | 'M', d.x, ',', d.y,
87 | 'L', mouse[0], ',', mouse[1]
88 | );
89 |
90 | if(!self.state.dragged) {
91 | self.state.dragged = true;
92 | self.dragLine
93 | .classed(self.options.css.hide, false)
94 | .attr('d', path);
95 | self.dispatch.call('new-link-start', self, d);
96 | }
97 | else {
98 | self.transition(self.dragLine, 'drag')
99 | .attr('d', path);
100 | }
101 | }
102 | else {
103 | self.state.dragged = true;
104 | d.x += d3.event.dx;
105 | d.y += d3.event.dy;
106 | if(self.state.simulationStarted) {
107 | d.fx = d.x;
108 | d.fy = d.y;
109 | self.simulation('restart');
110 | }
111 | self.updateNode(this);
112 | }
113 | })
114 | .on('end', function dragEnd(d) {
115 | d.fx = null;
116 | d.fy = null;
117 |
118 | self.state.dragPos = self.clickPosition();
119 |
120 | if(self.state.dragged) {
121 | self.state.dragged = false;
122 | if(self.state.dragToLink) {
123 | self.dragLine.classed(self.options.css.hide, true);
124 | if(self.state.newLinkTarget) {
125 | var target = self.state.newLinkTarget;
126 | self.state.newLinkTarget = null;
127 | self.dispatch.call(
128 | 'new-link-end',
129 | self, d, target
130 | );
131 | }
132 | else {
133 | self.dispatch.call('new-link-cancel', self, d);
134 | }
135 | }
136 | else if(self.state.simulationStarted) {
137 | self.simulation('restart');
138 | }
139 | }
140 | else {
141 | self.dispatch.call('node-click', self, d);
142 | }
143 | });
144 | };
145 |
146 | /**
147 | * Set zoom event handlers.
148 | * @private
149 | * @param {D3Zoom} zoom Graph zoom behavior.
150 | * @fires click
151 | * @returns {D3Zoom}
152 | */
153 | ge.GraphEditor.prototype.zoomEvents = function zoomEvents(zoom) {
154 | var self = this;
155 | //var prevScale = 1;
156 |
157 | return zoom
158 | .duration(self.options.transition.zoom)
159 | .on('end', function zoomEnd() {
160 | if(!self.state.zoomed) {
161 | var pos = self.clickPosition();
162 | if(!ge.equal(self.state.dragPos, pos)) {
163 | self.dispatch.call('click', self, pos);
164 | }
165 | else {
166 | self.state.dragPos = null;
167 | }
168 | }
169 | self.state.zoomed = false;
170 | })
171 | .on('zoom', function zoom() {
172 | self.state.zoomed = true;
173 |
174 | /*var scale = d3.event.transform.k;
175 |
176 | if(Math.abs(scale - prevScale) > 0.001)
177 | {
178 | prevScale = scale;
179 |
180 | if(scale > graph.options.sizeScaleMax)
181 | {
182 | self.state.sizeScale = graph.options.sizeScaleMax / scale;
183 | }
184 | else if(scale < graph.options.sizeScaleMin)
185 | {
186 | self.state.sizeScale = graph.options.sizeScaleMin / scale;
187 | }
188 | else
189 | {
190 | self.state.sizeScale = 1;
191 | }
192 |
193 | self.updateSize();
194 | }*/
195 |
196 | var type;
197 |
198 | if(ge.equal(d3.event.transform.k, self.state.zoom)) {
199 | type = 'drag';
200 | }
201 | else {
202 | type = 'zoom';
203 | self.state.zoom = d3.event.transform.k;
204 | }
205 |
206 | self.transition(self.container, type, 'zoom')
207 | .attr(
208 | 'transform',
209 | d3.event.transform.toString()
210 | );
211 | });
212 | };
213 |
214 | /**
215 | * Set link event handlers.
216 | * @private
217 | * @param {D3Selection} links
218 | * @fires link-click
219 | * @returns {D3Selection}
220 | */
221 | ge.GraphEditor.prototype.linkEvents = function linkEvents(links) {
222 | var self = this;
223 |
224 | return links
225 | .on('mousedown', function(/*d*/) {
226 | d3.event.stopPropagation();
227 | d3.event.preventDefault();
228 | })
229 | .on('touchstart', function(/*d*/) {
230 | self.state.dragPos = self.clickPosition();
231 | })
232 | .on('mouseup', function(d) {
233 | d3.event.stopPropagation();
234 | d3.event.preventDefault();
235 | self.dispatch.call('link-click', self, d);
236 | });
237 | };
238 |
239 |
240 | /**
241 | * Set node event handlers.
242 | * @private
243 | * @param {D3Selection} nodes
244 | * @returns {D3Selection}
245 | */
246 | ge.GraphEditor.prototype.nodeEvents = function nodeEvents(nodes) {
247 | var self = this;
248 |
249 | return nodes
250 | /*.attr(
251 | 'transform',
252 | function(d) {
253 | return 'translate('.concat(d.x, ',', d.y, ')');
254 | }
255 | )*/
256 | .on('mouseover', function mouseOver(d) {
257 | if(self.state.dragToLink) {
258 | self.state.newLinkTarget = d;
259 | /*d3.select(this).classed(
260 | self.options.css.connect,
261 | true
262 | );*/
263 | }
264 | })
265 | .on('mouseout', function mouseOut(/*d*/) {
266 | if(self.state.dragToLink) {
267 | self.state.newLinkTarget = null;
268 | }
269 | /*d3.select(this).classed(
270 | self.options.css.connect,
271 | false
272 | );*/
273 | })
274 | .on('touchmove', function touchMove() {
275 | if(self.state.dragToLink) {
276 | self.state.newLinkTarget = self.touchedNode();
277 | }
278 | })
279 | .call(self.drag);
280 | };
281 |
--------------------------------------------------------------------------------
/src/js/init.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Create SVG markers.
5 | * @private
6 | * @param {D3Selection} svg
7 | * @returns {ge.GraphEditor}
8 | */
9 | ge.GraphEditor.prototype.initMarkers = function initMarkers(svg) {
10 | /*var defs = d3.select('#' + this.options.css.markers).node();
11 | if(defs !== null) {
12 | return this;
13 | }*/
14 |
15 | /*defs = d3.select('head')
16 | .append('svg')
17 | .attr('id', this.options.css.markers)
18 | .append('svg:defs');*/
19 |
20 | var defs = svg.append('svg:defs');
21 |
22 | defs.append('marker')
23 | .attr('id', 'ge-dragline-end')
24 | .attr('viewBox', '0 -7 12 14')
25 | .attr('refX', '7')
26 | .attr('refY', '0')
27 | .attr('markerWidth', 3.5)
28 | .attr('markerHeight', 3.5)
29 | .attr('orient', 'auto')
30 | .append('path')
31 | .attr('d', 'M0,-5L10,0L0,5Z');
32 |
33 | defs.append('marker')
34 | .attr('id', 'ge-dragline-start')
35 | .attr('viewBox', '-5 -5 5 5')
36 | .attr('refX', -2)
37 | .attr('refY', -2)
38 | .attr('markerWidth', 4)
39 | .attr('markerHeight', 4)
40 | .append('circle')
41 | .attr('r', 2)
42 | .attr('cx', -2)
43 | .attr('cy', -2);
44 |
45 | return this;
46 | };
47 |
48 | /**
49 | * Initialize SVG element.
50 | * @private
51 | * @param {D3Selection} svg
52 | * @this ge.GraphEditor
53 | * @returns {ge.GraphEditor}
54 | */
55 | ge.GraphEditor.prototype.initSvg = function initSvg(svg) {
56 | svg.attr('id', this.options.id)
57 | .classed(this.options.css.graph, true)
58 | .classed(this.options.css.digraph, this.options.directed);
59 |
60 | this.initMarkers(svg);
61 |
62 | var g = svg.append('g');
63 | var defsContainer = svg.select('defs'); //g.append('defs');
64 | var linkContainer = g.append('g');
65 | var nodeContainer = g.append('g');
66 |
67 | /**
68 | * Graph container element.
69 | * @readonly
70 | * @member {D3Selection}
71 | */
72 | this.container = g;
73 | /**
74 | * Link text path elements.
75 | * @readonly
76 | * @member {D3Selection}
77 | */
78 | this.defs = defsContainer.selectAll('.' + this.options.css.textpath);
79 | /**
80 | * Link elements.
81 | * @readonly
82 | * @member {D3Selection}
83 | */
84 | this.links = linkContainer.selectAll('g');
85 | /**
86 | * Node elements.
87 | * @readonly
88 | * @member {D3Selection}
89 | */
90 | this.nodes = nodeContainer.selectAll('g');
91 | /**
92 | * 'Drag to link nodes' line.
93 | * @readonly
94 | * @member {D3Selection}
95 | */
96 | this.dragLine = g.append('path')
97 | .classed(this.options.css.dragline, true)
98 | .classed(this.options.css.hide, true)
99 | .attr('marker-start', 'url(#ge-dragline-start)')
100 | .attr('marker-end', 'url(#ge-dragline-end)')
101 | .attr('d', 'M0,0L0,0');
102 | /**
103 | * Text size calculator.
104 | * @readonly
105 | * @member {ge.TextSize}
106 | */
107 | this.textSize = new ge.TextSize(
108 | svg.append('text')
109 | .classed(this.options.css.node, true)
110 | .node()
111 | );
112 |
113 | /**
114 | * Graph SVG element.
115 | * @readonly
116 | * @member {D3Selection}
117 | */
118 | this.svg = svg;
119 |
120 | return this;
121 | };
122 |
123 |
124 | /**
125 | * Initialize graph data.
126 | * @private
127 | * @param {ImportGraphData} data
128 | * @this ge.GraphEditor
129 | * @returns {ge.GraphEditor}
130 | */
131 | ge.GraphEditor.prototype.initData = function initData(data) {
132 | /**
133 | * Nodes by ID.
134 | * @readonly
135 | * @member {Object}
136 | */
137 | this.nodeById = {};
138 | /**
139 | * Links by ID.
140 | * @readonly
141 | * @member {Object}
142 | */
143 | this.linkById = {};
144 | /**
145 | * Graph data.
146 | * @readonly
147 | * @member {GraphData}
148 | */
149 | this.data = {
150 | nodes: [],
151 | links: []
152 | };
153 | this.addNodes(data.nodes, true);
154 | this.addLinks(data.links, true);
155 | return this;
156 | };
157 |
158 | /**
159 | * Initialize graph state.
160 | * @private
161 | * @this ge.GraphEditor
162 | * @returns {ge.GraphEditor}
163 | */
164 | ge.GraphEditor.prototype.initState = function initState() {
165 | /**
166 | * Graph editor state.
167 | * @readonly
168 | * @member {Object}
169 | * @property {boolean} zoomed False if the graph is clicked.
170 | * @property {boolean} dragged False if a node is clicked.
171 | * @property {?D3Simulation} simulation Current simulation object.
172 | * @property {boolean} simulationStarted True if a simulation is started.
173 | * @property {boolean} dragToLink True in 'drag to link nodes' mode.
174 | * @property {?Node} newLinkTarget New link target in 'drag to link nodes' mode.
175 | * @property {?Node} selectedNode Selected node.
176 | * @property {?Link} selectedLink Selected link.
177 | * @property {?Point} dragPos Last drag end position.
178 | * @property {number} zoom Zoom level.
179 | */
180 | this.state = {
181 | zoomed: false,
182 | dragged: false,
183 | simulation: null,
184 | simulationStarted: false,
185 | dragToLink: false,
186 | newLinkTarget: null,
187 | selectedNode: null,
188 | selectedLink: null,
189 | dragPos: null,
190 | zoom: 1
191 | //sizeScale: 1
192 | };
193 | return this;
194 | };
195 |
--------------------------------------------------------------------------------
/src/js/link.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Graph link constructor.
3 | * @class
4 | * @classdesc Graph link class.
5 | * @param {ge.GraphEditor} graph Link container.
6 | * @param {ImportLinkData} data Link data.
7 | */
8 | ge.Link = function Link(graph, data) {
9 | /**
10 | * Source node.
11 | * @member {ge.Node}
12 | */
13 | this.source = graph.getData(data.source);
14 | if(!this.source) {
15 | throw new Error('invalid link source');
16 | }
17 | /**
18 | * Target node.
19 | * @member {ge.Node}
20 | */
21 | this.target = graph.getData(data.target);
22 | if(!this.target) {
23 | throw new Error('invalid link target');
24 | }
25 | /**
26 | * ID.
27 | * @readonly
28 | * @member {ID}
29 | */
30 | this.id = /*data.id || */graph.linkId(this.source, this.target);
31 | /**
32 | * Path generator.
33 | * @member {ge.path.Path}
34 | */
35 | this.shape = data.shape
36 | ? ge.path.fromJson(data.shape)
37 | : graph.options.link.shape;
38 | /**
39 | * Title.
40 | * @member {string}
41 | */
42 | this.title = data.title === undefined ? this.id : data.title;
43 | /**
44 | * User data.
45 | * @member {*}
46 | */
47 | this.data = data.data;
48 | /**
49 | * SVG path.
50 | * @readonly
51 | * @member {string}
52 | */
53 | this.path = '';
54 | /**
55 | * True if text path is reversed.
56 | * @readonly
57 | * @member {boolean}
58 | */
59 | this.reversed = false;
60 | /**
61 | * SVG element ID.
62 | * @readonly
63 | * @member {string}
64 | */
65 | this.elementId = graph.options.id.concat('l', this.id);
66 | /**
67 | * SVG element selector.
68 | * @readonly
69 | * @member {string}
70 | */
71 | this.selector = '#' + this.elementId;
72 | /**
73 | * SVG element ID.
74 | * @readonly
75 | * @member {string}
76 | */
77 | this.pathId = graph.options.id.concat('p', this.id);
78 | /**
79 | * SVG element selector.
80 | * @readonly
81 | * @member {Selector}
82 | */
83 | this.pathSelector = '#' + this.pathId;
84 | /**
85 | * SVG text path element ID.
86 | * @readonly
87 | * @member {string}
88 | */
89 | this.defId = graph.options.id.concat('d', this.id);
90 | /**
91 | * SVG text path element selector.
92 | * @readonly
93 | * @member {Selector}
94 | */
95 | this.defSelector = '#' + this.defId;
96 |
97 | return this;
98 | };
99 |
100 | /**
101 | * Initialize SVG elements.
102 | * @static
103 | * @param {D3Selection} links SVG element enter selection.
104 | * @param {D3Selection} defs SVG element enter selection.
105 | * @param {object} opts Link options.
106 | * @param {number} opts.text.dx Link title X offset.
107 | * @param {number} opts.text.dy Link title Y offset.
108 | * @param {string} cls CSS class.
109 | */
110 | ge.Link.initSelection = function initSelection(links, defs, opts) {
111 | defs.attr('id', function(d) { return d.defId; });
112 |
113 | links.attr('id', function(d) { return d.elementId; });
114 | links.append('path')
115 | .attr('id', function(d) { return d.pathId; });
116 | links.append('text')
117 | .attr('dx', opts.text.dx)
118 | .attr('dy', opts.text.dy)
119 | .append('textPath')
120 | .attr('xlink:href', function(d) { return d.defSelector; });
121 | };
122 |
123 | /**
124 | * Update SVG elements.
125 | * @static
126 | * @param {D3Selection} links SVG element selection.
127 | * @param {D3Selection} defs SVG element selection.
128 | * @param {object} opts Link options.
129 | * @param {function} transition Transition generator.
130 | * @see ge.GraphEditor.updateLink
131 | */
132 | ge.Link.updateSelection = function updateSelection(
133 | links, defs,
134 | opts, transition
135 | ) {
136 | transition(defs)
137 | .attr('d', function(d) { return d.shape.path(d); });
138 |
139 | transition(links.select('path'))
140 | .style('stroke-width', function(d) { return d.size + 'px'; })
141 | .attr('d', function(d) { return d.path; });
142 |
143 | var offset = opts.text.offset;
144 | var anchor = opts.text.anchor;
145 |
146 | transition(links.select('textPath'))
147 | .text(function(d) { return d.title; })
148 | .attr('startOffset', function(d) { return offset[+d.reversed]; })
149 | .attr('text-anchor', function(d) { return anchor[+d.reversed]; });
150 | };
151 |
152 | /**
153 | * Initialize SVG elements.
154 | * @param {D3Selection} links SVG element enter selection.
155 | * @see ge.GraphEditor.update
156 | * @see ge.GraphEditor.linkEvents
157 | */
158 | ge.Link.prototype.initSelection = ge.Link.initSelection;
159 |
160 | /**
161 | * Update SVG elements.
162 | * @param {D3Selection} links SVG element selection.
163 | * @see ge.GraphEditor.updateLink
164 | */
165 | ge.Link.prototype.updateSelection = ge.Link.updateSelection;
166 |
167 | /**
168 | * Update link data.
169 | */
170 | ge.Link.prototype.update = function update() {
171 | this.shape(this);
172 | };
173 |
174 | /**
175 | * Convert to JSON.
176 | * @param {ge.GraphEditor} graph Graph.
177 | * @returns {ExportLinkData} JSON data.
178 | */
179 | ge.Link.prototype.toJson = function toJson(graph) {
180 | return {
181 | id: this.id,
182 | source: this.source.id,
183 | target: this.target.id,
184 | size: this.size,
185 | title: this.title,
186 | data: this.data,
187 | shape: this.shape === graph.options.link.shape
188 | ? null
189 | : ge.path.toJson(this.shape)
190 | };
191 | };
192 |
--------------------------------------------------------------------------------
/src/js/node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Graph node constructor.
3 | * @class
4 | * @classdesc Graph node class.
5 | * @param {ge.GraphEditor} graph Graph
6 | * @param {ImportNodeData} data Node JSON data
7 | * @see ge.GraphEditor.initNode
8 | */
9 | ge.Node = function Node(graph, data) {
10 | var opts = graph.options.node;
11 | /**
12 | * ID.
13 | * @readonly
14 | * @member {ID}
15 | */
16 | this.id = data.id || graph.nodeId();
17 | /**
18 | * X coordinate.
19 | * @member {number}
20 | */
21 | this.x = +data.x || 0;
22 | /**
23 | * Y coordinate.
24 | * @member {number}
25 | */
26 | this.y = +data.y || 0;
27 | /**
28 | * Width.
29 | * @member {number}
30 | */
31 | this.width = Math.max(+data.width || opts.size.def, opts.size.min);
32 | /**
33 | * Height.
34 | * @member {number}
35 | */
36 | this.height = Math.max(+data.height || opts.size.def, opts.size.min);
37 | /**
38 | * Shape.
39 | * @member {Shape}
40 | */
41 | this.shape = data.shape
42 | ? ge.shape.fromJson(data.shape)
43 | : opts.shape;
44 | /**
45 | * Title.
46 | * @member {string}
47 | */
48 | this.title = data.title == null ? this.id.toString() : data.title;
49 | /**
50 | * Text X offset.
51 | * @readonly
52 | * @member {?number}
53 | */
54 | this.textX = null;
55 | /**
56 | * Text Y offset.
57 | * @readonly
58 | * @member {?number}
59 | */
60 | this.textY = null;
61 | /**
62 | * Text container width.
63 | * @readonly
64 | * @member {?number}
65 | */
66 | this.textWidth = null;
67 | /**
68 | * Text container height.
69 | * @readonly
70 | * @member {?number}
71 | */
72 | this.textHeight = null;
73 | /**
74 | * True if node title is inside its shape.
75 | * @readonly
76 | * @member {ge.GraphEditor}
77 | */
78 | this.textInside = opts.text.inside;
79 | /**
80 | * Text size calculator.
81 | * @readonly
82 | * @member {ge.TextSize}
83 | */
84 | this.textSize = graph.textSize;
85 | /**
86 | * User data.
87 | * @member {*}
88 | */
89 | this.data = data.data;
90 | /**
91 | * SVG element ID.
92 | * @readonly
93 | * @member {string}
94 | */
95 | this.elementId = graph.options.id.concat('n', this.id);
96 | /**
97 | * SVG element selector.
98 | * @readonly
99 | * @member {Selector}
100 | */
101 | this.selector = '#' + this.elementId;
102 | /**
103 | * SVG group transform.
104 | * @readonly
105 | * @member {?string}
106 | */
107 | this.transform = null;
108 | /**
109 | * Previous title.
110 | * @private
111 | * @member {?string}
112 | */
113 | this.prevTitle = null;
114 |
115 | return this;
116 | };
117 |
118 | /**
119 | * Initialize SVG elements.
120 | * @static
121 | * @param {D3Selection} nodes SVG element enter selection.
122 | * @param {string} cls CSS class.
123 | */
124 | ge.Node.initSelection = function initSelection(nodes) {
125 | nodes.attr('id', function(d) { return d.elementId; });
126 | nodes.append('path');
127 | nodes.append('foreignObject')
128 | .append('xhtml:div')
129 | .append('xhtml:span');
130 | };
131 |
132 | /**
133 | * Update SVG elements.
134 | * @static
135 | * @param {D3Selection} nodes SVG element selection.
136 | * @param {function} transition Transition generator.
137 | * @param {ge.TextSize} textSize Text size calculator.
138 | */
139 | ge.Node.updateSelection = function updateSelection(
140 | nodes,
141 | transition,
142 | textSize
143 | ) {
144 | console.log(nodes);
145 | transition(nodes)
146 | .attr(
147 | 'transform',
148 | function(d) {
149 | return 'translate('.concat(d.x, ',', d.y, ')');
150 | }
151 | );
152 |
153 | nodes.each(function(d) {
154 | //if(d.textInside) {
155 | d.shape.fitTitleInside(d, textSize);
156 | //}
157 | /*else {
158 | d.shape.fitTitleOutside(d, textSize);
159 | }*/
160 | });
161 |
162 | transition(nodes.select('foreignObject'))
163 | .attr('x', function(d) { return -d.textWidth / 2; })
164 | .attr('y', function(d) { return -d.textHeight / 2; })
165 | .attr('width', function(d) { return d.textWidth; })
166 | .attr('height', function(d) { return d.textHeight; })
167 | .select('span')
168 | .text(function(d) { return d.title; });
169 |
170 | transition(nodes.select('path'))
171 | .attr('d', function(d) { return d.shape.path(d); });
172 | };
173 |
174 | /**
175 | * Initialize SVG elements.
176 | * @param {D3Selection} nodes SVG element enter selection.
177 | */
178 | ge.Node.prototype.initSelection = ge.Node.initSelection;
179 |
180 | /**
181 | * Update SVG elements.
182 | * @param {D3Selection} nodes SVG element selection.
183 | * @param {function} transition Transition generator.
184 | */
185 | ge.Node.prototype.updateSelection = ge.Node.updateSelection;
186 |
187 | /**
188 | * Update node data.
189 | * @returns {boolean} True if node position changed.
190 | */
191 | ge.Node.prototype.update = function update() {
192 | var moved = false;
193 |
194 | if(this.textInside) {
195 | this.shape.fitTextInside(this);
196 | }
197 |
198 | var transform = 'translate('.concat(this.x, ',', this.y, ')');
199 | if(transform !== this.transform) {
200 | this.transform = transform;
201 | moved = true;
202 | }
203 |
204 | return moved;
205 | };
206 |
207 | /**
208 | * Convert to JSON.
209 | * @param {ge.GraphEditor} graph Graph.
210 | * @param {number} [ox=0] Origin X coordinate.
211 | * @param {number} [oy=0] Origin Y coordinate.
212 | * @returns {ExportNodeData} JSON data.
213 | */
214 | ge.Node.prototype.toJson = function toJson(graph, ox, oy) {
215 | return {
216 | id: this.id,
217 | x: this.x - ox,
218 | y: this.y - oy,
219 | width: this.width,
220 | height: this.height,
221 | shape: this.shape === graph.options.node.shape
222 | ? null
223 | : ge.shape.toJson(this.shape),
224 | title: this.title,
225 | data: this.data
226 | };
227 | };
228 |
--------------------------------------------------------------------------------
/src/js/options.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Default options for all graph types.
5 | * @type GraphOptions
6 | */
7 | ge.GraphEditor.prototype.defaults = {
8 | id: null,
9 | directed: false,
10 |
11 | node: {
12 | shape: new ge.shape.Circle(),
13 | border: 2,
14 | size: {
15 | def: 10,
16 | min: 10
17 | },
18 | text: {
19 | dx: 0,
20 | dy: 0,
21 | inside: true
22 | }
23 | },
24 |
25 | link: {
26 | size: {
27 | def: 2
28 | },
29 | text: {
30 | dx: 0,
31 | dy: '1.1em',
32 | offset: null,
33 | anchor: null
34 | }
35 | },
36 |
37 | simulation: {
38 | create: ge.defaultSimulation,
39 | start: false,
40 | stop: true,
41 | step: 1
42 | },
43 |
44 | transition: {
45 | zoom: 250,
46 | drag: 0,
47 | simulation: 50
48 | },
49 |
50 | scale: {
51 | min: 0.25,
52 | max: 8.0,
53 | /*size: {
54 | min: 0.5,
55 | max: 2.0
56 | }*/
57 | },
58 |
59 | bbox: {
60 | padding: 80
61 | },
62 |
63 | css: {
64 | //markers: 'ge-markers',
65 | node: 'ge-node',
66 | graph: 'ge-graph',
67 | digraph: 'ge-digraph',
68 | hide: 'ge-hidden',
69 | dragline: 'ge-dragline',
70 | link: 'ge-link',
71 | textpath: 'ge-text-path',
72 | //connect: 'ge-connect',
73 | selection: {
74 | node: 'ge-selection',
75 | link: 'ge-link-selection'
76 | }
77 | }
78 | };
79 |
80 | /**
81 | * Default options by graph type.
82 | * @type Array
83 | */
84 | ge.GraphEditor.prototype.typeDefaults = [
85 | {
86 | link: {
87 | shape: new ge.path.Line({
88 | loopStart: 0,
89 | loopEnd: 90
90 | }),
91 | text: {
92 | offset: '50%',
93 | anchor: 'middle'
94 | }
95 | }
96 | },
97 | {
98 | link: {
99 | shape: new ge.path.Line({
100 | arrow: true,
101 | loopStart: 0,
102 | loopEnd: 90
103 | }),
104 | text: {
105 | offset: '20%',
106 | anchor: 'start'
107 | }
108 | }
109 | }
110 | ];
111 |
112 | /**
113 | * Initialize graph options.
114 | * @private
115 | * @param {GraphOptions} options
116 | * @param {D3Selection} svg
117 | * @returns {ge.GraphEditor}
118 | */
119 | ge.GraphEditor.prototype.initOptions = function initOptions(options, svg) {
120 | var directed;
121 | if(options && options.hasOwnProperty('directed')) {
122 | directed = options.directed;
123 | }
124 | else {
125 | directed = this.defaults.directed;
126 | }
127 |
128 | var typeDefaults = this.typeDefaults[+directed];
129 |
130 | var opt = ge.extend({}, this.defaults, typeDefaults, options);
131 |
132 | opt.id = opt.id
133 | || svg.attr('id')
134 | || 'ge'.concat(Math.floor(Math.random() * 100));
135 |
136 | var reversed = (100 - parseInt(opt.link.text.offset)) + '%';
137 | opt.link.text.offset = [ opt.link.text.offset, reversed ];
138 |
139 | switch(opt.link.text.anchor) {
140 | case 'start':
141 | reversed = 'end';
142 | break;
143 | case 'end':
144 | reversed = 'start';
145 | break;
146 | default:
147 | reversed = 'middle';
148 | }
149 | opt.link.text.anchor = [ opt.link.text.anchor, reversed ];
150 |
151 | this.options = opt;
152 | return this;
153 | };
154 |
--------------------------------------------------------------------------------
/src/js/path/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Link shapes.
3 | * @namespace
4 | */
5 | ge.path = new ge.SaveLoad();
6 |
7 | /**
8 | * Abstract base path class.
9 | * @class
10 | * @abstract
11 | * @param {?object} [data] JSON data.
12 | * @param {?number} [data.loopStart=180] Loop start angle in degrees.
13 | * @param {?number} [data.loopEnd=270] Loop end angle in degrees.
14 | */
15 | ge.path.Path = function Path(data) { // eslint-disable-line no-unused-vars
16 | if(this.constructor === Path) {
17 | throw new Error('abstract class');
18 | }
19 | data = data || {};
20 | this.loopStart = new ge.Angle(+data.loopStart || 180);
21 | this.loopEnd = new ge.Angle(+data.loopEnd || 270);
22 | };
23 |
24 | /* eslint-disable no-unused-vars */
25 |
26 | /**
27 | * Return text path and set link path.
28 | * @abstract
29 | * @param {ge.Link} link Link data.
30 | * @returns {string} SVG path.
31 | */
32 | ge.path.Path.prototype.path = function path(link) {
33 | throw new Error('abstract method');
34 | };
35 |
36 | /* eslint-enable no-unused-vars */
37 |
38 | /**
39 | * Convert to JSON.
40 | * @returns {object} JSON data.
41 | */
42 | ge.path.Path.prototype.toJson = function toJson() {
43 | return {
44 | loopStart: this.loopStart.deg,
45 | loopEnd: this.loopEnd.deg
46 | };
47 | };
48 |
--------------------------------------------------------------------------------
/src/js/path/line.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Line.
3 | * @class
4 | * @param {?object} [data] JSON data.
5 | * @param {?number} [data.loopStart=180] Loop start angle in degrees.
6 | * @param {?number} [data.loopEnd=270] Loop end angle in degrees.
7 | * @param {?boolean} [data.arrow=false]
8 | * @param {?number} [data.arrowLength=10]
9 | * @param {?number} [data.arrowAngle=15]
10 | */
11 | ge.path.Line = function Line(data) {
12 | ge.path.Path.call(this, data);
13 | this.arrow = !!data.arrow;
14 | this.arrowLength = +data.arrowLength || 10;
15 | this.arrowAngle = new ge.Angle(+data.arrowAngle || 15);
16 | this.arrowAngle2 = new ge.Angle(-data.arrowAngle || -15);
17 | };
18 |
19 | ge.path.Line.prototype = Object.create(ge.path.Line);
20 | Object.defineProperty(
21 | ge.path.Line.prototype,
22 | 'constructor',
23 | {
24 | value: ge.path.Line,
25 | enumerable: false,
26 | writable: true
27 | }
28 | );
29 | ge.path.addClass(ge.path.Line);
30 |
31 | /**
32 | * Return arrow path.
33 | * @param {ge.Point} src Link source.
34 | * @param {ge.Point} dst Link target.
35 | * @returns {string} SVG path.
36 | */
37 | ge.path.Line.prototype.arrowPath = function arrow(src, dst) {
38 | var b = src.clone().sub(dst).normalize();
39 | var c = b.clone().rotate(this.arrowAngle2).mul(this.arrowLength).add(dst);
40 | b.rotate(this.arrowAngle).mul(this.arrowLength).add(dst);
41 | return 'M'.concat(
42 | dst.x, ',', dst.y,
43 | 'L', b.x, ',', b.y,
44 | 'L', c.x, ',', c.y,
45 | 'Z'
46 | );
47 | };
48 |
49 | /**
50 | * Return text path and set link path.
51 | * @param {ge.Link} link Link data.
52 | * @returns {string} SVG path.
53 | */
54 | ge.path.Line.prototype.path = function path(link) {
55 | var src, dst;
56 |
57 | if(link.source === link.target) {
58 | src = link.source.shape.getPoint(link.source, this.loopStart);
59 | dst = link.source.shape.getPoint(link.source, this.loopEnd);
60 | var rx = link.source.width / 2;
61 | var ry = link.source.height / 2;
62 |
63 | link.reversed = false;
64 | link.path = 'M'.concat(
65 | src.x, ',', src.y,
66 | 'A', rx, ',', ry, ',0,1,0,', dst.x, ',', dst.y
67 | );
68 |
69 | /*if(this.arrow) {
70 | }*/
71 |
72 | return link.path;
73 | }
74 |
75 | src = new ge.Point(link.source.x, link.source.y);
76 | dst = new ge.Point(link.target.x, link.target.y);
77 | var src2 = link.source.shape.intersect(link.source, link.target);
78 | var dst2 = link.target.shape.intersect(link.target, link.source);
79 | src = src2 || src;
80 | dst = dst2 || dst;
81 |
82 | var dst3 = dst;
83 |
84 | if(this.arrow) {
85 | dst3 = dst.clone().sub(src);
86 | var l = dst3.length();
87 | var l2 = this.arrowLength * this.arrowAngle.cos;
88 | dst3.mul(1 - l2 / l).add(src);
89 | }
90 |
91 | link.path = 'M'.concat(src.x, ',', src.y, 'L', dst3.x, ',', dst3.y);
92 |
93 | if(this.arrow) {
94 | link.path += this.arrowPath(src, dst);
95 | }
96 |
97 | if((link.reversed = src.x > dst.x)) {
98 | src2 = src;
99 | src = dst;
100 | dst = src2;
101 | }
102 |
103 | return 'M'.concat(src.x, ',', src.y, 'L', dst.x, ',', dst.y);
104 | };
105 |
--------------------------------------------------------------------------------
/src/js/shape/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Node shapes.
3 | * @namespace
4 | */
5 |
6 | ge.shape = new ge.SaveLoad();
7 |
8 | /**
9 | * Abstract base shape class.
10 | * @class
11 | * @abstract
12 | * @param {?object} [data] JSON data.
13 | */
14 | ge.shape.Shape = function Shape(data) { // eslint-disable-line no-unused-vars
15 | if(this.constructor === Shape) {
16 | throw new Error('abstract class');
17 | }
18 | };
19 |
20 | /* eslint-disable no-unused-vars */
21 |
22 | /**
23 | * Return SVG path.
24 | * @abstract
25 | * @param {ge.Node} node Node data.
26 | * @returns {string} SVG path.
27 | */
28 | ge.shape.Shape.prototype.path = function path(node) {
29 | throw new Error('abstract method');
30 | };
31 |
32 | /**
33 | * Return point at angle.
34 | * @abstract
35 | * @param {ge.Node} node Node data.
36 | * @param {ge.Angle} angle Angle.
37 | * @returns {ge.Point}
38 | */
39 | ge.shape.Shape.prototype.getPoint = function getPoint(node, angle) {
40 | throw new Error('abstract method');
41 | };
42 |
43 | /**
44 | * Resize a node to fit its title inside.
45 | * @abstract
46 | * @param {ge.Node} node Node data.
47 | * @param {ge.ContainerSize} size Text container size.
48 | * @param {ge.TextSize} textSize Text size calculator.
49 | */
50 | ge.shape.Shape.prototype.doFitTitleInside = function doFitTitleInside(
51 | node,
52 | size,
53 | textSize
54 | ) {
55 | throw new Error('abstract method');
56 | };
57 |
58 |
59 | /* eslint-enable no-unused-vars */
60 |
61 | /**
62 | * Convert to JSON.
63 | * @returns {object} JSON data.
64 | */
65 | ge.shape.Shape.prototype.toJson = function toJson() {
66 | return {};
67 | };
68 |
69 | /**
70 | * Intersect with a link.
71 | * @param {ge.Node} node Node data.
72 | * @param {ge.Node} node2 Node data.
73 | * @returns {?ge.Point} Intersection point.
74 | */
75 | ge.shape.Shape.prototype.intersect = function intersect(node, node2) {
76 | var a = Math.atan2(node2.y - node.y, node2.x - node.x);
77 | return this.getPoint(node, new ge.Angle(a, true));
78 | };
79 |
80 | /**
81 | * Resize a node to fit its title inside if necessary.
82 | * @param {ge.Node} node Node data.
83 | * @param {ge.TextSize} textSize Text size calculator.
84 | */
85 | ge.shape.Shape.prototype.fitTitleInside = function fitTitleInside(
86 | node,
87 | textSize
88 | ) {
89 | if(node.title === node.prevTitle) {
90 | return;
91 | }
92 | var size = textSize.containerSize(node.title);
93 | this.doFitTitleInside(node, size, textSize);
94 | node.prevTitle = node.title;
95 | };
96 |
--------------------------------------------------------------------------------
/src/js/shape/circle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Circle.
3 | * @class
4 | * @param {?object} [data] JSON data.
5 | */
6 | ge.shape.Circle = function Circle(data) {
7 | ge.shape.Shape.call(this, data);
8 | };
9 |
10 | ge.shape.Circle.prototype = Object.create(ge.shape.Shape.prototype);
11 | Object.defineProperty(
12 | ge.shape.Circle.prototype,
13 | 'constructor',
14 | {
15 | value: ge.shape.Circle,
16 | enumerable: false,
17 | writable: true
18 | }
19 | );
20 | ge.shape.addClass(ge.shape.Circle);
21 |
22 | /**
23 | * Return SVG path.
24 | * @param {ge.Node} node Node data.
25 | * @returns {string} SVG path.
26 | */
27 | ge.shape.Circle.prototype.path = function path(node) {
28 | var dx = node.width, rx = dx / 2, ry = node.height / 2;
29 | var arc = 'a'.concat(rx, ',', ry, ',0,1,0,');
30 | return 'M'.concat(-rx, ',0', arc, dx, ',0', arc, -dx, ',0');
31 | };
32 |
33 | /**
34 | * Return point at angle.
35 | * @abstract
36 | * @param {ge.Node} node Node data.
37 | * @param {ge.Angle} angle Angle.
38 | * @returns {ge.Point}
39 | */
40 | ge.shape.Circle.prototype.getPoint = function getPoint(node, angle) {
41 | return new ge.Point(
42 | node.x + node.width * angle.cos * 0.5,
43 | node.y + node.height * angle.sin * 0.5
44 | );
45 | };
46 |
47 | /**
48 | * Resize a node to fit its title inside.
49 | * @abstract
50 | * @param {ge.Node} node Node data.
51 | * @param {ge.ContainerSize} size Text container size.
52 | * @param {ge.TextSize} textSize Text size calculator.
53 | */
54 | ge.shape.Circle.prototype.doFitTitleInside = function doFitTitleInside(
55 | node,
56 | size/*,
57 | textSize*/
58 | ) {
59 | node.width = node.height = Math.max(
60 | Math.ceil(Math.sqrt(size.minArea * 2)),
61 | size.minWidth,
62 | size.minHeight
63 | );
64 | node.textWidth = node.textHeight = Math.ceil(node.width / 1.414213);
65 | };
66 |
--------------------------------------------------------------------------------
/src/js/shape/rect.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rectangle.
3 | * @class
4 | * @param {?object} [data] JSON data.
5 | * @param {?number} [data.aspect=1] Aspect ratio.
6 | */
7 | ge.shape.Rect = function Rect(data) {
8 | data = data || {};
9 | ge.shape.Shape.call(this, data);
10 | /**
11 | * Aspect ratio.
12 | * @member {number}
13 | */
14 | this.aspect = data.aspect || 1;
15 | };
16 |
17 | ge.shape.Rect.prototype = Object.create(ge.shape.Shape.prototype);
18 | Object.defineProperty(
19 | ge.shape.Rect.prototype,
20 | 'constructor',
21 | {
22 | value: ge.shape.Rect,
23 | enumerable: false,
24 | writable: true
25 | }
26 | );
27 | ge.shape.addClass(ge.shape.Rect);
28 |
29 | /**
30 | * Convert to JSON.
31 | * @abstract
32 | * @returns {object} JSON data.
33 | */
34 | ge.shape.Rect.prototype.toJson = function toJson() {
35 | return {
36 | aspect: this.aspect
37 | };
38 | };
39 |
40 | /**
41 | * Return SVG path.
42 | * @param {ge.Node} node Node data.
43 | * @returns {string} SVG path.
44 | */
45 | ge.shape.Rect.prototype.path = function path(node) {
46 | var w = node.width, h = node.height;
47 | return 'M'.concat(
48 | (-w / 2), ',', (-h / 2),
49 | 'l', w, ',0l0,', h, 'l', -w, ',0z'
50 | );
51 | };
52 |
53 | /**
54 | * Return point at angle.
55 | * @abstract
56 | * @param {ge.Node} node Node data.
57 | * @param {ge.Angle} angle Angle.
58 | * @returns {ge.Point}
59 | */
60 | ge.shape.Rect.prototype.getPoint = function getPoint(node, angle) {
61 | var w = node.width / 2;
62 | var h = node.height / 2;
63 |
64 | var x, y, t;
65 | var ret = new ge.Point(node.x, node.y);
66 |
67 | x = null;
68 | if(angle.cos > 1e-8) {
69 | x = w;
70 | }
71 | else if(angle.cos < -1e-8) {
72 | x = -w;
73 | }
74 | if(x !== null) {
75 | t = x / angle.cos;
76 | y = t * angle.sin;
77 | if(y < h + 1e-8 && y > -h - 1e-8) {
78 | ret.x += x;
79 | ret.y += y;
80 | return ret;
81 | }
82 | }
83 |
84 | y = null;
85 | if(angle.sin > 1e-8) {
86 | y = h;
87 | }
88 | else if(angle.sin < -1e-8) {
89 | y = -h;
90 | }
91 | if(y !== null) {
92 | t = y / angle.sin;
93 | x = t * angle.cos;
94 | if(x < w + 1e-8 && x > -w - 1e-8) {
95 | ret.x += x;
96 | ret.y += y;
97 | return ret;
98 | }
99 | }
100 |
101 | // throw new Error()
102 | return ret;
103 | };
104 |
105 | /**
106 | * Resize a node to fit its title inside.
107 | * @abstract
108 | * @param {ge.Node} node Node data.
109 | * @param {ge.ContainerSize} size Text container size.
110 | * @param {ge.TextSize} textSize Text size calculator.
111 | */
112 | ge.shape.Rect.prototype.doFitTitleInside = function doFitTitleInside(
113 | node,
114 | size/*,
115 | textSize*/
116 | ) {
117 | node.width = Math.max(
118 | Math.ceil(this.aspect * Math.sqrt(size.minArea)),
119 | size.minWidth
120 | );
121 | node.height = Math.max(size.minHeight, node.width / this.aspect);
122 | node.textWidth = node.width;
123 | node.textHeight = node.height;
124 | };
125 |
--------------------------------------------------------------------------------
/src/js/util/angle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Angle constructor.
3 | * @class
4 | * @classdesc Angle class.
5 | * @param {?number} value Angle value.
6 | * @param {?boolean} rad true if value is in radians, false if in degrees.
7 | */
8 | ge.Angle = function Angle(value, rad) {
9 | /**
10 | * Angle in degrees.
11 | * @member {number}
12 | */
13 | this.deg = 0;
14 | /**
15 | * Angle in radians.
16 | * @member {number}
17 | */
18 | this.rad = 0;
19 |
20 | if(rad) {
21 | this.rad = +value || 0;
22 | this.deg = this.rad * 180.0 / Math.PI;
23 | }
24 | else {
25 | this.deg = +value || 0;
26 | this.rad = this.deg * Math.PI / 180.0;
27 | }
28 |
29 | /**
30 | * Sine.
31 | * @member {number}
32 | */
33 | this.sin = Math.sin(this.rad);
34 | /**
35 | * Cosine.
36 | * @member {number}
37 | */
38 | this.cos = Math.cos(this.rad);
39 | };
40 |
--------------------------------------------------------------------------------
/src/js/util/bbox.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Bounding box constructor.
3 | * @class
4 | * @classdesc Bounding box class.
5 | * @param {?number} left Left X coordinate.
6 | * @param {?number} top Top Y coordinate.
7 | * @param {?number} right Right X coordinate.
8 | * @param {?number} bottom Bottom Y coordinate.
9 | */
10 | ge.BBox = function BBox(left, top, right, bottom) {
11 | /**
12 | * Left X coordinate.
13 | * @member {number}
14 | */
15 | this.left = +left || 0;
16 | /**
17 | * Top Y coordinate.
18 | * @member {number}
19 | */
20 | this.top = +top || 0;
21 | /**
22 | * Right X coordinate.
23 | * @member {number}
24 | */
25 | this.right = +right || 0;
26 | /**
27 | * Bottom Y coordinate.
28 | * @member {number}
29 | */
30 | this.bottom = +bottom || 0;
31 | };
32 |
--------------------------------------------------------------------------------
/src/js/util/common.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get object ID.
3 | * @param {?(Object|undefined)} obj
4 | * @returns {?ID}
5 | */
6 | ge.id = function id(obj) {
7 | return obj && obj.id || null;
8 | };
9 |
10 | /**
11 | * @param {Object} _this
12 | * @param {function} func
13 | * @returns {function}
14 | */
15 | ge.bind = function bind(_this, func) {
16 | return function bound() {
17 | return func.apply(_this, arguments);
18 | };
19 | };
20 |
21 | /**
22 | * Compare numbers or arrays of numbers.
23 | * @param {(number|Array)} u
24 | * @param {(number|Array)} v
25 | * @param {number} [eps=1e-5] Precision.
26 | * @returns {boolean}
27 | */
28 | ge.equal = function equal(u, v, eps) {
29 | eps = eps || 1e-5;
30 | var eq = function(x, y) { return Math.abs(x - y) < eps; };
31 |
32 | if(u === null || v === null
33 | || u === undefined || v === undefined
34 | || typeof u === 'number' && Array.isArray(v)
35 | || typeof v === 'number' && Array.isArray(u)) {
36 | return false;
37 | }
38 |
39 | if(typeof u === 'number' && typeof v === 'number') {
40 | return eq(u, v);
41 | }
42 |
43 | if(!Array.isArray(u) || !Array.isArray(v)) {
44 | throw new Error(
45 | 'ge.equal: invalid argument type: '
46 | .concat(u.toString(), ' ', v.toString())
47 | );
48 | }
49 |
50 | if(u.length !== v.length) {
51 | return false;
52 | }
53 |
54 | for(var i = 0; i < u.length; ++i) {
55 | if(!eq(u[i], v[i])) {
56 | return false;
57 | }
58 | }
59 |
60 | return true;
61 | };
62 |
63 | /**
64 | * Default simulation update function.
65 | * @param {?D3Simulation} simulation Old simulation object.
66 | * @param {Array} nodes Graph nodes.
67 | * @param {Array} links Graph links.
68 | * @this ge.GraphEditor
69 | * @returns {D3Simulation} New/updated simulation object.
70 | */
71 | ge.defaultSimulation = function defaultSimulation(simulation, nodes, links) {
72 | if(!simulation) {
73 | simulation = d3.forceSimulation()
74 | .force('charge', d3.forceManyBody())
75 | .force('link', d3.forceLink())
76 | .force('center', d3.forceCenter());
77 | }
78 |
79 | var dist = 2 * d3.max(nodes, function(d) {
80 | return Math.max(d.width, d.height);
81 | });
82 |
83 | var cx = d3.mean(nodes, function(d) { return d.x; });
84 | var cy = d3.mean(nodes, function(d) { return d.y; });
85 |
86 | simulation.nodes(nodes);
87 | simulation.force('center').x(cx).y(cy);
88 | simulation.force('link').links(links).distance(dist);
89 |
90 | return simulation;
91 | };
92 |
93 | /**
94 | * Extend an object.
95 | * @param {object} dst Object to extend.
96 | * @param {object} src Source object.
97 | * @returns {object} Extended object.
98 | */
99 | ge._extend = function _extend(dst, src) {
100 | if(!src) {
101 | return dst;
102 | }
103 | if(typeof src !== 'object') {
104 | throw new Error('src is not an object: ' + src.toString());
105 | }
106 |
107 | if(!dst) {
108 | dst = {};
109 | }
110 | else if(typeof dst !== 'object') {
111 | throw new Error('dst is not an object: ' + dst.toString());
112 | }
113 |
114 | for(var key in src) {
115 | var value = src[key];
116 | if(typeof value === 'object' && value !== null) {
117 | if(Array.isArray(value)) {
118 | dst[key] = value.slice();
119 | }
120 | else if(value.constructor.name !== 'Object') {
121 | dst[key] = value;
122 | }
123 | else {
124 | var dstValue = dst[key];
125 | if(!dstValue
126 | || typeof dstValue !== 'object'
127 | || Array.isArray(dstValue)) {
128 | dst[key] = dstValue = {};
129 | }
130 | ge._extend(dstValue, value);
131 | }
132 | }
133 | else {
134 | dst[key] = value;
135 | }
136 | }
137 | return dst;
138 | };
139 |
140 | /**
141 | * Extend an object.
142 | * @param {object} dst Object to extend.
143 | * @param {...object} src Source objects.
144 | * @returns {object} Extended object.
145 | */
146 | ge.extend = function extend(dst, src) { // eslint-disable-line no-unused-vars
147 | for(var i = 1; i < arguments.length; ++i) {
148 | dst = ge._extend(dst, arguments[i]);
149 | }
150 | return dst;
151 | };
152 |
153 | /*ge.debounceD3 = function debounceD3(func, delay) {
154 | var timeout = null;
155 |
156 | return function() {
157 | var context = this;
158 | var args = arguments;
159 | var d3ev = d3.event;
160 |
161 | if(timeout !== null) {
162 | clearTimeout(timeout);
163 | }
164 |
165 | timeout = setTimeout(
166 | function() {
167 | var tmp = d3.event;
168 | timeout = null;
169 | d3.event = d3ev;
170 | try {
171 | func.apply(context, args);
172 | }
173 | finally {
174 | d3.event = tmp;
175 | }
176 | },
177 | delay
178 | );
179 | };
180 | };*/
181 |
--------------------------------------------------------------------------------
/src/js/util/container-size.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Container size constructor.
3 | * @class
4 | * @classdesc Point class.
5 | * @param {?number} [minWidth=1] Minimum width.
6 | * @param {?number} [minHeight=1] Minimum height.
7 | * @param {?number} [minArea=minWidth*minHeight] Minimum area.
8 | */
9 | ge.ContainerSize = function ContainerSize(minWidth, minHeight, minArea) {
10 | /**
11 | * Minimum width.
12 | * @member {number}
13 | */
14 | this.minWidth = +minWidth || 1;
15 | /**
16 | * Minimum height.
17 | * @member {number}
18 | */
19 | this.minHeight = +minHeight || 1;
20 | /**
21 | * Minimum area.
22 | * @member {number}
23 | */
24 | this.minArea = +minArea || this.minWidth * this.minHeight;
25 | };
26 |
--------------------------------------------------------------------------------
/src/js/util/point.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Point constructor.
3 | * @class
4 | * @classdesc Point class.
5 | * @param {?number} x X coordinate.
6 | * @param {?number} y Y coordinate.
7 | */
8 | ge.Point = function Point(x, y) {
9 | /**
10 | * X coordinate.
11 | * @member {number}
12 | */
13 | this.x = +x || 0;
14 | /**
15 | * Y coordinate.
16 | * @member {number}
17 | */
18 | this.y = +y || 0;
19 | };
20 |
21 | /**
22 | * Add.
23 | * @param {ge.Point} p
24 | * @returns {ge.Point}
25 | */
26 | ge.Point.prototype.add = function add(p) {
27 | this.x += p.x;
28 | this.y += p.y;
29 | return this;
30 | };
31 |
32 | /**
33 | * Subtract.
34 | * @param {ge.Point} p
35 | * @returns {ge.Point}
36 | */
37 | ge.Point.prototype.sub = function sub(p) {
38 | this.x -= p.x;
39 | this.y -= p.y;
40 | return this;
41 | };
42 |
43 | /**
44 | * Multiply.
45 | * @param {number} k
46 | * @returns {ge.Point}
47 | */
48 | ge.Point.prototype.mul = function mul(k) {
49 | this.x *= k;
50 | this.y *= k;
51 | return this;
52 | };
53 |
54 | /**
55 | * Return vector length.
56 | * @returns {number}
57 | */
58 | ge.Point.prototype.length = function length() {
59 | return Math.sqrt(this.x * this.x + this.y * this.y);
60 | };
61 |
62 | /**
63 | * Normalize.
64 | * @returns {ge.Point}
65 | */
66 | ge.Point.prototype.normalize = function normalize() {
67 | var l = this.length();
68 | if(l > 1e-8) {
69 | this.x /= l;
70 | this.y /= l;
71 | }
72 | return this;
73 | };
74 |
75 | /**
76 | * Rotate.
77 | * @param {ge.Angle} a
78 | * @returns {ge.Point}
79 | */
80 | ge.Point.prototype.rotate = function rotate(a) {
81 | var x = this.x, y = this.y;
82 | this.x = x * a.cos - y * a.sin;
83 | this.y = x * a.sin + y * a.cos;
84 | return this;
85 | };
86 |
87 | /**
88 | * Clone.
89 | * @returns {ge.Point}
90 | */
91 | ge.Point.prototype.clone = function clone() {
92 | return new ge.Point(this.x, this.y);
93 | };
94 |
--------------------------------------------------------------------------------
/src/js/util/save-load.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Class for saving/loading JSON.
3 | * @class
4 | * @param {?number} x X coordinate.
5 | * @param {?number} y Y coordinate.
6 | */
7 | ge.SaveLoad = function SaveLoad() {
8 | /**
9 | * Class constructors by name.
10 | * @member {object}
11 | */
12 | this.classes = {};
13 | };
14 |
15 | /**
16 | * Add a class.
17 | * @param {function} constructor Class constructor.
18 | * @param {string} [name=constructor.name] Class name.
19 | * @param {boolean} [overwrite=false] Overwrite if class exists.
20 | */
21 | ge.SaveLoad.prototype.addClass = function addClass(
22 | constructor,
23 | name,
24 | overwrite
25 | ) {
26 | name = name || constructor.name;
27 | if(this.classes[name] && !overwrite) {
28 | throw new Error('class "' + name +'" exists');
29 | }
30 | this.classes[name] = constructor;
31 | };
32 |
33 | /**
34 | * Get class by name.
35 | * @param {string} name Class name.
36 | * @returns {function} Class constructor.
37 | */
38 | ge.SaveLoad.prototype.getClass = function getClass(name) {
39 | var ret = this.classes[name];
40 | if(!ret) {
41 | throw new Error('class "' + name +'" not found');
42 | }
43 | return ret;
44 | };
45 |
46 | /**
47 | * Load from JSON.
48 | * @param {object} data JSON data.
49 | * @returns {object}
50 | */
51 | ge.SaveLoad.prototype.fromJson = function fromJson(data) {
52 | var cls = this.getClass(data['class']);
53 | return new cls(data);
54 | };
55 |
56 | /**
57 | * Save to JSON.
58 | * @param {object} obj Object to save.
59 | * @param {string} [name=obj.constructor.name] Class name.
60 | * @returns {object}
61 | */
62 | ge.SaveLoad.prototype.toJson = function toJson(obj, name) {
63 | var ret = obj.toJson();
64 | ret['class'] = name || obj.constructor.name;
65 | return ret;
66 | };
67 |
--------------------------------------------------------------------------------
/src/js/util/text-size.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Text size calculator constructor.
3 | * @class
4 | * @classdesc Text size calculator class.
5 | * @param {SVGElement} el SVG element.
6 | */
7 | ge.TextSize = function TextSize(el) {
8 | el.setAttribute('style', 'stroke:none;fill:none');
9 | this.el = el;
10 | return this;
11 | };
12 |
13 | /**
14 | * Compute text width.
15 | * @param {string} text
16 | * @returns {number}
17 | */
18 | ge.TextSize.prototype.width = function length(text) {
19 | this.el.textContent = text;
20 | return this.el.getComputedTextLength();
21 | };
22 |
23 | /**
24 | * Compute text width and height.
25 | * @param {string} text
26 | * @returns {ge.Point}
27 | */
28 | ge.TextSize.prototype.size = function size(text) {
29 | this.el.textContent = text;
30 | var bbox = this.el.getBBox();
31 | return new ge.Point(bbox.width, bbox.height);
32 | };
33 |
34 | /**
35 | * Compute text container size.
36 | * @param {string} text
37 | * @returns {ge.ContainerSize}
38 | */
39 | ge.TextSize.prototype.containerSize = function containerSize(text) {
40 | var size = this.size(text);
41 | var words = text.split(/\s+/);
42 | var width;
43 | if(words.length == 1) {
44 | width = size.x;
45 | }
46 | else {
47 | width = 0;
48 | var self = this;
49 | words.forEach(function(word) {
50 | width = Math.max(width, self.width(word));
51 | });
52 | }
53 | return new ge.ContainerSize(
54 | width,
55 | size.y,
56 | size.x * size.y
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* eslint-disable no-unused-vars */
4 |
5 | /**
6 | * Graph editor module.
7 | * @namespace
8 | */
9 | var ge = exports;
10 |
11 | /**
12 | * Graph editor constructor.
13 | * @class
14 | * @classdesc Graph editor class.
15 | * @param {(SVGElement|Selector|D3Selection)} svg SVG element.
16 | * @param {ImportGraphData} data Graph data.
17 | * @param {GraphOptions} options Graph options.
18 | */
19 | ge.GraphEditor = function GraphEditor(svg, data, options) {
20 | if(!svg.select) {
21 | svg = d3.select(svg);
22 | }
23 |
24 | this.initOptions(options, svg)
25 | .initSvg(svg)
26 | .initData(data)
27 | .initState();
28 |
29 | /**
30 | * @readonly
31 | * @member {D3Dispatch}
32 | */
33 | this.dispatch = d3.dispatch(
34 | 'node-click', 'link-click',
35 | 'new-link-start', 'new-link-end', 'new-link-cancel',
36 | 'click',
37 | 'simulation-start', 'simulation-stop'
38 | );
39 |
40 | /**
41 | * Graph bounding box.
42 | * @readonly
43 | * @member {BBox}
44 | */
45 | this.bbox = [[0, 0], [0, 0]];
46 | /**
47 | * Graph zoom behavior.
48 | * @readonly
49 | * @member {D3Zoom}
50 | */
51 | this.zoom = this.zoomEvents(d3.zoom())
52 | .scaleExtent([this.options.scale.min, this.options.scale.max]);
53 | /**
54 | * Node drag behavior.
55 | * @readonly
56 | * @member {D3Drag}
57 | */
58 | this.drag = this.dragEvents(d3.drag());
59 | this.svg.call(this.zoom);
60 |
61 | /**
62 | * Window resize event handler.
63 | * @readonly
64 | * @member {function}
65 | */
66 | this.onresize = ge.bind(this, this.resized);
67 | window.addEventListener('resize', this.onresize);
68 |
69 | this.updateBBox();
70 |
71 | setTimeout(ge.bind(this, function() {
72 | this.update()
73 | .resized()
74 | .simulation(this.options.simulation.start);
75 | }), 10);
76 |
77 | return this;
78 | };
79 |
80 | /* eslint-enable no-unused-vars */
81 |
--------------------------------------------------------------------------------
/src/types.jsdoc:
--------------------------------------------------------------------------------
1 | /**
2 | * D3 selection.
3 | * @typedef {Object} D3Selection
4 | * @see [d3-selection]{@link https://github.com/d3/d3-selection#api-reference}
5 | */
6 |
7 | /**
8 | * D3 dispatch.
9 | * @typedef {Object} D3Dispatch
10 | * @see [d3.dispatch]{@link https://github.com/d3/d3-dispatch#api-reference}
11 | */
12 |
13 | /**
14 | * D3 force simulation.
15 | * @typedef {Object} D3Simulation
16 | * @see [d3-force]{@link https://github.com/d3/d3-force#api-reference}
17 | */
18 |
19 | /**
20 | * D3 drag behavior.
21 | * @typedef {Object} D3Drag
22 | * @see [d3.drag]{@link https://github.com/d3/d3-drag/blob/master/README.md#api-reference}
23 | */
24 |
25 | /**
26 | * D3 zoom behavior.
27 | * @typedef {Object} D3Zoom
28 | * @see [d3.zoom]{@link https://github.com/d3/d3-zoom/blob/master/README.md#api-reference}
29 | */
30 |
31 | /**
32 | * CSS selector.
33 | * @typedef {string} Selector
34 | */
35 |
36 | /**
37 | * Node or link ID.
38 | * @typedef {(number|string)} ID
39 | */
40 |
41 | /**
42 | * Imported node data.
43 | * @typedef {Object} ImportNodeData
44 | * @property {ID} [id] Node ID.
45 | * @property {number} [x] Center X coordinate.
46 | * @property {number} [y] Center Y coordinate.
47 | * @property {number} [size] Size.
48 | * @property {string} [title] Title.
49 | * @property {*} [data] User data.
50 | */
51 |
52 | /**
53 | * Imported link data.
54 | * @typedef {Object} ImportLinkData
55 | * @property {(ID|ImportNodeData)} source Source node.
56 | * @property {(ID|ImportNodeData)} target Target node.
57 | * @property {number} [size] Size.
58 | * @property {string} [title] Title.
59 | * @property {*} [data] User data.
60 | */
61 |
62 | /**
63 | * Imported graph data.
64 | * @typedef {Object} ImportGraphData
65 | * @property {Array} nodes Node data.
66 | * @property {Array} links Link data.
67 | */
68 |
69 | /**
70 | * Exported node data.
71 | * @typedef {Object} ExportNodeData
72 | * @property {ID} id Node ID.
73 | * @property {number} x Center X coordinate.
74 | * @property {number} y Center Y coordinate.
75 | * @property {number} size Size.
76 | * @property {string} title Title.
77 | * @property {*} data User data.
78 | */
79 |
80 | /**
81 | * Exported link data.
82 | * @typedef {Object} ExportLinkData
83 | * @property {ID} source Source node ID.
84 | * @property {ID} target Target node ID.
85 | * @property {number} [size] Size.
86 | * @property {string} [title] Title.
87 | * @property {*} [data] User data.
88 | */
89 |
90 | /**
91 | * Exported graph data.
92 | * @typedef {Object} ExportGraphData
93 | * @property {Array} nodes Node data.
94 | * @property {Array} links Link data.
95 | */
96 |
97 | /**
98 | * Graph data.
99 | * @typedef {Object} GraphData
100 | * @property {Array} nodes Node data.
101 | * @property {Array } links Link data.
102 | */
103 |
104 | /**
105 | * Node or link reference.
106 | * @typedef {(D3Selection|SVGElement|Node|Link|ID|Selector)} Reference
107 | */
108 |
109 | /**
110 | * Added nodes and links.
111 | * @typedef {Array} AddedObjects
112 | * @property {Array} 0 Nodes.
113 | * @property {Array } 1 Links.
114 | */
115 |
116 | /**
117 | * Selection.
118 | * @typedef {Object} Selection
119 | * @property {?Node} node Selected node.
120 | * @property {?Link} link Selected link.
121 | */
122 |
123 |
124 | /**
125 | * Graph options.
126 | * @typedef {Object} GraphOptions
127 | * @property {?string}
128 | * [id='ge' + ]
129 | * Graph SVG ID.
130 | * @property {boolean}
131 | * [directed=false]
132 | * Graph type.
133 | * @property {number}
134 | * [node.border=2]
135 | * Node border size.
136 | * @property {number}
137 | * [node.size.def=10]
138 | * Default node size.
139 | * @property {number}
140 | * [node.size.min=10]
141 | * Minimal node size.
142 | * @property {number|string}
143 | * [node.text.dx=0]
144 | * Node title X offset.
145 | * @property {number|string}
146 | * [node.text.dy=0]
147 | * Node title Y offset.
148 | * @property {boolean}
149 | * [node.text.inside=true]
150 | * Title inside/outside nodes.
151 | * @property {function}
152 | * [link.path={@link ge.defaultLinkPath}]
153 | * Link SVG path generator.
154 | * @property {number}
155 | * [link.size.def=2]
156 | * Default link size.
157 | * @property {number|string}
158 | * [link.text.dx=0]
159 | * Link title X offset.
160 | * @property {number|string}
161 | * [link.text.dy='1.1em']
162 | * Link title Y offset.
163 | * @property {string}
164 | * [link.text.offset='50%' for undirected graphs, '20%' for directed]
165 | * Link title start-offset in %.
166 | * @property {string}
167 | * [link.text.anchor='middle' for undirected graphs, 'start' for directed]
168 | * Link title text-anchor ('start' | 'middle' | 'end').
169 | * @property {number}
170 | * [link.arc.start=180]
171 | * Reflexive link start angle in degrees.
172 | * @property {number}
173 | * [link.arc.end=270]
174 | * Reflexive link end angle in degrees.
175 | * @property {function}
176 | * [simulation.create={@link ge.defaultSimulation}]
177 | * Simulation start function.
178 | * @property {boolean}
179 | * [simulation.start=false]
180 | * Start simulation after creating graph.
181 | * @property {boolean}
182 | * [simulation.stop=true]
183 | * Stop simulation when it converges.
184 | * @property {number}
185 | * [simulation.step=1]
186 | * DOM update interval in simulation ticks.
187 | * @property {number}
188 | * [transition.zoom=250]
189 | * Zoom transition duration in milliseconds.
190 | * @property {number}
191 | * [transition.drag=50]
192 | * Drag transition duration in milliseconds.
193 | * @property {number}
194 | * [transition.simulation=50]
195 | * Simulation transition duration in milliseconds.
196 | * @property {number}
197 | * [scale.min=0.25]
198 | * Minimal zoom level.
199 | * @property {number}
200 | * [scale.max=8.0]
201 | * Maximal zoom level.
202 | * @property {number}
203 | * [bbox.padding=80]
204 | * Graph bounding box padding.
205 | * @property {string}
206 | * [css.graph='ge-graph']
207 | * Graph CSS classes.
208 | * @property {string}
209 | * [css.digraph='ge-digraph']
210 | * Directed graph CSS classes.
211 | * @property {string}
212 | * [css.node='ge-node']
213 | * Node CSS classes.
214 | * @property {string}
215 | * [css.link='ge-link']
216 | * Link CSS classes.
217 | * @property {string}
218 | * [css.hide='ge-hide']
219 | * Hidden element CSS classes.
220 | * @property {string}
221 | * [css.dragline='ge-dragline']
222 | * Drag line CSS classes.
223 | * @property {string}
224 | * [css.selection.node='ge-selection']
225 | * Selected node CSS classes.
226 | * @property {string}
227 | * [css.selection.link='ge-link-selection']
228 | * Selected link CSS classes.
229 | */
230 |
--------------------------------------------------------------------------------
/src/umd/umd-end.js:
--------------------------------------------------------------------------------
1 | return exports;});
2 |
--------------------------------------------------------------------------------
/src/umd/umd-start.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function(root, factory) {
4 | if(typeof define === 'function' && define.amd) {
5 | define(['d3'], factory);
6 | }
7 | else if(typeof module === 'object' && module.exports) {
8 | module.exports = factory(require('d3'));
9 | }
10 | else {
11 | root.ge = factory(root.d3);
12 | }
13 | })(this, function(d3) {
14 | var exports = {};
15 |
--------------------------------------------------------------------------------
/tests/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": false,
3 | "globals": {
4 | "ge": true,
5 | "jasmine": true,
6 | "beforeEach": true,
7 | "beforeAll": true,
8 | "afterEach": true,
9 | "afterAll": true,
10 | "describe": true,
11 | "inject": true,
12 | "expect": true,
13 | "spyOn": true,
14 | "it": true,
15 | "element": true,
16 | "by": true,
17 | "browser": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/module.test.js:
--------------------------------------------------------------------------------
1 | describe('module', function() {
2 | it('should be defined', function() {
3 | expect(ge).toBeDefined();
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/tests/test-start.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | var exports = {};
3 | var ge = exports;
4 | /* eslint-enable no-unused-vars */
5 |
--------------------------------------------------------------------------------