Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39551
1
$ = window.$
2
misc = require('smc-util/misc')
3
{defaults, required} = misc
4
5
# load the npm install'd d3; NOTE!: just doing require('d3') itself fails with webpack for unknown reasons.
6
d3 = require('d3/d3')
7
8
# Make d3 available to users in general.
9
window?.d3 = d3
10
11
$.fn.extend
12
d3: (opts={}) ->
13
opts = defaults opts,
14
viewer : required
15
data : required
16
@each () ->
17
t = $(this)
18
elt = $("<div>")
19
t.replaceWith(elt)
20
switch opts.viewer
21
when 'graph'
22
d3_graph(elt, opts.data)
23
else
24
elt.append($("<span>unknown d3 viewer '#{opts.viewer}'</span>"))
25
return elt
26
27
# Rewrite of code in Sage by Nathann Cohen.
28
d3_graph = (elt, graph) ->
29
color = d3.scale.category20() # List of colors
30
width = graph.width
31
if not width?
32
width = Math.min(elt.width(), 700)
33
height = graph.height
34
if not height?
35
height = .6*width
36
elt.width(width); elt.height(height)
37
elt.addClass("smc-d3-graph")
38
39
#dbg = (m) -> console.log("d3_graph: #{JSON.stringify(m)}")
40
#dbg([width, height])
41
42
force = d3.layout.force()
43
.charge(graph.charge)
44
.linkDistance(graph.link_distance)
45
.linkStrength(graph.link_strength)
46
.gravity(graph.gravity)
47
.size([width, height])
48
.links(graph.links)
49
.nodes(graph.nodes)
50
51
# Returns the coordinates of a point located at distance d from the
52
# barycenter of two points pa, pb.
53
third_point_of_curved_edge = (pa, pb, d) ->
54
dx = pb.x - pa.x
55
dy = pb.y - pa.y
56
ox = pa.x
57
oy = pa.y
58
dx = pb.x
59
dy = pb.y
60
cx = (dx + ox)/2
61
cy = (dy + oy)/2
62
ny = -(dx - ox)
63
nx = dy - oy
64
nn = Math.sqrt(nx*nx + ny*ny)
65
return [cx+d*nx/nn, cy+d*ny/nn]
66
67
# Applies a transformation to the points of the graph respecting the
68
# aspect ratio, so that the graph takes the whole rendering target
69
# and is centered
70
center_and_scale = (graph) ->
71
minx = graph.pos[0][0]
72
maxx = graph.pos[0][0]
73
miny = graph.pos[0][1]
74
maxy = graph.pos[0][1]
75
76
graph.nodes.forEach (d,i) ->
77
maxx = Math.max(maxx, graph.pos[i][0])
78
minx = Math.min(minx, graph.pos[i][0])
79
maxy = Math.max(maxy, graph.pos[i][1])
80
miny = Math.min(miny, graph.pos[i][1])
81
82
border = 60
83
xspan = maxx - minx
84
yspan = maxy - miny
85
86
scale = Math.min((height - border)/yspan, (width - border)/xspan)
87
xshift = (width - scale*xspan)/2
88
yshift = (height - scale*yspan)/2
89
90
force.nodes().forEach (d,i) ->
91
d.x = scale*(graph.pos[i][0] - minx) + xshift
92
d.y = scale*(graph.pos[i][1] - miny) + yshift
93
94
# Adapts the graph layout to the window's dimensions
95
if graph.pos.length != 0
96
center_and_scale(graph)
97
98
# SVG
99
id = 'a' + misc.uuid()
100
elt.attr('id', id)
101
svg = d3.select("##{id}").append("svg")
102
.attr("width", width)
103
.attr("height", height)
104
105
# Edges
106
link = svg.selectAll(".link")
107
.data(force.links())
108
.enter().append("path")
109
.attr("class", (d) -> "link directed")
110
.attr("marker-end", (d) -> "url(#directed)")
111
.style("stroke", (d) -> d.color)
112
.style("stroke-width", graph.edge_thickness+"px")
113
114
# Loops
115
loops = svg.selectAll(".loop")
116
.data(graph.loops)
117
.enter().append("circle")
118
.attr("class", "link")
119
.attr("r", (d) -> d.curve)
120
.style("stroke", (d) -> d.color)
121
.style("stroke-width", graph.edge_thickness+"px")
122
123
# Nodes
124
node = svg.selectAll(".node")
125
.data(force.nodes())
126
.enter().append("circle")
127
.attr("class", "node")
128
.attr("r", graph.vertex_size)
129
.style("fill", (d) -> color(d.group))
130
.call(force.drag)
131
132
node.append("title").text((d) -> d.name)
133
134
# Vertex labels
135
if graph.vertex_labels
136
v_labels = svg.selectAll(".v_label")
137
.data(force.nodes())
138
.enter()
139
.append("svg:text")
140
.attr("vertical-align", "middle")
141
.text((d)-> return d.name)
142
143
# Edge labels
144
if graph.edge_labels
145
e_labels = svg.selectAll(".e_label")
146
.data(force.links())
147
.enter()
148
.append("svg:text")
149
.attr("text-anchor", "middle")
150
.text((d) -> d.name)
151
152
l_labels = svg.selectAll(".l_label")
153
.data(graph.loops)
154
.enter()
155
.append("svg:text")
156
.attr("text-anchor", "middle")
157
.text((d,i) -> graph.loops[i].name)
158
159
# Arrows, for directed graphs
160
if graph.directed
161
svg.append("svg:defs").selectAll("marker")
162
.data(["directed"])
163
.enter().append("svg:marker")
164
.attr("id", String)
165
# viewbox is a rectangle with bottom-left corder (0,-2), width 4 and height 4
166
.attr("viewBox", "0 -2 4 4")
167
# This formula took some time ... :-P
168
.attr("refX", Math.ceil(2*Math.sqrt(graph.vertex_size)))
169
.attr("refY", 0)
170
.attr("markerWidth", 4)
171
.attr("markerHeight", 4)
172
.attr("orient", "auto")
173
.append("svg:path")
174
# triangles with endpoints (0,-2), (4,0), (0,2)
175
.attr("d", "M0,-2L4,0L0,2")
176
177
#.attr("preserveAspectRatio",false) # SMELL: this gives an error.
178
179
# The function 'line' takes as input a sequence of tuples, and returns a
180
# curve interpolating these points.
181
line = d3.svg.line()
182
.interpolate("cardinal")
183
.tension(.2)
184
.x((d) -> d.x)
185
.y((d) -> d.y)
186
187
# This is where all movements are defined
188
force.on "tick", () ->
189
190
# Position of vertices
191
node.attr("cx", (d) -> d.x)
192
.attr("cy", (d) -> d.y)
193
194
# Position of edges
195
link.attr "d", (d) ->
196
# Straight edges
197
if d.curve == 0
198
return "M#{d.source.x},#{d.source.y} L#{d.target.x},#{d.target.y}"
199
# Curved edges
200
else
201
p = third_point_of_curved_edge(d.source,d.target,d.curve)
202
return line([{'x':d.source.x,'y':d.source.y},
203
{'x':p[0],'y':p[1]},
204
{'x':d.target.x,'y':d.target.y}])
205
206
# Position of Loops
207
if graph.loops.length != 0
208
loops
209
.attr("cx", (d) -> return force.nodes()[d.source].x)
210
.attr("cy", (d) -> return force.nodes()[d.source].y-d.curve)
211
212
# Position of vertex labels
213
if graph.vertex_labels
214
v_labels
215
.attr("x", (d) -> d.x+graph.vertex_size)
216
.attr("y", (d) -> return d.y)
217
218
# Position of the edge labels
219
if graph.edge_labels
220
e_labels
221
.attr("x", (d) -> third_point_of_curved_edge(d.source,d.target,d.curve+3)[0])
222
.attr("y", (d) -> third_point_of_curved_edge(d.source,d.target,d.curve+3)[1])
223
l_labels
224
.attr("x", (d,i) -> force.nodes()[d.source].x)
225
.attr("y", (d,i) -> force.nodes()[d.source].y-2*d.curve-1)
226
227
# Starts the automatic force layout
228
force.start()
229
if graph.pos.length != 0
230
force.tick()
231
force.stop()
232
graph.nodes.forEach (d,i) ->
233
d.fixed = true
234
235
236