Matplotlib: Points do not show in SVG - python
I have a scatter plot that I'd like to output as SVG (Python 3.5). However, when used with agg as backend, some points are simply missing. See the data and the PNG and SVG output. Is this some kind of misconfiguration or a bug?
Code:
import matplotlig
matplotlib.use('agg')
import matplotlib.pyplot as plt
x = [22752.9597858324,33434.3100283611,None,None,3973.2239542398,None,None,None
,None,None,None,None,None,960.6513071797,None,None,None,None,None,None,None
,None,None,None,None,None,749470.931292081,None,None,None,None,None,None
,None,None,None,None,None,None,None,None,23045.262784499,None,None,None
,None,None,None,None,1390.8383822667,None,None,9802.5632611025
,3803.3240362092,None,None,None,None,None,2058.1191666219,None
,3777.5383953988,None,91224.0759036624,23296.1857550166,27956.249381887
,None,237247.707648005,None,None,None,None,None,None,None,None,None
,760.3493458787,None,321687.799104496,None,None,22339.5617383239,None,None
,None,None,None,28135.0261453192,None,None,None,None,None,None,None
,1687.4387356974,None,None,29037.8494868489,None,None,None,None,None,None
,None,3937.3066755226,None,None,None,None]
y = [63557.4319306279,None,None,None,9466.0204228915,None,None,None,None,None
,None,None,None,3080.3393940948,None,None,None,None,None,None,None,None
,None,None,None,None,592184.803802073,None,None,None,None,None,None,None
,None,None,None,None,None,None,None,18098.725166318,None,None,None,None
,None,None,None,789.2710621298,None,None,7450.9539135753,4251.6033622036
,None,None,None,None,None,1277.1691956597,None,4273.5950324508,None
,51861.5572682614,19415.3369388317,2117.2407148378,None,160776.887146683
,None,None,None,None,None,None,None,None,None,1550.3003177484,None
,402333.163939038,None,None,16604.3340243551,None,None,None,None,None
,32545.0784355136,None,None,None,None,None,None,None,2567.9264180605,None
,None,45786.935597305,None,None,None,None,None,None,None,5645.5218715636
,None,None,None,None]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, '.')
fig.savefig('/home/me/test_svg', format='svg')
fig.savefig('/home/me/test_png', format='png')
The result:
PNG:
SVG:
The problem seems to be related to the None values. Though there is simply no point included if no matching point exists, it seems to influence the rendering of the SVG. Removing both entries if at one or the other point is None fixes the issue.
data = np.array([x, y])
data = data.transpose()
# Filter out pairs of points of which at least one is None.
data = [pair for pair in data if pair[0] and pair[1]]
data = np.array(data).transpose()
x = data[0]
y = data[1]
ax.plot(x, y, '.')
fig.savefig('/home/me/test_svg', format='svg')
fig.savefig('/home/me/test_png', format='png')
Update
This looks like a bug that was fixed some time between matplotlib 2.0.0 and 3.1.1. Upgrading solved the problem for me.
Original Answer
I ran into the same problem, so I created a minimal example to reproduce it:
import numpy as np
from matplotlib import pyplot as plt
data = np.array([1.0, np.nan, 1.0])
plt.plot(data, 'o')
plt.savefig('example.svg')
plt.savefig('example.png')
It works fine as a PNG:
However, the left point is missing from the SVG.
Using your suggestion of removing invalid data, I used the numpy indexing features:
import numpy as np
from matplotlib import pyplot as plt
data = np.array([1.0, np.nan, 1.0])
indexes = np.arange(data.size)
is_valid = np.negative(np.isnan(data))
plt.plot(indexes[is_valid], data[is_valid], 'o')
plt.savefig('example.svg')
plt.savefig('example.png')
Now the PNG and the SVG display both points.
Related
Heatmap from 3D-data, with float-numbers
I am trying to generate a heatmap from 3D-data in a csv-file. The csv-file has the format x,y,z for each line. The problem is when I create a array to link the values, I can't use float-numbers as keys. When setting the dtype to int in np.loadtext(), the code works fine; but this makes the resolution only half of what the csv-file can replicate. Is there another way of linking the values? The code so far is: import numpy as np import seaborn as sb import matplotlib.pyplot as plt fname = 'test18.csv' x, y, z = np.loadtxt(fname, delimiter=',', dtype=float).T pltZ = np.zeros((y.max()+1, x.max()+1), dtype=float) pltZ[y, x] = z heat_map = sb.heatmap(pltZ, cmap=plt.cm.rainbow) plt.show()
matplotlib event.xdata out of timeries range
Having an issue using matplotlib event.xdata when plotting pandas.Timeseries, I tried to reproduce the answer proposed in a very related question, but get a very strange behavior. Here's the code, adapted to python3 and with a little more stuff in the on_click() function: import numpy as np import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates def on_click(event): if event.inaxes is not None: # provide raw and converted x data print(f"{event.xdata} --> {mdates.num2date(event.xdata)}") # add a vertical line at clicked location line = ax.axvline(x=event.xdata) plt.draw() t = pd.date_range('2015-11-01', '2016-01-06', freq='H') y = np.random.normal(0, 1, t.size).cumsum() df = pd.DataFrame({'Y':y}, index=t) fig, ax = plt.subplots() line = None df.plot(ax=ax) fig.canvas.mpl_connect('button_press_event', on_click) plt.show() If I launch this, I get the following diagram, with expected date range between Nov. 2015 and Jan. 2016, as is the cursor position information provided in the footer of the window (here 2015-11-01 10:00), and correct location of the vertical lines: However, the command-line output is as follows: C:\Users\me\Documents\code\>python matplotlib_even.xdate_num2date.py 402189.6454115977 --> 1102-02-27 15:29:23.562039+00:00 402907.10400704964 --> 1104-02-15 02:29:46.209088+00:00 Those event.xdata values are clearly out of both input data range and x axis data range, and are unusable for later use (like, try to find the closest y value in the serie). So, does anyone know how I can get a correct xdata?
Something must have changed in the way matplotlib/pandas handles datetime info between the answer to the related question you linked and now. I cannot comment on why, but I found a solution to your problem. I went digging through the code that shows the coordinates in the bottom left of the status bar, and I found that when you're plotting a timeseries, pandas patches the functions that prints this info and replaces it with this one. From there, you can see that you need to convert the float value to a Period object. import numpy as np import pandas as pd import matplotlib.pyplot as plt def on_click(event): print(pd.Period(ordinal=int(event.xdata), freq='H')) t = pd.date_range('2015-11-01', '2016-01-06', freq='H') y = np.random.normal(0, 1, t.size).cumsum() df = pd.DataFrame({'Y': y}, index=t) fig, ax = plt.subplots() df.plot(ax=ax) fig.canvas.mpl_connect('button_press_event', on_click) plt.show()
HDF5 file to diagram in python
I'm trying to generate some diagrams from an .h5 file but I don't know how to do it. I'm using pytables, numpy and matplotlib. The hdf5 files I use contains 2 sets of data, 2 differents curves. My goal is to get diagrams like this one. This is what I managed to do for the moment: import tables as tb import numpy as np import matplotlib.pyplot as plt h5file = tb.openFile(args['FILE'], "a") for group in h5file.walkGroups("/"): for array in h5file.walkNodes("/","Array"): if(isinstance(array.atom.dflt, int)): tab = np.array(array.read()) x = tab[0] y = tab[1] plt.plot(x, y) plt.show() x and y values are good but I don't know how to use them, so the result is wrong. I get a triangle instead of what I want ^^ Thank you for your help EDIT I solved my problem. Here is the code : fig = plt.figure() tableau = np.array(array.read()) x = tableau[0] y = tableau[1] ax1 = fig.add_subplot(211) ax2 = fig.add_subplot(212) ax1.plot(x) ax2.plot(y) plt.title(array.name) plt.show()
Interactive pixel information of an image in Python?
Short version: is there a Python method for displaying an image which shows, in real time, the pixel indices and intensities? So that as I move the cursor over the image, I have a continually updated display such as pixel[103,214] = 198 (for grayscale) or pixel[103,214] = (138,24,211) for rgb? Long version: Suppose I open a grayscale image saved as an ndarray im and display it with imshow from matplotlib: im = plt.imread('image.png') plt.imshow(im,cm.gray) What I get is the image, and in the bottom right of the window frame, an interactive display of the pixel indices. Except that they're not quite, as the values are not integers: x=134.64 y=129.169 for example. If I set the display with correct resolution: plt.axis('equal') the x and y values are still not integers. The imshow method from the spectral package does a better job: import spectral as spc spc.imshow(im) Then in the bottom right I now have pixel=[103,152] for example. However, none of these methods also shows the pixel values. So I have two questions: Can the imshow from matplotlib (and the imshow from scikit-image) be coerced into showing the correct (integer) pixel indices? Can any of these methods be extended to show the pixel values as well?
There a couple of different ways to go about this. You can monkey-patch ax.format_coord, similar to this official example. I'm going to use a slightly more "pythonic" approach here that doesn't rely on global variables. (Note that I'm assuming no extent kwarg was specified, similar to the matplotlib example. To be fully general, you need to do a touch more work.) import numpy as np import matplotlib.pyplot as plt class Formatter(object): def __init__(self, im): self.im = im def __call__(self, x, y): z = self.im.get_array()[int(y), int(x)] return 'x={:.01f}, y={:.01f}, z={:.01f}'.format(x, y, z) data = np.random.random((10,10)) fig, ax = plt.subplots() im = ax.imshow(data, interpolation='none') ax.format_coord = Formatter(im) plt.show() Alternatively, just to plug one of my own projects, you can use mpldatacursor for this. If you specify hover=True, the box will pop up whenever you hover over an enabled artist. (By default it only pops up when clicked.) Note that mpldatacursor does handle the extent and origin kwargs to imshow correctly. import numpy as np import matplotlib.pyplot as plt import mpldatacursor data = np.random.random((10,10)) fig, ax = plt.subplots() ax.imshow(data, interpolation='none') mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w')) plt.show() Also, I forgot to mention how to show the pixel indices. In the first example, it's just assuming that i, j = int(y), int(x). You can add those in place of x and y, if you'd prefer. With mpldatacursor, you can specify them with a custom formatter. The i and j arguments are the correct pixel indices, regardless of the extent and origin of the image plotted. For example (note the extent of the image vs. the i,j coordinates displayed): import numpy as np import matplotlib.pyplot as plt import mpldatacursor data = np.random.random((10,10)) fig, ax = plt.subplots() ax.imshow(data, interpolation='none', extent=[0, 1.5*np.pi, 0, np.pi]) mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w'), formatter='i, j = {i}, {j}\nz = {z:.02g}'.format) plt.show()
An absolute bare-bones "one-liner" to do this: (without relying on datacursor) def val_shower(im): return lambda x,y: '%dx%d = %d' % (x,y,im[int(y+.5),int(x+.5)]) plt.imshow(image) plt.gca().format_coord = val_shower(ims) It puts the image in closure so makes sure if you have multiple images each will display its own values.
All of the examples that I have seen only work if your x and y extents start from 0. Here is code that uses your image extents to find the z value. import numpy as np import matplotlib.pyplot as plt fig, ax = plt.subplots() d = np.array([[i+j for i in range(-5, 6)] for j in range(-5, 6)]) im = ax.imshow(d) im.set_extent((-5, 5, -5, 5)) def format_coord(x, y): """Format the x and y string display.""" imgs = ax.get_images() if len(imgs) > 0: for img in imgs: try: array = img.get_array() extent = img.get_extent() # Get the x and y index spacing x_space = np.linspace(extent[0], extent[1], array.shape[1]) y_space = np.linspace(extent[3], extent[2], array.shape[0]) # Find the closest index x_idx= (np.abs(x_space - x)).argmin() y_idx= (np.abs(y_space - y)).argmin() # Grab z z = array[y_idx, x_idx] return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, z) except (TypeError, ValueError): pass return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, 0) return 'x={:1.4f}, y={:1.4f}'.format(x, y) # end format_coord ax.format_coord = format_coord If you are using PySide/PyQT here is an example to have a mouse hover tooltip for the data import matplotlib matplotlib.use("Qt4Agg") matplotlib.rcParams["backend.qt4"] = "PySide" import matplotlib.pyplot as plt fig, ax = plt.subplots() # Mouse tooltip from PySide import QtGui, QtCore mouse_tooltip = QtGui.QLabel() mouse_tooltip.setFrameShape(QtGui.QFrame.StyledPanel) mouse_tooltip.setWindowFlags(QtCore.Qt.ToolTip) mouse_tooltip.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) mouse_tooltip.show() def show_tooltip(msg): msg = msg.replace(', ', '\n') mouse_tooltip.setText(msg) pos = QtGui.QCursor.pos() mouse_tooltip.move(pos.x()+20, pos.y()+15) mouse_tooltip.adjustSize() fig.canvas.toolbar.message.connect(show_tooltip) # Show the plot plt.show()
with Jupyter you can do so either with datacursor(myax)or by ax.format_coord. Sample code: %matplotlib nbagg import numpy as np import matplotlib.pyplot as plt X = 10*np.random.rand(5,3) fig,ax = plt.subplots() myax = ax.imshow(X, cmap=cm.jet,interpolation='nearest') ax.set_title('hover over the image') datacursor(myax) plt.show() the datacursor(myax) can also be replaced with ax.format_coord = lambda x,y : "x=%g y=%g" % (x, y)
In case you, like me, work on Google Colab, this solutions do not work as Colab disabled interactive feature of images for matplotlib. Then you might simply use Plotly: https://plotly.com/python/imshow/ import plotly.express as px import numpy as np img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]], [[0, 255, 0], [0, 0, 255], [255, 0, 0]] ], dtype=np.uint8) fig = px.imshow(img_rgb) fig.show()
Matplotlib has built-in interactive plot which logs pixel values at the corner of the screen. To setup first install pip install ipympl Then use either %matplotlib notebook or %matplotlib widget instead of %matplotlib inline The drawback with plotly or Bokeh is that they don't work on Pycharm. For more information take a look at the doc
To get interactive pixel information of an image use the module imagetoolbox To download the module open the command prompt and write pip install imagetoolbox Write the given code to get interactive pixel information of an image enter image description here Output:enter image description here
Python Matplotlib add Colorbar
i've got a problem using MatlobLib with "Custom" Shapes from a shapereader. Importing and viewing inserted faces works fine, but i'm not able to place a colorbar on my figure. I already tried several ways from the tutorial, but im quite sure there is a smart solution for this problem. maybe somebody can help me, my current code is attached below: from formencode.national import pycountry import itertools from matplotlib import cm, pyplot from matplotlib import from mpl_toolkits.basemap import Basemap from numpy.dual import norm import cartopy.crs as ccrs import cartopy.io.shapereader as shpreader import matplotlib as mpl import matplotlib.colors as colors import matplotlib.mlab as mlab import matplotlib.pyplot as plt import numpy as np def draw_map_for_non_normalized_data_with_alpha2_counrty_description(data, title=None): m = Basemap() ax = plt.axes(projection=ccrs.PlateCarree()) list = [] sum = 0 for key in data: sum += data[key] for key in data.keys(): new_val = (data[key]+0.00)/sum list.append(new_val) data[key] = new_val #=========================================================================== # print str(min(list)) # print str(max(list)) #=========================================================================== cmap = mpl.cm.cool colors = matplotlib.colors.Normalize(min(list)+0.0, max(list)+0.0) labels = [] features = [] for country in shpreader.Reader(shapename).records(): a3_code = country.attributes["gu_a3"] try : a2_code = pycountry.countries.get(alpha3=a3_code).alpha2 except: a2_code = "" if a2_code in data: val = data[a2_code] color = cm.jet(norm(val)) print str(val) + " value for color: " + str(color) labels.append(country.attributes['name_long']) feat = ax.add_geometries(country.geometry, ccrs.PlateCarree(), facecolor=color, label=country.attributes['name_long']) features.append(feat) #ax.legend(features, labels, loc='upper right') #=========================================================================== # fig = pyplot.figure(figsize=(8,3)) # ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15]) #=========================================================================== #cbar = m.colorbar(location='bottom') cb1 = mpl.colorbar.ColorbarBase(ax, cmap=cmap,norm=colors,orientation='horizontal') cb1.set_label('foo') m.drawcoastlines() m.drawcountries() if title: plt.title(title) plt.show() as you can see inside the code, i already tried several ways, but none of them worked for me. maybe somebody has "the" hint for me. thanks for help, kind regards
As mentioned in the comments above, i would think twice about mixing Basemap and Cartopy, is there a specific reason to do so? Both are basically doing the same thing, extending Matplotlib with geographical plotting capabilities. Both are valid to use, they both have their pro's and con's. In your example you have a Basemap axes m, a Cartopy axes ax and you are using the Pylab interface by using plt. which operates on the currently active axes. Perhaps it theoretically possible, but it seems prone to errors to me. I cant modify your example to make it work, since the data is missing and your code is not valid Python, the indentation for the function is incorrect for example. But here is a Cartopy-only example showing how you can plot a Shapefile and use the same cmap/norm combination to add a colorbar to the axes. One difference with your code is that you provide the axes containing the map to the ColorbarBase function, this should be a seperate axes specifically for the colorbar. import cartopy.crs as ccrs import matplotlib.pyplot as plt import matplotlib as mpl import cartopy.io.shapereader as shpreader fig, ax = plt.subplots(figsize=(12,6), subplot_kw={'projection': ccrs.PlateCarree()}) norm = mpl.colors.Normalize(vmin=0, vmax=1000000) cmap = plt.cm.RdYlBu_r for n, country in enumerate(shpreader.Reader(r'D:\ne_50m_admin_0_countries_lakes.shp').records()): ax.add_geometries(country.geometry, ccrs.PlateCarree(), facecolor=cmap(norm(country.attributes['gdp_md_est'])), label=country.attributes['name']) ax.set_title('gdp_md_est') cax = fig.add_axes([0.95, 0.2, 0.02, 0.6]) cb = mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm, spacing='proportional') cb.set_label('gdp_md_est')