@shawcx/avispa 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014-2023 Matthew Shaw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ Avispa
2
+ ======
3
+
4
+ Javascript Graph Layout and Interaction
5
+
6
+ Avispa (spanish for wasp) is a visualization framework. It is intended to be a
7
+ general purpose Javascript library for rendering graphs.
8
+
9
+ Depends on jQuery, Underscore.js, and Backbone.js
10
+
11
+ Demo at [http://shawcx.github.io/avispa/](http://shawcx.github.io/avispa/)
package/css/Avispa.css ADDED
@@ -0,0 +1,57 @@
1
+ g.group rect {
2
+ cursor: pointer;
3
+ fill: #fff;
4
+ opacity: 0.75;
5
+ stroke: #999;
6
+ stroke-width: 1px;
7
+ }
8
+ g.group rect.hover {
9
+ stroke: #333;
10
+ stroke-width: 2px;
11
+ }
12
+ g.group text {
13
+ cursor: pointer;
14
+ font: bold 8pt "Andale Mono", AndaleMono, monospace;
15
+ fill: #000;
16
+ text-anchor: left;
17
+ }
18
+
19
+ g.node circle {
20
+ cursor: pointer;
21
+ fill: #fff;
22
+ stroke: #999;
23
+ stroke-width: 1px;
24
+ }
25
+ g.node circle:hover {
26
+ stroke: #333;
27
+ stroke-width: 2px;
28
+ }
29
+ g.node text {
30
+ cursor: pointer;
31
+ font: bold 8pt "Andale Mono", AndaleMono, monospace;
32
+ fill: #000;
33
+ text-anchor: middle;
34
+ }
35
+
36
+ g.link line {
37
+ fill: none;
38
+ stroke: #666;
39
+ stroke-width: 3px;
40
+ stroke-linecap: round;
41
+ }
42
+ g.link path {
43
+ fill: none;
44
+ stroke: #999;
45
+ stroke-width: 3px;
46
+ stroke-linecap: round;
47
+ cursor: pointer;
48
+ stroke-dasharray: 8, 4;
49
+ }
50
+ g.link text {
51
+ font: bold 6pt "Andale Mono", AndaleMono, monospace;
52
+ fill: #666;
53
+ cursor: pointer;
54
+ text-anchor: middle;
55
+ }
56
+
57
+ /*# sourceMappingURL=Avispa.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sourceRoot":"","sources":["../scss/Avispa.scss"],"names":[],"mappings":"AAMI;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;;AAKJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;;AAKJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EAEA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA","file":"Avispa.css"}
package/js/Avispa.js ADDED
@@ -0,0 +1,230 @@
1
+ 'use strict'
2
+
3
+ const _ = require('underscore')
4
+ const Backbone = require('backbone')
5
+
6
+ const { Position } = require('./Types.js')
7
+ const { cancelEvent, $SVG } = require('./Utils.js')
8
+
9
+ const context = require('./Context.js')
10
+ const Group = require('./Group.js')
11
+ const Node = require('./Node.js')
12
+ const Link = require('./Link.js')
13
+
14
+ let normalizeWheel = function(event) {
15
+ if (event.wheelDelta) {
16
+ return event.wheelDelta / 120
17
+ }
18
+ if (event.detail) {
19
+ return event.detail / -3
20
+ }
21
+ return 0
22
+ }
23
+
24
+
25
+ const $SVG = function(name) {
26
+ return $(document.createElementNS('http://www.w3.org/2000/svg', name))
27
+ }
28
+
29
+
30
+ class Avispa extends Backbone.View {
31
+ events() {
32
+ return {
33
+ 'mousedown.avispa' : 'OnMouseDown',
34
+ 'mousemove.avispa' : 'OnMouseMove',
35
+ 'mouseup.avispa' : 'OnMouseUp',
36
+ 'mousewheel.avispa' : 'OnMouseWheel',
37
+ 'DOMMouseScroll.avispa' : 'OnMouseWheel',
38
+ 'contextmenu.avispa' : 'OnContextMenu',
39
+ }
40
+ }
41
+
42
+ initialize(options) {
43
+ context.a = this
44
+
45
+ _.bindAll(this, 'render',
46
+ 'OnMouseDown', 'OnMouseMove', 'OnMouseUp', 'OnMouseWheel', 'OnContextMenu')
47
+
48
+ this.scale = 1.0
49
+ this.links = {}
50
+ this.offset = null
51
+ this.dragItem = null
52
+ this.arrow = null
53
+
54
+ this.position = {
55
+ x: 0,
56
+ y: 0
57
+ }
58
+
59
+ this.zoom = {
60
+ step : 0.125,
61
+ min : 0.125,
62
+ max : 2.5,
63
+ }
64
+
65
+ this.$parent = this.$el.parent()
66
+
67
+ this.$pan = this.$el.find('g.pan')
68
+ this.$zoom = this.$el.find('g.zoom')
69
+
70
+ this.$groups = this.$el.find('g.groups')
71
+ this.$links = this.$el.find('g.links')
72
+ this.$objects = this.$el.find('g.objects')
73
+ this.$labels = this.$el.find('g.labels')
74
+
75
+ this.$pan.x = parseInt(window.innerWidth / 2)
76
+ this.$pan.y = parseInt(window.innerHeight / 2)
77
+
78
+ this.Pan(0,0)
79
+ }
80
+
81
+ Pan(dx, dy) {
82
+ this.$pan.x += dx
83
+ this.$pan.y += dy
84
+
85
+ this.$pan.attr('transform', `translate(${this.$pan.x}, ${this.$pan.y})`)
86
+ this.$parent.css('background-position', `${this.$pan.x}px ${this.$pan.y}px`)
87
+
88
+ return this
89
+ }
90
+
91
+ Scale(scale) {
92
+ this.scale = scale
93
+ this.$zoom.attr('transform', `scale(${scale})`)
94
+ return this
95
+ }
96
+
97
+ Zoom(delta) {
98
+ let scale = (delta == 0) ? 1.0 : this.scale + delta * this.zoom.step
99
+ if (scale >= this.zoom.min || scale <= this.zoom.max) {
100
+ this.Scale(scale)
101
+ }
102
+ return this
103
+ }
104
+
105
+ Point(event) {
106
+ // translates the client x,y into the surface x,y
107
+ let point = this.el.createSVGPoint()
108
+ point.x = event.clientX
109
+ point.y = event.clientY
110
+ point = point.matrixTransform(this.el.getScreenCTM().inverse())
111
+
112
+ // account for the current pan and scale
113
+ point.x = parseInt((point.x - this.$pan.x) / this.scale)
114
+ point.y = parseInt((point.y - this.$pan.y) / this.scale)
115
+
116
+ return [point.x, point.y]
117
+ }
118
+
119
+ OnMouseDown(event) {
120
+ if (this.arrow) {
121
+ this.arrow.Remove()
122
+ this.arrow = null
123
+ return cancelEvent(event)
124
+ }
125
+
126
+ switch (event.which) {
127
+ case 1:
128
+ this.LeftDown(event)
129
+ break
130
+ case 2:
131
+ this.MiddleDown(event)
132
+ break
133
+ case 3:
134
+ if (this.RightDown) {
135
+ this.RightDown(event)
136
+ }
137
+ break
138
+ }
139
+
140
+ return cancelEvent(event)
141
+ }
142
+
143
+ LeftDown(event) {
144
+ //if event.shiftKey
145
+ this.offset = [event.clientX, event.clientY]
146
+ }
147
+
148
+ MiddleDown(event) {
149
+ this.Pan(-this.$pan.x + window.innerWidth / 2, -this.$pan.y + window.innerHeight / 2)
150
+ this.Zoom(0)
151
+ //this.$('#zoomslider').slider('option', 'value', 1)
152
+ }
153
+
154
+ OnMouseMove(event) {
155
+ // drag the entire scene around
156
+ if (this.offset) {
157
+ this.Pan(event.clientX - this.offset[0], event.clientY - this.offset[1])
158
+ this.offset = [event.clientX, event.clientY]
159
+ }
160
+ else if (this.arrow) {
161
+ this.arrow.Drag(event)
162
+ }
163
+ else if (this.dragItem) {
164
+ this.dragItem.jitter++
165
+ if (this.dragItem.Drag) {
166
+ this.dragItem.Drag(event)
167
+ }
168
+ }
169
+
170
+ return cancelEvent(event)
171
+ }
172
+
173
+ OnMouseUp(event) {
174
+ this.offset = null
175
+
176
+ if (this.dragItem) {
177
+ if (this.dragItem.jitter < 3) {
178
+ switch (event.which) {
179
+ case 1:
180
+ this.dragItem.LeftClick(event)
181
+ break
182
+ case 2:
183
+ this.dragItem.MiddleClick(event)
184
+ break
185
+ case 3:
186
+ this.dragItem.RightClick(event)
187
+ break
188
+ }
189
+ }
190
+ else if(this.dragItem.MouseUp) {
191
+ this.dragItem.MouseUp(event)
192
+ }
193
+ this.dragItem = null
194
+ }
195
+ else {
196
+ switch (event.which) {
197
+ case 1:
198
+ this.LeftClick(event)
199
+ break
200
+ case 2:
201
+ this.MiddleClick(event)
202
+ break
203
+ case 3:
204
+ this.RightClick(event)
205
+ break
206
+ }
207
+ }
208
+
209
+ return cancelEvent(event)
210
+ }
211
+
212
+ OnMouseWheel(event) {
213
+ this.Zoom(normalizeWheel(event))
214
+ //this.$('#zoomslider').slider('option', 'value', this.scale)
215
+ return cancelEvent(event)
216
+ }
217
+
218
+ LeftClick(event) {}
219
+ MiddleClick(event) {}
220
+ RightClick(event) {}
221
+ OnContextMenu(event) {}
222
+ }
223
+
224
+ exports.Avispa = Avispa
225
+ exports.Position = Position
226
+ exports.Group = Group
227
+ exports.Node = Node
228
+ exports.Link = Link
229
+ exports.$SVG = $SVG
230
+ exports.cancelEvent = cancelEvent
@@ -0,0 +1,92 @@
1
+ 'use strict'
2
+
3
+ const _ = require('underscore')
4
+ const Backbone = require('backbone')
5
+
6
+ const { cancelEvent } = require('./Utils.js')
7
+ const context = require('./Context.js')
8
+
9
+
10
+ class BaseObject extends Backbone.View {
11
+ events() {
12
+ return {
13
+ 'mousedown' : 'OnMouseDown',
14
+ 'mouseenter' : 'OnMouseEnter',
15
+ 'mouseleave' : 'OnMouseLeave',
16
+ 'mouseup' : 'OnMouseUp',
17
+ 'contextmenu' : 'OnContextMenu',
18
+ }
19
+ }
20
+
21
+ delegateEvents(events) {
22
+ super.delegateEvents(events)
23
+ }
24
+
25
+ initialize(options) {
26
+ _.bindAll(this, 'OnMouseDown', 'OnMouseUp', 'OnContextMenu', 'render')
27
+
28
+ this.parent = options.parent
29
+ this.position = options.position
30
+ if (this.position) {
31
+ this.position.bind('change', this.render, this)
32
+ }
33
+
34
+ if (this.parent) {
35
+ this.offset = {
36
+ x: this.position.get('x'),
37
+ y: this.position.get('y'),
38
+ }
39
+ this.ParentDrag(this.parent.position)
40
+ this.parent.position.bind('change', this.ParentDrag, this)
41
+ }
42
+ }
43
+
44
+ size(w,h) {
45
+ this.position.set({w:w,h:h})
46
+ return this
47
+ }
48
+
49
+ ParentDrag(parentPosition) {
50
+ this.position.set({
51
+ x: this.offset.x + parentPosition.get('x'),
52
+ y: this.offset.y + parentPosition.get('y'),
53
+ })
54
+ }
55
+
56
+ OnMouseDown(event) {
57
+ this.jitter = 0
58
+
59
+ this.x1 = (event.clientX / context.a.scale) - this.position.get('x')
60
+ this.y1 = (event.clientY / context.a.scale) - this.position.get('y')
61
+
62
+ if (this.parent) {
63
+ this.ox1 = this.offset.x - this.position.get('x')
64
+ this.oy1 = this.offset.y - this.position.get('y')
65
+ }
66
+
67
+ // TODO: calculate the bounds of the parent element
68
+
69
+ if (event.shiftKey) {
70
+ this.$el.parent().append(this.$el)
71
+ }
72
+
73
+ context.a.dragItem = this
74
+
75
+ return cancelEvent(event)
76
+ }
77
+
78
+ Drag(event) {
79
+ let x = (event.clientX / context.a.scale) - this.x1
80
+ let y = (event.clientY / context.a.scale) - this.y1
81
+ this.position.set({x:x,y:y})
82
+ return cancelEvent(event)
83
+ }
84
+
85
+ OnMouseUp(event) {}
86
+ OnContextMenu(event) {}
87
+ LeftClick(event) {}
88
+ MiddleClick(event) {}
89
+ RightClick(event) {}
90
+ }
91
+
92
+ module.exports = BaseObject
package/js/Context.js ADDED
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ var context = {
4
+
5
+ }
6
+
7
+ module.exports = context
package/js/Group.js ADDED
@@ -0,0 +1,97 @@
1
+ 'use strict'
2
+
3
+ const _ = require('underscore')
4
+ const Backbone = require('backbone')
5
+
6
+ const { cancelEvent, $SVG } = require('./Utils.js')
7
+ const context = require('./Context.js')
8
+ const BaseObject = require('./BaseObject.js')
9
+
10
+
11
+ class Group extends BaseObject {
12
+ el() {
13
+ return $SVG('g').attr('class', 'group')
14
+ }
15
+
16
+ initialize(options) {
17
+ this.$rect = $SVG('rect')
18
+ .attr('width', options.position.get('w'))
19
+ .attr('height', options.position.get('h'))
20
+ .css('fill', options.position.get('fill'))
21
+ .appendTo(this.$el)
22
+
23
+ super.initialize(options)
24
+ }
25
+
26
+ render() {
27
+ this.$rect
28
+ .attr('width', this.position.get('w'))
29
+ .attr('height', this.position.get('h'))
30
+ .attr('x', this.position.get('x'))
31
+ .attr('y', this.position.get('y'))
32
+ return this
33
+ }
34
+
35
+ OnMouseEnter(event) {
36
+ if (!context.a.dragItem) {
37
+ this.$rect.attr('class', 'hover')
38
+ }
39
+ return cancelEvent(event)
40
+ }
41
+
42
+ OnMouseLeave(event) {
43
+ if (!context.a.dragItem) {
44
+ this.$rect.removeAttr('class')
45
+ }
46
+ return cancelEvent(event)
47
+ }
48
+
49
+ Drag(event) {
50
+ let x = (event.clientX / context.a.scale) - this.x1
51
+ let y = (event.clientY / context.a.scale) - this.y1
52
+
53
+ let newparentw = 0
54
+ let newparenth = 0
55
+
56
+ if (this.offset) {
57
+ this.offset.x = this.ox1 + x
58
+ this.offset.y = this.oy1 + y
59
+
60
+ let boundsx = this.parent.position.get('w') - this.position.get('w') - 10
61
+ let boundsy = this.parent.position.get('h') - this.position.get('h') - 10
62
+
63
+ if (this.offset.x < 10) {
64
+ this.offset.x = 10
65
+ x = this.parent.position.get('x') + 10
66
+ }
67
+ else if (this.offset.x > boundsx) {
68
+ //this.offset.x = boundsx
69
+ //x = this.parent.position.get('x') + boundsx
70
+ newparentw = this.offset.x - boundsx
71
+ x = this.offset.x //this.parent.position.get('x') + boundsx
72
+ }
73
+
74
+ if (this.offset.y < 30) {
75
+ this.offset.y = 30
76
+ y = this.parent.position.get('y') + 30
77
+ }
78
+ else if (this.offset.y > boundsy) {
79
+ //this.offset.y = boundsy
80
+ //y = this.parent.position.get('y') + boundsy
81
+ newparenth = this.offset.y - boundsy
82
+ y = this.offset.y //this.parent.position.get('x') + boundsx
83
+ }
84
+ }
85
+
86
+ this.position.set({x:x,y:y})
87
+ if (newparentw || newparenth) {
88
+ newparentw = this.parent.position.get('w') + newparentw
89
+ newparenth = this.parent.position.get('h') + newparenth
90
+ this.parent.Size(newparentw, newparenth)
91
+ }
92
+
93
+ return cancelEvent(event)
94
+ }
95
+ }
96
+
97
+ module.exports = Group
package/js/Link.js ADDED
@@ -0,0 +1,154 @@
1
+ 'use strict'
2
+
3
+ const _ = require('underscore')
4
+ const Backbone = require('backbone')
5
+
6
+ const { cancelEvent, $SVG } = require('./Utils.js')
7
+ const context = require('./Context.js')
8
+
9
+
10
+ class Link extends Backbone.View {
11
+ static RAD = 180.0 / Math.PI
12
+
13
+ el() {
14
+ return $SVG('g').attr('class', 'link')
15
+ }
16
+
17
+ events() {
18
+ return {
19
+ 'mousedown' : 'OnMouseDown',
20
+ 'mouseenter' : 'OnMouseEnter',
21
+ 'mouseleave' : 'OnMouseLeave',
22
+ 'contextmenu' : 'OnContextMenu',
23
+ }
24
+ }
25
+
26
+ initialize(options) {
27
+ this.path = $SVG('path')
28
+ .css('marker-end', 'url(#Arrow)')
29
+ .css('opacity', '0.5')
30
+ .appendTo(this.$el)
31
+
32
+ _.bindAll(this, 'render', 'OnMouseDown', 'OnMouseEnter', 'OnMouseLeave', 'OnContextMenu')
33
+
34
+ this.arc = new Backbone.Model({arc:10})
35
+ this.arc.bind('change', this.render, this)
36
+
37
+ this.left = options.left
38
+ this.right = options.right
39
+ this.left.position.bind('change', this.render, this)
40
+ this.right.position.bind('change', this.render, this)
41
+
42
+ this.render()
43
+
44
+ if (options.parent) {
45
+ options.parent.$links.append(this.$el)
46
+ }
47
+ }
48
+
49
+ update() {
50
+ //this.label.text(name)
51
+ return
52
+ }
53
+
54
+ render() {
55
+ if (!this.arc) { return this }
56
+
57
+ let arc = this.arc.get('arc')
58
+ let lx = this.left.position.get('x')
59
+ let ly = this.left.position.get('y')
60
+ let rx = this.right.position.get('x')
61
+ let ry = this.right.position.get('y')
62
+
63
+ // calculate the angle between 2 nodes
64
+ let ang = Math.atan2(rx - lx, ry - ly)
65
+
66
+ // bound the offset to about half the circle
67
+ let offset = Math.max(-1.5, Math.min(1.5, arc / 100))
68
+
69
+ // draw to the edge of the node
70
+ lx += 30 * Math.sin(ang + offset)
71
+ ly += 30 * Math.cos(ang + offset)
72
+ rx += -33 * Math.sin(ang - offset)
73
+ ry += -33 * Math.cos(ang - offset)
74
+
75
+ // calculate the the position for the quadratic bezier curve
76
+ let xc = ((lx + rx) >> 1) + arc * Math.cos(ang)
77
+ let yc = ((ly + ry) >> 1) - arc * Math.sin(ang)
78
+
79
+ let mx = xc - (arc>>1) * Math.cos(ang)
80
+ let my = yc + (arc>>1) * Math.sin(ang)
81
+
82
+ let rot = -(this.RAD * ang)
83
+ rot += (rot > 0 && rot < 180) ? -90 : 90
84
+
85
+ this.path.attr('d', "M #{lx} #{ly} Q #{xc} #{yc} #{rx} #{ry}")
86
+ //this.label.attr('x', mx).attr('y', my).attr('transform', "rotate(#{rot}, #{mx} #{my})")
87
+
88
+ return this
89
+ }
90
+
91
+ // Events
92
+
93
+ Drag(event) {
94
+ let [x,y] = context.a.Point(event)
95
+
96
+ let from_x = this.left.position.get('x')
97
+ let from_y = this.left.position.get('y')
98
+ let to_x = this.right.position.get('x')
99
+ let to_y = this.right.position.get('y')
100
+
101
+ let d = (to_x - from_x) * (y - from_y) - (to_y - from_y) * (x - from_x)
102
+
103
+ if (d) {
104
+ d = Math.pow(Math.abs(d), 0.5) * (d > 0) ? -1 : 1
105
+ }
106
+
107
+ if (!this.od && this.od != 0) {
108
+ this.od = d
109
+ }
110
+
111
+ // will trigger a call to render
112
+ this.arc.set('arc', Math.max(0, this.oarc + d - this.od))
113
+ }
114
+
115
+ OnMouseDown(event) {
116
+ this.jitter = 0
117
+
118
+ context.a.dragItem = this
119
+ this.oarc = this.arc.get('arc')
120
+ this.od = null
121
+
122
+ return cancelEvent(event)
123
+ }
124
+
125
+ MouseUp(event) {
126
+ if (this.jitter > 3) {
127
+ this.path.css('stroke-width', '3px')
128
+ }
129
+ }
130
+
131
+ OnMouseEnter() {
132
+ if (!context.a.dragItem) {
133
+ this.path.css('stroke-width', '6px')
134
+ }
135
+ }
136
+
137
+ OnMouseLeave() {
138
+ if (!context.a.dragItem) {
139
+ this.path.css('stroke-width', '3px')
140
+ }
141
+ }
142
+
143
+ LeftClick(event) {
144
+ if (event.shiftKey) {
145
+ this.arc.set('arc', 0)
146
+ }
147
+ }
148
+
149
+ OnContextMenu(event) {
150
+ }
151
+ }
152
+
153
+ module.exports = Link
154
+
package/js/Node.js ADDED
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+
3
+ const _ = require('underscore')
4
+ const Backbone = require('backbone')
5
+
6
+ const { cancelEvent, $SVG } = require('./Utils.js')
7
+ const context = require('./Context.js')
8
+ const BaseObject = require('./BaseObject.js')
9
+
10
+
11
+ class Node extends BaseObject {
12
+ el() {
13
+ return $SVG('g').attr('class', 'node')
14
+ }
15
+
16
+ initialize(options) {
17
+ _.bindAll(this, 'OnMouseDown', 'OnMouseUp', 'OnContextMenu', 'render')
18
+
19
+ this.$circle = $SVG('circle')
20
+ .attr('r', options.position.get('radius'))
21
+ .css('fill', options.position.get('fill'))
22
+ .appendTo(this.$el)
23
+
24
+ this.$label = $SVG('text')
25
+ .attr('dy', '0.5em')
26
+ .text(options.label)
27
+ .appendTo(this.$el)
28
+
29
+ super.initialize(options)
30
+
31
+ this.render()
32
+ }
33
+
34
+ render() {
35
+ this.$circle
36
+ .attr('cx', this.position.get('x'))
37
+ .attr('cy', this.position.get('y'))
38
+ this.$label
39
+ .attr('x', this.position.get('x'))
40
+ .attr('y', this.position.get('y'))
41
+ return this
42
+ }
43
+
44
+ OnMouseEnter(event) {
45
+ if (!context.a.dragItem) {
46
+ this.$circle.attr('class', 'hover')
47
+ }
48
+ return cancelEvent(event)
49
+ }
50
+
51
+ OnMouseLeave(event) {
52
+ if (!context.a.dragItem) {
53
+ this.$circle.removeAttr('class')
54
+ }
55
+ return cancelEvent(event)
56
+ }
57
+
58
+ Drag(event) {
59
+ let x = (event.clientX / context.a.scale) - this.x1
60
+ let y = (event.clientY / context.a.scale) - this.y1
61
+
62
+ if (this.offset) {
63
+ this.offset.x = this.ox1 + x
64
+ this.offset.y = this.oy1 + y
65
+
66
+ if (this.offset.x < 0) {
67
+ this.offset.x = 0
68
+ x = this.parent.position.get('x')
69
+ }
70
+ else if (this.offset.x > this.parent.position.get('w')) {
71
+ this.offset.x = this.parent.position.get('w')
72
+ x = this.parent.position.get('x') + this.parent.position.get('w')
73
+ }
74
+ if (this.offset.y < 0) {
75
+ this.offset.y = 0
76
+ y = this.parent.position.get('y')
77
+ }
78
+ else if (this.offset.y > this.parent.position.get('h')) {
79
+ this.offset.y = this.parent.position.get('h')
80
+ y = this.parent.position.get('y') + this.parent.position.get('h')
81
+ }
82
+ }
83
+
84
+ this.position.set({x:x,y:y})
85
+
86
+ return cancelEvent(event)
87
+ }
88
+ }
89
+
90
+ module.exports = Node
package/js/Types.js ADDED
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const Backbone = require('backbone')
4
+
5
+ class Position extends Backbone.Model {}
6
+ class Arc extends Backbone.Model {}
7
+
8
+ exports.Position = Position
9
+ exports.Arc = Arc
package/js/Utils.js ADDED
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const $ = require('jquery')
4
+
5
+
6
+ exports.cancelEvent = function(event) {
7
+ event.preventDefault()
8
+ event.stopPropagation()
9
+ return false
10
+ }
11
+
12
+ exports.normalizeWheel = function(event) {
13
+ if (event.wheelDelta) return event.wheelDelta / 120
14
+ if (event.detail) return event.detail / -3
15
+ return 0
16
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@shawcx/avispa",
3
+ "version": "1.0.0",
4
+ "description": "SVG + Websockets",
5
+ "main": "js/Avispa.js",
6
+ "sass": "scss/Avispa.scss",
7
+ "style": "css/Avispa.css",
8
+ "scripts": {
9
+ "build": "npx sass scss/Avispa.scss css/Avispa.css"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/shawcx/avispa.git"
14
+ },
15
+ "author": "Matthew Shaw",
16
+ "license": "ISC",
17
+ "bugs": {
18
+ "url": "https://github.com/shawcx/avispa/issues"
19
+ },
20
+ "homepage": "https://github.com/shawcx/avispa#readme",
21
+ "dependencies": {
22
+ "backbone": "^1.4.1",
23
+ "jquery": "^3.6.3",
24
+ "underscore": "^1.13.6"
25
+ },
26
+ "devDependencies": {
27
+ "sass": "^1.58.2"
28
+ }
29
+ }
@@ -0,0 +1,73 @@
1
+
2
+ $mono: "Andale Mono", AndaleMono, monospace;
3
+
4
+ g.group {
5
+ //TODO: elements .user-select();
6
+
7
+ rect {
8
+ cursor: pointer;
9
+ fill: #fff;
10
+ opacity: 0.75;
11
+ stroke: #999;
12
+ stroke-width: 1px;
13
+ }
14
+
15
+ rect.hover {
16
+ stroke: #333;
17
+ stroke-width: 2px;
18
+ }
19
+
20
+ text {
21
+ cursor: pointer;
22
+ font: bold 8pt $mono;
23
+ fill: #000;
24
+ text-anchor: left;
25
+ }
26
+ }
27
+
28
+ g.node {
29
+ circle {
30
+ cursor: pointer;
31
+ fill: #fff;
32
+ stroke: #999;
33
+ stroke-width: 1px;
34
+ }
35
+
36
+ circle:hover {
37
+ stroke: #333;
38
+ stroke-width: 2px;
39
+ }
40
+
41
+ text {
42
+ cursor: pointer;
43
+ font: bold 8pt $mono;
44
+ fill: #000;
45
+ text-anchor: middle;
46
+ }
47
+ }
48
+
49
+ g.link {
50
+ line {
51
+ fill: none;
52
+ stroke: #666;
53
+ stroke-width: 3px;
54
+ stroke-linecap: round;
55
+ }
56
+
57
+ path {
58
+ fill: none;
59
+ //opacity: 0.5;
60
+ stroke: #999;
61
+ stroke-width: 3px;
62
+ stroke-linecap: round;
63
+ cursor: pointer;
64
+ stroke-dasharray: 8,4;
65
+ }
66
+
67
+ text {
68
+ font: bold 6pt $mono;
69
+ fill: #666;
70
+ cursor: pointer;
71
+ text-anchor: middle;
72
+ }
73
+ }