Python matplotlib : Error while saving scatter plot in svg format

标签 python matplotlib

我正在尝试创建一个散点图,其中每个点的颜色基于 y 维度的值,每个点的工具提示基于 x 轴的值。我需要为鼠标悬停事件的每个点创建工具提示,我能够实现这一点。我想将此图保存到带有事件的 svg 文件中,以便可以在浏览器中使用工具提示查看该 svg 文件。

当我尝试保存时出现以下错误,

Traceback (most recent call last):


File "./altered_tooltip.py", line 160, in <module>
    example.plot()
  File "./altered_tooltip.py", line 70, in plot
    pl.savefig(f, format="svg")
  File "/usr/lib/pymodules/python2.7/matplotlib/pyplot.py", line 561, in savefig
    return fig.savefig(*args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/figure.py", line 1421, in savefig
    self.canvas.print_figure(*args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/backend_bases.py", line 2220, in print_figure
    **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/backend_bases.py", line 1978, in print_svg
    return svg.print_svg(*args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_svg.py", line 1157, in print_svg
    return self._print_svg(filename, svgwriter, fh_to_close, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_svg.py", line 1185, in _print_svg
    self.figure.draw(renderer)
  File "/usr/lib/pymodules/python2.7/matplotlib/artist.py", line 55, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/figure.py", line 1034, in draw
    func(*args)
  File "/usr/lib/pymodules/python2.7/matplotlib/artist.py", line 55, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/axes.py", line 2086, in draw
    a.draw(renderer)
  File "/usr/lib/pymodules/python2.7/matplotlib/artist.py", line 55, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/collections.py", line 718, in draw
    return Collection.draw(self, renderer)
  File "/usr/lib/pymodules/python2.7/matplotlib/artist.py", line 55, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/collections.py", line 250, in draw
    renderer.open_group(self.__class__.__name__, self.get_gid())
  File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_svg.py", line 516, in open_group
    self.writer.start('g', id=gid)
  File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_svg.py", line 141, in start
    v = escape_attrib(v)
  File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_svg.py", line 80, in escape_attrib
    s = s.replace(u"&", u"&amp;")
AttributeError: 'list' object has no attribute 'replace'

我正在运行的代码是

from numpy import *
import pylab as pl
import matplotlib.colors as mcolors
import xml.etree.ElementTree as ET
from StringIO import StringIO

ET.register_namespace("","http://www.w3.org/2000/svg")

class wxToolTipExample(object):

    def __init__(self, plot_data):
        self.figure = pl.figure()
        self.axis = pl.axes()
        self.ax = self.figure.add_subplot(111)

        self.tooltip.SetTip() calls
        self.dataX = plot_data[:,0]
        self.dataY = plot_data[:,1]
        self.annotes = []
        self.pathCol = None #to hold the scatter object

    def plot(self):

        for idx in arange(self.dataX.size):
            # create a tuple of co-ordinates
            tup = (self.dataX[idx], self.dataY[idx])

            # create annotation with tooltip
            annotation = self.axis.annotate("Column %s" % int(self.dataX[idx]),
                xy=tup, xycoords='data',
                xytext=(+10, +30), textcoords='offset points',
                #horizontalalignment="right",
                arrowprops=dict(arrowstyle="->",
                                connectionstyle="arc3,rad=0.2"),
                #bbox=dict(boxstyle="round", facecolor="w", 
                 #         edgecolor="0.0", alpha=0.0)
                )

            # by default, disable the annotation visibility
            annotation.set_visible(False)

            # append the annotation object and co-ords tuple to the list
            self.annotes.append([tup, annotation])

        self.figure.canvas.mpl_connect('motion_notify_event', self._onMotion)
        c_map = self.WGrYR()
        self.pathCol = self.axis.scatter(self.dataX, self.dataY, c=self.dataY, marker='s', s=40, cmap=c_map)

        # Set id for the annotations
        for i, t in enumerate(self.axis.texts):
            t.set_gid('tooltip_%d'%i)

        # Set id for the points on the scatter plot
        points = ['point_{0}'.format(ii) for ii in arange(1, self.dataX.size+1)]
        self.pathCol.set_gid(points)

        f = StringIO()
        #pl.show()
        pl.savefig(f, format="svg")
        """
        # Create XML tree from the SVG file.
        tree, xmlid = ET.XMLID(f.getvalue())
        tree.set('onload', 'init(evt)')

        # Hide the tooltips
        for i, t in enumerate(self.axis.texts):
            ele = xmlid['tooltip_%d'%i]
            ele.set('visibility','hidden')

        # assign mouseover and mouseout events
        for p in points:
            ele = xmlid[p]
            ele.set('onmouseover', "ShowTooltip(this)")
            ele.set('onmouseout', "HideTooltip(this)")

        script = self.getSvgScript()

        # Insert the script at the top of the file and save it.
        tree.insert(0, ET.XML(script))
        ET.ElementTree(tree).write('svg_tooltip.svg')
        """
    def getSvgScript(self):
        return """
            <script type="text/ecmascript">
            <![CDATA[

            function init(evt) {
                if ( window.svgDocument == null ) {
                    svgDocument = evt.target.ownerDocument;
                    }
                }

            function ShowTooltip(obj) {
                var cur = obj.id.slice(-1);

                var tip = svgDocument.getElementById('tooltip_' + cur);
                tip.setAttribute('visibility',"visible")
                }

            function HideTooltip(obj) {
                var cur = obj.id.slice(-1);
                var tip = svgDocument.getElementById('tooltip_' + cur);
                tip.setAttribute('visibility',"hidden")
                }

            ]]>
            </script>
            """

    def _onMotion(self, event):
        visibility_changed = False
        for point, annotation in self.annotes:
            if event.xdata != None and event.ydata != None: # mouse is inside the axes
                should_be_visible = abs(point[0]-event.xdata) < 0.2 and abs(point[1]-event.ydata) < 0.05

                if should_be_visible != annotation.get_visible():
                    visibility_changed = True
                    annotation.set_visible(should_be_visible)

        if visibility_changed:        
            pl.draw()

    def WGrYR(self):
        c = mcolors.ColorConverter().to_rgb
        seq = [c('white'), c('grey'), 0.33, c('grey'), c('yellow'), 0.66, c('yellow'), c('red')]

        seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
        cdict = {'red': [], 'green': [], 'blue': []}
        for i, item in enumerate(seq):
            if isinstance(item, float):
                r1, g1, b1 = seq[i - 1]
                r2, g2, b2 = seq[i + 1]
                cdict['red'].append([item, r1, r2])
                cdict['green'].append([item, g1, g2])
                cdict['blue'].append([item, b1, b2])
        #print cdict
        return mcolors.LinearSegmentedColormap('Custom_WGrYR', cdict)

ET.register_namespace("","http://www.w3.org/2000/svg")

# test column heat for nodes
n_cols = 5
plot_data = zeros((n_cols,2))

# generate column numbers and random heat values to test
plot_data[:,0] = asarray(arange(1, n_cols+1))#.reshape(n_cols,1)
plot_data[:,1] = random.rand(n_cols,1).reshape(n_cols,)
example = wxToolTipExample(plot_data)
example.plot()

我哪里出错了?

最佳答案

您的代码看起来像是一项正在进行的工作,尚未完全清理。 (我必须注释掉 self.tooltip.SetTip() calls 才能让它运行。)但以下是您立即出现问题的原因(您注意到的异常)以及我是如何发现它的:

在我的机器上,我编辑了 backend_svg.py 函数 start()添加print(extra)然后我运行你的代码。由于你的台词:

  points = ['point_{0}'.format(ii) for ii in arange(1, self.dataX.size+1)]
  self.pathCol.set_gid(points)

matplotlib 后端尝试创建 SVG <g> ID 为 ['point_1', 'point_2', 'point_3', 'point_4', 'point_5'] 的节点。这是一个列表,而不是一个有效的字符串,所以 s.replace()失败。

最终您必须更改代码,以便 set_gid()只接收字符串参数。最简单的方法是将上面的两行更改为:

  self.pathCol.set_gid('point_1')

但是您不会获得生成的 SVG 中各个点的 ID。您还可以删除 self.pathCol.set_gid线(pathCol 它将被渲染为 SVG <g id="PathCollection_1"> 并且点也没有 ID)。

将 SVG ID 分配给 pathCollection 中包含的各个点/顶点似乎并不简单。如果您需要这样做,您可能需要想出另一种方法来绘制它们 - 如果我正确理解问题,您需要绘制单个点而不是路径。

关于Python matplotlib : Error while saving scatter plot in svg format,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28626567/

相关文章:

python - Pytest 如何包含范围为 "setup"的 "class" fixture

python - 适合内存加载时间的数据集上的 Dask

python - 在 Pycharm 控制台正确执行代码时从系统控制台获取错误

numpy - 使用 Python/iPython 的网络绘图错误

python - 如何更改直方图中显示的轴范围

python - 在 matplotlib 中将 "The Economist"样式保存为 mplstyle 中的默认样式

python - 如何修复在 Flask-uwsgi-Nginx 设置中被 Post 请求延迟的 websocket 消息?

python - 文件如何在磁盘上打乱而不加载到内存中

python - 如何在 Matplotlib 中使用 3D 数据(给定的 x、y 和 z 数组)和颜色编码的 Z 轴表示来绘制 2D 图形?

python - 当 sharex 时,使刻度标签在 pandas 图中可见