1

I want the edgecolor of an ellipse to be a function of a third variable. Let's assume the third variable is called flux. If the value of the variable 'flux' is high, I want the edgecolor of the ellipse to be dark blue, if the value is low I want the color to be yellow. Any intermediate values should be a mix of these colors. I want this color gradient to be visible on the z axis of the plot with the highest and lowest values. I tried referring to this link Matplotlib scatterplot; colour as a function of a third variable but this doesn't seem to apply in my case. I am reading the parameters required to plot the ellipse from a text file which looks like this:

149.20562 2.29594 0.00418 0.00310 83.40 1.15569 

149.23158 1.99783 0.00437 0.00319 90.30 3.46331 

149.23296 2.45440 0.00349 0.00264 120.30 2.15457 

The fifth column is the column named 'flux' based on which the color gradient must be plotted.

Here is an example of my attempt.

import matplotlib.pyplot as plt
import numpy as np
import math
import astropy.io.ascii as asciitable
from matplotlib.patches import Ellipse
ax = plt.gca()
path="/users/xxxx/Desktop/"
plt.xlim([149,151.3])
plt.ylim([1,3.3])
fw=open(path + 'data_plot.txt', 'r')
data = asciitable.read(path+ "data_plot.txt") 
np.array(data)
for i in range(len(data)):
    ra,dec,maj,minor,ang,flux =data[i][0],data[i][1],data[i][2],data[i][3],data[i][4],data[i][5]
    ellipse = Ellipse(xy=(ra, dec), width=maj, height=minor, angle=ang, edgecolor=flux, lw=3, fc='None')
    ax.add_patch(ellipse) 


plt.xlabel('Right Ascention')
plt.ylabel('Declination')
plt.title('abc') 
plt.savefig(path+'abc.eps')

As expected this didn't work. Here is my error log.

runfile('/users/vishnu/.spyder2-py3/radio_sources.py',   wdir='/users/vishnu/.spyder2-py3')


    Traceback (most recent call last):

  File "<ipython-input-695-a0011c0326f5>", line 1, in <module>
    runfile('/users/vishnu/.spyder2-py3/radio_sources.py', wdir='/users/vishnu/.spyder2-py3')

  File "/users/vishnu/anaconda3/lib/python3.5/site-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 699, in runfile
    execfile(filename, namespace)

  File "/users/vishnu/anaconda3/lib/python3.5/site-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 88, in execfile
    exec(compile(open(filename, 'rb').read(), filename, 'exec'), namespace)

  File "/users/vishnu/.spyder2-py3/radio_sources.py", line 63, in <module>
    ellipse = Ellipse(xy=(ra, dec), width=maj, height=minor, angle=ang, edgecolor=flux, lw=3, fc='None')

  File "/users/vishnu/anaconda3/lib/python3.5/site-packages/matplotlib/patches.py", line 1378, in __init__
    Patch.__init__(self, **kwargs)

  File "/users/vishnu/anaconda3/lib/python3.5/site-packages/matplotlib/patches.py", line 111, in __init__
    self.set_edgecolor(edgecolor)

  File "/users/vishnu/anaconda3/lib/python3.5/site-packages/matplotlib/patches.py", line 277, in set_edgecolor
    self._edgecolor = colors.colorConverter.to_rgba(color, self._alpha)

  File "/users/vishnu/anaconda3/lib/python3.5/site-packages/matplotlib/colors.py", line 376, in to_rgba
    'to_rgba: Invalid rgba arg "%s"\n%s' % (str(arg), exc))

ValueError: to_rgba: Invalid rgba arg "1.15569"
to_rgb: Invalid rgb arg "1.15569"
cannot convert argument to rgb sequence
Community
  • 1
  • 1
Vishnu
  • 499
  • 1
  • 5
  • 23

1 Answers1

3

You just need to change the value for flux into a matplotlib colour. We could use a colormap to do this, or in your case you could use flux to just define the colour, assuming some minimum and maximum values.

Since yellow is just a mix of red and green, we can use 1 minus the normalised flux for the R and G channels, and the the normalised flux as the B channel of your RGB tuple to make the matplotlib color.

# Change these based on your definition of a 'high' value and a 'low' value (or the min/max of the data)
minflux = data[:][5].min()
maxflux = data[:][5].max()

for i in range(len(data)):
    ra,dec,maj,minor,ang,flux =data[i][0],data[i][1],data[i][2],data[i][3],data[i][4],data[i][5]

    # Normalise the flux value to the range 0-1
    normflux = (flux - minflux) / (maxflux - minflux)
    # RGB tuple. This will be yellow for min value and blue for max value
    fluxcolor = (1.-normflux, 1.-normflux, normflux)

    ellipse = Ellipse(xy=(ra, dec), width=maj, height=minor, angle=ang, edgecolor=fluxcolor, lw=3, fc='None')
    ax.add_patch(ellipse) 

Here's a minimal example to check it works:

import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import numpy as np

fig,ax = plt.subplots(1)

minflux = 0.
maxflux = 10.

for i in range(10):

    flux = float(i)

    normflux = (flux - minflux) / (maxflux - minflux)
    fluxcolor = (1.-normflux, 1.-normflux, normflux)

    ell = Ellipse(xy=(i+1,0.5), width=0.5, height=0.3, angle=90., edgecolor=fluxcolor, lw=3, fc='None')

    ax.add_patch(ell)

ax.set_xlim(0,11)
plt.show()

enter image description here


To also add a colorbar, perhaps the easiest way is to use a colormap instead of the method I showed above. In this case, we can also use a PatchCollection to add all the ellipses to the axes, and then set the collection's array to the flux values to define their colours.

For example:

import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import numpy as np

import matplotlib.colors as colors
from matplotlib.collections import PatchCollection

# Define our colormap here. 
# Want red and green to be 1 at 0, and 0 at 1. Blue to be 0 at 0, and 1 at 1.
cdict = {'red':  ((0.0,1.0,1.0),
                  (1.0,0.0,0.0)),
        'green': ((0.0,1.0,1.0),
                  (1.0,0.0,0.0)),
        'blue':  ((0.0,0.0,0.0),
                  (1.0,1.0,1.0))
        }
# Use that dictionary to define the Linear SegmentedColormap
YlBu = colors.LinearSegmentedColormap('YlBu',cdict)

# Create figure
fig,ax = plt.subplots(1)

# Set the aspect ratio
ax.set_aspect('equal')

# We will populate these lists as we loop over our ellipses
ellipses = []
fluxes = []

for i in range(9):

    # Use i as a dummy value for our flux
    flux = float(i)

    # Store the fluxes. You have this already in your data array
    fluxes.append(flux)

    # Angle is in degrees
    angle = float(i) * 45.

    # Create the ellipse patch. Don't add to the axes yet
    ell = Ellipse(xy=(i,2.), width=0.8, height=0.2, angle=angle)

    # Just add it to this list
    ellipses.append(ell)

# Now, create a patch collection from the ellipses. 
# Turn off facecolor, and set the colormap to the one we created earlier
# For now, set edgecolor to black; we will change this in the next step
pc = PatchCollection(ellipses, match_original=False, lw=3, cmap=YlBu, facecolor='None', edgecolor='k')

# Set the color array here.
pc.set_array(np.array(fluxes))

# Now we add the collection to the axes
ax.add_collection(pc)

# And create a colorbar
fig.colorbar(pc,orientation='horizontal')

# Set the axes limits
ax.set_xlim(-1,9)
ax.set_ylim(0,4)

plt.show()

enter image description here

tmdavison
  • 64,360
  • 12
  • 187
  • 165
  • How do I display the color map on the z axis so that if someone sees the plot, they know that yellow indicates low value and blue indicates higher value? – Vishnu May 16 '16 at 20:38
  • I think that is easier with using a `PatchCollection` and defining a colormap. See my edited answer above. – tmdavison May 17 '16 at 11:18
  • That was a very well explained answer. Thank you. :) – Vishnu May 17 '16 at 22:12
  • Are you sure that the angles can be edited properly? For example if I set ell = Ellipse(xy=(i+0.5,0.5), width=0.5, height=0.3, angle=2*np.pi) The ellipse does not orient as expected. – Vishnu May 23 '16 at 11:17
  • ah, my bad. `angle` is in degrees, not radians. Also, make sure your axes has its aspect ratio set to `'equal'` : `ax.set_aspect('equal')`. See my updated answer – tmdavison May 23 '16 at 12:17
  • This gives a blank plot (with colorbar) for me, with Matplotlib 3.3.2. Changing `match_original` from `False` to `True` seems to fix it, but I don't understand this. – jmmcd Mar 31 '21 at 18:52
  • You're right, I think this is because we do not set any colour for the ellipse when we create it or the collection. Setting an edgecolor here (doesn't matter what we set it to, we change it later) seems to also work. Thanks for catching that, I edited the answer. – tmdavison Apr 01 '21 at 11:10