Accessing FastGridTemplate's full-screen capability for components contained in components

I recently found Marc Skov Madsen’s YouTube video titled “FastGridTemplate Deep Dive”. It demonstrated a number of capabilities that greatly interest me, but the one capability that sticks out the most is the full screen button at the top right of each component within the main body of the template.

My question/request is how to access this type of functionality on components contained inside of components (such as Tabs or embedded Cards) - not just the outermost components in the main window? I have been experimenting with ways of producing panel-based presentations that run on any web browser (ship as an .html file only). Using tabs has worked as an acceptable work-around for the lack of embedding support for templates.

How can I make use of this demonstrated capability for full screen in the FastGridTemplate on a per-component basis - even if it is only available on a Card (Although Tab and Accordion make sense also)?

Marc suggested that using the ReactiveHTML component might be a path to a solution. Any guidance on how to access this capability is appreciated.

Hi @rcravotta

I’ve created the FastFullScreenComponent below. Please note that it only works inside Fast Templates. If you or someone else wants it to work in other places we need to carve out small parts of the css and js that ships with the Fast Templates.

Try it out. It probably needs a few iterations before its working well for lots of use cases.

full-screen

import panel as pn

from panel.layout.base import NamedListLike
from panel.reactive import ReactiveHTML

class FastFullScreenComponent(ReactiveHTML, NamedListLike):
    _template = """
      <fast-card id="fullScreenComponent" class="fullScreenComponent">
        <span class="fullscreen-button" >
          <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18"><path d="M4.5 11H3v4h4v-1.5H4.5V11zM3 7h1.5V4.5H7V3H3v4zm10.5 6.5H11V15h4v-4h-1.5v2.5zM11 3v1.5h2.5V7H15V3h-4z"/></svg>
        </span>
        {% for object in objects %}
          <div id="fullScreenComponentItem" class="fullScreenComponentItem">
            ${object}
          </div>
        {% endfor %}
      </fast-card>
     """

    _scripts = {
      "render": """fullScreenComponent.children[0].onclick=()=>{toggleFullScreen(fullScreenComponent)}""",
      "toggle": """console.log(event.target);toggleFullScreen(event.target.parentElement.parentElement)""",
    }

    def __init__(self, *objects, **params):
        NamedListLike.__init__(self, *objects, **params)
        ReactiveHTML.__init__(self, objects=self.objects, **params)

    @property
    def _child_names(self):
        return {'objects': self._names}

pn.extension(sizing_mode="stretch_width")

fullscreen_component1 = FastFullScreenComponent(
  "Panel is awesome 0",
  "Panel is awesome 1",
  "Panel is awesome 2",
  "Panel is awesome 3",
  "Panel is awesome 4",
  "Panel is awesome 5",
  name="Component 1"
)

fullscreen_component2 = FastFullScreenComponent(
  "Fast Design is also nice 0",
  "Fast Design is also nice 1",
  "Fast Design is also nice 2",
  "Fast Design is also nice 3",
  "Fast Design is also nice 4",
  "Fast Design is also nice 5",
  name="Component 2"
)

description = """Every Panel/ Card in the Fast Templates have a full screen button in the uppper
right corner.

But **a user needed full screen functionality inside nested layouts like Tabs**. Using
`ReactiveHTML` it was easy to provide.
"""

pn.template.FastListTemplate(
  site="Awesome Panel",
  title="Full Screen Component for Fast Templates",
  main=[description, pn.Tabs(fullscreen_component1, fullscreen_component2)],
  header_color="#777777",
  header_background="#f3e4df",
  accent_base_color="#f3e4df",
  # You can play around with the below as well
  # main_layout=""
  # main_layout="card"
).servable()

I did some testing and learned a lot about what does and does not work. I have more testing, especially on how to embed a fullscreen component with something else next it (like text). below is the code I am testing with.

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

from panel.layout.base import NamedListLike
from panel.reactive import ReactiveHTML

class FastFullScreenComponent(ReactiveHTML, NamedListLike):
    _template = """
      <fast-card id="fullScreenComponent" class="fullScreenComponent">
        <span class="fullscreen-button" >
          <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 18"><path d="M4.5 11H3v4h4v-1.5H4.5V11zM3 7h1.5V4.5H7V3H3v4zm10.5 6.5H11V15h4v-4h-1.5v2.5zM11 3v1.5h2.5V7H15V3h-4z"/></svg>
        </span>
        {% for object in objects %}
          <div id="fullScreenComponentItem" class="fullScreenComponentItem">
            ${object}
          </div>
        {% endfor %}
      </fast-card>
     """

    _scripts = {
      "render": """fullScreenComponent.children[0].onclick=()=>{toggleFullScreen(fullScreenComponent)}""",
      "toggle": """console.log(event.target);toggleFullScreen(event.target.parentElement.parentElement)""",
    }

    def __init__(self, *objects, **params):
        NamedListLike.__init__(self, *objects, **params)
        ReactiveHTML.__init__(self, objects=self.objects, **params)

    @property
    def _child_names(self):
        return {'objects': self._names}

pn.extension(sizing_mode="stretch_both")

grid_style = {'grid_line_color':'lightgray', 'grid_line_width':1.5, 'grid_line_alpha':0.3, 
              'minor_grid_line_color':'lightgray', 'minor_grid_line_width':0.5, 'minor_grid_line_alpha':0.3, 'minor_grid_line_dash':[6,4]}

colors = ['#014baf', '#84ae50', '#793688', '#cd7e3a', '#7183d6', '#49b46f', '#cb5084', '#356e84', '#bb4740', '#73a9d4',
          '#7a492e', '#c88bc8', '#49772b', '#5b4b74', '#a8ab83', '#833349', '#5bb9b0', '#3b6240', '#cc8598', '#d09377']

hv.opts.defaults(
    hv.opts.Curve(min_height=200, responsive=True, padding=(0.01, 0.01), gridstyle=grid_style, show_grid=True, tools=['hover'], hover_line_color='red', color=hv.Cycle(colors)),
    hv.opts.VLine(color='red', line_width=1, line_dash='dashed'),
    hv.opts.HLine(color=hv.Cycle(colors))
    )

XS = np.linspace(0, np.pi)

a = hv.Curve((XS, np.sin(XS * 3 + 0))).opts(title="Sine")
b = hv.Curve((XS, np.cos(XS * 3 + 0))).opts(title="Cosine")

fsa = FastFullScreenComponent(a)
fsb = FastFullScreenComponent(b)

fullscreen_component1 = FastFullScreenComponent(
  "Panel is awesome 0",
  "Panel is awesome 1",
  "Panel is awesome 2",
  "Panel is awesome 3",
  "Panel is awesome 4",
  "Panel is awesome 5",
  name="Component 1"
)

fullscreen_component2 = FastFullScreenComponent(
  "Fast Design is also nice 0",
  "Fast Design is also nice 1",
  "Fast Design is also nice 2",
  "Fast Design is also nice 3",
  "Fast Design is also nice 4",
  "Fast Design is also nice 5",
  name="Component 2"
)

# fs3 = FastFullScreenComponent(a) + FastFullScreenComponent(b)

description = """Every Panel/ Card in the Fast Templates have a full screen button in the uppper
right corner.

But **a user needed full screen functionality inside nested layouts like Tabs**. Using
`ReactiveHTML` it was easy to provide.
"""

app = pn.template.FastGridTemplate(
    site="Awesome Panel",
    title="Full Screen Component for Fast Grid Templates",
    header_color="#777777",
    header_background="#f3e4df",
    accent_base_color="#f3e4df",
    # main_layout="", # default is 'card'
    corner_radius=15,
    row_height=100,
    sizing_mode='stretch_both',
)

app.main[:1,:]= description
app.main[1:7, :] = pn.Tabs(
    fullscreen_component1, 
    fullscreen_component2, 
    ('Side-by-side', pn.Row(fullscreen_component1, fullscreen_component2)),
    ('2x2 plots', pn.pane.HoloViews(hv.Layout(a + b + b + a).cols(2))),
    ('text left of plot', pn.Row(
        pn.pane.Markdown('''# Text Title
            Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

            This is where explanation text would go for the plot on the right.
            
            The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
            ),
        pn.pane.HoloViews(a)), # I have not figured out syntax for putting a fullscreen_component here yet
        ), 
    ('text right of plot', pn.Row(
        pn.pane.HoloViews(a), # I have not figured out syntax for putting a fullscreen_component here yet
        pn.pane.Markdown('''# Text Title
            Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

            This is where explanation text would go for the plot on the right.
            
            The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
            ))),
    ('text above plot', pn.Column(# I have not figured out syntax for putting a fullscreen_component here yet
        pn.pane.Markdown('''# Text Title
            Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

            This is where explanation text would go for the plot on the right.
            
            The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
            ),
        pn.pane.HoloViews(a),
        )), 
    ('text below plot', pn.Column(
        pn.pane.HoloViews(a), # I have not figured out syntax for putting a fullscreen_component here yet
        pn.pane.Markdown('''# Text Title
            Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

            This is where explanation text would go for the plot on the right.
            
            The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
            ),
        )),
    ('tests remaining', 'get a working version of a single and a double plot layout in a full screen component rather than as part of the top-level tab to addressing sizing problems'),
    ('Initial test of fullscreen', fsa),
    scroll=False,
    )
app.show()

pn.template.FastListTemplate(
    site="Awesome Panel",
    title="Full Screen Component for Fast List Templates",
    main=[description, pn.Tabs(
        fullscreen_component1,
        fullscreen_component2, 
        ('Side-by-side', pn.Row(fullscreen_component1, fullscreen_component2)),
        ('2x2 plots', pn.pane.HoloViews(hv.Layout(a + b + b + a).cols(2))),
        ('text left of plot', pn.Row(
            pn.pane.Markdown('''# Text Title
                Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

                This is where explanation text would go for the plot on the right.
                
                The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
                ),
            pn.pane.HoloViews(a)), # I have not figured out syntax for putting a fullscreen_component here yet
            ), 
        ('text right of plot', pn.Row(
            pn.pane.HoloViews(a), # I have not figured out syntax for putting a fullscreen_component here yet
            pn.pane.Markdown('''# Text Title
                Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

                This is where explanation text would go for the plot on the right.
                
                The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
                ))),
        ('text above plot', pn.Column(# I have not figured out syntax for putting a fullscreen_component here yet
            pn.pane.Markdown('''# Text Title
                Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

                This is where explanation text would go for the plot on the right.
                
                The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
                ),
            pn.pane.HoloViews(a),
            )), 
        ('text below plot', pn.Column(
            pn.pane.HoloViews(a), # I have not figured out syntax for putting a fullscreen_component here yet
            pn.pane.Markdown('''# Text Title
                Only top-level or explicit fullscreen components have a full screen button. However, text does not wrap appropriately.

                This is where explanation text would go for the plot on the right.
                
                The desire is to ne able to examine only the layout on the right in full screen mode. It is a single plot on this tab.''',
                ),
            )),
        ('tests remaining', 'get a working version of a single and a double plot layout in a full screen component rather than as part of the top-level tab to addressing sizing problems'),
        ('Initial test of fullscreen', fsa),
        scroll=False,
        )],
    header_color="#777777",
    header_background="#f3e4df",
    accent_base_color="#f3e4df",
    # main_layout="", # default is 'card'
    corner_radius=15,
).show()

All testing has used no interactivity other than changing tabs and selecting the full screen button on each component. More testing of the above code yields the following observations: (FSC = full screen component):

  • Plots with both min_height and min_width declared initially render at min dimensions

  • These plots maintain min_height but render stretch_width when Tabs FSC expanded

  • upon closing FSC, re-rendered plot width is too long for frame, height is unchanged

  • These plots maintain min_height but render stretch_width when Plot FSC expanded

  • upon closing FSC, re-rendered plot width is much longer than frame, height is unchanged

  • Plots with only min_height specified initially render a zero width plot

  • These plots stretch_width but maintain height when Tabs FSC expanded (both should expand because used min_height and declared stretch both for template)

  • Upon closing FSC, re-rendered plot width too long for frame, height is unchanged

  • These plots maintain min_height but render stretch_width when Plot FSC expanded

  • Upon closing FSC, re-rendered plot width is much longer than frame, height is unchanged

It seems the renderer is having different issues with determining the size of the frame it is placing the plot in on initial and subsequent renderings based on whether the FSC was top-level or embedded-level.

In all cases, the FSC is not supporting stretch_height when min_height is used. The non-FSC tabs are provided for comparison; they exhibit desirable behavior other than I can only expand the Tab FSC (which is normal behavior without FSC).

For the plotting could you try to implement something similar to example 3 or 4 from here:

1 Like

I tested plots generated by pd.hvplot with responsive=False and shared_axes=False. plots not in a FSC filled their container component. Plots in a FSC behaved like the original plots - stretch_width is applied only after a re-render to full screen.

There appears to be a timing or missing step issue with the plot sizing that is captured on the re-render - however, the rendering outside of full screen is always a little too large for the actual container component.

The FSC functionality is not critical if template.save() supports embed=True because all of the data could reside in top-level components and switch based on the embedded data. I do not know the complexities of bringing that support into the templates.