pyqtgraph/pyqtgraph/exporters/SVGExporter.py

179 lines
6.6 KiB
Python
Raw Normal View History

from .Exporter import Exporter
from pyqtgraph.parametertree import Parameter
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
import re
import xml.dom.minidom as xml
__all__ = ['SVGExporter']
class SVGExporter(Exporter):
Name = "Scalable Vector Graphics (SVG)"
def __init__(self, item):
Exporter.__init__(self, item)
#tr = self.getTargetRect()
self.params = Parameter(name='params', type='group', children=[
#{'name': 'width', 'type': 'float', 'value': tr.width(), 'limits': (0, None)},
#{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)},
])
#self.params.param('width').sigValueChanged.connect(self.widthChanged)
#self.params.param('height').sigValueChanged.connect(self.heightChanged)
def widthChanged(self):
sr = self.getSourceRect()
ar = sr.height() / sr.width()
self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged)
def heightChanged(self):
sr = self.getSourceRect()
ar = sr.width() / sr.height()
self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged)
def parameters(self):
return self.params
def export(self, fileName=None, toBytes=False):
if toBytes is False and fileName is None:
self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)")
return
#self.svg = QtSvg.QSvgGenerator()
#self.svg.setFileName(fileName)
#dpi = QtGui.QDesktopWidget().physicalDpiX()
### not really sure why this works, but it seems to be important:
#self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.))
#self.svg.setResolution(dpi)
##self.svg.setViewBox()
#targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height'])
#sourceRect = self.getSourceRect()
#painter = QtGui.QPainter(self.svg)
#try:
#self.setExportMode(True)
#self.render(painter, QtCore.QRectF(targetRect), sourceRect)
#finally:
#self.setExportMode(False)
#painter.end()
## Workaround to set pen widths correctly
#data = open(fileName).readlines()
#for i in range(len(data)):
#line = data[i]
#m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line)
#if m is not None:
##print "Matched group:", line
#g = m.groups()
#matrix = list(map(float, g[2].split(',')))
##print "matrix:", matrix
#scale = max(abs(matrix[0]), abs(matrix[3]))
#if scale == 0 or scale == 1.0:
#continue
#data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n'
##print "old line:", line
##print "new line:", data[i]
#open(fileName, 'w').write(''.join(data))
node = self.generateItemSvg(self.item)
xml = """\
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny">
<title>pyqtgraph SVG export</title>
<desc>Generated with Qt and pyqtgraph</desc>
<defs>
</defs>
""" + node.toprettyxml(indent=' ') + "\n</svg>\n"
if toBytes:
return bytes(xml)
else:
with open(fileName, 'w') as fh:
fh.write(xml)
def generateItemSvg(self, item):
if isinstance(item, QtGui.QGraphicsScene):
xmlStr = "<g></g>"
childs = [i for i in item.items() if i.parentItem() is None]
else:
tr = QtGui.QTransform()
tr.translate(item.pos().x(), item.pos().y())
tr = tr * item.transform()
if not item.isVisible() or int(item.flags() & item.ItemHasNoContents) > 0:
m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32())
#print item, m
xmlStr = '<g transform="matrix(%f,%f,%f,%f,%f,%f)"></g>' % m
else:
arr = QtCore.QByteArray()
buf = QtCore.QBuffer(arr)
svg = QtSvg.QSvgGenerator()
svg.setOutputDevice(buf)
dpi = QtGui.QDesktopWidget().physicalDpiX()
### not really sure why this works, but it seems to be important:
#self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.))
svg.setResolution(dpi)
p = QtGui.QPainter()
p.begin(svg)
if hasattr(item, 'setExportMode'):
item.setExportMode(True, {'painter': p})
try:
#tr = QtGui.QTransform()
#tr.translate(item.pos().x(), item.pos().y())
#p.setTransform(tr * item.transform())
p.setTransform(tr)
item.paint(p, QtGui.QStyleOptionGraphicsItem(), None)
finally:
p.end()
if hasattr(item, 'setExportMode'):
item.setExportMode(False)
xmlStr = str(arr)
childs = item.childItems()
doc = xml.parseString(xmlStr)
try:
groups = doc.getElementsByTagName('g')
if len(groups) == 1:
g1 = g2 = groups[0]
else:
g1,g2 = groups[:2]
except:
print doc.toxml()
raise
g1.setAttribute('id', item.__class__.__name__)
## Check for item visibility
visible = True
if not isinstance(item, QtGui.QGraphicsScene):
parent = item
while visible and parent is not None:
visible = parent.isVisible()
parent = parent.parentItem()
if not visible:
style = g1.getAttribute('style').strip()
if len(style)>0 and not style.endswith(';'):
style += ';'
style += 'display:none;'
g1.setAttribute('style', style)
childs.sort(key=lambda c: c.zValue())
for ch in childs:
cg = self.generateItemSvg(ch)
g2.appendChild(cg)
return g1
### To check:
### do all items really generate this double-group structure?
### are both groups necessary?
### How do we implement clipping? (can we clip to an object that is visible?)