Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets

Compare changes

Choose any two refs to compare.

Changed files
+56 -10
docs
drawsvg
+3 -3
docs/img/04_align.svg
···
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
-
width="250" height="120" viewBox="0 0 250 120">
+
width="250" height="120" viewBox="0 0 250 120">
<defs>
</defs>
<path d="M75,100 L75,0" stroke="gray" />
···
<text x="75" y="30" font-size="24" text-anchor="start">Start</text>
<text x="75" y="60" font-size="24" text-anchor="middle">Middle</text>
<text x="75" y="90" font-size="24" text-anchor="end">End</text>
-
<text x="150" y="30" font-size="24" dominant-baseline="bottom">Auto</text>
+
<text x="150" y="30" font-size="24" dominant-baseline="auto">Auto</text>
<text x="150" y="60" font-size="24" dominant-baseline="middle">Middle</text>
-
<text x="150" y="90" font-size="24" dominant-baseline="top">Hanging</text>
+
<text x="150" y="90" font-size="24" dominant-baseline="hanging">Hanging</text>
</svg>
+2 -2
docs/index.md
···
d.append(dw.Text('Start', 24, 75, 30, text_anchor='start'))
d.append(dw.Text('Middle', 24, 75, 60, text_anchor='middle'))
d.append(dw.Text('End', 24, 75, 90, text_anchor='end'))
-
d.append(dw.Text('Auto', 24, 150, 30, dominant_baseline='bottom'))
+
d.append(dw.Text('Auto', 24, 150, 30, dominant_baseline='auto'))
d.append(dw.Text('Middle', 24, 150, 60, dominant_baseline='middle'))
-
d.append(dw.Text('Hanging', 24, 150, 90, dominant_baseline='top'))
+
d.append(dw.Text('Hanging', 24, 150, 90, dominant_baseline='hanging'))
```
![svg](img/04_align.svg)
+14 -3
drawsvg/drawing.py
···
else:
self.elements.extend(iterable)
def insert(self, i, element):
+
'''Inserts a top-level element at the given array index.'''
self.elements.insert(i, element)
def remove(self, element):
+
'''Removes a top-level element (except those with a z-index).'''
self.elements.remove(element)
def clear(self):
+
'''Clears all drawing elements, with or without a z-index, but keeps
+
defs-type elements added with `append_def()`.
+
'''
self.elements.clear()
+
self.ordered_elements.clear()
def index(self, *args, **kwargs):
+
'''Finds the array-index of a top-level element (except those with a
+
z-index).
+
'''
return self.elements.index(*args, **kwargs)
def count(self, element):
+
'''Counts the number of top-level elements (except those with a z-index
+
).
+
'''
return self.elements.count(element)
def reverse(self):
+
'''Reverses the order of all elements (except those with a z-index).'''
self.elements.reverse()
def draw_def(self, obj, **kwargs):
if not hasattr(obj, 'write_svg_element'):
···
id_index += 1
return id_str
id_map = defaultdict(id_gen)
-
prev_set = set((id(defn) for defn in self.other_defs))
-
prev_list = []
+
prev_set = set()
def is_duplicate(obj):
nonlocal prev_set
dup = id(obj) in prev_set
prev_set.add(id(obj))
-
prev_list.append(obj)
return dup
for element in self.other_defs:
if hasattr(element, 'write_svg_element'):
+25 -1
drawsvg/elements.py
···
'''
TAG_NAME = 'text'
has_content = True
-
def __new__(cls, text, *args, path=None, id=None, _skip_check=False,
+
def __new__(cls, text='', *args, path=None, id=None, _skip_check=False,
**kwargs):
# Check for the special case of multi-line text on a path
# This is inconsistently implemented by renderers so we return a group
···
def __init__(self, cx, cy, r, start_deg, end_deg, cw=False, **kwargs):
super().__init__(d='', **kwargs)
self.arc(cx, cy, r, start_deg, end_deg, cw=cw, include_m=True)
+
+
class ForeignObject(DrawingBasicElement):
+
'''A foreign object, i.e. for embedding HTML.
+
+
In the context of SVG embedded in an HTML document, the XHTML namespace
+
could be omitted in the foreignObject content, but it is mandatory in the
+
context of an SVG document.
+
+
This element works best when viewing the SVG in a browser. Drawing.rasterize()
+
and Drawing.save_png() are unable to display this element.
+
+
Additional keyword arguments are output as additional arguments to the SVG
+
node e.g. fill="red", stroke="#ff4477", stroke_width=2.
+
'''
+
TAG_NAME = 'foreignObject'
+
has_content = True
+
def __init__(self, content, **kwargs):
+
super().__init__(**kwargs)
+
self.content = content
+
def write_content(self, id_map, is_duplicate, output_file, lcontext,
+
dry_run):
+
if dry_run:
+
return
+
output_file.write(self.content)
+11
drawsvg/widgets/drawing_javascript.py
···
var svg_pt = this.cursor_point.matrixTransform(
this.svg_view.getScreenCTM().inverse());
+
var target_parents = [];
+
var target = e.target;
+
while(target && target != this.svg_view)
+
{
+
if (target.id) {
+
target_parents.push(target.id);
+
}
+
target = target.parentNode;
+
}
+
this.send({
name: name,
x: svg_pt.x,
···
movementY: e.movementY,
timeStamp: e.timeStamp,
targetId: e.target ? e.target.id : null,
+
targetParentIds: target_parents,
currentTargetId: e.currentTarget ? e.currentTarget.id : null,
relatedTargetId: e.relatedTarget ? e.relatedTarget.id : null,
});
+1 -1
setup.py
···
import logging
logger = logging.getLogger(__name__)
-
version = '2.1.1'
+
version = '2.4.0'
try:
with open('README.md', 'r') as f: