neo-viz 1.0.1

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.
Files changed (52) hide show
  1. data/.gitignore +9 -0
  2. data/.livereload +20 -0
  3. data/.rspec +2 -0
  4. data/.rvmrc +3 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +19 -0
  7. data/README.md +120 -0
  8. data/Rakefile +12 -0
  9. data/bin/neo-viz +12 -0
  10. data/config.ru +35 -0
  11. data/lib/neo-viz.rb +185 -0
  12. data/lib/neo-viz/version.rb +6 -0
  13. data/neo-viz.gemspec +37 -0
  14. data/public/coffeescripts/app_context.coffee +89 -0
  15. data/public/coffeescripts/canvas_util.coffee +68 -0
  16. data/public/coffeescripts/event_broker.coffee +16 -0
  17. data/public/coffeescripts/filters.coffee +113 -0
  18. data/public/coffeescripts/main.coffee.erb +132 -0
  19. data/public/coffeescripts/neo4j.coffee +90 -0
  20. data/public/coffeescripts/renderer.coffee +141 -0
  21. data/public/coffeescripts/space.coffee +81 -0
  22. data/public/images/ajax-loader.gif +0 -0
  23. data/public/javascripts/data.js +45 -0
  24. data/public/javascripts/data2.js +1287 -0
  25. data/public/javascripts/main.sprockets.js +9 -0
  26. data/public/lib/arbor/arbor-tween.js +86 -0
  27. data/public/lib/arbor/arbor.js +67 -0
  28. data/public/lib/jQuery/jquery-1.6.1.min.js +18 -0
  29. data/public/lib/jasmine-1.1.0/MIT.LICENSE +20 -0
  30. data/public/lib/jasmine-1.1.0/jasmine-html.js +190 -0
  31. data/public/lib/jasmine-1.1.0/jasmine.css +166 -0
  32. data/public/lib/jasmine-1.1.0/jasmine.js +2476 -0
  33. data/public/lib/jasmine-1.1.0/jasmine_favicon.png +0 -0
  34. data/public/lib/stdlib/stdlib.js +115 -0
  35. data/public/lib/sylvester-0.1.3/CHANGELOG.txt +29 -0
  36. data/public/lib/sylvester-0.1.3/sylvester.js +1 -0
  37. data/public/lib/sylvester-0.1.3/sylvester.js.gz +0 -0
  38. data/public/lib/sylvester-0.1.3/sylvester.src.js +1254 -0
  39. data/public/scss/main.scss +152 -0
  40. data/public/scss/mixins.scss +37 -0
  41. data/spec/coffeescripts/canvas_util_spec.coffee +4 -0
  42. data/spec/coffeescripts/filters_spec.coffee +37 -0
  43. data/spec/coffeescripts/neo4j_spec.coffee +76 -0
  44. data/spec/neo_viz_spec.rb +48 -0
  45. data/spec/spec_helper.rb +17 -0
  46. data/spec/support/struct_matcher.rb +117 -0
  47. data/views/_filters.haml +22 -0
  48. data/views/_partial.haml +39 -0
  49. data/views/embedded.haml +15 -0
  50. data/views/index.haml +19 -0
  51. data/views/jasmine_specs_runner.haml +50 -0
  52. metadata +236 -0
@@ -0,0 +1,152 @@
1
+ @import 'mixins.scss';
2
+
3
+ $page-width: 760px;
4
+ $body-color-top: #ffe;
5
+ $body-color-bottom: #dfa;
6
+
7
+ $default-radius: .8em;
8
+
9
+ $header-font: normal 24px Verdana, sans-serif;
10
+
11
+ .clear { clear: both; }
12
+
13
+ .neoviz_body {
14
+ font-family: Century Gothic, sans-serif;
15
+ font-size: 14px;
16
+ line-height: 18px;
17
+ @include background-gradient($body-color-top, $body-color-bottom);
18
+ }
19
+
20
+ .neoviz_header {
21
+ p {
22
+ float: right;
23
+ width: 240px;
24
+ margin-right: 1em;
25
+ margin-top: 0;
26
+ padding: 1em;
27
+ @include sym-border-radius($default-radius);
28
+ color: white;
29
+ background: gray;
30
+ }
31
+
32
+ td{
33
+ vertical-align:top;
34
+ }
35
+
36
+ form {
37
+ float: left;
38
+ padding: 0.4em;
39
+ margin: 1em;
40
+ @include sym-border-radius($default-radius);
41
+ color: black;
42
+ border: gray solid 2px;
43
+
44
+ input[type=number] {
45
+ width: 3em;
46
+ }
47
+ textarea {
48
+ width: 20em;
49
+ height: 4em;
50
+ }
51
+ }
52
+ }
53
+
54
+ #console {
55
+ width: 600px;
56
+ height: 100px;
57
+ }
58
+
59
+ #consoleOutputArea{
60
+ width: 600px;
61
+ height: 50px;
62
+ }
63
+
64
+ .viewport {
65
+ width: 960px;
66
+ height: 500px;
67
+ }
68
+
69
+ #container {
70
+ clear: both;
71
+ margin: 1em 1em 1em 1em;
72
+ border: 2px gray solid;
73
+ padding: 2em;
74
+ @include sym-border-radius($default-radius);
75
+ @include background-gradient($body-color-top, $body-color-bottom);
76
+
77
+ td {
78
+ vertical-align: top;
79
+ }
80
+ }
81
+
82
+
83
+ .details {
84
+
85
+ vertical-align: bottom;
86
+
87
+ table {
88
+ border: 2px solid gray;
89
+ border-spacing: 0;
90
+
91
+ tr {
92
+ margin: 0;
93
+ border-width: 0;
94
+ padding: 0;
95
+
96
+ &:nth-child(2n) { background-color: white; }
97
+ &:nth-child(2n+1) { background-color: lightgray; }
98
+ }
99
+ td {
100
+ border: 0 none black;
101
+ padding: 0.3em;
102
+ margin: 0;
103
+
104
+ &:first-child {
105
+ text-align: right;
106
+ padding-left: 1em;
107
+ font-weight: bold;
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ .filters{
114
+ table {
115
+ border: 1px solid gray;
116
+ border-spacing: 0;
117
+ background-color: #eee;
118
+
119
+ tr {
120
+ margin: 0;
121
+ border-width: 0;
122
+ padding: 0;
123
+
124
+ }
125
+ td {
126
+ border: 0 none black;
127
+ padding: 0.3em;
128
+ margin: 0;
129
+
130
+ &:first-child {
131
+ text-align: left;
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ .relationsFilter{
138
+ table {
139
+ tr {
140
+ &:nth-child(2n) { background-color: white; }
141
+ &:nth-child(2n+1) { background-color: lightgray; }
142
+ }
143
+ td {
144
+
145
+ &:first-child {
146
+ text-align: right;
147
+ padding-left: 1em;
148
+ }
149
+ }
150
+ }
151
+ }
152
+
@@ -0,0 +1,37 @@
1
+ @mixin reset {
2
+ html, body, div, span, applet, object, iframe,
3
+ h1, h2, h3, h4, h5, h6, tr, th, td {
4
+ margin: 0;
5
+ padding: 0;
6
+ border: 0;
7
+ outline: 0;
8
+ vertical-align: baseline;
9
+ background: transparent;
10
+ }
11
+ table {
12
+ border-collapse: collapse;
13
+ border-spacing: 0;
14
+ }
15
+
16
+ }
17
+
18
+ @mixin border-radius($tl, $tr, $br, $bl) {
19
+ -webkit-border-radius: $tl $tr $br $bl;
20
+ -moz-border-radius: $tl $tr $br $bl;
21
+ border-radius: $tl $tr $br $bl;
22
+ }
23
+
24
+ @mixin sym-border-radius($r) {
25
+ @include border-radius($r, $r, $r, $r);
26
+ }
27
+
28
+ @mixin background-gradient($top, $bottom) {
29
+ background: -webkit-gradient(linear, left top, left bottom, from($top), to($bottom));
30
+ background: -webkit-linear-gradient(top, $top, $bottom);
31
+ background: -moz-linear-gradient(top, $top, $bottom);
32
+ background: -ms-linear-gradient(top, $top, $bottom);
33
+ background: -o-linear-gradient(top, $top, $bottom);
34
+ background: linear-gradient(top, $top, $bottom);
35
+ }
36
+
37
+
@@ -0,0 +1,4 @@
1
+ describe 'canvas_util', ->
2
+ describe '#centerToEdge', ->
3
+ it 'calculates the edge from the center', ->
4
+ expect(CanvasUtil.centerToEdge(200, 30)).toEqual 185
@@ -0,0 +1,37 @@
1
+ describe 'filters', ->
2
+
3
+ it 'can build hiddenNodeData for circular subgraph hidden by user', ->
4
+ # n0--->---n1-----<-----n3
5
+ # \-->--n2->--/
6
+ nodeData = [{"id":0},{"id":1},{"id":2},{"id":3}]
7
+ relData = [{"id":0, "start_node":0, "end_node":1, "data":{"rel_type":""}},
8
+ {"id":1, "start_node":1, "end_node":2, "data":{"rel_type":""}},
9
+ {"id":2, "start_node":2, "end_node":3, "data":{"rel_type":""}}
10
+ {"id":3, "start_node":3, "end_node":1, "data":{"rel_type":""}}]
11
+ graph = new Graph(nodeData, relData)
12
+
13
+ activatedNode = graph.load(0)
14
+ relsHiddenByUser = graph.relationships[0..0]
15
+ hiddenNodeData = test_buildHiddenNodeData(graph, activatedNode, relsHiddenByUser)
16
+
17
+ expect(hiddenNodeData.nodeIds.length).toEqual(3)
18
+ expect(hiddenNodeData.relIds.length).toEqual(4) #4: rels hidden by user should be included
19
+
20
+ it 'can build hiddenNodeData for 6 node mesh with one hidden rel', ->
21
+ nodeData = [{"id":0},{"id":1},{"id":2},{"id":3},{"id":4},{"id":5}]
22
+ relData = [{"id":0, "start_node":0, "end_node":1, "data":{"rel_type":""}},
23
+ {"id":1, "start_node":1, "end_node":2, "data":{"rel_type":""}},
24
+ {"id":2, "start_node":2, "end_node":3, "data":{"rel_type":""}},
25
+ {"id":3, "start_node":3, "end_node":0, "data":{"rel_type":""}},
26
+ {"id":4, "start_node":3, "end_node":4, "data":{"rel_type":""}},
27
+ {"id":5, "start_node":4, "end_node":5, "data":{"rel_type":""}},
28
+ {"id":6, "start_node":5, "end_node":0, "data":{"rel_type":""}}]
29
+ graph = new Graph(nodeData, relData)
30
+
31
+ activatedNode = graph.load(0)
32
+ relsHiddenByUser = graph.relationships[6..6]
33
+ hiddenNodeData = test_buildHiddenNodeData(graph, activatedNode, relsHiddenByUser)
34
+
35
+ expect(hiddenNodeData.nodeIds.length).toEqual(0)
36
+ expect(hiddenNodeData.relIds.length).toEqual(1)
37
+ expect(hiddenNodeData.relIds[0]).toEqual(6)
@@ -0,0 +1,76 @@
1
+ describe 'neo4j', ->
2
+ describe 'Graph', ->
3
+
4
+ describe 'Node', ->
5
+
6
+ graph = null
7
+ node = null
8
+
9
+ beforeEach ->
10
+ nodeData = [{"id":0},{"id":1},{"id":2}]
11
+ relData = [{"id":0, "start_node":0, "end_node":1, "data":{"rel_type":"friend"}},
12
+ {"id":1, "start_node":1, "end_node":2, "data":{"rel_type":"enemy"}},
13
+ {"id":2, "start_node":2, "end_node":0, "data":{"rel_type":"brother"}},
14
+ {"id":3, "start_node":0, "end_node":2, "data":{"rel_type":"brother"}}]
15
+ graph = new Graph(nodeData, relData)
16
+ node = graph.load(0)
17
+
18
+ it 'has incoming relationships', ->
19
+ expect(node.incoming()[0].type).toEqual("brother")
20
+
21
+ it 'has outgoing relationships', ->
22
+ expect(node.outgoing()[0].type).toEqual("friend")
23
+
24
+ it 'has incoming relationships of certain type', ->
25
+ expect(node.incoming(["foo"]).length).toEqual(0)
26
+ expect(node.incoming(["brother"]).length).toEqual(1)
27
+
28
+ it 'has outgoing relationships of certain type', ->
29
+ expect(node.outgoing(["foo"]).length).toEqual(0)
30
+ expect(node.outgoing(["friend", "brother"]).length).toEqual(2)
31
+
32
+ describe 'Connectivity', ->
33
+
34
+ graph = null
35
+
36
+ beforeEach ->
37
+ nodeData = [{"id":0},{"id":1}]
38
+ relData = [{"id":0, "start_node":0, "end_node":1, "data":{"rel_type":"networks"}}]
39
+ graph = new Graph(nodeData, relData)
40
+
41
+ it 'knows two neighbors are connected', ->
42
+ expect(graph.areConnected(graph.load(0), graph.load(1))).toEqual(true)
43
+
44
+ it 'only considers active relations', ->
45
+ expect(graph.areConnected(graph.load(0), graph.load(1), [])).toEqual(false)
46
+
47
+ it 'knows circular triplet is connected when one relationship is inactive', ->
48
+ # n0-----<-----n2
49
+ # \-->--n1->--/
50
+ nodeData = [{"id":0},{"id":1},{"id":2}]
51
+ relData = [{"id":0, "start_node":0, "end_node":1, "data":{"rel_type":""}},
52
+ {"id":1, "start_node":1, "end_node":2, "data":{"rel_type":""}},
53
+ {"id":2, "start_node":2, "end_node":0, "data":{"rel_type":""}}]
54
+ graph = new Graph(nodeData, relData)
55
+
56
+ # deactivate connection between n0 and n1:
57
+ activeRels = graph.relationships[1..2]
58
+
59
+ expect(graph.areConnected(graph.load(1), graph.load(0), activeRels)).toEqual(true)
60
+
61
+ it 'knows a 6 node mesh is connected when one relationship is inactive', ->
62
+ nodeData = [{"id":0},{"id":1},{"id":2},{"id":3},{"id":4},{"id":5}]
63
+ relData = [{"id":0, "start_node":0, "end_node":1, "data":{"rel_type":""}},
64
+ {"id":1, "start_node":1, "end_node":2, "data":{"rel_type":""}},
65
+ {"id":2, "start_node":2, "end_node":3, "data":{"rel_type":""}},
66
+ {"id":3, "start_node":3, "end_node":0, "data":{"rel_type":""}},
67
+ {"id":4, "start_node":3, "end_node":4, "data":{"rel_type":""}},
68
+ {"id":5, "start_node":4, "end_node":5, "data":{"rel_type":""}},
69
+ {"id":6, "start_node":5, "end_node":0, "data":{"rel_type":""}}]
70
+ graph = new Graph(nodeData, relData)
71
+
72
+ # deactivate connection between n0 and n5:
73
+ activeRels = graph.relationships[0..5]
74
+ console.dir activeRels
75
+ expect(graph.areConnected(graph.load(0), graph.load(5), activeRels)).toEqual(true)
76
+
@@ -0,0 +1,48 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Neo::Viz::App do
4
+
5
+ def app
6
+ Neo::Viz::App
7
+ end
8
+
9
+ specify "get / should return OK" do
10
+ get '/'
11
+ last_response.should be_redirect
12
+ end
13
+
14
+ specify 'get /node-count should return number of nodes' do
15
+ get '/node-count'
16
+ last_response.body.should == '155'
17
+ end
18
+
19
+ context 'get /nodes/0' do
20
+ before do
21
+ get '/nodes/0'
22
+ end
23
+
24
+ let(:body) { last_response.body }
25
+
26
+ it 'should return the root node' do
27
+ body.should struct_match({
28
+ :nodes => [
29
+ { :id => 0, :data => {} },
30
+ { :id => 5, :data => {} },
31
+ { :id => 1, :data => {} },
32
+ { :id => 2, :data => {} },
33
+ { :id => 6, :data => {} },
34
+ { :id => 4, :data => {} },
35
+ { :id => 3, :data => {} }
36
+ ],
37
+ :rels => [
38
+ {:id => 4, :start_node => 0, :end_node => 5, :data => { :rel_type => 'BirdiesBackend::User'}},
39
+ {},
40
+ {},
41
+ {},
42
+ {},
43
+ {:id => 2, :start_node => 0, :end_node => 3, :data => { :rel_type => 'BirdiesBackend::Tag'}}
44
+ ]
45
+ })
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/..'
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rack/test'
5
+ require 'support/struct_matcher'
6
+ require 'neo-viz'
7
+
8
+
9
+ class Neo::Viz::App
10
+ set :environment, :test
11
+ end
12
+
13
+ RSpec.configure do |c|
14
+ c.include Rack::Test::Methods
15
+ c.mock_with :rspec
16
+ c.include Matchers
17
+ end
@@ -0,0 +1,117 @@
1
+ require 'json'
2
+
3
+ module Matchers
4
+ # Compares two structures
5
+ #
6
+ # If the actual structure is a String it will be converted with JSON.parse
7
+ # If the expected structure is
8
+ # A Hash is compared by:
9
+ # * If the expected value of a key is nil, it must not be present in the actual
10
+ # * If the expected key is missing, it is allowed in the actual
11
+ # * Otherwise, the values are compared as if they were structures themselves.
12
+ # An Array is compared by:
13
+ # * If the expected value is an empty array, any actual is OK
14
+ # * Otherwise, the lengths AND the values are compared
15
+ # A Regexp is compare by =~
16
+ # All other values are compare by ==
17
+ #
18
+ class StructMatcher
19
+
20
+ def initialize(expected)
21
+ @expected = expected
22
+ @errors = []
23
+ end
24
+
25
+ def matches?(actual)
26
+ if actual.kind_of? String
27
+ @actual = JSON.parse(actual)
28
+ else
29
+ @actual = actual
30
+ end
31
+ match_struct(@expected, @actual, '')
32
+ @errors.size == 0
33
+ end
34
+
35
+ def match_struct(expected, actual, prop)
36
+ if expected.kind_of? Hash
37
+ match_hash(expected, actual, prop)
38
+ elsif expected.kind_of? Array
39
+ match_array(expected, actual, prop)
40
+ elsif expected.kind_of? Regexp
41
+ match_regexp(expected, actual, prop)
42
+ elsif expected.kind_of? Class
43
+ match_class(expected, actual, prop)
44
+ else
45
+ match_equals(expected, actual, prop)
46
+ end
47
+ end
48
+
49
+ def match_hash(expected, actual, prop)
50
+ unless actual.kind_of? Hash
51
+ @errors << "(#{prop}) expected Hash, actual #{actual.class}"
52
+ return
53
+ end
54
+ expected.each do |expected_key, expected_value|
55
+ if expected_value.nil? #value nil means that the key should not exist
56
+ if actual.has_key? expected_key or actual.has_key? expected_key.to_s
57
+ @errors << "(#{prop}) expected #{expected_key} to not exists: #{actual[expected_key]}"
58
+ next
59
+ end
60
+ else
61
+ unless actual.has_key? expected_key or actual.has_key? expected_key.to_s
62
+ @errors << "(#{prop}) expected #{expected_key} to exists"
63
+ next
64
+ end
65
+ actual_value = actual[expected_key.to_s]
66
+ match_struct(expected_value, actual_value, "#{prop}-#{expected_key}")
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ def match_array(expected, actual, prop)
73
+ unless actual.kind_of? Array
74
+ @errors << "(#{prop}) expected Array, actual #{actual.class}"
75
+ return
76
+ end
77
+ if actual.size != expected.size and expected.size > 0
78
+ @errors << "(#{prop}) expected size #{expected.size}, actual #{actual.size}"
79
+ end
80
+ min = [actual.size, expected.size].min
81
+ (0...min).each do |i|
82
+ match_struct(expected[i], actual[i], "#{prop}[#{i}]")
83
+ end
84
+ end
85
+
86
+ def match_regexp(expected, actual, prop)
87
+ unless actual.respond_to? :=~
88
+ @errors << "(#{prop}) cannot be matched with =~: expected #{expected.inspect}, actual #{actual.inspect}"
89
+ return
90
+ end
91
+ unless actual =~ expected
92
+ @errors << "(#{prop}) no match: expected #{expected.inspect}, actual #{actual.inspect}"
93
+ end
94
+ end
95
+
96
+ def match_equals(expected, actual, prop)
97
+ unless actual == expected
98
+ @errors << "(#{prop}) not equal: expected #{expected.inspect}, actual #{actual.inspect}"
99
+ end
100
+ end
101
+
102
+ def match_class(expected, actual, prop)
103
+ unless actual.kind_of? expected
104
+ @errors << "(#{prop}) not a kind of: expected #{expected.inspect}, actual #{actual.class.inspect}"
105
+ end
106
+ end
107
+
108
+ def failure_message_for_should
109
+ "expected\n#{@actual.inspect}\nto match\n#{@expected.inspect}\nerrors\n#{@errors.join(%{\n})}"
110
+ end
111
+ end
112
+
113
+ def struct_match(expected)
114
+ StructMatcher.new(expected)
115
+ end
116
+
117
+ end