rubrowser 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/Gemfile.lock +1 -1
- data/MIT-LICENSE +20 -0
- data/lib/data.rb +9 -20
- data/lib/formatter/json.rb +46 -0
- data/lib/parser/definition/base.rb +35 -0
- data/lib/parser/definition/class.rb +10 -0
- data/lib/parser/definition/module.rb +10 -0
- data/lib/parser/directory.rb +2 -2
- data/lib/parser/file.rb +66 -47
- data/lib/parser/relation/base.rb +60 -0
- data/lib/rubrowser.rb +1 -1
- data/lib/server.rb +21 -9
- data/public/css/application.css +11 -9
- data/public/javascript/application.js +29 -29
- data/public/javascript/lodash.js +16733 -0
- data/readme.md +7 -6
- data/views/index.erb +8 -4
- metadata +10 -4
- data/lib/d3.rb +0 -33
- data/lib/tree.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 533883a63e4f0656b260f7495ea2a62acc82ef64
|
4
|
+
data.tar.gz: 2bb665c87d91821898dc4e5965289291dc79809b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 457739897ae0990378db89a4c8bdf95d6f5e21ff3d2b5bef310d21318e5c33f8216a0df006aa954710a8b0e32fce86995e8d7ac40e6439ff8bbd4d0c4ad38768
|
7
|
+
data.tar.gz: 3923cc2724f4f0602ef7a8651ad84852274f4536f53c62ac8e9188d64573178b228cd3a7d31de109343522b9491ee8206976fd72b5d30b79c0565b2409093f2a
|
data/.rubocop.yml
ADDED
data/Gemfile.lock
CHANGED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Emad Elsaid
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/data.rb
CHANGED
@@ -1,30 +1,29 @@
|
|
1
1
|
require 'parser/factory'
|
2
|
-
require 'tree'
|
3
|
-
require 'd3'
|
4
2
|
|
5
3
|
module Rubrowser
|
6
4
|
class Data
|
7
|
-
def initialize(
|
8
|
-
@files =
|
5
|
+
def initialize(files)
|
6
|
+
@files = files
|
9
7
|
@parsed = false
|
8
|
+
parse
|
10
9
|
end
|
11
10
|
|
12
|
-
def
|
13
|
-
@_constants ||=
|
11
|
+
def definitions
|
12
|
+
@_constants ||= parsers.map(&:definitions).reduce(:+).to_a
|
14
13
|
end
|
15
14
|
|
16
|
-
def
|
17
|
-
@
|
15
|
+
def relations
|
16
|
+
@_relations ||= parsers.map(&:relations).reduce(:+).to_a
|
18
17
|
end
|
19
18
|
|
19
|
+
private
|
20
|
+
|
20
21
|
def parse
|
21
22
|
return if parsed?
|
22
23
|
parsers.each(&:parse)
|
23
24
|
@parsed = true
|
24
25
|
end
|
25
26
|
|
26
|
-
private
|
27
|
-
|
28
27
|
attr_reader :files, :parsed
|
29
28
|
alias parsed? parsed
|
30
29
|
|
@@ -33,15 +32,5 @@ module Rubrowser
|
|
33
32
|
Rubrowser::Parser::Factory.build(file)
|
34
33
|
end
|
35
34
|
end
|
36
|
-
|
37
|
-
def d3
|
38
|
-
@_d3 ||= Rubrowser::D3.new(tree)
|
39
|
-
end
|
40
|
-
|
41
|
-
def tree
|
42
|
-
parse
|
43
|
-
@_tree ||= Rubrowser::Tree.from_parsers(parsers)
|
44
|
-
end
|
45
|
-
|
46
35
|
end
|
47
36
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rubrowser
|
4
|
+
module Formatter
|
5
|
+
class JSON
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
{
|
12
|
+
definitions: data.definitions.map { |d| definition_as_json(d) },
|
13
|
+
relations: data.relations.map { |r| relation_as_json(r, data.definitions) }
|
14
|
+
}.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :data
|
20
|
+
|
21
|
+
def definition_as_json(definition)
|
22
|
+
{
|
23
|
+
type: demoularize(definition.class.name),
|
24
|
+
namespace: definition.to_s,
|
25
|
+
file: definition.file,
|
26
|
+
line: definition.line
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def relation_as_json(relation, definitions)
|
31
|
+
{
|
32
|
+
type: demoularize(relation.class.name),
|
33
|
+
namespace: relation.namespace.to_s,
|
34
|
+
resolved_namespace: relation.resolve(definitions).to_s,
|
35
|
+
caller: relation.caller_namespace.to_s,
|
36
|
+
file: relation.file,
|
37
|
+
line: relation.line
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def demoularize(class_name)
|
42
|
+
class_name.split('::').last || ''
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Rubrowser
|
2
|
+
module Parser
|
3
|
+
module Definition
|
4
|
+
class Base
|
5
|
+
attr_reader :namespace, :file, :line
|
6
|
+
|
7
|
+
def initialize(namespace, file: nil, line: nil)
|
8
|
+
@namespace = Array(namespace)
|
9
|
+
@file = file
|
10
|
+
@line = line
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
namespace.last
|
15
|
+
end
|
16
|
+
|
17
|
+
def parent
|
18
|
+
new(namespace[0...-1])
|
19
|
+
end
|
20
|
+
|
21
|
+
def kernel?
|
22
|
+
namespace.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
namespace == other.namespace
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
namespace.join('::')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/parser/directory.rb
CHANGED
data/lib/parser/file.rb
CHANGED
@@ -1,27 +1,29 @@
|
|
1
1
|
require 'parser/current'
|
2
|
+
require 'parser/definition/class'
|
3
|
+
require 'parser/definition/module'
|
4
|
+
require 'parser/relation/base'
|
2
5
|
|
3
6
|
module Rubrowser
|
4
7
|
module Parser
|
5
8
|
class File
|
6
9
|
FILE_SIZE_LIMIT = 2 * 1024 * 1024
|
7
10
|
|
8
|
-
attr_reader :file, :definitions, :
|
11
|
+
attr_reader :file, :definitions, :relations
|
9
12
|
|
10
13
|
def initialize(file)
|
11
|
-
@file = file
|
14
|
+
@file = ::File.absolute_path(file)
|
12
15
|
@definitions = []
|
13
|
-
@
|
16
|
+
@relations = []
|
14
17
|
end
|
15
18
|
|
16
19
|
def parse
|
17
20
|
if file_valid?(file)
|
18
|
-
::File.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
21
|
+
code = ::File.read(file)
|
22
|
+
ast = ::Parser::CurrentRuby.parse(code)
|
23
|
+
constants = parse_block(ast)
|
24
|
+
|
25
|
+
@definitions = constants[:definitions]
|
26
|
+
@relations = constants[:relations]
|
25
27
|
end
|
26
28
|
rescue ::Parser::SyntaxError
|
27
29
|
warn "SyntaxError in #{file}"
|
@@ -36,62 +38,79 @@ module Rubrowser
|
|
36
38
|
private
|
37
39
|
|
38
40
|
def parse_block(node, parents = [])
|
39
|
-
return
|
41
|
+
return empty_result unless valid_node?(node)
|
40
42
|
|
41
43
|
case node.type
|
42
|
-
when :module
|
43
|
-
|
44
|
-
when :
|
45
|
-
|
46
|
-
when :const
|
47
|
-
parse_const(node, parents)
|
48
|
-
else
|
49
|
-
node
|
50
|
-
.children
|
51
|
-
.map { |n| parse_block(n, parents) }
|
52
|
-
.reduce { |a, e| merge_constants(a, e) } || { definitions: [], occurences: [] }
|
44
|
+
when :module then parse_module(node, parents)
|
45
|
+
when :class then parse_class(node, parents)
|
46
|
+
when :const then parse_const(node, parents)
|
47
|
+
else parse_array(node.children, parents)
|
53
48
|
end
|
54
49
|
end
|
55
50
|
|
56
51
|
def parse_module(node, parents = [])
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
.
|
62
|
-
|
52
|
+
namespace = ast_consts_to_array(node.children.first, parents)
|
53
|
+
definition = Definition::Module.new(
|
54
|
+
namespace,
|
55
|
+
file: file,
|
56
|
+
line: node.loc.line
|
57
|
+
)
|
58
|
+
constants = { definitions: [definition] }
|
59
|
+
children_constants = parse_array(node.children[1..-1], namespace)
|
60
|
+
|
61
|
+
merge_constants(children_constants, constants)
|
63
62
|
end
|
64
63
|
|
65
64
|
def parse_class(node, parents = [])
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
.
|
71
|
-
|
65
|
+
namespace = ast_consts_to_array(node.children.first, parents)
|
66
|
+
definition = Definition::Class.new(
|
67
|
+
namespace,
|
68
|
+
file: file,
|
69
|
+
line: node.loc.line
|
70
|
+
)
|
71
|
+
constants = { definitions: [definition] }
|
72
|
+
children_constants = parse_array(node.children[1..-1], namespace)
|
73
|
+
|
74
|
+
merge_constants(children_constants, constants)
|
72
75
|
end
|
73
76
|
|
74
77
|
def parse_const(node, parents = [])
|
75
|
-
constant =
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
{
|
78
|
+
constant = ast_consts_to_array(node)
|
79
|
+
definition = Relation::Base.new(
|
80
|
+
constant,
|
81
|
+
parents,
|
82
|
+
file: file,
|
83
|
+
line: node.loc.line
|
84
|
+
)
|
85
|
+
{ relations: [definition] }
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_array(arr, parents = [])
|
89
|
+
arr.map { |n| parse_block(n, parents) }
|
90
|
+
.reduce { |a, e| merge_constants(a, e) }
|
83
91
|
end
|
84
92
|
|
85
93
|
def merge_constants(c1, c2)
|
94
|
+
c1 ||= {}
|
95
|
+
c2 ||= {}
|
86
96
|
{
|
87
|
-
definitions: c1[:definitions] + c2[:definitions],
|
88
|
-
|
97
|
+
definitions: c1[:definitions].to_a + c2[:definitions].to_a,
|
98
|
+
relations: c1[:relations].to_a + c2[:relations].to_a
|
89
99
|
}
|
90
100
|
end
|
91
101
|
|
92
|
-
def
|
93
|
-
return parents unless
|
94
|
-
|
102
|
+
def ast_consts_to_array(node, parents = [])
|
103
|
+
return parents unless valid_node?(node) &&
|
104
|
+
[:const, :cbase].include?(node.type)
|
105
|
+
ast_consts_to_array(node.children.first, parents) + [node.children.last]
|
106
|
+
end
|
107
|
+
|
108
|
+
def empty_result
|
109
|
+
{}
|
110
|
+
end
|
111
|
+
|
112
|
+
def valid_node?(node)
|
113
|
+
node.is_a?(::Parser::AST::Node)
|
95
114
|
end
|
96
115
|
end
|
97
116
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'parser/definition/base'
|
2
|
+
|
3
|
+
module Rubrowser
|
4
|
+
module Parser
|
5
|
+
module Relation
|
6
|
+
class Base
|
7
|
+
attr_reader :namespace, :caller_namespace, :file, :line
|
8
|
+
|
9
|
+
def initialize(namespace, caller_namespace, file: nil, line: nil)
|
10
|
+
@namespace = namespace
|
11
|
+
@caller_namespace = caller_namespace
|
12
|
+
@file = file
|
13
|
+
@line = line
|
14
|
+
end
|
15
|
+
|
16
|
+
def namespace
|
17
|
+
Definition::Base.new(@namespace, file: file, line: line)
|
18
|
+
end
|
19
|
+
|
20
|
+
def caller_namespace
|
21
|
+
Definition::Base.new(@caller_namespace, file: file, line: line)
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve(definitions)
|
25
|
+
possibilities.find do |possibility|
|
26
|
+
definitions.any? { |definition| definition == possibility }
|
27
|
+
end || possibilities.last
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
namespace == other.namespace &&
|
32
|
+
caller_namespace == other.caller_namespace
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def possibilities
|
38
|
+
return [
|
39
|
+
Definition::Base.new(@namespace.compact, file: file, line: line)
|
40
|
+
] if absolute?
|
41
|
+
|
42
|
+
possible_parent_namespaces
|
43
|
+
.map { |possible_parent| possible_parent + @namespace }
|
44
|
+
.push(@namespace)
|
45
|
+
.map { |i| Definition::Base.new(i, file: file, line: line) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def possible_parent_namespaces
|
49
|
+
(@caller_namespace.size - 1)
|
50
|
+
.downto(0)
|
51
|
+
.map { |i| @caller_namespace[0..i] }
|
52
|
+
end
|
53
|
+
|
54
|
+
def absolute?
|
55
|
+
@caller_namespace.empty? || @namespace.first.nil?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/rubrowser.rb
CHANGED
data/lib/server.rb
CHANGED
@@ -1,38 +1,50 @@
|
|
1
1
|
require 'webrick'
|
2
2
|
require 'data'
|
3
|
-
require 'json'
|
3
|
+
require 'formatter/json'
|
4
4
|
require 'erb'
|
5
5
|
|
6
6
|
module Rubrowser
|
7
7
|
class Server < WEBrick::HTTPServer
|
8
8
|
include ERB::Util
|
9
9
|
|
10
|
+
ROUTES = {
|
11
|
+
'/' => :root,
|
12
|
+
'/data.json' => :data
|
13
|
+
}
|
14
|
+
|
10
15
|
def self.start(options = {})
|
11
16
|
new(options).start
|
12
17
|
end
|
13
18
|
|
14
19
|
def initialize(options)
|
15
20
|
super Port: options[:port]
|
16
|
-
|
17
|
-
@data = Rubrowser::Data.new(options[:files])
|
18
|
-
@data.parse
|
21
|
+
@files = options[:files]
|
19
22
|
|
20
23
|
mount_proc '/' do |req, res|
|
21
|
-
res.body =
|
24
|
+
res.body = router(req.path)
|
22
25
|
end
|
23
|
-
|
24
|
-
trap('INT') { shutdown }
|
25
26
|
end
|
26
27
|
|
27
28
|
private
|
28
29
|
|
29
|
-
attr_reader :
|
30
|
+
attr_reader :files
|
30
31
|
|
31
|
-
def
|
32
|
+
def router(path)
|
32
33
|
return file(path) if file?(path)
|
34
|
+
return send(ROUTES[path]) if ROUTES.key?(path)
|
35
|
+
'Route not found.'
|
36
|
+
end
|
37
|
+
|
38
|
+
def root
|
33
39
|
erb :index
|
34
40
|
end
|
35
41
|
|
42
|
+
def data
|
43
|
+
data = Data.new(files)
|
44
|
+
formatter = Formatter::JSON.new(data)
|
45
|
+
formatter.call
|
46
|
+
end
|
47
|
+
|
36
48
|
def file?(path)
|
37
49
|
path = resolve_file_path("/public#{path}")
|
38
50
|
File.file?(path)
|
data/public/css/application.css
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
html, body{
|
1
|
+
html, body, .dependency_graph, .dependency_graph svg{
|
2
2
|
width: 100%;
|
3
3
|
height: 100%;
|
4
4
|
padding: 0px;
|
5
5
|
margin: 0px;
|
6
|
-
|
7
|
-
.dependency_graph, .dependency_graph svg{
|
8
|
-
width: 100%;
|
9
|
-
height: 100%;
|
6
|
+
font: 10px sans-serif;
|
10
7
|
}
|
11
8
|
|
12
|
-
|
13
|
-
|
9
|
+
.loading{
|
10
|
+
position: absolute;
|
11
|
+
top: 30px;
|
12
|
+
left: 0px;
|
13
|
+
text-align: center;
|
14
|
+
width: 100%;
|
15
|
+
font-size: 3rem;
|
14
16
|
}
|
15
17
|
|
16
18
|
.link {
|
@@ -20,13 +22,13 @@ line{
|
|
20
22
|
}
|
21
23
|
|
22
24
|
circle {
|
23
|
-
fill: #
|
25
|
+
fill: #fff;
|
24
26
|
stroke: #333;
|
25
27
|
stroke-width: 1.5px;
|
26
28
|
}
|
27
29
|
|
28
30
|
.fixed circle {
|
29
|
-
|
31
|
+
stroke-width: 3px;
|
30
32
|
}
|
31
33
|
|
32
34
|
text {
|
@@ -1,19 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
d3.json("/data.json", function(error, data) {
|
2
|
+
parseGraph(data);
|
3
|
+
$('.loading').hide();
|
4
|
+
});
|
5
|
+
|
6
|
+
var parseGraph = function(data){
|
7
|
+
var svg = d3.select(".dependency_graph svg"),
|
8
|
+
$svg = $('.dependency_graph svg'),
|
9
|
+
width = $svg.width(),
|
10
10
|
height = $svg.height(),
|
11
|
-
constants = JSON.parse(svg.attr('data-constants')),
|
12
|
-
occurences = JSON.parse(svg.attr('data-occurences')),
|
13
11
|
drag = d3.drag()
|
14
12
|
.on("start", dragstarted)
|
15
13
|
.on("drag", dragged)
|
16
|
-
.on("end", dragended)
|
14
|
+
.on("end", dragended),
|
15
|
+
constants = _.uniqWith(data.definitions.map(function(d){ return {id: d.namespace}; }), _.isEqual),
|
16
|
+
namespaces = constants.map(function(d){ return d.id; }),
|
17
|
+
relations = data.relations.map(function(d){ return {source: d.caller, target: d.resolved_namespace }; });
|
18
|
+
|
19
|
+
relations = relations.filter(function(d){
|
20
|
+
return namespaces.indexOf(d.source) >= 0 && namespaces.indexOf(d.target) >= 0;
|
21
|
+
});
|
22
|
+
relations = _.uniqWith(relations, _.isEqual);
|
17
23
|
|
18
24
|
var zoom = d3.zoom()
|
19
25
|
.on("zoom", function () {
|
@@ -23,9 +29,8 @@ var ParseGraph = function(){
|
|
23
29
|
svg.call(zoom)
|
24
30
|
.on("dblclick.zoom", null);
|
25
31
|
|
26
|
-
var container = svg.append('g')
|
27
|
-
|
28
|
-
var simulation = d3.forceSimulation()
|
32
|
+
var container = svg.append('g'),
|
33
|
+
simulation = d3.forceSimulation()
|
29
34
|
.force("link", d3.forceLink().id(function(d) { return d.id; }))
|
30
35
|
.force("charge", d3.forceManyBody())
|
31
36
|
.force("center", d3.forceCenter(width / 2, height / 2))
|
@@ -36,36 +41,33 @@ var ParseGraph = function(){
|
|
36
41
|
.on("tick", ticked);
|
37
42
|
|
38
43
|
simulation.force("link")
|
39
|
-
.links(
|
44
|
+
.links(relations);
|
40
45
|
|
41
46
|
var link = container.append("g")
|
42
47
|
.attr("class", "links")
|
43
48
|
.selectAll("path")
|
44
|
-
.data(
|
49
|
+
.data(relations)
|
45
50
|
.enter().append("path")
|
46
51
|
.attr("class", 'link')
|
47
|
-
.attr("marker-end", "url(#
|
48
|
-
|
49
|
-
var node = container.append("g")
|
52
|
+
.attr("marker-end", "url(#relation)"),
|
53
|
+
node = container.append("g")
|
50
54
|
.attr("class", "nodes")
|
51
55
|
.selectAll("g")
|
52
56
|
.data(constants)
|
53
57
|
.enter().append("g")
|
54
58
|
.call(drag)
|
55
|
-
.on("dblclick", dblclick)
|
56
|
-
|
57
|
-
var circle = node
|
59
|
+
.on("dblclick", dblclick),
|
60
|
+
circle = node
|
58
61
|
.append("circle")
|
59
|
-
.attr("r", 6)
|
60
|
-
|
61
|
-
var text = node
|
62
|
+
.attr("r", 6),
|
63
|
+
text = node
|
62
64
|
.append("text")
|
63
65
|
.attr("x", 8)
|
64
66
|
.attr("y", ".31em")
|
65
67
|
.text(function(d) { return d.id; });
|
66
68
|
|
67
69
|
container.append("defs").selectAll("marker")
|
68
|
-
.data(['
|
70
|
+
.data(['relation'])
|
69
71
|
.enter().append("marker")
|
70
72
|
.attr("id", function(d) { return d; })
|
71
73
|
.attr("viewBox", "0 -5 10 10")
|
@@ -144,5 +146,3 @@ var ParseGraph = function(){
|
|
144
146
|
return true;
|
145
147
|
|
146
148
|
};
|
147
|
-
|
148
|
-
$(function(){ParseGraph();});
|