Display Widgets#

The purpose of a display widget is to generate arbitrary HTML (including TeX to be typeset by MathJax), to produce a display of mathematics.

Your main job when defining a display widget is to write Python code, making use of SymPy as needed, and returning the string of HTML that is to be displayed.

The Python code that you write runs in the user’s browser, using Pyodide (however, see Trust settings below).

Naturally, your code will often want to make use of the current values of Parameter Widgets on the same page, as starting points (this is the whole purpose of parameter widgets). Accordingly, display widgets are able to import parameter values.

Conversely, in the course of your code you may compute useful intermediate values, which your display widget can export. Any values you export can be imported by other display widgets.

Format#

  • TYPE: disp

  • NAME: Optional.

  • LABEL: Not accepted.

  • ROLE_FIELD: None.

  • CONTENT_FIELD: build

Fields#

Required#

Field

Type

Description

build

string

A (usually multi-line) string of Python code in which you build the desired display HTML. See The build string.

Note: In Sphinx pages, build is the CONTENT_FIELD (see Directive format).

Optional#

Field

Type

Description

import

object

Import the values of parameters, and/or values exported (see below) by other displays.

To import the value of a parameter, use a key-value pair in which the value is a relative libpath pointing to the desired param widget, and the key is the local name you want to use for that parameter.

To import values exported by another display, use a key-value pair in which the value is a relative libpath pointing to the desired display widget, and the key is an import string.

export

array of strings

You can list here the names of any Python variables defined in the course of the build code. The final value of each of these variables will then be available for other display widgets to import.

Import strings#

When importing values exported by another display widget, you need to use an import string as key in the import object.

An import string is a comma-delimited list of “imports,” each of which at least gives the name of a variable as exported from the display widget in question, and may also give a local name you wish to use for it after importing, separated by keyword as.

In other words, an import string matches this grammar:

import_string := import ("," import)*
import := CNAME ("as" CNAME)?
%skip whitespace

Thus, imports can be as simple as,

import: {
    f: some_disp_widget
}

importing f as 'f' from some_disp_widget, or as complex as,

import: {
    'f as g, a, b, alpha as mu': some_disp_widget
}

importing f as 'g', a as 'a', b as 'b', and alpha as 'mu'.

Note that there is no risk of key collision in an import object, because it does not make sense to import more than one thing under the same local name.

The build string#

The string you write for the build field of a display widget should define the internal code of a Python function that returns the desired HTML as a string, and whose arguments are precisely the local variables defined in import.

By “internal code” we mean that you do not write the def function_name(args): line that starts off a function definition in Python; you write just the block that comes under that (no need for indentation) and that ends with a return statement.

For example, if your import field looked like this:

import {
    a: some_param_widget,
    b: another_param_widget,
    'x, q as y': a_disp_widget,
}

then you could imagine you were defining a Python function like this:

def build_html(a, b, x, y):
    html = ''
    html += f'If $a = {a}$, $b = {b}$, $x = {x}$, and $y = {y}$,\n'
    html += rf'then $\frac{{a + b}}{{x + y}} = \frac{a + b}{x + y}$'
    return html

whereas the build field for your display widget should be just the internal code block of such a function definition, like this:

.. pfsc-disp::
    :import: {
            a: some_param_widget,
            b: another_param_widget,
            'x, q as y': a_disp_widget,
        }

    html = ''
    html += f'If $a = {a}$, $b = {b}$, $x = {x}$, and $y = {y}$,\n'
    html += rf'then $\frac{{a + b}}{{x + y}} = \frac{a + b}{x + y}$'
    return html

Blank lines and indentation#

Leading and trailing blank lines are automatically stripped off of the build string that you define.

The first non-blank line sets the “base indentation level,” and this amount of indentation is ignored, on all lines of your build string.

These rules leave you free to format the build string in a way that makes sense within the surrounding widget definition. This is usually most relevant in annotations (as opposed to Sphinx pages). For example, here,

<disp:>[]{
    import: {
        a: some_param_widget,
        b: another_param_widget,
        'x, q as y': a_disp_widget,
    },
    build: """
    html = ''
    html += f'If $a = {a}$, $b = {b}$, $x = {x}$, and $y = {y}$,\n'
    html += rf'then $\frac{{a + b}}{{x + y}} = \frac{a + b}{x + y}$'
    return html
    """
}

the triple-quoted string that defines the build field begins and ends with a blank line, and each other line starts with four spaces of indentation; however, all of this is automatically stripped away before the page is built, so makes no difference.

Editable sections#

When someone views a display widget designed by you, you might want to allow them to edit parts of the build code interactively, and then rebuild the display, all right from within PISE. You can do this by making one or more editable sections in your build code.

For example, this is one easy way to let users specify certain input values. When you need an input for which there is not yet an existing parameter type, this can be a workaround. Of course, you can think of myriad other ways to use editable sections as well.

To mark a section of your build string as user-editable, all you have to do is put a comment line saying

# BEGIN EDIT

before it, and another comment line saying

# END EDIT

after it. Everything coming between these two lines will be visible to, and editable by, the user, from within the page the user browses in PISE, where your display widget appears. The user will have a “Build” button with which to rebuild the display after they have finished editing.

Your build string can have as many editable sections in it as you wish.

For example, here is a display widget with one editable section, prompting the user to enter a vector of four integers:

.. pfsc-disp::
    :import: {
            a: some_param_widget,
        }

    # BEGIN EDIT
    # Choose a 4-dimensional row vector by entering a list of four integers:
    v = [2, 3, 5, 7]
    # END EDIT
    comps = ", ".join(str(a*c) for c in v)
    return (
        r'The scalar multiple of your vector $\mathbf{v}$'
        ' by the scalar $a$ chosen earlier is:\n'
        rf'$$ a \mathbf{{v}} = [ {comps} ] $$'
    )

Trust settings#

Because display widgets can contain arbitrary code, there are certain cases in which users first have to formally trust the repo to which the widgets belong, before PISE will allow the code to run.

The official software#

The official PISE software that users can download and operate on their own computer comes pre-configured to trust certain Proofscape repos by default. At time of writing, this includes all repos, at all version numbers, published by the following owners:

For repos other than these, users can formally trust them (one version number at a time) in PISE, by right-clicking the repo root module in either the “Structure” tab or “File System” tab in the sidebar, and selecting “Trust…”.

Users can also configure broader trust settings.

Websites#

Caution

Remember that the Internet at large is “the Wild West,” and websites in general can deliver whatever JavaScript they choose to your browser.

When PISE is operated on the web, it is entirely up to the site operator to determine which display widgets will run by default, without visitors having to first trust them.