Introduction

This series of vignettes will walk through the process of creating a very basic SVG output device using devout.

The only code to be written is R code. All the tricky C/C++ code is taken care of within devout.

Vignettes in this series:

  1. A simple callback function
  2. Setting up a ‘canvas’ upon which to write the SVG
  3. Adding support for device calls which draw on the canvas
  4. This vignette:
    • Simple experimentation
    • Just because we’re told to draw a line doesn’t mean we have to

%#$^ you I won’t do what you tell me.

In the previous vignettes we filled out the svg_callback() function with more support for drawing graphics primitives.

Now comes the interesting part: just because the device call wants us to draw a line between two points, we don’t have to do exactly as asked. For example, instead of drawing a single line from A to B, we could:

  • Draw a wavy line
  • Draw multiple jittered lines
  • Draw a curve
  • Replace the line with a sequence of circles
  • Play beepr::beep() instead of drawing anything at all.
  • Draw a spline
  • Draw a tapered polygon to mimic a thick brush stroke

The following version of the graphics device adds multiple jittered lines for every line requested. It doesn’t look pretty, or very interesting, but hopefully shows that rule breaking is possible now that we control the graphics device!

svg_polyline <- function(args, state) {
  n <- length(args$x)
  state$rdata$svg <- paste(
    state$rdata$svg,
    glue::glue('<polyline points="{paste(args$x/72 + rnorm(n) * 3, args$y/72 + rnorm(n) * 3, sep=",", collapse = " ")}" stroke="black"  stroke-opacity="0.2" fill = "none" />'),
    glue::glue('<polyline points="{paste(args$x/72 + rnorm(n) * 3, args$y/72 + rnorm(n) * 3, sep=",", collapse = " ")}" stroke="black"  stroke-opacity="0.2" fill = "none" />'),
    glue::glue('<polyline points="{paste(args$x/72 + rnorm(n) * 3, args$y/72 + rnorm(n) * 3, sep=",", collapse = " ")}" stroke="black"  stroke-opacity="0.2" fill = "none" />'),
    sep = "\n"
  )
  state
}

Example plot

rdevice(svg_callback, filename = "svg/test-experiment.svg", width=10*72, height = 6*72) 
ggplot(mtcars) + geom_point(aes(mpg, wt))
invisible(dev.off())
View the raw SVG text
cat(paste(readLines("svg/test-experiment.svg"), collapse = "\n"))
#> <svg height="432" width="720">
#> <polyline points="-1.16504636373385,410.706093200988 720.474826471905,417.998821444632" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="1.70283838393923,414.675266195504 717.462491346885,415.427953153606" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="2.44165912345363,417.748322543571 719.00773133777,414.382508747453" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-1.14940667313226,316.300877462574 713.279796837706,312.791283881099" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="0.665744279196313,315.389748293439 722.755405131295,314.707788671797" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="3.47124718331781,313.14977965824 722.270305401432,306.958029620554" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="2.57379231217728,212.171803857954 719.755510280652,208.226933226372" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-0.72013509683271,216.715229032657 721.177721179838,212.330827196041" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="1.87732990317126,208.508011707584 719.76248137749,211.394206688171" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-0.468554792637897,115.652372163947 719.745956309106,114.641823293368" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="0.22074427973239,114.44318617421 719.16381145883,114.022285644017" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-1.35195201515541,113.446041934406 717.801411028353,114.657894821542" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="0.377276432042467,13.2597193590226 722.567219678602,10.2293225381691" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="1.73767414276643,16.3644728230245 716.535807210997,18.006601413439" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-0.387349360906376,13.7745606002486 716.791492620289,11.6602379062789" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="99.0375969451987,433.589569685526 91.7150236554849,0.160109977102987" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="89.6029240370372,426.1054757115 92.3991205622274,4.47276808544965" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="92.2925036783201,432.946880184872 98.3505786747709,-2.05373579200445" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="232.777027449634,427.759451397456 228.142555494234,0.950442207313377" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="229.614972769512,431.743375570629 230.948164444062,-1.69245933780323" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="229.238842190712,435.054612587893 230.539312543796,-4.4945969005283" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="371.838791677535,434.709651110003 371.055804228291,-0.836448270141913" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="371.167009544399,429.892791505199 370.858249486967,3.69970691871051" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="373.538160961735,436.280851961796 372.157594063195,1.7515627774569" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="505.339281214759,427.846513753478 507.449261597422,-1.34409840855695" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="507.30795780846,428.787586652516 509.295405459565,0.550189817973214" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="507.205303908376,433.670474026225 514.470919648251,2.80662618924667" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="649.433718639095,429.613142236327 653.327690807174,-1.30883069032071" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="652.57800928124,430.898209367368 646.329073505684,-1.10232028756496" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="647.32118395997,433.002916591977 647.443822656686,-0.455887946047831" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-0.803557317849621,362.425793647541 723.95301297475,362.530862283051" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="0.413742844852465,362.848838660802 722.061895420393,362.95662935878" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-1.33066636777382,363.250013194485 718.9510856825,361.302848414402" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="2.30880364639087,263.702311125476 715.368714253879,258.173288072494" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-0.188613312016681,260.826353201064 718.339056785904,262.611947209565" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-5.02876320854689,257.787720234471 723.453646435295,261.39204633869" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-2.63344524663539,168.757242849918 717.671439495659,162.547930072113" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-3.14458652280983,163.846304516851 715.002079895983,162.440063714889" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-0.239890055138382,158.033362282608 717.135810056878,159.270166323186" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="3.71440147963489,58.055568340805 718.060096414849,67.8167207502292" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="1.98961620061049,65.3842980290856 719.2079551965,64.8681168370833" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-1.14341407620029,61.4437677925354 726.54220389286,57.9353649323201" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="21.8434924950803,438.444286795352 22.89930631864,0.393510604442123" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="23.6476877172132,430.518349071074 22.0452324948734,-0.0280745841728399" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="24.6396101178335,434.602528914465 28.5024263268999,3.6998286959211" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="157.694825761752,432.180126009156 164.340482736586,-4.32564658651574" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="162.95186425437,435.914113534083 160.912532212278,-2.22214249805301" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="160.09816076464,430.989039713864 158.610467534293,1.28213883922" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="298.30565623255,427.89610323926 302.992020255854,-3.06784973766224" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="304.824373872441,432.756275735126 297.453358355995,-1.06712465256577" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="301.729106270861,436.280116898499 305.567520610521,-0.91661990128731" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="432.757298871514,433.52155009847 447.105990246894,1.70008550865209" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="439.572806907954,431.027224102865 441.14333074625,1.33818743052453" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="438.412265245458,434.483866400538 435.502381844749,4.63533857725962" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="577.798888495197,433.447075515207 574.965240936051,-0.0580239099209487" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="573.5254549824,429.629450596389 578.731505781915,-0.946802238820227" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="575.255393114967,430.526955489 584.134535926627,-4.74043673599824" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="718.428795244576,428.562871496946 718.626741867574,-8.59065851427765" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="715.915769820662,431.341181770046 719.548737836119,-0.218432731254715" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="719.519677114217,434.810682730996 714.277839012619,0.0600714159264916" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <circle cx="328.323321697005" cy="300.891657388535" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="328.323321697005" cy="275.318499334366" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="378.403679398179" cy="330.977725687558" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="339.452290075044" cy="241.220955262141" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="264.331753523283" cy="218.656404037874" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="247.638300956225" cy="216.650666151273" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="141.913101364858" cy="205.619107774965" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="422.919552910333" cy="243.728127620393" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="378.403679398179" cy="247.739603393596" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="278.242963995831" cy="218.656404037874" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="239.291574672696" cy="218.656404037874" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="200.340185349561" cy="155.475660609928" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="225.380364200148" cy="189.573204682153" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="166.953280215445" cy="184.558859965649" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="33.4056596789816" cy="37.1371253004399" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="33.4056596789816" cy="19.687205687007" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="153.042069742897" cy="27.6098703390829" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="645.498920471106" cy="343.012153007166" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="589.854078580913" cy="401.67998619026" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="687.232551888751" cy="379.616869437644" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="342.234532169553" cy="316.436126009697" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="175.300006498974" cy="210.633452491468" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="166.953280215445" cy="219.157838509525" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="114.090680419762" cy="178.541646305845" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="278.242963995831" cy="178.040211834194" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="503.604573651114" cy="369.588180004636" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="467.435426422488" cy="349.029366666971" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="589.854078580913" cy="411.909249411927" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="183.646732782503" cy="245.733865506994" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="292.15417446838" cy="285.848623239024" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="161.388796026426" cy="205.619107774965" r="1.95479822834646" stroke="black" fill="black" />
#> <circle cx="339.452290075044" cy="284.845754295723" r="1.95479822834646" stroke="black" fill="black" />
#> <text x="0.279155251141552" y="363.135531873181" transform="rotate(0, 0.279155251141552, 363.135531873181)" fill="black">2</text>
#> <text x="0.279155251141552" y="262.848637543107" transform="rotate(0, 0.279155251141552, 262.848637543107)" fill="black">3</text>
#> <text x="0.279155251141552" y="162.561743213033" transform="rotate(0, 0.279155251141552, 162.561743213033)" fill="black">4</text>
#> <text x="0.279155251141552" y="62.2748488829585" transform="rotate(0, 0.279155251141552, 62.2748488829585)" fill="black">5</text>
#> <polyline points="2.79819532030687,363.739973117927 3.81663827270554,360.433409034583" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="4.16515698601462,361.435159653176 -5.28617976586328,362.30251974571" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="0.177900207817615,363.47819755249 3.77570679484547,364.291034683453" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="0.467298879073919,264.869289962966 -0.0286779563648415,266.221322614754" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-6.53302532656298,263.906810763443 2.43253373423067,261.506834378439" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="3.52930174084308,261.929645557708 -0.453396476661986,265.354866877346" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="5.83514521547444,161.22919118367 1.52447977130484,158.928403328454" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-0.316835618524396,161.718945463676 -2.10517291103691,163.678880717697" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-1.87930795795896,162.963778240028 8.66181571177779,165.886365015398" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-6.19110862140768,58.2601134016038 2.93731854007947,64.9682599157859" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="1.8706537844685,66.1816247734903 -0.508270669316165,60.1051538752177" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="-1.06557959460898,60.2043130626983 -2.28890147458311,65.0444037430777" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="23.5777977495785,430.388047356393 25.2921689539741,432.649462473497" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="23.0091860744025,436.89369121098 17.9979192737954,431.923694580975" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="24.5734882984166,431.406706244227 25.1421013316699,430.602905338873" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="164.069817133702,437.472415507227 158.246901579242,430.369455279308" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="166.351231933251,431.807300549312 165.925434108278,433.222014342728" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="158.315150587822,434.689240723623 162.357815535493,431.817587058865" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="299.138490024446,431.450636080687 298.533555194557,434.727835980204" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="299.048975961005,427.675983337092 300.137870417926,433.003290106218" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="304.424605312251,434.002511541246 304.992023780117,425.910985227566" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="441.059093989763,430.498202491064 440.981412287295,432.031820011017" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="437.020897615011,430.577100304595 441.650697799438,426.813105042742" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="438.51065320888,430.555559254361 443.706310264601,433.718601724801" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="581.564867123467,430.501436431904 578.738306315853,429.931265070773" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="580.943877879599,432.297035879163 575.534737956426,430.651853498481" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="571.93044213341,434.306461334932 574.498558834655,430.946514746568" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="720.247064576758,435.980046891881 723.499638318283,433.552157075113" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="718.977102988057,436.292078733267 717.258819648985,433.309053926129" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <polyline points="714.316484105743,425.801673887745 717.370287323686,430.934575059842" stroke="black"  stroke-opacity="0.2" fill = "none" />
#> <text x="22.0322468564986" y="431.720844748858" transform="rotate(0, 22.0322468564986, 431.720844748858)" fill="black">10</text>
#> <text x="161.144351581981" y="431.720844748858" transform="rotate(0, 161.144351581981, 431.720844748858)" fill="black">15</text>
#> <text x="300.256456307464" y="431.720844748858" transform="rotate(0, 300.256456307464, 431.720844748858)" fill="black">20</text>
#> <text x="439.368561032947" y="431.720844748858" transform="rotate(0, 439.368561032947, 431.720844748858)" fill="black">25</text>
#> <text x="578.48066575843" y="431.720844748858" transform="rotate(0, 578.48066575843, 431.720844748858)" fill="black">30</text>
#> <text x="717.592770483913" y="431.720844748858" transform="rotate(0, 717.592770483913, 431.720844748858)" fill="black">35</text>
#> <text x="359.937161339422" y="431.923896499239" transform="rotate(0, 359.937161339422, 431.923896499239)" fill="black">mpg</text>
#> <text x="0.241103500761035" y="216.103783105023" transform="rotate(-90, 0.241103500761035, 216.103783105023)" fill="black">wt</text>
#> </svg>


Open the output in an SVG viewer

txt <- readLines("svg/test-experiment.svg")
htmltools::HTML(txt)

2345101520253035mpgwt