Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maximize option to card #1688

Closed
wants to merge 17 commits into from
Closed

Maximize option to card #1688

wants to merge 17 commits into from

Conversation

nghenzi
Copy link
Contributor

@nghenzi nghenzi commented Oct 23, 2020

In this PR there is the posibility of maximize the card to the viewport height and width.

Some assumptions were done to make this plausible. I hardcoded the rowheight of the rgl grid to 50px and make the grid calculations based in the following diagram.

image

I spent a lot of time trying to use only functional components, but I was not able yet to do it work. Then I come back to a class component to manage the state of the maximized card.

I found this nice example with angular + bokeh

https://towardsdatascience.com/building-dynamic-dashboards-with-angular-and-bokeh-51668a5367f1

Maybe it is posible to add a right div container to minimize the card but that would be other PR.

Here is the code to test the example

import panel as pn, holoviews as hv, numpy as np
pn.extension()

responsive = pn.template.ReactTemplate(title='React Grid Template', row_height=50)

pn.config.sizing_mode = 'stretch_both'

sli = pn.widgets.FloatSlider(name='Float Slider', start=0, end=3.141, step=0.01, 
                            value=1.57, width = 200, sizing_mode='fixed')
responsive.header.append(sli)

xs = np.linspace(0, np.pi)
freq = pn.widgets.FloatSlider(name="Frequency", 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)

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

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

responsive.sidebar.append(freq)
responsive.sidebar.append(phase)

responsive.main[0:10, 0:4] = pn.Card(hv.DynamicMap(sine), title='Sine')
responsive.main[0:10, 4:8] = pn.Card(hv.DynamicMap(cosine), title='Cosine')
responsive.main[0:10, 8:11] = pn.Card(hv.DynamicMap(tan), title='Tan')

pn.serve(responsive, port=5901)

and here it is a gif showing how it works. I testeed in a big monitor and in my 13 inches laptop and it works ok. One problem I see with this aproach is when the grid is done, it is not possible to generalize to different resolution monitors.

maximized_caard

width: 24px;
height: 24px;
z-index: 10000;
background-image: url("data:image/svg+xml,%3Csvg viewBox='7 10.073 27.163 26.308' xmlns='http://www.w3.org/2000/svg'%3E%3Cg style='' transform='matrix(0.106995, 0, 0, 0.103603, 6.25104, 9.02985)'%3E%3Cpath d='M 85.5 11.4 C 81.8 13 76.3 19.1 75.3 22.8 C 74.8 24.3 74.5 52.8 74.5 86.1 C 74.5 144.4 74.6 146.9 76.4 148.9 C 78.7 151.4 80.8 151.6 83.8 149.4 L 86 147.9 L 86 87.6 C 86 28.1 86 27.2 88.1 24.6 L 90.1 22 L 167.6 22 C 243.7 22 245 22 247 24 C 249 26 249 27.3 249 103.4 L 249 180.9 L 246.4 182.9 C 243.8 185 242.9 185 183.4 185 L 123.1 185 L 121.6 187.2 C 119.6 190.1 119.6 191.3 121.8 194 L 123.6 196.2 L 186 196 C 246.5 195.9 248.5 195.9 252 193.9 C 253.9 192.8 256.7 190.3 258 188.3 L 260.5 184.6 L 260.8 105.6 C 261 52.5 260.8 25.3 260 22.9 C 258.6 17.8 255.3 14.1 250.6 11.9 C 246.7 10.1 242.3 10 167.5 10.1 C 104.4 10.1 87.9 10.3 85.5 11.4 Z' fill='%23000000' stroke='none'/%3E%3Cpath d='M 167.7 39.8 C 165.3 42.4 165.6 46.6 168.2 48.4 C 170.1 49.7 174.1 50 191.5 50 L 212.5 50 L 123.7 138.8 C 51.9 210.6 35 228 35 230.1 C 35 233.3 37.8 236 41.1 236 C 42.9 236 63.7 215.8 132.2 147.3 L 221 58.5 L 221 79.5 C 221 102.8 221.5 105 226.9 105 C 232.9 105 233 104.4 233 71.3 C 233 43.5 232.9 41.2 231.2 39.7 C 229.6 38.2 225.9 38 199.3 38 C 171.5 38 169.2 38.1 167.7 39.8 Z' fill='%23000000' stroke='none'/%3E%3Cpath d='M 15.6 159.3 C 13.1 160.7 10.8 163.2 9.3 165.9 C 7 170.3 7 170.4 7 210.1 C 7 235.8 7.4 250.9 8.1 252.7 C 9.5 256.5 13.8 261 17.7 262.6 C 20.3 263.7 29.2 264 60.8 264 C 100.6 264 100.7 264 105.1 261.7 C 107.8 260.2 110.3 257.9 111.7 255.4 C 113.9 251.7 114 250.4 114 229.4 C 114 213.3 113.7 207 112.8 205.8 C 110.6 203.1 107.5 202.8 104.7 205.1 L 102 207.1 L 102 228.5 C 102 248.7 101.9 250 100.1 251 C 98.8 251.6 84.4 252 60.2 252 C 24.8 252 22.2 251.9 20.7 250.2 C 19.2 248.5 19 244.3 19 210.6 C 19 186.5 19.4 172.2 20 170.9 C 21 169.1 22.3 169 42.5 169 L 63.9 169 L 65.9 166.3 C 68.2 163.5 67.9 160.4 65.2 158.2 C 64 157.3 57.7 157 41.6 157 C 20.6 157 19.3 157.1 15.6 159.3 Z' fill='%23000000' stroke='none'/%3E%3C/g%3E%3C/svg%3E");
Copy link
Member

Choose a reason for hiding this comment

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

Can we base64 encode this?

Copy link
Member

Choose a reason for hiding this comment

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

And add one to dark.css where the path is white?

@philippjfr
Copy link
Member

Looks like a great start but it's not quite ready so won't go into the 0.10.0 release.

@nghenzi
Copy link
Contributor Author

nghenzi commented Oct 23, 2020

There is no hurry. I am going to watch the 64 encoding , and make the requested changes.

@nghenzi
Copy link
Contributor Author

nghenzi commented Oct 26, 2020

Still figuring out some problems that appear.

I found a hacky way to control the layout from python (using global variables in javascript which maybe it's not good). With something like this and hiding the divs, it is easy "add" and "remove" cards dinamically from python, though it has to be tested yet.

The workaorund is suggested in this SO question

https://stackoverflow.com/questions/31612598/call-a-react-component-method-from-outside

Then including a function in the grid component and making the function global with window.helloComponent = this; in the props

maximize_item_from_python(id){
      const { layouts, resizedWidgets } = this.state;
       console.log("Called from outside", layouts);
      //  const id='1'
       const newState = {...this.state.resizedWidgets};
       const maximized = typeof newState[id] === 'boolean' ? newState[id] : false;
      newState[id] = !maximized;

      this.setState({resizedWidgets: newState,}, () => {
                    window.dispatchEvent(new Event("resize"));          
         });
    } 

it is possible to call to the function from python with a HTML pane

def from_python(e):
    html.object = f""" <script>
    window.helloComponent.maximize_item_from_python({str(int_input.value)});
    console.log('triggering maximize with python code' + {str(np.random.random(1))});
    </script>
    """
    html.param.trigger('object')

Here I added a button and a number input to show the functionality (I have some problems with the css yet because it is a old version of panel).

control_layout_from_python

@nghenzi
Copy link
Contributor Author

nghenzi commented Oct 31, 2020

The 64 based icon in light and dark mode are added.

dark_64

I don't know how to solve the overlap of the maximize icon with the bokeh logo of the toolbar.

@MarcSkovMadsen
Copy link
Collaborator

It’s really cool. The first thing I as a user would ask for is an option to save the current layout as the layout to use the next time I open the app. And a way to reset to the default.

@nghenzi
Copy link
Contributor Author

nghenzi commented Nov 1, 2020

In the examples of the RGL they save the layou to localstorage of the browser. In this PR the state is conserved, then it will be easy to implement it. The problem is how to implement the way to reset it? Add a button like they do in the examples do not seem a nice solution. Maybe it can be added a method to be called from python.

@nghenzi
Copy link
Contributor Author

nghenzi commented Nov 11, 2020

I think this is ready to review @philippjfr .

The only thing to discuss is the superposition with the bokeh logo when the bokeh figure has no title

image

I can try to look for a smaller icon, for example something like this

image

The main problem is the space in the top left corner is really tight with the toolbar. I was watching other people using the RGL cards, and they add a top margin to put icons like a normal desktop program.

Pd: sorry for the lots of commits to this PR. Severals are of a previous PR. In the following, I will try to clean this non-wished commits.

@nghenzi
Copy link
Contributor Author

nghenzi commented Nov 11, 2020

@MarcSkovMadsen

Watching this talk from the voila people.

https://youtu.be/5DYH1TY7M8s?t=750

They use gridstack as you suggested in other comment. They save the layout inside the metadata of the notebook, or something similar to use the resizable items like a layout constructor. I did not use voila, but i think their api must be different to the panel api.

besides changing all the card properties with
.react-grid-item{
   background-color: #545454 !important;

 }

you can do something like

#reactcard4 {border: 5px solid #6b5b95;
padding: 10px;
           padding-top: 20px !important;}

#reactcard3 {overflow: auto;}

This solve several problems besides the styling.

With the padding and depending in the card content, you can avoid that the maximize item gets overlaped with the bokeh logo. Besides with overflow in a specific card you can put items which are not completely responsive like tables.
@MarcSkovMadsen
Copy link
Collaborator

@nghenzi . Regarding maximize take a look at awesome-Streamlit.org. Every plot etc. In Streamlit has maximize button automatically.

@nghenzi
Copy link
Contributor Author

nghenzi commented Nov 14, 2020

@nghenzi . Regarding maximize take a look at awesome-Streamlit.org. Every plot etc. In Streamlit has maximize button automatically.

@MarcSkovMadsen Nice page the streamlit, but I think the functionality is a little different. In streamlit you maximize to full screen, then you can not change the controls in the sidebar for example. The idea here was after watching all the plots in the dashboard, maybe you want analize one particular plot, then you can maximize to the viewport in the main div to change the controls in the sidebar and perform your analysis. I think more in an analytical functionality, but if you consider the full screen option is more relevant, I think it should be easier implement it. I am going to give it a try.

@nghenzi
Copy link
Contributor Author

nghenzi commented Nov 21, 2020

I am closing this for now due it can be done with the full screen api as shown in the gif. I'll make other PR with that functionality, adding an ID to each of the card divs. It is going to be more easy to implement it, it works better and it does not depends on the row height.

fullscreen

@nghenzi nghenzi closed this Nov 21, 2020
@MarcSkovMadsen
Copy link
Collaborator

@nghenzi . I did not see you comment about maximize in main div before now. Never thought about that. But you are right that it would be more functional.

@nghenzi
Copy link
Contributor Author

nghenzi commented Nov 21, 2020

Yes, it would be much more functional, but there is no really way to get the grid to full height. There is some kind of border according to the rowheight value. If we put something fixed like rowheight=10 maybe it is almost imperceptible. The fullscreen is nice and more easy to get working with the code that now it is in master branch

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

Successfully merging this pull request may close these issues.

3 participants