Skip to content

CSS Layout Design

Mateusz Paprocki edited this page May 28, 2021 · 1 revision

CSS/DOM Layout

Working document draft

Motivation

State of the art

The current layout was designed as a black box from CSS/DOM perspective, not respecting the underlying native layout/rendering engine. This is due to:

  1. The historical possibility of using the existing layout in runtimes that don't provide a CSS/DOM layout/rendering engine.
  2. Cross browser compatibility issues, e.g. lack of support for CSS grid, or even Flexbox much earlier on.
  3. Lack of capability to implement robust and intrinsic aspect management and axis alignment using pure CSS/DOM layout.

The effect is that it's hard or often impossible to integrate bokeh's generated CSS/DOM layout into anything else (e.g. thridparty UI toolkits) and the other way. It's impossible to style anything under bokeh's layout using plain CSS.

Performance of widgets/UI components, especially when many widgets and nested layouts are involved, is very bad even for a moderate number of components. This is due to extensive measurements of CSS/DOM components, which each requires a separate CSS layout.

Why custom layout?

CSS is not expressive enough to allow alignment of sub-components across parent components. This is needed to allow in particular axis alignment across plots. This may be resolved in future with CSS sub-grids (CSS Grid Layout L2) (initial implementation only available in Firefox). Additionally aspect management (via aspect-ratio CSS property is available only in most recent versions of modern web browsers and not available in Safari altogether. Some of the implementations are limited to simple <div>, and may not support Flexbox and/or CSS grid layouts.

Design

The design consists of two, interconnected layouts: un-managed and managed layouts. Un-managed layout, also the top-level layout, is based, as a thin abstraction, on top of the native CSS/DOM layout. The term un-managed comes from the fact that all involved layout elements are directly related to native layout elements, and any positioning (actual layout) is done implicitly by the native layout engine.

Un-managed layouts consists of raw DOM elements and user-provided CSS styling. On top of that, we will provide a thin layer of Web components (DOM elements with shadow DOM attached and some basic, but overridable CSS styling), that would allow to persist the current (managed) layout APIs across un-managed layout (to an extent possible, e.g. can't preserve component aspect ratio management in pure CSS; until at least aspect-ratio CSS property is widely available).

Managed layout, on the other hand, consists of "virtual" entities (JS objects), object sizing and positioning is computed exclusively in JS and does not require any involvement from CSS/DOM, except for establishing the initial available viewport for layout computations. Not depending on CSS/DOM, means that the managed layout can be computed in environments that do not provide or allow access to CSS/DOM, e.g. web workers, web assembly, node.js (in case of out-of-browser rendering). Managed layout is primarily intended for usage in plots, grid plots, etc., within the <canvas> element, but is not limited to (any element will do), as only viewport dimensions and possibly screen position are needed. Thus manged layout can be computed when no DOM elements are present, e.g. in node.js + node-canvas environment.

Un-managed layouts

  • primitive DOM elements (bokeh.models.dom), e.g.
from bokeh.models.dom import Button, Style
button = Button(
  style=Style(border_radius="5px"),
  data=dict(category="emphasized"),
  aria=dict(label="open"), # TODO: model if set of attributes is well defined
  "Open!",
)
button.stylesheet.add("""
:host(:hover) {
  background-color: blue;
}
""")
button.set_attribute("autofocus", "true")
button.add_js_event_listener(CustomJS("console.log('Open!')"))
# TODO: attach shadow
  • composite (shadowed) DOM layouts (bokeh.models.ui), e.g.

    • Row flex box row layout
    • Column flex box column layout
    • Grid CSS grid layout
    • Toolbar
    • Tabs
    • Button
    • ...

Managed layouts

  • Canvas expose as a first class UI component
  • Plot a Canvas with internal layout set to (managed) border layout
  • GridPlot a Canvas with internal layout set to (managed) grid layout

Design goals

  • Do not set elements styles unless otherwise impossible. Prefer private or global stylesheets. i.e. <styles> element, preferably as a part of private shadow DOM of an element; possibly use adoptedStyleSheets if available).

  • Redesign widgets and other components as a unified UI library.

  • Split up, at least conceptually, visualization parts of bokeh/bokehjs from UI and others.

  • No support for legacy web browsers, in particular IE 11.

  • Allow CSS variable propagation across both layouts (limited to what makes sense in managed layout).

  • Use ResizeObserver, MutationObserver and IntersectionObserver in managed layout to track changes to the parent CSS/DOM layout.

  • Smooth transition from un-managed layout -> <canvas> (other block element) -> managed layout.

  • Managed layout support everything that exist layout supports, however simplified, more robust, improved performance.

  • Allow un-managed to be children in managed layouts, with reasonable limitations. In particular managed layout should not be required to measure DOM nodes (the source of the performance issues of the existing layout).

  • Allow axis management work across un-managed layouts with a set of reasonable API/functional limitations.

Web component custom elements

Provide a set of custom DOM elements for un-managed and managed layout components, e.g.:

<plot>TODO</plot>

Design decisions

No virtual DOM

Employing virtual DOM would make sense, if our update model invalidated entire screen when anything got updated, but we prefer to do incremental updates. In this case, virtual DOM would only add extra cost on top of the native DOM, without having any beneficial effect on the system.

Tangential requirements

  • improve performance of the properties' system, especially on bokehjs side

    Currently it's prohibitively expensive to to initialize many models, especially models with lots of properties (like Styles()), because properties in bokehjs are initialized per instance. This needs to be change to initialization per prototype. This will make models not much more expensive than initializing plane JavaScript objects.

  • make models a thinner abstraction

    Currently models contain quite a lot of auxiliary logic both in bokeh and bokehjs. This could be further improved by leaving only properties' and initialization code directly in models, and handle everything else in accompanying code (not included directly on model classes and their prototypes) (similar approach to Python's dataclasses). This would further improve performance and reduce memory usage, making models even closer to plain objects. It would also reduced possibility for name collisions within models' property namespace (especially important for user contributed models; in particular in light of recently added data models).

  • make the protocol schema handle many models more efficiently

    We may want to consider a denser representation than the current simple JSON encoding. Additionally we could implement an encoding that allows to reconstruct model objects without having to parse the entire serialized representation.

Open questions

  1. Packaging UI components. It shouldn't be necessary to include all components just to show a toolbar next to a plot.

  2. ...

Task plan

  • drop support for legacy browsers, in particular IE 11 (PR #10931)
  • implement single canvas grid plot layouts (GridPlot() component)
  • support shadow DOM across all existing components (PR #11239)
  • implement an abstraction over native DOM elements (partially done in PR #11165)
  • implement a thin abstraction layer on top of native DOM element models, supporting existing layout API (to an extent permitted by CSS/DOM, so e.g. no aspect management)
  • in parallel, start implementing a new set of UI components based on existing widgets and other components (e.g. toolbars, menus, tooltips)
  • establish unified CSS styling scheme using CSS variables
  • ...

Work already done