Skip to content

Commit

Permalink
Add default file format when Quarto format is typst (#7)
Browse files Browse the repository at this point in the history
* Add default SVG format for Typst output

Also add DarkMauve and DarkFlagshipTerrastruct as explicit theme options

* Remove duplicate theme

DarkMauve was already in the theme list

* Allow uppercase layout values

Also:

- Adds is_nonempty_string helper function
- Appends random number for diagram file name
- Use d2 file extension not txt for input
- Allow alternate syntax for attribute classes on code blocks

* Fix d2 class check

Also:

- Add support for reading d2 diagrams from external files using `file` parameter. Block text is ignored if file parameter is supplied.
- Add support for alternate code block syntax without curly braces.
- Insert rendered diagrams into the Pandoc mediabag when `embed_type="link"`
- Refactor to add helper functions `setPreD2RenderOptions` and `setD2RenderFormat`

* Update CHANGELOG.md

* Use d2-render instead of svg-convert for temp directory

* Add support for gif format

Also

- add animate_interval parameter
- drop setD2RenderFormat helper function
- use quarto.project.output_directory to set default folder
- revise check for d2 code block class
- replace codeblock text if file is provided
- add support for tala layout

* Use outputPath (not mediabag) if folder is supplied

* Update README

* Fix typo

* Fix error in inline embedding logic

* Add check for d2 or txt file extension in file option

Also:

- add check to see if file exists
- Improve handling of code block formatting (when echo=true)
- remove duplicative processing for options.pad and options.animate_interval

* Update README

* Add partial support for code folding (#6)

Based on https://github.com/shafayetShafee/add-code-files

* Add typst format tests w/ multiple diagram formats

Also add external d2 file to test file argument

* Update CHANGELOG.md

---------

Co-authored-by: Robrecht Cannoodt <[email protected]>
elipousson and rcannood authored Mar 20, 2024
1 parent cc97e8b commit 1f6fb19
Showing 11 changed files with 817 additions and 260 deletions.
17 changes: 14 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,21 +4,32 @@

* Added minimal CI testing using GitHub actions (#10).

* Make SVG as the default diagram format when using the [Typst output format](https://quarto.org/docs/output-formats/typst.html) (#7, @elipousson).

* Add support for reading d2 diagrams from external files using `file` parameter. Block text is replaced with file contents (#7, @elipousson).

* Use Pandoc mediabag for rendered diagram images when `embed_type="link"` (#7, @elipousson).

* Add support for alternate code block syntax without curly braces (#7, @elipousson).

## BUG FIXES

* Added tala to the list of layouts (#9, thanks @tosaddler!).
* Added [TALA](https://d2lang.com/tour/tala/) to the list of layouts (#9, @tosaddler).

## OTHER

* Refactor to add helper functions `setPreD2RenderOptions` and `is_nonempty_string`.

# quarto-d2 1.1.0

## NEW FUNCTIONALITY

- When the output type is html and the image format is svg, also setting the `embed_type="raw"` will embed the svg directly into the html document (#1). This is useful enabling interactive content such as hover or links to work.


# quarto-d2 1.0.0

Initial release. Main features:

- Render [D2](https://d2lang.com) diagrams directly within your [Quarto](https://quarto.org) markdown documents.
- Control the appearance and layout of your diagrams using global settings or code block attributes.
- Tune the width and height of the resulting figures using the "width" and "height" arguments.
- Tune the width and height of the resulting figures using the "width" and "height" arguments.
90 changes: 48 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# D2 Extension For Quarto


This [Quarto](https://quarto.org) extension allows you to render
[D2](https://d2lang.com) diagrams directly within your markdown
documents.
@@ -34,7 +35,13 @@ quarto add data-intuitive/quarto-d2
This will install the extension under the `_extensions` subdirectory. If
you’re using version control, you will want to check in this directory.

## Example
## Examples

D2 can be used for simple diagrams.

![](images/diagram-1.svg)

And for more complex diagrams.

<div>

@@ -85,68 +92,61 @@ user -> network.portal.UI: access {
}
```

<img src="./images/diagram-1.svg" style="width:50.0%" />
<img src="images/diagram-2.svg" style="width:50.0%" />

</div>

## Usage
The enclosing curly brakets are optional if you are only using document
level options. Quarto block-level options, e.g. `#|`, are not currently
supported.

To use the d2 filter, add the d2 filter to your quarto document. Next,
add the `.d2` class to any code blocks containing D2 diagram code. Here
is a basic example:

```` markdown
---
title: "D2 Example"
filters:
- d2
---

```{.d2}
x -> y
```
````

With this setup, the `d2` filter will process any code blocks with the
`.d2` class, applying the attributes you specify.

That’s it! Now you know how to use the `d2` filter to generate diagrams
in your quarto documents.
![](images/diagram-3.svg)

## Attributes

You can specify additional attributes to control the appearance and
layout of the diagram.
layout of the diagram and document:

- `theme`: Specifies the theme of the diagram. Default is
`"NeutralDefault"`. Options are `"NeutralDefault"`, `"NeutralGrey"`,
`"FlagshipTerrastruc"t`, `"CoolClassics"`, `"MixedBerryBlue"`,
`"GrapeSoda"`, `"Aubergine"`, `"ColorblindClear"`,
`"VanillaNitroCola"`, `"ShirelyTemple"`, `"EarthTones"`,
`"EvergladeGreen"`, `"ButteredToast"`, `"DarkMauve"`, `"Terminal"`,
`"TerminalGrayscale"`, `"Origami"`.
`"FlagshipTerrastruct"`, `"DarkFlagshipTerrastruct"`,
`"CoolClassics"`, `"MixedBerryBlue"`, `"GrapeSoda"`, `"Aubergine"`,
`"ColorblindClear"`, `"VanillaNitroCola"`, `"ShirelyTemple"`,
`"EarthTones"`, `"EvergladeGreen"`, `"ButteredToast"`, `"DarkMauve"`,
`"Terminal"`, `"TerminalGrayscale"`, and `"Origami"`.
- `layout`: Specifies the layout algorithm to use. Default is `"elk"`.
Options are `"dagre"`, `"elk"`, `"tala"`.
Options are `"dagre"`, `"elk"`, `"tala"`. layout is not case sensitive
so `"ELK"` or `"TALA"` are also supported.
- `format`: Specifies the format of the output image. Default is `svg`.
Option are `"svg"`, `"png"`, `"pdf"`.
Option are `"svg"`, `"png"`, `"pdf"`, `"gif"`.
- `sketch`: Whether to use a “sketch” style for the diagram. Default is
`false`.
- `pad`: Amount of padding around the diagram. Default is `100`.
- `pad`: Amount of padding around the diagram in pixels. Default is
`100`.
- `caption`: Caption to add to the diagram.
- `folder`: Folder where the generated diagram will be saved. If not
provided, the image will be embedded inline in the document (HTML
only).
- `filename`: Name of the output file.
- `width`: Width of the output image. Default is `100%`. Examples are
`"100px"`, `"50%"`, `"3cm"`.
- `height`: Height of the output image. Default is `auto`. Examples are
`"100px"`, `"50%"`, `"3cm"`.
- `echo`: Whether to echo the original diagram code in the output.
Default is `false`.

You can also replace the contents of the block with an external D2 file
by using the `file` parameter. `file` must be an existing file ending in
a “d2” or “txt” file extension. Other parameters related to rendering
and embedding diagrams include:

- `folder`: Folder where the generated diagram will be saved. If not
provided, the image will be embedded inline in the document (HTML
only).
- `filename`: Name of the output file.
- `embed_mode`: How to embed the diagram in the output. Default is
`"inline"` for HTML output and `"link"` for other output formats.
Options are `"inline"`, `"link"`, `"raw"`.

Note that for Typst format output the width and height can’t be supplied
as a percent value.

Here’s an example that uses multiple attributes:

```` markdown
@@ -175,6 +175,16 @@ x -> y -> z
```
````

## Setting an input file

You can specify an input d2 file. If `echo=true`, the contents of the
file block is replaced by the contents of the file.

```` markdown
```{.d2 file="./diagram.d2"}
```
````

## Setting output folder and file name

You can specify a folder where the generated diagram will be saved using
@@ -187,15 +197,11 @@ x -> y -> z
```
````

<div>

> **Note**
> [!NOTE]
>
> If the `folder` attribute is not provided and the output format is
> HTML, the image will be embedded inline in the document.
</div>

## Interactive diagrams

Interactive diagrams will only work when the Quarto output format is
61 changes: 35 additions & 26 deletions README.qmd
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ filters:
- d2
d2:
layout: "elk"
folder: "images"
---

This [Quarto](https://quarto.org) extension allows you to render [D2](https://d2lang.com) diagrams directly within your markdown documents.
@@ -34,10 +35,17 @@ quarto add data-intuitive/quarto-d2
This will install the extension under the `_extensions` subdirectory.
If you're using version control, you will want to check in this directory.

## Examples

D2 can be used for simple diagrams.

## Example

```{.d2}
x -> y: hello world
```


And for more complex diagrams.

```{.d2 width="50%" echo="true"}
logs: {
@@ -87,44 +95,36 @@ user -> network.portal.UI: access {
```


## Usage
The enclosing curly brakets are optional if you are only using document level options. Quarto block-level options, e.g. `#|`, are not currently supported.

To use the d2 filter, add the d2 filter to your quarto document. Next, add the `.d2` class to any code blocks containing D2 diagram code. Here is a basic example:


````markdown
---
title: "D2 Example"
filters:
- d2
---

```{.d2}
x -> y
```d2
Database -> S3: backup
Database -> S3
Database -> S3: backup
```
````

With this setup, the `d2` filter will process any code blocks with the `.d2` class, applying the attributes you specify.

That's it! Now you know how to use the `d2` filter to generate diagrams in your quarto documents.

## Attributes

You can specify additional attributes to control the appearance and layout of the diagram.
You can specify additional attributes to control the appearance and layout of the diagram and document:

- `theme`: Specifies the theme of the diagram. Default is `"NeutralDefault"`. Options are `"NeutralDefault"`, `"NeutralGrey"`, `"FlagshipTerrastruc"t`, `"CoolClassics"`, `"MixedBerryBlue"`, `"GrapeSoda"`, `"Aubergine"`, `"ColorblindClear"`, `"VanillaNitroCola"`, `"ShirelyTemple"`, `"EarthTones"`, `"EvergladeGreen"`, `"ButteredToast"`, `"DarkMauve"`, `"Terminal"`, `"TerminalGrayscale"`, `"Origami"`.
- `layout`: Specifies the layout algorithm to use. Default is `"elk"`. Options are `"dagre"`, `"elk"`, `"tala"`.
- `format`: Specifies the format of the output image. Default is `svg`. Option are `"svg"`, `"png"`, `"pdf"`.
- `theme`: Specifies the theme of the diagram. Default is `"NeutralDefault"`. Options are `"NeutralDefault"`, `"NeutralGrey"`, `"FlagshipTerrastruct"`, `"DarkFlagshipTerrastruct"`, `"CoolClassics"`, `"MixedBerryBlue"`, `"GrapeSoda"`, `"Aubergine"`, `"ColorblindClear"`, `"VanillaNitroCola"`, `"ShirelyTemple"`, `"EarthTones"`, `"EvergladeGreen"`, `"ButteredToast"`, `"DarkMauve"`, `"Terminal"`, `"TerminalGrayscale"`, and `"Origami"`.
- `layout`: Specifies the layout algorithm to use. Default is `"elk"`. Options are `"dagre"`, `"elk"`, `"tala"`. layout is not case sensitive so `"ELK"` or `"TALA"` are also supported.
- `format`: Specifies the format of the output image. Default is `svg`. Option are `"svg"`, `"png"`, `"pdf"`, `"gif"`.
- `sketch`: Whether to use a "sketch" style for the diagram. Default is `false`.
- `pad`: Amount of padding around the diagram. Default is `100`.
- `pad`: Amount of padding around the diagram in pixels. Default is `100`.
- `caption`: Caption to add to the diagram.
- `folder`: Folder where the generated diagram will be saved. If not provided, the image will be embedded inline in the document (HTML only).
- `filename`: Name of the output file.
- `width`: Width of the output image. Default is `100%`. Examples are `"100px"`, `"50%"`, `"3cm"`.
- `height`: Height of the output image. Default is `auto`. Examples are `"100px"`, `"50%"`, `"3cm"`.
- `echo`: Whether to echo the original diagram code in the output. Default is `false`.

You can also replace the contents of the block with an external D2 file by using the `file` parameter. `file` must be an existing file ending in a "d2" or "txt" file extension. Other parameters related to rendering and embedding diagrams include:

- `folder`: Folder where the generated diagram will be saved. If not provided, the image will be embedded inline in the document (HTML only).
- `filename`: Name of the output file.
- `embed_mode`: How to embed the diagram in the output. Default is `"inline"` for HTML output and `"link"` for other output formats. Options are `"inline"`, `"link"`, `"raw"`.

Note that for Typst format output the width and height can't be supplied as a percent value.

Here's an example that uses multiple attributes:

````markdown
@@ -152,6 +152,15 @@ x -> y -> z
```
````

## Setting an input file

You can specify an input d2 file. If `echo=true`, the contents of the file block is replaced by the contents of the file.

````markdown
```{.d2 file="./diagram.d2"}
```
````

## Setting output folder and file name

You can specify a folder where the generated diagram will be saved using the `folder` attribute. The `filename` attribute allows you to set a custom name for the output file.
249 changes: 165 additions & 84 deletions _extensions/d2/d2.lua
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ local D2Theme = {
EvergladeGreen = 104,
ButteredToast = 105,
DarkMauve = 200,
DarkFlagshipTerrastruct = 201,
Terminal = 300,
TerminalGrayscale = 301,
Origami = 302
@@ -31,8 +32,10 @@ local D2Layout = {
local D2Format = {
svg = 'svg',
png = 'png',
gif = 'gif',
pdf = 'pdf'
}

-- Enum for Embed mode
local EmbedMode = {
inline = "inline",
@@ -68,15 +71,90 @@ function dump(o)
end
end

-- Helper for non empty string
function is_nonempty_string(x)
return x ~= nil and type(x) == "string"
end

-- Counter for the diagram files
local counter = 0

-- Transform and validate options
function setPreD2RenderOptions(options)
if is_nonempty_string(options.theme) then
assert(D2Theme[options.theme] ~= nil,
"Invalid theme: " .. options.theme .. ". Options are: " .. dump(D2Theme))
options.theme = D2Theme[options.theme]
end
if is_nonempty_string(options.layout) then
assert(D2Layout[string.lower(options.layout)] ~= nil,
"Invalid layout: " .. options.layout .. ". Options are: " .. dump(D2Layout))
options.layout = D2Layout[string.lower(options.layout)]
end
if is_nonempty_string(options.format) then
assert(D2Format[options.format] ~= nil,
"Invalid format: " .. options.format .. ". Options are: " .. dump(D2Format))
options.format = D2Format[options.format]
end
if is_nonempty_string(options.embed_mode) then
assert(EmbedMode[options.embed_mode] ~= nil,
"Invalid embed_mode: " .. options.embed_mode .. ". Options are: " .. dump(EmbedMode))
options.embed_mode = EmbedMode[options.embed_mode]
end
if is_nonempty_string(options.sketch) then
assert(options.sketch == "true" or options.sketch == "false",
"Invalid sketch: " .. options.sketch .. ". Options are: true, false")
options.sketch = tostring(options.sketch == "true")
end
if is_nonempty_string(options.pad) then
assert(tonumber(options.pad) ~= nil,
"Invalid pad: " .. options.pad .. ". Must be a number")
end
if is_nonempty_string(options.echo) then
assert(options.echo == "true" or options.echo == "false",
"Invalid echo: " .. options.echo .. ". Options are: true, false")
options.echo = options.echo == "true"
end
if is_nonempty_string(options.animate_interval) and options.format == D2Format.gif then
assert(tonumber(options.animate_interval) > 0,
"Invalid animate_interval: " .. options.animate_interval .. ". Must be greater than 0 for .gif outputs")
end
-- Check file extension
if is_nonempty_string(options.file) then
local d2path,d2ext = pandoc.path.split_extension(options.file)
assert(d2ext == ".d2" or d2ext == ".txt",
"Invalid file: " .. options.file .. ". Must use a 'd2' or 'txt' file extension")
end

-- Set default filename
if not is_nonempty_string(options.filename) then
options.filename = "diagram-" .. counter
end

-- Set the default format to pdf since svg is not supported in PDF output
if options.format == D2Format.svg and quarto.doc.is_format("latex") then
options.format = D2Format.pdf
end
-- Set the default format to svg since pdf is not supported in Typst output
if options.format == D2Format.pdf and quarto.doc.is_format("typst") then
options.format = D2Format.svg
end
-- Set the default embed_mode to link if the quarto format is not html or the figure format is pdf
if not quarto.doc.is_format("html") or options.format == D2Format.pdf then
options.embed_mode = EmbedMode.link
end
-- Set the default folder to project output directory when embed_mode is link
if options.folder == nil and options.embed_mode == EmbedMode.link then
options.folder = quarto.project.output_directory
end

return options
end

local function render_graph(globalOptions)
local filter = {
CodeBlock = function(cb)
local CodeBlock = function(cb)
-- Check if the CodeBlock has the 'd2' class
if not cb.classes:includes('d2') or cb.text == nil then
if not cb.classes:includes('d2') then
return nil
end

@@ -89,114 +167,105 @@ local function render_graph(globalOptions)
for k, v in pairs(cb.attributes) do
options[k] = v
end

-- Transform options
if options.theme ~= nil and type(options.theme) == "string" then
assert(D2Theme[options.theme] ~= nil, "Invalid theme: " .. options.theme .. ". Options are: " .. dump(D2Theme))
options.theme = D2Theme[options.theme]
end
if options.layout ~= nil and type(options.layout) == "string" then
assert(D2Layout[options.layout] ~= nil, "Invalid layout: " .. options.layout .. ". Options are: " .. dump(D2Layout))
options.layout = D2Layout[options.layout]
end
if options.format ~= nil and type(options.format) == "string" then
assert(D2Format[options.format] ~= nil, "Invalid format: " .. options.format .. ". Options are: " .. dump(D2Format))
options.format = D2Format[options.format]
end
if options.embed_mode ~= nil and type(options.embed_mode) == "string" then
assert(EmbedMode[options.embed_mode] ~= nil, "Invalid embed_mode: " .. options.embed_mode .. ". Options are: " .. dump(EmbedMode))
options.embed_mode = EmbedMode[options.embed_mode]
end
if options.sketch ~= nil and type(options.sketch) == "string" then
assert(options.sketch == "true" or options.sketch == "false", "Invalid sketch: " .. options.sketch .. ". Options are: true, false")
options.sketch = options.sketch == "true"
end
if options.pad ~= nil and type(options.pad) == "string" then
assert(tonumber(options.pad) ~= nil, "Invalid pad: " .. options.pad .. ". Must be a number")
options.pad = tonumber(options.pad)
end
if options.echo ~= nil and type(options.echo) == "string" then
assert(options.echo == "true" or options.echo == "false", "Invalid echo: " .. options.echo .. ". Options are: true, false")
options.echo = options.echo == "true"
end

-- Set default filename
if options.filename == nil then
options.filename = "diagram-" .. counter
end

-- Set the default format to pdf since svg is not supported in PDF output
if options.format == D2Format.svg and quarto.doc.is_format("latex") then
options.format = D2Format.pdf
end
-- Set the default embed_mode to link if the quarto format is not html or the figure format is pdf
if not quarto.doc.is_format("html") or options.format == D2Format.pdf then
options.embed_mode = EmbedMode.link

options = setPreD2RenderOptions(options)

if options.echo then
cb.classes:insert('sourceCode')
cb.classes:insert('cell-code')
end

-- Set the default folder to ./images when embed_mode is link
if options.folder == nil and options.embed_mode == EmbedMode.link then
options.folder = "./images"

if options.file == nil and cb.text == nil then
return nil
end

-- Generate diagram using `d2` CLI utility
local result = pandoc.system.with_temporary_directory('svg-convert', function (tmpdir)
local result = pandoc.system.with_temporary_directory('d2-render', function (tmpdir)
-- determine path name of input file
local inputPath = pandoc.path.join({tmpdir, "temp_" .. counter .. ".txt"})

-- determine path name of output file
local outputPath
if options.folder ~= nil then
os.execute("mkdir -p " .. options.folder)
outputPath = options.folder .. "/" .. options.filename .. "." .. options.format
else
outputPath = pandoc.path.join({tmpdir, options.filename .. "." .. options.format})
end
local inputPath = pandoc.path.join({tmpdir, "diagram-" .. counter .. ".d2"})

-- write graph text to file
local tmpFile = io.open(inputPath, "w")
if tmpFile == nil then
print("Error: Could not open file for writing")
return nil
end

if is_nonempty_string(options.file) then
local d2File = io.open(options.file)
if d2File == nil then
print("Error: Diagram file " .. options.file .. " can't be opened")
return nil
end

local d2Text = d2File:read('*all')
cb.text = d2Text
cb.attributes.filename = pandoc.path.filename(options.file)
end

tmpFile:write(cb.text)
tmpFile:close()

-- determine path name of output file
local outputPath
local outputFilename = options.filename .. "." .. options.format

if options.folder ~= nil then
os.execute("mkdir -p " .. options.folder)
outputPath = pandoc.path.join({options.folder, outputFilename})
else
outputPath = pandoc.path.join({tmpdir, outputFilename})
end

-- run d2
os.execute(
"d2" ..
" --theme=" .. options.theme ..
" --layout=" .. options.layout ..
" --sketch=" .. tostring(options.sketch) ..
" --pad=" .. options.pad ..
" --animate-interval=" .. options.animate_interval ..
" " .. inputPath ..
" " .. outputPath
)

local outputFile = io.open(outputPath, "rb")
local data

if outputFile then
data = outputFile:read('*all')
outputFile:close()
end

local mimetype

if options.format == "svg" then
mimetype = "image/svg+xml"
elseif options.format == "png" then
mimetype = "image/png"
elseif options.format == "pdf" then
mimetype = "application/pdf"
elseif options.format == "gif" then
mimetype = "image/gif"
end

if options.embed_mode == EmbedMode.link then
return outputPath
else
local file = io.open(outputPath, "rb")
local data
if file then
data = file:read('*all')
file:close()
if options.folder ~= nil then
return outputPath
end

pandoc.mediabag.insert(outputFilename, mt, data)
return outputFilename
elseif options.embed_mode == EmbedMode.raw then
os.remove(outputPath)

if options.embed_mode == EmbedMode.raw then
return data
elseif options.embed_mode == EmbedMode.inline then
dump(options)

if options.format == "svg" then
return "data:image/svg+xml;base64," .. quarto.base64.encode(data)
elseif options.format == "png" then
return "data:image/png;base64," .. quarto.base64.encode(data)
else
print("Error: Unsupported format")
return nil
end
return data
elseif options.embed_mode == EmbedMode.inline then
if options.format ~= "pdf" then
os.remove(outputPath)
return "data:" .. mimetype .. ";base64," .. quarto.base64.encode(data)
else
print("Error: Unsupported format")
return nil
end
end
end)
@@ -214,7 +283,10 @@ local function render_graph(globalOptions)
end

else
local image = pandoc.Image({}, result)
local image = pandoc.Image({
classes = cb.classes,
identifier = cb.identifier
}, result)

-- Set the width and height attributes, if they exist
if options.width ~= nil then
@@ -239,8 +311,15 @@ local function render_graph(globalOptions)
end
return output
end
-- see https://github.com/quarto-dev/quarto-cli/discussions/8926#discussioncomment-8624950
local DecoratedCodeBlock = function(node)
return CodeBlock(node.code_block)
end

return {
CodeBlock = CodeBlock,
DecoratedCodeBlock = DecoratedCodeBlock
}
return filter
end


@@ -253,11 +332,13 @@ function Pandoc(doc)
sketch = false,
pad = 100,
folder = nil,
file = nil,
filename = nil,
caption = '',
width = nil,
height = nil,
echo = false,
animate_interval = 0,
embed_mode = "inline"
}

190 changes: 85 additions & 105 deletions images/diagram-1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
124 changes: 124 additions & 0 deletions images/diagram-2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
105 changes: 105 additions & 0 deletions images/diagram-3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
216 changes: 216 additions & 0 deletions test.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
title: D2 Extension For Quarto
format: html
eval: false
filters:
- d2
d2:
layout: "elk"
---

This [Quarto](https://quarto.org) extension allows you to render [D2](https://d2lang.com) diagrams directly within your markdown documents.

Main features:

- Render [D2](https://d2lang.com) diagrams directly within your [Quarto](https://quarto.org) markdown documents.
- Control the appearance and layout of your diagrams using global settings or code block attributes.
- Tune the width and height of the resulting figures using the "width" and "height" arguments.

This extension was inspired by [`ram02z/d2-filter`](https://github.com/ram02z/d2-filter).

## Installation

### Prerequisites

Ensure that you have [D2](https://d2lang.com/tour/install) installed on your system.

### Install

Run the following command to add this extension to your current project:

``` bash
quarto add data-intuitive/quarto-d2
```

This will install the extension under the `_extensions` subdirectory.
If you're using version control, you will want to check in this directory.

## Examples

D2 can be used for simple diagrams.


```{.d2}
x -> y: hello world
```


And for more complex diagrams.

```{.d2 width="50%" echo="true"}
logs: {
shape: page
style.multiple: true
}
user: User {shape: person}
network: Network {
tower: Cell Tower {
satellites: {
shape: stored_data
style.multiple: true
}
satellites -> transmitter
satellites -> transmitter
satellites -> transmitter
transmitter
}
processor: Data Processor {
storage: Storage {
shape: cylinder
style.multiple: true
}
}
portal: Online Portal {
UI
}
tower.transmitter -> processor: phone logs
}
server: API Server
user -> network.tower: Make call
network.processor -> server
network.processor -> server
network.processor -> server
server -> logs
server -> logs
server -> logs: persist
server -> network.portal.UI: display
user -> network.portal.UI: access {
style.stroke-dash: 3
}
```


The enclosing curly brakets are optional if you are only using document level options. Quarto block-level options, e.g. `#|`, are not currently supported.


```d2
Database -> S3: backup
Database -> S3
Database -> S3: backup
```


## Usage

To use the d2 filter, add the d2 filter to your quarto document. Next, add the `.d2` class to any code blocks containing D2 diagram code. Here is a basic example:


````markdown
---
title: "D2 Example"
filters:
- d2
---

```{.d2}
x -> y
```
````

With this setup, the `d2` filter will process any code blocks with the `.d2` class, applying the attributes you specify.

That's it! Now you know how to use the `d2` filter to generate diagrams in your quarto documents.

## Attributes

You can specify additional attributes to control the appearance and layout of the diagram and document:

- `theme`: Specifies the theme of the diagram. Default is `"NeutralDefault"`. Options are `"NeutralDefault"`, `"NeutralGrey"`, `"FlagshipTerrastruct"`, `"DarkFlagshipTerrastruct"`, `"CoolClassics"`, `"MixedBerryBlue"`, `"GrapeSoda"`, `"Aubergine"`, `"ColorblindClear"`, `"VanillaNitroCola"`, `"ShirelyTemple"`, `"EarthTones"`, `"EvergladeGreen"`, `"ButteredToast"`, `"DarkMauve"`, `"Terminal"`, `"TerminalGrayscale"`, and `"Origami"`.
- `layout`: Specifies the layout algorithm to use. Default is `"elk"`. Options are `"dagre"`, `"elk"`, `"tala"`. layout is not case sensitive so `"ELK"` or `"TALA"` are also supported.
- `format`: Specifies the format of the output image. Default is `svg`. Option are `"svg"`, `"png"`, `"pdf"`.
- `sketch`: Whether to use a "sketch" style for the diagram. Default is `false`.
- `pad`: Amount of padding around the diagram. Default is `100`.
- `caption`: Caption to add to the diagram.
- `width`: Width of the output image. Default is `100%`. Examples are `"100px"`, `"50%"`, `"3cm"`.
- `height`: Height of the output image. Default is `auto`. Examples are `"100px"`, `"50%"`, `"3cm"`.
- `echo`: Whether to echo the original diagram code in the output. Default is `false`.

You can also replace the contents of the block with an external d2 file using the `file` parameter. Other parameters related to rendering and embedding diagrams include:

- `folder`: Folder where the generated diagram will be saved. If not provided, the image will be embedded inline in the document (HTML only).
- `filename`: Name of the output file.
- `embed_mode`: How to embed the diagram in the output. Default is `"inline"` for HTML output and `"link"` for other output formats. Options are `"inline"`, `"link"`, `"raw"`.

Note that for Typst format output the width and height can't be supplied as a percent value.

Here's an example that uses multiple attributes:

````markdown
```{.d2 theme="CoolClassics" layout="elk" pad=20 caption="This is a caption" width="50%"}
x -> y -> z
```
````

## Global Options

You can set global options for the d2 filter using the `d2` field in the document metadata. Here's an example:

````markdown
---
title: "D2 Example"
filters:
- d2
d2:
layout: elk
theme: "GrapeSoda"
---

```{.d2 width="40%" echo=true}
x -> y -> z
```
````

## Setting output folder and file name

You can specify a folder where the generated diagram will be saved using the `folder` attribute. The `filename` attribute allows you to set a custom name for the output file.

````markdown
```{.d2 folder="./images" filename="my_diagram"}
x -> y -> z
```
````

:::{.callout-note}
If the `folder` attribute is not provided and the output format is HTML, the image will be embedded inline in the document.
:::

## Interactive diagrams

Interactive diagrams will only work when the Quarto output format is HTML, the figure format is `"svg"`, and the embed mode is `"raw"`. Example:

````markdown
---
title: "D2 Example"
format: html
filters:
- d2
d2:
format: svg
embed_mode: raw
---

```{.d2 width="40%"}
x {
link: "https://quarto.org"
}
y {
tooltip: "This is a tooltip"
}
x -> y -> z
```
````
4 changes: 4 additions & 0 deletions tests/global.qmd
Original file line number Diff line number Diff line change
@@ -10,3 +10,7 @@ d2:
```{.d2 width="40%" echo=true}
x -> y -> z
```

```{.d2 file=test.d2 echo=true}
x -> y -> z
```
1 change: 1 addition & 0 deletions tests/test.d2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
file -> is -> working
20 changes: 20 additions & 0 deletions tests/typst.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: "D2 Example"
format: typst
filters:
- d2
d2:
layout: elk
format: svg
theme: "GrapeSoda"
---

```{.d2 width="6in" echo=true}
direction: right
x -> y -> z
```


```{.d2 format=png width="3in" echo=true}
a -> b
```

0 comments on commit 1f6fb19

Please sign in to comment.