0

I have a big vector graphic in SVG format and I want to plot into it using matplotlib. To do so I create the matplotlib plot, store it as SVG and then merge the files. However, the coordinates that matplotlib uses do not match the coordinates of my main SVG file.

To find the location of the object I want to plot over, I use its id and translate property (recursively, if necessary), e.g.

If I want to plot to the location of "X" in

<?xml version="1.0" encoding="UTF-8"?>
<svg width="2283px" height="1252px" viewBox="0 0 300 300" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    
    <g xmlns="http://www.w3.org/2000/svg" id="X" 
      transform="translate(10.0, 20.0)" fill-rule="nonzero">
        <circle id="circle" stroke="#FF5252" stroke-width="2" fill="#FFFFFF" cx="4" cy="4" r="4"/>
    </g>
</svg>

I plot to (10, 20) using the following code:

import matplotlib as mpl
import matplotlib.pyplot as plt

# set the backend to SVG, 
# according to https://stackoverflow.com/questions/70759115/text-position-changes-when-a-matplotlib-figure-is-saved-in-svg
mpl.use('svg')  

fig, ax = plt.subplots(1, 1, squeeze=True)
plt.axis('off')

# print to the location of "X"
ax.scatter([10], [20])

# needs to be done according to 
# https://stackoverflow.com/questions/24525111/how-can-i-get-the-output-of-a-matplotlib-plot-as-an-svg
# otherwise the produced output is not in a visible bound
plt.gca().set_position([0, 0, 1, 1])
plt.savefig('/tmp/test.svg', backend='svg', format='svg', transparent=True)

But the output has an offset and is scaled.

Result:
Red circle is from the original SVG, blue dot is from matplotlib.

enter image description here

Note that I put the SVG object "X" into the body of "/tmp/test.svg"

luifire
  • 13
  • 3
  • This thread on importing an SVG image into a matplotlib figure could be of interest to anyone ending up here: https://stackoverflow.com/questions/31452451/importing-an-svg-file-into-a-matplotlib-figure – luifire May 15 '23 at 13:57

1 Answers1

0

You should watch out for the sizes of your figures and the coordinates of the drawn elements.

Once you fix those values, you should invert the y-axis of your mpl plot (y is going from bottom to top in mpl and top to bottom in svg).

Simple example, adjusting the size of your original svg to the viewBox (1px=0.75pt see there):

<?xml version="1.0" encoding="UTF-8"?>
<svg width="400px" height="400px" viewBox="0 0 300 300" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    
    <g xmlns="http://www.w3.org/2000/svg" id="X" 
      transform="translate(10.0, 20.0)" fill-rule="nonzero">
        <circle id="circle" stroke="#FF5252" stroke-width="2" fill="#FFFFFF" cx="4" cy="4" r="4"/>
    </g>
    
</svg>

It seems that the dpi for the svg backend is hardcoded at 72 (see mpl source code). Hence you'll get fitting coordinates with the following code:

import matplotlib.pyplot as plt

dpi = 72 # default for svg backend
width_in_pt = 300
height_in_pt = 300

fig, ax = plt.subplots(1, 1, figsize=[width_in_pt/dpi,height_in_pt/dpi], dpi=dpi)
plt.axis('off')

# print to the location of "X"
ax.scatter([14], [24])  # coordinates are 10+4 (x-translation + cx) and 20+4 (y-translation + cy)
ax.set_xlim([0, width_in_pt])
ax.set_ylim([0, height_in_pt])
ax.invert_yaxis()

fig.subplots_adjust(bottom=0, top=1, left=0, right=1)
plt.savefig('test_mpl.svg', backend='svg', transparent=True)

Edit: I got mixed up between pt and px and rewrote the answer in a clearer way

Tranbi
  • 11,407
  • 6
  • 16
  • 33