Merge pull request #671 from campagnola/svg-export-updates
Svg export updates
This commit is contained in:
commit
1d1d7bfe1e
@ -23,7 +23,8 @@ class SVGExporter(Exporter):
|
||||
#{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)},
|
||||
#{'name': 'viewbox clipping', 'type': 'bool', 'value': True},
|
||||
#{'name': 'normalize coordinates', 'type': 'bool', 'value': True},
|
||||
#{'name': 'normalize line width', 'type': 'bool', 'value': True},
|
||||
{'name': 'scaling stroke', 'type': 'bool', 'value': False, 'tip': "If False, strokes are non-scaling, "
|
||||
"which means that they appear the same width on screen regardless of how they are scaled or how the view is zoomed."},
|
||||
])
|
||||
#self.params.param('width').sigValueChanged.connect(self.widthChanged)
|
||||
#self.params.param('height').sigValueChanged.connect(self.heightChanged)
|
||||
@ -49,7 +50,8 @@ class SVGExporter(Exporter):
|
||||
## Qt's SVG generator is not complete. (notably, it lacks clipping)
|
||||
## Instead, we will use Qt to generate SVG for each item independently,
|
||||
## then manually reconstruct the entire document.
|
||||
xml = generateSvg(self.item)
|
||||
options = {ch.name():ch.value() for ch in self.params.children()}
|
||||
xml = generateSvg(self.item, options)
|
||||
|
||||
if toBytes:
|
||||
return xml.encode('UTF-8')
|
||||
@ -69,10 +71,10 @@ xmlHeader = """\
|
||||
<desc>Generated with Qt and pyqtgraph</desc>
|
||||
"""
|
||||
|
||||
def generateSvg(item):
|
||||
def generateSvg(item, options={}):
|
||||
global xmlHeader
|
||||
try:
|
||||
node, defs = _generateItemSvg(item)
|
||||
node, defs = _generateItemSvg(item, options=options)
|
||||
finally:
|
||||
## reset export mode for all items in the tree
|
||||
if isinstance(item, QtGui.QGraphicsScene):
|
||||
@ -94,7 +96,7 @@ def generateSvg(item):
|
||||
return xmlHeader + defsXml + node.toprettyxml(indent=' ') + "\n</svg>\n"
|
||||
|
||||
|
||||
def _generateItemSvg(item, nodes=None, root=None):
|
||||
def _generateItemSvg(item, nodes=None, root=None, options={}):
|
||||
## This function is intended to work around some issues with Qt's SVG generator
|
||||
## and SVG in general.
|
||||
## 1) Qt SVG does not implement clipping paths. This is absurd.
|
||||
@ -209,18 +211,8 @@ def _generateItemSvg(item, nodes=None, root=None):
|
||||
|
||||
## Get rid of group transformation matrices by applying
|
||||
## transformation to inner coordinates
|
||||
correctCoordinates(g1, defs, item)
|
||||
correctCoordinates(g1, defs, item, options)
|
||||
profiler('correct')
|
||||
## make sure g1 has the transformation matrix
|
||||
#m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32())
|
||||
#g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m)
|
||||
|
||||
#print "=================",item,"====================="
|
||||
#print g1.toprettyxml(indent=" ", newl='')
|
||||
|
||||
## Inkscape does not support non-scaling-stroke (this is SVG 1.2, inkscape supports 1.1)
|
||||
## So we need to correct anything attempting to use this.
|
||||
#correctStroke(g1, item, root)
|
||||
|
||||
## decide on a name for this item
|
||||
baseName = item.__class__.__name__
|
||||
@ -239,15 +231,10 @@ def _generateItemSvg(item, nodes=None, root=None):
|
||||
## See if this item clips its children
|
||||
if int(item.flags() & item.ItemClipsChildrenToShape) > 0:
|
||||
## Generate svg for just the path
|
||||
#if isinstance(root, QtGui.QGraphicsScene):
|
||||
#path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
|
||||
#else:
|
||||
#path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape())))
|
||||
path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
|
||||
item.scene().addItem(path)
|
||||
try:
|
||||
#pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0]
|
||||
pathNode = _generateItemSvg(path, root=root)[0].getElementsByTagName('path')[0]
|
||||
pathNode = _generateItemSvg(path, root=root, options=options)[0].getElementsByTagName('path')[0]
|
||||
# assume <defs> for this path is empty.. possibly problematic.
|
||||
finally:
|
||||
item.scene().removeItem(path)
|
||||
@ -267,17 +254,18 @@ def _generateItemSvg(item, nodes=None, root=None):
|
||||
## Add all child items as sub-elements.
|
||||
childs.sort(key=lambda c: c.zValue())
|
||||
for ch in childs:
|
||||
csvg = _generateItemSvg(ch, nodes, root)
|
||||
csvg = _generateItemSvg(ch, nodes, root, options=options)
|
||||
if csvg is None:
|
||||
continue
|
||||
cg, cdefs = csvg
|
||||
childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now)
|
||||
defs.extend(cdefs)
|
||||
|
||||
|
||||
profiler('children')
|
||||
return g1, defs
|
||||
|
||||
def correctCoordinates(node, defs, item):
|
||||
|
||||
def correctCoordinates(node, defs, item, options):
|
||||
# TODO: correct gradient coordinates inside defs
|
||||
|
||||
## Remove transformation matrices from <g> tags by applying matrix to coordinates inside.
|
||||
@ -344,6 +332,10 @@ def correctCoordinates(node, defs, item):
|
||||
t = ''
|
||||
nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True)
|
||||
newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' '
|
||||
# If coords start with L instead of M, then the entire path will not be rendered.
|
||||
# (This can happen if the first point had nan values in it--Qt will skip it on export)
|
||||
if newCoords[0] != 'M':
|
||||
newCoords = 'M' + newCoords[1:]
|
||||
ch.setAttribute('d', newCoords)
|
||||
elif ch.tagName == 'text':
|
||||
removeTransform = False
|
||||
@ -378,6 +370,10 @@ def correctCoordinates(node, defs, item):
|
||||
w = ((s[0]-s[1])**2).sum()**0.5
|
||||
ch.setAttribute('stroke-width', str(w))
|
||||
|
||||
# Remove non-scaling-stroke if requested
|
||||
if options.get('scaling stroke') is True and ch.getAttribute('vector-effect') == 'non-scaling-stroke':
|
||||
ch.removeAttribute('vector-effect')
|
||||
|
||||
if removeTransform:
|
||||
grp.removeAttribute('transform')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user