Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
| Download
Views: 39598
1
###############################################################################
2
#
3
# CoCalc: Collaborative Calculation in the Cloud
4
#
5
# Copyright (C) 2016, Sagemath Inc.
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#
20
###############################################################################
21
22
import json, math
23
import sage_salvus
24
25
from uuid import uuid4
26
def uuid():
27
return str(uuid4())
28
29
def json_float(t):
30
if t is None:
31
return t
32
t = float(t)
33
# Neither of nan or inf get JSON'd in a way that works properly, for some reason. I don't understand why.
34
if math.isnan(t) or math.isinf(t):
35
return None
36
else:
37
return t
38
39
#######################################################
40
# Three.js based plotting
41
#######################################################
42
43
noneint = lambda n : n if n is None else int(n)
44
45
class ThreeJS(object):
46
def __init__(self, renderer=None, width=None, height=None,
47
frame=True, background=None, foreground=None,
48
spin=False, viewer=None, aspect_ratio=None,
49
frame_aspect_ratio = None,
50
**ignored):
51
"""
52
INPUT:
53
54
- renderer -- None (automatic), 'canvas2d', or 'webgl'
55
- width -- None (automatic) or an integer
56
- height -- None (automatic) or an integer
57
- frame -- bool (default: True); draw a frame that includes every object.
58
- background -- None (transparent); otherwise a color such as 'black' or 'white'
59
- foreground -- None (automatic = black if transparent; otherwise opposite of background);
60
or a color; this is used for drawing the frame and axes labels.
61
- spin -- False; if True, spins 3d plot, with number determining speed (requires webgl and mouse over plot)
62
- aspect_ratio -- None (square) or a triple [x,y,z] so that everything is scaled by x,y,z.
63
64
- frame_aspect_ratio -- synonym for aspect_ratio
65
- viewer -- synonym for renderer
66
"""
67
if viewer is not None and renderer is None:
68
renderer = viewer
69
if renderer not in [None, 'webgl', 'canvas', 'canvas2d']:
70
raise ValueError("unknown renderer='%s'; it must be None, webgl, or canvas2d"%renderer)
71
self._frame = frame
72
self._salvus = sage_salvus.salvus # object for this cell
73
self._id = uuid()
74
self._selector = "#%s"%self._id
75
self._obj = "$('%s').data('salvus-threejs')"%self._selector
76
self._salvus.html("<span id=%s class='salvus-3d-container'></span>"%self._id)
77
if not isinstance(spin, bool):
78
spin = json_float(spin)
79
if frame_aspect_ratio is not None:
80
aspect_ratio = frame_aspect_ratio
81
if aspect_ratio is not None:
82
if aspect_ratio == 1 or aspect_ratio=='automatic':
83
aspect_ratio = None
84
elif not (isinstance(aspect_ratio, (list, tuple)) and len(aspect_ratio) == 3):
85
raise TypeError("aspect_ratio must be None, 1 or a 3-tuple ")
86
else:
87
aspect_ratio = [json_float(x) for x in aspect_ratio]
88
self._salvus.javascript("$('%s').salvus_threejs(obj)"%self._selector,
89
once = False,
90
obj = {
91
'renderer' : renderer,
92
'width' : noneint(width),
93
'height' : noneint(height),
94
'background' : background,
95
'foreground' : foreground,
96
'spin' : spin,
97
'aspect_ratio' : aspect_ratio
98
})
99
self._graphics = []
100
self._call('init()')
101
102
def _call(self, s, obj=None):
103
cmd = 'misc.eval_until_defined({code:"%s", cb:(function(err, __t__) { __t__ != null ? __t__.%s:void 0 })})'%(
104
self._obj, s)
105
self._salvus.execute_javascript(cmd, obj=obj)
106
107
def bounding_box(self):
108
if not self._graphics:
109
return -1,1,-1,1,-1,1
110
b = self._graphics[0].bounding_box()
111
xmin, xmax, ymin, ymax, zmin, zmax = b[0][0], b[1][0], b[0][1], b[1][1], b[0][2], b[1][2]
112
for g in self._graphics[1:]:
113
b = g.bounding_box()
114
xmin, xmax, ymin, ymax, zmin, zmax = (
115
min(xmin,b[0][0]), max(b[1][0],xmax),
116
min(b[0][1],ymin), max(b[1][1],ymax),
117
min(b[0][2],zmin), max(b[1][2],zmax))
118
v = xmin, xmax, ymin, ymax, zmin, zmax
119
return [json_float(x) for x in v]
120
121
def frame_options(self):
122
xmin, xmax, ymin, ymax, zmin, zmax = self.bounding_box()
123
return {'xmin':xmin, 'xmax':xmax, 'ymin':ymin, 'ymax':ymax, 'zmin':zmin, 'zmax':zmax,
124
'draw' : self._frame}
125
126
def add(self, graphics3d, **kwds):
127
kwds = graphics3d._process_viewing_options(kwds)
128
self._graphics.append(graphics3d)
129
obj = {'obj' : graphics3d_to_jsonable(graphics3d),
130
'wireframe' : jsonable(kwds.get('wireframe')),
131
'set_frame' : self.frame_options()}
132
self._call('add_3dgraphics_obj(obj)', obj=obj)
133
134
def render_scene(self, force=True):
135
self._call('render_scene(obj)', obj={'force':force})
136
137
def add_text(self, pos, text, fontsize=18, fontface='Arial', sprite_alignment='topLeft'):
138
self._call('add_text(obj)',
139
obj={'pos':[json_float(pos[0]), json_float(pos[1]), json_float(pos[2])],'text':str(text),
140
'fontsize':int(fontsize),'fontface':str(fontface), 'sprite_alignment':str(sprite_alignment)})
141
142
def animate(self, fps=None, stop=None, mouseover=True):
143
self._call('animate(obj)', obj={'fps':noneint(fps), 'stop':stop, 'mouseover':mouseover})
144
145
def init_done(self):
146
self._call('init_done()')
147
148
def show_3d_plot_using_threejs(g, **kwds):
149
for k in ['spin', 'renderer', 'viewer', 'frame', 'height', 'width', 'background', 'foreground', 'aspect_ratio']:
150
extra_kwds = {} if g._extra_kwds is None else g._extra_kwds
151
if k in extra_kwds and k not in kwds:
152
kwds[k] = g._extra_kwds[k]
153
if 'camera_distance' in kwds:
154
del kwds['camera_distance'] # deprecated
155
t = ThreeJS(**kwds)
156
t.add(g, **kwds)
157
if kwds.get('spin', False):
158
t.animate(mouseover=False)
159
t.init_done()
160
161
import sage.plot.plot3d.index_face_set
162
import sage.plot.plot3d.shapes
163
import sage.plot.plot3d.base
164
import sage.plot.plot3d.shapes2
165
from sage.structure.element import Element
166
167
def jsonable(x):
168
if isinstance(x, Element):
169
return json_float(x)
170
elif isinstance(x, (list, tuple)):
171
return [jsonable(y) for y in x]
172
return x
173
174
175
def graphics3d_to_jsonable(p):
176
obj_list = []
177
178
def parse_obj(obj):
179
material_name = ''
180
faces = []
181
for item in obj.split("\n"):
182
tmp = str(item.strip())
183
if not tmp:
184
continue
185
k = tmp.split()
186
if k[0] == "usemtl": # material name
187
material_name = k[1]
188
elif k[0] == 'f': # face
189
v = [int(a) for a in k[1:]]
190
faces.append(v)
191
# other types are parse elsewhere in a different pass.
192
193
return [{"material_name":material_name, "faces":faces}]
194
195
def parse_texture(p):
196
texture_dict = []
197
textures = p.texture_set()
198
for item in range(0,len(textures)):
199
texture_pop = textures.pop()
200
string = str(texture_pop)
201
item = string.split("(")[1]
202
name = item.split(",")[0]
203
color = texture_pop.color
204
tmp_dict = {"name":name,"color":color}
205
texture_dict.append(tmp_dict)
206
return texture_dict
207
208
def get_color(name,texture_set):
209
for item in range(0,len(texture_set)):
210
if(texture_set[item]["name"] == name):
211
color = texture_set[item]["color"]
212
color_list = [color[0],color[1],color[2]]
213
break
214
else:
215
color_list = []
216
return color_list
217
218
def parse_mtl(p):
219
mtl = p.mtl_str()
220
all_material = []
221
for item in mtl.split("\n"):
222
if "newmtl" in item:
223
tmp = str(item.strip())
224
tmp_list = []
225
try:
226
texture_set = parse_texture(p)
227
color = get_color(name,texture_set)
228
except (ValueError,UnboundLocalError):
229
pass
230
try:
231
tmp_list = {"name":name,"ambient":ambient, "specular":specular, "diffuse":diffuse, "illum":illum_list[0],
232
"shininess":shininess_list[0],"opacity":opacity_diffuse[3],"color":color}
233
all_material.append(tmp_list)
234
except (ValueError,UnboundLocalError):
235
pass
236
237
ambient = []
238
specular = []
239
diffuse = []
240
illum_list = []
241
shininess_list = []
242
opacity_list = []
243
opacity_diffuse = []
244
tmp_list = []
245
name = tmp.split()[1]
246
247
if "Ka" in item:
248
tmp = str(item.strip())
249
for t in tmp.split():
250
try:
251
ambient.append(json_float(t))
252
except ValueError:
253
pass
254
255
if "Ks" in item:
256
tmp = str(item.strip())
257
for t in tmp.split():
258
try:
259
specular.append(json_float(t))
260
except ValueError:
261
pass
262
263
if "Kd" in item:
264
tmp = str(item.strip())
265
for t in tmp.split():
266
try:
267
diffuse.append(json_float(t))
268
except ValueError:
269
pass
270
271
if "illum" in item:
272
tmp = str(item.strip())
273
for t in tmp.split():
274
try:
275
illum_list.append(json_float(t))
276
except ValueError:
277
pass
278
279
280
281
if "Ns" in item:
282
tmp = str(item.strip())
283
for t in tmp.split():
284
try:
285
shininess_list.append(json_float(t))
286
except ValueError:
287
pass
288
289
if "d" in item:
290
tmp = str(item.strip())
291
for t in tmp.split():
292
try:
293
opacity_diffuse.append(json_float(t))
294
except ValueError:
295
pass
296
297
try:
298
color = list(p.all[0].texture.color.rgb())
299
except (ValueError, AttributeError):
300
pass
301
302
try:
303
texture_set = parse_texture(p)
304
color = get_color(name,texture_set)
305
except (ValueError, AttributeError):
306
color = []
307
#pass
308
309
tmp_list = {"name":name,"ambient":ambient, "specular":specular, "diffuse":diffuse, "illum":illum_list[0],
310
"shininess":shininess_list[0],"opacity":opacity_diffuse[3],"color":color}
311
all_material.append(tmp_list)
312
313
return all_material
314
315
#####################################
316
# Conversion functions
317
#####################################
318
319
def convert_index_face_set(p, T, extra_kwds):
320
if T is not None:
321
p = p.transform(T=T)
322
face_geometry = parse_obj(p.obj())
323
if hasattr(p, 'has_local_colors') and p.has_local_colors():
324
convert_index_face_set_with_colors(p, T, extra_kwds)
325
return
326
material = parse_mtl(p)
327
vertex_geometry = []
328
obj = p.obj()
329
for item in obj.split("\n"):
330
if "v" in item:
331
tmp = str(item.strip())
332
for t in tmp.split():
333
try:
334
vertex_geometry.append(json_float(t))
335
except ValueError:
336
pass
337
myobj = {"face_geometry" : face_geometry,
338
"type" : 'index_face_set',
339
"vertex_geometry" : vertex_geometry,
340
"material" : material,
341
"has_local_colors" : 0}
342
for e in ['wireframe', 'mesh']:
343
if p._extra_kwds is not None:
344
v = p._extra_kwds.get(e, None)
345
if v is not None:
346
myobj[e] = jsonable(v)
347
obj_list.append(myobj)
348
349
def convert_index_face_set_with_colors(p, T, extra_kwds):
350
face_geometry = [{"material_name": p.texture.id,
351
"faces": [[int(v) + 1 for v in f[0]] + [f[1]] for f in p.index_faces_with_colors()]}]
352
material = parse_mtl(p)
353
vertex_geometry = [json_float(t) for v in p.vertices() for t in v]
354
myobj = {"face_geometry" : face_geometry,
355
"type" : 'index_face_set',
356
"vertex_geometry" : vertex_geometry,
357
"material" : material,
358
"has_local_colors" : 1}
359
for e in ['wireframe', 'mesh']:
360
if p._extra_kwds is not None:
361
v = p._extra_kwds.get(e, None)
362
if v is not None:
363
myobj[e] = jsonable(v)
364
obj_list.append(myobj)
365
366
def convert_text3d(p, T, extra_kwds):
367
obj_list.append(
368
{"type" : "text",
369
"text" : p.string,
370
"pos" : [0,0,0] if T is None else T([0,0,0]),
371
"color" : "#" + p.get_texture().hex_rgb(),
372
'fontface' : str(extra_kwds.get('fontface', 'Arial')),
373
'constant_size' : bool(extra_kwds.get('constant_size', True)),
374
'fontsize' : int(extra_kwds.get('fontsize', 12))})
375
376
def convert_line(p, T, extra_kwds):
377
obj_list.append({"type" : "line",
378
"points" : jsonable(p.points if T is None else [T.transform_point(point) for point in p.points]),
379
"thickness" : jsonable(p.thickness),
380
"color" : "#" + p.get_texture().hex_rgb(),
381
"arrow_head" : bool(p.arrow_head)})
382
383
def convert_point(p, T, extra_kwds):
384
obj_list.append({"type" : "point",
385
"loc" : p.loc if T is None else T(p.loc),
386
"size" : json_float(p.size),
387
"color" : "#" + p.get_texture().hex_rgb()})
388
389
def convert_combination(p, T, extra_kwds):
390
for x in p.all:
391
handler(x)(x, T, p._extra_kwds)
392
393
def convert_transform_group(p, T, extra_kwds):
394
if T is not None:
395
T = T * p.get_transformation()
396
else:
397
T = p.get_transformation()
398
for x in p.all:
399
handler(x)(x, T, p._extra_kwds)
400
401
def nothing(p, T, extra_kwds):
402
pass
403
404
def handler(p):
405
if isinstance(p, sage.plot.plot3d.index_face_set.IndexFaceSet):
406
return convert_index_face_set
407
elif isinstance(p, sage.plot.plot3d.shapes.Text):
408
return convert_text3d
409
elif isinstance(p, sage.plot.plot3d.base.TransformGroup):
410
return convert_transform_group
411
elif isinstance(p, sage.plot.plot3d.base.Graphics3dGroup):
412
return convert_combination
413
elif isinstance(p, sage.plot.plot3d.shapes2.Line):
414
return convert_line
415
elif isinstance(p, sage.plot.plot3d.shapes2.Point):
416
return convert_point
417
elif isinstance(p, sage.plot.plot3d.base.PrimitiveObject):
418
return convert_index_face_set
419
elif isinstance(p, sage.plot.plot3d.base.Graphics3d):
420
# this is an empty scene
421
return nothing
422
else:
423
raise NotImplementedError("unhandled type ", type(p))
424
425
426
# start it going -- this modifies obj_list
427
handler(p)(p, None, None)
428
429
# now obj_list is full of the objects
430
return obj_list
431
432
433
434
435
436
437
###
438
# Interactive 2d Graphics
439
###
440
441
import os, matplotlib.figure
442
443
class InteractiveGraphics(object):
444
def __init__(self, g, **events):
445
self._g = g
446
self._events = events
447
448
def figure(self, **kwds):
449
if isinstance(self._g, matplotlib.figure.Figure):
450
return self._g
451
452
options = dict()
453
options.update(self._g.SHOW_OPTIONS)
454
options.update(self._g._extra_kwds)
455
options.update(kwds)
456
options.pop('dpi'); options.pop('transparent'); options.pop('fig_tight')
457
fig = self._g.matplotlib(**options)
458
459
from matplotlib.backends.backend_agg import FigureCanvasAgg
460
canvas = FigureCanvasAgg(fig)
461
fig.set_canvas(canvas)
462
fig.tight_layout() # critical, since sage does this -- if not, coords all wrong
463
return fig
464
465
def save(self, filename, **kwds):
466
if isinstance(self._g, matplotlib.figure.Figure):
467
self._g.savefig(filename)
468
else:
469
# When fig_tight=True (the default), the margins are very slightly different.
470
# I don't know how to properly account for this yet (or even if it is possible),
471
# since it only happens at figsize time -- do "a=plot(sin); a.save??".
472
# So for interactive graphics, we just set this to false no matter what.
473
kwds['fig_tight'] = False
474
self._g.save(filename, **kwds)
475
476
def show(self, **kwds):
477
fig = self.figure(**kwds)
478
ax = fig.axes[0]
479
# upper left data coordinates
480
xmin, ymax = ax.transData.inverted().transform( fig.transFigure.transform((0,1)) )
481
# lower right data coordinates
482
xmax, ymin = ax.transData.inverted().transform( fig.transFigure.transform((1,0)) )
483
484
id = '_a' + uuid().replace('-','')
485
486
def to_data_coords(p):
487
# 0<=x,y<=1
488
return ((xmax-xmin)*p[0] + xmin, (ymax-ymin)*(1-p[1]) + ymin)
489
490
if kwds.get('svg',False):
491
filename = '%s.svg'%id
492
del kwds['svg']
493
else:
494
filename = '%s.png'%id
495
496
fig.savefig(filename)
497
498
def f(event, p):
499
self._events[event](to_data_coords(p))
500
sage_salvus.salvus.namespace[id] = f
501
x = {}
502
for ev in self._events.keys():
503
x[ev] = id
504
505
sage_salvus.salvus.file(filename, show=True, events=x)
506
os.unlink(filename)
507
508
def __del__(self):
509
for ev in self._events:
510
u = self._id+ev
511
if u in sage_salvus.salvus.namespace:
512
del sage_salvus.salvus.namespace[u]
513
514
515
516
517
518
519
520
521
522
523
524
525
526
###
527
# D3-based interactive 2d Graphics
528
###
529
530
###
531
# The following is a modified version of graph_plot_js.py from the Sage library, which was
532
# written by Nathann Cohen in 2013.
533
###
534
def graph_to_d3_jsonable(G,
535
vertex_labels = True,
536
edge_labels = False,
537
vertex_partition = [],
538
edge_partition = [],
539
force_spring_layout = False,
540
charge = -120,
541
link_distance = 50,
542
link_strength = 1,
543
gravity = .04,
544
vertex_size = 7,
545
edge_thickness = 2,
546
width = None,
547
height = None,
548
**ignored):
549
r"""
550
Display a graph in CoCalc using the D3 visualization library.
551
552
INPUT:
553
554
- ``G`` -- the graph
555
556
- ``vertex_labels`` (boolean) -- Whether to display vertex labels (set to
557
``True`` by default).
558
559
- ``edge_labels`` (boolean) -- Whether to display edge labels (set to
560
``False`` by default).
561
562
- ``vertex_partition`` -- a list of lists representing a partition of the
563
vertex set. Vertices are then colored in the graph according to the
564
partition. Set to ``[]`` by default.
565
566
- ``edge_partition`` -- same as ``vertex_partition``, with edges
567
instead. Set to ``[]`` by default.
568
569
- ``force_spring_layout`` -- whether to take sage's position into account if
570
there is one (see :meth:`~sage.graphs.generic_graph.GenericGraph.` and
571
:meth:`~sage.graphs.generic_graph.GenericGraph.`), or to compute a spring
572
layout. Set to ``False`` by default.
573
574
- ``vertex_size`` -- The size of a vertex' circle. Set to `7` by default.
575
576
- ``edge_thickness`` -- Thickness of an edge. Set to ``2`` by default.
577
578
- ``charge`` -- the vertices' charge. Defines how they repulse each
579
other. See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more
580
information. Set to ``-120`` by default.
581
582
- ``link_distance`` -- See
583
`<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more
584
information. Set to ``30`` by default.
585
586
- ``link_strength`` -- See
587
`<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more
588
information. Set to ``1.5`` by default.
589
590
- ``gravity`` -- See
591
`<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more
592
information. Set to ``0.04`` by default.
593
594
595
EXAMPLES::
596
597
show(graphs.RandomTree(50), d3=True)
598
599
show(graphs.PetersenGraph(), d3=True, vertex_partition=g.coloring())
600
601
show(graphs.DodecahedralGraph(), d3=True, force_spring_layout=True)
602
603
show(graphs.DodecahedralGraph(), d3=True)
604
605
g = digraphs.DeBruijn(2,2)
606
g.allow_multiple_edges(True)
607
g.add_edge("10","10","a")
608
g.add_edge("10","10","b")
609
g.add_edge("10","10","c")
610
g.add_edge("10","10","d")
611
g.add_edge("01","11","1")
612
show(g, d3=True, vertex_labels=True,edge_labels=True,
613
link_distance=200,gravity=.05,charge=-500,
614
edge_partition=[[("11","12","2"),("21","21","a")]],
615
edge_thickness=4)
616
617
"""
618
directed = G.is_directed()
619
multiple_edges = G.has_multiple_edges()
620
621
# Associated an integer to each vertex
622
v_to_id = {v: i for i, v in enumerate(G.vertices())}
623
624
# Vertex colors
625
color = {i: len(vertex_partition) for i in range(G.order())}
626
for i, l in enumerate(vertex_partition):
627
for v in l:
628
color[v_to_id[v]] = i
629
630
# Vertex list
631
nodes = []
632
for v in G.vertices():
633
nodes.append({"name": str(v), "group": str(color[v_to_id[v]])})
634
635
# Edge colors.
636
edge_color_default = "#aaa"
637
from sage.plot.colors import rainbow
638
color_list = rainbow(len(edge_partition))
639
edge_color = {}
640
for i, l in enumerate(edge_partition):
641
for e in l:
642
u, v, label = e if len(e) == 3 else e+(None,)
643
edge_color[u, v, label] = color_list[i]
644
if not directed:
645
edge_color[v, u, label] = color_list[i]
646
647
# Edge list
648
edges = []
649
seen = {} # How many times has this edge been seen ?
650
651
for u, v, l in G.edges():
652
653
# Edge color
654
color = edge_color.get((u, v, l), edge_color_default)
655
656
# Computes the curve of the edge
657
curve = 0
658
659
# Loop ?
660
if u == v:
661
seen[u, v] = seen.get((u, v), 0)+1
662
curve = seen[u, v]*10+10
663
664
# For directed graphs, one also has to take into accounts
665
# edges in the opposite direction
666
elif directed:
667
if G.has_edge(v, u):
668
seen[u, v] = seen.get((u, v), 0)+1
669
curve = seen[u, v]*15
670
else:
671
if multiple_edges and len(G.edge_label(u, v)) != 1:
672
# Multiple edges. The first one has curve 15, then
673
# -15, then 30, then -30, ...
674
seen[u, v] = seen.get((u, v), 0) + 1
675
curve = (1 if seen[u, v] % 2 else -1)*(seen[u, v]//2)*15
676
677
elif not directed and multiple_edges:
678
# Same formula as above for multiple edges
679
if len(G.edge_label(u, v)) != 1:
680
seen[u, v] = seen.get((u, v), 0) + 1
681
curve = (1 if seen[u, v] % 2 else -1)*(seen[u, v]//2)*15
682
683
# Adding the edge to the list
684
edges.append({"source": v_to_id[u],
685
"target": v_to_id[v],
686
"strength": 0,
687
"color": color,
688
"curve": curve,
689
"name": str(l) if edge_labels else ""})
690
691
loops = [e for e in edges if e["source"] == e["target"]]
692
edges = [e for e in edges if e["source"] != e["target"]]
693
694
# Defines the vertices' layout if possible
695
Gpos = G.get_pos()
696
pos = []
697
if Gpos is not None and force_spring_layout is False:
698
charge = 0
699
link_strength = 0
700
gravity = 0
701
702
for v in G.vertices():
703
x, y = Gpos[v]
704
pos.append([json_float(x), json_float(-y)])
705
706
return {"nodes" : nodes,
707
"links" : edges, "loops": loops, "pos": pos,
708
"directed" : G.is_directed(),
709
"charge" : int(charge),
710
"link_distance" : int(link_distance),
711
"link_strength" : int(link_strength),
712
"gravity" : float(gravity),
713
"vertex_labels" : bool(vertex_labels),
714
"edge_labels" : bool(edge_labels),
715
"vertex_size" : int(vertex_size),
716
"edge_thickness" : int(edge_thickness),
717
"width" : json_float(width),
718
"height" : json_float(height) }
719
720
721
722
723
724
725
726
727
728