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 |
---|---|---|
|
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, |
Optional#
Field |
Type |
Description |
---|---|---|
|
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. |
|
array of strings |
You can list here the names of any Python variables defined in the course of the |
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
<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
"""
}
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} ] $$'
)
<disp:>[]{
import: {
a: some_param_widget,
},
build: """
# 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.