Final Flourishes

visruth.com/flourish-talk

Visruth Srimath Kandali

Cal Poly - San Luis Obispo

Kelly Bodwin

Cal Poly - San Luis Obispo

Introduction

About Me

  • Final year stats student studying at Cal Poly
  • Interested in R (obviously), computing in general, Bayesian methods, and more.

Outline

  • First part of presentation features flourish
  • Second part is about Quarto extensions and how we built flourish

flourish Features

flourish’s Elder Sibling, flair

  • R package to highlight text in RMarkdown documents
  • Doesn’t work with Quarto
  • Had to rebuild from scratch

What is flourish

  • Quarto Extension (filter)
  • flourish allows users to dynamically style text in code chunks
    • Akin to flair, but now language agnostic!

Installation

Install (in a project) by running in your terminal:

quarto add kbodwin/flourish

and add this yaml in your qmd:

filters:
 - flourish.lua

Source Code: github.com/kbodwin/flourish

What Can flourish Do?

  • Highlight arbitrary literal text
#| flourish:
#| - target: "print"
print("Hello, useR!")
[1] "Hello, useR!"

What Can flourish Do?

  • Target text using regular expressions
#| flourish:
#| - target-rx: "[0-9]*"
x <- 1:10
mean(x * 10)
[1] 55

flourish any HTML Output with any CSS Styles

  • flourish supports HTML outputs
    • “normal” HTML
    • reveal.js
    • etc.
  • Can style with arbitrary (properly formatted) CSS:
#| flourish:
#| - target:
#|      - "mean"
#|      - style:
#|            - "background-color: #bd8c51;"
#|            - "font-weight: bold"
x <- 1:10
mean(x)
[1] 5.5

ANY CSS…

Best use of LLMs I’ve found so far…

#| flourish:
#| - target:
#|      - "1:10"
#|      - "wackyyy"
#|      - "mean"
#|      - style:
#|            - "background-color: #bd8c51;"
#|            - "font-weight: bold;"
#|            - "font-style: italic;"
#|            - "text-transform: uppercase;"
#|            - "text-decoration: line-through;"
#|            - "letter-spacing: 5px;"
#|            - "word-spacing: 20px;"
#|            - "text-shadow: 3px 3px 5px #888888;"
#|            - "writing-mode: vertical-rl;"
#|            - "font-variant: small-caps;"
#|            - "-webkit-text-stroke: 1px black;"
#|            - "filter: blur(1px);"
#|            - "filter: drop-shadow(8px 8px 10px gray);"
#|            - "filter: contrast(2);"
#|            - "filter: sepia(1);"
#|            - "font-family: 'Brush Script MT', cursive;"

ANY CSS…

wackyyy <- 1:10
mean(wackyyy)
[1] 5.5

ANY CSS…

wackyyy <- 1:10
mean(wackyyy)
[1] 5.5

ANY CSS…

wackyyy <- 1:10
mean(wackyyy)
[1] 5.5

ANY CSS…

wackyyy <- 1:10
mean(wackyyy * 4)
[1] 22

Flourish any Language! (ft. Python)

#| flourish:
#| - target: "lambda"
list(map(lambda x: x**2, [1, 2, 3, 4]))
[1, 4, 9, 16]

Flourish any Language! (ft. Julia)

#| flourish:
#| - target: 
#|      - "α"
#|      - "β"
#|      - "π"
α = 10;
β = 20;
γ = 3;

@show α * β^γ / π
(α * β ^ γ) / π = 25464.790894703256
25464.790894703256

Flourish any Language! (ft. ???)

Let’s get meta…

Quis flourishiet ipsos flourishes? flourish ipso.

#| flourish:
#| - target: "flourish"
#| flourish:
#| - target: 
#|      - "α"
#|      - "β"
#|      - "π"

Hide Code for Teaching

#| flourish:
#| - target:
#|     - "mean"
#|     - mask: true
#|     - style: "text-decoration: underline;"
set.seed(0)
x <- rexp(100, 1 / 10)
mean(x)
[1] 10.32147

Comprehensive example

#| flourish:
#| - target: "mean"
#| - target:
#|     - "sd"
#|     - mask: true
#|     - style: "text-decoration: underline;"
#| - target-rx: "[0-9]*"
#| - target:
#|      - "x"
#|      - style: "font-weight: bold;"
#| - target:
#|      - "set.seed"
#|      - "rnorm"
#|      - style:
#|            - "font-style: italic;"
#|            - "background-color: #468e5d;"
set.seed(0)
x <- rnorm(10)
mean(x)
[1] 0.358924
sd(x)
[1] 1.205336

What Can’t flourish Do Yet?

  • Lots of goals
    • Provide shortcuts, e.g. “mask-underline”
    • Specify number of matches to flourish
    • Target specific ranges of chunks
    • Document level YAML
    • Admit custom class so you don’t have to write CSS through flourish
    • Flourish code output, plaintext as well?
  • Some bugs (mostly related to sanitizing content)
    • regex aren’t sanitized, e.g. literal character “*”
    • HTML characters such as < get stored as &lt;, so can’t target something like <-

PRs always welcome.

What Won’t flourish Do?

  • Target pdf or docx output
  • Style non-text output (e.g. words in a ggplot graph title)
    • Though if the aforementioned untenable plot was saved as an SVG and embedded, may be possible?
    • Someone adventurous should write a PR :)

Technicalities…

Brief Intro to Quarto Extensions and Filters?

Quarto extensions add functionality to Quarto in a number of different ways.1

  • Custom Format
  • Project Types
  • Feature Extensions
    • Shortcodes
    • Filters
    • revealjs Plugins

INPUT –reader–> AST –filter–> AST –writer–> OUTPUT2

Lots of things are filters–Pandoc citations, Quarto cross refs, etc.

How Does it Work

  • flourish is written almost entirely in Javascript
  • Hooks into document load to inject our flourishes into rendered document
    • Couldn’t get it to (can’t?) work on AST
  • Maintains Quarto goodies: colours functions, numbers, etc.

Data from #|s

  • flourish relies on hashpipes like built-in Quarto options
  • Parsed into dictionary which stores match, styles, masking, etc.

Lessons Learnt

  • Quarto has many kinds of extensions
  • Written in Lua or Javascript for portability
    • Unfamiliar to most R users, but not to LLMs
    • Parts of flourish were one-shotted by LLMs; others were written in R and translated into JS by Dr. Bodwin.
  • Writing the extensions is pretty simple once you have an clear idea

Thanks! And Stickers :)

github.com/kbodwin/flourish

EXTRA: Detailed Look at the Flourish Process

Case Study

Let’s dissect how Flourish works with a simple example.

#| flourish:
#| - target: "= 1:10"
mean(x = 1:10)

This will get rendered as (in one line)

<span class="fu">mean</span>
(
<span class="at">x =</span>
 
<span class="dv">1</span>
<span class="sc">:</span>
<span class="dv">10</span>
)

Classes attach syntax highlighting to various elements of this code.

To identify “= 1:10”, we’ll need to extract the text from within the spans, match the target, and wrap each of the pieces in a flourish div.

Split into text and HTML layers

First thing we need to do is separate content and HTML

Element Type
<span class="fu"> HTML
mean text
</span> HTML
( text
<span class="at"> HTML
x = text
</span> HTML
<span class="dv"> HTML
1 text
</span> HTML
<span class="sc"> HTML
: text
</span> HTML
<span class="dv"> HTML
10 text
</span> HTML
) text

Process Text With Respect to Target

target: "= 1:10"

mean(x = 1:10)

Our match is from chars 7 to 13

Start Position Element StartMatch EndMatch
1 mean NA NA
5 ( NA NA
6 x = 2 4
9 1 1
10 1 1 1
11 : 1 1
12 10 1 2
14 ) NA NA

Insert Styling

We’re using the default styling, so class will be "flr-default"

Element Type
<span class="fu"> html
mean text
</span> html
( text
<span class="at"> html
x<span class="flr-default"> =</span> text
</span> html
<span class="flr-default"> </span> text
<span class="dv"> html
<span class="flr-default">1</span> text
</span> html
<span class="sc"> html
<span class="flr-default">:</span> text
</span> html
<span class="dv"> html
<span class="flr-default">10</span> text
</span> html
) text

Reconstruct HTML

Once we have flourished the chunk, we stitch it back together and insert it into the rendered document!

<span class="fu">mean</span>
(
<span class="at">x 
  <span class="flr-default">=</span>
</span>
<span class="flr-default"> </span>
<span class="dv">
  <span class="flr-default">1</span>
</span>
<span class="sc">
  <span class="flr-default">:</span>
</span>
<span class="dv">
  <span class="flr-default">10</span>
</span>
)
mean(x = 1:10)
[1] 5.5

Remember, this whole process happens at document load!