3

You can assign a colormap to a scatter plot by using scatter(X, Y, c=..., cmap="rainbow") for example, but is there a way to modify the colormap later?

I know we can change the transparency and color via set_color() and set_alpha() but I did not find the equivalent for colormaps. A workaround would be to save the data of the scatter, erase it, then scatter() again with new parameters but that is going to be expensive. That is an issue since the goal of this question is to be able to change the c parameter in real time so that old points "cool down" to darker colors.

Thanks for your time.


Edit to add more information: I would like to be able to switch back and forth between a scatter made with a single color and a scatter with a variable colormap and normalization. Between scatter(X, Y, color="#FF0000") and scatter(X, Y, c=age, cmap="rainbow") for example, with a variable age = [i for i,x in enumerate(X)].

Guimoute
  • 4,407
  • 3
  • 12
  • 28
  • 4
    There is also [`.set_cmap`](https://matplotlib.org/api/cm_api.html#matplotlib.cm.ScalarMappable.set_cmap). – ImportanceOfBeingErnest Dec 19 '18 at 10:11
  • Thank you! I looked at the documentation for [scatter()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.scatter.html) but did not think about `ScalarMappable`s. I assume using `set_cmap(None)` makes the curve use its initial color back? – Guimoute Dec 19 '18 at 10:22
  • 1
    If you have a scatter with a `c` argument defined, you necessarily need a colormap. If you want to make all points black, you can either set the array to all the same value (if black is in the chosen colormap), or use a colormap that only consists of black color (there is none within the defaults, so you need to create that via `ListedColormap`). If you want to modify your question to ask about the actual problem, one can sure be of more help here. – ImportanceOfBeingErnest Dec 19 '18 at 10:32

2 Answers2

2

Instead of changing the colormap, you can actually change the values. If you set an under color to the colormap, you can change the values to anything below the norm and have it appear in that color.

The following would set the values to one less than the minimal value when n is pressed. They are set back to their original value upon pressing m.

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(42)

a = np.random.randint(10,30, size=20)
x = np.random.rand(20)
y = np.random.rand(20)

cmap = plt.get_cmap("rainbow")
cmap.set_under("black")

fig, ax = plt.subplots()
ax.set_title("Press n and m keys to change colors")
sc = ax.scatter(x,y, c=a, cmap=cmap)

def change(evt):
    if evt.key in list("nm"):
        if evt.key == "n":
            sc.set_array(np.ones_like(a)*a.min()-1)
        if evt.key == "m":
            sc.set_array(a)
        fig.canvas.draw_idle()


fig.canvas.mpl_connect("key_press_event", change)

plt.show()

If you really need to start with a scatter that doesn't have its array defined, you can do that as shown in the below.

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(42)

a = np.random.randint(10,30, size=20)
x = np.random.rand(20)
y = np.random.rand(20)

cmap = plt.get_cmap("rainbow")
cmap.set_under("black")
norm = plt.Normalize(a.min(), a.max())

fig, ax = plt.subplots()
ax.set_title("Press n and m keys to change colors")
sc = ax.scatter(x,y, color="black")

def change(evt):
    if evt.key in list("nm"):
        sc.set_cmap(cmap)
        sc.set_norm(norm)
        if evt.key == "n":
            sc.set_array(np.ones_like(a)*a.min()-1)
        if evt.key == "m":
            sc.set_array(a)
        fig.canvas.draw_idle()
        

fig.canvas.mpl_connect("key_press_event", change)

plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thanks. So the under color is used when you try to reach a color outside the normalized range. Interesting. Your reply and @Diziet Asahi's seem to imply that we always have to specify a `cmap` and `c` even if we are going to ignore them later. I was hoping there was a way to avoid that, in the affirmative I could add a self-contained method to change the colormap of an existing curve, in the negative I have to do that AND go modify other bits of code everywhere to supply the initial colormap. – Guimoute Dec 19 '18 at 14:26
  • Well, as the other answer shows, you can bypass the array and directly set facecolors. I added an option to my answer where you start with a scatter that doesn't have its array defined. – ImportanceOfBeingErnest Dec 19 '18 at 14:51
1

It seems fairly straightforward to do using set_cmap() as pointed out by @ImportanceOfBeingErnest:

x = np.linspace(0,10,10)
y = x**2
c = np.linspace(-10,10,10)
cmap = "seismic"
norm = matplotlib.colors.Normalize(vmin=min(c),vmax=max(c))



from matplotlib.widgets import RadioButtons

fig, ax = plt.subplots()

scat = plt.scatter(x,y,c=c,cmap=cmap,norm=norm)
plt.colorbar()
scat.update_scalarmappable()
scat.set_facecolors('k')

plt.subplots_adjust(left=0.3)

axcolor = 'lightgoldenrodyellow'
rax = plt.axes([0.05, 0.7, 0.15, 0.15], facecolor=axcolor)
radio = RadioButtons(rax, ('no-cmap','cmap'))

def colorfunc(label):
    if label=="cmap":
        scat.set_cmap(cmap)
        scat.set_norm(norm)
    elif label=="no-cmap":
        scat.set_facecolors('k')
    else:
        raise KeyError("invalid label")
    plt.draw()
radio.on_clicked(colorfunc)

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75
  • Hi, thanks for the help. In between I added some precisions about my problem. For instance my initial plot won't have a `c` parameter but just a fixed color, so I'm missing some sort of `set_c()`. – Guimoute Dec 19 '18 at 11:10
  • If that's possible, I think you're better off creating the scatter with the colormap, then immediately change the color to black. I've amended my answer to illustrate this case – Diziet Asahi Dec 19 '18 at 11:20
  • Sorry for the slow reply, I am trying to see how to adapt my code to either your solution or @ImportanceOfBeingErnest's. It's interesting how you can use `scat.set_facecolors('k')` and not have to care about removing the colormap via `scat.set_cmap(None)`. Do you mind explaining what `scat.update_scalarmappable()` does? – Guimoute Dec 19 '18 at 14:22
  • See the comment by @ImportanceOfBeingErnest to my answer here https://stackoverflow.com/a/53631526/1356000 – Diziet Asahi Dec 19 '18 at 15:05