--- title: "Overview of the minixcali package" author: "Baptiste Auguié" date: '`r Sys.Date()`' vignette: > %\VignetteEngine{knitr::rmarkdown} %\VignetteIndexEntry{Overview of the minixcali package} %\VignetteEncoding{UTF-8} output: rmarkdown::html_vignette: toc: true toc_depth: 3 self_contained: false --- ```{r setup, echo=FALSE, message=FALSE,warning=FALSE,out.width='100%'} library(minixcali) library(purrr) library(knitr) opts_chunk$set(message = FALSE, warning=FALSE, fig.width = 7, fig.height = 3, out.width='100%') knitr::include_graphics('overview.png') ``` ## The Excalidraw format Excalidraw can import and export its scenes in json; the format consists of 5 top-level entries, of which the important one is `elements`, which is an array containing the description of each basic glyph in the scene. ``` { "type": "excalidraw", "version": 2, "source": "https://excalidraw.com", "elements": [ { "type": "rectangle", "fillStyle": "solid", [...] "groupIds": [] }, { "type": "rectangle", "fillStyle": "solid", [...] "groupIds": [] } ], "appState": { "viewBackgroundColor": "#ffffff", "gridSize": null } } ``` Note that there doesn't appear to be a device size / viewport / viewbox. I'm also unsure what the units are; looking at the SVG export it seems to be the same default "px", i.e. 1/96th of an inch, i.e. about a quarter of a mm. ## Elements - "type": "rectangle" or "ellipse" or "draw" or "text". There's also "line" and "arrow" but the difference is only relevant for the user interface. Most attributes are pretty straight-forward, - "fillStyle": "solid" or "hatch" or "cross-hatch" - "strokeWidth": 1 or 2 or 4 - "strokeStyle": "solid" or "dashed" or "dotted" - "roughness": 0 or 1 or 2 # degree of wiggliness - "opacity": 0 to 100 - "angle": in radians - "x": -156 # the origin appears to be the centre of the page? - "y": -80 # units seem more or less like SVG's px - "strokeColor": "#000000" # html codes - "backgroundColor": "#ced4da" - "width": 400 - "height": 300 - "strokeSharpness": "sharp" or "round" The following are for text (but it doesn't seem to hurt if irrelevant parameters are passed to another shape type), - "text": "this is text" - "fontSize": 36 - "fontFamily": 1 # "FG_Virgil.woff2" or 2 for "Cascadia.woff2" - "textAlign": "left" - "verticalAlign": "top" - "baseline": 3 Each glyph is assigned some identification, - "seed": 233882977 # to generate unique random variations - "id": "3HjMtdwNS5YZdRqmv3BFM" # unique ID, e.g. md5 hash of object - "groupIds": [] # array of strings indicating glyph groupings - "boundElementIds" — ids of (linear) elements that are bound to this element for connected glyphs Finally the following attributes for collaboration, - "version": integer that is sequentially incremented on each change. Currently used to reconcile elements during collaboration or when saving to server. - "versionNonce": random integer that is regenerated on each change. Used for deterministic reconciliation of updates during collaboration, in case the versions are identical. - "isDeleted": flag to keep track of deletes in collaboration ## Creating a simple scene `minixcali` defines a very basic R6 class `ExcaliDocument` which initialises the 5 top-level nodes of the json object. Two methods are defined, - `$add`, to add glyphs to the `elements` field - `$export`, to save the object to a json file (via `jsonlite`) The `$add` method simply appends glyph(s) to the list of elements. Each glyph needs to be a well-formed list of attributes, such as ```{r, eval=FALSE} list(type = "rectangle", x = -407.242554, y = 0, width = 44, height = 44, angle = 0, strokeColor = "#495057", backgroundColor = "#ced4da", fillStyle = "hachure", strokeWidth = 1, strokeStyle = "solid", roughness = 1L, opacity = 100L, strokeSharpness = "sharp", isDeleted = FALSE, groupIds = list(), boundElementIds = NA, id = "2732dc14872d3709d5978813d7bf550c", seed = 1260353516L, version = 32L, versionNonce = 784119031L) ``` `minixcali` provides 4 functions (`xkd_rectangle()`, `xkd_ellipse()`, `xkd_draw()`, `xkd_text()`) to generate such lists from the set of default parameters. ```{r test_shape, eval=TRUE} str(xkd_text(text = "new label", strokeColor = "#555555")) ``` To create multiple shapes at once it can be useful to create a list or data.frame of attributes, and use `purrr` functions to iterate over them, ```{r iterate, eval=TRUE} a <- tibble::tribble(~x, ~y, ~width, ~height, ~roughness, ~backgroundColor, -300 , -80, 300, 300, 0, "#ced4da", 10 , -80, 300, 300, 1, "#ced4da", 320 , -80, 300, 300, 2, "#ced4da") a$strokeWidth <- 2 d <- Excali_doc() invoke(d$add, pmap(a, xkd_rectangle)) str(d$elements, max.level = 1) ``` We can the export the full tree to json and open it in Excalidraw, ```{r export, out.width="100%", echo=1} d$export(file='testing.json') knitr::include_graphics('testing.png') ``` The drawing may be edited at https://excalidraw.com/#json=5181621544157184,h3q8WL5-2HPBFjkjQeu5RA ## Lines `line`, `arrow` and `draw` elements (the difference is in the interaction with online tools; there's none from the JSON perspective) require a `points` attribute that encodes (x,y) node coordinates in an array structure. Say we have inherited some (x,y) coordinates representing a polygon from a drawing program; we can insert them into the `points` attribute and from there generate the scene as above. It is also possible to group multiple paths by giving them a common `group` attribute, as illustrated below. The resulting group can then be edited (moved, rotated, attribute changes, etc.) as one object in Excalidraw. ```{r kevin} source(system.file("samples/pdl.R", package = "minixcali")) str(.kevin) # stored coords in the package under data/ d <- Excali_doc() for (l in .kevin) { call <- c(l, list( groupIds = list(list("kevin")), strokeSharpness = "round", fillStyle = "solid", strokeWidth = 1L, roughness = 0L ) ) shape <- invoke(xkd_draw, call) d$add(shape) } d$export('drawing.json') ``` Drawing at https://excalidraw.com/#json=5903088405708800,72BcP2Ry6NHWbHEUpidg9w ```{r kevindrawing, out.width="40%", fig.align='center', echo=FALSE} knitr::include_graphics('kevin.png') ```