0

Python 3.9.0 Matplotlib 3.5.1

I drew a table and merged cells by this method. However the output in MPL figure looks good but if I save it as SVG format, then it becomes weird. Additionally, when I convert the file into EMF format through Inkscape, it becomes worse. How can I save the figure as SVG format as it is shown in the figure handle? (bitmap formats-jpg, png, ..- have no problem)

Reproducible Code

import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

def mergecells(table, cells):
    '''
    Merge N matplotlib.Table cells

    Parameters
    -----------
    table: matplotlib.Table
        the table
    cells: list[set]
        list of sets od the table coordinates
        - example: [(0,1), (0,0), (0,2)]

    Notes
    ------
    https://stackoverflow.com/a/53819765/12684122
    '''
    cells_array = [np.asarray(c) for c in cells]
    h = np.array([cells_array[i+1][0] - cells_array[i][0] for i in range(len(cells_array) - 1)])
    v = np.array([cells_array[i+1][1] - cells_array[i][1] for i in range(len(cells_array) - 1)])

    # if it's a horizontal merge, all values for `h` are 0
    if not np.any(h):
        # sort by horizontal coord
        cells = np.array(sorted(list(cells), key=lambda v: v[1]))
        edges = ['BTL'] + ['BT' for i in range(len(cells) - 2)] + ['BTR']
    elif not np.any(v):
        cells = np.array(sorted(list(cells), key=lambda h: h[0]))
        edges = ['TRL'] + ['RL' for i in range(len(cells) - 2)] + ['BRL']
    else:
        raise ValueError("Only horizontal and vertical merges allowed")

    for cell, e in zip(cells, edges):
        table[cell[0], cell[1]].visible_edges = e
        
    txts = [table[cell[0], cell[1]].get_text() for cell in cells]
    tpos = [np.array(t.get_position()) for t in txts]

    # transpose the text of the left cell
    trans = (tpos[-1] - tpos[0])/2
    # didn't had to check for ha because I only want ha='center'
    txts[0].set_transform(mpl.transforms.Affine2D().translate(*trans))
    for txt in txts[1:]:
        txt.set_visible(False)

df = pd.DataFrame()
df['Animal'] = ['Cow', 'Bear']
df['Weight'] = [250, 450]
df['Favorite'] = ['Grass', 'Honey']
df['Least Favorite'] = ['Meat', 'Leaves']

fig = plt.figure(figsize=(9,2))
ax=fig.gca()
ax.axis('off')
r,c = df.shape

# plot the real table
table = ax.table(cellText=np.vstack([df.columns, df.values]), 
                 cellColours=[['none']*c]*(r+1),
                 bbox=[0, 0, 1, 1],
                 cellLoc="center")

# need to draw here so the text positions are calculated
fig.canvas.draw()

mergecells(table, [(0,0),(1,0),(2,0)])

fig.savefig("svgoutput.svg")
plt.show()

Image shown in MPL figure handle Image shown in MPL figure handle

Image shown in the saved SVG file Image shown in the saved SVG file

You can see the text in merged cells ("Animal") goes down. Though I have not attached the EMF file, it goes further down and even some texts disappeared.

sssbbbaaa
  • 204
  • 2
  • 12
  • You are getting the text position in data co-ordinates, but then add a transform to do a translation. That seems fragile to me - why not just change the position of the text? But I’ve not tried to follow all the steps, maybe that is not the problem. – Jody Klymak Jan 18 '22 at 22:11
  • txts[0].set_position() does not affect its position at all. I don't know why. – sssbbbaaa Jan 19 '22 at 10:22

1 Answers1

0

Setting the backend before plotting solved the problem.

import matplotlib as mpl

# This line must comes before plotting a figure
mpl.use("SVG")
sssbbbaaa
  • 204
  • 2
  • 12