The image is created by the following source-code. Requirements:
#!/usr/bin/python
# -*- coding: utf8 -*-
from
lxml
import
etree
import
os
import
scipy
as
sc
import
scipy.optimize
as
op
import
scipy.integrate
as
ig
from
math
import
*
class
SvgDocument
:
'''
creates a svg document structure using lxml.etree
'''
def
__init__
(
self
,
name
,
width
=
800
,
height
=
600
,
bgcolor
=
'#ffffff'
,
center
=
None
,
unit
=
1.0
):
self
.
name
=
name
self
.
width
=
int
(
width
)
self
.
height
=
int
(
height
)
self
.
unit
=
float
(
unit
)
if
center
==
None
:
self
.
center
=
[
width
/
2.
,
height
/
2.
]
else
:
self
.
center
=
[
float
(
i
)
for
i
in
center
]
# create document structure
self
.
svg
=
etree
.
Element
(
'svg'
,
nsmap
=
{
None
:
'http://www.w3.org/2000/svg'
,
'xlink'
:
'http://www.w3.org/1999/xlink'
})
self
.
svg
.
set
(
'version'
,
'1.1'
)
self
.
svg
.
set
(
'baseProfile'
,
'full'
)
self
.
svg
.
set
(
'width'
,
str
(
int
(
width
)))
self
.
svg
.
set
(
'height'
,
str
(
int
(
height
)))
# title
self
.
title
=
etree
.
SubElement
(
self
.
svg
,
'title'
)
self
.
title
.
text
=
self
.
name
# background
if
bgcolor
!=
None
:
self
.
background
=
etree
.
SubElement
(
self
.
svg
,
'rect'
)
self
.
background
.
set
(
'id'
,
'background'
)
self
.
background
.
set
(
'x'
,
'0'
)
self
.
background
.
set
(
'y'
,
'0'
)
self
.
background
.
set
(
'width'
,
str
(
width
))
self
.
background
.
set
(
'height'
,
str
(
height
))
self
.
background
.
set
(
'fill'
,
bgcolor
)
# image elements
self
.
content
=
etree
.
SubElement
(
self
.
svg
,
'g'
)
self
.
content
.
set
(
'id'
,
'image'
)
self
.
content
.
set
(
'transform'
,
'translate(
{0}
,
{1}
) scale(
{2}
,-
{2}
)'
.
format
(
self
.
center
[
0
],
self
.
center
[
1
],
self
.
unit
))
def
draw_object
(
self
,
name
,
params
,
group
=
None
):
'''
Draw arbitraty svg object.
Params must be a dictionary of valid svg parameters.
'''
if
group
==
None
:
obj
=
etree
.
SubElement
(
self
.
content
,
name
)
else
:
obj
=
etree
.
SubElement
(
group
,
name
)
for
i
,
j
in
params
.
iteritems
():
obj
.
set
(
str
(
i
),
str
(
j
))
return
obj
def
write
(
self
,
filename
=
None
):
# write content to file
if
filename
==
None
:
filename
=
self
.
name
outfile
=
open
(
filename
+
'.svg'
,
'w'
)
outfile
.
write
(
etree
.
tostring
(
self
.
svg
,
xml_declaration
=
True
,
pretty_print
=
True
,
encoding
=
'utf-8'
))
outfile
.
close
()
print
'image written to'
,
filename
+
'.svg'
# physics: calculate the catenary curve
def
catenary
(
x
,
r
):
# catenary curve f(x) with l=2 and horizontal distance 2r
if
r
==
1.0
:
return
0.0
# search a-value for given length and distance
def
fct
(
a
):
if
a
==
0.0
:
return
r
-
1.0
return
sinh
(
r
*
a
)
/
a
-
1.0
x0
=
1.0
while
fct
(
x0
)
>
0.0
:
x0
/=
2.0
while
fct
(
2.0
*
x0
)
<
0.0
:
x0
*=
2.0
a
=
1.0
/
op
.
brentq
(
fct
,
x0
,
2
*
x0
)
return
a
*
(
cosh
(
x
/
a
)
-
cosh
(
r
/
a
))
render_mult
=
8
# supersampling to improve quality
folder
=
'frames'
commands
=
[
'mkdir '
+
folder
]
for
c
in
commands
:
print
c
;
os
.
system
(
c
)
# render frames
frames
=
100
names
=
[]
for
i
in
range
(
frames
):
name
=
'catenary_
{0:0>2}
'
.
format
(
i
)
names
.
append
(
name
)
w
,
h
=
150
,
80
r0
=
65.0
doc
=
SvgDocument
(
name
,
width
=
w
,
height
=
h
,
center
=
[
w
/
2
,
10
],
unit
=
1
)
a
=
0.5
+
0.5
*
cos
(
i
*
2
*
pi
/
frames
)
r
=
a
**
1.5
+
12
*
a
**
2
-
21
*
a
**
2.5
+
9
*
a
**
3
for
ri
in
[
-
r
,
r
]:
doc
.
draw_object
(
'circle'
,
{
'cx'
:
str
(
ri
*
r0
),
'cy'
:
'0'
,
'r'
:
'7'
,
'style'
:
'fill:#888; stroke:none'
})
if
r
!=
0.0
:
x
=
sc
.
linspace
(
-
r
,
r
,
401
)
path_data
=
zip
(
r0
*
x
,
[
r0
*
catenary
(
xx
,
r
)
for
xx
in
x
])
else
:
path_data
=
[[
0.0
,
0.0
],
[
0.0
,
-
r0
],
[
0.0
,
0.0
]]
# append extra segment to have last point displayed
v
=
sc
.
array
(
path_data
[
-
1
])
-
sc
.
array
(
path_data
[
-
2
])
v
*=
0.1
/
r0
/
sqrt
(
v
[
0
]
**
2
+
v
[
1
]
**
2
)
path_data
.
append
(
sc
.
array
(
path_data
[
-
1
])
+
v
)
path
=
'M '
+
' L '
.
join
([
str
(
xy
[
0
])
+
','
+
str
(
xy
[
1
])
for
xy
in
path_data
])
doc
.
draw_object
(
'path'
,
{
'd'
:
path
,
'stroke-linecap'
:
'round'
,
'style'
:
'fill:none; stroke:black; stroke-width:4'
,
'stroke-linejoin'
:
'round'
,
'stroke-dasharray'
:
'0,5'
})
doc
.
write
(
filename
=
folder
+
'/'
+
name
)
for
name
in
names
:
commands
=
[
'rsvg -w
{0}
-h
{1}
{3}
/
{2}
.svg
{3}
/
{2}
.png'
.
format
(
w
*
render_mult
,
h
*
render_mult
,
name
,
folder
),
'rm
{1}
/
{0}
.svg'
.
format
(
name
,
folder
),
'convert -scale
{0}
x
{1}
{3}
/
{2}
.png
{3}
/
{2}
.gif'
.
format
(
w
,
h
,
name
,
folder
),
'rm
{1}
/
{0}
.png'
.
format
(
name
,
folder
)]
for
c
in
commands
:
print
c
;
os
.
system
(
c
)
commands
=
[
'gifsicle -d5 -l0 --colors 256 '
+
folder
+
'/*.gif > catenary_animation.gif'
,
'rm -rf '
+
folder
]
for
c
in
commands
:
print
c
;
os
.
system
(
c
)