Ability to layer widgets in an svg, or have a background image?

I have been exploring layouts to make my widgets more appealing and ordered. I came across this page and wanted to replicate it:

Here is the functional code that has all the calculations in it and a basic visual representation:

from ipywidgets import widgets
from ipywidgets import VBox, HBox, Label, Layout, HTML
from IPython.display import SVG, Markdown




layout = widgets.Layout(width='65px')
style = {'description_width': 'initial',
        #'width'='50%'
        }

layout_label = Layout(width='100%', display='flex', align_items='center')# widgets.Layout(width='65px')
style_label = {'text_align': 'center'}

# Build out the basic widgets
ten = widgets.IntText(layout=layout)
nine = widgets.IntText(layout=layout)
eight = widgets.IntText(layout=layout)
seven = widgets.IntText(layout=layout)
six = widgets.IntText(layout=layout)
five = widgets.IntText(layout=layout)
four = widgets.IntText(layout=layout)
three = widgets.IntText(layout=layout)
two = widgets.IntText(layout=layout)
one = widgets.IntText(layout=layout)
zero = widgets.IntText(layout=layout)

total_promoter = widgets.IntText(layout=layout)
total_passive = widgets.IntText(layout=layout)
total_detractor = widgets.IntText(layout=layout)

percent_promoter = widgets.Text(layout=layout)
percent_detractor = widgets.Text(layout=layout)
nps_result = widgets.Text(layout=layout)



# Build out the watchers to update calculated values
def changed_promoter(change):
    total_promoter.value = ten.value + nine.value

ten.observe(changed_promoter, 'value')
nine.observe(changed_promoter, 'value')

def changed_passive(change):
    total_passive.value = eight.value + seven.value

eight.observe(changed_passive, 'value')
seven.observe(changed_passive, 'value')

def changed_detractor(change):
    total_detractor.value = (
    six.value + 
    five.value +
    four.value +
    three.value +
    two.value +
    one.value +
    zero.value
    )

six.observe(changed_detractor, 'value')
five.observe(changed_detractor, 'value')
four.observe(changed_detractor, 'value')
three.observe(changed_detractor, 'value')
two.observe(changed_detractor, 'value')
one.observe(changed_detractor, 'value')
zero.observe(changed_detractor, 'value')




# Generate/Update the Third row of data
# The percents
def percent_calc(change):
    total_surveys = (total_promoter.value + 
                     total_passive.value + 
                     total_detractor.value
                    )
    
    percent_promoter.value = '{:.1%}'.format(total_promoter.value/total_surveys)
    percent_detractor.value = '{:.1%}'.format(total_detractor.value/total_surveys)

total_promoter.observe(percent_calc)
total_passive.observe(percent_calc)
total_detractor.observe(percent_calc)


def nps_score(change):
    # Since promoter detractors are represented as strings
    # some conversions are necessary to do the last calculation
    promoter = float(percent_promoter.value[:-1])
    detractor = float(percent_detractor.value[:-1])
    total = promoter - detractor
    total = total/100
    nps_result.value = '{:.1%}'.format(total)

percent_promoter.observe(nps_score)
percent_detractor.observe(nps_score)

# Present the widgets
VBox([
    HBox([
        VBox([HTML("<b><font color='a1b800'>10</b>"), ten], layout=layout_label),
        VBox([HTML("<b><font color='a1b800'>9</b>"), nine], layout=layout_label),
        VBox([HTML("<b><font color='969692'>8</b>"), eight], layout=layout_label),
        VBox([HTML("<b><font color='969692'>7</b>"), seven], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>6</b>"), six], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>5</b>"), five], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>4</b>"), four], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>3</b>"), three], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>2</b>"), two], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>1</b>"), one], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>0</b>"), zero], layout=layout_label),
    ]),
    HBox([
        VBox([HTML("<b><font color='a1b800'>Total</b>"), total_promoter], layout=layout_label),
        VBox([HTML("<b><font color='969692'>Total</b>"), total_passive], layout=layout_label),
        VBox([HTML("<b><font color='e43300'>Total</b>"), total_detractor], layout=layout_label),
    ]),
    HBox([
        VBox([percent_promoter, HTML("<b><font color='a1b800'>of total<br> responses</b>")], layout=layout_label),
        HTML("<font size='40px'>-"),
        VBox([percent_detractor, HTML("<b><font color='e43300'>of total<br> responses</b>")], layout=layout_label),
        HTML("<font size='40px'>="),
        VBox([nps_result, Label("This is your NPS")], layout=layout_label),
    ])
])

That has the output (after moving some numbers) of:
(I removed the image as discourse said I could only post one media item per post)

I have been reading through this page for ideas/background:
https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Styling.html
but I am not seeing any way to visually layer or make someting a background.

The Question:
I have created svg’s to contain some of the widgets, here is an example:
(I removed the image as discourse said I could only post one media item per post, but in the image at the bottom of the post it is the green outline with the smiley face that is the svg)

Is there a mechanism in which I could contain the widgets I created (for example ten and nine) within an svg or other image?

The ideal result would be:
image

Here is one of the svg’s, called in the notebook as:

SVG('nps_promoter_block-break.svg')

You can download the sample svg by right clicking on the below and clicking ‘save image as’ in the context menu.
nps_promoter_block-break

Neat example! wxyz_datagrid offers an SVGBox (example, binder) which lets you lay out its children (by index) onto the space taken up by g elements, by default the inkscape:label created by Inkscape. It can get a little confused when there are lots of elements with the same id on the page, but that’s just the browser, really. A little bit of post-processing can clean some of these things up.

I am not sure if the built-in styles will be flexible enough on, e.g. IntText, and you’d likely have to overload a fair amount of CSS… the cleanest way to do this I’ve found is to set some _dom_classes on the outermost Box widget, and scope all the selectors to that in an HTML widget.

1 Like

Thanks @bollwyvl , I was not sure if I was missing some way to possibly make an image a background on a widget Tab or some other workaround that I could use to fake it out. Thanks for the tips, I will dive into reading up on some of the ideas you shared.