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