Sticky IconBar

Hi to everyone !!!

I am developing a multipage app with panel and I need a nice navBar to navigate between different apps served with pn.serve.

I found in youtube this nice sticky IconBar.I utilize it to switch between differents panel apps, but it can be utilized to link to external pages too, and when the datamodel is available, it will be possible to trigger python actions with the icons.

As a sidenote, the new hotloading feature it is really amazing. One thing I needed 2 hours, now I spent only 45 minutes to make it.

Here you have a short video and the code.

import panel as pn
import numpy as np
import holoviews as hv

pn.extension()
print ('version', pn.__version__)

material = pn.template.MaterialTemplate(title='Template')
pn.config.sizing_mode = 'stretch_width'

xs = np.linspace(0, np.pi)
freq = pn.widgets.FloatSlider(name="Freqfghuasdassdency", start=0, end=10, value=2)
phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)

@pn.depends(freq=freq, phase=phase)
def sine(freq, phase):
    return hv.Curve((xs, np.sin(xs*freq+phase))).opts(
        responsive=True, min_height=400)

@pn.depends(freq=freq, phase=phase)
def cosine(freq, phase):
    return hv.Curve((xs, np.cos(xs*freq+phase))).opts(
        responsive=True, min_height=400)

material.sidebar.append(freq)
material.sidebar.append(phase)

script = """
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" integrity="sha512-5A8nwdMOWrSz20fDsjczgUidUBR8liPYU+WymTZP1lmY9G6Oc7HlZv156XqnsgNUzTyMefFTcsFH/tnJE/+xBg==" crossorigin="anonymous" />

<style>
*{
    margin:0;
    padding:0;
    box-sizing:border-box;
    list-style:none;
    text-decoration:none;
    font-family:sans-serif;
    font-weight:800;
}

nav{
    position:fixed;
    z-index: 100;
    width:50px; 
    height:200px;
    border-radius:10px;
    background: #00aa41; 
    bottom:20px; 
    left:20px;
    box-shadow: 10px 10px 50px #00000055;
}

li{
    position:relative;
    width:50px;
    height:50px;
}

li a{
    position:absolute;
    top:50%;
    left:50%;
    transform:translate(-50%,-50%);
}

.fa {
    color:white;
}

.info{
    display:none;
    pointer-events:none;
    position:absolute;
    color:white;
    background:#00aa41;
    padding: 7px;
    border-radius: 5px;
    top:50%;
    left:125%;
    transform: translate(0%,-50%);
    font-size: 0.8rem;
}

.info::before{
    position:absolute;
    content:"";
    width:10px;
    height:10px;
    background: #00aa41;
    top:50%;
    left:-5px;
    transform: translate(0%,-50%) rotate(45deg);
}

li a:hover ~ .info{
    display:block;
}

li:hover{
    transform: scale(1.2);
}

</style>

<nav draggable="true">
  <ul>
    <li><a href="app1" class="link"><i class="fa fa-instagram fa-2x"></i></a><p class="info">Instagram</p></li>
    <li><a href="app2" class="link"><i class="fa fa-facebook fa-2x"></i></a><p class="info">Facebook</p></li>
    <li><a href="app3" class="link"><i class="fa fa-youtube fa-2x"></i></a><p class="info">Youtube</p></li>
    <li><a href="app4" class="link"><i class="fa fa-twitter fa-2x"></i></a><p class="info">Twitter</p></li>
  </ul>
</nav>

<script>
iconBar=document.querySelector("nav");
iconBar.ondragend=(e)=>{
    iconBar.style.top=e.screenY-window.screenY-200+"px";
    iconBar.style.left=e.screenX-window.screenX-50+"px";
}
</script>

"""

material.header.append(
    pn.pane.HTML(script, sizing_mode='stretch_both')
)

material.main.append(
    pn.Row(
        pn.Card(hv.DynamicMap(sine), title='Sine'),
        pn.Card(hv.DynamicMap(cosine), title='Cosine')
    )
)
material.servable();

Best regards,
N

2 Likes

Amazing.

Could/ should this be made into a general component? A draggable menu? A draggable pane?

I was thinking the same thing, in a similar way to the draggable menu of gimp with a lot of widgets. I am going to begin to work on it and comment the advances here or in a PR.

1 Like

For the record and possible PR. Based on the draggable element in

https://www.w3schools.com/howto/howto_js_draggable.asp

I constructed a draggagle menu to add to the templates. The code can be found in the following gist

and a recording of the functionality

This should be easy to add to the templates. I am going to begin to try to generate a general draggable element based on the card panel.

2 Likes

this is the last post in this thread. It is not needed to create a custom new panel or pane, addind a css classes and using setProperty in JS a Column element can be made draggable using https://www.w3schools.com/howto/howto_js_draggable.asp

Here a screenshot and the code

import panel as pn
import numpy as np
import holoviews as hv

css = """
.mydiv{
  position: relative;
  top: 300px !important;
  /*background : blue;*/
  z-index : 100;
  padding: 0px;
  border: 0px solid #00aa41;
  border-top-left-radius:10px;
  border-top-right-radius:10px;
  border-bottom-right-radius:10px;
  border-bottom-left-radius:10px;
  cursor: move;
  z-index: 10;
  /*background-color: orange ;*/
  color: black;
    opacity: 1;
  box-shadow: 10px 10px 50px #00000055;
}

#mydivheader {
  padding: 10px;
  width: 180px;
  position:relative;
  height: 30px ;
  top: -5px;
  left: -5px;
  border: 0px solid #00aa41;
  border-top-left-radius:10px;
  border-top-right-radius:10px;
  cursor: move;
  z-index: 10;
  background-color: #00aa41;
 /* color: blue;*/
 color: #fff;
   opacity: 1;
   font-family:sans-serif;
    font-weight:800;
}

"""

pn.extension(raw_css=[css])

script = """
<div id="mydivheader"> Mover </div>
<script>
dragElement(document.getElementsByClassName("bk mydiv")[0]);

function dragElement(elmnt) {
  var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  ide = document.getElementsByClassName("mydiv")[0].className.replace("bk ","");
  if (document.getElementById(ide + "header")) {
    // if present, the header is where you move the DIV from:
    document.getElementById(ide + "header").onmousedown = dragMouseDown;
  } else {
    // otherwise, move the DIV from anywhere inside the DIV:
    elmnt.onmousedown = dragMouseDown;
  }

  function dragMouseDown(e) {
    e = e || window.event;
    e.preventDefault();
    // get the mouse cursor position at startup:
    pos3 = e.clientX;
    pos4 = e.clientY;
    document.onmouseup = closeDragElement;
    // call a function whenever the cursor moves:
    document.onmousemove = elementDrag;
  }

  function elementDrag(e) {
    e = e || window.event;
    e.preventDefault();
    // calculate the new cursor position:
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;

    // set the element's new position:  
    elmnt.style.setProperty('top', (elmnt.offsetTop - pos2) + "px", 'important');
    elmnt.style.setProperty('left', (elmnt.offsetLeft - pos1) + "px", 'important');
  }

  function closeDragElement() {
    // stop moving when mouse button is released:
    document.onmouseup = null;
    document.onmousemove = null;
  }
}
</script>
"""

html = pn.pane.HTML(script, width=200, height=50, sizing_mode='fixed')
xs = np.linspace(0, np.pi)
freq = pn.widgets.FloatSlider(name="Frequency", start=0, 
            end=10, value=2, css_classes = ['slid'])
phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)

@pn.depends(freq=freq, phase=phase)
def sine(freq, phase):
    return hv.Curve((xs, np.sin(xs*freq+phase))).opts(
        responsive=True, min_height=400)

@pn.depends(freq=freq, phase=phase)
def cosine(freq, phase):
    return hv.Curve((xs, np.cos(xs*freq+phase))).opts(
        responsive=True, min_height=400)

def update(attr, old, new):
  freq.value = sl.value

material = pn.template.MaterialTemplate(title='Reloading Template')

col = pn.Column(html, freq,phase, width=200, css_classes = ['mydiv'])
material.header.append( col )

material.main.append(
    pn.Row(
        pn.Card(hv.DynamicMap(sine), title='Sine',sizing_mode='stretch_width'),
        pn.Card(hv.DynamicMap(cosine), title='Cosine',sizing_mode='stretch_width'),
        sizing_mode='stretch_width'
    )
)

material.servable();
2 Likes

Awesome

  • Can you have multiple draggable elements?
  • Can you resize the draggable element?

For multiple draggable elements a new id for the divs has to be defined. I will give a try, but i am fighting now with the fact that a resize event resets the position of the div. I think i have to desactivate in any way the bokeh css engine, but i do not how.

It´s not resizable, but I think to add widgets yo a toolbar there is no necessity to resize it.

1 Like

I come here to let the advances with the new Reactive HTML component. It only remains to add the dragging option and that when one opens a menu the others close

the code can be found here

1 Like

Pretty amazing stuff @nghenzi2019 . I hope it could find its way into Panel one day. :+1:

Regarding the api. Some suggestions.

  • Don’t fix the api on including and using font-awesome js. Instead change the icon to support an svg icon. You can download them in many places. Including at the font-awesome web site.

Being able to even using an icon model like found in the awesome-panel-extensions package one day would be awesome.

https://awesome-panel.readthedocs.io/en/latest/packages/awesome-panel-extensions/index.html#icon

  • change the menu name to id, element, element-id or something similar.
  • get rid of the js_dummy pane. I would assume the ReactiveHTML provides what is needed for such a use case. Otherwise it’s probably a Feature Request away.
1 Like

Thanks for your suggestions. I’ll try to implement then. !!!

Not sure how to send JS code during the initialization of the class with the reactiveHTML component. I will watch it with care.