18

I design a notebook so that variables that could be changed by the user are grouped into distinct cells throughout the notebook. I would like to highlight those cells with a different background color so that it is obvious to the user where the knobs are.

How could I achieve that?

NB: This related question was about static code highlighting (for a manual) and the accepted answer proposed to basically put everything in markup comments. In my case, I want highlighted code to be in a runnable cell.

P-Gn
  • 23,115
  • 9
  • 87
  • 104
  • How about using [widgets](https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html) to set those variables, so that the users do not have to touch the code at all? – SergiyKolesnikov Mar 22 '18 at 14:03
  • Not a bad idea, but I would say this is not equivalent. My understanding is that widgets live more in the interaction space, whereas here I want to change configuration variables. If one variable corresponds to a parameter to a long computation, I would like this parameter not to change during the computation, and make that clear to the user. I would personally stick to widgets for immediate interactive feedback (typically through `%interac`). – P-Gn Mar 22 '18 at 14:17
  • 1
    You can disable the widgets used to set configuration variables at the start of the computation. This way, you will prevent the user from changing the variables and will signal that the values are currently used in a computation and cannot be changed. – SergiyKolesnikov Mar 23 '18 at 09:26
  • Certainly something to consider. Thanks! – P-Gn Mar 23 '18 at 09:31

4 Answers4

25

Here you go (assuming that you use Python kernel):

from IPython.display import HTML, display

def set_background(color):    
    script = (
        "var cell = this.closest('.jp-CodeCell');"
        "var editor = cell.querySelector('.jp-Editor');"
        "editor.style.background='{}';"
        "this.parentNode.removeChild(this)"
    ).format(color)
    
    display(HTML('<img src onerror="{}" style="display:none">'.format(script)))

Then use it like this:

set_background('honeydew')

The solution is a bit hacky, and I would be happy to see a more elegant one. Demo:

enter image description here

Tested in Firefox 60 and Chrome 67 using JupyterLab 0.32.1.

Edit to have it as cell magic, you could simply do:

from IPython.core.magic import register_cell_magic

@register_cell_magic
def background(color, cell):
    set_background(color)
    return eval(cell)

and use it like:

%%background honeydew
my_important_param = 42
krassowski
  • 13,598
  • 4
  • 60
  • 92
  • 1
    PS. Use `set_background('')` to disable the coloring. – krassowski Jun 12 '18 at 19:56
  • Very nice! Could you also create a cell magic out of it and add it to your solution? – P-Gn Jun 13 '18 at 07:35
  • Added. At first I was skeptical to idea of using backgrounds (widgets were my initial thought), but after a while I think it has a great potential for presentations and so on. And, as it uses CSS, one can embed gradients and images in background too! – krassowski Jun 13 '18 at 12:00
  • 1
    This doesn't seem to work anymore on newer versions (Tested with jupyter notebook server: 6.0.0, Chrome: 76.0.3809.132) – martin-martin Sep 17 '19 at 09:05
  • @martin-martin do you use JupyterLab (the next-gen interface for Jupyter notebooks) or the old Jupyter notebook? (It still works in JupyterLab 1.1.3) – krassowski Sep 17 '19 at 11:54
  • I'm using Jupyter Notebooks. Haven't tried it on Jupyter Labs. – martin-martin Sep 18 '19 at 08:07
  • This works in jupyter lab 1.1.4 on browsers, but has anyone got the colour change to survive an `nbconvert` or using `nbviewer`? I can get various other html properties working (changing colour of text, changing window background, but NOT the colour of a runnable cell. (let me know if this should be a different question) – Firas Oct 05 '19 at 19:17
  • This only work for code cell as `%%background` does not work for markdown cell. – yoonghm Nov 10 '19 at 04:34
  • 5
    For Jupyter notebook v6+, use this: ```def set_background(color): script = ( "var cell = this.closest('.code_cell');" "var editor = cell.querySelector('.input_area');" "editor.style.background='{}';" "this.parentNode.removeChild(this)" ).format(color) display(HTML(''.format(script)))``` – psychemedia Dec 05 '19 at 08:30
  • 1
    The function works great! The magic almost works--it colors the cell, but suppresses any print() output in that cell. Is there a way to get the magic to allow a print() statement (or any other cell output) to be seen? – MJoseph Apr 02 '20 at 17:03
  • @MJoseph, yes just add eval(cell). I modified the answer to include it. – krassowski Nov 03 '20 at 17:20
  • @krassowski thanks! Simple variable assignment and arithmetic now works in the cell. Unfortunately, if you use any imported libraries in your cell, `eval` does not recognize them. For instance `os.curdir` cannot be run when using this magic if `os` was imported in a previous cell. – MJoseph Nov 20 '20 at 01:03
  • Tested on google colab 2021-04-16 It has no effect. – ctrl-alt-delor Apr 16 '21 at 20:18
2

Small addition to krassowski's code (tried to add it as comment but couldn't get the formatting to work).

from IPython.core.magic import register_cell_magic
from IPython.display import HTML, display

@register_cell_magic
def bgc(color, cell=None):
    script = (
        "var cell = this.closest('.jp-CodeCell');"
        "var editor = cell.querySelector('.jp-Editor');"
        "editor.style.background='{}';"
        "this.parentNode.removeChild(this)"
    ).format(color)

    display(HTML('<img src onerror="{}">'.format(script)))

This way you can use it both as magic and with normal function call:

bgc('yellow')
bla = 'bla'*3

or

%%bgc yellow
bla = 'bla'*3
Gabe
  • 396
  • 2
  • 8
  • The magic appears to suppress print() and other output in the cell (as it does in krassowski's answer). However, the function works fine. Do you know how to allow these outputs when invoking with the magic? – MJoseph Apr 06 '20 at 22:05
2

If you only need to change the color of cells converted with nbconvert, create a template mytemplate.tpl in your folder and add:

{% extends 'full.tpl'%}
{% block any_cell %}
{% if 'highlight' in cell['metadata'].get('tags', []) %}
    <div style="background:lightpink">
        {{ super() }}
    </div>
{% else %}
    {{ super() }}
{% endif %}
{% endblock any_cell %}

(adapted from the official docs)

.. then add a tag "highlight" to your cell. In Jupyter lab, you can do this on the left for the selected cell: enter image description here

Now, convert the notebook with nbconvert using the template:

jupyter nbconvert --to html 'mynb.ipynb' --template=mytemplate.tpl

The resulting HTML will look like this:

enter image description here

I found this suitable to highlight specific cells to readers.

Alex
  • 2,784
  • 2
  • 32
  • 46
1

Here's what worked for me in both jupyter-notebook (v6.3.0) and jupyter-nbconvert --to=html (v6.0.7).

It's different from @krassowski and @Gabe's answers in two ways:

  1. The interactive notebook uses the class names .cell and .input_area, but the nbconvert HTML uses .jp-CodeCell and .jp-Editor and .highlight. This code handles all of those.

  2. I prefer "line magic" over "cell magic", because line magic doesn't change the evaluation of the rest of the cell.

from IPython.core.magic import register_line_magic
from IPython.display import HTML, display
import json

@register_line_magic
def bg(color, cell=None):    
    script = (
        "var n = [this.closest('.cell,.jp-CodeCell')];"
        "n = n.concat([].slice.call(n[0].querySelectorAll('.input_area,.highlight,.jp-Editor')));"
        f"n.forEach(e=>e.style.background='{color}');"
        "this.parentNode.removeChild(this)"
    )
    display(HTML(f'<img src onerror="{script}" style="display:none">'))  

%bg yellow
pjvandehaar
  • 1,070
  • 1
  • 10
  • 24