Skip to content

Commit

Permalink
Merge branch 'main' of github.com:data-intuitive/quarto-d2
Browse files Browse the repository at this point in the history
  • Loading branch information
rcannood committed Aug 28, 2023
2 parents 38b26d9 + ecb0824 commit 51ecfa1
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 75 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 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:
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ layout of the diagram.
`"100px"`, `"50%"`, `"3cm"`.
- `echo`: Whether to echo the original diagram code in the output.
Default is `false`.
- `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"`.

Here’s an example that uses multiple attributes:

Expand Down Expand Up @@ -172,7 +175,7 @@ x -> y -> z
```
````

## Setting Output Folder and File Name
## 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
Expand All @@ -192,3 +195,31 @@ x -> y -> z
> HTML, the image will be embedded inline in the document.
</div>

## 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
```
````
28 changes: 27 additions & 1 deletion README.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ You can specify additional attributes to control the appearance and layout of th
- `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`.
- `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"`.

Here's an example that uses multiple attributes:

Expand Down Expand Up @@ -151,7 +152,7 @@ x -> y -> z
```
````

## Setting Output Folder and File Name
## 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.

Expand All @@ -165,3 +166,28 @@ x -> y -> z
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
```
````
189 changes: 116 additions & 73 deletions _extensions/d2/d2.lua
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
-- Enum for D2Theme
local D2Theme = {
NeutralDefault = 0,
NeutralGrey = 1,
FlagshipTerrastruct = 3,
CoolClassics = 4,
MixedBerryBlue = 5,
GrapeSoda = 6,
Aubergine = 7,
ColorblindClear = 8,
VanillaNitroCola = 100,
OrangeCreamsicle = 101,
ShirelyTemple = 102,
EarthTones = 103,
EvergladeGreen = 104,
ButteredToast = 105,
DarkMauve = 200,
Terminal = 300,
TerminalGrayscale = 301,
Origami = 302
NeutralDefault = 0,
NeutralGrey = 1,
FlagshipTerrastruct = 3,
CoolClassics = 4,
MixedBerryBlue = 5,
GrapeSoda = 6,
Aubergine = 7,
ColorblindClear = 8,
VanillaNitroCola = 100,
OrangeCreamsicle = 101,
ShirelyTemple = 102,
EarthTones = 103,
EvergladeGreen = 104,
ButteredToast = 105,
DarkMauve = 200,
Terminal = 300,
TerminalGrayscale = 301,
Origami = 302
}

-- Enum for D2Layout
local D2Layout = {
dagre = 'dagre',
elk = 'elk'
dagre = 'dagre',
elk = 'elk'
}

-- Enum for D2Format
local D2Format = {
svg = 'svg',
png = 'png',
pdf = 'pdf'
svg = 'svg',
png = 'png',
pdf = 'pdf'
}
-- Enum for Embed mode
local EmbedMode = {
inline = "inline",
link = "link",
raw = "raw"
}

-- Helper function to copy a table
Expand Down Expand Up @@ -83,22 +89,33 @@ local function render_graph(globalOptions)
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

Expand All @@ -107,89 +124,114 @@ local function render_graph(globalOptions)
options.filename = "diagram-" .. counter
end

-- Set the default folder to ./images since inline images are not supported
if not quarto.doc.is_format("html") then
options.folder = "./images"
end
-- Set the default format to pdf since svg is not supported
if quarto.doc.is_format("latex") then
-- 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
end

-- Determine output path
local outputPath
if options.folder ~= nil then
os.execute("mkdir -p " .. options.folder)
outputPath = options.folder .. "/" .. options.filename .. "." .. options.format
else
local prefix = os.tmpname()
outputPath = prefix .. "_" .. counter .. "." .. options.format
-- 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"
end

-- Generate diagram using `d2` CLI utility
local result = pandoc.system.with_temporary_directory('svg-convert', function (tmpdir)
local tempPath = pandoc.path.join({tmpdir, "temp_" .. counter .. ".txt"})

local tmpFile = io.open(tempPath, "w")
local result = pandoc.system.with_temporary_directory('svg-convert', 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

-- 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
tmpFile:write(cb.text)
tmpFile:close()

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

return outputPath
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()
end
os.remove(outputPath)

-- Read the generated output if need be
if options.folder == nil then
local file = io.open(result, "rb")
if file then
data = file:read('*all')
file:close()
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
end
end
os.remove(result) -- Remove the output file since it'll be inline
end)

-- Read the generated output into a Pandoc Image element
local output
if options.embed_mode == EmbedMode.raw then
output = pandoc.Div({pandoc.RawInline("html", result)})

if options.format == "svg" then
imageData = "data:image/svg+xml;base64," .. quarto.base64.encode(data)
elseif options.format == "pdf" then
imageData = result
else
imageData = "data:image/png;base64," .. quarto.base64.encode(data)
if options.width ~= nil then
output.attributes.style = "width: " .. options.width .. ";"
end
if options.height ~= nil then
output.attributes.style = output.attributes.style .. "height: " .. options.height .. ";"
end

else
imageData = result
end

-- Read the generated output into a Pandoc Image element
local img = pandoc.Image({}, imageData)
local image = pandoc.Image({}, result)

-- Set the width and height attributes, if they exist
if options.width ~= nil then
image.attributes.width = options.width
end

-- Set the width and height attributes, if they exist
if options.width ~= nil then
img.attributes.width = options.width
end
if options.height ~= nil then
image.attributes.height = options.height
end

if options.height ~= nil then
img.attributes.height = options.height
end
if options.caption ~= '' then
image.caption = pandoc.Str(options.caption)
end

if options.caption ~= '' then
img.caption = pandoc.Str(options.caption)
output = pandoc.Para({image})
end

-- Wrap the Image element in a Para element and return it
local output = pandoc.Para({img})
if options.echo then
local codeBlock = pandoc.CodeBlock(cb.text, cb.attr)
output = pandoc.Div({codeBlock, output})
Expand All @@ -214,7 +256,8 @@ function Pandoc(doc)
caption = '',
width = nil,
height = nil,
echo = false
echo = false,
embed_mode = "inline"
}

-- Process global attributes
Expand Down

0 comments on commit 51ecfa1

Please sign in to comment.