Skip to content

Comments

grass.jupyter: Created attracting and elegant legend feature for rasters in InteractiveMap.#7077

Open
shahid-rahaman wants to merge 7 commits intoOSGeo:mainfrom
shahid-rahaman:elegant-legend-for-rasters
Open

grass.jupyter: Created attracting and elegant legend feature for rasters in InteractiveMap.#7077
shahid-rahaman wants to merge 7 commits intoOSGeo:mainfrom
shahid-rahaman:elegant-legend-for-rasters

Conversation

@shahid-rahaman
Copy link

@shahid-rahaman shahid-rahaman commented Feb 11, 2026

Created legend feature for rasters with easy to use commands. Gave it a transparent glass frost look for better UX.
@wenzeslaus If looks good to you, I will write the tests for the corresponding entities.
Let me know if requires any adjustments.
For instance, use it like below.

from grass.jupyter import InteractiveMap
m = InteractiveMap(height=700, width = 1000)
raster = "elevation"
gs.run_command('r.colors', map=raster, color='rainbow')
m.add_raster(raster)
m.add_legend(raster, position = "bottomright")
m.show()
elevation-rainbow
from grass.jupyter import InteractiveMap
m = InteractiveMap(height=700, width = 1000)
raster = "elevation"
gs.run_command('r.colors', map=raster, color='viridis')
m.add_raster(raster)
m.add_legend(raster, position = "bottomright")
m.show()
elevation-viridis
from grass.jupyter import InteractiveMap
m = InteractiveMap(height=700, width = 1000)
raster = "geology_30m"
gs.run_command('r.colors', map=raster, color='viridis')
m.add_raster(raster)
m.add_legend(raster, position = "bottomright")
m.show()
geology-viridis
from grass.jupyter import InteractiveMap
m = InteractiveMap(height=700, width = 1000)
raster = "geology_30m"
gs.run_command('r.colors', map=raster, color='rainbow')
m.add_raster(raster)
m.add_legend(raster, position = "bottomright")
m.show()
geology-rainbow
from grass.jupyter import InteractiveMap
m = InteractiveMap(height=700, width = 1000)
raster = "facility"
gs.run_command('r.colors', map=raster, color='viridis')
m.add_raster(raster)
m.add_legend(raster, position = "bottomright")
m.show()
facility-viridis
from grass.jupyter import InteractiveMap
m = InteractiveMap(height=700, width = 1000)
raster = "roadsmajor"
gs.run_command('r.colors', map=raster, color='rainbow')
m.add_raster(raster)
m.add_legend(raster, position = "bottomright")
m.show()
roadsmajor-rainbow

You can use any colors like sepia, rainbow, etc. apart from viridis and magma.

regards

@shahid-rahaman shahid-rahaman force-pushed the elegant-legend-for-rasters branch from f0b4776 to e568a16 Compare February 11, 2026 22:36
@wenzeslaus
Copy link
Member

Reproducible code examples and screenshots?

@github-actions github-actions bot added Python Related code is in Python libraries notebook labels Feb 12, 2026
@shahid-rahaman
Copy link
Author

shahid-rahaman commented Feb 12, 2026

Reproducible code examples and screenshots?

@wenzeslaus Sorry for late reply, I have updated the description. I have been facing some dev system crash. Is there any standard way to setup dev environment? I somehow did but it was quirky, I installed grass from local repo by ./configure>make -j10> sudo make install, but it throws few errors sometimes due to which I cannot run local test/checks as it is not installed properly. Am I missing something or is there any documentation to setup dev enviroment?
regards

@shahid-rahaman shahid-rahaman changed the title Created attracting and elegant legend feature for rasters. grass.jupyter: Created attracting and elegant legend feature for rasters in InteractiveMap. Feb 12, 2026
Copy link
Member

@wenzeslaus wenzeslaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the legend in screenshots does look attractive and elegant.

Anyway, you should still make the title shorter and more technical.

More work is needed here and I did not test myself yet, but I like the direction here.

@shahid-rahaman
Copy link
Author

@wenzeslaus Thanks for your time, I understand there are some preferred approach for GRASS, I used python core logics and module to make it work, like "subprocess", I had used it several times personally. However, I will try align the code with the existing dependencies and preferred approach.
regards

@wenzeslaus
Copy link
Member

Please, sync up to the main branch.

Then, the following link will allow reviewers to test without the need to have the changes locally:

https://mybinder.org/v2/gh/shahid-rahaman/grass/elegant-legend-for-rasters?urlpath=lab%2Ftree%2Fdoc%2Fexamples%2Fnotebooks%2Fjupyter_example.ipynb

@wenzeslaus
Copy link
Member

I didn't get any legend on mobile.

@shahid-rahaman shahid-rahaman force-pushed the elegant-legend-for-rasters branch 2 times, most recently from 939dcaf to cbb28e3 Compare February 14, 2026 21:08
from branca.element import MacroElement
import folium
from folium.map import MacroElement
from jinja2 import Template
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wenzeslaus You earlier worried about these dependencies, but both of them are the dependencies of of folium itself See.
Folium has the following dependencies, all of which are installed automatically with the above installation commands:
branca, Jinja2, Numpy ,Requests
However, I avoided branca, as it is not very active but we can use jinja as it is already installed. Folium internally calles Template() method of jinja to create Template object. And this object is essential as it further calls for module attribute associate with this object. So, far it is working fine as expected as per my observation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. While it is not bulletproof, transitive vs direct dependency makes as difference.

@shahid-rahaman
Copy link
Author

shahid-rahaman commented Feb 14, 2026

I didn't get any legend on mobile.

@wenzeslaus I have not checked it on the phones yet. I am not sure if phones allows html code to be injected the same way. Although, in chrome dev tool when I toggle the device to be iphone 12 or responsive, I see the legend there. So, I think it is the not the resolution problem.
regards

@shahid-rahaman
Copy link
Author

@wenzeslaus Any update here?

Copy link
Member

@wenzeslaus wenzeslaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this and I get the legend in Binder at least with a desktop. However, there is couple of issues here:

  1. Now, I always get a viridis. The logic is wrong.
Image
  1. The gradient values taken from GRASS may not work as is with CSS as visible in the image - at least in Firefox (see above).
  2. Your previous examples were much better. Also add geology. Make them copy-pastable to the notebooked used in Binder. Test your result as if you would be a user. Use a tutorial.
  3. See my other comments.

Just a heads-up that this starts to feel like interacting with AI. Please, review our AI policy in the contributing file.

colors = [item["color"] for item in color_maps]
css_gradient = ", ".join(colors)
# Setting some interval ticks to compare values relatively.
info = tools.r_info(map=raster, format='json')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explore what you get with the r_colors_out call, r_info, and r_univar for the range of values for different computational regions (after changing the extent by g_region/g.region user-level call to change the extent which will change what values are considered). This influenced whether or not the r_info call here appropriate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont' get me wrong, I am not defensive, I do checkout r_colors and r_info without seeing the nature of returned values of APIs we cannot frame a code, I am not a seasonal dev, I am quite new and a GSoC aspirant. Although, trying to give my best hit and not to leave any stone unturned. I see the relevant parts of docs as I progress. I do use LLMs to understand code snippets sometimes as it is not practical to tag maintainers for every queries. Correct me if I said something wrong.
regards

@shahid-rahaman
Copy link
Author

I like this and I get the legend in Binder at least with a desktop. However, there is couple of issues here:

  1. Now, I always get a viridis. The logic is wrong.
Image 2. The gradient values taken from GRASS may not work as is with CSS as visible in the image - at least in Firefox (see above). 3. Your previous examples were much better. Also add geology. Make them copy-pastable to the notebooked used in Binder. Test your result as if you would be a user. Use a tutorial. 4. See my other comments.

Just a heads-up that this starts to feel like interacting with AI. Please, review our AI policy in the contributing file.

@wenzeslaus Thanks for review. Very first thing I don't trust AI generated code they are very often poorly strcutured and redundant. Although I use LLMs for code review and guidance, like where to find the appropriate info in the documentation. Now, lets, come to your concerns.

  1. Firstly, I think that is rainbow color scheme. Rainbow produces such vibrant colors.
rainbow But I dont know why binder shows "rainbow" colors as you have not provided any color arguments and default is "viridis". And there is indeed a bug I am working on that is when a color scheme is applied it is somehow saved in cache or something(I am not sure yet) but when you refresh the same cell it renders the rasters with desired colors. I should see some binder usage.

@wenzeslaus
Copy link
Member

Firstly, I think that is rainbow color scheme. Rainbow produces such vibrant colors.

The geology map in that sample dataset has colors already set (it may be indeed rainbow). Importantly, the resulting color table is not a gradient one. The values are categorical. Run r.report to understand more about this specific data. Compare to elevation. Aspect may be yet a little different.

But I dont know why binder shows "rainbow" colors as you have not provided any color arguments and default is "viridis".

viridis is used as a default internally. No work from users or in your code is needed. I hope this clarifies that.

And there is indeed a bug I am working on that is when a color scheme is applied it is somehow saved in cache or something(I am not sure yet) but when you refresh the same cell it renders the rasters with desired colors.

The color table is saved with the data in the GRASS project, so in a way, r.colors call modifies the data (or let's say visualization metadata), so that's why you should not be using it in the code (you should have an example which uses that, though). Generate new data with r.surf.fractal to get an example for that use case.

@shahid-rahaman
Copy link
Author

shahid-rahaman commented Feb 16, 2026

Firstly, I think that is rainbow color scheme. Rainbow produces such vibrant colors.

The geology map in that sample dataset has colors already set (it may be indeed rainbow). Importantly, the resulting color table is not a gradient one. The values are categorical. Run r.report to understand more about this specific data. Compare to elevation. Aspect may be yet a little different.

But I dont know why binder shows "rainbow" colors as you have not provided any color arguments and default is "viridis".

viridis is used as a default internally. No work from users or in your code is needed. I hope this clarifies that.

And there is indeed a bug I am working on that is when a color scheme is applied it is somehow saved in cache or something(I am not sure yet) but when you refresh the same cell it renders the rasters with desired colors.

The color table is saved with the data in the GRASS project, so in a way, r.colors call modifies the data (or let's say visualization metadata), so that's why you should not be using it in the code (you should have an example which uses that, though). Generate new data with r.surf.fractal to get an example for that use case.

@wenzeslaus In the meantime, I observed binder itself behaves little quirky. Forget about the add_legend method for now and kindly see the snippet below.

m = InteractiveMap(height = 500, width = 1000)
raster = "elevation"
gs.run_command('r.colors', map=raster, color='magma')
m.add_raster(raster)
m.show()
binder1
m = InteractiveMap(height = 500, width = 1000)
raster = "elevation"
gs.run_command('r.colors', map=raster, color='viridis')
m.add_raster(raster)
m.show()
binder1

No matter what color scheme I choose it never updates the rendering for any rasters, although add_legend method works fine it generates as per any given color scheme. Firefox SS you shared worked fine in my end.
While working in actual jupyter lab or notebook works fine in both chrome and firefox but binder produces buggy and quirky outputs.

Also, I can see the legend on my mobile(iPhone 12 mini).
image

Am I missing something?
regards

@shahid-rahaman shahid-rahaman force-pushed the elegant-legend-for-rasters branch from 382ebd8 to 5ce1fd6 Compare February 18, 2026 17:50
@shahid-rahaman
Copy link
Author

shahid-rahaman commented Feb 18, 2026

@wenzeslaus I have tried changes as per your suggestion. But first see this comment.

  • legend only consists colors which are used in rendering, no interpolation like earlier. (You can use rainbow color to confirm).
  • add_raster already had the feature to change color table. Therefore, complying with the existing datas.
    Please see my earllier comment above for the context.
    See if it requires further changes. I have updated the examples and usage in the description atop.
    regards

finished += color_width
colors.extend(color)

css_gradient = ", ".join(colors)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only contains the colors used in the rendering. No interpolation to fill and make smoother transition.

minv = float(info["min"])
maxv = float(info["max"])
num_ticks = len(color_maps) if len(color_maps) < 7 else 6
ticks = [minv + i * (maxv - minv) / (num_ticks - 1) for i in range(num_ticks)] if num_ticks > 1 else [minv]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some rasters like roadsmajor, it returns only one data which earlier caused ZeroDivisionErro, I also address that.

@shahid-rahaman
Copy link
Author

@wenzeslaus If we call this add_legend via add_raster via arguemnt, I think that would be more succinct and dynamic. For instance we can do as following with add_raster.

def add_raster(self, name, title=None, legend=False, **kwargs)

if user enters legend arguement with it's locaation like bottomright, we can all add_legend with such arguemnt.
What's you thought?
regards

@shahid-rahaman
Copy link
Author

@wenzeslaus Any updates? Is it fine to tag you every alternative day? I am not sure if disturbing you.
regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libraries notebook Python Related code is in Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants