I’ve created a notebook with everything I’ve tried as to reproduce it you need the datafile too and this is the easiest way to share it as a whole:
This is what I get if I simply use the “terrain” colormap:
hv_image_basic = hv.Image(hv_dataset).opts(cmap='terrain', title='Basic')
The blue colors are misleading as those areas are above the water line too. Now this is how the colormap should work given values below and above ground level, creating a visualization that differentiates between ground and water. In this case however our dataset contains positive values, nothing below ground level, so what we want is to discard the blue part of the colormap and start from there.
hv_image_clim = hv.Image(hv_dataset).opts(cmap='terrain', clim=(-1666, 5007), title='clim')
The
clim
solution gives us the image we want, but it has two main problems:
-
Now the colorbar shows a range from -1666 even though our data starts at 0, which is expected as we just forced it to do this, but this is not what we want.
-
Also we just gave it some arbitrary numbers that may work for this exact dataset, but not necessarily for others as if the max value of the dataset would be twice as much, our artificial lower bound should be lower too:
min_val = float(hv_dataset.data[‘elevation’].min())
max_val = float(hv_dataset.data[‘elevation’].max())
val_range = max_val - min_val
artificial_min = int(min_val - (val_range / 3))
hv_image_clim_auto = hv.Image(hv_dataset).opts(cmap=‘terrain’, clim=(artificial_min, max_val), title=‘clim auto’)
The clim
solution with automatic values solves the second problem, now the data will always fall at the top 75% of the colormap no matter how big the highest value is. But we still have the missized colorbar.
hv_image_redim = hv.Image(hv_dataset).opts(cmap='terrain', title='redim.range()').redim.range(elevation=(-1500, 5007))
redim.range()
gives the exact same result as clim
. We could automate the values the same way, but the colorbar is still wrong.
So let’s try another direction and give the image exactly the colormap we want to see. I had an old export laying around with the top 75% of the terrain colormap exported into 50 colors, let’s see that:
manually_exported_colormap = ['#01CC66', '#0DCE68', '#1DD16B', '#2DD56F', '#3DD872', '#4DDB75', '#5DDE78', '#6DE17B', '#7DE57F', '#8DE882', '#9DEB85', '#ADEE88', '#BDF18B', '#C8F48E', '#D9F791', '#E8FA94', '#F9FD97', '#FAF896', '#F2EE91', '#EAE48D', '#E2D989', '#DACF85', '#D2C580', '#CABB7C', '#C2B078', '#BCA974', '#B39F70', '#AC946C', '#A38A67', '#9C8063', '#93765F', '#8C6B5A', '#836156', '#836058', '#8B6A63', '#93746E', '#9B7F79', '#A18681', '#A8908B', '#B19B96', '#B9A5A1', '#C1AFAB', '#C8B9B6', '#D1C4C1', '#D9CECC', '#E1D8D6', '#E8E2E1', '#F1EDEC', '#F9F7F6', '#FFFFFF']
hv_image_list_manual = hv.Image(hv_dataset).opts(cmap=manually_exported_colormap, title='manual colormap')
Now everything looks right at first glance, except that now we have few distinct colors, so both the colorbar and the image itself show heavy banding. Let’s se it with more colors then. For that we start from the original matplotlib colormap and export the desired amount of colors.
# 3 step export:
# - creating the 0.25-1 range we want to export, with 512 steps
# - for each step exporting the color at that position in the original colormap
# - for each export converting it to #RRGGBB format
cmap_terrain_top_75_percent_512 = [matplotlib.colors.rgb2hex(c) for c in plt.cm.terrain(np.linspace(0.25, 1, 512))]
hv_image_list_auto_512 = hv.Image(hv_dataset).opts(cmap=cmap_terrain_top_75_percent_512, title='exported colormap')
This worked, but just feels wrong. We just arbitrarily decided to create a list with 512 elements. Is that too much, causing performance issues? Is that too few, still causing banding? Well, I have no idea about the first question, but we can check the second one.
print('the first 15 colors: ', cmap_terrain_top_75_percent_512[0:15])
the first 15 colors: [‘#01cc66’, ‘#01cc66’, ‘#01cc66’, ‘#05cd67’, ‘#05cd67’, ‘#05cd67’, ‘#09ce68’, ‘#09ce68’, ‘#0dcf69’, ‘#0dcf69’, ‘#0dcf69’, ‘#11cf69’, ‘#11cf69’, ‘#11cf69’, ‘#15d06a’]
Okay, so in this case every 2-3 colors are the same, meaning we asked for more distinct colors the colormap possibly could provide. For this colormap. Is this the same for every colormap? Are there colormaps where we should use a bigger number? Is there any other way to know that other than running through every colormap and trying out increasing amounts of colors to see where the repetition starts?
So my current solution looks okay, and I’m completely fine with in practice, but it just feels like I’m doing something wrong with handling everything colormap-related manually, exporting a huge list and then importing it. It just feels like I missed a feature somewhere that would let me tell Holoviews to do all this and not reinvent the wheel. Like if I can give Holoviews just the name of a colormap and it can work directly with that colormap, meaning it already samples the colors it needs, then it is possible that there is a parameter that would le me tell it to use only the top colormap from 0.25 to 1 and I just did not find that.