neo-viz 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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