diff --git a/docs/_quarto.yml b/docs/_quarto.yml index a57a76a..8bd4e54 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -28,12 +28,13 @@ website: - interactive/data.qmd - interactive/dynamic.qmd - interactive/hybrid.qmd + - interactive/dashboards.qmd - section: Other Features contents: - other/tables.qmd - other/resources.qmd - - other/html.qmd - - other/python.qmd + - other/htmlwidgets.qmd + - other/jupyter.qmd - other/slides.qmd - section: Reference contents: diff --git a/docs/interactive/dashboards.qmd b/docs/interactive/dashboards.qmd new file mode 100644 index 0000000..4e461d2 --- /dev/null +++ b/docs/interactive/dashboards.qmd @@ -0,0 +1,110 @@ +--- +title: Dashboard Example +subtitle: Interactive Quarto Dashboards with WebAssembly computation +format: live-dashboard +pyodide: + packages: + - numpy + - scipy + - matplotlib +--- + +## Row + +### {.sidebar} + +```{ojs} +viewof aRange = Inputs.range([0, 180], {value: 0, step: 0.5, label: "angle"}); +viewof sRange = Inputs.range([0, 50], {value: 10, step: 0.5, label: "sigma"}); +viewof bRange = Inputs.range([0, 20], {value: 2.5, step: 0.2, label: "beta"}); +viewof rRange = Inputs.range([0, 50], {value: 28, step: 0.5, label: "rho"}); +viewof cmap = Inputs.select( + [ + 'viridis', 'plasma', 'inferno', 'magma', 'cividis', + 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', + 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', + 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn', + 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', + 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', + 'hot', 'afmhot', 'gist_heat', 'copper' + ], + { value: 'viridis', label: "Color map" } +); +angle = debounce(viewof aRange); +sigma = debounce(viewof sRange); +beta = debounce(viewof bRange); +rho = debounce(viewof rRange); + +// From @mbostock/debouncing-input +function debounce(input, delay = 500) { + return Generators.observe(notify => { + let timer = null; + let value; + function inputted() { + if (timer !== null) return; + notify(value = input.value); + timer = setTimeout(delayed, delay); + } + function delayed() { + timer = null; + if (value === input.value) return; + notify(value = input.value); + } + input.addEventListener("input", inputted), inputted(); + return () => input.removeEventListener("input", inputted); + }); +} +``` + +### Column + +::: {.card height="100%" title="The Lorenz System"} + +```{pyodide} +#| edit: false +#| echo: false +#| fig-width: 8 +#| fig-height: 7 +#| input: +#| - sigma +#| - beta +#| - rho +#| - angle +#| - cmap +# Based on ipywidgets/docs/source/examples/Lorenz Differential Equations.ipynb +import numpy as np +from scipy import integrate +from matplotlib import pyplot as plt + +def solve_lorenz(N=10, angle=0.0, max_time=4.0, sigma=10.0, beta=8./3, rho=28.0, cmap = "viridis"): + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1], projection='3d') + ax.axis('off') + ax.set_xlim((-25, 25)) + ax.set_ylim((-35, 35)) + ax.set_zlim((5, 55)) + + def lorenz_deriv(x_y_z, t0, sigma=sigma, beta=beta, rho=rho): + x, y, z = x_y_z + return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z] + + np.random.seed(1) + x0 = -15 + 30 * np.random.random((N, 3)) + + t = np.linspace(0, max_time, int(250*max_time)) + x_t = np.asarray([integrate.odeint(lorenz_deriv, x0i, t) + for x0i in x0]) + + colors = plt.get_cmap(cmap)(np.linspace(0, 1, N)) + + for i in range(N): + x, y, z = x_t[i,:,:].T + lines = ax.plot(x, y, z, '-', c=colors[i]) + plt.setp(lines, linewidth=1) + + ax.view_init(30, angle) + plt.show() + +solve_lorenz(angle = angle, sigma = sigma, beta = beta, rho = rho, cmap = cmap) +``` +::: diff --git a/docs/interactive/reactivity.qmd b/docs/interactive/reactivity.qmd index b0074f2..232565a 100644 --- a/docs/interactive/reactivity.qmd +++ b/docs/interactive/reactivity.qmd @@ -153,11 +153,11 @@ Notice how re-evaluating the code block reactively updates the value calculated You might see an OJS error breifly appear as the page loads. This is normal, caused by the fact that the variables defined by the `quarto-live` cell do not exist until after the WebAssembly engine has loaded and executed the code block contents. ::: -## Interactive documents and dashboards +## Interactive documents The above described OJS integration can be used to implement complex computational and statistical methods using R or Python code, executed under WebAssembly, which can then be invoked reactively with OJS. -This is a useful pattern for creating engaging documents and interactive dashboards using a serverless client-side rendering approach. +This is a useful pattern for creating engaging documents with a client-side rendering approach. See also this [Quarto dashboard example](dashboards.qmd). ::: {.callout-note} Invoking exported R and Python functions from OJS is generally more efficient than re-evaluating a `quarto-live` code block, since the code does not need to be re-parsed each time it needs to run. diff --git a/docs/other/html.qmd b/docs/other/html.qmd deleted file mode 100644 index eb342ec..0000000 --- a/docs/other/html.qmd +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: R HTML Widgets -format: live-html -webr: - packages: - - rgl - - gt - - leaflet ---- - -```{webr} -#| edit: false -#| output: false -library(htmltools) -library(rgl) -library(gt) -library(leaflet) -options(rgl.printRglwidget = TRUE) -``` - -```{webr} -#| edit: false -# Some HTML tags -h1("Hello") -tags$b("bold") -tags$input(type="checkbox") -``` - -```{webr} -#| output: asis -"This is a span" -``` - -```{webr} -knitr::kable(mtcars) -# mtcars |> gt() -``` - - -```{webr} -leaflet() |> - addTiles() |> - addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R") -``` - -```{webr} -theta <- seq(0, 6*pi, len=100) -xyz <- cbind(sin(theta), cos(theta), theta) -lineid <- plot3d(xyz, type="l", alpha = 1:0, - lwd = 5, col = "blue")["data"] - -browsable(tagList( - rglwidget(elementId = "example", width = 800, height = 400, - controllers = "player"), - playwidget("example", - ageControl(births = theta, ages = c(0, 0, 1), - objids = lineid, alpha = c(0, 1, 0)), - start = 1, stop = 6*pi, step = 0.1, - rate = 6,elementId = "player") -)) -``` diff --git a/docs/other/htmlwidgets.qmd b/docs/other/htmlwidgets.qmd new file mode 100644 index 0000000..956232b --- /dev/null +++ b/docs/other/htmlwidgets.qmd @@ -0,0 +1,86 @@ +--- +title: HTML Widgets +engine: knitr +format: live-html +toc: true +webr: + packages: + - rgl + - gt + - leaflet +--- + +{{< include ../_extensions/live/_knitr.qmd >}} + +```{webr} +#| edit: false +#| output: false +library(htmltools) +library(rgl) +library(gt) +library(leaflet) +``` + +The `quarto-live` extension has support for displaying the output of R's popular [`htmltools`](https://rstudio.github.io/htmltools/) and [htmlWidgets](https://www.htmlwidgets.org/) packages, making rich HTML and JavaScript output possible with interactive code blocks. + +## Example: `htmltools` + +```{webr} +tags$div( + tags$h3("Hello from", tags$code("htmltools"), "!"), + tags$p( + "This is some HTML output from", + tags$a( + "htmltools", + href = "https://rstudio.github.io/htmltools/" + ), + "." + ), + tags$p( + "Below is a checkbox, feel free to", + tags$em("check"), + "it out!" + ), + tags$input(type="checkbox") +) +``` + +## Example: `htmlwidgets` + +### Leaflet + +```{webr} +leaflet() |> + addTiles() |> + addMarkers(lng = 174.768, lat = -36.852, popup = "The birthplace of R") +``` + +### rgl + +```{webr} +options(rgl.printRglwidget = TRUE) +theta <- seq(0, 6 * pi, len = 100) +xyz <- cbind(sin(theta), cos(theta), theta) +lineid <- plot3d(xyz, + type = "l", alpha = 1:0, lwd = 5, col = "blue" +)["data"] +browsable( + tagList( + rglwidget( + elementId = "example", width = 800, height = 400, + controllers = "player" + ), + playwidget("example", ageControl( + births = theta, ages = c(0, 0, 1), + objids = lineid, alpha = c(0, 1, 0) + ), + start = 1, stop = 6 * pi, step = 0.1, + rate = 6, elementId = "player" + ) + ) +) +``` + +::: {.callout-note} +At the moment `htmltools` cannot be used as reactive inputs for `quarto-live`'s [OJS integration](../interactive/reactivity.qmd). This is planned to be added in a future release of `quarto-live`. +::: diff --git a/docs/other/jupyter.qmd b/docs/other/jupyter.qmd new file mode 100644 index 0000000..b7b2ce6 --- /dev/null +++ b/docs/other/jupyter.qmd @@ -0,0 +1,68 @@ +--- +title: Jupyter Widgets +format: live-html +toc: true +resources: + - "images" +pyodide: + packages: + - matplotlib + - ipywidgets +--- + +The `quarto-live` extension has support for displaying several types of `ipython` [rich output](https://ipython.readthedocs.io/en/stable/interactive/plotting.html#rich-outputs) and [Jupyter Widgets](https://ipywidgets.readthedocs.io/en/latest/) with interactive code blocks. + +## Example: `ipython` rich output + +```{pyodide} +from IPython.display import HTML +from IPython.display import Image +display(HTML("
ipython
!This is HTML output, and below is an image.
")) +Image(filename='images/python-logo.png') +``` + +## Example: Jupyter Widgets + +```{pyodide} +import ipywidgets as widgets + +out = widgets.Output( + layout={'border': '1px solid black', 'padding': '1em'} +) +out.append_display_data( + widgets.IntSlider( + value=7, min=0, max=10, step=1, + description='Slider:', + disabled=False, + ) +) +out.append_display_data( + widgets.Text( + value="Hello", + description='Text:', + disabled=False + ) +) +out.append_display_data( + widgets.Dropdown( + options=[('One', 1), ('Two', 2), ('Three', 3)], + value=2, + description='Number:', + ) +) +out.append_display_data( + widgets.ColorPicker( + concise=False, + description='Color picker', + value='blue', + disabled=False + ) +) +out +``` + +::: {.callout-note} +At the moment Jupyter widgets cannot be used as reactive inputs for `quarto-live`'s [OJS integration](../interactive/reactivity.qmd). This is planned to be added in a future release of `quarto-live`. +::: + diff --git a/docs/other/python.qmd b/docs/other/python.qmd deleted file mode 100644 index 999ff2c..0000000 --- a/docs/other/python.qmd +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Jupyter Widgets and `ipython` -format: live-html -resources: - - "images" -pyodide: - packages: - - matplotlib - - ipywidgets ---- - -```{pyodide} -from IPython.display import Image -Image(filename='images/python-logo.png') -``` - -```{pyodide} -from IPython.display import HTML -display(HTML("